From 0acef49a7c3275faf829bdf9f0ae39eb8b45f409 Mon Sep 17 00:00:00 2001 From: photonstorm Date: Mon, 23 Dec 2013 04:19:52 +0000 Subject: [PATCH] RenderTexture now displays correctly in Canvas games. Stage.display property added. A direct reference to the root Pixi Stage object (very useful for RenderTexture manipulation) --- README.md | 4 + examples/_site/examples.json | 4 + .../audio/protracker/global_trash_3_v2.mod | Bin 0 -> 204728 bytes .../assets/audio/protracker/sd-ingame1.mod | Bin 0 -> 29778 bytes examples/assets/sprites/spinObj_01.png | Bin 0 -> 35843 bytes examples/assets/sprites/spinObj_02.png | Bin 0 -> 37746 bytes examples/assets/sprites/spinObj_03.png | Bin 0 -> 37240 bytes examples/assets/sprites/spinObj_04.png | Bin 0 -> 35136 bytes examples/assets/sprites/spinObj_05.png | Bin 0 -> 36128 bytes examples/assets/sprites/spinObj_06.png | Bin 0 -> 36751 bytes examples/assets/sprites/spinObj_07.png | Bin 0 -> 35407 bytes examples/assets/sprites/spinObj_08.png | Bin 0 -> 33533 bytes examples/display/pixi render texture.js | 78 + examples/wip/mod.js | 142 +- examples/wip/rendertexture1.js | 76 +- examples/wip/tilemap.js | 2 +- src/PixiPatch.js | 7 + src/core/Stage.js | 5 + src/gameobjects/RenderTexture.js | 2 +- src/pixi/InteractionManager.js | 1030 +++--- src/pixi/Intro.js | 2 +- src/pixi/core/Circle.js | 6 +- src/pixi/core/Ellipse.js | 10 +- src/pixi/core/Matrix.js | 318 +- src/pixi/core/Point.js | 30 +- src/pixi/core/Polygon.js | 20 +- src/pixi/core/Rectangle.js | 76 +- src/pixi/display/DisplayObject.js | 815 +++-- src/pixi/display/DisplayObjectContainer.js | 526 ++- src/pixi/display/MovieClip.js | 156 +- src/pixi/display/Sprite.js | 176 +- src/pixi/display/Stage.js | 142 +- src/pixi/extras/CustomRenderable.js | 31 +- src/pixi/extras/Rope.js | 233 +- src/pixi/extras/Spine.js | 2828 +++++++++-------- src/pixi/extras/Strip.js | 121 +- src/pixi/extras/TilingSprite.js | 97 +- src/pixi/filters/AbstractFilter.js | 38 +- src/pixi/filters/BlurFilter.js | 21 +- src/pixi/filters/BlurXFilter.js | 71 +- src/pixi/filters/BlurYFilter.js | 68 +- src/pixi/filters/ColorMatrixFilter.js | 60 +- src/pixi/filters/ColorStepFilter.js | 50 +- src/pixi/filters/CrossHatchFilter.js | 101 +- src/pixi/filters/DisplacementFilter.js | 134 +- src/pixi/filters/DotScreenFilter.js | 82 +- src/pixi/filters/FilterBlock.js | 6 +- src/pixi/filters/GrayFilter.js | 50 +- src/pixi/filters/InvertFilter.js | 54 +- src/pixi/filters/PixelateFilter.js | 63 +- src/pixi/filters/RGBSplitFilter.js | 65 +- src/pixi/filters/SepiaFilter.js | 56 +- src/pixi/filters/SmartBlurFilter.js | 91 +- src/pixi/filters/TwistFilter.js | 88 +- src/pixi/loaders/AssetLoader.js | 106 +- src/pixi/loaders/AtlasLoader.js | 184 ++ src/pixi/loaders/BitmapFontLoader.js | 60 +- src/pixi/loaders/ImageLoader.js | 14 +- src/pixi/loaders/JsonLoader.js | 202 +- src/pixi/loaders/SpineLoader.js | 74 +- src/pixi/loaders/SpriteSheetLoader.js | 140 +- src/pixi/primitives/Graphics.js | 235 +- src/pixi/renderers/canvas/CanvasGraphics.js | 333 +- src/pixi/renderers/canvas/CanvasRenderer.js | 557 ++-- src/pixi/renderers/webgl/PixiShader.js | 113 +- src/pixi/renderers/webgl/PrimitiveShader.js | 83 +- src/pixi/renderers/webgl/StripShader.js | 98 +- src/pixi/renderers/webgl/WebGLBatch.js | 666 ++-- .../renderers/webgl/WebGLFilterManager.js | 823 +++-- src/pixi/renderers/webgl/WebGLGraphics.js | 862 +++-- src/pixi/renderers/webgl/WebGLRenderGroup.js | 1 + src/pixi/renderers/webgl/WebGLRenderer.js | 386 +-- src/pixi/renderers/webgl/WebGLShaders.js | 132 +- src/pixi/text/BitmapText.js | 35 +- src/pixi/text/Text.js | 246 +- src/pixi/textures/BaseTexture.js | 214 +- src/pixi/textures/RenderTexture.js | 287 +- src/pixi/textures/Texture.js | 175 +- src/pixi/utils/Detector.js | 40 +- src/pixi/utils/EventTarget.js | 56 +- src/pixi/utils/Polyk.js | 207 +- src/pixi/utils/Utils.js | 138 +- src/tilemap/Tilemap.js | 23 +- src/tilemap/TilemapParser.js | 8 +- src/utils/Utils.js | 10 + 85 files changed, 7476 insertions(+), 7067 deletions(-) create mode 100644 examples/assets/audio/protracker/global_trash_3_v2.mod create mode 100644 examples/assets/audio/protracker/sd-ingame1.mod create mode 100644 examples/assets/sprites/spinObj_01.png create mode 100644 examples/assets/sprites/spinObj_02.png create mode 100644 examples/assets/sprites/spinObj_03.png create mode 100644 examples/assets/sprites/spinObj_04.png create mode 100644 examples/assets/sprites/spinObj_05.png create mode 100644 examples/assets/sprites/spinObj_06.png create mode 100644 examples/assets/sprites/spinObj_07.png create mode 100644 examples/assets/sprites/spinObj_08.png create mode 100644 examples/display/pixi render texture.js create mode 100644 src/pixi/loaders/AtlasLoader.js diff --git a/README.md b/README.md index 07b6a39ee..98ebf75b4 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ New features: * Tweens have a new event: onLoop. * You can now load any binary file via the Loader: game.load.binary(key, url, callback) - the optional callback allows for post-load processing before entering the Cache. * Group.set will let you deep set a new propery on a single child of the Group. +* Stage.display property added. A direct reference to the root Pixi Stage object (very useful for RenderTexture manipulation) New Examples: @@ -74,10 +75,12 @@ New Examples: * Added touch joystick example showing how to use the clay.io virtual game controller (thanks gabehollombe) * Games - Matching Pairs by Patrick OReilly. * Tweens - Example showing how to use the tween events, onStart, onLoop and onComplete. +* Display - Pixi Render Texture. A Phaser conversion of the Pixi.js Render Texture example. Updates: +* Updated to latest Pixi.js dev branch build * When a Sprite is destroyed any active filters are removed as well. * Updated Pixi.js so that removing filters now works correctly without breaking the display list. * Phaser.Canvas.create updated to it can be given an ID as the 3rd parameter. @@ -109,6 +112,7 @@ Bug Fixes: * Fixed Pixi bug (#425) incorrect width property for multi-line BitmapText (thanks jcd-as) * Tween.onStart is now called when the tween starts AFTER the delay value, if given (thanks stevenbouma) * Sprites that are fixedToCamera can now be input dragged regardless of world position (thanks RafaelOliveira) +* RenderTexture now displays correctly in Canvas games. You can view the Change Log for all previous versions at https://github.com/photonstorm/phaser/changelog.md diff --git a/examples/_site/examples.json b/examples/_site/examples.json index 55124fa86..cc779cbad 100644 --- a/examples/_site/examples.json +++ b/examples/_site/examples.json @@ -160,6 +160,10 @@ "file": "graphics.js", "title": "graphics" }, + { + "file": "pixi+render+texture.js", + "title": "pixi render texture" + }, { "file": "render+crisp.js", "title": "render crisp" diff --git a/examples/assets/audio/protracker/global_trash_3_v2.mod b/examples/assets/audio/protracker/global_trash_3_v2.mod new file mode 100644 index 0000000000000000000000000000000000000000..876ec46790c7332013d420374d433e7d5b6cc52b GIT binary patch literal 204728 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%0DfUm@`_mIrCiMMafZdy+U$6vfW;`4UdbeN)% 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

!fIt<-8{b*=UBmfX>Lltyjq?w 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

mgLY~2Eu>83w7Zhh;` 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 xz{~xzTKgs|A literal 0 HcmV?d00001 diff --git a/examples/assets/audio/protracker/sd-ingame1.mod b/examples/assets/audio/protracker/sd-ingame1.mod new file mode 100644 index 0000000000000000000000000000000000000000..ec3434f6d5ad4c9786c0320fa1e2549defcf7682 GIT binary patch literal 29778 zcmeHw30M>P)E_R>k+qe6^YHM3< z-CApFt97kb8>@h*$R=wbAsYz^*_bROA^*7(g<$)=@ArTI|9t=R{9k7>ckVss+;h)% z&z+e};PEx1)^A*Q|6^sm7-JDO8eunWUio?v%NJPW1 z;n-~K_(PkvKDahN_ldO+KD=?$ zYY_c_y!5y-#w|EH)A(gfY&Q`M!h+khkIPuWZR~Pc2)6s3q4a;Z{jf9UxC5A+YRH#NV4+LUa&O9w?n{8an>%{R8wt@*OH;3>n|@pV9x#ApanL z5d;1K{zL2^h>t;gQFDI?{q6G~#mk_4x66i^sAkNh7jFYt$W8SD@AL;Y|0EC0Ve?3RH8@jt}=L;Mf?hr}P!f5`Y26fcA0 z1N{*HgFJts|Db;Z;}7~1=!g0r&HbQwMeF5|_&{?%$p5q#%Gkm3(O$SM|NjQ|x&eJ| zZWR5)t8XC-|PQv`Ty_r|L^sGAUi?@t=|{J z2O!k?JJ3S-G8R2<5RcY>+Qo!H0CoLJ4ECVtgYdBa2d`h$`aeibjZcqD7`vdL3^(8UTrmi;zTU3q4A2bI=AB+e7 ze_Ee{)@w?>#e@BC=TDP>e7EPfVE*X&!EdXBA<+I}fZ=~ef7-|I5AFYdS^s|}|LywI z_q^hp08d>|eM@qelRkoyzVfAk z<-_3npTC&@!Tl-vpz$gGgYCbZ|3msCc?Ri`Ji+$(@6bPZe9E7}@_{}>>_f>HWS`sg z56u6;`%fC08$tG`azQYED!%^j>K`ocf5!j8`cv29!Sek({crPkz<-1G`Kx?5B>o`& zgXR5Gf9m;nG6fzeZj%q&J$U~g=pST%w6+D?A3g65?hhS;$EPVuhvLCM!T13GLH)2! zL{Cs)s<_>s{kM%zt%pJW{>%9v#1H%Y{O)s2*E^O!9HULTN3mUS=L_K#es&R#@80fQ zg`X#0>)wPf2v7QhIJa)JGaXZ3>K>8V`Q5psOic?xoSX3Ins9<3bXZ;b$({b^vF{q( zv-T1{oZRuwiH`W>vG*$ct2X0!VYfeJE6=2;s3ikHgqhkf_p`07RBBc!>*BPXvZxF=Il>B-xK5!Li> zt0rG3h?kBM!~wf`?gESttt!4}=Jhwv`pNj@e;#nYPJBcVuN?4aV&@6sHM?$<;>T8g zeRcNuVdwXpa57J|CWZgxUs=fEWiJOoO74Q9l%?oSzkZ}+#69ba@4~T;O?c@hJSKZn zvUxIK(N-F5G|zmlkm>Ygr)DwVJGbe@%l^lo&usgt+&>pzJ#i`&*!eFTCxqAjbWX!h zw}8G^;5aN78226c=BpE}8bLwV+1eeibx15{-X{*6JMuB{y{8vQ2;!Z6f9x+GJ6i3V zyJqxx;vI8R(P$1k`W}39lkP73&XF;axzFKuW){pDo5f{McnmKMor`Z?g^Y-=o>r0> zH(?!)FIa~c$D~h&KBcoV)3F+PMOEIm(#h+edtOZ(wk!OjljDW)!^7LEG~yD)$s-r9 zpE&muH8%0yspE%#*n{ey9j@F>?DF?Euv{%iDCG8CzjVlVP}QFa;w^K<9%r1lv!c>p zw0Kczalxp?_!>#p2K>bpch8v#CX0-eNMgikV>cnu0^7s$0Uk9O8l~cy>qfU#J15^g zI<>1tFgXkB5R{chkDYnfaM|vozPvl8$0+O>*!u+0&{OeGf~XcpSx&w}jZGZ0*6w}> zs{bUme@hTMcW?js{BdZWsBSuQs!H2L5xx7_2QMAaRlI+W%{=+ed)0!1!f+mPEnYI| zIec@WWHF9E{=~xzW+glPV`j|XfG{}zOyD#taq_ko?=6}8=mSeehb0w9^3p8d9xlI8 zbx!5)8v!JxieJh-u~8iMh>4jo6>opvdVcqt#6R|&xNzYhjQ#RixwVgNk?n^1Yv-DO z`Sknhrt@EX`ZYrBI}65c?yLc^iEnniOAsF&ZsbQXn|> z($V3<5#uK>--env{?r5OpW3+Yk$Yo&6QH$v-Rc>oG2OoOaRn)uqTO_T_pWNEM?CkT zMd1~D&+?Yzo0qPfA7}garJWb$wTHeuR#kiE3mBLn-uV8S&S|OmwN81XruF)v11Em_ z<>;@!?k0%Oj~x8s<2`li)2{>7OCNl&r?T7WV(}PVr@lJcomNylVet$23-@M?fe?Fs zGZZ)_*Q2YKEqv&v?eJ%D{DIQU@KD~UvSp*PXWv;mW!jXx%8J8{6-U1Q?8_5pe{JxO zm|EEP)0-ci(D38JlkS)p?&>!Bd|egaY~Scqj%jx^sW z@9>6(SZj{G{>B@heDTBYl}(23D_^|#-h1zVbM(}CS(Ry6bmTBLFE)F0PEz9dvK33_ zjw@ca=D`OSPMW%4>9WufvB*E4q7hotiFVk5S!l@sB?$YCH6L zqt)Eg-O(V|o9u3uZ!gf`fY{W;ef*oNyT~*FZEEUaLao6LI-0=X8Klbd?CZZ03aN3+HWwsJ@LEj^CGc z@643Aus*A*{`~&KzrVEO+gFH}-nymFOYiJ_hxqB6*SMz(){8z6jTR1B>BQx0-*4Nx#TXFE{=R1h^P)fUPd;Qfnc6{^w!P6(tU8uZR zV>5;ri+X93c*F0UbkDL4AnxtD1NSE%ynpqAS-B%ejdn)Ib@w>C6-q^U-Om^HeS2X0 zySKYLXt>w^`SwRge*EQEO+%ZRNoR{BqY_8mHDTu63(MB8e&Eq{k3W4g1^vlD6yU|J z53F4~qd0$DUKA^uY1eAIFaKKq+qe6_*z*PfY3|=_FMagU`@2pb{;|BgqEpkOXNH6) zC#Q^=I&szVd)L6~0oM-yZhP{PyXUQ*Il81|I5#7l-e-23+S@NwUA=Jf>mxsXuxr<= z|9tb`h!8-({`Ri#_x*hF(nXEZ-sWoy?w@x+jU|5fApCpK-q@1FZt&YnAMa!KLn?DUj`a1PJ!CC#>Wbz9^0 z+Ukqve>(Zgq1|79_R0Hizy30;yZ?H7`PJ9neCNFnzxw>U1BZ{Ey7-&ya!vJhZD(h1 zx5t4+Frr}jPtF>ZT{3a}9kXZ6S#rzL>Oi74|jN*mSSstIa*KE^-n=&%SSV@80#*7dvZ{!c4}ACC&&N(3|LxZc<(F!&U2jurjTX{I^M^3QB4fl!i7CT#vd0vUEt))W z(&W;qb7st#J!k5S`EzE^o;z>O+&S~^ymQ|CJMWx3Z{FNlvuDqlHS>;XQ>PYBo>(}p zuplcpH9a*sF*+_HDpDv63loG3*u1ci&=AP~3?_|1@9X#W_j_DD_U<07UfbTOY;0}1 zQD0Ycz4}^JWkq>;`Q?hr^6ILq*RNf@di7fM^}3q6y861>x*LrxO-)UWsy4a2L!nS8 zRjN*9r%~UfHyBMlR*SW_*WqxvFdr-o92T1s8X6TL6huTui^WL^iP0%Z$w|q{X=ods zo{>I0WBBk?YD-B?227$PK_ZbPB*ckhU`-OTVU^-@L)m^hGbDt?V$$gh2AxJ{z?sS7 zuvsBYCWD0#3?`GwV)@YW)bDk}=CU~KHoMJcFq@ONn@Y_AE~TWDhn%+a!^Ai4Zx%_3}HA@x=f1t0T_MqDD15kAQ1|7nN-y!liCoX z-s$nnZct!M)-EfOR`do)U>H$=9FVt0vdEY>S)u`<(L<5kKomYSVgM_XvH^|+4#1>( zs6;1FSgCB}IGKSOa2z7YzU~R5dP&REZ}pdQNSSOjqN$R$gvodlq1FP2>;fo?lmHXR zG6KCQ)uAJ_@};9pfV4^@psbTg(}A?jFO^-lAky3hK{`51WsX0jvSK(J;GmcQDZ2@= z$V2FxAXY8Zq_RTP1oJ})9}Ozu7z?8WAhI4rg;5<52oA~;6gjjx0Mh0=rP={O(}4y$ z0RdkMGAB`e5R=hLGq_fVdIl^Q;8#u2c1dN!)e}%J=*}<>bQ-UuATsG#@MmC`PHGSI zlx9%mafqTE^^i)hOD_XV7K2DTD9Ft|2%V5Z4Fv`0AZvrLa8m#v0gLxYuTaB-uCU9b z;ot(PtQ~PhkR%8{RHq7=w75bF4hWzCtW`+6Whtn6Gc2MSs-Pv48Yo$$vK;x%h?MmL zkt|m#OO$0&qhF-TOj$U9ol;p6)dcgyezO=46~2^25shFnX^Sjlm{b*e5fM=2QV1bu z9keu*<^TkbApXU&9O*FVCJqEPn2L{>Tt%89-QaZNvJ?fLOe~9&ab)0!fXnTOVgw~a zsu}2{vI40E^@YzQsvJpmM{UX|DnO2cmRTy*ORX|#q72P2sVp-Tjp7%{3NlCgWU+xx z4p~MHVg`HQV~GLDWUPTU@&bI2?63zqU6=Vrk1N+nWtS)rKns}CF)}F+k@UmSD-~bJ z9qyN=_eo>hpbfJEj%Za=ADnJ|Fe&f`VWqN32vkv6UM^MM8bX%HQ!|n@U=OK`Zk1&P z#N)xFLbYhw#8yZNAu^@(kC>X8QOJ;y(U>gv3WhmL{4yyQ$qYd0HJLP@8nR1sQ$Z=4 z5@38UBnO}Lf;0@$%Z*WmkRebPSrp=50bNix%11JA7FQ}u1}7EhI#=UrZL1tZ?Q~TA`=1=fKtIT9^s*#0Z9Xe9I_-ezbpk38Dt2lEW*GZ181Ot zTofWzj)Zh_72+qMBT_4cRMAH-$U((00c6fNiL538(lVq;XaX8aG|*U?^ba)Upn{3>sdP^zln$7v zEFkIV5pa@+$@DN-L95Ow<<`Amf3!U<;X4P$kP9RnGutR{x30ktb*Vr9~-fcgQp>Iz+_NK zD(nZjds?LCc9Ya6O9taf6_5!~R&7xvgK;6vGa$TgBwi@#h;(U0fTP0^51nmru7a}* zCOM;@&Q!bdP0$=xH8M+vWoc2|0C|M8P6q3>l+J}vlSwavd2(V=S2m~>!?%HBAihkU z5}RI_D3h9zp1`F>Bb6ywF>aFX4W~g{+C#IcCng~uGSVVIO1sW6Np|hlo$<}{FSXUJ zjjE2iMpbxBIIHIpdJgkn*mlXe=Nekz?{Y~A)C?T^6AxA^$vHJzPhHh+vW{t zo2{CwP3?8H^2X{qovoYfGI1gV0yd2v!tk?084j0^6&V-B1TVgF-G>nLRYWQ?JsS-Tpqay1A~Q5$ol0V zl^T=7MOsW|v(?SwhV;9fK95ORS9P^PWwM)fI;#gBR`ux97p-18C4YFlDAcLbvlv_k zqmOjjJ${w=D2XICj4Kq2g`wUaeV0z7Qt1rcJyx^HWOX{-zCM?i6*`Q^4vk13 znIQ?MJN;v~;`o-bDY+@J!cbmBR9s3%dP+E#FB0(>J})DL#ukLKJ!VaFV}o2_u-eRq z9*~3=E)=q9e#~dnHr8CLuDM!%p}eNCsjjxMv!~ap(;Lh-tJUgZ3xpATtXJFKTwB+q zP^p#e9ZIzh1aZ1O1$UP%nl*V$iZI02$4H46v*}josMP4~K9*1*G$!|BodC#sO#uxZ))(P3Ox^oaO)4&7{0sylR@ zCOzr&d;MMplQ&E-jE(vGNTaT;rV{QptE#TmH#B!@yY#wFovyo^r1N?FP=>qLWYB4q z9jz_Rt<=%pA(!h&muKXv4Qub6IX*8{6w08{`QmV&9pa|D+e|t=v`_&&&Ww&vNlO+- ziUhn6y2q~9Xu5PN2r!5(doO6g4iiO0g0hi(HraI7m zY4qL3?rtmTb~)_5q|@!Tn{-+ocuA#HDCDgzZSsyzjZUZ6>jg_6du;Xm3E3%e!{|P@ z)7|HGx=0&n=NE|^Sg9-lufG?dF_ zGibm8yksMN8Ec<^X2bH?<1!_YU~6GilsGXxBSix9h7J09+%}`J$JC7`u*2i^GuWX# zfq)mv1|6N2E@ev-Org5k`li+nMJMQDg$G9-5Ag1_^_Yy^T^g9GYLG#ReA2E`gDO3& zjVsr#y<=)|VqCb#+wY>Wd|r>8bb&wme5_D@L`1kKN*o^_8yyADoP|7$1aIl}5L^b6 z$!sItein!Tn!txm^YyvWlWfv%cd}=1d*R8o3#W`piwYA6h2j+S+&V2bO2i3e(?T$? zEX-bbwhf_0XN7X$nZqzp1T0}U>y>RSu)zcH=4N>Zgt5Mdbc0>pPLd>TCYUwglP;}V z1*o=$nz|-AOlgyU-LmJFt}U6I9528aRv*d0Xbgwj>T!87ACtxlixNl0M8!tMiXy`J zd^XKR8oR-f-9{5AWP;do(^z3b0Ur`Dn?>*Uz|#nq%i|`kR!3CXmM7OOpE*7&H7=SL zC5(?xN=#0QkBboT_zbqkYqsevW{2J7#2|Y^+GF!rEDRDGWK>N@M{8?iYikDt4vJxj zViH2d?Q+`fHj7cO)qo37c&St!4cD$#*EGsIb&e&Qmp@gyG<{4|6pt3_4s{Ll^St1D zzl-5w`S@%BA0G6Jg<@e8Ul7UyNo{6eZ8P=g4PYn;T0fJ=8y3o8F5s18uzcS1(W8yt=w{YL&9Jxv{F^^3^7}&Qgjm+_Y}& zUFq>@v?vxP?BlaAuAS~8X?9v4#=`_Wp;!Hxu2V`)_NMRv7hQHrt?a>+3I)g^nrRr>NLsMC=H<=yaYv61(8TAlZluNo{wyN6O z8k-v`uhcXsx^nO}b2m*LH7zzGgTWH_hlxA_zAu91=8z=Q?hEyV3OO-x;jn-USw6Pk z>9&y`yPY%{y441gq1Oq^4U5a7!9@ha2RViGfH&+;r`>6X1g$j*AH>(JTQ_||Uc4ke zDp8V{E=kEqNJ$b#2^hoJKAPL^>vPiF4hDlI429>d9zSV!KoaOOSwYIy*4CyDWxGnJ zw~|hW-C`&0W;8?5LZVV>b?Q!qyt$#VwYmOkU87v^guM`Pg<(sGAnU4~^n7hB54YZn&_3Zvc9_tvaP%qXd>_wWRM*&TwK;8&rn@0u8_h)9WDi)XI*Q`nraitM!e| z^2QoDdo?~QcllCLR8cG`N^!drLc7^fu3ny}%g507FdaI#$LQiSA*kT0#%8dS-A2-^ z)pvKa>$*C-ttM7dnm8s-5W)}b?`QOTXmC~KBOw$ZtVxrt*JS3G;rNOr>lcqInOjt_ z_@0uIxnpC~MhFF|v5@fk-7L)73&b=(D2DskN%sW`#y?frv8clv~b?c6XVK@WqBsE^lvaR47}T+Zt=?Z(P;S!{?>UTs20JK03@N$&I23V`vV3l*i0q zb!#!dN=x$?oNT9tq}%j9nnB}sb(wq)t=VH%nk?!lJ#rfmohNYx)*(r&1e+1u7GCg_+7c;vU zc8%R<>NHuDa+6u9G@BJ2-Fmgv*Q+yOJm27JrR!qREk z@mb^2h4DG*LSa%In;{fpE=b3ukJj65^H{rjNRvtp;_LJpg;JxG!*!tvE$C*W2EJC+ zYxG8~QfJaD0oT&1QZ_e(Pa0d>8XLOvH?EYhM@&lX^N5o;PF_l+(-{%tGc$$#S~Jt5 zRC?%!4jawfs_SvQeQ`scke2-0QcSY!0&yCXos--FkCxk4mA_wl%dXn(JE}<8gcn zO*myzsK%W>+;5M_k@Wjwli60Eh;NX)Li=?pr%&6^=_EB8r$?{QJMAiYx7)5a`$%Q8 z#v>32<0S&P2w?Sj*eu9mY{+2|A{Ob!{7#q8Z|}BX3{vNt4VpZ8&;9q6h#|6)*@En% zG+}&RmWUS_6+)6BVSZ~bXl;OOtkc+BRukzqDLc(Bt40Z%9ug+$X433Nv)yVklQx~! z;&j@~X1$_WY0xw`wJSTiMr^}ple(lC8QmK3_++O$B|pmUOUY#G9U`GsMTU?~jgW}t zN~h1P&^nyD&R!PHZg%_3b_|0%#={j*>^n~ojk+kSTGm*<`-FJ(h?&b5bO|0Gos=vZ zUUCN9M+M_>{49c4ey=6%NbC@aRT1f_+(GH+!Hn8p{Xm^Ou_M4ap*M=9DibU zW)yZFz9oAt@7~D^GN~`+->uYL!3i9Fa9l2fGwcL95R6F0iDasR8Y7{A2Eg#WsM-p! zKu>hK*&uZDq5qQvp$8^x57 zBRbgO1M0$ezEH&wKMP`PvQHc}F9H_D% zzkvHu(@y~xLKU?rtpn9Zd3gRSBZ*G}Yycw!Oa1^P)dVhsCZeW31dM?iDyqcgMHHu7 z{BYtG{33n=Mg*GFu>lj{*N{5^@FFn-bwzxkgpUmbe3$|uFjYYXe0~s7;SvDP2%w5H zS0Nll)()6ML@dJN1P>1Qq*6U0ARx?8F%uaE_5vsVlt1K3JjMc_x~LJjtF#Fha6!wr!LP=xXYfqzcm?KI+pL#U3x0o{>zGQ1d+ z_(dcxfpd1^#uB0$Ctje~6Zj|i1%%$eWB&n zRuIP8>KBPpP)y)gz+X6VodNizC2D#xfq#k1-jW9R%T4s+?I1Bx z17i^QtB8)k|G_J4;DIra1g0Var=kEbMP7(#8VnXN!x7$FKtK)LPT(I?;Ecm?f_(#J zlMmB9ARSIT0J%|&6VKt5Pqx~P_-+chg*f^wP_`TOMQGx}zU6iNaCo#0YLdW=k$Z{R z&+fbj08{d*K12!Rv`xg4vUXBh-I}`a3@P$%I3$A3^$s^3gAl+$K}c>yr4$59lMrhS zEzBMGfp7%fQhUDmA4`ndXh>tKD=0xfN#H*v$d~Ag8rWasAERY}{*znWl9mZZxQM@! zZ})FG)c){A;zci9KKA`tzCiD?$g$!VzD)yDD3^l2}YC@DJ?x@v@723^{JUqPS@$Py9NS{}x(k z{)8jBY@Q74wv~7e+_AE1F|jh4SaH+_D<8~gwtumVu3b;6XUMi358pyenah3eMefC| z$BV(;I}7y1T3UksS(xL7Bpug0_lLCBQt_*^SA$HS;76}y=hRzV_FT2}a9VwTB>!|6 zJQckdjafilwU$+UEWHUo?pbn)4`6@E;j}!sMNBw|6KVab8BNdbDbn8!-;wdp^~<@m zVwhYG?W7A~q_nZ-p#$l=ANX#hjGd#^WAJ)mO_E0)X)3BuQ*yQv1*uBbMG*ayga%2h z;qbH*kwOlsoBsiR-ozK%>6mIOG0CBh)+?DFf26H4``pB{H6Q)DP^VOEQh9I{MNw2PUk1ckv`^=v4C z4m&>9=1(wyUU??UjB=K08kkaRlQyz4#MeYy_CIiO!dXfFB~r+7(EKL+f_i~jAsk!J zmU6w`v}#>~=Mw%y>-r09Wu#XfE$-)Q`@MetnQdpesXycjTa%4me-g)rVbK?p=Kmm> zMl5F6EU0B+JXM%^$tBYY71Kd$@ji4SvtB5^HbFU(XfDClQ45i=}azehi>#F2A$#QJCwvt-!$EkbTi3mdD*5E)Xzc&!|Jx{~cL`7Tq`T2PZ= zfs4kay*JI$5*KSQ@{vQ-)7ryHm=+f~@S3147T%jm@Uz;5ToXULm1*H%s_Bia znb#!YJO_`>W+v$z2{ct0hI!d6Bex&3<tZ@)j%!c;|iy^JUk~W> ze>6W|T?El+m|dM=)!i3=F6TWsRnk}SI7Q*mAsm!^!Qkzy?eLWVE0 zVSEF7qykH@U|1NN)g<=%6ZCY9Y2bu4fgE10jh5HSq=Vcnf4IY*;PK}wu)=l-qBOcC z^Gs^qH9pS(PxB*NSrUU^x3Urj#W|LIKa$Ttg z58Z{){oVwYgUqqFbIEBn`C57^2>}SqitE{278t21&g+der!^H;_H+8_WVQiR(DQ7m zhJ3fjJE6YcFt3J=SHr7^kv`<^Tq@aqd>IK&%D6697YV!@w#q??%#QhXN$isZG2rD|OTN@g4# zVy0RgT`i6zD?t*>RwN&LE|z2P#M;2@FptK{W;+}hALddx9kaQnG_u^OVMe+?L!H*L zK;Ihc##9R$!CwjOtcClfBJBi|ms!wA%Te(7N%nqw0sm~O2jn}QnY**VK{AsaEILi3 zW=F%U(38`SO_4K276`z4cAQ>XuY9t>G%~#rjckJls<5=3DpIpN3H@|`j)F;Ju@x&X z^5YDSaCyGD@#*8FD7qXJ5a?|4sbRH?Dv1p(G$uY%&3jnJQ!;C0_ z^#jlo*9dX%r-9GGA82YTvu!RhheU}0K9SmVLw~Y?9%~`v5Czy=!h^|!NIlUgirqOz zo-5g-4fBUN=l}+$Fh5PMJSFllS|8v9Z_k4-)9th{_3RVVBDF~@u`!OzX1Qo#4w@}T zONX93WEYZ|VE0F|m0?LbUVDL_4svm5%y2i#a3YkJFyIKw8l41}GW~Q?dODUyX0~SA zxbA*G9cB+55+svMu!NiPbf7mRJdnq4XM;(hlgLIUaAA1>v%4i0WCBxhOOjf`uZUpl61f z&3CYnuE^p{j!Ou#(`gAtEZoKOn>eu4Cm8rF#5kOC3e0{COE$6{5M(rZB#q_b3oUVW zh_5gvO)L-N!a@S7aBXq$Ca|9#=3#<+Tqvh=Ahm_NL@sbLL?TGrxUj~Xsq2R+=#Mng zX?_~GKGp*3BMmNh*x@!TmZW)wCRp`oe0u^Y0WvuJv0Pm=*^dn5k5#5YgtQ82EQ^G0 zr@<*zovM;}Aply0ej2C?<_K4FVJukvcy_RExFU?sbc-Ny@kora5Fhb{!~*$`$+FPt zND!6>q5?%B{K5l>U{oZp3ngqwS;&A~2M6kakF>yo1&_d>g(jKb!l8)`Xtse#fGdIn zhe#XBl|nEn_zfnH1*5T85WgtI02A);LJ&dX20t)qd?QE>OBp0pKONeTn_H627@DOC zIu6gpWkPSdpO1mKh$!4a1EWzASm-<#aN}Y?3qAc@2yrjZ!C@)G=wJ#IltRZR}3H_;a#BUXZlIS3mXHM=&eM@4! zGO1)V`PlH9j3?;$(mtHGY?E0s3Li0L9nQmJU~hgX636557x#Pa9er%B{>zcRuUpO# z`t!i#6*)otat@waQO^|NS>h1@!Un%nOZ>4S>%^p0ccdxtDUZCcar3OPcqTkti-%w7 zq@hpzG^oep8B?J4-0-P*C~g~Zcl-RR($i;ieBb9EHK{5-_P1RoXmzi4b;>`ze4ZdH z38Rc~6XirKaVGLD-oA9ju|iJmDE<~&D&CidZ_U8t7g0=~Nuf^LQ2BH={zPWR7(D8^ zw)+z9JHEJtwW#FtiT;?MbKXc*zTFKE5<3D00m&)Z2{`=&DS_G+z`p;MlTbE)6#j8S z#l>+|<9;r)thn*qEOODTM>6olsbHYINib0UgeM-#x~0_}g{62nj*Y-8L-CyWkZ~8+ zT-%WU{fw5@!Xu?Uzy8jAtt#c?6vFpU>&u1-Z7i-`ZQ0uQf~gqi;?WPlsBzn}@%YVwVTsGcJ~k-=l0W@dN6?Ez3X2SzZ0*u2Xs~v zCqbC^TcH^EAo!0VHw9hN7zPD;!-P6jr$3;(uM&VJ@ zaS6U2e2QckU;N@TDX1#JDX%Q1gazks#N)R;RJWOi8(*wkSLvQ}angx~l3j(auY_M! z^{Dn9jQAORy|*8Juc-tBUEA~9O$n65o_ZwLnI^(R^bkK0LgLGQqVgq|`Q`9a-$cC= zbKrE`M}9EFX;*KbixAWg++7 z+^6A1b4n0oLJ=N+4}|Lmz`!F-2HuDF;zqoIx4ikmrsZRQnayaJcszAa&aRYiMIZRT z`O;4G5#9g1L6~7jfkQd;>V-g^D!;A0*?6^s*!K@D*u9%@5Z+xMGl}Szgn#Ued@JU| zez)z5)C=AeUil2p(dT22G+5xo7U$ zkcE$bGIL`oJR*i5+;rcnfGQ9dsd!l$UKk6Hc$fD*n{9aP#*8^_%S5THw#D3zoTDCL z^3mF{U*?~Z84}(O*^@>@>^<=zlVA{)O5z<8@mf8y``1*7VAjH7e8D9g(_s=lK1&9?+Mv=^~)oj$=mIYAK0*0S6Js4t|FKlnXyb6$KO@))5B+P9^@?0pHcU_A5m~ z5Z93MYC_gVeBb$cljF^*CIgZD5?y5~s$C`vy!xy9lXR)|k#eyx+d#`!$y zlcYD|iNj7pQTP3|y=tPpgV4Qwn&|!xB_h}_eFDXSDkuV%X{(F8kN6e#4)LC=A}7T7idPN zMEKV$XQbWN^Q05cn8kT?s{rSNK4m!pM=t=4B>3YqR&Ser?`HT_=ep&1?vntU^x(=> zs|uj9W(1Yx?t5X)NPGok-kD3*7U4NhWFhFpyIvTM&nbHZa^w2>K${1wa<@sJAG@w( zSuE~bu%++0^w6TQlH!7n2d_p=xiY5Tm37&h)o`ZZ_wWOwe$)KS{w(e#!S{RF#O3lj z0#bDE7u&B9V5tu(h-+{C^CEHP(;YG}z^n3ipnOyQ^1CN1i4!~ae7V_bP~* zcemFQ2d@*=#E-kcG*^hPPrZKrv;9B~d9~*4J-faq&hGr?wG%r4r6hU@^=oZ}^XTig zPcQ0U5fVw?wb#&2xa5&_ijbPL#<29$CDFr_%NX3dEK`cixNCXF%IsNlr#{cd<5sSj zwc*|u$2>dtg;hEDwq(4x6wkkB+xpq7R<77G|JkBvXRdkZfvFq8X0tc1DBGNgPg}O; z@$t{h!Sf$^al&KwK}V$|3EKYBXr&e0Bb z)z+Rl-~A!yb6Mq?Bd1U9*AQ)A?EPiem*0Q&&B4##K1qCabQjq1>-TGjBkvynXzxG1 z`}XrMiOV0od-SbOkA3o14RLnI7eDVj{MIjTR}wYEzCB-k|ItrG!)trL`+EE5U+(+$ zEQAJuOg&K+#2_@kxfXXB+S$~xnf_SRNi9qqbXtv9pl z8Swpjf}GFiGFg09p|!-FnCZ%Jz0)6hJ{8Y>B7gCcv5OYodB@yYQy-6&J}{~X{sh3xIb-t2Oijp| zlrDbQow}$mKRibqnUy9= zt+~<n=&UeoI zy1o2|pN^j0f998yhc4_pe&*-XwTFJ+(@U5SRbIGw&UC2UaJZ@JN?pyh`lfnCo9bG} z4Qs2ZHRRV(C!%#BZ5+496W+t8^Bqa9sBm#eG9xi6mYbF+OixIT8=aILS2%K9R6%a+ z)ZF2dQVT|>lw{9NFDaZ8x}+#`LDZ}fMH9!*${RIjQplQ&yx9{+jhQ^QsAPQJ(v&+2 z@AKfinZ+fOM$gQjIDX35qRgqqdD0nalk&!o&0HidnHxE+Fnh$<(Yd*KaU;j1#^VxV>mgMNNrACIM(O&y1fnqUCru*w`AcLn*G0}o+rEHy)Ez#N3*ux*xKIS zUei@&KjCP&qG_wrU2ke=YN~DMxZ2Xt(Ard`Z@ku1-k_+p)V9^M%Ns7*Dz!Hh)oq>4 zt(q%_*7lA%V=esbQQl~j*E6g13g_`q8S8wXwyRU4>2y?E4eEA%uTtM-Xz{4A>$Wa< zH-yvD+oKg+XK1h{VI!m4-D?lMo^XRndX1Rf=YrPSl)u8GonM(fqr2_Hu#M{Z`d#% zj|&?I?(9QC;6WUl`n@HS0sp;#Mnf_o$htqooUbE3lm?|(XALoF9jcsxEI>f6-ZX$k4A#J3eB@}l|PJF+)wWb$X{@cgbhWvwm-{TfHCFEa~zQ6DI{=Vb;`;PDLJHEf~`2N1*3*7Vl>pQ;x1yA;=CjbBd literal 0 HcmV?d00001 diff --git a/examples/assets/sprites/spinObj_01.png b/examples/assets/sprites/spinObj_01.png new file mode 100644 index 0000000000000000000000000000000000000000..86873665265d92a65933abcbc5b4dc5202043023 GIT binary patch literal 35843 zcmaI5V{|4_w=NpnPRF)w+h)h+8yjzI+qP}nNym0NwmRwL_P5X3=iDFr-WpX)V?NKs znxodLRgubyQiyOr;6Ok?5M`vrRsYp>|9N1b{+0CzoHzd}TvrKgS2afqR}W)na}W_T zM-y{m8GB<(b5(O=GcTt}bAAvIaB^#PZC7mtd0taTdq(5`$S``^1OA~wK=_3`0mi1b z=B~sh=9bnD0;IRy{iMX!W&)&I916?|05NkbYiVz1b2V>8byII!Qyw!?Awgn(Pu_n7 z_U5j}#Gdwc4lcZ&0;K=N%lohWpJ66a;{OtHwG|-!Uq)#wC=-i0I-3)7FtRb2GPAG{ zb8<4Wuyb&7vd|N=GPAHUG5@Cr^k8Fjbhcz-;o;%=4+kqN!#@cI7cU1_V^0PL7qb6m5I1)*b+!h$T01%r z|A*1o#L>-Ffb^fG|H}n?!2h9jaQWY1`WG-JPh$WR3nTM?T>5W81%>~AQG5IUBkkg< zYW{!i{U3>4)V%=aOseKCj&9DT|0d3Y>_4giykgGg#;%Ue>W+?f{~bkTD@RvH7b`~q zv6vbsF|~rRskOs@259~Zp`gGk|kmxBQ8MtkAuNIuZeIC8=a5Tz-D<4}u<_wJ zygIx(tIy%ez(^#BHy}uI5~Ih&uxzN{9D(34`>&5-apDMV4GbH-Wg@hNhAJdPOo0{x zmS}QZ-#N!+b~J9~R(9Ch({Dq}Y)kqwuQSij>%P{szM1ekc7~0;I^xz)9VEXMpE&F* z`?LmYAJwz&js@%vUVHOgYR=A{Ti0eS&g?zone?LXLKI9sY(o%ogwGm+|vU(TNOY z6KSDjW484(6XX`e`@Mx}+hm_?vYBS!Aak5;U`Ht-i?h;_wIb4tP%WG-JA7YCH$TeS zUPYX51VFycfZsz&-7E-H=wLWP!{{h4{X?42`JGJ>n5{juFs@-bJO;_cK56~UUl;&YZcynQ-TQO zgs(5V0$hea!m}Q9KVe@J`{EMHVY0ENrNs2~z}HLhDwL0Gm-(QXdF) zHP-H73k(0UgW#ch*^-(W?W9xnc)nNix`{+WlnCzkNcVI=+XXGRMHwlY?#C#13a4@LJvRK(2(kGlj z8%vdAX4EG)?ZeW|6RIOTgB4%!u8fw-B|AuM-(4|ZA2wRMDhhG{Ll|$D%3n`qblMUw zSL;1DdoAd*WWJ2{U7>By?36X}Nbz<2b2_fy^aZqIUV6Jc@JWClSrt%)JK=0 GR* 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 IM6N<$g6^4=CjbBd literal 0 HcmV?d00001 diff --git a/examples/assets/sprites/spinObj_02.png b/examples/assets/sprites/spinObj_02.png new file mode 100644 index 0000000000000000000000000000000000000000..a140876d7e1c2f049a54f7510ab1733b3a3b4a0b GIT binary patch literal 37746 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

;FW8`8i$f_#y7`4gTo9 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 z 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 B%W41s literal 0 HcmV?d00001 diff --git a/examples/assets/sprites/spinObj_03.png b/examples/assets/sprites/spinObj_03.png new file mode 100644 index 0000000000000000000000000000000000000000..82532c791e32b15ea1f6b482020ec2a553856ef1 GIT binary patch literal 37240 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 literal 0 HcmV?d00001 diff --git a/examples/assets/sprites/spinObj_04.png b/examples/assets/sprites/spinObj_04.png new file mode 100644 index 0000000000000000000000000000000000000000..9c790c370a456370c9c939e679141ca6ed4ff633 GIT binary patch literal 35136 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%0A@jrmmtfl3 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 literal 0 HcmV?d00001 diff --git a/examples/assets/sprites/spinObj_05.png b/examples/assets/sprites/spinObj_05.png new file mode 100644 index 0000000000000000000000000000000000000000..ebb28c84ca0e9830b6345ad75153fd51a58b0c77 GIT binary patch literal 36128 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 uC7+Q{|A?+X4V1ABFE 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 literal 0 HcmV?d00001 diff --git a/examples/assets/sprites/spinObj_06.png b/examples/assets/sprites/spinObj_06.png new file mode 100644 index 0000000000000000000000000000000000000000..1dec5c810de56b3db1d402fe7d66096df65c528a GIT binary patch literal 36751 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%0AT{ zFiJff*xCaBtqPnsHYlYM)GeqXRs*PSR(18xR0`8O8)Fs{6W*>hgCN=%%B z@wZQ&Os2ukjkpfB!+%+gxhL!0GupzjzZ@2KRV`UllW`R#-ij#6lM59Jke)mcEEI&YHc@F{JHiobkRui;jRbJ5 z_}-|doZ(RnLV@9sBP`BK31nv>$NlYGg_|KmRg~F*ziLLif_xfTGPS?=E2=a1L*s%D z-5TthwUQA*inJSda>S5GykuhN6C1ep3y1=M&B<4}L=rV{0vrqmT)1l6C%8itR{&9-rbmexZA z{jO*65~~!*C9WKs(-XmR+bd>fTO7^7L=8fCtp3JwFX)Te0`eHdYG>LglKca2FcNoSkFEpRKN$PHF|KsZ7 zxiI~2{aGd+?%z?65fJ|90}mT&5f0^^UTnN!&eH=mzOKvi?1i-Bj>=cWZ7vVgFXIJH zqmBL%Mp7pIdK(_0Bvy=~czM1CFjee@{YX+grhlNX8ym#hv$Wo|SfAPpP8XFwZ@H*S z^^XV3snfqTHAED1<)Sg`Xi<7q8BYo>of#l`J;kD8sx1vPXe`i6Roda752c@cuvUkQ z{4kfJwIY8BnF*MA@M-B64}f|8MozMqKVakzJo88X>5jl~CzEunR|?HICY1!n!F3H# zb;8{+L><`L&jAg&NgwNFvBH`LXox~WqWgRYHe1MOlK!HI=~TG3PZ*K2MH=&}5BXcc z5zqFdT>;?_5aQ`Qb!O2(!!nP<=)O2uB@?f=*R1D z0hW{RhWegJ^_R2Wy$)%6i~6aK>0%zS_kdhGAt|9qb>&13PaHQ#N_&Hv-*tbxuE6mPR@n&upb|Qm0=8@Q@x;6gY zxfkq|3MH!!m%;AgVx6dH1t;_=wXt|9UWT17<|~^Yuo;iB1M@|H)@A?nCuz)d@{AXn ztZN?3SC;#b$Y+F~ksBP@=8{cmgj&aaI7Fwt+ej<8p7%ifbhp@a3PksPTbj26kI>Tp z1v5a*zYqhRR?bwOZSySR1qQizlNMT_4(Vh$NXI2cj**rJV?X10+pw*a2MR2F##un6 z5B>VvV(Uq^x?Hxjh(d{wK;sWt_boV5Jy#{q%F-^RBxlMZ4Fu1E+=gTw%PuWf+$vuG zz%htjSOr}SEO^Jq#LAW~Ch9JmJEc&&qLs_W7$wKJ9Ca;fS~>ErnY*Vb2KoPt-!-uu z$@mSuJ9=BixO9}MD3E;xDoNZjk;=b>2RU1Ilfk3i4XkEWIVGy`ytKJWDk6*PajBkZ(I@!T#WJJO$GHJV+Gg;BzzB%$5 zxUutJK+rOJdOsvPehzw_1lFb(U~+8&Mo%``UFYTK80Ys}iA=z}Dtx!IpjzoU z!OwBpB)2E^!13k(VEds=msDxQLGvxcz$~ z(0R@t3O}AHJB+MIPP-AZmZBaq&|LU6ny~2dDGyV{Lz<5u|;4dvY}O`$WY6Rgc%Wl>-w{?&y|-+ z%k<#@MC~g$G%UlS*S@t?;PYg58?X5?1)`^4y%C!sPf` z=`Kjy^%nL*QKT9MdEXIWt9-a z!3yR5V>tg%b2!SG=70l{07I4)hiGbUPSr1Hc_B4@C8Rb*qKWMCMz{;z=YO4Z7#WL9b<~04>}n z?}1#w)4qS?6>#0cNBvGH@4>D^ylVxT(F`Q%DEs+&Py}{*-onqq{bPRwAB+|ouY@Zz z_G7v7e!6OvB+|GP;4!J-HdeMnBE zW=PYK;2a~wqgqf~V!D>&$S`kMdNW*;y%7GF$^RGLCYPeO;bHt9Ov4fU@SkzGehPjv zeLhE)k|K#FrwlH<2T97;xP_pdJRZ@c=!5n(X)wQ$B45amNW&=-T)ght?Od7`3SCQX zvKa*8L=G{^f}|tS;KHdZxJOUz%pr^OnkDQ?uSN6Z5z5B#|L}J+-QZw@S_}T00FGi= z<7X_~0XI+H3m>1{7yU(RF?#*VRGZ2ysahECM9&8o5S%#<1LZn6SN2erQLvBoC{=}! zh!DNtt0j5ItSCe5tB`iW`^1WfS+Tf0T`*m^a9w|_d$3u!8x zRk0)E@Y-i#Q|}+)L+T3nka!L^*zwEcB7pEBkD+lkep0b#pftj)e?GqrL5p)n>o|@b z8u?3}ft{`NEesTH6&GIA|A2yd;*l6=JsiILcxW9wkluvjTmx5g-+1ywDYMpwTkiT> zU{^uzQ)X7Dw!&&O3nC-|@o^YGKp+f~n;!TPxc7VhKX`L%bnK_rVQIvN1AN;(QOyFi z(pc^sCF-E!ihG{r{l`UEpN&pf7I~W9Ea4w7ya@!_o~Tmn#E=5`v-QwXLn`6=auq}{ zqfF^dP$6Hzge8!C)ejZx*bwrqCvJ0VcbdwB4wf8rK(=%-&Sx<1uZ+&J1` zJ77aP1;4TMBk+p$_rZHcABImgAA`fI?}lx?2l*mAPka>KrJjRh&0i3`g5X$aLK6+K zF4T-K>mL_3leLe=(;lQH<=-ch#fpmI+&OMUiG}sb*62>-%k5}jD_c)F?VAS4gcv_^ z62>1s<}3DczmIj42t6|F@%bYDX<`Fj*hX0M+Ie?d>+8E=_KN4j+!ZO@trwG9DL2QL zSb0Rb(wkq*tJFHGsQ;=YxhE3u(CS$zG2u$5zu@R+;nN51u2US0>lgR6Uvsr~Gp-iF z6+>E-1n}x|s&{=7z(TPeZB_LkIKiTLElbu7$u?vv;mKx|$y8>TF|!k>jW_%t0G~}P zw)$y}+XP?5ybL{cnT=4YqBgeLQjuefuy4B9Qt#P_d(B1`UqfOO-;4IOZJT~{bUjkGNBbqPK%_sN$Ud4?Z@p=faxtbxHjWCjrjY0#r0^Ush!@Mv)xgdXA#=} znam8?`ngDDCVMUuJ$#1$(QA=ed~~V6cV*^qR5{X6JtSH*#AwAI+bg{Zg7hwX6sW^a z#)cZ{HZs=yB?rq zgi&5uOM5mYmr@6<`jb2H?kdA7sf~u)>JES6ogFxOQ8$b{ro6@#=Lc4(>0K`y0LJgt zuzD>Hn(=OVnI%e>=J6Ot_A6L@43Fm&PGAcmtJZk)NMuW}?{ffKu0()d4RH8707t%s z0LH;LvmIaqzGo&K9NId?6XP}MeQYD(7KxlXG`L*kxN$)y0xhR7Fj^3_&ZI*S+Z+Mw zXgPKS9U5h{pf-U{#u@wNM9o+eyl4M+<9uGPCNOiGf?fc*FBfvqD@%u5c;E42;=a9? zWxtjv({OBQpanm!J4-(N+4AKFi$4mFx(=qdf?5pQ_Lsc}l@tOI;1`LL^&AO*UqxMY z6?eK;Z|2=;te0u__#Zn**5BT|Y2+@vXYc=~( zIk&WvR|lnBTbj*ilIxK%MjnGPyqd9t^(h100NN^TIRqA44kjMP_j^z#LR|=83wa?z zR=S=iZIpms^|B>i63oUag4L{2(b))e*BUUkw&D+j++o=XXq{MueJ7@1BLa2n(~yWa z0j$mA=kXeJ7IEWCyP1D`%ppGuXLu4P*I2Ok9g+sl<~+$b>LzIo5sBrf7-jPH@<3P8 zhn?6E&vQ>WGPu0(!qPrmt+xZO2;_oKLHN(PkV{`AjmuDW>!2|1U0I7`Yly44#B^CU zEY8q4$qi}CFwBKPA!-!_c#-&JUCrUie+;g+< zjlP1f_kRXIyUQnZ;%!>C+ELq-&@(|2&g9#6b7O#i^!my>njJX^M2fX zASyA~$8Q*D#So`3$Exult@AZQE`-FqpGf$1z<7aPa(@-AjC5H4w?!>7$@z_^n(%vE z67L9@G5LiZt>eWS!MXDGOP4w6O#({k811Hp+KsFxl%V1dX#v@`oh+g@od&ru2Kh=0 z;(U#deF2YRVGET4;|=vW{oX3?6;(>6yi?V+raS`;qkGY&MB+2*{M!8uW7)!hav{pa z-T+&#9 z!Fk!HkamVao2K4m&s;bomUZ3_rHGx0bzS&;_-$W~?#h$`=xa73P9pyE<8MjSPH6?wJ+yi~U%$9q15y^T|LS2F9x^9rIM z3sAnl=o-|ycs@&eyhP|CU>m5>r^zn+1a8j+I5pagcO`3WZlKPdcTYClr!03xw1XPx zgak+O%i02qet|#7$W0!ad=!;6CIGRj#t>zSmkFJj zv+}j9A+Zm*^{9sK>kB4pZG4K19u>R;RXCVE3^t||8Fpm(dWsBB!mvO3sAoSe zF{t%Zi{)z2By_{^T_7#>pOFp669a;klbA#FvrIjzp#o8J!{~hW)B3^gdmp&I@sB70 zZb!pJ9~lEZyA6$=1vnlz;H23KvvUvfrQUw@Ymnm&Hx&8m;11g!JPM!7Rb<_&|Ls~c1=ak**+f&vh{KDC)knQ&k5r; zq(t#dE34J)th0re%!lNIy9Gir6_+yOl}MeJ13a}`pJ(O^1bh;20o01^s|tV7r*Zvr z8`{p;kZe;tQl3vp(iLUGOi^Mk`c|8%5^Y4t3G?%47IMi#F&iZnS|5y{?^Apx;^&d? za+51wO~)GT6IZ=z61kze3FkAg4UZ?vM{>pFT6p#gq0V7NO4%X0khbjLi@W;Oo4(b3 z?}x5$eB~j4n_iDoChkrh8i!VlhK*G4X!tQeve776~r7%jP|1)B+h1_84d0`mq3da@L?B`LX6N z!cQ-K4(?Xh!$;+hGUvVhhd&Bm*u4wh)4dr!@-Beqp`Ly2>u`iOs^i&)!~G$Y3!i)1 zw&*9eK1xffQld-F4fULiP|uDEYIWU%Sfs8YQbP-5ulc1d2uNrRP^r1_a|NYmi5^Lg ztcZFzPfQL#)aD-gJcG@;fnG#KCL;7@Bi1#q3`RyFCxZWp4cTCvRoS>UNzP^WJvbXJAj_y7o*ZmPu@QE_UJG3Qir4$9<9lIJFGE{R zlsdCM^U!>MR@Vfl?my{66?~UPZg; zDv+LCLY6&sx1a7+;oBvdg2()gEk6#oqt5-3cqQc&!B4;RUby{PdwF}>$SL(4uVZE!RT0#QeG6F9k~;UU?et^8fk|pM+ok<+}>sVSdZ?bNP?lw#Q~@JbwDF zS$W@Q0jBUb#&5=B!-4nB5BtLQpLpq>lhe&k4^1xe;bt;589w$XiALnMgWDR;f@_fF zE2woz57T>HVpx%7lskf8&KmfcCKxnpIoE^~Kc&i%G|UjZ!FVG*oM)Cj<3+H`lts9< zmMK=vNwSvKC=S2&qslM8>qF_P})CN_)0~vs8`r0yu&k^^?0K%|5DX6f{ZWNNn zb*SK_1|gx#4ZWtSU|Q3x04inObAx6{O+gZ_S<@oH*A(4a$|u~xbjCi?n@t}|XVQJy zXtrP@lTyxUL*>y&-hRX8?kT)+Pe(9~A_kYdTX}6}BW!x)3$T0NUhh^~UWCu=_<05d zfz%Cl)3RRwwsFeN*F9+4s5#U5;1Nju)IPcm8F9Kw0x4SAgoM8Gy*B=AdK{mwcsExU zS({cEUPCZ1<5W(82Ggq;-xZn|VG(JLLBV|J9`>L9+dn`Qv-W7?)n7vGv@kiB;l;vr*)FLt{TshEVEEIVZ-JpE264xftLU}?2 zaB30yh6&~-T5nfMzwR!5m)7d#B~ZDhjJy*~-Vo5Pp;pYeIFQbY`@1{gn`_sZdpg_m z$Fd2tWE*u!_uwhsLbz>Xisn?B*4acyMA)uS2Q@!&_9eXdi{UiL$K9-h=L{nlr$*u07}#-pDExB+LNB|Lw;;^qf#-FuwambWZ` zG^MqH4ZY6DtY$Fh2DNNXwQ9(=Il?%l=eg*0GDvB)O9Zc_A#GMsldVFI&noe5 zMZ3lzBRe*}qtehXn&0VxE;Uux8W+0RKrNZJEZNn40R`XKLzezoRT6pC413l;ZCi>s~i#zv;xn|>@PwX09`y9aJtMH~H!_9pQ;NYWU zWtRbR0sZpwKf<1W`~~>P9q;s#p(tdfK*obUQ6;3qLq?r5*ZrEyBp*i6u#cv_Hce0Y zea4&mjk3a5%U%e496=CTJeJjc`0;^dUr11mOyaf^{PM;F9*fqRkAc1DjsrG;bv0~j zVWW9u4FSCVv%h`U;g|fIM?e4PZ+OWI@40j5b5A1ZsolxJdpAx9s!d?VwVq*-X@Pt2 z`l+0m*IIh*%inUmP?w#WhFa&Af`B?;CldE^KsXEbA9r2(ql2J5qfWDI9(^pLQAkAyOoyY|%G#FGa z5xMC0B=0ipRz z{a=KGZdco12J%iwwFl z^NYL=MLsuyIcG0~x0c9=la4(ZU_*$v-jS;6Kd$?@wea_vK$AXS&FQ{ObQf6Fm2qM* zBy+))`DCKjNiSG?!EW}kY?2r3v0`t11fe9Ba9Wwi5jvM`8SNc@>+br( z(0W)q+5wX%KLc>P*?N>-8Fq3U`MHB^7$Hk0C>b8zdmDT@x&dCXa$h+JBfMvCI`jef z>hd3Pa!-v< zX(gS@-jH>_nk)tTZkh9J`{K8K!8YPJ5qUg;^Y@BTy_Fhx+qJDzJpSk({?nIm`_!l3 z_~L6{_Q#*U3ol16IWCjtW7Ne4u2L&@3rd6r8>kejwYC=?9fKD)?@^^$lb%}-RGvj~ zv-BQ$6WFI`Y2iR7F=DIKIv-W71-Bsdyj5Y@F;{Ei4CTCU%WMS^5LVWlDhWApk?45`M@<}Pb1sw832bi`Sw+$FBU(AJKP}Qi_JfWo7101AUs(9f=Y8{NJFI& zCkIw-9ky&URKPBLxYUh^zy_v2;Lzu3D4aI7$k7fboqMomXnb{6LV|kmAl-(8=tV1j z1(@gl^23>=`g@}j?V39gIZ>|L_vaA8kPb>#)pMDd>jB@Z`>>fw!!yp5Qu{bUPaRjF>JZ0A_vC~$zP~;R6tn< z`j&xnJ3e6pZEnG;uEY78=lHkl+|7p1#kNJ4?q02pel)Xvb&=XNW;iE)9>2MU`Y=5L z@N#_piU<6cXr!%~*FxJZ!JK<5+>U=cihq0W*g&Y3oP68Df~QA=d#j#vBz*tA#^%7b z^`kyVrsK+fF7`gHK_UIS6|d-<=ln_1Nsqt%i}}cF4(HNDq)82LhWml>d%B}&;s4Zos8&ZG^wHn5O7I(E^geib)PD@~PAZjUOVj$(Q z+GQ3fyvG|Q0n2kN#y8~Ow%YUV=|bg9Y&*!fy@8hcYIHft$whq(U-giG)*r$feE^;!YX>$&zuldW@{^T#s;uV>lYkhP1-s%6J zy>9`Q2`r~B*w{Lk|fV-L(MVsX7ip1Lcrzl-mIiu%)$>yRBMT zYh`H0RWdMESFPnqUz}CD2>nAnk*gqvSc)WM)?5G#TGoV|WZwZ4T#1X?c14emoS)v^ ziuJM!mBo#z^=ToWerkRUecJX6FTE3Y3Gq`OVHwBHU;W0p?$1AZN`3FqnXw06xy#Mu z>Ds%#Dw1cW<@o#W6mR_4OT>Ku$`1h`f9qNR|KLPO#yJj2ksrLYu1u|e)Y>m_a8lh5+v~ul^$doA0`bo2;k0l&cN-?b6%(Jv4gMs_$(aQ zz2FY)0nAPjUnX^rVehTw782!TNSQjHb+UoJ)lZ1-Mm4% zWX>s;6Jy33E2sa%?Q-EguaVWc`{-Ot{PtW!eC@`>`xkJAPx>mGtB-dp;~ed#WbCj( z0B6Pk{C{t;Cw_X7by_++-OSEpB{}%itR5 z0q%rt++Ns-poSe{ah2#-OH@&*TJh(WVs-L1hz}BR|3?o~Vq^7fr=RTIed0u;JN0rv z>I88MU_XzlRPk)G_H*Bqoxk#&n0o(pYW82=DDPh^#r=zg_#aDq#RtasipSyFa6?uZ z>w_z|35Tfx=lJc%PKjSS@ocyz7k~NLql>@#%(Ff-(ALS?Vp{CF3ta?cih+O(;A%@` zmr{krLP8=jDAuY&g#)aB;nV@xUjVa~xt(gRs2^#b7`v^QE&k-p-Nj=r3+}=B0DGPx zE;- ziLxEqp4q4xOq>Ob!8<72M`T>Z9Z!C_@ua)!=C93*yB_I@AA0f&#Ob{F>xUm7Tb{lP zEbXL4rWOZh-yxF@@eE|dtA*Xwd#qS_?_s(2&SS~=A2h}MFH9#tdF~nUljqvvQ{y*_ z-yeUec%-pcEXyp6(P06>SuvP6>2Kl(WbcR1xV=~s-`zbfe(LO({DLthR^Q$|wfyeo zBgHFvbPazL4)OKXdv`>d1M(dOr1?QRfXmX9FpDck0vB!VZO=W~c(Qxf%MLfikNw5N zC_y5Y-#hv6Sa0r?LaH&aPi=*j2DDWY8PAZ@0S5@JMwU!LZUcB}`>b1e&&r9_x1QD0 z?>BREzg*70vGbgGW9Orkar)ENcZzRi`^BNLz2f-toLF0J!Me?k?i55{`^>U9P@Wa@ zaKBfd75A)sKJs|R2(XOBAu8=NXz0q_cyn^%u7x`PLPGk>IgtNiS_`;Z25 zv_#b4`8X(X7W(V)`5Lxiiebgm$f6ycAXhH&S|YM9bG2THBnI^mV|oPw_s+qA`BCnI z9Kt0R-rv1v>CuUg9e(R8UN}zG1but&uzAaCcB_II%-fP`I|>`xl|v>LB~oW?fs{jz zh&NG>%{5I@oC1_QFBU|$_)n9w%ReWYlfS3tCqI(xZ9P;p-q8DV@rEA6JgYi#`UmUf z>43X#MentZmHbs=rSoQWs{4KE>9|M`-TMA+PoI1+>o!HB+xi~3#5(BD;&6=AN{iBT zVTn!^@-b;QCa6Ub!ajNr0vEoNnrJK*6xu@DG|BBfQ(tm7Pkg2O@Yx?}eXaAdrWpd( zAsl}3IlFTsC)rYEPtvU-O04*AMTn=WfK5e}SvNpca^(Q`=|SL%5tTDJ1hm6OIq7ld zzA^9-zbS4$*`woA{KTzB_Q09VP_>5`&9V32kUoV%F=|!Kva#-jk||iutJM7Du|;~^f;#AU#@Mi!Jrxw2zru@vQ3^C19USMjom&X=z_ z{=4(vxaRFip>AZ$Yb?0~m|-YP7jA91O~S95MKTWpG_@$A<+a$@jaC>eaDh!VD_EYD z&b>4{rT5?TvxmM?&bTif`PVlt4+rn_=gE+4I*NDx_lZ!R0tn=q4DUS^}s3`x_$nuGK<C8okOvB%IvgE~i2hv_+8a}BbWh#6hKaY4OE+_tUst=>jK;&8fyy?Z;&OF^Xv-`K3C)0NTASGB^oIJoBFIrdxC%6O}z-yZDcm{wu51>9Nu;U9| z1>y}emgd9|xEfps06)AAL0yi`^j+vtKpkj`p$gQ=*PZ^0>pr=AEbZ98fgM?tH9#=pWCMb+ zpsp_Xt>E{Tb`DRyyyrRb zm8Xge+nXpG;JzJzOPTtNX;wD$>(_Bs+ku`ndVb&UbY7V)$sa-vUGPFziRs)@-#r$_ zEP+=U%ymw<%&fuRs}iS>F)d~&n~PPmF+J`6tg~zRiRiEg&?=p_vd;K^Wo6fq)&b7b z@p!&NTTO&+G{%e-OvA1MQ`cikm@FBzfX29V0K4l(1nw4~N6<5^fU92!byz^Cm=XO@YUA631x6aXzT zJu?ng_gH85%BKWR5~u<2s_`>3*QFgb@1*TWn!>Ln8Wj7tM7b#&2vLXrvPAV6>`_%{ z@l(gVv4-jNU8CWe3u0H$*wq6KM5BN@v@zzy53c^O`?*KdochPPcjZ+ojb>51Q^5E4gYI5B>JRiXyVaMHvjR}dz4AO3k5cqt=`~} z2u(PNM#92ynA|GGEOjh7dkX0lC8|@R>ww~|X+j)#<%Id8&K{)s=<9t59cQh@$(vLU z4W`Rk*`2u}kUJK26BxJ<3?WS?`M3yXWw;KEwPYLwWTF#nYDkOZ8rb)qO~T0JVB6*> zu&(RmDt1}7d}s0BXCI;B!^>f*UqcngDo{RgO@lcX-xQ>5`gdY#KG%-yN_EJR50}Zq9|UR|0T@@B~URo7cL6C#FTI7TP4*(@SQD#X>9*@Kh9k2a?b1MZm+>=B| z%ESG(l&qSc2Jp=SP}iuT8>O7k zx*E8r{}6=bDsdthyNhnRI04V)G?HWSy&ByH9=|C|c&=o+P~yBT9Hr76SY%V;vAmud zFE}yZ-qhKRrJ&qp1FozKB2o`u>@O~tOWT2Wd~sj8mV7?|XThbmP3h@o)4`JHLc2SP zonWL)#7*gpOZ6E^s=7qJhr$VpLNdHhvzBe>&z<`I_M-!3=NHP z=Cn}VT_##4Bo$O}<^XHPrJ;<1v>v#m=`qgJtVpCxHBGQ@rj@zm8fj!sm)`k-x{(ep zI%bFdVRqu+-|zl6u#j(s@5;#9qwh+f#bq9>rtTT?aWw$7D2@O~=K-Aizym)9-?U1u zgVP+Kp|vKxtR0>{^l#CL zfEZty9?OQ}d=B=9dWqb_24To!6qe3v_V#UV>2JIwaonPd*0U zc|U+tlC|aWa$Mp$XhRnONj?k(ZN~wS)A0N*4#qHmwfuhJfy!(XnCrEiN`udko)e78 zKr0&1S-2mA8;XmsZORdlmjk%jc|xj-2QNLo@drNnQZRC_5@|CLO)%#@h_D(>X6_+P zhe^j_7=ROEC84rC0)ifZ@hR*8qp1vCqQIXe-ZE@FQGod@{mjYxkiDy~)2#3D)Dzi_ zvRf{|WzuG=prl{kI8B1w(L95v=psU}LKsgL7g5T+PAM%zM0(7s4@A7-#Gc7#n)3kM z#ft&z5Wv>kzyDWHil6<`)HJ>k?Qt?#x2H{HH4@8GLGAY5zWWl}Wma7n5? zqgMh;yeAwU_bv&n;MrJ0C#aD@Fa{uX$uZKpbV&vWT9aRDQ$=JINs81z&-M0+2L~*| zI@#T9N!%vU@=)eLidtDbZ4h7+#oRX};Em8VRD6&apvh`%MP|f7fSCs(%`lFp6r3E( zti*wzr$&^p#R$-j25h`N@Nd5;{^q+|;0}DZ(4Cx!wh^@C?;5U_5r`@9LJPVAw+^gO z3jo-8cn&Xo?Ku)GbL5uHm#zb8o$=fm)qWb^l4yJz+Km~RhP-i_6`$(pq&O2J9W9VWgRP{+3b0l_>rpMtx7ep*goV+Tb+ABgz zDIAqGZan$a$@@`EbaeC1zE3n}R7by5pk|*WEw4%fRQN8kN&?9UFS&^CljK6!(lc)x z=ES1eh@82$n3hVShJ4|!k51@FkjD2H1Z;mF!6U-t$nt&%Slyf~trOm7#iE1e3Qw;Wf4kX4Y$V?5F~4Y>`j z$!@EGTV*)6YrFN@795r zf(Ay_i6fd*5>gS9aBv^%9!wt_63g}lgRK|D4X}L9!?SI25x~Kfbg#HeL1D0EyQJ^N z2gXsPt)!RG^ZmoljrmkjK^ITeNP^h6_PTwaXtqxLXx<)i$~F(yc>CSovf?M+`Y>wg zfOXvu7jm0i226RCSQ&%;wODxYDKHGmu*=NpN3Gq+fCKoV+28CxboQHf)%VKSVCH54q9yz!-wQyI0gtW=S*PI9OJLhF#*Kj1E0 zJWz*izyF~^yy?vk0r2i6OInr%r)foV5~SK&S8lXUrV-;C@#A+CM*X@YgP|Nd3yc znStMXBYba%4ls|GpvjfqWx*5DCQwVNuC&Nipp%Hwpw@U<0v_hKA?2k#*%;=N@raHq zqpt&?KDn*O_LKi>P279mpNT@>4HtR~ZUuu8t6`ex#JSjje_M733^7wL!#M#1Vx~O+ zmxYK#Mru+6Cu)}Qe)2j#qAt>hjtOq}Tu*Xm_avI7qj^RiDAtU(l70-AnKU zEonHTZz%=iYw9Hr0T%f@-D?OWFgniL+FJ`PiVgrc_icM65)Ge(6Yzx%?N6TH0_Spu zYX6!#r8G5V7i9_2HI_k3krz3V9sB1U`HR>SfY0SRhsW?9x)mZvqA>W3yu^Q3@_XRP zJTGAX4~T#EzU?o;fBKKlf|~$7Z_y=#P=Pkb{q}tdn&fF=jVQR`CP!-g0$e3~iln@p znDSYKx=4~moqMv6_o$*csDUjaW}75Z-QH4*yHmsJd%$8kdw^(NQddw90j5N=2C1Ed zA4d$i7#Lx$7+rzhtZ)sKo;pIA29%*fI+?QP{a;Ky^|)68ya~}5`=a>Izqz)deKPtb zEG&omwBVL#@@*ycUSPSH-GDL#I+U|5S*(;TZV??pRxGzwUv4DPD8emjR@INuZ&{iyr0BsZ!=RNGmW1L*G|(0}i@eDgB1iu*W#DJ40n-*xP5x-2&kfgh%| za8yhfaui7Gi?a);5T5hJQGKgpacyL*w5G1Ys{||@ihKxW&b1X8vgdTfjxDCYUEL8w z@%b+w5_jE^0YGmPIDv%wihQ`}F7yDzXn`ey)~CL*kS9Qw3EVN_N|=PRw@l!*L4?zW z08NR0-mr>NsV4Yv7mKlUXx?mxtn9(-eh~w%J@9>V6lErYD_Yc;RN|lvcFH;uv`r|1 zDk*2H;kkwz2cLBo{sk=k0InoWg-$j3u~^DPtqByKbUC(eX9OIX{vYvQAJ|wK(wLh7 zkQ2;4i5HFb%3O-AJvWVaC>@6BEm96wzRl5AEs3^{oUviddB4{gAtEAIB1i@hIEew; zH^okEdGRfglmJP%K&iLGvCE)6Xz^ZI8+Z?FP=>_6m#JA)5$zSG!P@Jpe*M-q=Du}- z;OipMmR@{9VoS;vyZu#L`Uy`L`Zh`ZuJogdSpG*6wWgy?0%nhMHzzWOB2BB5_M*Bf z=5euSq2Va|fa)TYJIzo$<4IT=4~w_GYrxBwtUw|M!qB3;1zX2Cn~)^*d6WXf6tYrY zD5C2#2>z1y83p^&fzq?QToqwRjW_zXZH+<=T3$b{D z=_M~1WJf}++CBxx7LK^t1ID5RS4~sYi&=)$)-2&J6d7V*c6}=+%inr3_5qef5nKZ~ zK=2_i-b7xm(H(RI7D)Oug&(dxvwy89=xHLzU6d4oYk%&u3_Ann!Ix|U7|#ina@iFJ zy`*JM15#u+i6~R_Cjqo>MWl_#Nw`6u>EDz2z3asm(0`L=FY*>pnhPsNBy)`bMz;X< zD-uIslf(%usDD*Tre8(N>ufpDWa0t{q-SX(FTpT?Ev41qnAmA}era>Sg+uW2?-kA5{zd3VKxgf)`}bMqCf_~7f#3FKu-k~5qY(Y*LzS3-*V zz+UcIV!?sf(c!t|{|ebyCV=MMs+zqXXVv9rqe{m}Vt?k%ROOR7nA) zlkDSC(1>dZ^6=Z7f$Mw`!n$Co2^=;h&RJJJy?K8X9j|MJI0Zj7_fkQd+j4KiwrmuGaPm>eV3@v(l&ZZ_`i)whj9bv$GT|I^_T?x zG*)a85R8ekMU@=l{k9o(Ct<6i!Fp@y*v&-COI1k#AUMgGMX+my$k*c9J=lE!?KlMS z-QIdPA#Lua6N!S^6qKP)TL_;Db0jvHWNRcur+j*hnr?|vv-Dni>D>h%w2`K<=UH&v zLTP=|3O$D%R|Q6XyGW67@KWB8%K(RE&rL!c|CVQp-T*?37q{T|wqehogNHYG+0shU zyx%<#k+?{e=29WeReDyMG?3@3pbWWXY@q3mxd%*iW+S405ghdDI!rvPR9qh6#tpoV zk#BVo{>C6Cp^hc=Z&Nt&jg}*2K#fxAUg9WC$G}xM0=MIRsxO>^g166LgUqxXkg=4b4nnz=M=OKH6)zUxX4H%7yu79HE#@j*k3xtb`mQKnSXHn6A`8z< z3RX($5Qve?!ui~Ofx#23lVboN*D$o~ry2vFHvGG9k|wzu04_4EAg@DF_AjaEa$t*L zBDn#Cvj>uM0f-HE^0=6uLqZz#n<*8+N@U8^!UOF0YEHx}UudX4ANLtmAMnd*5`r&Z zQw`!N9*1$R32#NKyK+%S8&ijK;4&O}N(4EejTV{ee8!Sjbzbm22-O;CqXNUA5J~-5 zMk%nExNZxJTH6U+2BIZPYJ6*j^YrKHk z06SQ%HrzKXi&>YL!*z$?eam$}LzJj)EWiow^y@OwiBmHGq*(x6j}zr!Y0={1siS-2 z12sA!T~U!slQl*QnsA$=gZ)6@Vks4aM06o&1dZgSB@HE3`CF1&5|x^$qYK@-UGVa= z7jG)SD_gi8tnTv{4-!e^H<(wSBDvDa=dHDKjCUPuWLNt&$!f6@yOo>@4BYLuxk=~; zvO#Y}dI{pHKscZoMvqv4c)};W*F4pc35qS0%*Os)BfPAtg%upj#AuV3z)w9yex5e{ zzAfsFVg^l@>)i(>_d;;uDf&?X9>6LMUs8!7{yG3KxI77#fO~8?@#IRco0G#nr4>nD z$>m~3I|X9ww>FtgoFqz&ifEKLtzjGxQJ6Cvioe_uS}<=ZbByl1@?Zbfa1 zRX)~6^!J0HwZS#8+f&X;x2>nP5>%_}aVj)hh!C~Pz8KR*mKW>coKsj1>2Kx#oeg?G z3}V)d<)VP0DbyDPPu;Fh)K5<^zm*%XwhnxTB;7-!$;kJuFqqQHSHrk+My-pc#n}Wu zilS2EFMmII?Ce8 zU~e;@PgwHcR1vUM&~&eStkki^(r9hnS$Oq~D29{-(Vv~3WpUi~FG5g+3@F8ip|F-spQ-RDe+8h;WX4joCJdv#i%2SUjgDwzH zfUJ+o=TjW{ImA@vH4pG&g6wdJ)Qs4DazRKfPP_Y3Sq z;i1;RPCS=aWr0-`cXAyq^MOwp{9)%qyfm~SvxikdU;BD^B2JNp@Zdlxz6Y((I5SW1>RRBJCtBYvy3xZ!sl^0?`C~YlykV$aYe{?{#wf^ zDpbw~sh$r)THh&VDwhFP0DJL%Zb(GV!SR8wELJbg{#h!_w@JIfMh!D^h=Ps~+uo?P za|MVb{M;(PAbbs5_}l_!3mSeXr}VBuRS1@m6sw{u-h?b;1-vdiKF#m4>(f2xCUkm3 zjwodVrT~NEl)F`uL&t=AZ!Iz=2<;tBnIzPBBT>@YPXI_7oMa_&>64+3a<>Kj9lU$z z{aNpDA^|g|#1u~5Iu88k7gc#)lI|IYk6XT^Y^7koXZ95qcM@_f?7SBs_0||BKf?vTv8emnb9*Q7oQ2 z^kp?qek#)FE8qE>NsrZ1v#J)!YE;ryfzf=6U zq^yFY*CbW?X#Ok=@iKTV&e_Q+*pKB)U-7_jC*U&J#fx_t;=YE=C%QxwYiz|QU!f)6 zm{7?pY*)?VVf-#9SV|AdxHKVre|-`HhQvDy2=*jW6M z1KSrB`?WGfh6KKp63YRb3mFTV`W4I5X(j zal8UCSzU5HrojKbbW%>Lko!PE>aAGDLqr<2DvF&2?soU%+5zCYK5lLe=?2j@?vm1MqM#wISRPrLg^;B041GVG;}%ouE^VfLy?8Vwg|G=j)o+= zBEt?ase(YV^%D?Sag)Txn2MSvwB|2#o)Dz!x}ts{%S}P5vRmNeB7oUCH-*ue*enOv zP*)8b=@KR)+?vfqA=zaBuG;}CJ1kgBq9G3z0+4#RnG*XRn6fX;Z9^V{0fC}#osnao z!{ko-cW}x#R2wSRQhJM|zw3a^1yKVG|8ok=6b_a<9^@b>JGBcmFz!EV1PRf;Gk8vxq@RtHJw){v{lDe%kOFItP%i$`Bb=H^J zP<)6J<$Y}k<5Y-gtNzfW@4!hvqoqb%FjkD7W#)@x!slCkG6%^z4Bra!Zy9u2r`ShB zV8j58cxzPUzj7{9Q#u2c)VNAk6*|$VdRh>0J8~l3!#)PX)Z}!5mdriMR=FxlkVFsm zUap9%+Z6)b{u*2C|5jeDjI`U82M1>uv~QYYORGoZk9mRB=gl#X`AettX*n7GBbNjL zW_YR?gC$Enwz3NtQ%hA5=KDgFra>78`n&3!axNk^t#Z`~ z&2-sNi`ts4@=)bf;9fd(WmJN2f$rCOi^;aJvTT{>5$Dv*=&gdz=o2*I`yQ;nTx9UT zWw6Oiok~3wt_2vh1wD9MGee%e7+wN@EP*b~`v5n2M1Xf8`e$D+FDhQdUM zEsjRiRp4%2kwS*tuJQ?&x?Mv=)|X3+Jt59xhwypKgk~eZFQF8i^(izJG8lE^j|oDZ z!hcew@L1X9%0lu!QGWzw6}ZVp`dXiGF-R=U2RPqQAMddlDA?3tX>;JcTVr(_SAn~= z_P3h%wUpOQqN)6MnG-Ip3XJXC7SCM#Mnol?H*^-tr@D#P$*w{(lBs;b|wTgX#%2g7r1tnvGuS zHnIeAKSV`|SwqncFp^jQXKSo|KXKK6QCUeYJiER+ng1fKrFLJ!hx2-&fI>3t^xX9`Yu7&V@Q*4JH?Czt{#p_i9#Qn{&WP7|AnYBU7Rm zCa!Em6qj)(!9}iuhyW@*M^+IQbs5dhxjtR8Dv)H0s|Naw6CXAvzRNff@$`ob7p)ke z@vQpeb}H9?6}Ve;SY6*-{^)m_4O!hRc&=_87iBFuFQUMVDWY`v4i!SksxwMQds|uH z{!v>OKxss*NnCx74YSWKWHdy`KV&{2-L4hxMddQ2;@fT8n46&dBx=1N$DO1q${|aC zoK%=-=+7u5VWO&ts(H%$x27*hthurZ|42vUN-nG{wF@G`DxkJgi~Op^z5JlPaFm?* z#oa`HYMj^-eL6alz(R=Vmp zvu(%ET?Ovt0qw>{v6UBZa>0A7dm(Dznm#|a4!-I}X&KU3iKqr4tLlA~W;4N(yFPb8 z2Vhq2fs4v6GIS!l$a$$QtL+7@y$al&+ma3EBc;#Unt_hrIvX34lhk3D^|@mk`GPW3 z^_;i3H9#xi+I49s*B7Q)AEedku=?_1FsD)A<~&2rcKf+60?R8&sOWMn=~bGj6t$jf z)f8g}F0Vo9x?0Xf}t!jA4kVkNlM_cy*S0HS119@Z>T|^kq{RE8==<&bB_jmgw zw4JDDotcuV*JtC>J`&E_Wl*8z-fU}tR_&P?0p2t^-lKSw)xf@A1?~{wZbJJt*fAAR z3fn%=ieOJ;nkbexqW&#k&5}c0la`2!%j|Ms<*e7|CdfYE@;q5Jjh7Ap)kYp}+-}5W z=k2wLZbahkDsZvui+nt}BQxV~@j8bMc@}&Eha=jy-s2So-Uj^=E18#j%VjngQ-zSG;G?S2P5kh^@gA`VIRn*KJrj|OQrTd$Bj>5O3fwCXxM^eo zQ*X!QK>oIBX;&EIT6TkptZcLe#B|wr`BI4)-ed%AE?It^)Up z2X5UdrTbg~+1I68H{;YEr}H*D_Ci%?&@}4+>mtC{E`l4#_qugOh;+y7?27`p-+q;| ztqV_TR;i_@%07*qoM6N<$g4w&3IsgCw literal 0 HcmV?d00001 diff --git a/examples/assets/sprites/spinObj_07.png b/examples/assets/sprites/spinObj_07.png new file mode 100644 index 0000000000000000000000000000000000000000..e14a0215fbb470074651269a9484d1976121672c GIT binary patch literal 35407 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 0; i--) - { - var child = children[i]; - -// if(child.visible) { - // push all interactive bits - if(child.interactive) - { - iParent.interactiveChildren = true; - //child.__iParent = iParent; - this.interactiveItems.push(child); + var children = displayObject.children; + var length = children.length; - if(child.children.length > 0) - { - this.collectInteractiveSprite(child, child); - } - } - else - { - child.__iParent = null; + /// make an interaction tree... {item.__interactiveParent} + for (var i = length-1; i >= 0; i--) + { + var child = children[i]; - if(child.children.length > 0) - { - this.collectInteractiveSprite(child, iParent); - } - } -// } - } -} +// if(child.visible) { + // push all interactive bits + if(child.interactive) + { + iParent.interactiveChildren = true; + //child.__iParent = iParent; + this.interactiveItems.push(child); + + if(child.children.length > 0) + { + this.collectInteractiveSprite(child, child); + } + } + else + { + child.__iParent = null; + + if(child.children.length > 0) + { + this.collectInteractiveSprite(child, iParent); + } + } +// } + } +}; /** * Sets the target for event delegation @@ -120,16 +116,16 @@ PIXI.InteractionManager.prototype.collectInteractiveSprite = function(displayObj */ PIXI.InteractionManager.prototype.setTarget = function(target) { - this.target = target; + this.target = target; - //check if the dom element has been set. If it has don't do anything - if( this.interactionDOMElement === null ) { + //check if the dom element has been set. If it has don't do anything + if( this.interactionDOMElement === null ) { - this.setTargetDomElement( target.view ); - } + this.setTargetDomElement( target.view ); + } - document.body.addEventListener('mouseup', this.onMouseUp, true); -} + document.body.addEventListener('mouseup', this.onMouseUp, true); +}; /** @@ -143,44 +139,43 @@ PIXI.InteractionManager.prototype.setTarget = function(target) */ PIXI.InteractionManager.prototype.setTargetDomElement = function(domElement) { - //remove previouse listeners - if( this.interactionDOMElement !== null ) - { - this.interactionDOMElement.style['-ms-content-zooming'] = ''; - this.interactionDOMElement.style['-ms-touch-action'] = ''; + //remove previouse listeners + if( this.interactionDOMElement !== null ) + { + this.interactionDOMElement.style['-ms-content-zooming'] = ''; + this.interactionDOMElement.style['-ms-touch-action'] = ''; - this.interactionDOMElement.removeEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.removeEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); - // aint no multi touch just yet! - this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); - } + // aint no multi touch just yet! + this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); + } - if (window.navigator.msPointerEnabled) - { - // time to remove some of that zoom in ja.. - domElement.style['-ms-content-zooming'] = 'none'; - domElement.style['-ms-touch-action'] = 'none'; - - // DO some window specific touch! - } + if (window.navigator.msPointerEnabled) + { + // time to remove some of that zoom in ja.. + domElement.style['-ms-content-zooming'] = 'none'; + domElement.style['-ms-touch-action'] = 'none'; - this.interactionDOMElement = domElement; + // DO some window specific touch! + } - domElement.addEventListener('mousemove', this.onMouseMove, true); - domElement.addEventListener('mousedown', this.onMouseDown, true); - domElement.addEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement = domElement; - // aint no multi touch just yet! - domElement.addEventListener('touchstart', this.onTouchStart, true); - domElement.addEventListener('touchend', this.onTouchEnd, true); - domElement.addEventListener('touchmove', this.onTouchMove, true); -} + domElement.addEventListener('mousemove', this.onMouseMove, true); + domElement.addEventListener('mousedown', this.onMouseDown, true); + domElement.addEventListener('mouseout', this.onMouseOut, true); + // aint no multi touch just yet! + domElement.addEventListener('touchstart', this.onTouchStart, true); + domElement.addEventListener('touchend', this.onTouchEnd, true); + domElement.addEventListener('touchmove', this.onTouchMove, true); +}; /** * updates the state of interactive objects @@ -190,85 +185,86 @@ PIXI.InteractionManager.prototype.setTargetDomElement = function(domElement) */ PIXI.InteractionManager.prototype.update = function() { - if(!this.target)return; - - // frequency of 30fps?? - var now = Date.now(); - var diff = now - this.last; - diff = (diff * 30) / 1000; - if(diff < 1)return; - this.last = now; - // - - // ok.. so mouse events?? - // yes for now :) - // OPTIMSE - how often to check?? - if(this.dirty) - { - this.dirty = false; - - var len = this.interactiveItems.length; - - for (var i=0; i < len; i++) { - this.interactiveItems[i].interactiveChildren = false; - } - - this.interactiveItems = []; - - if(this.stage.interactive)this.interactiveItems.push(this.stage); - // go through and collect all the objects that are interactive.. - this.collectInteractiveSprite(this.stage, this.stage); - } - - // loop through interactive objects! - var length = this.interactiveItems.length; - - this.interactionDOMElement.style.cursor = "inherit"; - - for (var i = 0; i < length; i++) - { - var item = this.interactiveItems[i]; - - - //if(!item.visible)continue; - - // OPTIMISATION - only calculate every time if the mousemove function exists.. - // OK so.. does the object have any other interactive functions? - // hit-test the clip! - - - if(item.mouseover || item.mouseout || item.buttonMode) - { - // ok so there are some functions so lets hit test it.. - item.__hit = this.hitTest(item, this.mouse); - this.mouse.target = item; - // ok so deal with interactions.. - // loks like there was a hit! - if(item.__hit) - { - if(item.buttonMode) this.interactionDOMElement.style.cursor = item.defaultCursor; - - if(!item.__isOver) - { - - if(item.mouseover)item.mouseover(this.mouse); - item.__isOver = true; - } - } - else - { - if(item.__isOver) - { - // roll out! - if(item.mouseout)item.mouseout(this.mouse); - item.__isOver = false; - } - } - } - - // ---> - } -} + if(!this.target)return; + + // frequency of 30fps?? + var now = Date.now(); + var diff = now - this.last; + diff = (diff * 30) / 1000; + if(diff < 1)return; + this.last = now; + // + + var i = 0; + + // ok.. so mouse events?? + // yes for now :) + // OPTIMSE - how often to check?? + if(this.dirty) + { + this.dirty = false; + + var len = this.interactiveItems.length; + + for (i = 0; i < len; i++) { + this.interactiveItems[i].interactiveChildren = false; + } + + this.interactiveItems = []; + + if(this.stage.interactive)this.interactiveItems.push(this.stage); + // go through and collect all the objects that are interactive.. + this.collectInteractiveSprite(this.stage, this.stage); + } + + // loop through interactive objects! + var length = this.interactiveItems.length; + + this.interactionDOMElement.style.cursor = 'inherit'; + + for (i = 0; i < length; i++) + { + var item = this.interactiveItems[i]; + + + //if(!item.visible)continue; + + // OPTIMISATION - only calculate every time if the mousemove function exists.. + // OK so.. does the object have any other interactive functions? + // hit-test the clip! + + + if(item.mouseover || item.mouseout || item.buttonMode) + { + // ok so there are some functions so lets hit test it.. + item.__hit = this.hitTest(item, this.mouse); + this.mouse.target = item; + // ok so deal with interactions.. + // loks like there was a hit! + if(item.__hit) + { + if(item.buttonMode) this.interactionDOMElement.style.cursor = item.defaultCursor; + + if(!item.__isOver) + { + + if(item.mouseover)item.mouseover(this.mouse); + item.__isOver = true; + } + } + else + { + if(item.__isOver) + { + // roll out! + if(item.mouseout)item.mouseout(this.mouse); + item.__isOver = false; + } + } + } + // ---> + } +}; /** * Is called when the mouse moves accross the renderer element @@ -279,28 +275,26 @@ PIXI.InteractionManager.prototype.update = function() */ PIXI.InteractionManager.prototype.onMouseMove = function(event) { - this.mouse.originalEvent = event || window.event; //IE uses window.event - // TODO optimize by not check EVERY TIME! maybe half as often? // - var rect = this.interactionDOMElement.getBoundingClientRect(); - - this.mouse.global.x = (event.clientX - rect.left) * (this.target.width / rect.width); - this.mouse.global.y = (event.clientY - rect.top) * ( this.target.height / rect.height); - - var length = this.interactiveItems.length; - var global = this.mouse.global; - - - for (var i = 0; i < length; i++) - { - var item = this.interactiveItems[i]; - - if(item.mousemove) - { - //call the function! - item.mousemove(this.mouse); - } - } -} + this.mouse.originalEvent = event || window.event; //IE uses window.event + // TODO optimize by not check EVERY TIME! maybe half as often? // + var rect = this.interactionDOMElement.getBoundingClientRect(); + + this.mouse.global.x = (event.clientX - rect.left) * (this.target.width / rect.width); + this.mouse.global.y = (event.clientY - rect.top) * ( this.target.height / rect.height); + + var length = this.interactiveItems.length; + + for (var i = 0; i < length; i++) + { + var item = this.interactiveItems[i]; + + if(item.mousemove) + { + //call the function! + item.mousemove(this.mouse); + } + } +}; /** * Is called when the mouse button is pressed down on the renderer element @@ -311,61 +305,57 @@ PIXI.InteractionManager.prototype.onMouseMove = function(event) */ PIXI.InteractionManager.prototype.onMouseDown = function(event) { - this.mouse.originalEvent = event || window.event; //IE uses window.event - - // loop through inteaction tree... - // hit test each item! -> - // get interactive items under point?? - //stage.__i - var length = this.interactiveItems.length; - var global = this.mouse.global; - - var index = 0; - var parent = this.stage; - - // while - // hit test - for (var i = 0; i < length; i++) - { - var item = this.interactiveItems[i]; - - if(item.mousedown || item.click) - { - item.__mouseIsDown = true; - item.__hit = this.hitTest(item, this.mouse); - - if(item.__hit) - { - //call the function! - if(item.mousedown)item.mousedown(this.mouse); - item.__isDown = true; - - // just the one! - if(!item.interactiveChildren)break; - } - } - } -} + this.mouse.originalEvent = event || window.event; //IE uses window.event + + // loop through inteaction tree... + // hit test each item! -> + // get interactive items under point?? + //stage.__i + var length = this.interactiveItems.length; + + // while + // hit test + for (var i = 0; i < length; i++) + { + var item = this.interactiveItems[i]; + + if(item.mousedown || item.click) + { + item.__mouseIsDown = true; + item.__hit = this.hitTest(item, this.mouse); + + if(item.__hit) + { + //call the function! + if(item.mousedown)item.mousedown(this.mouse); + item.__isDown = true; + + // just the one! + if(!item.interactiveChildren)break; + } + } + } +}; -PIXI.InteractionManager.prototype.onMouseOut = function(event) +PIXI.InteractionManager.prototype.onMouseOut = function() { - var length = this.interactiveItems.length; - - this.interactionDOMElement.style.cursor = "inherit"; - - for (var i = 0; i < length; i++) - { - var item = this.interactiveItems[i]; - - if(item.__isOver) - { - this.mouse.target = item; - if(item.mouseout)item.mouseout(this.mouse); - item.__isOver = false; - } - } -} + var length = this.interactiveItems.length; + + this.interactionDOMElement.style.cursor = 'inherit'; + + for (var i = 0; i < length; i++) + { + var item = this.interactiveItems[i]; + + if(item.__isOver) + { + this.mouse.target = item; + if(item.mouseout)item.mouseout(this.mouse); + item.__isOver = false; + } + } +}; /** * Is called when the mouse button is released on the renderer element @@ -376,48 +366,45 @@ PIXI.InteractionManager.prototype.onMouseOut = function(event) */ PIXI.InteractionManager.prototype.onMouseUp = function(event) { - this.mouse.originalEvent = event || window.event; //IE uses window.event - - var global = this.mouse.global; - - - var length = this.interactiveItems.length; - var up = false; - - for (var i = 0; i < length; i++) - { - var item = this.interactiveItems[i]; - - if(item.mouseup || item.mouseupoutside || item.click) - { - item.__hit = this.hitTest(item, this.mouse); - - if(item.__hit && !up) - { - //call the function! - if(item.mouseup) - { - item.mouseup(this.mouse); - } - if(item.__isDown) - { - if(item.click)item.click(this.mouse); - } - - if(!item.interactiveChildren)up = true; - } - else - { - if(item.__isDown) - { - if(item.mouseupoutside)item.mouseupoutside(this.mouse); - } - } - - item.__isDown = false; - } - } -} + this.mouse.originalEvent = event || window.event; //IE uses window.event + + var length = this.interactiveItems.length; + var up = false; + + for (var i = 0; i < length; i++) + { + var item = this.interactiveItems[i]; + + if(item.mouseup || item.mouseupoutside || item.click) + { + item.__hit = this.hitTest(item, this.mouse); + + if(item.__hit && !up) + { + //call the function! + if(item.mouseup) + { + item.mouseup(this.mouse); + } + if(item.__isDown) + { + if(item.click)item.click(this.mouse); + } + + if(!item.interactiveChildren)up = true; + } + else + { + if(item.__isDown) + { + if(item.mouseupoutside)item.mouseupoutside(this.mouse); + } + } + + item.__isDown = false; + } + } +}; /** * Tests if the current mouse coords hit a sprite @@ -429,68 +416,68 @@ PIXI.InteractionManager.prototype.onMouseUp = function(event) */ PIXI.InteractionManager.prototype.hitTest = function(item, interactionData) { - var global = interactionData.global; - - if(item.vcount !== PIXI.visibleCount)return false; + var global = interactionData.global; - var isSprite = (item instanceof PIXI.Sprite), - worldTransform = item.worldTransform, - a00 = worldTransform[0], a01 = worldTransform[1], a02 = worldTransform[2], - a10 = worldTransform[3], a11 = worldTransform[4], a12 = worldTransform[5], - id = 1 / (a00 * a11 + a01 * -a10), - x = a11 * id * global.x + -a01 * id * global.y + (a12 * a01 - a02 * a11) * id, - y = a00 * id * global.y + -a10 * id * global.x + (-a12 * a00 + a02 * a10) * id; + if(item.vcount !== PIXI.visibleCount)return false; - interactionData.target = item; - - //a sprite or display object with a hit area defined - if(item.hitArea && item.hitArea.contains) { - if(item.hitArea.contains(x, y)) { - //if(isSprite) - interactionData.target = item; + var isSprite = (item instanceof PIXI.Sprite), + worldTransform = item.worldTransform, + a00 = worldTransform[0], a01 = worldTransform[1], a02 = worldTransform[2], + a10 = worldTransform[3], a11 = worldTransform[4], a12 = worldTransform[5], + id = 1 / (a00 * a11 + a01 * -a10), + x = a11 * id * global.x + -a01 * id * global.y + (a12 * a01 - a02 * a11) * id, + y = a00 * id * global.y + -a10 * id * global.x + (-a12 * a00 + a02 * a10) * id; - return true; - } - - return false; - } - // a sprite with no hitarea defined - else if(isSprite) - { - var width = item.texture.frame.width, - height = item.texture.frame.height, - x1 = -width * item.anchor.x, - y1; - - if(x > x1 && x < x1 + width) - { - y1 = -height * item.anchor.y; - - if(y > y1 && y < y1 + height) - { - // set the target property if a hit is true! - interactionData.target = item - return true; - } - } - } + interactionData.target = item; - var length = item.children.length; - - for (var i = 0; i < length; i++) - { - var tempItem = item.children[i]; - var hit = this.hitTest(tempItem, interactionData); - if(hit) - { - // hmm.. TODO SET CORRECT TARGET? - interactionData.target = item - return true; - } - } + //a sprite or display object with a hit area defined + if(item.hitArea && item.hitArea.contains) { + if(item.hitArea.contains(x, y)) { + //if(isSprite) + interactionData.target = item; - return false; -} + return true; + } + + return false; + } + // a sprite with no hitarea defined + else if(isSprite) + { + var width = item.texture.frame.width, + height = item.texture.frame.height, + x1 = -width * item.anchor.x, + y1; + + if(x > x1 && x < x1 + width) + { + y1 = -height * item.anchor.y; + + if(y > y1 && y < y1 + height) + { + // set the target property if a hit is true! + interactionData.target = item; + return true; + } + } + } + + var length = item.children.length; + + for (var i = 0; i < length; i++) + { + var tempItem = item.children[i]; + var hit = this.hitTest(tempItem, interactionData); + if(hit) + { + // hmm.. TODO SET CORRECT TARGET? + interactionData.target = item; + return true; + } + } + + return false; +}; /** * Is called when a touch is moved accross the renderer element @@ -501,27 +488,30 @@ PIXI.InteractionManager.prototype.hitTest = function(item, interactionData) */ PIXI.InteractionManager.prototype.onTouchMove = function(event) { - var rect = this.interactionDOMElement.getBoundingClientRect(); - var changedTouches = event.changedTouches; - - for (var i=0; i < changedTouches.length; i++) - { - var touchEvent = changedTouches[i]; - var touchData = this.touchs[touchEvent.identifier]; - touchData.originalEvent = event || window.event; - - // update the touch position - touchData.global.x = (touchEvent.clientX - rect.left) * (this.target.width / rect.width); - touchData.global.y = (touchEvent.clientY - rect.top) * (this.target.height / rect.height); - } - - var length = this.interactiveItems.length; - for (var i = 0; i < length; i++) - { - var item = this.interactiveItems[i]; - if(item.touchmove)item.touchmove(touchData); - } -} + var rect = this.interactionDOMElement.getBoundingClientRect(); + var changedTouches = event.changedTouches; + var touchData; + var i = 0; + + for (i = 0; i < changedTouches.length; i++) + { + var touchEvent = changedTouches[i]; + touchData = this.touchs[touchEvent.identifier]; + touchData.originalEvent = event || window.event; + + // update the touch position + touchData.global.x = (touchEvent.clientX - rect.left) * (this.target.width / rect.width); + touchData.global.y = (touchEvent.clientY - rect.top) * (this.target.height / rect.height); + } + + var length = this.interactiveItems.length; + for (i = 0; i < length; i++) + { + var item = this.interactiveItems[i]; + if(item.touchmove) + item.touchmove(touchData); + } +}; /** * Is called when a touch is started on the renderer element @@ -532,45 +522,45 @@ PIXI.InteractionManager.prototype.onTouchMove = function(event) */ PIXI.InteractionManager.prototype.onTouchStart = function(event) { - var rect = this.interactionDOMElement.getBoundingClientRect(); - - var changedTouches = event.changedTouches; - for (var i=0; i < changedTouches.length; i++) - { - var touchEvent = changedTouches[i]; - - var touchData = this.pool.pop(); - if(!touchData)touchData = new PIXI.InteractionData(); - - touchData.originalEvent = event || window.event; - - this.touchs[touchEvent.identifier] = touchData; - touchData.global.x = (touchEvent.clientX - rect.left) * (this.target.width / rect.width); - touchData.global.y = (touchEvent.clientY - rect.top) * (this.target.height / rect.height); - - var length = this.interactiveItems.length; - - for (var j = 0; j < length; j++) - { - var item = this.interactiveItems[j]; - - if(item.touchstart || item.tap) - { - item.__hit = this.hitTest(item, touchData); - - if(item.__hit) - { - //call the function! - if(item.touchstart)item.touchstart(touchData); - item.__isDown = true; - item.__touchData = touchData; - - if(!item.interactiveChildren)break; - } - } - } - } -} + var rect = this.interactionDOMElement.getBoundingClientRect(); + + var changedTouches = event.changedTouches; + for (var i=0; i < changedTouches.length; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.pool.pop(); + if(!touchData)touchData = new PIXI.InteractionData(); + + touchData.originalEvent = event || window.event; + + this.touchs[touchEvent.identifier] = touchData; + touchData.global.x = (touchEvent.clientX - rect.left) * (this.target.width / rect.width); + touchData.global.y = (touchEvent.clientY - rect.top) * (this.target.height / rect.height); + + var length = this.interactiveItems.length; + + for (var j = 0; j < length; j++) + { + var item = this.interactiveItems[j]; + + if(item.touchstart || item.tap) + { + item.__hit = this.hitTest(item, touchData); + + if(item.__hit) + { + //call the function! + if(item.touchstart)item.touchstart(touchData); + item.__isDown = true; + item.__touchData = touchData; + + if(!item.interactiveChildren)break; + } + } + } + } +}; /** * Is called when a touch is ended on the renderer element @@ -581,67 +571,69 @@ PIXI.InteractionManager.prototype.onTouchStart = function(event) */ PIXI.InteractionManager.prototype.onTouchEnd = function(event) { - //this.mouse.originalEvent = event || window.event; //IE uses window.event - var rect = this.interactionDOMElement.getBoundingClientRect(); - var changedTouches = event.changedTouches; - - for (var i=0; i < changedTouches.length; i++) - { - var touchEvent = changedTouches[i]; - var touchData = this.touchs[touchEvent.identifier]; - var up = false; - touchData.global.x = (touchEvent.clientX - rect.left) * (this.target.width / rect.width); - touchData.global.y = (touchEvent.clientY - rect.top) * (this.target.height / rect.height); - - var length = this.interactiveItems.length; - for (var j = 0; j < length; j++) - { - var item = this.interactiveItems[j]; - var itemTouchData = item.__touchData; // <-- Here! - item.__hit = this.hitTest(item, touchData); - - if(itemTouchData == touchData) - { - // so this one WAS down... - touchData.originalEvent = event || window.event; - // hitTest?? - - if(item.touchend || item.tap) - { - if(item.__hit && !up) - { - if(item.touchend)item.touchend(touchData); - if(item.__isDown) - { - if(item.tap)item.tap(touchData); - } - - if(!item.interactiveChildren)up = true; - } - else - { - if(item.__isDown) - { - if(item.touchendoutside)item.touchendoutside(touchData); - } - } - - item.__isDown = false; - } - - item.__touchData = null; - - } - else - { - - } - } - // remove the touch.. - this.pool.push(touchData); - this.touchs[touchEvent.identifier] = null; - } -} + //this.mouse.originalEvent = event || window.event; //IE uses window.event + var rect = this.interactionDOMElement.getBoundingClientRect(); + var changedTouches = event.changedTouches; + + for (var i=0; i < changedTouches.length; i++) + { + var touchEvent = changedTouches[i]; + var touchData = this.touchs[touchEvent.identifier]; + var up = false; + touchData.global.x = (touchEvent.clientX - rect.left) * (this.target.width / rect.width); + touchData.global.y = (touchEvent.clientY - rect.top) * (this.target.height / rect.height); + + var length = this.interactiveItems.length; + for (var j = 0; j < length; j++) + { + var item = this.interactiveItems[j]; + var itemTouchData = item.__touchData; // <-- Here! + item.__hit = this.hitTest(item, touchData); + + if(itemTouchData === touchData) + { + // so this one WAS down... + touchData.originalEvent = event || window.event; + // hitTest?? + + if(item.touchend || item.tap) + { + if(item.__hit && !up) + { + if(item.touchend)item.touchend(touchData); + if(item.__isDown) + { + if(item.tap)item.tap(touchData); + } + + if(!item.interactiveChildren)up = true; + } + else + { + if(item.__isDown) + { + if(item.touchendoutside)item.touchendoutside(touchData); + } + } + + item.__isDown = false; + } + + item.__touchData = null; + + } + /* + else + { + + } + */ + } + // remove the touch.. + this.pool.push(touchData); + this.touchs[touchEvent.identifier] = null; + } +}; /** * Holds all information related to an Interaction event @@ -651,33 +643,33 @@ PIXI.InteractionManager.prototype.onTouchEnd = function(event) */ PIXI.InteractionData = function() { - /** - * This point stores the global coords of where the touch/mouse event happened - * - * @property global - * @type Point - */ - this.global = new PIXI.Point(); - - // this is here for legacy... but will remove - this.local = new PIXI.Point(); + /** + * This point stores the global coords of where the touch/mouse event happened + * + * @property global + * @type Point + */ + this.global = new PIXI.Point(); - /** - * The target Sprite that was interacted with - * - * @property target - * @type Sprite - */ - this.target; + // this is here for legacy... but will remove + this.local = new PIXI.Point(); - /** - * When passed to an event handler, this will be the original DOM Event that was captured - * - * @property originalEvent - * @type Event - */ - this.originalEvent; -} + /** + * The target Sprite that was interacted with + * + * @property target + * @type Sprite + */ + this.target = null; + + /** + * When passed to an event handler, this will be the original DOM Event that was captured + * + * @property originalEvent + * @type Event + */ + this.originalEvent = null; +}; /** * This will return the local coords of the specified displayObject for this InteractionData @@ -688,17 +680,17 @@ PIXI.InteractionData = function() */ PIXI.InteractionData.prototype.getLocalPosition = function(displayObject) { - var worldTransform = displayObject.worldTransform; - var global = this.global; - - // do a cheeky transform to get the mouse coords; - var a00 = worldTransform[0], a01 = worldTransform[1], a02 = worldTransform[2], + var worldTransform = displayObject.worldTransform; + var global = this.global; + + // do a cheeky transform to get the mouse coords; + var a00 = worldTransform[0], a01 = worldTransform[1], a02 = worldTransform[2], a10 = worldTransform[3], a11 = worldTransform[4], a12 = worldTransform[5], id = 1 / (a00 * a11 + a01 * -a10); - // set the mouse coords... - return new PIXI.Point(a11 * id * global.x + -a01 * id * global.y + (a12 * a01 - a02 * a11) * id, - a00 * id * global.y + -a10 * id * global.x + (-a12 * a00 + a02 * a10) * id) -} + // set the mouse coords... + return new PIXI.Point(a11 * id * global.x + -a01 * id * global.y + (a12 * a01 - a02 * a11) * id, + a00 * id * global.y + -a10 * id * global.x + (-a12 * a00 + a02 * a10) * id); +}; // constructor PIXI.InteractionData.prototype.constructor = PIXI.InteractionData; diff --git a/src/pixi/Intro.js b/src/pixi/Intro.js index 4023084f5..f485ffc0e 100644 --- a/src/pixi/Intro.js +++ b/src/pixi/Intro.js @@ -4,4 +4,4 @@ (function(){ - var root = this; + var root = this; diff --git a/src/pixi/core/Circle.js b/src/pixi/core/Circle.js index e2728335b..d17246199 100644 --- a/src/pixi/core/Circle.js +++ b/src/pixi/core/Circle.js @@ -33,7 +33,7 @@ PIXI.Circle = function(x, y, radius) * @default 0 */ this.radius = radius || 0; -} +}; /** * Creates a clone of this Circle instance @@ -44,7 +44,7 @@ PIXI.Circle = function(x, y, radius) PIXI.Circle.prototype.clone = function() { return new PIXI.Circle(this.x, this.y, this.radius); -} +}; /** * Checks if the x, and y coords passed to this function are contained within this circle @@ -67,7 +67,7 @@ PIXI.Circle.prototype.contains = function(x, y) dy *= dy; return (dx + dy <= r2); -} +}; // constructor PIXI.Circle.prototype.constructor = PIXI.Circle; diff --git a/src/pixi/core/Ellipse.js b/src/pixi/core/Ellipse.js index 1a95d20a1..7a9c2f64c 100644 --- a/src/pixi/core/Ellipse.js +++ b/src/pixi/core/Ellipse.js @@ -41,7 +41,7 @@ PIXI.Ellipse = function(x, y, width, height) * @default 0 */ this.height = height || 0; -} +}; /** * Creates a clone of this Ellipse instance @@ -52,7 +52,7 @@ PIXI.Ellipse = function(x, y, width, height) PIXI.Ellipse.prototype.clone = function() { return new PIXI.Ellipse(this.x, this.y, this.width, this.height); -} +}; /** * Checks if the x, and y coords passed to this function are contained within this ellipse @@ -76,12 +76,12 @@ PIXI.Ellipse.prototype.contains = function(x, y) normy *= normy; return (normx + normy < 0.25); -} +}; -PIXI.Ellipse.getBounds = function() +PIXI.Ellipse.prototype.getBounds = function() { return new PIXI.Rectangle(this.x, this.y, this.width, this.height); -} +}; // constructor PIXI.Ellipse.prototype.constructor = PIXI.Ellipse; diff --git a/src/pixi/core/Matrix.js b/src/pixi/core/Matrix.js index 923682c68..1f5a67a5c 100644 --- a/src/pixi/core/Matrix.js +++ b/src/pixi/core/Matrix.js @@ -16,112 +16,112 @@ PIXI.mat3 = {}; PIXI.mat3.create = function() { - var matrix = new PIXI.Matrix(9); + var matrix = new PIXI.Matrix(9); - matrix[0] = 1; - matrix[1] = 0; - matrix[2] = 0; - matrix[3] = 0; - matrix[4] = 1; - matrix[5] = 0; - matrix[6] = 0; - matrix[7] = 0; - matrix[8] = 1; + matrix[0] = 1; + matrix[1] = 0; + matrix[2] = 0; + matrix[3] = 0; + matrix[4] = 1; + matrix[5] = 0; + matrix[6] = 0; + matrix[7] = 0; + matrix[8] = 1; - return matrix; -} + return matrix; +}; PIXI.mat3.identity = function(matrix) { - matrix[0] = 1; - matrix[1] = 0; - matrix[2] = 0; - matrix[3] = 0; - matrix[4] = 1; - matrix[5] = 0; - matrix[6] = 0; - matrix[7] = 0; - matrix[8] = 1; + matrix[0] = 1; + matrix[1] = 0; + matrix[2] = 0; + matrix[3] = 0; + matrix[4] = 1; + matrix[5] = 0; + matrix[6] = 0; + matrix[7] = 0; + matrix[8] = 1; - return matrix; -} + return matrix; +}; PIXI.mat4 = {}; PIXI.mat4.create = function() { - var matrix = new PIXI.Matrix(16); + var matrix = new PIXI.Matrix(16); - matrix[0] = 1; - matrix[1] = 0; - matrix[2] = 0; - matrix[3] = 0; - matrix[4] = 0; - matrix[5] = 1; - matrix[6] = 0; - matrix[7] = 0; - matrix[8] = 0; - matrix[9] = 0; - matrix[10] = 1; - matrix[11] = 0; - matrix[12] = 0; - matrix[13] = 0; - matrix[14] = 0; - matrix[15] = 1; + matrix[0] = 1; + matrix[1] = 0; + matrix[2] = 0; + matrix[3] = 0; + matrix[4] = 0; + matrix[5] = 1; + matrix[6] = 0; + matrix[7] = 0; + matrix[8] = 0; + matrix[9] = 0; + matrix[10] = 1; + matrix[11] = 0; + matrix[12] = 0; + matrix[13] = 0; + matrix[14] = 0; + matrix[15] = 1; - return matrix; -} + return matrix; +}; PIXI.mat3.multiply = function (mat, mat2, dest) { - if (!dest) { dest = mat; } + if (!dest) { dest = mat; } - // Cache the matrix values (makes for huge speed increases!) - var a00 = mat[0], a01 = mat[1], a02 = mat[2], - a10 = mat[3], a11 = mat[4], a12 = mat[5], - a20 = mat[6], a21 = mat[7], a22 = mat[8], + // Cache the matrix values (makes for huge speed increases!) + var a00 = mat[0], a01 = mat[1], a02 = mat[2], + a10 = mat[3], a11 = mat[4], a12 = mat[5], + a20 = mat[6], a21 = mat[7], a22 = mat[8], - b00 = mat2[0], b01 = mat2[1], b02 = mat2[2], - b10 = mat2[3], b11 = mat2[4], b12 = mat2[5], - b20 = mat2[6], b21 = mat2[7], b22 = mat2[8]; + b00 = mat2[0], b01 = mat2[1], b02 = mat2[2], + b10 = mat2[3], b11 = mat2[4], b12 = mat2[5], + b20 = mat2[6], b21 = mat2[7], b22 = mat2[8]; - dest[0] = b00 * a00 + b01 * a10 + b02 * a20; - dest[1] = b00 * a01 + b01 * a11 + b02 * a21; - dest[2] = b00 * a02 + b01 * a12 + b02 * a22; + dest[0] = b00 * a00 + b01 * a10 + b02 * a20; + dest[1] = b00 * a01 + b01 * a11 + b02 * a21; + dest[2] = b00 * a02 + b01 * a12 + b02 * a22; - dest[3] = b10 * a00 + b11 * a10 + b12 * a20; - dest[4] = b10 * a01 + b11 * a11 + b12 * a21; - dest[5] = b10 * a02 + b11 * a12 + b12 * a22; + dest[3] = b10 * a00 + b11 * a10 + b12 * a20; + dest[4] = b10 * a01 + b11 * a11 + b12 * a21; + dest[5] = b10 * a02 + b11 * a12 + b12 * a22; - dest[6] = b20 * a00 + b21 * a10 + b22 * a20; - dest[7] = b20 * a01 + b21 * a11 + b22 * a21; - dest[8] = b20 * a02 + b21 * a12 + b22 * a22; + dest[6] = b20 * a00 + b21 * a10 + b22 * a20; + dest[7] = b20 * a01 + b21 * a11 + b22 * a21; + dest[8] = b20 * a02 + b21 * a12 + b22 * a22; - return dest; -} + return dest; +}; PIXI.mat3.clone = function(mat) { - var matrix = new PIXI.Matrix(9); + var matrix = new PIXI.Matrix(9); - matrix[0] = mat[0]; - matrix[1] = mat[1]; - matrix[2] = mat[2]; - matrix[3] = mat[3]; - matrix[4] = mat[4]; - matrix[5] = mat[5]; - matrix[6] = mat[6]; - matrix[7] = mat[7]; - matrix[8] = mat[8]; + matrix[0] = mat[0]; + matrix[1] = mat[1]; + matrix[2] = mat[2]; + matrix[3] = mat[3]; + matrix[4] = mat[4]; + matrix[5] = mat[5]; + matrix[6] = mat[6]; + matrix[7] = mat[7]; + matrix[8] = mat[8]; - return matrix; -} + return matrix; +}; PIXI.mat3.transpose = function (mat, dest) { - // If we are transposing ourselves we can skip a few steps but have to cache some values + // If we are transposing ourselves we can skip a few steps but have to cache some values if (!dest || mat === dest) { var a01 = mat[1], a02 = mat[2], a12 = mat[5]; @@ -145,34 +145,34 @@ PIXI.mat3.transpose = function (mat, dest) dest[7] = mat[5]; dest[8] = mat[8]; return dest; -} +}; PIXI.mat3.toMat4 = function (mat, dest) { - if (!dest) { dest = PIXI.mat4.create(); } + if (!dest) { dest = PIXI.mat4.create(); } - dest[15] = 1; - dest[14] = 0; - dest[13] = 0; - dest[12] = 0; + dest[15] = 1; + dest[14] = 0; + dest[13] = 0; + dest[12] = 0; - dest[11] = 0; - dest[10] = mat[8]; - dest[9] = mat[7]; - dest[8] = mat[6]; + dest[11] = 0; + dest[10] = mat[8]; + dest[9] = mat[7]; + dest[8] = mat[6]; - dest[7] = 0; - dest[6] = mat[5]; - dest[5] = mat[4]; - dest[4] = mat[3]; + dest[7] = 0; + dest[6] = mat[5]; + dest[5] = mat[4]; + dest[4] = mat[3]; - dest[3] = 0; - dest[2] = mat[2]; - dest[1] = mat[1]; - dest[0] = mat[0]; + dest[3] = 0; + dest[2] = mat[2]; + dest[1] = mat[1]; + dest[0] = mat[0]; - return dest; -} + return dest; +}; ///// @@ -180,82 +180,82 @@ PIXI.mat3.toMat4 = function (mat, dest) PIXI.mat4.create = function() { - var matrix = new PIXI.Matrix(16); + var matrix = new PIXI.Matrix(16); - matrix[0] = 1; - matrix[1] = 0; - matrix[2] = 0; - matrix[3] = 0; - matrix[4] = 0; - matrix[5] = 1; - matrix[6] = 0; - matrix[7] = 0; - matrix[8] = 0; - matrix[9] = 0; - matrix[10] = 1; - matrix[11] = 0; - matrix[12] = 0; - matrix[13] = 0; - matrix[14] = 0; - matrix[15] = 1; + matrix[0] = 1; + matrix[1] = 0; + matrix[2] = 0; + matrix[3] = 0; + matrix[4] = 0; + matrix[5] = 1; + matrix[6] = 0; + matrix[7] = 0; + matrix[8] = 0; + matrix[9] = 0; + matrix[10] = 1; + matrix[11] = 0; + matrix[12] = 0; + matrix[13] = 0; + matrix[14] = 0; + matrix[15] = 1; - return matrix; -} + return matrix; +}; PIXI.mat4.transpose = function (mat, dest) { - // If we are transposing ourselves we can skip a few steps but have to cache some values - if (!dest || mat === dest) - { - var a01 = mat[1], a02 = mat[2], a03 = mat[3], - a12 = mat[6], a13 = mat[7], - a23 = mat[11]; + // If we are transposing ourselves we can skip a few steps but have to cache some values + if (!dest || mat === dest) + { + var a01 = mat[1], a02 = mat[2], a03 = mat[3], + a12 = mat[6], a13 = mat[7], + a23 = mat[11]; - mat[1] = mat[4]; - mat[2] = mat[8]; - mat[3] = mat[12]; - mat[4] = a01; - mat[6] = mat[9]; - mat[7] = mat[13]; - mat[8] = a02; - mat[9] = a12; - mat[11] = mat[14]; - mat[12] = a03; - mat[13] = a13; - mat[14] = a23; - return mat; - } + mat[1] = mat[4]; + mat[2] = mat[8]; + mat[3] = mat[12]; + mat[4] = a01; + mat[6] = mat[9]; + mat[7] = mat[13]; + mat[8] = a02; + mat[9] = a12; + mat[11] = mat[14]; + mat[12] = a03; + mat[13] = a13; + mat[14] = a23; + return mat; + } - dest[0] = mat[0]; - dest[1] = mat[4]; - dest[2] = mat[8]; - dest[3] = mat[12]; - dest[4] = mat[1]; - dest[5] = mat[5]; - dest[6] = mat[9]; - dest[7] = mat[13]; - dest[8] = mat[2]; - dest[9] = mat[6]; - dest[10] = mat[10]; - dest[11] = mat[14]; - dest[12] = mat[3]; - dest[13] = mat[7]; - dest[14] = mat[11]; - dest[15] = mat[15]; - return dest; -} + dest[0] = mat[0]; + dest[1] = mat[4]; + dest[2] = mat[8]; + dest[3] = mat[12]; + dest[4] = mat[1]; + dest[5] = mat[5]; + dest[6] = mat[9]; + dest[7] = mat[13]; + dest[8] = mat[2]; + dest[9] = mat[6]; + dest[10] = mat[10]; + dest[11] = mat[14]; + dest[12] = mat[3]; + dest[13] = mat[7]; + dest[14] = mat[11]; + dest[15] = mat[15]; + return dest; +}; PIXI.mat4.multiply = function (mat, mat2, dest) { - if (!dest) { dest = mat; } + if (!dest) { dest = mat; } - // Cache the matrix values (makes for huge speed increases!) - var a00 = mat[ 0], a01 = mat[ 1], a02 = mat[ 2], a03 = mat[3]; - var a10 = mat[ 4], a11 = mat[ 5], a12 = mat[ 6], a13 = mat[7]; - var a20 = mat[ 8], a21 = mat[ 9], a22 = mat[10], a23 = mat[11]; - var a30 = mat[12], a31 = mat[13], a32 = mat[14], a33 = mat[15]; + // Cache the matrix values (makes for huge speed increases!) + var a00 = mat[ 0], a01 = mat[ 1], a02 = mat[ 2], a03 = mat[3]; + var a10 = mat[ 4], a11 = mat[ 5], a12 = mat[ 6], a13 = mat[7]; + var a20 = mat[ 8], a21 = mat[ 9], a22 = mat[10], a23 = mat[11]; + var a30 = mat[12], a31 = mat[13], a32 = mat[14], a33 = mat[15]; - // Cache only the current line of the second matrix + // Cache only the current line of the second matrix var b0 = mat2[0], b1 = mat2[1], b2 = mat2[2], b3 = mat2[3]; dest[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30; dest[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31; @@ -290,4 +290,4 @@ PIXI.mat4.multiply = function (mat, mat2, dest) dest[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33; return dest; -} +}; diff --git a/src/pixi/core/Point.js b/src/pixi/core/Point.js index 7c7fe2d72..c7e277209 100644 --- a/src/pixi/core/Point.js +++ b/src/pixi/core/Point.js @@ -12,20 +12,20 @@ */ PIXI.Point = function(x, y) { - /** - * @property x - * @type Number - * @default 0 - */ - this.x = x || 0; + /** + * @property x + * @type Number + * @default 0 + */ + this.x = x || 0; - /** - * @property y - * @type Number - * @default 0 - */ - this.y = y || 0; -} + /** + * @property y + * @type Number + * @default 0 + */ + this.y = y || 0; +}; /** * Creates a clone of this point @@ -35,8 +35,8 @@ PIXI.Point = function(x, y) */ PIXI.Point.prototype.clone = function() { - return new PIXI.Point(this.x, this.y); -} + return new PIXI.Point(this.x, this.y); +}; // constructor PIXI.Point.prototype.constructor = PIXI.Point; diff --git a/src/pixi/core/Polygon.js b/src/pixi/core/Polygon.js index 4ef74f118..4887f6c4f 100644 --- a/src/pixi/core/Polygon.js +++ b/src/pixi/core/Polygon.js @@ -29,8 +29,8 @@ PIXI.Polygon = function(points) points = p; } - this.points = points; -} + this.points = points; +}; /** * Creates a clone of this polygon @@ -40,13 +40,13 @@ PIXI.Polygon = function(points) */ PIXI.Polygon.prototype.clone = function() { - var points = []; - for (var i=0; i y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); if(intersect) inside = !inside; } return inside; -} +}; // constructor PIXI.Polygon.prototype.constructor = PIXI.Polygon; diff --git a/src/pixi/core/Rectangle.js b/src/pixi/core/Rectangle.js index 8a0e04025..70390a161 100644 --- a/src/pixi/core/Rectangle.js +++ b/src/pixi/core/Rectangle.js @@ -14,34 +14,34 @@ */ PIXI.Rectangle = function(x, y, width, height) { - /** - * @property x - * @type Number - * @default 0 - */ - this.x = x || 0; + /** + * @property x + * @type Number + * @default 0 + */ + this.x = x || 0; - /** - * @property y - * @type Number - * @default 0 - */ - this.y = y || 0; + /** + * @property y + * @type Number + * @default 0 + */ + this.y = y || 0; - /** - * @property width - * @type Number - * @default 0 - */ - this.width = width || 0; + /** + * @property width + * @type Number + * @default 0 + */ + this.width = width || 0; - /** - * @property height - * @type Number - * @default 0 - */ - this.height = height || 0; -} + /** + * @property height + * @type Number + * @default 0 + */ + this.height = height || 0; +}; /** * Creates a clone of this Rectangle @@ -51,8 +51,8 @@ PIXI.Rectangle = function(x, y, width, height) */ PIXI.Rectangle.prototype.clone = function() { - return new PIXI.Rectangle(this.x, this.y, this.width, this.height); -} + return new PIXI.Rectangle(this.x, this.y, this.width, this.height); +}; /** * Checks if the x, and y coords passed to this function are contained within this Rectangle @@ -67,19 +67,19 @@ PIXI.Rectangle.prototype.contains = function(x, y) if(this.width <= 0 || this.height <= 0) return false; - var x1 = this.x; - if(x >= x1 && x <= x1 + this.width) - { - var y1 = this.y; + var x1 = this.x; + if(x >= x1 && x <= x1 + this.width) + { + var y1 = this.y; - if(y >= y1 && y <= y1 + this.height) - { - return true; - } - } + if(y >= y1 && y <= y1 + this.height) + { + return true; + } + } - return false; -} + return false; +}; // constructor PIXI.Rectangle.prototype.constructor = PIXI.Rectangle; diff --git a/src/pixi/display/DisplayObject.js b/src/pixi/display/DisplayObject.js index df5e898be..53cce04d1 100644 --- a/src/pixi/display/DisplayObject.js +++ b/src/pixi/display/DisplayObject.js @@ -10,238 +10,238 @@ */ PIXI.DisplayObject = function() { - this.last = this; - this.first = this; - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @property position - * @type Point - */ - this.position = new PIXI.Point(); + this.last = this; + this.first = this; + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @property position + * @type Point + */ + this.position = new PIXI.Point(); - /** - * The scale factor of the object. - * - * @property scale - * @type Point - */ - this.scale = new PIXI.Point(1,1);//{x:1, y:1}; + /** + * The scale factor of the object. + * + * @property scale + * @type Point + */ + this.scale = new PIXI.Point(1,1);//{x:1, y:1}; - /** - * The pivot point of the displayObject that it rotates around - * - * @property pivot - * @type Point - */ - this.pivot = new PIXI.Point(0,0); + /** + * The pivot point of the displayObject that it rotates around + * + * @property pivot + * @type Point + */ + this.pivot = new PIXI.Point(0,0); - /** - * The rotation of the object in radians. - * - * @property rotation - * @type Number - */ - this.rotation = 0; + /** + * The rotation of the object in radians. + * + * @property rotation + * @type Number + */ + this.rotation = 0; - /** - * The opacity of the object. - * - * @property alpha - * @type Number - */ - this.alpha = 1; + /** + * The opacity of the object. + * + * @property alpha + * @type Number + */ + this.alpha = 1; - /** - * The visibility of the object. - * - * @property visible - * @type Boolean - */ - this.visible = true; + /** + * The visibility of the object. + * + * @property visible + * @type Boolean + */ + this.visible = true; - /** - * This is the defined area that will pick up mouse / touch events. It is null by default. - * Setting it is a neat way of optimising the hitTest function that the interactionManager will use (as it will not need to hit test all the children) - * - * @property hitArea - * @type Rectangle|Circle|Ellipse|Polygon - */ - this.hitArea = null; + /** + * This is the defined area that will pick up mouse / touch events. It is null by default. + * Setting it is a neat way of optimising the hitTest function that the interactionManager will use (as it will not need to hit test all the children) + * + * @property hitArea + * @type Rectangle|Circle|Ellipse|Polygon + */ + this.hitArea = null; - /** - * This is used to indicate if the displayObject should display a mouse hand cursor on rollover - * - * @property buttonMode - * @type Boolean - */ - this.buttonMode = false; + /** + * This is used to indicate if the displayObject should display a mouse hand cursor on rollover + * + * @property buttonMode + * @type Boolean + */ + this.buttonMode = false; - /** - * Can this object be rendered - * - * @property renderable - * @type Boolean - */ - this.renderable = false; + /** + * Can this object be rendered + * + * @property renderable + * @type Boolean + */ + this.renderable = false; - /** - * [read-only] The display object container that contains this display object. - * - * @property parent - * @type DisplayObjectContainer - * @readOnly - */ - this.parent = null; + /** + * [read-only] The display object container that contains this display object. + * + * @property parent + * @type DisplayObjectContainer + * @readOnly + */ + this.parent = null; - /** - * [read-only] The stage the display object is connected to, or undefined if it is not connected to the stage. - * - * @property stage - * @type Stage - * @readOnly - */ - this.stage = null; + /** + * [read-only] The stage the display object is connected to, or undefined if it is not connected to the stage. + * + * @property stage + * @type Stage + * @readOnly + */ + this.stage = null; - /** - * [read-only] The multiplied alpha of the displayobject - * - * @property worldAlpha - * @type Number - * @readOnly - */ - this.worldAlpha = 1; + /** + * [read-only] The multiplied alpha of the displayobject + * + * @property worldAlpha + * @type Number + * @readOnly + */ + this.worldAlpha = 1; - /** - * [read-only] Whether or not the object is interactive, do not toggle directly! use the `interactive` property - * - * @property _interactive - * @type Boolean - * @readOnly - * @private - */ - this._interactive = false; + /** + * [read-only] Whether or not the object is interactive, do not toggle directly! use the `interactive` property + * + * @property _interactive + * @type Boolean + * @readOnly + * @private + */ + this._interactive = false; - this.defaultCursor = "pointer"; - - /** - * [read-only] Current transform of the object based on world (parent) factors - * - * @property worldTransform - * @type Mat3 - * @readOnly - * @private - */ - this.worldTransform = PIXI.mat3.create()//mat3.identity(); + this.defaultCursor = 'pointer'; - /** - * [read-only] Current transform of the object locally - * - * @property localTransform - * @type Mat3 - * @readOnly - * @private - */ - this.localTransform = PIXI.mat3.create()//mat3.identity(); + /** + * [read-only] Current transform of the object based on world (parent) factors + * + * @property worldTransform + * @type Mat3 + * @readOnly + * @private + */ + this.worldTransform = PIXI.mat3.create(); //mat3.identity(); - /** - * [NYI] Unkown - * - * @property color - * @type Array<> - * @private - */ - this.color = []; + /** + * [read-only] Current transform of the object locally + * + * @property localTransform + * @type Mat3 + * @readOnly + * @private + */ + this.localTransform = PIXI.mat3.create(); //mat3.identity(); - /** - * [NYI] Holds whether or not this object is dynamic, for rendering optimization - * - * @property dynamic - * @type Boolean - * @private - */ - this.dynamic = true; + /** + * [NYI] Unkown + * + * @property color + * @type Array<> + * @private + */ + this.color = []; - // chach that puppy! - this._sr = 0; - this._cr = 1; + /** + * [NYI] Holds whether or not this object is dynamic, for rendering optimization + * + * @property dynamic + * @type Boolean + * @private + */ + this.dynamic = true; + + // chach that puppy! + this._sr = 0; + this._cr = 1; - this.filterArea = new PIXI.Rectangle(0,0,1,1); - - /* - * MOUSE Callbacks - */ + this.filterArea = new PIXI.Rectangle(0,0,1,1); - /** - * A callback that is used when the users clicks on the displayObject with their mouse - * @method click - * @param interactionData {InteractionData} - */ + /* + * MOUSE Callbacks + */ - /** - * A callback that is used when the user clicks the mouse down over the sprite - * @method mousedown - * @param interactionData {InteractionData} - */ + /** + * A callback that is used when the users clicks on the displayObject with their mouse + * @method click + * @param interactionData {InteractionData} + */ - /** - * A callback that is used when the user releases the mouse that was over the displayObject - * for this callback to be fired the mouse must have been pressed down over the displayObject - * @method mouseup - * @param interactionData {InteractionData} - */ + /** + * A callback that is used when the user clicks the mouse down over the sprite + * @method mousedown + * @param interactionData {InteractionData} + */ - /** - * A callback that is used when the user releases the mouse that was over the displayObject but is no longer over the displayObject - * for this callback to be fired, The touch must have started over the displayObject - * @method mouseupoutside - * @param interactionData {InteractionData} - */ + /** + * A callback that is used when the user releases the mouse that was over the displayObject + * for this callback to be fired the mouse must have been pressed down over the displayObject + * @method mouseup + * @param interactionData {InteractionData} + */ - /** - * A callback that is used when the users mouse rolls over the displayObject - * @method mouseover - * @param interactionData {InteractionData} - */ + /** + * A callback that is used when the user releases the mouse that was over the displayObject but is no longer over the displayObject + * for this callback to be fired, The touch must have started over the displayObject + * @method mouseupoutside + * @param interactionData {InteractionData} + */ - /** - * A callback that is used when the users mouse leaves the displayObject - * @method mouseout - * @param interactionData {InteractionData} - */ + /** + * A callback that is used when the users mouse rolls over the displayObject + * @method mouseover + * @param interactionData {InteractionData} + */ + + /** + * A callback that is used when the users mouse leaves the displayObject + * @method mouseout + * @param interactionData {InteractionData} + */ - /* - * TOUCH Callbacks - */ + /* + * TOUCH Callbacks + */ - /** - * A callback that is used when the users taps on the sprite with their finger - * basically a touch version of click - * @method tap - * @param interactionData {InteractionData} - */ + /** + * A callback that is used when the users taps on the sprite with their finger + * basically a touch version of click + * @method tap + * @param interactionData {InteractionData} + */ - /** - * A callback that is used when the user touch's over the displayObject - * @method touchstart - * @param interactionData {InteractionData} - */ + /** + * A callback that is used when the user touch's over the displayObject + * @method touchstart + * @param interactionData {InteractionData} + */ - /** - * A callback that is used when the user releases a touch over the displayObject - * @method touchend - * @param interactionData {InteractionData} - */ + /** + * A callback that is used when the user releases a touch over the displayObject + * @method touchend + * @param interactionData {InteractionData} + */ - /** - * A callback that is used when the user releases the touch that was over the displayObject - * for this callback to be fired, The touch must have started over the sprite - * @method touchendoutside - * @param interactionData {InteractionData} - */ -} + /** + * A callback that is used when the user releases the touch that was over the displayObject + * for this callback to be fired, The touch must have started over the sprite + * @method touchendoutside + * @param interactionData {InteractionData} + */ +}; // constructor PIXI.DisplayObject.prototype.constructor = PIXI.DisplayObject; @@ -256,8 +256,8 @@ PIXI.DisplayObject.prototype.constructor = PIXI.DisplayObject; */ PIXI.DisplayObject.prototype.setInteractive = function(interactive) { - this.interactive = interactive; -} + this.interactive = interactive; +}; /** * Indicates if the sprite will have touch and mouse interactivity. It is false by default @@ -271,11 +271,11 @@ Object.defineProperty(PIXI.DisplayObject.prototype, 'interactive', { return this._interactive; }, set: function(value) { - this._interactive = value; - - // TODO more to be done here.. - // need to sort out a re-crawl! - if(this.stage)this.stage.dirty = true; + this._interactive = value; + + // TODO more to be done here.. + // need to sort out a re-crawl! + if(this.stage)this.stage.dirty = true; } }); @@ -292,33 +292,33 @@ Object.defineProperty(PIXI.DisplayObject.prototype, 'mask', { return this._mask; }, set: function(value) { - - + + if(value) { - if(this._mask) - { - value.start = this._mask.start; - value.end = this._mask.end; - } - else - { - this.addFilter(value); - value.renderable = false; - } + if(this._mask) + { + value.start = this._mask.start; + value.end = this._mask.end; + } + else + { + this.addFilter(value); + value.renderable = false; + } } else { - this.removeFilter(this._mask); - this._mask.renderable = true; + this.removeFilter(this._mask); + this._mask.renderable = true; } - + this._mask = value; } }); /** - * Sets the filters for the displayObject. + * Sets the filters for the displayObject. * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer. * To remove filters simply set this property to 'null' * @property filters @@ -329,35 +329,33 @@ Object.defineProperty(PIXI.DisplayObject.prototype, 'filters', { return this._filters; }, set: function(value) { - + if(value) { - if(this._filters)this.removeFilter(this._filters); - this.addFilter(value); + if(this._filters)this.removeFilter(this._filters); + this.addFilter(value); - // now put all the passes in one place.. - var passes = []; - for (var i = 0; i < value.length; i++) - { - var filterPasses = value[i].passes; - for (var j = 0; j < filterPasses.length; j++) - { - passes.push(filterPasses[j]); - }; - }; + // now put all the passes in one place.. + var passes = []; + for (var i = 0; i < value.length; i++) + { + var filterPasses = value[i].passes; + for (var j = 0; j < filterPasses.length; j++) + { + passes.push(filterPasses[j]); + } + } - value.start.filterPasses = passes; + value.start.filterPasses = passes; } else { - if(this._filters)this.removeFilter(this._filters); + if(this._filters) { + this.removeFilter(this._filters); + } } - + this._filters = value; - - - - } }); @@ -370,101 +368,99 @@ Object.defineProperty(PIXI.DisplayObject.prototype, 'filters', { */ PIXI.DisplayObject.prototype.addFilter = function(data) { - //if(this.filter)return; - //this.filter = true; -// data[0].target = this; - + //if(this.filter)return; + //this.filter = true; +// data[0].target = this; - // insert a filter block.. - // TODO Onject pool thease bad boys.. - var start = new PIXI.FilterBlock(); - var end = new PIXI.FilterBlock(); - - data.start = start; - data.end = end; - - start.data = data; - end.data = data; - - start.first = start.last = this; - end.first = end.last = this; - - start.open = true; - - start.target = this; - - /* - * insert start - */ - - var childFirst = start - var childLast = start - var nextObject; - var previousObject; - - previousObject = this.first._iPrev; - - if(previousObject) - { - nextObject = previousObject._iNext; - childFirst._iPrev = previousObject; - previousObject._iNext = childFirst; - } - else - { - nextObject = this; - } - - if(nextObject) - { - nextObject._iPrev = childLast; - childLast._iNext = nextObject; - } - - - // now insert the end filter block.. - - /* - * insert end filter - */ - var childFirst = end - var childLast = end - var nextObject = null; - var previousObject = null; - - previousObject = this.last; - nextObject = previousObject._iNext; - - if(nextObject) - { - nextObject._iPrev = childLast; - childLast._iNext = nextObject; - } - - childFirst._iPrev = previousObject; - previousObject._iNext = childFirst; - - var updateLast = this; - - var prevLast = this.last; - while(updateLast) - { - if(updateLast.last == prevLast) - { - updateLast.last = end; - } - updateLast = updateLast.parent; - } - - this.first = start; - - // if webGL... - if(this.__renderGroup) - { - this.__renderGroup.addFilterBlocks(start, end); - } - -} + + // insert a filter block.. + // TODO Onject pool thease bad boys.. + var start = new PIXI.FilterBlock(); + var end = new PIXI.FilterBlock(); + + data.start = start; + data.end = end; + + start.data = data; + end.data = data; + + start.first = start.last = this; + end.first = end.last = this; + + start.open = true; + + start.target = this; + + /* + * insert start + */ + + var childFirst = start; + var childLast = start; + var nextObject; + var previousObject; + + previousObject = this.first._iPrev; + + if(previousObject) + { + nextObject = previousObject._iNext; + childFirst._iPrev = previousObject; + previousObject._iNext = childFirst; + } + else + { + nextObject = this; + } + + if(nextObject) + { + nextObject._iPrev = childLast; + childLast._iNext = nextObject; + } + + // now insert the end filter block.. + + /* + * insert end filter + */ + childFirst = end; + childLast = end; + nextObject = null; + previousObject = null; + + previousObject = this.last; + nextObject = previousObject._iNext; + + if(nextObject) + { + nextObject._iPrev = childLast; + childLast._iNext = nextObject; + } + + childFirst._iPrev = previousObject; + previousObject._iNext = childFirst; + + var updateLast = this; + + var prevLast = this.last; + while(updateLast) + { + if(updateLast.last === prevLast) + { + updateLast.last = end; + } + updateLast = updateLast.parent; + } + + this.first = start; + + // if webGL... + if(this.__renderGroup) + { + this.__renderGroup.addFilterBlocks(start, end); + } +}; /* * Removes the filter to this displayObject @@ -474,47 +470,47 @@ PIXI.DisplayObject.prototype.addFilter = function(data) */ PIXI.DisplayObject.prototype.removeFilter = function(data) { - //if(!this.filter)return; - //this.filter = false; - // console.log("YUOIO") - // modify the list.. - var startBlock = data.start; - - - var nextObject = startBlock._iNext; - var previousObject = startBlock._iPrev; - - if(nextObject)nextObject._iPrev = previousObject; - if(previousObject)previousObject._iNext = nextObject; - - this.first = startBlock._iNext; - - // remove the end filter - var lastBlock = data.end; - - var nextObject = lastBlock._iNext; - var previousObject = lastBlock._iPrev; - - if(nextObject)nextObject._iPrev = previousObject; - previousObject._iNext = nextObject; - - // this is always true too! - var tempLast = lastBlock._iPrev; - // need to make sure the parents last is updated too - var updateLast = this; - while(updateLast.last == lastBlock) - { - updateLast.last = tempLast; - updateLast = updateLast.parent; - if(!updateLast)break; - } - - // if webGL... - if(this.__renderGroup) - { - this.__renderGroup.removeFilterBlocks(startBlock, lastBlock); - } -} + //if(!this.filter)return; + //this.filter = false; + // console.log('YUOIO') + // modify the list.. + var startBlock = data.start; + + + var nextObject = startBlock._iNext; + var previousObject = startBlock._iPrev; + + if(nextObject)nextObject._iPrev = previousObject; + if(previousObject)previousObject._iNext = nextObject; + + this.first = startBlock._iNext; + + // remove the end filter + var lastBlock = data.end; + + nextObject = lastBlock._iNext; + previousObject = lastBlock._iPrev; + + if(nextObject)nextObject._iPrev = previousObject; + previousObject._iNext = nextObject; + + // this is always true too! + var tempLast = lastBlock._iPrev; + // need to make sure the parents last is updated too + var updateLast = this; + while(updateLast.last === lastBlock) + { + updateLast.last = tempLast; + updateLast = updateLast.parent; + if(!updateLast)break; + } + + // if webGL... + if(this.__renderGroup) + { + this.__renderGroup.removeFilterBlocks(startBlock, lastBlock); + } +}; /* * Updates the object transform for rendering @@ -524,28 +520,28 @@ PIXI.DisplayObject.prototype.removeFilter = function(data) */ PIXI.DisplayObject.prototype.updateTransform = function() { - // TODO OPTIMIZE THIS!! with dirty - if(this.rotation !== this.rotationCache) - { - this.rotationCache = this.rotation; - this._sr = Math.sin(this.rotation); - this._cr = Math.cos(this.rotation); - } - - var localTransform = this.localTransform; - var parentTransform = this.parent.worldTransform; - var worldTransform = this.worldTransform; - //console.log(localTransform) - localTransform[0] = this._cr * this.scale.x; - localTransform[1] = -this._sr * this.scale.y - localTransform[3] = this._sr * this.scale.x; - localTransform[4] = this._cr * this.scale.y; - - // TODO --> do we even need a local matrix??? - - var px = this.pivot.x; - var py = this.pivot.y; - + // TODO OPTIMIZE THIS!! with dirty + if(this.rotation !== this.rotationCache) + { + this.rotationCache = this.rotation; + this._sr = Math.sin(this.rotation); + this._cr = Math.cos(this.rotation); + } + + var localTransform = this.localTransform; + var parentTransform = this.parent.worldTransform; + var worldTransform = this.worldTransform; + //console.log(localTransform) + localTransform[0] = this._cr * this.scale.x; + localTransform[1] = -this._sr * this.scale.y; + localTransform[3] = this._sr * this.scale.x; + localTransform[4] = this._cr * this.scale.y; + + // TODO --> do we even need a local matrix??? + + var px = this.pivot.x; + var py = this.pivot.y; + // Cache the matrix values (makes for huge speed increases!) var a00 = localTransform[0], a01 = localTransform[1], a02 = this.position.x - localTransform[0] * px - py * localTransform[1], a10 = localTransform[3], a11 = localTransform[4], a12 = this.position.y - localTransform[4] * py - px * localTransform[3], @@ -553,9 +549,9 @@ PIXI.DisplayObject.prototype.updateTransform = function() b00 = parentTransform[0], b01 = parentTransform[1], b02 = parentTransform[2], b10 = parentTransform[3], b11 = parentTransform[4], b12 = parentTransform[5]; - localTransform[2] = a02 - localTransform[5] = a12 - + localTransform[2] = a02; + localTransform[5] = a12; + worldTransform[0] = b00 * a00 + b01 * a10; worldTransform[1] = b00 * a01 + b01 * a11; worldTransform[2] = b00 * a02 + b01 * a12 + b02; @@ -564,12 +560,11 @@ PIXI.DisplayObject.prototype.updateTransform = function() worldTransform[4] = b10 * a01 + b11 * a11; worldTransform[5] = b10 * a02 + b11 * a12 + b12; - // because we are using affine transformation, we can optimise the matrix concatenation process.. wooo! - // mat3.multiply(this.localTransform, this.parent.worldTransform, this.worldTransform); - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this.vcount = PIXI.visibleCount; + // because we are using affine transformation, we can optimise the matrix concatenation process.. wooo! + // mat3.multiply(this.localTransform, this.parent.worldTransform, this.worldTransform); + this.worldAlpha = this.alpha * this.parent.worldAlpha; -} + this.vcount = PIXI.visibleCount; +}; PIXI.visibleCount = 0; \ No newline at end of file diff --git a/src/pixi/display/DisplayObjectContainer.js b/src/pixi/display/DisplayObjectContainer.js index eb95624a8..44cbaa19f 100644 --- a/src/pixi/display/DisplayObjectContainer.js +++ b/src/pixi/display/DisplayObjectContainer.js @@ -7,23 +7,23 @@ * A DisplayObjectContainer represents a collection of display objects. * It is the base class of all display objects that act as a container for other objects. * - * @class DisplayObjectContainer + * @class DisplayObjectContainer * @extends DisplayObject * @constructor */ PIXI.DisplayObjectContainer = function() { - PIXI.DisplayObject.call( this ); - - /** - * [read-only] The of children of this container. - * - * @property children - * @type Array - * @readOnly - */ - this.children = []; -} + PIXI.DisplayObject.call( this ); + + /** + * [read-only] The of children of this container. + * + * @property children + * @type Array + * @readOnly + */ + this.children = []; +}; // constructor PIXI.DisplayObjectContainer.prototype = Object.create( PIXI.DisplayObject.prototype ); @@ -37,85 +37,83 @@ PIXI.DisplayObjectContainer.prototype.constructor = PIXI.DisplayObjectContainer; */ PIXI.DisplayObjectContainer.prototype.addChild = function(child) { - if(child.parent != undefined) - { - - //// COULD BE THIS??? - child.parent.removeChild(child); - // return; - } + if(child.parent && child.parent !== this) + { + //// COULD BE THIS??? + child.parent.removeChild(child); + // return; + } - child.parent = this; - - this.children.push(child); - - // update the stage refference.. - - if(this.stage) - { - var tmpChild = child; - do - { - if(tmpChild.interactive)this.stage.dirty = true; - tmpChild.stage = this.stage; - tmpChild = tmpChild._iNext; - } - while(tmpChild) - } - - // LINKED LIST // - - // modify the list.. - var childFirst = child.first - var childLast = child.last; - var nextObject; - var previousObject; - - // this could be wrong if there is a filter?? - if(this._filters || this._mask) - { - previousObject = this.last._iPrev; - } - else - { - previousObject = this.last; - } + child.parent = this; - nextObject = previousObject._iNext; - - // always true in this case - // need to make sure the parents last is updated too - var updateLast = this; - var prevLast = previousObject; - - while(updateLast) - { - if(updateLast.last == prevLast) - { - updateLast.last = child.last; - } - updateLast = updateLast.parent; - } - - if(nextObject) - { - nextObject._iPrev = childLast; - childLast._iNext = nextObject; - } - - childFirst._iPrev = previousObject; - previousObject._iNext = childFirst; + this.children.push(child); - // need to remove any render groups.. - if(this.__renderGroup) - { - // being used by a renderTexture.. if it exists then it must be from a render texture; - if(child.__renderGroup)child.__renderGroup.removeDisplayObjectAndChildren(child); - // add them to the new render group.. - this.__renderGroup.addDisplayObjectAndChildren(child); - } - -} + // update the stage refference.. + + if(this.stage) + { + var tmpChild = child; + do + { + if(tmpChild.interactive)this.stage.dirty = true; + tmpChild.stage = this.stage; + tmpChild = tmpChild._iNext; + } + while(tmpChild); + } + + // LINKED LIST // + + // modify the list.. + var childFirst = child.first; + var childLast = child.last; + var nextObject; + var previousObject; + + // this could be wrong if there is a filter?? + if(this._filters || this._mask) + { + previousObject = this.last._iPrev; + } + else + { + previousObject = this.last; + } + + nextObject = previousObject._iNext; + + // always true in this case + // need to make sure the parents last is updated too + var updateLast = this; + var prevLast = previousObject; + + while(updateLast) + { + if(updateLast.last === prevLast) + { + updateLast.last = child.last; + } + updateLast = updateLast.parent; + } + + if(nextObject) + { + nextObject._iPrev = childLast; + childLast._iNext = nextObject; + } + + childFirst._iPrev = previousObject; + previousObject._iNext = childFirst; + + // need to remove any render groups.. + if(this.__renderGroup) + { + // being used by a renderTexture.. if it exists then it must be from a render texture; + if(child.__renderGroup)child.__renderGroup.removeDisplayObjectAndChildren(child); + // add them to the new render group.. + this.__renderGroup.addDisplayObjectAndChildren(child); + } +}; /** * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown @@ -126,83 +124,84 @@ PIXI.DisplayObjectContainer.prototype.addChild = function(child) */ PIXI.DisplayObjectContainer.prototype.addChildAt = function(child, index) { - if(index >= 0 && index <= this.children.length) - { - if(child.parent != undefined) - { - child.parent.removeChild(child); - } - child.parent = this; - - if(this.stage) - { - var tmpChild = child; - do - { - if(tmpChild.interactive)this.stage.dirty = true; - tmpChild.stage = this.stage; - tmpChild = tmpChild._iNext; - } - while(tmpChild) - } - - // modify the list.. - var childFirst = child.first; - var childLast = child.last; - var nextObject; - var previousObject; - - if(index == this.children.length) - { - previousObject = this.last; - var updateLast = this; - var prevLast = this.last; - while(updateLast) - { - if(updateLast.last == prevLast) - { - updateLast.last = child.last; - } - updateLast = updateLast.parent; - } - } - else if(index == 0) - { - previousObject = this; - } - else - { - previousObject = this.children[index-1].last; - } - - nextObject = previousObject._iNext; - - // always true in this case - if(nextObject) - { - nextObject._iPrev = childLast; - childLast._iNext = nextObject; - } - - childFirst._iPrev = previousObject; - previousObject._iNext = childFirst; + if(index >= 0 && index <= this.children.length) + { + if(child.parent !== undefined) + { + child.parent.removeChild(child); + } - this.children.splice(index, 0, child); - // need to remove any render groups.. - if(this.__renderGroup) - { - // being used by a renderTexture.. if it exists then it must be from a render texture; - if(child.__renderGroup)child.__renderGroup.removeDisplayObjectAndChildren(child); - // add them to the new render group.. - this.__renderGroup.addDisplayObjectAndChildren(child); - } - - } - else - { - throw new Error(child + " The index "+ index +" supplied is out of bounds " + this.children.length); - } -} + child.parent = this; + + if(this.stage) + { + var tmpChild = child; + do + { + if(tmpChild.interactive)this.stage.dirty = true; + tmpChild.stage = this.stage; + tmpChild = tmpChild._iNext; + } + while(tmpChild); + } + + // modify the list.. + var childFirst = child.first; + var childLast = child.last; + var nextObject; + var previousObject; + + if(index === this.children.length) + { + previousObject = this.last; + var updateLast = this; + var prevLast = this.last; + while(updateLast) + { + if(updateLast.last === prevLast) + { + updateLast.last = child.last; + } + updateLast = updateLast.parent; + } + } + else if(index === 0) + { + previousObject = this; + } + else + { + previousObject = this.children[index-1].last; + } + + nextObject = previousObject._iNext; + + // always true in this case + if(nextObject) + { + nextObject._iPrev = childLast; + childLast._iNext = nextObject; + } + + childFirst._iPrev = previousObject; + previousObject._iNext = childFirst; + + this.children.splice(index, 0, child); + // need to remove any render groups.. + if(this.__renderGroup) + { + // being used by a renderTexture.. if it exists then it must be from a render texture; + if(child.__renderGroup)child.__renderGroup.removeDisplayObjectAndChildren(child); + // add them to the new render group.. + this.__renderGroup.addDisplayObjectAndChildren(child); + } + + } + else + { + throw new Error(child + ' The index '+ index +' supplied is out of bounds ' + this.children.length); + } +}; /** * [NYI] Swaps the depth of 2 displayObjects @@ -214,31 +213,31 @@ PIXI.DisplayObjectContainer.prototype.addChildAt = function(child, index) */ PIXI.DisplayObjectContainer.prototype.swapChildren = function(child, child2) { - if(child === child2) { - return; - } + if(child === child2) { + return; + } - var index1 = this.children.indexOf(child); - var index2 = this.children.indexOf(child2); - - if(index1 < 0 || index2 < 0) { - throw new Error("swapChildren: Both the supplied DisplayObjects must be a child of the caller."); - } + var index1 = this.children.indexOf(child); + var index2 = this.children.indexOf(child2); - this.removeChild(child); - this.removeChild(child2); - - if(index1 < index2) - { - this.addChildAt(child2, index1); - this.addChildAt(child, index2); - } - else - { - this.addChildAt(child, index2); - this.addChildAt(child2, index1); - } -} + if(index1 < 0 || index2 < 0) { + throw new Error('swapChildren: Both the supplied DisplayObjects must be a child of the caller.'); + } + + this.removeChild(child); + this.removeChild(child2); + + if(index1 < index2) + { + this.addChildAt(child2, index1); + this.addChildAt(child, index2); + } + else + { + this.addChildAt(child, index2); + this.addChildAt(child2, index1); + } +}; /** * Returns the Child at the specified index @@ -248,15 +247,15 @@ PIXI.DisplayObjectContainer.prototype.swapChildren = function(child, child2) */ PIXI.DisplayObjectContainer.prototype.getChildAt = function(index) { - if(index >= 0 && index < this.children.length) - { - return this.children[index]; - } - else - { - throw new Error(child + " Both the supplied DisplayObjects must be a child of the caller " + this); - } -} + if(index >= 0 && index < this.children.length) + { + return this.children[index]; + } + else + { + throw new Error('Both the supplied DisplayObjects must be a child of the caller ' + this); + } +}; /** * Removes a child from the container. @@ -266,66 +265,65 @@ PIXI.DisplayObjectContainer.prototype.getChildAt = function(index) */ PIXI.DisplayObjectContainer.prototype.removeChild = function(child) { - var index = this.children.indexOf( child ); - if ( index !== -1 ) - { - // unlink // - // modify the list.. - var childFirst = child.first; - var childLast = child.last; - - var nextObject = childLast._iNext; - var previousObject = childFirst._iPrev; - - if(nextObject)nextObject._iPrev = previousObject; - previousObject._iNext = nextObject; - - if(this.last == childLast) - { + var index = this.children.indexOf( child ); + if ( index !== -1 ) + { + // unlink // + // modify the list.. + var childFirst = child.first; + var childLast = child.last; - var tempLast = childFirst._iPrev; - // need to make sure the parents last is updated too - var updateLast = this; - - while(updateLast.last == childLast) - { - updateLast.last = tempLast; - updateLast = updateLast.parent; - if(!updateLast)break; - - } - } - - childLast._iNext = null; - childFirst._iPrev = null; - - // update the stage reference.. - if(this.stage) - { - var tmpChild = child; - do - { - if(tmpChild.interactive)this.stage.dirty = true; - tmpChild.stage = null; - tmpChild = tmpChild._iNext; - } - while(tmpChild) - } - - // webGL trim - if(child.__renderGroup) - { - child.__renderGroup.removeDisplayObjectAndChildren(child); - } - - child.parent = undefined; - this.children.splice( index, 1 ); - } - else - { - throw new Error(child + " The supplied DisplayObject must be a child of the caller " + this); - } -} + var nextObject = childLast._iNext; + var previousObject = childFirst._iPrev; + + if(nextObject)nextObject._iPrev = previousObject; + previousObject._iNext = nextObject; + + if(this.last === childLast) + { + var tempLast = childFirst._iPrev; + // need to make sure the parents last is updated too + var updateLast = this; + + while(updateLast.last === childLast) + { + updateLast.last = tempLast; + updateLast = updateLast.parent; + if(!updateLast)break; + + } + } + + childLast._iNext = null; + childFirst._iPrev = null; + + // update the stage reference.. + if(this.stage) + { + var tmpChild = child; + do + { + if(tmpChild.interactive)this.stage.dirty = true; + tmpChild.stage = null; + tmpChild = tmpChild._iNext; + } + while(tmpChild); + } + + // webGL trim + if(child.__renderGroup) + { + child.__renderGroup.removeDisplayObjectAndChildren(child); + } + + child.parent = undefined; + this.children.splice( index, 1 ); + } + else + { + throw new Error(child + ' The supplied DisplayObject must be a child of the caller ' + this); + } +}; /* * Updates the container's children's transform for rendering @@ -335,12 +333,12 @@ PIXI.DisplayObjectContainer.prototype.removeChild = function(child) */ PIXI.DisplayObjectContainer.prototype.updateTransform = function() { - if(!this.visible)return; - - PIXI.DisplayObject.prototype.updateTransform.call( this ); - - for(var i=0,j=this.children.length; i= this.textures.length) - { - this.gotoAndStop(this.textures.length - 1); - if(this.onComplete) - { - this.onComplete(); - } - } -} \ No newline at end of file + if(this.loop || round < this.textures.length) + { + this.setTexture(this.textures[round % this.textures.length]); + } + else if(round >= this.textures.length) + { + this.gotoAndStop(this.textures.length - 1); + if(this.onComplete) + { + this.onComplete(); + } + } +}; diff --git a/src/pixi/display/Sprite.js b/src/pixi/display/Sprite.js index e567c7956..35f1db0a3 100644 --- a/src/pixi/display/Sprite.js +++ b/src/pixi/display/Sprite.js @@ -18,66 +18,66 @@ PIXI.blendModes.SCREEN = 1; */ PIXI.Sprite = function(texture) { - PIXI.DisplayObjectContainer.call( this ); + PIXI.DisplayObjectContainer.call( this ); - /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the textures origin is the top left - * Setting than anchor to 0.5,0.5 means the textures origin is centered - * Setting the anchor to 1,1 would mean the textures origin points will be the bottom right - * + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the textures origin is the top left + * Setting than anchor to 0.5,0.5 means the textures origin is centered + * Setting the anchor to 1,1 would mean the textures origin points will be the bottom right + * * @property anchor * @type Point */ - this.anchor = new PIXI.Point(); + this.anchor = new PIXI.Point(); - /** - * The texture that the sprite is using - * - * @property texture - * @type Texture - */ - this.texture = texture; + /** + * The texture that the sprite is using + * + * @property texture + * @type Texture + */ + this.texture = texture; - /** - * The blend mode of sprite. - * currently supports PIXI.blendModes.NORMAL and PIXI.blendModes.SCREEN - * - * @property blendMode - * @type Number - */ - this.blendMode = PIXI.blendModes.NORMAL; + /** + * The blend mode of sprite. + * currently supports PIXI.blendModes.NORMAL and PIXI.blendModes.SCREEN + * + * @property blendMode + * @type Number + */ + this.blendMode = PIXI.blendModes.NORMAL; - /** - * The width of the sprite (this is initially set by the texture) - * - * @property _width - * @type Number - * @private - */ - this._width = 0; + /** + * The width of the sprite (this is initially set by the texture) + * + * @property _width + * @type Number + * @private + */ + this._width = 0; - /** - * The height of the sprite (this is initially set by the texture) - * - * @property _height - * @type Number - * @private - */ - this._height = 0; + /** + * The height of the sprite (this is initially set by the texture) + * + * @property _height + * @type Number + * @private + */ + this._height = 0; - if(texture.baseTexture.hasLoaded) - { - this.updateFrame = true; - } - else - { - this.onTextureUpdateBind = this.onTextureUpdate.bind(this); - this.texture.addEventListener( 'update', this.onTextureUpdateBind ); - } + if(texture.baseTexture.hasLoaded) + { + this.updateFrame = true; + } + else + { + this.onTextureUpdateBind = this.onTextureUpdate.bind(this); + this.texture.addEventListener( 'update', this.onTextureUpdateBind ); + } - this.renderable = true; -} + this.renderable = true; +}; // constructor PIXI.Sprite.prototype = Object.create( PIXI.DisplayObjectContainer.prototype ); @@ -94,7 +94,7 @@ Object.defineProperty(PIXI.Sprite.prototype, 'width', { return this.scale.x * this.texture.frame.width; }, set: function(value) { - this.scale.x = value / this.texture.frame.width + this.scale.x = value / this.texture.frame.width; this._width = value; } }); @@ -110,7 +110,7 @@ Object.defineProperty(PIXI.Sprite.prototype, 'height', { return this.scale.y * this.texture.frame.height; }, set: function(value) { - this.scale.y = value / this.texture.frame.height + this.scale.y = value / this.texture.frame.height; this._height = value; } }); @@ -123,24 +123,24 @@ Object.defineProperty(PIXI.Sprite.prototype, 'height', { */ PIXI.Sprite.prototype.setTexture = function(texture) { - // stop current texture; - if(this.texture.baseTexture != texture.baseTexture) - { - this.textureChange = true; - this.texture = texture; - - if(this.__renderGroup) - { - this.__renderGroup.updateTexture(this); - } - } - else - { - this.texture = texture; - } - - this.updateFrame = true; -} + // stop current texture; + if(this.texture.baseTexture !== texture.baseTexture) + { + this.textureChange = true; + this.texture = texture; + + if(this.__renderGroup) + { + this.__renderGroup.updateTexture(this); + } + } + else + { + this.texture = texture; + } + + this.updateFrame = true; +}; /** * When the texture is updated, this event will fire to update the scale and frame @@ -149,21 +149,21 @@ PIXI.Sprite.prototype.setTexture = function(texture) * @param event * @private */ -PIXI.Sprite.prototype.onTextureUpdate = function(event) +PIXI.Sprite.prototype.onTextureUpdate = function() { - //this.texture.removeEventListener( 'update', this.onTextureUpdateBind ); - - // so if _width is 0 then width was not set.. - if(this._width)this.scale.x = this._width / this.texture.frame.width; - if(this._height)this.scale.y = this._height / this.texture.frame.height; - - this.updateFrame = true; -} + //this.texture.removeEventListener( 'update', this.onTextureUpdateBind ); + + // so if _width is 0 then width was not set.. + if(this._width)this.scale.x = this._width / this.texture.frame.width; + if(this._height)this.scale.y = this._height / this.texture.frame.height; + + this.updateFrame = true; +}; // some helper functions.. /** - * + * * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId * The frame ids are created when a Texture packer file has been loaded * @@ -174,13 +174,13 @@ PIXI.Sprite.prototype.onTextureUpdate = function(event) */ PIXI.Sprite.fromFrame = function(frameId) { - var texture = PIXI.TextureCache[frameId]; - if(!texture)throw new Error("The frameId '"+ frameId +"' does not exist in the texture cache" + this); - return new PIXI.Sprite(texture); -} + var texture = PIXI.TextureCache[frameId]; + if(!texture) throw new Error('The frameId "' + frameId + '" does not exist in the texture cache' + this); + return new PIXI.Sprite(texture); +}; /** - * + * * Helper function that creates a sprite that will contain a texture based on an image url * If the image is not in the texture cache it will be loaded * @@ -191,6 +191,6 @@ PIXI.Sprite.fromFrame = function(frameId) */ PIXI.Sprite.fromImage = function(imageId) { - var texture = PIXI.Texture.fromImage(imageId); - return new PIXI.Sprite(texture); -} + var texture = PIXI.Texture.fromImage(imageId); + return new PIXI.Sprite(texture); +}; diff --git a/src/pixi/display/Stage.js b/src/pixi/display/Stage.js index 513c0ad06..1aa16202f 100644 --- a/src/pixi/display/Stage.js +++ b/src/pixi/display/Stage.js @@ -9,59 +9,59 @@ * @extends DisplayObjectContainer * @constructor * @param backgroundColor {Number} the background color of the stage, easiest way to pass this in is in hex format - * like: 0xFFFFFF for white + * like: 0xFFFFFF for white */ PIXI.Stage = function(backgroundColor) { - PIXI.DisplayObjectContainer.call( this ); + PIXI.DisplayObjectContainer.call( this ); - /** - * [read-only] Current transform of the object based on world (parent) factors - * - * @property worldTransform - * @type Mat3 - * @readOnly - * @private - */ - this.worldTransform = PIXI.mat3.create(); + /** + * [read-only] Current transform of the object based on world (parent) factors + * + * @property worldTransform + * @type Mat3 + * @readOnly + * @private + */ + this.worldTransform = PIXI.mat3.create(); - /** - * Whether or not the stage is interactive - * - * @property interactive - * @type Boolean - */ - this.interactive = true; + /** + * Whether or not the stage is interactive + * + * @property interactive + * @type Boolean + */ + this.interactive = true; - /** - * The interaction manage for this stage, manages all interactive activity on the stage - * - * @property interactive - * @type InteractionManager - */ - this.interactionManager = new PIXI.InteractionManager(this); + /** + * The interaction manage for this stage, manages all interactive activity on the stage + * + * @property interactive + * @type InteractionManager + */ + this.interactionManager = new PIXI.InteractionManager(this); - /** - * Whether the stage is dirty and needs to have interactions updated - * - * @property dirty - * @type Boolean - * @private - */ - this.dirty = true; + /** + * Whether the stage is dirty and needs to have interactions updated + * + * @property dirty + * @type Boolean + * @private + */ + this.dirty = true; - this.__childrenAdded = []; - this.__childrenRemoved = []; + this.__childrenAdded = []; + this.__childrenRemoved = []; - //the stage is it's own stage - this.stage = this; + //the stage is it's own stage + this.stage = this; - //optimize hit detection a bit - this.stage.hitArea = new PIXI.Rectangle(0,0,100000, 100000); + //optimize hit detection a bit + this.stage.hitArea = new PIXI.Rectangle(0,0,100000, 100000); - this.setBackgroundColor(backgroundColor); - this.worldVisible = true; -} + this.setBackgroundColor(backgroundColor); + this.worldVisible = true; +}; // constructor PIXI.Stage.prototype = Object.create( PIXI.DisplayObjectContainer.prototype ); @@ -76,8 +76,8 @@ PIXI.Stage.prototype.constructor = PIXI.Stage; */ PIXI.Stage.prototype.setInteractionDelegate = function(domElement) { - this.interactionManager.setTargetDomElement( domElement ); -} + this.interactionManager.setTargetDomElement( domElement ); +}; /* * Updates the object transform for rendering @@ -87,40 +87,40 @@ PIXI.Stage.prototype.setInteractionDelegate = function(domElement) */ PIXI.Stage.prototype.updateTransform = function() { - this.worldAlpha = 1; - this.vcount = PIXI.visibleCount; - - for(var i=0,j=this.children.length; i 1) ratio = 1; - var ratio = (1 - (i / (total-1))) * 10; - if(ratio > 1)ratio = 1; + perpLength = Math.sqrt(perp.x * perp.x + perp.y * perp.y); + num = this.texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; + perp.x /= perpLength; + perp.y /= perpLength; - var perpLength = Math.sqrt(perp.x * perp.x + perp.y * perp.y); - var num = this.texture.height/2//(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; - perp.x /= perpLength; - perp.y /= perpLength; + perp.x *= num; + perp.y *= num; - perp.x *= num; - perp.y *= num; + verticies[index] = point.x + perp.x; + verticies[index+1] = point.y + perp.y; + verticies[index+2] = point.x - perp.x; + verticies[index+3] = point.y - perp.y; - verticies[index] = point.x + perp.x - verticies[index+1] = point.y + perp.y - verticies[index+2] = point.x - perp.x - verticies[index+3] = point.y - perp.y + lastPoint = point; + } - lastPoint = point; - } - - PIXI.DisplayObjectContainer.prototype.updateTransform.call( this ); -} + PIXI.DisplayObjectContainer.prototype.updateTransform.call( this ); +}; PIXI.Rope.prototype.setTexture = function(texture) { - // stop current texture - this.texture = texture; - this.updateFrame = true; -} - - - - + // stop current texture + this.texture = texture; + this.updateFrame = true; +}; diff --git a/src/pixi/extras/Spine.js b/src/pixi/extras/Spine.js index 27995ae68..50db87d5b 100644 --- a/src/pixi/extras/Spine.js +++ b/src/pixi/extras/Spine.js @@ -7,6 +7,1350 @@ * */ +/* + * Awesome JS run time provided by EsotericSoftware + * + * https://github.com/EsotericSoftware/spine-runtimes + * + */ + +var spine = {}; + +spine.BoneData = function (name, parent) { + this.name = name; + this.parent = parent; +}; +spine.BoneData.prototype = { + length: 0, + x: 0, y: 0, + rotation: 0, + scaleX: 1, scaleY: 1 +}; + +spine.SlotData = function (name, boneData) { + this.name = name; + this.boneData = boneData; +}; +spine.SlotData.prototype = { + r: 1, g: 1, b: 1, a: 1, + attachmentName: null +}; + +spine.Bone = function (boneData, parent) { + this.data = boneData; + this.parent = parent; + this.setToSetupPose(); +}; +spine.Bone.yDown = false; +spine.Bone.prototype = { + x: 0, y: 0, + rotation: 0, + scaleX: 1, scaleY: 1, + m00: 0, m01: 0, worldX: 0, // a b x + m10: 0, m11: 0, worldY: 0, // c d y + worldRotation: 0, + worldScaleX: 1, worldScaleY: 1, + updateWorldTransform: function (flipX, flipY) { + var parent = this.parent; + if (parent != null) { + this.worldX = this.x * parent.m00 + this.y * parent.m01 + parent.worldX; + this.worldY = this.x * parent.m10 + this.y * parent.m11 + parent.worldY; + this.worldScaleX = parent.worldScaleX * this.scaleX; + this.worldScaleY = parent.worldScaleY * this.scaleY; + this.worldRotation = parent.worldRotation + this.rotation; + } else { + this.worldX = this.x; + this.worldY = this.y; + this.worldScaleX = this.scaleX; + this.worldScaleY = this.scaleY; + this.worldRotation = this.rotation; + } + var radians = this.worldRotation * Math.PI / 180; + var cos = Math.cos(radians); + var sin = Math.sin(radians); + this.m00 = cos * this.worldScaleX; + this.m10 = sin * this.worldScaleX; + this.m01 = -sin * this.worldScaleY; + this.m11 = cos * this.worldScaleY; + if (flipX) { + this.m00 = -this.m00; + this.m01 = -this.m01; + } + if (flipY) { + this.m10 = -this.m10; + this.m11 = -this.m11; + } + if (spine.Bone.yDown) { + this.m10 = -this.m10; + this.m11 = -this.m11; + } + }, + setToSetupPose: function () { + var data = this.data; + this.x = data.x; + this.y = data.y; + this.rotation = data.rotation; + this.scaleX = data.scaleX; + this.scaleY = data.scaleY; + } +}; + +spine.Slot = function (slotData, skeleton, bone) { + this.data = slotData; + this.skeleton = skeleton; + this.bone = bone; + this.setToSetupPose(); +}; +spine.Slot.prototype = { + r: 1, g: 1, b: 1, a: 1, + _attachmentTime: 0, + attachment: null, + setAttachment: function (attachment) { + this.attachment = attachment; + this._attachmentTime = this.skeleton.time; + }, + setAttachmentTime: function (time) { + this._attachmentTime = this.skeleton.time - time; + }, + getAttachmentTime: function () { + return this.skeleton.time - this._attachmentTime; + }, + setToSetupPose: function () { + var data = this.data; + this.r = data.r; + this.g = data.g; + this.b = data.b; + this.a = data.a; + + var slotDatas = this.skeleton.data.slots; + for (var i = 0, n = slotDatas.length; i < n; i++) { + if (slotDatas[i] == data) { + this.setAttachment(!data.attachmentName ? null : this.skeleton.getAttachmentBySlotIndex(i, data.attachmentName)); + break; + } + } + } +}; + +spine.Skin = function (name) { + this.name = name; + this.attachments = {}; +}; +spine.Skin.prototype = { + addAttachment: function (slotIndex, name, attachment) { + this.attachments[slotIndex + ":" + name] = attachment; + }, + getAttachment: function (slotIndex, name) { + return this.attachments[slotIndex + ":" + name]; + }, + _attachAll: function (skeleton, oldSkin) { + for (var key in oldSkin.attachments) { + var colon = key.indexOf(":"); + var slotIndex = parseInt(key.substring(0, colon), 10); + var name = key.substring(colon + 1); + var slot = skeleton.slots[slotIndex]; + if (slot.attachment && slot.attachment.name == name) { + var attachment = this.getAttachment(slotIndex, name); + if (attachment) slot.setAttachment(attachment); + } + } + } +}; + +spine.Animation = function (name, timelines, duration) { + this.name = name; + this.timelines = timelines; + this.duration = duration; +}; +spine.Animation.prototype = { + apply: function (skeleton, time, loop) { + if (loop && this.duration) time %= this.duration; + var timelines = this.timelines; + for (var i = 0, n = timelines.length; i < n; i++) + timelines[i].apply(skeleton, time, 1); + }, + mix: function (skeleton, time, loop, alpha) { + if (loop && this.duration) time %= this.duration; + var timelines = this.timelines; + for (var i = 0, n = timelines.length; i < n; i++) + timelines[i].apply(skeleton, time, alpha); + } +}; + +spine.binarySearch = function (values, target, step) { + var low = 0; + var high = Math.floor(values.length / step) - 2; + if (!high) return step; + var current = high >>> 1; + while (true) { + if (values[(current + 1) * step] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1) * step; + current = (low + high) >>> 1; + } +}; +spine.linearSearch = function (values, target, step) { + for (var i = 0, last = values.length - step; i <= last; i += step) + if (values[i] > target) return i; + return -1; +}; + +spine.Curves = function (frameCount) { + this.curves = []; // dfx, dfy, ddfx, ddfy, dddfx, dddfy, ... + this.curves.length = (frameCount - 1) * 6; +}; +spine.Curves.prototype = { + setLinear: function (frameIndex) { + this.curves[frameIndex * 6] = 0/*LINEAR*/; + }, + setStepped: function (frameIndex) { + this.curves[frameIndex * 6] = -1/*STEPPED*/; + }, + /** Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. + * cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of + * the difference between the keyframe's values. */ + setCurve: function (frameIndex, cx1, cy1, cx2, cy2) { + var subdiv_step = 1 / 10/*BEZIER_SEGMENTS*/; + var subdiv_step2 = subdiv_step * subdiv_step; + var subdiv_step3 = subdiv_step2 * subdiv_step; + var pre1 = 3 * subdiv_step; + var pre2 = 3 * subdiv_step2; + var pre4 = 6 * subdiv_step2; + var pre5 = 6 * subdiv_step3; + var tmp1x = -cx1 * 2 + cx2; + var tmp1y = -cy1 * 2 + cy2; + var tmp2x = (cx1 - cx2) * 3 + 1; + var tmp2y = (cy1 - cy2) * 3 + 1; + var i = frameIndex * 6; + var curves = this.curves; + curves[i] = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3; + curves[i + 1] = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3; + curves[i + 2] = tmp1x * pre4 + tmp2x * pre5; + curves[i + 3] = tmp1y * pre4 + tmp2y * pre5; + curves[i + 4] = tmp2x * pre5; + curves[i + 5] = tmp2y * pre5; + }, + getCurvePercent: function (frameIndex, percent) { + percent = percent < 0 ? 0 : (percent > 1 ? 1 : percent); + var curveIndex = frameIndex * 6; + var curves = this.curves; + var dfx = curves[curveIndex]; + if (!dfx/*LINEAR*/) return percent; + if (dfx == -1/*STEPPED*/) return 0; + var dfy = curves[curveIndex + 1]; + var ddfx = curves[curveIndex + 2]; + var ddfy = curves[curveIndex + 3]; + var dddfx = curves[curveIndex + 4]; + var dddfy = curves[curveIndex + 5]; + var x = dfx, y = dfy; + var i = 10/*BEZIER_SEGMENTS*/ - 2; + while (true) { + if (x >= percent) { + var lastX = x - dfx; + var lastY = y - dfy; + return lastY + (y - lastY) * (percent - lastX) / (x - lastX); + } + if (!i) break; + i--; + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + x += dfx; + y += dfy; + } + return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. + } +}; + +spine.RotateTimeline = function (frameCount) { + this.curves = new spine.Curves(frameCount); + this.frames = []; // time, angle, ... + this.frames.length = frameCount * 2; +}; +spine.RotateTimeline.prototype = { + boneIndex: 0, + getFrameCount: function () { + return this.frames.length / 2; + }, + setFrame: function (frameIndex, time, angle) { + frameIndex *= 2; + this.frames[frameIndex] = time; + this.frames[frameIndex + 1] = angle; + }, + apply: function (skeleton, time, alpha) { + var frames = this.frames, + amount; + + if (time < frames[0]) return; // Time is before first frame. + + var bone = skeleton.bones[this.boneIndex]; + + if (time >= frames[frames.length - 2]) { // Time is after last frame. + amount = bone.data.rotation + frames[frames.length - 1] - bone.rotation; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + bone.rotation += amount * alpha; + return; + } + + // Interpolate between the last frame and the current frame. + var frameIndex = spine.binarySearch(frames, time, 2); + var lastFrameValue = frames[frameIndex - 1]; + var frameTime = frames[frameIndex]; + var percent = 1 - (time - frameTime) / (frames[frameIndex - 2/*LAST_FRAME_TIME*/] - frameTime); + percent = this.curves.getCurvePercent(frameIndex / 2 - 1, percent); + + amount = frames[frameIndex + 1/*FRAME_VALUE*/] - lastFrameValue; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + amount = bone.data.rotation + (lastFrameValue + amount * percent) - bone.rotation; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + bone.rotation += amount * alpha; + } +}; + +spine.TranslateTimeline = function (frameCount) { + this.curves = new spine.Curves(frameCount); + this.frames = []; // time, x, y, ... + this.frames.length = frameCount * 3; +}; +spine.TranslateTimeline.prototype = { + boneIndex: 0, + getFrameCount: function () { + return this.frames.length / 3; + }, + setFrame: function (frameIndex, time, x, y) { + frameIndex *= 3; + this.frames[frameIndex] = time; + this.frames[frameIndex + 1] = x; + this.frames[frameIndex + 2] = y; + }, + apply: function (skeleton, time, alpha) { + var frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + var bone = skeleton.bones[this.boneIndex]; + + if (time >= frames[frames.length - 3]) { // Time is after last frame. + bone.x += (bone.data.x + frames[frames.length - 2] - bone.x) * alpha; + bone.y += (bone.data.y + frames[frames.length - 1] - bone.y) * alpha; + return; + } + + // Interpolate between the last frame and the current frame. + var frameIndex = spine.binarySearch(frames, time, 3); + var lastFrameX = frames[frameIndex - 2]; + var lastFrameY = frames[frameIndex - 1]; + var frameTime = frames[frameIndex]; + var percent = 1 - (time - frameTime) / (frames[frameIndex + -3/*LAST_FRAME_TIME*/] - frameTime); + percent = this.curves.getCurvePercent(frameIndex / 3 - 1, percent); + + bone.x += (bone.data.x + lastFrameX + (frames[frameIndex + 1/*FRAME_X*/] - lastFrameX) * percent - bone.x) * alpha; + bone.y += (bone.data.y + lastFrameY + (frames[frameIndex + 2/*FRAME_Y*/] - lastFrameY) * percent - bone.y) * alpha; + } +}; + +spine.ScaleTimeline = function (frameCount) { + this.curves = new spine.Curves(frameCount); + this.frames = []; // time, x, y, ... + this.frames.length = frameCount * 3; +}; +spine.ScaleTimeline.prototype = { + boneIndex: 0, + getFrameCount: function () { + return this.frames.length / 3; + }, + setFrame: function (frameIndex, time, x, y) { + frameIndex *= 3; + this.frames[frameIndex] = time; + this.frames[frameIndex + 1] = x; + this.frames[frameIndex + 2] = y; + }, + apply: function (skeleton, time, alpha) { + var frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + var bone = skeleton.bones[this.boneIndex]; + + if (time >= frames[frames.length - 3]) { // Time is after last frame. + bone.scaleX += (bone.data.scaleX - 1 + frames[frames.length - 2] - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY - 1 + frames[frames.length - 1] - bone.scaleY) * alpha; + return; + } + + // Interpolate between the last frame and the current frame. + var frameIndex = spine.binarySearch(frames, time, 3); + var lastFrameX = frames[frameIndex - 2]; + var lastFrameY = frames[frameIndex - 1]; + var frameTime = frames[frameIndex]; + var percent = 1 - (time - frameTime) / (frames[frameIndex + -3/*LAST_FRAME_TIME*/] - frameTime); + percent = this.curves.getCurvePercent(frameIndex / 3 - 1, percent); + + bone.scaleX += (bone.data.scaleX - 1 + lastFrameX + (frames[frameIndex + 1/*FRAME_X*/] - lastFrameX) * percent - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY - 1 + lastFrameY + (frames[frameIndex + 2/*FRAME_Y*/] - lastFrameY) * percent - bone.scaleY) * alpha; + } +}; + +spine.ColorTimeline = function (frameCount) { + this.curves = new spine.Curves(frameCount); + this.frames = []; // time, r, g, b, a, ... + this.frames.length = frameCount * 5; +}; +spine.ColorTimeline.prototype = { + slotIndex: 0, + getFrameCount: function () { + return this.frames.length / 2; + }, + setFrame: function (frameIndex, time, x, y) { + frameIndex *= 5; + this.frames[frameIndex] = time; + this.frames[frameIndex + 1] = r; + this.frames[frameIndex + 2] = g; + this.frames[frameIndex + 3] = b; + this.frames[frameIndex + 4] = a; + }, + apply: function (skeleton, time, alpha) { + var frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + var slot = skeleton.slots[this.slotIndex]; + + if (time >= frames[frames.length - 5]) { // Time is after last frame. + var i = frames.length - 1; + slot.r = frames[i - 3]; + slot.g = frames[i - 2]; + slot.b = frames[i - 1]; + slot.a = frames[i]; + return; + } + + // Interpolate between the last frame and the current frame. + var frameIndex = spine.binarySearch(frames, time, 5); + var lastFrameR = frames[frameIndex - 4]; + var lastFrameG = frames[frameIndex - 3]; + var lastFrameB = frames[frameIndex - 2]; + var lastFrameA = frames[frameIndex - 1]; + var frameTime = frames[frameIndex]; + var percent = 1 - (time - frameTime) / (frames[frameIndex - 5/*LAST_FRAME_TIME*/] - frameTime); + percent = this.curves.getCurvePercent(frameIndex / 5 - 1, percent); + + var r = lastFrameR + (frames[frameIndex + 1/*FRAME_R*/] - lastFrameR) * percent; + var g = lastFrameG + (frames[frameIndex + 2/*FRAME_G*/] - lastFrameG) * percent; + var b = lastFrameB + (frames[frameIndex + 3/*FRAME_B*/] - lastFrameB) * percent; + var a = lastFrameA + (frames[frameIndex + 4/*FRAME_A*/] - lastFrameA) * percent; + if (alpha < 1) { + slot.r += (r - slot.r) * alpha; + slot.g += (g - slot.g) * alpha; + slot.b += (b - slot.b) * alpha; + slot.a += (a - slot.a) * alpha; + } else { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + } + } +}; + +spine.AttachmentTimeline = function (frameCount) { + this.curves = new spine.Curves(frameCount); + this.frames = []; // time, ... + this.frames.length = frameCount; + this.attachmentNames = []; // time, ... + this.attachmentNames.length = frameCount; +}; +spine.AttachmentTimeline.prototype = { + slotIndex: 0, + getFrameCount: function () { + return this.frames.length; + }, + setFrame: function (frameIndex, time, attachmentName) { + this.frames[frameIndex] = time; + this.attachmentNames[frameIndex] = attachmentName; + }, + apply: function (skeleton, time, alpha) { + var frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + var frameIndex; + if (time >= frames[frames.length - 1]) // Time is after last frame. + frameIndex = frames.length - 1; + else + frameIndex = spine.binarySearch(frames, time, 1) - 1; + + var attachmentName = this.attachmentNames[frameIndex]; + skeleton.slots[this.slotIndex].setAttachment(!attachmentName ? null : skeleton.getAttachmentBySlotIndex(this.slotIndex, attachmentName)); + } +}; + +spine.SkeletonData = function () { + this.bones = []; + this.slots = []; + this.skins = []; + this.animations = []; +}; +spine.SkeletonData.prototype = { + defaultSkin: null, + /** @return May be null. */ + findBone: function (boneName) { + var bones = this.bones; + for (var i = 0, n = bones.length; i < n; i++) + if (bones[i].name == boneName) return bones[i]; + return null; + }, + /** @return -1 if the bone was not found. */ + findBoneIndex: function (boneName) { + var bones = this.bones; + for (var i = 0, n = bones.length; i < n; i++) + if (bones[i].name == boneName) return i; + return -1; + }, + /** @return May be null. */ + findSlot: function (slotName) { + var slots = this.slots; + for (var i = 0, n = slots.length; i < n; i++) { + if (slots[i].name == slotName) return slot[i]; + } + return null; + }, + /** @return -1 if the bone was not found. */ + findSlotIndex: function (slotName) { + var slots = this.slots; + for (var i = 0, n = slots.length; i < n; i++) + if (slots[i].name == slotName) return i; + return -1; + }, + /** @return May be null. */ + findSkin: function (skinName) { + var skins = this.skins; + for (var i = 0, n = skins.length; i < n; i++) + if (skins[i].name == skinName) return skins[i]; + return null; + }, + /** @return May be null. */ + findAnimation: function (animationName) { + var animations = this.animations; + for (var i = 0, n = animations.length; i < n; i++) + if (animations[i].name == animationName) return animations[i]; + return null; + } +}; + +spine.Skeleton = function (skeletonData) { + this.data = skeletonData; + + this.bones = []; + for (var i = 0, n = skeletonData.bones.length; i < n; i++) { + var boneData = skeletonData.bones[i]; + var parent = !boneData.parent ? null : this.bones[skeletonData.bones.indexOf(boneData.parent)]; + this.bones.push(new spine.Bone(boneData, parent)); + } + + this.slots = []; + this.drawOrder = []; + for (i = 0, n = skeletonData.slots.length; i < n; i++) { + var slotData = skeletonData.slots[i]; + var bone = this.bones[skeletonData.bones.indexOf(slotData.boneData)]; + var slot = new spine.Slot(slotData, this, bone); + this.slots.push(slot); + this.drawOrder.push(slot); + } +}; +spine.Skeleton.prototype = { + x: 0, y: 0, + skin: null, + r: 1, g: 1, b: 1, a: 1, + time: 0, + flipX: false, flipY: false, + /** Updates the world transform for each bone. */ + updateWorldTransform: function () { + var flipX = this.flipX; + var flipY = this.flipY; + var bones = this.bones; + for (var i = 0, n = bones.length; i < n; i++) + bones[i].updateWorldTransform(flipX, flipY); + }, + /** Sets the bones and slots to their setup pose values. */ + setToSetupPose: function () { + this.setBonesToSetupPose(); + this.setSlotsToSetupPose(); + }, + setBonesToSetupPose: function () { + var bones = this.bones; + for (var i = 0, n = bones.length; i < n; i++) + bones[i].setToSetupPose(); + }, + setSlotsToSetupPose: function () { + var slots = this.slots; + for (var i = 0, n = slots.length; i < n; i++) + slots[i].setToSetupPose(i); + }, + /** @return May return null. */ + getRootBone: function () { + return this.bones.length ? this.bones[0] : null; + }, + /** @return May be null. */ + findBone: function (boneName) { + var bones = this.bones; + for (var i = 0, n = bones.length; i < n; i++) + if (bones[i].data.name == boneName) return bones[i]; + return null; + }, + /** @return -1 if the bone was not found. */ + findBoneIndex: function (boneName) { + var bones = this.bones; + for (var i = 0, n = bones.length; i < n; i++) + if (bones[i].data.name == boneName) return i; + return -1; + }, + /** @return May be null. */ + findSlot: function (slotName) { + var slots = this.slots; + for (var i = 0, n = slots.length; i < n; i++) + if (slots[i].data.name == slotName) return slots[i]; + return null; + }, + /** @return -1 if the bone was not found. */ + findSlotIndex: function (slotName) { + var slots = this.slots; + for (var i = 0, n = slots.length; i < n; i++) + if (slots[i].data.name == slotName) return i; + return -1; + }, + setSkinByName: function (skinName) { + var skin = this.data.findSkin(skinName); + if (!skin) throw "Skin not found: " + skinName; + this.setSkin(skin); + }, + /** Sets the skin used to look up attachments not found in the {@link SkeletonData#getDefaultSkin() default skin}. Attachments + * from the new skin are attached if the corresponding attachment from the old skin was attached. + * @param newSkin May be null. */ + setSkin: function (newSkin) { + if (this.skin && newSkin) newSkin._attachAll(this, this.skin); + this.skin = newSkin; + }, + /** @return May be null. */ + getAttachmentBySlotName: function (slotName, attachmentName) { + return this.getAttachmentBySlotIndex(this.data.findSlotIndex(slotName), attachmentName); + }, + /** @return May be null. */ + getAttachmentBySlotIndex: function (slotIndex, attachmentName) { + if (this.skin) { + var attachment = this.skin.getAttachment(slotIndex, attachmentName); + if (attachment) return attachment; + } + if (this.data.defaultSkin) return this.data.defaultSkin.getAttachment(slotIndex, attachmentName); + return null; + }, + /** @param attachmentName May be null. */ + setAttachment: function (slotName, attachmentName) { + var slots = this.slots; + for (var i = 0, n = slots.size; i < n; i++) { + var slot = slots[i]; + if (slot.data.name == slotName) { + var attachment = null; + if (attachmentName) { + attachment = this.getAttachment(i, attachmentName); + if (attachment == null) throw "Attachment not found: " + attachmentName + ", for slot: " + slotName; + } + slot.setAttachment(attachment); + return; + } + } + throw "Slot not found: " + slotName; + }, + update: function (delta) { + time += delta; + } +}; + +spine.AttachmentType = { + region: 0 +}; + +spine.RegionAttachment = function () { + this.offset = []; + this.offset.length = 8; + this.uvs = []; + this.uvs.length = 8; +}; +spine.RegionAttachment.prototype = { + x: 0, y: 0, + rotation: 0, + scaleX: 1, scaleY: 1, + width: 0, height: 0, + rendererObject: null, + regionOffsetX: 0, regionOffsetY: 0, + regionWidth: 0, regionHeight: 0, + regionOriginalWidth: 0, regionOriginalHeight: 0, + setUVs: function (u, v, u2, v2, rotate) { + var uvs = this.uvs; + if (rotate) { + uvs[2/*X2*/] = u; + uvs[3/*Y2*/] = v2; + uvs[4/*X3*/] = u; + uvs[5/*Y3*/] = v; + uvs[6/*X4*/] = u2; + uvs[7/*Y4*/] = v; + uvs[0/*X1*/] = u2; + uvs[1/*Y1*/] = v2; + } else { + uvs[0/*X1*/] = u; + uvs[1/*Y1*/] = v2; + uvs[2/*X2*/] = u; + uvs[3/*Y2*/] = v; + uvs[4/*X3*/] = u2; + uvs[5/*Y3*/] = v; + uvs[6/*X4*/] = u2; + uvs[7/*Y4*/] = v2; + } + }, + updateOffset: function () { + var regionScaleX = this.width / this.regionOriginalWidth * this.scaleX; + var regionScaleY = this.height / this.regionOriginalHeight * this.scaleY; + var localX = -this.width / 2 * this.scaleX + this.regionOffsetX * regionScaleX; + var localY = -this.height / 2 * this.scaleY + this.regionOffsetY * regionScaleY; + var localX2 = localX + this.regionWidth * regionScaleX; + var localY2 = localY + this.regionHeight * regionScaleY; + var radians = this.rotation * Math.PI / 180; + var cos = Math.cos(radians); + var sin = Math.sin(radians); + var localXCos = localX * cos + this.x; + var localXSin = localX * sin; + var localYCos = localY * cos + this.y; + var localYSin = localY * sin; + var localX2Cos = localX2 * cos + this.x; + var localX2Sin = localX2 * sin; + var localY2Cos = localY2 * cos + this.y; + var localY2Sin = localY2 * sin; + var offset = this.offset; + offset[0/*X1*/] = localXCos - localYSin; + offset[1/*Y1*/] = localYCos + localXSin; + offset[2/*X2*/] = localXCos - localY2Sin; + offset[3/*Y2*/] = localY2Cos + localXSin; + offset[4/*X3*/] = localX2Cos - localY2Sin; + offset[5/*Y3*/] = localY2Cos + localX2Sin; + offset[6/*X4*/] = localX2Cos - localYSin; + offset[7/*Y4*/] = localYCos + localX2Sin; + }, + computeVertices: function (x, y, bone, vertices) { + x += bone.worldX; + y += bone.worldY; + var m00 = bone.m00; + var m01 = bone.m01; + var m10 = bone.m10; + var m11 = bone.m11; + var offset = this.offset; + vertices[0/*X1*/] = offset[0/*X1*/] * m00 + offset[1/*Y1*/] * m01 + x; + vertices[1/*Y1*/] = offset[0/*X1*/] * m10 + offset[1/*Y1*/] * m11 + y; + vertices[2/*X2*/] = offset[2/*X2*/] * m00 + offset[3/*Y2*/] * m01 + x; + vertices[3/*Y2*/] = offset[2/*X2*/] * m10 + offset[3/*Y2*/] * m11 + y; + vertices[4/*X3*/] = offset[4/*X3*/] * m00 + offset[5/*X3*/] * m01 + x; + vertices[5/*X3*/] = offset[4/*X3*/] * m10 + offset[5/*X3*/] * m11 + y; + vertices[6/*X4*/] = offset[6/*X4*/] * m00 + offset[7/*Y4*/] * m01 + x; + vertices[7/*Y4*/] = offset[6/*X4*/] * m10 + offset[7/*Y4*/] * m11 + y; + } +} + +spine.AnimationStateData = function (skeletonData) { + this.skeletonData = skeletonData; + this.animationToMixTime = {}; +}; +spine.AnimationStateData.prototype = { + defaultMix: 0, + setMixByName: function (fromName, toName, duration) { + var from = this.skeletonData.findAnimation(fromName); + if (!from) throw "Animation not found: " + fromName; + var to = this.skeletonData.findAnimation(toName); + if (!to) throw "Animation not found: " + toName; + this.setMix(from, to, duration); + }, + setMix: function (from, to, duration) { + this.animationToMixTime[from.name + ":" + to.name] = duration; + }, + getMix: function (from, to) { + var time = this.animationToMixTime[from.name + ":" + to.name]; + return time ? time : this.defaultMix; + } +}; + +spine.AnimationState = function (stateData) { + this.data = stateData; + this.queue = []; +}; +spine.AnimationState.prototype = { + current: null, + previous: null, + currentTime: 0, + previousTime: 0, + currentLoop: false, + previousLoop: false, + mixTime: 0, + mixDuration: 0, + update: function (delta) { + this.currentTime += delta; + this.previousTime += delta; + this.mixTime += delta; + + if (this.queue.length > 0) { + var entry = this.queue[0]; + if (this.currentTime >= entry.delay) { + this._setAnimation(entry.animation, entry.loop); + this.queue.shift(); + } + } + }, + apply: function (skeleton) { + if (!this.current) return; + if (this.previous) { + this.previous.apply(skeleton, this.previousTime, this.previousLoop); + var alpha = this.mixTime / this.mixDuration; + if (alpha >= 1) { + alpha = 1; + this.previous = null; + } + this.current.mix(skeleton, this.currentTime, this.currentLoop, alpha); + } else + this.current.apply(skeleton, this.currentTime, this.currentLoop); + }, + clearAnimation: function () { + this.previous = null; + this.current = null; + this.queue.length = 0; + }, + _setAnimation: function (animation, loop) { + this.previous = null; + if (animation && this.current) { + this.mixDuration = this.data.getMix(this.current, animation); + if (this.mixDuration > 0) { + this.mixTime = 0; + this.previous = this.current; + this.previousTime = this.currentTime; + this.previousLoop = this.currentLoop; + } + } + this.current = animation; + this.currentLoop = loop; + this.currentTime = 0; + }, + /** @see #setAnimation(Animation, Boolean) */ + setAnimationByName: function (animationName, loop) { + var animation = this.data.skeletonData.findAnimation(animationName); + if (!animation) throw "Animation not found: " + animationName; + this.setAnimation(animation, loop); + }, + /** Set the current animation. Any queued animations are cleared and the current animation time is set to 0. + * @param animation May be null. */ + setAnimation: function (animation, loop) { + this.queue.length = 0; + this._setAnimation(animation, loop); + }, + /** @see #addAnimation(Animation, Boolean, Number) */ + addAnimationByName: function (animationName, loop, delay) { + var animation = this.data.skeletonData.findAnimation(animationName); + if (!animation) throw "Animation not found: " + animationName; + this.addAnimation(animation, loop, delay); + }, + /** Adds an animation to be played delay seconds after the current or last queued animation. + * @param delay May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. */ + addAnimation: function (animation, loop, delay) { + var entry = {}; + entry.animation = animation; + entry.loop = loop; + + if (!delay || delay <= 0) { + var previousAnimation = this.queue.length ? this.queue[this.queue.length - 1].animation : this.current; + if (previousAnimation != null) + delay = previousAnimation.duration - this.data.getMix(previousAnimation, animation) + (delay || 0); + else + delay = 0; + } + entry.delay = delay; + + this.queue.push(entry); + }, + /** Returns true if no animation is set or if the current time is greater than the animation duration, regardless of looping. */ + isComplete: function () { + return !this.current || this.currentTime >= this.current.duration; + } +}; + +spine.SkeletonJson = function (attachmentLoader) { + this.attachmentLoader = attachmentLoader; +}; +spine.SkeletonJson.prototype = { + scale: 1, + readSkeletonData: function (root) { + /*jshint -W069*/ + var skeletonData = new spine.SkeletonData(), + boneData; + + // Bones. + var bones = root["bones"]; + for (var i = 0, n = bones.length; i < n; i++) { + var boneMap = bones[i]; + var parent = null; + if (boneMap["parent"]) { + parent = skeletonData.findBone(boneMap["parent"]); + if (!parent) throw "Parent bone not found: " + boneMap["parent"]; + } + boneData = new spine.BoneData(boneMap["name"], parent); + boneData.length = (boneMap["length"] || 0) * this.scale; + boneData.x = (boneMap["x"] || 0) * this.scale; + boneData.y = (boneMap["y"] || 0) * this.scale; + boneData.rotation = (boneMap["rotation"] || 0); + boneData.scaleX = boneMap["scaleX"] || 1; + boneData.scaleY = boneMap["scaleY"] || 1; + skeletonData.bones.push(boneData); + } + + // Slots. + var slots = root["slots"]; + for (i = 0, n = slots.length; i < n; i++) { + var slotMap = slots[i]; + boneData = skeletonData.findBone(slotMap["bone"]); + if (!boneData) throw "Slot bone not found: " + slotMap["bone"]; + var slotData = new spine.SlotData(slotMap["name"], boneData); + + var color = slotMap["color"]; + if (color) { + slotData.r = spine.SkeletonJson.toColor(color, 0); + slotData.g = spine.SkeletonJson.toColor(color, 1); + slotData.b = spine.SkeletonJson.toColor(color, 2); + slotData.a = spine.SkeletonJson.toColor(color, 3); + } + + slotData.attachmentName = slotMap["attachment"]; + + skeletonData.slots.push(slotData); + } + + // Skins. + var skins = root["skins"]; + for (var skinName in skins) { + if (!skins.hasOwnProperty(skinName)) continue; + var skinMap = skins[skinName]; + var skin = new spine.Skin(skinName); + for (var slotName in skinMap) { + if (!skinMap.hasOwnProperty(slotName)) continue; + var slotIndex = skeletonData.findSlotIndex(slotName); + var slotEntry = skinMap[slotName]; + for (var attachmentName in slotEntry) { + if (!slotEntry.hasOwnProperty(attachmentName)) continue; + var attachment = this.readAttachment(skin, attachmentName, slotEntry[attachmentName]); + if (attachment != null) skin.addAttachment(slotIndex, attachmentName, attachment); + } + } + skeletonData.skins.push(skin); + if (skin.name == "default") skeletonData.defaultSkin = skin; + } + + // Animations. + var animations = root["animations"]; + for (var animationName in animations) { + if (!animations.hasOwnProperty(animationName)) continue; + this.readAnimation(animationName, animations[animationName], skeletonData); + } + + return skeletonData; + }, + readAttachment: function (skin, name, map) { + /*jshint -W069*/ + name = map["name"] || name; + + var type = spine.AttachmentType[map["type"] || "region"]; + + if (type == spine.AttachmentType.region) { + var attachment = new spine.RegionAttachment(); + attachment.x = (map["x"] || 0) * this.scale; + attachment.y = (map["y"] || 0) * this.scale; + attachment.scaleX = map["scaleX"] || 1; + attachment.scaleY = map["scaleY"] || 1; + attachment.rotation = map["rotation"] || 0; + attachment.width = (map["width"] || 32) * this.scale; + attachment.height = (map["height"] || 32) * this.scale; + attachment.updateOffset(); + + attachment.rendererObject = {}; + attachment.rendererObject.name = name; + attachment.rendererObject.scale = {}; + attachment.rendererObject.scale.x = attachment.scaleX; + attachment.rendererObject.scale.y = attachment.scaleY; + attachment.rendererObject.rotation = -attachment.rotation * Math.PI / 180; + return attachment; + } + + throw "Unknown attachment type: " + type; + }, + + readAnimation: function (name, map, skeletonData) { + /*jshint -W069*/ + var timelines = []; + var duration = 0; + var frameIndex, timeline, timelineName, valueMap, values, + i, n; + + var bones = map["bones"]; + for (var boneName in bones) { + if (!bones.hasOwnProperty(boneName)) continue; + var boneIndex = skeletonData.findBoneIndex(boneName); + if (boneIndex == -1) throw "Bone not found: " + boneName; + var boneMap = bones[boneName]; + + for (timelineName in boneMap) { + if (!boneMap.hasOwnProperty(timelineName)) continue; + values = boneMap[timelineName]; + if (timelineName == "rotate") { + timeline = new spine.RotateTimeline(values.length); + timeline.boneIndex = boneIndex; + + frameIndex = 0; + for (i = 0, n = values.length; i < n; i++) { + valueMap = values[i]; + timeline.setFrame(frameIndex, valueMap["time"], valueMap["angle"]); + spine.SkeletonJson.readCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 2 - 2]); + + } else if (timelineName == "translate" || timelineName == "scale") { + var timelineScale = 1; + if (timelineName == "scale") + timeline = new spine.ScaleTimeline(values.length); + else { + timeline = new spine.TranslateTimeline(values.length); + timelineScale = this.scale; + } + timeline.boneIndex = boneIndex; + + frameIndex = 0; + for (i = 0, n = values.length; i < n; i++) { + valueMap = values[i]; + var x = (valueMap["x"] || 0) * timelineScale; + var y = (valueMap["y"] || 0) * timelineScale; + timeline.setFrame(frameIndex, valueMap["time"], x, y); + spine.SkeletonJson.readCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 3 - 3]); + + } else + throw "Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"; + } + } + var slots = map["slots"]; + for (var slotName in slots) { + if (!slots.hasOwnProperty(slotName)) continue; + var slotMap = slots[slotName]; + var slotIndex = skeletonData.findSlotIndex(slotName); + + for (timelineName in slotMap) { + if (!slotMap.hasOwnProperty(timelineName)) continue; + values = slotMap[timelineName]; + if (timelineName == "color") { + timeline = new spine.ColorTimeline(values.length); + timeline.slotIndex = slotIndex; + + frameIndex = 0; + for (i = 0, n = values.length; i < n; i++) { + valueMap = values[i]; + var color = valueMap["color"]; + var r = spine.SkeletonJson.toColor(color, 0); + var g = spine.SkeletonJson.toColor(color, 1); + var b = spine.SkeletonJson.toColor(color, 2); + var a = spine.SkeletonJson.toColor(color, 3); + timeline.setFrame(frameIndex, valueMap["time"], r, g, b, a); + spine.SkeletonJson.readCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 5 - 5]); + + } else if (timelineName == "attachment") { + timeline = new spine.AttachmentTimeline(values.length); + timeline.slotIndex = slotIndex; + + frameIndex = 0; + for (i = 0, n = values.length; i < n; i++) { + valueMap = values[i]; + timeline.setFrame(frameIndex++, valueMap["time"], valueMap["name"]); + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]); + + } else + throw "Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"; + } + } + skeletonData.animations.push(new spine.Animation(name, timelines, duration)); + } +}; +spine.SkeletonJson.readCurve = function (timeline, frameIndex, valueMap) { + /*jshint -W069*/ + var curve = valueMap["curve"]; + if (!curve) return; + if (curve == "stepped") + timeline.curves.setStepped(frameIndex); + else if (curve instanceof Array) + timeline.curves.setCurve(frameIndex, curve[0], curve[1], curve[2], curve[3]); +}; +spine.SkeletonJson.toColor = function (hexString, colorIndex) { + if (hexString.length != 8) throw "Color hexidecimal length must be 8, recieved: " + hexString; + return parseInt(hexString.substring(colorIndex * 2, 2), 16) / 255; +}; + +spine.Atlas = function (atlasText, textureLoader) { + this.textureLoader = textureLoader; + this.pages = []; + this.regions = []; + + var reader = new spine.AtlasReader(atlasText); + var tuple = []; + tuple.length = 4; + var page = null; + while (true) { + var line = reader.readLine(); + if (line == null) break; + line = reader.trim(line); + if (!line.length) + page = null; + else if (!page) { + page = new spine.AtlasPage(); + page.name = line; + + page.format = spine.Atlas.Format[reader.readValue()]; + + reader.readTuple(tuple); + page.minFilter = spine.Atlas.TextureFilter[tuple[0]]; + page.magFilter = spine.Atlas.TextureFilter[tuple[1]]; + + var direction = reader.readValue(); + page.uWrap = spine.Atlas.TextureWrap.clampToEdge; + page.vWrap = spine.Atlas.TextureWrap.clampToEdge; + if (direction == "x") + page.uWrap = spine.Atlas.TextureWrap.repeat; + else if (direction == "y") + page.vWrap = spine.Atlas.TextureWrap.repeat; + else if (direction == "xy") + page.uWrap = page.vWrap = spine.Atlas.TextureWrap.repeat; + + textureLoader.load(page, line); + + this.pages.push(page); + + } else { + var region = new spine.AtlasRegion(); + region.name = line; + region.page = page; + + region.rotate = reader.readValue() == "true"; + + reader.readTuple(tuple); + var x = parseInt(tuple[0], 10); + var y = parseInt(tuple[1], 10); + + reader.readTuple(tuple); + var width = parseInt(tuple[0], 10); + var height = parseInt(tuple[1], 10); + + region.u = x / page.width; + region.v = y / page.height; + if (region.rotate) { + region.u2 = (x + height) / page.width; + region.v2 = (y + width) / page.height; + } else { + region.u2 = (x + width) / page.width; + region.v2 = (y + height) / page.height; + } + region.x = x; + region.y = y; + region.width = Math.abs(width); + region.height = Math.abs(height); + + if (reader.readTuple(tuple) == 4) { // split is optional + region.splits = [parseInt(tuple[0], 10), parseInt(tuple[1], 10), parseInt(tuple[2], 10), parseInt(tuple[3], 10)]; + + if (reader.readTuple(tuple) == 4) { // pad is optional, but only present with splits + region.pads = [parseInt(tuple[0], 10), parseInt(tuple[1], 10), parseInt(tuple[2], 10), parseInt(tuple[3], 10)]; + + reader.readTuple(tuple); + } + } + + region.originalWidth = parseInt(tuple[0], 10); + region.originalHeight = parseInt(tuple[1], 10); + + reader.readTuple(tuple); + region.offsetX = parseInt(tuple[0], 10); + region.offsetY = parseInt(tuple[1], 10); + + region.index = parseInt(reader.readValue(), 10); + + this.regions.push(region); + } + } +}; +spine.Atlas.prototype = { + findRegion: function (name) { + var regions = this.regions; + for (var i = 0, n = regions.length; i < n; i++) + if (regions[i].name == name) return regions[i]; + return null; + }, + dispose: function () { + var pages = this.pages; + for (var i = 0, n = pages.length; i < n; i++) + this.textureLoader.unload(pages[i].rendererObject); + }, + updateUVs: function (page) { + var regions = this.regions; + for (var i = 0, n = regions.length; i < n; i++) { + var region = regions[i]; + if (region.page != page) continue; + region.u = region.x / page.width; + region.v = region.y / page.height; + if (region.rotate) { + region.u2 = (region.x + region.height) / page.width; + region.v2 = (region.y + region.width) / page.height; + } else { + region.u2 = (region.x + region.width) / page.width; + region.v2 = (region.y + region.height) / page.height; + } + } + } +}; + +spine.Atlas.Format = { + alpha: 0, + intensity: 1, + luminanceAlpha: 2, + rgb565: 3, + rgba4444: 4, + rgb888: 5, + rgba8888: 6 +}; + +spine.Atlas.TextureFilter = { + nearest: 0, + linear: 1, + mipMap: 2, + mipMapNearestNearest: 3, + mipMapLinearNearest: 4, + mipMapNearestLinear: 5, + mipMapLinearLinear: 6 +}; + +spine.Atlas.TextureWrap = { + mirroredRepeat: 0, + clampToEdge: 1, + repeat: 2 +}; + +spine.AtlasPage = function () {}; +spine.AtlasPage.prototype = { + name: null, + format: null, + minFilter: null, + magFilter: null, + uWrap: null, + vWrap: null, + rendererObject: null, + width: 0, + height: 0 +}; + +spine.AtlasRegion = function () {}; +spine.AtlasRegion.prototype = { + page: null, + name: null, + x: 0, y: 0, + width: 0, height: 0, + u: 0, v: 0, u2: 0, v2: 0, + offsetX: 0, offsetY: 0, + originalWidth: 0, originalHeight: 0, + index: 0, + rotate: false, + splits: null, + pads: null, +}; + +spine.AtlasReader = function (text) { + this.lines = text.split(/\r\n|\r|\n/); +}; +spine.AtlasReader.prototype = { + index: 0, + trim: function (value) { + return value.replace(/^\s+|\s+$/g, ""); + }, + readLine: function () { + if (this.index >= this.lines.length) return null; + return this.lines[this.index++]; + }, + readValue: function () { + var line = this.readLine(); + var colon = line.indexOf(":"); + if (colon == -1) throw "Invalid line: " + line; + return this.trim(line.substring(colon + 1)); + }, + /** Returns the number of tuple values read (2 or 4). */ + readTuple: function (tuple) { + var line = this.readLine(); + var colon = line.indexOf(":"); + if (colon == -1) throw "Invalid line: " + line; + var i = 0, lastMatch= colon + 1; + for (; i < 3; i++) { + var comma = line.indexOf(",", lastMatch); + if (comma == -1) { + if (!i) throw "Invalid line: " + line; + break; + } + tuple[i] = this.trim(line.substr(lastMatch, comma - lastMatch)); + lastMatch = comma + 1; + } + tuple[i] = this.trim(line.substring(lastMatch)); + return i + 1; + } +} + +spine.AtlasAttachmentLoader = function (atlas) { + this.atlas = atlas; +} +spine.AtlasAttachmentLoader.prototype = { + newAttachment: function (skin, type, name) { + switch (type) { + case spine.AttachmentType.region: + var region = this.atlas.findRegion(name); + if (!region) throw "Region not found in atlas: " + name + " (" + type + ")"; + var attachment = new spine.RegionAttachment(name); + attachment.rendererObject = region; + attachment.setUVs(region.u, region.v, region.u2, region.v2, region.rotate); + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } + throw "Unknown attachment type: " + type; + } +} + +spine.Bone.yDown = true; +PIXI.AnimCache = {}; + /** * A class that enables the you to import and run your spine animations in pixi. * Spine animation data needs to be loaded using the PIXI.AssetLoader or PIXI.SpineLoader before it can be used by this class @@ -18,37 +1362,37 @@ * @param url {String} The url of the spine anim file to be used */ PIXI.Spine = function (url) { - PIXI.DisplayObjectContainer.call(this); + PIXI.DisplayObjectContainer.call(this); - this.spineData = PIXI.AnimCache[url]; + this.spineData = PIXI.AnimCache[url]; - if (!this.spineData) { - throw new Error("Spine data must be preloaded using PIXI.SpineLoader or PIXI.AssetLoader: " + url); - } + if (!this.spineData) { + throw new Error("Spine data must be preloaded using PIXI.SpineLoader or PIXI.AssetLoader: " + url); + } - this.skeleton = new spine.Skeleton(this.spineData); - this.skeleton.updateWorldTransform(); + this.skeleton = new spine.Skeleton(this.spineData); + this.skeleton.updateWorldTransform(); - this.stateData = new spine.AnimationStateData(this.spineData); - this.state = new spine.AnimationState(this.stateData); + this.stateData = new spine.AnimationStateData(this.spineData); + this.state = new spine.AnimationState(this.stateData); - this.slotContainers = []; + this.slotContainers = []; - for (var i = 0, n = this.skeleton.drawOrder.length; i < n; i++) { - var slot = this.skeleton.drawOrder[i]; - var attachment = slot.attachment; - var slotContainer = new PIXI.DisplayObjectContainer(); - this.slotContainers.push(slotContainer); - this.addChild(slotContainer); - if (!(attachment instanceof spine.RegionAttachment)) { - continue; - } - var spriteName = attachment.rendererObject.name; - var sprite = this.createSprite(slot, attachment.rendererObject); - slot.currentSprite = sprite; - slot.currentSpriteName = spriteName; - slotContainer.addChild(sprite); - } + for (var i = 0, n = this.skeleton.drawOrder.length; i < n; i++) { + var slot = this.skeleton.drawOrder[i]; + var attachment = slot.attachment; + var slotContainer = new PIXI.DisplayObjectContainer(); + this.slotContainers.push(slotContainer); + this.addChild(slotContainer); + if (!(attachment instanceof spine.RegionAttachment)) { + continue; + } + var spriteName = attachment.rendererObject.name; + var sprite = this.createSprite(slot, attachment.rendererObject); + slot.currentSprite = sprite; + slot.currentSpriteName = spriteName; + slotContainer.addChild(sprite); + } }; PIXI.Spine.prototype = Object.create(PIXI.DisplayObjectContainer.prototype); @@ -61,1400 +1405,64 @@ PIXI.Spine.prototype.constructor = PIXI.Spine; * @private */ PIXI.Spine.prototype.updateTransform = function () { - this.lastTime = this.lastTime || Date.now(); - var timeDelta = (Date.now() - this.lastTime) * 0.001; - this.lastTime = Date.now(); - this.state.update(timeDelta); - this.state.apply(this.skeleton); - this.skeleton.updateWorldTransform(); + this.lastTime = this.lastTime || Date.now(); + var timeDelta = (Date.now() - this.lastTime) * 0.001; + this.lastTime = Date.now(); + this.state.update(timeDelta); + this.state.apply(this.skeleton); + this.skeleton.updateWorldTransform(); - var drawOrder = this.skeleton.drawOrder; - for (var i = 0, n = drawOrder.length; i < n; i++) { - var slot = drawOrder[i]; - var attachment = slot.attachment; - var slotContainer = this.slotContainers[i]; - if (!(attachment instanceof spine.RegionAttachment)) { - slotContainer.visible = false; - continue; - } + var drawOrder = this.skeleton.drawOrder; + for (var i = 0, n = drawOrder.length; i < n; i++) { + var slot = drawOrder[i]; + var attachment = slot.attachment; + var slotContainer = this.slotContainers[i]; + if (!(attachment instanceof spine.RegionAttachment)) { + slotContainer.visible = false; + continue; + } - if (attachment.rendererObject) { - if (!slot.currentSpriteName || slot.currentSpriteName != attachment.name) { - var spriteName = attachment.rendererObject.name; - if (slot.currentSprite !== undefined) { - slot.currentSprite.visible = false; - } - slot.sprites = slot.sprites || {}; - if (slot.sprites[spriteName] !== undefined) { - slot.sprites[spriteName].visible = true; - } else { - var sprite = this.createSprite(slot, attachment.rendererObject); - slotContainer.addChild(sprite); - } - slot.currentSprite = slot.sprites[spriteName]; - slot.currentSpriteName = spriteName; - } - } - slotContainer.visible = true; + if (attachment.rendererObject) { + if (!slot.currentSpriteName || slot.currentSpriteName != attachment.name) { + var spriteName = attachment.rendererObject.name; + if (slot.currentSprite !== undefined) { + slot.currentSprite.visible = false; + } + slot.sprites = slot.sprites || {}; + if (slot.sprites[spriteName] !== undefined) { + slot.sprites[spriteName].visible = true; + } else { + var sprite = this.createSprite(slot, attachment.rendererObject); + slotContainer.addChild(sprite); + } + slot.currentSprite = slot.sprites[spriteName]; + slot.currentSpriteName = spriteName; + } + } + slotContainer.visible = true; - var bone = slot.bone; + var bone = slot.bone; - slotContainer.position.x = bone.worldX + attachment.x * bone.m00 + attachment.y * bone.m01; - slotContainer.position.y = bone.worldY + attachment.x * bone.m10 + attachment.y * bone.m11; - slotContainer.scale.x = bone.worldScaleX; - slotContainer.scale.y = bone.worldScaleY; + slotContainer.position.x = bone.worldX + attachment.x * bone.m00 + attachment.y * bone.m01; + slotContainer.position.y = bone.worldY + attachment.x * bone.m10 + attachment.y * bone.m11; + slotContainer.scale.x = bone.worldScaleX; + slotContainer.scale.y = bone.worldScaleY; - slotContainer.rotation = -(slot.bone.worldRotation * Math.PI / 180); - } + slotContainer.rotation = -(slot.bone.worldRotation * Math.PI / 180); + } - PIXI.DisplayObjectContainer.prototype.updateTransform.call(this); + PIXI.DisplayObjectContainer.prototype.updateTransform.call(this); }; PIXI.Spine.prototype.createSprite = function (slot, descriptor) { - var name = PIXI.TextureCache[descriptor.name] ? descriptor.name : descriptor.name + ".png"; - var sprite = new PIXI.Sprite(PIXI.Texture.fromFrame(name)); - sprite.scale = descriptor.scale; - sprite.rotation = descriptor.rotation; - sprite.anchor.x = sprite.anchor.y = 0.5; + var name = PIXI.TextureCache[descriptor.name] ? descriptor.name : descriptor.name + ".png"; + var sprite = new PIXI.Sprite(PIXI.Texture.fromFrame(name)); + sprite.scale = descriptor.scale; + sprite.rotation = descriptor.rotation; + sprite.anchor.x = sprite.anchor.y = 0.5; - slot.sprites = slot.sprites || {}; - slot.sprites[descriptor.name] = sprite; - return sprite; + slot.sprites = slot.sprites || {}; + slot.sprites[descriptor.name] = sprite; + return sprite; }; - -/* - * Awesome JS run time provided by EsotericSoftware - * - * https://github.com/EsotericSoftware/spine-runtimes - * - */ - -var spine = {}; - -spine.BoneData = function (name, parent) { - this.name = name; - this.parent = parent; -}; -spine.BoneData.prototype = { - length: 0, - x: 0, y: 0, - rotation: 0, - scaleX: 1, scaleY: 1 -}; - -spine.SlotData = function (name, boneData) { - this.name = name; - this.boneData = boneData; -}; -spine.SlotData.prototype = { - r: 1, g: 1, b: 1, a: 1, - attachmentName: null -}; - -spine.Bone = function (boneData, parent) { - this.data = boneData; - this.parent = parent; - this.setToSetupPose(); -}; -spine.Bone.yDown = false; -spine.Bone.prototype = { - x: 0, y: 0, - rotation: 0, - scaleX: 1, scaleY: 1, - m00: 0, m01: 0, worldX: 0, // a b x - m10: 0, m11: 0, worldY: 0, // c d y - worldRotation: 0, - worldScaleX: 1, worldScaleY: 1, - updateWorldTransform: function (flipX, flipY) { - var parent = this.parent; - if (parent != null) { - this.worldX = this.x * parent.m00 + this.y * parent.m01 + parent.worldX; - this.worldY = this.x * parent.m10 + this.y * parent.m11 + parent.worldY; - this.worldScaleX = parent.worldScaleX * this.scaleX; - this.worldScaleY = parent.worldScaleY * this.scaleY; - this.worldRotation = parent.worldRotation + this.rotation; - } else { - this.worldX = this.x; - this.worldY = this.y; - this.worldScaleX = this.scaleX; - this.worldScaleY = this.scaleY; - this.worldRotation = this.rotation; - } - var radians = this.worldRotation * Math.PI / 180; - var cos = Math.cos(radians); - var sin = Math.sin(radians); - this.m00 = cos * this.worldScaleX; - this.m10 = sin * this.worldScaleX; - this.m01 = -sin * this.worldScaleY; - this.m11 = cos * this.worldScaleY; - if (flipX) { - this.m00 = -this.m00; - this.m01 = -this.m01; - } - if (flipY) { - this.m10 = -this.m10; - this.m11 = -this.m11; - } - if (spine.Bone.yDown) { - this.m10 = -this.m10; - this.m11 = -this.m11; - } - }, - setToSetupPose: function () { - var data = this.data; - this.x = data.x; - this.y = data.y; - this.rotation = data.rotation; - this.scaleX = data.scaleX; - this.scaleY = data.scaleY; - } -}; - -spine.Slot = function (slotData, skeleton, bone) { - this.data = slotData; - this.skeleton = skeleton; - this.bone = bone; - this.setToSetupPose(); -}; -spine.Slot.prototype = { - r: 1, g: 1, b: 1, a: 1, - _attachmentTime: 0, - attachment: null, - setAttachment: function (attachment) { - this.attachment = attachment; - this._attachmentTime = this.skeleton.time; - }, - setAttachmentTime: function (time) { - this._attachmentTime = this.skeleton.time - time; - }, - getAttachmentTime: function () { - return this.skeleton.time - this._attachmentTime; - }, - setToSetupPose: function () { - var data = this.data; - this.r = data.r; - this.g = data.g; - this.b = data.b; - this.a = data.a; - - var slotDatas = this.skeleton.data.slots; - for (var i = 0, n = slotDatas.length; i < n; i++) { - if (slotDatas[i] == data) { - this.setAttachment(!data.attachmentName ? null : this.skeleton.getAttachmentBySlotIndex(i, data.attachmentName)); - break; - } - } - } -}; - -spine.Skin = function (name) { - this.name = name; - this.attachments = {}; -}; -spine.Skin.prototype = { - addAttachment: function (slotIndex, name, attachment) { - this.attachments[slotIndex + ":" + name] = attachment; - }, - getAttachment: function (slotIndex, name) { - return this.attachments[slotIndex + ":" + name]; - }, - _attachAll: function (skeleton, oldSkin) { - for (var key in oldSkin.attachments) { - var colon = key.indexOf(":"); - var slotIndex = parseInt(key.substring(0, colon)); - var name = key.substring(colon + 1); - var slot = skeleton.slots[slotIndex]; - if (slot.attachment && slot.attachment.name == name) { - var attachment = this.getAttachment(slotIndex, name); - if (attachment) slot.setAttachment(attachment); - } - } - } -}; - -spine.Animation = function (name, timelines, duration) { - this.name = name; - this.timelines = timelines; - this.duration = duration; -}; -spine.Animation.prototype = { - apply: function (skeleton, time, loop) { - if (loop && this.duration != 0) time %= this.duration; - var timelines = this.timelines; - for (var i = 0, n = timelines.length; i < n; i++) - timelines[i].apply(skeleton, time, 1); - }, - mix: function (skeleton, time, loop, alpha) { - if (loop && this.duration != 0) time %= this.duration; - var timelines = this.timelines; - for (var i = 0, n = timelines.length; i < n; i++) - timelines[i].apply(skeleton, time, alpha); - } -}; - -spine.binarySearch = function (values, target, step) { - var low = 0; - var high = Math.floor(values.length / step) - 2; - if (high == 0) return step; - var current = high >>> 1; - while (true) { - if (values[(current + 1) * step] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1) * step; - current = (low + high) >>> 1; - } -}; -spine.linearSearch = function (values, target, step) { - for (var i = 0, last = values.length - step; i <= last; i += step) - if (values[i] > target) return i; - return -1; -}; - -spine.Curves = function (frameCount) { - this.curves = []; // dfx, dfy, ddfx, ddfy, dddfx, dddfy, ... - this.curves.length = (frameCount - 1) * 6; -}; -spine.Curves.prototype = { - setLinear: function (frameIndex) { - this.curves[frameIndex * 6] = 0/*LINEAR*/; - }, - setStepped: function (frameIndex) { - this.curves[frameIndex * 6] = -1/*STEPPED*/; - }, - /** Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. - * cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of - * the difference between the keyframe's values. */ - setCurve: function (frameIndex, cx1, cy1, cx2, cy2) { - var subdiv_step = 1 / 10/*BEZIER_SEGMENTS*/; - var subdiv_step2 = subdiv_step * subdiv_step; - var subdiv_step3 = subdiv_step2 * subdiv_step; - var pre1 = 3 * subdiv_step; - var pre2 = 3 * subdiv_step2; - var pre4 = 6 * subdiv_step2; - var pre5 = 6 * subdiv_step3; - var tmp1x = -cx1 * 2 + cx2; - var tmp1y = -cy1 * 2 + cy2; - var tmp2x = (cx1 - cx2) * 3 + 1; - var tmp2y = (cy1 - cy2) * 3 + 1; - var i = frameIndex * 6; - var curves = this.curves; - curves[i] = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3; - curves[i + 1] = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3; - curves[i + 2] = tmp1x * pre4 + tmp2x * pre5; - curves[i + 3] = tmp1y * pre4 + tmp2y * pre5; - curves[i + 4] = tmp2x * pre5; - curves[i + 5] = tmp2y * pre5; - }, - getCurvePercent: function (frameIndex, percent) { - percent = percent < 0 ? 0 : (percent > 1 ? 1 : percent); - var curveIndex = frameIndex * 6; - var curves = this.curves; - var dfx = curves[curveIndex]; - if (!dfx/*LINEAR*/) return percent; - if (dfx == -1/*STEPPED*/) return 0; - var dfy = curves[curveIndex + 1]; - var ddfx = curves[curveIndex + 2]; - var ddfy = curves[curveIndex + 3]; - var dddfx = curves[curveIndex + 4]; - var dddfy = curves[curveIndex + 5]; - var x = dfx, y = dfy; - var i = 10/*BEZIER_SEGMENTS*/ - 2; - while (true) { - if (x >= percent) { - var lastX = x - dfx; - var lastY = y - dfy; - return lastY + (y - lastY) * (percent - lastX) / (x - lastX); - } - if (i == 0) break; - i--; - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - x += dfx; - y += dfy; - } - return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. - } -}; - -spine.RotateTimeline = function (frameCount) { - this.curves = new spine.Curves(frameCount); - this.frames = []; // time, angle, ... - this.frames.length = frameCount * 2; -}; -spine.RotateTimeline.prototype = { - boneIndex: 0, - getFrameCount: function () { - return this.frames.length / 2; - }, - setFrame: function (frameIndex, time, angle) { - frameIndex *= 2; - this.frames[frameIndex] = time; - this.frames[frameIndex + 1] = angle; - }, - apply: function (skeleton, time, alpha) { - var frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - var bone = skeleton.bones[this.boneIndex]; - - if (time >= frames[frames.length - 2]) { // Time is after last frame. - var amount = bone.data.rotation + frames[frames.length - 1] - bone.rotation; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - bone.rotation += amount * alpha; - return; - } - - // Interpolate between the last frame and the current frame. - var frameIndex = spine.binarySearch(frames, time, 2); - var lastFrameValue = frames[frameIndex - 1]; - var frameTime = frames[frameIndex]; - var percent = 1 - (time - frameTime) / (frames[frameIndex - 2/*LAST_FRAME_TIME*/] - frameTime); - percent = this.curves.getCurvePercent(frameIndex / 2 - 1, percent); - - var amount = frames[frameIndex + 1/*FRAME_VALUE*/] - lastFrameValue; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - amount = bone.data.rotation + (lastFrameValue + amount * percent) - bone.rotation; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - bone.rotation += amount * alpha; - } -}; - -spine.TranslateTimeline = function (frameCount) { - this.curves = new spine.Curves(frameCount); - this.frames = []; // time, x, y, ... - this.frames.length = frameCount * 3; -}; -spine.TranslateTimeline.prototype = { - boneIndex: 0, - getFrameCount: function () { - return this.frames.length / 3; - }, - setFrame: function (frameIndex, time, x, y) { - frameIndex *= 3; - this.frames[frameIndex] = time; - this.frames[frameIndex + 1] = x; - this.frames[frameIndex + 2] = y; - }, - apply: function (skeleton, time, alpha) { - var frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - var bone = skeleton.bones[this.boneIndex]; - - if (time >= frames[frames.length - 3]) { // Time is after last frame. - bone.x += (bone.data.x + frames[frames.length - 2] - bone.x) * alpha; - bone.y += (bone.data.y + frames[frames.length - 1] - bone.y) * alpha; - return; - } - - // Interpolate between the last frame and the current frame. - var frameIndex = spine.binarySearch(frames, time, 3); - var lastFrameX = frames[frameIndex - 2]; - var lastFrameY = frames[frameIndex - 1]; - var frameTime = frames[frameIndex]; - var percent = 1 - (time - frameTime) / (frames[frameIndex + -3/*LAST_FRAME_TIME*/] - frameTime); - percent = this.curves.getCurvePercent(frameIndex / 3 - 1, percent); - - bone.x += (bone.data.x + lastFrameX + (frames[frameIndex + 1/*FRAME_X*/] - lastFrameX) * percent - bone.x) * alpha; - bone.y += (bone.data.y + lastFrameY + (frames[frameIndex + 2/*FRAME_Y*/] - lastFrameY) * percent - bone.y) * alpha; - } -}; - -spine.ScaleTimeline = function (frameCount) { - this.curves = new spine.Curves(frameCount); - this.frames = []; // time, x, y, ... - this.frames.length = frameCount * 3; -}; -spine.ScaleTimeline.prototype = { - boneIndex: 0, - getFrameCount: function () { - return this.frames.length / 3; - }, - setFrame: function (frameIndex, time, x, y) { - frameIndex *= 3; - this.frames[frameIndex] = time; - this.frames[frameIndex + 1] = x; - this.frames[frameIndex + 2] = y; - }, - apply: function (skeleton, time, alpha) { - var frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - var bone = skeleton.bones[this.boneIndex]; - - if (time >= frames[frames.length - 3]) { // Time is after last frame. - bone.scaleX += (bone.data.scaleX - 1 + frames[frames.length - 2] - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY - 1 + frames[frames.length - 1] - bone.scaleY) * alpha; - return; - } - - // Interpolate between the last frame and the current frame. - var frameIndex = spine.binarySearch(frames, time, 3); - var lastFrameX = frames[frameIndex - 2]; - var lastFrameY = frames[frameIndex - 1]; - var frameTime = frames[frameIndex]; - var percent = 1 - (time - frameTime) / (frames[frameIndex + -3/*LAST_FRAME_TIME*/] - frameTime); - percent = this.curves.getCurvePercent(frameIndex / 3 - 1, percent); - - bone.scaleX += (bone.data.scaleX - 1 + lastFrameX + (frames[frameIndex + 1/*FRAME_X*/] - lastFrameX) * percent - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY - 1 + lastFrameY + (frames[frameIndex + 2/*FRAME_Y*/] - lastFrameY) * percent - bone.scaleY) * alpha; - } -}; - -spine.ColorTimeline = function (frameCount) { - this.curves = new spine.Curves(frameCount); - this.frames = []; // time, r, g, b, a, ... - this.frames.length = frameCount * 5; -}; -spine.ColorTimeline.prototype = { - slotIndex: 0, - getFrameCount: function () { - return this.frames.length / 2; - }, - setFrame: function (frameIndex, time, x, y) { - frameIndex *= 5; - this.frames[frameIndex] = time; - this.frames[frameIndex + 1] = r; - this.frames[frameIndex + 2] = g; - this.frames[frameIndex + 3] = b; - this.frames[frameIndex + 4] = a; - }, - apply: function (skeleton, time, alpha) { - var frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - var slot = skeleton.slots[this.slotIndex]; - - if (time >= frames[frames.length - 5]) { // Time is after last frame. - var i = frames.length - 1; - slot.r = frames[i - 3]; - slot.g = frames[i - 2]; - slot.b = frames[i - 1]; - slot.a = frames[i]; - return; - } - - // Interpolate between the last frame and the current frame. - var frameIndex = spine.binarySearch(frames, time, 5); - var lastFrameR = frames[frameIndex - 4]; - var lastFrameG = frames[frameIndex - 3]; - var lastFrameB = frames[frameIndex - 2]; - var lastFrameA = frames[frameIndex - 1]; - var frameTime = frames[frameIndex]; - var percent = 1 - (time - frameTime) / (frames[frameIndex - 5/*LAST_FRAME_TIME*/] - frameTime); - percent = this.curves.getCurvePercent(frameIndex / 5 - 1, percent); - - var r = lastFrameR + (frames[frameIndex + 1/*FRAME_R*/] - lastFrameR) * percent; - var g = lastFrameG + (frames[frameIndex + 2/*FRAME_G*/] - lastFrameG) * percent; - var b = lastFrameB + (frames[frameIndex + 3/*FRAME_B*/] - lastFrameB) * percent; - var a = lastFrameA + (frames[frameIndex + 4/*FRAME_A*/] - lastFrameA) * percent; - if (alpha < 1) { - slot.r += (r - slot.r) * alpha; - slot.g += (g - slot.g) * alpha; - slot.b += (b - slot.b) * alpha; - slot.a += (a - slot.a) * alpha; - } else { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - } - } -}; - -spine.AttachmentTimeline = function (frameCount) { - this.curves = new spine.Curves(frameCount); - this.frames = []; // time, ... - this.frames.length = frameCount; - this.attachmentNames = []; // time, ... - this.attachmentNames.length = frameCount; -}; -spine.AttachmentTimeline.prototype = { - slotIndex: 0, - getFrameCount: function () { - return this.frames.length; - }, - setFrame: function (frameIndex, time, attachmentName) { - this.frames[frameIndex] = time; - this.attachmentNames[frameIndex] = attachmentName; - }, - apply: function (skeleton, time, alpha) { - var frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - var frameIndex; - if (time >= frames[frames.length - 1]) // Time is after last frame. - frameIndex = frames.length - 1; - else - frameIndex = spine.binarySearch(frames, time, 1) - 1; - - var attachmentName = this.attachmentNames[frameIndex]; - skeleton.slots[this.slotIndex].setAttachment(!attachmentName ? null : skeleton.getAttachmentBySlotIndex(this.slotIndex, attachmentName)); - } -}; - -spine.SkeletonData = function () { - this.bones = []; - this.slots = []; - this.skins = []; - this.animations = []; -}; -spine.SkeletonData.prototype = { - defaultSkin: null, - /** @return May be null. */ - findBone: function (boneName) { - var bones = this.bones; - for (var i = 0, n = bones.length; i < n; i++) - if (bones[i].name == boneName) return bones[i]; - return null; - }, - /** @return -1 if the bone was not found. */ - findBoneIndex: function (boneName) { - var bones = this.bones; - for (var i = 0, n = bones.length; i < n; i++) - if (bones[i].name == boneName) return i; - return -1; - }, - /** @return May be null. */ - findSlot: function (slotName) { - var slots = this.slots; - for (var i = 0, n = slots.length; i < n; i++) { - if (slots[i].name == slotName) return slot[i]; - } - return null; - }, - /** @return -1 if the bone was not found. */ - findSlotIndex: function (slotName) { - var slots = this.slots; - for (var i = 0, n = slots.length; i < n; i++) - if (slots[i].name == slotName) return i; - return -1; - }, - /** @return May be null. */ - findSkin: function (skinName) { - var skins = this.skins; - for (var i = 0, n = skins.length; i < n; i++) - if (skins[i].name == skinName) return skins[i]; - return null; - }, - /** @return May be null. */ - findAnimation: function (animationName) { - var animations = this.animations; - for (var i = 0, n = animations.length; i < n; i++) - if (animations[i].name == animationName) return animations[i]; - return null; - } -}; - -spine.Skeleton = function (skeletonData) { - this.data = skeletonData; - - this.bones = []; - for (var i = 0, n = skeletonData.bones.length; i < n; i++) { - var boneData = skeletonData.bones[i]; - var parent = !boneData.parent ? null : this.bones[skeletonData.bones.indexOf(boneData.parent)]; - this.bones.push(new spine.Bone(boneData, parent)); - } - - this.slots = []; - this.drawOrder = []; - for (var i = 0, n = skeletonData.slots.length; i < n; i++) { - var slotData = skeletonData.slots[i]; - var bone = this.bones[skeletonData.bones.indexOf(slotData.boneData)]; - var slot = new spine.Slot(slotData, this, bone); - this.slots.push(slot); - this.drawOrder.push(slot); - } -}; -spine.Skeleton.prototype = { - x: 0, y: 0, - skin: null, - r: 1, g: 1, b: 1, a: 1, - time: 0, - flipX: false, flipY: false, - /** Updates the world transform for each bone. */ - updateWorldTransform: function () { - var flipX = this.flipX; - var flipY = this.flipY; - var bones = this.bones; - for (var i = 0, n = bones.length; i < n; i++) - bones[i].updateWorldTransform(flipX, flipY); - }, - /** Sets the bones and slots to their setup pose values. */ - setToSetupPose: function () { - this.setBonesToSetupPose(); - this.setSlotsToSetupPose(); - }, - setBonesToSetupPose: function () { - var bones = this.bones; - for (var i = 0, n = bones.length; i < n; i++) - bones[i].setToSetupPose(); - }, - setSlotsToSetupPose: function () { - var slots = this.slots; - for (var i = 0, n = slots.length; i < n; i++) - slots[i].setToSetupPose(i); - }, - /** @return May return null. */ - getRootBone: function () { - return this.bones.length == 0 ? null : this.bones[0]; - }, - /** @return May be null. */ - findBone: function (boneName) { - var bones = this.bones; - for (var i = 0, n = bones.length; i < n; i++) - if (bones[i].data.name == boneName) return bones[i]; - return null; - }, - /** @return -1 if the bone was not found. */ - findBoneIndex: function (boneName) { - var bones = this.bones; - for (var i = 0, n = bones.length; i < n; i++) - if (bones[i].data.name == boneName) return i; - return -1; - }, - /** @return May be null. */ - findSlot: function (slotName) { - var slots = this.slots; - for (var i = 0, n = slots.length; i < n; i++) - if (slots[i].data.name == slotName) return slots[i]; - return null; - }, - /** @return -1 if the bone was not found. */ - findSlotIndex: function (slotName) { - var slots = this.slots; - for (var i = 0, n = slots.length; i < n; i++) - if (slots[i].data.name == slotName) return i; - return -1; - }, - setSkinByName: function (skinName) { - var skin = this.data.findSkin(skinName); - if (!skin) throw "Skin not found: " + skinName; - this.setSkin(skin); - }, - /** Sets the skin used to look up attachments not found in the {@link SkeletonData#getDefaultSkin() default skin}. Attachments - * from the new skin are attached if the corresponding attachment from the old skin was attached. - * @param newSkin May be null. */ - setSkin: function (newSkin) { - if (this.skin && newSkin) newSkin._attachAll(this, this.skin); - this.skin = newSkin; - }, - /** @return May be null. */ - getAttachmentBySlotName: function (slotName, attachmentName) { - return this.getAttachmentBySlotIndex(this.data.findSlotIndex(slotName), attachmentName); - }, - /** @return May be null. */ - getAttachmentBySlotIndex: function (slotIndex, attachmentName) { - if (this.skin) { - var attachment = this.skin.getAttachment(slotIndex, attachmentName); - if (attachment) return attachment; - } - if (this.data.defaultSkin) return this.data.defaultSkin.getAttachment(slotIndex, attachmentName); - return null; - }, - /** @param attachmentName May be null. */ - setAttachment: function (slotName, attachmentName) { - var slots = this.slots; - for (var i = 0, n = slots.size; i < n; i++) { - var slot = slots[i]; - if (slot.data.name == slotName) { - var attachment = null; - if (attachmentName) { - attachment = this.getAttachment(i, attachmentName); - if (attachment == null) throw "Attachment not found: " + attachmentName + ", for slot: " + slotName; - } - slot.setAttachment(attachment); - return; - } - } - throw "Slot not found: " + slotName; - }, - update: function (delta) { - time += delta; - } -}; - -spine.AttachmentType = { - region: 0 -}; - -spine.RegionAttachment = function () { - this.offset = []; - this.offset.length = 8; - this.uvs = []; - this.uvs.length = 8; -}; -spine.RegionAttachment.prototype = { - x: 0, y: 0, - rotation: 0, - scaleX: 1, scaleY: 1, - width: 0, height: 0, - rendererObject: null, - regionOffsetX: 0, regionOffsetY: 0, - regionWidth: 0, regionHeight: 0, - regionOriginalWidth: 0, regionOriginalHeight: 0, - setUVs: function (u, v, u2, v2, rotate) { - var uvs = this.uvs; - if (rotate) { - uvs[2/*X2*/] = u; - uvs[3/*Y2*/] = v2; - uvs[4/*X3*/] = u; - uvs[5/*Y3*/] = v; - uvs[6/*X4*/] = u2; - uvs[7/*Y4*/] = v; - uvs[0/*X1*/] = u2; - uvs[1/*Y1*/] = v2; - } else { - uvs[0/*X1*/] = u; - uvs[1/*Y1*/] = v2; - uvs[2/*X2*/] = u; - uvs[3/*Y2*/] = v; - uvs[4/*X3*/] = u2; - uvs[5/*Y3*/] = v; - uvs[6/*X4*/] = u2; - uvs[7/*Y4*/] = v2; - } - }, - updateOffset: function () { - var regionScaleX = this.width / this.regionOriginalWidth * this.scaleX; - var regionScaleY = this.height / this.regionOriginalHeight * this.scaleY; - var localX = -this.width / 2 * this.scaleX + this.regionOffsetX * regionScaleX; - var localY = -this.height / 2 * this.scaleY + this.regionOffsetY * regionScaleY; - var localX2 = localX + this.regionWidth * regionScaleX; - var localY2 = localY + this.regionHeight * regionScaleY; - var radians = this.rotation * Math.PI / 180; - var cos = Math.cos(radians); - var sin = Math.sin(radians); - var localXCos = localX * cos + this.x; - var localXSin = localX * sin; - var localYCos = localY * cos + this.y; - var localYSin = localY * sin; - var localX2Cos = localX2 * cos + this.x; - var localX2Sin = localX2 * sin; - var localY2Cos = localY2 * cos + this.y; - var localY2Sin = localY2 * sin; - var offset = this.offset; - offset[0/*X1*/] = localXCos - localYSin; - offset[1/*Y1*/] = localYCos + localXSin; - offset[2/*X2*/] = localXCos - localY2Sin; - offset[3/*Y2*/] = localY2Cos + localXSin; - offset[4/*X3*/] = localX2Cos - localY2Sin; - offset[5/*Y3*/] = localY2Cos + localX2Sin; - offset[6/*X4*/] = localX2Cos - localYSin; - offset[7/*Y4*/] = localYCos + localX2Sin; - }, - computeVertices: function (x, y, bone, vertices) { - x += bone.worldX; - y += bone.worldY; - var m00 = bone.m00; - var m01 = bone.m01; - var m10 = bone.m10; - var m11 = bone.m11; - var offset = this.offset; - vertices[0/*X1*/] = offset[0/*X1*/] * m00 + offset[1/*Y1*/] * m01 + x; - vertices[1/*Y1*/] = offset[0/*X1*/] * m10 + offset[1/*Y1*/] * m11 + y; - vertices[2/*X2*/] = offset[2/*X2*/] * m00 + offset[3/*Y2*/] * m01 + x; - vertices[3/*Y2*/] = offset[2/*X2*/] * m10 + offset[3/*Y2*/] * m11 + y; - vertices[4/*X3*/] = offset[4/*X3*/] * m00 + offset[5/*X3*/] * m01 + x; - vertices[5/*X3*/] = offset[4/*X3*/] * m10 + offset[5/*X3*/] * m11 + y; - vertices[6/*X4*/] = offset[6/*X4*/] * m00 + offset[7/*Y4*/] * m01 + x; - vertices[7/*Y4*/] = offset[6/*X4*/] * m10 + offset[7/*Y4*/] * m11 + y; - } -} - -spine.AnimationStateData = function (skeletonData) { - this.skeletonData = skeletonData; - this.animationToMixTime = {}; -}; -spine.AnimationStateData.prototype = { - defaultMix: 0, - setMixByName: function (fromName, toName, duration) { - var from = this.skeletonData.findAnimation(fromName); - if (!from) throw "Animation not found: " + fromName; - var to = this.skeletonData.findAnimation(toName); - if (!to) throw "Animation not found: " + toName; - this.setMix(from, to, duration); - }, - setMix: function (from, to, duration) { - this.animationToMixTime[from.name + ":" + to.name] = duration; - }, - getMix: function (from, to) { - var time = this.animationToMixTime[from.name + ":" + to.name]; - return time ? time : this.defaultMix; - } -}; - -spine.AnimationState = function (stateData) { - this.data = stateData; - this.queue = []; -}; -spine.AnimationState.prototype = { - current: null, - previous: null, - currentTime: 0, - previousTime: 0, - currentLoop: false, - previousLoop: false, - mixTime: 0, - mixDuration: 0, - update: function (delta) { - this.currentTime += delta; - this.previousTime += delta; - this.mixTime += delta; - - if (this.queue.length > 0) { - var entry = this.queue[0]; - if (this.currentTime >= entry.delay) { - this._setAnimation(entry.animation, entry.loop); - this.queue.shift(); - } - } - }, - apply: function (skeleton) { - if (!this.current) return; - if (this.previous) { - this.previous.apply(skeleton, this.previousTime, this.previousLoop); - var alpha = this.mixTime / this.mixDuration; - if (alpha >= 1) { - alpha = 1; - this.previous = null; - } - this.current.mix(skeleton, this.currentTime, this.currentLoop, alpha); - } else - this.current.apply(skeleton, this.currentTime, this.currentLoop); - }, - clearAnimation: function () { - this.previous = null; - this.current = null; - this.queue.length = 0; - }, - _setAnimation: function (animation, loop) { - this.previous = null; - if (animation && this.current) { - this.mixDuration = this.data.getMix(this.current, animation); - if (this.mixDuration > 0) { - this.mixTime = 0; - this.previous = this.current; - this.previousTime = this.currentTime; - this.previousLoop = this.currentLoop; - } - } - this.current = animation; - this.currentLoop = loop; - this.currentTime = 0; - }, - /** @see #setAnimation(Animation, Boolean) */ - setAnimationByName: function (animationName, loop) { - var animation = this.data.skeletonData.findAnimation(animationName); - if (!animation) throw "Animation not found: " + animationName; - this.setAnimation(animation, loop); - }, - /** Set the current animation. Any queued animations are cleared and the current animation time is set to 0. - * @param animation May be null. */ - setAnimation: function (animation, loop) { - this.queue.length = 0; - this._setAnimation(animation, loop); - }, - /** @see #addAnimation(Animation, Boolean, Number) */ - addAnimationByName: function (animationName, loop, delay) { - var animation = this.data.skeletonData.findAnimation(animationName); - if (!animation) throw "Animation not found: " + animationName; - this.addAnimation(animation, loop, delay); - }, - /** Adds an animation to be played delay seconds after the current or last queued animation. - * @param delay May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. */ - addAnimation: function (animation, loop, delay) { - var entry = {}; - entry.animation = animation; - entry.loop = loop; - - if (!delay || delay <= 0) { - var previousAnimation = this.queue.length == 0 ? this.current : this.queue[this.queue.length - 1].animation; - if (previousAnimation != null) - delay = previousAnimation.duration - this.data.getMix(previousAnimation, animation) + (delay || 0); - else - delay = 0; - } - entry.delay = delay; - - this.queue.push(entry); - }, - /** Returns true if no animation is set or if the current time is greater than the animation duration, regardless of looping. */ - isComplete: function () { - return !this.current || this.currentTime >= this.current.duration; - } -}; - -spine.SkeletonJson = function (attachmentLoader) { - this.attachmentLoader = attachmentLoader; -}; -spine.SkeletonJson.prototype = { - scale: 1, - readSkeletonData: function (root) { - var skeletonData = new spine.SkeletonData(); - - // Bones. - var bones = root["bones"]; - for (var i = 0, n = bones.length; i < n; i++) { - var boneMap = bones[i]; - var parent = null; - if (boneMap["parent"]) { - parent = skeletonData.findBone(boneMap["parent"]); - if (!parent) throw "Parent bone not found: " + boneMap["parent"]; - } - var boneData = new spine.BoneData(boneMap["name"], parent); - boneData.length = (boneMap["length"] || 0) * this.scale; - boneData.x = (boneMap["x"] || 0) * this.scale; - boneData.y = (boneMap["y"] || 0) * this.scale; - boneData.rotation = (boneMap["rotation"] || 0); - boneData.scaleX = boneMap["scaleX"] || 1; - boneData.scaleY = boneMap["scaleY"] || 1; - skeletonData.bones.push(boneData); - } - - // Slots. - var slots = root["slots"]; - for (var i = 0, n = slots.length; i < n; i++) { - var slotMap = slots[i]; - var boneData = skeletonData.findBone(slotMap["bone"]); - if (!boneData) throw "Slot bone not found: " + slotMap["bone"]; - var slotData = new spine.SlotData(slotMap["name"], boneData); - - var color = slotMap["color"]; - if (color) { - slotData.r = spine.SkeletonJson.toColor(color, 0); - slotData.g = spine.SkeletonJson.toColor(color, 1); - slotData.b = spine.SkeletonJson.toColor(color, 2); - slotData.a = spine.SkeletonJson.toColor(color, 3); - } - - slotData.attachmentName = slotMap["attachment"]; - - skeletonData.slots.push(slotData); - } - - // Skins. - var skins = root["skins"]; - for (var skinName in skins) { - if (!skins.hasOwnProperty(skinName)) continue; - var skinMap = skins[skinName]; - var skin = new spine.Skin(skinName); - for (var slotName in skinMap) { - if (!skinMap.hasOwnProperty(slotName)) continue; - var slotIndex = skeletonData.findSlotIndex(slotName); - var slotEntry = skinMap[slotName]; - for (var attachmentName in slotEntry) { - if (!slotEntry.hasOwnProperty(attachmentName)) continue; - var attachment = this.readAttachment(skin, attachmentName, slotEntry[attachmentName]); - if (attachment != null) skin.addAttachment(slotIndex, attachmentName, attachment); - } - } - skeletonData.skins.push(skin); - if (skin.name == "default") skeletonData.defaultSkin = skin; - } - - // Animations. - var animations = root["animations"]; - for (var animationName in animations) { - if (!animations.hasOwnProperty(animationName)) continue; - this.readAnimation(animationName, animations[animationName], skeletonData); - } - - return skeletonData; - }, - readAttachment: function (skin, name, map) { - name = map["name"] || name; - - var type = spine.AttachmentType[map["type"] || "region"]; - - if (type == spine.AttachmentType.region) { - var attachment = new spine.RegionAttachment(); - attachment.x = (map["x"] || 0) * this.scale; - attachment.y = (map["y"] || 0) * this.scale; - attachment.scaleX = map["scaleX"] || 1; - attachment.scaleY = map["scaleY"] || 1; - attachment.rotation = map["rotation"] || 0; - attachment.width = (map["width"] || 32) * this.scale; - attachment.height = (map["height"] || 32) * this.scale; - attachment.updateOffset(); - - attachment.rendererObject = {}; - attachment.rendererObject.name = name; - attachment.rendererObject.scale = {}; - attachment.rendererObject.scale.x = attachment.scaleX; - attachment.rendererObject.scale.y = attachment.scaleY; - attachment.rendererObject.rotation = -attachment.rotation * Math.PI / 180; - return attachment; - } - - throw "Unknown attachment type: " + type; - }, - - readAnimation: function (name, map, skeletonData) { - var timelines = []; - var duration = 0; - - var bones = map["bones"]; - for (var boneName in bones) { - if (!bones.hasOwnProperty(boneName)) continue; - var boneIndex = skeletonData.findBoneIndex(boneName); - if (boneIndex == -1) throw "Bone not found: " + boneName; - var boneMap = bones[boneName]; - - for (var timelineName in boneMap) { - if (!boneMap.hasOwnProperty(timelineName)) continue; - var values = boneMap[timelineName]; - if (timelineName == "rotate") { - var timeline = new spine.RotateTimeline(values.length); - timeline.boneIndex = boneIndex; - - var frameIndex = 0; - for (var i = 0, n = values.length; i < n; i++) { - var valueMap = values[i]; - timeline.setFrame(frameIndex, valueMap["time"], valueMap["angle"]); - spine.SkeletonJson.readCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.push(timeline); - duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 2 - 2]); - - } else if (timelineName == "translate" || timelineName == "scale") { - var timeline; - var timelineScale = 1; - if (timelineName == "scale") - timeline = new spine.ScaleTimeline(values.length); - else { - timeline = new spine.TranslateTimeline(values.length); - timelineScale = this.scale; - } - timeline.boneIndex = boneIndex; - - var frameIndex = 0; - for (var i = 0, n = values.length; i < n; i++) { - var valueMap = values[i]; - var x = (valueMap["x"] || 0) * timelineScale; - var y = (valueMap["y"] || 0) * timelineScale; - timeline.setFrame(frameIndex, valueMap["time"], x, y); - spine.SkeletonJson.readCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.push(timeline); - duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 3 - 3]); - - } else - throw "Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"; - } - } - var slots = map["slots"]; - for (var slotName in slots) { - if (!slots.hasOwnProperty(slotName)) continue; - var slotMap = slots[slotName]; - var slotIndex = skeletonData.findSlotIndex(slotName); - - for (var timelineName in slotMap) { - if (!slotMap.hasOwnProperty(timelineName)) continue; - var values = slotMap[timelineName]; - if (timelineName == "color") { - var timeline = new spine.ColorTimeline(values.length); - timeline.slotIndex = slotIndex; - - var frameIndex = 0; - for (var i = 0, n = values.length; i < n; i++) { - var valueMap = values[i]; - var color = valueMap["color"]; - var r = spine.SkeletonJson.toColor(color, 0); - var g = spine.SkeletonJson.toColor(color, 1); - var b = spine.SkeletonJson.toColor(color, 2); - var a = spine.SkeletonJson.toColor(color, 3); - timeline.setFrame(frameIndex, valueMap["time"], r, g, b, a); - spine.SkeletonJson.readCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.push(timeline); - duration = Math.max(duration, timeline.frames[timeline.getFrameCount() * 5 - 5]); - - } else if (timelineName == "attachment") { - var timeline = new spine.AttachmentTimeline(values.length); - timeline.slotIndex = slotIndex; - - var frameIndex = 0; - for (var i = 0, n = values.length; i < n; i++) { - var valueMap = values[i]; - timeline.setFrame(frameIndex++, valueMap["time"], valueMap["name"]); - } - timelines.push(timeline); - duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]); - - } else - throw "Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"; - } - } - skeletonData.animations.push(new spine.Animation(name, timelines, duration)); - } -}; -spine.SkeletonJson.readCurve = function (timeline, frameIndex, valueMap) { - var curve = valueMap["curve"]; - if (!curve) return; - if (curve == "stepped") - timeline.curves.setStepped(frameIndex); - else if (curve instanceof Array) - timeline.curves.setCurve(frameIndex, curve[0], curve[1], curve[2], curve[3]); -}; -spine.SkeletonJson.toColor = function (hexString, colorIndex) { - if (hexString.length != 8) throw "Color hexidecimal length must be 8, recieved: " + hexString; - return parseInt(hexString.substring(colorIndex * 2, 2), 16) / 255; -}; - -spine.Atlas = function (atlasText, textureLoader) { - this.textureLoader = textureLoader; - this.pages = []; - this.regions = []; - - var reader = new spine.AtlasReader(atlasText); - var tuple = []; - tuple.length = 4; - var page = null; - while (true) { - var line = reader.readLine(); - if (line == null) break; - line = reader.trim(line); - if (line.length == 0) - page = null; - else if (!page) { - page = new spine.AtlasPage(); - page.name = line; - - page.format = spine.Atlas.Format[reader.readValue()]; - - reader.readTuple(tuple); - page.minFilter = spine.Atlas.TextureFilter[tuple[0]]; - page.magFilter = spine.Atlas.TextureFilter[tuple[1]]; - - var direction = reader.readValue(); - page.uWrap = spine.Atlas.TextureWrap.clampToEdge; - page.vWrap = spine.Atlas.TextureWrap.clampToEdge; - if (direction == "x") - page.uWrap = spine.Atlas.TextureWrap.repeat; - else if (direction == "y") - page.vWrap = spine.Atlas.TextureWrap.repeat; - else if (direction == "xy") - page.uWrap = page.vWrap = spine.Atlas.TextureWrap.repeat; - - textureLoader.load(page, line); - - this.pages.push(page); - - } else { - var region = new spine.AtlasRegion(); - region.name = line; - region.page = page; - - region.rotate = reader.readValue() == "true"; - - reader.readTuple(tuple); - var x = parseInt(tuple[0]); - var y = parseInt(tuple[1]); - - reader.readTuple(tuple); - var width = parseInt(tuple[0]); - var height = parseInt(tuple[1]); - - region.u = x / page.width; - region.v = y / page.height; - if (region.rotate) { - region.u2 = (x + height) / page.width; - region.v2 = (y + width) / page.height; - } else { - region.u2 = (x + width) / page.width; - region.v2 = (y + height) / page.height; - } - region.x = x; - region.y = y; - region.width = Math.abs(width); - region.height = Math.abs(height); - - if (reader.readTuple(tuple) == 4) { // split is optional - region.splits = [parseInt(tuple[0]), parseInt(tuple[1]), parseInt(tuple[2]), parseInt(tuple[3])]; - - if (reader.readTuple(tuple) == 4) { // pad is optional, but only present with splits - region.pads = [parseInt(tuple[0]), parseInt(tuple[1]), parseInt(tuple[2]), parseInt(tuple[3])]; - - reader.readTuple(tuple); - } - } - - region.originalWidth = parseInt(tuple[0]); - region.originalHeight = parseInt(tuple[1]); - - reader.readTuple(tuple); - region.offsetX = parseInt(tuple[0]); - region.offsetY = parseInt(tuple[1]); - - region.index = parseInt(reader.readValue()); - - this.regions.push(region); - } - } -}; -spine.Atlas.prototype = { - findRegion: function (name) { - var regions = this.regions; - for (var i = 0, n = regions.length; i < n; i++) - if (regions[i].name == name) return regions[i]; - return null; - }, - dispose: function () { - var pages = this.pages; - for (var i = 0, n = pages.length; i < n; i++) - this.textureLoader.unload(pages[i].rendererObject); - }, - updateUVs: function (page) { - var regions = this.regions; - for (var i = 0, n = regions.length; i < n; i++) { - var region = regions[i]; - if (region.page != page) continue; - region.u = region.x / page.width; - region.v = region.y / page.height; - if (region.rotate) { - region.u2 = (region.x + region.height) / page.width; - region.v2 = (region.y + region.width) / page.height; - } else { - region.u2 = (region.x + region.width) / page.width; - region.v2 = (region.y + region.height) / page.height; - } - } - } -}; - -spine.Atlas.Format = { - alpha: 0, - intensity: 1, - luminanceAlpha: 2, - rgb565: 3, - rgba4444: 4, - rgb888: 5, - rgba8888: 6 -}; - -spine.Atlas.TextureFilter = { - nearest: 0, - linear: 1, - mipMap: 2, - mipMapNearestNearest: 3, - mipMapLinearNearest: 4, - mipMapNearestLinear: 5, - mipMapLinearLinear: 6 -}; - -spine.Atlas.TextureWrap = { - mirroredRepeat: 0, - clampToEdge: 1, - repeat: 2 -}; - -spine.AtlasPage = function () {}; -spine.AtlasPage.prototype = { - name: null, - format: null, - minFilter: null, - magFilter: null, - uWrap: null, - vWrap: null, - rendererObject: null, - width: 0, - height: 0 -}; - -spine.AtlasRegion = function () {}; -spine.AtlasRegion.prototype = { - page: null, - name: null, - x: 0, y: 0, - width: 0, height: 0, - u: 0, v: 0, u2: 0, v2: 0, - offsetX: 0, offsetY: 0, - originalWidth: 0, originalHeight: 0, - index: 0, - rotate: false, - splits: null, - pads: null, -}; - -spine.AtlasReader = function (text) { - this.lines = text.split(/\r\n|\r|\n/); -}; -spine.AtlasReader.prototype = { - index: 0, - trim: function (value) { - return value.replace(/^\s+|\s+$/g, ""); - }, - readLine: function () { - if (this.index >= this.lines.length) return null; - return this.lines[this.index++]; - }, - readValue: function () { - var line = this.readLine(); - var colon = line.indexOf(":"); - if (colon == -1) throw "Invalid line: " + line; - return this.trim(line.substring(colon + 1)); - }, - /** Returns the number of tuple values read (2 or 4). */ - readTuple: function (tuple) { - var line = this.readLine(); - var colon = line.indexOf(":"); - if (colon == -1) throw "Invalid line: " + line; - var i = 0, lastMatch= colon + 1; - for (; i < 3; i++) { - var comma = line.indexOf(",", lastMatch); - if (comma == -1) { - if (i == 0) throw "Invalid line: " + line; - break; - } - tuple[i] = this.trim(line.substr(lastMatch, comma - lastMatch)); - lastMatch = comma + 1; - } - tuple[i] = this.trim(line.substring(lastMatch)); - return i + 1; - } -} - -spine.AtlasAttachmentLoader = function (atlas) { - this.atlas = atlas; -} -spine.AtlasAttachmentLoader.prototype = { - newAttachment: function (skin, type, name) { - switch (type) { - case spine.AttachmentType.region: - var region = this.atlas.findRegion(name); - if (!region) throw "Region not found in atlas: " + name + " (" + type + ")"; - var attachment = new spine.RegionAttachment(name); - attachment.rendererObject = region; - attachment.setUVs(region.u, region.v, region.u2, region.v2, region.rotate); - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } - throw "Unknown attachment type: " + type; - } -} - -PIXI.AnimCache = {}; -spine.Bone.yDown = true; diff --git a/src/pixi/extras/Strip.js b/src/pixi/extras/Strip.js index 857e41296..e7e18c58b 100644 --- a/src/pixi/extras/Strip.js +++ b/src/pixi/extras/Strip.js @@ -4,66 +4,66 @@ PIXI.Strip = function(texture, width, height) { - PIXI.DisplayObjectContainer.call( this ); - this.texture = texture; - this.blendMode = PIXI.blendModes.NORMAL; + PIXI.DisplayObjectContainer.call( this ); + this.texture = texture; + this.blendMode = PIXI.blendModes.NORMAL; - try - { - this.uvs = new Float32Array([0, 1, - 1, 1, - 1, 0, 0,1]); + try + { + this.uvs = new Float32Array([0, 1, + 1, 1, + 1, 0, 0,1]); - this.verticies = new Float32Array([0, 0, - 0,0, - 0,0, 0, - 0, 0]); + this.verticies = new Float32Array([0, 0, + 0,0, + 0,0, 0, + 0, 0]); - this.colors = new Float32Array([1, 1, 1, 1]); + this.colors = new Float32Array([1, 1, 1, 1]); - this.indices = new Uint16Array([0, 1, 2, 3]); - } - catch(error) - { - this.uvs = [0, 1, - 1, 1, - 1, 0, 0,1]; + this.indices = new Uint16Array([0, 1, 2, 3]); + } + catch(error) + { + this.uvs = [0, 1, + 1, 1, + 1, 0, 0,1]; - this.verticies = [0, 0, - 0,0, - 0,0, 0, - 0, 0]; + this.verticies = [0, 0, + 0,0, + 0,0, 0, + 0, 0]; - this.colors = [1, 1, 1, 1]; + this.colors = [1, 1, 1, 1]; - this.indices = [0, 1, 2, 3]; - } + this.indices = [0, 1, 2, 3]; + } - /* - this.uvs = new Float32Array() - this.verticies = new Float32Array() - this.colors = new Float32Array() - this.indices = new Uint16Array() -*/ - this.width = width; - this.height = height; + /* + this.uvs = new Float32Array() + this.verticies = new Float32Array() + this.colors = new Float32Array() + this.indices = new Uint16Array() + */ + this.width = width; + this.height = height; - // load the texture! - if(texture.baseTexture.hasLoaded) - { - this.width = this.texture.frame.width; - this.height = this.texture.frame.height; - this.updateFrame = true; - } - else - { - this.onTextureUpdateBind = this.onTextureUpdate.bind(this); - this.texture.addEventListener( 'update', this.onTextureUpdateBind ); - } + // load the texture! + if(texture.baseTexture.hasLoaded) + { + this.width = this.texture.frame.width; + this.height = this.texture.frame.height; + this.updateFrame = true; + } + else + { + this.onTextureUpdateBind = this.onTextureUpdate.bind(this); + this.texture.addEventListener( 'update', this.onTextureUpdateBind ); + } - this.renderable = true; -} + this.renderable = true; +}; // constructor PIXI.Strip.prototype = Object.create( PIXI.DisplayObjectContainer.prototype ); @@ -71,19 +71,18 @@ PIXI.Strip.prototype.constructor = PIXI.Strip; PIXI.Strip.prototype.setTexture = function(texture) { - //TODO SET THE TEXTURES - //TODO VISIBILITY + //TODO SET THE TEXTURES + //TODO VISIBILITY - // stop current texture - this.texture = texture; - this.width = texture.frame.width; - this.height = texture.frame.height; - this.updateFrame = true; -} + // stop current texture + this.texture = texture; + this.width = texture.frame.width; + this.height = texture.frame.height; + this.updateFrame = true; +}; -PIXI.Strip.prototype.onTextureUpdate = function(event) +PIXI.Strip.prototype.onTextureUpdate = function() { - this.updateFrame = true; -} + this.updateFrame = true; +}; // some helper functions.. - diff --git a/src/pixi/extras/TilingSprite.js b/src/pixi/extras/TilingSprite.js index be2e93fa9..821d7ca51 100644 --- a/src/pixi/extras/TilingSprite.js +++ b/src/pixi/extras/TilingSprite.js @@ -14,52 +14,52 @@ */ PIXI.TilingSprite = function(texture, width, height) { - PIXI.DisplayObjectContainer.call( this ); + PIXI.DisplayObjectContainer.call( this ); - /** - * The texture that the sprite is using - * - * @property texture - * @type Texture - */ - this.texture = texture; + /** + * The texture that the sprite is using + * + * @property texture + * @type Texture + */ + this.texture = texture; - /** - * The width of the tiling sprite - * - * @property width - * @type Number - */ - this.width = width; + /** + * The width of the tiling sprite + * + * @property width + * @type Number + */ + this.width = width; - /** - * The height of the tiling sprite - * - * @property height - * @type Number - */ - this.height = height; + /** + * The height of the tiling sprite + * + * @property height + * @type Number + */ + this.height = height; - /** - * The scaling of the image that is being tiled - * - * @property tileScale - * @type Point - */ - this.tileScale = new PIXI.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @property tileScale + * @type Point + */ + this.tileScale = new PIXI.Point(1,1); - /** - * The offset position of the image that is being tiled - * - * @property tilePosition - * @type Point - */ - this.tilePosition = new PIXI.Point(0,0); + /** + * The offset position of the image that is being tiled + * + * @property tilePosition + * @type Point + */ + this.tilePosition = new PIXI.Point(0,0); - this.renderable = true; + this.renderable = true; - this.blendMode = PIXI.blendModes.NORMAL -} + this.blendMode = PIXI.blendModes.NORMAL; +}; // constructor PIXI.TilingSprite.prototype = Object.create( PIXI.DisplayObjectContainer.prototype ); @@ -73,13 +73,13 @@ PIXI.TilingSprite.prototype.constructor = PIXI.TilingSprite; */ PIXI.TilingSprite.prototype.setTexture = function(texture) { - //TODO SET THE TEXTURES - //TODO VISIBILITY + //TODO SET THE TEXTURES + //TODO VISIBILITY - // stop current texture - this.texture = texture; - this.updateFrame = true; -} + // stop current texture + this.texture = texture; + this.updateFrame = true; +}; /** * When the texture is updated, this event will fire to update the frame @@ -88,8 +88,7 @@ PIXI.TilingSprite.prototype.setTexture = function(texture) * @param event * @private */ -PIXI.TilingSprite.prototype.onTextureUpdate = function(event) +PIXI.TilingSprite.prototype.onTextureUpdate = function() { - this.updateFrame = true; -} - + this.updateFrame = true; +}; diff --git a/src/pixi/filters/AbstractFilter.js b/src/pixi/filters/AbstractFilter.js index 4e40c653a..43665de4b 100644 --- a/src/pixi/filters/AbstractFilter.js +++ b/src/pixi/filters/AbstractFilter.js @@ -2,36 +2,34 @@ * @author Mat Groves http://matgroves.com/ @Doormat23 */ - /** * This is the base class for creating a pixi.js filter. Currently only webGL supports filters. * If you want to make a custom filter this should be your base class. * @class AbstractFilter * @constructor * @param fragmentSrc - * @param uniforms + * @param uniforms */ PIXI.AbstractFilter = function(fragmentSrc, uniforms) { - /** - * An array of passes - some filters contain a few steps this array simply stores the steps in a liniear fashion. - * For example the blur filter has two passes blurX and blurY. - * @property passes - * @type Array an array of filter objects - * @private - */ - this.passes = [this]; + /** + * An array of passes - some filters contain a few steps this array simply stores the steps in a liniear fashion. + * For example the blur filter has two passes blurX and blurY. + * @property passes + * @type Array an array of filter objects + * @private + */ + this.passes = [this]; - this.dirty = true; - this.padding = 0; + this.dirty = true; + this.padding = 0; - /** - @property uniforms - @private - */ - this.uniforms = uniforms || {}; - - this.fragmentSrc = fragmentSrc || []; -} + /** + @property uniforms + @private + */ + this.uniforms = uniforms || {}; + this.fragmentSrc = fragmentSrc || []; +}; diff --git a/src/pixi/filters/BlurFilter.js b/src/pixi/filters/BlurFilter.js index 43caa0c43..5f89bff84 100644 --- a/src/pixi/filters/BlurFilter.js +++ b/src/pixi/filters/BlurFilter.js @@ -2,10 +2,9 @@ * @author Mat Groves http://matgroves.com/ @Doormat23 */ - /** - * - * The BlurFilter applies a Gaussian blur to an object. + * + * The BlurFilter applies a Gaussian blur to an object. * The strength of the blur can be set for x- and y-axis separately (always relative to the stage). * * @class BlurFilter @@ -13,13 +12,11 @@ */ PIXI.BlurFilter = function() { - - this.blurXFilter = new PIXI.BlurXFilter(); - this.blurYFilter = new PIXI.BlurYFilter(); + this.blurXFilter = new PIXI.BlurXFilter(); + this.blurYFilter = new PIXI.BlurYFilter(); - this.passes =[this.blurXFilter, this.blurYFilter]; - -} + this.passes =[this.blurXFilter, this.blurYFilter]; +}; /** * Sets the strength of both the blurX and blurY properties simultaneously @@ -33,7 +30,7 @@ Object.defineProperty(PIXI.BlurFilter.prototype, 'blur', { return this.blurXFilter.blur; }, set: function(value) { - this.blurXFilter.blur = this.blurYFilter.blur = value; + this.blurXFilter.blur = this.blurYFilter.blur = value; } }); @@ -49,7 +46,7 @@ Object.defineProperty(PIXI.BlurFilter.prototype, 'blurX', { return this.blurXFilter.blur; }, set: function(value) { - this.blurXFilter.blur = value; + this.blurXFilter.blur = value; } }); @@ -65,6 +62,6 @@ Object.defineProperty(PIXI.BlurFilter.prototype, 'blurY', { return this.blurYFilter.blur; }, set: function(value) { - this.blurYFilter.blur = value; + this.blurYFilter.blur = value; } }); diff --git a/src/pixi/filters/BlurXFilter.js b/src/pixi/filters/BlurXFilter.js index 38a99b46f..6e7be332a 100644 --- a/src/pixi/filters/BlurXFilter.js +++ b/src/pixi/filters/BlurXFilter.js @@ -2,55 +2,52 @@ * @author Mat Groves http://matgroves.com/ @Doormat23 */ - - PIXI.BlurXFilter = function() { - PIXI.AbstractFilter.call( this ); - - this.passes = [this]; - - // set the uniforms - this.uniforms = { - blur: {type: '1f', value: 1/512}, - }; - - this.fragmentSrc = [ - "precision mediump float;", - "varying vec2 vTextureCoord;", - "varying float vColor;", - "uniform float blur;", - "uniform sampler2D uSampler;", - "void main(void) {", - "vec4 sum = vec4(0.0);", + PIXI.AbstractFilter.call( this ); - "sum += texture2D(uSampler, vec2(vTextureCoord.x - 4.0*blur, vTextureCoord.y)) * 0.05;", - "sum += texture2D(uSampler, vec2(vTextureCoord.x - 3.0*blur, vTextureCoord.y)) * 0.09;", - "sum += texture2D(uSampler, vec2(vTextureCoord.x - 2.0*blur, vTextureCoord.y)) * 0.12;", - "sum += texture2D(uSampler, vec2(vTextureCoord.x - blur, vTextureCoord.y)) * 0.15;", - "sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * 0.16;", - "sum += texture2D(uSampler, vec2(vTextureCoord.x + blur, vTextureCoord.y)) * 0.15;", - "sum += texture2D(uSampler, vec2(vTextureCoord.x + 2.0*blur, vTextureCoord.y)) * 0.12;", - "sum += texture2D(uSampler, vec2(vTextureCoord.x + 3.0*blur, vTextureCoord.y)) * 0.09;", - "sum += texture2D(uSampler, vec2(vTextureCoord.x + 4.0*blur, vTextureCoord.y)) * 0.05;", - - "gl_FragColor = sum;", + this.passes = [this]; - "}" - ]; -} + // set the uniforms + this.uniforms = { + blur: {type: '1f', value: 1/512}, + }; + + this.fragmentSrc = [ + 'precision mediump float;', + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + 'uniform float blur;', + 'uniform sampler2D uSampler;', + + 'void main(void) {', + ' vec4 sum = vec4(0.0);', + + ' sum += texture2D(uSampler, vec2(vTextureCoord.x - 4.0*blur, vTextureCoord.y)) * 0.05;', + ' sum += texture2D(uSampler, vec2(vTextureCoord.x - 3.0*blur, vTextureCoord.y)) * 0.09;', + ' sum += texture2D(uSampler, vec2(vTextureCoord.x - 2.0*blur, vTextureCoord.y)) * 0.12;', + ' sum += texture2D(uSampler, vec2(vTextureCoord.x - blur, vTextureCoord.y)) * 0.15;', + ' sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * 0.16;', + ' sum += texture2D(uSampler, vec2(vTextureCoord.x + blur, vTextureCoord.y)) * 0.15;', + ' sum += texture2D(uSampler, vec2(vTextureCoord.x + 2.0*blur, vTextureCoord.y)) * 0.12;', + ' sum += texture2D(uSampler, vec2(vTextureCoord.x + 3.0*blur, vTextureCoord.y)) * 0.09;', + ' sum += texture2D(uSampler, vec2(vTextureCoord.x + 4.0*blur, vTextureCoord.y)) * 0.05;', + + ' gl_FragColor = sum;', + '}' + ]; +}; PIXI.BlurXFilter.prototype = Object.create( PIXI.AbstractFilter.prototype ); PIXI.BlurXFilter.prototype.constructor = PIXI.BlurXFilter; - Object.defineProperty(PIXI.BlurXFilter.prototype, 'blur', { get: function() { return this.uniforms.blur.value / (1/7000); }, set: function(value) { - - this.dirty = true; - this.uniforms.blur.value = (1/7000) * value; + + this.dirty = true; + this.uniforms.blur.value = (1/7000) * value; } }); diff --git a/src/pixi/filters/BlurYFilter.js b/src/pixi/filters/BlurYFilter.js index d472f79c4..99829fb7c 100644 --- a/src/pixi/filters/BlurYFilter.js +++ b/src/pixi/filters/BlurYFilter.js @@ -2,43 +2,41 @@ * @author Mat Groves http://matgroves.com/ @Doormat23 */ - - PIXI.BlurYFilter = function() { - PIXI.AbstractFilter.call( this ); - - this.passes = [this]; - - // set the uniforms - this.uniforms = { - blur: {type: '1f', value: 1/512}, - }; - - this.fragmentSrc = [ - "precision mediump float;", - "varying vec2 vTextureCoord;", - "varying float vColor;", - "uniform float blur;", - "uniform sampler2D uSampler;", - "void main(void) {", - "vec4 sum = vec4(0.0);", + PIXI.AbstractFilter.call( this ); - "sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y - 4.0*blur)) * 0.05;", - "sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y - 3.0*blur)) * 0.09;", - "sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y - 2.0*blur)) * 0.12;", - "sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y - blur)) * 0.15;", - "sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * 0.16;", - "sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y + blur)) * 0.15;", - "sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y + 2.0*blur)) * 0.12;", - "sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y + 3.0*blur)) * 0.09;", - "sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y + 4.0*blur)) * 0.05;", - - "gl_FragColor = sum;", + this.passes = [this]; - "}" - ]; -} + // set the uniforms + this.uniforms = { + blur: {type: '1f', value: 1/512}, + }; + + this.fragmentSrc = [ + 'precision mediump float;', + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + 'uniform float blur;', + 'uniform sampler2D uSampler;', + + 'void main(void) {', + ' vec4 sum = vec4(0.0);', + + ' sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y - 4.0*blur)) * 0.05;', + ' sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y - 3.0*blur)) * 0.09;', + ' sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y - 2.0*blur)) * 0.12;', + ' sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y - blur)) * 0.15;', + ' sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * 0.16;', + ' sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y + blur)) * 0.15;', + ' sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y + 2.0*blur)) * 0.12;', + ' sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y + 3.0*blur)) * 0.09;', + ' sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y + 4.0*blur)) * 0.05;', + + ' gl_FragColor = sum;', + '}' + ]; +}; PIXI.BlurYFilter.prototype = Object.create( PIXI.AbstractFilter.prototype ); PIXI.BlurYFilter.prototype.constructor = PIXI.BlurYFilter; @@ -48,7 +46,7 @@ Object.defineProperty(PIXI.BlurYFilter.prototype, 'blur', { return this.uniforms.blur.value / (1/7000); }, set: function(value) { - //this.padding = value; - this.uniforms.blur.value = (1/7000) * value; + //this.padding = value; + this.uniforms.blur.value = (1/7000) * value; } }); diff --git a/src/pixi/filters/ColorMatrixFilter.js b/src/pixi/filters/ColorMatrixFilter.js index 1386f8b2c..122b6fbd2 100644 --- a/src/pixi/filters/ColorMatrixFilter.js +++ b/src/pixi/filters/ColorMatrixFilter.js @@ -3,41 +3,41 @@ */ /** - * - * The ColorMatrixFilter class lets you apply a 4x4 matrix transformation on the RGBA - * color and alpha values of every pixel on your displayObject to produce a result + * + * The ColorMatrixFilter class lets you apply a 4x4 matrix transformation on the RGBA + * color and alpha values of every pixel on your displayObject to produce a result * with a new set of RGBA color and alpha values. Its pretty powerful! * @class ColorMatrixFilter * @contructor */ PIXI.ColorMatrixFilter = function() { - PIXI.AbstractFilter.call( this ); - - this.passes = [this]; - - // set the uniforms - this.uniforms = { - matrix: {type: 'mat4', value: [1,0,0,0, - 0,1,0,0, - 0,0,1,0, - 0,0,0,1]}, - }; - - this.fragmentSrc = [ - "precision mediump float;", - "varying vec2 vTextureCoord;", - "varying float vColor;", - "uniform float invert;", - "uniform mat4 matrix;", - "uniform sampler2D uSampler;", - "void main(void) {", - "gl_FragColor = texture2D(uSampler, vTextureCoord) * matrix;", - "gl_FragColor = gl_FragColor * vColor;", - "}" - ]; - -} + PIXI.AbstractFilter.call( this ); + + this.passes = [this]; + + // set the uniforms + this.uniforms = { + matrix: {type: 'mat4', value: [1,0,0,0, + 0,1,0,0, + 0,0,1,0, + 0,0,0,1]}, + }; + + this.fragmentSrc = [ + 'precision mediump float;', + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + 'uniform float invert;', + 'uniform mat4 matrix;', + 'uniform sampler2D uSampler;', + + 'void main(void) {', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * matrix;', + ' gl_FragColor = gl_FragColor * vColor;', + '}' + ]; +}; PIXI.ColorMatrixFilter.prototype = Object.create( PIXI.AbstractFilter.prototype ); PIXI.ColorMatrixFilter.prototype.constructor = PIXI.ColorMatrixFilter; @@ -54,6 +54,6 @@ Object.defineProperty(PIXI.ColorMatrixFilter.prototype, 'matrix', { return this.uniforms.matrix.value; }, set: function(value) { - this.uniforms.matrix.value = value; + this.uniforms.matrix.value = value; } }); \ No newline at end of file diff --git a/src/pixi/filters/ColorStepFilter.js b/src/pixi/filters/ColorStepFilter.js index 8039af84b..c83c887be 100644 --- a/src/pixi/filters/ColorStepFilter.js +++ b/src/pixi/filters/ColorStepFilter.js @@ -2,37 +2,37 @@ * @author Mat Groves http://matgroves.com/ @Doormat23 */ - /** - * + * * This turns your displayObjects to black and white. * @class ColorStepFilter * @contructor */ PIXI.ColorStepFilter = function() { - PIXI.AbstractFilter.call( this ); - - this.passes = [this]; - - // set the uniforms - this.uniforms = { - step: {type: '1f', value: 5}, - }; - - this.fragmentSrc = [ - "precision mediump float;", - "varying vec2 vTextureCoord;", - "varying float vColor;", - "uniform sampler2D uSampler;", - "uniform float step;", - "void main(void) {", - "vec4 color = texture2D(uSampler, vTextureCoord);", - "color = floor(color * step) / step;", - "gl_FragColor = color * vColor;", - "}" - ]; -} + PIXI.AbstractFilter.call( this ); + + this.passes = [this]; + + // set the uniforms + this.uniforms = { + step: {type: '1f', value: 5}, + }; + + this.fragmentSrc = [ + 'precision mediump float;', + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + 'uniform sampler2D uSampler;', + 'uniform float step;', + + 'void main(void) {', + ' vec4 color = texture2D(uSampler, vTextureCoord);', + ' color = floor(color * step) / step;', + ' gl_FragColor = color * vColor;', + '}' + ]; +}; PIXI.ColorStepFilter.prototype = Object.create( PIXI.AbstractFilter.prototype ); PIXI.ColorStepFilter.prototype.constructor = PIXI.ColorStepFilter; @@ -46,6 +46,6 @@ Object.defineProperty(PIXI.ColorStepFilter.prototype, 'step', { return this.uniforms.step.value; }, set: function(value) { - this.uniforms.step.value = value; + this.uniforms.step.value = value; } }); diff --git a/src/pixi/filters/CrossHatchFilter.js b/src/pixi/filters/CrossHatchFilter.js index 2d26715e4..43f3c2619 100644 --- a/src/pixi/filters/CrossHatchFilter.js +++ b/src/pixi/filters/CrossHatchFilter.js @@ -2,58 +2,55 @@ * @author Mat Groves http://matgroves.com/ @Doormat23 */ - - PIXI.CrossHatchFilter = function() { - PIXI.AbstractFilter.call( this ); - - this.passes = [this]; - - // set the uniforms - this.uniforms = { - blur: {type: '1f', value: 1/512}, - }; - - this.fragmentSrc = [ - "precision mediump float;", - "varying vec2 vTextureCoord;", - "varying float vColor;", - "uniform float blur;", - "uniform sampler2D uSampler;", - "void main(void) {", - - - " float lum = length(texture2D(uSampler, vTextureCoord.xy).rgb);", - " ", - " gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);", - " ", - " if (lum < 1.00) {", - " if (mod(gl_FragCoord.x + gl_FragCoord.y, 10.0) == 0.0) {", - " gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);", - " }", - " }", - " ", - " if (lum < 0.75) {", - " if (mod(gl_FragCoord.x - gl_FragCoord.y, 10.0) == 0.0) {", - " gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);", - " }", - " }", - " ", - " if (lum < 0.50) {", - " if (mod(gl_FragCoord.x + gl_FragCoord.y - 5.0, 10.0) == 0.0) {", - " gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);", - " }", - " }", - " ", - " if (lum < 0.3) {", - " if (mod(gl_FragCoord.x - gl_FragCoord.y - 5.0, 10.0) == 0.0) {", - " gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);", - " }", - " }", - "}" - ]; -} + PIXI.AbstractFilter.call( this ); + + this.passes = [this]; + + // set the uniforms + this.uniforms = { + blur: {type: '1f', value: 1 / 512}, + }; + + this.fragmentSrc = [ + 'precision mediump float;', + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + 'uniform float blur;', + 'uniform sampler2D uSampler;', + + 'void main(void) {', + ' float lum = length(texture2D(uSampler, vTextureCoord.xy).rgb);', + + ' gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);', + + ' if (lum < 1.00) {', + ' if (mod(gl_FragCoord.x + gl_FragCoord.y, 10.0) == 0.0) {', + ' gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);', + ' }', + ' }', + + ' if (lum < 0.75) {', + ' if (mod(gl_FragCoord.x - gl_FragCoord.y, 10.0) == 0.0) {', + ' gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);', + ' }', + ' }', + + ' if (lum < 0.50) {', + ' if (mod(gl_FragCoord.x + gl_FragCoord.y - 5.0, 10.0) == 0.0) {', + ' gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);', + ' }', + ' }', + + ' if (lum < 0.3) {', + ' if (mod(gl_FragCoord.x - gl_FragCoord.y - 5.0, 10.0) == 0.0) {', + ' gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);', + ' }', + ' }', + '}' + ]; +}; PIXI.CrossHatchFilter.prototype = Object.create( PIXI.AbstractFilter.prototype ); PIXI.CrossHatchFilter.prototype.constructor = PIXI.BlurYFilter; @@ -63,7 +60,7 @@ Object.defineProperty(PIXI.CrossHatchFilter.prototype, 'blur', { return this.uniforms.blur.value / (1/7000); }, set: function(value) { - //this.padding = value; - this.uniforms.blur.value = (1/7000) * value; + //this.padding = value; + this.uniforms.blur.value = (1/7000) * value; } }); diff --git a/src/pixi/filters/DisplacementFilter.js b/src/pixi/filters/DisplacementFilter.js index 5662731f4..40af87038 100644 --- a/src/pixi/filters/DisplacementFilter.js +++ b/src/pixi/filters/DisplacementFilter.js @@ -2,10 +2,9 @@ * @author Mat Groves http://matgroves.com/ @Doormat23 */ - /** - * - * The DisplacementFilter class uses the pixel values from the specified texture (called the displacement map) to perform a displacement of an object. + * + * The DisplacementFilter class uses the pixel values from the specified texture (called the displacement map) to perform a displacement of an object. * You can use this filter to apply all manor of crazy warping effects * Currently the r property of the texture is used offset the x and the g propery of the texture is used to offset the y. * @class DisplacementFilter @@ -14,80 +13,75 @@ */ PIXI.DisplacementFilter = function(texture) { - PIXI.AbstractFilter.call( this ); - - this.passes = [this]; - texture.baseTexture._powerOf2 = true; + PIXI.AbstractFilter.call( this ); - // set the uniforms - //console.log() - this.uniforms = { - displacementMap: {type: 'sampler2D', value:texture}, - scale: {type: '2f', value:{x:30, y:30}}, - offset: {type: '2f', value:{x:0, y:0}}, - mapDimensions: {type: '2f', value:{x:1, y:5112}}, - dimensions: {type: '4fv', value:[0,0,0,0]} - }; - + this.passes = [this]; + texture.baseTexture._powerOf2 = true; - if(texture.baseTexture.hasLoaded) - { - this.uniforms.mapDimensions.value.x = texture.width; - this.uniforms.mapDimensions.value.y = texture.height; - } - else - { - this.boundLoadedFunction = this.onTextureLoaded.bind(this); + // set the uniforms + //console.log() + this.uniforms = { + displacementMap: {type: 'sampler2D', value:texture}, + scale: {type: '2f', value:{x:30, y:30}}, + offset: {type: '2f', value:{x:0, y:0}}, + mapDimensions: {type: '2f', value:{x:1, y:5112}}, + dimensions: {type: '4fv', value:[0,0,0,0]} + }; - texture.baseTexture.on("loaded", this.boundLoadedFunction); - } + if(texture.baseTexture.hasLoaded) + { + this.uniforms.mapDimensions.value.x = texture.width; + this.uniforms.mapDimensions.value.y = texture.height; + } + else + { + this.boundLoadedFunction = this.onTextureLoaded.bind(this); - this.fragmentSrc = [ - "precision mediump float;", - "varying vec2 vTextureCoord;", - "varying float vColor;", - "uniform sampler2D displacementMap;", - "uniform sampler2D uSampler;", - "uniform vec2 scale;", - "uniform vec2 offset;", - "uniform vec4 dimensions;", - "uniform vec2 mapDimensions;",// = vec2(256.0, 256.0);", - // "const vec2 textureDimensions = vec2(750.0, 750.0);", - - "void main(void) {", - "vec2 mapCords = vTextureCoord.xy;", -// "mapCords -= ;", - "mapCords += (dimensions.zw + offset)/ dimensions.xy ;", - "mapCords.y *= -1.0;", - "mapCords.y += 1.0;", - "vec2 matSample = texture2D(displacementMap, mapCords).xy;", - "matSample -= 0.5;", - "matSample *= scale;", - "matSample /= mapDimensions;", - "gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x + matSample.x, vTextureCoord.y + matSample.y));", - "gl_FragColor.rgb = mix( gl_FragColor.rgb, gl_FragColor.rgb, 1.0);", - "vec2 cord = vTextureCoord;", - - //"gl_FragColor = texture2D(displacementMap, cord);", - "gl_FragColor = gl_FragColor * vColor;", - - "}" - ]; - -} + texture.baseTexture.on('loaded', this.boundLoadedFunction); + } + + this.fragmentSrc = [ + 'precision mediump float;', + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + 'uniform sampler2D displacementMap;', + 'uniform sampler2D uSampler;', + 'uniform vec2 scale;', + 'uniform vec2 offset;', + 'uniform vec4 dimensions;', + 'uniform vec2 mapDimensions;',// = vec2(256.0, 256.0);', + // 'const vec2 textureDimensions = vec2(750.0, 750.0);', + + 'void main(void) {', + ' vec2 mapCords = vTextureCoord.xy;', + //' mapCords -= ;', + ' mapCords += (dimensions.zw + offset)/ dimensions.xy ;', + ' mapCords.y *= -1.0;', + ' mapCords.y += 1.0;', + ' vec2 matSample = texture2D(displacementMap, mapCords).xy;', + ' matSample -= 0.5;', + ' matSample *= scale;', + ' matSample /= mapDimensions;', + ' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x + matSample.x, vTextureCoord.y + matSample.y));', + ' gl_FragColor.rgb = mix( gl_FragColor.rgb, gl_FragColor.rgb, 1.0);', + ' vec2 cord = vTextureCoord;', + + //' gl_FragColor = texture2D(displacementMap, cord);', + ' gl_FragColor = gl_FragColor * vColor;', + '}' + ]; +}; PIXI.DisplacementFilter.prototype = Object.create( PIXI.AbstractFilter.prototype ); PIXI.DisplacementFilter.prototype.constructor = PIXI.DisplacementFilter; PIXI.DisplacementFilter.prototype.onTextureLoaded = function() { - - this.uniforms.mapDimensions.value.x = this.uniforms.displacementMap.value.width; - this.uniforms.mapDimensions.value.y = this.uniforms.displacementMap.value.height; + this.uniforms.mapDimensions.value.x = this.uniforms.displacementMap.value.width; + this.uniforms.mapDimensions.value.y = this.uniforms.displacementMap.value.height; - this.uniforms.displacementMap.value.baseTexture.off("loaded", this.boundLoadedFunction) - -} + this.uniforms.displacementMap.value.baseTexture.off('loaded', this.boundLoadedFunction); +}; /** * The texture used for the displacemtent map * must be power of 2 texture at the moment @@ -100,7 +94,7 @@ Object.defineProperty(PIXI.DisplacementFilter.prototype, 'map', { return this.uniforms.displacementMap.value; }, set: function(value) { - this.uniforms.displacementMap.value = value; + this.uniforms.displacementMap.value = value; } }); @@ -115,7 +109,7 @@ Object.defineProperty(PIXI.DisplacementFilter.prototype, 'scale', { return this.uniforms.scale.value; }, set: function(value) { - this.uniforms.scale.value = value; + this.uniforms.scale.value = value; } }); @@ -130,6 +124,6 @@ Object.defineProperty(PIXI.DisplacementFilter.prototype, 'offset', { return this.uniforms.offset.value; }, set: function(value) { - this.uniforms.offset.value = value; + this.uniforms.offset.value = value; } -}); \ No newline at end of file +}); diff --git a/src/pixi/filters/DotScreenFilter.js b/src/pixi/filters/DotScreenFilter.js index 6c59d4c4a..7a684a134 100644 --- a/src/pixi/filters/DotScreenFilter.js +++ b/src/pixi/filters/DotScreenFilter.js @@ -4,57 +4,57 @@ */ /** - * - * This filter applies a pixlate effect making display objects appear "blocky" + * + * This filter applies a pixlate effect making display objects appear 'blocky' * @class PixelateFilter * @contructor */ PIXI.DotScreenFilter = function() { - PIXI.AbstractFilter.call( this ); + PIXI.AbstractFilter.call( this ); - this.passes = [this]; - - // set the uniforms - this.uniforms = { - scale: {type: '1f', value:1}, - angle: {type: '1f', value:5}, - dimensions: {type: '4fv', value:[0,0,0,0]} - }; + this.passes = [this]; - this.fragmentSrc = [ - "precision mediump float;", - "varying vec2 vTextureCoord;", - "varying float vColor;", - "uniform vec4 dimensions;", - "uniform sampler2D uSampler;", + // set the uniforms + this.uniforms = { + scale: {type: '1f', value:1}, + angle: {type: '1f', value:5}, + dimensions: {type: '4fv', value:[0,0,0,0]} + }; - "uniform float angle;", - "uniform float scale;", + this.fragmentSrc = [ + 'precision mediump float;', + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + 'uniform vec4 dimensions;', + 'uniform sampler2D uSampler;', - "float pattern() {", - "float s = sin(angle), c = cos(angle);", - "vec2 tex = vTextureCoord * dimensions.xy;", - "vec2 point = vec2(", - "c * tex.x - s * tex.y,", - "s * tex.x + c * tex.y", - ") * scale;", - "return (sin(point.x) * sin(point.y)) * 4.0;", - "}", + 'uniform float angle;', + 'uniform float scale;', - "void main() {", - "vec4 color = texture2D(uSampler, vTextureCoord);", - "float average = (color.r + color.g + color.b) / 3.0;", - "gl_FragColor = vec4(vec3(average * 10.0 - 5.0 + pattern()), color.a);", - "}", - ]; -} + 'float pattern() {', + ' float s = sin(angle), c = cos(angle);', + ' vec2 tex = vTextureCoord * dimensions.xy;', + ' vec2 point = vec2(', + ' c * tex.x - s * tex.y,', + ' s * tex.x + c * tex.y', + ' ) * scale;', + ' return (sin(point.x) * sin(point.y)) * 4.0;', + '}', + + 'void main() {', + ' vec4 color = texture2D(uSampler, vTextureCoord);', + ' float average = (color.r + color.g + color.b) / 3.0;', + ' gl_FragColor = vec4(vec3(average * 10.0 - 5.0 + pattern()), color.a);', + '}' + ]; +}; PIXI.DotScreenFilter.prototype = Object.create( PIXI.DotScreenFilter.prototype ); PIXI.DotScreenFilter.prototype.constructor = PIXI.DotScreenFilter; /** - * + * * This describes the the scale * @property scale * @type Number @@ -64,13 +64,13 @@ Object.defineProperty(PIXI.DotScreenFilter.prototype, 'scale', { return this.uniforms.scale.value; }, set: function(value) { - this.dirty = true; - this.uniforms.scale.value = value; + this.dirty = true; + this.uniforms.scale.value = value; } }); /** - * + * * This radius describes angle * @property angle * @type Number @@ -80,7 +80,7 @@ Object.defineProperty(PIXI.DotScreenFilter.prototype, 'angle', { return this.uniforms.angle.value; }, set: function(value) { - this.dirty = true; - this.uniforms.angle.value = value; + this.dirty = true; + this.uniforms.angle.value = value; } }); \ No newline at end of file diff --git a/src/pixi/filters/FilterBlock.js b/src/pixi/filters/FilterBlock.js index bfcfdb771..3b6ab5204 100644 --- a/src/pixi/filters/FilterBlock.js +++ b/src/pixi/filters/FilterBlock.js @@ -6,6 +6,6 @@ PIXI.FilterBlock = function() { - this.visible = true; - this.renderable = true; -} \ No newline at end of file + this.visible = true; + this.renderable = true; +}; diff --git a/src/pixi/filters/GrayFilter.js b/src/pixi/filters/GrayFilter.js index 9b6bb61db..5065834a9 100644 --- a/src/pixi/filters/GrayFilter.js +++ b/src/pixi/filters/GrayFilter.js @@ -2,37 +2,37 @@ * @author Mat Groves http://matgroves.com/ @Doormat23 */ - /** - * + * * This turns your displayObjects to black and white. * @class GrayFilter * @contructor */ PIXI.GrayFilter = function() { - PIXI.AbstractFilter.call( this ); - - this.passes = [this]; - - // set the uniforms - this.uniforms = { - gray: {type: '1f', value: 1}, - }; - - this.fragmentSrc = [ - "precision mediump float;", - "varying vec2 vTextureCoord;", - "varying float vColor;", - "uniform sampler2D uSampler;", - "uniform float gray;", - "void main(void) {", - "gl_FragColor = texture2D(uSampler, vTextureCoord);", - "gl_FragColor.rgb = mix(gl_FragColor.rgb, vec3(0.2126*gl_FragColor.r + 0.7152*gl_FragColor.g + 0.0722*gl_FragColor.b), gray);", - "gl_FragColor = gl_FragColor * vColor;", - "}" - ]; -} + PIXI.AbstractFilter.call( this ); + + this.passes = [this]; + + // set the uniforms + this.uniforms = { + gray: {type: '1f', value: 1}, + }; + + this.fragmentSrc = [ + 'precision mediump float;', + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + 'uniform sampler2D uSampler;', + 'uniform float gray;', + + 'void main(void) {', + ' gl_FragColor = texture2D(uSampler, vTextureCoord);', + ' gl_FragColor.rgb = mix(gl_FragColor.rgb, vec3(0.2126*gl_FragColor.r + 0.7152*gl_FragColor.g + 0.0722*gl_FragColor.b), gray);', + ' gl_FragColor = gl_FragColor * vColor;', + '}' + ]; +}; PIXI.GrayFilter.prototype = Object.create( PIXI.AbstractFilter.prototype ); PIXI.GrayFilter.prototype.constructor = PIXI.GrayFilter; @@ -46,6 +46,6 @@ Object.defineProperty(PIXI.GrayFilter.prototype, 'gray', { return this.uniforms.gray.value; }, set: function(value) { - this.uniforms.gray.value = value; + this.uniforms.gray.value = value; } }); diff --git a/src/pixi/filters/InvertFilter.js b/src/pixi/filters/InvertFilter.js index afd196e1e..ac4393f91 100644 --- a/src/pixi/filters/InvertFilter.js +++ b/src/pixi/filters/InvertFilter.js @@ -3,37 +3,37 @@ */ /** - * + * * This inverts your displayObjects colors. * @class InvertFilter * @contructor */ PIXI.InvertFilter = function() { - PIXI.AbstractFilter.call( this ); - - this.passes = [this]; - - // set the uniforms - this.uniforms = { - invert: {type: '1f', value: 1}, - }; - - this.fragmentSrc = [ - "precision mediump float;", - "varying vec2 vTextureCoord;", - "varying float vColor;", - "uniform float invert;", - "uniform sampler2D uSampler;", - "void main(void) {", - "gl_FragColor = texture2D(uSampler, vTextureCoord);", - "gl_FragColor.rgb = mix( (vec3(1)-gl_FragColor.rgb) * gl_FragColor.a, gl_FragColor.rgb, 1.0 - invert);", - //"gl_FragColor.rgb = gl_FragColor.rgb * gl_FragColor.a;", - "gl_FragColor = gl_FragColor * vColor;", - "}" - ]; - -} + PIXI.AbstractFilter.call( this ); + + this.passes = [this]; + + // set the uniforms + this.uniforms = { + invert: {type: '1f', value: 1}, + }; + + this.fragmentSrc = [ + 'precision mediump float;', + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + 'uniform float invert;', + 'uniform sampler2D uSampler;', + + 'void main(void) {', + ' gl_FragColor = texture2D(uSampler, vTextureCoord);', + ' gl_FragColor.rgb = mix( (vec3(1)-gl_FragColor.rgb) * gl_FragColor.a, gl_FragColor.rgb, 1.0 - invert);', + //' gl_FragColor.rgb = gl_FragColor.rgb * gl_FragColor.a;', + ' gl_FragColor = gl_FragColor * vColor;', + '}' + ]; +}; PIXI.InvertFilter.prototype = Object.create( PIXI.AbstractFilter.prototype ); PIXI.InvertFilter.prototype.constructor = PIXI.InvertFilter; @@ -47,6 +47,6 @@ Object.defineProperty(PIXI.InvertFilter.prototype, 'invert', { return this.uniforms.invert.value; }, set: function(value) { - this.uniforms.invert.value = value; + this.uniforms.invert.value = value; } -}); \ No newline at end of file +}); diff --git a/src/pixi/filters/PixelateFilter.js b/src/pixi/filters/PixelateFilter.js index d26801586..e4320ebff 100644 --- a/src/pixi/filters/PixelateFilter.js +++ b/src/pixi/filters/PixelateFilter.js @@ -3,50 +3,49 @@ */ /** - * - * This filter applies a pixlate effect making display objects appear "blocky" + * + * This filter applies a pixlate effect making display objects appear 'blocky' * @class PixelateFilter * @contructor */ PIXI.PixelateFilter = function() { - PIXI.AbstractFilter.call( this ); + PIXI.AbstractFilter.call( this ); - this.passes = [this]; - - // set the uniforms - this.uniforms = { - invert: {type: '1f', value: 0}, - dimensions: {type: '4fv', value:new Float32Array([10000, 100, 10, 10])}, - pixelSize: {type: '2f', value:{x:10, y:10}}, - }; + this.passes = [this]; - this.fragmentSrc = [ - "precision mediump float;", - "varying vec2 vTextureCoord;", - "varying float vColor;", - "uniform vec2 testDim;", - "uniform vec4 dimensions;", - "uniform vec2 pixelSize;", - "uniform sampler2D uSampler;", - "void main(void) {", - "vec2 coord = vTextureCoord;", + // set the uniforms + this.uniforms = { + invert: {type: '1f', value: 0}, + dimensions: {type: '4fv', value:new Float32Array([10000, 100, 10, 10])}, + pixelSize: {type: '2f', value:{x:10, y:10}}, + }; - "vec2 size = dimensions.xy/pixelSize;", + this.fragmentSrc = [ + 'precision mediump float;', + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + 'uniform vec2 testDim;', + 'uniform vec4 dimensions;', + 'uniform vec2 pixelSize;', + 'uniform sampler2D uSampler;', - "vec2 color = floor( ( vTextureCoord * size ) ) / size + pixelSize/dimensions.xy * 0.5;", - "gl_FragColor = texture2D(uSampler, color);", - "}" - ]; - + 'void main(void) {', + ' vec2 coord = vTextureCoord;', -} + ' vec2 size = dimensions.xy/pixelSize;', + + ' vec2 color = floor( ( vTextureCoord * size ) ) / size + pixelSize/dimensions.xy * 0.5;', + ' gl_FragColor = texture2D(uSampler, color);', + '}' + ]; +}; PIXI.PixelateFilter.prototype = Object.create( PIXI.AbstractFilter.prototype ); PIXI.PixelateFilter.prototype.constructor = PIXI.PixelateFilter; /** - * + * * This a point that describes the size of the blocs. x is the width of the block and y is the the height * @property size * @type Point @@ -56,7 +55,7 @@ Object.defineProperty(PIXI.PixelateFilter.prototype, 'size', { return this.uniforms.pixelSize.value; }, set: function(value) { - this.dirty = true; - this.uniforms.pixelSize.value = value; + this.dirty = true; + this.uniforms.pixelSize.value = value; } -}); \ No newline at end of file +}); diff --git a/src/pixi/filters/RGBSplitFilter.js b/src/pixi/filters/RGBSplitFilter.js index 35a837ebd..81439dd5b 100644 --- a/src/pixi/filters/RGBSplitFilter.js +++ b/src/pixi/filters/RGBSplitFilter.js @@ -2,39 +2,38 @@ * @author Mat Groves http://matgroves.com/ @Doormat23 */ - - PIXI.RGBSplitFilter = function() { - PIXI.AbstractFilter.call( this ); - - this.passes = [this]; - - // set the uniforms - this.uniforms = { - red: {type: '2f', value: {x:20, y:20}}, - green: {type: '2f', value: {x:-20, y:20}}, - blue: {type: '2f', value: {x:20, y:-20}}, - dimensions: {type: '4fv', value:[0,0,0,0]} - }; - - this.fragmentSrc = [ - "precision mediump float;", - "varying vec2 vTextureCoord;", - "varying float vColor;", - "uniform vec2 red;", - "uniform vec2 green;", - "uniform vec2 blue;", - "uniform vec4 dimensions;", - "uniform sampler2D uSampler;", - "void main(void) {", - "gl_FragColor.r = texture2D(uSampler, vTextureCoord + red/dimensions.xy).r;", - "gl_FragColor.g = texture2D(uSampler, vTextureCoord + green/dimensions.xy).g;", - "gl_FragColor.b = texture2D(uSampler, vTextureCoord + blue/dimensions.xy).b;", - "gl_FragColor.a = texture2D(uSampler, vTextureCoord).a;", - "}" - ]; -} + PIXI.AbstractFilter.call( this ); + + this.passes = [this]; + + // set the uniforms + this.uniforms = { + red: {type: '2f', value: {x:20, y:20}}, + green: {type: '2f', value: {x:-20, y:20}}, + blue: {type: '2f', value: {x:20, y:-20}}, + dimensions: {type: '4fv', value:[0,0,0,0]} + }; + + this.fragmentSrc = [ + 'precision mediump float;', + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + 'uniform vec2 red;', + 'uniform vec2 green;', + 'uniform vec2 blue;', + 'uniform vec4 dimensions;', + 'uniform sampler2D uSampler;', + + 'void main(void) {', + ' gl_FragColor.r = texture2D(uSampler, vTextureCoord + red/dimensions.xy).r;', + ' gl_FragColor.g = texture2D(uSampler, vTextureCoord + green/dimensions.xy).g;', + ' gl_FragColor.b = texture2D(uSampler, vTextureCoord + blue/dimensions.xy).b;', + ' gl_FragColor.a = texture2D(uSampler, vTextureCoord).a;', + '}' + ]; +}; PIXI.RGBSplitFilter.prototype = Object.create( PIXI.AbstractFilter.prototype ); PIXI.RGBSplitFilter.prototype.constructor = PIXI.RGBSplitFilter; @@ -44,7 +43,7 @@ Object.defineProperty(PIXI.RGBSplitFilter.prototype, 'angle', { return this.uniforms.blur.value / (1/7000); }, set: function(value) { - //this.padding = value; - this.uniforms.blur.value = (1/7000) * value; + //this.padding = value; + this.uniforms.blur.value = (1/7000) * value; } }); diff --git a/src/pixi/filters/SepiaFilter.js b/src/pixi/filters/SepiaFilter.js index 22f31a43c..f114d7617 100644 --- a/src/pixi/filters/SepiaFilter.js +++ b/src/pixi/filters/SepiaFilter.js @@ -1,42 +1,40 @@ -/** /** * @author Mat Groves http://matgroves.com/ @Doormat23 */ - /** - * + * * This applies a sepia effect to your displayObjects. * @class SepiaFilter * @contructor */ PIXI.SepiaFilter = function() { - PIXI.AbstractFilter.call( this ); - - this.passes = [this]; - - // set the uniforms - this.uniforms = { - sepia: {type: '1f', value: 1}, - }; - - this.fragmentSrc = [ - "precision mediump float;", - "varying vec2 vTextureCoord;", - "varying float vColor;", - "uniform float sepia;", - "uniform sampler2D uSampler;", - - "const mat3 sepiaMatrix = mat3(0.3588, 0.7044, 0.1368, 0.2990, 0.5870, 0.1140, 0.2392, 0.4696, 0.0912);", - "void main(void) {", - "gl_FragColor = texture2D(uSampler, vTextureCoord);", - "gl_FragColor.rgb = mix( gl_FragColor.rgb, gl_FragColor.rgb * sepiaMatrix, sepia);", - "gl_FragColor = gl_FragColor * vColor;", - "}" - ]; - -} + PIXI.AbstractFilter.call( this ); + + this.passes = [this]; + + // set the uniforms + this.uniforms = { + sepia: {type: '1f', value: 1}, + }; + + this.fragmentSrc = [ + 'precision mediump float;', + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + 'uniform float sepia;', + 'uniform sampler2D uSampler;', + + 'const mat3 sepiaMatrix = mat3(0.3588, 0.7044, 0.1368, 0.2990, 0.5870, 0.1140, 0.2392, 0.4696, 0.0912);', + + 'void main(void) {', + ' gl_FragColor = texture2D(uSampler, vTextureCoord);', + ' gl_FragColor.rgb = mix( gl_FragColor.rgb, gl_FragColor.rgb * sepiaMatrix, sepia);', + ' gl_FragColor = gl_FragColor * vColor;', + '}' + ]; +}; PIXI.SepiaFilter.prototype = Object.create( PIXI.AbstractFilter.prototype ); PIXI.SepiaFilter.prototype.constructor = PIXI.SepiaFilter; @@ -50,6 +48,6 @@ Object.defineProperty(PIXI.SepiaFilter.prototype, 'sepia', { return this.uniforms.sepia.value; }, set: function(value) { - this.uniforms.sepia.value = value; + this.uniforms.sepia.value = value; } }); diff --git a/src/pixi/filters/SmartBlurFilter.js b/src/pixi/filters/SmartBlurFilter.js index 93567e852..1140a562c 100644 --- a/src/pixi/filters/SmartBlurFilter.js +++ b/src/pixi/filters/SmartBlurFilter.js @@ -2,54 +2,51 @@ * @author Mat Groves http://matgroves.com/ @Doormat23 */ - - PIXI.SmartBlurFilter = function() { - PIXI.AbstractFilter.call( this ); - - this.passes = [this]; - - // set the uniforms - this.uniforms = { - blur: {type: '1f', value: 1/512}, - }; - - this.fragmentSrc = [ - "precision mediump float;", - "varying vec2 vTextureCoord;", - "uniform sampler2D uSampler;", - // "uniform vec2 delta;", - "const vec2 delta = vec2(1.0/10.0, 0.0);", - // "uniform float darkness;", - - "float random(vec3 scale, float seed) {", - "return fract(sin(dot(gl_FragCoord.xyz + seed, scale)) * 43758.5453 + seed);", - "}", - - - "void main(void) {", - - "vec4 color = vec4(0.0);", - "float total = 0.0;", - - "float offset = random(vec3(12.9898, 78.233, 151.7182), 0.0);", - - "for (float t = -30.0; t <= 30.0; t++) {", - "float percent = (t + offset - 0.5) / 30.0;", - "float weight = 1.0 - abs(percent);", - "vec4 sample = texture2D(uSampler, vTextureCoord + delta * percent);", - "sample.rgb *= sample.a;", - "color += sample * weight;", - "total += weight;", - "}", - - "gl_FragColor = color / total;", - "gl_FragColor.rgb /= gl_FragColor.a + 0.00001;", - // "gl_FragColor.rgb *= darkness;", - "}" - ]; -} + PIXI.AbstractFilter.call( this ); + + this.passes = [this]; + + // set the uniforms + this.uniforms = { + blur: {type: '1f', value: 1/512}, + }; + + this.fragmentSrc = [ + 'precision mediump float;', + 'varying vec2 vTextureCoord;', + 'uniform sampler2D uSampler;', + //'uniform vec2 delta;', + 'const vec2 delta = vec2(1.0/10.0, 0.0);', + //'uniform float darkness;', + + 'float random(vec3 scale, float seed) {', + ' return fract(sin(dot(gl_FragCoord.xyz + seed, scale)) * 43758.5453 + seed);', + '}', + + + 'void main(void) {', + ' vec4 color = vec4(0.0);', + ' float total = 0.0;', + + ' float offset = random(vec3(12.9898, 78.233, 151.7182), 0.0);', + + ' for (float t = -30.0; t <= 30.0; t++) {', + ' float percent = (t + offset - 0.5) / 30.0;', + ' float weight = 1.0 - abs(percent);', + ' vec4 sample = texture2D(uSampler, vTextureCoord + delta * percent);', + ' sample.rgb *= sample.a;', + ' color += sample * weight;', + ' total += weight;', + ' }', + + ' gl_FragColor = color / total;', + ' gl_FragColor.rgb /= gl_FragColor.a + 0.00001;', + //' gl_FragColor.rgb *= darkness;', + '}' + ]; +}; PIXI.SmartBlurFilter.prototype = Object.create( PIXI.AbstractFilter.prototype ); PIXI.SmartBlurFilter.prototype.constructor = PIXI.SmartBlurFilter; @@ -59,6 +56,6 @@ Object.defineProperty(PIXI.SmartBlurFilter.prototype, 'blur', { return this.uniforms.blur.value; }, set: function(value) { - this.uniforms.blur.value = value; + this.uniforms.blur.value = value; } }); diff --git a/src/pixi/filters/TwistFilter.js b/src/pixi/filters/TwistFilter.js index 0dce008bf..c529dd78b 100644 --- a/src/pixi/filters/TwistFilter.js +++ b/src/pixi/filters/TwistFilter.js @@ -3,59 +3,57 @@ */ /** - * - * This filter applies a pixlate effect making display objects appear "blocky" + * + * This filter applies a pixlate effect making display objects appear 'blocky' * @class PixelateFilter * @contructor */ PIXI.TwistFilter = function() { - PIXI.AbstractFilter.call( this ); + PIXI.AbstractFilter.call( this ); - this.passes = [this]; - - // set the uniforms - this.uniforms = { - radius: {type: '1f', value:0.5}, - angle: {type: '1f', value:5}, - offset: {type: '2f', value:{x:0.5, y:0.5}}, - }; + this.passes = [this]; - this.fragmentSrc = [ - "precision mediump float;", - "varying vec2 vTextureCoord;", - "varying float vColor;", - "uniform vec4 dimensions;", - "uniform sampler2D uSampler;", - - "uniform float radius;", - "uniform float angle;", - "uniform vec2 offset;", + // set the uniforms + this.uniforms = { + radius: {type: '1f', value:0.5}, + angle: {type: '1f', value:5}, + offset: {type: '2f', value:{x:0.5, y:0.5}}, + }; - "void main(void) {", - "vec2 coord = vTextureCoord - offset;", - "float distance = length(coord);", - - "if (distance < radius){", + this.fragmentSrc = [ + 'precision mediump float;', + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + 'uniform vec4 dimensions;', + 'uniform sampler2D uSampler;', - "float ratio = (radius - distance) / radius;", - "float angleMod = ratio * ratio * angle;", - "float s = sin(angleMod);", - "float c = cos(angleMod);", - "coord = vec2(coord.x * c - coord.y * s, coord.x * s + coord.y * c);", + 'uniform float radius;', + 'uniform float angle;', + 'uniform vec2 offset;', - "}", + 'void main(void) {', + ' vec2 coord = vTextureCoord - offset;', + ' float distance = length(coord);', - "gl_FragColor = texture2D(uSampler, coord+offset);", - "}" - ]; -} + ' if (distance < radius) {', + ' float ratio = (radius - distance) / radius;', + ' float angleMod = ratio * ratio * angle;', + ' float s = sin(angleMod);', + ' float c = cos(angleMod);', + ' coord = vec2(coord.x * c - coord.y * s, coord.x * s + coord.y * c);', + ' }', + + ' gl_FragColor = texture2D(uSampler, coord+offset);', + '}' + ]; +}; PIXI.TwistFilter.prototype = Object.create( PIXI.AbstractFilter.prototype ); PIXI.TwistFilter.prototype.constructor = PIXI.TwistFilter; /** - * + * * This point describes the the offset of the twist * @property size * @type Point @@ -65,13 +63,13 @@ Object.defineProperty(PIXI.TwistFilter.prototype, 'offset', { return this.uniforms.offset.value; }, set: function(value) { - this.dirty = true; - this.uniforms.offset.value = value; + this.dirty = true; + this.uniforms.offset.value = value; } }); /** - * + * * This radius describes size of the twist * @property size * @type Number @@ -81,13 +79,13 @@ Object.defineProperty(PIXI.TwistFilter.prototype, 'radius', { return this.uniforms.radius.value; }, set: function(value) { - this.dirty = true; - this.uniforms.radius.value = value; + this.dirty = true; + this.uniforms.radius.value = value; } }); /** - * + * * This radius describes angle of the twist * @property angle * @type Number @@ -97,7 +95,7 @@ Object.defineProperty(PIXI.TwistFilter.prototype, 'angle', { return this.uniforms.angle.value; }, set: function(value) { - this.dirty = true; - this.uniforms.angle.value = value; + this.dirty = true; + this.uniforms.angle.value = value; } }); \ No newline at end of file diff --git a/src/pixi/loaders/AssetLoader.js b/src/pixi/loaders/AssetLoader.js index 7aefa1683..c2e8ef18e 100644 --- a/src/pixi/loaders/AssetLoader.js +++ b/src/pixi/loaders/AssetLoader.js @@ -6,29 +6,29 @@ * A Class that loads a bunch of images / sprite sheet / bitmap font files. Once the * assets have been loaded they are added to the PIXI Texture cache and can be accessed * easily through PIXI.Texture.fromImage() and PIXI.Sprite.fromImage() - * When all items have been loaded this class will dispatch a "onLoaded" event - * As each individual item is loaded this class will dispatch a "onProgress" event + * When all items have been loaded this class will dispatch a 'onLoaded' event + * As each individual item is loaded this class will dispatch a 'onProgress' event * * @class AssetLoader * @constructor * @uses EventTarget * @param {Array} assetURLs an array of image/sprite sheet urls that you would like loaded - * supported. Supported image formats include "jpeg", "jpg", "png", "gif". Supported - * sprite sheet data formats only include "JSON" at this time. Supported bitmap font - * data formats include "xml" and "fnt". + * supported. Supported image formats include 'jpeg', 'jpg', 'png', 'gif'. Supported + * sprite sheet data formats only include 'JSON' at this time. Supported bitmap font + * data formats include 'xml' and 'fnt'. * @param crossorigin {Boolean} Whether requests should be treated as crossorigin */ PIXI.AssetLoader = function(assetURLs, crossorigin) { - PIXI.EventTarget.call(this); + PIXI.EventTarget.call(this); - /** - * The array of asset URLs that are going to be loaded + /** + * The array of asset URLs that are going to be loaded * - * @property assetURLs - * @type Array - */ - this.assetURLs = assetURLs; + * @property assetURLs + * @type Array + */ + this.assetURLs = assetURLs; /** * Whether the requests should be treated as cross origin @@ -36,7 +36,7 @@ PIXI.AssetLoader = function(assetURLs, crossorigin) * @property crossorigin * @type Boolean */ - this.crossorigin = crossorigin; + this.crossorigin = crossorigin; /** * Maps file extension to loader types @@ -45,17 +45,16 @@ PIXI.AssetLoader = function(assetURLs, crossorigin) * @type Object */ this.loadersByType = { - "jpg": PIXI.ImageLoader, - "jpeg": PIXI.ImageLoader, - "png": PIXI.ImageLoader, - "gif": PIXI.ImageLoader, - "json": PIXI.JsonLoader, - "anim": PIXI.SpineLoader, - "xml": PIXI.BitmapFontLoader, - "fnt": PIXI.BitmapFontLoader + 'jpg': PIXI.ImageLoader, + 'jpeg': PIXI.ImageLoader, + 'png': PIXI.ImageLoader, + 'gif': PIXI.ImageLoader, + 'json': PIXI.JsonLoader, + 'atlas': PIXI.AtlasLoader, + 'anim': PIXI.SpineLoader, + 'xml': PIXI.BitmapFontLoader, + 'fnt': PIXI.BitmapFontLoader }; - - }; /** @@ -72,31 +71,32 @@ PIXI.AssetLoader = function(assetURLs, crossorigin) PIXI.AssetLoader.prototype.constructor = PIXI.AssetLoader; -PIXI.AssetLoader.prototype._getDataType = function(str) +PIXI.AssetLoader.prototype._getDataType = function(str) { - var test = "data:"; + var test = 'data:'; //starts with 'data:' var start = str.slice(0, test.length).toLowerCase(); - if (start == test) { + if (start === test) { var data = str.slice(test.length); - + var sepIdx = data.indexOf(','); if (sepIdx === -1) //malformed data URI scheme return null; - //e.g. "image/gif;base64" => "image/gif" + //e.g. 'image/gif;base64' => 'image/gif' var info = data.slice(0, sepIdx).split(';')[0]; //We might need to handle some special cases here... - //standardize text/plain to "txt" file extension - if (!info || info.toLowerCase() == "text/plain") - return "txt" + //standardize text/plain to 'txt' file extension + if (!info || info.toLowerCase() === 'text/plain') + return 'txt'; //User specified mime type, try splitting it by '/' return info.split('/').pop().toLowerCase(); } + return null; -} +}; /** * Starts loading the assets sequentially @@ -107,30 +107,31 @@ PIXI.AssetLoader.prototype.load = function() { var scope = this; - this.loadCount = this.assetURLs.length; + function onLoad() { + scope.onAssetLoaded(); + } + + this.loadCount = this.assetURLs.length; for (var i=0; i < this.assetURLs.length; i++) - { - var fileName = this.assetURLs[i]; + { + var fileName = this.assetURLs[i]; //first see if we have a data URI scheme.. var fileType = this._getDataType(fileName); //if not, assume it's a file URI if (!fileType) - fileType = fileName.split("?").shift().split(".").pop().toLowerCase(); + fileType = fileName.split('?').shift().split('.').pop().toLowerCase(); - var loaderClass = this.loadersByType[fileType]; - if(!loaderClass) - throw new Error(fileType + " is an unsupported file type"); + var Constructor = this.loadersByType[fileType]; + if(!Constructor) + throw new Error(fileType + ' is an unsupported file type'); - var loader = new loaderClass(fileName, this.crossorigin); + var loader = new Constructor(fileName, this.crossorigin); - loader.addEventListener("loaded", function() - { - scope.onAssetLoaded(); - }); + loader.addEventListener('loaded', onLoad); loader.load(); - } + } }; /** @@ -142,13 +143,12 @@ PIXI.AssetLoader.prototype.load = function() PIXI.AssetLoader.prototype.onAssetLoaded = function() { this.loadCount--; - this.dispatchEvent({type: "onProgress", content: this}); - if(this.onProgress) this.onProgress(); + this.dispatchEvent({type: 'onProgress', content: this}); + if (this.onProgress) this.onProgress(); - if(this.loadCount == 0) - { - this.dispatchEvent({type: "onComplete", content: this}); - if(this.onComplete) this.onComplete(); - } + if (!this.loadCount) + { + this.dispatchEvent({type: 'onComplete', content: this}); + if(this.onComplete) this.onComplete(); + } }; - diff --git a/src/pixi/loaders/AtlasLoader.js b/src/pixi/loaders/AtlasLoader.js new file mode 100644 index 000000000..68300ebd1 --- /dev/null +++ b/src/pixi/loaders/AtlasLoader.js @@ -0,0 +1,184 @@ +/** + * @author Martin Kelm http://mkelm.github.com + */ + +/** + * The atlas file loader is used to load in Atlas data and parsing it + * When loaded this class will dispatch a 'loaded' event + * If load failed this class will dispatch a 'error' event + * @class AtlasLoader + * @extends EventTarget + * @constructor + * @param {String} url the url of the JSON file + * @param {Boolean} crossorigin + */ + +PIXI.AtlasLoader = function (url, crossorigin) { + PIXI.EventTarget.call(this); + this.url = url; + this.baseUrl = url.replace(/[^\/]*$/, ''); + this.crossorigin = crossorigin; + this.loaded = false; + +}; + +// constructor +PIXI.AtlasLoader.constructor = PIXI.AtlasLoader; + +/** + * This will begin loading the JSON file + */ +PIXI.AtlasLoader.prototype.load = function () { + this.ajaxRequest = new PIXI.AjaxRequest(); + this.ajaxRequest.onreadystatechange = this.onAtlasLoaded.bind(this); + + this.ajaxRequest.open('GET', this.url, true); + if (this.ajaxRequest.overrideMimeType) this.ajaxRequest.overrideMimeType('application/json'); + this.ajaxRequest.send(null); +}; + +/** + * Invoke when JSON file is loaded + * @private + */ +PIXI.AtlasLoader.prototype.onAtlasLoaded = function () { + if (this.ajaxRequest.readyState === 4) { + if (this.ajaxRequest.status === 200 || window.location.href.indexOf('http') === -1) { + this.atlas = { + meta : { + image : [] + }, + frames : [] + }; + var result = this.ajaxRequest.responseText.split(/\r?\n/); + var lineCount = -3; + + var currentImageId = 0; + var currentFrame = null; + var nameInNextLine = false; + + var i = 0, + j = 0, + selfOnLoaded = this.onLoaded.bind(this); + + // parser without rotation support yet! + for (i = 0; i < result.length; i++) { + result[i] = result[i].replace(/^\s+|\s+$/g, ''); + if (result[i] === '') { + nameInNextLine = i+1; + } + if (result[i].length > 0) { + if (nameInNextLine === i) { + this.atlas.meta.image.push(result[i]); + currentImageId = this.atlas.meta.image.length - 1; + this.atlas.frames.push({}); + lineCount = -3; + } else if (lineCount > 0) { + if (lineCount % 7 === 1) { // frame name + if (currentFrame != null) { //jshint ignore:line + this.atlas.frames[currentImageId][currentFrame.name] = currentFrame; + } + currentFrame = { name: result[i], frame : {} }; + } else { + var text = result[i].split(' '); + if (lineCount % 7 === 3) { // position + currentFrame.frame.x = Number(text[1].replace(',', '')); + currentFrame.frame.y = Number(text[2]); + } else if (lineCount % 7 === 4) { // size + currentFrame.frame.w = Number(text[1].replace(',', '')); + currentFrame.frame.h = Number(text[2]); + } else if (lineCount % 7 === 5) { // real size + var realSize = { + x : 0, + y : 0, + w : Number(text[1].replace(',', '')), + h : Number(text[2]) + }; + + if (realSize.w > currentFrame.frame.w || realSize.h > currentFrame.frame.h) { + currentFrame.trimmed = true; + currentFrame.realSize = realSize; + } else { + currentFrame.trimmed = false; + } + } + } + } + lineCount++; + } + } + + if (currentFrame != null) { //jshint ignore:line + this.atlas.frames[currentImageId][currentFrame.name] = currentFrame; + } + + if (this.atlas.meta.image.length > 0) { + this.images = []; + for (j = 0; j < this.atlas.meta.image.length; j++) { + // sprite sheet + var textureUrl = this.baseUrl + this.atlas.meta.image[j]; + var frameData = this.atlas.frames[j]; + this.images.push(new PIXI.ImageLoader(textureUrl, this.crossorigin)); + + for (i in frameData) { + var rect = frameData[i].frame; + if (rect) { + PIXI.TextureCache[i] = new PIXI.Texture(this.images[j].texture.baseTexture, { + x: rect.x, + y: rect.y, + width: rect.w, + height: rect.h + }); + if (frameData[i].trimmed) { + PIXI.TextureCache[i].realSize = frameData[i].realSize; + // trim in pixi not supported yet, todo update trim properties if it is done ... + PIXI.TextureCache[i].trim.x = 0; + PIXI.TextureCache[i].trim.y = 0; + } + } + } + } + + this.currentImageId = 0; + for (j = 0; j < this.images.length; j++) { + this.images[j].addEventListener('loaded', selfOnLoaded); + } + this.images[this.currentImageId].load(); + + } else { + this.onLoaded(); + } + + } else { + this.onError(); + } + } +}; + +/** + * Invoke when json file loaded + * @private + */ +PIXI.AtlasLoader.prototype.onLoaded = function () { + if (this.images.length - 1 > this.currentImageId) { + this.currentImageId++; + this.images[this.currentImageId].load(); + } else { + this.loaded = true; + this.dispatchEvent({ + type: 'loaded', + content: this + }); + } +}; + +/** + * Invoke when error occured + * @private + */ +PIXI.AtlasLoader.prototype.onError = function () { + this.dispatchEvent({ + type: 'error', + content: this + }); +}; diff --git a/src/pixi/loaders/BitmapFontLoader.js b/src/pixi/loaders/BitmapFontLoader.js index ef0509437..8421f90a8 100644 --- a/src/pixi/loaders/BitmapFontLoader.js +++ b/src/pixi/loaders/BitmapFontLoader.js @@ -3,10 +3,10 @@ */ /** - * The xml loader is used to load in XML bitmap font data ("xml" or "fnt") + * The xml loader is used to load in XML bitmap font data ('xml' or 'fnt') * To generate the data you can use http://www.angelcode.com/products/bmfont/ * This loader will also load the image file as the data. - * When loaded this class will dispatch a "loaded" event + * When loaded this class will dispatch a 'loaded' event * * @class BitmapFontLoader * @uses EventTarget @@ -19,7 +19,7 @@ PIXI.BitmapFontLoader = function(url, crossorigin) /* * i use texture packer to load the assets.. * http://www.codeandweb.com/texturepacker - * make sure to set the format as "JSON" + * make sure to set the format as 'JSON' */ PIXI.EventTarget.call(this); @@ -46,7 +46,7 @@ PIXI.BitmapFontLoader = function(url, crossorigin) * @type String * @readOnly */ - this.baseUrl = url.replace(/[^\/]*$/, ""); + this.baseUrl = url.replace(/[^\/]*$/, ''); /** * [read-only] The texture of the bitmap font @@ -74,9 +74,9 @@ PIXI.BitmapFontLoader.prototype.load = function() scope.onXMLLoaded(); }; - this.ajaxRequest.open("GET", this.url, true); - if (this.ajaxRequest.overrideMimeType) this.ajaxRequest.overrideMimeType("application/xml"); - this.ajaxRequest.send(null) + this.ajaxRequest.open('GET', this.url, true); + if (this.ajaxRequest.overrideMimeType) this.ajaxRequest.overrideMimeType('application/xml'); + this.ajaxRequest.send(null); }; /** @@ -87,40 +87,40 @@ PIXI.BitmapFontLoader.prototype.load = function() */ PIXI.BitmapFontLoader.prototype.onXMLLoaded = function() { - if (this.ajaxRequest.readyState == 4) + if (this.ajaxRequest.readyState === 4) { - if (this.ajaxRequest.status == 200 || window.location.href.indexOf("http") == -1) + if (this.ajaxRequest.status === 200 || window.location.protocol.indexOf('http') === -1) { - var textureUrl = this.baseUrl + this.ajaxRequest.responseXML.getElementsByTagName("page")[0].attributes.getNamedItem("file").nodeValue; + var textureUrl = this.baseUrl + this.ajaxRequest.responseXML.getElementsByTagName('page')[0].attributes.getNamedItem('file').nodeValue; var image = new PIXI.ImageLoader(textureUrl, this.crossorigin); this.texture = image.texture.baseTexture; var data = {}; - var info = this.ajaxRequest.responseXML.getElementsByTagName("info")[0]; - var common = this.ajaxRequest.responseXML.getElementsByTagName("common")[0]; - data.font = info.attributes.getNamedItem("face").nodeValue; - data.size = parseInt(info.attributes.getNamedItem("size").nodeValue, 10); - data.lineHeight = parseInt(common.attributes.getNamedItem("lineHeight").nodeValue, 10); + var info = this.ajaxRequest.responseXML.getElementsByTagName('info')[0]; + var common = this.ajaxRequest.responseXML.getElementsByTagName('common')[0]; + data.font = info.attributes.getNamedItem('face').nodeValue; + data.size = parseInt(info.attributes.getNamedItem('size').nodeValue, 10); + data.lineHeight = parseInt(common.attributes.getNamedItem('lineHeight').nodeValue, 10); data.chars = {}; //parse letters - var letters = this.ajaxRequest.responseXML.getElementsByTagName("char"); + var letters = this.ajaxRequest.responseXML.getElementsByTagName('char'); for (var i = 0; i < letters.length; i++) { - var charCode = parseInt(letters[i].attributes.getNamedItem("id").nodeValue, 10); + var charCode = parseInt(letters[i].attributes.getNamedItem('id').nodeValue, 10); var textureRect = new PIXI.Rectangle( - parseInt(letters[i].attributes.getNamedItem("x").nodeValue, 10), - parseInt(letters[i].attributes.getNamedItem("y").nodeValue, 10), - parseInt(letters[i].attributes.getNamedItem("width").nodeValue, 10), - parseInt(letters[i].attributes.getNamedItem("height").nodeValue, 10) + parseInt(letters[i].attributes.getNamedItem('x').nodeValue, 10), + parseInt(letters[i].attributes.getNamedItem('y').nodeValue, 10), + parseInt(letters[i].attributes.getNamedItem('width').nodeValue, 10), + parseInt(letters[i].attributes.getNamedItem('height').nodeValue, 10) ); data.chars[charCode] = { - xOffset: parseInt(letters[i].attributes.getNamedItem("xoffset").nodeValue, 10), - yOffset: parseInt(letters[i].attributes.getNamedItem("yoffset").nodeValue, 10), - xAdvance: parseInt(letters[i].attributes.getNamedItem("xadvance").nodeValue, 10), + xOffset: parseInt(letters[i].attributes.getNamedItem('xoffset').nodeValue, 10), + yOffset: parseInt(letters[i].attributes.getNamedItem('yoffset').nodeValue, 10), + xAdvance: parseInt(letters[i].attributes.getNamedItem('xadvance').nodeValue, 10), kerning: {}, texture: PIXI.TextureCache[charCode] = new PIXI.Texture(this.texture, textureRect) @@ -128,12 +128,12 @@ PIXI.BitmapFontLoader.prototype.onXMLLoaded = function() } //parse kernings - var kernings = this.ajaxRequest.responseXML.getElementsByTagName("kerning"); + var kernings = this.ajaxRequest.responseXML.getElementsByTagName('kerning'); for (i = 0; i < kernings.length; i++) { - var first = parseInt(kernings[i].attributes.getNamedItem("first").nodeValue, 10); - var second = parseInt(kernings[i].attributes.getNamedItem("second").nodeValue, 10); - var amount = parseInt(kernings[i].attributes.getNamedItem("amount").nodeValue, 10); + var first = parseInt(kernings[i].attributes.getNamedItem('first').nodeValue, 10); + var second = parseInt(kernings[i].attributes.getNamedItem('second').nodeValue, 10); + var amount = parseInt(kernings[i].attributes.getNamedItem('amount').nodeValue, 10); data.chars[second].kerning[first] = amount; @@ -142,7 +142,7 @@ PIXI.BitmapFontLoader.prototype.onXMLLoaded = function() PIXI.BitmapText.fonts[data.font] = data; var scope = this; - image.addEventListener("loaded", function() { + image.addEventListener('loaded', function() { scope.onLoaded(); }); image.load(); @@ -158,5 +158,5 @@ PIXI.BitmapFontLoader.prototype.onXMLLoaded = function() */ PIXI.BitmapFontLoader.prototype.onLoaded = function() { - this.dispatchEvent({type: "loaded", content: this}); + this.dispatchEvent({type: 'loaded', content: this}); }; diff --git a/src/pixi/loaders/ImageLoader.js b/src/pixi/loaders/ImageLoader.js index d8a413745..c73893f6e 100644 --- a/src/pixi/loaders/ImageLoader.js +++ b/src/pixi/loaders/ImageLoader.js @@ -3,7 +3,7 @@ */ /** - * The image loader class is responsible for loading images file formats ("jpeg", "jpg", "png" and "gif") + * The image loader class is responsible for loading images file formats ('jpeg', 'jpg', 'png' and 'gif') * Once the image has been loaded it is stored in the PIXI texture cache and can be accessed though PIXI.Texture.fromFrameId() and PIXI.Sprite.fromFromeId() * When loaded this class will dispatch a 'loaded' event * @@ -46,7 +46,7 @@ PIXI.ImageLoader.prototype.load = function() if(!this.texture.baseTexture.hasLoaded) { var scope = this; - this.texture.baseTexture.addEventListener("loaded", function() + this.texture.baseTexture.addEventListener('loaded', function() { scope.onLoaded(); }); @@ -65,7 +65,7 @@ PIXI.ImageLoader.prototype.load = function() */ PIXI.ImageLoader.prototype.onLoaded = function() { - this.dispatchEvent({type: "loaded", content: this}); + this.dispatchEvent({type: 'loaded', content: this}); }; /** @@ -75,7 +75,7 @@ PIXI.ImageLoader.prototype.onLoaded = function() * @method loadFramedSpriteSheet * @param frameWidth {Number} with of each frame * @param frameHeight {Number} height of each frame - * @param textureName {String} if given, the frames will be cached in - format + * @param textureName {String} if given, the frames will be cached in - format */ PIXI.ImageLoader.prototype.loadFramedSpriteSheet = function(frameWidth, frameHeight, textureName) { @@ -96,14 +96,14 @@ PIXI.ImageLoader.prototype.loadFramedSpriteSheet = function(frameWidth, frameHei }); this.frames.push(texture); - if (textureName) PIXI.TextureCache[textureName+'-'+i] = texture; + if (textureName) PIXI.TextureCache[textureName + '-' + i] = texture; } } if(!this.texture.baseTexture.hasLoaded) { var scope = this; - this.texture.baseTexture.addEventListener("loaded", function() { + this.texture.baseTexture.addEventListener('loaded', function() { scope.onLoaded(); }); } @@ -111,4 +111,4 @@ PIXI.ImageLoader.prototype.loadFramedSpriteSheet = function(frameWidth, frameHei { this.onLoaded(); } -}; \ No newline at end of file +}; diff --git a/src/pixi/loaders/JsonLoader.js b/src/pixi/loaders/JsonLoader.js index 640ecbf0f..127d8cf03 100644 --- a/src/pixi/loaders/JsonLoader.js +++ b/src/pixi/loaders/JsonLoader.js @@ -4,8 +4,8 @@ /** * The json file loader is used to load in JSON data and parsing it - * When loaded this class will dispatch a "loaded" event - * If load failed this class will dispatch a "error" event + * When loaded this class will dispatch a 'loaded' event + * If load failed this class will dispatch a 'error' event * * @class JsonLoader * @uses EventTarget @@ -14,41 +14,41 @@ * @param crossorigin {Boolean} Whether requests should be treated as crossorigin */ PIXI.JsonLoader = function (url, crossorigin) { - PIXI.EventTarget.call(this); + PIXI.EventTarget.call(this); - /** - * The url of the bitmap font data - * - * @property url - * @type String - */ - this.url = url; + /** + * The url of the bitmap font data + * + * @property url + * @type String + */ + this.url = url; - /** - * Whether the requests should be treated as cross origin - * - * @property crossorigin - * @type Boolean - */ - this.crossorigin = crossorigin; + /** + * Whether the requests should be treated as cross origin + * + * @property crossorigin + * @type Boolean + */ + this.crossorigin = crossorigin; - /** - * [read-only] The base url of the bitmap font data - * - * @property baseUrl - * @type String - * @readOnly - */ - this.baseUrl = url.replace(/[^\/]*$/, ""); + /** + * [read-only] The base url of the bitmap font data + * + * @property baseUrl + * @type String + * @readOnly + */ + this.baseUrl = url.replace(/[^\/]*$/, ''); - /** - * [read-only] Whether the data has loaded yet - * - * @property loaded - * @type Boolean - * @readOnly - */ - this.loaded = false; + /** + * [read-only] Whether the data has loaded yet + * + * @property loaded + * @type Boolean + * @readOnly + */ + this.loaded = false; }; @@ -61,15 +61,15 @@ PIXI.JsonLoader.prototype.constructor = PIXI.JsonLoader; * @method load */ PIXI.JsonLoader.prototype.load = function () { - this.ajaxRequest = new AjaxRequest(); - var scope = this; - this.ajaxRequest.onreadystatechange = function () { - scope.onJSONLoaded(); - }; + this.ajaxRequest = new PIXI.AjaxRequest(); + var scope = this; + this.ajaxRequest.onreadystatechange = function () { + scope.onJSONLoaded(); + }; - this.ajaxRequest.open("GET", this.url, true); - if (this.ajaxRequest.overrideMimeType) this.ajaxRequest.overrideMimeType("application/json"); - this.ajaxRequest.send(null); + this.ajaxRequest.open('GET', this.url, true); + if (this.ajaxRequest.overrideMimeType) this.ajaxRequest.overrideMimeType('application/json'); + this.ajaxRequest.send(null); }; /** @@ -79,62 +79,62 @@ PIXI.JsonLoader.prototype.load = function () { * @private */ PIXI.JsonLoader.prototype.onJSONLoaded = function () { - if (this.ajaxRequest.readyState == 4) { - if (this.ajaxRequest.status == 200 || window.location.href.indexOf("http") == -1) { - this.json = JSON.parse(this.ajaxRequest.responseText); + if (this.ajaxRequest.readyState === 4) { + if (this.ajaxRequest.status === 200 || window.location.protocol.indexOf('http') === -1) { + this.json = JSON.parse(this.ajaxRequest.responseText); - if(this.json.frames) - { - // sprite sheet - var scope = this; - var textureUrl = this.baseUrl + this.json.meta.image; - var image = new PIXI.ImageLoader(textureUrl, this.crossorigin); - var frameData = this.json.frames; + if(this.json.frames) + { + // sprite sheet + var scope = this; + var textureUrl = this.baseUrl + this.json.meta.image; + var image = new PIXI.ImageLoader(textureUrl, this.crossorigin); + var frameData = this.json.frames; - this.texture = image.texture.baseTexture; - image.addEventListener("loaded", function (event) { - scope.onLoaded(); - }); + this.texture = image.texture.baseTexture; + image.addEventListener('loaded', function() { + scope.onLoaded(); + }); - for (var i in frameData) { - var rect = frameData[i].frame; - if (rect) { - PIXI.TextureCache[i] = new PIXI.Texture(this.texture, { - x: rect.x, - y: rect.y, - width: rect.w, - height: rect.h - }); - if (frameData[i].trimmed) { - //var realSize = frameData[i].spriteSourceSize; - PIXI.TextureCache[i].realSize = frameData[i].spriteSourceSize; - PIXI.TextureCache[i].trim.x = 0; // (realSize.x / rect.w) - // calculate the offset! - } - } - } + for (var i in frameData) { + var rect = frameData[i].frame; + if (rect) { + PIXI.TextureCache[i] = new PIXI.Texture(this.texture, { + x: rect.x, + y: rect.y, + width: rect.w, + height: rect.h + }); + if (frameData[i].trimmed) { + //var realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].trim.x = 0; // (realSize.x / rect.w) + // calculate the offset! + } + } + } - image.load(); + image.load(); - } - else if(this.json.bones) - { - // spine animation - var spineJsonParser = new spine.SkeletonJson(); - var skeletonData = spineJsonParser.readSkeletonData(this.json); - PIXI.AnimCache[this.url] = skeletonData; - this.onLoaded(); - } - else - { - this.onLoaded(); - } - } - else - { - this.onError(); - } - } + } + else if(this.json.bones) + { + // spine animation + var spineJsonParser = new spine.SkeletonJson(); + var skeletonData = spineJsonParser.readSkeletonData(this.json); + PIXI.AnimCache[this.url] = skeletonData; + this.onLoaded(); + } + else + { + this.onLoaded(); + } + } + else + { + this.onError(); + } + } }; /** @@ -144,11 +144,11 @@ PIXI.JsonLoader.prototype.onJSONLoaded = function () { * @private */ PIXI.JsonLoader.prototype.onLoaded = function () { - this.loaded = true; - this.dispatchEvent({ - type: "loaded", - content: this - }); + this.loaded = true; + this.dispatchEvent({ + type: 'loaded', + content: this + }); }; /** @@ -158,8 +158,8 @@ PIXI.JsonLoader.prototype.onLoaded = function () { * @private */ PIXI.JsonLoader.prototype.onError = function () { - this.dispatchEvent({ - type: "error", - content: this - }); + this.dispatchEvent({ + type: 'error', + content: this + }); }; \ No newline at end of file diff --git a/src/pixi/loaders/SpineLoader.js b/src/pixi/loaders/SpineLoader.js index 00360980c..f0a38ca07 100644 --- a/src/pixi/loaders/SpineLoader.js +++ b/src/pixi/loaders/SpineLoader.js @@ -23,33 +23,33 @@ */ PIXI.SpineLoader = function(url, crossorigin) { - PIXI.EventTarget.call(this); + PIXI.EventTarget.call(this); - /** - * The url of the bitmap font data - * - * @property url - * @type String - */ - this.url = url; + /** + * The url of the bitmap font data + * + * @property url + * @type String + */ + this.url = url; - /** - * Whether the requests should be treated as cross origin - * - * @property crossorigin - * @type Boolean - */ - this.crossorigin = crossorigin; + /** + * Whether the requests should be treated as cross origin + * + * @property crossorigin + * @type Boolean + */ + this.crossorigin = crossorigin; - /** - * [read-only] Whether the data has loaded yet - * - * @property loaded - * @type Boolean - * @readOnly - */ - this.loaded = false; -} + /** + * [read-only] Whether the data has loaded yet + * + * @property loaded + * @type Boolean + * @readOnly + */ + this.loaded = false; +}; PIXI.SpineLoader.prototype.constructor = PIXI.SpineLoader; @@ -60,13 +60,13 @@ PIXI.SpineLoader.prototype.constructor = PIXI.SpineLoader; */ PIXI.SpineLoader.prototype.load = function () { - var scope = this; - var jsonLoader = new PIXI.JsonLoader(this.url, this.crossorigin); - jsonLoader.addEventListener("loaded", function (event) { - scope.json = event.content.json; - scope.onJSONLoaded(); - }); - jsonLoader.load(); + var scope = this; + var jsonLoader = new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener("loaded", function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); }; /** @@ -75,13 +75,13 @@ PIXI.SpineLoader.prototype.load = function () { * @method onJSONLoaded * @private */ -PIXI.SpineLoader.prototype.onJSONLoaded = function (event) { - var spineJsonParser = new spine.SkeletonJson(); - var skeletonData = spineJsonParser.readSkeletonData(this.json); +PIXI.SpineLoader.prototype.onJSONLoaded = function () { + var spineJsonParser = new spine.SkeletonJson(); + var skeletonData = spineJsonParser.readSkeletonData(this.json); - PIXI.AnimCache[this.url] = skeletonData; + PIXI.AnimCache[this.url] = skeletonData; - this.onLoaded(); + this.onLoaded(); }; /** @@ -91,7 +91,7 @@ PIXI.SpineLoader.prototype.onJSONLoaded = function (event) { * @private */ PIXI.SpineLoader.prototype.onLoaded = function () { - this.loaded = true; + this.loaded = true; this.dispatchEvent({type: "loaded", content: this}); }; diff --git a/src/pixi/loaders/SpriteSheetLoader.js b/src/pixi/loaders/SpriteSheetLoader.js index 169fb70df..e9844cece 100644 --- a/src/pixi/loaders/SpriteSheetLoader.js +++ b/src/pixi/loaders/SpriteSheetLoader.js @@ -4,12 +4,12 @@ /** * The sprite sheet loader is used to load in JSON sprite sheet data - * To generate the data you can use http://www.codeandweb.com/texturepacker and publish the "JSON" format + * To generate the data you can use http://www.codeandweb.com/texturepacker and publish the 'JSON' format * There is a free version so thats nice, although the paid version is great value for money. - * It is highly recommended to use Sprite sheets (also know as texture atlas") as it means sprite"s can be batched and drawn together for highly increased rendering speed. + * It is highly recommended to use Sprite sheets (also know as texture atlas') as it means sprite's can be batched and drawn together for highly increased rendering speed. * Once the data has been loaded the frames are stored in the PIXI texture cache and can be accessed though PIXI.Texture.fromFrameId() and PIXI.Sprite.fromFromeId() * This loader will also load the image file that the Spritesheet points to as well as the data. - * When loaded this class will dispatch a "loaded" event + * When loaded this class will dispatch a 'loaded' event * * @class SpriteSheetLoader * @uses EventTarget @@ -17,39 +17,38 @@ * @param url {String} The url of the sprite sheet JSON file * @param crossorigin {Boolean} Whether requests should be treated as crossorigin */ - PIXI.SpriteSheetLoader = function (url, crossorigin) { - /* - * i use texture packer to load the assets.. - * http://www.codeandweb.com/texturepacker - * make sure to set the format as "JSON" - */ - PIXI.EventTarget.call(this); + /* + * i use texture packer to load the assets.. + * http://www.codeandweb.com/texturepacker + * make sure to set the format as 'JSON' + */ + PIXI.EventTarget.call(this); - /** - * The url of the bitmap font data - * - * @property url - * @type String - */ - this.url = url; + /** + * The url of the bitmap font data + * + * @property url + * @type String + */ + this.url = url; - /** - * Whether the requests should be treated as cross origin - * - * @property crossorigin - * @type Boolean - */ - this.crossorigin = crossorigin; + /** + * Whether the requests should be treated as cross origin + * + * @property crossorigin + * @type Boolean + */ + this.crossorigin = crossorigin; - /** - * [read-only] The base url of the bitmap font data - * - * @property baseUrl - * @type String - * @readOnly - */ - this.baseUrl = url.replace(/[^\/]*$/, ""); + /** + * [read-only] The base url of the bitmap font data + * + * @property baseUrl + * @type String + * @readOnly + */ + this.baseUrl = url.replace(/[^\/]*$/, ''); /** * The texture being loaded @@ -65,7 +64,7 @@ PIXI.SpriteSheetLoader = function (url, crossorigin) { * @property frames * @type Object */ - this.frames = {}; + this.frames = {}; }; // constructor @@ -77,13 +76,13 @@ PIXI.SpriteSheetLoader.prototype.constructor = PIXI.SpriteSheetLoader; * @method load */ PIXI.SpriteSheetLoader.prototype.load = function () { - var scope = this; - var jsonLoader = new PIXI.JsonLoader(this.url, this.crossorigin); - jsonLoader.addEventListener("loaded", function (event) { - scope.json = event.content.json; - scope.onJSONLoaded(); - }); - jsonLoader.load(); + var scope = this; + var jsonLoader = new PIXI.JsonLoader(this.url, this.crossorigin); + jsonLoader.addEventListener('loaded', function (event) { + scope.json = event.content.json; + scope.onJSONLoaded(); + }); + jsonLoader.load(); }; /** @@ -93,36 +92,37 @@ PIXI.SpriteSheetLoader.prototype.load = function () { * @private */ PIXI.SpriteSheetLoader.prototype.onJSONLoaded = function () { - var scope = this; - var textureUrl = this.baseUrl + this.json.meta.image; - var image = new PIXI.ImageLoader(textureUrl, this.crossorigin); - var frameData = this.json.frames; + var scope = this; + var textureUrl = this.baseUrl + this.json.meta.image; + var image = new PIXI.ImageLoader(textureUrl, this.crossorigin); + var frameData = this.json.frames; - this.texture = image.texture.baseTexture; - image.addEventListener("loaded", function (event) { - scope.onLoaded(); - }); + this.texture = image.texture.baseTexture; + image.addEventListener('loaded', function () { + scope.onLoaded(); + }); - for (var i in frameData) { - var rect = frameData[i].frame; - if (rect) { - PIXI.TextureCache[i] = new PIXI.Texture(this.texture, { - x: rect.x, - y: rect.y, - width: rect.w, - height: rect.h - }); - if (frameData[i].trimmed) { - //var realSize = frameData[i].spriteSourceSize; - PIXI.TextureCache[i].realSize = frameData[i].spriteSourceSize; - PIXI.TextureCache[i].trim.x = 0; // (realSize.x / rect.w) - // calculate the offset! - } - } - } + for (var i in frameData) { + var rect = frameData[i].frame; + if (rect) { + PIXI.TextureCache[i] = new PIXI.Texture(this.texture, { + x: rect.x, + y: rect.y, + width: rect.w, + height: rect.h + }); + if (frameData[i].trimmed) { + //var realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].realSize = frameData[i].spriteSourceSize; + PIXI.TextureCache[i].trim.x = 0; // (realSize.x / rect.w) + // calculate the offset! + } + } + } - image.load(); + image.load(); }; + /** * Invoke when all files are loaded (json and texture) * @@ -130,8 +130,8 @@ PIXI.SpriteSheetLoader.prototype.onJSONLoaded = function () { * @private */ PIXI.SpriteSheetLoader.prototype.onLoaded = function () { - this.dispatchEvent({ - type: "loaded", - content: this - }); + this.dispatchEvent({ + type: 'loaded', + content: this + }); }; diff --git a/src/pixi/primitives/Graphics.js b/src/pixi/primitives/Graphics.js index 8e8c3dd2f..92dee1e2e 100644 --- a/src/pixi/primitives/Graphics.js +++ b/src/pixi/primitives/Graphics.js @@ -14,9 +14,9 @@ */ PIXI.Graphics = function() { - PIXI.DisplayObjectContainer.call( this ); + PIXI.DisplayObjectContainer.call( this ); - this.renderable = true; + this.renderable = true; /** * The alpha of the fill of this graphics object @@ -24,7 +24,7 @@ PIXI.Graphics = function() * @property fillAlpha * @type Number */ - this.fillAlpha = 1; + this.fillAlpha = 1; /** * The width of any lines drawn @@ -32,7 +32,7 @@ PIXI.Graphics = function() * @property lineWidth * @type Number */ - this.lineWidth = 0; + this.lineWidth = 0; /** * The color of any lines drawn @@ -40,7 +40,7 @@ PIXI.Graphics = function() * @property lineColor * @type String */ - this.lineColor = "black"; + this.lineColor = "black"; /** * Graphics data @@ -49,7 +49,7 @@ PIXI.Graphics = function() * @type Array * @private */ - this.graphicsData = []; + this.graphicsData = []; /** * Current path @@ -58,8 +58,8 @@ PIXI.Graphics = function() * @type Object * @private */ - this.currentPath = {points:[]}; -} + this.currentPath = {points:[]}; +}; // constructor PIXI.Graphics.prototype = Object.create( PIXI.DisplayObjectContainer.prototype ); @@ -75,17 +75,17 @@ PIXI.Graphics.prototype.constructor = PIXI.Graphics; */ PIXI.Graphics.prototype.lineStyle = function(lineWidth, color, alpha) { - if(this.currentPath.points.length == 0)this.graphicsData.pop(); + if (!this.currentPath.points.length) this.graphicsData.pop(); - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha == undefined) ? 1 : alpha; + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - this.currentPath = {lineWidth:this.lineWidth, lineColor:this.lineColor, lineAlpha:this.lineAlpha, - fillColor:this.fillColor, fillAlpha:this.fillAlpha, fill:this.filling, points:[], type:PIXI.Graphics.POLY}; + this.currentPath = {lineWidth:this.lineWidth, lineColor:this.lineColor, lineAlpha:this.lineAlpha, + fillColor:this.fillColor, fillAlpha:this.fillAlpha, fill:this.filling, points:[], type:PIXI.Graphics.POLY}; - this.graphicsData.push(this.currentPath); -} + this.graphicsData.push(this.currentPath); +}; /** * Moves the current drawing position to (x, y). @@ -96,15 +96,15 @@ PIXI.Graphics.prototype.lineStyle = function(lineWidth, color, alpha) */ PIXI.Graphics.prototype.moveTo = function(x, y) { - if(this.currentPath.points.length == 0)this.graphicsData.pop(); + if (!this.currentPath.points.length) this.graphicsData.pop(); - this.currentPath = this.currentPath = {lineWidth:this.lineWidth, lineColor:this.lineColor, lineAlpha:this.lineAlpha, - fillColor:this.fillColor, fillAlpha:this.fillAlpha, fill:this.filling, points:[], type:PIXI.Graphics.POLY}; + this.currentPath = this.currentPath = {lineWidth:this.lineWidth, lineColor:this.lineColor, lineAlpha:this.lineAlpha, + fillColor:this.fillColor, fillAlpha:this.fillAlpha, fill:this.filling, points:[], type:PIXI.Graphics.POLY}; - this.currentPath.points.push(x, y); + this.currentPath.points.push(x, y); - this.graphicsData.push(this.currentPath); -} + this.graphicsData.push(this.currentPath); +}; /** * Draws a line using the current line style from the current drawing position to (x, y); @@ -116,9 +116,9 @@ PIXI.Graphics.prototype.moveTo = function(x, y) */ PIXI.Graphics.prototype.lineTo = function(x, y) { - this.currentPath.points.push(x, y); - this.dirty = true; -} + this.currentPath.points.push(x, y); + this.dirty = true; +}; /** * Specifies a simple one-color fill that subsequent calls to other Graphics methods @@ -130,10 +130,10 @@ PIXI.Graphics.prototype.lineTo = function(x, y) */ PIXI.Graphics.prototype.beginFill = function(color, alpha) { - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha == undefined) ? 1 : alpha; -} + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (arguments.length < 2) ? 1 : alpha; +}; /** * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. @@ -142,10 +142,10 @@ PIXI.Graphics.prototype.beginFill = function(color, alpha) */ PIXI.Graphics.prototype.endFill = function() { - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; -} + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; +}; /** * @method drawRect @@ -157,15 +157,15 @@ PIXI.Graphics.prototype.endFill = function() */ PIXI.Graphics.prototype.drawRect = function( x, y, width, height ) { - if(this.currentPath.points.length == 0)this.graphicsData.pop(); + if (!this.currentPath.points.length) this.graphicsData.pop(); - this.currentPath = {lineWidth:this.lineWidth, lineColor:this.lineColor, lineAlpha:this.lineAlpha, - fillColor:this.fillColor, fillAlpha:this.fillAlpha, fill:this.filling, - points:[x, y, width, height], type:PIXI.Graphics.RECT}; + this.currentPath = {lineWidth:this.lineWidth, lineColor:this.lineColor, lineAlpha:this.lineAlpha, + fillColor:this.fillColor, fillAlpha:this.fillAlpha, fill:this.filling, + points:[x, y, width, height], type:PIXI.Graphics.RECT}; - this.graphicsData.push(this.currentPath); - this.dirty = true; -} + this.graphicsData.push(this.currentPath); + this.dirty = true; +}; /** * Draws a circle. @@ -177,36 +177,36 @@ PIXI.Graphics.prototype.drawRect = function( x, y, width, height ) */ PIXI.Graphics.prototype.drawCircle = function( x, y, radius) { - if(this.currentPath.points.length == 0)this.graphicsData.pop(); + if (!this.currentPath.points.length) this.graphicsData.pop(); - this.currentPath = {lineWidth:this.lineWidth, lineColor:this.lineColor, lineAlpha:this.lineAlpha, - fillColor:this.fillColor, fillAlpha:this.fillAlpha, fill:this.filling, - points:[x, y, radius, radius], type:PIXI.Graphics.CIRC}; + this.currentPath = {lineWidth:this.lineWidth, lineColor:this.lineColor, lineAlpha:this.lineAlpha, + fillColor:this.fillColor, fillAlpha:this.fillAlpha, fill:this.filling, + points:[x, y, radius, radius], type:PIXI.Graphics.CIRC}; - this.graphicsData.push(this.currentPath); - this.dirty = true; -} + this.graphicsData.push(this.currentPath); + this.dirty = true; +}; /** - * Draws an elipse. + * Draws an ellipse. * - * @method drawElipse + * @method drawEllipse * @param x {Number} * @param y {Number} * @param width {Number} * @param height {Number} */ -PIXI.Graphics.prototype.drawElipse = function( x, y, width, height) +PIXI.Graphics.prototype.drawEllipse = function( x, y, width, height) { - if(this.currentPath.points.length == 0)this.graphicsData.pop(); + if (!this.currentPath.points.length) this.graphicsData.pop(); - this.currentPath = {lineWidth:this.lineWidth, lineColor:this.lineColor, lineAlpha:this.lineAlpha, - fillColor:this.fillColor, fillAlpha:this.fillAlpha, fill:this.filling, - points:[x, y, width, height], type:PIXI.Graphics.ELIP}; + this.currentPath = {lineWidth:this.lineWidth, lineColor:this.lineColor, lineAlpha:this.lineAlpha, + fillColor:this.fillColor, fillAlpha:this.fillAlpha, fill:this.filling, + points:[x, y, width, height], type:PIXI.Graphics.ELIP}; - this.graphicsData.push(this.currentPath); - this.dirty = true; -} + this.graphicsData.push(this.currentPath); + this.dirty = true; +}; /** * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. @@ -215,88 +215,83 @@ PIXI.Graphics.prototype.drawElipse = function( x, y, width, height) */ PIXI.Graphics.prototype.clear = function() { - this.lineWidth = 0; - this.filling = false; + this.lineWidth = 0; + this.filling = false; - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; - this.bounds = null//new PIXI.Rectangle(); -} + this.bounds = null; //new PIXI.Rectangle(); +}; PIXI.Graphics.prototype.updateFilterBounds = function() { - if(!this.bounds) - { - var minX = Infinity; - var maxX = -Infinity; + if(!this.bounds) + { + var minX = Infinity; + var maxX = -Infinity; - var minY = Infinity; - var maxY = -Infinity; + var minY = Infinity; + var maxY = -Infinity; - var points, x, y; + var points, x, y; - for (var i = 0; i < this.graphicsData.length; i++) { - + for (var i = 0; i < this.graphicsData.length; i++) { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; + points = data.points; - points = data.points; + if(type === PIXI.Graphics.RECT) + { + x = points.x - lineWidth/2; + y = points.y - lineWidth/2; + var width = points.width + lineWidth; + var height = points.height + lineWidth; - if(type === PIXI.Graphics.RECT) - { - x = points.x - lineWidth/2; - y = points.y - lineWidth/2; - var width = points.width + lineWidth; - var height = points.height + lineWidth; + minX = x < minX ? x : minX; + maxX = x + width > maxX ? x + width : maxX; - minX = x < minX ? x : minX; - maxX = x + width > maxX ? x + width : maxX; + minY = y < minY ? x : minY; + maxY = y + height > maxY ? y + height : maxY; + } + else if(type === PIXI.Graphics.CIRC || type === PIXI.Graphics.ELIP) + { + x = points.x; + y = points.y; + var radius = points.radius + lineWidth/2; - minY = y < minY ? x : minY; - maxY = y + height > maxY ? y + height : maxY; - } - else if(type === PIXI.Graphics.CIRC || type === PIXI.Graphics.ELIP) - { - x = points.x; - y = points.y; - var radius = points.radius + lineWidth/2; - - minX = x - radius < minX ? x - radius : minX; - maxX = x + radius > maxX ? x + radius : maxX; + minX = x - radius < minX ? x - radius : minX; + maxX = x + radius > maxX ? x + radius : maxX; - minY = y - radius < minY ? y - radius : minY; - maxY = y + radius > maxY ? y + radius : maxY; - } - else - { - // POLY - for (var j = 0; j < points.length; j+=2) - { - - x = points[j]; - y = points[j+1]; + minY = y - radius < minY ? y - radius : minY; + maxY = y + radius > maxY ? y + radius : maxY; + } + else + { + // POLY + for (var j = 0; j < points.length; j+=2) + { - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + x = points[j]; + y = points[j+1]; - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - }; - } + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - }; + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } - this.bounds = new PIXI.Rectangle(minX, minY, maxX - minX, maxY - minY); - - } - -// console.log(this.bounds); -} + this.bounds = new PIXI.Rectangle(minX, minY, maxX - minX, maxY - minY); + } +// console.log(this.bounds); +}; // SOME TYPES: PIXI.Graphics.POLY = 0; diff --git a/src/pixi/renderers/canvas/CanvasGraphics.js b/src/pixi/renderers/canvas/CanvasGraphics.js index f5352b9bc..a34c40023 100644 --- a/src/pixi/renderers/canvas/CanvasGraphics.js +++ b/src/pixi/renderers/canvas/CanvasGraphics.js @@ -11,7 +11,7 @@ PIXI.CanvasGraphics = function() { -} +}; /* @@ -25,128 +25,128 @@ PIXI.CanvasGraphics = function() */ PIXI.CanvasGraphics.renderGraphics = function(graphics, context) { - var worldAlpha = graphics.worldAlpha; + var worldAlpha = graphics.worldAlpha; + var color = ''; - for (var i=0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var points = data.points; + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var points = data.points; - context.strokeStyle = color = '#' + ('00000' + ( data.lineColor | 0).toString(16)).substr(-6); + context.strokeStyle = color = '#' + ('00000' + ( data.lineColor | 0).toString(16)).substr(-6); - context.lineWidth = data.lineWidth; + context.lineWidth = data.lineWidth; - if(data.type == PIXI.Graphics.POLY) - { - context.beginPath(); + if(data.type === PIXI.Graphics.POLY) + { + context.beginPath(); - context.moveTo(points[0], points[1]); + context.moveTo(points[0], points[1]); - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } - // if the first and last point are the same close the path - much neater :) - if(points[0] == points[points.length-2] && points[1] == points[points.length-1]) - { - context.closePath(); - } + // if the first and last point are the same close the path - much neater :) + if(points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } - if(data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = color = '#' + ('00000' + ( data.fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if(data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.stroke(); - } - } - else if(data.type == PIXI.Graphics.RECT) - { + if(data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = color = '#' + ('00000' + ( data.fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if(data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.stroke(); + } + } + else if(data.type === PIXI.Graphics.RECT) + { - if(data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = color = '#' + ('00000' + ( data.fillColor | 0).toString(16)).substr(-6); - context.fillRect(points[0], points[1], points[2], points[3]); + if(data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = color = '#' + ('00000' + ( data.fillColor | 0).toString(16)).substr(-6); + context.fillRect(points[0], points[1], points[2], points[3]); - } - if(data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeRect(points[0], points[1], points[2], points[3]); - } + } + if(data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeRect(points[0], points[1], points[2], points[3]); + } - } - else if(data.type == PIXI.Graphics.CIRC) - { - // TODO - need to be Undefined! - context.beginPath(); - context.arc(points[0], points[1], points[2],0,2*Math.PI); - context.closePath(); + } + else if(data.type === PIXI.Graphics.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(points[0], points[1], points[2],0,2*Math.PI); + context.closePath(); - if(data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = color = '#' + ('00000' + ( data.fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if(data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.stroke(); - } - } - else if(data.type == PIXI.Graphics.ELIP) - { + if(data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = color = '#' + ('00000' + ( data.fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if(data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.stroke(); + } + } + else if(data.type === PIXI.Graphics.ELIP) + { - // elipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - var elipseData = data.points; + var ellipseData = data.points; - var w = elipseData[2] * 2; - var h = elipseData[3] * 2; + var w = ellipseData[2] * 2; + var h = ellipseData[3] * 2; - var x = elipseData[0] - w/2; - var y = elipseData[1] - h/2; + var x = ellipseData[0] - w/2; + var y = ellipseData[1] - h/2; - context.beginPath(); + context.beginPath(); - var kappa = .5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); + context.closePath(); - if(data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = color = '#' + ('00000' + ( data.fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if(data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.stroke(); - } - } - - }; -} + if(data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = color = '#' + ('00000' + ( data.fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if(data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.stroke(); + } + } + } +}; /* * Renders a graphics mask @@ -159,82 +159,79 @@ PIXI.CanvasGraphics.renderGraphics = function(graphics, context) */ PIXI.CanvasGraphics.renderGraphicsMask = function(graphics, context) { - var worldAlpha = graphics.worldAlpha; + var len = graphics.graphicsData.length; - var len = graphics.graphicsData.length; - if(len === 0)return; + if(len === 0) return; - if(len > 1) - { - len = 1; - console.log("Pixi.js warning: masks in canvas can only mask using the first path in the graphics object") - } + if(len > 1) + { + len = 1; + window.console.log('Pixi.js warning: masks in canvas can only mask using the first path in the graphics object'); + } - for (var i=0; i < 1; i++) - { - var data = graphics.graphicsData[i]; - var points = data.points; + for (var i = 0; i < 1; i++) + { + var data = graphics.graphicsData[i]; + var points = data.points; - if(data.type == PIXI.Graphics.POLY) - { - context.beginPath(); - context.moveTo(points[0], points[1]); + if(data.type === PIXI.Graphics.POLY) + { + context.beginPath(); + context.moveTo(points[0], points[1]); - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } - // if the first and last point are the same close the path - much neater :) - if(points[0] == points[points.length-2] && points[1] == points[points.length-1]) - { - context.closePath(); - } + // if the first and last point are the same close the path - much neater :) + if(points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } - } - else if(data.type == PIXI.Graphics.RECT) - { - context.beginPath(); - context.rect(points[0], points[1], points[2], points[3]); - context.closePath(); - } - else if(data.type == PIXI.Graphics.CIRC) - { - // TODO - need to be Undefined! - context.beginPath(); - context.arc(points[0], points[1], points[2],0,2*Math.PI); - context.closePath(); - } - else if(data.type == PIXI.Graphics.ELIP) - { + } + else if(data.type === PIXI.Graphics.RECT) + { + context.beginPath(); + context.rect(points[0], points[1], points[2], points[3]); + context.closePath(); + } + else if(data.type === PIXI.Graphics.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(points[0], points[1], points[2],0,2*Math.PI); + context.closePath(); + } + else if(data.type === PIXI.Graphics.ELIP) + { - // elipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - var elipseData = data.points; + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var ellipseData = data.points; - var w = elipseData[2] * 2; - var h = elipseData[3] * 2; + var w = ellipseData[2] * 2; + var h = ellipseData[3] * 2; - var x = elipseData[0] - w/2; - var y = elipseData[1] - h/2; + var x = ellipseData[0] - w/2; + var y = ellipseData[1] - h/2; - context.beginPath(); + context.beginPath(); - var kappa = .5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - - - }; -} + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + } +}; diff --git a/src/pixi/renderers/canvas/CanvasRenderer.js b/src/pixi/renderers/canvas/CanvasRenderer.js index aa15b4fe4..8062a6eb4 100644 --- a/src/pixi/renderers/canvas/CanvasRenderer.js +++ b/src/pixi/renderers/canvas/CanvasRenderer.js @@ -2,7 +2,6 @@ * @author Mat Groves http://matgroves.com/ @Doormat23 */ - /** * the CanvasRenderer draws the stage and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL. * Dont forget to add the view to your DOM or you will not see anything :) @@ -16,49 +15,63 @@ */ PIXI.CanvasRenderer = function(width, height, view, transparent) { - this.transparent = transparent; + this.transparent = transparent; - /** - * The width of the canvas view - * - * @property width - * @type Number - * @default 800 - */ - this.width = width || 800; + /** + * The width of the canvas view + * + * @property width + * @type Number + * @default 800 + */ + this.width = width || 800; - /** - * The height of the canvas view - * - * @property height - * @type Number - * @default 600 - */ - this.height = height || 600; + /** + * The height of the canvas view + * + * @property height + * @type Number + * @default 600 + */ + this.height = height || 600; - /** - * The canvas element that the everything is drawn to - * - * @property view - * @type Canvas - */ - this.view = view || document.createElement( 'canvas' ); + /** + * The canvas element that the everything is drawn to + * + * @property view + * @type Canvas + */ + this.view = view || document.createElement( 'canvas' ); - /** - * The canvas context that the everything is drawn to - * @property context - * @type Canvas 2d Context - */ - this.context = this.view.getContext("2d"); + /** + * The canvas context that the everything is drawn to + * @property context + * @type Canvas 2d Context + */ + this.context = this.view.getContext( '2d' ); + + //some filter variables + this.smoothProperty = null; + + if('imageSmoothingEnabled' in this.context) + this.smoothProperty = 'imageSmoothingEnabled'; + else if('webkitImageSmoothingEnabled' in this.context) + this.smoothProperty = 'webkitImageSmoothingEnabled'; + else if('mozImageSmoothingEnabled' in this.context) + this.smoothProperty = 'mozImageSmoothingEnabled'; + else if('oImageSmoothingEnabled' in this.context) + this.smoothProperty = 'oImageSmoothingEnabled'; + + this.scaleMode = null; + + this.refresh = true; + // hack to enable some hardware acceleration! + //this.view.style["transform"] = "translatez(0)"; - this.refresh = true; - // hack to enable some hardware acceleration! - //this.view.style["transform"] = "translatez(0)"; - this.view.width = this.width; - this.view.height = this.height; - this.count = 0; -} + this.view.height = this.height; + this.count = 0; +}; // constructor PIXI.CanvasRenderer.prototype.constructor = PIXI.CanvasRenderer; @@ -71,44 +84,42 @@ PIXI.CanvasRenderer.prototype.constructor = PIXI.CanvasRenderer; */ PIXI.CanvasRenderer.prototype.render = function(stage) { - - //stage.__childrenAdded = []; - //stage.__childrenRemoved = []; - - // update textures if need be - PIXI.texturesToUpdate = []; - PIXI.texturesToDestroy = []; - - PIXI.visibleCount++; - stage.updateTransform(); - - // update the background color - if(this.view.style.backgroundColor!=stage.backgroundColorString && !this.transparent)this.view.style.backgroundColor = stage.backgroundColorString; + //stage.__childrenAdded = []; + //stage.__childrenRemoved = []; - this.context.setTransform(1,0,0,1,0,0); - this.context.clearRect(0, 0, this.width, this.height) + // update textures if need be + PIXI.texturesToUpdate = []; + PIXI.texturesToDestroy = []; + + PIXI.visibleCount++; + stage.updateTransform(); + + // update the background color + if(this.view.style.backgroundColor !== stage.backgroundColorString && !this.transparent) + this.view.style.backgroundColor = stage.backgroundColorString; + + this.context.setTransform(1,0,0,1,0,0); + this.context.clearRect(0, 0, this.width, this.height); this.renderDisplayObject(stage); //as - + // run interaction! - if(stage.interactive) - { - //need to add some events! - if(!stage._interactiveEventsAdded) - { - stage._interactiveEventsAdded = true; - stage.interactionManager.setTarget(this); - } - } - - // remove frame updates.. - if(PIXI.Texture.frameUpdates.length > 0) - { - PIXI.Texture.frameUpdates = []; - } - - -} + if(stage.interactive) + { + //need to add some events! + if(!stage._interactiveEventsAdded) + { + stage._interactiveEventsAdded = true; + stage.interactionManager.setTarget(this); + } + } + + // remove frame updates.. + if(PIXI.Texture.frameUpdates.length > 0) + { + PIXI.Texture.frameUpdates = []; + } +}; /** * resizes the canvas view to the specified width and height @@ -119,12 +130,12 @@ PIXI.CanvasRenderer.prototype.render = function(stage) */ PIXI.CanvasRenderer.prototype.resize = function(width, height) { - this.width = width; - this.height = height; - - this.view.width = width; - this.view.height = height; -} + this.width = width; + this.height = height; + + this.view.width = width; + this.view.height = height; +}; /** * Renders a display object @@ -135,118 +146,116 @@ PIXI.CanvasRenderer.prototype.resize = function(width, height) */ PIXI.CanvasRenderer.prototype.renderDisplayObject = function(displayObject) { - // no loger recurrsive! - var transform; - var context = this.context; - - context.globalCompositeOperation = 'source-over'; - - // one the display object hits this. we can break the loop - var testObject = displayObject.last._iNext; - displayObject = displayObject.first; - - do - { - transform = displayObject.worldTransform; - - if(!displayObject.visible) - { - displayObject = displayObject.last._iNext; - continue; - } - - if(!displayObject.renderable) - { - displayObject = displayObject._iNext; - continue; - } - - if(displayObject instanceof PIXI.Sprite) - { + // no loger recurrsive! + var transform; + var context = this.context; - var frame = displayObject.texture.frame; + context.globalCompositeOperation = 'source-over'; - //ignore null sources - if(frame && frame.width && frame.height && displayObject.texture.baseTexture.source) - { - context.globalAlpha = displayObject.worldAlpha; - - context.setTransform(transform[0], transform[3], transform[1], transform[4], transform[2], transform[5]); - - context.drawImage(displayObject.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - (displayObject.anchor.x) * -frame.width, - (displayObject.anchor.y) * -frame.height, - frame.width, - frame.height); - } - } - else if(displayObject instanceof PIXI.Strip) - { - context.setTransform(transform[0], transform[3], transform[1], transform[4], transform[2], transform[5]) - this.renderStrip(displayObject); - } - else if(displayObject instanceof PIXI.TilingSprite) - { - context.setTransform(transform[0], transform[3], transform[1], transform[4], transform[2], transform[5]) - this.renderTilingSprite(displayObject); - } - else if(displayObject instanceof PIXI.CustomRenderable) - { - context.setTransform(transform[0], transform[3], transform[1], transform[4], transform[2], transform[5]); - displayObject.renderCanvas(this); - } - else if(displayObject instanceof PIXI.Graphics) - { - context.setTransform(transform[0], transform[3], transform[1], transform[4], transform[2], transform[5]) - PIXI.CanvasGraphics.renderGraphics(displayObject, context); - } - else if(displayObject instanceof PIXI.FilterBlock) - { - if(displayObject.data instanceof PIXI.Graphics) - { - var mask = displayObject.data; + // one the display object hits this. we can break the loop + var testObject = displayObject.last._iNext; + displayObject = displayObject.first; - if(displayObject.open) - { - context.save(); - - var cacheAlpha = mask.alpha; - var maskTransform = mask.worldTransform; - - context.setTransform(maskTransform[0], maskTransform[3], maskTransform[1], maskTransform[4], maskTransform[2], maskTransform[5]) - - mask.worldAlpha = 0.5; - - context.worldAlpha = 0; - - PIXI.CanvasGraphics.renderGraphicsMask(mask, context); - context.clip(); - - mask.worldAlpha = cacheAlpha; - } - else - { - context.restore(); - } - } - else - { - // only masks supported right now! - } - } - // count++ - displayObject = displayObject._iNext; - - - } - while(displayObject != testObject) + do + { + transform = displayObject.worldTransform; - -} + if(!displayObject.visible) + { + displayObject = displayObject.last._iNext; + continue; + } + + if(!displayObject.renderable) + { + displayObject = displayObject._iNext; + continue; + } + + if(displayObject instanceof PIXI.Sprite) + { + + var frame = displayObject.texture.frame; + + //ignore null sources + if(frame && frame.width && frame.height && displayObject.texture.baseTexture.source) + { + context.globalAlpha = displayObject.worldAlpha; + + context.setTransform(transform[0], transform[3], transform[1], transform[4], transform[2], transform[5]); + + //if smoothingEnabled is supported and we need to change the smoothing property for this texture + if(this.smoothProperty && this.scaleMode !== displayObject.texture.baseTexture.scaleMode) { + this.scaleMode = displayObject.texture.baseTexture.scaleMode; + context[this.smoothProperty] = (this.scaleMode === PIXI.BaseTexture.SCALE_MODE.LINEAR); + } + + context.drawImage(displayObject.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + (displayObject.anchor.x) * -frame.width, + (displayObject.anchor.y) * -frame.height, + frame.width, + frame.height); + } + } + else if(displayObject instanceof PIXI.Strip) + { + context.setTransform(transform[0], transform[3], transform[1], transform[4], transform[2], transform[5]); + this.renderStrip(displayObject); + } + else if(displayObject instanceof PIXI.TilingSprite) + { + context.setTransform(transform[0], transform[3], transform[1], transform[4], transform[2], transform[5]); + this.renderTilingSprite(displayObject); + } + else if(displayObject instanceof PIXI.CustomRenderable) + { + context.setTransform(transform[0], transform[3], transform[1], transform[4], transform[2], transform[5]); + displayObject.renderCanvas(this); + } + else if(displayObject instanceof PIXI.Graphics) + { + context.setTransform(transform[0], transform[3], transform[1], transform[4], transform[2], transform[5]); + PIXI.CanvasGraphics.renderGraphics(displayObject, context); + } + else if(displayObject instanceof PIXI.FilterBlock) + { + if(displayObject.data instanceof PIXI.Graphics) + { + var mask = displayObject.data; + + if(displayObject.open) + { + context.save(); + + var cacheAlpha = mask.alpha; + var maskTransform = mask.worldTransform; + + context.setTransform(maskTransform[0], maskTransform[3], maskTransform[1], maskTransform[4], maskTransform[2], maskTransform[5]); + + mask.worldAlpha = 0.5; + + context.worldAlpha = 0; + + PIXI.CanvasGraphics.renderGraphicsMask(mask, context); + context.clip(); + + mask.worldAlpha = cacheAlpha; + } + else + { + context.restore(); + } + } + } + //count++ + displayObject = displayObject._iNext; + } + while(displayObject !== testObject); +}; /** * Renders a flat strip @@ -257,33 +266,30 @@ PIXI.CanvasRenderer.prototype.renderDisplayObject = function(displayObject) */ PIXI.CanvasRenderer.prototype.renderStripFlat = function(strip) { - var context = this.context; - var verticies = strip.verticies; - var uvs = strip.uvs; - - var length = verticies.length/2; - this.count++; - - context.beginPath(); - for (var i=1; i < length-2; i++) - { - - // draw some triangles! - var index = i*2; - - var x0 = verticies[index], x1 = verticies[index+2], x2 = verticies[index+4]; - var y0 = verticies[index+1], y1 = verticies[index+3], y2 = verticies[index+5]; - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - - }; - - context.fillStyle = "#FF0000"; - context.fill(); - context.closePath(); -} + var context = this.context; + var verticies = strip.verticies; + + var length = verticies.length/2; + this.count++; + + context.beginPath(); + for (var i=1; i < length-2; i++) + { + // draw some triangles! + var index = i*2; + + var x0 = verticies[index], x1 = verticies[index+2], x2 = verticies[index+4]; + var y0 = verticies[index+1], y1 = verticies[index+3], y2 = verticies[index+5]; + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + } + + context.fillStyle = '#FF0000'; + context.fill(); + context.closePath(); +}; /** * Renders a tiling sprite @@ -294,29 +300,30 @@ PIXI.CanvasRenderer.prototype.renderStripFlat = function(strip) */ PIXI.CanvasRenderer.prototype.renderTilingSprite = function(sprite) { - var context = this.context; - - context.globalAlpha = sprite.worldAlpha; - - if(!sprite.__tilePattern) sprite.__tilePattern = context.createPattern(sprite.texture.baseTexture.source, "repeat"); - - context.beginPath(); - - var tilePosition = sprite.tilePosition; - var tileScale = sprite.tileScale; - + var context = this.context; + + context.globalAlpha = sprite.worldAlpha; + + if(!sprite.__tilePattern) + sprite.__tilePattern = context.createPattern(sprite.texture.baseTexture.source, 'repeat'); + + context.beginPath(); + + var tilePosition = sprite.tilePosition; + var tileScale = sprite.tileScale; + // offset context.scale(tileScale.x,tileScale.y); context.translate(tilePosition.x, tilePosition.y); - - context.fillStyle = sprite.__tilePattern; - context.fillRect(-tilePosition.x,-tilePosition.y,sprite.width / tileScale.x, sprite.height / tileScale.y); - - context.scale(1/tileScale.x, 1/tileScale.y); + + context.fillStyle = sprite.__tilePattern; + context.fillRect(-tilePosition.x,-tilePosition.y,sprite.width / tileScale.x, sprite.height / tileScale.y); + + context.scale(1/tileScale.x, 1/tileScale.y); context.translate(-tilePosition.x, -tilePosition.y); - + context.closePath(); -} +}; /** * Renders a strip @@ -327,55 +334,49 @@ PIXI.CanvasRenderer.prototype.renderTilingSprite = function(sprite) */ PIXI.CanvasRenderer.prototype.renderStrip = function(strip) { - var context = this.context; + var context = this.context; - // draw triangles!! - var verticies = strip.verticies; - var uvs = strip.uvs; - - var length = verticies.length/2; - this.count++; - for (var i=1; i < length-2; i++) - { - - // draw some triangles! - var index = i*2; - - var x0 = verticies[index], x1 = verticies[index+2], x2 = verticies[index+4]; - var y0 = verticies[index+1], y1 = verticies[index+3], y2 = verticies[index+5]; - - var u0 = uvs[index] * strip.texture.width, u1 = uvs[index+2] * strip.texture.width, u2 = uvs[index+4]* strip.texture.width; - var v0 = uvs[index+1]* strip.texture.height, v1 = uvs[index+3] * strip.texture.height, v2 = uvs[index+5]* strip.texture.height; + // draw triangles!! + var verticies = strip.verticies; + var uvs = strip.uvs; + var length = verticies.length/2; + this.count++; + + for (var i = 1; i < length-2; i++) + { + // draw some triangles! + var index = i*2; + + var x0 = verticies[index], x1 = verticies[index+2], x2 = verticies[index+4]; + var y0 = verticies[index+1], y1 = verticies[index+3], y2 = verticies[index+5]; + + var u0 = uvs[index] * strip.texture.width, u1 = uvs[index+2] * strip.texture.width, u2 = uvs[index+4]* strip.texture.width; + var v0 = uvs[index+1]* strip.texture.height, v1 = uvs[index+3] * strip.texture.height, v2 = uvs[index+5]* strip.texture.height; + + context.save(); + context.beginPath(); + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + context.closePath(); + + context.clip(); - context.save(); - context.beginPath(); - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - context.closePath(); - - context.clip(); - - // Compute matrix transform var delta = u0*v1 + v0*u2 + u1*v2 - v1*u2 - v0*u1 - u0*v2; - var delta_a = x0*v1 + v0*x2 + x1*v2 - v1*x2 - v0*x1 - x0*v2; - var delta_b = u0*x1 + x0*u2 + u1*x2 - x1*u2 - x0*u1 - u0*x2; - var delta_c = u0*v1*x2 + v0*x1*u2 + x0*u1*v2 - x0*v1*u2 - v0*u1*x2 - u0*x1*v2; - var delta_d = y0*v1 + v0*y2 + y1*v2 - v1*y2 - v0*y1 - y0*v2; - var delta_e = u0*y1 + y0*u2 + u1*y2 - y1*u2 - y0*u1 - u0*y2; - var delta_f = u0*v1*y2 + v0*y1*u2 + y0*u1*v2 - y0*v1*u2 - v0*u1*y2 - u0*y1*v2; - - - - - context.transform(delta_a/delta, delta_d/delta, - delta_b/delta, delta_e/delta, - delta_c/delta, delta_f/delta); - - context.drawImage(strip.texture.baseTexture.source, 0, 0); - context.restore(); - }; - -} + var deltaA = x0*v1 + v0*x2 + x1*v2 - v1*x2 - v0*x1 - x0*v2; + var deltaB = u0*x1 + x0*u2 + u1*x2 - x1*u2 - x0*u1 - u0*x2; + var deltaC = u0*v1*x2 + v0*x1*u2 + x0*u1*v2 - x0*v1*u2 - v0*u1*x2 - u0*x1*v2; + var deltaD = y0*v1 + v0*y2 + y1*v2 - v1*y2 - v0*y1 - y0*v2; + var deltaE = u0*y1 + y0*u2 + u1*y2 - y1*u2 - y0*u1 - u0*y2; + var deltaF = u0*v1*y2 + v0*y1*u2 + y0*u1*v2 - y0*v1*u2 - v0*u1*y2 - u0*y1*v2; + + context.transform(deltaA / delta, deltaD / delta, + deltaB / delta, deltaE / delta, + deltaC / delta, deltaF / delta); + + context.drawImage(strip.texture.baseTexture.source, 0, 0); + context.restore(); + } +}; diff --git a/src/pixi/renderers/webgl/PixiShader.js b/src/pixi/renderers/webgl/PixiShader.js index f7fbf8508..01721c073 100644 --- a/src/pixi/renderers/webgl/PixiShader.js +++ b/src/pixi/renderers/webgl/PixiShader.js @@ -12,26 +12,25 @@ PIXI.PixiShader = function() /** * @property {any} program - The WebGL program. */ - this.program; - + this.program = null; + /** * @property {array} fragmentSrc - The fragment shader. */ this.fragmentSrc = [ - "precision lowp float;", - "varying vec2 vTextureCoord;", - "varying float vColor;", - "uniform sampler2D uSampler;", - "void main(void) {", - "gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor;", - "}" + 'precision lowp float;', + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + 'uniform sampler2D uSampler;', + 'void main(void) {', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor;', + '}' ]; /** * @property {number} textureCount - A local texture counter for multi-texture shaders. */ this.textureCount = 0; - }; /** @@ -39,23 +38,23 @@ PIXI.PixiShader = function() */ PIXI.PixiShader.prototype.init = function() { - var program = PIXI.compileProgram(this.vertexSrc || PIXI.PixiShader.defaultVertexSrc, this.fragmentSrc) - + var program = PIXI.compileProgram(this.vertexSrc || PIXI.PixiShader.defaultVertexSrc, this.fragmentSrc); + var gl = PIXI.gl; gl.useProgram(program); - + // get and store the uniforms for the shader - this.uSampler = gl.getUniformLocation(program, "uSampler"); - this.projectionVector = gl.getUniformLocation(program, "projectionVector"); - this.offsetVector = gl.getUniformLocation(program, "offsetVector"); - this.dimensions = gl.getUniformLocation(program, "dimensions"); - + this.uSampler = gl.getUniformLocation(program, 'uSampler'); + this.projectionVector = gl.getUniformLocation(program, 'projectionVector'); + this.offsetVector = gl.getUniformLocation(program, 'offsetVector'); + this.dimensions = gl.getUniformLocation(program, 'dimensions'); + // get and store the attributes - this.aVertexPosition = gl.getAttribLocation(program, "aVertexPosition"); - this.colorAttribute = gl.getAttribLocation(program, "aColor"); - this.aTextureCoord = gl.getAttribLocation(program, "aTextureCoord"); - + this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition'); + this.colorAttribute = gl.getAttribLocation(program, 'aColor'); + this.aTextureCoord = gl.getAttribLocation(program, 'aTextureCoord'); + // add those custom shaders! for (var key in this.uniforms) { @@ -64,7 +63,7 @@ PIXI.PixiShader.prototype.init = function() } this.initUniforms(); - + this.program = program; }; @@ -80,13 +79,14 @@ PIXI.PixiShader.prototype.initUniforms = function() this.textureCount = 1; var uniform; - - for (var key in this.uniforms) + + for (var key in this.uniforms) { - var uniform = this.uniforms[key]; + uniform = this.uniforms[key]; + var type = uniform.type; - if (type == 'sampler2D') + if (type === 'sampler2D') { uniform._init = false; @@ -95,21 +95,21 @@ PIXI.PixiShader.prototype.initUniforms = function() this.initSampler2D(uniform); } } - else if (type == 'mat2' || type == 'mat3' || type == 'mat4') + else if (type === 'mat2' || type === 'mat3' || type === 'mat4') { // These require special handling uniform.glMatrix = true; uniform.glValueLength = 1; - if (type == 'mat2') + if (type === 'mat2') { uniform.glFunc = PIXI.gl.uniformMatrix2fv; } - else if (type == 'mat3') + else if (type === 'mat3') { uniform.glFunc = PIXI.gl.uniformMatrix3fv; } - else if (type == 'mat4') + else if (type === 'mat4') { uniform.glFunc = PIXI.gl.uniformMatrix4fv; } @@ -119,15 +119,15 @@ PIXI.PixiShader.prototype.initUniforms = function() // GL function reference uniform.glFunc = PIXI.gl['uniform' + type]; - if (type == '2f' || type == '2i') + if (type === '2f' || type === '2i') { uniform.glValueLength = 2; } - else if (type == '3f' || type == '3i') + else if (type === '3f' || type === '3i') { uniform.glValueLength = 3; } - else if (type == '4f' || type == '4i') + else if (type === '4f' || type === '4i') { uniform.glValueLength = 4; } @@ -137,7 +137,7 @@ PIXI.PixiShader.prototype.initUniforms = function() } } } - + }; /** @@ -224,12 +224,12 @@ PIXI.PixiShader.prototype.syncUniforms = function() var uniform; // This would probably be faster in an array and it would guarantee key order - for (var key in this.uniforms) + for (var key in this.uniforms) { uniform = this.uniforms[key]; - if (uniform.glValueLength == 1) + if (uniform.glValueLength === 1) { if (uniform.glMatrix === true) { @@ -240,19 +240,19 @@ PIXI.PixiShader.prototype.syncUniforms = function() uniform.glFunc.call(PIXI.gl, uniform.uniformLocation, uniform.value); } } - else if (uniform.glValueLength == 2) + else if (uniform.glValueLength === 2) { uniform.glFunc.call(PIXI.gl, uniform.uniformLocation, uniform.value.x, uniform.value.y); } - else if (uniform.glValueLength == 3) + else if (uniform.glValueLength === 3) { uniform.glFunc.call(PIXI.gl, uniform.uniformLocation, uniform.value.x, uniform.value.y, uniform.value.z); } - else if (uniform.glValueLength == 4) + else if (uniform.glValueLength === 4) { uniform.glFunc.call(PIXI.gl, uniform.uniformLocation, uniform.value.x, uniform.value.y, uniform.value.z, uniform.value.w); } - else if (uniform.type == 'sampler2D') + else if (uniform.type === 'sampler2D') { if (uniform._init) { @@ -267,26 +267,25 @@ PIXI.PixiShader.prototype.syncUniforms = function() } } } - + }; PIXI.PixiShader.defaultVertexSrc = [ - - "attribute vec2 aVertexPosition;", - "attribute vec2 aTextureCoord;", - "attribute float aColor;", + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + 'attribute float aColor;', - "uniform vec2 projectionVector;", - "uniform vec2 offsetVector;", - "varying vec2 vTextureCoord;", + 'uniform vec2 projectionVector;', + 'uniform vec2 offsetVector;', + 'varying vec2 vTextureCoord;', - "varying float vColor;", + 'varying float vColor;', - "const vec2 center = vec2(-1.0, 1.0);", - - "void main(void) {", - "gl_Position = vec4( ((aVertexPosition + offsetVector) / projectionVector) + center , 0.0, 1.0);", - "vTextureCoord = aTextureCoord;", - "vColor = aColor;", - "}" + 'const vec2 center = vec2(-1.0, 1.0);', + + 'void main(void) {', + ' gl_Position = vec4( ((aVertexPosition + offsetVector) / projectionVector) + center , 0.0, 1.0);', + ' vTextureCoord = aTextureCoord;', + ' vColor = aColor;', + '}' ]; diff --git a/src/pixi/renderers/webgl/PrimitiveShader.js b/src/pixi/renderers/webgl/PrimitiveShader.js index 1089d84de..b206e79a3 100644 --- a/src/pixi/renderers/webgl/PrimitiveShader.js +++ b/src/pixi/renderers/webgl/PrimitiveShader.js @@ -5,53 +5,54 @@ PIXI.PrimitiveShader = function() { - // the webGL program.. - this.program; - + // the webGL program.. + this.program = null; + this.fragmentSrc = [ - "precision mediump float;", - "varying vec4 vColor;", - "void main(void) {", - "gl_FragColor = vColor;", - "}" + 'precision mediump float;', + 'varying vec4 vColor;', + + 'void main(void) {', + ' gl_FragColor = vColor;', + '}' ]; this.vertexSrc = [ - "attribute vec2 aVertexPosition;", - "attribute vec4 aColor;", - "uniform mat3 translationMatrix;", - "uniform vec2 projectionVector;", - "uniform vec2 offsetVector;", - "uniform float alpha;", - "varying vec4 vColor;", - "void main(void) {", - "vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);", - "v -= offsetVector.xyx;", - "gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);", - "vColor = aColor * alpha;", - "}" + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + 'uniform mat3 translationMatrix;', + 'uniform vec2 projectionVector;', + 'uniform vec2 offsetVector;', + 'uniform float alpha;', + 'varying vec4 vColor;', + + 'void main(void) {', + ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', + ' v -= offsetVector.xyx;', + ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', + ' vColor = aColor * alpha;', + '}' ]; - -} +}; PIXI.PrimitiveShader.prototype.init = function() { - var program = PIXI.compileProgram(this.vertexSrc, this.fragmentSrc); - - var gl = PIXI.gl; - - gl.useProgram(program); - - // get and store the uniforms for the shader - this.projectionVector = gl.getUniformLocation(program, "projectionVector"); - this.offsetVector = gl.getUniformLocation(program, "offsetVector"); - - // get and store the attributes - this.aVertexPosition = gl.getAttribLocation(program, "aVertexPosition"); - this.colorAttribute = gl.getAttribLocation(program, "aColor"); - - this.translationMatrix = gl.getUniformLocation(program, "translationMatrix"); - this.alpha = gl.getUniformLocation(program, "alpha"); + var program = PIXI.compileProgram(this.vertexSrc, this.fragmentSrc); - this.program = program; -} + var gl = PIXI.gl; + + gl.useProgram(program); + + // get and store the uniforms for the shader + this.projectionVector = gl.getUniformLocation(program, 'projectionVector'); + this.offsetVector = gl.getUniformLocation(program, 'offsetVector'); + + // get and store the attributes + this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition'); + this.colorAttribute = gl.getAttribLocation(program, 'aColor'); + + this.translationMatrix = gl.getUniformLocation(program, 'translationMatrix'); + this.alpha = gl.getUniformLocation(program, 'alpha'); + + this.program = program; +}; diff --git a/src/pixi/renderers/webgl/StripShader.js b/src/pixi/renderers/webgl/StripShader.js index 80d8394c4..e82f33ad4 100644 --- a/src/pixi/renderers/webgl/StripShader.js +++ b/src/pixi/renderers/webgl/StripShader.js @@ -5,61 +5,63 @@ PIXI.StripShader = function() { - // the webGL program.. - this.program; - + // the webGL program.. + this.program = null; + this.fragmentSrc = [ - "precision mediump float;", - "varying vec2 vTextureCoord;", - "varying float vColor;", - "uniform float alpha;", - "uniform sampler2D uSampler;", - "void main(void) {", - "gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y));", - "gl_FragColor = gl_FragColor * alpha;", - "}" + 'precision mediump float;', + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + 'uniform float alpha;', + 'uniform sampler2D uSampler;', + + 'void main(void) {', + ' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y));', + ' gl_FragColor = gl_FragColor * alpha;', + '}' ]; this.vertexSrc = [ - "attribute vec2 aVertexPosition;", - "attribute vec2 aTextureCoord;", - "attribute float aColor;", - "uniform mat3 translationMatrix;", - "uniform vec2 projectionVector;", - "varying vec2 vTextureCoord;", - "varying vec2 offsetVector;", - "varying float vColor;", - "void main(void) {", - "vec3 v = translationMatrix * vec3(aVertexPosition, 1.0);", - "v -= offsetVector.xyx;", - "gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / projectionVector.y + 1.0 , 0.0, 1.0);", - "vTextureCoord = aTextureCoord;", - "vColor = aColor;", - "}" + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + 'attribute float aColor;', + 'uniform mat3 translationMatrix;', + 'uniform vec2 projectionVector;', + 'varying vec2 vTextureCoord;', + 'varying vec2 offsetVector;', + 'varying float vColor;', + + 'void main(void) {', + ' vec3 v = translationMatrix * vec3(aVertexPosition, 1.0);', + ' v -= offsetVector.xyx;', + ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / projectionVector.y + 1.0 , 0.0, 1.0);', + ' vTextureCoord = aTextureCoord;', + ' vColor = aColor;', + '}' ]; -} +}; PIXI.StripShader.prototype.init = function() { - var program = PIXI.compileProgram(this.vertexSrc, this.fragmentSrc) - - var gl = PIXI.gl; - + var program = PIXI.compileProgram(this.vertexSrc, this.fragmentSrc); + + var gl = PIXI.gl; + gl.useProgram(program); - // get and store the uniforms for the shader - this.uSampler = gl.getUniformLocation(program, "uSampler"); - this.projectionVector = gl.getUniformLocation(program, "projectionVector"); - this.offsetVector = gl.getUniformLocation(program, "offsetVector"); - this.colorAttribute = gl.getAttribLocation(program, "aColor"); - //this.dimensions = gl.getUniformLocation(this.program, "dimensions"); - - // get and store the attributes - this.aVertexPosition = gl.getAttribLocation(program, "aVertexPosition"); - this.aTextureCoord = gl.getAttribLocation(program, "aTextureCoord"); - - this.translationMatrix = gl.getUniformLocation(program, "translationMatrix"); - this.alpha = gl.getUniformLocation(program, "alpha"); - - this.program = program; -} + // get and store the uniforms for the shader + this.uSampler = gl.getUniformLocation(program, 'uSampler'); + this.projectionVector = gl.getUniformLocation(program, 'projectionVector'); + this.offsetVector = gl.getUniformLocation(program, 'offsetVector'); + this.colorAttribute = gl.getAttribLocation(program, 'aColor'); + //this.dimensions = gl.getUniformLocation(this.program, 'dimensions'); + + // get and store the attributes + this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition'); + this.aTextureCoord = gl.getAttribLocation(program, 'aTextureCoord'); + + this.translationMatrix = gl.getUniformLocation(program, 'translationMatrix'); + this.alpha = gl.getUniformLocation(program, 'alpha'); + + this.program = program; +}; diff --git a/src/pixi/renderers/webgl/WebGLBatch.js b/src/pixi/renderers/webgl/WebGLBatch.js index fa09fc433..ba719b369 100644 --- a/src/pixi/renderers/webgl/WebGLBatch.js +++ b/src/pixi/renderers/webgl/WebGLBatch.js @@ -9,35 +9,35 @@ PIXI._batchs = []; */ PIXI._getBatch = function(gl) { - if(PIXI._batchs.length == 0) - { - return new PIXI.WebGLBatch(gl); - } - else - { - return PIXI._batchs.pop(); - } -} + if(PIXI._batchs.length === 0) + { + return new PIXI.WebGLBatch(gl); + } + else + { + return PIXI._batchs.pop(); + } +}; /** * @private */ PIXI._returnBatch = function(batch) { - batch.clean(); - PIXI._batchs.push(batch); -} + batch.clean(); + PIXI._batchs.push(batch); +}; /** * @private */ PIXI._restoreBatchs = function(gl) { - for (var i=0; i < PIXI._batchs.length; i++) - { - PIXI._batchs[i].restoreLostContext(gl); - }; -} + for (var i=0; i < PIXI._batchs.length; i++) + { + PIXI._batchs[i].restoreLostContext(gl); + } +}; /** * A WebGLBatch Enables a group of sprites to be drawn using the same settings. @@ -53,17 +53,17 @@ PIXI._restoreBatchs = function(gl) */ PIXI.WebGLBatch = function(gl) { - this.gl = gl; - - this.size = 0; + this.gl = gl; - this.vertexBuffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.uvBuffer = gl.createBuffer(); - this.colorBuffer = gl.createBuffer(); - this.blendMode = PIXI.blendModes.NORMAL; - this.dynamicSize = 1; -} + this.size = 0; + + this.vertexBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.uvBuffer = gl.createBuffer(); + this.colorBuffer = gl.createBuffer(); + this.blendMode = PIXI.blendModes.NORMAL; + this.dynamicSize = 1; +}; // constructor PIXI.WebGLBatch.prototype.constructor = PIXI.WebGLBatch; @@ -75,17 +75,17 @@ PIXI.WebGLBatch.prototype.constructor = PIXI.WebGLBatch; */ PIXI.WebGLBatch.prototype.clean = function() { - this.verticies = []; - this.uvs = []; - this.indices = []; - this.colors = []; - this.dynamicSize = 1; - this.texture = null; - this.last = null; - this.size = 0; - this.head; - this.tail; -} + this.verticies = []; + this.uvs = []; + this.indices = []; + this.colors = []; + this.dynamicSize = 1; + this.texture = null; + this.last = null; + this.size = 0; + this.head = null; + this.tail = null; +}; /** * Recreates the buffers in the event of a context loss @@ -95,32 +95,32 @@ PIXI.WebGLBatch.prototype.clean = function() */ PIXI.WebGLBatch.prototype.restoreLostContext = function(gl) { - this.gl = gl; - this.vertexBuffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.uvBuffer = gl.createBuffer(); - this.colorBuffer = gl.createBuffer(); -} + this.gl = gl; + this.vertexBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.uvBuffer = gl.createBuffer(); + this.colorBuffer = gl.createBuffer(); +}; /** * inits the batch's texture and blend mode based if the supplied sprite * * @method init * @param sprite {Sprite} the first sprite to be added to the batch. Only sprites with - * the same base texture and blend mode will be allowed to be added to this batch - */ + * the same base texture and blend mode will be allowed to be added to this batch + */ PIXI.WebGLBatch.prototype.init = function(sprite) { - sprite.batch = this; - this.dirty = true; - this.blendMode = sprite.blendMode; - this.texture = sprite.texture.baseTexture; - this.head = sprite; - this.tail = sprite; - this.size = 1; + sprite.batch = this; + this.dirty = true; + this.blendMode = sprite.blendMode; + this.texture = sprite.texture.baseTexture; + this.head = sprite; + this.tail = sprite; + this.size = 1; - this.growBatch(); -} + this.growBatch(); +}; /** * inserts a sprite before the specified sprite @@ -128,27 +128,27 @@ PIXI.WebGLBatch.prototype.init = function(sprite) * @method insertBefore * @param sprite {Sprite} the sprite to be added * @param nextSprite {nextSprite} the first sprite will be inserted before this sprite - */ + */ PIXI.WebGLBatch.prototype.insertBefore = function(sprite, nextSprite) { - this.size++; + this.size++; - sprite.batch = this; - this.dirty = true; - var tempPrev = nextSprite.__prev; - nextSprite.__prev = sprite; - sprite.__next = nextSprite; + sprite.batch = this; + this.dirty = true; + var tempPrev = nextSprite.__prev; + nextSprite.__prev = sprite; + sprite.__next = nextSprite; - if(tempPrev) - { - sprite.__prev = tempPrev; - tempPrev.__next = sprite; - } - else - { - this.head = sprite; - } -} + if(tempPrev) + { + sprite.__prev = tempPrev; + tempPrev.__next = sprite; + } + else + { + this.head = sprite; + } +}; /** * inserts a sprite after the specified sprite @@ -156,72 +156,72 @@ PIXI.WebGLBatch.prototype.insertBefore = function(sprite, nextSprite) * @method insertAfter * @param sprite {Sprite} the sprite to be added * @param previousSprite {Sprite} the first sprite will be inserted after this sprite - */ + */ PIXI.WebGLBatch.prototype.insertAfter = function(sprite, previousSprite) { - this.size++; + this.size++; - sprite.batch = this; - this.dirty = true; + sprite.batch = this; + this.dirty = true; - var tempNext = previousSprite.__next; - previousSprite.__next = sprite; - sprite.__prev = previousSprite; + var tempNext = previousSprite.__next; + previousSprite.__next = sprite; + sprite.__prev = previousSprite; - if(tempNext) - { - sprite.__next = tempNext; - tempNext.__prev = sprite; - } - else - { - this.tail = sprite - } -} + if(tempNext) + { + sprite.__next = tempNext; + tempNext.__prev = sprite; + } + else + { + this.tail = sprite; + } +}; /** * removes a sprite from the batch * * @method remove * @param sprite {Sprite} the sprite to be removed - */ + */ PIXI.WebGLBatch.prototype.remove = function(sprite) { - this.size--; + this.size--; - if(this.size == 0) - { - sprite.batch = null; - sprite.__prev = null; - sprite.__next = null; - return; - } + if(this.size === 0) + { + sprite.batch = null; + sprite.__prev = null; + sprite.__next = null; + return; + } - if(sprite.__prev) - { - sprite.__prev.__next = sprite.__next; - } - else - { - this.head = sprite.__next; - this.head.__prev = null; - } + if(sprite.__prev) + { + sprite.__prev.__next = sprite.__next; + } + else + { + this.head = sprite.__next; + this.head.__prev = null; + } - if(sprite.__next) - { - sprite.__next.__prev = sprite.__prev; - } - else - { - this.tail = sprite.__prev; - this.tail.__next = null - } + if(sprite.__next) + { + sprite.__next.__prev = sprite.__prev; + } + else + { + this.tail = sprite.__prev; + this.tail.__next = null; + } - sprite.batch = null; - sprite.__next = null; - sprite.__prev = null; - this.dirty = true; -} + sprite.batch = null; + sprite.__next = null; + sprite.__prev = null; + this.dirty = true; +}; /** * Splits the batch into two with the specified sprite being the start of the new batch. @@ -232,62 +232,62 @@ PIXI.WebGLBatch.prototype.remove = function(sprite) */ PIXI.WebGLBatch.prototype.split = function(sprite) { - this.dirty = true; + this.dirty = true; - var batch = new PIXI.WebGLBatch(this.gl); - batch.init(sprite); - batch.texture = this.texture; - batch.tail = this.tail; + var batch = new PIXI.WebGLBatch(this.gl); + batch.init(sprite); + batch.texture = this.texture; + batch.tail = this.tail; - this.tail = sprite.__prev; - this.tail.__next = null; + this.tail = sprite.__prev; + this.tail.__next = null; - sprite.__prev = null; - // return a splite batch! + sprite.__prev = null; + // return a splite batch! - // TODO this size is wrong! - // need to recalculate :/ problem with a linked list! - // unless it gets calculated in the "clean"? + // TODO this size is wrong! + // need to recalculate :/ problem with a linked list! + // unless it gets calculated in the "clean"? - // need to loop through items as there is no way to know the length on a linked list :/ - var tempSize = 0; - while(sprite) - { - tempSize++; - sprite.batch = batch; - sprite = sprite.__next; - } + // need to loop through items as there is no way to know the length on a linked list :/ + var tempSize = 0; + while(sprite) + { + tempSize++; + sprite.batch = batch; + sprite = sprite.__next; + } - batch.size = tempSize; - this.size -= tempSize; + batch.size = tempSize; + this.size -= tempSize; - return batch; -} + return batch; +}; /** * Merges two batchs together * * @method merge - * @param batch {WebGLBatch} the batch that will be merged + * @param batch {WebGLBatch} the batch that will be merged */ PIXI.WebGLBatch.prototype.merge = function(batch) { - this.dirty = true; + this.dirty = true; - this.tail.__next = batch.head; - batch.head.__prev = this.tail; + this.tail.__next = batch.head; + batch.head.__prev = this.tail; - this.size += batch.size; + this.size += batch.size; - this.tail = batch.tail; + this.tail = batch.tail; - var sprite = batch.head; - while(sprite) - { - sprite.batch = this; - sprite = sprite.__next; - } -} + var sprite = batch.head; + while(sprite) + { + sprite.batch = this; + sprite = sprite.__next; + } +}; /** * Grows the size of the batch. As the elements in the batch cannot have a dynamic size this @@ -298,51 +298,52 @@ PIXI.WebGLBatch.prototype.merge = function(batch) */ PIXI.WebGLBatch.prototype.growBatch = function() { - var gl = this.gl; - if( this.size == 1) - { - this.dynamicSize = 1; - } - else - { - this.dynamicSize = this.size * 1.5 - } - // grow verts - this.verticies = new Float32Array(this.dynamicSize * 8); + var gl = this.gl; + if( this.size === 1) + { + this.dynamicSize = 1; + } + else + { + this.dynamicSize = this.size * 1.5; + } - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER,this.verticies , gl.DYNAMIC_DRAW); + // grow verts + this.verticies = new Float32Array(this.dynamicSize * 8); - this.uvs = new Float32Array( this.dynamicSize * 8 ); - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.uvs , gl.DYNAMIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER,this.verticies , gl.DYNAMIC_DRAW); - this.dirtyUVS = true; + this.uvs = new Float32Array( this.dynamicSize * 8 ); + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvs , gl.DYNAMIC_DRAW); - this.colors = new Float32Array( this.dynamicSize * 4 ); - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.colors , gl.DYNAMIC_DRAW); + this.dirtyUVS = true; - this.dirtyColors = true; + this.colors = new Float32Array( this.dynamicSize * 4 ); + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.colors , gl.DYNAMIC_DRAW); - this.indices = new Uint16Array(this.dynamicSize * 6); - var length = this.indices.length/6; + this.dirtyColors = true; - for (var i=0; i < length; i++) - { - var index2 = i * 6; - var index3 = i * 4; - this.indices[index2 + 0] = index3 + 0; - this.indices[index2 + 1] = index3 + 1; - this.indices[index2 + 2] = index3 + 2; - this.indices[index2 + 3] = index3 + 0; - this.indices[index2 + 4] = index3 + 2; - this.indices[index2 + 5] = index3 + 3; - }; + this.indices = new Uint16Array(this.dynamicSize * 6); + var length = this.indices.length/6; - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + for (var i = 0; i < length; i++) + { + var index2 = i * 6; + var index3 = i * 4; + this.indices[index2 + 0] = index3 + 0; + this.indices[index2 + 1] = index3 + 1; + this.indices[index2 + 2] = index3 + 2; + this.indices[index2 + 3] = index3 + 0; + this.indices[index2 + 4] = index3 + 2; + this.indices[index2 + 5] = index3 + 3; + } + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); -} +}; /** * Refresh's all the data in the batch and sync's it with the webGL buffers @@ -351,54 +352,51 @@ PIXI.WebGLBatch.prototype.growBatch = function() */ PIXI.WebGLBatch.prototype.refresh = function() { - var gl = this.gl; + if (this.dynamicSize < this.size) + { + this.growBatch(); + } - if (this.dynamicSize < this.size) - { - this.growBatch(); - } + var indexRun = 0; + var index, colorIndex; - var indexRun = 0; - var worldTransform, width, height, aX, aY, w0, w1, h0, h1, index; - var a, b, c, d, tx, ty; + var displayObject = this.head; - var displayObject = this.head; + while(displayObject) + { + index = indexRun * 8; - while(displayObject) - { - index = indexRun * 8; + var texture = displayObject.texture; - var texture = displayObject.texture; + var frame = texture.frame; + var tw = texture.baseTexture.width; + var th = texture.baseTexture.height; - var frame = texture.frame; - var tw = texture.baseTexture.width; - var th = texture.baseTexture.height; + this.uvs[index + 0] = frame.x / tw; + this.uvs[index +1] = frame.y / th; - this.uvs[index + 0] = frame.x / tw; - this.uvs[index +1] = frame.y / th; + this.uvs[index +2] = (frame.x + frame.width) / tw; + this.uvs[index +3] = frame.y / th; - this.uvs[index +2] = (frame.x + frame.width) / tw; - this.uvs[index +3] = frame.y / th; + this.uvs[index +4] = (frame.x + frame.width) / tw; + this.uvs[index +5] = (frame.y + frame.height) / th; - this.uvs[index +4] = (frame.x + frame.width) / tw; - this.uvs[index +5] = (frame.y + frame.height) / th; + this.uvs[index +6] = frame.x / tw; + this.uvs[index +7] = (frame.y + frame.height) / th; - this.uvs[index +6] = frame.x / tw; - this.uvs[index +7] = (frame.y + frame.height) / th; + displayObject.updateFrame = false; - displayObject.updateFrame = false; + colorIndex = indexRun * 4; + this.colors[colorIndex] = this.colors[colorIndex + 1] = this.colors[colorIndex + 2] = this.colors[colorIndex + 3] = displayObject.worldAlpha; - colorIndex = indexRun * 4; - this.colors[colorIndex] = this.colors[colorIndex + 1] = this.colors[colorIndex + 2] = this.colors[colorIndex + 3] = displayObject.worldAlpha; + displayObject = displayObject.__next; - displayObject = displayObject.__next; + indexRun++; + } - indexRun ++; - } - - this.dirtyUVS = true; - this.dirtyColors = true; -} + this.dirtyUVS = true; + this.dirtyColors = true; +}; /** * Updates all the relevant geometry and uploads the data to the GPU @@ -407,103 +405,102 @@ PIXI.WebGLBatch.prototype.refresh = function() */ PIXI.WebGLBatch.prototype.update = function() { - var gl = this.gl; - var worldTransform, width, height, aX, aY, w0, w1, h0, h1, index, index2, index3 + var worldTransform, width, height, aX, aY, w0, w1, h0, h1, index; - var a, b, c, d, tx, ty; + var a, b, c, d, tx, ty; - var indexRun = 0; + var indexRun = 0; - var displayObject = this.head; - var verticies = this.verticies; - var uvs = this.uvs; - var colors = this.colors; - - while(displayObject) - { - if(displayObject.vcount === PIXI.visibleCount) - { - width = displayObject.texture.frame.width; - height = displayObject.texture.frame.height; + var displayObject = this.head; + var verticies = this.verticies; + var uvs = this.uvs; + var colors = this.colors; - // TODO trim?? - aX = displayObject.anchor.x;// - displayObject.texture.trim.x - aY = displayObject.anchor.y; //- displayObject.texture.trim.y - w0 = width * (1-aX); - w1 = width * -aX; + while(displayObject) + { + if(displayObject.vcount === PIXI.visibleCount) + { + width = displayObject.texture.frame.width; + height = displayObject.texture.frame.height; - h0 = height * (1-aY); - h1 = height * -aY; + // TODO trim?? + aX = displayObject.anchor.x;// - displayObject.texture.trim.x + aY = displayObject.anchor.y; //- displayObject.texture.trim.y + w0 = width * (1-aX); + w1 = width * -aX; - index = indexRun * 8; + h0 = height * (1-aY); + h1 = height * -aY; - worldTransform = displayObject.worldTransform; + index = indexRun * 8; - a = worldTransform[0]; - b = worldTransform[3]; - c = worldTransform[1]; - d = worldTransform[4]; - tx = worldTransform[2]; - ty = worldTransform[5]; + worldTransform = displayObject.worldTransform; - verticies[index + 0 ] = a * w1 + c * h1 + tx; - verticies[index + 1 ] = d * h1 + b * w1 + ty; + a = worldTransform[0]; + b = worldTransform[3]; + c = worldTransform[1]; + d = worldTransform[4]; + tx = worldTransform[2]; + ty = worldTransform[5]; - verticies[index + 2 ] = a * w0 + c * h1 + tx; - verticies[index + 3 ] = d * h1 + b * w0 + ty; + verticies[index + 0 ] = a * w1 + c * h1 + tx; + verticies[index + 1 ] = d * h1 + b * w1 + ty; - verticies[index + 4 ] = a * w0 + c * h0 + tx; - verticies[index + 5 ] = d * h0 + b * w0 + ty; + verticies[index + 2 ] = a * w0 + c * h1 + tx; + verticies[index + 3 ] = d * h1 + b * w0 + ty; - verticies[index + 6] = a * w1 + c * h0 + tx; - verticies[index + 7] = d * h0 + b * w1 + ty; + verticies[index + 4 ] = a * w0 + c * h0 + tx; + verticies[index + 5 ] = d * h0 + b * w0 + ty; - if(displayObject.updateFrame || displayObject.texture.updateFrame) - { - this.dirtyUVS = true; + verticies[index + 6] = a * w1 + c * h0 + tx; + verticies[index + 7] = d * h0 + b * w1 + ty; - var texture = displayObject.texture; + if(displayObject.updateFrame || displayObject.texture.updateFrame) + { + this.dirtyUVS = true; - var frame = texture.frame; - var tw = texture.baseTexture.width; - var th = texture.baseTexture.height; + var texture = displayObject.texture; - uvs[index + 0] = frame.x / tw; - uvs[index +1] = frame.y / th; + var frame = texture.frame; + var tw = texture.baseTexture.width; + var th = texture.baseTexture.height; - uvs[index +2] = (frame.x + frame.width) / tw; - uvs[index +3] = frame.y / th; + uvs[index + 0] = frame.x / tw; + uvs[index +1] = frame.y / th; - uvs[index +4] = (frame.x + frame.width) / tw; - uvs[index +5] = (frame.y + frame.height) / th; + uvs[index +2] = (frame.x + frame.width) / tw; + uvs[index +3] = frame.y / th; - uvs[index +6] = frame.x / tw; - uvs[index +7] = (frame.y + frame.height) / th; + uvs[index +4] = (frame.x + frame.width) / tw; + uvs[index +5] = (frame.y + frame.height) / th; - displayObject.updateFrame = false; - } + uvs[index +6] = frame.x / tw; + uvs[index +7] = (frame.y + frame.height) / th; - // TODO this probably could do with some optimisation.... - if(displayObject.cacheAlpha != displayObject.worldAlpha) - { - displayObject.cacheAlpha = displayObject.worldAlpha; + displayObject.updateFrame = false; + } - var colorIndex = indexRun * 4; - colors[colorIndex] = colors[colorIndex + 1] = colors[colorIndex + 2] = colors[colorIndex + 3] = displayObject.worldAlpha; - this.dirtyColors = true; - } - } - else - { - index = indexRun * 8; + // TODO this probably could do with some optimisation.... + if(displayObject.cacheAlpha !== displayObject.worldAlpha) + { + displayObject.cacheAlpha = displayObject.worldAlpha; - verticies[index + 0 ] = verticies[index + 1 ] = verticies[index + 2 ] = verticies[index + 3 ] = verticies[index + 4 ] = verticies[index + 5 ] = verticies[index + 6] = verticies[index + 7] = 0; - } + var colorIndex = indexRun * 4; + colors[colorIndex] = colors[colorIndex + 1] = colors[colorIndex + 2] = colors[colorIndex + 3] = displayObject.worldAlpha; + this.dirtyColors = true; + } + } + else + { + index = indexRun * 8; - indexRun++; - displayObject = displayObject.__next; - } -} + verticies[index + 0 ] = verticies[index + 1 ] = verticies[index + 2 ] = verticies[index + 3 ] = verticies[index + 4 ] = verticies[index + 5 ] = verticies[index + 6] = verticies[index + 7] = 0; + } + + indexRun++; + displayObject = displayObject.__next; + } +}; /** * Draws the batch to the frame buffer @@ -512,41 +509,42 @@ PIXI.WebGLBatch.prototype.update = function() */ PIXI.WebGLBatch.prototype.render = function(start, end) { - start = start || 0; + start = start || 0; - if(end == undefined)end = this.size; - - if(this.dirty) - { - this.refresh(); - this.dirty = false; - } + if(end === undefined) + end = this.size; - if (this.size == 0)return; + if(this.dirty) + { + this.refresh(); + this.dirty = false; + } - this.update(); - var gl = this.gl; + if (this.size === 0)return; - //TODO optimize this! + this.update(); + var gl = this.gl; - var shaderProgram = PIXI.defaultShader; - - //gl.useProgram(shaderProgram); + //TODO optimize this! - // update the verts.. - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - // ok.. - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.verticies) + var shaderProgram = PIXI.defaultShader; + + //gl.useProgram(shaderProgram); + + // update the verts.. + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + // ok.. + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.verticies); gl.vertexAttribPointer(shaderProgram.aVertexPosition, 2, gl.FLOAT, false, 0, 0); - // update the uvs - //var isDefault = (shaderProgram == PIXI.shaderProgram) + // update the uvs + //var isDefault = (shaderProgram == PIXI.shaderProgram) - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); if(this.dirtyUVS) { - this.dirtyUVS = false; - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvs); + this.dirtyUVS = false; + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvs); } gl.vertexAttribPointer(shaderProgram.aTextureCoord, 2, gl.FLOAT, false, 0, 0); @@ -554,21 +552,21 @@ PIXI.WebGLBatch.prototype.render = function(start, end) gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, this.texture._glTexture); - // update color! - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + // update color! + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - if(this.dirtyColors) + if(this.dirtyColors) { - this.dirtyColors = false; - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.colors); - } + this.dirtyColors = false; + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.colors); + } gl.vertexAttribPointer(shaderProgram.colorAttribute, 1, gl.FLOAT, false, 0, 0); - // dont need to upload! + // dont need to upload! gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - var len = end - start; + var len = end - start; // DRAW THAT this! gl.drawElements(gl.TRIANGLES, len * 6, gl.UNSIGNED_SHORT, start * 2 * 6 ); -} +}; diff --git a/src/pixi/renderers/webgl/WebGLFilterManager.js b/src/pixi/renderers/webgl/WebGLFilterManager.js index a98afb458..2dd4b7591 100644 --- a/src/pixi/renderers/webgl/WebGLFilterManager.js +++ b/src/pixi/renderers/webgl/WebGLFilterManager.js @@ -5,524 +5,507 @@ PIXI.WebGLFilterManager = function(transparent) { - this.transparent = transparent; - - this.filterStack = []; - this.texturePool = []; - - this.offsetX = 0; - this.offsetY = 0; - - this.initShaderBuffers(); -} + this.transparent = transparent; + + this.filterStack = []; + this.texturePool = []; + + this.offsetX = 0; + this.offsetY = 0; + + this.initShaderBuffers(); +}; // API PIXI.WebGLFilterManager.prototype.begin = function(projection, buffer) { - this.width = projection.x * 2; - this.height = -projection.y * 2; - this.buffer = buffer; -} + this.width = projection.x * 2; + this.height = -projection.y * 2; + this.buffer = buffer; +}; PIXI.WebGLFilterManager.prototype.pushFilter = function(filterBlock) { - var gl = PIXI.gl; + var gl = PIXI.gl; - // filter program - // OPTIMISATION - the first filter is free if its a simple color change? - this.filterStack.push(filterBlock); + // filter program + // OPTIMISATION - the first filter is free if its a simple color change? + this.filterStack.push(filterBlock); - var filter = filterBlock.filterPasses[0]; + var filter = filterBlock.filterPasses[0]; - + this.offsetX += filterBlock.target.filterArea.x; + this.offsetY += filterBlock.target.filterArea.y; - this.offsetX += filterBlock.target.filterArea.x; - this.offsetY += filterBlock.target.filterArea.y; - - - - - - var texture = this.texturePool.pop(); - if(!texture) - { - texture = new PIXI.FilterTexture(this.width, this.height); - } - else - { - texture.resize(this.width, this.height); - } + var texture = this.texturePool.pop(); + if(!texture) + { + texture = new PIXI.FilterTexture(this.width, this.height); + } + else + { + texture.resize(this.width, this.height); + } - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - this.getBounds(filterBlock.target); - - // addpadding? - //displayObject.filterArea.x + gl.bindTexture(gl.TEXTURE_2D, texture.texture); - var filterArea = filterBlock.target.filterArea; + this.getBounds(filterBlock.target); - var padidng = filter.padding; - filterArea.x -= padidng; - filterArea.y -= padidng; - filterArea.width += padidng * 2; - filterArea.height += padidng * 2; + // addpadding? + //displayObject.filterArea.x - // cap filter to screen size.. - if(filterArea.x < 0)filterArea.x = 0; - if(filterArea.width > this.width)filterArea.width = this.width; - if(filterArea.y < 0)filterArea.y = 0; - if(filterArea.height > this.height)filterArea.height = this.height; + var filterArea = filterBlock.target.filterArea; + var padidng = filter.padding; + filterArea.x -= padidng; + filterArea.y -= padidng; + filterArea.width += padidng * 2; + filterArea.height += padidng * 2; - //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); - - // console.log(filterArea) - // set view port - gl.viewport(0, 0, filterArea.width, filterArea.height); - - PIXI.projection.x = filterArea.width/2; - PIXI.projection.y = -filterArea.height/2; - - PIXI.offset.x = -filterArea.x; - PIXI.offset.y = -filterArea.y; + // cap filter to screen size.. + if(filterArea.x < 0)filterArea.x = 0; + if(filterArea.width > this.width)filterArea.width = this.width; + if(filterArea.y < 0)filterArea.y = 0; + if(filterArea.height > this.height)filterArea.height = this.height; - //console.log(PIXI.defaultShader.projectionVector) - // update projection - gl.uniform2f(PIXI.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); - gl.uniform2f(PIXI.defaultShader.offsetVector, -filterArea.x, -filterArea.y); - //PIXI.primitiveProgram + //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); - gl.colorMask(true, true, true, true); - gl.clearColor(0,0,0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); - - //filter.texture = texture; - filterBlock._glFilterTexture = texture; + //console.log(filterArea) + // set view port + gl.viewport(0, 0, filterArea.width, filterArea.height); - //console.log("PUSH") -} + PIXI.projection.x = filterArea.width/2; + PIXI.projection.y = -filterArea.height/2; + + PIXI.offset.x = -filterArea.x; + PIXI.offset.y = -filterArea.y; + + //console.log(PIXI.defaultShader.projectionVector) + // update projection + gl.uniform2f(PIXI.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); + gl.uniform2f(PIXI.defaultShader.offsetVector, -filterArea.x, -filterArea.y); + //PIXI.primitiveProgram + + gl.colorMask(true, true, true, true); + gl.clearColor(0,0,0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + //filter.texture = texture; + filterBlock._glFilterTexture = texture; + + //console.log("PUSH") +}; PIXI.WebGLFilterManager.prototype.popFilter = function() { - - var gl = PIXI.gl; - - var filterBlock = this.filterStack.pop(); + var gl = PIXI.gl; + var filterBlock = this.filterStack.pop(); + var filterArea = filterBlock.target.filterArea; + var texture = filterBlock._glFilterTexture; + + if(filterBlock.filterPasses.length > 1) + { + gl.viewport(0, 0, filterArea.width, filterArea.height); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = 0; + this.vertexArray[1] = filterArea.height; + + this.vertexArray[2] = filterArea.width; + this.vertexArray[3] = filterArea.height; + + this.vertexArray[4] = 0; + this.vertexArray[5] = 0; + + this.vertexArray[6] = filterArea.width; + this.vertexArray[7] = 0; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + // nnow set the uvs.. + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + var inputTexture = texture; + var outputTexture = this.texturePool.pop(); + if(!outputTexture)outputTexture = new PIXI.FilterTexture(this.width, this.height); + + // need to clear this FBO as it may have some left over elements from a prvious filter. + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.disable(gl.BLEND); + + for (var i = 0; i < filterBlock.filterPasses.length-1; i++) + { + var filterPass = filterBlock.filterPasses[i]; + + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); + + // draw texture.. + //filterPass.applyFilterPass(filterArea.width, filterArea.height); + this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); + + // swap the textures.. + var temp = inputTexture; + inputTexture = outputTexture; + outputTexture = temp; + } + + gl.enable(gl.BLEND); + + texture = inputTexture; + this.texturePool.push(outputTexture); + } + + var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; + + this.offsetX -= filterArea.x; + this.offsetY -= filterArea.y; + + + var sizeX = this.width; + var sizeY = this.height; + + var offsetX = 0; + var offsetY = 0; + + var buffer = this.buffer; + + // time to render the filters texture to the previous scene + if(this.filterStack.length === 0) + { + gl.colorMask(true, true, true, this.transparent); + } + else + { + var currentFilter = this.filterStack[this.filterStack.length-1]; + filterArea = currentFilter.target.filterArea; + + sizeX = filterArea.width; + sizeY = filterArea.height; + + offsetX = filterArea.x; + offsetY = filterArea.y; + + buffer = currentFilter._glFilterTexture.frameBuffer; + } - var filterArea = filterBlock.target.filterArea; + // TODO need toremove thease global elements.. + PIXI.projection.x = sizeX/2; + PIXI.projection.y = -sizeY/2; - var texture = filterBlock._glFilterTexture; + PIXI.offset.x = offsetX; + PIXI.offset.y = offsetY; - if(filterBlock.filterPasses.length > 1) - { - gl.viewport(0, 0, filterArea.width, filterArea.height); + filterArea = filterBlock.target.filterArea; - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = 0; - this.vertexArray[1] = filterArea.height; - - this.vertexArray[2] = filterArea.width; - this.vertexArray[3] = filterArea.height; - - this.vertexArray[4] = 0; - this.vertexArray[5] = 0; - - this.vertexArray[6] = filterArea.width; - this.vertexArray[7] = 0; + var x = filterArea.x-offsetX; + var y = filterArea.y-offsetY; + // update the buffers.. + // make sure to flip the y! + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + this.vertexArray[0] = x; + this.vertexArray[1] = y + filterArea.height; - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - // nnow set the uvs.. - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + this.vertexArray[2] = x + filterArea.width; + this.vertexArray[3] = y + filterArea.height; - var inputTexture = texture; - var outputTexture = this.texturePool.pop(); - if(!outputTexture)outputTexture = new PIXI.FilterTexture(this.width, this.height); - - // need to clear this FBO as it may have some left over elements from a prvious filter. - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - gl.clear(gl.COLOR_BUFFER_BIT); - - gl.disable(gl.BLEND); - - for (var i = 0; i < filterBlock.filterPasses.length-1; i++) - { - var filterPass = filterBlock.filterPasses[i]; - - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); - - // draw texture.. - //filterPass.applyFilterPass(filterArea.width, filterArea.height); - this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); + this.vertexArray[4] = x; + this.vertexArray[5] = y; - // swap the textures.. - var temp = inputTexture; - inputTexture = outputTexture; - outputTexture = temp; - - }; + this.vertexArray[6] = x + filterArea.width; + this.vertexArray[7] = y; - gl.enable(gl.BLEND); + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - texture = inputTexture; - this.texturePool.push(outputTexture); - } + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; - - this.offsetX -= filterArea.x; - this.offsetY -= filterArea.y; + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; - - var sizeX = this.width; - var sizeY = this.height; - - var offsetX = 0; - var offsetY = 0; - - var buffer = this.buffer; - - // time to render the filters texture to the previous scene - if(this.filterStack.length === 0) - { - gl.colorMask(true, true, true, this.transparent); - } - else - { - var currentFilter = this.filterStack[this.filterStack.length-1]; - var filterArea = currentFilter.target.filterArea; - - sizeX = filterArea.width; - sizeY = filterArea.height; - - offsetX = filterArea.x; - offsetY = filterArea.y; - - buffer = currentFilter._glFilterTexture.frameBuffer; - } - - + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - // TODO need toremove thease global elements.. - PIXI.projection.x = sizeX/2; - PIXI.projection.y = -sizeY/2; + gl.viewport(0, 0, sizeX, sizeY); + // bind the buffer + gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); - PIXI.offset.x = offsetX; - PIXI.offset.y = offsetY; - - - var filterArea = filterBlock.target.filterArea; - var x = filterArea.x-offsetX; - var y = filterArea.y-offsetY; - - // update the buffers.. - // make sure to flip the y! - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = x; - this.vertexArray[1] = y + filterArea.height; - - this.vertexArray[2] = x + filterArea.width; - this.vertexArray[3] = y + filterArea.height; - - this.vertexArray[4] = x; - this.vertexArray[5] = y; - - this.vertexArray[6] = x + filterArea.width; - this.vertexArray[7] = y; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - gl.viewport(0, 0, sizeX, sizeY); - // bind the buffer - gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); - - // set texture + // set texture gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - // apply! - //filter.applyFilterPass(sizeX, sizeY); - this.applyFilterPass(filter, filterArea, sizeX, sizeY); + gl.bindTexture(gl.TEXTURE_2D, texture.texture); - // now restore the regular shader.. + // apply! + //filter.applyFilterPass(sizeX, sizeY); + this.applyFilterPass(filter, filterArea, sizeX, sizeY); + + // now restore the regular shader.. gl.useProgram(PIXI.defaultShader.program); - gl.uniform2f(PIXI.defaultShader.projectionVector, sizeX/2, -sizeY/2); - gl.uniform2f(PIXI.defaultShader.offsetVector, -offsetX, -offsetY); + gl.uniform2f(PIXI.defaultShader.projectionVector, sizeX/2, -sizeY/2); + gl.uniform2f(PIXI.defaultShader.offsetVector, -offsetX, -offsetY); - // return the texture to the pool - this.texturePool.push(texture); - filterBlock._glFilterTexture = null; -} + // return the texture to the pool + this.texturePool.push(texture); + filterBlock._glFilterTexture = null; +}; PIXI.WebGLFilterManager.prototype.applyFilterPass = function(filter, filterArea, width, height) { - // use program - var gl = PIXI.gl; + // use program + var gl = PIXI.gl; + var shader = filter.shader; - if(!filter.shader) - { - var shader = new PIXI.PixiShader(); - - shader.fragmentSrc = filter.fragmentSrc; - shader.uniforms = filter.uniforms; - shader.init(); - - filter.shader = shader; - } + if(!shader) + { + shader = new PIXI.PixiShader(); - var shader = filter.shader; - - // set the shader - gl.useProgram(shader.program); + shader.fragmentSrc = filter.fragmentSrc; + shader.uniforms = filter.uniforms; + shader.init(); - gl.uniform2f(shader.projectionVector, width/2, -height/2); - gl.uniform2f(shader.offsetVector, 0,0) + filter.shader = shader; + } - if(filter.uniforms.dimensions) - { - //console.log(filter.uniforms.dimensions) - filter.uniforms.dimensions.value[0] = this.width;//width; - filter.uniforms.dimensions.value[1] = this.height;//height; - filter.uniforms.dimensions.value[2] = this.vertexArray[0]; - filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; - // console.log(this.vertexArray[5]) - } + // set the shader + gl.useProgram(shader.program); - shader.syncUniforms(); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.uniform2f(shader.projectionVector, width/2, -height/2); + gl.uniform2f(shader.offsetVector, 0,0); + + if(filter.uniforms.dimensions) + { + //console.log(filter.uniforms.dimensions) + filter.uniforms.dimensions.value[0] = this.width;//width; + filter.uniforms.dimensions.value[1] = this.height;//height; + filter.uniforms.dimensions.value[2] = this.vertexArray[0]; + filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; + // console.log(this.vertexArray[5]) + } + + shader.syncUniforms(); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); - + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - - // draw the filter... + + // draw the filter... gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); -} +}; PIXI.WebGLFilterManager.prototype.initShaderBuffers = function() { - var gl = PIXI.gl; - - // create some buffers - this.vertexBuffer = gl.createBuffer(); - this.uvBuffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - - // bind and upload the vertexs.. - // keep a refferance to the vertexFloatData.. - this.vertexArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferData( - gl.ARRAY_BUFFER, - this.vertexArray, + var gl = PIXI.gl; + + // create some buffers + this.vertexBuffer = gl.createBuffer(); + this.uvBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + + // bind and upload the vertexs.. + // keep a refferance to the vertexFloatData.. + this.vertexArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData( + gl.ARRAY_BUFFER, + this.vertexArray, gl.STATIC_DRAW); - - + + // bind and upload the uv buffer - this.uvArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.bufferData( - gl.ARRAY_BUFFER, - this.uvArray, + this.uvArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.bufferData( + gl.ARRAY_BUFFER, + this.uvArray, gl.STATIC_DRAW); - - // bind and upload the index - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData( - gl.ELEMENT_ARRAY_BUFFER, - new Uint16Array([0, 1, 2, 1, 3, 2]), + + // bind and upload the index + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData( + gl.ELEMENT_ARRAY_BUFFER, + new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); -} +}; PIXI.WebGLFilterManager.prototype.getBounds = function(displayObject) { - // time to get the width and height of the object! - var worldTransform, width, height, aX, aY, w0, w1, h0, h1, index, doTest; - var a, b, c, d, tx, ty, x1, x2, x3, x4, y1, y2, y3, y4; + // time to get the width and height of the object! + var worldTransform, width, height, aX, aY, w0, w1, h0, h1, doTest; + var a, b, c, d, tx, ty, x1, x2, x3, x4, y1, y2, y3, y4; - var tempObject = displayObject.first; - var testObject = displayObject.last._iNext; - - var maxX = -Infinity; - var maxY = -Infinity; - - var minX = Infinity; - var minY = Infinity; - - do - { - // TODO can be optimized! - what if there is no scale / rotation? - - if(tempObject.visible) - { - if(tempObject instanceof PIXI.Sprite) - { - width = tempObject.texture.frame.width; - height = tempObject.texture.frame.height; + var tempObject = displayObject.first; + var testObject = displayObject.last._iNext; - // TODO trim?? - aX = tempObject.anchor.x; - aY = tempObject.anchor.y; - w0 = width * (1-aX); - w1 = width * -aX; + var maxX = -Infinity; + var maxY = -Infinity; - h0 = height * (1-aY); - h1 = height * -aY; + var minX = Infinity; + var minY = Infinity; - doTest = true; - } - else if(tempObject instanceof PIXI.Graphics) - { - tempObject.updateFilterBounds(); + do + { + // TODO can be optimized! - what if there is no scale / rotation? - var bounds = tempObject.bounds; + if(tempObject.visible) + { + if(tempObject instanceof PIXI.Sprite) + { + width = tempObject.texture.frame.width; + height = tempObject.texture.frame.height; - width = bounds.width; - height = bounds.height; + // TODO trim?? + aX = tempObject.anchor.x; + aY = tempObject.anchor.y; + w0 = width * (1-aX); + w1 = width * -aX; - w0 = bounds.x - w1 = bounds.x + bounds.width; + h0 = height * (1-aY); + h1 = height * -aY; - h0 = bounds.y - h1 = bounds.y + bounds.height; + doTest = true; + } + else if(tempObject instanceof PIXI.Graphics) + { + tempObject.updateFilterBounds(); - doTest = true; - } - } - - if(doTest) - { - worldTransform = tempObject.worldTransform; + var bounds = tempObject.bounds; - a = worldTransform[0]; - b = worldTransform[3]; - c = worldTransform[1]; - d = worldTransform[4]; - tx = worldTransform[2]; - ty = worldTransform[5]; + width = bounds.width; + height = bounds.height; - x1 = a * w1 + c * h1 + tx; - y1 = d * h1 + b * w1 + ty; + w0 = bounds.x; + w1 = bounds.x + bounds.width; - x2 = a * w0 + c * h1 + tx; - y2 = d * h1 + b * w0 + ty; + h0 = bounds.y; + h1 = bounds.y + bounds.height; - x3 = a * w0 + c * h0 + tx; - y3 = d * h0 + b * w0 + ty; + doTest = true; + } + } - x4 = a * w1 + c * h0 + tx; - y4 = d * h0 + b * w1 + ty; + if(doTest) + { + worldTransform = tempObject.worldTransform; - minX = x1 < minX ? x1 : minX; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1 < minY ? y1 : minY; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1 > maxX ? x1 : maxX; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1 > maxY ? y1 : maxY; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - } + a = worldTransform[0]; + b = worldTransform[3]; + c = worldTransform[1]; + d = worldTransform[4]; + tx = worldTransform[2]; + ty = worldTransform[5]; - doTest = false; - tempObject = tempObject._iNext; + x1 = a * w1 + c * h1 + tx; + y1 = d * h1 + b * w1 + ty; - } - while(tempObject != testObject) - - // maximum bounds is the size of the screen.. - //minX = minX > 0 ? minX : 0; - //minY = minY > 0 ? minY : 0; + x2 = a * w0 + c * h1 + tx; + y2 = d * h1 + b * w0 + ty; - displayObject.filterArea.x = minX; - displayObject.filterArea.y = minY; + x3 = a * w0 + c * h0 + tx; + y3 = d * h0 + b * w0 + ty; -// console.log(maxX+ " : " + minX) - displayObject.filterArea.width = maxX - minX; - displayObject.filterArea.height = maxY - minY; -} + x4 = a * w1 + c * h0 + tx; + y4 = d * h0 + b * w1 + ty; + + minX = x1 < minX ? x1 : minX; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1 < minY ? y1 : minY; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1 > maxX ? x1 : maxX; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1 > maxY ? y1 : maxY; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + } + + doTest = false; + tempObject = tempObject._iNext; + + } + while(tempObject !== testObject); + + // maximum bounds is the size of the screen.. + //minX = minX > 0 ? minX : 0; + //minY = minY > 0 ? minY : 0; + + displayObject.filterArea.x = minX; + displayObject.filterArea.y = minY; + +// console.log(maxX+ " : " + minX) + displayObject.filterArea.width = maxX - minX; + displayObject.filterArea.height = maxY - minY; +}; PIXI.FilterTexture = function(width, height) { - var gl = PIXI.gl; - + var gl = PIXI.gl; + // next time to create a frame buffer and texture - this.frameBuffer = gl.createFramebuffer(); + this.frameBuffer = gl.createFramebuffer(); this.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer ); - - gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); - gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); - - this.resize(width, height); -} + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer ); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); + + this.resize(width, height); +}; PIXI.FilterTexture.prototype.resize = function(width, height) { - if(this.width == width && this.height == height)return; + if(this.width === width && this.height === height) return; - this.width = width; - this.height = height; + this.width = width; + this.height = height; - var gl = PIXI.gl; + var gl = PIXI.gl; - gl.bindTexture(gl.TEXTURE_2D, this.texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - -} \ No newline at end of file + gl.bindTexture(gl.TEXTURE_2D, this.texture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + +}; diff --git a/src/pixi/renderers/webgl/WebGLGraphics.js b/src/pixi/renderers/webgl/WebGLGraphics.js index 1d9498531..50ba4482b 100644 --- a/src/pixi/renderers/webgl/WebGLGraphics.js +++ b/src/pixi/renderers/webgl/WebGLGraphics.js @@ -9,8 +9,8 @@ */ PIXI.WebGLGraphics = function() { - -} + +}; /** * Renders the graphics object @@ -23,62 +23,61 @@ PIXI.WebGLGraphics = function() */ PIXI.WebGLGraphics.renderGraphics = function(graphics, projection) { - var gl = PIXI.gl; - - if(!graphics._webGL)graphics._webGL = {points:[], indices:[], lastIndex:0, - buffer:gl.createBuffer(), - indexBuffer:gl.createBuffer()}; - - if(graphics.dirty) - { - graphics.dirty = false; - - if(graphics.clearDirty) - { - graphics.clearDirty = false; - - graphics._webGL.lastIndex = 0; - graphics._webGL.points = []; - graphics._webGL.indices = []; - - } - - PIXI.WebGLGraphics.updateGraphics(graphics); - } - - PIXI.activatePrimitiveShader(); - - // This could be speeded up fo sure! - var m = PIXI.mat3.clone(graphics.worldTransform); - - PIXI.mat3.transpose(m); - - // set the matrix transform for the - gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + var gl = PIXI.gl; - gl.uniformMatrix3fv(PIXI.primitiveShader.translationMatrix, false, m); - - gl.uniform2f(PIXI.primitiveShader.projectionVector, projection.x, -projection.y); - gl.uniform2f(PIXI.primitiveShader.offsetVector, -PIXI.offset.x, -PIXI.offset.y); - - gl.uniform1f(PIXI.primitiveShader.alpha, graphics.worldAlpha); - gl.bindBuffer(gl.ARRAY_BUFFER, graphics._webGL.buffer); - - gl.vertexAttribPointer(PIXI.primitiveShader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); - gl.vertexAttribPointer(PIXI.primitiveShader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); - - // set the index buffer! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, graphics._webGL.indexBuffer); - + if(!graphics._webGL)graphics._webGL = {points:[], indices:[], lastIndex:0, + buffer:gl.createBuffer(), + indexBuffer:gl.createBuffer()}; - gl.drawElements(gl.TRIANGLE_STRIP, graphics._webGL.indices.length, gl.UNSIGNED_SHORT, 0 ); - - PIXI.deactivatePrimitiveShader(); - - - // return to default shader... -// PIXI.activateShader(PIXI.defaultShader); -} + if(graphics.dirty) + { + graphics.dirty = false; + + if(graphics.clearDirty) + { + graphics.clearDirty = false; + + graphics._webGL.lastIndex = 0; + graphics._webGL.points = []; + graphics._webGL.indices = []; + + } + + PIXI.WebGLGraphics.updateGraphics(graphics); + } + + PIXI.activatePrimitiveShader(); + + // This could be speeded up fo sure! + var m = PIXI.mat3.clone(graphics.worldTransform); + + PIXI.mat3.transpose(m); + + // set the matrix transform for the + gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + + gl.uniformMatrix3fv(PIXI.primitiveShader.translationMatrix, false, m); + + gl.uniform2f(PIXI.primitiveShader.projectionVector, projection.x, -projection.y); + gl.uniform2f(PIXI.primitiveShader.offsetVector, -PIXI.offset.x, -PIXI.offset.y); + + gl.uniform1f(PIXI.primitiveShader.alpha, graphics.worldAlpha); + gl.bindBuffer(gl.ARRAY_BUFFER, graphics._webGL.buffer); + + gl.vertexAttribPointer(PIXI.primitiveShader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(PIXI.primitiveShader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, graphics._webGL.indexBuffer); + + + gl.drawElements(gl.TRIANGLE_STRIP, graphics._webGL.indices.length, gl.UNSIGNED_SHORT, 0 ); + + PIXI.deactivatePrimitiveShader(); + + // return to default shader... +// PIXI.activateShader(PIXI.defaultShader); +}; /** * Updates the graphics object @@ -90,47 +89,47 @@ PIXI.WebGLGraphics.renderGraphics = function(graphics, projection) */ PIXI.WebGLGraphics.updateGraphics = function(graphics) { - for (var i=graphics._webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - if(data.type == PIXI.Graphics.POLY) - { - if(data.fill) - { - if(data.points.length>3) - PIXI.WebGLGraphics.buildPoly(data, graphics._webGL); - } - - if(data.lineWidth > 0) - { - PIXI.WebGLGraphics.buildLine(data, graphics._webGL); - } - } - else if(data.type == PIXI.Graphics.RECT) - { - PIXI.WebGLGraphics.buildRectangle(data, graphics._webGL); - } - else if(data.type == PIXI.Graphics.CIRC || data.type == PIXI.Graphics.ELIP) - { - PIXI.WebGLGraphics.buildCircle(data, graphics._webGL); - } - }; - - graphics._webGL.lastIndex = graphics.graphicsData.length; - - var gl = PIXI.gl; + for (var i = graphics._webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; - graphics._webGL.glPoints = new Float32Array(graphics._webGL.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, graphics._webGL.buffer); - gl.bufferData(gl.ARRAY_BUFFER, graphics._webGL.glPoints, gl.STATIC_DRAW); - - graphics._webGL.glIndicies = new Uint16Array(graphics._webGL.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, graphics._webGL.indexBuffer); + if(data.type === PIXI.Graphics.POLY) + { + if(data.fill) + { + if(data.points.length>3) + PIXI.WebGLGraphics.buildPoly(data, graphics._webGL); + } + + if(data.lineWidth > 0) + { + PIXI.WebGLGraphics.buildLine(data, graphics._webGL); + } + } + else if(data.type === PIXI.Graphics.RECT) + { + PIXI.WebGLGraphics.buildRectangle(data, graphics._webGL); + } + else if(data.type === PIXI.Graphics.CIRC || data.type === PIXI.Graphics.ELIP); + { + PIXI.WebGLGraphics.buildCircle(data, graphics._webGL); + } + } + + graphics._webGL.lastIndex = graphics.graphicsData.length; + + var gl = PIXI.gl; + + graphics._webGL.glPoints = new Float32Array(graphics._webGL.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, graphics._webGL.buffer); + gl.bufferData(gl.ARRAY_BUFFER, graphics._webGL.glPoints, gl.STATIC_DRAW); + + graphics._webGL.glIndicies = new Uint16Array(graphics._webGL.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, graphics._webGL.indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, graphics._webGL.glIndicies, gl.STATIC_DRAW); -} +}; /** * Builds a rectangle to draw @@ -143,59 +142,58 @@ PIXI.WebGLGraphics.updateGraphics = function(graphics) */ PIXI.WebGLGraphics.buildRectangle = function(graphicsData, webGLData) { - // --- // - // need to convert points to a nice regular data - // - var rectData = graphicsData.points; - var x = rectData[0]; - var y = rectData[1]; - var width = rectData[2]; - var height = rectData[3]; - - - if(graphicsData.fill) - { - var color = HEXtoRGB(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var verts = webGLData.points; - var indices = webGLData.indices; - - var vertPos = verts.length/6; - - // start - verts.push(x, y); - verts.push(r, g, b, alpha); - - verts.push(x + width, y); - verts.push(r, g, b, alpha); - - verts.push(x , y + height); - verts.push(r, g, b, alpha); - - verts.push(x + width, y + height); - verts.push(r, g, b, alpha); - - // insert 2 dead triangles.. - indices.push(vertPos, vertPos, vertPos+1, vertPos+2, vertPos+3, vertPos+3) - } - - if(graphicsData.lineWidth) - { - graphicsData.points = [x, y, - x + width, y, - x + width, y + height, - x, y + height, - x, y]; - - PIXI.WebGLGraphics.buildLine(graphicsData, webGLData); - } - -} + // --- // + // need to convert points to a nice regular data + // + var rectData = graphicsData.points; + var x = rectData[0]; + var y = rectData[1]; + var width = rectData[2]; + var height = rectData[3]; + + + if(graphicsData.fill) + { + var color = PIXI.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vertPos = verts.length/6; + + // start + verts.push(x, y); + verts.push(r, g, b, alpha); + + verts.push(x + width, y); + verts.push(r, g, b, alpha); + + verts.push(x , y + height); + verts.push(r, g, b, alpha); + + verts.push(x + width, y + height); + verts.push(r, g, b, alpha); + + // insert 2 dead triangles.. + indices.push(vertPos, vertPos, vertPos+1, vertPos+2, vertPos+3, vertPos+3); + } + + if(graphicsData.lineWidth) + { + graphicsData.points = [x, y, + x + width, y, + x + width, y + height, + x, y + height, + x, y]; + + PIXI.WebGLGraphics.buildLine(graphicsData, webGLData); + } +}; /** * Builds a circle to draw @@ -208,62 +206,63 @@ PIXI.WebGLGraphics.buildRectangle = function(graphicsData, webGLData) */ PIXI.WebGLGraphics.buildCircle = function(graphicsData, webGLData) { - // --- // - // need to convert points to a nice regular data - // - var rectData = graphicsData.points; - var x = rectData[0]; - var y = rectData[1]; - var width = rectData[2]; - var height = rectData[3]; - - var totalSegs = 40; - var seg = (Math.PI * 2) / totalSegs ; - - if(graphicsData.fill) - { - var color = HEXtoRGB(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; + // --- // + // need to convert points to a nice regular data + // + var rectData = graphicsData.points; + var x = rectData[0]; + var y = rectData[1]; + var width = rectData[2]; + var height = rectData[3]; - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var verts = webGLData.points; - var indices = webGLData.indices; - - var vecPos = verts.length/6; - - indices.push(vecPos); - - for (var i=0; i < totalSegs + 1 ; i++) - { - verts.push(x,y, r, g, b, alpha); - - verts.push(x + Math.sin(seg * i) * width, - y + Math.cos(seg * i) * height, - r, g, b, alpha); - - indices.push(vecPos++, vecPos++); - }; - - indices.push(vecPos-1); - } - - if(graphicsData.lineWidth) - { - graphicsData.points = []; - - for (var i=0; i < totalSegs + 1; i++) - { - graphicsData.points.push(x + Math.sin(seg * i) * width, - y + Math.cos(seg * i) * height) - }; - - PIXI.WebGLGraphics.buildLine(graphicsData, webGLData); - } - -} + var totalSegs = 40; + var seg = (Math.PI * 2) / totalSegs ; + + var i = 0; + + if(graphicsData.fill) + { + var color = PIXI.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vecPos = verts.length/6; + + indices.push(vecPos); + + for (i = 0; i < totalSegs + 1 ; i++) + { + verts.push(x,y, r, g, b, alpha); + + verts.push(x + Math.sin(seg * i) * width, + y + Math.cos(seg * i) * height, + r, g, b, alpha); + + indices.push(vecPos++, vecPos++); + } + + indices.push(vecPos-1); + } + + if(graphicsData.lineWidth) + { + graphicsData.points = []; + + for (i = 0; i < totalSegs + 1; i++) + { + graphicsData.points.push(x + Math.sin(seg * i) * width, + y + Math.cos(seg * i) * height); + } + + PIXI.WebGLGraphics.buildLine(graphicsData, webGLData); + } +}; /** * Builds a line to draw @@ -276,205 +275,204 @@ PIXI.WebGLGraphics.buildCircle = function(graphicsData, webGLData) */ PIXI.WebGLGraphics.buildLine = function(graphicsData, webGLData) { - // TODO OPTIMISE! - - var wrap = true; - var points = graphicsData.points; - if(points.length == 0)return; - - // if the line width is an odd number add 0.5 to align to a whole pixel - if(graphicsData.lineWidth%2) - { - for (var i = 0; i < points.length; i++) { - points[i] += 0.5; - }; - } + // TODO OPTIMISE! + var i = 0; - // get first and last point.. figure out the middle! - var firstPoint = new PIXI.Point( points[0], points[1] ); - var lastPoint = new PIXI.Point( points[points.length - 2], points[points.length - 1] ); - - // if the first point is the last point - goona have issues :) - if(firstPoint.x == lastPoint.x && firstPoint.y == lastPoint.y) - { - points.pop(); - points.pop(); - - lastPoint = new PIXI.Point( points[points.length - 2], points[points.length - 1] ); - - var midPointX = lastPoint.x + (firstPoint.x - lastPoint.x) *0.5; - var midPointY = lastPoint.y + (firstPoint.y - lastPoint.y) *0.5; - - points.unshift(midPointX, midPointY); - points.push(midPointX, midPointY) - } - - var verts = webGLData.points; - var indices = webGLData.indices; - var length = points.length / 2; - var indexCount = points.length; - var indexStart = verts.length/6; - - // DRAW the Line - var width = graphicsData.lineWidth / 2; - - // sort color - var color = HEXtoRGB(graphicsData.lineColor); - var alpha = graphicsData.lineAlpha; - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var p1x, p1y, p2x, p2y, p3x, p3y; - var perpx, perpy, perp2x, perp2y, perp3x, perp3y; - var ipx, ipy; - var a1, b1, c1, a2, b2, c2; - var denom, pdist, dist; - - p1x = points[0]; - p1y = points[1]; - - p2x = points[2]; - p2y = points[3]; - - perpx = -(p1y - p2y); - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; - - // start - verts.push(p1x - perpx , p1y - perpy, - r, g, b, alpha); - - verts.push(p1x + perpx , p1y + perpy, - r, g, b, alpha); - - for (var i = 1; i < length-1; i++) - { - p1x = points[(i-1)*2]; - p1y = points[(i-1)*2 + 1]; - - p2x = points[(i)*2] - p2y = points[(i)*2 + 1] - - p3x = points[(i+1)*2]; - p3y = points[(i+1)*2 + 1]; - - perpx = -(p1y - p2y); - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; + var points = graphicsData.points; + if(points.length === 0)return; - perp2x = -(p2y - p3y); - perp2y = p2x - p3x; - - dist = Math.sqrt(perp2x*perp2x + perp2y*perp2y); - perp2x /= dist; - perp2y /= dist; - perp2x *= width; - perp2y *= width; - - a1 = (-perpy + p1y) - (-perpy + p2y); - b1 = (-perpx + p2x) - (-perpx + p1x); - c1 = (-perpx + p1x) * (-perpy + p2y) - (-perpx + p2x) * (-perpy + p1y); - a2 = (-perp2y + p3y) - (-perp2y + p2y); - b2 = (-perp2x + p2x) - (-perp2x + p3x); - c2 = (-perp2x + p3x) * (-perp2y + p2y) - (-perp2x + p2x) * (-perp2y + p3y); - - denom = a1*b2 - a2*b1; + // if the line width is an odd number add 0.5 to align to a whole pixel + if(graphicsData.lineWidth%2) + { + for (i = 0; i < points.length; i++) { + points[i] += 0.5; + } + } - if(Math.abs(denom) < 0.1 ) - { - - denom+=10.1; - verts.push(p2x - perpx , p2y - perpy, - r, g, b, alpha); - - verts.push(p2x + perpx , p2y + perpy, - r, g, b, alpha); - - continue; - } - - px = (b1*c2 - b2*c1)/denom; - py = (a2*c1 - a1*c2)/denom; - - - pdist = (px -p2x) * (px -p2x) + (py -p2y) + (py -p2y); - + // get first and last point.. figure out the middle! + var firstPoint = new PIXI.Point( points[0], points[1] ); + var lastPoint = new PIXI.Point( points[points.length - 2], points[points.length - 1] ); - if(pdist > 140 * 140) - { - perp3x = perpx - perp2x; - perp3y = perpy - perp2y; - - dist = Math.sqrt(perp3x*perp3x + perp3y*perp3y); - perp3x /= dist; - perp3y /= dist; - perp3x *= width; - perp3y *= width; - - verts.push(p2x - perp3x, p2y -perp3y); - verts.push(r, g, b, alpha); - - verts.push(p2x + perp3x, p2y +perp3y); - verts.push(r, g, b, alpha); - - verts.push(p2x - perp3x, p2y -perp3y); - verts.push(r, g, b, alpha); - - indexCount++; - } - else - { + // if the first point is the last point - goona have issues :) + if(firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) + { + points.pop(); + points.pop(); - verts.push(px , py); - verts.push(r, g, b, alpha); - - verts.push(p2x - (px-p2x), p2y - (py - p2y)); - verts.push(r, g, b, alpha); - } - } - - p1x = points[(length-2)*2] - p1y = points[(length-2)*2 + 1] - - p2x = points[(length-1)*2] - p2y = points[(length-1)*2 + 1] - - perpx = -(p1y - p2y) - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; - - verts.push(p2x - perpx , p2y - perpy) - verts.push(r, g, b, alpha); - - verts.push(p2x + perpx , p2y + perpy) - verts.push(r, g, b, alpha); - - indices.push(indexStart); - - for (var i=0; i < indexCount; i++) - { - indices.push(indexStart++); - }; - - indices.push(indexStart-1); -} + lastPoint = new PIXI.Point( points[points.length - 2], points[points.length - 1] ); + + var midPointX = lastPoint.x + (firstPoint.x - lastPoint.x) *0.5; + var midPointY = lastPoint.y + (firstPoint.y - lastPoint.y) *0.5; + + points.unshift(midPointX, midPointY); + points.push(midPointX, midPointY); + } + + var verts = webGLData.points; + var indices = webGLData.indices; + var length = points.length / 2; + var indexCount = points.length; + var indexStart = verts.length/6; + + // DRAW the Line + var width = graphicsData.lineWidth / 2; + + // sort color + var color = PIXI.hex2rgb(graphicsData.lineColor); + var alpha = graphicsData.lineAlpha; + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var px, py, p1x, p1y, p2x, p2y, p3x, p3y; + var perpx, perpy, perp2x, perp2y, perp3x, perp3y; + var a1, b1, c1, a2, b2, c2; + var denom, pdist, dist; + + p1x = points[0]; + p1y = points[1]; + + p2x = points[2]; + p2y = points[3]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + // start + verts.push(p1x - perpx , p1y - perpy, + r, g, b, alpha); + + verts.push(p1x + perpx , p1y + perpy, + r, g, b, alpha); + + for (i = 1; i < length-1; i++) + { + p1x = points[(i-1)*2]; + p1y = points[(i-1)*2 + 1]; + + p2x = points[(i)*2]; + p2y = points[(i)*2 + 1]; + + p3x = points[(i+1)*2]; + p3y = points[(i+1)*2 + 1]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + perp2x = -(p2y - p3y); + perp2y = p2x - p3x; + + dist = Math.sqrt(perp2x*perp2x + perp2y*perp2y); + perp2x /= dist; + perp2y /= dist; + perp2x *= width; + perp2y *= width; + + a1 = (-perpy + p1y) - (-perpy + p2y); + b1 = (-perpx + p2x) - (-perpx + p1x); + c1 = (-perpx + p1x) * (-perpy + p2y) - (-perpx + p2x) * (-perpy + p1y); + a2 = (-perp2y + p3y) - (-perp2y + p2y); + b2 = (-perp2x + p2x) - (-perp2x + p3x); + c2 = (-perp2x + p3x) * (-perp2y + p2y) - (-perp2x + p2x) * (-perp2y + p3y); + + denom = a1*b2 - a2*b1; + + if(Math.abs(denom) < 0.1 ) + { + + denom+=10.1; + verts.push(p2x - perpx , p2y - perpy, + r, g, b, alpha); + + verts.push(p2x + perpx , p2y + perpy, + r, g, b, alpha); + + continue; + } + + px = (b1*c2 - b2*c1)/denom; + py = (a2*c1 - a1*c2)/denom; + + + pdist = (px -p2x) * (px -p2x) + (py -p2y) + (py -p2y); + + + if(pdist > 140 * 140) + { + perp3x = perpx - perp2x; + perp3y = perpy - perp2y; + + dist = Math.sqrt(perp3x*perp3x + perp3y*perp3y); + perp3x /= dist; + perp3y /= dist; + perp3x *= width; + perp3y *= width; + + verts.push(p2x - perp3x, p2y -perp3y); + verts.push(r, g, b, alpha); + + verts.push(p2x + perp3x, p2y +perp3y); + verts.push(r, g, b, alpha); + + verts.push(p2x - perp3x, p2y -perp3y); + verts.push(r, g, b, alpha); + + indexCount++; + } + else + { + + verts.push(px , py); + verts.push(r, g, b, alpha); + + verts.push(p2x - (px-p2x), p2y - (py - p2y)); + verts.push(r, g, b, alpha); + } + } + + p1x = points[(length-2)*2]; + p1y = points[(length-2)*2 + 1]; + + p2x = points[(length-1)*2]; + p2y = points[(length-1)*2 + 1]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + verts.push(p2x - perpx , p2y - perpy); + verts.push(r, g, b, alpha); + + verts.push(p2x + perpx , p2y + perpy); + verts.push(r, g, b, alpha); + + indices.push(indexStart); + + for (i = 0; i < indexCount; i++) + { + indices.push(indexStart++); + } + + indices.push(indexStart-1); +}; /** * Builds a polygon to draw @@ -487,46 +485,40 @@ PIXI.WebGLGraphics.buildLine = function(graphicsData, webGLData) */ PIXI.WebGLGraphics.buildPoly = function(graphicsData, webGLData) { - var points = graphicsData.points; - if(points.length < 6)return; - - // get first and last point.. figure out the middle! - var verts = webGLData.points; - var indices = webGLData.indices; - - var length = points.length / 2; - - // sort color - var color = HEXtoRGB(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var triangles = PIXI.PolyK.Triangulate(points); - - var vertPos = verts.length / 6; - - for (var i=0; i < triangles.length; i+=3) - { - indices.push(triangles[i] + vertPos); - indices.push(triangles[i] + vertPos); - indices.push(triangles[i+1] + vertPos); - indices.push(triangles[i+2] +vertPos); - indices.push(triangles[i+2] + vertPos); - }; - - for (var i = 0; i < length; i++) - { - verts.push(points[i * 2], points[i * 2 + 1], - r, g, b, alpha); - }; -} + var points = graphicsData.points; + if(points.length < 6)return; -function HEXtoRGB(hex) { - return [(hex >> 16 & 0xFF) / 255, ( hex >> 8 & 0xFF) / 255, (hex & 0xFF)/ 255]; -} + // get first and last point.. figure out the middle! + var verts = webGLData.points; + var indices = webGLData.indices; + var length = points.length / 2; + // sort color + var color = PIXI.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + var triangles = PIXI.PolyK.Triangulate(points); + var vertPos = verts.length / 6; + + var i = 0; + + for (i = 0; i < triangles.length; i+=3) + { + indices.push(triangles[i] + vertPos); + indices.push(triangles[i] + vertPos); + indices.push(triangles[i+1] + vertPos); + indices.push(triangles[i+2] +vertPos); + indices.push(triangles[i+2] + vertPos); + } + + for (i = 0; i < length; i++) + { + verts.push(points[i * 2], points[i * 2 + 1], + r, g, b, alpha); + } +}; diff --git a/src/pixi/renderers/webgl/WebGLRenderGroup.js b/src/pixi/renderers/webgl/WebGLRenderGroup.js index 0cdf18a05..b0f2da767 100644 --- a/src/pixi/renderers/webgl/WebGLRenderGroup.js +++ b/src/pixi/renderers/webgl/WebGLRenderGroup.js @@ -25,6 +25,7 @@ PIXI.WebGLRenderGroup = function(gl, transparent) this.batchs = []; this.toRemove = []; + // console.log(this.transparent) this.filterManager = new PIXI.WebGLFilterManager(this.transparent); } diff --git a/src/pixi/renderers/webgl/WebGLRenderer.js b/src/pixi/renderers/webgl/WebGLRenderer.js index e11cd44f9..4d83833b5 100644 --- a/src/pixi/renderers/webgl/WebGLRenderer.js +++ b/src/pixi/renderers/webgl/WebGLRenderer.js @@ -6,7 +6,7 @@ PIXI._defaultFrame = new PIXI.Rectangle(0,0,1,1); // an instance of the gl context.. // only one at the moment :/ -PIXI.gl; +PIXI.gl = null; /** * the WebGLRenderer is draws the stage and all its content onto a webGL enabled canvas. This renderer @@ -21,68 +21,68 @@ PIXI.gl; * @param view {Canvas} the canvas to use as a view, optional * @param transparent=false {Boolean} the transparency of the render view, default false * @param antialias=false {Boolean} sets antialias (only applicable in chrome at the moment) - * + * */ PIXI.WebGLRenderer = function(width, height, view, transparent, antialias) { - // do a catch.. only 1 webGL renderer.. + // do a catch.. only 1 webGL renderer.. - this.transparent = !!transparent; + this.transparent = !!transparent; - this.width = width || 800; - this.height = height || 600; + this.width = width || 800; + this.height = height || 600; - this.view = view || document.createElement( 'canvas' ); + this.view = view || document.createElement( 'canvas' ); this.view.width = this.width; - this.view.height = this.height; + this.view.height = this.height; - // deal with losing context.. + // deal with losing context.. var scope = this; - this.view.addEventListener('webglcontextlost', function(event) { scope.handleContextLost(event); }, false) - this.view.addEventListener('webglcontextrestored', function(event) { scope.handleContextRestored(event); }, false) + this.view.addEventListener('webglcontextlost', function(event) { scope.handleContextLost(event); }, false); + this.view.addEventListener('webglcontextrestored', function(event) { scope.handleContextRestored(event); }, false); - this.batchs = []; + this.batchs = []; - var options = { - alpha: this.transparent, - antialias:!!antialias, // SPEED UP?? - premultipliedAlpha:false, - stencil:true - } + var options = { + alpha: this.transparent, + antialias:!!antialias, // SPEED UP?? + premultipliedAlpha:false, + stencil:true + }; - //try 'experimental-webgl' - try { - PIXI.gl = this.gl = this.view.getContext("experimental-webgl", options); - } catch (e) { - //try 'webgl' - try { - PIXI.gl = this.gl = this.view.getContext("webgl", options); - } catch (e) { - // fail, not able to get a context - throw new Error(" This browser does not support webGL. Try using the canvas renderer" + this); - } - } + //try 'experimental-webgl' + try { + PIXI.gl = this.gl = this.view.getContext('experimental-webgl', options); + } catch (e) { + //try 'webgl' + try { + PIXI.gl = this.gl = this.view.getContext('webgl', options); + } catch (e2) { + // fail, not able to get a context + throw new Error(' This browser does not support webGL. Try using the canvas renderer' + this); + } + } PIXI.initDefaultShaders(); - - + + // PIXI.activateDefaultShader(); var gl = this.gl; - + gl.useProgram(PIXI.defaultShader.program); PIXI.WebGLRenderer.gl = gl; this.batch = new PIXI.WebGLBatch(gl); - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.CULL_FACE); + gl.disable(gl.DEPTH_TEST); + gl.disable(gl.CULL_FACE); gl.enable(gl.BLEND); - gl.colorMask(true, true, true, this.transparent); + gl.colorMask(true, true, true, this.transparent); PIXI.projection = new PIXI.Point(400, 300); PIXI.offset = new PIXI.Point(0, 0); @@ -92,11 +92,11 @@ PIXI.WebGLRenderer = function(width, height, view, transparent, antialias) this.resize(this.width, this.height); this.contextLost = false; - //PIXI.pushShader(PIXI.defaultShader); + //PIXI.pushShader(PIXI.defaultShader); this.stageRenderGroup = new PIXI.WebGLRenderGroup(this.gl, this.transparent); // this.stageRenderGroup. = this.transparent -} +}; // constructor PIXI.WebGLRenderer.prototype.constructor = PIXI.WebGLRenderer; @@ -107,19 +107,19 @@ PIXI.WebGLRenderer.prototype.constructor = PIXI.WebGLRenderer; * @static * @method getBatch * @return {WebGLBatch} - * @private + * @private */ PIXI.WebGLRenderer.getBatch = function() { - if(PIXI._batchs.length == 0) - { - return new PIXI.WebGLBatch(PIXI.WebGLRenderer.gl); - } - else - { - return PIXI._batchs.pop(); - } -} + if(PIXI._batchs.length === 0) + { + return new PIXI.WebGLBatch(PIXI.WebGLRenderer.gl); + } + else + { + return PIXI._batchs.pop(); + } +}; /** * Puts a batch back into the pool @@ -131,9 +131,9 @@ PIXI.WebGLRenderer.getBatch = function() */ PIXI.WebGLRenderer.returnBatch = function(batch) { - batch.clean(); - PIXI._batchs.push(batch); -} + batch.clean(); + PIXI._batchs.push(batch); +}; /** * Renders the stage to its webGL view @@ -143,68 +143,68 @@ PIXI.WebGLRenderer.returnBatch = function(batch) */ PIXI.WebGLRenderer.prototype.render = function(stage) { - if(this.contextLost)return; - - - // if rendering a new stage clear the batchs.. - if(this.__stage !== stage) - { - // TODO make this work - // dont think this is needed any more? - this.__stage = stage; - this.stageRenderGroup.setRenderable(stage); - } + if(this.contextLost)return; - // update any textures - PIXI.WebGLRenderer.updateTextures(); - - // update the scene graph - PIXI.visibleCount++; - stage.updateTransform(); - - var gl = this.gl; - - // -- Does this need to be set every frame? -- // - gl.colorMask(true, true, true, this.transparent); - gl.viewport(0, 0, this.width, this.height); - - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - gl.clearColor(stage.backgroundColorSplit[0],stage.backgroundColorSplit[1],stage.backgroundColorSplit[2], !this.transparent); - gl.clear(gl.COLOR_BUFFER_BIT); - // HACK TO TEST - - this.stageRenderGroup.backgroundColor = stage.backgroundColorSplit; - - PIXI.projection.x = this.width/2; - PIXI.projection.y = -this.height/2; - - this.stageRenderGroup.render(PIXI.projection); - - // interaction - // run interaction! - if(stage.interactive) - { - //need to add some events! - if(!stage._interactiveEventsAdded) - { - stage._interactiveEventsAdded = true; - stage.interactionManager.setTarget(this); - } - } - - // after rendering lets confirm all frames that have been uodated.. - if(PIXI.Texture.frameUpdates.length > 0) - { - for (var i=0; i < PIXI.Texture.frameUpdates.length; i++) - { - PIXI.Texture.frameUpdates[i].updateFrame = false; - }; - - PIXI.Texture.frameUpdates = []; - } -} + // if rendering a new stage clear the batchs.. + if(this.__stage !== stage) + { + // TODO make this work + // dont think this is needed any more? + this.__stage = stage; + this.stageRenderGroup.setRenderable(stage); + } + + // update any textures + PIXI.WebGLRenderer.updateTextures(); + + // update the scene graph + PIXI.visibleCount++; + stage.updateTransform(); + + var gl = this.gl; + + // -- Does this need to be set every frame? -- // + gl.colorMask(true, true, true, this.transparent); + gl.viewport(0, 0, this.width, this.height); + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + gl.clearColor(stage.backgroundColorSplit[0],stage.backgroundColorSplit[1],stage.backgroundColorSplit[2], !this.transparent); + gl.clear(gl.COLOR_BUFFER_BIT); + + // HACK TO TEST + + this.stageRenderGroup.backgroundColor = stage.backgroundColorSplit; + + PIXI.projection.x = this.width/2; + PIXI.projection.y = -this.height/2; + + this.stageRenderGroup.render(PIXI.projection); + + // interaction + // run interaction! + if(stage.interactive) + { + //need to add some events! + if(!stage._interactiveEventsAdded) + { + stage._interactiveEventsAdded = true; + stage.interactionManager.setTarget(this); + } + } + + // after rendering lets confirm all frames that have been uodated.. + if(PIXI.Texture.frameUpdates.length > 0) + { + for (var i=0; i < PIXI.Texture.frameUpdates.length; i++) + { + PIXI.Texture.frameUpdates[i].updateFrame = false; + } + + PIXI.Texture.frameUpdates = []; + } +}; /** * Updates the textures loaded into this webgl renderer @@ -215,12 +215,18 @@ PIXI.WebGLRenderer.prototype.render = function(stage) */ PIXI.WebGLRenderer.updateTextures = function() { - //TODO break this out into a texture manager... - for (var i=0; i < PIXI.texturesToUpdate.length; i++) PIXI.WebGLRenderer.updateTexture(PIXI.texturesToUpdate[i]); - for (var i=0; i < PIXI.texturesToDestroy.length; i++) PIXI.WebGLRenderer.destroyTexture(PIXI.texturesToDestroy[i]); - PIXI.texturesToUpdate = []; - PIXI.texturesToDestroy = []; -} + var i = 0; + + //TODO break this out into a texture manager... + for (i = 0; i < PIXI.texturesToUpdate.length; i++) + PIXI.WebGLRenderer.updateTexture(PIXI.texturesToUpdate[i]); + + for (i = 0; i < PIXI.texturesToDestroy.length; i++) + PIXI.WebGLRenderer.destroyTexture(PIXI.texturesToDestroy[i]); + + PIXI.texturesToUpdate = []; + PIXI.texturesToDestroy = []; +}; /** * Updates a loaded webgl texture @@ -232,39 +238,39 @@ PIXI.WebGLRenderer.updateTextures = function() */ PIXI.WebGLRenderer.updateTexture = function(texture) { - //TODO break this out into a texture manager... - var gl = PIXI.gl; - - if(!texture._glTexture) - { - texture._glTexture = gl.createTexture(); - } + //TODO break this out into a texture manager... + var gl = PIXI.gl; - if(texture.hasLoaded) - { - gl.bindTexture(gl.TEXTURE_2D, texture._glTexture); - gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); + if(!texture._glTexture) + { + texture._glTexture = gl.createTexture(); + } - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.source); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + if(texture.hasLoaded) + { + gl.bindTexture(gl.TEXTURE_2D, texture._glTexture); + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); - // reguler... + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.source); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === PIXI.BaseTexture.SCALE_MODE.LINEAR ? gl.LINEAR : gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === PIXI.BaseTexture.SCALE_MODE.LINEAR ? gl.LINEAR : gl.NEAREST); - if(!texture._powerOf2) - { - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - } - else - { - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); - } + // reguler... - gl.bindTexture(gl.TEXTURE_2D, null); - } -} + if(!texture._powerOf2) + { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + } + else + { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); + } + + gl.bindTexture(gl.TEXTURE_2D, null); + } +}; /** * Destroys a loaded webgl texture @@ -275,15 +281,15 @@ PIXI.WebGLRenderer.updateTexture = function(texture) */ PIXI.WebGLRenderer.destroyTexture = function(texture) { - //TODO break this out into a texture manager... - var gl = PIXI.gl; + //TODO break this out into a texture manager... + var gl = PIXI.gl; - if(texture._glTexture) - { - texture._glTexture = gl.createTexture(); - gl.deleteTexture(gl.TEXTURE_2D, texture._glTexture); - } -} + if(texture._glTexture) + { + texture._glTexture = gl.createTexture(); + gl.deleteTexture(gl.TEXTURE_2D, texture._glTexture); + } +}; /** * resizes the webGL view to the specified width and height @@ -294,27 +300,27 @@ PIXI.WebGLRenderer.destroyTexture = function(texture) */ PIXI.WebGLRenderer.prototype.resize = function(width, height) { - this.width = width; - this.height = height; + this.width = width; + this.height = height; - this.view.width = width; - this.view.height = height; + this.view.width = width; + this.view.height = height; - this.gl.viewport(0, 0, this.width, this.height); + this.gl.viewport(0, 0, this.width, this.height); - //var projectionMatrix = this.projectionMatrix; + //var projectionMatrix = this.projectionMatrix; - PIXI.projection.x = this.width/2; - PIXI.projection.y = -this.height/2; - - //PIXI.size.x = this.width/2; - //PIXI.size.y = -this.height/2; + PIXI.projection.x = this.width/2; + PIXI.projection.y = -this.height/2; -// projectionMatrix[0] = 2/this.width; -// projectionMatrix[5] = -2/this.height; -// projectionMatrix[12] = -1; -// projectionMatrix[13] = 1; -} + //PIXI.size.x = this.width/2; + //PIXI.size.y = -this.height/2; + +// projectionMatrix[0] = 2/this.width; +// projectionMatrix[5] = -2/this.height; +// projectionMatrix[12] = -1; +// projectionMatrix[13] = 1; +}; /** * Handles a lost webgl context @@ -325,9 +331,9 @@ PIXI.WebGLRenderer.prototype.resize = function(width, height) */ PIXI.WebGLRenderer.prototype.handleContextLost = function(event) { - event.preventDefault(); - this.contextLost = true; -} + event.preventDefault(); + this.contextLost = true; +}; /** * Handles a restored webgl context @@ -336,28 +342,28 @@ PIXI.WebGLRenderer.prototype.handleContextLost = function(event) * @param event {Event} * @private */ -PIXI.WebGLRenderer.prototype.handleContextRestored = function(event) +PIXI.WebGLRenderer.prototype.handleContextRestored = function() { - this.gl = this.view.getContext("experimental-webgl", { - alpha: true + this.gl = this.view.getContext('experimental-webgl', { + alpha: true }); - this.initShaders(); + this.initShaders(); - for(var key in PIXI.TextureCache) - { - var texture = PIXI.TextureCache[key].baseTexture; - texture._glTexture = null; - PIXI.WebGLRenderer.updateTexture(texture); - }; + for(var key in PIXI.TextureCache) + { + var texture = PIXI.TextureCache[key].baseTexture; + texture._glTexture = null; + PIXI.WebGLRenderer.updateTexture(texture); + } - for (var i=0; i < this.batchs.length; i++) - { - this.batchs[i].restoreLostContext(this.gl)// - this.batchs[i].dirty = true; - }; + for (var i=0; i < this.batchs.length; i++) + { + this.batchs[i].restoreLostContext(this.gl); + this.batchs[i].dirty = true; + } - PIXI._restoreBatchs(this.gl); + PIXI._restoreBatchs(this.gl); - this.contextLost = false; -} + this.contextLost = false; +}; diff --git a/src/pixi/renderers/webgl/WebGLShaders.js b/src/pixi/renderers/webgl/WebGLShaders.js index 44123d278..b6b66b7e2 100644 --- a/src/pixi/renderers/webgl/WebGLShaders.js +++ b/src/pixi/renderers/webgl/WebGLShaders.js @@ -2,73 +2,70 @@ * @author Mat Groves http://matgroves.com/ @Doormat23 */ - -PIXI.initDefaultShaders = function() +PIXI.initDefaultShaders = function() { - PIXI.primitiveShader = new PIXI.PrimitiveShader(); - PIXI.primitiveShader.init(); + PIXI.primitiveShader = new PIXI.PrimitiveShader(); + PIXI.primitiveShader.init(); - PIXI.stripShader = new PIXI.StripShader(); - PIXI.stripShader.init(); + PIXI.stripShader = new PIXI.StripShader(); + PIXI.stripShader.init(); - PIXI.defaultShader = new PIXI.PixiShader(); - PIXI.defaultShader.init(); + PIXI.defaultShader = new PIXI.PixiShader(); + PIXI.defaultShader.init(); - var gl = PIXI.gl; - var shaderProgram = PIXI.defaultShader.program; - + var gl = PIXI.gl; + var shaderProgram = PIXI.defaultShader.program; - gl.useProgram(shaderProgram); - - gl.enableVertexAttribArray(PIXI.defaultShader.aVertexPosition); - gl.enableVertexAttribArray(PIXI.defaultShader.colorAttribute); - gl.enableVertexAttribArray(PIXI.defaultShader.aTextureCoord); -} + gl.useProgram(shaderProgram); + + gl.enableVertexAttribArray(PIXI.defaultShader.aVertexPosition); + gl.enableVertexAttribArray(PIXI.defaultShader.colorAttribute); + gl.enableVertexAttribArray(PIXI.defaultShader.aTextureCoord); +}; PIXI.activatePrimitiveShader = function() { - var gl = PIXI.gl; - - gl.useProgram(PIXI.primitiveShader.program); - - gl.disableVertexAttribArray(PIXI.defaultShader.aVertexPosition); - gl.disableVertexAttribArray(PIXI.defaultShader.colorAttribute); - gl.disableVertexAttribArray(PIXI.defaultShader.aTextureCoord); + var gl = PIXI.gl; - gl.enableVertexAttribArray(PIXI.primitiveShader.aVertexPosition); - gl.enableVertexAttribArray(PIXI.primitiveShader.colorAttribute); -} + gl.useProgram(PIXI.primitiveShader.program); + + gl.disableVertexAttribArray(PIXI.defaultShader.aVertexPosition); + gl.disableVertexAttribArray(PIXI.defaultShader.colorAttribute); + gl.disableVertexAttribArray(PIXI.defaultShader.aTextureCoord); + + gl.enableVertexAttribArray(PIXI.primitiveShader.aVertexPosition); + gl.enableVertexAttribArray(PIXI.primitiveShader.colorAttribute); +}; PIXI.deactivatePrimitiveShader = function() { - var gl = PIXI.gl; + var gl = PIXI.gl; - gl.useProgram(PIXI.defaultShader.program); - - gl.disableVertexAttribArray(PIXI.primitiveShader.aVertexPosition); - gl.disableVertexAttribArray(PIXI.primitiveShader.colorAttribute); + gl.useProgram(PIXI.defaultShader.program); - gl.enableVertexAttribArray(PIXI.defaultShader.aVertexPosition); - gl.enableVertexAttribArray(PIXI.defaultShader.colorAttribute); - gl.enableVertexAttribArray(PIXI.defaultShader.aTextureCoord); + gl.disableVertexAttribArray(PIXI.primitiveShader.aVertexPosition); + gl.disableVertexAttribArray(PIXI.primitiveShader.colorAttribute); -} + gl.enableVertexAttribArray(PIXI.defaultShader.aVertexPosition); + gl.enableVertexAttribArray(PIXI.defaultShader.colorAttribute); + gl.enableVertexAttribArray(PIXI.defaultShader.aTextureCoord); +}; PIXI.activateStripShader = function() { - var gl = PIXI.gl; - - gl.useProgram(PIXI.stripShader.program); + var gl = PIXI.gl; + + gl.useProgram(PIXI.stripShader.program); // gl.disableVertexAttribArray(PIXI.defaultShader.aTextureCoord); -} +}; PIXI.deactivateStripShader = function() { - var gl = PIXI.gl; + var gl = PIXI.gl; - gl.useProgram(PIXI.defaultShader.program); - //gl.enableVertexAttribArray(PIXI.defaultShader.aTextureCoord); -} + gl.useProgram(PIXI.defaultShader.program); + //gl.enableVertexAttribArray(PIXI.defaultShader.aTextureCoord); +}; /* @@ -77,45 +74,44 @@ SHADER COMPILER HELPERS PIXI.CompileVertexShader = function(gl, shaderSrc) { - return PIXI._CompileShader(gl, shaderSrc, gl.VERTEX_SHADER); -} + return PIXI._CompileShader(gl, shaderSrc, gl.VERTEX_SHADER); +}; PIXI.CompileFragmentShader = function(gl, shaderSrc) { - return PIXI._CompileShader(gl, shaderSrc, gl.FRAGMENT_SHADER); -} + return PIXI._CompileShader(gl, shaderSrc, gl.FRAGMENT_SHADER); +}; PIXI._CompileShader = function(gl, shaderSrc, shaderType) { - var src = shaderSrc.join("\n"); - var shader = gl.createShader(shaderType); - gl.shaderSource(shader, src); - gl.compileShader(shader); + var src = shaderSrc.join("\n"); + var shader = gl.createShader(shaderType); + gl.shaderSource(shader, src); + gl.compileShader(shader); - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { - console.log(gl.getShaderInfoLog(shader)); - return null; - } - - return shader; -} + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + window.console.log(gl.getShaderInfoLog(shader)); + return null; + } + return shader; +}; PIXI.compileProgram = function(vertexSrc, fragmentSrc) { - var gl = PIXI.gl; - var fragmentShader = PIXI.CompileFragmentShader(gl, fragmentSrc); - var vertexShader = PIXI.CompileVertexShader(gl, vertexSrc); - - var shaderProgram = gl.createProgram(); - + var gl = PIXI.gl; + var fragmentShader = PIXI.CompileFragmentShader(gl, fragmentSrc); + var vertexShader = PIXI.CompileVertexShader(gl, vertexSrc); + + var shaderProgram = gl.createProgram(); + gl.attachShader(shaderProgram, vertexShader); gl.attachShader(shaderProgram, fragmentShader); gl.linkProgram(shaderProgram); if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { - console.log("Could not initialise shaders"); + window.console.log("Could not initialise shaders"); } - return shaderProgram; -} + return shaderProgram; +}; diff --git a/src/pixi/text/BitmapText.js b/src/pixi/text/BitmapText.js index 7dd15ee34..fb5fb8ccf 100644 --- a/src/pixi/text/BitmapText.js +++ b/src/pixi/text/BitmapText.js @@ -3,7 +3,7 @@ */ /** - * A Text Object will create a line(s) of text using bitmap font. To split a line you can use "\n", "\r" or "\r\n" + * A Text Object will create a line(s) of text using bitmap font. To split a line you can use '\n', '\r' or '\r\n' * You can generate the fnt files using * http://www.angelcode.com/products/bmfont/ for windows or * http://www.bmglyph.com/ for mac. @@ -13,8 +13,8 @@ * @constructor * @param text {String} The copy that you would like the text to display * @param style {Object} The style parameters - * @param style.font {String} The size (optional) and bitmap font id (required) eq "Arial" or "20px Arial" (must have loaded previously) - * @param [style.align="left"] {String} An alignment of the multiline text ("left", "center" or "right") + * @param style.font {String} The size (optional) and bitmap font id (required) eq 'Arial' or '20px Arial' (must have loaded previously) + * @param [style.align='left'] {String} An alignment of the multiline text ('left', 'center' or 'right') */ PIXI.BitmapText = function(text, style) { @@ -23,8 +23,7 @@ PIXI.BitmapText = function(text, style) this.setText(text); this.setStyle(style); this.updateText(); - this.dirty = false - + this.dirty = false; }; // constructor @@ -39,7 +38,7 @@ PIXI.BitmapText.prototype.constructor = PIXI.BitmapText; */ PIXI.BitmapText.prototype.setText = function(text) { - this.text = text || " "; + this.text = text || ' '; this.dirty = true; }; @@ -48,16 +47,16 @@ PIXI.BitmapText.prototype.setText = function(text) * * @method setStyle * @param style {Object} The style parameters - * @param style.font {String} The size (optional) and bitmap font id (required) eq "Arial" or "20px Arial" (must have loaded previously) - * @param [style.align="left"] {String} An alignment of the multiline text ("left", "center" or "right") + * @param style.font {String} The size (optional) and bitmap font id (required) eq 'Arial' or '20px Arial' (must have loaded previously) + * @param [style.align='left'] {String} An alignment of the multiline text ('left', 'center' or 'right') */ PIXI.BitmapText.prototype.setStyle = function(style) { style = style || {}; - style.align = style.align || "left"; + style.align = style.align || 'left'; this.style = style; - var font = style.font.split(" "); + var font = style.font.split(' '); this.fontName = font[font.length - 1]; this.fontSize = font.length >= 2 ? parseInt(font[font.length - 2], 10) : PIXI.BitmapText.fonts[this.fontName].size; @@ -100,7 +99,7 @@ PIXI.BitmapText.prototype.updateText = function() if(prevCharCode && charData[prevCharCode]) { - pos.x += charData.kerning[prevCharCode]; + pos.x += charData.kerning[prevCharCode]; } chars.push({texture:charData.texture, line: line, charCode: charCode, position: new PIXI.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); pos.x += charData.xAdvance; @@ -115,11 +114,11 @@ PIXI.BitmapText.prototype.updateText = function() for(i = 0; i <= line; i++) { var alignOffset = 0; - if(this.style.align == "right") + if(this.style.align === 'right') { alignOffset = maxLineWidth - lineWidths[i]; } - else if(this.style.align == "center") + else if(this.style.align === 'center') { alignOffset = (maxLineWidth - lineWidths[i]) / 2; } @@ -128,7 +127,7 @@ PIXI.BitmapText.prototype.updateText = function() for(i = 0; i < chars.length; i++) { - var c = new PIXI.Sprite(chars[i].texture)//PIXI.Sprite.fromFrame(chars[i].charCode); + var c = new PIXI.Sprite(chars[i].texture); //PIXI.Sprite.fromFrame(chars[i].charCode); c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; c.position.y = chars[i].position.y * scale; c.scale.x = c.scale.y = scale; @@ -147,8 +146,8 @@ PIXI.BitmapText.prototype.updateText = function() */ PIXI.BitmapText.prototype.updateTransform = function() { - if(this.dirty) - { + if(this.dirty) + { while(this.children.length > 0) { this.removeChild(this.getChildAt(0)); @@ -156,9 +155,9 @@ PIXI.BitmapText.prototype.updateTransform = function() this.updateText(); this.dirty = false; - } + } - PIXI.DisplayObjectContainer.prototype.updateTransform.call(this); + PIXI.DisplayObjectContainer.prototype.updateTransform.call(this); }; PIXI.BitmapText.fonts = {}; diff --git a/src/pixi/text/Text.js b/src/pixi/text/Text.js index a2acdd6d1..75d50d051 100644 --- a/src/pixi/text/Text.js +++ b/src/pixi/text/Text.js @@ -3,25 +3,25 @@ */ /** - * A Text Object will create a line(s) of text to split a line you can use "\n" + * A Text Object will create a line(s) of text to split a line you can use '\n' * * @class Text * @extends Sprite * @constructor * @param text {String} The copy that you would like the text to display * @param [style] {Object} The style parameters - * @param [style.font] {String} default "bold 20pt Arial" The style and size of the font - * @param [style.fill="black"] {Object} A canvas fillstyle that will be used on the text eg "red", "#00FF00" - * @param [style.align="left"] {String} An alignment of the multiline text ("left", "center" or "right") - * @param [style.stroke] {String} A canvas fillstyle that will be used on the text stroke eg "blue", "#FCFF00" + * @param [style.font] {String} default 'bold 20pt Arial' The style and size of the font + * @param [style.fill='black'] {Object} A canvas fillstyle that will be used on the text eg 'red', '#00FF00' + * @param [style.align='left'] {String} An alignment of the multiline text ('left', 'center' or 'right') + * @param [style.stroke] {String} A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00' * @param [style.strokeThickness=0] {Number} A number that represents the thickness of the stroke. Default is 0 (no stroke) * @param [style.wordWrap=false] {Boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {Number} The width at which text will wrap */ PIXI.Text = function(text, style) { - this.canvas = document.createElement("canvas"); - this.context = this.canvas.getContext("2d"); + this.canvas = document.createElement('canvas'); + this.context = this.canvas.getContext('2d'); PIXI.Sprite.call(this, PIXI.Texture.fromCanvas(this.canvas)); this.setText(text); @@ -40,10 +40,10 @@ PIXI.Text.prototype.constructor = PIXI.Text; * * @method setStyle * @param [style] {Object} The style parameters - * @param [style.font="bold 20pt Arial"] {String} The style and size of the font - * @param [style.fill="black"] {Object} A canvas fillstyle that will be used on the text eg "red", "#00FF00" - * @param [style.align="left"] {String} An alignment of the multiline text ("left", "center" or "right") - * @param [style.stroke="black"] {String} A canvas fillstyle that will be used on the text stroke eg "blue", "#FCFF00" + * @param [style.font='bold 20pt Arial'] {String} The style and size of the font + * @param [style.fill='black'] {Object} A canvas fillstyle that will be used on the text eg 'red', '#00FF00' + * @param [style.align='left'] {String} An alignment of the multiline text ('left', 'center' or 'right') + * @param [style.stroke='black'] {String} A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00' * @param [style.strokeThickness=0] {Number} A number that represents the thickness of the stroke. Default is 0 (no stroke) * @param [style.wordWrap=false] {Boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {Number} The width at which text will wrap @@ -51,10 +51,10 @@ PIXI.Text.prototype.constructor = PIXI.Text; PIXI.Text.prototype.setStyle = function(style) { style = style || {}; - style.font = style.font || "bold 20pt Arial"; - style.fill = style.fill || "black"; - style.align = style.align || "left"; - style.stroke = style.stroke || "black"; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136 + style.font = style.font || 'bold 20pt Arial'; + style.fill = style.fill || 'black'; + style.align = style.align || 'left'; + style.stroke = style.stroke || 'black'; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136 style.strokeThickness = style.strokeThickness || 0; style.wordWrap = style.wordWrap || false; style.wordWrapWidth = style.wordWrapWidth || 100; @@ -63,14 +63,14 @@ PIXI.Text.prototype.setStyle = function(style) }; /** - * Set the copy for the text object. To split a line you can use "\n" + * Set the copy for the text object. To split a line you can use '\n' * * @method setText * @param {String} text The copy that you would like the text to display */ PIXI.Text.prototype.setText = function(text) { - this.text = text.toString() || " "; + this.text = text.toString() || ' '; this.dirty = true; }; @@ -82,65 +82,65 @@ PIXI.Text.prototype.setText = function(text) */ PIXI.Text.prototype.updateText = function() { - this.context.font = this.style.font; + this.context.font = this.style.font; - var outputText = this.text; + var outputText = this.text; - // word wrap - // preserve original text - if(this.style.wordWrap)outputText = this.wordWrap(this.text); + // word wrap + // preserve original text + if(this.style.wordWrap)outputText = this.wordWrap(this.text); - //split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); + //split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); - //calculate text width - var lineWidths = []; - var maxLineWidth = 0; - for (var i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width; - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - this.canvas.width = maxLineWidth + this.style.strokeThickness; + //calculate text width + var lineWidths = []; + var maxLineWidth = 0; + for (var i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width; + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + this.canvas.width = maxLineWidth + this.style.strokeThickness; - //calculate text height - var lineHeight = this.determineFontHeight("font: " + this.style.font + ";") + this.style.strokeThickness; - this.canvas.height = lineHeight * lines.length; + //calculate text height + var lineHeight = this.determineFontHeight('font: ' + this.style.font + ';') + this.style.strokeThickness; + this.canvas.height = lineHeight * lines.length; - //set canvas text styles - this.context.fillStyle = this.style.fill; - this.context.font = this.style.font; + //set canvas text styles + this.context.fillStyle = this.style.fill; + this.context.font = this.style.font; - this.context.strokeStyle = this.style.stroke; - this.context.lineWidth = this.style.strokeThickness; + this.context.strokeStyle = this.style.stroke; + this.context.lineWidth = this.style.strokeThickness; - this.context.textBaseline = "top"; + this.context.textBaseline = 'top'; - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - var linePosition = new PIXI.Point(this.style.strokeThickness / 2, this.style.strokeThickness / 2 + i * lineHeight); + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + var linePosition = new PIXI.Point(this.style.strokeThickness / 2, this.style.strokeThickness / 2 + i * lineHeight); - if(this.style.align == "right") - { - linePosition.x += maxLineWidth - lineWidths[i]; - } - else if(this.style.align == "center") - { - linePosition.x += (maxLineWidth - lineWidths[i]) / 2; - } + if(this.style.align === 'right') + { + linePosition.x += maxLineWidth - lineWidths[i]; + } + else if(this.style.align === 'center') + { + linePosition.x += (maxLineWidth - lineWidths[i]) / 2; + } - if(this.style.stroke && this.style.strokeThickness) - { - this.context.strokeText(lines[i], linePosition.x, linePosition.y); - } + if(this.style.stroke && this.style.strokeThickness) + { + this.context.strokeText(lines[i], linePosition.x, linePosition.y); + } - if(this.style.fill) - { - this.context.fillText(lines[i], linePosition.x, linePosition.y); - } - } + if(this.style.fill) + { + this.context.fillText(lines[i], linePosition.x, linePosition.y); + } + } this.updateTexture(); }; @@ -158,7 +158,7 @@ PIXI.Text.prototype.updateTexture = function() this.texture.frame.width = this.canvas.width; this.texture.frame.height = this.canvas.height; - this._width = this.canvas.width; + this._width = this.canvas.width; this._height = this.canvas.height; PIXI.texturesToUpdate.push(this.texture.baseTexture); @@ -172,13 +172,13 @@ PIXI.Text.prototype.updateTexture = function() */ PIXI.Text.prototype.updateTransform = function() { - if(this.dirty) - { - this.updateText(); - this.dirty = false; - } + if(this.dirty) + { + this.updateText(); + this.dirty = false; + } - PIXI.Sprite.prototype.updateTransform.call(this); + PIXI.Sprite.prototype.updateTransform.call(this); }; /* @@ -191,26 +191,26 @@ PIXI.Text.prototype.updateTransform = function() */ PIXI.Text.prototype.determineFontHeight = function(fontStyle) { - // build a little reference dictionary so if the font style has been used return a - // cached version... - var result = PIXI.Text.heightCache[fontStyle]; + // build a little reference dictionary so if the font style has been used return a + // cached version... + var result = PIXI.Text.heightCache[fontStyle]; - if(!result) - { - var body = document.getElementsByTagName("body")[0]; - var dummy = document.createElement("div"); - var dummyText = document.createTextNode("M"); - dummy.appendChild(dummyText); - dummy.setAttribute("style", fontStyle + ';position:absolute;top:0;left:0'); - body.appendChild(dummy); + if(!result) + { + var body = document.getElementsByTagName('body')[0]; + var dummy = document.createElement('div'); + var dummyText = document.createTextNode('M'); + dummy.appendChild(dummyText); + dummy.setAttribute('style', fontStyle + ';position:absolute;top:0;left:0'); + body.appendChild(dummy); - result = dummy.offsetHeight; - PIXI.Text.heightCache[fontStyle] = result; + result = dummy.offsetHeight; + PIXI.Text.heightCache[fontStyle] = result; - body.removeChild(dummy); - } + body.removeChild(dummy); + } - return result; + return result; }; /** @@ -223,38 +223,38 @@ PIXI.Text.prototype.determineFontHeight = function(fontStyle) */ PIXI.Text.prototype.wordWrap = function(text) { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ""; - var lines = text.split("\n"); - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = this.style.wordWrapWidth; - var words = lines[i].split(" "); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - var wordWidthWithSpace = wordWidth + this.context.measureText(" ").width; - if(wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if(j > 0) - { - result += "\n"; - } - result += words[j] + " "; - spaceLeft = this.style.wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += words[j] + " "; - } - } - result += "\n"; - } - return result; + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = this.style.wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if(wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if(j > 0) + { + result += '\n'; + } + result += words[j] + ' '; + spaceLeft = this.style.wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += words[j] + ' '; + } + } + result += '\n'; + } + return result; }; /** @@ -265,10 +265,10 @@ PIXI.Text.prototype.wordWrap = function(text) */ PIXI.Text.prototype.destroy = function(destroyTexture) { - if(destroyTexture) - { - this.texture.destroy(); - } + if(destroyTexture) + { + this.texture.destroy(); + } }; diff --git a/src/pixi/textures/BaseTexture.js b/src/pixi/textures/BaseTexture.js index 099f22759..1c78656de 100644 --- a/src/pixi/textures/BaseTexture.js +++ b/src/pixi/textures/BaseTexture.js @@ -14,86 +14,94 @@ PIXI.texturesToDestroy = []; * @constructor * @param source {String} the source object (image or canvas) */ -PIXI.BaseTexture = function(source) +PIXI.BaseTexture = function(source, scaleMode) { - PIXI.EventTarget.call( this ); + PIXI.EventTarget.call( this ); - /** - * [read-only] The width of the base texture set when the image has loaded - * - * @property width - * @type Number - * @readOnly - */ - this.width = 100; + /** + * [read-only] The width of the base texture set when the image has loaded + * + * @property width + * @type Number + * @readOnly + */ + this.width = 100; - /** - * [read-only] The height of the base texture set when the image has loaded - * - * @property height - * @type Number - * @readOnly - */ - this.height = 100; + /** + * [read-only] The height of the base texture set when the image has loaded + * + * @property height + * @type Number + * @readOnly + */ + this.height = 100; - /** - * [read-only] Describes if the base texture has loaded or not - * - * @property hasLoaded - * @type Boolean - * @readOnly - */ - this.hasLoaded = false; + /** + * The scale mode to apply when scaling this texture + * @property scaleMode + * @type PIXI.BaseTexture.SCALE_MODE + * @default PIXI.BaseTexture.SCALE_MODE.LINEAR + */ + this.scaleMode = scaleMode || PIXI.BaseTexture.SCALE_MODE.DEFAULT; - /** - * The source that is loaded to create the texture - * - * @property source - * @type Image - */ - this.source = source; + /** + * [read-only] Describes if the base texture has loaded or not + * + * @property hasLoaded + * @type Boolean + * @readOnly + */ + this.hasLoaded = false; - if(!source)return; + /** + * The source that is loaded to create the texture + * + * @property source + * @type Image + */ + this.source = source; - if(this.source instanceof Image || this.source instanceof HTMLImageElement) - { - if(this.source.complete) - { - this.hasLoaded = true; - this.width = this.source.width; - this.height = this.source.height; + if(!source)return; - PIXI.texturesToUpdate.push(this); - } - else - { + if(this.source instanceof Image || this.source instanceof HTMLImageElement) + { + if(this.source.complete) + { + this.hasLoaded = true; + this.width = this.source.width; + this.height = this.source.height; - var scope = this; - this.source.onload = function(){ + PIXI.texturesToUpdate.push(this); + } + else + { - scope.hasLoaded = true; - scope.width = scope.source.width; - scope.height = scope.source.height; + var scope = this; + this.source.onload = function() { - // add it to somewhere... - PIXI.texturesToUpdate.push(scope); - scope.dispatchEvent( { type: 'loaded', content: scope } ); - } - // this.image.src = imageUrl; - } - } - else - { - this.hasLoaded = true; - this.width = this.source.width; - this.height = this.source.height; + scope.hasLoaded = true; + scope.width = scope.source.width; + scope.height = scope.source.height; - PIXI.texturesToUpdate.push(this); - } + // add it to somewhere... + PIXI.texturesToUpdate.push(scope); + scope.dispatchEvent( { type: 'loaded', content: scope } ); + }; + //this.image.src = imageUrl; + } + } + else + { + this.hasLoaded = true; + this.width = this.source.width; + this.height = this.source.height; - this.imageUrl = null; - this._powerOf2 = false; -} + PIXI.texturesToUpdate.push(this); + } + + this.imageUrl = null; + this._powerOf2 = false; +}; PIXI.BaseTexture.prototype.constructor = PIXI.BaseTexture; @@ -104,29 +112,29 @@ PIXI.BaseTexture.prototype.constructor = PIXI.BaseTexture; */ PIXI.BaseTexture.prototype.destroy = function() { - if(this.source instanceof Image) - { - if (this.imageUrl in PIXI.BaseTextureCache) - delete PIXI.BaseTextureCache[this.imageUrl]; - this.imageUrl = null; - this.source.src = null; - } - this.source = null; - PIXI.texturesToDestroy.push(this); -} + if(this.source instanceof Image) + { + if (this.imageUrl in PIXI.BaseTextureCache) + delete PIXI.BaseTextureCache[this.imageUrl]; + this.imageUrl = null; + this.source.src = null; + } + this.source = null; + PIXI.texturesToDestroy.push(this); +}; /** - * + * * * @method destroy */ PIXI.BaseTexture.prototype.updateSourceImage = function(newSrc) { - this.hasLoaded = false; - this.source.src = null; - this.source.src = newSrc; -} + this.hasLoaded = false; + this.source.src = null; + this.source.src = newSrc; +}; /** * Helper function that returns a base texture based on an image url @@ -137,23 +145,29 @@ PIXI.BaseTexture.prototype.updateSourceImage = function(newSrc) * @param imageUrl {String} The image url of the texture * @return BaseTexture */ -PIXI.BaseTexture.fromImage = function(imageUrl, crossorigin) +PIXI.BaseTexture.fromImage = function(imageUrl, crossorigin, scaleMode) { - var baseTexture = PIXI.BaseTextureCache[imageUrl]; - if(!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - if (crossorigin) - { - image.crossOrigin = ''; - } - image.src = imageUrl; - baseTexture = new PIXI.BaseTexture(image); - baseTexture.imageUrl = imageUrl; - PIXI.BaseTextureCache[imageUrl] = baseTexture; - } + var baseTexture = PIXI.BaseTextureCache[imageUrl]; + if(!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + if (crossorigin) + { + image.crossOrigin = ''; + } + image.src = imageUrl; + baseTexture = new PIXI.BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + PIXI.BaseTextureCache[imageUrl] = baseTexture; + } - return baseTexture; -} + return baseTexture; +}; + +PIXI.BaseTexture.SCALE_MODE = { + DEFAULT: 0, //default to LINEAR + LINEAR: 0, + NEAREST: 1 +}; \ No newline at end of file diff --git a/src/pixi/textures/RenderTexture.js b/src/pixi/textures/RenderTexture.js index ec35c1891..fe1df3805 100644 --- a/src/pixi/textures/RenderTexture.js +++ b/src/pixi/textures/RenderTexture.js @@ -5,24 +5,24 @@ /** A RenderTexture is a special texture that allows any pixi displayObject to be rendered to it. - __Hint__: All DisplayObjects (exmpl. Sprites) that renders on RenderTexture should be preloaded. - Otherwise black rectangles will be drawn instead. - + __Hint__: All DisplayObjects (exmpl. Sprites) that renders on RenderTexture should be preloaded. + Otherwise black rectangles will be drawn instead. + RenderTexture takes snapshot of DisplayObject passed to render method. If DisplayObject is passed to render method, position and rotation of it will be ignored. For example: - - var renderTexture = new PIXI.RenderTexture(800, 600); - var sprite = PIXI.Sprite.fromImage("spinObj_01.png"); - sprite.position.x = 800/2; - sprite.position.y = 600/2; - sprite.anchor.x = 0.5; - sprite.anchor.y = 0.5; - renderTexture.render(sprite); + + var renderTexture = new PIXI.RenderTexture(800, 600); + var sprite = PIXI.Sprite.fromImage("spinObj_01.png"); + sprite.position.x = 800/2; + sprite.position.y = 600/2; + sprite.anchor.x = 0.5; + sprite.anchor.y = 0.5; + renderTexture.render(sprite); Sprite in this case will be rendered to 0,0 position. To render this sprite at center DisplayObjectContainer should be used: - var doc = new PIXI.DisplayObjectContainer(); - doc.addChild(sprite); - renderTexture.render(doc); // Renders to center of renderTexture + var doc = new PIXI.DisplayObjectContainer(); + doc.addChild(sprite); + renderTexture.render(doc); // Renders to center of renderTexture @class RenderTexture @extends Texture @@ -32,24 +32,24 @@ */ PIXI.RenderTexture = function(width, height) { - PIXI.EventTarget.call( this ); + PIXI.EventTarget.call( this ); - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.indetityMatrix = PIXI.mat3.create(); + this.indetityMatrix = PIXI.mat3.create(); - this.frame = new PIXI.Rectangle(0, 0, this.width, this.height); + this.frame = new PIXI.Rectangle(0, 0, this.width, this.height); - if(PIXI.gl) - { - this.initWebGL(); - } - else - { - this.initCanvas(); - } -} + if(PIXI.gl) + { + this.initWebGL(); + } + else + { + this.initCanvas(); + } +}; PIXI.RenderTexture.prototype = Object.create( PIXI.Texture.prototype ); PIXI.RenderTexture.prototype.constructor = PIXI.RenderTexture; @@ -62,65 +62,65 @@ PIXI.RenderTexture.prototype.constructor = PIXI.RenderTexture; */ PIXI.RenderTexture.prototype.initWebGL = function() { - var gl = PIXI.gl; - this.glFramebuffer = gl.createFramebuffer(); + var gl = PIXI.gl; + this.glFramebuffer = gl.createFramebuffer(); - gl.bindFramebuffer(gl.FRAMEBUFFER, this.glFramebuffer ); + gl.bindFramebuffer(gl.FRAMEBUFFER, this.glFramebuffer ); this.glFramebuffer.width = this.width; - this.glFramebuffer.height = this.height; + this.glFramebuffer.height = this.height; - this.baseTexture = new PIXI.BaseTexture(); + this.baseTexture = new PIXI.BaseTexture(); - this.baseTexture.width = this.width; - this.baseTexture.height = this.height; + this.baseTexture.width = this.width; + this.baseTexture.height = this.height; this.baseTexture._glTexture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.baseTexture._glTexture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.width, this.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.width, this.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - this.baseTexture.isRender = true; + this.baseTexture.isRender = true; - gl.bindFramebuffer(gl.FRAMEBUFFER, this.glFramebuffer ); - gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.baseTexture._glTexture, 0); + gl.bindFramebuffer(gl.FRAMEBUFFER, this.glFramebuffer ); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.baseTexture._glTexture, 0); - // create a projection matrix.. - this.projection = new PIXI.Point(this.width/2 , -this.height/2); + // create a projection matrix.. + this.projection = new PIXI.Point(this.width/2 , -this.height/2); - // set the correct render function.. - this.render = this.renderWebGL; -} + // set the correct render function.. + this.render = this.renderWebGL; +}; PIXI.RenderTexture.prototype.resize = function(width, height) { - this.width = width; - this.height = height; - - if(PIXI.gl) - { - this.projection.x = this.width/2 - this.projection.y = -this.height/2; - - var gl = PIXI.gl; - gl.bindTexture(gl.TEXTURE_2D, this.baseTexture._glTexture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.width, this.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - else - { - - this.frame.width = this.width - this.frame.height = this.height; - this.renderer.resize(this.width, this.height); - } -} + this.width = width; + this.height = height; + + if(PIXI.gl) + { + this.projection.x = this.width / 2; + this.projection.y = -this.height / 2; + + var gl = PIXI.gl; + gl.bindTexture(gl.TEXTURE_2D, this.baseTexture._glTexture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.width, this.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + } + else + { + + this.frame.width = this.width; + this.frame.height = this.height; + this.renderer.resize(this.width, this.height); + } +}; /** * Initializes the canvas data for this texture @@ -130,13 +130,13 @@ PIXI.RenderTexture.prototype.resize = function(width, height) */ PIXI.RenderTexture.prototype.initCanvas = function() { - this.renderer = new PIXI.CanvasRenderer(this.width, this.height, null, 0); + this.renderer = new PIXI.CanvasRenderer(this.width, this.height, null, 0); - this.baseTexture = new PIXI.BaseTexture(this.renderer.view); - this.frame = new PIXI.Rectangle(0, 0, this.width, this.height); + this.baseTexture = new PIXI.BaseTexture(this.renderer.view); + this.frame = new PIXI.Rectangle(0, 0, this.width, this.height); - this.render = this.renderCanvas; -} + this.render = this.renderCanvas; +}; /** * This function will draw the display object to the texture. @@ -148,67 +148,67 @@ PIXI.RenderTexture.prototype.initCanvas = function() */ PIXI.RenderTexture.prototype.renderWebGL = function(displayObject, position, clear) { - var gl = PIXI.gl; + var gl = PIXI.gl; - // enable the alpha color mask.. - gl.colorMask(true, true, true, true); + // enable the alpha color mask.. + gl.colorMask(true, true, true, true); - gl.viewport(0, 0, this.width, this.height); + gl.viewport(0, 0, this.width, this.height); - gl.bindFramebuffer(gl.FRAMEBUFFER, this.glFramebuffer ); + gl.bindFramebuffer(gl.FRAMEBUFFER, this.glFramebuffer ); - if(clear) - { - gl.clearColor(0,0,0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); - } + if(clear) + { + gl.clearColor(0,0,0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + } - // THIS WILL MESS WITH HIT TESTING! - var children = displayObject.children; + // THIS WILL MESS WITH HIT TESTING! + var children = displayObject.children; - //TODO -? create a new one??? dont think so! - var originalWorldTransform = displayObject.worldTransform; - displayObject.worldTransform = PIXI.mat3.create();//sthis.indetityMatrix; - // modify to flip... - displayObject.worldTransform[4] = -1; - displayObject.worldTransform[5] = this.projection.y * -2; + //TODO -? create a new one??? dont think so! + var originalWorldTransform = displayObject.worldTransform; + displayObject.worldTransform = PIXI.mat3.create();//sthis.indetityMatrix; + // modify to flip... + displayObject.worldTransform[4] = -1; + displayObject.worldTransform[5] = this.projection.y * -2; - if(position) - { - displayObject.worldTransform[2] = position.x; - displayObject.worldTransform[5] -= position.y; - } - - PIXI.visibleCount++; - displayObject.vcount = PIXI.visibleCount; - - for(var i=0,j=children.length; i this.baseTexture.width || frame.y + frame.height > this.baseTexture.height) - { - throw new Error("Texture Error: frame does not fit inside the base Texture dimensions " + this); - } + if(frame.x + frame.width > this.baseTexture.width || frame.y + frame.height > this.baseTexture.height) + { + throw new Error('Texture Error: frame does not fit inside the base Texture dimensions ' + this); + } - this.updateFrame = true; + this.updateFrame = true; - PIXI.Texture.frameUpdates.push(this); - //this.dispatchEvent( { type: 'update', content: this } ); -} + PIXI.Texture.frameUpdates.push(this); + //this.dispatchEvent( { type: 'update', content: this } ); +}; /** * Helper function that returns a texture based on an image url @@ -134,18 +134,18 @@ PIXI.Texture.prototype.setFrame = function(frame) * @param crossorigin {Boolean} Whether requests should be treated as crossorigin * @return Texture */ -PIXI.Texture.fromImage = function(imageUrl, crossorigin) +PIXI.Texture.fromImage = function(imageUrl, crossorigin, scaleMode) { - var texture = PIXI.TextureCache[imageUrl]; + var texture = PIXI.TextureCache[imageUrl]; - if(!texture) - { - texture = new PIXI.Texture(PIXI.BaseTexture.fromImage(imageUrl, crossorigin)); - PIXI.TextureCache[imageUrl] = texture; - } + if(!texture) + { + texture = new PIXI.Texture(PIXI.BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + PIXI.TextureCache[imageUrl] = texture; + } - return texture; -} + return texture; +}; /** * Helper function that returns a texture based on a frame id @@ -158,10 +158,10 @@ PIXI.Texture.fromImage = function(imageUrl, crossorigin) */ PIXI.Texture.fromFrame = function(frameId) { - var texture = PIXI.TextureCache[frameId]; - if(!texture)throw new Error("The frameId '"+ frameId +"' does not exist in the texture cache " + this); - return texture; -} + var texture = PIXI.TextureCache[frameId]; + if(!texture) throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + return texture; +}; /** * Helper function that returns a texture based on a canvas element @@ -172,11 +172,11 @@ PIXI.Texture.fromFrame = function(frameId) * @param canvas {Canvas} The canvas element source of the texture * @return Texture */ -PIXI.Texture.fromCanvas = function(canvas) +PIXI.Texture.fromCanvas = function(canvas, scaleMode) { - var baseTexture = new PIXI.BaseTexture(canvas); - return new PIXI.Texture(baseTexture); -} + var baseTexture = new PIXI.BaseTexture(canvas, scaleMode); + return new PIXI.Texture(baseTexture); +}; /** @@ -189,8 +189,8 @@ PIXI.Texture.fromCanvas = function(canvas) */ PIXI.Texture.addTextureToCache = function(texture, id) { - PIXI.TextureCache[id] = texture; -} + PIXI.TextureCache[id] = texture; +}; /** * Remove a texture from the textureCache. @@ -202,11 +202,12 @@ PIXI.Texture.addTextureToCache = function(texture, id) */ PIXI.Texture.removeTextureFromCache = function(id) { - var texture = PIXI.TextureCache[id] - PIXI.TextureCache[id] = null; - return texture; -} + var texture = PIXI.TextureCache[id]; + PIXI.TextureCache[id] = null; + return texture; +}; // this is more for webGL.. it contains updated frames.. PIXI.Texture.frameUpdates = []; +PIXI.Texture.SCALE_MODE = PIXI.BaseTexture.SCALE_MODE; diff --git a/src/pixi/utils/Detector.js b/src/pixi/utils/Detector.js index 250f609df..d6046a387 100644 --- a/src/pixi/utils/Detector.js +++ b/src/pixi/utils/Detector.js @@ -19,25 +19,29 @@ */ PIXI.autoDetectRenderer = function(width, height, view, transparent, antialias) { - if(!width)width = 800; - if(!height)height = 600; + if(!width)width = 800; + if(!height)height = 600; - // BORROWED from Mr Doob (mrdoob.com) - var webgl = ( function () { try { var canvas = document.createElement( 'canvas' ); return !! window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ); } catch( e ) { return false; } } )(); + // BORROWED from Mr Doob (mrdoob.com) + var webgl = ( function () { try { + var canvas = document.createElement( 'canvas' ); + return !! window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ); + } catch( e ) { + return false; + } + } )(); - if(webgl) - { - var ie = (navigator.userAgent.toLowerCase().indexOf('trident') != -1); - webgl = !ie; - } - - //console.log(webgl); - if( webgl ) - { - return new PIXI.WebGLRenderer(width, height, view, transparent, antialias); - } + if(webgl) + { + var ie = (navigator.userAgent.toLowerCase().indexOf('trident') !== -1); + webgl = !ie; + } - return new PIXI.CanvasRenderer(width, height, view, transparent); + //console.log(webgl); + if( webgl ) + { + return new PIXI.WebGLRenderer(width, height, view, transparent, antialias); + } + + return new PIXI.CanvasRenderer(width, height, view, transparent); }; - - diff --git a/src/pixi/utils/EventTarget.js b/src/pixi/utils/EventTarget.js index 3ee5fc28d..ce7d7a2c1 100644 --- a/src/pixi/utils/EventTarget.js +++ b/src/pixi/utils/EventTarget.js @@ -8,60 +8,60 @@ * * @class EventTarget * @example - * function MyEmitter() { - * PIXI.EventTarget.call(this); //mixes in event target stuff - * } + * function MyEmitter() { + * PIXI.EventTarget.call(this); //mixes in event target stuff + * } * - * var em = new MyEmitter(); - * em.emit({ type: 'eventName', data: 'some data' }); + * var em = new MyEmitter(); + * em.emit({ type: 'eventName', data: 'some data' }); */ PIXI.EventTarget = function () { - var listeners = {}; + var listeners = {}; - this.addEventListener = this.on = function ( type, listener ) { + this.addEventListener = this.on = function ( type, listener ) { - if ( listeners[ type ] === undefined ) { + if ( listeners[ type ] === undefined ) { - listeners[ type ] = []; + listeners[ type ] = []; - } + } - if ( listeners[ type ].indexOf( listener ) === - 1 ) { + if ( listeners[ type ].indexOf( listener ) === - 1 ) { - listeners[ type ].push( listener ); - } + listeners[ type ].push( listener ); + } - }; + }; - this.dispatchEvent = this.emit = function ( event ) { + this.dispatchEvent = this.emit = function ( event ) { - if ( !listeners[ event.type ] || !listeners[ event.type ].length ) { + if ( !listeners[ event.type ] || !listeners[ event.type ].length ) { - return; + return; - } + } - for(var i = 0, l = listeners[ event.type ].length; i < l; i++) { + for(var i = 0, l = listeners[ event.type ].length; i < l; i++) { - listeners[ event.type ][ i ]( event ); + listeners[ event.type ][ i ]( event ); - } + } - }; + }; - this.removeEventListener = this.off = function ( type, listener ) { + this.removeEventListener = this.off = function ( type, listener ) { - var index = listeners[ type ].indexOf( listener ); + var index = listeners[ type ].indexOf( listener ); - if ( index !== - 1 ) { + if ( index !== - 1 ) { - listeners[ type ].splice( index, 1 ); + listeners[ type ].splice( index, 1 ); - } + } - }; + }; this.removeAllEventListeners = function( type ) { var a = listeners[type]; diff --git a/src/pixi/utils/Polyk.js b/src/pixi/utils/Polyk.js index e29d77dac..8c5d30e52 100644 --- a/src/pixi/utils/Polyk.js +++ b/src/pixi/utils/Polyk.js @@ -1,34 +1,34 @@ /* - PolyK library - url: http://polyk.ivank.net - Released under MIT licence. + PolyK library + url: http://polyk.ivank.net + Released under MIT licence. - Copyright (c) 2012 Ivan Kuckir + Copyright (c) 2012 Ivan Kuckir - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without - restriction, including without limitation the rights to use, - copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following - conditions: + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. - This is an amazing lib! + This is an amazing lib! - slightly modified by mat groves (matgroves.com); + slightly modified by mat groves (matgroves.com); */ PIXI.PolyK = {}; @@ -42,69 +42,76 @@ PIXI.PolyK = {}; */ PIXI.PolyK.Triangulate = function(p) { - var sign = true; + var sign = true; - var n = p.length>>1; - if(n<3) return []; - var tgs = []; - var avl = []; - for(var i=0; i> 1; + if(n < 3) return []; - var i = 0; - var al = n; - while(al > 3) - { - var i0 = avl[(i+0)%al]; - var i1 = avl[(i+1)%al]; - var i2 = avl[(i+2)%al]; + var tgs = []; + var avl = []; + for(var i = 0; i < n; i++) avl.push(i); - var ax = p[2*i0], ay = p[2*i0+1]; - var bx = p[2*i1], by = p[2*i1+1]; - var cx = p[2*i2], cy = p[2*i2+1]; + i = 0; + var al = n; + while(al > 3) + { + var i0 = avl[(i+0)%al]; + var i1 = avl[(i+1)%al]; + var i2 = avl[(i+2)%al]; - var earFound = false; - if(PIXI.PolyK._convex(ax, ay, bx, by, cx, cy, sign)) - { - earFound = true; - for(var j=0; j 3*al) - { - // need to flip flip reverse it! - // reset! - if(sign) - { - var tgs = []; - avl = []; - for(var i=0; i 3*al) + { + // need to flip flip reverse it! + // reset! + if(sign) + { + tgs = []; + avl = []; + for(i = 0; i < n; i++) avl.push(i); + + i = 0; + al = n; + + sign = false; + } + else + { + window.console.log("PIXI Warning: shape too complex to fill"); + return []; + } + } + } + + tgs.push(avl[0], avl[1], avl[2]); + return tgs; +}; /** * Checks if a point is within a triangle @@ -115,26 +122,26 @@ PIXI.PolyK.Triangulate = function(p) */ PIXI.PolyK._PointInTriangle = function(px, py, ax, ay, bx, by, cx, cy) { - var v0x = cx-ax; - var v0y = cy-ay; - var v1x = bx-ax; - var v1y = by-ay; - var v2x = px-ax; - var v2y = py-ay; + var v0x = cx-ax; + var v0y = cy-ay; + var v1x = bx-ax; + var v1y = by-ay; + var v2x = px-ax; + var v2y = py-ay; - var dot00 = v0x*v0x+v0y*v0y; - var dot01 = v0x*v1x+v0y*v1y; - var dot02 = v0x*v2x+v0y*v2y; - var dot11 = v1x*v1x+v1y*v1y; - var dot12 = v1x*v2x+v1y*v2y; + var dot00 = v0x*v0x+v0y*v0y; + var dot01 = v0x*v1x+v0y*v1y; + var dot02 = v0x*v2x+v0y*v2y; + var dot11 = v1x*v1x+v1y*v1y; + var dot12 = v1x*v2x+v1y*v2y; - var invDenom = 1 / (dot00 * dot11 - dot01 * dot01); - var u = (dot11 * dot02 - dot01 * dot12) * invDenom; - var v = (dot00 * dot12 - dot01 * dot02) * invDenom; + var invDenom = 1 / (dot00 * dot11 - dot01 * dot01); + var u = (dot11 * dot02 - dot01 * dot12) * invDenom; + var v = (dot00 * dot12 - dot01 * dot02) * invDenom; - // Check if point is in triangle - return (u >= 0) && (v >= 0) && (u + v < 1); -} + // Check if point is in triangle + return (u >= 0) && (v >= 0) && (u + v < 1); +}; /** * Checks if a shape is convex @@ -145,5 +152,5 @@ PIXI.PolyK._PointInTriangle = function(px, py, ax, ay, bx, by, cx, cy) */ PIXI.PolyK._convex = function(ax, ay, bx, by, cx, cy, sign) { - return ((ay-by)*(cx-bx) + (bx-ax)*(cy-by) >= 0) == sign; -} + return ((ay-by)*(cx-bx) + (bx-ax)*(cy-by) >= 0) === sign; +}; diff --git a/src/pixi/utils/Utils.js b/src/pixi/utils/Utils.js index 71d7a65b5..4551722fd 100644 --- a/src/pixi/utils/Utils.js +++ b/src/pixi/utils/Utils.js @@ -18,13 +18,13 @@ var lastTime = 0; var vendors = ['ms', 'moz', 'webkit', 'o']; for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { - window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; - window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] - || window[vendors[x]+'CancelRequestAnimationFrame']; + window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']; + window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || + window[vendors[x] + 'CancelRequestAnimationFrame']; } -if (!window.requestAnimationFrame) - window.requestAnimationFrame = function(callback, element) { +if (!window.requestAnimationFrame) { + window.requestAnimationFrame = function(callback) { var currTime = new Date().getTime(); var timeToCall = Math.max(0, 16 - (currTime - lastTime)); var id = window.setTimeout(function() { callback(currTime + timeToCall); }, @@ -32,50 +32,52 @@ if (!window.requestAnimationFrame) lastTime = currTime + timeToCall; return id; }; +} -if (!window.cancelAnimationFrame) +if (!window.cancelAnimationFrame) { window.cancelAnimationFrame = function(id) { clearTimeout(id); }; +} window.requestAnimFrame = window.requestAnimationFrame; /** * Converts a hex color number to an [R, G, B] array * - * @method HEXtoRGB + * @method hex2rgb * @param hex {Number} */ -function HEXtoRGB(hex) { - return [(hex >> 16 & 0xFF) / 255, ( hex >> 8 & 0xFF) / 255, (hex & 0xFF)/ 255]; -} +PIXI.hex2rgb = function hex2rgb(hex) { + return [(hex >> 16 & 0xFF) / 255, ( hex >> 8 & 0xFF) / 255, (hex & 0xFF)/ 255]; +}; /** * A polyfill for Function.prototype.bind * * @method bind */ -if (typeof Function.prototype.bind != 'function') { - Function.prototype.bind = (function () { - var slice = Array.prototype.slice; - return function (thisArg) { - var target = this, boundArgs = slice.call(arguments, 1); +if (typeof Function.prototype.bind !== 'function') { + Function.prototype.bind = (function () { + var slice = Array.prototype.slice; + return function (thisArg) { + var target = this, boundArgs = slice.call(arguments, 1); - if (typeof target != 'function') throw new TypeError(); + if (typeof target !== 'function') throw new TypeError(); - function bound() { - var args = boundArgs.concat(slice.call(arguments)); - target.apply(this instanceof bound ? this : thisArg, args); - } + function bound() { + var args = boundArgs.concat(slice.call(arguments)); + target.apply(this instanceof bound ? this : thisArg, args); + } - bound.prototype = (function F(proto) { - proto && (F.prototype = proto); - if (!(this instanceof F)) return new F; - })(target.prototype); + bound.prototype = (function F(proto) { + if (proto) F.prototype = proto; + if (!(this instanceof F)) return new F(); + })(target.prototype); - return bound; - }; - })(); + return bound; + }; + })(); } /** @@ -84,57 +86,57 @@ if (typeof Function.prototype.bind != 'function') { * @class AjaxRequest * @constructor */ -var AjaxRequest = PIXI.AjaxRequest = function() +PIXI.AjaxRequest = function AjaxRequest() { - var activexmodes = ["Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.3.0", "Microsoft.XMLHTTP"] //activeX versions to check for in IE + var activexmodes = ['Msxml2.XMLHTTP.6.0', 'Msxml2.XMLHTTP.3.0', 'Microsoft.XMLHTTP']; //activeX versions to check for in IE - if (window.ActiveXObject) - { //Test for support for ActiveXObject in IE first (as XMLHttpRequest in IE7 is broken) - for (var i=0; i>>>>>>>>") - console.log("_") - var safe = 0; - var tmp = item.first; - console.log(tmp); + window.console.log('>>>>>>>>>'); + window.console.log('_'); + var safe = 0; + var tmp = item.first; + window.console.log(tmp); - while(tmp._iNext) - { - safe++; - tmp = tmp._iNext; - console.log(tmp); - // console.log(tmp); + while(tmp._iNext) + { + safe++; + tmp = tmp._iNext; + window.console.log(tmp); + // console.log(tmp); - if(safe > 100) - { - console.log("BREAK") - break - } - } -} + if(safe > 100) + { + window.console.log('BREAK'); + break; + } + } +}; diff --git a/src/tilemap/Tilemap.js b/src/tilemap/Tilemap.js index 75f02fd2f..098c0ae89 100644 --- a/src/tilemap/Tilemap.js +++ b/src/tilemap/Tilemap.js @@ -197,6 +197,7 @@ Phaser.Tilemap.prototype = { * Sprite is created. You could also give it a value like: body.velocity.x: 100 to set it moving automatically. * * @method Phaser.Tileset#createFromObjects + * @param {string} name - The name of the Object Group to create Sprites from. * @param {number} gid - The layer array index value, or if a string is given the layer name, within the map data that this TilemapLayer represents. * @param {string} key - The Game.cache key of the image that this Sprite will use. * @param {number|string} [frame] - If the Sprite image contains multiple frames you can specify which one to use here. @@ -204,28 +205,34 @@ Phaser.Tilemap.prototype = { * @param {boolean} [autoCull=true] - The default autoCull state of the Sprite. Sprites that are autoCulled are culled from the camera if out of its range. * @param {Phaser.Group} [group] - Optional Group to add the Sprite to. If not specified it will be added to the World group. */ - createFromObjects: function (gid, key, frame, exists, autoCull, group) { + createFromObjects: function (name, gid, key, frame, exists, autoCull, group) { if (typeof exists === 'undefined') { exists = true; } if (typeof autoCull === 'undefined') { autoCull = true; } if (typeof group === 'undefined') { group = this.game.world; } + if (!this.objects[name]) + { + console.warn('Tilemap.createFromObjects: Invalid objectgroup name given: ' + name); + return; + } + var sprite; - for (var i = 0; i < this.objects.length; i++) + for (var i = 0, len = this.objects[name].length; i < len; i++) { - if (this.objects[i].gid === gid) + if (this.objects[name][i].gid === gid) { - sprite = group.create(this.objects[i].x, this.objects[i].y, key, frame, exists); + sprite = group.create(this.objects[name][i].x, this.objects[name][i].y, key, frame, exists); sprite.anchor.setTo(0, 1); - sprite.name = this.objects[i].name; - sprite.visible = this.objects[i].visible; + sprite.name = this.objects[name][i].name; + sprite.visible = this.objects[name][i].visible; sprite.autoCull = autoCull; - for (property in this.objects[i].properties) + for (property in this.objects[name][i].properties) { - group.set(sprite, property, this.objects[i].properties[property], false, false, 0); + group.set(sprite, property, this.objects[name][i].properties[property], false, false, 0); } } } diff --git a/src/tilemap/TilemapParser.js b/src/tilemap/TilemapParser.js index 08844ef0f..b5fb23701 100644 --- a/src/tilemap/TilemapParser.js +++ b/src/tilemap/TilemapParser.js @@ -265,7 +265,7 @@ Phaser.TilemapParser = { map.images = images; // Objects - var objects = []; + var objects = {}; for (var i = 0; i < json.layers.length; i++) { @@ -274,7 +274,9 @@ Phaser.TilemapParser = { continue; } - for (var v = 0; v < json.layers[i].objects.length; v++) + objects[json.layers[i].name] = []; + + for (var v = 0, len = json.layers[i].objects.length; v < len; v++) { // For now we'll just support object tiles if (json.layers[i].objects[v].gid) @@ -290,7 +292,7 @@ Phaser.TilemapParser = { }; - objects.push(object); + objects[json.layers[i].name].push(object); } } diff --git a/src/utils/Utils.js b/src/utils/Utils.js index 8619f78bf..b7f3faf88 100644 --- a/src/utils/Utils.js +++ b/src/utils/Utils.js @@ -244,6 +244,16 @@ function HEXtoRGB(hex) { return [(hex >> 16 & 0xFF) / 255, ( hex >> 8 & 0xFF) / 255, (hex & 0xFF)/ 255]; } +/** + * Converts a hex color number to an [R, G, B] array + * + * @method hex2rgb + * @param hex {Number} + */ +PIXI.hex2rgb = function hex2rgb(hex) { + return [(hex >> 16 & 0xFF) / 255, ( hex >> 8 & 0xFF) / 255, (hex & 0xFF)/ 255]; +}; + /** * A polyfill for Function.prototype.bind */