From 73ad62f1792d1ca7c7658c39ee0ebe4c9220bba1 Mon Sep 17 00:00:00 2001 From: stemkoski Date: Mon, 17 Mar 2014 22:50:41 -0400 Subject: [PATCH] New demos: Touch Events, PeerJS networking. --- Three.js/Remote-Controller-Receive.html | 159 + Three.js/Remote-Controller-Send.html | 171 + Three.js/Touch-Events.html | 149 + Three.js/images/A-button.png | Bin 0 -> 40322 bytes Three.js/images/B-button.png | Bin 0 -> 32496 bytes Three.js/images/SquareYellow.png | Bin 0 -> 2123 bytes Three.js/images/X-button.png | Bin 0 -> 35479 bytes Three.js/images/Y-button.png | Bin 0 -> 30376 bytes Three.js/images/down-A.png | Bin 0 -> 12053 bytes Three.js/images/down-B.png | Bin 0 -> 13184 bytes Three.js/images/down-C.png | Bin 0 -> 10231 bytes Three.js/images/left-A.png | Bin 0 -> 12021 bytes Three.js/images/left-B.png | Bin 0 -> 12940 bytes Three.js/images/left-C.png | Bin 0 -> 10626 bytes Three.js/images/right-A.png | Bin 0 -> 11820 bytes Three.js/images/right-B.png | Bin 0 -> 13253 bytes Three.js/images/right-C.png | Bin 0 -> 10992 bytes Three.js/images/up-A.png | Bin 0 -> 11918 bytes Three.js/images/up-B.png | Bin 0 -> 13045 bytes Three.js/images/up-C.png | Bin 0 -> 11020 bytes Three.js/images/xbox-controller-FPS.png | Bin 0 -> 195049 bytes Three.js/js/GamepadState.js | 170 + Three.js/js/KeyboardState.js | 13 +- Three.js/js/Three66.js | 37760 ++++++++++++++++++++++ Three.js/js/peer.js | 2657 ++ 25 files changed, 41078 insertions(+), 1 deletion(-) create mode 100644 Three.js/Remote-Controller-Receive.html create mode 100644 Three.js/Remote-Controller-Send.html create mode 100644 Three.js/Touch-Events.html create mode 100644 Three.js/images/A-button.png create mode 100644 Three.js/images/B-button.png create mode 100644 Three.js/images/SquareYellow.png create mode 100644 Three.js/images/X-button.png create mode 100644 Three.js/images/Y-button.png create mode 100644 Three.js/images/down-A.png create mode 100644 Three.js/images/down-B.png create mode 100644 Three.js/images/down-C.png create mode 100644 Three.js/images/left-A.png create mode 100644 Three.js/images/left-B.png create mode 100644 Three.js/images/left-C.png create mode 100644 Three.js/images/right-A.png create mode 100644 Three.js/images/right-B.png create mode 100644 Three.js/images/right-C.png create mode 100644 Three.js/images/up-A.png create mode 100644 Three.js/images/up-B.png create mode 100644 Three.js/images/up-C.png create mode 100644 Three.js/images/xbox-controller-FPS.png create mode 100644 Three.js/js/GamepadState.js create mode 100644 Three.js/js/Three66.js create mode 100644 Three.js/js/peer.js diff --git a/Three.js/Remote-Controller-Receive.html b/Three.js/Remote-Controller-Receive.html new file mode 100644 index 0000000..9e0e4f5 --- /dev/null +++ b/Three.js/Remote-Controller-Receive.html @@ -0,0 +1,159 @@ + + + + PeerJS Receive Test + + + + + + + +
+ + +
+ +
+ + + + + + + + + + + diff --git a/Three.js/Remote-Controller-Send.html b/Three.js/Remote-Controller-Send.html new file mode 100644 index 0000000..ba9e220 --- /dev/null +++ b/Three.js/Remote-Controller-Send.html @@ -0,0 +1,171 @@ + + + +PeerJS/TouchEvent Send Test + + + + + + + + + +
+
+
+ +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/Three.js/Touch-Events.html b/Three.js/Touch-Events.html new file mode 100644 index 0000000..d0095dc --- /dev/null +++ b/Three.js/Touch-Events.html @@ -0,0 +1,149 @@ + + + +TouchEvent Test + + + + + + +This is a test. + +
Another test.
+ + +
+
+
+ +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/Three.js/images/A-button.png b/Three.js/images/A-button.png new file mode 100644 index 0000000000000000000000000000000000000000..0c74d87abd61c6abb14c63e2d09551e53e9b1e01 GIT binary patch literal 40322 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 B5DEYQ literal 0 HcmV?d00001 diff --git a/Three.js/images/X-button.png b/Three.js/images/X-button.png new file mode 100644 index 0000000000000000000000000000000000000000..9d3e087278b4ccf5e36e91dd012b80e9742f3b50 GIT binary patch literal 35479 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 BHyi)} literal 0 HcmV?d00001 diff --git a/Three.js/images/left-B.png b/Three.js/images/left-B.png new file mode 100644 index 0000000000000000000000000000000000000000..f56264f58efe12ddc0010905fb592e333b18e9f1 GIT binary patch literal 12940 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 literal 0 HcmV?d00001 diff --git a/Three.js/images/left-C.png b/Three.js/images/left-C.png new file mode 100644 index 0000000000000000000000000000000000000000..50b7d5fde5ec1b6e330bc0e0d98d311d9310761e GIT binary patch literal 10626 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 literal 0 HcmV?d00001 diff --git a/Three.js/images/right-A.png b/Three.js/images/right-A.png new file mode 100644 index 0000000000000000000000000000000000000000..a8970fff6000a6ebb37138fc913ad0f6fb6a24b6 GIT binary patch literal 11820 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 Fe*kOpg}?v+ literal 0 HcmV?d00001 diff --git a/Three.js/images/right-B.png b/Three.js/images/right-B.png new file mode 100644 index 0000000000000000000000000000000000000000..64ef5fc11a2a3237dae3d9b8d89b1527aa24c10e GIT binary patch literal 13253 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 literal 0 HcmV?d00001 diff --git a/Three.js/images/right-C.png b/Three.js/images/right-C.png new file mode 100644 index 0000000000000000000000000000000000000000..2db8d5469b4b0231dc84d5f1a2c29db524625a78 GIT binary patch literal 10992 zcmW++1yodB7kxtxr6V~=BNEcxC@rag3|$gR4c#C^2#O%m4GIiMcMmv#fJg~QcaBJl zl+^#ef7ZNtYrQq=-uuqE=j?skCJLr0Z(5&sTwi?Q>?4mAL<0valc z&-`b1L!7-CH!?PlLJTdOe3}Y<&5#4NeCuSrP&400Y(y^6kuJ>~WEmVuQI zGyeIQQIDD)5EBg2S(wu)lk<10 zF1;-~n+iwdn`C`bl;?0oyj$)|rS#(juVE?NU0gQg2RHgI`dJdkTb`JF!U)C5v9T_= z?asG^dkWt|fVTLfVcGaxDUa~qtpYqT>?NfS?kI%=q_-7e;gKI;Y%0%V8$5??vyZsZ zV%ks=N+oW#NYCMjY_GR4b}A)m2O^x@ko1VxusRQV4H(PzqDPfGW@1tIhw9~+LnRaTF3Us)==Z*O1A$Ap=^fF zNdR>yTeLKe=P02l*}>3I$7|T$(2#~|aFFXMIGi`c$)1S`e7} zmo1C{s!B^~{lR5;wlLb6MDYzgr6w#bRnnt=nOU^esxT=1@H;$+3Co*(6anu zk#(&I;e`Ri=3$CQp)Zeadrb{{1{&Q*OgxIbS z?(#lxL$u6_3pSoQ_RtyEkFD8x4mk=ZI@lb_+ImaHRcJ0i#}--dIlPw7?dvhTq{h?! zl)9&5b93`CIA#7Q_wyRK(kB>b1_DKHppsFFH>`<~dlxVxpB>w$3mr*ht>wFyHeloA zR0{vG7F$Cnjk|zgu+l6DL!Y0?945f565&xefxD4WTKbSfL}{rS3EOV+n85J4ujbY* zjl_TK36Ht&#^txSGmXBa{`3?Y6$Y4LQ}JpjyJf;Ac)z{Nn8Rxz{1vPa?;`OI+&Ok4 z^y^3G$(7rJp2MN)(DU{pM=rQ+L1qye_(n$Yjf@COXB8}L8FFl>k-7mx!PyFvV6_P_ z&j41~^qsdV`8CGm18IVSsK#_1txhwNYEg*34tZa?< zmOdD#?!!;Kpinj%$U03m5@ot2og#pGPE^SuZD*p!nWXq2MM)ydP)uizcsUI;7`-j{ z0&A_6#W>jyIC0e4VRQ1p20JjTs<#7iZ{dzHQe#gLA4uizE;THJz3U-G@xO4L5w_qj z|BcZ7$#B^N2wsDhAJ;CnjD$=6Fsvuep4TC(xAm@verA-yMIOy|J%PQ|+~NbU?3?#^f`C zqo#XxqzOxWF8%#0IGOf{6w8Y!{}_`WX=j4GO@Jq#Jf=mU*j!Xm;i_X~M2Dy_71!Z3 z9{ZTsSV3o8kvAt?l~i=x^>1RHC*~$TpZhUdpZ0^*X79x+CbNR$D87ZJL`;P+bK6>HZd>rw`0rqOG=#} zCI*H+g1BHh+Z}t0sd(YuG0qPmz!c@^%Vz|zF`ss2>K;p_Z$6)%{;EykHPYOktWb7# z-p(Sn)g+V+o!m<`7t|Vj``X#ya!)YU|*D&#eZd74fg=Xnf=ewBV`_dUktUY&I=ePjyX%gb%4csxcA~5J*vhaiR#>U3@N~*%+Hf6ebcv?610i! z;XxO3qXT-=@g5OlLja>U8v|)twS0Y0wrv7M|0)g*tNopw`RS=Cb$pHCOTqUMMn+u&=N|XanltIJT9xtocaTC7wtGNrwwHb7Be@rV zWzF-~$^CYzB;nP1u*j@5J`+zLE33SxeDw3#FWU;WrhI<2zW?;;(`aXB=iljRV`CV> z<3#Vf-Toj-5d(eqR8V5W~4^AVI0~-FLu=KYoK%n&PEjz4E6|(Fv#|*9(%ojM) z@PiG1xmW#QhOY@e|I_s+Ta)nZYLeM7af4m=*_qTW7KLEGtf56o1qFqG-#<$xYWc{( z#2e1)|LSOa;lKdduXjJFBDddMeBFA3b#siT)NwW-wgw&f;a>I;-{!|9|49hOdu!}t zX(%F4F+ak)VPrez9Zo z_OFZZ4){J_GZlAh5B)O6)LB-ssfIsSBg{siqoeb~CnTbBdmG9YTA1vhZBZ`x81!w; z$I5Msa8`2;jWO33*&e;K{AP_GOG^4D+3Rdsxm>@&yGsm;g(M^-f|o*0E4FraFsrl9 zRqeqGtv(Nf|MYX+*4Ea}&KPpY0->BO=J}9@eg*j+%@51VWhcs3O_bn7PizgCHAgrj zF-T6oKQ3YE!<_!&I@?9bdob0*lZi4TztiJmaPMUo1?(bAkmNTaOvdGCG`f0JPp-Xw|d{vCl>q^J7yh-mgjts~G(9Dc^Lj z2!U(4fXtJAw4(001&oD5s!0|F6k)yucwuH!l@@aW!_qkF)Q@RFw%Me_=n+BU*0CfD zMtpqy+j4)t-Am8RY~6hl`fvP4Sye}jornqTSLF1Vqh8TCo^I7u`!PXPlD5|xU9=LL zn4}~~D3EzNun{TL+}s@hO!Yk&G}v}`wt=a=z5VqHi|=-uW~ZrZ%HT@Ztb~xz0hmSo z-p9tuU4u4sRDaLNGblOkvv9PkRZLd-bIj}aN<&cY)VtEzMOfRreDm}p5eb+QlF)t6 z1?8^>VT|tsLEe{AsZlf0dtkP~S$0_A!UY;GRB@;8z3X?H`}5(SrD{B4UgQRBrh#u` zwCUM0!X)dvvkW>cs-~vK(KnlF&PXQ8N%K3hqR}bsux<2rWt59f%9$uo+xVz3JNOjk200hl*~8>cRRS>4knDOhO#m< zQ)SyX<3o0A(;AwZZU(Se>`H%szbK7r0ywN9PeaZfJwdvBTBCf^rca22S>YP)7@%5v z>i8JA1r4{p@9SbIY+2Z7uBBd_%Cn>B8~fWREM-MIQjkai?Fb; zc-+x>mTs!AlDX=6#2OcJ;}YyZ*S}M7x9p7oh(X^qyH>Civ{Y zrf_^KyqueOQ&Xlsx9@iYYJ~{nOP5M_K+EMr`vyM@D+}2vOs*_QZk%NB^0~5=mzPiW zVx}Rm0n?`>Xa={rdPf5Y#j&E|7|4VJ zL;VyUN3*-$KH?iIPGVI_Q)VR7x^Kbkx#A87bHE3$SK@4i>afVC-FQ+dny;mBAT5Yj zDH!FIM9#kyM#jrFC^$$kZ7<%G>;4v`5x9VCN0412)h+s!V1mfh%{G&ZiNfKXsH3_7Z;hI88*Dr z|5xcQb$PhBXj$LbnCkkv{z$!jk(1YPaBR5F6r=!+pi@Gd&o{GJva_=%cjq z^SLWySdft4Jsuu^Ja}i4bX{CBpDN8Hqj+~_=f_YPL*Vc?9j59Hv_#=w15 zz14i~6JoruksJ{GJN@O50~lYfVScK%hWdH`&Iyiy5&6@3bwk6ezXu2It6*_8PRhE0 zHP=vR9>w@>|C(=ROc8Urzl0gP@uEVHP!Qlji6VrRzR{7ewTC@oMclrexFk;HH&gR! zF@FY&JPv0Ku54>-YX>j);}%HX)Ld>>;patib2Y^L(GTU@6fZ zm?j!kbU!7vEdEnT)w~;{lmef>^&4iG_=Lz^Xj_{}l;vnFNCl_XduCMHFlaT#v(evAtH{!+CKK3*8g zwF{vWuIJi4=n@78eD@cxzPY_R%Ca;`y_o}(_ z;hol}x$;3BAzSFQ#a!KD7MWL<&2lm_GCB-Kb${D4EkBr6)SA{;Ss2f8u6s&vuOcUW zu0%77Dj-iA9-1H9<=)pytrzJHRMo@SZ+uVLU&I7>*+l2AMhG;0|2W%AS=GO=yv#~O zOnh7J`fTk#JaChMw1-@rFp#jxW@TkH@tM>X3O#L`|L)Y(_v;x?*^5wRevN8EgtI7v zvob^083ZK@VGOUy@6M8aW}m0*4i-_$iG_X*_4OF%tAEGizBmuFj&j3s=>=b$$jQ#k z(Uo%at*iCc?aCm8UUIxDd%{aIGqX3a?=A^{0Y3u=w>1;8ITA> zEqh?fUI10*Er~_?B?yf~2@qizrpir}D@-s**DO!B@VOQWox!iZgO~{3%otZk~ zxklFucae-s6^X2n7h-qOAbYOQKpuNC%%hexwp$@ChX`v+nVat_1X$gquyM{0FK%IH zTqK|$$*@a}O-+72zP_`T4Gn+uQt|WCIMXs3M=@U)+k;qgGcyCv{|pG;eE;_CTimCo zpW7-bD8e3LCEEPR>?Fsq{WzcJ=DqMg=W^7lCB)VPJ?FsnJ(LZ6T*%@!?|)(7p*ljRwWy!O&erjBTp`{RoO0f(lCPRU@wyfxS6w_lNF;X6~Y<^8X? zST`q;i;HVu*45H@n%95fAEvbQIauA;+SA{lh!P&ehH4A<Z;yOAy7;;0e1I)cgRarF9`gvgHPzNi1S0|?j7r|ACjGPdM-IEe-3duSd zf@M>H7*aS`Sm{gjzHf~kCno^yx?fRI!GI_?UNJ5?>`K?w$js_JJSUhR^S?gFh5r3% z@T7-YwZ%R-_)UBBm@;8@9P1zs_<_$U2eStGD7>HyH)?>Pc zo$6${COHMVySvlV)45;n65YOoohYKc)f!M+a3Vg{WHAPoy8_Jd5yBD`tD6mx(n-ph zI9Y}zuMzp%5bSG^z!^M9ndKSqbmO4pp?;LTBV;j^FyxRhu;CyIDf2UjT9cuu+^6k& z6T*(pgCA4(fHbWV1Zebbmv&p^(9muNvu*mOth<)&Qb&m2V#w)2(9!wjDJLiA7<&6q zbL4eCH@GnDe-u?*Ts*JS`OY@6_6v67hR9!gd$i&@@ANcBZbx^wgEhX_#LMSgg4u{; zM0e%U%v<-Q_1(Fq{iN2){XsjC3lJSM&=wVI!*(K|&pUtp;symL+NUm}AuEsXXCnyQ zC9){=HD$TcMm=kXUH>FxS!x!upr(i?;u;H#riwoC|8dnYu3Z0co*2)$pFe*-`juj5PrD#XZ z?zzcW5X3!Gt2E-XX!V{W6U6+-8r?u>4VFvle@V6rANbm4Q_rs=hcA^kq7}~i70Q+- zU{b3>9^GB65gll1k0hhh9hjm^jO3=fINcBBOL%Nfvg`m<3AzJK_|umC`gMfvoJk}x z`O`E|0^f4u8obz=7=BbkImTV|rgQsg((7T}w<@c7`^{_bxq!l{o-<@d>FZP79Cx8N>^$ni+efkY)<(FFaW}5^N|+ zCzPOJ^oyb*Ool^4rs=0l)>|FCb`A$53wlVEOdcGhl|SKeFbS3mkRyrbW!MZuBM@rY zM9bpNlVCZ_jk|OOQ(%Mt@uP+|giK0w89fqra{i8=^6ghas<&ThfDXVmrCaHZI53%# zbf*{s@~XFyAK++jXFl32BFm;E+Ff9~&~_U#{rVTa5y|@gs|{YO02ubRO|mV{>nT9@ zaq#!XI8e^52?|xddej&Bqn};#+#G!>W`*;Q30&?;){dx}`%D;y<~`<6AYJAJ-Uhsq z!ez-A*AgBC;BwUIZSV9>|F;27Ha8vSHk6Q(du0xc$!1tYAO%@Xx*k5me&F9x^=L>i zG4@POKK8ii)~y*le|Pt%9Fg^S{?1a2YOFAK`H?~1fmB|#XaBNv@3zjrt~2&=Z~686 zcPps<1=C{VfL9UH)3lAf(9G1?`o%Z;By4w-^21rPIaz+QkzuIsl^&-LT5*>*^0W=~htD zbJ60UTv}}L!sP(WN;Fg+o7{mm$tmb@0At&Ct8_c?4XjoGPOb*>1=U_&VuxbKSj*nu zK{J==XBYoied(sGLa+Qle~3FIo{q`+_b)iG)5%d_!P4jxs9EDKv!8O?wQc!|Unu?@ z4{&$xIcGiL;N1%V4f6yz=ksSpFRja$yjuTN8Q~FNkv~$gO z&Gwg)22586+3QzEqWwWdVM&;}?|85xjsM#(Xugqhe}zEIv-)R3Xdxd0UL6!-gt7Z!stjxyv_X|g?t^=(9qBV7%dD& zmH``*z$G=aL&{L^P*?rfB7r+7Xl!JIf}uq|H<8xWk`sdy2AHgf@@;d=7G!gZuX=S{ zw)BL9k_GR89w5HEZr`H=XpO_uVUz^briq1Ji2=o{;Zv_uCQyq5#wGe!+jVXWc0Cpk zuMgvI17~z_Dz8luCGN~THHh`|=i!-?Dn>$ZKG{bF2x%b$c}g<-YK?-vj2F4*lHmHj zq-!khN$dWZ%=8z>)S3&ZOXCzZ9Wf;(CHVzUt8(u1HarX@?5GNPkrEdwCnN+TbSI1L zyytcQULhU|KMeJlhvJTs3|P#S&EBa^wHwws9@gl1_Q3N@Z|Ofq0B(Y&Y_DPIJi>!_ z(vSa;0<`>OarzHP!eRW9UEhXOj-4J^#kp?&#fEYiR^vz+F3gDs zrBqUi{7{SfJ62{kG!hZEMm^1RM@wS`B_9K zraqN(DnpK6OLH&>Mh=5K3=a?fjz1BF^A<+G_@S8uy!jbCuG(u{nCR`k4QU(29JjL0 z1M~4S@%c1kl6Yq{zrTQulLAWhEwH}weE`KHgw-^@^^vU`d}oLldc9&dA${LNfow#Y z;YK9kF@_F{d%7fS+!-GExOiL$t=#PWvSF?#(=uyxq0;@N^-1;!hJtK8RIJZ>;oZNA z54Rv6#nJ{$+TLj8b1O~xs5_L2eJ{yfa6*TUQ}~CRxDE>SS%=Km&%LtZq80e7^i&bX z(ZAig?T%xxx< zBk)>FOEp+zDALwLnAOte>I^vp#u;WI*%Fvp9d@I4Xa{0#E|X>7*s=|)clp1nG?Ccp z7OxkKz0Zb98E`%MU*k+Kv_AYm=z~A zpiNGIXB{i7^u|TF+U%r^7Ble;Us9WIKqM+U`f5Xkf)-p|kYM}Yu@Io+c0p1(a{?h& z#p^qsOtFlz&6$q z7(+(kq@@WgGe;LVf|1y)A%~Cy3FDo`_KP-fKcC_?ll8v*85rf_WbMGGQR#fEVl zpVe@!0hS<%`1R{E_;1LbAy7$taNE5mA($c|w-+1EDnx~Gw3xC*s18%$w?h2I`*V|A z4m#(ggvG^=|Hv*LzE>aOsIaBIw*NkcrA3Jw8rt_7YmV2a&&(SfBCO)%YsYn2Fsb zV?T}8(HD%1389~@We;Q@+31Y3Y9u-XlMyxx&!(ktAM9tIOy7<0d1zm|*DD=J(oM)f z0f5}@GI z{P2HVmm)oUj3jbT;W+MkvR z^+t*7XgOul@*jr}=mh`O=l4pQPI*QMz5IE{Y5V?1$2f8QhsQ8- z7JnHOD1IfoNMuPUGFC)W*$}ULE(u;`3P-K1*nGL~i8MZ?a!B-5#qYoQL3>BmJOFDwk<&G>6OX?*Gc=Z%^YJJY%<=*Nyj z$WbKWAMQtD*at*rXJ>WIGFE8HEzQ^=)}Ee}8QQ(-KX)LfRf8P6bLlhJuLD(P?W|iE zQJ+dn8KDI49Wy0B_zv=5$Eo20J??)G$pLx0COY`pkW#wNW*cNa;-7fNNK}Z#`}`bL zttzPuuiT$LSDK* zUy3Zr+`hfCWbK_VEKB5&eMF7ANgCKdzENSXPY)|gAoX=Izr@tk2#4O;9IV?b)|p%o zdUD%_5!<;+P|1ga*h%9+0f`9|wxacAD%g!kFcTbp{dSayo7XJI@t{(AlKCH$M+jvO zq631ti8YViqyez&D-9=qkLRzam(1)ul|L3a{O-v5)Q2FMbW!GH4*xRGMLgf6XyXz;US?hV0Z zKXZRC%Pex`ke9 z;zmw1@C55kX4wk|>$TwZ33Y)>@!0FvW^|7e5j?Nr@`-$#D7!G<8a#k60~?+tV^OUapx(IZ zX}_aO)3_SnVt%W|d|2JuwLD1vZc~47(W;T}UIYVCR6aMUtqUA$+hVTUScsadF+kPu zz4Y`PQ@3u|Y7*wySAgqmUcLMEBN}o^t9IDsYx3UUhHEXGi{6&l&~Txu^morjuddqU`ObiW85(}xakJM{tb{Z!q5jFLleF6ak z4t1~clzp)6-?hga0zq4;kz#Ef{uE6RSSyQHbFi(e>WrV1m9`Ge!i%V}J$c)$@4-Gi zOB{38j+H;7T2espoxB(>n{rIG&LReYXBPD*k5R?euxxm?R3tnu#CG`>7=zMF$kuY4 z&kb$w=CqaNdNDiU@*Vg9j>&Ba=?ae%mbkU73JT6|>vgb+Qez1w6z*uwcQAuSO#qs$LKY_;Ntm9wyqK=OjGlu-tjf z9kV*(^%=JL0Tc8?O8L)S9?K{_31;7c4oiACke<)20YrOSd>Ju4bKJ7Fl6jU)#7Lyxs$klI$3w1S3K1Wo{14|NKWo&0WM*W#76E zu(!J+aAs~qjY?5^iUFipPJqQ!r96lU45cqe!dutF6w5)tNkNzXrQAM&F}7Ow)N{TsAeM zol|ZdJ6I1;(u*G->^4A;)MEdi4Z*CdZX=d)v3$(j%_(5{TCe)^z@)*kt`wjAB_B#w z+f}M6DKkH3dLO&9^R)wZkFX#z7xm7N){DfN>}v$@iiDMDIVrt(!@!Hnx2%6&5Mo3Z zvn3_xoM>jzCo)8UqK}P=?FN`pD-+h9d2GDUwQ*(f^vifO-H&^ z1ZhK5lM9ZVjtn7}6R}}II+U_92GVzOR`9ufIddJ?>nub#Mu^>Z4`C>aV{b->q_B56 zB+hOTb2hQfZl8UuRF(fIP<)*0v#?k#F1nP7e30dRmk>+)MqTG(ok6H5%4(X`(?*!0 zKda)5K2`?1{kgzGf{rqu`fIn4#P74rjD%qm5cvG856Szp_=he&@^m^7V(NOZXjTUy zImLiR$~I8C$G5PsSm4|i=w%D2OR3%!vF|QN`Bf@K3i~)PpjaS3AP4lp7`)}Xz<$K& zd0xiKP17`UK$6de&DnKbpd{7iFQ=jIgBUBxj5B`GGgCF|^est9GyD;jYp_-IF4k2j z)9V&~dbaB(&E_v`2Fbh_dAvImd!C*Vca{k;2dqotn_7Eev4_AHBKNPsf-fv2pkDrX z&Hb?kLYlF}5Er5!2;9xRa}CplHGBBRu{?0C&oNM)4w#d##%3&E$&w5Dro<;i=}hLL zY@$0})*fWPU!=Rc{`E8G8!l}aSYsnUPT`{2?(7j9;2$9!H)szaE|Eyfp(MakG$~vM zFb&=r3}B30S$b&mdx7NWD*X2HpIbfDs5PN~>T3Yv0-l-l1Gwx;CjTPol|p{^+nVcF zvwHNqaiCYbLfl0&=0kjm$-4rfe@4u}f%;MV2iyabtS5j#Dz!+g!-t0%`H8s@4}RDkjG@s2*oA0~k$}5+Y|+Oz z@ya8@q_*{2BR0(n-41)gZk&8WM*JE&tWC6#^KQ-$S1KdYy*A|ryhKP+isfwqS|ANi z+LU0u%=+(HZ*X>M3h9%s1FzfNp9|4C%fkaFeXkRT(nLHZQ3E!AFHiV9e~7@#Y+eyA z`w6MZ#0~o`3yA$Uz;h9IE|iy!keh{_OaOT12zb(MOd@LcYGkaLEPo~V)4!7XZw+s{ z(sf2DtYuM5LI@^ex@C`IYwA}gc>asY)o$9ho90pD#cc{W4tI?)OIe`7c6uW|V?~sN^59Ts=`p5Iy!r{2`0D ziLV~YR~WyUQIuH6w-GMm^gT`on9RuQ6TX<^G8Prr#d71A@eXbVIR^2ZTe<1Y#6BKF zT&gnA>`{%(JM?@4qr(&1~L!Ezt saUv}5!i)wpO-I!ce?(cz1y*nHM<^+0%rTXN;Mr6_LseI$Qpq~}e?^V*X#fBK literal 0 HcmV?d00001 diff --git a/Three.js/images/up-A.png b/Three.js/images/up-A.png new file mode 100644 index 0000000000000000000000000000000000000000..7a6363746f6a19fddf9126a3b8ccbc8a0abfb156 GIT binary patch literal 11918 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�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 SV&i|8TGf=a6ieZjZ~q6U$32Vy literal 0 HcmV?d00001 diff --git a/Three.js/images/xbox-controller-FPS.png b/Three.js/images/xbox-controller-FPS.png new file mode 100644 index 0000000000000000000000000000000000000000..c130c52931cc1900a0c48a99b6b6d7fa6ed923ed GIT binary patch literal 195049 zcmagFWmuH!8$AjLN{EznHv%Hv(w&mh-JLQtBAwC=5`uJtba#W4lysML&Ux5-|NiHE zIOjUog&)S5dFOqfSogZuT2JT)1xZw70%RB%7*uH~F=ZH-XJjxiu;+-+!T@ z$Y7+!gjFHG4_e&PP1G{3psiQxuQHsEGSl(e$!RgD@jfUCzgE@hF(=hsS}7VjN}c5> zZ!Bvn3onDcP{MFj^Z!Qn>Lpt{Oq|M?%V@Tf!qrt@X;Il8^VECTM$gvc+5JyyE4)`T zh1&I_JXvR`l7_H5jb)kNLc+)p{r~wJ3LQSxQ+-Wc{Wb1Q0UbY1qdq#k@Y7$w_qY74 z1r%ZmbV6I`qNXH@pdn?(A#G9McYIXLe*x_vQ0tKsX#D7oefATjjg+?MDCCufLC1I)NpjEZwv0;?!OEXML&n-*Zbw5({ zqVrG<)OidWgZ$r-@vq41ksSRU!-P(1NfzT>W&$9-M~qGcXN z=BPXU^pOo1))(?=wo(U2f3-(i`KxMD$$9E`TcVj4s{gEWU@)atatpk zjBBD}VbLPRk`xTwm_n~ZSUi7-AL;C!mZj}-Gh`@6?^nD=d`0*No(KWDV1_{+Sf*k0 z@2#xbi1qRh;d-b1YWV<>b88t4jn?=y6nTcycNtho$@5Ios_~>>Q&?_FtWW3jcA(uy z&h~^QqWO^&YziJ&Bg1?`bp`S_e~n-NvjW6|$kt4heko?rkRdOXV%i~%!X*bLhk9;5#9;(C*M{`aBgxk>j~y#5O6Z0GDk|WLr@a`_rWS6Qp#l$BD_Zh z9Ah-$vJ4wx2EI>c*gaGz+ZZJK*c2Hs5JAUC0tEWd=*(f?yyUNEF}-kr`HtY9H%`oe z%ci^&q=tv`?@U!JeozpG^j}+8hP`}vjWB3*lRouq+<7M2MBPj5&8tcSt@(6=frqTj z{5N6#6Y>b}`Z0ihJ6*0mGFOaoG)@3<2;p`NzM@S_M-*Do0l?K94I(oT^J%SFQ#q7jXSHO znSVxn6Y=bgsx*brKZB-m)VVHZYO+|(-_uv9A%hbKlVS%u&|rWaq$X53P1&Jp(AXfv z_qx$w#Gus@@)m)D%6zi|7ZH7%5zfQ|feN+!pRH~le$9>T#%Q=35*--w(i_7!Q3SW7 z6B4z%mdh+ag`1qIjW*62e4UK(f;EfmT<4qgsuo$GNH?c#nq7Vhi60Xi>^~bL9o1iq zr)NE|=MDRowz@t=mTe;fRgw&s8gtM)9YSy&yYF*n(#B@b7qPU2%(PKkn4@wXs9lB} z#X?-QsHvVM{(CdIFc^-GpTb{zBTct`2-sNTtI>q-KmUo2(vyul)skh9Xrtk5+M%3m ziNK;_gH$<8A)~MX{oWt_;U93Smh%fi=V=P)Q^q6DZYm7LkYiHjSXZhCK@nUD24 z%J?Q()~|h7wb|-39Q|wDhB@@9nuoyoYO`veezgCtq2$+`zKx|&Zty|j>;|W6dnN^+ zHCyEXRgL@c-D0@{;T>Bz`aoYy^45np&fhmPNU(C`u}G?Kys_!&Zdj2184Q0>ZbI?A z))2vZy%P-$ecRuV6?XX-!<>)7aTR{UG-CPMB60}Pzxlg^5&uO`6J`cR^K(-t#UBV+ z1rGQ6bP~uHeyl^+S#j88Q|p|s!`_lliT9rO?WWx`Bm4_?7#ZP;%p-Nl`K*21z8>k< zHpbAnD!<`6y zAJ=@S|A$Tb5yPBOS=PhlquE#rd0YwyxvrSl;_$)$%#A-8qq{I`77Ns0NimzX&(j%$ z^nZ}ZFV;V>^LdI!D8D#s{!;ef|9ru_gJt&m5wiZs_TQR)nkEJ03wZh+v2thnJ<0!- z73z-D-utPK{2vw6|J}+@KbfxnA7DMbfD`-QXE%eTnLht>OPwjS338`t_O{d;;eUJ4 zbcMxr2Q*I`vhFYJM3xZ=%XNg$_6S^bw--onIR-Rh-F*IP?fquh+~OWWM+y1&3bO4n z8PCp1Nw!1vkJkD}eR8s|!4*iak2rlEH~S#>1$?3K>vUoM#fzgecc0EyNbi<3or&Qw z$o_rycW;`Yi_*uRF@AGR8@p;$@Vqte*^UarjJR??RSXK1#Dqz|Dw?)1i`*s=XqOtx zTO^_o3fPpOPg(m2z2z3X-}nNNfHNQ2_W3*8BPl&-ML6E?Ix4s!@c9qhk`XF$! zWc$+1^Jo6p(1L&u>Z^L}j|~|sKAyDX`Vno=Q@+u#{igwIzCS-8q}d2ZNA2C^uKye> zLkn}4d1O?xAlkPj@tbRAdr0uC-RC~ekW;VHKj;;ugwcwM?n^iMip?Nbi`2omt3xxR zamDo_tFp#To#CxQ!=WD+*j8>NdS-IFBT}fgZI|3!Xru+io}veIx2$r(c=tL*iF_?%qrmvKN{Vyw5!w zzxs4?NbDP4R469G(t1Cd<+PKcryR;&sQTO`gVg)hP~dF;s_GVd1SaC#<9ax9rH>7h z3STis+Wh*^m*27*veSzvJ(b@peL<`S)?}tPCDqy+BB!(v7>vXhfix@O`fb8;t(tN6 z-K6U`l4w&^Wo18D(#s7LXeRS8-zbN=#Q0?a1}Dit-BJUT4aE^wSxD`*91iM;O{dL-!rZ6m3-=SnfI>U^uGPA&KoR;B8fJW& zwdj}-+A}z+1UW3E-cZT7JQaL)d1~+qrLO_1t^KCd(PT^BbuhNspS4T4z2QPfQg8jQ{eir{)6)FpWrM=2qoX$&EBEldXuL>)Ii?p7hf;G z3B7lfQvR|lHAulzo$G?Wi7-XGJVB893H_{Z$ z5$|#!LC@q~-Xh6?{U2+>N{d4t21ipJG>~KHs==%+pIn+s{#A~iCLAeFv&&|BTy9>n zIuV;X$YCG>s}hip4~+E6NX9}L(&po?5f20G1CUggL5Ljn)QwZ&L8}SBIDag&*=3xi zS%!+T5c&T1Z2Dw&?BXdo%Y^X3Ak0B4ow=pDbp?ySdnJlTyb){b3f_EvjPr=7S9C?O zxYRJa@#G<@YT{~?C^y1k|0p?Il|TVJ3FEANCuy5OS;?Wt7k)%_5v&iyTF zG9v-$^)2De+%$ARXq&rBH%@gX>146%g|t|l&co%|b-$av#FQ#ilO* z4~f;kqH}VD_Ll% z$cukTi(x|3_r8Lz$B+ti?L%*!n0(J@*yBWIkAyK_ZBLxOZ90v$F@iJe0hN!=kpYE~Z+Aeove=Rw-LzjB4OCf|) zd6N97b=24JwW@%EZd7%CDl*wiF>8PeUvZTf)#^@p2KCYjiM_=|<*TI>o@Rz6^~t;ZMNEb?j`V%l3bJ z5RQr3gY?2k?4G6b5f7{c4Ju)ZMD`M5O|JR$@%}uv9sCSA3CS_7QrD#lx*hzMALUFo zQwb&4%!{Vj?DFDl#Tqtdy;aZb!$-uV6Q+{Is)hw>lvM-e=tXG~m{MP}(Nvs!t$b(} zoHaMuPhiPZ zrayBUO9p2$5t{xz;@@!wp!in;|0vaDX9u3#{ybudOGcPBcK<`X&33_$AR#3Vgz)Kr zd9zV%NgdNqgGIXWGe3X)a8*&>%`2Iamktya8<0MEDj;);Sy`3P{l^7>tCy~zqaGgdxKaCDNi3~=($#G|pWKE>P099lkp!2yL&4NC@x4PZ0 z;{Ae|K3rsU7wUyR{4d)Tg1r{eNvE)+6GG`O^3UUVph)4rKNgQf6wiD4OHO zjgQamSTw+n;JHswJutA5)xR=*SA6g14|N>68dGL}2n}!22yT0g`G-mRSKhOXDtn&V zWPq__LHe?;$%~`>NdR>;rDRGrFxe|9f1tjC{N2G1snKPYt$>W8LJKq0;-!cY(+*7| zHZiHl@G31gy?E7MNguQhb4q@P7B7D8F~ekGMQOO3F`2lYgEnVqunzzlvjys-GpN3; zzO&D~4qns`24V&vghh!liuV(CaFO&1l60~W^@pRbSfH-}lG=id{A06kPci$Y(6ez>b-4=pKZLE z(T9eKu_ut$Y^QA*P02Z87epS*GDMyo`K-|yx9G_frF)${yV@GFma`EaHxFsq;;1c| zpX>~7J5#r_hvLHUcL_5=mUv4(VO2WB3ND{9YM1GM888`)Mw9U1XLnImT2TMoWlMe# zeb(;#euTlE2Tg72x*qMmYUe#l`KCq|uI{X3yoO^EUmhAJY}K{WYtz&QRs9e1>q{H7 zch8a^1;W>OLkF&EQoQ8NPyuzkjglcyS2xw2+S>N#upZ+meaDmHN&8NfmDdtx5?vk> zwO5J8DMzfsghf6yR+(>;gUL#3u+X;nTV1JtQIN7samG&uqMK{1UBvuxIr?(1yBnD1 zEnV$v!R<$ehu~hwR`xUT&8R~XliBr*hzoZmj)b~MR~?@z9&Xu#8&*Ej_uU5D-1|&+ zLwzi;HV@NGUhCwez9R&w>T26pq+a<+xTJjk_Kae-);vioF0X1!vBj_2+0aM~pmlbb zl~nS@#SMSAoZpnX4B*l-5=_K|cOiOET=#X!y}ZFcJZ1j9ZdfuF?#3uKM>-N7soGg# zBNrMuHCvBUXDdFkNMx(GtgD`4r4kuHMlh;O$Wdyl%8_X3rai&#s$i(Pq+|Be_QL&4 z4;DtdOuMUY<%sei{Pg2~YebJ7MMA%Un=4i1uNFlK3?7fJPowJGjXD>?)3TJP}| ze5#}QxV$41wmASu?s^%*q;++op+T|K(^9NgoH5#NX4In&3D&k6#|&7l#lAS&D4~lt=oiP! z0=f5N0#M0E?>@=DWm?cPG_>&+x8!2`_t8y94R7_2_K)un0387hAIIWj%0BFM2QA6Q za=Cz>ORJ>MHKW_{&E0*={VrkKSZhIJ_~89Do9`bKwB>}lsVNu=L(N%@zlf+9&1v$X6q8p}$ zQ)5WHd#SWP*Yc!sE}1AfCj2fOex6-#ThgVw%6+sUfA(AJz>Wdz+viacQx!SLe#I-N z$d@+ndyNIX$yWs1j)%&PE1dnolScI_(hYCCGdbu{oq^L@Mt@AVtY zIcvjLChV8MfjVQ?40ZV;8ym6_1mz zFi0tc5@}E>1=0&Uei=njV*EqG=)?T0hZXMfJ!Fd0OL*HXg)Jd$yK{=1Yz*BTRMhrV1|4rl67Vu zAcCQrCS9SE@`AGPLN9OX?960MQEE~CWOG^~B1TAGtc$=3nvkfZsld$cPMPqG&iL?j zeWs$WC$+9;(9dl)UxSjdp{^C*AZ_UBG>Ql5u?F)yfn{hdArCZF&E=3}tvzblE9Kbx z-tEA%C@m}qQ{Zn#y^`|g#!>V`;uk)Tn^``e(Iro)UMt%wH7*IyRZ%Fc;IoYNb5eru z<|~M~-Xw5XhB?_{oG5B$!v49;IHF4V$|crjlvCOBR;;YBIQ+j4a6ofm!(@!HtmA~H zroy6Kl_RQFIX5CP8Evz7HcvxUu&gaS_1cCBg*rijo>1mZxhuA5dun9V2r9sO{QV0j z15d7P{p#QSVHO-`m88vI76e@1w}z6v1$3Ur)^C(p-|Outrsrd`K4j;iyA$c4Z0+hc zOB5x*maceG-WP zepzMygO-br{1jx?-2M-7R&(O2F_xGp5+D(vl@42bZ~KZNs|DHuIvT(SEHpF&yG%uA zU6;>p2ptmV&Q{!JGs)qwK!Y(VUaoHr;YFkn(i`pF7b_q8Xv3IypwpLWw|c6q{jqz) z(?h;BHGhr19tb5rtmxS$A4karUYnW)7w&IVSLUgxpcvu{$IqvqBXy~fCn>YyNaI!V zSLVg6Qm0ZKL}OfvBYEToJX2a*6%!yf9sS6gH0D$GN`O`hQ`7grf;q~Uf7qfv_{h8K zS55pxMNZfj2M2q3@%Oq~|GoU`Df(CiQ^yJYz-+AUf%%gpijy6++$ysHsc3q00+*M3S9M7*Cqg z2R2KU6bx+Yv|n9PM={m~&RD!DazVSNqzwX!D%he28?hcg`bdS+-qJ96PSHdi9BlXW z@wzWamiL-eZVTFbOH#WYVk687y|q$t5;3?HfT^BWt)cli!Tqf;VlThy9$Hj1>JhcO zX-3SR%APv=EA!;8*ou8|GK}U#=0H5IU2&=H#ki%8X3yYR>iBT1+6VZ4OpAjptBU-# zy00k-Nks>;Z$!ZK-paQY6&ckQYZT~4PtC^^sNi=^ve+t>%1O}TvpFqD!b$ud_{x=* zI!Oq2Ist43rLaEG8-!sTSRhw$ej;cew zP~}Jf{X#}Xmo8Htre*7Ct3J+Q(@_+P`@W=p3Uo!&$b4rG{++<5fEYWpN(VTo7`N|z zr*9L7YJEJ*^7cB4eOs{n$4@6gZLR4H%TBMta>ko{16(GPRUv;{mkyS!?xUP?2n}i$ zDB;Aob3v^ET?E!MACF74De$?Ymad3PkCUjNkmtsEv3>fcUYwOI)T}l?8PZjMHM}YP zw8~V!PVa{Irf=wQPFKCFbG5>tF6Hk4H!`k0hxx1yZ%Sw!*7sw5x{uAKY;BKQSVTT` z(b>jW05jctYq^)9L!pA^Q=2Q-*+WX$p-^<#DtOI)v~#hJZ4@MRedLeef3dqhK)$flYl3xt{89{h5v@}eHSbc-zfEcnC(jL)8w|N)NxSh zC-E_NUk*T$E8Y!ilqW4eqx@C>ak8;8ibGq=VnIXER{JL6&WI<+3c0-X0gjOqYkI`g zrorwl11A?w!p$lz=|+-%O4w|_CR72Zx~Dsd}#XHr5D*033JXQ5o8n5d(KXEV?aw@yX{Jgy>{7vD=8taZ;s z>1a6u86>qw|-tNf&!K|J<0N7dfF8Q@1sOFBU#mYQ|MA@f#i z$E|Kd>E%}>L<8Z`+D9JP^^2}RPK|79`ny0hx6CF#jQ1r{j4@#)N#?t`>*f%()s9zo zR+iThMyB@;8Q^M3&lZ{-6XhQ=A9uHp*E%&ZIS@Nj@VI+Bk$UgE&ksnm&jNj-TDbfF zOd#E_XM5eA&j&V#<-bX`4I)e^UIY?25=ZIguex}Zd5do4c-6+gGfy4PycJ`=M}A;$ z3Jxg`Q;Q?zw$6m%&#lbwS1~zsizltJe`g?Sao1=VWa0Ng?=wsjcvOxu?(fn=&sz(^ zlD!Hvj%Zdy42_)qJyYqVyCVXtK@9|KNbcozwWcM11lsNC%NqdC5?}9Y=7-Xw-6+hi zozdJBC#&6()ygf*fL2svUy_^Cnn!p1FyGnjCkVZtuD9=4t;0AMPbT_1MyIW{2_GSb zn%hFMi2bV7eY?f|s)Hynlt4XWW%!>a#`=!`tNgE%->w?OW@|B2(T_VRISKP9SYUK^ zDKcc%@n4O%JgI!=lQ&*Z<@&l$M1rPRGaxuNva(`sn;=-|&?#x~8(UoMjB zCTjAZ7cY6Kr%Wx36^=FOc<&Mmnx0KF%s(!8186aiZ`tTYZ9XZW1nSpv%U=`}g^wZ|SzE+T3z>lWE=jQMIT;Ta6 zn=jo3r4S*bM^lAirf>9{rc^bQ#83bHVa>MIPw-JqWkpHavKGa>@NL`CxDzgsKUoM2 z4+D7GWG{0ps!vG?f3>(!8R#unRL4LSyGZibX8%B%#iv{~9WW zw;~pDkyKOpcToa#pt1r|OItNC25#EX+GRO)0c}lpIIU;(ZQ0gi=zyC6tm%8}))J=g zvw4~W$=nSk7*@wRh7O{>B*NeRd@SJIDTYdV-Afpb$zeg`IwOWU+qw^Dc^#la6E}dK zjOgq#O9sTg3TUC-Em|aIa>Q4eQMs~jDG~6*M>6J0G%bl2uAua1)ux=o3nz2pOt>z= z|Ck>EM!FwX*Oy#)x)=1H_YTw(9@>cL5On9{MP2Z#&KF*{vLrV#y| z@`XeIh&(?cH2h%=su5Y!t*7Ik#v=MT9d)aPLCxy#f$|^A43>II!HqGJkorE^C;y8KkyeyG z#SAdj4&s)BC69p5UHSTv1w4e7gS;`TGz>&A3K|V_xz~PMvkd7Up!3dM3zh{bBVr`Iz2Obxjc9y04 z!wbGX(D(V3Es27$?&2ayH^fkSjZm$YThcqS_jbg_XH#ah!3rU=xG1R^5hI{l$?N5( zsOs%^eRB`pN`O)Hap3?T0aspNgOS9v)-F|jcD%CTGw^ICeimt!5CeD4L;;q9NyDFP zm@MhloTgRZQJwOFmcst6mdB_qd)4v*fzinhck2ZDsg%FmOzB$;*;V{k4u5v-FeiY9 z9tKqH{UF|997{WR!#7{$@^+R4J2Tsej6rixG?Y+8uR)A6Xr;?uR$|uEfGVNh z9O(SCu73IR|k!S7(iG-|Y&rOY{?t zn2*R%PhHb?5-rawQX~$Rt4&rmtp2o4Lww4Dgi!DPe%}pryT5D387{}b=Zf4Y&1fi5 z2Llg;bd8HJvl$!9d~cO!YCPe=h-kv-PyVDsV?aokZLWK1LQ%f*9$(Q9JNk7YS2VET z0kC03Q=M7*e#u0X)F&b%VPhl*#KY0 zB*Xki)Z}|Kt#WDoX4jA*qHvY!+G8qJr-- zS@`nv0Aj|wmj-pI4hi#>0B_ZKs3B_TdPXj~w9NRcf1>n02|C7);zp=8@thi(G&+q} z7G|E6X^u3MYfA$H5P zR-=T~`hzB_$NFCXSc|Jj4Y^U_;Pf{!1=84}5w z#Q+)Z^>|OTxZD-9eRmoKU(g*~WMV9^=iXBrw4ipTB~3t%=>L>s!13%dnz7K+76p8Y zAOhIFyS5U00S{44!Tx)6A&^+x#r}43kZ&^}giKHiBHh@SH zbwrTot@6Isb`$jMNrzEgb?DrX`#mJya|w9-8U_wCFJihwS3RQZk-n+yr07@iC}O5M2bTxp8W z++lD}*jm|u;r1O%Udwd#B?b!Z(>I{2ES;?u%NH_AuMg}9f%2j0EzHJIiXHoU?WMDU zB%1H-fC3QVQe2*kt2zEGWiKtvE3HP2FqOnH|iq9GX`wFZvW;L_2Al;#! zIUA7MUB_r1wl+c4T8Z9T`C%C@UfLYkBVbGe9wwUkBFNJ1Ty)L7+7-d)B|KYhJkcZO zqW|)8*%IzsE_|RZ`}hu4@zIJ)@xcQPnAJctrLnbXd=&;ayX;5~-6pi}v|=0EZ+cHG zBKA*NsVMpIIxvfmp-$}1v9(@x+`#B z9%fb&Yzf11NL5(FF36qID1W54lTXt+lT!Oodt+Wb6#6_)Z5`^fKSH(s1~>%wH@NSZm-p9Kj}O}g zN!F&?8jS~hNGU=#2>z`|eMW3b3tGZJ3iLpWKo6%nBle(3WL=E=gf`LG8U+gSP}M5u z{pX8aK56PHqX+cS9d9n4*MH`Y$iZ@W-Q)8k^@)m0!B1#a9+Z*>!citLSN+{&LMnGt50u)<&Bj{szJr?~rKj!EZUXNX_{F(QtE1s#IX!*# z*@Wm5@i|)KRBy}SY4hDL|FlYo=J;qmGhS2VA>n-=M^5rE;T&;w2XL;d*nY*|n*>Xw zvFvR4%Zs_T(ljwf>8U-dvgR8d83tWi{>wjXEzQ?+7yvD-w1*n*^PPX#mcsn$>5F0> z=NZMe=XoKnbUme*WnMBX3NkTz-gh`eb_>$e#;u$F%ZSkrF0??KWiKn*YUE>ze=ZJM z5}IU&cE$vmvy-bjXOnFhoTikt$4eQ4kLvHj$Gld!9wXeS`V+Xa_ym0wR`bmLi_&B) zmp(}9YEv&yeDKlzs>&Ma#x6CsY#8(|Gq0%7=B6~NjLyPr?WeJmvwM}_qU^YrxqBvwvFQc`kCb(oXwRZ?IT0hcRrf{TsSH+Nf9 z5b#MZrOg@{-KzMT4TYePa&KG1+;1-Hy2E0k_T40<`3|2&lQ8jaLA_U_pQ1osd#zUn zoP=GC(3{{FJIj1e0&t)1)nekiyNjy_e+bn8pWqFF9d!6YeEX>>%1CPs^W{%pxw<>H zStnF{L@itI-V*m`5%6BWn!i$Yn|LJk-9qzP?cmdN?pS=-LlZb9yYjg>&GC-DKhZo| zKD+Y6uz!->0cbr5fbg`GH%@JLV+Ge+b!h8M&Wt}u&fan@z8?TmAZO<4pI;Z(<+kE& zq{8t1nmxvXo|&}1EopoCtOD1^T-Yr$)xOD35Tq)>r_fTdd1Ivm8Tf9$U`_({X_Hf|oWWvfs$7#r2DIoJWC`vzAPs(FES|iWm>@p7)7C zv8Eh&lXCQ_`DVDp>8t@eyj1Jbv8S(hYknFa75Ji)oQu80?K1{4eCWM(QPH0=hw;Yj z#2HuDx%5QSvlOh#*W~u_$4^!zBFGrDo8UoBl0oJC%9V;s4j4P+*EMQ>EKuzK$srjd zzWN~aLL5O}MM<9CEIg>ktatU2<@Yw_28;i#tpdvTX(sl=jfFCB`JaP_prLKNdbN4w zd%WH@79LIbxJRL$xzat=w$ukEb!$HO)!+Sc!Hxy!>NM>`>odt-$B(>_v*7YSC(aK* zFA-hoP$GGJe6@TzQM}E6t^`xowBl^dSaR<4?m>fi z3m-1;_>YDjUGwReq<0#A!@U;ew7#PsTOatDe)vrk`Yf$rO<{k8#E1o9D+7> zb4eI*#pMaIFB$8gcEryzmB-hlN`t(`n8uv^*bY@yb@f^hb-JBXp&Mx1PNSZOKUbs) zmynmmKTIR|VZ$gqooKgJoL)s9*Le~28}tOv_(9J~o8Aoh zYm7n1Hpfxdr97&R%AeT8+?wA5Tv@lwsKjbLu6NT+PxG>+8yzdN-8|SZM9hD6A`J6{ zkOG%qv|)dq%cotR6Sifwp#@Mg32k8_Rdd2)j-SP{O&$&i`T|jSV9+^jBL|T-FozBl zhMJPnY)4a$FOt2|y;C>w%>br4Mv-Wqd)Jf{YpVa78B+O7n3!q5{*CjYkY&Q( z)BP@=``r7NF-57MQtpBa5cXR_2|3(WnI9}5=cZo(HQQSjSpqZ)Zr zKNXo89E~4qQS3E>r|oD+q-EBth~WE&-atla{@+dYt;@s3`qJ>jwuL09?>6bHi6;Ai z;uXioboaF+ZJ~J^;qa*!VMX~lRz)nL`E)niC<9?mb>C7{i&6(Iv~)*0h+p|H(|Oco zD^e>1Q57&)q!(*L^n{5yc;PLDd*{+P#)(?@VS#MI-00>tec|R*Li8-yUS%dr<*Di) zRi;&g%(S#-&~#@*1F?YUh<9{3?$)lcVgf{eV{ZZfI71L~Y7s~g5S+DR7>q{{8>l<$ z&5gCcddta1t)?(&wWvXaqfk45=0OR0p$mIeVh9kS0hpGUNBMy>92ipjeAG z^$v_&mor0+6YtuqFsMwa`299{m|i^?p?W%C#FAx_-RuDxSreHLP8iUIv78j@D~SU= z7^Gq`HHDL;0rmp-@_wD1es6JT@0PA8EQh}OWj@ujatE{LyN{>n;9dVrDIbYhi+WZ1 zJ((Z+N`XyAmZct)ICQ7uoPPZ&g~vO0@c=j70-hzKivP(z?j zli`|Gu%;2UurR~IDF%I4uZ@zDswf29C1*jxUtZipLPB8kx3AU_sE?}rd9;6^&MLa| z-;acG`FQh7Y*j*xay!6=eMh&bM^Bd+Uc5DuEsWVon2^l+G6US*3+4b7z&vF9Pro4H-K)gKq;>PU# z(f^Oh?D(l|0wfPeNAjLz0~m)J`82xHPb%tcDiw^gepNsN20fS5cKEZ1Po9X7h?H8L z7v?`_@L)sL(|EEnC$@n7R|_2P>NJF=g`nzRz)0xOJ!;>oF>uzZn2TH=*37P26c6n& z%PFdi2SN-Zr*jOUI%B#)^NQp+A$f|FapfR?C|UZ;0kPC?T%C}%XpNt94}(T*D)SmL zYR0>^#;V4DMwy%Hfh?>l)OUW z3`Q_dAqI;+cp+#$F+*rlmGKjbSz6G%k%(HtJ=rfKE|4kvR+Sw;c#BE&y$+$Af;5_1 zx?nQm>IQNyIpzdyvqks@T$-mb@w=4209i|;Rd}?=t516FL+%=fI38$G6%THAu#lcw z>%ZHKrA}}accHmJ1$X$!eZ7@&70ZZDH8p?JPfl_(!KQPS8O~kNlBOH&23PU8Hp3GQ z2yO7R;ZLgfx4Guv62%kjqJBV602R=Ews05T8^bGC7`T{E){MY?M!l2v!ZvIe4RXgx zD`e+x!Ts=#$q*>ah(WallBKTk9kFBMQfzkH;AxWW&jVP$35X;t;-FkK}#2;UY~MC9+#2hz7Utu z^)lb%9w51&!ENYfHGNL3t*uVfErT@$3iin&fX`a>udu=Yy zdJ^M5{XZvY#teH#HQ#{A2LVj;l#EzpT2S4=7r13?ZU)HHhQf-6qn1p2l#IHJ*g%?A zNtN0fDP_<=k~3fkQSZe_35Xkkge?lrQ}{2`G)f2 z-?Mj*p#VV|N*~UC)vAG1cCrb7#9UW|E>3MRcm1_yF7ZJQV+8GFMyhy4Yp@Sa#dXrvxZJ_~4*d1je zO~9qcf3NbZ)9jMtzp+xPy_iR$_)46)udn)?w#X6)sG86`!Z*hw3X)5r3~_9v!&eKe(kJfq2K{?>r>_EMhzG#vK~D~dBI5cuI~a}f8w zc&P!BkC6MrERH=_Xnh^4iwjUMux{4Ny=M`e^}f_aUewtBFn%hMWy>ShR@d!Z%TnUD z(n|9qAIrNnt=2(gm|Uhy}eM9N!RS?KAC zN+*!>0<{t2i@Aq8t*hN_zuMw?Ofim2L{SmQt?WbY1Y1)(Mjc}-UnfL&Rv@yvh+rzW~nAVz?m0DHeQA` zr(yf$TRuhk!uQe`K3a5C@sxO7K3=i1>~wLFui_*AJ)9HR{4a}OS)pqHw9JM0U*f>(D}W_PlIip zhK=o8p9>fDWU5JU4nSm?4ydib0{JkdEYTH44~orrRcqAkB{Cqp-~^a#t1z1;J1~qF zH=}aeZQkbs69~zJqPz2SYEStMqB)8Lzgjxjzx=VoO(jB{&(s^9xdlFpGd<03R4AB~ zdxbD}mSlWz>r8u>b!6r=o)wKhgoI|z`6{>ZdwOqz%Au*e>`he-MVcHz>Hgl9T3usd ziN$+naV4{t>GCwFan^eRKxOJTFH1jckw*ZNs1hfg>n?xT+~;s3P}h*2y7iZ55S#WT zx89NymUk*=o5yf{t6C&*#7Uq8qa2*^t=33LUvIQH^pHwiFJ3lN()U4T@RUc_ocAnYp{+W?3%xplNp09Ce+OOnxzn=XyQ89 zFipm0jDeHh1sHmyIhDmvQ?onsIL+^QgVJZIT0s3AZ!OGBbgc#E;ZqciCp7t`;RGu3 z8Po9DhoyPf{p#Xb6u{TJheu_paiX^AUFiouwc^(+1bf&C76=?WU3_=Dj|Y%ra%X?F zx&BrGcZlPg%|)UasFo!yKN7&a(#MRx;M$vS-Y+bH+*6d&U3|=59AejF4hh;Ezp?=@PKVJ}o#alWsjlhgA#!#JDkB+cADF zQ70E1-FQ`>5T?b&nS@{}lA`sMIfZ5iX`K`$CZZs*q#rXNN#3tp1;j_-->*{Os*0;s zG%%|EYIOXGs}Ivywjbc7bSb#hRVL_G1=9KaU7wpbMb#n&D(YJNM?RLCtYZ_GwB?QT zZ&A-gnf9sK^FtVcjFZ=-l#s z&=*ruE2(R!7Et|_aI%CeI0_(kMZ;kk)|6b|WyY^+9m3=blv84qNKy&VZ0ATR!7vb`ntnXXFsYffF8RC~iieY6>3*p4kM*cK zK^0>V0Rihb)dbkY@v6LuIxlQC#@*Jy37&?FDU}>1qL9@rM1XdVNwUUo5mCrVq}Raf zSKVQ2GpcIfghg)NWCLCGHLxq5R^OO@RMj>h{#SQWG?O>yTOcw90th@;dkHo_OTM5c z)x8}+!ULvX6`~X^o5j`*S~`xXP4Dh-x+^QUj=EDVMi1T)%jPc`bi>QRhf=>NfZTBPX@aMGOSIa!O_*n-&dC zn1{>|r`UK;9sK=h<@jU23b_bt{K?bgk9~Luz!HbuDKdlqmNeantNXmfg}2LK8{RYP zzzARy(CJK@0Cy~NdNE>M#B-cF{ge^nz-2df*4jkD1@0fMCQ>-(yCLCK7?Vas&3+1> zGxMScNP5;?O{I`sD# zY&c5cL2H;HOo%9C=B5|X!RC3z94Q=6=D#X8j@r{60EkTFZRiKs2;N(C(gxQrAf)ZE zda*wT5q|+v3F*@)LQgIx3dz^E=e6tKJKv`mE9TqLs$6c?Wj3cbpNb|`uU1{=XrLJm z8m%$b8oOy1T91J6DGng!pXS3Mwu+&6!?IcBTVDdtkKlN z?5tT*0v}yel9sz7HyE%>@$i?a!}K6NVbffa;rbM0x~E}OxBS;8AC4=w6Nm=?6q&kD zpKlh0fm3T%Tq@wif}X*`d_W|X zmO{kua7;EgHs+VDGtvCNnEI=*xVENi7>3~PuEAY`ySo##2_D?tHF$7$C%9X1w*ZZM zXdq~?#zI4|ulIBB{r>+!A9NpZ%{8l5)fl73oU{T?_s|3MS6<5Qg|utwo0=WHI!}Wr z#Zr@c<~D4=#>aKK4)r@=B-6g6x=d}S&-ssdXT;<#Y!7rD0x>33b--I%*H!MmMmdLW znFaPtok5(QDVijEt{wL9hq3-uQ8?YSsD#8w-cZUKDxRNz>g#_(UM^HG3ND^_Vv*a! zP|xO2a1Sc;J3lZ`r6{UO(xgn5k>oqz^?tBFt~2xXODlU)uV&7W2;;np#}Qi@UH%kYm=!l;O|KvvGL+&*2*JvBq?lx_H}m71j8 zMvW^(xIVm)dphqtTkwXBLP>4j$DV&*+xyY=?Ly@HQ;*Sx-<$XiaHvKt)h@H1;wari z-)$42mMW82kq$Q&Wik~Lmo`O#tO9=oPYoDyYFC~SQI~nO&G~Z{&ga77UJ?lmRR>xe zrs;Nit}pv_4)-|>MKo;9hWZ5R6^WgJyN_voE?pzaLYdS0dU{uL^Wp4-<`5!FgkZi9ko-y;{H{NDZRtgjhYB zHfP*boY5>Ro#ZoT_-bZ``>t9>44u#mx0--qC0l0>qqKJ-(>fEXhT#dl;&-tlupTw= zRfK9qZl)v*#k5BJcj@g>!p)RB0r>f!xeD3{1{aAC3+-_dU*1)of^^sjVo_ zfYgv2YLhbATx=63{3f1`p!<@&DKh=di1PDUfryAD8v;X&4Z1^<^@k<;Av&_d078{%9spq>K=Ah>D-ViFE5Fb{^PXCwQk8JDk z2e$P}em-{X46zZA0AH@dKJav+JWUe54aK&e_yiohWO_Oc;cqt$N&gFd5q_{01n%j0 z#-EuTJz-7gcRUI-;idA3Yh-Cu64t{BV%bhZ^`J@S{&$SgZO3VAqt8pi&xRB%214R# zfhOc}gNw8IV>gaoKJ*xaH*P!OV%+;$x?4cnCv!`OMcjh`f$foF=Lh=`=s`zKC!KZI z1^dE>=T>6k>WQ>FplaRN})nQ+? zBTicTUqz5M8jZCcV=i<2?uub_xjr@XO1SM^<&9s#3*O%G3ti&)G<)(xs>Qx#zJ;m9 zUbqoq`e|Ny)#WSZH z&ZX$$nY=AzX*=i;uG~tn1sVtdA4hbWq|x;B)RKD0tU>M2HCn7?DhbyI0=e~0nWxv1 zpvz2d!yQRi{LGAdmYXt3qaaHRSlvy()oXsOBY-BVmKAFnq;ORyuioeK^AbDOhjt(D zq1o_=lJ#8#cVErrk3;VK4Ad&IAhGPvxdK7IY`X6E8+RWE0-0gbL@FP@m_8hEhs@Ud zoFC-9xr>&Eapdei4iOYQOR5W}FF}~5JN@2i=4B+*LnSx}qCgt1QwC#aXgXM{ z)=`+din5r>(Wqp6Z^w>hi;VoFC#5bHLI8O&LqfWrA+HNRNDLDBw(dTmelPADx)+PM z$wn?mQjd^EnyNZyhjtCJbg~_2QL#ppjk#X_d2q|jDM}PNZR!o%(|K_`9TLKt=j@k! z?-;Gjezt>V`gHCT_@G_-cPFY@Nc2z{U&1Cg1u2Jz_F zcG{nR$NsM_-G^L$)7Vz^`2J&T&Yyf_Iv3`r$1PkS}Vb%x_I;(_wQ?`q0{#0 z%kALM-$C_4JSG6N*#%NsR-`HnonJJuvKH@fV`T~5%|y5W#|IvI?h^u?O&6Ej2L(B= zY>EywBhZ(!^`daQPCO_d;IT(Xt<2i?_?{p+Ft*Apwz&ONzw!w2^QaIJG6cq)=x2J% zLhDB-(9QF}?kcBq?rm$xpVbhkSXnfNs0&eNli#BF29qgTTli0tvitLU(U z)8$g)HANs0F?4O`O1tp2mFRY2;D%W|;6?w4V(#tfKIAy5|1ThFTn6`gZQ!Z#=Y7Vk zBXmyvdRS&Uc9i(Xrm=W)Civu5;s`}?uc=GS9ziy5-w4)i)y-@UT?D+N;84S(5Guu( zyFl7>oQ|LL*k0sQu6 z4a4CF%E@j~hCrVz%4pzwY)S@P@4XDCg;^c&&%h(r^KLEV`nBi@er@MQp20^Al9sgA z3x53MI%fAX;FjO{=`;xD({&sgckEHXn->;LR^NeWi~WzNuWK-_5Fe`DyRU1mkJB7x zkAJl;UqJlgud5;E$1hJ0KTQJ@vR+n~j>sW4Z_m?A?D;S0X(Z$>uCE_8o=WE4)(bx- zvHrw)@kqyQ(>jr*T5C`DVNdJ%w(+zZCUhD|Bdw=GVk9=XYw(3G0rX$p3740(OP?htV8#EAF^}Fr0XD*H4HA>WQ8zSc2~BCj%#gzGB7M#!$~KgpMY`Gs0zViF1gr zV(msr?5egSUqF?okr1cpZI1_K&x4`ORd-S^eCVobbjZ_#0F3WpUeORudKaSKhZ&v*EXSyCkv3Y&#||)0TEB9=?)IQx;30 zI{6oKmc|^*LQySOvv&l52dzfm$|z};J-~^*sP1mYo%jX5Ti@T^~Co*q_@1G1;qMl^*5W4 ztCc6sXms^8#1AY9@y#nTbk%#tMNbIgzO#&vuUY(V(`pd+e?Gr;ZElY=`cD-oCrn@O zZ64o19gz3G^4*{9d{O@EaN)bxS1Gn+z5`?y=Ghj~C9Q}Y zY8IBkn-zS+iDh#uMR%cV8HH9oVnZQGk2ZdV-{TweQSH#-nDu=pQ9ASmL6!)YPRGiC zb=Y3KdWR!)*)xZhGSrON=r?x@lMPz?MI~`Z4|6Jmv{eeai!C}_3J7)q77cWP>+3|V z&_h?qGOwQmURgq(+uz3ai(Uj_`tN)?UAOf4U*^~P!AM(AQxhr;K`cd}Cv|a=Cvkpp zx9wk|IaVq~BEz{tr@{g8c;(f7?DydOZ(@0KkK`Y!&20_8grxxT-(vo)(s)XQn@#fi z=%rX2)m|J`<`+=#18rbzxpCnTHbHReWb^Sa8?nU0GXwc>>p)(&l7y#Y2?4NvMYi}|UZ5GgWUoNgAb}3E| zMdAzetAeyC6`7&sxzPQytVNo#XD*QOCb?A~0wG~>bvgJC&4$UUJIY4bygbuCA9}sb z93O`jtWCw$dmO`F4BxcJQ^RKXNVqkI6sHy8bt&Pf!4B2H?lUWpCm?gg`*- z$sgEvj;~kH5l0dyzi@Egl?9ov2^De*Ys0b+QlJ#{g>*L+-)JQJBV_SNdfn)}K#(PS4v^AlZtP zn^cD)($wt5yz;Dh^1qN0@{7l$HJd?c;}0E8ZQpu40Ule8*zc#%GvxUz-N|BQGZ~tp zO`mN6#>KZ($*DM8JDgR7BL8-V+_p6Yf?lwZe{>~{s`?XWOFkVHF?c~b@b^`1$n(O9_X7$}y9Eyg4Pxsuty&D3=z0jy%mlG9E-v>a7@n%n# z#$kP@+Vy9&Ed7rq2BM(#FvKz1qwP4Pj^{qo(f4HL;!_5Dz#L3Vx4u{7A)0 z*u7W5PVQHUOkg0P&mdS$ycidIOO-N-BKhesWl~#JQhRn#F`>jN0XybyA4?+uT6KYT z-|jmIXQ=;{$OlGuSTWJlYHBYP*RirSOlz|^(h+iW&iTnV3uOS%78J98k#j{c)g?5Qsg6K5(@O65 zZET~7hk{^$Cdu=H^Ye&`k>HJU80&d2VO?Er>dNB&|1%V-ZiS}25 zRco?lW+JqQFvP0xDJ*b*i^{D<(T$FyDjuXTyf@Ph8vWzhTZ`7wZ#{Iv)@H{`mVxsHf1y$g_|{`4kJ+ZBlh}W1&1&XkCpp>Y#}PyBAzLPwt`wq=c4_ z^&e7*i*@ZS$bVuWY#P zk^b}Brli%BDJM7=_Vl`3pu>=RBt4iT zA62nErR8SXjU#aUcYG1=X#d4WbWRudMH0L^QLlV^+5{qAT*HI2DMN+IjHSZgz}h>z zGR0aYox%S$l1G`91^O2=XSQ_PDsU5ws8M3H;yJPJ-T zI$S(xLbMnx2%{J1+5g7DnCiX=l&UsEAOSY<^!&ZEdhsq_$D;IG4{Px9wC;I%1KKnH zg?G46(jGba0h>$P{%UDAM!xnLl(AN^RVV?MTAo`+2#x6fgtAh3yGSKZVrdCS#8E?y z6r-EOv4FR1q{qgPm$Sy_K_u}eQo$VRsP~dYlVLyJ7pV#DhVoy*?TUk6I`>x+$P=6^ z&A=T{3oMTcRJ&_W;pPHZemu4Leaqhhyci(=$odDt*3Piwk;p)5$2|WLAQ4EvL_NjN1iIY&sDA2zoFOfOnMRql3<$Bj- zNsMC$YYG9G5;nRYXx4e1gFZ|_26|)FI>pzEf()Rt4)8hlk7^y2VQL08jvvbv$}R*` z=_Dk-y?r`B;?O4EiuiWB*vXQz!ClkAA zlCmZUf#~bVnzeewl%$`{%wU|fMXlE9iVMophy;nE4lAu~etQe6YN)X$HNMM=RU~?# z2ftu^JpbzJr10|=U{i+=ItzH$e7RyVMNwI=t{>SAx?nVhCdeH)T_ zyTP9flC;BpFpr1)6;g=s<)myn#tI7p9X_59nns= z{$OTz1yK9-w;(!{NzZ8ZPVna?NJleNt(2n_Wn7FipS}<=>~VWcRhE>cL&}8H2_x!1 zVOh=hMjc_zTC(M>TD7;WzXt8Q*)I6vB)7cVV^`J|zTo$9?k|!E0T+D`>cZP4#pxOk z2np(~rTO=1MpPtIi`mNJAr5Us3V1Su|XM6h*6t>HxjM~CqZ0puCH zwA1LDbhZeqkwbpR{?>t%w8bCQ%1r1<$jrB)hJr;Nj(xJX7q8ccKCsO+92U$cMjGfn zHE(5O$=LSw^YXJ@ltTsLSVzhHdQ!okHkT+EeAJ;V*zv8uBXWPR&=C4{q41Ke{eYHx zFo&`6*M`1C^wEzDUQ(ImGc}fW7MkdvHk9O;ZiHvUUEG^y@myS@z}z&w>+0x zY~b0nDdKk%+2F;15X;_Hs7Bq)uqpz@dikNma(XI?i(UJKAiwU5e4rylt3EVl@+=e$ zrs4a8^y`F!iJWC1#t)%3!if3Aa#`eUBFf|8gIoTSdAX92kVNvL}}ytL^%v>zz&sQJE8 z_2(7&Z?O7v{_(>R5&#*d{HrCGaJtq@bGaLAB|-Wdn&2W7rU^-|&-Kld7v z9*g>Q3f^1|{W=JNt)r}I2^_H_=81p^mEz`ObY^BXb+lnlHFyzf*p8uAO6}#332A5< zXoj~V0PKSIvP{QX2w45kPP?uf;{NSeSR^=HvAF_w=RwS|ITEKlNcyn(ewzT6KT^G? zO0TG;@U$?PU@>J100FjSq#h26P^`9vjq%CA2$kO1hNOp`l? zNv**R&u689NjIM)9nALsqRf+khVj9oM0QdNNUOw zMVe-IA*F!}7NzMwHzvYPVik?mJNeLp3)Yew(&hAfEg9PIy7tc>G_?hkvT z{2r%L!-Ug9Cq*Jx$~#V62k}LVL}M}NV+pM!qRU*t<~*UJW28*d4%X_&6j0{=KIH4G1Wjk4X%A`d^dc^by z`zNp^n`^&G_V3SL z-Qr=%u0KM)k+;Pezx6}llUP{amj>np+C@;$u~r2y?$l;$0wZ?K$CPUQIekFQz48jG z;=M`l(EnF96%TMeB2{5xmg+{G%m-KKNLw(a#kfzBhcsEqXXHdiY#aJ(_x<=7qu>B% z5?+#v6{jFy3kAx-zJnmNIwFZ}>?tBa7q@<7d%Xzd_rU#pw2&(U2Q7J5P)1Zaq1K_F z<~x=)(IY>pJ*Vt3+$&JC^-DL%fs%*WB#k^BNgUrnOwM_qT<3o|gX<@|3}3a3hPW66;ZhC?pss+WtYU}CLEFR z@yDB|LrHZR{s8fDXBlcYqyS4vZphQBaZyg?bwbQD{rBy zs0?F_C_~}VJ26Cf8x$0#`G3}s`NkxgEtp^5HS=rIwBPL@Xki&uq-Rz==e+M zmoin^K+|`dS%$Ig_h!(mzhb!ssYGSQqY(WY+s+N>sSVlb>*;QfJmY@K(*pljBI$1S zZ&y3}|75{tkgDlTg-=Ao)u+vem~``gRYVTenQIn;rw6E1AHV@33E6Olgrb@7mRo;N zYu^4Vz^uqJlMX9}|;dr-wK?va1F75<(NXCh5QQ>aP7EavUMk_-ai;k(DMP)O&L zGxhq!Cnnwp3y2URHMA#xj^c${Q6(Msnt)@d|Gh>!er*J{;#Xl&~CYC;X=FZx!hMc+ZKaS+E^4%1p= z+Zhvo^q0Z`{N6N-A8d_I8X7G*g81abUO_QV9fV2Q&sZzkQP#H?^5V7jBKNTqiH8?0T z=KCm;u>I*n{y}vvBy6?>ltI5$tmAv{qXS=(#t;vT=@?eznrU0-6 z99NzQ{8!&5AarJodM!l)H2nH5A1m>=CL~ygSckDqTG-*qDS5R^Q;wPbYbzUltq$z+ z!@~nv(%3b`x(AA2V%2owp`r*EP`JB3pQh8}3k?1LjkLu9D3g9LGAr89ast=Y`OI{q`0?M_&#=iAmIdftQCbIPvR? zgo6|XD*-ztS9_l}))cYb=K(3lg!R&1 z3qnT16;$VzIsnDgbU@##NQBkI!Czai1Na3zq+#OBXy== zC31rwy4Nh#2E?YY7MMpx3R+>U;ENd-K@uAXU!0JvZpw3tAID>%+*|iD6oZW%Kgv|^ zTtX9$mOVJ5ahK}-%SgG&)TU=!COBgq-kS!U&eL|#a_pAxuh6s@%5DxO3>t;j!gjBg z;a50%X_S}LnFyjO{w`bcK|!hiY9+nO1HP+x^3$;5c(_3Z`PZzZTelisqBb4jAdw%o zb64j`gzDwS36mW|)eO9_|6BftilHo@U=X?g9RB&jvm;5}8V+sD$%l>PT_Ko?G{241 zm9%%dHLBu)eh=o9vSfJPw$tXGU<#&4PSkEWqBNcAaumNpv@74;EgEf@HCWRtP>2`^ zol*W>s&1NJs~QxB7FaAisK!hxgcZR#pNM5LM9H`RXh~scm(RFgW^<~o7EFra+{wa< zGpNB`^=;HX5raZQOnT`i)ZYcs?y}p$@$Usoo~}Lt9CByn!Pt~;2(k`Cc9R4>!lK}pSQagqhtiYY+#>y{5cowU08C-vGkJpPSO)=5v_MQ*nn>@#NMx^ zJoP~s$9l}|@KWtP6v;BdNZ;iI1f4h!;ALfB$-cus6!lH4#wWR7Mjmqu>r}2*C4bI! z)=Ktahk$xmbvTC2bh)nJdohv-I+zOPDjZu=i+Sn$>+NDH=Z8Y4e=HVsH=1yjjBp{CftZ7~V^JyZYh*Ce zaXu6jw@JqqON;8Bs1AW5m!Ibf``+d2+;x&QiJ;tAY@?bg3ExYcZE4`(mcWasu<44dd@Nqh-=BIxKKOkK4n9_`q;9 z4_&Epsf-K5A(zS~%KFICE5Tz-t^S+xSZQWB;FpiS&EI=><7_0-=3ciY?tVq;f1On8 z@xHKdbUbI47!QzTBy~y*Oo;pW3w3QLFy>#>t-@s>LTGwJHjmS4umSpX*O8nuQ!$kIo0s~!Oa74d1Gak-+F#h?^M;I(n z2$_v(AhX~3t4wgb;shPpC?mt4Y{J=Y}Ra7C&w}FBnPSqq4oG#P{{3gQP z-ZXvpn=Ue2O!U3Lp|F?MHcSKSZ#L`wFR(YgUwycE(5CgRLlNA}#7YPMzB?1^Fe+v# zIb6VFAs1`aNSVj9s*i{mdD0Yf8`2ovD)m`4Xm`)|=>RU$cug1d~BglLhtQAgFs|m7N^oe*?+OOZF8iE#cqX;X1O0-kO9pHB_UUF0~gov zYN(;@l4Qn{o#z4*PKK7LVqrYuKxP=TPH`RfPx`4Cs3iS&9#&KbD@Fd27!%enc6oFF z^JvCRAIkkRaLTzp^fly*E9@T4Dp*asy$9|PqCfX``Lp6bru?D zdwctjWeb9bz6_ygT*Un(f}PKhz4`#wu!N-D-#Z*MZ>N0&9~yqVKKA23&`__iS?!ZB zdVuyhkw0{UXhCN2!zzgM=A@+p%6Rc{50pabH?+`2~fPVSY*G%~IAs ztP2ME5e{|ATF78Y^j)SnXeC=Asb7aaXfHSla?>dgm>cpuQ<;;KvpUEqcOcLUy(w7` zY{)!B*X>v<>YF5VJSBoqpqJQANl}*eFRpO=<(OTcofW1?jJP8IV(_7t#+~De-3)m* z{G*1|{{Ebl4a!C02myj`v=VNc(!%B)E4f%wn9LWWdNrYBjtbt!yF=NG)uG4UW7=P^ zhhKKWkT1N43JZD!--cW5I(vqG&IhFXrlYY)m68wKIW+FAZe9NR0{$CR*Iu*X*nj}M z2kqP#FjD>l(j^*hQD|;xc3s*BZ*=jB(IdTHJiu}D{mahn&2ouKc&W+=#(Eu5QPJUT z4)hfO2hD1oNqdrC>|34UgA@WgQ9hR>H15@qR~7EZKn|aQ#02PrCJ}YMJ>NYO45wA~iXhd4!cl0`4bzb`7`a z+4b>lxs9>Br7JRRM)CLkFB8$+q-jiTOzBLj^Q&@n=Fi-6}OK zAQ%?vaTwS<;jbb9ZVIu_RjyEXMuVTMr|snEO~h=x=-WSU)i_pLdm&u0Z|{V?HFkJ< zVMv;7&~X2JtMCsONwUeCcYtBYtFbPVs@2Rg?6Wcuo3@9ST)QDg zHSMlW;M4U=~@voQ4Wzbz>3jd0i!m!MrOrs!8Y~gLGqUWeFF`^g`4xxK(!?%SmI;#?| z?|SKM3oLNIa5DqZ?6hUb5_}{1Nh|P`d3`l1v=Ok77#OdCCAwEAI4CQap){N)9U|se zq6buhU`@av(y`}Z1l-b+>Sd1XZ&Uzzp;^;W&=p}bF!IH`!ZmU~eQE7_wX-iWYzP*{ zDc)};x5R&Of?>tu*hRVPpfuo`A_-11b?Lsp%z{PeUppb09-Oy6+y{u^#O$*w5JMZ2p(Wjx&s6Wg0d@Xg# zxpcNrg^ERo%nLC(iiCp7>a%A=Y{qnzZq|F{y}C*!VPs0#=thr58EwvR>4ho*SDH3J z>-k1c^sLXel#RW@<1FXlg90&dr_X_rP(=&h9EduUHHB&t?w*NMhTD{A8hF8Z#p>eg zRP_(Bk~1WE*+YqeP7WgI#KLvlMJjo=68_(irukRj1i@uY&#Cg%kokPHU_3RA3*sxB zS>492A7>zggSte4G9fO86R%QkaeK*;>Pb{=n1yesO6@o6;8W=on=Ygf@ikjwxEu(=TTyO@2y+S!r6n1K zxrH*C1|rsKY$%oiu#_Twxkxi?)9;DXH0$+jmrv0#F`FzNQCIWZ-y`d0eAx$_EtW5R z^d(Y72;zFix2)1X{oF1*&7jBCazy9txRV4i&hK1yAjps2*kG1t%-kb40AJCp-*mNU zXUo!>V5<^O-e9u3{;nXp-gGck$fT%aiktKZ_QZ8Q5?bXXtUmacyxz#Tu9~B%bQ;gp zhQgC4t<%P)lKmcb)KG+RdnpW4SY@Kd>Itbh00{ZdAXJ?{M z;``SCmw`N|yv|>JmVp+f5cebrDZHc|>=X`H!a<(G!1qL^ue_wQq{_OQ0LyrJ2RRGe z)bc3iwKOI447M`B^utu;rx-OJQT~}g$sEB5zJqH6{o*S@hmoKo(R#hS(rxRN?mz3j zcZ+VA^&bEihNJ#%+Ce+iHY0=G^zmIr$C}-$_>MTZh0a4`L`Ab+15aCYN~k4cmqoG% zBMK5&U-emp{Hv|r4u$lJ7`PZvw zR+Q(I0P2$~-ZvHI6jZF_(yQ?@s9~2~`|6aYXlJx_jY9d6UK!7Cwq z1N*KVK~F`Rldi6)mI0_x*RHo%M4eu%``!1+ou+<7 zy^@ESCgfcpwqk;w=$oC!LCdjdTHS(VCCU%m!LreaOgRb`>I~&bWj#4EsL(T+$}*j& z#xd~xC`dGe;6+?3U5;!q)nCa`(d3uhJ{AyaND)zEsp#8&Pp8CAjPqAh=^D7u z`m(G%=_Z(!wneTfRiZw;({YvGVvYfRnnxm`AUmrsGsRCal{mGD(`I7#NQIIRqEa1q zXg9|1N84wPftnV8!ov0W_o_ai&c9k2wnvOq1IU5syLEiEzCT(yZ9Kba~HGFuZatY%xG-}~_ z-P%KMBj!_1z;jQuNUN1@jB^k-+26hCFhk_T`JwpPOt8qP0>BQ{bhp>?&*ow86BcNM zvEhx!`R@-O*uou0e)guZfPo_eQz9H?qQ4_Pp)dKe*WA{$)Z@0K3Brc#bO~KP#Fmpi z<~DtkJuwRsKEmn6TK}vzXel#%9)-$rEwau3|MOpXdeVXuDne0X#6V)Ru@htszxyR(XcgmD#Z^S8S6C~ZwK^2IIc(cHKyTj z*(CKHbb&o5pMuW{Tr4aughff6aPvJ%_!hE)DFoxS@FOu=$!qXUG!g6Z+K92orfL}s zIs(PUnGlgk>AIs9I1a;6ut$rL(R}+flKfVVQs4Ko;gJauU+y#+NiO=xjgaA5hM!PbFscbZwT`GF< zl+V&grZcqdX(E(O(b2AJw{@x#$ou&=sOJsdoEy_vkNGe^%WkLzZ^?u5L+rx4=Z=or z_lvL8SJW?Ur)Fmv(oA(@M+he5PsXmYm3OF9xA|dVQoxYmv>Yb_TTbG%Vp)VSN}PO5 zRHFvZ*!zl8jZ{6i)lx4_khE0K)~{bGjR*0qRCDzLA$bxHg9=PG=5I~uBri>n*R{-( z3wJ)rOs2LE$Ow_`zSsDXa_K`|@9L$8Ytn~N%MA$|O5jwa9OSwD5i2l!<=VBzl(MO^ z3vw;SP6Q$DSn!p171_SetXDd_`u<7A*YTCj*Ut%TWE5pf%=4S@Rvn9JGPQ-(-&l|y z>Dw%-7aPk|5mig4JicBWl~=p2pcQ>7Db}k^wl>TjQNjR*ag!#MEjfQYA;(eZ;gPXm zk(wL|SfrD3N0GzF>s9Do(P7oqtIsyGwkC9-s8*_8u2G#ObOiP09I~z^XYU^4ZN8Uc zO=xX+4rKD301teDyn&v+zpR2~u28#X0|s!U(JVaL#!up4JC=FkoU7FJPD#pG5p5G! zx$HcplKwNpyj%R3k+W~hZVt0ueiY+`O>^V7>jZPAr*<`SY&Dz)G`i$&S7sOaa>ix@`zFCIz~?E>6}~4GHC?)v!+}7KDn<_*zqW>^uRD&s{AsX zQ#jgB{$m-R+{QGn1{#Bq^7x%B?Q1e%2J|jxOm>_*P4lfb=5f1?)iDJUEN;K4p(iAk zreoTxghFQ&qtQ>WRZ<1q!SQyV4FgLajVYg2$6aOXTYkk;Mi9H=8DqxzP5G zdH?Gt;~n4qgf)}k$3qRessLk@dicJM6E0m^i?X z(vU82N$;ONy*GFO%d$eML{GmErHeOG3|~(Gj8=m=CLgC0w2_I#)nx$XvT>IfD~HJ= zWUz`_s0f)Lm5Pe^oLg*lAaTntRjt{g>)~Xj>V5FO+?@SK@&ks)OMv}F6CD#K`b6x5GiC*~p4)1lsSQ78uW`&p&8+i*Zz88!5z zm(qWV3G)rLogW>ENm`YkvMS97Ip`H8&l66&)pLHNvqR>xk)MYEU$v~BB3J2RrR?6< zE@wM=2_7yl2|e}R8ny@0|DdC*mioGzPP@QBhs%;KL5_JiEv176L~7?K!vLZHhPk?0 zInaOqH8UZ8X2k#Abi3)Wrm0-UBd4}@;@#~Ds1J5MRv|I5n#(ZBwRdYv5R%TOMBx77<_&02iXp85bVp13p)m^SI74q}5= zZhLf0EOUYi{6<>t@Y~GMa$lS_x^qx_o_7GD)I9ETW(y!P<=Wi)VqGrx8%|6XL1V0V}IrI2+aSko2V+*sYJFn)~?y=ayO2P zfb@hwoP#08E_>r|e}$>uTCw0h(a;3cwU>?HVE}!H0asUp+dVJ_UZ;XKqUP+-A60RC zMri+a^r?hI-Ep;(R3K=bULBNS@Ma9=ByA0TEV}iKii%3nzD9&8NJr(bA(uqZ5B2mw zM9M`k^kUHN&}F6ha=$S1>8T`?4CvrjnCWAf;?Yag2(3te6Ua^sFyi4Cjlei3zldl2IJuP(OhMO$%oT12FfQGA;cn76Rez25iI~ zqWwpIjw=<(9dM~$qsQOziR+nTi9GwUT_)p+j7uzsul$0?Dak9a=IT>^TongxRc0(z z>C$b(wqqBaJ4$_eD2SmyXiA?L!T<>Sz)|8}BJ81bIeL04X*JtccR6e!#ny1t719eAi_V?I#KiD!f=IEepEu|0kTQf^zQ8xl-X_o6DyE@3jLc+c zqefTe>iNEUaN1Zr!h_!mI`;=#<+}g#0`!rs0CrX{4#?IDy7I?0GnOEQ@RAcU;>)-i~t!%9%cZrcecUm;x;N|iPDh%TNsciZt10nfTBa$YkTX@3%8 z_?16WJe`yLC0196H<3FED&R1JwOx2mygfDx_~klb+$!MRh?j}9d{M7Zby-9SNl7&)^Ps9PE^eh z(>3q^0c&WlX^dgQEyAPHvt;h+ON{e{-<)I2FZu&wTht<_LORw59N{{*GxAj1bhHct zu>W{EX4CiKj8l{MVVy$xKC9KAAc;GB6l;A5W|q111-(X}SFGhh%rM~+>hyk-AlZMX zn4irNRXvui{Ske{+H;y$zCexGF^Y_%j8R|bB)+zCiWk|plNJep<_>L;Cqm{ z=eih&K(brIPr3U-|1zMQhCj>OLpafOC)Ep6(Nj|1cknB_szzt+VC^)2hrm12TTsZ8 z;+j=h>@yqOB+L8vYOEwjZ!M6c>kcd3Ah6Gj(Aws#9k}ZlWCQ`7R+p0IvXtXQNOLuR z#YrC@9c6exXW+&%omsNs)!+_3FGabZnm7qS)RKYW8;@d&Kz@LMwTlDRlvc4+UEWu2 z%>q1t*RRAL^&ZYzp>mQb=UPR#)d9ebS$hb{fKafSUewsOs>;VLUUqK;p>P#nMt-Tq z2wLjWbLWfZ?&k4p+FD;z9&+@QO~-5zpKo}Km~KbQB%zJ34z5_+@S?EOLyv@^*ev{s zj;tU#-#(%xUSPJ$_Panfdo47WmMS^+(?-bb3(8Mx`DvL~ zXzaGg`@A0$Ku?wLzWfL%eEZpWP(#*GMk#D zmwlncV$*bF7Luh`qey?SXA{|0k;jI~*3b>zi+re8>lgSKG;tSHS}PQBWQnG?&2q6* z5&;Z4B^F#&ww5X)PU{;v+#3aMM>8*<{5Dn;bxS10$X!q7%{LjEJ3Afxu@`V}j=m0V zf>dRb!>-RY3dCo39yX6}#zLN0h5ih}^}b?-6q$yMf#K@Xqxil~5?*oW2I+P3yyAVP zFqk)et^KZ(nIGyyU`T8DYC&lxl$Rf0-wkLd&nyrXTqCCO7ra=ZVDaPM6?%CCii z*w!r7s)bVE;a%jLM1+_2ge?iL&)+^h%?;bVtx^+*)vhKnriB%?4418bEXaLax6Dt? zMT~iE0U;40+DF=wLCDAAKfJbjqNm;nM{_H5)7|<^XQQ1LQ%(OpcbY>4nACf&;QQO> z4ZBifTYv3~Fsx~HO-R$jsL68a>srm2`$#F_A3rq7`)zA=lb)%~1%mz;o1%%i{)@L= zZd1>dr6h8p8}wpt^@l|pv4UFX?}<`kbPqUA+VW-eFhjSY=i0v?Qn53KHn8W=b?~5tIFgHxN11mXfsKw#1p_iJHWlc@ z!@0k|&z>XeMbpt$w&YPFXT-i~6w0>sY%%!0#_fgzc z=|W>8-=j*B#lPmVOI_85#w30%<}g`Pl}5OvXM2rg^v|VNf6p4)cN|gRny>zb*fCo+ zZ^WRP;Ac_tt?I17h1rqkd5vI+tb9St%+6aRsOIxk;~ZKFLYgV9+>i;|tLB#Sdfdr{ zJ7z2kg9%KZhx14pF*d&ce@uN;Ht&zqdxNC3;?(R;2U_pYr(-7QU zg9U3`8ya_ahsPQB+;QK}9{Xp1?X{|A&6=~yta0?-3upbuo#5Gn(fFK}NK_lS8rshY zF8}Z=^0N=)c{&l2iN8N|@b{b3RHfoMZI$TNJ$^@E6_b!M`Yv)dj2ZE^kt}$ z{mESfs6-FPA6;2d=Pt@^NV_SGC@kv;`;0QCF4ZxlneMDjXIms2skipDXIJ%&wEegi z(X}J5G2XQ=>}v1C-2jd>q&I%b3Qg18(vsvXs}grFCClzEr2atEj)c+ZLTK%cjn8uz z)e@Yw3&!Y-Ho8AB`fdTZqvuh`@sfUhyXLNr^3krsWYg>=-{ng(p(~y^#g&lg!nhY+ zUueq}+W9oU2))`CZ#uXz&W~m=uPe%99LpU1Ha$mEMoS^#6Q%lX!2BNvcBVubjxb+` ztDu)f>e>Bm_mJ}IGn2I);~iO#eALg~rLxEQiLroyY14Dkb<1EhW1@Qlego-0C2KC9 z;#v=CnX6f9n>xg&6$BOn{#q>5E#FM*9PH{JJK-=8?Yj*8)a_vE&HCVIG4MeePtg9G zuoHw#-*=c!1;;NWF(|=VNk%mdOf1ErR%~^uMYR~pK>{iBrC0!(C0qYW<123&AW~Fq zgkZn}vsr3z<@pQsiL=Yw={32;znnUwcO0KZGfoF*CG_EB2}#>?|8ORR2$%G4^ksHj z8@(|ZzPz_&-2=~cgU+yi3uzkY`nj}?rE>;Z#p*%qEL`hUn=&96qxz_3Wqnm zzF{*ynI0vK8*0Hsf^reYwDZIir5}?T#`xu<op& zN;_4_nf=mo2EHXTgdm)i0 zjHd55lf>n=w))=Vg)A8->{{fKM&xqjEqg3X(qLgr;3jOGqsTkD8=?ls?O;5jTO z7r+{5xzrPZQU+=kYj-8Ia$zpmFd z8+-L~q<1;mrEY-dm;P({;v8Hj`vr!lWEO%%u`2kNHxLrN*j6(tuy%{@q9niL_cBNV zJ^$$`a-=iycrj3N|DK;7^a{#g3r&D|tVZz0$XlG%aN$9HJ-;s-b-F}CRZ&xdRlN+MUD|gcA2o{ zr$8#gP8&W_PVzWyVzzf|VwneTmC>pbvKvwAayW}G@(eHXFLlL5$mrsSV_l=Zr5h*9 zd~e=-gg{~9&a{)q_e&~`P)u#&IBQcgN5|BPDX%+K)mr;zHcI+_UkMtB9>M)TUyALJ zZcmC5hw<&EBBOF_YcEle{?&)oW9KFTO@l8ItVW6tHlqvhJ?+7~sT0tx9@8KyCxeFy zLx$X=m%Vee1=7vS^6H_{ppO)Mqk=h>IgHqtg$W7P#zpemxASfuu~w@Ox`uidbpj0e zVK%bYVA8=XPt>1ZfA&!wk%VK?k7Y8<9Q>j!5j;xgbvdqMqXahruX+qRHd6y5Mc&R& zzp|~e64JodU)G}P+O3L=oFTcm@j5!#nQK~eoLliSO zKWTYVFQq|)nP>L1!mgsetxIeNo+H^p1@7>dQlZX8ulTy%hql5cE39IAy%^|{xgdFM zT^kB;W>Dg?a)(9&=Xq%52VY?ktUOaWsmKPOJ1RJEAD~F`WXfL3#46&0JSrb&qO6ss zWO=Lz;>?sBW@sg(vH#`%iy(VbKA0YM?J=?n_AInF&!Pjc9wpD~eThRcv_!VJn&Uhn zeYUZ3FjG-gT_|~%|Am_9tlT9QZ7g5TqpG4J+B{Gu=5UtSKugC%25io`j?&1?k*!1& z^V8}U2@i6@`rb-)+$NgHa=vj_-uyRwzWBNJiUajNvd?O-8FyQ5OQRm| zxX5cPu==^KXqBz;G(3`r&Wwgw@mYBlX<82J=O?AmxgGPI+r^Tr`|8(r((XsTnc>hS zH>qELg5YR(G!jNmp0#Lq+K;x!N{A^dLp~L;31w7XKU&5`V=3--D z(^2T5%+aagMLe7pwujEqxe!2p)ki7c3OEMy3=2aSabmxwcrd0QhZGlZA4cd2vXQw< zjRWgUK7hSsZUSQyt+2xcPY4!`W=b5m@EV)83cbr&n*EEaF1MXz-u3oAYFU%NeHD~Q-EsFptvP@^kOT}tXV(KSU_NfvE~EZ)r@OWQW7 zu|`9^jVLqQ1)~%Ozpc|J{@F=xaFvOiPi;b3bPDsGG$@|BFuhp}Weuk&JY53w6@q#uhFdER69nI)M!ubb3*BjGIwpISIP z>jj1+Z{gX`V5*yOE`3eo$d?ab#-wbj@d6Tfmfgt^`MX-^~9O z52R?i4!(n#J6p9V4GlW4LBpBkj;LieoR2vo{zFfTD}sX~?-UB3C;AGl;Fhgn&rElj zxo|GFCE6cFJNxW`e5)w)k@$0T6U!IUpUg4g_oRIO?Uz%$lB_GMDU_1!79VfY3NuIf zknOUC7L?({ccOy?^Pw=I{9*+MLE`KeUQvGt_M&@?Rg#(dJz7ezqFUSBW138cI7}CU zWTxaIxo8knNoich6q;|(#R$=*#-!^5;PykLIiix6PL?I8{~S2Eu8kn%YVGz>In-3Oe^)7#3@6fBNU zPjJuS!R~hrQ`Ii91Gsa zBJs}x8OopF>}_EF3DrwSov+M*@G0O_X`o*rDWI=oYa>rTv$D-)BPx?T&MeMM6A_QF zxq#_-4$|sdx8bdVu)g%g__!UfkhIeM!DS7mj zsXPJc-Y2wrsECk=l?+Z3J@xLmGI}l+qNM@5N0vBD?oO7*o9@k37{gKLH`PWTK8M9VXpI8zLx(taDAh;e2{`Es7WB<7vkqy#yBQfhi9LCt@yh@xrPL0q}@vt{eCbDMbz42>N~lC zQ9il~eDNW^7#N&K*2F^s6JPRc$PiD0QY@gH#B4K7EI$-sIx%+mc|`3f`?ttCwO3M& zzm8he+GA>BBg;PRNzL{YTTtQZ79Qwz?5y+EUnw$BNlL6WA@9N2hd$UD!VrY2;QTN( z^$(#TAvp$H^n+NWP;{%*XIxr2J6#5V3j06Mfta&M;i+2Q8w)S?Ro%Dz0f$3@Sszck z*I87_iondI3%_a>I-=&eZBEdA|7n1?HcTY+`dVA$quD0M-DGgKLzH-nh8 z{a65eCtb!ZQJ;g#ZX-S^(BmX22$kNi3+=UTR^7QitHw1iu$mku+3~NJiz?M_>nGi@ zGR9Y!+*U#J4`muRZL~;p$kk9XP%&qdA7JRm8|>w00PhcQIwf7o>9JdQ$nP*!51wY5 z-Xw#h=v_M;96OiNkD`$}+z{t$zL_ATb-RM!(RxV{oXgtgPlEj|1DE%hePW7Sx%pr> zzAhB}uB8v~_@Wkct~>^63_TqRoa39#R?R5f<*wYW<_Sx!rc|x+i&V*DSoKq}d|Jt2 zurm(tgoxU|lPvGTv0WDE2dP5XO}PBam&SOL&l<@rf}xvL7Fxj8U!^~)naw-(uc9vR z>GdZjUM|_rM~vQB3~!=R;r${b{8Tg@wf#o^zo=9yVTOpBD)rUyS@{*Vuh_lFMI*nU z>U~>V3RWYCV=&^A_z26NMvW~(n$5iIy@F3u0g_y{&d>P=B)x zV$D&MOVds24U=M-Z9%2YKPy}A32mI4;F!ZgjOYLcA-0Lgwmj-`UKCcQX0EPb>Lsiy z3*yYZ0R=?P=j(se^*zTm8;C3NS(#d!uniFgy9%rM;Hikt=U~NrQsqq&iRY}~JlL90 zs-i-dgECw@X_*eKEV(>9SSQCwd`2r@5~Vdiq@<*jmc!02ffT9Y)k3P7GWpGX?M^~L z*7?MEz3o$dhhp}p`uSFz3Bh@MGR&3CwO#ou0gw~s>goipu|3xeLMp~hwEHyE1VuLs zE_fo{3@>y(QHJ0w;u%zP$?VO!-)f&V+;t3^1tTu3Lbv-{SBYj05bSY!f#2r8d7lZ0 z^hB0Z*ot`{Mtx0W3!40Kiz^+k;1LxS!fDQ7FC{(z5HQTDUD@7(l>tW*>RHqS6%(hj zP_=w!#9)hfu(LvvhCjIN@Q~Mlic3K5vtn%GjDPKZ`@4S7a+51asZ|Xl)})Py$3b*s zlqMl@XnR03e5tx)hh*2bRo(NeVA%Ku$i5+Ini~(LMj{Tx>Y@VDFjtfPR2VEUOsH}P zR#D%5FYFXfSBC%^o^jXw$?1{V5X%0V87+7ciY>Fw?nW;NGkgHx% zguNE!e-O5s%!)!UsAx$6A!GgcfVbYC8fvjMoXK(!KrS|NP3>yyr{>H#?Y|Uytjx?@ z($9AWG*7U&^&pv_N3U)6&Bt{5RmQ!PS>N71;2zhSmDKK+;Bss%LZ7eT?lR0i=|Ib? z(`Orb6h(xDAMDc{GktL$lXj~}w(DYJKx>f(^h`eb-+9xs}OZTh)5;!_S^rt^2- z%nK(OAwrcoywMO^&~Ump`;3 zp=vA1X#Sm@wTm>Rb_Wn1gkI=^;G3|kT1P$qwR|=c1QVr|AKC;CJW$DB55O!YfaW3q z7eOe6pDeDycVGoWq1u=T0yTv%kvw0gfe9bpZ$vz^sLYMv#M@)FDgWBmak{#l{Rw+! z!+gZvm>&;KvG(-%s9B*xD}X5N0y+g?u{~lsc8Gh=s6|N;K%$%XO~7rV;u$1lRaGCE z9b=u{KbU2Bk4EC8Iv+1$b*+EO4b@0CuejEzu9A(_NVZggX#DXYue|NewV^9lgaS0Z zD53|qsP1G4(sYO&@q$rxLRW~hCxLno6X2?&b$@37q&d-D?Ha4j(Y+k3?wuJFYK*SF zD<}Ct&b&*P%#3GS#pNNQxf$;iO&SToAT4a=WHchU;bAMkX%v*7gA>N&Sbh9aO6%rN!)=;O+;4M)itHC z6oD5)BKH&C69Q|!(x)wGQLTdo!kv$WsomKkflop!&&RgjgIDC;e3a3;Fz*zFjcTsP z3od0UsmS0iZK`T=zq!#$#{R=P?5x^|r9B`=2-6gB(dd!%$%$33Z>RFlFauTZT`Q@< z4AeX@t);1gn`yg_x(rD0-R_jEQKhirS~oWB*P7uM8hglQcD==#FDZTmh{43|B124w z{q@~n_d>|mpULj&>-+a`gfjyN(%v4lHFof^hue-^hh8AFp(yF#Gpe|?7gm;)$c;1j ziv$Nq{6#k2wU+Os82w4iPpQoT$DKS5Tb(y-GW-LDKb|GqOlz8qNO3}Pf}(&r0!-8N z$(Nwd>K~QqD9!3lc|#DX8Pq?E$qMfNWEwehgbDRdjIb}TCfR)lI1(ANb-cFe<9lOw z#Go1bCJ&GA1iX>TD-xj1bwlbErP*ZIOZz86=B6oIZ4^eFSrN92*I~*Zi!R)`J zZwMvZUDTr_*wmZKKuVuI`u*5{vog`|v>8=d@dJ>g)87Yw!-?if+bJmj_YWmiJb;Sh z#sW!6pF{jh323xo|J|CQ@TtwW2dLNSXkxn&GO)~Bt|+F4JUxrO-;yhkC_C)@)>-1U zsTj9mU;Xx_eu?Kjq9BG)^;-V)UR3YG#EBEovI9*eJW(~!rm^o021!yR>_7VRBfYjG zhaLqfKvDt)Ob$Vr%i#oVoD5j}5<4S6hQ}C6j}zJap&bdiC@(574Tse3)z1SvDFZDH zcVkn3c!@>m64j2v7nlY8z1c68OSS%w%x}j}U5RIY7xEGI^>ozAs&gVQelMbdFV&ri zyq&v3q^)k*j&JFE2G{NF&_I#y$B;L)J+k+8P)6yxbj~~0QzX_kGwDY;1&BFyT z{|_x8`Jz0B2E%Lg_66~#rbL)1ri3y7$7B-=2Q7f!C3oO^jG+Qu$0;2w2V?%Bop!Xp zr8Ql{oBxq#zo6N~(=ToW)a&$(~BMtB#eBVI&ftcF-Hjv2Qbu!TQ+W4* zrbU9s?AmM6u8+D>{v))6bu$Q7b#88RGEtH1wvYvv^nomAliN|Ud(1N@-0+gT)nbcu zB2i3RsSlaDs#*WGsZlhf{&{I_==+uSIg6NZKIzR|SUH0(e^?6@(1zeJF3&T^V<-^% zW)$GwF)SR=W*HQ63>JK3c!vj*E26$EVT{^l;lY@r2*byxia-y$m{kk#={$Ph&R2~( z34+l>+dk>yeZ0b_$)v8Q1+QcuW)6|V!Py%dy$5$Fj$d9G(`nmQNJSNMZC%~p!Tdtz>WXQLo#%9QKFe6y zWDGlpiu(3uX|f;Y>9`x-BTFr>z?8`r|L7U*eTz-fMlUrieOt8z_-gaw6$r;6aa~rs z9jdKM_8G$8k3HcTniW9q^ea`lfsB@|HD5?qRlc%hNMa&2^k#-I%RJ#42M@153_8qU zY1z{+3(J5~wcUk#q_GtD4AW>l;zo%j&=?7wl)*9JYc=jos^ZksJR4`H+>_WL1Cj(_ zv(W|?F21E4vHo^4H#bb=e#wp+l;u76ViR8AC>vC2?QndD5g6!Z91IuLvA`+ow2Pkl zDSK4l654-CYw6Knwe9SdK?-k}1fw*ww%8zd%b+WCGJabXZlWX-Hk7YSRP=0bsDmA8aP)q0YO0b8Qxq32T{rVnyky)I(( z09Q?xON5Rrb$4EAxGR#ueP^c(71f*zRRK^wzzm>q7k?8&RE>Gz(&T{pdpqOa-d0&nG>OPqFHDFu}<3ttd4mVSWId1n)Dv^1zM zae6$_V7i;6Y;YuK@jXjpPlK0N3S19s@lIZtL)OU@uDpj+j zGqPmuKhr&_McK1~Xb(!|)V?SWn5L}5ZQ351OhlG`_^bMDRT4>Ta#m;p@5yKs>mO}2 zTm`Z+Rz?$*GRRJV&-WHpK7{s^3_5i9h_bNaL?f^VLW;flnz`#$lz!nPfyW4{2Nfk- zitE7M(S9r{%sp0?KxJugj>W)p*G%*q+|zHlHJ|C<5Gwi{VJ0D@H1>TAkDxGsp;b^i zk$XB~=*fRXUQ^TKG(jzYfRk4(va$nGR5k=d_xJQbexBVH)~0MJ*8XEkRCL0O8PiF2 zdpeq}%#@W)mrN&?;-$PbTR+ z(>9h6tEh6(%hH-xr{S7Q8`GI-*%bPM0|F`AWxBKs(^{{o^}?`JI8nA zHF@iKFDZhcZuC-(V5FQ;NBStEZY2{9$6+DOCIKVyIiJ0VxLnNqHB zSQ(}(kB#oRNA5h)5VkNM;#_=kqh?BK3K1KN>KtAd3L;%rA&S(*mD-nYK=lQSKSv$( zf2^IC3DXtm-bs($dLS@!Uj%=lEQ|W86Tg_YrA+@0FTZU3vXxi`O+k6*^E!%;;;1V12egn6K{oY_a0!8nsILBmLyC67o2mb>rlJ!Ac@fnx!Tw} zr%W+~rrddn<@nTlcLBX%{dX>X$f0B*gq! zPk}^P#4iK%p9Oxnj;Rl(3c{;V7tU|<{?5|PXkvMQS?XV>MI-xo)l_VMSK3D$&Ku`s zwS~EL5mZ)oA?70#Mi9rRj5JtZU5@gBk;124xmTk1y1>yk)^#Lh+OQwWuq5Xv9$EkM zs$IZN$SLY0+c77xLV6=h-&}i((<|!H(deIULV_l z2k+kO&Y8|H+4Gd0yyfnneA2Xdxa*cxafdHKtxLv;*nC^ssiqad0rRHwmy?Lk(h`pRm_WnNA#pQ?U zGPc$Sp8jB6rOyeXjF&CFlVSZHYl1%mO8oZ`^kr8&MNQn5Za36}DvFlA-vxj|_Qy55 zVDVe|HLF#2Uy>_2_h9mly%oL1T|=#Tn}B5hR0CScL>6sm)M!;l!iH79mmr9-H&Bzi zs0@+v4~%)Zs|^U&{_99n+{xG&EZ-B(wRyXJwH*x%-daxWh! z+D`!0Z;hUHz5&Z`r^-f8VpRdp$ggXG4^J;YMjc;gDX!QIFVF<9!gxgr?9ZTkP{_pj zqfy|VB;-NF@PYO9=6&;kGR^DG*@JsV(3IcS$blEX!$!we}F^EihYjI$LL)ttmJz<_6j5V06azzHhDcwB3`M^mMNT2tj| zNtzRs!!g5z%69l49h7H}m8F&1-TV~jZa{+9f2J;N_Cblf8{uwoXi{l!#rMtn7Hni} zYY8s0Cu|3v1-|*}=&h~yM~>>~2}mG`kNlo0{qbiSWEK+uGMdB4KV*r_9u*p(OVzYv zKx@a%n^biaM`~JTmYOS22o~QF5mS=nu~C;(c`CsA(p{8>%*SwQF_m#^XnA~|!NRZ- z{CVb3Kj}(^cssVaTDOiR>RPXZS5ZPv=%k|Xh)OQL3}gRjCCa5%9e?cuZXJcTw#0+3 ziweJLU^5G_IN{=pP|dJ8#0B~GsA(z8IH}UaOcGXK&CX?5jSx_xf3Q>2`%YrA3eDns{T>Jgx+Q% z-I^qQyae^9_>Ry~W_Zivh^@9jL*(>rd7tn-oW7!Tu$zN+PSOFlJ~e3c0*a$<>yy2R z{WmB{&uXe$GW1`q*?>EI8NK5~^Uj0trYjNl` z{Opkodbb-_<7|?Ab{|Gi*Vfw4_L}<^hS7E75O68k-OC1jT+MY6F}kV~u`o9sTX;Tu zAJ#4)yc`lGa*yG-bJTUD{&DIpHP@G`JX79pE;@HmFW~MatKBhEBw(lgVN0tk(>BK` zp`0S{$3o{O*>+LKb8+_3{-+qKsv-daDa{_qPq+=shbX;!y zpb0Jo)*yhQJc-^ih@-3J%gh1kMQopRoYV0?tidhX!tWZ%+_lhPy50KpN1 z{?^cWq82R6@4<8`jl)t9eBwm5$aUI{6`-^oM0i0ohzEaxyq`3%?m(q135m+-VYu+0 z`H(Ebm1p-Wl7MA%>s^;GU;E8>aE{RE;D%zX9l5E!dr0yeEjpR*cg}1sHuW4HU8{_g z_ z90n2FzcBfqH4N)#hVBEN+Sc4F0HbNhMYAbuJ8=^Z2(wI`8>#9=R(9lwfw+%ZTt^5~ zCO~@)Mn&tuj1<#uwp}+1H*p`;)?gj`%y81(PssG$;opxRF4LjCVCAZlfCd17icDH;729 zwV#jA!kI)TeI0QI?S?pPKl8y)rf?TpkO#3tAmX#NJo@H0_fe|st`_|1&JMQGR~T8v z*^XEpy!8?5f5c<1f^FDEi|J$(6{4EbSDLm7d&ixUSqxE+V7Zz{E3#*Ju3mf@8dom+ z_b#C#z5+k|AZ*>&-5oE{U6;G`B7Y~w3*wwLLV_^pdII?@kr?oMp(4-jb3*?b3~n>1 zbMO7Xo*=u|pn|$#_J?J?`y4xt)P)_|c=s@jUOiyCHr`%2yUxoU5Z3hqpUWTijGkTn zt|ye84-5Bz9uI@`{@IN*@*T(HY#aS^nPVkrY;CX7K+iQEN58rDH*pxVmqkvI7y7l{ zS|*;Boe#VhLPob|uwK(z`V$z>yV1;c?*9FbFV`dwi}oefyDSX5(j6y9Ln+=%nTCHa z%F$gPO$qJ?%L9;tFkTLX-evIrQ$)UEA3nj5`@R$cedgxob%- z+-(fRH&WTWC!$Ic3M(gB(i#yA8-6-e4tlOj4CBJv)M`AxefRa#XEVgkgc7KJJKF&A z+fX_~R1NfErO*HV{u<8bY?sJ(|98msOvtk3s7j#a&9nsQ`zXc}S5e>Oku*2QJwK08 z+W{~Ub`;aCUy~elqr9soDLlYzTL-XYy>(e*MHG9ZY0A>F_4E(Z*F`p3i|%}djY)Be zal;9A!>Wv(-p_2&jlN3%(6NJ^mF8OaCnAYT?qjTk&!|ozq@P-QUN}+0bt<=Lme&%| zMJ_Fy2{vYjFO9Chj*&m#4{RZMByr2lcr3t0RO8%9l5u3`Fo*Z3y(r>ACI;iaM$TYRFY7o{D9YQDi<^fh|zh8zRo4d)k7qXYvURnCUY) zJr;}^6WgX}brs$5gz3em;Q zcah^+R0WBhN*OarQLx^nMT&%r(X~LzBHj#Rh*a5n`ovxYqKiTVKj0HkDH5%SXNm_o zCVn^F1JqDI?;@Ij4Kd+f`Rr1TU_j3ud2$6Avv4)Bt+E=J06#-Gtvsv}Tv&@dNpP?P zN2~;m9UX0E23!J6NLoQ{>9QwQkZ%E7=G~q%A3?zRu{e zJ}z}O=kD5f-vvhpc-%cvP&RsGcO1-}ZOroq5C$z7&WP;x#$`Pv!EFC5F&vI8=Y=Kw zW#bTV%l&+3^u$Hp@$_u8zi9ZD#Kh+j);XjZ_im|wJ}|m!G0b&7-)koTWwkv$(?D;J za$P;%0N^R?zsDJ4UfghF~{cGB4asv(G*Dd*9F03Qn}=}97p zo8ZE2_y9+1dIAH)l^WCs{;A3xt*IpTE16)*kNO?>37-l z_p+QT^Jh`T`}D<8?~q>+JDPmO3yp4yHTR-6_8+>fAvxl++!kfE7z3f=ymG@J`mVyv z^w_V_N9v^%Ih#Mlqs$8u)rqvzs{UDtrR2k^Sp|!i6AjY=)qdq{qZrqP`b;J?;;4S6 z*H3p#5`fF68xYFy?gNAIIT%w7eG~Dn z&09jb6MxD?$3&UI>L!#iNzVBN)->qvbZ5qFJk2o=T1KcKuQA7}y$qLm8yEd}vInuO zA}Iay(;EQWa*0zYL%~j=+RURhzA(X*u4Kh>`xu^|C2r=W$j2Y)IYs#UK6Q}Klr=Ly z2(2~g3sznLrsf}qbNw=Ddz6un$Ml&Ovc4w(8guZ zxN=`rvp`ki$r4RK)h7&$Dpojy$MmJ!SdTw#`)L0ai&*0Lbzpko^}H*)8JFUQr~K}? zY&snGB4m+-CkG?qtt=E}JDMh2d3d(1r~>DV-%SzQ_k)P3Mjn46zwdE1Y=2v1Yyk2X zFFHTFP8>AvE=!a!NY(#}v8Qm8;yjwsxaZ6#j#oDo^y7#L^Na=A`yPFBO->NU;4lbj zflo{TA2vkvL7Ivgbz$M*B*_fw;uhebUk+= zbgN!V=)4!@G#T+W|Lk0HZoW#Vfw8@LOx^vn<5s*0Y{M7+`0Y)9PMO&#cTq6F(0;P5 zG7IC|d&(PBX@yTriH40J>Szs^revVVQ!h?nHww~sZW{@6nBTGY+KWNa?x8~eTrw@; zG2d3JZ<>Rf%;QkoSszAcZtb0(NUt>6P>aE+z<_mbrUl2!RqFZ~n|v)?6;y zOVrJT+p&Tvcsrz%^Wo^)>H(Z@^{katNG}7U(y@|C#!F5tpSbSSSDP@d#)p&*e#&$2 zQLOZS9D1BGe}BKJdCY}&72l}7ot?eJIBqf+JseEMn`E-)G_5!j2Q?Os<~Eu>r7z#c z!P9=&2h_hPf8CSqm&SXDF*k)TGfm107s&E_YRVbF~%Lm@aIBj^9X(8r8pY3t~xsn!^c=iJ-| zRgTin?KwrkoKE0zOvJOT+Zzwv@CsXkdLR1y`!fU4cX#=KH8FpSPlX^QU%ukHSE=u^ z$#=JQM_K>gUbYg&gDXD@UxY#>XRS4l+z2xC%=Y8%={ zo}dp7IoI2^Eibb~Rlr)QBSvuU+D5{bmebiwNz3&jv0i)q3+2GylDo}um}V=ZM+wgZ zPI}5>f1%hmyM}yflQ#*)s$e3${E10{boZEg8#-y`uaeOe>~f5;T8eTAdt*ZK?rT$w zAWjY@T%PeUOV=jPrgzg;EQJWz3eXQ6a^qpb8NLkQq36snd+=uU=x#+C>$n^q|Kp!9 zzt~&bIuXu?iqwFF;+yEen@Eb(0>zlq_AS)AHSd(wD|M4hVH%k2s3HBo#`^!^JvOR4 zV(0^w3ZL|gv$!x;+^b5y`Pcaj*^2a^y-n-g>#aQk-m6mq?=uP#vctg=g9jafrrbaF zfmW6Ftgz=fZ_fW=7-N%TIM{qI0zpDBGXL9IdEI1kp7>}8e_l}n{Z&1fwZ`A~xSXG$ zWFM<$Z)24qBNw^kCq<&?YIPY~oNP|+zWR=y?~!I6#_S|H9z)%dXs|E~v4Ll**nIHh zQrB__j^)o)p}Vcv{2nTL#{O(&$NK}h*_B>^=xV5&tbN^(A!TSCt0A2?A*d|4D zKem_az6Qd9GOf-?8+mmgA?iP1`j!fK0Dm^)K?UJn?RAoS{MVGzzeX*izTw2wi(oy+ z9n-s&(d9#)a14s?=H}Uj(s9$EF*DA57E!Qg&>sv1Kw0V?=sO{LxYt>_0?6u@*yVBG#3=FZ z{7Ybgr*&Wc{CJ>7yJVL>+6cMHq?W=srlU+8P8v27H^6Aw%^39wvik`et7N^^@fA=2 z{E-G7Nd8m!2+ki%X82{^LwIa6gxOhxkr9+Hr=ug9d|nE z6|B#!5miro^xMOGFPWeMi8O66z#?eCsDv1msq2<@Vq4^8@yPq?eNLN*-*NYD27Q)U zo50?!$P;0o^Z5IMP5;3Bg~#;3XB)%U_tx0aSPrb(x-EIZ*9ZO8+mM@vJ;9$MzN_}f zIk}hP3(xFbE=4@`4LoL!woO)a|4sZf6>msRFUdnd@6|%6a0Q5MSzhoaKy}{%$utbbYJup1Rh)14JfHAFvcN`2sdW+xE3Pt~Mu&!{+`zQxi=o zF|)EdKld}JK__Q#u7XF$jV1W@!2ArjOG#X%p5O?czh=lm6809Rv67cT!*Z$h_(y_? z&e7G3=Xm8zmg%``g9kI^sC)Re0X^wlB^>j$z7vKfoCuiN|7+;}V!bWJjWJf@5FxLzzh6|yM+}$C zZHA0p)IOUc{>)zke_qO`3*Fk1qUUKyR!PL@ap3st(``_}B5#XDS~ApGO330!dUQ6D z4LL59M}n)N)vun}^wdf-HI(v%!nZ>Juh`EIIn2iWR)@ zm;@hd14m`3+kdkUu_AP&evvW#6*b01C-6<#ZnE<8e5^-BHetOpbB%L>sSDj?m9)u~ zHv);pkvBh@SUy1{NqPOKq7U-b`U=TQrn`x}zwo@;@Sk-4qJu4Cb-vQ?oYsVnGWh+_ zRbP=$>$RG>2^3Mnh@AmDet|2`E#nhc>wC)zz5GrGthYvWiD4%+Q!3e(XnO<5_6_V_ z|GHsOcR@3WXU_+gn;kpKfhXB3fltV%-7ldlf%&^cYuwx@<{v*X>Y-rwh zM~5C0Km3K8Iu(BNti6sA2nFjwUA4}r zzo7}PJw;hMpH&V$L~C_Fb({q@z4G2txPWtnuTpda5qkt{knG@_;01zLAM51<*U*_^ zKlR8^Xu&GwPS`!BS1x5#PWV4M!=HOt#yq4>1fmPv3H=BQ`ylK+WX}gL;BnletK3A5 zP+6AY?~75xE;3{`jU;KR&hP%viPTm3b026~;*E-^z~C06iLW_mwcH3AS8bS?PwQg8 zpp`EOMYeg3fYqO}(WWV|7N~SW(PW^SP!<1}y@23AKadtm!rwt+L#P|$s#IzumD9&h z4`437BRqOMxdc1>DMjfyI8a60mA>-ZRBv`Y?&^EDLVyhfv<+8=joHnHz+~@wOZUfL z+PD0pm$&A_0<{SU*$w|H$}+O3Dc&Z@{MOx@FQ&Q$#|G0uY)Iw#TM0vFI~Q+p6~UVzAL2+`vbzfiFH=09f)#2Cu{wml+Y=mF53?dteNh3 zR&G8i1OpGZnq0yXD3A`x}`W zLRr~GM8di8c?mD+t6xlc25rac{ZEQT9{n-oD=3rdC00IX1hV(Y$ezw{2UkUg@?67s zJPKakHz7A9#!e0%T-ocs;bJ`wE{FD(9v;YKYH=N~BOdm)O1dHarpe{+^{O=g?%m%N z?P-^eK0ugW(fkV^@#RG1eeKtF%H7Itw9JZgUKHscGvj=lf1S@qa#Ya-duiupNI_ zSL@9fnjS2{VC@GX62jZ|pg0lpA!otF9(BhZomd8I8XHh794a%4)Fc118=p^|fAkE} z(bqP!esb_b1NY%+=?{-A%`mett6?Cj2R9}Db$55yyg@hwn1)XGf({U+5=&yJ912v6 z(`uV=U1nI@t+?v=nPF`#Z7OF@ji?b1#`J8hO^aM&Vs3{|*(T zof;|Ccgt!dOm&{$@}o_M56Nh&`g0UjMy^7@`;8J!@lt2_(`jqw)0vP${S_)C<(4BhxOGZKKdKJ5?7bQK{b?Fola`=u zmf>UsfxV`sChydE&%?|Vw=J>$laLcuDrkjZ18mC^<(wpgT`p;ZV$w~lh93-2IFKS# zgG@CA_Zs&vKO*+|6jkcYasm%E{wNTo9onjWYhRU_JO~QHC?6shRoj5f-bgMiKN7vW zUy&z9Mu@U1-}P?v3_9Q(Xx?RPr>|!UjD!2^v^^`sPDT}ZVNuduSUbmzlQ=Nbt?ptn zbxwmHdWaH#Lps3_T}~|8ge{(iO=kg6gsJJ#=y zjE_ae#}nOJCN_w6<>3GhGX)#QO6t6I0xn!-a081USgN6;p7Yf6o@(fE14}&=m$7Q3 z*0r_6dyDm&aZy?Pz*7yJSk^k}JbS0V8-uaZl&GA;c&phuh>?O$LW@SmTG2!q)K#lY zX%%6G7+GEZ8ea7u|1$fh$)32mfn0?kHC zXd|JE=K_$2u;Kl5;`Rp@?VH!&)96OLcU<_Whn@yG=RRZcz4y=#_8xlp7p8}L&U#Rt z7bK152cOq%uPWc${62W?KDf;8O)gs2+8OxxB3Ls3GF!9trM12h;GzpJU^G7`UGJEw zd}iP7T`CNhZlUuvT^lK-8A)}@S$gCtNs_ZscOjG;@4W{tI%DCYXQ7UiFggH}L}So& zS?xikXlPQ424_n-o1`eTSjH&{-AY}xl#w!>y2L2JMsorr%{}V0Bw>uxDT>;?8p%eG zEDVRYhN^N@b%pl>hI4b&gPOVd1*+OJ9FAz4RsciFIA|ORSz-7(>8%)Cr&UwH_}M}5FO+3m=F^i z4>f5yo4xJuh1l#Vw6bCzn(Q_pz@3l)R88dp1pqZ;cSgJIn}+*#{zUM7vzu+o4-z*@W) zOJ$?MqE;Iq);chvaVh73cTOC{%Cr)ufU~z*ooYT}O2ioW$|qmN@HG{uPoDJqFWL2@ zIp6P}i|RVnbwcAH5KN|N48}G%vw=Ok!mu7PT3BIjZjRAto`v~&VvN+j!nqnqN@qf{ zAV^Zs8Qa^l8g-Lu+6jBMFSB>&4o;ms#m8>`5Ic4)u^bwz(Hym%qpE9i7g;^K2F6g; zwOFDW0TCffZi-M>ruB?;Qj-L}rQXnroP*d27f0a?xg4V;s%9~pzX zWf6N_j3&rlTGtvg_#ThU^@z^$gwmV)1G7Ii{TdojicRmwPW$(P81Hl@D5 zT)O5cjVkA9NLb0?K*iA#9yb;LimhsrGy=M|j}K{VXqSV}4Al=hPQNmuDyi{sR^5-x62<@o1K0IsL5C@9G}lY<%p#H8Ta^g)i+Knyw=(e1I; z_SzV6`^x?Hif&^QirKP*H7h1221(Q6v%s~S(^MaoQcp{av3xERoTS9s+8WKIA;riQ zS6tZ(*fW`+R=mXl&KM_6ex-*QBX-!?7*Yq7^OE1=WIwlEtJ))R`wCr0IAd5loY}ZH zGd>1k4N~`MK!+0m;YLs`K)qzBmn_vHRP&Z<&SD0MYG^SPSO?y#t+O>Mb~ifBawH?$ z9otN_NkS9JOg0>@5|Cl6rLJnKp87b@zj>@N_^P7vo-St554EPJby{m^x=ud3PPJkw zlZ}3NWkd_KX$qT*DM40xR)p_<>5B>gamJYcoOAx+*(KxQuNz))EjPU2+S!hntr-CM ze6A0^`Br}Ohu;9e_0N0E41mnmm*Vu}`M{qHs)~!QxRe|XuJ%HKuuk~*xfD9e&DSET zxb5U*MW|bHBPc1`)51}72C**`BSJkeM!M-C1~kYVV=N&CoVC-0W0YoL9npYM4TtC% zaz8Z@dXL?dkgf>BxlP-j$<|`CgtCc%SF#?gDK^0f)y4Zw!sH^dx55)QKo{t`z<4rY zeSL%Z`8ifs&a%9HJ9G1M3`Rq&^%$oGzn4HR>#fE>1l7DmrI09v%x12>>KZmC6K?+K zKeBS>3`>{y^#5NFWqlIbQWEdcix5g@^ zs$@>kPcd>X)~*kB;W0+5)KxWi?cmP|G#tgNgsKR;yep1pW)`ums3pEI5il2XJZxzZMGRRX+f zK{U^#3oSdVm+tt{Ss7TQdN1&U)K_`vC9y(~q*}5!?BJUwz&=fB)T|dJ}ivbcDx0 z>q@@d;RgY@C7uOeqDnnD+J5+efiMCz?n6R8(N8!OI8Ic43xB-^3g_nrgqDvZ(R zP+os2E}e7Ixwlf7d+SL_lh=*e1cEFf1)LQ_z7RqmPB)oMXqtw}WWxH|I!nvTEbmxi zI6qHa*V1M049jCysArKKeX!v(Cj z7~?h39AejiJ-3Q1HlCr=q+XL$7(TW6G`Ucjx}HwgBM^kvCSvq<6IEqOX-O%nu-9r1 zM%)A*49Rp51(aH2d-o@6EygPC=|LN9^ju0YNg-=op8#LZwGgv*)lpmK&^xXbaFUfy zW{o9up$EgF`^mjK8HM7dI)jX8hve^a7F)M@-%Js93c62Y##WLP9~IygFfj!>LId2G ztg^bcM%T0~&TnJq-kro&azJ97PCq2GF!q6v*2=jW%61#ImM;50MJitrqc|(YB)XHB zA$Kr-0M-vD)(%5^5@N$vfw|FX#{m2y40gb97YuemwUDS6J+?|XuVxKirjVsc5JSX! ztA=AjMH6>9BcPuIR;;zf<~izHOWOFbn~sas?}8TVD%$l`s%l7R*RbAEd5?7-um7$F zHC0u~q{KVoWF7BoOcLih=jnAtwg7j1olct`GM&trY{|wjS#SAGFJzrBt3zVTzsZ5x~yfV}gM|B?G{I}Y$U1Ca50!@K`n{H&k) zZC}yf{+8c;KY#ZJA3A@x*&4p?MNi-*zw+Eq>ohT2v-Q7e`tdyA&j7By`YP&rBsQMT zX#sD@s9qrpRYyrHIHTGND*z;`h@g$-T!*$vo4WAwT)C&zOpDV(uPPIjq(zp{siY4b zR9jHofqH9QBl8pq%gaQJOipsf7B?fcT`iq^2``lHxwS?_$|VXrPi=fk$2_Op7ezT+ zwQNml`YUbmtl)^8Szq5!ATnlsFWM zu;qDUo=#TEDJ9MIlIc-KZ}cEnwjrr~!e&8?PA+t!AWmFa-JB%J+c@)(=I_1rYD`2^ z+#to(D(U1Q#)!$#whbpvpCNP|d-v|89t>m(*d;M?NLjn_nGiJ_r)0@kONwF3HVE}F zs~}&fU0v5HHL_R@i~eYQEVF(XR`1NTr<8J)w@y6V5UM2@?lKH^!)TA8TFO*&@)^U> zpR*#a7r+ry)@Fnpo8AuC6qZ{S+7(6(0rWUnNvJoQZHSdFS5@*^cGeO?M^)9meuHF~ zG!0c13Bk}>hi@ignQhl`-cb)~oSlF*)OCdm9%mfB@-oTI8DDuq3|JKGmIMr_ z(85?A*rtfXN-$l5kf%PKeYjwunP?GWEq%gspBd5%md%tpWkluL^WB|OokXDlsk4+5 z@vrkv^#E$`i!x=5At?RFSR+%Il*v{LWKutMDB*=M2CNm^Tw8fk*W!KMcV}bh$lhTh z1S3|#A;cb_Aa0&OW%kEvfS=hG3U^v$ozE;7K$NP6ON)o^| z5KT~<=qSwA0JWm~NIflRN>Pe2^b@D3j$?XM1(YbYOhPE-HAG(|PGcd5DOay__G2bn zPfk&$SUF(&=@;ar$J9)L5sr9ZEy@yPyghCfno?5d;KtV=Uq-D$dOro10#~!qv zf5J2cA=YG)+6v3`LqZM5t7}ZUmUcX5*RDP6*s+sI(~{K1t5^efMFE~8#z}E(H1j6q zjJJ|$Ak)GU@F~;3QtTJ)QW2?#CeW@}Htsd7A6D?uZk>AAN@!N2y@uf)8109_vcZi~ ze_n%3%W}3o-3c;XC!fDU3oGY^u|gj!&SGS-=1nQdlt!nHF(n)Vvb?hrXoNfQXYl*K{r>^{huNiLwq|PvKtA`W$=hz_-rJ7z0ZxonOV=;`#lu3;pV`H$0ME2hP=gRJG$7FTAe*{o8*0>HNfVew&-$bqDW$ z{mp#fZ$80SJ@bk!kNN03Zs#B0emiGRZm_sxj%#jsB#(Q_W&Phj{@%N|^@I1Y|B_u? z_pB@X+i!Z;r?}&z_jB1-9OSAeUfkdQzQ4MKBX^$UF;9LZmt1o|-s`Q$xc-~3!8yxY zfBypS>JRgS7f zZi?azBThlK_sTEb%$Pce)AV5J+9!%u*IGw5k{gjto~orcS3R3n2tjRZlNOyqzcMjt zs<^02vKB8P1`QgMLMLn2o+QcDC_M-xbTTwcBuh(Ki)qn4hzpXWmZKmB?KGzx`p&z6 zp)3OSsKhdB>9WvXLfiHE134$U5D9Bzx-QaAI(F{JNe7rY>nwsC^}?BP9VN89itFsfl#moi&7Gx#POu@Q3&7nXv#ge8TahoP~h0^8p zyf2o|y{1JekKRwSXRLK>tQyAmTGsAPG$$V`kgD|=P=Jr|YQ5Gv?Yt)9@ z=rqESk_Jg?K!~+w+EmD3z3TyBnQHo~(#e)iDUr{!cQu%(6wFHUUP7ljCx`qefutqW zUCtM)dMUA5F2Bn}QoeU$jC!WY_m{P`epVrvL~0t0P7s~7L{l<{thnXrG%j`>-}~|( zC;((&jQP2o^N-K2B(pVJGXU~WUcdh{f5R>Bxr+zxILVuT@BOTv-01)IhTr&me&v7u zAdAa$TOM=f_$nWI>#YDh<=ejUVZF|9ZotcaB-bpoZyza!$^)#+meAjC^aqkKjUa^PY zy6H!yL)~=z>wUMK;B9~K!7cCg?f>J${KvQcKrbNthrhgq zpL*_Vz`+0ep&#brtM|{&>Mx@;91ed0c*U@;*nh!(a`IHQSJDZaC_z~#A5nPo#eOwR zsU1Z)y;&C^?lRiV&q?|EL5d19ofHPuD5UB1LtJ%F{^+^aWt(nvyK2{Bv8A zq4EF#AOJ~3K~#&)0p}~B+mJCS>NG^S=Qz`6D5PHWEme0?N3UX$RC!NKqCts(&sp>l zRw!yAM%B28KEN4I>LRxGq!4jVfK&_#Ycn*GOR+JYuyZ}r?TXATE-)O;ji*QD*X7#zXJ6|$bj5=B6Vwr|o ztOliG^?`CSr4j37r%q9lYA@n*p&uD*WO7$>XF@J)ES=I6mo?RvD8LmtjZq(3*Mlf2 ztRFgCp$ONzHqf;VT{EHW8c2pgJ>apAxt5RKQ{v7)nTkk#RzHk2@A0fqzuN_bN=4&4IYaF_A9}C;X5dF@Z?(c8^tsi+~4?dpt z;wSL3-*`S>`HV;D6yxLk$uGSfpa(2n6S?_acgU3DQztla?+O6-eBu}xk>(hL z@p-_EdtOA0fL&-|1EIJ>6)1@a&d_y%8=rhVgW(WhWQ_Ul*=1z5W@`pO zKIgTxd!Ao=&&zqqFF%)G_?z!%$AN7Cyz`Gga?XAK?hk~1^@Oj#iiM@oXZ1RJF5C{l z`pN`=jkO7H{@wQh@QfEeo|nJ+1$_PYeGNbR#(&L)SL^}cZLgKryXJ;RGMujg`U%WG zzWw$s&;P-<2$1>6-^uNJ9@^AXvKk>f9{lA~^ z^;hvV&%K(beCxG5`udA!XY`lRGR9mF%mZA1?PKwN9-Ia1gnFRKy2c3CTEw`iFRC#D zFpMTko9sx@ND6h*i@SD7OYT4^9xI)IX#LZJ5YZvnP2EqKiewEp6ls8pm3!JshUBR1 zfY_<^DQK}QzgrABB)l^;%>-Mz&e>pa;>x21pyYuRTU>d(w+@@FsP41mS;SmkD~fP7 zX1r2kO446OCqyY@@Z_Xj_nc{)mNRG0aQw(|j^6(OYo}Len=vs&T;-h9lUf-bY z8e-Rxb0TydF(lPC z6uVv|%(7%`#MEsHq>Nk78*Cpwnfri5r4QzWbCwvp{+x^&B8Svd&3sksXUCAU#bnz9Ow+s08H`D!6v&NXd|Yxqx^)x;>OQZ< z*=pe;7+sigi=y3;>59XN3~xzLX$KGi-H>? zl(@mb%LZ9+u1s?zf5$mTRn=mO;VYc8c4u2ZkuRJ?z-s+ zL9K(H@SI0a5uCMr#nUh6o=+U(o=+Yl=S)?59{1GCc<&oO#)lsSKrXs^A9vmS03Ukm ztvvI)pTI}{_BH^nd-j#*Y{M1T9b*5*Vh6nY;4<5G&2#p|I+L|#3s9N~<-cs#n*gqU z#6|46;&MWy?^b3N;G!P>(y*TL-i`4Xb-gMR6A=Q_Tkcv@63(r1)TjN3RP2;8l4+nF z+mfi4H>Xa=4r42@LOZg?(#41~hL}(=B2*!9%`&|R9SufISd0@nhj+E=3u14bD>?yj zeF`C9tS6?3^Hv8qYA38Vs7^cSrGPfd+Yy^k>Ao84`tCl3*RNWX+&dB(HZ~@7;V9$D zn8nov_Uzq>&#al|jTD0eYdW%VVuoNE z$#W^7%}6SEilG-yXPvsl7>R8|h?%x+XxmP;1O{JQ>T1B;!Zs@J7z{_aHgIYbv9@D# zK%%t>%7IFQXIi)@Rva!if2c<<=COjXyKWS$w0hB|3=I`J|% zI}jkABHmXry;3uYuIsSQ;f$j=)TI=uTrK7Xen5sE(5I9z&Jx;=Mzdvw_mI;B2l z*~V-w!)m~H{=iH4$(Q{gz>Tv@$!yKm41mamKntB_+)W)0_$`Kn%R_XP(8+JpaG);kVt!`q?qJe&BFF$@q->tuMxAC+Bkd+ zcLQAg6<0B7GXrmWA*ShzMGHHsJnLJ4T%AmI7 zGyrQD4h?Z`$iBV1a8)rNfc1@a);Bh2#uGNiW7=jzCK9xC0lcZm1aj8!M#VhV)O{9* zE#_N=My0j_(ovRF##{u&dJJ=xeV7emUeMdPmMNU+9g1=;bgw|psyQ+>Ifde3v?*~s zl8x1XOgUfc+T({qhJL_kG@=>|sB2F$;IX7G5Ok83VoPu$aDT89R+(BP{nUgI<^4qg z+&Sjn#=39|Qxa-b$`{WUf9*3>e!YMMq&OtL(WwGg_* za5%sq-vgD)#MH{9Zag8!7FSmoTa)VwTMwzLAvVKcI4Am|lrb(tvMQ|4GHtb$Xp};y znzI6ym}Yy(8GyBhn1VDgx>j7V^nEyM2(8=(hSUY2ogu)liyhwj{<9uJ>> z<^IFBV~qJ^&iPTZE6Z%n)<1IqvVHFY0I^HldD9Ud`Pd8kznwm|%JF+d%dq=`b6u-K z7q|R=-|dnYvb1O6K>$QDKCXY>HPnO8F3aOjfAt-GflzcAMH2#W?C_~A_djx{fR$~# z=BaAgrJLQa-tZfL&*=wNx%Ao#_^M}J!EmAG?1^>${FUz_#mr!?;+h*Tot=%dwRwFV zVCDt`7Iy5yW(md<&wHlDu}(M&-Na~cpz!f^0#Y&|wA}N~iJOw0rXiOwHKPKIqC1F5 zvnBv6^XDVaK&i93RiUDV=2+6Tzr?e^@u5U~v z<+KNE4CFJBe6eqnH&$`XE22ftTmxfQb2<(11e1SC+0(?M6sS<=K>K+N7<%KP5UtDov+OE{YuKal8FRa0Ni1fQw58RqsG>YW+9y2z=a zg1A7bb|wpamvc+q&CpDb6nn~-xQYoq&Zdm5Jl=rKk;+C!CUNRhALsM~chDU@Ar4_N z;zGhz9yjpJ@0n-kC6_YX^ElXjh^iW5yiR6f#^nlfi}9AOYp~8@)8+<8R=tM=qh`$- zadoYzS3mVi6D4OtoSwVn2;OP)(#ZK{QXgn3p_#CD<_zEUec#D1{=`oKJaTqPnXTD+ z_&rdj-=OJ#*)ZY@@y;Ii)XV#S|N0NSj-y)8ubf=tmtOqe`@dcH>~p6AANcD}@K?Y6 z9sq9s`#bpPJ3a-_3(^5T`ToP)fBOkwYxv+pt!C1(u{PoETOQyyf8-7P;m^Mnfc=;3 ztRDe5bYKs2i`(#Zr3I~A zkc$m%v7L3!_5APBp)Q#j=wzUPjjjul?!9HYCQ|L*g;(93$U;MjDN3QGsnjNmvrQ?a zN}(lb=URB_H&^M7J8KC_bpb>?Tj*rg322EK>%123O2dg@grwEmmy)xJk)!6Ar~;evoVvu8)nYML<^m!QNchTOtz%Hvc<&YLSSs%^ z#$lbqYZi_79%F?{<6Wh0V`U1blQHMW#`Ov825~8CyP^X!8W1RAkx+)x4r>pdAiFM2O=uTFiq zU?1w3fjph6B&CqW2wj&5LBK%{k<@nN5OG!lByC#3Ja7xg-}}dW@Hbz<{cm{#^*uMU z)IPu-PI9n5!`{(Z7Skz4<71q;=}vC>ejcC*N+MKs2skMB=bHe zB#Z8>cWBe^8`0CK0Nn~zF(w78I^E47(zTt)=tHsIb%fCNATPuYYb%{Ha6(=I>nb$i~EVjU{v$GmwzpP`15b&)(_mncU=6l?7L_Ocinu1u89oiYrg#_p0VXQ zu6y>C+LZXwz1j;Z|Ix zG!F!m$xX+Jlc(4iPgpy<#`f(y7%j|G*F&L_XG?wPFA`5y>Di2^%!$?=F+`TRp4q7 zzgy9*lP&lRRIqUtCL3^e4etHO&D?kMC%NFUALXJOpUYtBGE!ymRn;f2=PVR7nYv07 zp$ig9m^Gojr(x&mxhOJ~G}WNxh>Mc_G8hcSB`cKAaYyZ)>TuL4&lq;>*v{Sq`#E*; z1i%YtSC`qEt$*eK1mGwC=!N{=PyID-dd+*;INS8#V{zvk-|&*_`PLtQ`gtGs*r!~| z{L+wjzy4+bh6^<>`Q_(w^^?T$BF4x&{zx3Lp7Jf%o}(`*YyTxXxBTwBWo~K6p)2?D z#BaQsXZ`E1J}2DpJAdk#IB)rrUwH?sCpY?hjw`?V5dX)oe=`R!+r8!f*FWzXUiV9H zCx*mmalqA2mTkQ5Sy%F#Lr*seR6~Cb0I5gSmjCy+56`{*0@bLv%Ci5G9kVm~%VWJ1 z;E@L|U~%7mnvnZ0veATUsi_S~b0NT}C9UZ%@&%Y!bz91Pp=5h2T}pAT7Mw|mK>~1u zGL?){kr0uqGlpC#mB$*qin?z|cI;jFkpb!{to4y1m*Yt_js8%e^h1`%sK zU65i#xc;Wkj}W1FB4#5rFz-OAQduV>UTt_q3@`7?+D$nUMmXhFuamDlZMG;%+j{)EG#ZEw`~DyEp6LMoM@h6s`i(M3JiB&obZ_#wvrZZ-Gd#*WI_@evGA45%hYQ_a-JRo zs}{;8E452AX^L@K=%OBv&^yw_nDAu^3DWsj$Ftm<6zC+-h*3c|HneB**69aeJXDLB z(QeCNzU+HTQa3HJ-|bXSA`_|@J8~8$v4}XL#Uu`J;&&wzhpI~CNoM`XhdKWK4|8x2 z?A`~%0aP_q4npw{u=4ME+ZI|us_P6lJnbob{HB`#o<6&@%+_pu5j9(L&R!dz7O3@SfY^U7A}P$vU669kt3fv$;N8K!t#jym+U<6==_Ji_Lbc7p1b(! zZ+J96{iiSD=-sDSJ2U3s<+~Y->i&Ko_?u7g6VLfA0ABU}f6JwhJNS9uX(2@JyX`pL zB(U>>CARNbe8~HDZJ?cW4Cd;d7Y;&$X*N0rbJaP?<}pSl>n)>&!4?43b%A!=F~4oA zrSREP>k@XhZ8SThzx1ac*8}fz#@K)HFTau9`z~Nm*H~A9^BSw68V!^B!bt?adb_#~ z39p_hJt-;Yx==U_TGW)ZZJnBE!6_6Ss{&>LHcshLM!V~-@))wXU}<+2rbSV)>J@re zAclamlKl{i<5BG7wUczrCA?0$$KF@sdZhx^jEc~cWGZAW7BNOBGHN$Eea$4{eG1C3 z0$RLtI#CehQ|yXKUP?86f08+3`c!aRrazjmVvKbDF`2sZ7!0F@5zE`>SlG6M(P&=u z7MhNo$*i58aOb;@bJH8{<@7!46tgH}Vfo-3S3TzdhrW6{qorEjyCz!;eTqK&L$pBC z!rUm0tk8jy7S6>TEvI71Yo=ewn&_Q#F$XYx@s^Y-X05}dL@LKu&!3{_$eJrsfRV9I z+^lqek)#NB&S|$>buq4(3>Y~tGzC5DtS{=0NkpX@l`I?EJ3PrG#^{tsUVpOEa_>K^ za@X6>uyL&0vVRv2)LeMOBD)?v#117iFy~CuG`&)2x=)LxaqNL~R$5g7@G+%c|1zDT zDA>tK)5wb^t89okhjn9Ezr%3yLz&63Opep*o?px?KiaT-Wu{s(WUExR%=EKtolb-{ zPtR10A6lYj)d*M=mNN5MKpnJ zJ9n|L=K!bgJIrK!nqdu-40qiM_a1{C2M%z}w|ocn{;MEHCT**LI?y(q>WW&;`Vkj2 zS9v{8p{{F9PnW;fwNUy7)c{P!xeDhT&O6z+CgH57a`Ii^os=?0ubW+HnOj`s#%rHc zKo2I1`Ye=;7_?yOIpI=-#+bGg zk)=>{u5ix{F(L`(Ml}`2Py(cl^QuKa`2A}A>#W0hD|9SpsC=cOSBrOQjcG&xZk$l0 zqNZtkYY8bxU!vH%#;jcaD7g(G(T1pL>=9>UZ^c^*Jm*khcqNmGWJrknkF!qfbCZZ~ z#W&XU(}hH&!jns2+_Y>{8jD2?`kdruSagyQG2UTv>McK`3Xp|<7V*|Ho=m8#0qbYi zX(tob&o<02EU>V&NL3B`3097&r!GBsWv1H}N|77Y?<~_(drX?zDGMklc^d&^e6Kl? z-%TIpXB2!CYi0@B6A^O>xkR-z*%)G@>e#V+6~EsoPP$y~bI zMqSlx|F;+=Fp`}6aJ(+5NIw@))d>|%4wq1iddgFl_?J|YNz8Oo_Tc@SGz3wBYqDA0 z(ZroAMG?k#T_=F8fDjdtw^6bwtXI03jzs!Nl9^5-olXNw;A753lrF%Skco{Xp69e_ zTW!YG;O8^BRxMMMDSgmn_F_O$=yyqjLvv1~7zrT|g8(O|fF0X1>I}EO^KUq4R#=#W z;Ru!%rEM6-V1iJtDg$0N5u2gg=m-hUt{>;j;o~eFTw?Dm|JF0)f$Xx@Xpe7tBd!6VU2LT4PdKEs9DJCm9BI>sCvepTbTd=AOJ~3K~z8? zR4k#PU?kaFGVl^91e&HL=@evRJSL|^=o$$mQ(?R8icHFZlI||yXq#v>ISa)jM{#Z< z;!??jR((v2l2EOH$mzr*#UKW)TnSQB=bv2cL@jLyl7A8tUAMx< z#s;frS6EzJVt#P}>qV?xg!m6J2a}AC2)?d`t_3|W-djtIt%ASYPb6XxT35`S0+)dI zt{3VT>)gs$OeW$^Co0G#4W2eG)+k@}(&fT+7odo@U zDHN}qSYe9ivqg!Na_mo zoHh~M$l&H8&I`yDT~DXe%mO;f^eHF+kx<40lnZ$3V#FK6{J?PZgYRG$D=ZFR+`-B! zY+n%Sf;W)Gr+_&x*uBY6CCTUMB4P+(Vg3FU#>Z9|?i@0=Z8v95pXA_0j>$&H9e@9R z_FVi(<}P{y_(4B;B}xdVV6yTZHBE(wjQ90)f)_I0>%La^zqaid3?wn#I)^b4Vy3Db z*`gtr;GGw`pHAnV@WktQ?Q0fTUt1d)W4=A-{Ms+2i^8entGxQB|B841#Z9c9+2Eo_ z?c+s1_6(l(&5!%Mzt#ueaT~w&bAQdN-~6&K*!z9L-2Vx{KWzN!tRk2JkT3lDrk6g6 zM_+$&ul0DS^{V&%(Cnzr)|ccm#=J%+Abjoh*I|s8wBgE8bjE6|+KZz5;wcMXnk3Yi zM8cY^1BcWL(6dr*qEcoGeaLx_bq;HMZ)I9Uweq{edWZL3U6G^^bI$eu_q8W>(ka%w zHJmkItpJt+bkx>0VK6E>1;nN{2ycB`+cIu82wh+@o)AKyX&Rh$bWPg}Q>{&X&VmTo zBeqO*q}mf*N{V>r=|TWI%_2gnDH4WfMM$5b=vuOL%dLTA3|Yetp-_$llvpE^fl^^+ zpzS(Lgf2zKL!E83;Puu`(Z4|s9$HNHyDyOVqPJHINF7yKZ6r~`KN*lArU~|T~ zhU1^So#D~@*|ifoaTaEE1y<(Z;9eMvM1Wq)Az^JDwq@utG!dFkPjMZ%r3hhd!jT6i zT=JwRasQpSbLh&`-1fGH!yo(y9(CZ+WK(G{X^;t0)9F;El8{Lo)saraE0bpR^gvH2 zcGkL{mKC~yuPRBOCsO%J&bgSWs!9QEr{8U|t-JT`;j*i);v?^SU*F{ULM);5CAv1! zHG$YgT;-^0M?I>}t65!No$&on|8)-EcD%ps_M7kL-+#yN@C&c~Zl3zQ&o2o1foJ{3 z7yMc{10Mf8miLQGEie9s=X_3%QL{B$U*zi>0j@c8A&dL>qruX|Tnb+)V&qC3 zDN))QY7E(8jMtfo?TaFDxh&jtnFJVXN!~yu;GwE2ysvQHQu$hMtEsArs;;q>>p?`B zk~rsj0Af|V?JBRgd7N>0=Vc<|1cX#o#b7YNIY;e1b8~adjYbUWn&stX<`);3UtDD8 zp55%-cYvksJJ_}N|7Y*b$*LRbT`29;GoP(g7Y2gmv9 z2#Ss~Be;x%+qjI&I3xN~1Qlge7DZ%7wnX+NBqWfHKoYX2*Q&brob&tRIrmm~Ithev z<~v^}=k@AWr#scR?p9UL^L)1PbdDZPXXhwNl@hj`6#DHVj4hr76DP~g=lX#-Ee7gY z7Mmlhg5E0B$mji??%q4I_Ga0m_}y{ul(@GaLCg z`w;U$fP(hoJ*25j@(H2`%JVFYQPO}JgU@0(Wlqs@K}7P#;=OFoxeTABL(BqXjP8Rt zQ+XGnKExGhLFT;_*U_Fg46(3v@pw{~qC%d1;*eYz7j24NJYRW-?xSnl&Oh36M=F

^6ndnONaw4JWwViOdDD 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

ePASUH1}+OifMD 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 lu&9# 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

Lta`M?ja%q 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 deadZone) console.log(name, ": has value ", axis.value); + } + } +} // end of constructor + +GamepadState._buttonNames = ["A", "B", "X", "Y", "leftShoulder", "rightShoulder", "leftTrigger", "rightTrigger", + "back", "start", "leftStick", "rightStick", "dpadUp", "dpadDown", "dpadLeft", "dpadRight"]; +GamepadState._axisNames = ["leftStickX", "leftStickY", "rightStickX", "rightStickY"]; diff --git a/Three.js/js/KeyboardState.js b/Three.js/js/KeyboardState.js index ae2e85e..673af8f 100644 --- a/Three.js/js/KeyboardState.js +++ b/Three.js/js/KeyboardState.js @@ -1,6 +1,17 @@ /** * @author Lee Stemkoski - * August 2013 + * + * Usage: + * (1) create a global variable: + * var keyboard = new KeyboardState(); + * (2) during main loop: + * keyboard.update(); + * (3) check state of keys: + * keyboard.down("A") -- true for one update cycle after key is pressed + * keyboard.pressed("A") -- true as long as key is being pressed + * keyboard.up("A") -- true for one update cycle after key is released + * + * See KeyboardState.k object data below for names of keys whose state can be polled */ // initialization diff --git a/Three.js/js/Three66.js b/Three.js/js/Three66.js new file mode 100644 index 0000000..80a5354 --- /dev/null +++ b/Three.js/js/Three66.js @@ -0,0 +1,37760 @@ +/** + * @author mrdoob / http://mrdoob.com/ + * @author Larry Battle / http://bateru.com/news + * @author bhouston / http://exocortex.com + */ + +var THREE = { REVISION: '66' }; + +self.console = self.console || { + + info: function () {}, + log: function () {}, + debug: function () {}, + warn: function () {}, + error: function () {} + +}; + +// http://paulirish.com/2011/requestanimationframe-for-smart-animating/ +// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating + +// requestAnimationFrame polyfill by Erik Möller +// fixes from Paul Irish and Tino Zijdel +// using 'self' instead of 'window' for compatibility with both NodeJS and IE10. +( function () { + + var lastTime = 0; + var vendors = [ 'ms', 'moz', 'webkit', 'o' ]; + + for ( var x = 0; x < vendors.length && !self.requestAnimationFrame; ++ x ) { + + self.requestAnimationFrame = self[ vendors[ x ] + 'RequestAnimationFrame' ]; + self.cancelAnimationFrame = self[ vendors[ x ] + 'CancelAnimationFrame' ] || self[ vendors[ x ] + 'CancelRequestAnimationFrame' ]; + + } + + if ( self.requestAnimationFrame === undefined && self['setTimeout'] !== undefined ) { + + self.requestAnimationFrame = function ( callback ) { + + var currTime = Date.now(), timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) ); + var id = self.setTimeout( function() { callback( currTime + timeToCall ); }, timeToCall ); + lastTime = currTime + timeToCall; + return id; + + }; + + } + + if( self.cancelAnimationFrame === undefined && self['clearTimeout'] !== undefined ) { + + self.cancelAnimationFrame = function ( id ) { self.clearTimeout( id ) }; + + } + +}() ); + +// GL STATE CONSTANTS + +THREE.CullFaceNone = 0; +THREE.CullFaceBack = 1; +THREE.CullFaceFront = 2; +THREE.CullFaceFrontBack = 3; + +THREE.FrontFaceDirectionCW = 0; +THREE.FrontFaceDirectionCCW = 1; + +// SHADOWING TYPES + +THREE.BasicShadowMap = 0; +THREE.PCFShadowMap = 1; +THREE.PCFSoftShadowMap = 2; + +// MATERIAL CONSTANTS + +// side + +THREE.FrontSide = 0; +THREE.BackSide = 1; +THREE.DoubleSide = 2; + +// shading + +THREE.NoShading = 0; +THREE.FlatShading = 1; +THREE.SmoothShading = 2; + +// colors + +THREE.NoColors = 0; +THREE.FaceColors = 1; +THREE.VertexColors = 2; + +// blending modes + +THREE.NoBlending = 0; +THREE.NormalBlending = 1; +THREE.AdditiveBlending = 2; +THREE.SubtractiveBlending = 3; +THREE.MultiplyBlending = 4; +THREE.CustomBlending = 5; + +// custom blending equations +// (numbers start from 100 not to clash with other +// mappings to OpenGL constants defined in Texture.js) + +THREE.AddEquation = 100; +THREE.SubtractEquation = 101; +THREE.ReverseSubtractEquation = 102; + +// custom blending destination factors + +THREE.ZeroFactor = 200; +THREE.OneFactor = 201; +THREE.SrcColorFactor = 202; +THREE.OneMinusSrcColorFactor = 203; +THREE.SrcAlphaFactor = 204; +THREE.OneMinusSrcAlphaFactor = 205; +THREE.DstAlphaFactor = 206; +THREE.OneMinusDstAlphaFactor = 207; + +// custom blending source factors + +//THREE.ZeroFactor = 200; +//THREE.OneFactor = 201; +//THREE.SrcAlphaFactor = 204; +//THREE.OneMinusSrcAlphaFactor = 205; +//THREE.DstAlphaFactor = 206; +//THREE.OneMinusDstAlphaFactor = 207; +THREE.DstColorFactor = 208; +THREE.OneMinusDstColorFactor = 209; +THREE.SrcAlphaSaturateFactor = 210; + + +// TEXTURE CONSTANTS + +THREE.MultiplyOperation = 0; +THREE.MixOperation = 1; +THREE.AddOperation = 2; + +// Mapping modes + +THREE.UVMapping = function () {}; + +THREE.CubeReflectionMapping = function () {}; +THREE.CubeRefractionMapping = function () {}; + +THREE.SphericalReflectionMapping = function () {}; +THREE.SphericalRefractionMapping = function () {}; + +// Wrapping modes + +THREE.RepeatWrapping = 1000; +THREE.ClampToEdgeWrapping = 1001; +THREE.MirroredRepeatWrapping = 1002; + +// Filters + +THREE.NearestFilter = 1003; +THREE.NearestMipMapNearestFilter = 1004; +THREE.NearestMipMapLinearFilter = 1005; +THREE.LinearFilter = 1006; +THREE.LinearMipMapNearestFilter = 1007; +THREE.LinearMipMapLinearFilter = 1008; + +// Data types + +THREE.UnsignedByteType = 1009; +THREE.ByteType = 1010; +THREE.ShortType = 1011; +THREE.UnsignedShortType = 1012; +THREE.IntType = 1013; +THREE.UnsignedIntType = 1014; +THREE.FloatType = 1015; + +// Pixel types + +//THREE.UnsignedByteType = 1009; +THREE.UnsignedShort4444Type = 1016; +THREE.UnsignedShort5551Type = 1017; +THREE.UnsignedShort565Type = 1018; + +// Pixel formats + +THREE.AlphaFormat = 1019; +THREE.RGBFormat = 1020; +THREE.RGBAFormat = 1021; +THREE.LuminanceFormat = 1022; +THREE.LuminanceAlphaFormat = 1023; + +// Compressed texture formats + +THREE.RGB_S3TC_DXT1_Format = 2001; +THREE.RGBA_S3TC_DXT1_Format = 2002; +THREE.RGBA_S3TC_DXT3_Format = 2003; +THREE.RGBA_S3TC_DXT5_Format = 2004; + +/* +// Potential future PVRTC compressed texture formats +THREE.RGB_PVRTC_4BPPV1_Format = 2100; +THREE.RGB_PVRTC_2BPPV1_Format = 2101; +THREE.RGBA_PVRTC_4BPPV1_Format = 2102; +THREE.RGBA_PVRTC_2BPPV1_Format = 2103; +*/ + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Color = function ( color ) { + + if ( arguments.length === 3 ) { + + return this.setRGB( arguments[ 0 ], arguments[ 1 ], arguments[ 2 ] ); + + } + + return this.set( color ) + +}; + +THREE.Color.prototype = { + + constructor: THREE.Color, + + r: 1, g: 1, b: 1, + + set: function ( value ) { + + if ( value instanceof THREE.Color ) { + + this.copy( value ); + + } else if ( typeof value === 'number' ) { + + this.setHex( value ); + + } else if ( typeof value === 'string' ) { + + this.setStyle( value ); + + } + + return this; + + }, + + setHex: function ( hex ) { + + hex = Math.floor( hex ); + + this.r = ( hex >> 16 & 255 ) / 255; + this.g = ( hex >> 8 & 255 ) / 255; + this.b = ( hex & 255 ) / 255; + + return this; + + }, + + setRGB: function ( r, g, b ) { + + this.r = r; + this.g = g; + this.b = b; + + return this; + + }, + + setHSL: function ( h, s, l ) { + + // h,s,l ranges are in 0.0 - 1.0 + + if ( s === 0 ) { + + this.r = this.g = this.b = l; + + } else { + + var hue2rgb = function ( p, q, t ) { + + if ( t < 0 ) t += 1; + if ( t > 1 ) t -= 1; + if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t; + if ( t < 1 / 2 ) return q; + if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t ); + return p; + + }; + + var p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s ); + var q = ( 2 * l ) - p; + + this.r = hue2rgb( q, p, h + 1 / 3 ); + this.g = hue2rgb( q, p, h ); + this.b = hue2rgb( q, p, h - 1 / 3 ); + + } + + return this; + + }, + + setStyle: function ( style ) { + + // rgb(255,0,0) + + if ( /^rgb\((\d+), ?(\d+), ?(\d+)\)$/i.test( style ) ) { + + var color = /^rgb\((\d+), ?(\d+), ?(\d+)\)$/i.exec( style ); + + this.r = Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255; + this.g = Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255; + this.b = Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255; + + return this; + + } + + // rgb(100%,0%,0%) + + if ( /^rgb\((\d+)\%, ?(\d+)\%, ?(\d+)\%\)$/i.test( style ) ) { + + var color = /^rgb\((\d+)\%, ?(\d+)\%, ?(\d+)\%\)$/i.exec( style ); + + this.r = Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100; + this.g = Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100; + this.b = Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100; + + return this; + + } + + // #ff0000 + + if ( /^\#([0-9a-f]{6})$/i.test( style ) ) { + + var color = /^\#([0-9a-f]{6})$/i.exec( style ); + + this.setHex( parseInt( color[ 1 ], 16 ) ); + + return this; + + } + + // #f00 + + if ( /^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.test( style ) ) { + + var color = /^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec( style ); + + this.setHex( parseInt( color[ 1 ] + color[ 1 ] + color[ 2 ] + color[ 2 ] + color[ 3 ] + color[ 3 ], 16 ) ); + + return this; + + } + + // red + + if ( /^(\w+)$/i.test( style ) ) { + + this.setHex( THREE.ColorKeywords[ style ] ); + + return this; + + } + + + }, + + copy: function ( color ) { + + this.r = color.r; + this.g = color.g; + this.b = color.b; + + return this; + + }, + + copyGammaToLinear: function ( color ) { + + this.r = color.r * color.r; + this.g = color.g * color.g; + this.b = color.b * color.b; + + return this; + + }, + + copyLinearToGamma: function ( color ) { + + this.r = Math.sqrt( color.r ); + this.g = Math.sqrt( color.g ); + this.b = Math.sqrt( color.b ); + + return this; + + }, + + convertGammaToLinear: function () { + + var r = this.r, g = this.g, b = this.b; + + this.r = r * r; + this.g = g * g; + this.b = b * b; + + return this; + + }, + + convertLinearToGamma: function () { + + this.r = Math.sqrt( this.r ); + this.g = Math.sqrt( this.g ); + this.b = Math.sqrt( this.b ); + + return this; + + }, + + getHex: function () { + + return ( this.r * 255 ) << 16 ^ ( this.g * 255 ) << 8 ^ ( this.b * 255 ) << 0; + + }, + + getHexString: function () { + + return ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 ); + + }, + + getHSL: function ( optionalTarget ) { + + // h,s,l ranges are in 0.0 - 1.0 + + var hsl = optionalTarget || { h: 0, s: 0, l: 0 }; + + var r = this.r, g = this.g, b = this.b; + + var max = Math.max( r, g, b ); + var min = Math.min( r, g, b ); + + var hue, saturation; + var lightness = ( min + max ) / 2.0; + + if ( min === max ) { + + hue = 0; + saturation = 0; + + } else { + + var delta = max - min; + + saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min ); + + switch ( max ) { + + case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break; + case g: hue = ( b - r ) / delta + 2; break; + case b: hue = ( r - g ) / delta + 4; break; + + } + + hue /= 6; + + } + + hsl.h = hue; + hsl.s = saturation; + hsl.l = lightness; + + return hsl; + + }, + + getStyle: function () { + + return 'rgb(' + ( ( this.r * 255 ) | 0 ) + ',' + ( ( this.g * 255 ) | 0 ) + ',' + ( ( this.b * 255 ) | 0 ) + ')'; + + }, + + offsetHSL: function ( h, s, l ) { + + var hsl = this.getHSL(); + + hsl.h += h; hsl.s += s; hsl.l += l; + + this.setHSL( hsl.h, hsl.s, hsl.l ); + + return this; + + }, + + add: function ( color ) { + + this.r += color.r; + this.g += color.g; + this.b += color.b; + + return this; + + }, + + addColors: function ( color1, color2 ) { + + this.r = color1.r + color2.r; + this.g = color1.g + color2.g; + this.b = color1.b + color2.b; + + return this; + + }, + + addScalar: function ( s ) { + + this.r += s; + this.g += s; + this.b += s; + + return this; + + }, + + multiply: function ( color ) { + + this.r *= color.r; + this.g *= color.g; + this.b *= color.b; + + return this; + + }, + + multiplyScalar: function ( s ) { + + this.r *= s; + this.g *= s; + this.b *= s; + + return this; + + }, + + lerp: function ( color, alpha ) { + + this.r += ( color.r - this.r ) * alpha; + this.g += ( color.g - this.g ) * alpha; + this.b += ( color.b - this.b ) * alpha; + + return this; + + }, + + equals: function ( c ) { + + return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b ); + + }, + + fromArray: function ( array ) { + + this.r = array[ 0 ]; + this.g = array[ 1 ]; + this.b = array[ 2 ]; + + return this; + + }, + + toArray: function () { + + return [ this.r, this.g, this.b ]; + + }, + + clone: function () { + + return new THREE.Color().setRGB( this.r, this.g, this.b ); + + } + +}; + +THREE.ColorKeywords = { "aliceblue": 0xF0F8FF, "antiquewhite": 0xFAEBD7, "aqua": 0x00FFFF, "aquamarine": 0x7FFFD4, "azure": 0xF0FFFF, +"beige": 0xF5F5DC, "bisque": 0xFFE4C4, "black": 0x000000, "blanchedalmond": 0xFFEBCD, "blue": 0x0000FF, "blueviolet": 0x8A2BE2, +"brown": 0xA52A2A, "burlywood": 0xDEB887, "cadetblue": 0x5F9EA0, "chartreuse": 0x7FFF00, "chocolate": 0xD2691E, "coral": 0xFF7F50, +"cornflowerblue": 0x6495ED, "cornsilk": 0xFFF8DC, "crimson": 0xDC143C, "cyan": 0x00FFFF, "darkblue": 0x00008B, "darkcyan": 0x008B8B, +"darkgoldenrod": 0xB8860B, "darkgray": 0xA9A9A9, "darkgreen": 0x006400, "darkgrey": 0xA9A9A9, "darkkhaki": 0xBDB76B, "darkmagenta": 0x8B008B, +"darkolivegreen": 0x556B2F, "darkorange": 0xFF8C00, "darkorchid": 0x9932CC, "darkred": 0x8B0000, "darksalmon": 0xE9967A, "darkseagreen": 0x8FBC8F, +"darkslateblue": 0x483D8B, "darkslategray": 0x2F4F4F, "darkslategrey": 0x2F4F4F, "darkturquoise": 0x00CED1, "darkviolet": 0x9400D3, +"deeppink": 0xFF1493, "deepskyblue": 0x00BFFF, "dimgray": 0x696969, "dimgrey": 0x696969, "dodgerblue": 0x1E90FF, "firebrick": 0xB22222, +"floralwhite": 0xFFFAF0, "forestgreen": 0x228B22, "fuchsia": 0xFF00FF, "gainsboro": 0xDCDCDC, "ghostwhite": 0xF8F8FF, "gold": 0xFFD700, +"goldenrod": 0xDAA520, "gray": 0x808080, "green": 0x008000, "greenyellow": 0xADFF2F, "grey": 0x808080, "honeydew": 0xF0FFF0, "hotpink": 0xFF69B4, +"indianred": 0xCD5C5C, "indigo": 0x4B0082, "ivory": 0xFFFFF0, "khaki": 0xF0E68C, "lavender": 0xE6E6FA, "lavenderblush": 0xFFF0F5, "lawngreen": 0x7CFC00, +"lemonchiffon": 0xFFFACD, "lightblue": 0xADD8E6, "lightcoral": 0xF08080, "lightcyan": 0xE0FFFF, "lightgoldenrodyellow": 0xFAFAD2, "lightgray": 0xD3D3D3, +"lightgreen": 0x90EE90, "lightgrey": 0xD3D3D3, "lightpink": 0xFFB6C1, "lightsalmon": 0xFFA07A, "lightseagreen": 0x20B2AA, "lightskyblue": 0x87CEFA, +"lightslategray": 0x778899, "lightslategrey": 0x778899, "lightsteelblue": 0xB0C4DE, "lightyellow": 0xFFFFE0, "lime": 0x00FF00, "limegreen": 0x32CD32, +"linen": 0xFAF0E6, "magenta": 0xFF00FF, "maroon": 0x800000, "mediumaquamarine": 0x66CDAA, "mediumblue": 0x0000CD, "mediumorchid": 0xBA55D3, +"mediumpurple": 0x9370DB, "mediumseagreen": 0x3CB371, "mediumslateblue": 0x7B68EE, "mediumspringgreen": 0x00FA9A, "mediumturquoise": 0x48D1CC, +"mediumvioletred": 0xC71585, "midnightblue": 0x191970, "mintcream": 0xF5FFFA, "mistyrose": 0xFFE4E1, "moccasin": 0xFFE4B5, "navajowhite": 0xFFDEAD, +"navy": 0x000080, "oldlace": 0xFDF5E6, "olive": 0x808000, "olivedrab": 0x6B8E23, "orange": 0xFFA500, "orangered": 0xFF4500, "orchid": 0xDA70D6, +"palegoldenrod": 0xEEE8AA, "palegreen": 0x98FB98, "paleturquoise": 0xAFEEEE, "palevioletred": 0xDB7093, "papayawhip": 0xFFEFD5, "peachpuff": 0xFFDAB9, +"peru": 0xCD853F, "pink": 0xFFC0CB, "plum": 0xDDA0DD, "powderblue": 0xB0E0E6, "purple": 0x800080, "red": 0xFF0000, "rosybrown": 0xBC8F8F, +"royalblue": 0x4169E1, "saddlebrown": 0x8B4513, "salmon": 0xFA8072, "sandybrown": 0xF4A460, "seagreen": 0x2E8B57, "seashell": 0xFFF5EE, +"sienna": 0xA0522D, "silver": 0xC0C0C0, "skyblue": 0x87CEEB, "slateblue": 0x6A5ACD, "slategray": 0x708090, "slategrey": 0x708090, "snow": 0xFFFAFA, +"springgreen": 0x00FF7F, "steelblue": 0x4682B4, "tan": 0xD2B48C, "teal": 0x008080, "thistle": 0xD8BFD8, "tomato": 0xFF6347, "turquoise": 0x40E0D0, +"violet": 0xEE82EE, "wheat": 0xF5DEB3, "white": 0xFFFFFF, "whitesmoke": 0xF5F5F5, "yellow": 0xFFFF00, "yellowgreen": 0x9ACD32 }; + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + * @author WestLangley / http://github.com/WestLangley + * @author bhouston / http://exocortex.com + */ + +THREE.Quaternion = function ( x, y, z, w ) { + + this._x = x || 0; + this._y = y || 0; + this._z = z || 0; + this._w = ( w !== undefined ) ? w : 1; + +}; + +THREE.Quaternion.prototype = { + + constructor: THREE.Quaternion, + + _x: 0,_y: 0, _z: 0, _w: 0, + + _euler: undefined, + + _updateEuler: function ( callback ) { + + if ( this._euler !== undefined ) { + + this._euler.setFromQuaternion( this, undefined, false ); + + } + + }, + + get x () { + + return this._x; + + }, + + set x ( value ) { + + this._x = value; + this._updateEuler(); + + }, + + get y () { + + return this._y; + + }, + + set y ( value ) { + + this._y = value; + this._updateEuler(); + + }, + + get z () { + + return this._z; + + }, + + set z ( value ) { + + this._z = value; + this._updateEuler(); + + }, + + get w () { + + return this._w; + + }, + + set w ( value ) { + + this._w = value; + this._updateEuler(); + + }, + + set: function ( x, y, z, w ) { + + this._x = x; + this._y = y; + this._z = z; + this._w = w; + + this._updateEuler(); + + return this; + + }, + + copy: function ( quaternion ) { + + this._x = quaternion._x; + this._y = quaternion._y; + this._z = quaternion._z; + this._w = quaternion._w; + + this._updateEuler(); + + return this; + + }, + + setFromEuler: function ( euler, update ) { + + if ( euler instanceof THREE.Euler === false ) { + + throw new Error( 'ERROR: Quaternion\'s .setFromEuler() now expects a Euler rotation rather than a Vector3 and order. Please update your code.' ); + } + + // http://www.mathworks.com/matlabcentral/fileexchange/ + // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ + // content/SpinCalc.m + + var c1 = Math.cos( euler._x / 2 ); + var c2 = Math.cos( euler._y / 2 ); + var c3 = Math.cos( euler._z / 2 ); + var s1 = Math.sin( euler._x / 2 ); + var s2 = Math.sin( euler._y / 2 ); + var s3 = Math.sin( euler._z / 2 ); + + if ( euler.order === 'XYZ' ) { + + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + + } else if ( euler.order === 'YXZ' ) { + + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + + } else if ( euler.order === 'ZXY' ) { + + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + + } else if ( euler.order === 'ZYX' ) { + + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + + } else if ( euler.order === 'YZX' ) { + + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + + } else if ( euler.order === 'XZY' ) { + + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + + } + + if ( update !== false ) this._updateEuler(); + + return this; + + }, + + setFromAxisAngle: function ( axis, angle ) { + + // from http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm + // axis have to be normalized + + var halfAngle = angle / 2, s = Math.sin( halfAngle ); + + this._x = axis.x * s; + this._y = axis.y * s; + this._z = axis.z * s; + this._w = Math.cos( halfAngle ); + + this._updateEuler(); + + return this; + + }, + + setFromRotationMatrix: function ( m ) { + + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm + + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + + var te = m.elements, + + m11 = te[0], m12 = te[4], m13 = te[8], + m21 = te[1], m22 = te[5], m23 = te[9], + m31 = te[2], m32 = te[6], m33 = te[10], + + trace = m11 + m22 + m33, + s; + + if ( trace > 0 ) { + + s = 0.5 / Math.sqrt( trace + 1.0 ); + + this._w = 0.25 / s; + this._x = ( m32 - m23 ) * s; + this._y = ( m13 - m31 ) * s; + this._z = ( m21 - m12 ) * s; + + } else if ( m11 > m22 && m11 > m33 ) { + + s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); + + this._w = (m32 - m23 ) / s; + this._x = 0.25 * s; + this._y = (m12 + m21 ) / s; + this._z = (m13 + m31 ) / s; + + } else if ( m22 > m33 ) { + + s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 ); + + this._w = (m13 - m31 ) / s; + this._x = (m12 + m21 ) / s; + this._y = 0.25 * s; + this._z = (m23 + m32 ) / s; + + } else { + + s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 ); + + this._w = ( m21 - m12 ) / s; + this._x = ( m13 + m31 ) / s; + this._y = ( m23 + m32 ) / s; + this._z = 0.25 * s; + + } + + this._updateEuler(); + + return this; + + }, + + inverse: function () { + + this.conjugate().normalize(); + + return this; + + }, + + conjugate: function () { + + this._x *= -1; + this._y *= -1; + this._z *= -1; + + this._updateEuler(); + + return this; + + }, + + lengthSq: function () { + + return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; + + }, + + length: function () { + + return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w ); + + }, + + normalize: function () { + + var l = this.length(); + + if ( l === 0 ) { + + this._x = 0; + this._y = 0; + this._z = 0; + this._w = 1; + + } else { + + l = 1 / l; + + this._x = this._x * l; + this._y = this._y * l; + this._z = this._z * l; + this._w = this._w * l; + + } + + return this; + + }, + + multiply: function ( q, p ) { + + if ( p !== undefined ) { + + console.warn( 'DEPRECATED: Quaternion\'s .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' ); + return this.multiplyQuaternions( q, p ); + + } + + return this.multiplyQuaternions( this, q ); + + }, + + multiplyQuaternions: function ( a, b ) { + + // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm + + var qax = a._x, qay = a._y, qaz = a._z, qaw = a._w; + var qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w; + + this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; + this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; + this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; + this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; + + this._updateEuler(); + + return this; + + }, + + multiplyVector3: function ( vector ) { + + console.warn( 'DEPRECATED: Quaternion\'s .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' ); + return vector.applyQuaternion( this ); + + }, + + slerp: function ( qb, t ) { + + var x = this._x, y = this._y, z = this._z, w = this._w; + + // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ + + var cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; + + if ( cosHalfTheta < 0 ) { + + this._w = -qb._w; + this._x = -qb._x; + this._y = -qb._y; + this._z = -qb._z; + + cosHalfTheta = -cosHalfTheta; + + } else { + + this.copy( qb ); + + } + + if ( cosHalfTheta >= 1.0 ) { + + this._w = w; + this._x = x; + this._y = y; + this._z = z; + + return this; + + } + + var halfTheta = Math.acos( cosHalfTheta ); + var sinHalfTheta = Math.sqrt( 1.0 - cosHalfTheta * cosHalfTheta ); + + if ( Math.abs( sinHalfTheta ) < 0.001 ) { + + this._w = 0.5 * ( w + this._w ); + this._x = 0.5 * ( x + this._x ); + this._y = 0.5 * ( y + this._y ); + this._z = 0.5 * ( z + this._z ); + + return this; + + } + + var ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta, + ratioB = Math.sin( t * halfTheta ) / sinHalfTheta; + + this._w = ( w * ratioA + this._w * ratioB ); + this._x = ( x * ratioA + this._x * ratioB ); + this._y = ( y * ratioA + this._y * ratioB ); + this._z = ( z * ratioA + this._z * ratioB ); + + this._updateEuler(); + + return this; + + }, + + equals: function ( quaternion ) { + + return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); + + }, + + fromArray: function ( array ) { + + this._x = array[ 0 ]; + this._y = array[ 1 ]; + this._z = array[ 2 ]; + this._w = array[ 3 ]; + + this._updateEuler(); + + return this; + + }, + + toArray: function () { + + return [ this._x, this._y, this._z, this._w ]; + + }, + + clone: function () { + + return new THREE.Quaternion( this._x, this._y, this._z, this._w ); + + } + +}; + +THREE.Quaternion.slerp = function ( qa, qb, qm, t ) { + + return qm.copy( qa ).slerp( qb, t ); + +} + +/** + * @author mrdoob / http://mrdoob.com/ + * @author philogb / http://blog.thejit.org/ + * @author egraether / http://egraether.com/ + * @author zz85 / http://www.lab4games.net/zz85/blog + */ + +THREE.Vector2 = function ( x, y ) { + + this.x = x || 0; + this.y = y || 0; + +}; + +THREE.Vector2.prototype = { + + constructor: THREE.Vector2, + + set: function ( x, y ) { + + this.x = x; + this.y = y; + + return this; + + }, + + setX: function ( x ) { + + this.x = x; + + return this; + + }, + + setY: function ( y ) { + + this.y = y; + + return this; + + }, + + + setComponent: function ( index, value ) { + + switch ( index ) { + + case 0: this.x = value; break; + case 1: this.y = value; break; + default: throw new Error( "index is out of range: " + index ); + + } + + }, + + getComponent: function ( index ) { + + switch ( index ) { + + case 0: return this.x; + case 1: return this.y; + default: throw new Error( "index is out of range: " + index ); + + } + + }, + + copy: function ( v ) { + + this.x = v.x; + this.y = v.y; + + return this; + + }, + + add: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'DEPRECATED: Vector2\'s .add() now only accepts one argument. Use .addVectors( a, b ) instead.' ); + return this.addVectors( v, w ); + + } + + this.x += v.x; + this.y += v.y; + + return this; + + }, + + addVectors: function ( a, b ) { + + this.x = a.x + b.x; + this.y = a.y + b.y; + + return this; + + }, + + addScalar: function ( s ) { + + this.x += s; + this.y += s; + + return this; + + }, + + sub: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'DEPRECATED: Vector2\'s .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' ); + return this.subVectors( v, w ); + + } + + this.x -= v.x; + this.y -= v.y; + + return this; + + }, + + subVectors: function ( a, b ) { + + this.x = a.x - b.x; + this.y = a.y - b.y; + + return this; + + }, + + multiplyScalar: function ( s ) { + + this.x *= s; + this.y *= s; + + return this; + + }, + + divideScalar: function ( scalar ) { + + if ( scalar !== 0 ) { + + var invScalar = 1 / scalar; + + this.x *= invScalar; + this.y *= invScalar; + + } else { + + this.x = 0; + this.y = 0; + + } + + return this; + + }, + + min: function ( v ) { + + if ( this.x > v.x ) { + + this.x = v.x; + + } + + if ( this.y > v.y ) { + + this.y = v.y; + + } + + return this; + + }, + + max: function ( v ) { + + if ( this.x < v.x ) { + + this.x = v.x; + + } + + if ( this.y < v.y ) { + + this.y = v.y; + + } + + return this; + + }, + + clamp: function ( min, max ) { + + // This function assumes min < max, if this assumption isn't true it will not operate correctly + + if ( this.x < min.x ) { + + this.x = min.x; + + } else if ( this.x > max.x ) { + + this.x = max.x; + + } + + if ( this.y < min.y ) { + + this.y = min.y; + + } else if ( this.y > max.y ) { + + this.y = max.y; + + } + + return this; + }, + + clampScalar: ( function () { + + var min, max; + + return function ( minVal, maxVal ) { + + if ( min === undefined ) { + + min = new THREE.Vector2(); + max = new THREE.Vector2(); + + } + + min.set( minVal, minVal ); + max.set( maxVal, maxVal ); + + return this.clamp( min, max ); + + }; + + } )(), + + floor: function () { + + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); + + return this; + + }, + + ceil: function () { + + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); + + return this; + + }, + + round: function () { + + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); + + return this; + + }, + + roundToZero: function () { + + this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); + this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); + + return this; + + }, + + negate: function () { + + return this.multiplyScalar( - 1 ); + + }, + + dot: function ( v ) { + + return this.x * v.x + this.y * v.y; + + }, + + lengthSq: function () { + + return this.x * this.x + this.y * this.y; + + }, + + length: function () { + + return Math.sqrt( this.x * this.x + this.y * this.y ); + + }, + + normalize: function () { + + return this.divideScalar( this.length() ); + + }, + + distanceTo: function ( v ) { + + return Math.sqrt( this.distanceToSquared( v ) ); + + }, + + distanceToSquared: function ( v ) { + + var dx = this.x - v.x, dy = this.y - v.y; + return dx * dx + dy * dy; + + }, + + setLength: function ( l ) { + + var oldLength = this.length(); + + if ( oldLength !== 0 && l !== oldLength ) { + + this.multiplyScalar( l / oldLength ); + } + + return this; + + }, + + lerp: function ( v, alpha ) { + + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; + + return this; + + }, + + equals: function( v ) { + + return ( ( v.x === this.x ) && ( v.y === this.y ) ); + + }, + + fromArray: function ( array ) { + + this.x = array[ 0 ]; + this.y = array[ 1 ]; + + return this; + + }, + + toArray: function () { + + return [ this.x, this.y ]; + + }, + + clone: function () { + + return new THREE.Vector2( this.x, this.y ); + + } + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author *kile / http://kile.stravaganza.org/ + * @author philogb / http://blog.thejit.org/ + * @author mikael emtinger / http://gomo.se/ + * @author egraether / http://egraether.com/ + * @author WestLangley / http://github.com/WestLangley + */ + +THREE.Vector3 = function ( x, y, z ) { + + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; + +}; + +THREE.Vector3.prototype = { + + constructor: THREE.Vector3, + + set: function ( x, y, z ) { + + this.x = x; + this.y = y; + this.z = z; + + return this; + + }, + + setX: function ( x ) { + + this.x = x; + + return this; + + }, + + setY: function ( y ) { + + this.y = y; + + return this; + + }, + + setZ: function ( z ) { + + this.z = z; + + return this; + + }, + + setComponent: function ( index, value ) { + + switch ( index ) { + + case 0: this.x = value; break; + case 1: this.y = value; break; + case 2: this.z = value; break; + default: throw new Error( "index is out of range: " + index ); + + } + + }, + + getComponent: function ( index ) { + + switch ( index ) { + + case 0: return this.x; + case 1: return this.y; + case 2: return this.z; + default: throw new Error( "index is out of range: " + index ); + + } + + }, + + copy: function ( v ) { + + this.x = v.x; + this.y = v.y; + this.z = v.z; + + return this; + + }, + + add: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'DEPRECATED: Vector3\'s .add() now only accepts one argument. Use .addVectors( a, b ) instead.' ); + return this.addVectors( v, w ); + + } + + this.x += v.x; + this.y += v.y; + this.z += v.z; + + return this; + + }, + + addScalar: function ( s ) { + + this.x += s; + this.y += s; + this.z += s; + + return this; + + }, + + addVectors: function ( a, b ) { + + this.x = a.x + b.x; + this.y = a.y + b.y; + this.z = a.z + b.z; + + return this; + + }, + + sub: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'DEPRECATED: Vector3\'s .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' ); + return this.subVectors( v, w ); + + } + + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; + + return this; + + }, + + subVectors: function ( a, b ) { + + this.x = a.x - b.x; + this.y = a.y - b.y; + this.z = a.z - b.z; + + return this; + + }, + + multiply: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'DEPRECATED: Vector3\'s .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.' ); + return this.multiplyVectors( v, w ); + + } + + this.x *= v.x; + this.y *= v.y; + this.z *= v.z; + + return this; + + }, + + multiplyScalar: function ( scalar ) { + + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; + + return this; + + }, + + multiplyVectors: function ( a, b ) { + + this.x = a.x * b.x; + this.y = a.y * b.y; + this.z = a.z * b.z; + + return this; + + }, + + applyEuler: function () { + + var quaternion; + + return function ( euler ) { + + if ( euler instanceof THREE.Euler === false ) { + + console.error( 'ERROR: Vector3\'s .applyEuler() now expects a Euler rotation rather than a Vector3 and order. Please update your code.' ); + + } + + if ( quaternion === undefined ) quaternion = new THREE.Quaternion(); + + this.applyQuaternion( quaternion.setFromEuler( euler ) ); + + return this; + + }; + + }(), + + applyAxisAngle: function () { + + var quaternion; + + return function ( axis, angle ) { + + if ( quaternion === undefined ) quaternion = new THREE.Quaternion(); + + this.applyQuaternion( quaternion.setFromAxisAngle( axis, angle ) ); + + return this; + + }; + + }(), + + applyMatrix3: function ( m ) { + + var x = this.x; + var y = this.y; + var z = this.z; + + var e = m.elements; + + this.x = e[0] * x + e[3] * y + e[6] * z; + this.y = e[1] * x + e[4] * y + e[7] * z; + this.z = e[2] * x + e[5] * y + e[8] * z; + + return this; + + }, + + applyMatrix4: function ( m ) { + + // input: THREE.Matrix4 affine matrix + + var x = this.x, y = this.y, z = this.z; + + var e = m.elements; + + this.x = e[0] * x + e[4] * y + e[8] * z + e[12]; + this.y = e[1] * x + e[5] * y + e[9] * z + e[13]; + this.z = e[2] * x + e[6] * y + e[10] * z + e[14]; + + return this; + + }, + + applyProjection: function ( m ) { + + // input: THREE.Matrix4 projection matrix + + var x = this.x, y = this.y, z = this.z; + + var e = m.elements; + var d = 1 / ( e[3] * x + e[7] * y + e[11] * z + e[15] ); // perspective divide + + this.x = ( e[0] * x + e[4] * y + e[8] * z + e[12] ) * d; + this.y = ( e[1] * x + e[5] * y + e[9] * z + e[13] ) * d; + this.z = ( e[2] * x + e[6] * y + e[10] * z + e[14] ) * d; + + return this; + + }, + + applyQuaternion: function ( q ) { + + var x = this.x; + var y = this.y; + var z = this.z; + + var qx = q.x; + var qy = q.y; + var qz = q.z; + var qw = q.w; + + // calculate quat * vector + + var ix = qw * x + qy * z - qz * y; + var iy = qw * y + qz * x - qx * z; + var iz = qw * z + qx * y - qy * x; + var iw = -qx * x - qy * y - qz * z; + + // calculate result * inverse quat + + this.x = ix * qw + iw * -qx + iy * -qz - iz * -qy; + this.y = iy * qw + iw * -qy + iz * -qx - ix * -qz; + this.z = iz * qw + iw * -qz + ix * -qy - iy * -qx; + + return this; + + }, + + transformDirection: function ( m ) { + + // input: THREE.Matrix4 affine matrix + // vector interpreted as a direction + + var x = this.x, y = this.y, z = this.z; + + var e = m.elements; + + this.x = e[0] * x + e[4] * y + e[8] * z; + this.y = e[1] * x + e[5] * y + e[9] * z; + this.z = e[2] * x + e[6] * y + e[10] * z; + + this.normalize(); + + return this; + + }, + + divide: function ( v ) { + + this.x /= v.x; + this.y /= v.y; + this.z /= v.z; + + return this; + + }, + + divideScalar: function ( scalar ) { + + if ( scalar !== 0 ) { + + var invScalar = 1 / scalar; + + this.x *= invScalar; + this.y *= invScalar; + this.z *= invScalar; + + } else { + + this.x = 0; + this.y = 0; + this.z = 0; + + } + + return this; + + }, + + min: function ( v ) { + + if ( this.x > v.x ) { + + this.x = v.x; + + } + + if ( this.y > v.y ) { + + this.y = v.y; + + } + + if ( this.z > v.z ) { + + this.z = v.z; + + } + + return this; + + }, + + max: function ( v ) { + + if ( this.x < v.x ) { + + this.x = v.x; + + } + + if ( this.y < v.y ) { + + this.y = v.y; + + } + + if ( this.z < v.z ) { + + this.z = v.z; + + } + + return this; + + }, + + clamp: function ( min, max ) { + + // This function assumes min < max, if this assumption isn't true it will not operate correctly + + if ( this.x < min.x ) { + + this.x = min.x; + + } else if ( this.x > max.x ) { + + this.x = max.x; + + } + + if ( this.y < min.y ) { + + this.y = min.y; + + } else if ( this.y > max.y ) { + + this.y = max.y; + + } + + if ( this.z < min.z ) { + + this.z = min.z; + + } else if ( this.z > max.z ) { + + this.z = max.z; + + } + + return this; + + }, + + clampScalar: ( function () { + + var min, max; + + return function ( minVal, maxVal ) { + + if ( min === undefined ) { + + min = new THREE.Vector3(); + max = new THREE.Vector3(); + + } + + min.set( minVal, minVal, minVal ); + max.set( maxVal, maxVal, maxVal ); + + return this.clamp( min, max ); + + }; + + } )(), + + floor: function () { + + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); + this.z = Math.floor( this.z ); + + return this; + + }, + + ceil: function () { + + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); + this.z = Math.ceil( this.z ); + + return this; + + }, + + round: function () { + + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); + this.z = Math.round( this.z ); + + return this; + + }, + + roundToZero: function () { + + this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); + this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); + this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z ); + + return this; + + }, + + negate: function () { + + return this.multiplyScalar( - 1 ); + + }, + + dot: function ( v ) { + + return this.x * v.x + this.y * v.y + this.z * v.z; + + }, + + lengthSq: function () { + + return this.x * this.x + this.y * this.y + this.z * this.z; + + }, + + length: function () { + + return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); + + }, + + lengthManhattan: function () { + + return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); + + }, + + normalize: function () { + + return this.divideScalar( this.length() ); + + }, + + setLength: function ( l ) { + + var oldLength = this.length(); + + if ( oldLength !== 0 && l !== oldLength ) { + + this.multiplyScalar( l / oldLength ); + } + + return this; + + }, + + lerp: function ( v, alpha ) { + + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; + this.z += ( v.z - this.z ) * alpha; + + return this; + + }, + + cross: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'DEPRECATED: Vector3\'s .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.' ); + return this.crossVectors( v, w ); + + } + + var x = this.x, y = this.y, z = this.z; + + this.x = y * v.z - z * v.y; + this.y = z * v.x - x * v.z; + this.z = x * v.y - y * v.x; + + return this; + + }, + + crossVectors: function ( a, b ) { + + var ax = a.x, ay = a.y, az = a.z; + var bx = b.x, by = b.y, bz = b.z; + + this.x = ay * bz - az * by; + this.y = az * bx - ax * bz; + this.z = ax * by - ay * bx; + + return this; + + }, + + projectOnVector: function () { + + var v1, dot; + + return function ( vector ) { + + if ( v1 === undefined ) v1 = new THREE.Vector3(); + + v1.copy( vector ).normalize(); + + dot = this.dot( v1 ); + + return this.copy( v1 ).multiplyScalar( dot ); + + }; + + }(), + + projectOnPlane: function () { + + var v1; + + return function ( planeNormal ) { + + if ( v1 === undefined ) v1 = new THREE.Vector3(); + + v1.copy( this ).projectOnVector( planeNormal ); + + return this.sub( v1 ); + + } + + }(), + + reflect: function () { + + // reflect incident vector off plane orthogonal to normal + // normal is assumed to have unit length + + var v1; + + return function ( normal ) { + + if ( v1 === undefined ) v1 = new THREE.Vector3(); + + return this.sub( v1.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); + + } + + }(), + + angleTo: function ( v ) { + + var theta = this.dot( v ) / ( this.length() * v.length() ); + + // clamp, to handle numerical problems + + return Math.acos( THREE.Math.clamp( theta, -1, 1 ) ); + + }, + + distanceTo: function ( v ) { + + return Math.sqrt( this.distanceToSquared( v ) ); + + }, + + distanceToSquared: function ( v ) { + + var dx = this.x - v.x; + var dy = this.y - v.y; + var dz = this.z - v.z; + + return dx * dx + dy * dy + dz * dz; + + }, + + setEulerFromRotationMatrix: function ( m, order ) { + + console.error( "REMOVED: Vector3\'s setEulerFromRotationMatrix has been removed in favor of Euler.setFromRotationMatrix(), please update your code."); + + }, + + setEulerFromQuaternion: function ( q, order ) { + + console.error( "REMOVED: Vector3\'s setEulerFromQuaternion: has been removed in favor of Euler.setFromQuaternion(), please update your code."); + + }, + + getPositionFromMatrix: function ( m ) { + + console.warn( "DEPRECATED: Vector3\'s .getPositionFromMatrix() has been renamed to .setFromMatrixPosition(). Please update your code." ); + + return this.setFromMatrixPosition( m ); + + }, + + getScaleFromMatrix: function ( m ) { + + console.warn( "DEPRECATED: Vector3\'s .getScaleFromMatrix() has been renamed to .setFromMatrixScale(). Please update your code." ); + + return this.setFromMatrixScale( m ); + }, + + getColumnFromMatrix: function ( index, matrix ) { + + console.warn( "DEPRECATED: Vector3\'s .getColumnFromMatrix() has been renamed to .setFromMatrixColumn(). Please update your code." ); + + return this.setFromMatrixColumn( index, matrix ); + + }, + + setFromMatrixPosition: function ( m ) { + + this.x = m.elements[ 12 ]; + this.y = m.elements[ 13 ]; + this.z = m.elements[ 14 ]; + + return this; + + }, + + setFromMatrixScale: function ( m ) { + + var sx = this.set( m.elements[ 0 ], m.elements[ 1 ], m.elements[ 2 ] ).length(); + var sy = this.set( m.elements[ 4 ], m.elements[ 5 ], m.elements[ 6 ] ).length(); + var sz = this.set( m.elements[ 8 ], m.elements[ 9 ], m.elements[ 10 ] ).length(); + + this.x = sx; + this.y = sy; + this.z = sz; + + return this; + }, + + setFromMatrixColumn: function ( index, matrix ) { + + var offset = index * 4; + + var me = matrix.elements; + + this.x = me[ offset ]; + this.y = me[ offset + 1 ]; + this.z = me[ offset + 2 ]; + + return this; + + }, + + equals: function ( v ) { + + return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); + + }, + + fromArray: function ( array ) { + + this.x = array[ 0 ]; + this.y = array[ 1 ]; + this.z = array[ 2 ]; + + return this; + + }, + + toArray: function () { + + return [ this.x, this.y, this.z ]; + + }, + + clone: function () { + + return new THREE.Vector3( this.x, this.y, this.z ); + + } + +}; +/** + * @author supereggbert / http://www.paulbrunt.co.uk/ + * @author philogb / http://blog.thejit.org/ + * @author mikael emtinger / http://gomo.se/ + * @author egraether / http://egraether.com/ + * @author WestLangley / http://github.com/WestLangley + */ + +THREE.Vector4 = function ( x, y, z, w ) { + + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; + this.w = ( w !== undefined ) ? w : 1; + +}; + +THREE.Vector4.prototype = { + + constructor: THREE.Vector4, + + set: function ( x, y, z, w ) { + + this.x = x; + this.y = y; + this.z = z; + this.w = w; + + return this; + + }, + + setX: function ( x ) { + + this.x = x; + + return this; + + }, + + setY: function ( y ) { + + this.y = y; + + return this; + + }, + + setZ: function ( z ) { + + this.z = z; + + return this; + + }, + + setW: function ( w ) { + + this.w = w; + + return this; + + }, + + setComponent: function ( index, value ) { + + switch ( index ) { + + case 0: this.x = value; break; + case 1: this.y = value; break; + case 2: this.z = value; break; + case 3: this.w = value; break; + default: throw new Error( "index is out of range: " + index ); + + } + + }, + + getComponent: function ( index ) { + + switch ( index ) { + + case 0: return this.x; + case 1: return this.y; + case 2: return this.z; + case 3: return this.w; + default: throw new Error( "index is out of range: " + index ); + + } + + }, + + copy: function ( v ) { + + this.x = v.x; + this.y = v.y; + this.z = v.z; + this.w = ( v.w !== undefined ) ? v.w : 1; + + return this; + + }, + + add: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'DEPRECATED: Vector4\'s .add() now only accepts one argument. Use .addVectors( a, b ) instead.' ); + return this.addVectors( v, w ); + + } + + this.x += v.x; + this.y += v.y; + this.z += v.z; + this.w += v.w; + + return this; + + }, + + addScalar: function ( s ) { + + this.x += s; + this.y += s; + this.z += s; + this.w += s; + + return this; + + }, + + addVectors: function ( a, b ) { + + this.x = a.x + b.x; + this.y = a.y + b.y; + this.z = a.z + b.z; + this.w = a.w + b.w; + + return this; + + }, + + sub: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'DEPRECATED: Vector4\'s .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' ); + return this.subVectors( v, w ); + + } + + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; + this.w -= v.w; + + return this; + + }, + + subVectors: function ( a, b ) { + + this.x = a.x - b.x; + this.y = a.y - b.y; + this.z = a.z - b.z; + this.w = a.w - b.w; + + return this; + + }, + + multiplyScalar: function ( scalar ) { + + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; + this.w *= scalar; + + return this; + + }, + + applyMatrix4: function ( m ) { + + var x = this.x; + var y = this.y; + var z = this.z; + var w = this.w; + + var e = m.elements; + + this.x = e[0] * x + e[4] * y + e[8] * z + e[12] * w; + this.y = e[1] * x + e[5] * y + e[9] * z + e[13] * w; + this.z = e[2] * x + e[6] * y + e[10] * z + e[14] * w; + this.w = e[3] * x + e[7] * y + e[11] * z + e[15] * w; + + return this; + + }, + + divideScalar: function ( scalar ) { + + if ( scalar !== 0 ) { + + var invScalar = 1 / scalar; + + this.x *= invScalar; + this.y *= invScalar; + this.z *= invScalar; + this.w *= invScalar; + + } else { + + this.x = 0; + this.y = 0; + this.z = 0; + this.w = 1; + + } + + return this; + + }, + + setAxisAngleFromQuaternion: function ( q ) { + + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm + + // q is assumed to be normalized + + this.w = 2 * Math.acos( q.w ); + + var s = Math.sqrt( 1 - q.w * q.w ); + + if ( s < 0.0001 ) { + + this.x = 1; + this.y = 0; + this.z = 0; + + } else { + + this.x = q.x / s; + this.y = q.y / s; + this.z = q.z / s; + + } + + return this; + + }, + + setAxisAngleFromRotationMatrix: function ( m ) { + + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm + + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + + var angle, x, y, z, // variables for result + epsilon = 0.01, // margin to allow for rounding errors + epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees + + te = m.elements, + + m11 = te[0], m12 = te[4], m13 = te[8], + m21 = te[1], m22 = te[5], m23 = te[9], + m31 = te[2], m32 = te[6], m33 = te[10]; + + if ( ( Math.abs( m12 - m21 ) < epsilon ) + && ( Math.abs( m13 - m31 ) < epsilon ) + && ( Math.abs( m23 - m32 ) < epsilon ) ) { + + // singularity found + // first check for identity matrix which must have +1 for all terms + // in leading diagonal and zero in other terms + + if ( ( Math.abs( m12 + m21 ) < epsilon2 ) + && ( Math.abs( m13 + m31 ) < epsilon2 ) + && ( Math.abs( m23 + m32 ) < epsilon2 ) + && ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) { + + // this singularity is identity matrix so angle = 0 + + this.set( 1, 0, 0, 0 ); + + return this; // zero angle, arbitrary axis + + } + + // otherwise this singularity is angle = 180 + + angle = Math.PI; + + var xx = ( m11 + 1 ) / 2; + var yy = ( m22 + 1 ) / 2; + var zz = ( m33 + 1 ) / 2; + var xy = ( m12 + m21 ) / 4; + var xz = ( m13 + m31 ) / 4; + var yz = ( m23 + m32 ) / 4; + + if ( ( xx > yy ) && ( xx > zz ) ) { // m11 is the largest diagonal term + + if ( xx < epsilon ) { + + x = 0; + y = 0.707106781; + z = 0.707106781; + + } else { + + x = Math.sqrt( xx ); + y = xy / x; + z = xz / x; + + } + + } else if ( yy > zz ) { // m22 is the largest diagonal term + + if ( yy < epsilon ) { + + x = 0.707106781; + y = 0; + z = 0.707106781; + + } else { + + y = Math.sqrt( yy ); + x = xy / y; + z = yz / y; + + } + + } else { // m33 is the largest diagonal term so base result on this + + if ( zz < epsilon ) { + + x = 0.707106781; + y = 0.707106781; + z = 0; + + } else { + + z = Math.sqrt( zz ); + x = xz / z; + y = yz / z; + + } + + } + + this.set( x, y, z, angle ); + + return this; // return 180 deg rotation + + } + + // as we have reached here there are no singularities so we can handle normally + + var s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) + + ( m13 - m31 ) * ( m13 - m31 ) + + ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize + + if ( Math.abs( s ) < 0.001 ) s = 1; + + // prevent divide by zero, should not happen if matrix is orthogonal and should be + // caught by singularity test above, but I've left it in just in case + + this.x = ( m32 - m23 ) / s; + this.y = ( m13 - m31 ) / s; + this.z = ( m21 - m12 ) / s; + this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 ); + + return this; + + }, + + min: function ( v ) { + + if ( this.x > v.x ) { + + this.x = v.x; + + } + + if ( this.y > v.y ) { + + this.y = v.y; + + } + + if ( this.z > v.z ) { + + this.z = v.z; + + } + + if ( this.w > v.w ) { + + this.w = v.w; + + } + + return this; + + }, + + max: function ( v ) { + + if ( this.x < v.x ) { + + this.x = v.x; + + } + + if ( this.y < v.y ) { + + this.y = v.y; + + } + + if ( this.z < v.z ) { + + this.z = v.z; + + } + + if ( this.w < v.w ) { + + this.w = v.w; + + } + + return this; + + }, + + clamp: function ( min, max ) { + + // This function assumes min < max, if this assumption isn't true it will not operate correctly + + if ( this.x < min.x ) { + + this.x = min.x; + + } else if ( this.x > max.x ) { + + this.x = max.x; + + } + + if ( this.y < min.y ) { + + this.y = min.y; + + } else if ( this.y > max.y ) { + + this.y = max.y; + + } + + if ( this.z < min.z ) { + + this.z = min.z; + + } else if ( this.z > max.z ) { + + this.z = max.z; + + } + + if ( this.w < min.w ) { + + this.w = min.w; + + } else if ( this.w > max.w ) { + + this.w = max.w; + + } + + return this; + + }, + + clampScalar: ( function () { + + var min, max; + + return function ( minVal, maxVal ) { + + if ( min === undefined ) { + + min = new THREE.Vector4(); + max = new THREE.Vector4(); + + } + + min.set( minVal, minVal, minVal, minVal ); + max.set( maxVal, maxVal, maxVal, maxVal ); + + return this.clamp( min, max ); + + }; + + } )(), + + floor: function () { + + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); + this.z = Math.floor( this.z ); + this.w = Math.floor( this.w ); + + return this; + + }, + + ceil: function () { + + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); + this.z = Math.ceil( this.z ); + this.w = Math.ceil( this.w ); + + return this; + + }, + + round: function () { + + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); + this.z = Math.round( this.z ); + this.w = Math.round( this.w ); + + return this; + + }, + + roundToZero: function () { + + this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); + this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); + this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z ); + this.w = ( this.w < 0 ) ? Math.ceil( this.w ) : Math.floor( this.w ); + + return this; + + }, + + negate: function () { + + return this.multiplyScalar( -1 ); + + }, + + dot: function ( v ) { + + return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; + + }, + + lengthSq: function () { + + return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; + + }, + + length: function () { + + return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w ); + + }, + + lengthManhattan: function () { + + return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w ); + + }, + + normalize: function () { + + return this.divideScalar( this.length() ); + + }, + + setLength: function ( l ) { + + var oldLength = this.length(); + + if ( oldLength !== 0 && l !== oldLength ) { + + this.multiplyScalar( l / oldLength ); + + } + + return this; + + }, + + lerp: function ( v, alpha ) { + + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; + this.z += ( v.z - this.z ) * alpha; + this.w += ( v.w - this.w ) * alpha; + + return this; + + }, + + equals: function ( v ) { + + return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) ); + + }, + + fromArray: function ( array ) { + + this.x = array[ 0 ]; + this.y = array[ 1 ]; + this.z = array[ 2 ]; + this.w = array[ 3 ]; + + return this; + + }, + + toArray: function () { + + return [ this.x, this.y, this.z, this.w ]; + + }, + + clone: function () { + + return new THREE.Vector4( this.x, this.y, this.z, this.w ); + + } + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author WestLangley / http://github.com/WestLangley + * @author bhouston / http://exocortex.com + */ + +THREE.Euler = function ( x, y, z, order ) { + + this._x = x || 0; + this._y = y || 0; + this._z = z || 0; + this._order = order || THREE.Euler.DefaultOrder; + +}; + +THREE.Euler.RotationOrders = [ 'XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ', 'ZYX' ]; + +THREE.Euler.DefaultOrder = 'XYZ'; + +THREE.Euler.prototype = { + + constructor: THREE.Euler, + + _x: 0, _y: 0, _z: 0, _order: THREE.Euler.DefaultOrder, + + _quaternion: undefined, + + _updateQuaternion: function () { + + if ( this._quaternion !== undefined ) { + + this._quaternion.setFromEuler( this, false ); + + } + + }, + + get x () { + + return this._x; + + }, + + set x ( value ) { + + this._x = value; + this._updateQuaternion(); + + }, + + get y () { + + return this._y; + + }, + + set y ( value ) { + + this._y = value; + this._updateQuaternion(); + + }, + + get z () { + + return this._z; + + }, + + set z ( value ) { + + this._z = value; + this._updateQuaternion(); + + }, + + get order () { + + return this._order; + + }, + + set order ( value ) { + + this._order = value; + this._updateQuaternion(); + + }, + + set: function ( x, y, z, order ) { + + this._x = x; + this._y = y; + this._z = z; + this._order = order || this._order; + + this._updateQuaternion(); + + return this; + + }, + + copy: function ( euler ) { + + this._x = euler._x; + this._y = euler._y; + this._z = euler._z; + this._order = euler._order; + + this._updateQuaternion(); + + return this; + + }, + + setFromRotationMatrix: function ( m, order ) { + + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + + // clamp, to handle numerical problems + + function clamp( x ) { + + return Math.min( Math.max( x, -1 ), 1 ); + + } + + var te = m.elements; + var m11 = te[0], m12 = te[4], m13 = te[8]; + var m21 = te[1], m22 = te[5], m23 = te[9]; + var m31 = te[2], m32 = te[6], m33 = te[10]; + + order = order || this._order; + + if ( order === 'XYZ' ) { + + this._y = Math.asin( clamp( m13 ) ); + + if ( Math.abs( m13 ) < 0.99999 ) { + + this._x = Math.atan2( - m23, m33 ); + this._z = Math.atan2( - m12, m11 ); + + } else { + + this._x = Math.atan2( m32, m22 ); + this._z = 0; + + } + + } else if ( order === 'YXZ' ) { + + this._x = Math.asin( - clamp( m23 ) ); + + if ( Math.abs( m23 ) < 0.99999 ) { + + this._y = Math.atan2( m13, m33 ); + this._z = Math.atan2( m21, m22 ); + + } else { + + this._y = Math.atan2( - m31, m11 ); + this._z = 0; + + } + + } else if ( order === 'ZXY' ) { + + this._x = Math.asin( clamp( m32 ) ); + + if ( Math.abs( m32 ) < 0.99999 ) { + + this._y = Math.atan2( - m31, m33 ); + this._z = Math.atan2( - m12, m22 ); + + } else { + + this._y = 0; + this._z = Math.atan2( m21, m11 ); + + } + + } else if ( order === 'ZYX' ) { + + this._y = Math.asin( - clamp( m31 ) ); + + if ( Math.abs( m31 ) < 0.99999 ) { + + this._x = Math.atan2( m32, m33 ); + this._z = Math.atan2( m21, m11 ); + + } else { + + this._x = 0; + this._z = Math.atan2( - m12, m22 ); + + } + + } else if ( order === 'YZX' ) { + + this._z = Math.asin( clamp( m21 ) ); + + if ( Math.abs( m21 ) < 0.99999 ) { + + this._x = Math.atan2( - m23, m22 ); + this._y = Math.atan2( - m31, m11 ); + + } else { + + this._x = 0; + this._y = Math.atan2( m13, m33 ); + + } + + } else if ( order === 'XZY' ) { + + this._z = Math.asin( - clamp( m12 ) ); + + if ( Math.abs( m12 ) < 0.99999 ) { + + this._x = Math.atan2( m32, m22 ); + this._y = Math.atan2( m13, m11 ); + + } else { + + this._x = Math.atan2( - m23, m33 ); + this._y = 0; + + } + + } else { + + console.warn( 'WARNING: Euler.setFromRotationMatrix() given unsupported order: ' + order ) + + } + + this._order = order; + + this._updateQuaternion(); + + return this; + + }, + + setFromQuaternion: function ( q, order, update ) { + + // q is assumed to be normalized + + // clamp, to handle numerical problems + + function clamp( x ) { + + return Math.min( Math.max( x, -1 ), 1 ); + + } + + // http://www.mathworks.com/matlabcentral/fileexchange/20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/content/SpinCalc.m + + var sqx = q.x * q.x; + var sqy = q.y * q.y; + var sqz = q.z * q.z; + var sqw = q.w * q.w; + + order = order || this._order; + + if ( order === 'XYZ' ) { + + this._x = Math.atan2( 2 * ( q.x * q.w - q.y * q.z ), ( sqw - sqx - sqy + sqz ) ); + this._y = Math.asin( clamp( 2 * ( q.x * q.z + q.y * q.w ) ) ); + this._z = Math.atan2( 2 * ( q.z * q.w - q.x * q.y ), ( sqw + sqx - sqy - sqz ) ); + + } else if ( order === 'YXZ' ) { + + this._x = Math.asin( clamp( 2 * ( q.x * q.w - q.y * q.z ) ) ); + this._y = Math.atan2( 2 * ( q.x * q.z + q.y * q.w ), ( sqw - sqx - sqy + sqz ) ); + this._z = Math.atan2( 2 * ( q.x * q.y + q.z * q.w ), ( sqw - sqx + sqy - sqz ) ); + + } else if ( order === 'ZXY' ) { + + this._x = Math.asin( clamp( 2 * ( q.x * q.w + q.y * q.z ) ) ); + this._y = Math.atan2( 2 * ( q.y * q.w - q.z * q.x ), ( sqw - sqx - sqy + sqz ) ); + this._z = Math.atan2( 2 * ( q.z * q.w - q.x * q.y ), ( sqw - sqx + sqy - sqz ) ); + + } else if ( order === 'ZYX' ) { + + this._x = Math.atan2( 2 * ( q.x * q.w + q.z * q.y ), ( sqw - sqx - sqy + sqz ) ); + this._y = Math.asin( clamp( 2 * ( q.y * q.w - q.x * q.z ) ) ); + this._z = Math.atan2( 2 * ( q.x * q.y + q.z * q.w ), ( sqw + sqx - sqy - sqz ) ); + + } else if ( order === 'YZX' ) { + + this._x = Math.atan2( 2 * ( q.x * q.w - q.z * q.y ), ( sqw - sqx + sqy - sqz ) ); + this._y = Math.atan2( 2 * ( q.y * q.w - q.x * q.z ), ( sqw + sqx - sqy - sqz ) ); + this._z = Math.asin( clamp( 2 * ( q.x * q.y + q.z * q.w ) ) ); + + } else if ( order === 'XZY' ) { + + this._x = Math.atan2( 2 * ( q.x * q.w + q.y * q.z ), ( sqw - sqx + sqy - sqz ) ); + this._y = Math.atan2( 2 * ( q.x * q.z + q.y * q.w ), ( sqw + sqx - sqy - sqz ) ); + this._z = Math.asin( clamp( 2 * ( q.z * q.w - q.x * q.y ) ) ); + + } else { + + console.warn( 'WARNING: Euler.setFromQuaternion() given unsupported order: ' + order ) + + } + + this._order = order; + + if ( update !== false ) this._updateQuaternion(); + + return this; + + }, + + reorder: function () { + + // WARNING: this discards revolution information -bhouston + + var q = new THREE.Quaternion(); + + return function ( newOrder ) { + + q.setFromEuler( this ); + this.setFromQuaternion( q, newOrder ); + + }; + + + }(), + + fromArray: function ( array ) { + + this._x = array[ 0 ]; + this._y = array[ 1 ]; + this._z = array[ 2 ]; + if ( array[ 3 ] !== undefined ) this._order = array[ 3 ]; + + this._updateQuaternion(); + + return this; + + }, + + toArray: function () { + + return [ this._x, this._y, this._z, this._order ]; + + }, + + equals: function ( euler ) { + + return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order ); + + }, + + clone: function () { + + return new THREE.Euler( this._x, this._y, this._z, this._order ); + + } + +}; + +/** + * @author bhouston / http://exocortex.com + */ + +THREE.Line3 = function ( start, end ) { + + this.start = ( start !== undefined ) ? start : new THREE.Vector3(); + this.end = ( end !== undefined ) ? end : new THREE.Vector3(); + +}; + +THREE.Line3.prototype = { + + constructor: THREE.Line3, + + set: function ( start, end ) { + + this.start.copy( start ); + this.end.copy( end ); + + return this; + + }, + + copy: function ( line ) { + + this.start.copy( line.start ); + this.end.copy( line.end ); + + return this; + + }, + + center: function ( optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + return result.addVectors( this.start, this.end ).multiplyScalar( 0.5 ); + + }, + + delta: function ( optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + return result.subVectors( this.end, this.start ); + + }, + + distanceSq: function () { + + return this.start.distanceToSquared( this.end ); + + }, + + distance: function () { + + return this.start.distanceTo( this.end ); + + }, + + at: function ( t, optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + + return this.delta( result ).multiplyScalar( t ).add( this.start ); + + }, + + closestPointToPointParameter: function() { + + var startP = new THREE.Vector3(); + var startEnd = new THREE.Vector3(); + + return function ( point, clampToLine ) { + + startP.subVectors( point, this.start ); + startEnd.subVectors( this.end, this.start ); + + var startEnd2 = startEnd.dot( startEnd ); + var startEnd_startP = startEnd.dot( startP ); + + var t = startEnd_startP / startEnd2; + + if ( clampToLine ) { + + t = THREE.Math.clamp( t, 0, 1 ); + + } + + return t; + + }; + + }(), + + closestPointToPoint: function ( point, clampToLine, optionalTarget ) { + + var t = this.closestPointToPointParameter( point, clampToLine ); + + var result = optionalTarget || new THREE.Vector3(); + + return this.delta( result ).multiplyScalar( t ).add( this.start ); + + }, + + applyMatrix4: function ( matrix ) { + + this.start.applyMatrix4( matrix ); + this.end.applyMatrix4( matrix ); + + return this; + + }, + + equals: function ( line ) { + + return line.start.equals( this.start ) && line.end.equals( this.end ); + + }, + + clone: function () { + + return new THREE.Line3().copy( this ); + + } + +}; + +/** + * @author bhouston / http://exocortex.com + */ + +THREE.Box2 = function ( min, max ) { + + this.min = ( min !== undefined ) ? min : new THREE.Vector2( Infinity, Infinity ); + this.max = ( max !== undefined ) ? max : new THREE.Vector2( -Infinity, -Infinity ); + +}; + +THREE.Box2.prototype = { + + constructor: THREE.Box2, + + set: function ( min, max ) { + + this.min.copy( min ); + this.max.copy( max ); + + return this; + + }, + + setFromPoints: function ( points ) { + + if ( points.length > 0 ) { + + var point = points[ 0 ]; + + this.min.copy( point ); + this.max.copy( point ); + + for ( var i = 1, il = points.length; i < il; i ++ ) { + + point = points[ i ]; + + if ( point.x < this.min.x ) { + + this.min.x = point.x; + + } else if ( point.x > this.max.x ) { + + this.max.x = point.x; + + } + + if ( point.y < this.min.y ) { + + this.min.y = point.y; + + } else if ( point.y > this.max.y ) { + + this.max.y = point.y; + + } + + } + + } else { + + this.makeEmpty(); + + } + + return this; + + }, + + setFromCenterAndSize: function () { + + var v1 = new THREE.Vector2(); + + return function ( center, size ) { + + var halfSize = v1.copy( size ).multiplyScalar( 0.5 ); + this.min.copy( center ).sub( halfSize ); + this.max.copy( center ).add( halfSize ); + + return this; + + }; + + }(), + + copy: function ( box ) { + + this.min.copy( box.min ); + this.max.copy( box.max ); + + return this; + + }, + + makeEmpty: function () { + + this.min.x = this.min.y = Infinity; + this.max.x = this.max.y = -Infinity; + + return this; + + }, + + empty: function () { + + // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes + + return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ); + + }, + + center: function ( optionalTarget ) { + + var result = optionalTarget || new THREE.Vector2(); + return result.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); + + }, + + size: function ( optionalTarget ) { + + var result = optionalTarget || new THREE.Vector2(); + return result.subVectors( this.max, this.min ); + + }, + + expandByPoint: function ( point ) { + + this.min.min( point ); + this.max.max( point ); + + return this; + }, + + expandByVector: function ( vector ) { + + this.min.sub( vector ); + this.max.add( vector ); + + return this; + }, + + expandByScalar: function ( scalar ) { + + this.min.addScalar( -scalar ); + this.max.addScalar( scalar ); + + return this; + }, + + containsPoint: function ( point ) { + + if ( point.x < this.min.x || point.x > this.max.x || + point.y < this.min.y || point.y > this.max.y ) { + + return false; + + } + + return true; + + }, + + containsBox: function ( box ) { + + if ( ( this.min.x <= box.min.x ) && ( box.max.x <= this.max.x ) && + ( this.min.y <= box.min.y ) && ( box.max.y <= this.max.y ) ) { + + return true; + + } + + return false; + + }, + + getParameter: function ( point, optionalTarget ) { + + // This can potentially have a divide by zero if the box + // has a size dimension of 0. + + var result = optionalTarget || new THREE.Vector2(); + + return result.set( + ( point.x - this.min.x ) / ( this.max.x - this.min.x ), + ( point.y - this.min.y ) / ( this.max.y - this.min.y ) + ); + + }, + + isIntersectionBox: function ( box ) { + + // using 6 splitting planes to rule out intersections. + + if ( box.max.x < this.min.x || box.min.x > this.max.x || + box.max.y < this.min.y || box.min.y > this.max.y ) { + + return false; + + } + + return true; + + }, + + clampPoint: function ( point, optionalTarget ) { + + var result = optionalTarget || new THREE.Vector2(); + return result.copy( point ).clamp( this.min, this.max ); + + }, + + distanceToPoint: function () { + + var v1 = new THREE.Vector2(); + + return function ( point ) { + + var clampedPoint = v1.copy( point ).clamp( this.min, this.max ); + return clampedPoint.sub( point ).length(); + + }; + + }(), + + intersect: function ( box ) { + + this.min.max( box.min ); + this.max.min( box.max ); + + return this; + + }, + + union: function ( box ) { + + this.min.min( box.min ); + this.max.max( box.max ); + + return this; + + }, + + translate: function ( offset ) { + + this.min.add( offset ); + this.max.add( offset ); + + return this; + + }, + + equals: function ( box ) { + + return box.min.equals( this.min ) && box.max.equals( this.max ); + + }, + + clone: function () { + + return new THREE.Box2().copy( this ); + + } + +}; + +/** + * @author bhouston / http://exocortex.com + * @author WestLangley / http://github.com/WestLangley + */ + +THREE.Box3 = function ( min, max ) { + + this.min = ( min !== undefined ) ? min : new THREE.Vector3( Infinity, Infinity, Infinity ); + this.max = ( max !== undefined ) ? max : new THREE.Vector3( -Infinity, -Infinity, -Infinity ); + +}; + +THREE.Box3.prototype = { + + constructor: THREE.Box3, + + set: function ( min, max ) { + + this.min.copy( min ); + this.max.copy( max ); + + return this; + + }, + + addPoint: function ( point ) { + + if ( point.x < this.min.x ) { + + this.min.x = point.x; + + } else if ( point.x > this.max.x ) { + + this.max.x = point.x; + + } + + if ( point.y < this.min.y ) { + + this.min.y = point.y; + + } else if ( point.y > this.max.y ) { + + this.max.y = point.y; + + } + + if ( point.z < this.min.z ) { + + this.min.z = point.z; + + } else if ( point.z > this.max.z ) { + + this.max.z = point.z; + + } + + }, + + setFromPoints: function ( points ) { + + if ( points.length > 0 ) { + + var point = points[ 0 ]; + + this.min.copy( point ); + this.max.copy( point ); + + for ( var i = 1, il = points.length; i < il; i ++ ) { + + this.addPoint( points[ i ] ) + + } + + } else { + + this.makeEmpty(); + + } + + return this; + + }, + + setFromCenterAndSize: function() { + + var v1 = new THREE.Vector3(); + + return function ( center, size ) { + + var halfSize = v1.copy( size ).multiplyScalar( 0.5 ); + + this.min.copy( center ).sub( halfSize ); + this.max.copy( center ).add( halfSize ); + + return this; + + }; + + }(), + + setFromObject: function() { + + // Computes the world-axis-aligned bounding box of an object (including its children), + // accounting for both the object's, and childrens', world transforms + + var v1 = new THREE.Vector3(); + + return function( object ) { + + var scope = this; + + object.updateMatrixWorld( true ); + + this.makeEmpty(); + + object.traverse( function ( node ) { + + if ( node.geometry !== undefined && node.geometry.vertices !== undefined ) { + + var vertices = node.geometry.vertices; + + for ( var i = 0, il = vertices.length; i < il; i++ ) { + + v1.copy( vertices[ i ] ); + + v1.applyMatrix4( node.matrixWorld ); + + scope.expandByPoint( v1 ); + + } + + } + + } ); + + return this; + + }; + + }(), + + copy: function ( box ) { + + this.min.copy( box.min ); + this.max.copy( box.max ); + + return this; + + }, + + makeEmpty: function () { + + this.min.x = this.min.y = this.min.z = Infinity; + this.max.x = this.max.y = this.max.z = -Infinity; + + return this; + + }, + + empty: function () { + + // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes + + return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z ); + + }, + + center: function ( optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + return result.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); + + }, + + size: function ( optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + return result.subVectors( this.max, this.min ); + + }, + + expandByPoint: function ( point ) { + + this.min.min( point ); + this.max.max( point ); + + return this; + + }, + + expandByVector: function ( vector ) { + + this.min.sub( vector ); + this.max.add( vector ); + + return this; + + }, + + expandByScalar: function ( scalar ) { + + this.min.addScalar( -scalar ); + this.max.addScalar( scalar ); + + return this; + + }, + + containsPoint: function ( point ) { + + if ( point.x < this.min.x || point.x > this.max.x || + point.y < this.min.y || point.y > this.max.y || + point.z < this.min.z || point.z > this.max.z ) { + + return false; + + } + + return true; + + }, + + containsBox: function ( box ) { + + if ( ( this.min.x <= box.min.x ) && ( box.max.x <= this.max.x ) && + ( this.min.y <= box.min.y ) && ( box.max.y <= this.max.y ) && + ( this.min.z <= box.min.z ) && ( box.max.z <= this.max.z ) ) { + + return true; + + } + + return false; + + }, + + getParameter: function ( point, optionalTarget ) { + + // This can potentially have a divide by zero if the box + // has a size dimension of 0. + + var result = optionalTarget || new THREE.Vector3(); + + return result.set( + ( point.x - this.min.x ) / ( this.max.x - this.min.x ), + ( point.y - this.min.y ) / ( this.max.y - this.min.y ), + ( point.z - this.min.z ) / ( this.max.z - this.min.z ) + ); + + }, + + isIntersectionBox: function ( box ) { + + // using 6 splitting planes to rule out intersections. + + if ( box.max.x < this.min.x || box.min.x > this.max.x || + box.max.y < this.min.y || box.min.y > this.max.y || + box.max.z < this.min.z || box.min.z > this.max.z ) { + + return false; + + } + + return true; + + }, + + clampPoint: function ( point, optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + return result.copy( point ).clamp( this.min, this.max ); + + }, + + distanceToPoint: function() { + + var v1 = new THREE.Vector3(); + + return function ( point ) { + + var clampedPoint = v1.copy( point ).clamp( this.min, this.max ); + return clampedPoint.sub( point ).length(); + + }; + + }(), + + getBoundingSphere: function() { + + var v1 = new THREE.Vector3(); + + return function ( optionalTarget ) { + + var result = optionalTarget || new THREE.Sphere(); + + result.center = this.center(); + result.radius = this.size( v1 ).length() * 0.5; + + return result; + + }; + + }(), + + intersect: function ( box ) { + + this.min.max( box.min ); + this.max.min( box.max ); + + return this; + + }, + + union: function ( box ) { + + this.min.min( box.min ); + this.max.max( box.max ); + + return this; + + }, + + applyMatrix4: function() { + + var points = [ + new THREE.Vector3(), + new THREE.Vector3(), + new THREE.Vector3(), + new THREE.Vector3(), + new THREE.Vector3(), + new THREE.Vector3(), + new THREE.Vector3(), + new THREE.Vector3() + ]; + + return function ( matrix ) { + + // NOTE: I am using a binary pattern to specify all 2^3 combinations below + points[0].set( this.min.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 000 + points[1].set( this.min.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 001 + points[2].set( this.min.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 010 + points[3].set( this.min.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 011 + points[4].set( this.max.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 100 + points[5].set( this.max.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 101 + points[6].set( this.max.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 110 + points[7].set( this.max.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 111 + + this.makeEmpty(); + this.setFromPoints( points ); + + return this; + + }; + + }(), + + translate: function ( offset ) { + + this.min.add( offset ); + this.max.add( offset ); + + return this; + + }, + + equals: function ( box ) { + + return box.min.equals( this.min ) && box.max.equals( this.max ); + + }, + + clone: function () { + + return new THREE.Box3().copy( this ); + + } + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + * @author WestLangley / http://github.com/WestLangley + * @author bhouston / http://exocortex.com + */ + +THREE.Matrix3 = function ( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { + + this.elements = new Float32Array(9); + + this.set( + + ( n11 !== undefined ) ? n11 : 1, n12 || 0, n13 || 0, + n21 || 0, ( n22 !== undefined ) ? n22 : 1, n23 || 0, + n31 || 0, n32 || 0, ( n33 !== undefined ) ? n33 : 1 + + ); +}; + +THREE.Matrix3.prototype = { + + constructor: THREE.Matrix3, + + set: function ( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { + + var te = this.elements; + + te[0] = n11; te[3] = n12; te[6] = n13; + te[1] = n21; te[4] = n22; te[7] = n23; + te[2] = n31; te[5] = n32; te[8] = n33; + + return this; + + }, + + identity: function () { + + this.set( + + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 + + ); + + return this; + + }, + + copy: function ( m ) { + + var me = m.elements; + + this.set( + + me[0], me[3], me[6], + me[1], me[4], me[7], + me[2], me[5], me[8] + + ); + + return this; + + }, + + multiplyVector3: function ( vector ) { + + console.warn( 'DEPRECATED: Matrix3\'s .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.' ); + return vector.applyMatrix3( this ); + + }, + + multiplyVector3Array: function() { + + var v1 = new THREE.Vector3(); + + return function ( a ) { + + for ( var i = 0, il = a.length; i < il; i += 3 ) { + + v1.x = a[ i ]; + v1.y = a[ i + 1 ]; + v1.z = a[ i + 2 ]; + + v1.applyMatrix3(this); + + a[ i ] = v1.x; + a[ i + 1 ] = v1.y; + a[ i + 2 ] = v1.z; + + } + + return a; + + }; + + }(), + + multiplyScalar: function ( s ) { + + var te = this.elements; + + te[0] *= s; te[3] *= s; te[6] *= s; + te[1] *= s; te[4] *= s; te[7] *= s; + te[2] *= s; te[5] *= s; te[8] *= s; + + return this; + + }, + + determinant: function () { + + var te = this.elements; + + var a = te[0], b = te[1], c = te[2], + d = te[3], e = te[4], f = te[5], + g = te[6], h = te[7], i = te[8]; + + return a*e*i - a*f*h - b*d*i + b*f*g + c*d*h - c*e*g; + + }, + + getInverse: function ( matrix, throwOnInvertible ) { + + // input: THREE.Matrix4 + // ( based on http://code.google.com/p/webgl-mjs/ ) + + var me = matrix.elements; + var te = this.elements; + + te[ 0 ] = me[10] * me[5] - me[6] * me[9]; + te[ 1 ] = - me[10] * me[1] + me[2] * me[9]; + te[ 2 ] = me[6] * me[1] - me[2] * me[5]; + te[ 3 ] = - me[10] * me[4] + me[6] * me[8]; + te[ 4 ] = me[10] * me[0] - me[2] * me[8]; + te[ 5 ] = - me[6] * me[0] + me[2] * me[4]; + te[ 6 ] = me[9] * me[4] - me[5] * me[8]; + te[ 7 ] = - me[9] * me[0] + me[1] * me[8]; + te[ 8 ] = me[5] * me[0] - me[1] * me[4]; + + var det = me[ 0 ] * te[ 0 ] + me[ 1 ] * te[ 3 ] + me[ 2 ] * te[ 6 ]; + + // no inverse + + if ( det === 0 ) { + + var msg = "Matrix3.getInverse(): can't invert matrix, determinant is 0"; + + if ( throwOnInvertible || false ) { + + throw new Error( msg ); + + } else { + + console.warn( msg ); + + } + + this.identity(); + + return this; + + } + + this.multiplyScalar( 1.0 / det ); + + return this; + + }, + + transpose: function () { + + var tmp, m = this.elements; + + tmp = m[1]; m[1] = m[3]; m[3] = tmp; + tmp = m[2]; m[2] = m[6]; m[6] = tmp; + tmp = m[5]; m[5] = m[7]; m[7] = tmp; + + return this; + + }, + + getNormalMatrix: function ( m ) { + + // input: THREE.Matrix4 + + this.getInverse( m ).transpose(); + + return this; + + }, + + transposeIntoArray: function ( r ) { + + var m = this.elements; + + r[ 0 ] = m[ 0 ]; + r[ 1 ] = m[ 3 ]; + r[ 2 ] = m[ 6 ]; + r[ 3 ] = m[ 1 ]; + r[ 4 ] = m[ 4 ]; + r[ 5 ] = m[ 7 ]; + r[ 6 ] = m[ 2 ]; + r[ 7 ] = m[ 5 ]; + r[ 8 ] = m[ 8 ]; + + return this; + + }, + + fromArray: function ( array ) { + + this.elements.set( array ); + + return this; + + }, + + toArray: function () { + + var te = this.elements; + + return [ + te[ 0 ], te[ 1 ], te[ 2 ], + te[ 3 ], te[ 4 ], te[ 5 ], + te[ 6 ], te[ 7 ], te[ 8 ] + ]; + + }, + + clone: function () { + + var te = this.elements; + + return new THREE.Matrix3( + + te[0], te[3], te[6], + te[1], te[4], te[7], + te[2], te[5], te[8] + + ); + + } + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author supereggbert / http://www.paulbrunt.co.uk/ + * @author philogb / http://blog.thejit.org/ + * @author jordi_ros / http://plattsoft.com + * @author D1plo1d / http://github.com/D1plo1d + * @author alteredq / http://alteredqualia.com/ + * @author mikael emtinger / http://gomo.se/ + * @author timknip / http://www.floorplanner.com/ + * @author bhouston / http://exocortex.com + * @author WestLangley / http://github.com/WestLangley + */ + + +THREE.Matrix4 = function ( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { + + this.elements = new Float32Array( 16 ); + + // TODO: if n11 is undefined, then just set to identity, otherwise copy all other values into matrix + // we should not support semi specification of Matrix4, it is just weird. + + var te = this.elements; + + te[0] = ( n11 !== undefined ) ? n11 : 1; te[4] = n12 || 0; te[8] = n13 || 0; te[12] = n14 || 0; + te[1] = n21 || 0; te[5] = ( n22 !== undefined ) ? n22 : 1; te[9] = n23 || 0; te[13] = n24 || 0; + te[2] = n31 || 0; te[6] = n32 || 0; te[10] = ( n33 !== undefined ) ? n33 : 1; te[14] = n34 || 0; + te[3] = n41 || 0; te[7] = n42 || 0; te[11] = n43 || 0; te[15] = ( n44 !== undefined ) ? n44 : 1; + +}; + +THREE.Matrix4.prototype = { + + constructor: THREE.Matrix4, + + set: function ( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { + + var te = this.elements; + + te[0] = n11; te[4] = n12; te[8] = n13; te[12] = n14; + te[1] = n21; te[5] = n22; te[9] = n23; te[13] = n24; + te[2] = n31; te[6] = n32; te[10] = n33; te[14] = n34; + te[3] = n41; te[7] = n42; te[11] = n43; te[15] = n44; + + return this; + + }, + + identity: function () { + + this.set( + + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + + ); + + return this; + + }, + + copy: function ( m ) { + + this.elements.set( m.elements ); + + return this; + + }, + + extractPosition: function ( m ) { + + console.warn( 'DEPRECATED: Matrix4\'s .extractPosition() has been renamed to .copyPosition().' ); + return this.copyPosition( m ); + + }, + + copyPosition: function ( m ) { + + var te = this.elements; + var me = m.elements; + + te[12] = me[12]; + te[13] = me[13]; + te[14] = me[14]; + + return this; + + }, + + extractRotation: function () { + + var v1 = new THREE.Vector3(); + + return function ( m ) { + + var te = this.elements; + var me = m.elements; + + var scaleX = 1 / v1.set( me[0], me[1], me[2] ).length(); + var scaleY = 1 / v1.set( me[4], me[5], me[6] ).length(); + var scaleZ = 1 / v1.set( me[8], me[9], me[10] ).length(); + + te[0] = me[0] * scaleX; + te[1] = me[1] * scaleX; + te[2] = me[2] * scaleX; + + te[4] = me[4] * scaleY; + te[5] = me[5] * scaleY; + te[6] = me[6] * scaleY; + + te[8] = me[8] * scaleZ; + te[9] = me[9] * scaleZ; + te[10] = me[10] * scaleZ; + + return this; + + }; + + }(), + + makeRotationFromEuler: function ( euler ) { + + if ( euler instanceof THREE.Euler === false ) { + + console.error( 'ERROR: Matrix\'s .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order. Please update your code.' ); + + } + + var te = this.elements; + + var x = euler.x, y = euler.y, z = euler.z; + var a = Math.cos( x ), b = Math.sin( x ); + var c = Math.cos( y ), d = Math.sin( y ); + var e = Math.cos( z ), f = Math.sin( z ); + + if ( euler.order === 'XYZ' ) { + + var ae = a * e, af = a * f, be = b * e, bf = b * f; + + te[0] = c * e; + te[4] = - c * f; + te[8] = d; + + te[1] = af + be * d; + te[5] = ae - bf * d; + te[9] = - b * c; + + te[2] = bf - ae * d; + te[6] = be + af * d; + te[10] = a * c; + + } else if ( euler.order === 'YXZ' ) { + + var ce = c * e, cf = c * f, de = d * e, df = d * f; + + te[0] = ce + df * b; + te[4] = de * b - cf; + te[8] = a * d; + + te[1] = a * f; + te[5] = a * e; + te[9] = - b; + + te[2] = cf * b - de; + te[6] = df + ce * b; + te[10] = a * c; + + } else if ( euler.order === 'ZXY' ) { + + var ce = c * e, cf = c * f, de = d * e, df = d * f; + + te[0] = ce - df * b; + te[4] = - a * f; + te[8] = de + cf * b; + + te[1] = cf + de * b; + te[5] = a * e; + te[9] = df - ce * b; + + te[2] = - a * d; + te[6] = b; + te[10] = a * c; + + } else if ( euler.order === 'ZYX' ) { + + var ae = a * e, af = a * f, be = b * e, bf = b * f; + + te[0] = c * e; + te[4] = be * d - af; + te[8] = ae * d + bf; + + te[1] = c * f; + te[5] = bf * d + ae; + te[9] = af * d - be; + + te[2] = - d; + te[6] = b * c; + te[10] = a * c; + + } else if ( euler.order === 'YZX' ) { + + var ac = a * c, ad = a * d, bc = b * c, bd = b * d; + + te[0] = c * e; + te[4] = bd - ac * f; + te[8] = bc * f + ad; + + te[1] = f; + te[5] = a * e; + te[9] = - b * e; + + te[2] = - d * e; + te[6] = ad * f + bc; + te[10] = ac - bd * f; + + } else if ( euler.order === 'XZY' ) { + + var ac = a * c, ad = a * d, bc = b * c, bd = b * d; + + te[0] = c * e; + te[4] = - f; + te[8] = d * e; + + te[1] = ac * f + bd; + te[5] = a * e; + te[9] = ad * f - bc; + + te[2] = bc * f - ad; + te[6] = b * e; + te[10] = bd * f + ac; + + } + + // last column + te[3] = 0; + te[7] = 0; + te[11] = 0; + + // bottom row + te[12] = 0; + te[13] = 0; + te[14] = 0; + te[15] = 1; + + return this; + + }, + + setRotationFromQuaternion: function ( q ) { + + console.warn( 'DEPRECATED: Matrix4\'s .setRotationFromQuaternion() has been deprecated in favor of makeRotationFromQuaternion. Please update your code.' ); + + return this.makeRotationFromQuaternion( q ); + + }, + + makeRotationFromQuaternion: function ( q ) { + + var te = this.elements; + + var x = q.x, y = q.y, z = q.z, w = q.w; + var x2 = x + x, y2 = y + y, z2 = z + z; + var xx = x * x2, xy = x * y2, xz = x * z2; + var yy = y * y2, yz = y * z2, zz = z * z2; + var wx = w * x2, wy = w * y2, wz = w * z2; + + te[0] = 1 - ( yy + zz ); + te[4] = xy - wz; + te[8] = xz + wy; + + te[1] = xy + wz; + te[5] = 1 - ( xx + zz ); + te[9] = yz - wx; + + te[2] = xz - wy; + te[6] = yz + wx; + te[10] = 1 - ( xx + yy ); + + // last column + te[3] = 0; + te[7] = 0; + te[11] = 0; + + // bottom row + te[12] = 0; + te[13] = 0; + te[14] = 0; + te[15] = 1; + + return this; + + }, + + lookAt: function() { + + var x = new THREE.Vector3(); + var y = new THREE.Vector3(); + var z = new THREE.Vector3(); + + return function ( eye, target, up ) { + + var te = this.elements; + + z.subVectors( eye, target ).normalize(); + + if ( z.length() === 0 ) { + + z.z = 1; + + } + + x.crossVectors( up, z ).normalize(); + + if ( x.length() === 0 ) { + + z.x += 0.0001; + x.crossVectors( up, z ).normalize(); + + } + + y.crossVectors( z, x ); + + + te[0] = x.x; te[4] = y.x; te[8] = z.x; + te[1] = x.y; te[5] = y.y; te[9] = z.y; + te[2] = x.z; te[6] = y.z; te[10] = z.z; + + return this; + + }; + + }(), + + multiply: function ( m, n ) { + + if ( n !== undefined ) { + + console.warn( 'DEPRECATED: Matrix4\'s .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead.' ); + return this.multiplyMatrices( m, n ); + + } + + return this.multiplyMatrices( this, m ); + + }, + + multiplyMatrices: function ( a, b ) { + + var ae = a.elements; + var be = b.elements; + var te = this.elements; + + var a11 = ae[0], a12 = ae[4], a13 = ae[8], a14 = ae[12]; + var a21 = ae[1], a22 = ae[5], a23 = ae[9], a24 = ae[13]; + var a31 = ae[2], a32 = ae[6], a33 = ae[10], a34 = ae[14]; + var a41 = ae[3], a42 = ae[7], a43 = ae[11], a44 = ae[15]; + + var b11 = be[0], b12 = be[4], b13 = be[8], b14 = be[12]; + var b21 = be[1], b22 = be[5], b23 = be[9], b24 = be[13]; + var b31 = be[2], b32 = be[6], b33 = be[10], b34 = be[14]; + var b41 = be[3], b42 = be[7], b43 = be[11], b44 = be[15]; + + te[0] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41; + te[4] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42; + te[8] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43; + te[12] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44; + + te[1] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41; + te[5] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42; + te[9] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43; + te[13] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44; + + te[2] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41; + te[6] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42; + te[10] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43; + te[14] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44; + + te[3] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41; + te[7] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42; + te[11] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43; + te[15] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44; + + return this; + + }, + + multiplyToArray: function ( a, b, r ) { + + var te = this.elements; + + this.multiplyMatrices( a, b ); + + r[ 0 ] = te[0]; r[ 1 ] = te[1]; r[ 2 ] = te[2]; r[ 3 ] = te[3]; + r[ 4 ] = te[4]; r[ 5 ] = te[5]; r[ 6 ] = te[6]; r[ 7 ] = te[7]; + r[ 8 ] = te[8]; r[ 9 ] = te[9]; r[ 10 ] = te[10]; r[ 11 ] = te[11]; + r[ 12 ] = te[12]; r[ 13 ] = te[13]; r[ 14 ] = te[14]; r[ 15 ] = te[15]; + + return this; + + }, + + multiplyScalar: function ( s ) { + + var te = this.elements; + + te[0] *= s; te[4] *= s; te[8] *= s; te[12] *= s; + te[1] *= s; te[5] *= s; te[9] *= s; te[13] *= s; + te[2] *= s; te[6] *= s; te[10] *= s; te[14] *= s; + te[3] *= s; te[7] *= s; te[11] *= s; te[15] *= s; + + return this; + + }, + + multiplyVector3: function ( vector ) { + + console.warn( 'DEPRECATED: Matrix4\'s .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) or vector.applyProjection( matrix ) instead.' ); + return vector.applyProjection( this ); + + }, + + multiplyVector4: function ( vector ) { + + console.warn( 'DEPRECATED: Matrix4\'s .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.' ); + return vector.applyMatrix4( this ); + + }, + + multiplyVector3Array: function() { + + var v1 = new THREE.Vector3(); + + return function ( a ) { + + for ( var i = 0, il = a.length; i < il; i += 3 ) { + + v1.x = a[ i ]; + v1.y = a[ i + 1 ]; + v1.z = a[ i + 2 ]; + + v1.applyProjection( this ); + + a[ i ] = v1.x; + a[ i + 1 ] = v1.y; + a[ i + 2 ] = v1.z; + + } + + return a; + + }; + + }(), + + rotateAxis: function ( v ) { + + console.warn( 'DEPRECATED: Matrix4\'s .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.' ); + + v.transformDirection( this ); + + }, + + crossVector: function ( vector ) { + + console.warn( 'DEPRECATED: Matrix4\'s .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead.' ); + return vector.applyMatrix4( this ); + + }, + + determinant: function () { + + var te = this.elements; + + var n11 = te[0], n12 = te[4], n13 = te[8], n14 = te[12]; + var n21 = te[1], n22 = te[5], n23 = te[9], n24 = te[13]; + var n31 = te[2], n32 = te[6], n33 = te[10], n34 = te[14]; + var n41 = te[3], n42 = te[7], n43 = te[11], n44 = te[15]; + + //TODO: make this more efficient + //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm ) + + return ( + n41 * ( + +n14 * n23 * n32 + -n13 * n24 * n32 + -n14 * n22 * n33 + +n12 * n24 * n33 + +n13 * n22 * n34 + -n12 * n23 * n34 + ) + + n42 * ( + +n11 * n23 * n34 + -n11 * n24 * n33 + +n14 * n21 * n33 + -n13 * n21 * n34 + +n13 * n24 * n31 + -n14 * n23 * n31 + ) + + n43 * ( + +n11 * n24 * n32 + -n11 * n22 * n34 + -n14 * n21 * n32 + +n12 * n21 * n34 + +n14 * n22 * n31 + -n12 * n24 * n31 + ) + + n44 * ( + -n13 * n22 * n31 + -n11 * n23 * n32 + +n11 * n22 * n33 + +n13 * n21 * n32 + -n12 * n21 * n33 + +n12 * n23 * n31 + ) + + ); + + }, + + transpose: function () { + + var te = this.elements; + var tmp; + + tmp = te[1]; te[1] = te[4]; te[4] = tmp; + tmp = te[2]; te[2] = te[8]; te[8] = tmp; + tmp = te[6]; te[6] = te[9]; te[9] = tmp; + + tmp = te[3]; te[3] = te[12]; te[12] = tmp; + tmp = te[7]; te[7] = te[13]; te[13] = tmp; + tmp = te[11]; te[11] = te[14]; te[14] = tmp; + + return this; + + }, + + flattenToArray: function ( flat ) { + + var te = this.elements; + flat[ 0 ] = te[0]; flat[ 1 ] = te[1]; flat[ 2 ] = te[2]; flat[ 3 ] = te[3]; + flat[ 4 ] = te[4]; flat[ 5 ] = te[5]; flat[ 6 ] = te[6]; flat[ 7 ] = te[7]; + flat[ 8 ] = te[8]; flat[ 9 ] = te[9]; flat[ 10 ] = te[10]; flat[ 11 ] = te[11]; + flat[ 12 ] = te[12]; flat[ 13 ] = te[13]; flat[ 14 ] = te[14]; flat[ 15 ] = te[15]; + + return flat; + + }, + + flattenToArrayOffset: function( flat, offset ) { + + var te = this.elements; + flat[ offset ] = te[0]; + flat[ offset + 1 ] = te[1]; + flat[ offset + 2 ] = te[2]; + flat[ offset + 3 ] = te[3]; + + flat[ offset + 4 ] = te[4]; + flat[ offset + 5 ] = te[5]; + flat[ offset + 6 ] = te[6]; + flat[ offset + 7 ] = te[7]; + + flat[ offset + 8 ] = te[8]; + flat[ offset + 9 ] = te[9]; + flat[ offset + 10 ] = te[10]; + flat[ offset + 11 ] = te[11]; + + flat[ offset + 12 ] = te[12]; + flat[ offset + 13 ] = te[13]; + flat[ offset + 14 ] = te[14]; + flat[ offset + 15 ] = te[15]; + + return flat; + + }, + + getPosition: function() { + + var v1 = new THREE.Vector3(); + + return function () { + + console.warn( 'DEPRECATED: Matrix4\'s .getPosition() has been removed. Use Vector3.setFromMatrixPosition( matrix ) instead.' ); + + var te = this.elements; + return v1.set( te[12], te[13], te[14] ); + + }; + + }(), + + setPosition: function ( v ) { + + var te = this.elements; + + te[12] = v.x; + te[13] = v.y; + te[14] = v.z; + + return this; + + }, + + getInverse: function ( m, throwOnInvertible ) { + + // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm + var te = this.elements; + var me = m.elements; + + var n11 = me[0], n12 = me[4], n13 = me[8], n14 = me[12]; + var n21 = me[1], n22 = me[5], n23 = me[9], n24 = me[13]; + var n31 = me[2], n32 = me[6], n33 = me[10], n34 = me[14]; + var n41 = me[3], n42 = me[7], n43 = me[11], n44 = me[15]; + + te[0] = n23*n34*n42 - n24*n33*n42 + n24*n32*n43 - n22*n34*n43 - n23*n32*n44 + n22*n33*n44; + te[4] = n14*n33*n42 - n13*n34*n42 - n14*n32*n43 + n12*n34*n43 + n13*n32*n44 - n12*n33*n44; + te[8] = n13*n24*n42 - n14*n23*n42 + n14*n22*n43 - n12*n24*n43 - n13*n22*n44 + n12*n23*n44; + te[12] = n14*n23*n32 - n13*n24*n32 - n14*n22*n33 + n12*n24*n33 + n13*n22*n34 - n12*n23*n34; + te[1] = n24*n33*n41 - n23*n34*n41 - n24*n31*n43 + n21*n34*n43 + n23*n31*n44 - n21*n33*n44; + te[5] = n13*n34*n41 - n14*n33*n41 + n14*n31*n43 - n11*n34*n43 - n13*n31*n44 + n11*n33*n44; + te[9] = n14*n23*n41 - n13*n24*n41 - n14*n21*n43 + n11*n24*n43 + n13*n21*n44 - n11*n23*n44; + te[13] = n13*n24*n31 - n14*n23*n31 + n14*n21*n33 - n11*n24*n33 - n13*n21*n34 + n11*n23*n34; + te[2] = n22*n34*n41 - n24*n32*n41 + n24*n31*n42 - n21*n34*n42 - n22*n31*n44 + n21*n32*n44; + te[6] = n14*n32*n41 - n12*n34*n41 - n14*n31*n42 + n11*n34*n42 + n12*n31*n44 - n11*n32*n44; + te[10] = n12*n24*n41 - n14*n22*n41 + n14*n21*n42 - n11*n24*n42 - n12*n21*n44 + n11*n22*n44; + te[14] = n14*n22*n31 - n12*n24*n31 - n14*n21*n32 + n11*n24*n32 + n12*n21*n34 - n11*n22*n34; + te[3] = n23*n32*n41 - n22*n33*n41 - n23*n31*n42 + n21*n33*n42 + n22*n31*n43 - n21*n32*n43; + te[7] = n12*n33*n41 - n13*n32*n41 + n13*n31*n42 - n11*n33*n42 - n12*n31*n43 + n11*n32*n43; + te[11] = n13*n22*n41 - n12*n23*n41 - n13*n21*n42 + n11*n23*n42 + n12*n21*n43 - n11*n22*n43; + te[15] = n12*n23*n31 - n13*n22*n31 + n13*n21*n32 - n11*n23*n32 - n12*n21*n33 + n11*n22*n33; + + var det = n11 * te[ 0 ] + n21 * te[ 4 ] + n31 * te[ 8 ] + n41 * te[ 12 ]; + + if ( det == 0 ) { + + var msg = "Matrix4.getInverse(): can't invert matrix, determinant is 0"; + + if ( throwOnInvertible || false ) { + + throw new Error( msg ); + + } else { + + console.warn( msg ); + + } + + this.identity(); + + return this; + } + + this.multiplyScalar( 1 / det ); + + return this; + + }, + + translate: function ( v ) { + + console.warn( 'DEPRECATED: Matrix4\'s .translate() has been removed.'); + + }, + + rotateX: function ( angle ) { + + console.warn( 'DEPRECATED: Matrix4\'s .rotateX() has been removed.'); + + }, + + rotateY: function ( angle ) { + + console.warn( 'DEPRECATED: Matrix4\'s .rotateY() has been removed.'); + + }, + + rotateZ: function ( angle ) { + + console.warn( 'DEPRECATED: Matrix4\'s .rotateZ() has been removed.'); + + }, + + rotateByAxis: function ( axis, angle ) { + + console.warn( 'DEPRECATED: Matrix4\'s .rotateByAxis() has been removed.'); + + }, + + scale: function ( v ) { + + var te = this.elements; + var x = v.x, y = v.y, z = v.z; + + te[0] *= x; te[4] *= y; te[8] *= z; + te[1] *= x; te[5] *= y; te[9] *= z; + te[2] *= x; te[6] *= y; te[10] *= z; + te[3] *= x; te[7] *= y; te[11] *= z; + + return this; + + }, + + getMaxScaleOnAxis: function () { + + var te = this.elements; + + var scaleXSq = te[0] * te[0] + te[1] * te[1] + te[2] * te[2]; + var scaleYSq = te[4] * te[4] + te[5] * te[5] + te[6] * te[6]; + var scaleZSq = te[8] * te[8] + te[9] * te[9] + te[10] * te[10]; + + return Math.sqrt( Math.max( scaleXSq, Math.max( scaleYSq, scaleZSq ) ) ); + + }, + + makeTranslation: function ( x, y, z ) { + + this.set( + + 1, 0, 0, x, + 0, 1, 0, y, + 0, 0, 1, z, + 0, 0, 0, 1 + + ); + + return this; + + }, + + makeRotationX: function ( theta ) { + + var c = Math.cos( theta ), s = Math.sin( theta ); + + this.set( + + 1, 0, 0, 0, + 0, c, -s, 0, + 0, s, c, 0, + 0, 0, 0, 1 + + ); + + return this; + + }, + + makeRotationY: function ( theta ) { + + var c = Math.cos( theta ), s = Math.sin( theta ); + + this.set( + + c, 0, s, 0, + 0, 1, 0, 0, + -s, 0, c, 0, + 0, 0, 0, 1 + + ); + + return this; + + }, + + makeRotationZ: function ( theta ) { + + var c = Math.cos( theta ), s = Math.sin( theta ); + + this.set( + + c, -s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + + ); + + return this; + + }, + + makeRotationAxis: function ( axis, angle ) { + + // Based on http://www.gamedev.net/reference/articles/article1199.asp + + var c = Math.cos( angle ); + var s = Math.sin( angle ); + var t = 1 - c; + var x = axis.x, y = axis.y, z = axis.z; + var tx = t * x, ty = t * y; + + this.set( + + tx * x + c, tx * y - s * z, tx * z + s * y, 0, + tx * y + s * z, ty * y + c, ty * z - s * x, 0, + tx * z - s * y, ty * z + s * x, t * z * z + c, 0, + 0, 0, 0, 1 + + ); + + return this; + + }, + + makeScale: function ( x, y, z ) { + + this.set( + + x, 0, 0, 0, + 0, y, 0, 0, + 0, 0, z, 0, + 0, 0, 0, 1 + + ); + + return this; + + }, + + compose: function ( position, quaternion, scale ) { + + this.makeRotationFromQuaternion( quaternion ); + this.scale( scale ); + this.setPosition( position ); + + return this; + + }, + + decompose: function () { + + var vector = new THREE.Vector3(); + var matrix = new THREE.Matrix4(); + + return function ( position, quaternion, scale ) { + + var te = this.elements; + + var sx = vector.set( te[0], te[1], te[2] ).length(); + var sy = vector.set( te[4], te[5], te[6] ).length(); + var sz = vector.set( te[8], te[9], te[10] ).length(); + + // if determine is negative, we need to invert one scale + var det = this.determinant(); + if( det < 0 ) { + sx = -sx; + } + + position.x = te[12]; + position.y = te[13]; + position.z = te[14]; + + // scale the rotation part + + matrix.elements.set( this.elements ); // at this point matrix is incomplete so we can't use .copy() + + var invSX = 1 / sx; + var invSY = 1 / sy; + var invSZ = 1 / sz; + + matrix.elements[0] *= invSX; + matrix.elements[1] *= invSX; + matrix.elements[2] *= invSX; + + matrix.elements[4] *= invSY; + matrix.elements[5] *= invSY; + matrix.elements[6] *= invSY; + + matrix.elements[8] *= invSZ; + matrix.elements[9] *= invSZ; + matrix.elements[10] *= invSZ; + + quaternion.setFromRotationMatrix( matrix ); + + scale.x = sx; + scale.y = sy; + scale.z = sz; + + return this; + + }; + + }(), + + makeFrustum: function ( left, right, bottom, top, near, far ) { + + var te = this.elements; + var x = 2 * near / ( right - left ); + var y = 2 * near / ( top - bottom ); + + var a = ( right + left ) / ( right - left ); + var b = ( top + bottom ) / ( top - bottom ); + var c = - ( far + near ) / ( far - near ); + var d = - 2 * far * near / ( far - near ); + + te[0] = x; te[4] = 0; te[8] = a; te[12] = 0; + te[1] = 0; te[5] = y; te[9] = b; te[13] = 0; + te[2] = 0; te[6] = 0; te[10] = c; te[14] = d; + te[3] = 0; te[7] = 0; te[11] = - 1; te[15] = 0; + + return this; + + }, + + makePerspective: function ( fov, aspect, near, far ) { + + var ymax = near * Math.tan( THREE.Math.degToRad( fov * 0.5 ) ); + var ymin = - ymax; + var xmin = ymin * aspect; + var xmax = ymax * aspect; + + return this.makeFrustum( xmin, xmax, ymin, ymax, near, far ); + + }, + + makeOrthographic: function ( left, right, top, bottom, near, far ) { + + var te = this.elements; + var w = right - left; + var h = top - bottom; + var p = far - near; + + var x = ( right + left ) / w; + var y = ( top + bottom ) / h; + var z = ( far + near ) / p; + + te[0] = 2 / w; te[4] = 0; te[8] = 0; te[12] = -x; + te[1] = 0; te[5] = 2 / h; te[9] = 0; te[13] = -y; + te[2] = 0; te[6] = 0; te[10] = -2/p; te[14] = -z; + te[3] = 0; te[7] = 0; te[11] = 0; te[15] = 1; + + return this; + + }, + + fromArray: function ( array ) { + + this.elements.set( array ); + + return this; + + }, + + toArray: function () { + + var te = this.elements; + + return [ + te[ 0 ], te[ 1 ], te[ 2 ], te[ 3 ], + te[ 4 ], te[ 5 ], te[ 6 ], te[ 7 ], + te[ 8 ], te[ 9 ], te[ 10 ], te[ 11 ], + te[ 12 ], te[ 13 ], te[ 14 ], te[ 15 ] + ]; + + }, + + clone: function () { + + var te = this.elements; + + return new THREE.Matrix4( + + te[0], te[4], te[8], te[12], + te[1], te[5], te[9], te[13], + te[2], te[6], te[10], te[14], + te[3], te[7], te[11], te[15] + + ); + + } + +}; + +/** + * @author bhouston / http://exocortex.com + */ + +THREE.Ray = function ( origin, direction ) { + + this.origin = ( origin !== undefined ) ? origin : new THREE.Vector3(); + this.direction = ( direction !== undefined ) ? direction : new THREE.Vector3(); + +}; + +THREE.Ray.prototype = { + + constructor: THREE.Ray, + + set: function ( origin, direction ) { + + this.origin.copy( origin ); + this.direction.copy( direction ); + + return this; + + }, + + copy: function ( ray ) { + + this.origin.copy( ray.origin ); + this.direction.copy( ray.direction ); + + return this; + + }, + + at: function ( t, optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + + return result.copy( this.direction ).multiplyScalar( t ).add( this.origin ); + + }, + + recast: function () { + + var v1 = new THREE.Vector3(); + + return function ( t ) { + + this.origin.copy( this.at( t, v1 ) ); + + return this; + + }; + + }(), + + closestPointToPoint: function ( point, optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + result.subVectors( point, this.origin ); + var directionDistance = result.dot( this.direction ); + + if ( directionDistance < 0 ) { + + return result.copy( this.origin ); + + } + + return result.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin ); + + }, + + distanceToPoint: function () { + + var v1 = new THREE.Vector3(); + + return function ( point ) { + + var directionDistance = v1.subVectors( point, this.origin ).dot( this.direction ); + + // point behind the ray + + if ( directionDistance < 0 ) { + + return this.origin.distanceTo( point ); + + } + + v1.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin ); + + return v1.distanceTo( point ); + + }; + + }(), + + distanceSqToSegment: function( v0, v1, optionalPointOnRay, optionalPointOnSegment ) { + + // from http://www.geometrictools.com/LibMathematics/Distance/Wm5DistRay3Segment3.cpp + // It returns the min distance between the ray and the segment + // defined by v0 and v1 + // It can also set two optional targets : + // - The closest point on the ray + // - The closest point on the segment + + var segCenter = v0.clone().add( v1 ).multiplyScalar( 0.5 ); + var segDir = v1.clone().sub( v0 ).normalize(); + var segExtent = v0.distanceTo( v1 ) * 0.5; + var diff = this.origin.clone().sub( segCenter ); + var a01 = - this.direction.dot( segDir ); + var b0 = diff.dot( this.direction ); + var b1 = - diff.dot( segDir ); + var c = diff.lengthSq(); + var det = Math.abs( 1 - a01 * a01 ); + var s0, s1, sqrDist, extDet; + + if ( det >= 0 ) { + + // The ray and segment are not parallel. + + s0 = a01 * b1 - b0; + s1 = a01 * b0 - b1; + extDet = segExtent * det; + + if ( s0 >= 0 ) { + + if ( s1 >= - extDet ) { + + if ( s1 <= extDet ) { + + // region 0 + // Minimum at interior points of ray and segment. + + var invDet = 1 / det; + s0 *= invDet; + s1 *= invDet; + sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c; + + } else { + + // region 1 + + s1 = segExtent; + s0 = Math.max( 0, - ( a01 * s1 + b0) ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + + } + + } else { + + // region 5 + + s1 = - segExtent; + s0 = Math.max( 0, - ( a01 * s1 + b0) ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + + } + + } else { + + if ( s1 <= - extDet) { + + // region 4 + + s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) ); + s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + + } else if ( s1 <= extDet ) { + + // region 3 + + s0 = 0; + s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent ); + sqrDist = s1 * ( s1 + 2 * b1 ) + c; + + } else { + + // region 2 + + s0 = Math.max( 0, - ( a01 * segExtent + b0 ) ); + s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + + } + + } + + } else { + + // Ray and segment are parallel. + + s1 = ( a01 > 0 ) ? - segExtent : segExtent; + s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + + } + + if ( optionalPointOnRay ) { + + optionalPointOnRay.copy( this.direction.clone().multiplyScalar( s0 ).add( this.origin ) ); + + } + + if ( optionalPointOnSegment ) { + + optionalPointOnSegment.copy( segDir.clone().multiplyScalar( s1 ).add( segCenter ) ); + + } + + return sqrDist; + + }, + + isIntersectionSphere: function ( sphere ) { + + return this.distanceToPoint( sphere.center ) <= sphere.radius; + + }, + + isIntersectionPlane: function ( plane ) { + + // check if the ray lies on the plane first + + var distToPoint = plane.distanceToPoint( this.origin ); + + if ( distToPoint === 0 ) { + + return true; + + } + + var denominator = plane.normal.dot( this.direction ); + + if ( denominator * distToPoint < 0 ) { + + return true + + } + + // ray origin is behind the plane (and is pointing behind it) + + return false; + + }, + + distanceToPlane: function ( plane ) { + + var denominator = plane.normal.dot( this.direction ); + if ( denominator == 0 ) { + + // line is coplanar, return origin + if( plane.distanceToPoint( this.origin ) == 0 ) { + + return 0; + + } + + // Null is preferable to undefined since undefined means.... it is undefined + + return null; + + } + + var t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator; + + // Return if the ray never intersects the plane + + return t >= 0 ? t : null; + + }, + + intersectPlane: function ( plane, optionalTarget ) { + + var t = this.distanceToPlane( plane ); + + if ( t === null ) { + + return null; + } + + return this.at( t, optionalTarget ); + + }, + + isIntersectionBox: function () { + + var v = new THREE.Vector3(); + + return function ( box ) { + + return this.intersectBox( box, v ) !== null; + + } + + }(), + + intersectBox: function ( box , optionalTarget ) { + + // http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-7-intersecting-simple-shapes/ray-box-intersection/ + + var tmin,tmax,tymin,tymax,tzmin,tzmax; + + var invdirx = 1/this.direction.x, + invdiry = 1/this.direction.y, + invdirz = 1/this.direction.z; + + var origin = this.origin; + + if (invdirx >= 0) { + + tmin = (box.min.x - origin.x) * invdirx; + tmax = (box.max.x - origin.x) * invdirx; + + } else { + + tmin = (box.max.x - origin.x) * invdirx; + tmax = (box.min.x - origin.x) * invdirx; + } + + if (invdiry >= 0) { + + tymin = (box.min.y - origin.y) * invdiry; + tymax = (box.max.y - origin.y) * invdiry; + + } else { + + tymin = (box.max.y - origin.y) * invdiry; + tymax = (box.min.y - origin.y) * invdiry; + } + + if ((tmin > tymax) || (tymin > tmax)) return null; + + // These lines also handle the case where tmin or tmax is NaN + // (result of 0 * Infinity). x !== x returns true if x is NaN + + if (tymin > tmin || tmin !== tmin ) tmin = tymin; + + if (tymax < tmax || tmax !== tmax ) tmax = tymax; + + if (invdirz >= 0) { + + tzmin = (box.min.z - origin.z) * invdirz; + tzmax = (box.max.z - origin.z) * invdirz; + + } else { + + tzmin = (box.max.z - origin.z) * invdirz; + tzmax = (box.min.z - origin.z) * invdirz; + } + + if ((tmin > tzmax) || (tzmin > tmax)) return null; + + if (tzmin > tmin || tmin !== tmin ) tmin = tzmin; + + if (tzmax < tmax || tmax !== tmax ) tmax = tzmax; + + //return point closest to the ray (positive side) + + if ( tmax < 0 ) return null; + + return this.at( tmin >= 0 ? tmin : tmax, optionalTarget ); + + }, + + intersectTriangle: function() { + + // Compute the offset origin, edges, and normal. + var diff = new THREE.Vector3(); + var edge1 = new THREE.Vector3(); + var edge2 = new THREE.Vector3(); + var normal = new THREE.Vector3(); + + return function ( a, b, c, backfaceCulling, optionalTarget ) { + + // from http://www.geometrictools.com/LibMathematics/Intersection/Wm5IntrRay3Triangle3.cpp + + edge1.subVectors( b, a ); + edge2.subVectors( c, a ); + normal.crossVectors( edge1, edge2 ); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, + // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + var DdN = this.direction.dot( normal ); + var sign; + + if ( DdN > 0 ) { + + if ( backfaceCulling ) return null; + sign = 1; + + } else if ( DdN < 0 ) { + + sign = - 1; + DdN = - DdN; + + } else { + + return null; + + } + + diff.subVectors( this.origin, a ); + var DdQxE2 = sign * this.direction.dot( edge2.crossVectors( diff, edge2 ) ); + + // b1 < 0, no intersection + if ( DdQxE2 < 0 ) { + + return null; + + } + + var DdE1xQ = sign * this.direction.dot( edge1.cross( diff ) ); + + // b2 < 0, no intersection + if ( DdE1xQ < 0 ) { + + return null; + + } + + // b1+b2 > 1, no intersection + if ( DdQxE2 + DdE1xQ > DdN ) { + + return null; + + } + + // Line intersects triangle, check if ray does. + var QdN = - sign * diff.dot( normal ); + + // t < 0, no intersection + if ( QdN < 0 ) { + + return null; + + } + + // Ray intersects triangle. + return this.at( QdN / DdN, optionalTarget ); + + } + + }(), + + applyMatrix4: function ( matrix4 ) { + + this.direction.add( this.origin ).applyMatrix4( matrix4 ); + this.origin.applyMatrix4( matrix4 ); + this.direction.sub( this.origin ); + this.direction.normalize(); + + return this; + }, + + equals: function ( ray ) { + + return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction ); + + }, + + clone: function () { + + return new THREE.Ray().copy( this ); + + } + +}; + +/** + * @author bhouston / http://exocortex.com + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Sphere = function ( center, radius ) { + + this.center = ( center !== undefined ) ? center : new THREE.Vector3(); + this.radius = ( radius !== undefined ) ? radius : 0; + +}; + +THREE.Sphere.prototype = { + + constructor: THREE.Sphere, + + set: function ( center, radius ) { + + this.center.copy( center ); + this.radius = radius; + + return this; + }, + + + setFromPoints: function () { + + var box = new THREE.Box3(); + + return function ( points, optionalCenter ) { + + var center = this.center; + + if ( optionalCenter !== undefined ) { + + center.copy( optionalCenter ); + + } else { + + box.setFromPoints( points ).center( center ); + + } + + var maxRadiusSq = 0; + + for ( var i = 0, il = points.length; i < il; i ++ ) { + + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( points[ i ] ) ); + + } + + this.radius = Math.sqrt( maxRadiusSq ); + + return this; + + }; + + }(), + + copy: function ( sphere ) { + + this.center.copy( sphere.center ); + this.radius = sphere.radius; + + return this; + + }, + + empty: function () { + + return ( this.radius <= 0 ); + + }, + + containsPoint: function ( point ) { + + return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) ); + + }, + + distanceToPoint: function ( point ) { + + return ( point.distanceTo( this.center ) - this.radius ); + + }, + + intersectsSphere: function ( sphere ) { + + var radiusSum = this.radius + sphere.radius; + + return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum ); + + }, + + clampPoint: function ( point, optionalTarget ) { + + var deltaLengthSq = this.center.distanceToSquared( point ); + + var result = optionalTarget || new THREE.Vector3(); + result.copy( point ); + + if ( deltaLengthSq > ( this.radius * this.radius ) ) { + + result.sub( this.center ).normalize(); + result.multiplyScalar( this.radius ).add( this.center ); + + } + + return result; + + }, + + getBoundingBox: function ( optionalTarget ) { + + var box = optionalTarget || new THREE.Box3(); + + box.set( this.center, this.center ); + box.expandByScalar( this.radius ); + + return box; + + }, + + applyMatrix4: function ( matrix ) { + + this.center.applyMatrix4( matrix ); + this.radius = this.radius * matrix.getMaxScaleOnAxis(); + + return this; + + }, + + translate: function ( offset ) { + + this.center.add( offset ); + + return this; + + }, + + equals: function ( sphere ) { + + return sphere.center.equals( this.center ) && ( sphere.radius === this.radius ); + + }, + + clone: function () { + + return new THREE.Sphere().copy( this ); + + } + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * @author bhouston / http://exocortex.com + */ + +THREE.Frustum = function ( p0, p1, p2, p3, p4, p5 ) { + + this.planes = [ + + ( p0 !== undefined ) ? p0 : new THREE.Plane(), + ( p1 !== undefined ) ? p1 : new THREE.Plane(), + ( p2 !== undefined ) ? p2 : new THREE.Plane(), + ( p3 !== undefined ) ? p3 : new THREE.Plane(), + ( p4 !== undefined ) ? p4 : new THREE.Plane(), + ( p5 !== undefined ) ? p5 : new THREE.Plane() + + ]; + +}; + +THREE.Frustum.prototype = { + + constructor: THREE.Frustum, + + set: function ( p0, p1, p2, p3, p4, p5 ) { + + var planes = this.planes; + + planes[0].copy( p0 ); + planes[1].copy( p1 ); + planes[2].copy( p2 ); + planes[3].copy( p3 ); + planes[4].copy( p4 ); + planes[5].copy( p5 ); + + return this; + + }, + + copy: function ( frustum ) { + + var planes = this.planes; + + for( var i = 0; i < 6; i ++ ) { + + planes[i].copy( frustum.planes[i] ); + + } + + return this; + + }, + + setFromMatrix: function ( m ) { + + var planes = this.planes; + var me = m.elements; + var me0 = me[0], me1 = me[1], me2 = me[2], me3 = me[3]; + var me4 = me[4], me5 = me[5], me6 = me[6], me7 = me[7]; + var me8 = me[8], me9 = me[9], me10 = me[10], me11 = me[11]; + var me12 = me[12], me13 = me[13], me14 = me[14], me15 = me[15]; + + planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize(); + planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize(); + planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize(); + planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize(); + planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize(); + planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize(); + + return this; + + }, + + intersectsObject: function () { + + var sphere = new THREE.Sphere(); + + return function ( object ) { + + var geometry = object.geometry; + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + sphere.copy( geometry.boundingSphere ); + sphere.applyMatrix4( object.matrixWorld ); + + return this.intersectsSphere( sphere ); + + }; + + }(), + + intersectsSphere: function ( sphere ) { + + var planes = this.planes; + var center = sphere.center; + var negRadius = -sphere.radius; + + for ( var i = 0; i < 6; i ++ ) { + + var distance = planes[ i ].distanceToPoint( center ); + + if ( distance < negRadius ) { + + return false; + + } + + } + + return true; + + }, + + intersectsBox : function() { + + var p1 = new THREE.Vector3(), + p2 = new THREE.Vector3(); + + return function( box ) { + + var planes = this.planes; + + for ( var i = 0; i < 6 ; i ++ ) { + + var plane = planes[i]; + + p1.x = plane.normal.x > 0 ? box.min.x : box.max.x; + p2.x = plane.normal.x > 0 ? box.max.x : box.min.x; + p1.y = plane.normal.y > 0 ? box.min.y : box.max.y; + p2.y = plane.normal.y > 0 ? box.max.y : box.min.y; + p1.z = plane.normal.z > 0 ? box.min.z : box.max.z; + p2.z = plane.normal.z > 0 ? box.max.z : box.min.z; + + var d1 = plane.distanceToPoint( p1 ); + var d2 = plane.distanceToPoint( p2 ); + + // if both outside plane, no intersection + + if ( d1 < 0 && d2 < 0 ) { + + return false; + + } + } + + return true; + }; + + }(), + + + containsPoint: function ( point ) { + + var planes = this.planes; + + for ( var i = 0; i < 6; i ++ ) { + + if ( planes[ i ].distanceToPoint( point ) < 0 ) { + + return false; + + } + + } + + return true; + + }, + + clone: function () { + + return new THREE.Frustum().copy( this ); + + } + +}; + +/** + * @author bhouston / http://exocortex.com + */ + +THREE.Plane = function ( normal, constant ) { + + this.normal = ( normal !== undefined ) ? normal : new THREE.Vector3( 1, 0, 0 ); + this.constant = ( constant !== undefined ) ? constant : 0; + +}; + +THREE.Plane.prototype = { + + constructor: THREE.Plane, + + set: function ( normal, constant ) { + + this.normal.copy( normal ); + this.constant = constant; + + return this; + + }, + + setComponents: function ( x, y, z, w ) { + + this.normal.set( x, y, z ); + this.constant = w; + + return this; + + }, + + setFromNormalAndCoplanarPoint: function ( normal, point ) { + + this.normal.copy( normal ); + this.constant = - point.dot( this.normal ); // must be this.normal, not normal, as this.normal is normalized + + return this; + + }, + + setFromCoplanarPoints: function() { + + var v1 = new THREE.Vector3(); + var v2 = new THREE.Vector3(); + + return function ( a, b, c ) { + + var normal = v1.subVectors( c, b ).cross( v2.subVectors( a, b ) ).normalize(); + + // Q: should an error be thrown if normal is zero (e.g. degenerate plane)? + + this.setFromNormalAndCoplanarPoint( normal, a ); + + return this; + + }; + + }(), + + + copy: function ( plane ) { + + this.normal.copy( plane.normal ); + this.constant = plane.constant; + + return this; + + }, + + normalize: function () { + + // Note: will lead to a divide by zero if the plane is invalid. + + var inverseNormalLength = 1.0 / this.normal.length(); + this.normal.multiplyScalar( inverseNormalLength ); + this.constant *= inverseNormalLength; + + return this; + + }, + + negate: function () { + + this.constant *= -1; + this.normal.negate(); + + return this; + + }, + + distanceToPoint: function ( point ) { + + return this.normal.dot( point ) + this.constant; + + }, + + distanceToSphere: function ( sphere ) { + + return this.distanceToPoint( sphere.center ) - sphere.radius; + + }, + + projectPoint: function ( point, optionalTarget ) { + + return this.orthoPoint( point, optionalTarget ).sub( point ).negate(); + + }, + + orthoPoint: function ( point, optionalTarget ) { + + var perpendicularMagnitude = this.distanceToPoint( point ); + + var result = optionalTarget || new THREE.Vector3(); + return result.copy( this.normal ).multiplyScalar( perpendicularMagnitude ); + + }, + + isIntersectionLine: function ( line ) { + + // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it. + + var startSign = this.distanceToPoint( line.start ); + var endSign = this.distanceToPoint( line.end ); + + return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 ); + + }, + + intersectLine: function() { + + var v1 = new THREE.Vector3(); + + return function ( line, optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + + var direction = line.delta( v1 ); + + var denominator = this.normal.dot( direction ); + + if ( denominator == 0 ) { + + // line is coplanar, return origin + if( this.distanceToPoint( line.start ) == 0 ) { + + return result.copy( line.start ); + + } + + // Unsure if this is the correct method to handle this case. + return undefined; + + } + + var t = - ( line.start.dot( this.normal ) + this.constant ) / denominator; + + if( t < 0 || t > 1 ) { + + return undefined; + + } + + return result.copy( direction ).multiplyScalar( t ).add( line.start ); + + }; + + }(), + + + coplanarPoint: function ( optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + return result.copy( this.normal ).multiplyScalar( - this.constant ); + + }, + + applyMatrix4: function() { + + var v1 = new THREE.Vector3(); + var v2 = new THREE.Vector3(); + var m1 = new THREE.Matrix3(); + + return function ( matrix, optionalNormalMatrix ) { + + // compute new normal based on theory here: + // http://www.songho.ca/opengl/gl_normaltransform.html + var normalMatrix = optionalNormalMatrix || m1.getNormalMatrix( matrix ); + var newNormal = v1.copy( this.normal ).applyMatrix3( normalMatrix ); + + var newCoplanarPoint = this.coplanarPoint( v2 ); + newCoplanarPoint.applyMatrix4( matrix ); + + this.setFromNormalAndCoplanarPoint( newNormal, newCoplanarPoint ); + + return this; + + }; + + }(), + + translate: function ( offset ) { + + this.constant = this.constant - offset.dot( this.normal ); + + return this; + + }, + + equals: function ( plane ) { + + return plane.normal.equals( this.normal ) && ( plane.constant == this.constant ); + + }, + + clone: function () { + + return new THREE.Plane().copy( this ); + + } + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Math = { + + PI2: Math.PI * 2, + + generateUUID: function () { + + // http://www.broofa.com/Tools/Math.uuid.htm + + var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); + var uuid = new Array(36); + var rnd = 0, r; + + return function () { + + for ( var i = 0; i < 36; i ++ ) { + + if ( i == 8 || i == 13 || i == 18 || i == 23 ) { + + uuid[ i ] = '-'; + + } else if ( i == 14 ) { + + uuid[ i ] = '4'; + + } else { + + if (rnd <= 0x02) rnd = 0x2000000 + (Math.random()*0x1000000)|0; + r = rnd & 0xf; + rnd = rnd >> 4; + uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; + + } + } + + return uuid.join(''); + + }; + + }(), + + // Clamp value to range + + clamp: function ( x, a, b ) { + + return ( x < a ) ? a : ( ( x > b ) ? b : x ); + + }, + + // Clamp value to range to range + + mapLinear: function ( x, a1, a2, b1, b2 ) { + + return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 ); + + }, + + // http://en.wikipedia.org/wiki/Smoothstep + + smoothstep: function ( x, min, max ) { + + if ( x <= min ) return 0; + if ( x >= max ) return 1; + + x = ( x - min )/( max - min ); + + return x*x*(3 - 2*x); + + }, + + smootherstep: function ( x, min, max ) { + + if ( x <= min ) return 0; + if ( x >= max ) return 1; + + x = ( x - min )/( max - min ); + + return x*x*x*(x*(x*6 - 15) + 10); + + }, + + // Random float from <0, 1> with 16 bits of randomness + // (standard Math.random() creates repetitive patterns when applied over larger space) + + random16: function () { + + return ( 65280 * Math.random() + 255 * Math.random() ) / 65535; + + }, + + // Random integer from interval + + randInt: function ( low, high ) { + + return low + Math.floor( Math.random() * ( high - low + 1 ) ); + + }, + + // Random float from interval + + randFloat: function ( low, high ) { + + return low + Math.random() * ( high - low ); + + }, + + // Random float from <-range/2, range/2> interval + + randFloatSpread: function ( range ) { + + return range * ( 0.5 - Math.random() ); + + }, + + sign: function ( x ) { + + return ( x < 0 ) ? - 1 : ( x > 0 ) ? 1 : 0; + + }, + + degToRad: function() { + + var degreeToRadiansFactor = Math.PI / 180; + + return function ( degrees ) { + + return degrees * degreeToRadiansFactor; + + }; + + }(), + + radToDeg: function() { + + var radianToDegreesFactor = 180 / Math.PI; + + return function ( radians ) { + + return radians * radianToDegreesFactor; + + }; + + }(), + + isPowerOfTwo: function ( value ) { + return ( value & ( value - 1 ) ) === 0 && value !== 0; + } + +}; + +/** + * Spline from Tween.js, slightly optimized (and trashed) + * http://sole.github.com/tween.js/examples/05_spline.html + * + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Spline = function ( points ) { + + this.points = points; + + var c = [], v3 = { x: 0, y: 0, z: 0 }, + point, intPoint, weight, w2, w3, + pa, pb, pc, pd; + + this.initFromArray = function( a ) { + + this.points = []; + + for ( var i = 0; i < a.length; i++ ) { + + this.points[ i ] = { x: a[ i ][ 0 ], y: a[ i ][ 1 ], z: a[ i ][ 2 ] }; + + } + + }; + + this.getPoint = function ( k ) { + + point = ( this.points.length - 1 ) * k; + intPoint = Math.floor( point ); + weight = point - intPoint; + + c[ 0 ] = intPoint === 0 ? intPoint : intPoint - 1; + c[ 1 ] = intPoint; + c[ 2 ] = intPoint > this.points.length - 2 ? this.points.length - 1 : intPoint + 1; + c[ 3 ] = intPoint > this.points.length - 3 ? this.points.length - 1 : intPoint + 2; + + pa = this.points[ c[ 0 ] ]; + pb = this.points[ c[ 1 ] ]; + pc = this.points[ c[ 2 ] ]; + pd = this.points[ c[ 3 ] ]; + + w2 = weight * weight; + w3 = weight * w2; + + v3.x = interpolate( pa.x, pb.x, pc.x, pd.x, weight, w2, w3 ); + v3.y = interpolate( pa.y, pb.y, pc.y, pd.y, weight, w2, w3 ); + v3.z = interpolate( pa.z, pb.z, pc.z, pd.z, weight, w2, w3 ); + + return v3; + + }; + + this.getControlPointsArray = function () { + + var i, p, l = this.points.length, + coords = []; + + for ( i = 0; i < l; i ++ ) { + + p = this.points[ i ]; + coords[ i ] = [ p.x, p.y, p.z ]; + + } + + return coords; + + }; + + // approximate length by summing linear segments + + this.getLength = function ( nSubDivisions ) { + + var i, index, nSamples, position, + point = 0, intPoint = 0, oldIntPoint = 0, + oldPosition = new THREE.Vector3(), + tmpVec = new THREE.Vector3(), + chunkLengths = [], + totalLength = 0; + + // first point has 0 length + + chunkLengths[ 0 ] = 0; + + if ( !nSubDivisions ) nSubDivisions = 100; + + nSamples = this.points.length * nSubDivisions; + + oldPosition.copy( this.points[ 0 ] ); + + for ( i = 1; i < nSamples; i ++ ) { + + index = i / nSamples; + + position = this.getPoint( index ); + tmpVec.copy( position ); + + totalLength += tmpVec.distanceTo( oldPosition ); + + oldPosition.copy( position ); + + point = ( this.points.length - 1 ) * index; + intPoint = Math.floor( point ); + + if ( intPoint != oldIntPoint ) { + + chunkLengths[ intPoint ] = totalLength; + oldIntPoint = intPoint; + + } + + } + + // last point ends with total length + + chunkLengths[ chunkLengths.length ] = totalLength; + + return { chunks: chunkLengths, total: totalLength }; + + }; + + this.reparametrizeByArcLength = function ( samplingCoef ) { + + var i, j, + index, indexCurrent, indexNext, + linearDistance, realDistance, + sampling, position, + newpoints = [], + tmpVec = new THREE.Vector3(), + sl = this.getLength(); + + newpoints.push( tmpVec.copy( this.points[ 0 ] ).clone() ); + + for ( i = 1; i < this.points.length; i++ ) { + + //tmpVec.copy( this.points[ i - 1 ] ); + //linearDistance = tmpVec.distanceTo( this.points[ i ] ); + + realDistance = sl.chunks[ i ] - sl.chunks[ i - 1 ]; + + sampling = Math.ceil( samplingCoef * realDistance / sl.total ); + + indexCurrent = ( i - 1 ) / ( this.points.length - 1 ); + indexNext = i / ( this.points.length - 1 ); + + for ( j = 1; j < sampling - 1; j++ ) { + + index = indexCurrent + j * ( 1 / sampling ) * ( indexNext - indexCurrent ); + + position = this.getPoint( index ); + newpoints.push( tmpVec.copy( position ).clone() ); + + } + + newpoints.push( tmpVec.copy( this.points[ i ] ).clone() ); + + } + + this.points = newpoints; + + }; + + // Catmull-Rom + + function interpolate( p0, p1, p2, p3, t, t2, t3 ) { + + var v0 = ( p2 - p0 ) * 0.5, + v1 = ( p3 - p1 ) * 0.5; + + return ( 2 * ( p1 - p2 ) + v0 + v1 ) * t3 + ( - 3 * ( p1 - p2 ) - 2 * v0 - v1 ) * t2 + v0 * t + p1; + + }; + +}; + +/** + * @author bhouston / http://exocortex.com + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Triangle = function ( a, b, c ) { + + this.a = ( a !== undefined ) ? a : new THREE.Vector3(); + this.b = ( b !== undefined ) ? b : new THREE.Vector3(); + this.c = ( c !== undefined ) ? c : new THREE.Vector3(); + +}; + +THREE.Triangle.normal = function() { + + var v0 = new THREE.Vector3(); + + return function ( a, b, c, optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + + result.subVectors( c, b ); + v0.subVectors( a, b ); + result.cross( v0 ); + + var resultLengthSq = result.lengthSq(); + if( resultLengthSq > 0 ) { + + return result.multiplyScalar( 1 / Math.sqrt( resultLengthSq ) ); + + } + + return result.set( 0, 0, 0 ); + + }; + +}(); + +// static/instance method to calculate barycoordinates +// based on: http://www.blackpawn.com/texts/pointinpoly/default.html +THREE.Triangle.barycoordFromPoint = function() { + + var v0 = new THREE.Vector3(); + var v1 = new THREE.Vector3(); + var v2 = new THREE.Vector3(); + + return function ( point, a, b, c, optionalTarget ) { + + v0.subVectors( c, a ); + v1.subVectors( b, a ); + v2.subVectors( point, a ); + + var dot00 = v0.dot( v0 ); + var dot01 = v0.dot( v1 ); + var dot02 = v0.dot( v2 ); + var dot11 = v1.dot( v1 ); + var dot12 = v1.dot( v2 ); + + var denom = ( dot00 * dot11 - dot01 * dot01 ); + + var result = optionalTarget || new THREE.Vector3(); + + // colinear or singular triangle + if( denom == 0 ) { + // arbitrary location outside of triangle? + // not sure if this is the best idea, maybe should be returning undefined + return result.set( -2, -1, -1 ); + } + + var invDenom = 1 / denom; + var u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom; + var v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom; + + // barycoordinates must always sum to 1 + return result.set( 1 - u - v, v, u ); + + }; + +}(); + +THREE.Triangle.containsPoint = function() { + + var v1 = new THREE.Vector3(); + + return function ( point, a, b, c ) { + + var result = THREE.Triangle.barycoordFromPoint( point, a, b, c, v1 ); + + return ( result.x >= 0 ) && ( result.y >= 0 ) && ( ( result.x + result.y ) <= 1 ); + + }; + +}(); + +THREE.Triangle.prototype = { + + constructor: THREE.Triangle, + + set: function ( a, b, c ) { + + this.a.copy( a ); + this.b.copy( b ); + this.c.copy( c ); + + return this; + + }, + + setFromPointsAndIndices: function ( points, i0, i1, i2 ) { + + this.a.copy( points[i0] ); + this.b.copy( points[i1] ); + this.c.copy( points[i2] ); + + return this; + + }, + + copy: function ( triangle ) { + + this.a.copy( triangle.a ); + this.b.copy( triangle.b ); + this.c.copy( triangle.c ); + + return this; + + }, + + area: function() { + + var v0 = new THREE.Vector3(); + var v1 = new THREE.Vector3(); + + return function () { + + v0.subVectors( this.c, this.b ); + v1.subVectors( this.a, this.b ); + + return v0.cross( v1 ).length() * 0.5; + + }; + + }(), + + midpoint: function ( optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + return result.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 ); + + }, + + normal: function ( optionalTarget ) { + + return THREE.Triangle.normal( this.a, this.b, this.c, optionalTarget ); + + }, + + plane: function ( optionalTarget ) { + + var result = optionalTarget || new THREE.Plane(); + + return result.setFromCoplanarPoints( this.a, this.b, this.c ); + + }, + + barycoordFromPoint: function ( point, optionalTarget ) { + + return THREE.Triangle.barycoordFromPoint( point, this.a, this.b, this.c, optionalTarget ); + + }, + + containsPoint: function ( point ) { + + return THREE.Triangle.containsPoint( point, this.a, this.b, this.c ); + + }, + + equals: function ( triangle ) { + + return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c ); + + }, + + clone: function () { + + return new THREE.Triangle().copy( this ); + + } + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Vertex = function ( v ) { + + console.warn( 'THREE.Vertex has been DEPRECATED. Use THREE.Vector3 instead.') + return v; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.UV = function ( u, v ) { + + console.warn( 'THREE.UV has been DEPRECATED. Use THREE.Vector2 instead.') + return new THREE.Vector2( u, v ); + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Clock = function ( autoStart ) { + + this.autoStart = ( autoStart !== undefined ) ? autoStart : true; + + this.startTime = 0; + this.oldTime = 0; + this.elapsedTime = 0; + + this.running = false; + +}; + +THREE.Clock.prototype = { + + constructor: THREE.Clock, + + start: function () { + + this.startTime = self.performance !== undefined && self.performance.now !== undefined + ? self.performance.now() + : Date.now(); + + this.oldTime = this.startTime; + this.running = true; + }, + + stop: function () { + + this.getElapsedTime(); + this.running = false; + + }, + + getElapsedTime: function () { + + this.getDelta(); + return this.elapsedTime; + + }, + + getDelta: function () { + + var diff = 0; + + if ( this.autoStart && ! this.running ) { + + this.start(); + + } + + if ( this.running ) { + + var newTime = self.performance !== undefined && self.performance.now !== undefined + ? self.performance.now() + : Date.now(); + + diff = 0.001 * ( newTime - this.oldTime ); + this.oldTime = newTime; + + this.elapsedTime += diff; + + } + + return diff; + + } + +}; + +/** + * https://github.com/mrdoob/eventdispatcher.js/ + */ + +THREE.EventDispatcher = function () {} + +THREE.EventDispatcher.prototype = { + + constructor: THREE.EventDispatcher, + + apply: function ( object ) { + + object.addEventListener = THREE.EventDispatcher.prototype.addEventListener; + object.hasEventListener = THREE.EventDispatcher.prototype.hasEventListener; + object.removeEventListener = THREE.EventDispatcher.prototype.removeEventListener; + object.dispatchEvent = THREE.EventDispatcher.prototype.dispatchEvent; + + }, + + addEventListener: function ( type, listener ) { + + if ( this._listeners === undefined ) this._listeners = {}; + + var listeners = this._listeners; + + if ( listeners[ type ] === undefined ) { + + listeners[ type ] = []; + + } + + if ( listeners[ type ].indexOf( listener ) === - 1 ) { + + listeners[ type ].push( listener ); + + } + + }, + + hasEventListener: function ( type, listener ) { + + if ( this._listeners === undefined ) return false; + + var listeners = this._listeners; + + if ( listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1 ) { + + return true; + + } + + return false; + + }, + + removeEventListener: function ( type, listener ) { + + if ( this._listeners === undefined ) return; + + var listeners = this._listeners; + var listenerArray = listeners[ type ]; + + if ( listenerArray !== undefined ) { + + var index = listenerArray.indexOf( listener ); + + if ( index !== - 1 ) { + + listenerArray.splice( index, 1 ); + + } + + } + + }, + + dispatchEvent: function () { + + var array = []; + + return function ( event ) { + + if ( this._listeners === undefined ) return; + + var listeners = this._listeners; + var listenerArray = listeners[ event.type ]; + + if ( listenerArray !== undefined ) { + + event.target = this; + + var length = listenerArray.length; + + for ( var i = 0; i < length; i ++ ) { + + array[ i ] = listenerArray[ i ]; + + } + + for ( var i = 0; i < length; i ++ ) { + + array[ i ].call( this, event ); + + } + + } + + }; + + }() + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author bhouston / http://exocortex.com/ + * @author stephomi / http://stephaneginier.com/ + */ + +( function ( THREE ) { + + THREE.Raycaster = function ( origin, direction, near, far ) { + + this.ray = new THREE.Ray( origin, direction ); + // direction is assumed to be normalized (for accurate distance calculations) + + this.near = near || 0; + this.far = far || Infinity; + + }; + + var sphere = new THREE.Sphere(); + var localRay = new THREE.Ray(); + var facePlane = new THREE.Plane(); + var intersectPoint = new THREE.Vector3(); + var matrixPosition = new THREE.Vector3(); + + var inverseMatrix = new THREE.Matrix4(); + + var descSort = function ( a, b ) { + + return a.distance - b.distance; + + }; + + var vA = new THREE.Vector3(); + var vB = new THREE.Vector3(); + var vC = new THREE.Vector3(); + + var intersectObject = function ( object, raycaster, intersects ) { + + if ( object instanceof THREE.Sprite ) { + + matrixPosition.setFromMatrixPosition( object.matrixWorld ); + + var distance = raycaster.ray.distanceToPoint( matrixPosition ); + + if ( distance > object.scale.x ) { + + return intersects; + + } + + intersects.push( { + + distance: distance, + point: object.position, + face: null, + object: object + + } ); + + } else if ( object instanceof THREE.LOD ) { + + matrixPosition.setFromMatrixPosition( object.matrixWorld ); + var distance = raycaster.ray.origin.distanceTo( matrixPosition ); + + intersectObject( object.getObjectForDistance( distance ), raycaster, intersects ); + + } else if ( object instanceof THREE.Mesh ) { + + var geometry = object.geometry; + + // Checking boundingSphere distance to ray + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + sphere.copy( geometry.boundingSphere ); + sphere.applyMatrix4( object.matrixWorld ); + + if ( raycaster.ray.isIntersectionSphere( sphere ) === false ) { + + return intersects; + + } + + // Check boundingBox before continuing + + inverseMatrix.getInverse( object.matrixWorld ); + localRay.copy( raycaster.ray ).applyMatrix4( inverseMatrix ); + + if ( geometry.boundingBox !== null ) { + + if ( localRay.isIntersectionBox( geometry.boundingBox ) === false ) { + + return intersects; + + } + + } + + if ( geometry instanceof THREE.BufferGeometry ) { + + var material = object.material; + + if ( material === undefined ) return intersects; + + var attributes = geometry.attributes; + + var a, b, c; + var precision = raycaster.precision; + + if ( attributes.index !== undefined ) { + + var offsets = geometry.offsets; + var indices = attributes.index.array; + var positions = attributes.position.array; + + for ( var oi = 0, ol = offsets.length; oi < ol; ++oi ) { + + var start = offsets[ oi ].start; + var count = offsets[ oi ].count; + var index = offsets[ oi ].index; + + for ( var i = start, il = start + count; i < il; i += 3 ) { + + a = index + indices[ i ]; + b = index + indices[ i + 1 ]; + c = index + indices[ i + 2 ]; + + vA.set( + positions[ a * 3 ], + positions[ a * 3 + 1 ], + positions[ a * 3 + 2 ] + ); + vB.set( + positions[ b * 3 ], + positions[ b * 3 + 1 ], + positions[ b * 3 + 2 ] + ); + vC.set( + positions[ c * 3 ], + positions[ c * 3 + 1 ], + positions[ c * 3 + 2 ] + ); + + + if ( material.side === THREE.BackSide ) { + + var intersectionPoint = localRay.intersectTriangle( vC, vB, vA, true ); + + } else { + + var intersectionPoint = localRay.intersectTriangle( vA, vB, vC, material.side !== THREE.DoubleSide ); + + } + + if ( intersectionPoint === null ) continue; + + intersectionPoint.applyMatrix4( object.matrixWorld ); + + var distance = raycaster.ray.origin.distanceTo( intersectionPoint ); + + if ( distance < precision || distance < raycaster.near || distance > raycaster.far ) continue; + + intersects.push( { + + distance: distance, + point: intersectionPoint, + indices: [a, b, c], + face: null, + faceIndex: null, + object: object + + } ); + + } + + } + + } else { + + var offsets = geometry.offsets; + var positions = attributes.position.array; + + for ( var i = 0, il = attributes.position.array.length; i < il; i += 3 ) { + + a = i; + b = i + 1; + c = i + 2; + + vA.set( + positions[ a * 3 ], + positions[ a * 3 + 1 ], + positions[ a * 3 + 2 ] + ); + vB.set( + positions[ b * 3 ], + positions[ b * 3 + 1 ], + positions[ b * 3 + 2 ] + ); + vC.set( + positions[ c * 3 ], + positions[ c * 3 + 1 ], + positions[ c * 3 + 2 ] + ); + + + if ( material.side === THREE.BackSide ) { + + var intersectionPoint = localRay.intersectTriangle( vC, vB, vA, true ); + + } else { + + var intersectionPoint = localRay.intersectTriangle( vA, vB, vC, material.side !== THREE.DoubleSide ); + + } + + if ( intersectionPoint === null ) continue; + + intersectionPoint.applyMatrix4( object.matrixWorld ); + + var distance = raycaster.ray.origin.distanceTo( intersectionPoint ); + + if ( distance < precision || distance < raycaster.near || distance > raycaster.far ) continue; + + intersects.push( { + + distance: distance, + point: intersectionPoint, + indices: [a, b, c], + face: null, + faceIndex: null, + object: object + + } ); + + } + + } + + } else if ( geometry instanceof THREE.Geometry ) { + + var isFaceMaterial = object.material instanceof THREE.MeshFaceMaterial; + var objectMaterials = isFaceMaterial === true ? object.material.materials : null; + + var a, b, c, d; + var precision = raycaster.precision; + + var vertices = geometry.vertices; + + for ( var f = 0, fl = geometry.faces.length; f < fl; f ++ ) { + + var face = geometry.faces[ f ]; + + var material = isFaceMaterial === true ? objectMaterials[ face.materialIndex ] : object.material; + + if ( material === undefined ) continue; + + a = vertices[ face.a ]; + b = vertices[ face.b ]; + c = vertices[ face.c ]; + + if ( material.morphTargets === true ) { + + var morphTargets = geometry.morphTargets; + var morphInfluences = object.morphTargetInfluences; + + vA.set( 0, 0, 0 ); + vB.set( 0, 0, 0 ); + vC.set( 0, 0, 0 ); + + for ( var t = 0, tl = morphTargets.length; t < tl; t ++ ) { + + var influence = morphInfluences[ t ]; + + if ( influence === 0 ) continue; + + var targets = morphTargets[ t ].vertices; + + vA.x += ( targets[ face.a ].x - a.x ) * influence; + vA.y += ( targets[ face.a ].y - a.y ) * influence; + vA.z += ( targets[ face.a ].z - a.z ) * influence; + + vB.x += ( targets[ face.b ].x - b.x ) * influence; + vB.y += ( targets[ face.b ].y - b.y ) * influence; + vB.z += ( targets[ face.b ].z - b.z ) * influence; + + vC.x += ( targets[ face.c ].x - c.x ) * influence; + vC.y += ( targets[ face.c ].y - c.y ) * influence; + vC.z += ( targets[ face.c ].z - c.z ) * influence; + + } + + vA.add( a ); + vB.add( b ); + vC.add( c ); + + a = vA; + b = vB; + c = vC; + + } + + if ( material.side === THREE.BackSide ) { + + var intersectionPoint = localRay.intersectTriangle( c, b, a, true ); + + } else { + + var intersectionPoint = localRay.intersectTriangle( a, b, c, material.side !== THREE.DoubleSide ); + + } + + if ( intersectionPoint === null ) continue; + + intersectionPoint.applyMatrix4( object.matrixWorld ); + + var distance = raycaster.ray.origin.distanceTo( intersectionPoint ); + + if ( distance < precision || distance < raycaster.near || distance > raycaster.far ) continue; + + intersects.push( { + + distance: distance, + point: intersectionPoint, + face: face, + faceIndex: f, + object: object + + } ); + + } + + } + + } else if ( object instanceof THREE.Line ) { + + var precision = raycaster.linePrecision; + var precisionSq = precision * precision; + + var geometry = object.geometry; + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + // Checking boundingSphere distance to ray + + sphere.copy( geometry.boundingSphere ); + sphere.applyMatrix4( object.matrixWorld ); + + if ( raycaster.ray.isIntersectionSphere( sphere ) === false ) { + + return intersects; + + } + + inverseMatrix.getInverse( object.matrixWorld ); + localRay.copy( raycaster.ray ).applyMatrix4( inverseMatrix ); + + /* if ( geometry instanceof THREE.BufferGeometry ) { + + } else */ if ( geometry instanceof THREE.Geometry ) { + + var vertices = geometry.vertices; + var nbVertices = vertices.length; + var interSegment = new THREE.Vector3(); + var interRay = new THREE.Vector3(); + var step = object.type === THREE.LineStrip ? 1 : 2; + + for ( var i = 0; i < nbVertices - 1; i = i + step ) { + + var distSq = localRay.distanceSqToSegment( vertices[ i ], vertices[ i + 1 ], interRay, interSegment ); + + if ( distSq > precisionSq ) continue; + + var distance = localRay.origin.distanceTo( interRay ); + + if ( distance < raycaster.near || distance > raycaster.far ) continue; + + intersects.push( { + + distance: distance, + // What do we want? intersection point on the ray or on the segment?? + // point: raycaster.ray.at( distance ), + point: interSegment.clone().applyMatrix4( object.matrixWorld ), + face: null, + faceIndex: null, + object: object + + } ); + + } + + } + + } + + }; + + var intersectDescendants = function ( object, raycaster, intersects ) { + + var descendants = object.getDescendants(); + + for ( var i = 0, l = descendants.length; i < l; i ++ ) { + + intersectObject( descendants[ i ], raycaster, intersects ); + + } + }; + + // + + THREE.Raycaster.prototype.precision = 0.0001; + THREE.Raycaster.prototype.linePrecision = 1; + + THREE.Raycaster.prototype.set = function ( origin, direction ) { + + this.ray.set( origin, direction ); + // direction is assumed to be normalized (for accurate distance calculations) + + }; + + THREE.Raycaster.prototype.intersectObject = function ( object, recursive ) { + + var intersects = []; + + if ( recursive === true ) { + + intersectDescendants( object, this, intersects ); + + } + + intersectObject( object, this, intersects ); + + intersects.sort( descSort ); + + return intersects; + + }; + + THREE.Raycaster.prototype.intersectObjects = function ( objects, recursive ) { + + var intersects = []; + + for ( var i = 0, l = objects.length; i < l; i ++ ) { + + intersectObject( objects[ i ], this, intersects ); + + if ( recursive === true ) { + + intersectDescendants( objects[ i ], this, intersects ); + + } + + } + + intersects.sort( descSort ); + + return intersects; + + }; + +}( THREE ) ); + +/** + * @author mrdoob / http://mrdoob.com/ + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + * @author WestLangley / http://github.com/WestLangley + */ + +THREE.Object3D = function () { + + this.id = THREE.Object3DIdCount ++; + this.uuid = THREE.Math.generateUUID(); + + this.name = ''; + + this.parent = undefined; + this.children = []; + + this.up = new THREE.Vector3( 0, 1, 0 ); + + this.position = new THREE.Vector3(); + this._rotation = new THREE.Euler(); + this._quaternion = new THREE.Quaternion(); + this.scale = new THREE.Vector3( 1, 1, 1 ); + + // keep rotation and quaternion in sync + + this._rotation._quaternion = this.quaternion; + this._quaternion._euler = this.rotation; + + this.renderDepth = null; + + this.rotationAutoUpdate = true; + + this.matrix = new THREE.Matrix4(); + this.matrixWorld = new THREE.Matrix4(); + + this.matrixAutoUpdate = true; + this.matrixWorldNeedsUpdate = true; + + this.visible = true; + + this.castShadow = false; + this.receiveShadow = false; + + this.frustumCulled = true; + + this.userData = {}; + +}; + + +THREE.Object3D.prototype = { + + constructor: THREE.Object3D, + + get rotation () { + return this._rotation; + }, + + set rotation ( value ) { + + this._rotation = value; + this._rotation._quaternion = this._quaternion; + this._quaternion._euler = this._rotation; + this._rotation._updateQuaternion(); + + }, + + get quaternion () { + return this._quaternion; + }, + + set quaternion ( value ) { + + this._quaternion = value; + this._quaternion._euler = this._rotation; + this._rotation._quaternion = this._quaternion; + this._quaternion._updateEuler(); + + }, + + get eulerOrder () { + + console.warn( 'DEPRECATED: Object3D\'s .eulerOrder has been moved to Object3D\'s .rotation.order.' ); + + return this.rotation.order; + + }, + + set eulerOrder ( value ) { + + console.warn( 'DEPRECATED: Object3D\'s .eulerOrder has been moved to Object3D\'s .rotation.order.' ); + + this.rotation.order = value; + + }, + + get useQuaternion () { + + console.warn( 'DEPRECATED: Object3D\'s .useQuaternion has been removed. The library now uses quaternions by default.' ); + + }, + + set useQuaternion ( value ) { + + console.warn( 'DEPRECATED: Object3D\'s .useQuaternion has been removed. The library now uses quaternions by default.' ); + + }, + + applyMatrix: function ( matrix ) { + + this.matrix.multiplyMatrices( matrix, this.matrix ); + + this.matrix.decompose( this.position, this.quaternion, this.scale ); + + }, + + setRotationFromAxisAngle: function ( axis, angle ) { + + // assumes axis is normalized + + this.quaternion.setFromAxisAngle( axis, angle ); + + }, + + setRotationFromEuler: function ( euler ) { + + this.quaternion.setFromEuler( euler, true ); + + }, + + setRotationFromMatrix: function ( m ) { + + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + + this.quaternion.setFromRotationMatrix( m ); + + }, + + setRotationFromQuaternion: function ( q ) { + + // assumes q is normalized + + this.quaternion.copy( q ); + + }, + + rotateOnAxis: function() { + + // rotate object on axis in object space + // axis is assumed to be normalized + + var q1 = new THREE.Quaternion(); + + return function ( axis, angle ) { + + q1.setFromAxisAngle( axis, angle ); + + this.quaternion.multiply( q1 ); + + return this; + + } + + }(), + + rotateX: function () { + + var v1 = new THREE.Vector3( 1, 0, 0 ); + + return function ( angle ) { + + return this.rotateOnAxis( v1, angle ); + + }; + + }(), + + rotateY: function () { + + var v1 = new THREE.Vector3( 0, 1, 0 ); + + return function ( angle ) { + + return this.rotateOnAxis( v1, angle ); + + }; + + }(), + + rotateZ: function () { + + var v1 = new THREE.Vector3( 0, 0, 1 ); + + return function ( angle ) { + + return this.rotateOnAxis( v1, angle ); + + }; + + }(), + + translateOnAxis: function () { + + // translate object by distance along axis in object space + // axis is assumed to be normalized + + var v1 = new THREE.Vector3(); + + return function ( axis, distance ) { + + v1.copy( axis ); + + v1.applyQuaternion( this.quaternion ); + + this.position.add( v1.multiplyScalar( distance ) ); + + return this; + + } + + }(), + + translate: function ( distance, axis ) { + + console.warn( 'DEPRECATED: Object3D\'s .translate() has been removed. Use .translateOnAxis( axis, distance ) instead. Note args have been changed.' ); + return this.translateOnAxis( axis, distance ); + + }, + + translateX: function () { + + var v1 = new THREE.Vector3( 1, 0, 0 ); + + return function ( distance ) { + + return this.translateOnAxis( v1, distance ); + + }; + + }(), + + translateY: function () { + + var v1 = new THREE.Vector3( 0, 1, 0 ); + + return function ( distance ) { + + return this.translateOnAxis( v1, distance ); + + }; + + }(), + + translateZ: function () { + + var v1 = new THREE.Vector3( 0, 0, 1 ); + + return function ( distance ) { + + return this.translateOnAxis( v1, distance ); + + }; + + }(), + + localToWorld: function ( vector ) { + + return vector.applyMatrix4( this.matrixWorld ); + + }, + + worldToLocal: function () { + + var m1 = new THREE.Matrix4(); + + return function ( vector ) { + + return vector.applyMatrix4( m1.getInverse( this.matrixWorld ) ); + + }; + + }(), + + lookAt: function () { + + // This routine does not support objects with rotated and/or translated parent(s) + + var m1 = new THREE.Matrix4(); + + return function ( vector ) { + + m1.lookAt( vector, this.position, this.up ); + + this.quaternion.setFromRotationMatrix( m1 ); + + }; + + }(), + + add: function ( object ) { + + if ( object === this ) { + + console.warn( 'THREE.Object3D.add: An object can\'t be added as a child of itself.' ); + return; + + } + + if ( object instanceof THREE.Object3D ) { + + if ( object.parent !== undefined ) { + + object.parent.remove( object ); + + } + + object.parent = this; + object.dispatchEvent( { type: 'added' } ); + + this.children.push( object ); + + // add to scene + + var scene = this; + + while ( scene.parent !== undefined ) { + + scene = scene.parent; + + } + + if ( scene !== undefined && scene instanceof THREE.Scene ) { + + scene.__addObject( object ); + + } + + } + + }, + + remove: function ( object ) { + + var index = this.children.indexOf( object ); + + if ( index !== - 1 ) { + + object.parent = undefined; + object.dispatchEvent( { type: 'removed' } ); + + this.children.splice( index, 1 ); + + // remove from scene + + var scene = this; + + while ( scene.parent !== undefined ) { + + scene = scene.parent; + + } + + if ( scene !== undefined && scene instanceof THREE.Scene ) { + + scene.__removeObject( object ); + + } + + } + + }, + + traverse: function ( callback ) { + + callback( this ); + + for ( var i = 0, l = this.children.length; i < l; i ++ ) { + + this.children[ i ].traverse( callback ); + + } + + }, + + getObjectById: function ( id, recursive ) { + + for ( var i = 0, l = this.children.length; i < l; i ++ ) { + + var child = this.children[ i ]; + + if ( child.id === id ) { + + return child; + + } + + if ( recursive === true ) { + + child = child.getObjectById( id, recursive ); + + if ( child !== undefined ) { + + return child; + + } + + } + + } + + return undefined; + + }, + + getObjectByName: function ( name, recursive ) { + + for ( var i = 0, l = this.children.length; i < l; i ++ ) { + + var child = this.children[ i ]; + + if ( child.name === name ) { + + return child; + + } + + if ( recursive === true ) { + + child = child.getObjectByName( name, recursive ); + + if ( child !== undefined ) { + + return child; + + } + + } + + } + + return undefined; + + }, + + getChildByName: function ( name, recursive ) { + + console.warn( 'DEPRECATED: Object3D\'s .getChildByName() has been renamed to .getObjectByName().' ); + return this.getObjectByName( name, recursive ); + + }, + + getDescendants: function ( array ) { + + if ( array === undefined ) array = []; + + Array.prototype.push.apply( array, this.children ); + + for ( var i = 0, l = this.children.length; i < l; i ++ ) { + + this.children[ i ].getDescendants( array ); + + } + + return array; + + }, + + updateMatrix: function () { + + this.matrix.compose( this.position, this.quaternion, this.scale ); + + this.matrixWorldNeedsUpdate = true; + + }, + + updateMatrixWorld: function ( force ) { + + if ( this.matrixAutoUpdate === true ) this.updateMatrix(); + + if ( this.matrixWorldNeedsUpdate === true || force === true ) { + + if ( this.parent === undefined ) { + + this.matrixWorld.copy( this.matrix ); + + } else { + + this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); + + } + + this.matrixWorldNeedsUpdate = false; + + force = true; + + } + + // update children + + for ( var i = 0, l = this.children.length; i < l; i ++ ) { + + this.children[ i ].updateMatrixWorld( force ); + + } + + }, + + clone: function ( object, recursive ) { + + if ( object === undefined ) object = new THREE.Object3D(); + if ( recursive === undefined ) recursive = true; + + object.name = this.name; + + object.up.copy( this.up ); + + object.position.copy( this.position ); + object.quaternion.copy( this.quaternion ); + object.scale.copy( this.scale ); + + object.renderDepth = this.renderDepth; + + object.rotationAutoUpdate = this.rotationAutoUpdate; + + object.matrix.copy( this.matrix ); + object.matrixWorld.copy( this.matrixWorld ); + + object.matrixAutoUpdate = this.matrixAutoUpdate; + object.matrixWorldNeedsUpdate = this.matrixWorldNeedsUpdate; + + object.visible = this.visible; + + object.castShadow = this.castShadow; + object.receiveShadow = this.receiveShadow; + + object.frustumCulled = this.frustumCulled; + + object.userData = JSON.parse( JSON.stringify( this.userData ) ); + + if ( recursive === true ) { + + for ( var i = 0; i < this.children.length; i ++ ) { + + var child = this.children[ i ]; + object.add( child.clone() ); + + } + + } + + return object; + + } + +}; + +THREE.EventDispatcher.prototype.apply( THREE.Object3D.prototype ); + +THREE.Object3DIdCount = 0; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author supereggbert / http://www.paulbrunt.co.uk/ + * @author julianwa / https://github.com/julianwa + */ + +THREE.Projector = function () { + + var _object, _objectCount, _objectPool = [], _objectPoolLength = 0, + _vertex, _vertexCount, _vertexPool = [], _vertexPoolLength = 0, + _face, _faceCount, _facePool = [], _facePoolLength = 0, + _line, _lineCount, _linePool = [], _linePoolLength = 0, + _sprite, _spriteCount, _spritePool = [], _spritePoolLength = 0, + + _renderData = { objects: [], lights: [], elements: [] }, + + _vA = new THREE.Vector3(), + _vB = new THREE.Vector3(), + _vC = new THREE.Vector3(), + + _vector3 = new THREE.Vector3(), + _vector4 = new THREE.Vector4(), + + _clipBox = new THREE.Box3( new THREE.Vector3( -1, -1, -1 ), new THREE.Vector3( 1, 1, 1 ) ), + _boundingBox = new THREE.Box3(), + _points3 = new Array( 3 ), + _points4 = new Array( 4 ), + + _viewMatrix = new THREE.Matrix4(), + _viewProjectionMatrix = new THREE.Matrix4(), + + _modelMatrix, + _modelViewProjectionMatrix = new THREE.Matrix4(), + + _normalMatrix = new THREE.Matrix3(), + + _frustum = new THREE.Frustum(), + + _clippedVertex1PositionScreen = new THREE.Vector4(), + _clippedVertex2PositionScreen = new THREE.Vector4(); + + this.projectVector = function ( vector, camera ) { + + camera.matrixWorldInverse.getInverse( camera.matrixWorld ); + + _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); + + return vector.applyProjection( _viewProjectionMatrix ); + + }; + + this.unprojectVector = function () { + + var projectionMatrixInverse = new THREE.Matrix4(); + + return function ( vector, camera ) { + + projectionMatrixInverse.getInverse( camera.projectionMatrix ); + _viewProjectionMatrix.multiplyMatrices( camera.matrixWorld, projectionMatrixInverse ); + + return vector.applyProjection( _viewProjectionMatrix ); + + }; + + }(); + + this.pickingRay = function ( vector, camera ) { + + // set two vectors with opposing z values + vector.z = -1.0; + var end = new THREE.Vector3( vector.x, vector.y, 1.0 ); + + this.unprojectVector( vector, camera ); + this.unprojectVector( end, camera ); + + // find direction from vector to end + end.sub( vector ).normalize(); + + return new THREE.Raycaster( vector, end ); + + }; + + var projectObject = function ( object ) { + + if ( object.visible === false ) return; + + if ( object instanceof THREE.Light ) { + + _renderData.lights.push( object ); + + } else if ( object instanceof THREE.Mesh || object instanceof THREE.Line || object instanceof THREE.Sprite ) { + + if ( object.frustumCulled === false || _frustum.intersectsObject( object ) === true ) { + + _object = getNextObjectInPool(); + _object.id = object.id; + _object.object = object; + + if ( object.renderDepth !== null ) { + + _object.z = object.renderDepth; + + } else { + + _vector3.setFromMatrixPosition( object.matrixWorld ); + _vector3.applyProjection( _viewProjectionMatrix ); + _object.z = _vector3.z; + + } + + _renderData.objects.push( _object ); + + } + + } + + for ( var i = 0, l = object.children.length; i < l; i ++ ) { + + projectObject( object.children[ i ] ); + + } + + }; + + var projectGraph = function ( root, sortObjects ) { + + _objectCount = 0; + + _renderData.objects.length = 0; + _renderData.lights.length = 0; + + projectObject( root ); + + if ( sortObjects === true ) { + + _renderData.objects.sort( painterSort ); + + } + + }; + + var RenderList = function () { + + var normals = []; + + var object = null; + var normalMatrix = new THREE.Matrix3(); + + var setObject = function ( value ) { + + object = value; + normalMatrix.getNormalMatrix( object.matrixWorld ); + + normals.length = 0; + + }; + + var projectVertex = function ( vertex ) { + + var position = vertex.position; + var positionWorld = vertex.positionWorld; + var positionScreen = vertex.positionScreen; + + positionWorld.copy( position ).applyMatrix4( _modelMatrix ); + positionScreen.copy( positionWorld ).applyMatrix4( _viewProjectionMatrix ); + + var invW = 1 / positionScreen.w; + + positionScreen.x *= invW; + positionScreen.y *= invW; + positionScreen.z *= invW; + + vertex.visible = positionScreen.x >= -1 && positionScreen.x <= 1 && + positionScreen.y >= -1 && positionScreen.y <= 1 && + positionScreen.z >= -1 && positionScreen.z <= 1; + + }; + + var pushVertex = function ( x, y, z ) { + + _vertex = getNextVertexInPool(); + _vertex.position.set( x, y, z ); + + projectVertex( _vertex ); + + }; + + var pushNormal = function ( x, y, z ) { + + normals.push( x, y, z ); + + }; + + var checkTriangleVisibility = function ( v1, v2, v3 ) { + + _points3[ 0 ] = v1.positionScreen; + _points3[ 1 ] = v2.positionScreen; + _points3[ 2 ] = v3.positionScreen; + + if ( v1.visible === true || v2.visible === true || v3.visible === true || + _clipBox.isIntersectionBox( _boundingBox.setFromPoints( _points3 ) ) ) { + + return ( ( v3.positionScreen.x - v1.positionScreen.x ) * + ( v2.positionScreen.y - v1.positionScreen.y ) - + ( v3.positionScreen.y - v1.positionScreen.y ) * + ( v2.positionScreen.x - v1.positionScreen.x ) ) < 0; + + } + + return false; + + }; + + var pushLine = function ( a, b ) { + + var v1 = _vertexPool[ a ]; + var v2 = _vertexPool[ b ]; + + _line = getNextLineInPool(); + + _line.id = object.id; + _line.v1.copy( v1 ); + _line.v2.copy( v2 ); + _line.z = ( v1.positionScreen.z + v2.positionScreen.z ) / 2; + + _line.material = object.material; + + _renderData.elements.push( _line ); + + }; + + var pushTriangle = function ( a, b, c ) { + + var v1 = _vertexPool[ a ]; + var v2 = _vertexPool[ b ]; + var v3 = _vertexPool[ c ]; + + if ( checkTriangleVisibility( v1, v2, v3 ) === true ) { + + _face = getNextFaceInPool(); + + _face.id = object.id; + _face.v1.copy( v1 ); + _face.v2.copy( v2 ); + _face.v3.copy( v3 ); + _face.z = ( v1.positionScreen.z + v2.positionScreen.z + v3.positionScreen.z ) / 3; + + for ( var i = 0; i < 3; i ++ ) { + + var offset = arguments[ i ] * 3; + var normal = _face.vertexNormalsModel[ i ]; + + normal.set( normals[ offset + 0 ], normals[ offset + 1 ], normals[ offset + 2 ] ); + normal.applyMatrix3( normalMatrix ).normalize(); + + } + + _face.vertexNormalsLength = 3; + + _face.material = object.material; + + _renderData.elements.push( _face ); + + } + + }; + + return { + setObject: setObject, + projectVertex: projectVertex, + checkTriangleVisibility: checkTriangleVisibility, + pushVertex: pushVertex, + pushNormal: pushNormal, + pushLine: pushLine, + pushTriangle: pushTriangle + } + + }; + + var renderList = new RenderList(); + + this.projectScene = function ( scene, camera, sortObjects, sortElements ) { + + var object, geometry, vertices, faces, face, faceVertexNormals, faceVertexUvs, uvs, + isFaceMaterial, objectMaterials; + + _faceCount = 0; + _lineCount = 0; + _spriteCount = 0; + + _renderData.elements.length = 0; + + if ( scene.autoUpdate === true ) scene.updateMatrixWorld(); + if ( camera.parent === undefined ) camera.updateMatrixWorld(); + + _viewMatrix.copy( camera.matrixWorldInverse.getInverse( camera.matrixWorld ) ); + _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix ); + + _frustum.setFromMatrix( _viewProjectionMatrix ); + + projectGraph( scene, sortObjects ); + + for ( var o = 0, ol = _renderData.objects.length; o < ol; o ++ ) { + + object = _renderData.objects[ o ].object; + geometry = object.geometry; + + renderList.setObject( object ); + + _modelMatrix = object.matrixWorld; + + _vertexCount = 0; + + if ( object instanceof THREE.Mesh ) { + + if ( geometry instanceof THREE.BufferGeometry ) { + + var attributes = geometry.attributes; + var offsets = geometry.offsets; + + if ( attributes.position === undefined ) continue; + + var positions = attributes.position.array; + + for ( var i = 0, l = positions.length; i < l; i += 3 ) { + + renderList.pushVertex( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] ); + + } + + var normals = attributes.normal.array; + + for ( var i = 0, l = normals.length; i < l; i += 3 ) { + + renderList.pushNormal( normals[ i ], normals[ i + 1 ], normals[ i + 2 ] ); + + } + + if ( attributes.index !== undefined ) { + + var indices = attributes.index.array; + + if ( offsets.length > 0 ) { + + for ( var o = 0; o < offsets.length; o ++ ) { + + var offset = offsets[ o ]; + var index = offset.index; + + for ( var i = offset.start, l = offset.start + offset.count; i < l; i += 3 ) { + + renderList.pushTriangle( indices[ i ] + index, indices[ i + 1 ] + index, indices[ i + 2 ] + index ); + + } + + } + + } else { + + for ( var i = 0, l = indices.length; i < l; i += 3 ) { + + renderList.pushTriangle( indices[ i ], indices[ i + 1 ], indices[ i + 2 ] ); + + } + + } + + } else { + + for ( var i = 0, l = positions.length / 3; i < l; i += 3 ) { + + renderList.pushTriangle( i, i + 1, i + 2 ); + + } + + } + + } else if ( geometry instanceof THREE.Geometry ) { + + vertices = geometry.vertices; + faces = geometry.faces; + faceVertexUvs = geometry.faceVertexUvs; + + _normalMatrix.getNormalMatrix( _modelMatrix ); + + isFaceMaterial = object.material instanceof THREE.MeshFaceMaterial; + objectMaterials = isFaceMaterial === true ? object.material : null; + + for ( var v = 0, vl = vertices.length; v < vl; v ++ ) { + + var vertex = vertices[ v ]; + renderList.pushVertex( vertex.x, vertex.y, vertex.z ); + + } + + for ( var f = 0, fl = faces.length; f < fl; f ++ ) { + + face = faces[ f ]; + + var material = isFaceMaterial === true + ? objectMaterials.materials[ face.materialIndex ] + : object.material; + + if ( material === undefined ) continue; + + var side = material.side; + + var v1 = _vertexPool[ face.a ]; + var v2 = _vertexPool[ face.b ]; + var v3 = _vertexPool[ face.c ]; + + if ( material.morphTargets === true ) { + + var morphTargets = geometry.morphTargets; + var morphInfluences = object.morphTargetInfluences; + + var v1p = v1.position; + var v2p = v2.position; + var v3p = v3.position; + + _vA.set( 0, 0, 0 ); + _vB.set( 0, 0, 0 ); + _vC.set( 0, 0, 0 ); + + for ( var t = 0, tl = morphTargets.length; t < tl; t ++ ) { + + var influence = morphInfluences[ t ]; + + if ( influence === 0 ) continue; + + var targets = morphTargets[ t ].vertices; + + _vA.x += ( targets[ face.a ].x - v1p.x ) * influence; + _vA.y += ( targets[ face.a ].y - v1p.y ) * influence; + _vA.z += ( targets[ face.a ].z - v1p.z ) * influence; + + _vB.x += ( targets[ face.b ].x - v2p.x ) * influence; + _vB.y += ( targets[ face.b ].y - v2p.y ) * influence; + _vB.z += ( targets[ face.b ].z - v2p.z ) * influence; + + _vC.x += ( targets[ face.c ].x - v3p.x ) * influence; + _vC.y += ( targets[ face.c ].y - v3p.y ) * influence; + _vC.z += ( targets[ face.c ].z - v3p.z ) * influence; + + } + + v1.position.add( _vA ); + v2.position.add( _vB ); + v3.position.add( _vC ); + + renderList.projectVertex( v1 ); + renderList.projectVertex( v2 ); + renderList.projectVertex( v3 ); + + } + + var visible = renderList.checkTriangleVisibility( v1, v2, v3 ); + + if ( ( visible === false && side === THREE.FrontSide ) || + ( visible === true && side === THREE.BackSide ) ) continue; + + _face = getNextFaceInPool(); + + _face.id = object.id; + _face.v1.copy( v1 ); + _face.v2.copy( v2 ); + _face.v3.copy( v3 ); + + _face.normalModel.copy( face.normal ); + + if ( visible === false && ( side === THREE.BackSide || side === THREE.DoubleSide ) ) { + + _face.normalModel.negate(); + + } + + _face.normalModel.applyMatrix3( _normalMatrix ).normalize(); + + _face.centroidModel.copy( face.centroid ).applyMatrix4( _modelMatrix ); + + faceVertexNormals = face.vertexNormals; + + for ( var n = 0, nl = Math.min( faceVertexNormals.length, 3 ); n < nl; n ++ ) { + + var normalModel = _face.vertexNormalsModel[ n ]; + normalModel.copy( faceVertexNormals[ n ] ); + + if ( visible === false && ( side === THREE.BackSide || side === THREE.DoubleSide ) ) { + + normalModel.negate(); + + } + + normalModel.applyMatrix3( _normalMatrix ).normalize(); + + } + + _face.vertexNormalsLength = faceVertexNormals.length; + + for ( var c = 0, cl = Math.min( faceVertexUvs.length, 3 ); c < cl; c ++ ) { + + uvs = faceVertexUvs[ c ][ f ]; + + if ( uvs === undefined ) continue; + + for ( var u = 0, ul = uvs.length; u < ul; u ++ ) { + + _face.uvs[ c ][ u ] = uvs[ u ]; + + } + + } + + _face.color = face.color; + _face.material = material; + + _face.z = ( v1.positionScreen.z + v2.positionScreen.z + v3.positionScreen.z ) / 3; + + _renderData.elements.push( _face ); + + } + + } + + } else if ( object instanceof THREE.Line ) { + + if ( geometry instanceof THREE.BufferGeometry ) { + + var attributes = geometry.attributes; + + if ( attributes.position !== undefined ) { + + var positions = attributes.position.array; + + for ( var i = 0, l = positions.length; i < l; i += 3 ) { + + renderList.pushVertex( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] ); + + } + + if ( attributes.index !== undefined ) { + + var indices = attributes.index.array; + + for ( var i = 0, l = indices.length; i < l; i += 2 ) { + + renderList.pushLine( indices[ i ], indices[ i + 1 ] ); + + } + + } else { + + for ( var i = 0, l = ( positions.length / 3 ) - 1; i < l; i ++ ) { + + renderList.pushLine( i, i + 1 ); + + } + + } + + } + + } else if ( geometry instanceof THREE.Geometry ) { + + _modelViewProjectionMatrix.multiplyMatrices( _viewProjectionMatrix, _modelMatrix ); + + vertices = object.geometry.vertices; + + if ( vertices.length === 0 ) continue; + + v1 = getNextVertexInPool(); + v1.positionScreen.copy( vertices[ 0 ] ).applyMatrix4( _modelViewProjectionMatrix ); + + // Handle LineStrip and LinePieces + var step = object.type === THREE.LinePieces ? 2 : 1; + + for ( var v = 1, vl = vertices.length; v < vl; v ++ ) { + + v1 = getNextVertexInPool(); + v1.positionScreen.copy( vertices[ v ] ).applyMatrix4( _modelViewProjectionMatrix ); + + if ( ( v + 1 ) % step > 0 ) continue; + + v2 = _vertexPool[ _vertexCount - 2 ]; + + _clippedVertex1PositionScreen.copy( v1.positionScreen ); + _clippedVertex2PositionScreen.copy( v2.positionScreen ); + + if ( clipLine( _clippedVertex1PositionScreen, _clippedVertex2PositionScreen ) === true ) { + + // Perform the perspective divide + _clippedVertex1PositionScreen.multiplyScalar( 1 / _clippedVertex1PositionScreen.w ); + _clippedVertex2PositionScreen.multiplyScalar( 1 / _clippedVertex2PositionScreen.w ); + + _line = getNextLineInPool(); + + _line.id = object.id; + _line.v1.positionScreen.copy( _clippedVertex1PositionScreen ); + _line.v2.positionScreen.copy( _clippedVertex2PositionScreen ); + + _line.z = Math.max( _clippedVertex1PositionScreen.z, _clippedVertex2PositionScreen.z ); + + _line.material = object.material; + + if ( object.material.vertexColors === THREE.VertexColors ) { + + _line.vertexColors[ 0 ].copy( object.geometry.colors[ v ] ); + _line.vertexColors[ 1 ].copy( object.geometry.colors[ v - 1 ] ); + + } + + _renderData.elements.push( _line ); + + } + + } + + } + + } else if ( object instanceof THREE.Sprite ) { + + _vector4.set( _modelMatrix.elements[12], _modelMatrix.elements[13], _modelMatrix.elements[14], 1 ); + _vector4.applyMatrix4( _viewProjectionMatrix ); + + var invW = 1 / _vector4.w; + + _vector4.z *= invW; + + if ( _vector4.z >= -1 && _vector4.z <= 1 ) { + + _sprite = getNextSpriteInPool(); + _sprite.id = object.id; + _sprite.x = _vector4.x * invW; + _sprite.y = _vector4.y * invW; + _sprite.z = _vector4.z; + _sprite.object = object; + + _sprite.rotation = object.rotation; + + _sprite.scale.x = object.scale.x * Math.abs( _sprite.x - ( _vector4.x + camera.projectionMatrix.elements[0] ) / ( _vector4.w + camera.projectionMatrix.elements[12] ) ); + _sprite.scale.y = object.scale.y * Math.abs( _sprite.y - ( _vector4.y + camera.projectionMatrix.elements[5] ) / ( _vector4.w + camera.projectionMatrix.elements[13] ) ); + + _sprite.material = object.material; + + _renderData.elements.push( _sprite ); + + } + + } + + } + + if ( sortElements === true ) _renderData.elements.sort( painterSort ); + + return _renderData; + + }; + + // Pools + + function getNextObjectInPool() { + + if ( _objectCount === _objectPoolLength ) { + + var object = new THREE.RenderableObject(); + _objectPool.push( object ); + _objectPoolLength ++; + _objectCount ++; + return object; + + } + + return _objectPool[ _objectCount ++ ]; + + } + + function getNextVertexInPool() { + + if ( _vertexCount === _vertexPoolLength ) { + + var vertex = new THREE.RenderableVertex(); + _vertexPool.push( vertex ); + _vertexPoolLength ++; + _vertexCount ++; + return vertex; + + } + + return _vertexPool[ _vertexCount ++ ]; + + } + + function getNextFaceInPool() { + + if ( _faceCount === _facePoolLength ) { + + var face = new THREE.RenderableFace(); + _facePool.push( face ); + _facePoolLength ++; + _faceCount ++; + return face; + + } + + return _facePool[ _faceCount ++ ]; + + + } + + function getNextLineInPool() { + + if ( _lineCount === _linePoolLength ) { + + var line = new THREE.RenderableLine(); + _linePool.push( line ); + _linePoolLength ++; + _lineCount ++ + return line; + + } + + return _linePool[ _lineCount ++ ]; + + } + + function getNextSpriteInPool() { + + if ( _spriteCount === _spritePoolLength ) { + + var sprite = new THREE.RenderableSprite(); + _spritePool.push( sprite ); + _spritePoolLength ++; + _spriteCount ++ + return sprite; + + } + + return _spritePool[ _spriteCount ++ ]; + + } + + // + + function painterSort( a, b ) { + + if ( a.z !== b.z ) { + + return b.z - a.z; + + } else if ( a.id !== b.id ) { + + return a.id - b.id; + + } else { + + return 0; + + } + + } + + function clipLine( s1, s2 ) { + + var alpha1 = 0, alpha2 = 1, + + // Calculate the boundary coordinate of each vertex for the near and far clip planes, + // Z = -1 and Z = +1, respectively. + bc1near = s1.z + s1.w, + bc2near = s2.z + s2.w, + bc1far = - s1.z + s1.w, + bc2far = - s2.z + s2.w; + + if ( bc1near >= 0 && bc2near >= 0 && bc1far >= 0 && bc2far >= 0 ) { + + // Both vertices lie entirely within all clip planes. + return true; + + } else if ( ( bc1near < 0 && bc2near < 0) || (bc1far < 0 && bc2far < 0 ) ) { + + // Both vertices lie entirely outside one of the clip planes. + return false; + + } else { + + // The line segment spans at least one clip plane. + + if ( bc1near < 0 ) { + + // v1 lies outside the near plane, v2 inside + alpha1 = Math.max( alpha1, bc1near / ( bc1near - bc2near ) ); + + } else if ( bc2near < 0 ) { + + // v2 lies outside the near plane, v1 inside + alpha2 = Math.min( alpha2, bc1near / ( bc1near - bc2near ) ); + + } + + if ( bc1far < 0 ) { + + // v1 lies outside the far plane, v2 inside + alpha1 = Math.max( alpha1, bc1far / ( bc1far - bc2far ) ); + + } else if ( bc2far < 0 ) { + + // v2 lies outside the far plane, v2 inside + alpha2 = Math.min( alpha2, bc1far / ( bc1far - bc2far ) ); + + } + + if ( alpha2 < alpha1 ) { + + // The line segment spans two boundaries, but is outside both of them. + // (This can't happen when we're only clipping against just near/far but good + // to leave the check here for future usage if other clip planes are added.) + return false; + + } else { + + // Update the s1 and s2 vertices to match the clipped line segment. + s1.lerp( s2, alpha1 ); + s2.lerp( s1, 1 - alpha2 ); + + return true; + + } + + } + + } + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Face3 = function ( a, b, c, normal, color, materialIndex ) { + + this.a = a; + this.b = b; + this.c = c; + + this.normal = normal instanceof THREE.Vector3 ? normal : new THREE.Vector3(); + this.vertexNormals = normal instanceof Array ? normal : [ ]; + + this.color = color instanceof THREE.Color ? color : new THREE.Color(); + this.vertexColors = color instanceof Array ? color : []; + + this.vertexTangents = []; + + this.materialIndex = materialIndex !== undefined ? materialIndex : 0; + + this.centroid = new THREE.Vector3(); + +}; + +THREE.Face3.prototype = { + + constructor: THREE.Face3, + + clone: function () { + + var face = new THREE.Face3( this.a, this.b, this.c ); + + face.normal.copy( this.normal ); + face.color.copy( this.color ); + face.centroid.copy( this.centroid ); + + face.materialIndex = this.materialIndex; + + var i, il; + for ( i = 0, il = this.vertexNormals.length; i < il; i ++ ) face.vertexNormals[ i ] = this.vertexNormals[ i ].clone(); + for ( i = 0, il = this.vertexColors.length; i < il; i ++ ) face.vertexColors[ i ] = this.vertexColors[ i ].clone(); + for ( i = 0, il = this.vertexTangents.length; i < il; i ++ ) face.vertexTangents[ i ] = this.vertexTangents[ i ].clone(); + + return face; + + } + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Face4 = function ( a, b, c, d, normal, color, materialIndex ) { + + console.warn( 'THREE.Face4 has been removed. A THREE.Face3 will be created instead.') + + return new THREE.Face3( a, b, c, normal, color, materialIndex ); + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.BufferGeometry = function () { + + this.id = THREE.GeometryIdCount ++; + this.uuid = THREE.Math.generateUUID(); + + this.name = ''; + + // attributes + + this.attributes = {}; + + // offsets for chunks when using indexed elements + + this.offsets = []; + + // boundings + + this.boundingBox = null; + this.boundingSphere = null; + +}; + +THREE.BufferGeometry.prototype = { + + constructor: THREE.BufferGeometry, + + addAttribute: function ( name, type, numItems, itemSize ) { + + this.attributes[ name ] = { + + array: new type( numItems * itemSize ), + itemSize: itemSize + + }; + + return this.attributes[ name ]; + + }, + + applyMatrix: function ( matrix ) { + + var position = this.attributes.position; + + if ( position !== undefined ) { + + matrix.multiplyVector3Array( position.array ); + position.needsUpdate = true; + + } + + var normal = this.attributes.normal; + + if ( normal !== undefined ) { + + var normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix ); + + normalMatrix.multiplyVector3Array( normal.array ); + normal.needsUpdate = true; + + } + + }, + + computeBoundingBox: function () { + + if ( this.boundingBox === null ) { + + this.boundingBox = new THREE.Box3(); + + } + + var positions = this.attributes[ "position" ].array; + + if ( positions ) { + + var bb = this.boundingBox; + + if( positions.length >= 3 ) { + bb.min.x = bb.max.x = positions[ 0 ]; + bb.min.y = bb.max.y = positions[ 1 ]; + bb.min.z = bb.max.z = positions[ 2 ]; + } + + for ( var i = 3, il = positions.length; i < il; i += 3 ) { + + var x = positions[ i ]; + var y = positions[ i + 1 ]; + var z = positions[ i + 2 ]; + + // bounding box + + if ( x < bb.min.x ) { + + bb.min.x = x; + + } else if ( x > bb.max.x ) { + + bb.max.x = x; + + } + + if ( y < bb.min.y ) { + + bb.min.y = y; + + } else if ( y > bb.max.y ) { + + bb.max.y = y; + + } + + if ( z < bb.min.z ) { + + bb.min.z = z; + + } else if ( z > bb.max.z ) { + + bb.max.z = z; + + } + + } + + } + + if ( positions === undefined || positions.length === 0 ) { + + this.boundingBox.min.set( 0, 0, 0 ); + this.boundingBox.max.set( 0, 0, 0 ); + + } + + }, + + computeBoundingSphere: function () { + + var box = new THREE.Box3(); + var vector = new THREE.Vector3(); + + return function () { + + if ( this.boundingSphere === null ) { + + this.boundingSphere = new THREE.Sphere(); + + } + + var positions = this.attributes[ "position" ].array; + + if ( positions ) { + + box.makeEmpty(); + + var center = this.boundingSphere.center; + + for ( var i = 0, il = positions.length; i < il; i += 3 ) { + + vector.set( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] ); + box.addPoint( vector ); + + } + + box.center( center ); + + var maxRadiusSq = 0; + + for ( var i = 0, il = positions.length; i < il; i += 3 ) { + + vector.set( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] ); + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) ); + + } + + this.boundingSphere.radius = Math.sqrt( maxRadiusSq ); + + } + + } + + }(), + + computeVertexNormals: function () { + + if ( this.attributes[ "position" ] ) { + + var i, il; + var j, jl; + + var nVertexElements = this.attributes[ "position" ].array.length; + + if ( this.attributes[ "normal" ] === undefined ) { + + this.attributes[ "normal" ] = { + + itemSize: 3, + array: new Float32Array( nVertexElements ) + + }; + + } else { + + // reset existing normals to zero + + for ( i = 0, il = this.attributes[ "normal" ].array.length; i < il; i ++ ) { + + this.attributes[ "normal" ].array[ i ] = 0; + + } + + } + + var positions = this.attributes[ "position" ].array; + var normals = this.attributes[ "normal" ].array; + + var vA, vB, vC, x, y, z, + + pA = new THREE.Vector3(), + pB = new THREE.Vector3(), + pC = new THREE.Vector3(), + + cb = new THREE.Vector3(), + ab = new THREE.Vector3(); + + // indexed elements + + if ( this.attributes[ "index" ] ) { + + var indices = this.attributes[ "index" ].array; + + var offsets = this.offsets; + + for ( j = 0, jl = offsets.length; j < jl; ++ j ) { + + var start = offsets[ j ].start; + var count = offsets[ j ].count; + var index = offsets[ j ].index; + + for ( i = start, il = start + count; i < il; i += 3 ) { + + vA = index + indices[ i ]; + vB = index + indices[ i + 1 ]; + vC = index + indices[ i + 2 ]; + + x = positions[ vA * 3 ]; + y = positions[ vA * 3 + 1 ]; + z = positions[ vA * 3 + 2 ]; + pA.set( x, y, z ); + + x = positions[ vB * 3 ]; + y = positions[ vB * 3 + 1 ]; + z = positions[ vB * 3 + 2 ]; + pB.set( x, y, z ); + + x = positions[ vC * 3 ]; + y = positions[ vC * 3 + 1 ]; + z = positions[ vC * 3 + 2 ]; + pC.set( x, y, z ); + + cb.subVectors( pC, pB ); + ab.subVectors( pA, pB ); + cb.cross( ab ); + + normals[ vA * 3 ] += cb.x; + normals[ vA * 3 + 1 ] += cb.y; + normals[ vA * 3 + 2 ] += cb.z; + + normals[ vB * 3 ] += cb.x; + normals[ vB * 3 + 1 ] += cb.y; + normals[ vB * 3 + 2 ] += cb.z; + + normals[ vC * 3 ] += cb.x; + normals[ vC * 3 + 1 ] += cb.y; + normals[ vC * 3 + 2 ] += cb.z; + + } + + } + + // non-indexed elements (unconnected triangle soup) + + } else { + + for ( i = 0, il = positions.length; i < il; i += 9 ) { + + x = positions[ i ]; + y = positions[ i + 1 ]; + z = positions[ i + 2 ]; + pA.set( x, y, z ); + + x = positions[ i + 3 ]; + y = positions[ i + 4 ]; + z = positions[ i + 5 ]; + pB.set( x, y, z ); + + x = positions[ i + 6 ]; + y = positions[ i + 7 ]; + z = positions[ i + 8 ]; + pC.set( x, y, z ); + + cb.subVectors( pC, pB ); + ab.subVectors( pA, pB ); + cb.cross( ab ); + + normals[ i ] = cb.x; + normals[ i + 1 ] = cb.y; + normals[ i + 2 ] = cb.z; + + normals[ i + 3 ] = cb.x; + normals[ i + 4 ] = cb.y; + normals[ i + 5 ] = cb.z; + + normals[ i + 6 ] = cb.x; + normals[ i + 7 ] = cb.y; + normals[ i + 8 ] = cb.z; + + } + + } + + this.normalizeNormals(); + + this.normalsNeedUpdate = true; + + } + + }, + + normalizeNormals: function () { + + var normals = this.attributes[ "normal" ].array; + + var x, y, z, n; + + for ( var i = 0, il = normals.length; i < il; i += 3 ) { + + x = normals[ i ]; + y = normals[ i + 1 ]; + z = normals[ i + 2 ]; + + n = 1.0 / Math.sqrt( x * x + y * y + z * z ); + + normals[ i ] *= n; + normals[ i + 1 ] *= n; + normals[ i + 2 ] *= n; + + } + + }, + + computeTangents: function () { + + // based on http://www.terathon.com/code/tangent.html + // (per vertex tangents) + + if ( this.attributes[ "index" ] === undefined || + this.attributes[ "position" ] === undefined || + this.attributes[ "normal" ] === undefined || + this.attributes[ "uv" ] === undefined ) { + + console.warn( "Missing required attributes (index, position, normal or uv) in BufferGeometry.computeTangents()" ); + return; + + } + + var indices = this.attributes[ "index" ].array; + var positions = this.attributes[ "position" ].array; + var normals = this.attributes[ "normal" ].array; + var uvs = this.attributes[ "uv" ].array; + + var nVertices = positions.length / 3; + + if ( this.attributes[ "tangent" ] === undefined ) { + + var nTangentElements = 4 * nVertices; + + this.attributes[ "tangent" ] = { + + itemSize: 4, + array: new Float32Array( nTangentElements ) + + }; + + } + + var tangents = this.attributes[ "tangent" ].array; + + var tan1 = [], tan2 = []; + + for ( var k = 0; k < nVertices; k ++ ) { + + tan1[ k ] = new THREE.Vector3(); + tan2[ k ] = new THREE.Vector3(); + + } + + var xA, yA, zA, + xB, yB, zB, + xC, yC, zC, + + uA, vA, + uB, vB, + uC, vC, + + x1, x2, y1, y2, z1, z2, + s1, s2, t1, t2, r; + + var sdir = new THREE.Vector3(), tdir = new THREE.Vector3(); + + function handleTriangle( a, b, c ) { + + xA = positions[ a * 3 ]; + yA = positions[ a * 3 + 1 ]; + zA = positions[ a * 3 + 2 ]; + + xB = positions[ b * 3 ]; + yB = positions[ b * 3 + 1 ]; + zB = positions[ b * 3 + 2 ]; + + xC = positions[ c * 3 ]; + yC = positions[ c * 3 + 1 ]; + zC = positions[ c * 3 + 2 ]; + + uA = uvs[ a * 2 ]; + vA = uvs[ a * 2 + 1 ]; + + uB = uvs[ b * 2 ]; + vB = uvs[ b * 2 + 1 ]; + + uC = uvs[ c * 2 ]; + vC = uvs[ c * 2 + 1 ]; + + x1 = xB - xA; + x2 = xC - xA; + + y1 = yB - yA; + y2 = yC - yA; + + z1 = zB - zA; + z2 = zC - zA; + + s1 = uB - uA; + s2 = uC - uA; + + t1 = vB - vA; + t2 = vC - vA; + + r = 1.0 / ( s1 * t2 - s2 * t1 ); + + sdir.set( + ( t2 * x1 - t1 * x2 ) * r, + ( t2 * y1 - t1 * y2 ) * r, + ( t2 * z1 - t1 * z2 ) * r + ); + + tdir.set( + ( s1 * x2 - s2 * x1 ) * r, + ( s1 * y2 - s2 * y1 ) * r, + ( s1 * z2 - s2 * z1 ) * r + ); + + tan1[ a ].add( sdir ); + tan1[ b ].add( sdir ); + tan1[ c ].add( sdir ); + + tan2[ a ].add( tdir ); + tan2[ b ].add( tdir ); + tan2[ c ].add( tdir ); + + } + + var i, il; + var j, jl; + var iA, iB, iC; + + var offsets = this.offsets; + + for ( j = 0, jl = offsets.length; j < jl; ++ j ) { + + var start = offsets[ j ].start; + var count = offsets[ j ].count; + var index = offsets[ j ].index; + + for ( i = start, il = start + count; i < il; i += 3 ) { + + iA = index + indices[ i ]; + iB = index + indices[ i + 1 ]; + iC = index + indices[ i + 2 ]; + + handleTriangle( iA, iB, iC ); + + } + + } + + var tmp = new THREE.Vector3(), tmp2 = new THREE.Vector3(); + var n = new THREE.Vector3(), n2 = new THREE.Vector3(); + var w, t, test; + + function handleVertex( v ) { + + n.x = normals[ v * 3 ]; + n.y = normals[ v * 3 + 1 ]; + n.z = normals[ v * 3 + 2 ]; + + n2.copy( n ); + + t = tan1[ v ]; + + // Gram-Schmidt orthogonalize + + tmp.copy( t ); + tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize(); + + // Calculate handedness + + tmp2.crossVectors( n2, t ); + test = tmp2.dot( tan2[ v ] ); + w = ( test < 0.0 ) ? -1.0 : 1.0; + + tangents[ v * 4 ] = tmp.x; + tangents[ v * 4 + 1 ] = tmp.y; + tangents[ v * 4 + 2 ] = tmp.z; + tangents[ v * 4 + 3 ] = w; + + } + + for ( j = 0, jl = offsets.length; j < jl; ++ j ) { + + var start = offsets[ j ].start; + var count = offsets[ j ].count; + var index = offsets[ j ].index; + + for ( i = start, il = start + count; i < il; i += 3 ) { + + iA = index + indices[ i ]; + iB = index + indices[ i + 1 ]; + iC = index + indices[ i + 2 ]; + + handleVertex( iA ); + handleVertex( iB ); + handleVertex( iC ); + + } + + } + + }, + + /* + computeOffsets + Compute the draw offset for large models by chunking the index buffer into chunks of 65k addressable vertices. + This method will effectively rewrite the index buffer and remap all attributes to match the new indices. + WARNING: This method will also expand the vertex count to prevent sprawled triangles across draw offsets. + indexBufferSize - Defaults to 65535, but allows for larger or smaller chunks. + */ + computeOffsets: function(indexBufferSize) { + + var size = indexBufferSize; + if(indexBufferSize === undefined) + size = 65535; //WebGL limits type of index buffer values to 16-bit. + + var s = Date.now(); + + var indices = this.attributes['index'].array; + var vertices = this.attributes['position'].array; + + var verticesCount = (vertices.length/3); + var facesCount = (indices.length/3); + + /* + console.log("Computing buffers in offsets of "+size+" -> indices:"+indices.length+" vertices:"+vertices.length); + console.log("Faces to process: "+(indices.length/3)); + console.log("Reordering "+verticesCount+" vertices."); + */ + + var sortedIndices = new Uint16Array( indices.length ); //16-bit buffers + var indexPtr = 0; + var vertexPtr = 0; + + var offsets = [ { start:0, count:0, index:0 } ]; + var offset = offsets[0]; + + var duplicatedVertices = 0; + var newVerticeMaps = 0; + var faceVertices = new Int32Array(6); + var vertexMap = new Int32Array( vertices.length ); + var revVertexMap = new Int32Array( vertices.length ); + for(var j = 0; j < vertices.length; j++) { vertexMap[j] = -1; revVertexMap[j] = -1; } + + /* + Traverse every face and reorder vertices in the proper offsets of 65k. + We can have more than 65k entries in the index buffer per offset, but only reference 65k values. + */ + for(var findex = 0; findex < facesCount; findex++) { + newVerticeMaps = 0; + + for(var vo = 0; vo < 3; vo++) { + var vid = indices[ findex*3 + vo ]; + if(vertexMap[vid] == -1) { + //Unmapped vertice + faceVertices[vo*2] = vid; + faceVertices[vo*2+1] = -1; + newVerticeMaps++; + } else if(vertexMap[vid] < offset.index) { + //Reused vertices from previous block (duplicate) + faceVertices[vo*2] = vid; + faceVertices[vo*2+1] = -1; + duplicatedVertices++; + } else { + //Reused vertice in the current block + faceVertices[vo*2] = vid; + faceVertices[vo*2+1] = vertexMap[vid]; + } + } + + var faceMax = vertexPtr + newVerticeMaps; + if(faceMax > (offset.index + size)) { + var new_offset = { start:indexPtr, count:0, index:vertexPtr }; + offsets.push(new_offset); + offset = new_offset; + + //Re-evaluate reused vertices in light of new offset. + for(var v = 0; v < 6; v+=2) { + var new_vid = faceVertices[v+1]; + if(new_vid > -1 && new_vid < offset.index) + faceVertices[v+1] = -1; + } + } + + //Reindex the face. + for(var v = 0; v < 6; v+=2) { + var vid = faceVertices[v]; + var new_vid = faceVertices[v+1]; + + if(new_vid === -1) + new_vid = vertexPtr++; + + vertexMap[vid] = new_vid; + revVertexMap[new_vid] = vid; + sortedIndices[indexPtr++] = new_vid - offset.index; //XXX overflows at 16bit + offset.count++; + } + } + + /* Move all attribute values to map to the new computed indices , also expand the vertice stack to match our new vertexPtr. */ + this.reorderBuffers(sortedIndices, revVertexMap, vertexPtr); + this.offsets = offsets; + + /* + var orderTime = Date.now(); + console.log("Reorder time: "+(orderTime-s)+"ms"); + console.log("Duplicated "+duplicatedVertices+" vertices."); + console.log("Compute Buffers time: "+(Date.now()-s)+"ms"); + console.log("Draw offsets: "+offsets.length); + */ + + return offsets; + }, + + /* + reoderBuffers: + Reorder attributes based on a new indexBuffer and indexMap. + indexBuffer - Uint16Array of the new ordered indices. + indexMap - Int32Array where the position is the new vertex ID and the value the old vertex ID for each vertex. + vertexCount - Amount of total vertices considered in this reordering (in case you want to grow the vertice stack). + */ + reorderBuffers: function(indexBuffer, indexMap, vertexCount) { + + /* Create a copy of all attributes for reordering. */ + var sortedAttributes = {}; + var types = [ Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array ]; + for( var attr in this.attributes ) { + if(attr == 'index') + continue; + var sourceArray = this.attributes[attr].array; + for ( var i = 0, il = types.length; i < il; i++ ) { + var type = types[i]; + if (sourceArray instanceof type) { + sortedAttributes[attr] = new type( this.attributes[attr].itemSize * vertexCount ); + break; + } + } + } + + /* Move attribute positions based on the new index map */ + for(var new_vid = 0; new_vid < vertexCount; new_vid++) { + var vid = indexMap[new_vid]; + for ( var attr in this.attributes ) { + if(attr == 'index') + continue; + var attrArray = this.attributes[attr].array; + var attrSize = this.attributes[attr].itemSize; + var sortedAttr = sortedAttributes[attr]; + for(var k = 0; k < attrSize; k++) + sortedAttr[ new_vid * attrSize + k ] = attrArray[ vid * attrSize + k ]; + } + } + + /* Carry the new sorted buffers locally */ + this.attributes['index'].array = indexBuffer; + for ( var attr in this.attributes ) { + if(attr == 'index') + continue; + this.attributes[attr].array = sortedAttributes[attr]; + this.attributes[attr].numItems = this.attributes[attr].itemSize * vertexCount; + } + }, + + clone: function () { + + var geometry = new THREE.BufferGeometry(); + + var types = [ Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array ]; + + for ( var attr in this.attributes ) { + + var sourceAttr = this.attributes[ attr ]; + var sourceArray = sourceAttr.array; + + var attribute = { + + itemSize: sourceAttr.itemSize, + array: null + + }; + + for ( var i = 0, il = types.length; i < il; i ++ ) { + + var type = types[ i ]; + + if ( sourceArray instanceof type ) { + + attribute.array = new type( sourceArray ); + break; + + } + + } + + geometry.attributes[ attr ] = attribute; + + } + + for ( var i = 0, il = this.offsets.length; i < il; i ++ ) { + + var offset = this.offsets[ i ]; + + geometry.offsets.push( { + + start: offset.start, + index: offset.index, + count: offset.count + + } ); + + } + + return geometry; + + }, + + dispose: function () { + + this.dispatchEvent( { type: 'dispose' } ); + + } + +}; + +THREE.EventDispatcher.prototype.apply( THREE.BufferGeometry.prototype ); + +/** + * @author mrdoob / http://mrdoob.com/ + * @author kile / http://kile.stravaganza.org/ + * @author alteredq / http://alteredqualia.com/ + * @author mikael emtinger / http://gomo.se/ + * @author zz85 / http://www.lab4games.net/zz85/blog + * @author bhouston / http://exocortex.com + */ + +THREE.Geometry = function () { + + this.id = THREE.GeometryIdCount ++; + this.uuid = THREE.Math.generateUUID(); + + this.name = ''; + + this.vertices = []; + this.colors = []; // one-to-one vertex colors, used in ParticleSystem and Line + + this.faces = []; + + this.faceVertexUvs = [[]]; + + this.morphTargets = []; + this.morphColors = []; + this.morphNormals = []; + + this.skinWeights = []; + this.skinIndices = []; + + this.lineDistances = []; + + this.boundingBox = null; + this.boundingSphere = null; + + this.hasTangents = false; + + this.dynamic = true; // the intermediate typed arrays will be deleted when set to false + + // update flags + + this.verticesNeedUpdate = false; + this.elementsNeedUpdate = false; + this.uvsNeedUpdate = false; + this.normalsNeedUpdate = false; + this.tangentsNeedUpdate = false; + this.colorsNeedUpdate = false; + this.lineDistancesNeedUpdate = false; + + this.buffersNeedUpdate = false; + +}; + +THREE.Geometry.prototype = { + + constructor: THREE.Geometry, + + applyMatrix: function ( matrix ) { + + var normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix ); + + for ( var i = 0, il = this.vertices.length; i < il; i ++ ) { + + var vertex = this.vertices[ i ]; + vertex.applyMatrix4( matrix ); + + } + + for ( var i = 0, il = this.faces.length; i < il; i ++ ) { + + var face = this.faces[ i ]; + face.normal.applyMatrix3( normalMatrix ).normalize(); + + for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) { + + face.vertexNormals[ j ].applyMatrix3( normalMatrix ).normalize(); + + } + + face.centroid.applyMatrix4( matrix ); + + } + + if ( this.boundingBox instanceof THREE.Box3 ) { + + this.computeBoundingBox(); + + } + + if ( this.boundingSphere instanceof THREE.Sphere ) { + + this.computeBoundingSphere(); + + } + + }, + + computeCentroids: function () { + + var f, fl, face; + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + face.centroid.set( 0, 0, 0 ); + + face.centroid.add( this.vertices[ face.a ] ); + face.centroid.add( this.vertices[ face.b ] ); + face.centroid.add( this.vertices[ face.c ] ); + face.centroid.divideScalar( 3 ); + + } + + }, + + computeFaceNormals: function () { + + var cb = new THREE.Vector3(), ab = new THREE.Vector3(); + + for ( var f = 0, fl = this.faces.length; f < fl; f ++ ) { + + var face = this.faces[ f ]; + + var vA = this.vertices[ face.a ]; + var vB = this.vertices[ face.b ]; + var vC = this.vertices[ face.c ]; + + cb.subVectors( vC, vB ); + ab.subVectors( vA, vB ); + cb.cross( ab ); + + cb.normalize(); + + face.normal.copy( cb ); + + } + + }, + + computeVertexNormals: function ( areaWeighted ) { + + var v, vl, f, fl, face, vertices; + + vertices = new Array( this.vertices.length ); + + for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) { + + vertices[ v ] = new THREE.Vector3(); + + } + + if ( areaWeighted ) { + + // vertex normals weighted by triangle areas + // http://www.iquilezles.org/www/articles/normals/normals.htm + + var vA, vB, vC, vD; + var cb = new THREE.Vector3(), ab = new THREE.Vector3(), + db = new THREE.Vector3(), dc = new THREE.Vector3(), bc = new THREE.Vector3(); + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + vA = this.vertices[ face.a ]; + vB = this.vertices[ face.b ]; + vC = this.vertices[ face.c ]; + + cb.subVectors( vC, vB ); + ab.subVectors( vA, vB ); + cb.cross( ab ); + + vertices[ face.a ].add( cb ); + vertices[ face.b ].add( cb ); + vertices[ face.c ].add( cb ); + + } + + } else { + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + vertices[ face.a ].add( face.normal ); + vertices[ face.b ].add( face.normal ); + vertices[ face.c ].add( face.normal ); + + } + + } + + for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) { + + vertices[ v ].normalize(); + + } + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + face.vertexNormals[ 0 ] = vertices[ face.a ].clone(); + face.vertexNormals[ 1 ] = vertices[ face.b ].clone(); + face.vertexNormals[ 2 ] = vertices[ face.c ].clone(); + + } + + }, + + computeMorphNormals: function () { + + var i, il, f, fl, face; + + // save original normals + // - create temp variables on first access + // otherwise just copy (for faster repeated calls) + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + if ( ! face.__originalFaceNormal ) { + + face.__originalFaceNormal = face.normal.clone(); + + } else { + + face.__originalFaceNormal.copy( face.normal ); + + } + + if ( ! face.__originalVertexNormals ) face.__originalVertexNormals = []; + + for ( i = 0, il = face.vertexNormals.length; i < il; i ++ ) { + + if ( ! face.__originalVertexNormals[ i ] ) { + + face.__originalVertexNormals[ i ] = face.vertexNormals[ i ].clone(); + + } else { + + face.__originalVertexNormals[ i ].copy( face.vertexNormals[ i ] ); + + } + + } + + } + + // use temp geometry to compute face and vertex normals for each morph + + var tmpGeo = new THREE.Geometry(); + tmpGeo.faces = this.faces; + + for ( i = 0, il = this.morphTargets.length; i < il; i ++ ) { + + // create on first access + + if ( ! this.morphNormals[ i ] ) { + + this.morphNormals[ i ] = {}; + this.morphNormals[ i ].faceNormals = []; + this.morphNormals[ i ].vertexNormals = []; + + var dstNormalsFace = this.morphNormals[ i ].faceNormals; + var dstNormalsVertex = this.morphNormals[ i ].vertexNormals; + + var faceNormal, vertexNormals; + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + faceNormal = new THREE.Vector3(); + vertexNormals = { a: new THREE.Vector3(), b: new THREE.Vector3(), c: new THREE.Vector3() }; + + dstNormalsFace.push( faceNormal ); + dstNormalsVertex.push( vertexNormals ); + + } + + } + + var morphNormals = this.morphNormals[ i ]; + + // set vertices to morph target + + tmpGeo.vertices = this.morphTargets[ i ].vertices; + + // compute morph normals + + tmpGeo.computeFaceNormals(); + tmpGeo.computeVertexNormals(); + + // store morph normals + + var faceNormal, vertexNormals; + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + faceNormal = morphNormals.faceNormals[ f ]; + vertexNormals = morphNormals.vertexNormals[ f ]; + + faceNormal.copy( face.normal ); + + vertexNormals.a.copy( face.vertexNormals[ 0 ] ); + vertexNormals.b.copy( face.vertexNormals[ 1 ] ); + vertexNormals.c.copy( face.vertexNormals[ 2 ] ); + + } + + } + + // restore original normals + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + face.normal = face.__originalFaceNormal; + face.vertexNormals = face.__originalVertexNormals; + + } + + }, + + computeTangents: function () { + + // based on http://www.terathon.com/code/tangent.html + // tangents go to vertices + + var f, fl, v, vl, i, il, vertexIndex, + face, uv, vA, vB, vC, uvA, uvB, uvC, + x1, x2, y1, y2, z1, z2, + s1, s2, t1, t2, r, t, test, + tan1 = [], tan2 = [], + sdir = new THREE.Vector3(), tdir = new THREE.Vector3(), + tmp = new THREE.Vector3(), tmp2 = new THREE.Vector3(), + n = new THREE.Vector3(), w; + + for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) { + + tan1[ v ] = new THREE.Vector3(); + tan2[ v ] = new THREE.Vector3(); + + } + + function handleTriangle( context, a, b, c, ua, ub, uc ) { + + vA = context.vertices[ a ]; + vB = context.vertices[ b ]; + vC = context.vertices[ c ]; + + uvA = uv[ ua ]; + uvB = uv[ ub ]; + uvC = uv[ uc ]; + + x1 = vB.x - vA.x; + x2 = vC.x - vA.x; + y1 = vB.y - vA.y; + y2 = vC.y - vA.y; + z1 = vB.z - vA.z; + z2 = vC.z - vA.z; + + s1 = uvB.x - uvA.x; + s2 = uvC.x - uvA.x; + t1 = uvB.y - uvA.y; + t2 = uvC.y - uvA.y; + + r = 1.0 / ( s1 * t2 - s2 * t1 ); + sdir.set( ( t2 * x1 - t1 * x2 ) * r, + ( t2 * y1 - t1 * y2 ) * r, + ( t2 * z1 - t1 * z2 ) * r ); + tdir.set( ( s1 * x2 - s2 * x1 ) * r, + ( s1 * y2 - s2 * y1 ) * r, + ( s1 * z2 - s2 * z1 ) * r ); + + tan1[ a ].add( sdir ); + tan1[ b ].add( sdir ); + tan1[ c ].add( sdir ); + + tan2[ a ].add( tdir ); + tan2[ b ].add( tdir ); + tan2[ c ].add( tdir ); + + } + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + uv = this.faceVertexUvs[ 0 ][ f ]; // use UV layer 0 for tangents + + handleTriangle( this, face.a, face.b, face.c, 0, 1, 2 ); + + } + + var faceIndex = [ 'a', 'b', 'c', 'd' ]; + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + for ( i = 0; i < Math.min( face.vertexNormals.length, 3 ); i++ ) { + + n.copy( face.vertexNormals[ i ] ); + + vertexIndex = face[ faceIndex[ i ] ]; + + t = tan1[ vertexIndex ]; + + // Gram-Schmidt orthogonalize + + tmp.copy( t ); + tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize(); + + // Calculate handedness + + tmp2.crossVectors( face.vertexNormals[ i ], t ); + test = tmp2.dot( tan2[ vertexIndex ] ); + w = (test < 0.0) ? -1.0 : 1.0; + + face.vertexTangents[ i ] = new THREE.Vector4( tmp.x, tmp.y, tmp.z, w ); + + } + + } + + this.hasTangents = true; + + }, + + computeLineDistances: function ( ) { + + var d = 0; + var vertices = this.vertices; + + for ( var i = 0, il = vertices.length; i < il; i ++ ) { + + if ( i > 0 ) { + + d += vertices[ i ].distanceTo( vertices[ i - 1 ] ); + + } + + this.lineDistances[ i ] = d; + + } + + }, + + computeBoundingBox: function () { + + if ( this.boundingBox === null ) { + + this.boundingBox = new THREE.Box3(); + + } + + this.boundingBox.setFromPoints( this.vertices ); + + }, + + computeBoundingSphere: function () { + + if ( this.boundingSphere === null ) { + + this.boundingSphere = new THREE.Sphere(); + + } + + this.boundingSphere.setFromPoints( this.vertices ); + + }, + + /* + * Checks for duplicate vertices with hashmap. + * Duplicated vertices are removed + * and faces' vertices are updated. + */ + + mergeVertices: function () { + + var verticesMap = {}; // Hashmap for looking up vertice by position coordinates (and making sure they are unique) + var unique = [], changes = []; + + var v, key; + var precisionPoints = 4; // number of decimal points, eg. 4 for epsilon of 0.0001 + var precision = Math.pow( 10, precisionPoints ); + var i,il, face; + var indices, k, j, jl, u; + + for ( i = 0, il = this.vertices.length; i < il; i ++ ) { + + v = this.vertices[ i ]; + key = Math.round( v.x * precision ) + '_' + Math.round( v.y * precision ) + '_' + Math.round( v.z * precision ); + + if ( verticesMap[ key ] === undefined ) { + + verticesMap[ key ] = i; + unique.push( this.vertices[ i ] ); + changes[ i ] = unique.length - 1; + + } else { + + //console.log('Duplicate vertex found. ', i, ' could be using ', verticesMap[key]); + changes[ i ] = changes[ verticesMap[ key ] ]; + + } + + }; + + + // if faces are completely degenerate after merging vertices, we + // have to remove them from the geometry. + var faceIndicesToRemove = []; + + for( i = 0, il = this.faces.length; i < il; i ++ ) { + + face = this.faces[ i ]; + + face.a = changes[ face.a ]; + face.b = changes[ face.b ]; + face.c = changes[ face.c ]; + + indices = [ face.a, face.b, face.c ]; + + var dupIndex = -1; + + // if any duplicate vertices are found in a Face3 + // we have to remove the face as nothing can be saved + for ( var n = 0; n < 3; n ++ ) { + if ( indices[ n ] == indices[ ( n + 1 ) % 3 ] ) { + + dupIndex = n; + faceIndicesToRemove.push( i ); + break; + + } + } + + } + + for ( i = faceIndicesToRemove.length - 1; i >= 0; i -- ) { + var idx = faceIndicesToRemove[ i ]; + + this.faces.splice( idx, 1 ); + + for ( j = 0, jl = this.faceVertexUvs.length; j < jl; j ++ ) { + + this.faceVertexUvs[ j ].splice( idx, 1 ); + + } + + } + + // Use unique set of vertices + + var diff = this.vertices.length - unique.length; + this.vertices = unique; + return diff; + + }, + + // Geometry splitting + + makeGroups: ( function () { + + var geometryGroupCounter = 0; + + return function ( usesFaceMaterial ) { + + var f, fl, face, materialIndex, + groupHash, hash_map = {}; + + var numMorphTargets = this.morphTargets.length; + var numMorphNormals = this.morphNormals.length; + + this.geometryGroups = {}; + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + materialIndex = usesFaceMaterial ? face.materialIndex : 0; + + if ( ! ( materialIndex in hash_map ) ) { + + hash_map[ materialIndex ] = { 'hash': materialIndex, 'counter': 0 }; + + } + + groupHash = hash_map[ materialIndex ].hash + '_' + hash_map[ materialIndex ].counter; + + if ( ! ( groupHash in this.geometryGroups ) ) { + + this.geometryGroups[ groupHash ] = { 'faces3': [], 'materialIndex': materialIndex, 'vertices': 0, 'numMorphTargets': numMorphTargets, 'numMorphNormals': numMorphNormals }; + + } + + if ( this.geometryGroups[ groupHash ].vertices + 3 > 65535 ) { + + hash_map[ materialIndex ].counter += 1; + groupHash = hash_map[ materialIndex ].hash + '_' + hash_map[ materialIndex ].counter; + + if ( ! ( groupHash in this.geometryGroups ) ) { + + this.geometryGroups[ groupHash ] = { 'faces3': [], 'materialIndex': materialIndex, 'vertices': 0, 'numMorphTargets': numMorphTargets, 'numMorphNormals': numMorphNormals }; + + } + + } + + this.geometryGroups[ groupHash ].faces3.push( f ); + this.geometryGroups[ groupHash ].vertices += 3; + + } + + this.geometryGroupsList = []; + + for ( var g in this.geometryGroups ) { + + this.geometryGroups[ g ].id = geometryGroupCounter ++; + + this.geometryGroupsList.push( this.geometryGroups[ g ] ); + + } + + }; + + } )(), + + clone: function () { + + var geometry = new THREE.Geometry(); + + var vertices = this.vertices; + + for ( var i = 0, il = vertices.length; i < il; i ++ ) { + + geometry.vertices.push( vertices[ i ].clone() ); + + } + + var faces = this.faces; + + for ( var i = 0, il = faces.length; i < il; i ++ ) { + + geometry.faces.push( faces[ i ].clone() ); + + } + + var uvs = this.faceVertexUvs[ 0 ]; + + for ( var i = 0, il = uvs.length; i < il; i ++ ) { + + var uv = uvs[ i ], uvCopy = []; + + for ( var j = 0, jl = uv.length; j < jl; j ++ ) { + + uvCopy.push( new THREE.Vector2( uv[ j ].x, uv[ j ].y ) ); + + } + + geometry.faceVertexUvs[ 0 ].push( uvCopy ); + + } + + return geometry; + + }, + + dispose: function () { + + this.dispatchEvent( { type: 'dispose' } ); + + } + +}; + +THREE.EventDispatcher.prototype.apply( THREE.Geometry.prototype ); + +THREE.GeometryIdCount = 0; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Geometry2 = function ( size ) { + + THREE.BufferGeometry.call( this ); + + this.vertices = this.addAttribute( 'position', Float32Array, size, 3 ).array; + this.normals = this.addAttribute( 'normal', Float32Array, size, 3 ).array; + this.uvs = this.addAttribute( 'uv', Float32Array, size, 2 ).array; + + this.boundingBox = null; + this.boundingSphere = null; + +}; + +THREE.Geometry2.prototype = Object.create( THREE.BufferGeometry.prototype ); +/** + * @author mrdoob / http://mrdoob.com/ + * @author mikael emtinger / http://gomo.se/ + * @author WestLangley / http://github.com/WestLangley +*/ + +THREE.Camera = function () { + + THREE.Object3D.call( this ); + + this.matrixWorldInverse = new THREE.Matrix4(); + this.projectionMatrix = new THREE.Matrix4(); + +}; + +THREE.Camera.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.Camera.prototype.lookAt = function () { + + // This routine does not support cameras with rotated and/or translated parent(s) + + var m1 = new THREE.Matrix4(); + + return function ( vector ) { + + m1.lookAt( this.position, vector, this.up ); + + this.quaternion.setFromRotationMatrix( m1 ); + + }; + +}(); + +THREE.Camera.prototype.clone = function (camera) { + + if ( camera === undefined ) camera = new THREE.Camera(); + + THREE.Object3D.prototype.clone.call( this, camera ); + + camera.matrixWorldInverse.copy( this.matrixWorldInverse ); + camera.projectionMatrix.copy( this.projectionMatrix ); + + return camera; +}; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.OrthographicCamera = function ( left, right, top, bottom, near, far ) { + + THREE.Camera.call( this ); + + this.left = left; + this.right = right; + this.top = top; + this.bottom = bottom; + + this.near = ( near !== undefined ) ? near : 0.1; + this.far = ( far !== undefined ) ? far : 2000; + + this.updateProjectionMatrix(); + +}; + +THREE.OrthographicCamera.prototype = Object.create( THREE.Camera.prototype ); + +THREE.OrthographicCamera.prototype.updateProjectionMatrix = function () { + + this.projectionMatrix.makeOrthographic( this.left, this.right, this.top, this.bottom, this.near, this.far ); + +}; + +THREE.OrthographicCamera.prototype.clone = function () { + + var camera = new THREE.OrthographicCamera(); + + THREE.Camera.prototype.clone.call( this, camera ); + + camera.left = this.left; + camera.right = this.right; + camera.top = this.top; + camera.bottom = this.bottom; + + camera.near = this.near; + camera.far = this.far; + + return camera; +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author greggman / http://games.greggman.com/ + * @author zz85 / http://www.lab4games.net/zz85/blog + */ + +THREE.PerspectiveCamera = function ( fov, aspect, near, far ) { + + THREE.Camera.call( this ); + + this.fov = fov !== undefined ? fov : 50; + this.aspect = aspect !== undefined ? aspect : 1; + this.near = near !== undefined ? near : 0.1; + this.far = far !== undefined ? far : 2000; + + this.updateProjectionMatrix(); + +}; + +THREE.PerspectiveCamera.prototype = Object.create( THREE.Camera.prototype ); + + +/** + * Uses Focal Length (in mm) to estimate and set FOV + * 35mm (fullframe) camera is used if frame size is not specified; + * Formula based on http://www.bobatkins.com/photography/technical/field_of_view.html + */ + +THREE.PerspectiveCamera.prototype.setLens = function ( focalLength, frameHeight ) { + + if ( frameHeight === undefined ) frameHeight = 24; + + this.fov = 2 * THREE.Math.radToDeg( Math.atan( frameHeight / ( focalLength * 2 ) ) ); + this.updateProjectionMatrix(); + +} + + +/** + * Sets an offset in a larger frustum. This is useful for multi-window or + * multi-monitor/multi-machine setups. + * + * For example, if you have 3x2 monitors and each monitor is 1920x1080 and + * the monitors are in grid like this + * + * +---+---+---+ + * | A | B | C | + * +---+---+---+ + * | D | E | F | + * +---+---+---+ + * + * then for each monitor you would call it like this + * + * var w = 1920; + * var h = 1080; + * var fullWidth = w * 3; + * var fullHeight = h * 2; + * + * --A-- + * camera.setOffset( fullWidth, fullHeight, w * 0, h * 0, w, h ); + * --B-- + * camera.setOffset( fullWidth, fullHeight, w * 1, h * 0, w, h ); + * --C-- + * camera.setOffset( fullWidth, fullHeight, w * 2, h * 0, w, h ); + * --D-- + * camera.setOffset( fullWidth, fullHeight, w * 0, h * 1, w, h ); + * --E-- + * camera.setOffset( fullWidth, fullHeight, w * 1, h * 1, w, h ); + * --F-- + * camera.setOffset( fullWidth, fullHeight, w * 2, h * 1, w, h ); + * + * Note there is no reason monitors have to be the same size or in a grid. + */ + +THREE.PerspectiveCamera.prototype.setViewOffset = function ( fullWidth, fullHeight, x, y, width, height ) { + + this.fullWidth = fullWidth; + this.fullHeight = fullHeight; + this.x = x; + this.y = y; + this.width = width; + this.height = height; + + this.updateProjectionMatrix(); + +}; + + +THREE.PerspectiveCamera.prototype.updateProjectionMatrix = function () { + + if ( this.fullWidth ) { + + var aspect = this.fullWidth / this.fullHeight; + var top = Math.tan( THREE.Math.degToRad( this.fov * 0.5 ) ) * this.near; + var bottom = -top; + var left = aspect * bottom; + var right = aspect * top; + var width = Math.abs( right - left ); + var height = Math.abs( top - bottom ); + + this.projectionMatrix.makeFrustum( + left + this.x * width / this.fullWidth, + left + ( this.x + this.width ) * width / this.fullWidth, + top - ( this.y + this.height ) * height / this.fullHeight, + top - this.y * height / this.fullHeight, + this.near, + this.far + ); + + } else { + + this.projectionMatrix.makePerspective( this.fov, this.aspect, this.near, this.far ); + + } + +}; + +THREE.PerspectiveCamera.prototype.clone = function () { + + var camera = new THREE.PerspectiveCamera(); + + THREE.Camera.prototype.clone.call( this, camera ); + + camera.fov = this.fov; + camera.aspect = this.aspect; + camera.near = this.near; + camera.far = this.far; + + return camera; +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Light = function ( color ) { + + THREE.Object3D.call( this ); + + this.color = new THREE.Color( color ); + +}; + +THREE.Light.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.Light.prototype.clone = function ( light ) { + + if ( light === undefined ) light = new THREE.Light(); + + THREE.Object3D.prototype.clone.call( this, light ); + + light.color.copy( this.color ); + + return light; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.AmbientLight = function ( color ) { + + THREE.Light.call( this, color ); + +}; + +THREE.AmbientLight.prototype = Object.create( THREE.Light.prototype ); + +THREE.AmbientLight.prototype.clone = function () { + + var light = new THREE.AmbientLight(); + + THREE.Light.prototype.clone.call( this, light ); + + return light; + +}; + +/** + * @author MPanknin / http://www.redplant.de/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.AreaLight = function ( color, intensity ) { + + THREE.Light.call( this, color ); + + this.normal = new THREE.Vector3( 0, -1, 0 ); + this.right = new THREE.Vector3( 1, 0, 0 ); + + this.intensity = ( intensity !== undefined ) ? intensity : 1; + + this.width = 1.0; + this.height = 1.0; + + this.constantAttenuation = 1.5; + this.linearAttenuation = 0.5; + this.quadraticAttenuation = 0.1; + +}; + +THREE.AreaLight.prototype = Object.create( THREE.Light.prototype ); + + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.DirectionalLight = function ( color, intensity ) { + + THREE.Light.call( this, color ); + + this.position.set( 0, 1, 0 ); + this.target = new THREE.Object3D(); + + this.intensity = ( intensity !== undefined ) ? intensity : 1; + + this.castShadow = false; + this.onlyShadow = false; + + // + + this.shadowCameraNear = 50; + this.shadowCameraFar = 5000; + + this.shadowCameraLeft = -500; + this.shadowCameraRight = 500; + this.shadowCameraTop = 500; + this.shadowCameraBottom = -500; + + this.shadowCameraVisible = false; + + this.shadowBias = 0; + this.shadowDarkness = 0.5; + + this.shadowMapWidth = 512; + this.shadowMapHeight = 512; + + // + + this.shadowCascade = false; + + this.shadowCascadeOffset = new THREE.Vector3( 0, 0, -1000 ); + this.shadowCascadeCount = 2; + + this.shadowCascadeBias = [ 0, 0, 0 ]; + this.shadowCascadeWidth = [ 512, 512, 512 ]; + this.shadowCascadeHeight = [ 512, 512, 512 ]; + + this.shadowCascadeNearZ = [ -1.000, 0.990, 0.998 ]; + this.shadowCascadeFarZ = [ 0.990, 0.998, 1.000 ]; + + this.shadowCascadeArray = []; + + // + + this.shadowMap = null; + this.shadowMapSize = null; + this.shadowCamera = null; + this.shadowMatrix = null; + +}; + +THREE.DirectionalLight.prototype = Object.create( THREE.Light.prototype ); + +THREE.DirectionalLight.prototype.clone = function () { + + var light = new THREE.DirectionalLight(); + + THREE.Light.prototype.clone.call( this, light ); + + light.target = this.target.clone(); + + light.intensity = this.intensity; + + light.castShadow = this.castShadow; + light.onlyShadow = this.onlyShadow; + + return light; + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.HemisphereLight = function ( skyColor, groundColor, intensity ) { + + THREE.Light.call( this, skyColor ); + + this.position.set( 0, 100, 0 ); + + this.groundColor = new THREE.Color( groundColor ); + this.intensity = ( intensity !== undefined ) ? intensity : 1; + +}; + +THREE.HemisphereLight.prototype = Object.create( THREE.Light.prototype ); + +THREE.HemisphereLight.prototype.clone = function () { + + var light = new THREE.HemisphereLight(); + + THREE.Light.prototype.clone.call( this, light ); + + light.groundColor.copy( this.groundColor ); + light.intensity = this.intensity; + + return light; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.PointLight = function ( color, intensity, distance ) { + + THREE.Light.call( this, color ); + + this.intensity = ( intensity !== undefined ) ? intensity : 1; + this.distance = ( distance !== undefined ) ? distance : 0; + +}; + +THREE.PointLight.prototype = Object.create( THREE.Light.prototype ); + +THREE.PointLight.prototype.clone = function () { + + var light = new THREE.PointLight(); + + THREE.Light.prototype.clone.call( this, light ); + + light.intensity = this.intensity; + light.distance = this.distance; + + return light; + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.SpotLight = function ( color, intensity, distance, angle, exponent ) { + + THREE.Light.call( this, color ); + + this.position.set( 0, 1, 0 ); + this.target = new THREE.Object3D(); + + this.intensity = ( intensity !== undefined ) ? intensity : 1; + this.distance = ( distance !== undefined ) ? distance : 0; + this.angle = ( angle !== undefined ) ? angle : Math.PI / 3; + this.exponent = ( exponent !== undefined ) ? exponent : 10; + + this.castShadow = false; + this.onlyShadow = false; + + // + + this.shadowCameraNear = 50; + this.shadowCameraFar = 5000; + this.shadowCameraFov = 50; + + this.shadowCameraVisible = false; + + this.shadowBias = 0; + this.shadowDarkness = 0.5; + + this.shadowMapWidth = 512; + this.shadowMapHeight = 512; + + // + + this.shadowMap = null; + this.shadowMapSize = null; + this.shadowCamera = null; + this.shadowMatrix = null; + +}; + +THREE.SpotLight.prototype = Object.create( THREE.Light.prototype ); + +THREE.SpotLight.prototype.clone = function () { + + var light = new THREE.SpotLight(); + + THREE.Light.prototype.clone.call( this, light ); + + light.target = this.target.clone(); + + light.intensity = this.intensity; + light.distance = this.distance; + light.angle = this.angle; + light.exponent = this.exponent; + + light.castShadow = this.castShadow; + light.onlyShadow = this.onlyShadow; + + return light; + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Loader = function ( showStatus ) { + + this.showStatus = showStatus; + this.statusDomElement = showStatus ? THREE.Loader.prototype.addStatusElement() : null; + + this.onLoadStart = function () {}; + this.onLoadProgress = function () {}; + this.onLoadComplete = function () {}; + +}; + +THREE.Loader.prototype = { + + constructor: THREE.Loader, + + crossOrigin: undefined, + + addStatusElement: function () { + + var e = document.createElement( "div" ); + + e.style.position = "absolute"; + e.style.right = "0px"; + e.style.top = "0px"; + e.style.fontSize = "0.8em"; + e.style.textAlign = "left"; + e.style.background = "rgba(0,0,0,0.25)"; + e.style.color = "#fff"; + e.style.width = "120px"; + e.style.padding = "0.5em 0.5em 0.5em 0.5em"; + e.style.zIndex = 1000; + + e.innerHTML = "Loading ..."; + + return e; + + }, + + updateProgress: function ( progress ) { + + var message = "Loaded "; + + if ( progress.total ) { + + message += ( 100 * progress.loaded / progress.total ).toFixed(0) + "%"; + + + } else { + + message += ( progress.loaded / 1000 ).toFixed(2) + " KB"; + + } + + this.statusDomElement.innerHTML = message; + + }, + + extractUrlBase: function ( url ) { + + var parts = url.split( '/' ); + + if ( parts.length === 1 ) return './'; + + parts.pop(); + + return parts.join( '/' ) + '/'; + + }, + + initMaterials: function ( materials, texturePath ) { + + var array = []; + + for ( var i = 0; i < materials.length; ++ i ) { + + array[ i ] = THREE.Loader.prototype.createMaterial( materials[ i ], texturePath ); + + } + + return array; + + }, + + needsTangents: function ( materials ) { + + for( var i = 0, il = materials.length; i < il; i ++ ) { + + var m = materials[ i ]; + + if ( m instanceof THREE.ShaderMaterial ) return true; + + } + + return false; + + }, + + createMaterial: function ( m, texturePath ) { + + var _this = this; + + function is_pow2( n ) { + + var l = Math.log( n ) / Math.LN2; + return Math.floor( l ) == l; + + } + + function nearest_pow2( n ) { + + var l = Math.log( n ) / Math.LN2; + return Math.pow( 2, Math.round( l ) ); + + } + + function load_image( where, url ) { + + var image = new Image(); + + image.onload = function () { + + if ( !is_pow2( this.width ) || !is_pow2( this.height ) ) { + + var width = nearest_pow2( this.width ); + var height = nearest_pow2( this.height ); + + where.image.width = width; + where.image.height = height; + where.image.getContext( '2d' ).drawImage( this, 0, 0, width, height ); + + } else { + + where.image = this; + + } + + where.needsUpdate = true; + + }; + + if ( _this.crossOrigin !== undefined ) image.crossOrigin = _this.crossOrigin; + image.src = url; + + } + + function create_texture( where, name, sourceFile, repeat, offset, wrap, anisotropy ) { + + var isCompressed = /\.dds$/i.test( sourceFile ); + + var fullPath = texturePath + sourceFile; + + if ( isCompressed ) { + + var texture = THREE.ImageUtils.loadCompressedTexture( fullPath ); + + where[ name ] = texture; + + } else { + + var texture = document.createElement( 'canvas' ); + + where[ name ] = new THREE.Texture( texture ); + + } + + where[ name ].sourceFile = sourceFile; + + if( repeat ) { + + where[ name ].repeat.set( repeat[ 0 ], repeat[ 1 ] ); + + if ( repeat[ 0 ] !== 1 ) where[ name ].wrapS = THREE.RepeatWrapping; + if ( repeat[ 1 ] !== 1 ) where[ name ].wrapT = THREE.RepeatWrapping; + + } + + if ( offset ) { + + where[ name ].offset.set( offset[ 0 ], offset[ 1 ] ); + + } + + if ( wrap ) { + + var wrapMap = { + "repeat": THREE.RepeatWrapping, + "mirror": THREE.MirroredRepeatWrapping + } + + if ( wrapMap[ wrap[ 0 ] ] !== undefined ) where[ name ].wrapS = wrapMap[ wrap[ 0 ] ]; + if ( wrapMap[ wrap[ 1 ] ] !== undefined ) where[ name ].wrapT = wrapMap[ wrap[ 1 ] ]; + + } + + if ( anisotropy ) { + + where[ name ].anisotropy = anisotropy; + + } + + if ( ! isCompressed ) { + + load_image( where[ name ], fullPath ); + + } + + } + + function rgb2hex( rgb ) { + + return ( rgb[ 0 ] * 255 << 16 ) + ( rgb[ 1 ] * 255 << 8 ) + rgb[ 2 ] * 255; + + } + + // defaults + + var mtype = "MeshLambertMaterial"; + var mpars = { color: 0xeeeeee, opacity: 1.0, map: null, lightMap: null, normalMap: null, bumpMap: null, wireframe: false }; + + // parameters from model file + + if ( m.shading ) { + + var shading = m.shading.toLowerCase(); + + if ( shading === "phong" ) mtype = "MeshPhongMaterial"; + else if ( shading === "basic" ) mtype = "MeshBasicMaterial"; + + } + + if ( m.blending !== undefined && THREE[ m.blending ] !== undefined ) { + + mpars.blending = THREE[ m.blending ]; + + } + + if ( m.transparent !== undefined || m.opacity < 1.0 ) { + + mpars.transparent = m.transparent; + + } + + if ( m.depthTest !== undefined ) { + + mpars.depthTest = m.depthTest; + + } + + if ( m.depthWrite !== undefined ) { + + mpars.depthWrite = m.depthWrite; + + } + + if ( m.visible !== undefined ) { + + mpars.visible = m.visible; + + } + + if ( m.flipSided !== undefined ) { + + mpars.side = THREE.BackSide; + + } + + if ( m.doubleSided !== undefined ) { + + mpars.side = THREE.DoubleSide; + + } + + if ( m.wireframe !== undefined ) { + + mpars.wireframe = m.wireframe; + + } + + if ( m.vertexColors !== undefined ) { + + if ( m.vertexColors === "face" ) { + + mpars.vertexColors = THREE.FaceColors; + + } else if ( m.vertexColors ) { + + mpars.vertexColors = THREE.VertexColors; + + } + + } + + // colors + + if ( m.colorDiffuse ) { + + mpars.color = rgb2hex( m.colorDiffuse ); + + } else if ( m.DbgColor ) { + + mpars.color = m.DbgColor; + + } + + if ( m.colorSpecular ) { + + mpars.specular = rgb2hex( m.colorSpecular ); + + } + + if ( m.colorAmbient ) { + + mpars.ambient = rgb2hex( m.colorAmbient ); + + } + + // modifiers + + if ( m.transparency ) { + + mpars.opacity = m.transparency; + + } + + if ( m.specularCoef ) { + + mpars.shininess = m.specularCoef; + + } + + // textures + + if ( m.mapDiffuse && texturePath ) { + + create_texture( mpars, "map", m.mapDiffuse, m.mapDiffuseRepeat, m.mapDiffuseOffset, m.mapDiffuseWrap, m.mapDiffuseAnisotropy ); + + } + + if ( m.mapLight && texturePath ) { + + create_texture( mpars, "lightMap", m.mapLight, m.mapLightRepeat, m.mapLightOffset, m.mapLightWrap, m.mapLightAnisotropy ); + + } + + if ( m.mapBump && texturePath ) { + + create_texture( mpars, "bumpMap", m.mapBump, m.mapBumpRepeat, m.mapBumpOffset, m.mapBumpWrap, m.mapBumpAnisotropy ); + + } + + if ( m.mapNormal && texturePath ) { + + create_texture( mpars, "normalMap", m.mapNormal, m.mapNormalRepeat, m.mapNormalOffset, m.mapNormalWrap, m.mapNormalAnisotropy ); + + } + + if ( m.mapSpecular && texturePath ) { + + create_texture( mpars, "specularMap", m.mapSpecular, m.mapSpecularRepeat, m.mapSpecularOffset, m.mapSpecularWrap, m.mapSpecularAnisotropy ); + + } + + // + + if ( m.mapBumpScale ) { + + mpars.bumpScale = m.mapBumpScale; + + } + + // special case for normal mapped material + + if ( m.mapNormal ) { + + var shader = THREE.ShaderLib[ "normalmap" ]; + var uniforms = THREE.UniformsUtils.clone( shader.uniforms ); + + uniforms[ "tNormal" ].value = mpars.normalMap; + + if ( m.mapNormalFactor ) { + + uniforms[ "uNormalScale" ].value.set( m.mapNormalFactor, m.mapNormalFactor ); + + } + + if ( mpars.map ) { + + uniforms[ "tDiffuse" ].value = mpars.map; + uniforms[ "enableDiffuse" ].value = true; + + } + + if ( mpars.specularMap ) { + + uniforms[ "tSpecular" ].value = mpars.specularMap; + uniforms[ "enableSpecular" ].value = true; + + } + + if ( mpars.lightMap ) { + + uniforms[ "tAO" ].value = mpars.lightMap; + uniforms[ "enableAO" ].value = true; + + } + + // for the moment don't handle displacement texture + + uniforms[ "diffuse" ].value.setHex( mpars.color ); + uniforms[ "specular" ].value.setHex( mpars.specular ); + uniforms[ "ambient" ].value.setHex( mpars.ambient ); + + uniforms[ "shininess" ].value = mpars.shininess; + + if ( mpars.opacity !== undefined ) { + + uniforms[ "opacity" ].value = mpars.opacity; + + } + + var parameters = { fragmentShader: shader.fragmentShader, vertexShader: shader.vertexShader, uniforms: uniforms, lights: true, fog: true }; + var material = new THREE.ShaderMaterial( parameters ); + + if ( mpars.transparent ) { + + material.transparent = true; + + } + + } else { + + var material = new THREE[ mtype ]( mpars ); + + } + + if ( m.DbgName !== undefined ) material.name = m.DbgName; + + return material; + + } + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.XHRLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.XHRLoader.prototype = { + + constructor: THREE.XHRLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + var request = new XMLHttpRequest(); + + if ( onLoad !== undefined ) { + + request.addEventListener( 'load', function ( event ) { + + onLoad( event.target.responseText ); + scope.manager.itemEnd( url ); + + }, false ); + + } + + if ( onProgress !== undefined ) { + + request.addEventListener( 'progress', function ( event ) { + + onProgress( event ); + + }, false ); + + } + + if ( onError !== undefined ) { + + request.addEventListener( 'error', function ( event ) { + + onError( event ); + + }, false ); + + } + + if ( this.crossOrigin !== undefined ) request.crossOrigin = this.crossOrigin; + + request.open( 'GET', url, true ); + request.send( null ); + + scope.manager.itemStart( url ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + } + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.ImageLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.ImageLoader.prototype = { + + constructor: THREE.ImageLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + var image = document.createElement( 'img' ); + + if ( onLoad !== undefined ) { + + image.addEventListener( 'load', function ( event ) { + + scope.manager.itemEnd( url ); + onLoad( this ); + + }, false ); + + } + + if ( onProgress !== undefined ) { + + image.addEventListener( 'progress', function ( event ) { + + onProgress( event ); + + }, false ); + + } + + if ( onError !== undefined ) { + + image.addEventListener( 'error', function ( event ) { + + onError( event ); + + }, false ); + + } + + if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin; + + image.src = url; + + scope.manager.itemStart( url ); + + return image; + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + } + +} + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.JSONLoader = function ( showStatus ) { + + THREE.Loader.call( this, showStatus ); + + this.withCredentials = false; + +}; + +THREE.JSONLoader.prototype = Object.create( THREE.Loader.prototype ); + +THREE.JSONLoader.prototype.load = function ( url, callback, texturePath ) { + + var scope = this; + + // todo: unify load API to for easier SceneLoader use + + texturePath = texturePath && ( typeof texturePath === "string" ) ? texturePath : this.extractUrlBase( url ); + + this.onLoadStart(); + this.loadAjaxJSON( this, url, callback, texturePath ); + +}; + +THREE.JSONLoader.prototype.loadAjaxJSON = function ( context, url, callback, texturePath, callbackProgress ) { + + var xhr = new XMLHttpRequest(); + + var length = 0; + + xhr.onreadystatechange = function () { + + if ( xhr.readyState === xhr.DONE ) { + + if ( xhr.status === 200 || xhr.status === 0 ) { + + if ( xhr.responseText ) { + + var json = JSON.parse( xhr.responseText ); + + if ( json.metadata.type === 'scene' ) { + + console.error( 'THREE.JSONLoader: "' + url + '" seems to be a Scene. Use THREE.SceneLoader instead.' ); + return; + + } + + var result = context.parse( json, texturePath ); + callback( result.geometry, result.materials ); + + } else { + + console.error( 'THREE.JSONLoader: "' + url + '" seems to be unreachable or the file is empty.' ); + + } + + // in context of more complex asset initialization + // do not block on single failed file + // maybe should go even one more level up + + context.onLoadComplete(); + + } else { + + console.error( 'THREE.JSONLoader: Couldn\'t load "' + url + '" (' + xhr.status + ')' ); + + } + + } else if ( xhr.readyState === xhr.LOADING ) { + + if ( callbackProgress ) { + + if ( length === 0 ) { + + length = xhr.getResponseHeader( 'Content-Length' ); + + } + + callbackProgress( { total: length, loaded: xhr.responseText.length } ); + + } + + } else if ( xhr.readyState === xhr.HEADERS_RECEIVED ) { + + if ( callbackProgress !== undefined ) { + + length = xhr.getResponseHeader( "Content-Length" ); + + } + + } + + }; + + xhr.open( "GET", url, true ); + xhr.withCredentials = this.withCredentials; + xhr.send( null ); + +}; + +THREE.JSONLoader.prototype.parse = function ( json, texturePath ) { + + var scope = this, + geometry = new THREE.Geometry(), + scale = ( json.scale !== undefined ) ? 1.0 / json.scale : 1.0; + + parseModel( scale ); + + parseSkin(); + parseMorphing( scale ); + + geometry.computeCentroids(); + geometry.computeFaceNormals(); + geometry.computeBoundingSphere(); + + function parseModel( scale ) { + + function isBitSet( value, position ) { + + return value & ( 1 << position ); + + } + + var i, j, fi, + + offset, zLength, + + colorIndex, normalIndex, uvIndex, materialIndex, + + type, + isQuad, + hasMaterial, + hasFaceVertexUv, + hasFaceNormal, hasFaceVertexNormal, + hasFaceColor, hasFaceVertexColor, + + vertex, face, faceA, faceB, color, hex, normal, + + uvLayer, uv, u, v, + + faces = json.faces, + vertices = json.vertices, + normals = json.normals, + colors = json.colors, + + nUvLayers = 0; + + if ( json.uvs !== undefined ) { + + // disregard empty arrays + + for ( i = 0; i < json.uvs.length; i++ ) { + + if ( json.uvs[ i ].length ) nUvLayers ++; + + } + + for ( i = 0; i < nUvLayers; i++ ) { + + geometry.faceVertexUvs[ i ] = []; + + } + + } + + offset = 0; + zLength = vertices.length; + + while ( offset < zLength ) { + + vertex = new THREE.Vector3(); + + vertex.x = vertices[ offset ++ ] * scale; + vertex.y = vertices[ offset ++ ] * scale; + vertex.z = vertices[ offset ++ ] * scale; + + geometry.vertices.push( vertex ); + + } + + offset = 0; + zLength = faces.length; + + while ( offset < zLength ) { + + type = faces[ offset ++ ]; + + + isQuad = isBitSet( type, 0 ); + hasMaterial = isBitSet( type, 1 ); + hasFaceVertexUv = isBitSet( type, 3 ); + hasFaceNormal = isBitSet( type, 4 ); + hasFaceVertexNormal = isBitSet( type, 5 ); + hasFaceColor = isBitSet( type, 6 ); + hasFaceVertexColor = isBitSet( type, 7 ); + + // console.log("type", type, "bits", isQuad, hasMaterial, hasFaceVertexUv, hasFaceNormal, hasFaceVertexNormal, hasFaceColor, hasFaceVertexColor); + + if ( isQuad ) { + + faceA = new THREE.Face3(); + faceA.a = faces[ offset ]; + faceA.b = faces[ offset + 1 ]; + faceA.c = faces[ offset + 3 ]; + + faceB = new THREE.Face3(); + faceB.a = faces[ offset + 1 ]; + faceB.b = faces[ offset + 2 ]; + faceB.c = faces[ offset + 3 ]; + + offset += 4; + + if ( hasMaterial ) { + + materialIndex = faces[ offset ++ ]; + faceA.materialIndex = materialIndex; + faceB.materialIndex = materialIndex; + + } + + // to get face <=> uv index correspondence + + fi = geometry.faces.length; + + if ( hasFaceVertexUv ) { + + for ( i = 0; i < nUvLayers; i++ ) { + + uvLayer = json.uvs[ i ]; + + geometry.faceVertexUvs[ i ][ fi ] = []; + geometry.faceVertexUvs[ i ][ fi + 1 ] = [] + + for ( j = 0; j < 4; j ++ ) { + + uvIndex = faces[ offset ++ ]; + + u = uvLayer[ uvIndex * 2 ]; + v = uvLayer[ uvIndex * 2 + 1 ]; + + uv = new THREE.Vector2( u, v ); + + if ( j !== 2 ) geometry.faceVertexUvs[ i ][ fi ].push( uv ); + if ( j !== 0 ) geometry.faceVertexUvs[ i ][ fi + 1 ].push( uv ); + + } + + } + + } + + if ( hasFaceNormal ) { + + normalIndex = faces[ offset ++ ] * 3; + + faceA.normal.set( + normals[ normalIndex ++ ], + normals[ normalIndex ++ ], + normals[ normalIndex ] + ); + + faceB.normal.copy( faceA.normal ); + + } + + if ( hasFaceVertexNormal ) { + + for ( i = 0; i < 4; i++ ) { + + normalIndex = faces[ offset ++ ] * 3; + + normal = new THREE.Vector3( + normals[ normalIndex ++ ], + normals[ normalIndex ++ ], + normals[ normalIndex ] + ); + + + if ( i !== 2 ) faceA.vertexNormals.push( normal ); + if ( i !== 0 ) faceB.vertexNormals.push( normal ); + + } + + } + + + if ( hasFaceColor ) { + + colorIndex = faces[ offset ++ ]; + hex = colors[ colorIndex ]; + + faceA.color.setHex( hex ); + faceB.color.setHex( hex ); + + } + + + if ( hasFaceVertexColor ) { + + for ( i = 0; i < 4; i++ ) { + + colorIndex = faces[ offset ++ ]; + hex = colors[ colorIndex ]; + + if ( i !== 2 ) faceA.vertexColors.push( new THREE.Color( hex ) ); + if ( i !== 0 ) faceB.vertexColors.push( new THREE.Color( hex ) ); + + } + + } + + geometry.faces.push( faceA ); + geometry.faces.push( faceB ); + + } else { + + face = new THREE.Face3(); + face.a = faces[ offset ++ ]; + face.b = faces[ offset ++ ]; + face.c = faces[ offset ++ ]; + + if ( hasMaterial ) { + + materialIndex = faces[ offset ++ ]; + face.materialIndex = materialIndex; + + } + + // to get face <=> uv index correspondence + + fi = geometry.faces.length; + + if ( hasFaceVertexUv ) { + + for ( i = 0; i < nUvLayers; i++ ) { + + uvLayer = json.uvs[ i ]; + + geometry.faceVertexUvs[ i ][ fi ] = []; + + for ( j = 0; j < 3; j ++ ) { + + uvIndex = faces[ offset ++ ]; + + u = uvLayer[ uvIndex * 2 ]; + v = uvLayer[ uvIndex * 2 + 1 ]; + + uv = new THREE.Vector2( u, v ); + + geometry.faceVertexUvs[ i ][ fi ].push( uv ); + + } + + } + + } + + if ( hasFaceNormal ) { + + normalIndex = faces[ offset ++ ] * 3; + + face.normal.set( + normals[ normalIndex ++ ], + normals[ normalIndex ++ ], + normals[ normalIndex ] + ); + + } + + if ( hasFaceVertexNormal ) { + + for ( i = 0; i < 3; i++ ) { + + normalIndex = faces[ offset ++ ] * 3; + + normal = new THREE.Vector3( + normals[ normalIndex ++ ], + normals[ normalIndex ++ ], + normals[ normalIndex ] + ); + + face.vertexNormals.push( normal ); + + } + + } + + + if ( hasFaceColor ) { + + colorIndex = faces[ offset ++ ]; + face.color.setHex( colors[ colorIndex ] ); + + } + + + if ( hasFaceVertexColor ) { + + for ( i = 0; i < 3; i++ ) { + + colorIndex = faces[ offset ++ ]; + face.vertexColors.push( new THREE.Color( colors[ colorIndex ] ) ); + + } + + } + + geometry.faces.push( face ); + + } + + } + + }; + + function parseSkin() { + + if ( json.skinWeights ) { + + for ( var i = 0, l = json.skinWeights.length; i < l; i += 2 ) { + + var x = json.skinWeights[ i ]; + var y = json.skinWeights[ i + 1 ]; + var z = 0; + var w = 0; + + geometry.skinWeights.push( new THREE.Vector4( x, y, z, w ) ); + + } + + } + + if ( json.skinIndices ) { + + for ( var i = 0, l = json.skinIndices.length; i < l; i += 2 ) { + + var a = json.skinIndices[ i ]; + var b = json.skinIndices[ i + 1 ]; + var c = 0; + var d = 0; + + geometry.skinIndices.push( new THREE.Vector4( a, b, c, d ) ); + + } + + } + + geometry.bones = json.bones; + + if ( geometry.bones && geometry.bones.length > 0 && ( geometry.skinWeights.length !== geometry.skinIndices.length || geometry.skinIndices.length !== geometry.vertices.length ) ) { + + console.warn( 'When skinning, number of vertices (' + geometry.vertices.length + '), skinIndices (' + + geometry.skinIndices.length + '), and skinWeights (' + geometry.skinWeights.length + ') should match.' ); + + } + + + // could change this to json.animations[0] or remove completely + + geometry.animation = json.animation; + geometry.animations = json.animations; + + }; + + function parseMorphing( scale ) { + + if ( json.morphTargets !== undefined ) { + + var i, l, v, vl, dstVertices, srcVertices; + + for ( i = 0, l = json.morphTargets.length; i < l; i ++ ) { + + geometry.morphTargets[ i ] = {}; + geometry.morphTargets[ i ].name = json.morphTargets[ i ].name; + geometry.morphTargets[ i ].vertices = []; + + dstVertices = geometry.morphTargets[ i ].vertices; + srcVertices = json.morphTargets [ i ].vertices; + + for( v = 0, vl = srcVertices.length; v < vl; v += 3 ) { + + var vertex = new THREE.Vector3(); + vertex.x = srcVertices[ v ] * scale; + vertex.y = srcVertices[ v + 1 ] * scale; + vertex.z = srcVertices[ v + 2 ] * scale; + + dstVertices.push( vertex ); + + } + + } + + } + + if ( json.morphColors !== undefined ) { + + var i, l, c, cl, dstColors, srcColors, color; + + for ( i = 0, l = json.morphColors.length; i < l; i++ ) { + + geometry.morphColors[ i ] = {}; + geometry.morphColors[ i ].name = json.morphColors[ i ].name; + geometry.morphColors[ i ].colors = []; + + dstColors = geometry.morphColors[ i ].colors; + srcColors = json.morphColors [ i ].colors; + + for ( c = 0, cl = srcColors.length; c < cl; c += 3 ) { + + color = new THREE.Color( 0xffaa00 ); + color.setRGB( srcColors[ c ], srcColors[ c + 1 ], srcColors[ c + 2 ] ); + dstColors.push( color ); + + } + + } + + } + + }; + + if ( json.materials === undefined ) { + + return { geometry: geometry }; + + } else { + + var materials = this.initMaterials( json.materials, texturePath ); + + if ( this.needsTangents( materials ) ) { + + geometry.computeTangents(); + + } + + return { geometry: geometry, materials: materials }; + + } + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.LoadingManager = function ( onLoad, onProgress, onError ) { + + var scope = this; + + var loaded = 0, total = 0; + + this.onLoad = onLoad; + this.onProgress = onProgress; + this.onError = onError; + + this.itemStart = function ( url ) { + + total ++; + + }; + + this.itemEnd = function ( url ) { + + loaded ++; + + if ( scope.onProgress !== undefined ) { + + scope.onProgress( url, loaded, total ); + + } + + if ( loaded === total && scope.onLoad !== undefined ) { + + scope.onLoad(); + + } + + }; + +}; + +THREE.DefaultLoadingManager = new THREE.LoadingManager(); + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.BufferGeometryLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.BufferGeometryLoader.prototype = { + + constructor: THREE.BufferGeometryLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var loader = new THREE.XHRLoader(); + loader.setCrossOrigin( this.crossOrigin ); + loader.load( url, function ( text ) { + + onLoad( scope.parse( JSON.parse( text ) ) ); + + } ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + }, + + parse: function ( json ) { + + var geometry = new THREE.BufferGeometry(); + + var attributes = json.attributes; + var offsets = json.offsets; + var boundingSphere = json.boundingSphere; + + for ( var key in attributes ) { + + var attribute = attributes[ key ]; + + geometry.attributes[ key ] = { + itemSize: attribute.itemSize, + array: new self[ attribute.type ]( attribute.array ) + } + + } + + if ( offsets !== undefined ) { + + geometry.offsets = JSON.parse( JSON.stringify( offsets ) ); + + } + + if ( boundingSphere !== undefined ) { + + geometry.boundingSphere = new THREE.Sphere( + new THREE.Vector3().fromArray( boundingSphere.center !== undefined ? boundingSphere.center : [ 0, 0, 0 ] ), + boundingSphere.radius + ); + + } + + return geometry; + + } + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Geometry2Loader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.Geometry2Loader.prototype = { + + constructor: THREE.Geometry2Loader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var loader = new THREE.XHRLoader(); + loader.setCrossOrigin( this.crossOrigin ); + loader.load( url, function ( text ) { + + onLoad( scope.parse( JSON.parse( text ) ) ); + + } ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + }, + + parse: function ( json ) { + + var geometry = new THREE.Geometry2( json.vertices.length / 3 ); + + var attributes = [ 'vertices', 'normals', 'uvs' ]; + var boundingSphere = json.boundingSphere; + + for ( var key in attributes ) { + + var attribute = attributes[ key ]; + geometry[ attribute ].set( json[ attribute ] ); + + } + + if ( boundingSphere !== undefined ) { + + geometry.boundingSphere = new THREE.Sphere( + new THREE.Vector3().fromArray( boundingSphere.center !== undefined ? boundingSphere.center : [ 0, 0, 0 ] ), + boundingSphere.radius + ); + + } + + return geometry; + + } + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.MaterialLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.MaterialLoader.prototype = { + + constructor: THREE.MaterialLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var loader = new THREE.XHRLoader(); + loader.setCrossOrigin( this.crossOrigin ); + loader.load( url, function ( text ) { + + onLoad( scope.parse( JSON.parse( text ) ) ); + + } ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + }, + + parse: function ( json ) { + + var material = new THREE[ json.type ]; + + if ( json.color !== undefined ) material.color.setHex( json.color ); + if ( json.ambient !== undefined ) material.ambient.setHex( json.ambient ); + if ( json.emissive !== undefined ) material.emissive.setHex( json.emissive ); + if ( json.specular !== undefined ) material.specular.setHex( json.specular ); + if ( json.shininess !== undefined ) material.shininess = json.shininess; + if ( json.vertexColors !== undefined ) material.vertexColors = json.vertexColors; + if ( json.blending !== undefined ) material.blending = json.blending; + if ( json.side !== undefined ) material.side = json.side; + if ( json.opacity !== undefined ) material.opacity = json.opacity; + if ( json.transparent !== undefined ) material.transparent = json.transparent; + if ( json.wireframe !== undefined ) material.wireframe = json.wireframe; + + if ( json.materials !== undefined ) { + + for ( var i = 0, l = json.materials.length; i < l; i ++ ) { + + material.materials.push( this.parse( json.materials[ i ] ) ); + + } + + } + + return material; + + } + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.ObjectLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.ObjectLoader.prototype = { + + constructor: THREE.ObjectLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var loader = new THREE.XHRLoader( scope.manager ); + loader.setCrossOrigin( this.crossOrigin ); + loader.load( url, function ( text ) { + + onLoad( scope.parse( JSON.parse( text ) ) ); + + } ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + }, + + parse: function ( json ) { + + var geometries = this.parseGeometries( json.geometries ); + var materials = this.parseMaterials( json.materials ); + var object = this.parseObject( json.object, geometries, materials ); + + return object; + + }, + + parseGeometries: function ( json ) { + + var geometries = {}; + + if ( json !== undefined ) { + + var geometryLoader = new THREE.JSONLoader(); + var geometry2Loader = new THREE.Geometry2Loader(); + var bufferGeometryLoader = new THREE.BufferGeometryLoader(); + + for ( var i = 0, l = json.length; i < l; i ++ ) { + + var geometry; + var data = json[ i ]; + + switch ( data.type ) { + + case 'PlaneGeometry': + + geometry = new THREE.PlaneGeometry( + data.width, + data.height, + data.widthSegments, + data.heightSegments + ); + + break; + + case 'BoxGeometry': + case 'CubeGeometry': // DEPRECATED + + geometry = new THREE.BoxGeometry( + data.width, + data.height, + data.depth, + data.widthSegments, + data.heightSegments, + data.depthSegments + ); + + break; + + case 'CircleGeometry': + + geometry = new THREE.CircleGeometry( + data.radius, + data.segments + ); + + break; + + case 'CylinderGeometry': + + geometry = new THREE.CylinderGeometry( + data.radiusTop, + data.radiusBottom, + data.height, + data.radialSegments, + data.heightSegments, + data.openEnded + ); + + break; + + case 'SphereGeometry': + + geometry = new THREE.SphereGeometry( + data.radius, + data.widthSegments, + data.heightSegments, + data.phiStart, + data.phiLength, + data.thetaStart, + data.thetaLength + ); + + break; + + case 'IcosahedronGeometry': + + geometry = new THREE.IcosahedronGeometry( + data.radius, + data.detail + ); + + break; + + case 'TorusGeometry': + + geometry = new THREE.TorusGeometry( + data.radius, + data.tube, + data.radialSegments, + data.tubularSegments, + data.arc + ); + + break; + + case 'TorusKnotGeometry': + + geometry = new THREE.TorusKnotGeometry( + data.radius, + data.tube, + data.radialSegments, + data.tubularSegments, + data.p, + data.q, + data.heightScale + ); + + break; + + case 'BufferGeometry': + + geometry = bufferGeometryLoader.parse( data.data ); + + break; + + case 'Geometry2': + + geometry = geometry2Loader.parse( data.data ); + + break; + + case 'Geometry': + + geometry = geometryLoader.parse( data.data ).geometry; + + break; + + } + + geometry.uuid = data.uuid; + + if ( data.name !== undefined ) geometry.name = data.name; + + geometries[ data.uuid ] = geometry; + + } + + } + + return geometries; + + }, + + parseMaterials: function ( json ) { + + var materials = {}; + + if ( json !== undefined ) { + + var loader = new THREE.MaterialLoader(); + + for ( var i = 0, l = json.length; i < l; i ++ ) { + + var data = json[ i ]; + var material = loader.parse( data ); + + material.uuid = data.uuid; + + if ( data.name !== undefined ) material.name = data.name; + + materials[ data.uuid ] = material; + + } + + } + + return materials; + + }, + + parseObject: function () { + + var matrix = new THREE.Matrix4(); + + return function ( data, geometries, materials ) { + + var object; + + switch ( data.type ) { + + case 'Scene': + + object = new THREE.Scene(); + + break; + + case 'PerspectiveCamera': + + object = new THREE.PerspectiveCamera( data.fov, data.aspect, data.near, data.far ); + + break; + + case 'OrthographicCamera': + + object = new THREE.OrthographicCamera( data.left, data.right, data.top, data.bottom, data.near, data.far ); + + break; + + case 'AmbientLight': + + object = new THREE.AmbientLight( data.color ); + + break; + + case 'DirectionalLight': + + object = new THREE.DirectionalLight( data.color, data.intensity ); + + break; + + case 'PointLight': + + object = new THREE.PointLight( data.color, data.intensity, data.distance ); + + break; + + case 'SpotLight': + + object = new THREE.SpotLight( data.color, data.intensity, data.distance, data.angle, data.exponent ); + + break; + + case 'HemisphereLight': + + object = new THREE.HemisphereLight( data.color, data.groundColor, data.intensity ); + + break; + + case 'Mesh': + + var geometry = geometries[ data.geometry ]; + var material = materials[ data.material ]; + + if ( geometry === undefined ) { + + console.error( 'THREE.ObjectLoader: Undefined geometry ' + data.geometry ); + + } + + if ( material === undefined ) { + + console.error( 'THREE.ObjectLoader: Undefined material ' + data.material ); + + } + + object = new THREE.Mesh( geometry, material ); + + break; + + case 'Sprite': + + var material = materials[ data.material ]; + + if ( material === undefined ) { + + console.error( 'THREE.ObjectLoader: Undefined material ' + data.material ); + + } + + object = new THREE.Sprite( material ); + + break; + + default: + + object = new THREE.Object3D(); + + } + + object.uuid = data.uuid; + + if ( data.name !== undefined ) object.name = data.name; + if ( data.matrix !== undefined ) { + + matrix.fromArray( data.matrix ); + matrix.decompose( object.position, object.quaternion, object.scale ); + + } else { + + if ( data.position !== undefined ) object.position.fromArray( data.position ); + if ( data.rotation !== undefined ) object.rotation.fromArray( data.rotation ); + if ( data.scale !== undefined ) object.scale.fromArray( data.scale ); + + } + + if ( data.visible !== undefined ) object.visible = data.visible; + if ( data.userData !== undefined ) object.userData = data.userData; + + if ( data.children !== undefined ) { + + for ( var child in data.children ) { + + object.add( this.parseObject( data.children[ child ], geometries, materials ) ); + + } + + } + + return object; + + } + + }() + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.SceneLoader = function () { + + this.onLoadStart = function () {}; + this.onLoadProgress = function() {}; + this.onLoadComplete = function () {}; + + this.callbackSync = function () {}; + this.callbackProgress = function () {}; + + this.geometryHandlers = {}; + this.hierarchyHandlers = {}; + + this.addGeometryHandler( "ascii", THREE.JSONLoader ); + +}; + +THREE.SceneLoader.prototype = { + + constructor: THREE.SceneLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var loader = new THREE.XHRLoader( scope.manager ); + loader.setCrossOrigin( this.crossOrigin ); + loader.load( url, function ( text ) { + + scope.parse( JSON.parse( text ), onLoad, url ); + + } ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + }, + + addGeometryHandler: function ( typeID, loaderClass ) { + + this.geometryHandlers[ typeID ] = { "loaderClass": loaderClass }; + + }, + + addHierarchyHandler: function ( typeID, loaderClass ) { + + this.hierarchyHandlers[ typeID ] = { "loaderClass": loaderClass }; + + }, + + parse: function ( json, callbackFinished, url ) { + + var scope = this; + + var urlBase = THREE.Loader.prototype.extractUrlBase( url ); + + var geometry, material, camera, fog, + texture, images, color, + light, hex, intensity, + counter_models, counter_textures, + total_models, total_textures, + result; + + var target_array = []; + + var data = json; + + // async geometry loaders + + for ( var typeID in this.geometryHandlers ) { + + var loaderClass = this.geometryHandlers[ typeID ][ "loaderClass" ]; + this.geometryHandlers[ typeID ][ "loaderObject" ] = new loaderClass(); + + } + + // async hierachy loaders + + for ( var typeID in this.hierarchyHandlers ) { + + var loaderClass = this.hierarchyHandlers[ typeID ][ "loaderClass" ]; + this.hierarchyHandlers[ typeID ][ "loaderObject" ] = new loaderClass(); + + } + + counter_models = 0; + counter_textures = 0; + + result = { + + scene: new THREE.Scene(), + geometries: {}, + face_materials: {}, + materials: {}, + textures: {}, + objects: {}, + cameras: {}, + lights: {}, + fogs: {}, + empties: {}, + groups: {} + + }; + + if ( data.transform ) { + + var position = data.transform.position, + rotation = data.transform.rotation, + scale = data.transform.scale; + + if ( position ) { + + result.scene.position.fromArray( position ); + + } + + if ( rotation ) { + + result.scene.rotation.fromArray( rotation ); + + } + + if ( scale ) { + + result.scene.scale.fromArray( scale ); + + } + + if ( position || rotation || scale ) { + + result.scene.updateMatrix(); + result.scene.updateMatrixWorld(); + + } + + } + + function get_url( source_url, url_type ) { + + if ( url_type == "relativeToHTML" ) { + + return source_url; + + } else { + + return urlBase + source_url; + + } + + }; + + // toplevel loader function, delegates to handle_children + + function handle_objects() { + + handle_children( result.scene, data.objects ); + + } + + // handle all the children from the loaded json and attach them to given parent + + function handle_children( parent, children ) { + + var mat, dst, pos, rot, scl, quat; + + for ( var objID in children ) { + + // check by id if child has already been handled, + // if not, create new object + + var object = result.objects[ objID ]; + var objJSON = children[ objID ]; + + if ( object === undefined ) { + + // meshes + + if ( objJSON.type && ( objJSON.type in scope.hierarchyHandlers ) ) { + + if ( objJSON.loading === undefined ) { + + var reservedTypes = { + "type": 1, "url": 1, "material": 1, + "position": 1, "rotation": 1, "scale" : 1, + "visible": 1, "children": 1, "userData": 1, + "skin": 1, "morph": 1, "mirroredLoop": 1, "duration": 1 + }; + + var loaderParameters = {}; + + for ( var parType in objJSON ) { + + if ( ! ( parType in reservedTypes ) ) { + + loaderParameters[ parType ] = objJSON[ parType ]; + + } + + } + + material = result.materials[ objJSON.material ]; + + objJSON.loading = true; + + var loader = scope.hierarchyHandlers[ objJSON.type ][ "loaderObject" ]; + + // ColladaLoader + + if ( loader.options ) { + + loader.load( get_url( objJSON.url, data.urlBaseType ), create_callback_hierachy( objID, parent, material, objJSON ) ); + + // UTF8Loader + // OBJLoader + + } else { + + loader.load( get_url( objJSON.url, data.urlBaseType ), create_callback_hierachy( objID, parent, material, objJSON ), loaderParameters ); + + } + + } + + } else if ( objJSON.geometry !== undefined ) { + + geometry = result.geometries[ objJSON.geometry ]; + + // geometry already loaded + + if ( geometry ) { + + var needsTangents = false; + + material = result.materials[ objJSON.material ]; + needsTangents = material instanceof THREE.ShaderMaterial; + + pos = objJSON.position; + rot = objJSON.rotation; + scl = objJSON.scale; + mat = objJSON.matrix; + quat = objJSON.quaternion; + + // use materials from the model file + // if there is no material specified in the object + + if ( ! objJSON.material ) { + + material = new THREE.MeshFaceMaterial( result.face_materials[ objJSON.geometry ] ); + + } + + // use materials from the model file + // if there is just empty face material + // (must create new material as each model has its own face material) + + if ( ( material instanceof THREE.MeshFaceMaterial ) && material.materials.length === 0 ) { + + material = new THREE.MeshFaceMaterial( result.face_materials[ objJSON.geometry ] ); + + } + + if ( material instanceof THREE.MeshFaceMaterial ) { + + for ( var i = 0; i < material.materials.length; i ++ ) { + + needsTangents = needsTangents || ( material.materials[ i ] instanceof THREE.ShaderMaterial ); + + } + + } + + if ( needsTangents ) { + + geometry.computeTangents(); + + } + + if ( objJSON.skin ) { + + object = new THREE.SkinnedMesh( geometry, material ); + + } else if ( objJSON.morph ) { + + object = new THREE.MorphAnimMesh( geometry, material ); + + if ( objJSON.duration !== undefined ) { + + object.duration = objJSON.duration; + + } + + if ( objJSON.time !== undefined ) { + + object.time = objJSON.time; + + } + + if ( objJSON.mirroredLoop !== undefined ) { + + object.mirroredLoop = objJSON.mirroredLoop; + + } + + if ( material.morphNormals ) { + + geometry.computeMorphNormals(); + + } + + } else { + + object = new THREE.Mesh( geometry, material ); + + } + + object.name = objID; + + if ( mat ) { + + object.matrixAutoUpdate = false; + object.matrix.set( + mat[0], mat[1], mat[2], mat[3], + mat[4], mat[5], mat[6], mat[7], + mat[8], mat[9], mat[10], mat[11], + mat[12], mat[13], mat[14], mat[15] + ); + + } else { + + object.position.fromArray( pos ); + + if ( quat ) { + + object.quaternion.fromArray( quat ); + + } else { + + object.rotation.fromArray( rot ); + + } + + object.scale.fromArray( scl ); + + } + + object.visible = objJSON.visible; + object.castShadow = objJSON.castShadow; + object.receiveShadow = objJSON.receiveShadow; + + parent.add( object ); + + result.objects[ objID ] = object; + + } + + // lights + + } else if ( objJSON.type === "AmbientLight" || objJSON.type === "PointLight" || + objJSON.type === "DirectionalLight" || objJSON.type === "SpotLight" || + objJSON.type === "HemisphereLight" || objJSON.type === "AreaLight" ) { + + var color = objJSON.color; + var intensity = objJSON.intensity; + var distance = objJSON.distance; + var position = objJSON.position; + var rotation = objJSON.rotation; + + switch ( objJSON.type ) { + + case 'AmbientLight': + light = new THREE.AmbientLight( color ); + break; + + case 'PointLight': + light = new THREE.PointLight( color, intensity, distance ); + light.position.fromArray( position ); + break; + + case 'DirectionalLight': + light = new THREE.DirectionalLight( color, intensity ); + light.position.fromArray( objJSON.direction ); + break; + + case 'SpotLight': + light = new THREE.SpotLight( color, intensity, distance, 1 ); + light.angle = objJSON.angle; + light.position.fromArray( position ); + light.target.set( position[ 0 ], position[ 1 ] - distance, position[ 2 ] ); + light.target.applyEuler( new THREE.Euler( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ], 'XYZ' ) ); + break; + + case 'HemisphereLight': + light = new THREE.DirectionalLight( color, intensity, distance ); + light.target.set( position[ 0 ], position[ 1 ] - distance, position[ 2 ] ); + light.target.applyEuler( new THREE.Euler( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ], 'XYZ' ) ); + break; + + case 'AreaLight': + light = new THREE.AreaLight(color, intensity); + light.position.fromArray( position ); + light.width = objJSON.size; + light.height = objJSON.size_y; + break; + + } + + parent.add( light ); + + light.name = objID; + result.lights[ objID ] = light; + result.objects[ objID ] = light; + + // cameras + + } else if ( objJSON.type === "PerspectiveCamera" || objJSON.type === "OrthographicCamera" ) { + + pos = objJSON.position; + rot = objJSON.rotation; + quat = objJSON.quaternion; + + if ( objJSON.type === "PerspectiveCamera" ) { + + camera = new THREE.PerspectiveCamera( objJSON.fov, objJSON.aspect, objJSON.near, objJSON.far ); + + } else if ( objJSON.type === "OrthographicCamera" ) { + + camera = new THREE.OrthographicCamera( objJSON.left, objJSON.right, objJSON.top, objJSON.bottom, objJSON.near, objJSON.far ); + + } + + camera.name = objID; + camera.position.fromArray( pos ); + + if ( quat !== undefined ) { + + camera.quaternion.fromArray( quat ); + + } else if ( rot !== undefined ) { + + camera.rotation.fromArray( rot ); + + } + + parent.add( camera ); + + result.cameras[ objID ] = camera; + result.objects[ objID ] = camera; + + // pure Object3D + + } else { + + pos = objJSON.position; + rot = objJSON.rotation; + scl = objJSON.scale; + quat = objJSON.quaternion; + + object = new THREE.Object3D(); + object.name = objID; + object.position.fromArray( pos ); + + if ( quat ) { + + object.quaternion.fromArray( quat ); + + } else { + + object.rotation.fromArray( rot ); + + } + + object.scale.fromArray( scl ); + object.visible = ( objJSON.visible !== undefined ) ? objJSON.visible : false; + + parent.add( object ); + + result.objects[ objID ] = object; + result.empties[ objID ] = object; + + } + + if ( object ) { + + if ( objJSON.userData !== undefined ) { + + for ( var key in objJSON.userData ) { + + var value = objJSON.userData[ key ]; + object.userData[ key ] = value; + + } + + } + + if ( objJSON.groups !== undefined ) { + + for ( var i = 0; i < objJSON.groups.length; i ++ ) { + + var groupID = objJSON.groups[ i ]; + + if ( result.groups[ groupID ] === undefined ) { + + result.groups[ groupID ] = []; + + } + + result.groups[ groupID ].push( objID ); + + } + + } + + } + + } + + if ( object !== undefined && objJSON.children !== undefined ) { + + handle_children( object, objJSON.children ); + + } + + } + + }; + + function handle_mesh( geo, mat, id ) { + + result.geometries[ id ] = geo; + result.face_materials[ id ] = mat; + handle_objects(); + + }; + + function handle_hierarchy( node, id, parent, material, obj ) { + + var p = obj.position; + var r = obj.rotation; + var q = obj.quaternion; + var s = obj.scale; + + node.position.fromArray( p ); + + if ( q ) { + + node.quaternion.fromArray( q ); + + } else { + + node.rotation.fromArray( r ); + + } + + node.scale.fromArray( s ); + + // override children materials + // if object material was specified in JSON explicitly + + if ( material ) { + + node.traverse( function ( child ) { + + child.material = material; + + } ); + + } + + // override children visibility + // with root node visibility as specified in JSON + + var visible = ( obj.visible !== undefined ) ? obj.visible : true; + + node.traverse( function ( child ) { + + child.visible = visible; + + } ); + + parent.add( node ); + + node.name = id; + + result.objects[ id ] = node; + handle_objects(); + + }; + + function create_callback_geometry( id ) { + + return function ( geo, mat ) { + + geo.name = id; + + handle_mesh( geo, mat, id ); + + counter_models -= 1; + + scope.onLoadComplete(); + + async_callback_gate(); + + } + + }; + + function create_callback_hierachy( id, parent, material, obj ) { + + return function ( event ) { + + var result; + + // loaders which use EventDispatcher + + if ( event.content ) { + + result = event.content; + + // ColladaLoader + + } else if ( event.dae ) { + + result = event.scene; + + + // UTF8Loader + + } else { + + result = event; + + } + + handle_hierarchy( result, id, parent, material, obj ); + + counter_models -= 1; + + scope.onLoadComplete(); + + async_callback_gate(); + + } + + }; + + function create_callback_embed( id ) { + + return function ( geo, mat ) { + + geo.name = id; + + result.geometries[ id ] = geo; + result.face_materials[ id ] = mat; + + } + + }; + + function async_callback_gate() { + + var progress = { + + totalModels : total_models, + totalTextures : total_textures, + loadedModels : total_models - counter_models, + loadedTextures : total_textures - counter_textures + + }; + + scope.callbackProgress( progress, result ); + + scope.onLoadProgress(); + + if ( counter_models === 0 && counter_textures === 0 ) { + + finalize(); + callbackFinished( result ); + + } + + }; + + function finalize() { + + // take care of targets which could be asynchronously loaded objects + + for ( var i = 0; i < target_array.length; i ++ ) { + + var ta = target_array[ i ]; + + var target = result.objects[ ta.targetName ]; + + if ( target ) { + + ta.object.target = target; + + } else { + + // if there was error and target of specified name doesn't exist in the scene file + // create instead dummy target + // (target must be added to scene explicitly as parent is already added) + + ta.object.target = new THREE.Object3D(); + result.scene.add( ta.object.target ); + + } + + ta.object.target.userData.targetInverse = ta.object; + + } + + }; + + var callbackTexture = function ( count ) { + + counter_textures -= count; + async_callback_gate(); + + scope.onLoadComplete(); + + }; + + // must use this instead of just directly calling callbackTexture + // because of closure in the calling context loop + + var generateTextureCallback = function ( count ) { + + return function () { + + callbackTexture( count ); + + }; + + }; + + function traverse_json_hierarchy( objJSON, callback ) { + + callback( objJSON ); + + if ( objJSON.children !== undefined ) { + + for ( var objChildID in objJSON.children ) { + + traverse_json_hierarchy( objJSON.children[ objChildID ], callback ); + + } + + } + + }; + + // first go synchronous elements + + // fogs + + var fogID, fogJSON; + + for ( fogID in data.fogs ) { + + fogJSON = data.fogs[ fogID ]; + + if ( fogJSON.type === "linear" ) { + + fog = new THREE.Fog( 0x000000, fogJSON.near, fogJSON.far ); + + } else if ( fogJSON.type === "exp2" ) { + + fog = new THREE.FogExp2( 0x000000, fogJSON.density ); + + } + + color = fogJSON.color; + fog.color.setRGB( color[0], color[1], color[2] ); + + result.fogs[ fogID ] = fog; + + } + + // now come potentially asynchronous elements + + // geometries + + // count how many geometries will be loaded asynchronously + + var geoID, geoJSON; + + for ( geoID in data.geometries ) { + + geoJSON = data.geometries[ geoID ]; + + if ( geoJSON.type in this.geometryHandlers ) { + + counter_models += 1; + + scope.onLoadStart(); + + } + + } + + // count how many hierarchies will be loaded asynchronously + + for ( var objID in data.objects ) { + + traverse_json_hierarchy( data.objects[ objID ], function ( objJSON ) { + + if ( objJSON.type && ( objJSON.type in scope.hierarchyHandlers ) ) { + + counter_models += 1; + + scope.onLoadStart(); + + } + + }); + + } + + total_models = counter_models; + + for ( geoID in data.geometries ) { + + geoJSON = data.geometries[ geoID ]; + + if ( geoJSON.type === "cube" ) { + + geometry = new THREE.BoxGeometry( geoJSON.width, geoJSON.height, geoJSON.depth, geoJSON.widthSegments, geoJSON.heightSegments, geoJSON.depthSegments ); + geometry.name = geoID; + result.geometries[ geoID ] = geometry; + + } else if ( geoJSON.type === "plane" ) { + + geometry = new THREE.PlaneGeometry( geoJSON.width, geoJSON.height, geoJSON.widthSegments, geoJSON.heightSegments ); + geometry.name = geoID; + result.geometries[ geoID ] = geometry; + + } else if ( geoJSON.type === "sphere" ) { + + geometry = new THREE.SphereGeometry( geoJSON.radius, geoJSON.widthSegments, geoJSON.heightSegments ); + geometry.name = geoID; + result.geometries[ geoID ] = geometry; + + } else if ( geoJSON.type === "cylinder" ) { + + geometry = new THREE.CylinderGeometry( geoJSON.topRad, geoJSON.botRad, geoJSON.height, geoJSON.radSegs, geoJSON.heightSegs ); + geometry.name = geoID; + result.geometries[ geoID ] = geometry; + + } else if ( geoJSON.type === "torus" ) { + + geometry = new THREE.TorusGeometry( geoJSON.radius, geoJSON.tube, geoJSON.segmentsR, geoJSON.segmentsT ); + geometry.name = geoID; + result.geometries[ geoID ] = geometry; + + } else if ( geoJSON.type === "icosahedron" ) { + + geometry = new THREE.IcosahedronGeometry( geoJSON.radius, geoJSON.subdivisions ); + geometry.name = geoID; + result.geometries[ geoID ] = geometry; + + } else if ( geoJSON.type in this.geometryHandlers ) { + + var loaderParameters = {}; + + for ( var parType in geoJSON ) { + + if ( parType !== "type" && parType !== "url" ) { + + loaderParameters[ parType ] = geoJSON[ parType ]; + + } + + } + + var loader = this.geometryHandlers[ geoJSON.type ][ "loaderObject" ]; + loader.load( get_url( geoJSON.url, data.urlBaseType ), create_callback_geometry( geoID ), loaderParameters ); + + } else if ( geoJSON.type === "embedded" ) { + + var modelJson = data.embeds[ geoJSON.id ], + texture_path = ""; + + // pass metadata along to jsonLoader so it knows the format version + + modelJson.metadata = data.metadata; + + if ( modelJson ) { + + var jsonLoader = this.geometryHandlers[ "ascii" ][ "loaderObject" ]; + var model = jsonLoader.parse( modelJson, texture_path ); + create_callback_embed( geoID )( model.geometry, model.materials ); + + } + + } + + } + + // textures + + // count how many textures will be loaded asynchronously + + var textureID, textureJSON; + + for ( textureID in data.textures ) { + + textureJSON = data.textures[ textureID ]; + + if ( textureJSON.url instanceof Array ) { + + counter_textures += textureJSON.url.length; + + for( var n = 0; n < textureJSON.url.length; n ++ ) { + + scope.onLoadStart(); + + } + + } else { + + counter_textures += 1; + + scope.onLoadStart(); + + } + + } + + total_textures = counter_textures; + + for ( textureID in data.textures ) { + + textureJSON = data.textures[ textureID ]; + + if ( textureJSON.mapping !== undefined && THREE[ textureJSON.mapping ] !== undefined ) { + + textureJSON.mapping = new THREE[ textureJSON.mapping ](); + + } + + if ( textureJSON.url instanceof Array ) { + + var count = textureJSON.url.length; + var url_array = []; + + for( var i = 0; i < count; i ++ ) { + + url_array[ i ] = get_url( textureJSON.url[ i ], data.urlBaseType ); + + } + + var isCompressed = /\.dds$/i.test( url_array[ 0 ] ); + + if ( isCompressed ) { + + texture = THREE.ImageUtils.loadCompressedTextureCube( url_array, textureJSON.mapping, generateTextureCallback( count ) ); + + } else { + + texture = THREE.ImageUtils.loadTextureCube( url_array, textureJSON.mapping, generateTextureCallback( count ) ); + + } + + } else { + + var isCompressed = /\.dds$/i.test( textureJSON.url ); + var fullUrl = get_url( textureJSON.url, data.urlBaseType ); + var textureCallback = generateTextureCallback( 1 ); + + if ( isCompressed ) { + + texture = THREE.ImageUtils.loadCompressedTexture( fullUrl, textureJSON.mapping, textureCallback ); + + } else { + + texture = THREE.ImageUtils.loadTexture( fullUrl, textureJSON.mapping, textureCallback ); + + } + + if ( THREE[ textureJSON.minFilter ] !== undefined ) + texture.minFilter = THREE[ textureJSON.minFilter ]; + + if ( THREE[ textureJSON.magFilter ] !== undefined ) + texture.magFilter = THREE[ textureJSON.magFilter ]; + + if ( textureJSON.anisotropy ) texture.anisotropy = textureJSON.anisotropy; + + if ( textureJSON.repeat ) { + + texture.repeat.set( textureJSON.repeat[ 0 ], textureJSON.repeat[ 1 ] ); + + if ( textureJSON.repeat[ 0 ] !== 1 ) texture.wrapS = THREE.RepeatWrapping; + if ( textureJSON.repeat[ 1 ] !== 1 ) texture.wrapT = THREE.RepeatWrapping; + + } + + if ( textureJSON.offset ) { + + texture.offset.set( textureJSON.offset[ 0 ], textureJSON.offset[ 1 ] ); + + } + + // handle wrap after repeat so that default repeat can be overriden + + if ( textureJSON.wrap ) { + + var wrapMap = { + "repeat": THREE.RepeatWrapping, + "mirror": THREE.MirroredRepeatWrapping + } + + if ( wrapMap[ textureJSON.wrap[ 0 ] ] !== undefined ) texture.wrapS = wrapMap[ textureJSON.wrap[ 0 ] ]; + if ( wrapMap[ textureJSON.wrap[ 1 ] ] !== undefined ) texture.wrapT = wrapMap[ textureJSON.wrap[ 1 ] ]; + + } + + } + + result.textures[ textureID ] = texture; + + } + + // materials + + var matID, matJSON; + var parID; + + for ( matID in data.materials ) { + + matJSON = data.materials[ matID ]; + + for ( parID in matJSON.parameters ) { + + if ( parID === "envMap" || parID === "map" || parID === "lightMap" || parID === "bumpMap" ) { + + matJSON.parameters[ parID ] = result.textures[ matJSON.parameters[ parID ] ]; + + } else if ( parID === "shading" ) { + + matJSON.parameters[ parID ] = ( matJSON.parameters[ parID ] === "flat" ) ? THREE.FlatShading : THREE.SmoothShading; + + } else if ( parID === "side" ) { + + if ( matJSON.parameters[ parID ] == "double" ) { + + matJSON.parameters[ parID ] = THREE.DoubleSide; + + } else if ( matJSON.parameters[ parID ] == "back" ) { + + matJSON.parameters[ parID ] = THREE.BackSide; + + } else { + + matJSON.parameters[ parID ] = THREE.FrontSide; + + } + + } else if ( parID === "blending" ) { + + matJSON.parameters[ parID ] = matJSON.parameters[ parID ] in THREE ? THREE[ matJSON.parameters[ parID ] ] : THREE.NormalBlending; + + } else if ( parID === "combine" ) { + + matJSON.parameters[ parID ] = matJSON.parameters[ parID ] in THREE ? THREE[ matJSON.parameters[ parID ] ] : THREE.MultiplyOperation; + + } else if ( parID === "vertexColors" ) { + + if ( matJSON.parameters[ parID ] == "face" ) { + + matJSON.parameters[ parID ] = THREE.FaceColors; + + // default to vertex colors if "vertexColors" is anything else face colors or 0 / null / false + + } else if ( matJSON.parameters[ parID ] ) { + + matJSON.parameters[ parID ] = THREE.VertexColors; + + } + + } else if ( parID === "wrapRGB" ) { + + var v3 = matJSON.parameters[ parID ]; + matJSON.parameters[ parID ] = new THREE.Vector3( v3[ 0 ], v3[ 1 ], v3[ 2 ] ); + + } + + } + + if ( matJSON.parameters.opacity !== undefined && matJSON.parameters.opacity < 1.0 ) { + + matJSON.parameters.transparent = true; + + } + + if ( matJSON.parameters.normalMap ) { + + var shader = THREE.ShaderLib[ "normalmap" ]; + var uniforms = THREE.UniformsUtils.clone( shader.uniforms ); + + var diffuse = matJSON.parameters.color; + var specular = matJSON.parameters.specular; + var ambient = matJSON.parameters.ambient; + var shininess = matJSON.parameters.shininess; + + uniforms[ "tNormal" ].value = result.textures[ matJSON.parameters.normalMap ]; + + if ( matJSON.parameters.normalScale ) { + + uniforms[ "uNormalScale" ].value.set( matJSON.parameters.normalScale[ 0 ], matJSON.parameters.normalScale[ 1 ] ); + + } + + if ( matJSON.parameters.map ) { + + uniforms[ "tDiffuse" ].value = matJSON.parameters.map; + uniforms[ "enableDiffuse" ].value = true; + + } + + if ( matJSON.parameters.envMap ) { + + uniforms[ "tCube" ].value = matJSON.parameters.envMap; + uniforms[ "enableReflection" ].value = true; + uniforms[ "reflectivity" ].value = matJSON.parameters.reflectivity; + + } + + if ( matJSON.parameters.lightMap ) { + + uniforms[ "tAO" ].value = matJSON.parameters.lightMap; + uniforms[ "enableAO" ].value = true; + + } + + if ( matJSON.parameters.specularMap ) { + + uniforms[ "tSpecular" ].value = result.textures[ matJSON.parameters.specularMap ]; + uniforms[ "enableSpecular" ].value = true; + + } + + if ( matJSON.parameters.displacementMap ) { + + uniforms[ "tDisplacement" ].value = result.textures[ matJSON.parameters.displacementMap ]; + uniforms[ "enableDisplacement" ].value = true; + + uniforms[ "uDisplacementBias" ].value = matJSON.parameters.displacementBias; + uniforms[ "uDisplacementScale" ].value = matJSON.parameters.displacementScale; + + } + + uniforms[ "diffuse" ].value.setHex( diffuse ); + uniforms[ "specular" ].value.setHex( specular ); + uniforms[ "ambient" ].value.setHex( ambient ); + + uniforms[ "shininess" ].value = shininess; + + if ( matJSON.parameters.opacity ) { + + uniforms[ "opacity" ].value = matJSON.parameters.opacity; + + } + + var parameters = { fragmentShader: shader.fragmentShader, vertexShader: shader.vertexShader, uniforms: uniforms, lights: true, fog: true }; + + material = new THREE.ShaderMaterial( parameters ); + + } else { + + material = new THREE[ matJSON.type ]( matJSON.parameters ); + + } + + material.name = matID; + + result.materials[ matID ] = material; + + } + + // second pass through all materials to initialize MeshFaceMaterials + // that could be referring to other materials out of order + + for ( matID in data.materials ) { + + matJSON = data.materials[ matID ]; + + if ( matJSON.parameters.materials ) { + + var materialArray = []; + + for ( var i = 0; i < matJSON.parameters.materials.length; i ++ ) { + + var label = matJSON.parameters.materials[ i ]; + materialArray.push( result.materials[ label ] ); + + } + + result.materials[ matID ].materials = materialArray; + + } + + } + + // objects ( synchronous init of procedural primitives ) + + handle_objects(); + + // defaults + + if ( result.cameras && data.defaults.camera ) { + + result.currentCamera = result.cameras[ data.defaults.camera ]; + + } + + if ( result.fogs && data.defaults.fog ) { + + result.scene.fog = result.fogs[ data.defaults.fog ]; + + } + + // synchronous callback + + scope.callbackSync( result ); + + // just in case there are no async elements + + async_callback_gate(); + + } + +} + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.TextureLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.TextureLoader.prototype = { + + constructor: THREE.TextureLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var loader = new THREE.ImageLoader( scope.manager ); + loader.setCrossOrigin( this.crossOrigin ); + loader.load( url, function ( image ) { + + var texture = new THREE.Texture( image ); + texture.needsUpdate = true; + + if ( onLoad !== undefined ) { + + onLoad( texture ); + + } + + } ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + } + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Material = function () { + + this.id = THREE.MaterialIdCount ++; + this.uuid = THREE.Math.generateUUID(); + + this.name = ''; + + this.side = THREE.FrontSide; + + this.opacity = 1; + this.transparent = false; + + this.blending = THREE.NormalBlending; + + this.blendSrc = THREE.SrcAlphaFactor; + this.blendDst = THREE.OneMinusSrcAlphaFactor; + this.blendEquation = THREE.AddEquation; + + this.depthTest = true; + this.depthWrite = true; + + this.polygonOffset = false; + this.polygonOffsetFactor = 0; + this.polygonOffsetUnits = 0; + + this.alphaTest = 0; + + this.overdraw = 0; // Overdrawn pixels (typically between 0 and 1) for fixing antialiasing gaps in CanvasRenderer + + this.visible = true; + + this.needsUpdate = true; + +}; + +THREE.Material.prototype = { + + constructor: THREE.Material, + + setValues: function ( values ) { + + if ( values === undefined ) return; + + for ( var key in values ) { + + var newValue = values[ key ]; + + if ( newValue === undefined ) { + + console.warn( 'THREE.Material: \'' + key + '\' parameter is undefined.' ); + continue; + + } + + if ( key in this ) { + + var currentValue = this[ key ]; + + if ( currentValue instanceof THREE.Color ) { + + currentValue.set( newValue ); + + } else if ( currentValue instanceof THREE.Vector3 && newValue instanceof THREE.Vector3 ) { + + currentValue.copy( newValue ); + + } else if ( key == 'overdraw') { + + // ensure overdraw is backwards-compatable with legacy boolean type + this[ key ] = Number(newValue); + + } else { + + this[ key ] = newValue; + + } + + } + + } + + }, + + clone: function ( material ) { + + if ( material === undefined ) material = new THREE.Material(); + + material.name = this.name; + + material.side = this.side; + + material.opacity = this.opacity; + material.transparent = this.transparent; + + material.blending = this.blending; + + material.blendSrc = this.blendSrc; + material.blendDst = this.blendDst; + material.blendEquation = this.blendEquation; + + material.depthTest = this.depthTest; + material.depthWrite = this.depthWrite; + + material.polygonOffset = this.polygonOffset; + material.polygonOffsetFactor = this.polygonOffsetFactor; + material.polygonOffsetUnits = this.polygonOffsetUnits; + + material.alphaTest = this.alphaTest; + + material.overdraw = this.overdraw; + + material.visible = this.visible; + + return material; + + }, + + dispose: function () { + + this.dispatchEvent( { type: 'dispose' } ); + + } + +}; + +THREE.EventDispatcher.prototype.apply( THREE.Material.prototype ); + +THREE.MaterialIdCount = 0; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * opacity: , + * + * blending: THREE.NormalBlending, + * depthTest: , + * depthWrite: , + * + * linewidth: , + * linecap: "round", + * linejoin: "round", + * + * vertexColors: + * + * fog: + * } + */ + +THREE.LineBasicMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + this.color = new THREE.Color( 0xffffff ); + + this.linewidth = 1; + this.linecap = 'round'; + this.linejoin = 'round'; + + this.vertexColors = false; + + this.fog = true; + + this.setValues( parameters ); + +}; + +THREE.LineBasicMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.LineBasicMaterial.prototype.clone = function () { + + var material = new THREE.LineBasicMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.color.copy( this.color ); + + material.linewidth = this.linewidth; + material.linecap = this.linecap; + material.linejoin = this.linejoin; + + material.vertexColors = this.vertexColors; + + material.fog = this.fog; + + return material; + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * opacity: , + * + * blending: THREE.NormalBlending, + * depthTest: , + * depthWrite: , + * + * linewidth: , + * + * scale: , + * dashSize: , + * gapSize: , + * + * vertexColors: + * + * fog: + * } + */ + +THREE.LineDashedMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + this.color = new THREE.Color( 0xffffff ); + + this.linewidth = 1; + + this.scale = 1; + this.dashSize = 3; + this.gapSize = 1; + + this.vertexColors = false; + + this.fog = true; + + this.setValues( parameters ); + +}; + +THREE.LineDashedMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.LineDashedMaterial.prototype.clone = function () { + + var material = new THREE.LineDashedMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.color.copy( this.color ); + + material.linewidth = this.linewidth; + + material.scale = this.scale; + material.dashSize = this.dashSize; + material.gapSize = this.gapSize; + + material.vertexColors = this.vertexColors; + + material.fog = this.fog; + + return material; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * opacity: , + * map: new THREE.Texture( ), + * + * lightMap: new THREE.Texture( ), + * + * specularMap: new THREE.Texture( ), + * + * envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ), + * combine: THREE.Multiply, + * reflectivity: , + * refractionRatio: , + * + * shading: THREE.SmoothShading, + * blending: THREE.NormalBlending, + * depthTest: , + * depthWrite: , + * + * wireframe: , + * wireframeLinewidth: , + * + * vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors, + * + * skinning: , + * morphTargets: , + * + * fog: + * } + */ + +THREE.MeshBasicMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + this.color = new THREE.Color( 0xffffff ); // emissive + + this.map = null; + + this.lightMap = null; + + this.specularMap = null; + + this.envMap = null; + this.combine = THREE.MultiplyOperation; + this.reflectivity = 1; + this.refractionRatio = 0.98; + + this.fog = true; + + this.shading = THREE.SmoothShading; + + this.wireframe = false; + this.wireframeLinewidth = 1; + this.wireframeLinecap = 'round'; + this.wireframeLinejoin = 'round'; + + this.vertexColors = THREE.NoColors; + + this.skinning = false; + this.morphTargets = false; + + this.setValues( parameters ); + +}; + +THREE.MeshBasicMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.MeshBasicMaterial.prototype.clone = function () { + + var material = new THREE.MeshBasicMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.color.copy( this.color ); + + material.map = this.map; + + material.lightMap = this.lightMap; + + material.specularMap = this.specularMap; + + material.envMap = this.envMap; + material.combine = this.combine; + material.reflectivity = this.reflectivity; + material.refractionRatio = this.refractionRatio; + + material.fog = this.fog; + + material.shading = this.shading; + + material.wireframe = this.wireframe; + material.wireframeLinewidth = this.wireframeLinewidth; + material.wireframeLinecap = this.wireframeLinecap; + material.wireframeLinejoin = this.wireframeLinejoin; + + material.vertexColors = this.vertexColors; + + material.skinning = this.skinning; + material.morphTargets = this.morphTargets; + + return material; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * ambient: , + * emissive: , + * opacity: , + * + * map: new THREE.Texture( ), + * + * lightMap: new THREE.Texture( ), + * + * specularMap: new THREE.Texture( ), + * + * envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ), + * combine: THREE.Multiply, + * reflectivity: , + * refractionRatio: , + * + * shading: THREE.SmoothShading, + * blending: THREE.NormalBlending, + * depthTest: , + * depthWrite: , + * + * wireframe: , + * wireframeLinewidth: , + * + * vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors, + * + * skinning: , + * morphTargets: , + * morphNormals: , + * + * fog: + * } + */ + +THREE.MeshLambertMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + this.color = new THREE.Color( 0xffffff ); // diffuse + this.ambient = new THREE.Color( 0xffffff ); + this.emissive = new THREE.Color( 0x000000 ); + + this.wrapAround = false; + this.wrapRGB = new THREE.Vector3( 1, 1, 1 ); + + this.map = null; + + this.lightMap = null; + + this.specularMap = null; + + this.envMap = null; + this.combine = THREE.MultiplyOperation; + this.reflectivity = 1; + this.refractionRatio = 0.98; + + this.fog = true; + + this.shading = THREE.SmoothShading; + + this.wireframe = false; + this.wireframeLinewidth = 1; + this.wireframeLinecap = 'round'; + this.wireframeLinejoin = 'round'; + + this.vertexColors = THREE.NoColors; + + this.skinning = false; + this.morphTargets = false; + this.morphNormals = false; + + this.setValues( parameters ); + +}; + +THREE.MeshLambertMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.MeshLambertMaterial.prototype.clone = function () { + + var material = new THREE.MeshLambertMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.color.copy( this.color ); + material.ambient.copy( this.ambient ); + material.emissive.copy( this.emissive ); + + material.wrapAround = this.wrapAround; + material.wrapRGB.copy( this.wrapRGB ); + + material.map = this.map; + + material.lightMap = this.lightMap; + + material.specularMap = this.specularMap; + + material.envMap = this.envMap; + material.combine = this.combine; + material.reflectivity = this.reflectivity; + material.refractionRatio = this.refractionRatio; + + material.fog = this.fog; + + material.shading = this.shading; + + material.wireframe = this.wireframe; + material.wireframeLinewidth = this.wireframeLinewidth; + material.wireframeLinecap = this.wireframeLinecap; + material.wireframeLinejoin = this.wireframeLinejoin; + + material.vertexColors = this.vertexColors; + + material.skinning = this.skinning; + material.morphTargets = this.morphTargets; + material.morphNormals = this.morphNormals; + + return material; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * ambient: , + * emissive: , + * specular: , + * shininess: , + * opacity: , + * + * map: new THREE.Texture( ), + * + * lightMap: new THREE.Texture( ), + * + * bumpMap: new THREE.Texture( ), + * bumpScale: , + * + * normalMap: new THREE.Texture( ), + * normalScale: , + * + * specularMap: new THREE.Texture( ), + * + * envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ), + * combine: THREE.Multiply, + * reflectivity: , + * refractionRatio: , + * + * shading: THREE.SmoothShading, + * blending: THREE.NormalBlending, + * depthTest: , + * depthWrite: , + * + * wireframe: , + * wireframeLinewidth: , + * + * vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors, + * + * skinning: , + * morphTargets: , + * morphNormals: , + * + * fog: + * } + */ + +THREE.MeshPhongMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + this.color = new THREE.Color( 0xffffff ); // diffuse + this.ambient = new THREE.Color( 0xffffff ); + this.emissive = new THREE.Color( 0x000000 ); + this.specular = new THREE.Color( 0x111111 ); + this.shininess = 30; + + this.metal = false; + + this.wrapAround = false; + this.wrapRGB = new THREE.Vector3( 1, 1, 1 ); + + this.map = null; + + this.lightMap = null; + + this.bumpMap = null; + this.bumpScale = 1; + + this.normalMap = null; + this.normalScale = new THREE.Vector2( 1, 1 ); + + this.specularMap = null; + + this.envMap = null; + this.combine = THREE.MultiplyOperation; + this.reflectivity = 1; + this.refractionRatio = 0.98; + + this.fog = true; + + this.shading = THREE.SmoothShading; + + this.wireframe = false; + this.wireframeLinewidth = 1; + this.wireframeLinecap = 'round'; + this.wireframeLinejoin = 'round'; + + this.vertexColors = THREE.NoColors; + + this.skinning = false; + this.morphTargets = false; + this.morphNormals = false; + + this.setValues( parameters ); + +}; + +THREE.MeshPhongMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.MeshPhongMaterial.prototype.clone = function () { + + var material = new THREE.MeshPhongMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.color.copy( this.color ); + material.ambient.copy( this.ambient ); + material.emissive.copy( this.emissive ); + material.specular.copy( this.specular ); + material.shininess = this.shininess; + + material.metal = this.metal; + + material.wrapAround = this.wrapAround; + material.wrapRGB.copy( this.wrapRGB ); + + material.map = this.map; + + material.lightMap = this.lightMap; + + material.bumpMap = this.bumpMap; + material.bumpScale = this.bumpScale; + + material.normalMap = this.normalMap; + material.normalScale.copy( this.normalScale ); + + material.specularMap = this.specularMap; + + material.envMap = this.envMap; + material.combine = this.combine; + material.reflectivity = this.reflectivity; + material.refractionRatio = this.refractionRatio; + + material.fog = this.fog; + + material.shading = this.shading; + + material.wireframe = this.wireframe; + material.wireframeLinewidth = this.wireframeLinewidth; + material.wireframeLinecap = this.wireframeLinecap; + material.wireframeLinejoin = this.wireframeLinejoin; + + material.vertexColors = this.vertexColors; + + material.skinning = this.skinning; + material.morphTargets = this.morphTargets; + material.morphNormals = this.morphNormals; + + return material; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * opacity: , + * + * blending: THREE.NormalBlending, + * depthTest: , + * depthWrite: , + * + * wireframe: , + * wireframeLinewidth: + * } + */ + +THREE.MeshDepthMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + this.wireframe = false; + this.wireframeLinewidth = 1; + + this.setValues( parameters ); + +}; + +THREE.MeshDepthMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.MeshDepthMaterial.prototype.clone = function () { + + var material = new THREE.MeshDepthMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.wireframe = this.wireframe; + material.wireframeLinewidth = this.wireframeLinewidth; + + return material; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * + * parameters = { + * opacity: , + * + * shading: THREE.FlatShading, + * blending: THREE.NormalBlending, + * depthTest: , + * depthWrite: , + * + * wireframe: , + * wireframeLinewidth: + * } + */ + +THREE.MeshNormalMaterial = function ( parameters ) { + + THREE.Material.call( this, parameters ); + + this.shading = THREE.FlatShading; + + this.wireframe = false; + this.wireframeLinewidth = 1; + + this.morphTargets = false; + + this.setValues( parameters ); + +}; + +THREE.MeshNormalMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.MeshNormalMaterial.prototype.clone = function () { + + var material = new THREE.MeshNormalMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.shading = this.shading; + + material.wireframe = this.wireframe; + material.wireframeLinewidth = this.wireframeLinewidth; + + return material; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.MeshFaceMaterial = function ( materials ) { + + this.materials = materials instanceof Array ? materials : []; + +}; + +THREE.MeshFaceMaterial.prototype.clone = function () { + + var material = new THREE.MeshFaceMaterial(); + + for ( var i = 0; i < this.materials.length; i ++ ) { + + material.materials.push( this.materials[ i ].clone() ); + + } + + return material; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * opacity: , + * map: new THREE.Texture( ), + * + * size: , + * + * blending: THREE.NormalBlending, + * depthTest: , + * depthWrite: , + * + * vertexColors: , + * + * fog: + * } + */ + +THREE.ParticleSystemMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + this.color = new THREE.Color( 0xffffff ); + + this.map = null; + + this.size = 1; + this.sizeAttenuation = true; + + this.vertexColors = false; + + this.fog = true; + + this.setValues( parameters ); + +}; + +THREE.ParticleSystemMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.ParticleSystemMaterial.prototype.clone = function () { + + var material = new THREE.ParticleSystemMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.color.copy( this.color ); + + material.map = this.map; + + material.size = this.size; + material.sizeAttenuation = this.sizeAttenuation; + + material.vertexColors = this.vertexColors; + + material.fog = this.fog; + + return material; + +}; + +// backwards compatibility + +THREE.ParticleBasicMaterial = THREE.ParticleSystemMaterial; + +/** + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * fragmentShader: , + * vertexShader: , + * + * uniforms: { "parameter1": { type: "f", value: 1.0 }, "parameter2": { type: "i" value2: 2 } }, + * + * defines: { "label" : "value" }, + * + * shading: THREE.SmoothShading, + * blending: THREE.NormalBlending, + * depthTest: , + * depthWrite: , + * + * wireframe: , + * wireframeLinewidth: , + * + * lights: , + * + * vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors, + * + * skinning: , + * morphTargets: , + * morphNormals: , + * + * fog: + * } + */ + +THREE.ShaderMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + this.fragmentShader = "void main() {}"; + this.vertexShader = "void main() {}"; + this.uniforms = {}; + this.defines = {}; + this.attributes = null; + + this.shading = THREE.SmoothShading; + + this.linewidth = 1; + + this.wireframe = false; + this.wireframeLinewidth = 1; + + this.fog = false; // set to use scene fog + + this.lights = false; // set to use scene lights + + this.vertexColors = THREE.NoColors; // set to use "color" attribute stream + + this.skinning = false; // set to use skinning attribute streams + + this.morphTargets = false; // set to use morph targets + this.morphNormals = false; // set to use morph normals + + // When rendered geometry doesn't include these attributes but the material does, + // use these default values in WebGL. This avoids errors when buffer data is missing. + this.defaultAttributeValues = { + "color" : [ 1, 1, 1], + "uv" : [ 0, 0 ], + "uv2" : [ 0, 0 ] + }; + + // By default, bind position to attribute index 0. In WebGL, attribute 0 + // should always be used to avoid potentially expensive emulation. + this.index0AttributeName = "position"; + + this.setValues( parameters ); + +}; + +THREE.ShaderMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.ShaderMaterial.prototype.clone = function () { + + var material = new THREE.ShaderMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.fragmentShader = this.fragmentShader; + material.vertexShader = this.vertexShader; + + material.uniforms = THREE.UniformsUtils.clone( this.uniforms ); + + material.attributes = this.attributes; + material.defines = this.defines; + + material.shading = this.shading; + + material.wireframe = this.wireframe; + material.wireframeLinewidth = this.wireframeLinewidth; + + material.fog = this.fog; + + material.lights = this.lights; + + material.vertexColors = this.vertexColors; + + material.skinning = this.skinning; + + material.morphTargets = this.morphTargets; + material.morphNormals = this.morphNormals; + + return material; + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * opacity: , + * map: new THREE.Texture( ), + * + * blending: THREE.NormalBlending, + * depthTest: , + * depthWrite: , + * + * uvOffset: new THREE.Vector2(), + * uvScale: new THREE.Vector2(), + * + * fog: + * } + */ + +THREE.SpriteMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + // defaults + + this.color = new THREE.Color( 0xffffff ); + this.map = null; + + this.rotation = 0; + + this.fog = false; + + // set parameters + + this.setValues( parameters ); + +}; + +THREE.SpriteMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.SpriteMaterial.prototype.clone = function () { + + var material = new THREE.SpriteMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.color.copy( this.color ); + material.map = this.map; + + material.rotation = this.rotation; + + material.fog = this.fog; + + return material; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * + * parameters = { + * color: , + * program: , + * opacity: , + * blending: THREE.NormalBlending + * } + */ + +THREE.SpriteCanvasMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + this.color = new THREE.Color( 0xffffff ); + this.program = function ( context, color ) {}; + + this.setValues( parameters ); + +}; + +THREE.SpriteCanvasMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.SpriteCanvasMaterial.prototype.clone = function () { + + var material = new THREE.SpriteCanvasMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.color.copy( this.color ); + material.program = this.program; + + return material; + +}; + +// backwards compatibility + +THREE.ParticleCanvasMaterial = THREE.SpriteCanvasMaterial; +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * @author szimek / https://github.com/szimek/ + */ + +THREE.Texture = function ( image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { + + this.id = THREE.TextureIdCount ++; + this.uuid = THREE.Math.generateUUID(); + + this.name = ''; + + this.image = image; + this.mipmaps = []; + + this.mapping = mapping !== undefined ? mapping : new THREE.UVMapping(); + + this.wrapS = wrapS !== undefined ? wrapS : THREE.ClampToEdgeWrapping; + this.wrapT = wrapT !== undefined ? wrapT : THREE.ClampToEdgeWrapping; + + this.magFilter = magFilter !== undefined ? magFilter : THREE.LinearFilter; + this.minFilter = minFilter !== undefined ? minFilter : THREE.LinearMipMapLinearFilter; + + this.anisotropy = anisotropy !== undefined ? anisotropy : 1; + + this.format = format !== undefined ? format : THREE.RGBAFormat; + this.type = type !== undefined ? type : THREE.UnsignedByteType; + + this.offset = new THREE.Vector2( 0, 0 ); + this.repeat = new THREE.Vector2( 1, 1 ); + + this.generateMipmaps = true; + this.premultiplyAlpha = false; + this.flipY = true; + this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml) + + this._needsUpdate = false; + this.onUpdate = null; + +}; + +THREE.Texture.prototype = { + + constructor: THREE.Texture, + + get needsUpdate () { + + return this._needsUpdate; + + }, + + set needsUpdate ( value ) { + + if ( value === true ) this.update(); + + this._needsUpdate = value; + + }, + + clone: function ( texture ) { + + if ( texture === undefined ) texture = new THREE.Texture(); + + texture.image = this.image; + texture.mipmaps = this.mipmaps.slice(0); + + texture.mapping = this.mapping; + + texture.wrapS = this.wrapS; + texture.wrapT = this.wrapT; + + texture.magFilter = this.magFilter; + texture.minFilter = this.minFilter; + + texture.anisotropy = this.anisotropy; + + texture.format = this.format; + texture.type = this.type; + + texture.offset.copy( this.offset ); + texture.repeat.copy( this.repeat ); + + texture.generateMipmaps = this.generateMipmaps; + texture.premultiplyAlpha = this.premultiplyAlpha; + texture.flipY = this.flipY; + texture.unpackAlignment = this.unpackAlignment; + + return texture; + + }, + + update: function () { + + this.dispatchEvent( { type: 'update' } ); + + }, + + dispose: function () { + + this.dispatchEvent( { type: 'dispose' } ); + + } + +}; + +THREE.EventDispatcher.prototype.apply( THREE.Texture.prototype ); + +THREE.TextureIdCount = 0; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.CompressedTexture = function ( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy ) { + + THREE.Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + + this.image = { width: width, height: height }; + this.mipmaps = mipmaps; + + this.generateMipmaps = false; // WebGL currently can't generate mipmaps for compressed textures, they must be embedded in DDS file + +}; + +THREE.CompressedTexture.prototype = Object.create( THREE.Texture.prototype ); + +THREE.CompressedTexture.prototype.clone = function () { + + var texture = new THREE.CompressedTexture(); + + THREE.Texture.prototype.clone.call( this, texture ); + + return texture; + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.DataTexture = function ( data, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy ) { + + THREE.Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + + this.image = { data: data, width: width, height: height }; + +}; + +THREE.DataTexture.prototype = Object.create( THREE.Texture.prototype ); + +THREE.DataTexture.prototype.clone = function () { + + var texture = new THREE.DataTexture(); + + THREE.Texture.prototype.clone.call( this, texture ); + + return texture; + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.ParticleSystem = function ( geometry, material ) { + + THREE.Object3D.call( this ); + + this.geometry = geometry !== undefined ? geometry : new THREE.Geometry(); + this.material = material !== undefined ? material : new THREE.ParticleSystemMaterial( { color: Math.random() * 0xffffff } ); + + this.sortParticles = false; + this.frustumCulled = false; + +}; + +THREE.ParticleSystem.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.ParticleSystem.prototype.clone = function ( object ) { + + if ( object === undefined ) object = new THREE.ParticleSystem( this.geometry, this.material ); + + object.sortParticles = this.sortParticles; + + THREE.Object3D.prototype.clone.call( this, object ); + + return object; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Line = function ( geometry, material, type ) { + + THREE.Object3D.call( this ); + + this.geometry = geometry !== undefined ? geometry : new THREE.Geometry(); + this.material = material !== undefined ? material : new THREE.LineBasicMaterial( { color: Math.random() * 0xffffff } ); + + this.type = ( type !== undefined ) ? type : THREE.LineStrip; + +}; + +THREE.LineStrip = 0; +THREE.LinePieces = 1; + +THREE.Line.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.Line.prototype.clone = function ( object ) { + + if ( object === undefined ) object = new THREE.Line( this.geometry, this.material, this.type ); + + THREE.Object3D.prototype.clone.call( this, object ); + + return object; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * @author mikael emtinger / http://gomo.se/ + * @author jonobr1 / http://jonobr1.com/ + */ + +THREE.Mesh = function ( geometry, material ) { + + THREE.Object3D.call( this ); + + this.geometry = geometry !== undefined ? geometry : new THREE.Geometry(); + this.material = material !== undefined ? material : new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff } ); + + this.updateMorphTargets(); + +}; + +THREE.Mesh.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.Mesh.prototype.updateMorphTargets = function () { + + if ( this.geometry.morphTargets !== undefined && this.geometry.morphTargets.length > 0 ) { + + this.morphTargetBase = -1; + this.morphTargetForcedOrder = []; + this.morphTargetInfluences = []; + this.morphTargetDictionary = {}; + + for ( var m = 0, ml = this.geometry.morphTargets.length; m < ml; m ++ ) { + + this.morphTargetInfluences.push( 0 ); + this.morphTargetDictionary[ this.geometry.morphTargets[ m ].name ] = m; + + } + + } + +}; + +THREE.Mesh.prototype.getMorphTargetIndexByName = function ( name ) { + + if ( this.morphTargetDictionary[ name ] !== undefined ) { + + return this.morphTargetDictionary[ name ]; + + } + + console.log( "THREE.Mesh.getMorphTargetIndexByName: morph target " + name + " does not exist. Returning 0." ); + + return 0; + +}; + +THREE.Mesh.prototype.clone = function ( object ) { + + if ( object === undefined ) object = new THREE.Mesh( this.geometry, this.material ); + + THREE.Object3D.prototype.clone.call( this, object ); + + return object; + +}; + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Bone = function( belongsToSkin ) { + + THREE.Object3D.call( this ); + + this.skin = belongsToSkin; + this.skinMatrix = new THREE.Matrix4(); + +}; + +THREE.Bone.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.Bone.prototype.update = function ( parentSkinMatrix, forceUpdate ) { + + // update local + + if ( this.matrixAutoUpdate ) { + + forceUpdate |= this.updateMatrix(); + + } + + // update skin matrix + + if ( forceUpdate || this.matrixWorldNeedsUpdate ) { + + if( parentSkinMatrix ) { + + this.skinMatrix.multiplyMatrices( parentSkinMatrix, this.matrix ); + + } else { + + this.skinMatrix.copy( this.matrix ); + + } + + this.matrixWorldNeedsUpdate = false; + forceUpdate = true; + + } + + // update children + + var child, i, l = this.children.length; + + for ( i = 0; i < l; i ++ ) { + + this.children[ i ].update( this.skinMatrix, forceUpdate ); + + } + +}; + + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.SkinnedMesh = function ( geometry, material, useVertexTexture ) { + + THREE.Mesh.call( this, geometry, material ); + + // + + this.useVertexTexture = useVertexTexture !== undefined ? useVertexTexture : true; + + // init bones + + this.identityMatrix = new THREE.Matrix4(); + + this.bones = []; + this.boneMatrices = []; + + var b, bone, gbone, p, q, s; + + if ( this.geometry && this.geometry.bones !== undefined ) { + + for ( b = 0; b < this.geometry.bones.length; b ++ ) { + + gbone = this.geometry.bones[ b ]; + + p = gbone.pos; + q = gbone.rotq; + s = gbone.scl; + + bone = this.addBone(); + + bone.name = gbone.name; + bone.position.set( p[0], p[1], p[2] ); + bone.quaternion.set( q[0], q[1], q[2], q[3] ); + + if ( s !== undefined ) { + + bone.scale.set( s[0], s[1], s[2] ); + + } else { + + bone.scale.set( 1, 1, 1 ); + + } + + } + + for ( b = 0; b < this.bones.length; b ++ ) { + + gbone = this.geometry.bones[ b ]; + bone = this.bones[ b ]; + + if ( gbone.parent === -1 ) { + + this.add( bone ); + + } else { + + this.bones[ gbone.parent ].add( bone ); + + } + + } + + // + + var nBones = this.bones.length; + + if ( this.useVertexTexture ) { + + // layout (1 matrix = 4 pixels) + // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4) + // with 8x8 pixel texture max 16 bones (8 * 8 / 4) + // 16x16 pixel texture max 64 bones (16 * 16 / 4) + // 32x32 pixel texture max 256 bones (32 * 32 / 4) + // 64x64 pixel texture max 1024 bones (64 * 64 / 4) + + var size; + + if ( nBones > 256 ) + size = 64; + else if ( nBones > 64 ) + size = 32; + else if ( nBones > 16 ) + size = 16; + else + size = 8; + + this.boneTextureWidth = size; + this.boneTextureHeight = size; + + this.boneMatrices = new Float32Array( this.boneTextureWidth * this.boneTextureHeight * 4 ); // 4 floats per RGBA pixel + this.boneTexture = new THREE.DataTexture( this.boneMatrices, this.boneTextureWidth, this.boneTextureHeight, THREE.RGBAFormat, THREE.FloatType ); + this.boneTexture.minFilter = THREE.NearestFilter; + this.boneTexture.magFilter = THREE.NearestFilter; + this.boneTexture.generateMipmaps = false; + this.boneTexture.flipY = false; + + } else { + + this.boneMatrices = new Float32Array( 16 * nBones ); + + } + + this.pose(); + + } + +}; + +THREE.SkinnedMesh.prototype = Object.create( THREE.Mesh.prototype ); + +THREE.SkinnedMesh.prototype.addBone = function( bone ) { + + if ( bone === undefined ) { + + bone = new THREE.Bone( this ); + + } + + this.bones.push( bone ); + + return bone; + +}; + +THREE.SkinnedMesh.prototype.updateMatrixWorld = function () { + + var offsetMatrix = new THREE.Matrix4(); + + return function ( force ) { + + this.matrixAutoUpdate && this.updateMatrix(); + + // update matrixWorld + + if ( this.matrixWorldNeedsUpdate || force ) { + + if ( this.parent ) { + + this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); + + } else { + + this.matrixWorld.copy( this.matrix ); + + } + + this.matrixWorldNeedsUpdate = false; + + force = true; + + } + + // update children + + for ( var i = 0, l = this.children.length; i < l; i ++ ) { + + var child = this.children[ i ]; + + if ( child instanceof THREE.Bone ) { + + child.update( this.identityMatrix, false ); + + } else { + + child.updateMatrixWorld( true ); + + } + + } + + // make a snapshot of the bones' rest position + + if ( this.boneInverses == undefined ) { + + this.boneInverses = []; + + for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) { + + var inverse = new THREE.Matrix4(); + + inverse.getInverse( this.bones[ b ].skinMatrix ); + + this.boneInverses.push( inverse ); + + } + + } + + // flatten bone matrices to array + + for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) { + + // compute the offset between the current and the original transform; + + // TODO: we could get rid of this multiplication step if the skinMatrix + // was already representing the offset; however, this requires some + // major changes to the animation system + + offsetMatrix.multiplyMatrices( this.bones[ b ].skinMatrix, this.boneInverses[ b ] ); + offsetMatrix.flattenToArrayOffset( this.boneMatrices, b * 16 ); + + } + + if ( this.useVertexTexture ) { + + this.boneTexture.needsUpdate = true; + + } + + }; + +}(); + +THREE.SkinnedMesh.prototype.pose = function () { + + this.updateMatrixWorld( true ); + + this.normalizeSkinWeights(); + +}; + +THREE.SkinnedMesh.prototype.normalizeSkinWeights = function () { + + if ( this.geometry instanceof THREE.Geometry ) { + + for ( var i = 0; i < this.geometry.skinIndices.length; i ++ ) { + + var sw = this.geometry.skinWeights[ i ]; + + var scale = 1.0 / sw.lengthManhattan(); + + if ( scale !== Infinity ) { + + sw.multiplyScalar( scale ); + + } else { + + sw.set( 1 ); // this will be normalized by the shader anyway + + } + + } + + } else { + + // skinning weights assumed to be normalized for THREE.BufferGeometry + + } + +}; + +THREE.SkinnedMesh.prototype.clone = function ( object ) { + + if ( object === undefined ) { + + object = new THREE.SkinnedMesh( this.geometry, this.material, this.useVertexTexture ); + + } + + THREE.Mesh.prototype.clone.call( this, object ); + + return object; + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.MorphAnimMesh = function ( geometry, material ) { + + THREE.Mesh.call( this, geometry, material ); + + // API + + this.duration = 1000; // milliseconds + this.mirroredLoop = false; + this.time = 0; + + // internals + + this.lastKeyframe = 0; + this.currentKeyframe = 0; + + this.direction = 1; + this.directionBackwards = false; + + this.setFrameRange( 0, this.geometry.morphTargets.length - 1 ); + +}; + +THREE.MorphAnimMesh.prototype = Object.create( THREE.Mesh.prototype ); + +THREE.MorphAnimMesh.prototype.setFrameRange = function ( start, end ) { + + this.startKeyframe = start; + this.endKeyframe = end; + + this.length = this.endKeyframe - this.startKeyframe + 1; + +}; + +THREE.MorphAnimMesh.prototype.setDirectionForward = function () { + + this.direction = 1; + this.directionBackwards = false; + +}; + +THREE.MorphAnimMesh.prototype.setDirectionBackward = function () { + + this.direction = -1; + this.directionBackwards = true; + +}; + +THREE.MorphAnimMesh.prototype.parseAnimations = function () { + + var geometry = this.geometry; + + if ( ! geometry.animations ) geometry.animations = {}; + + var firstAnimation, animations = geometry.animations; + + var pattern = /([a-z]+)(\d+)/; + + for ( var i = 0, il = geometry.morphTargets.length; i < il; i ++ ) { + + var morph = geometry.morphTargets[ i ]; + var parts = morph.name.match( pattern ); + + if ( parts && parts.length > 1 ) { + + var label = parts[ 1 ]; + var num = parts[ 2 ]; + + if ( ! animations[ label ] ) animations[ label ] = { start: Infinity, end: -Infinity }; + + var animation = animations[ label ]; + + if ( i < animation.start ) animation.start = i; + if ( i > animation.end ) animation.end = i; + + if ( ! firstAnimation ) firstAnimation = label; + + } + + } + + geometry.firstAnimation = firstAnimation; + +}; + +THREE.MorphAnimMesh.prototype.setAnimationLabel = function ( label, start, end ) { + + if ( ! this.geometry.animations ) this.geometry.animations = {}; + + this.geometry.animations[ label ] = { start: start, end: end }; + +}; + +THREE.MorphAnimMesh.prototype.playAnimation = function ( label, fps ) { + + var animation = this.geometry.animations[ label ]; + + if ( animation ) { + + this.setFrameRange( animation.start, animation.end ); + this.duration = 1000 * ( ( animation.end - animation.start ) / fps ); + this.time = 0; + + } else { + + console.warn( "animation[" + label + "] undefined" ); + + } + +}; + +THREE.MorphAnimMesh.prototype.updateAnimation = function ( delta ) { + + var frameTime = this.duration / this.length; + + this.time += this.direction * delta; + + if ( this.mirroredLoop ) { + + if ( this.time > this.duration || this.time < 0 ) { + + this.direction *= -1; + + if ( this.time > this.duration ) { + + this.time = this.duration; + this.directionBackwards = true; + + } + + if ( this.time < 0 ) { + + this.time = 0; + this.directionBackwards = false; + + } + + } + + } else { + + this.time = this.time % this.duration; + + if ( this.time < 0 ) this.time += this.duration; + + } + + var keyframe = this.startKeyframe + THREE.Math.clamp( Math.floor( this.time / frameTime ), 0, this.length - 1 ); + + if ( keyframe !== this.currentKeyframe ) { + + this.morphTargetInfluences[ this.lastKeyframe ] = 0; + this.morphTargetInfluences[ this.currentKeyframe ] = 1; + + this.morphTargetInfluences[ keyframe ] = 0; + + this.lastKeyframe = this.currentKeyframe; + this.currentKeyframe = keyframe; + + } + + var mix = ( this.time % frameTime ) / frameTime; + + if ( this.directionBackwards ) { + + mix = 1 - mix; + + } + + this.morphTargetInfluences[ this.currentKeyframe ] = mix; + this.morphTargetInfluences[ this.lastKeyframe ] = 1 - mix; + +}; + +THREE.MorphAnimMesh.prototype.clone = function ( object ) { + + if ( object === undefined ) object = new THREE.MorphAnimMesh( this.geometry, this.material ); + + object.duration = this.duration; + object.mirroredLoop = this.mirroredLoop; + object.time = this.time; + + object.lastKeyframe = this.lastKeyframe; + object.currentKeyframe = this.currentKeyframe; + + object.direction = this.direction; + object.directionBackwards = this.directionBackwards; + + THREE.Mesh.prototype.clone.call( this, object ); + + return object; + +}; + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.LOD = function () { + + THREE.Object3D.call( this ); + + this.objects = []; + +}; + + +THREE.LOD.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.LOD.prototype.addLevel = function ( object, distance ) { + + if ( distance === undefined ) distance = 0; + + distance = Math.abs( distance ); + + for ( var l = 0; l < this.objects.length; l ++ ) { + + if ( distance < this.objects[ l ].distance ) { + + break; + + } + + } + + this.objects.splice( l, 0, { distance: distance, object: object } ); + this.add( object ); + +}; + +THREE.LOD.prototype.getObjectForDistance = function ( distance ) { + + for ( var i = 1, l = this.objects.length; i < l; i ++ ) { + + if ( distance < this.objects[ i ].distance ) { + + break; + + } + + } + + return this.objects[ i - 1 ].object; + +}; + +THREE.LOD.prototype.update = function () { + + var v1 = new THREE.Vector3(); + var v2 = new THREE.Vector3(); + + return function ( camera ) { + + if ( this.objects.length > 1 ) { + + v1.setFromMatrixPosition( camera.matrixWorld ); + v2.setFromMatrixPosition( this.matrixWorld ); + + var distance = v1.distanceTo( v2 ); + + this.objects[ 0 ].object.visible = true; + + for ( var i = 1, l = this.objects.length; i < l; i ++ ) { + + if ( distance >= this.objects[ i ].distance ) { + + this.objects[ i - 1 ].object.visible = false; + this.objects[ i ].object.visible = true; + + } else { + + break; + + } + + } + + for( ; i < l; i ++ ) { + + this.objects[ i ].object.visible = false; + + } + + } + + }; + +}(); + +THREE.LOD.prototype.clone = function ( object ) { + + if ( object === undefined ) object = new THREE.LOD(); + + THREE.Object3D.prototype.clone.call( this, object ); + + for ( var i = 0, l = this.objects.length; i < l; i ++ ) { + var x = this.objects[i].object.clone(); + x.visible = i === 0; + object.addLevel( x, this.objects[i].distance ); + } + + return object; + +}; + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Sprite = ( function () { + + var geometry = new THREE.Geometry2( 3 ); + geometry.vertices.set( [ - 0.5, - 0.5, 0, 0.5, - 0.5, 0, 0.5, 0.5, 0 ] ); + + return function ( material ) { + + THREE.Object3D.call( this ); + + this.geometry = geometry; + this.material = ( material !== undefined ) ? material : new THREE.SpriteMaterial(); + + }; + +} )(); + +THREE.Sprite.prototype = Object.create( THREE.Object3D.prototype ); + +/* + * Custom update matrix + */ + +THREE.Sprite.prototype.updateMatrix = function () { + + this.matrix.compose( this.position, this.quaternion, this.scale ); + + this.matrixWorldNeedsUpdate = true; + +}; + +THREE.Sprite.prototype.clone = function ( object ) { + + if ( object === undefined ) object = new THREE.Sprite( this.material ); + + THREE.Object3D.prototype.clone.call( this, object ); + + return object; + +}; + +// Backwards compatibility + +THREE.Particle = THREE.Sprite; +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Scene = function () { + + THREE.Object3D.call( this ); + + this.fog = null; + this.overrideMaterial = null; + + this.autoUpdate = true; // checked by the renderer + this.matrixAutoUpdate = false; + + this.__lights = []; + + this.__objectsAdded = []; + this.__objectsRemoved = []; + +}; + +THREE.Scene.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.Scene.prototype.__addObject = function ( object ) { + + if ( object instanceof THREE.Light ) { + + if ( this.__lights.indexOf( object ) === - 1 ) { + + this.__lights.push( object ); + + } + + if ( object.target && object.target.parent === undefined ) { + + this.add( object.target ); + + } + + } else if ( !( object instanceof THREE.Camera || object instanceof THREE.Bone ) ) { + + this.__objectsAdded.push( object ); + + // check if previously removed + + var i = this.__objectsRemoved.indexOf( object ); + + if ( i !== -1 ) { + + this.__objectsRemoved.splice( i, 1 ); + + } + + } + + this.dispatchEvent( { type: 'objectAdded', object: object } ); + object.dispatchEvent( { type: 'addedToScene', scene: this } ); + + for ( var c = 0; c < object.children.length; c ++ ) { + + this.__addObject( object.children[ c ] ); + + } + +}; + +THREE.Scene.prototype.__removeObject = function ( object ) { + + if ( object instanceof THREE.Light ) { + + var i = this.__lights.indexOf( object ); + + if ( i !== -1 ) { + + this.__lights.splice( i, 1 ); + + } + + if ( object.shadowCascadeArray ) { + + for ( var x = 0; x < object.shadowCascadeArray.length; x ++ ) { + + this.__removeObject( object.shadowCascadeArray[ x ] ); + + } + + } + + } else if ( !( object instanceof THREE.Camera ) ) { + + this.__objectsRemoved.push( object ); + + // check if previously added + + var i = this.__objectsAdded.indexOf( object ); + + if ( i !== -1 ) { + + this.__objectsAdded.splice( i, 1 ); + + } + + } + + this.dispatchEvent( { type: 'objectRemoved', object: object } ); + object.dispatchEvent( { type: 'removedFromScene', scene: this } ); + + for ( var c = 0; c < object.children.length; c ++ ) { + + this.__removeObject( object.children[ c ] ); + + } + +}; + +THREE.Scene.prototype.clone = function ( object ) { + + if ( object === undefined ) object = new THREE.Scene(); + + THREE.Object3D.prototype.clone.call(this, object); + + if ( this.fog !== null ) object.fog = this.fog.clone(); + if ( this.overrideMaterial !== null ) object.overrideMaterial = this.overrideMaterial.clone(); + + object.autoUpdate = this.autoUpdate; + object.matrixAutoUpdate = this.matrixAutoUpdate; + + return object; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Fog = function ( color, near, far ) { + + this.name = ''; + + this.color = new THREE.Color( color ); + + this.near = ( near !== undefined ) ? near : 1; + this.far = ( far !== undefined ) ? far : 1000; + +}; + +THREE.Fog.prototype.clone = function () { + + return new THREE.Fog( this.color.getHex(), this.near, this.far ); + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.FogExp2 = function ( color, density ) { + + this.name = ''; + + this.color = new THREE.Color( color ); + this.density = ( density !== undefined ) ? density : 0.00025; + +}; + +THREE.FogExp2.prototype.clone = function () { + + return new THREE.FogExp2( this.color.getHex(), this.density ); + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.CanvasRenderer = function ( parameters ) { + + console.log( 'THREE.CanvasRenderer', THREE.REVISION ); + + var smoothstep = THREE.Math.smoothstep; + + parameters = parameters || {}; + + var _this = this, + _renderData, _elements, _lights, + _projector = new THREE.Projector(), + + _canvas = parameters.canvas !== undefined + ? parameters.canvas + : document.createElement( 'canvas' ), + + _canvasWidth = _canvas.width, + _canvasHeight = _canvas.height, + _canvasWidthHalf = Math.floor( _canvasWidth / 2 ), + _canvasHeightHalf = Math.floor( _canvasHeight / 2 ), + + _context = _canvas.getContext( '2d', { + alpha: parameters.alpha === true + } ), + + _clearColor = new THREE.Color( 0x000000 ), + _clearAlpha = 0, + + _contextGlobalAlpha = 1, + _contextGlobalCompositeOperation = 0, + _contextStrokeStyle = null, + _contextFillStyle = null, + _contextLineWidth = null, + _contextLineCap = null, + _contextLineJoin = null, + _contextDashSize = null, + _contextGapSize = 0, + + _camera, + + _v1, _v2, _v3, _v4, + _v5 = new THREE.RenderableVertex(), + _v6 = new THREE.RenderableVertex(), + + _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, + _v4x, _v4y, _v5x, _v5y, _v6x, _v6y, + + _color = new THREE.Color(), + _color1 = new THREE.Color(), + _color2 = new THREE.Color(), + _color3 = new THREE.Color(), + _color4 = new THREE.Color(), + + _diffuseColor = new THREE.Color(), + _emissiveColor = new THREE.Color(), + + _lightColor = new THREE.Color(), + + _patterns = {}, + + _near, _far, + + _image, _uvs, + _uv1x, _uv1y, _uv2x, _uv2y, _uv3x, _uv3y, + + _clipBox = new THREE.Box2(), + _clearBox = new THREE.Box2(), + _elemBox = new THREE.Box2(), + + _ambientLight = new THREE.Color(), + _directionalLights = new THREE.Color(), + _pointLights = new THREE.Color(), + + _vector3 = new THREE.Vector3(), // Needed for PointLight + _normal = new THREE.Vector3(), + _normalViewMatrix = new THREE.Matrix3(), + + _pixelMap, _pixelMapContext, _pixelMapImage, _pixelMapData, + _gradientMap, _gradientMapContext, _gradientMapQuality = 16; + + _pixelMap = document.createElement( 'canvas' ); + _pixelMap.width = _pixelMap.height = 2; + + _pixelMapContext = _pixelMap.getContext( '2d' ); + _pixelMapContext.fillStyle = 'rgba(0,0,0,1)'; + _pixelMapContext.fillRect( 0, 0, 2, 2 ); + + _pixelMapImage = _pixelMapContext.getImageData( 0, 0, 2, 2 ); + _pixelMapData = _pixelMapImage.data; + + _gradientMap = document.createElement( 'canvas' ); + _gradientMap.width = _gradientMap.height = _gradientMapQuality; + + _gradientMapContext = _gradientMap.getContext( '2d' ); + _gradientMapContext.translate( - _gradientMapQuality / 2, - _gradientMapQuality / 2 ); + _gradientMapContext.scale( _gradientMapQuality, _gradientMapQuality ); + + _gradientMapQuality --; // Fix UVs + + // dash+gap fallbacks for Firefox and everything else + + if ( _context.setLineDash === undefined ) { + + if ( _context.mozDash !== undefined ) { + + _context.setLineDash = function ( values ) { + + _context.mozDash = values[ 0 ] !== null ? values : null; + + } + + } else { + + _context.setLineDash = function () {} + + } + + } + + this.domElement = _canvas; + + this.devicePixelRatio = parameters.devicePixelRatio !== undefined + ? parameters.devicePixelRatio + : self.devicePixelRatio !== undefined + ? self.devicePixelRatio + : 1; + + this.autoClear = true; + this.sortObjects = true; + this.sortElements = true; + + this.info = { + + render: { + + vertices: 0, + faces: 0 + + } + + } + + // WebGLRenderer compatibility + + this.supportsVertexTextures = function () {}; + this.setFaceCulling = function () {}; + + this.setSize = function ( width, height, updateStyle ) { + + _canvasWidth = width * this.devicePixelRatio; + _canvasHeight = height * this.devicePixelRatio; + + _canvasWidthHalf = Math.floor( _canvasWidth / 2 ); + _canvasHeightHalf = Math.floor( _canvasHeight / 2 ); + + _canvas.width = _canvasWidth; + _canvas.height = _canvasHeight; + + if ( this.devicePixelRatio !== 1 && updateStyle !== false ) { + + _canvas.style.width = width + 'px'; + _canvas.style.height = height + 'px'; + + } + + _clipBox.min.set( - _canvasWidthHalf, - _canvasHeightHalf ), + _clipBox.max.set( _canvasWidthHalf, _canvasHeightHalf ); + + _clearBox.min.set( - _canvasWidthHalf, - _canvasHeightHalf ); + _clearBox.max.set( _canvasWidthHalf, _canvasHeightHalf ); + + _contextGlobalAlpha = 1; + _contextGlobalCompositeOperation = 0; + _contextStrokeStyle = null; + _contextFillStyle = null; + _contextLineWidth = null; + _contextLineCap = null; + _contextLineJoin = null; + + }; + + this.setClearColor = function ( color, alpha ) { + + _clearColor.set( color ); + _clearAlpha = alpha !== undefined ? alpha : 1; + + _clearBox.min.set( - _canvasWidthHalf, - _canvasHeightHalf ); + _clearBox.max.set( _canvasWidthHalf, _canvasHeightHalf ); + + }; + + this.setClearColorHex = function ( hex, alpha ) { + + console.warn( 'DEPRECATED: .setClearColorHex() is being removed. Use .setClearColor() instead.' ); + this.setClearColor( hex, alpha ); + + }; + + this.getMaxAnisotropy = function () { + + return 0; + + }; + + this.clear = function () { + + _context.setTransform( 1, 0, 0, - 1, _canvasWidthHalf, _canvasHeightHalf ); + + if ( _clearBox.empty() === false ) { + + _clearBox.intersect( _clipBox ); + _clearBox.expandByScalar( 2 ); + + if ( _clearAlpha < 1 ) { + + _context.clearRect( + _clearBox.min.x | 0, + _clearBox.min.y | 0, + ( _clearBox.max.x - _clearBox.min.x ) | 0, + ( _clearBox.max.y - _clearBox.min.y ) | 0 + ); + + } + + if ( _clearAlpha > 0 ) { + + setBlending( THREE.NormalBlending ); + setOpacity( 1 ); + + setFillStyle( 'rgba(' + Math.floor( _clearColor.r * 255 ) + ',' + Math.floor( _clearColor.g * 255 ) + ',' + Math.floor( _clearColor.b * 255 ) + ',' + _clearAlpha + ')' ); + + _context.fillRect( + _clearBox.min.x | 0, + _clearBox.min.y | 0, + ( _clearBox.max.x - _clearBox.min.x ) | 0, + ( _clearBox.max.y - _clearBox.min.y ) | 0 + ); + + } + + _clearBox.makeEmpty(); + + } + + }; + + // compatibility + + this.clearColor = function () {}; + this.clearDepth = function () {}; + this.clearStencil = function () {}; + + this.render = function ( scene, camera ) { + + if ( camera instanceof THREE.Camera === false ) { + + console.error( 'THREE.CanvasRenderer.render: camera is not an instance of THREE.Camera.' ); + return; + + } + + if ( this.autoClear === true ) this.clear(); + + _context.setTransform( 1, 0, 0, - 1, _canvasWidthHalf, _canvasHeightHalf ); + + _this.info.render.vertices = 0; + _this.info.render.faces = 0; + + _renderData = _projector.projectScene( scene, camera, this.sortObjects, this.sortElements ); + _elements = _renderData.elements; + _lights = _renderData.lights; + _camera = camera; + + _normalViewMatrix.getNormalMatrix( camera.matrixWorldInverse ); + + /* DEBUG + setFillStyle( 'rgba( 0, 255, 255, 0.5 )' ); + _context.fillRect( _clipBox.min.x, _clipBox.min.y, _clipBox.max.x - _clipBox.min.x, _clipBox.max.y - _clipBox.min.y ); + */ + + calculateLights(); + + for ( var e = 0, el = _elements.length; e < el; e ++ ) { + + var element = _elements[ e ]; + + var material = element.material; + + if ( material === undefined || material.visible === false ) continue; + + _elemBox.makeEmpty(); + + if ( element instanceof THREE.RenderableSprite ) { + + _v1 = element; + _v1.x *= _canvasWidthHalf; _v1.y *= _canvasHeightHalf; + + renderSprite( _v1, element, material ); + + } else if ( element instanceof THREE.RenderableLine ) { + + _v1 = element.v1; _v2 = element.v2; + + _v1.positionScreen.x *= _canvasWidthHalf; _v1.positionScreen.y *= _canvasHeightHalf; + _v2.positionScreen.x *= _canvasWidthHalf; _v2.positionScreen.y *= _canvasHeightHalf; + + _elemBox.setFromPoints( [ + _v1.positionScreen, + _v2.positionScreen + ] ); + + if ( _clipBox.isIntersectionBox( _elemBox ) === true ) { + + renderLine( _v1, _v2, element, material ); + + } + + } else if ( element instanceof THREE.RenderableFace ) { + + _v1 = element.v1; _v2 = element.v2; _v3 = element.v3; + + if ( _v1.positionScreen.z < -1 || _v1.positionScreen.z > 1 ) continue; + if ( _v2.positionScreen.z < -1 || _v2.positionScreen.z > 1 ) continue; + if ( _v3.positionScreen.z < -1 || _v3.positionScreen.z > 1 ) continue; + + _v1.positionScreen.x *= _canvasWidthHalf; _v1.positionScreen.y *= _canvasHeightHalf; + _v2.positionScreen.x *= _canvasWidthHalf; _v2.positionScreen.y *= _canvasHeightHalf; + _v3.positionScreen.x *= _canvasWidthHalf; _v3.positionScreen.y *= _canvasHeightHalf; + + if ( material.overdraw > 0 ) { + + expand( _v1.positionScreen, _v2.positionScreen, material.overdraw ); + expand( _v2.positionScreen, _v3.positionScreen, material.overdraw ); + expand( _v3.positionScreen, _v1.positionScreen, material.overdraw ); + + } + + _elemBox.setFromPoints( [ + _v1.positionScreen, + _v2.positionScreen, + _v3.positionScreen + ] ); + + if ( _clipBox.isIntersectionBox( _elemBox ) === true ) { + + renderFace3( _v1, _v2, _v3, 0, 1, 2, element, material ); + + } + + } + + /* DEBUG + setLineWidth( 1 ); + setStrokeStyle( 'rgba( 0, 255, 0, 0.5 )' ); + _context.strokeRect( _elemBox.min.x, _elemBox.min.y, _elemBox.max.x - _elemBox.min.x, _elemBox.max.y - _elemBox.min.y ); + */ + + _clearBox.union( _elemBox ); + + } + + /* DEBUG + setLineWidth( 1 ); + setStrokeStyle( 'rgba( 255, 0, 0, 0.5 )' ); + _context.strokeRect( _clearBox.min.x, _clearBox.min.y, _clearBox.max.x - _clearBox.min.x, _clearBox.max.y - _clearBox.min.y ); + */ + + _context.setTransform( 1, 0, 0, 1, 0, 0 ); + + }; + + // + + function calculateLights() { + + _ambientLight.setRGB( 0, 0, 0 ); + _directionalLights.setRGB( 0, 0, 0 ); + _pointLights.setRGB( 0, 0, 0 ); + + for ( var l = 0, ll = _lights.length; l < ll; l ++ ) { + + var light = _lights[ l ]; + var lightColor = light.color; + + if ( light instanceof THREE.AmbientLight ) { + + _ambientLight.add( lightColor ); + + } else if ( light instanceof THREE.DirectionalLight ) { + + // for sprites + + _directionalLights.add( lightColor ); + + } else if ( light instanceof THREE.PointLight ) { + + // for sprites + + _pointLights.add( lightColor ); + + } + + } + + } + + function calculateLight( position, normal, color ) { + + for ( var l = 0, ll = _lights.length; l < ll; l ++ ) { + + var light = _lights[ l ]; + + _lightColor.copy( light.color ); + + if ( light instanceof THREE.DirectionalLight ) { + + var lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld ).normalize(); + + var amount = normal.dot( lightPosition ); + + if ( amount <= 0 ) continue; + + amount *= light.intensity; + + color.add( _lightColor.multiplyScalar( amount ) ); + + } else if ( light instanceof THREE.PointLight ) { + + var lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld ); + + var amount = normal.dot( _vector3.subVectors( lightPosition, position ).normalize() ); + + if ( amount <= 0 ) continue; + + amount *= light.distance == 0 ? 1 : 1 - Math.min( position.distanceTo( lightPosition ) / light.distance, 1 ); + + if ( amount == 0 ) continue; + + amount *= light.intensity; + + color.add( _lightColor.multiplyScalar( amount ) ); + + } + + } + + } + + function renderSprite( v1, element, material ) { + + setOpacity( material.opacity ); + setBlending( material.blending ); + + var scaleX = element.scale.x * _canvasWidthHalf; + var scaleY = element.scale.y * _canvasHeightHalf; + + var dist = 0.5 * Math.sqrt( scaleX * scaleX + scaleY * scaleY ); // allow for rotated sprite + _elemBox.min.set( v1.x - dist, v1.y - dist ); + _elemBox.max.set( v1.x + dist, v1.y + dist ); + + if ( material instanceof THREE.SpriteMaterial || + material instanceof THREE.ParticleSystemMaterial ) { // Backwards compatibility + + var texture = material.map; + + if ( texture !== null ) { + + if ( texture.hasEventListener( 'update', onTextureUpdate ) === false ) { + + if ( texture.image !== undefined && texture.image.width > 0 ) { + + textureToPattern( texture ); + + } + + texture.addEventListener( 'update', onTextureUpdate ); + + } + + var pattern = _patterns[ texture.id ]; + + if ( pattern !== undefined ) { + + setFillStyle( pattern ); + + } else { + + setFillStyle( 'rgba( 0, 0, 0, 1 )' ); + + } + + // + + var bitmap = texture.image; + + var ox = bitmap.width * texture.offset.x; + var oy = bitmap.height * texture.offset.y; + + var sx = bitmap.width * texture.repeat.x; + var sy = bitmap.height * texture.repeat.y; + + var cx = scaleX / sx; + var cy = scaleY / sy; + + _context.save(); + _context.translate( v1.x, v1.y ); + if ( material.rotation !== 0 ) _context.rotate( material.rotation ); + _context.translate( - scaleX / 2, - scaleY / 2 ); + _context.scale( cx, cy ); + _context.translate( - ox, - oy ); + _context.fillRect( ox, oy, sx, sy ); + _context.restore(); + + } else { // no texture + + setFillStyle( material.color.getStyle() ); + + _context.save(); + _context.translate( v1.x, v1.y ); + if ( material.rotation !== 0 ) _context.rotate( material.rotation ); + _context.scale( scaleX, - scaleY ); + _context.fillRect( - 0.5, - 0.5, 1, 1 ); + _context.restore(); + + } + + } else if ( material instanceof THREE.SpriteCanvasMaterial ) { + + setStrokeStyle( material.color.getStyle() ); + setFillStyle( material.color.getStyle() ); + + _context.save(); + _context.translate( v1.x, v1.y ); + if ( material.rotation !== 0 ) _context.rotate( material.rotation ); + _context.scale( scaleX, scaleY ); + + material.program( _context ); + + _context.restore(); + + } + + /* DEBUG + setStrokeStyle( 'rgb(255,255,0)' ); + _context.beginPath(); + _context.moveTo( v1.x - 10, v1.y ); + _context.lineTo( v1.x + 10, v1.y ); + _context.moveTo( v1.x, v1.y - 10 ); + _context.lineTo( v1.x, v1.y + 10 ); + _context.stroke(); + */ + + } + + function renderLine( v1, v2, element, material ) { + + setOpacity( material.opacity ); + setBlending( material.blending ); + + _context.beginPath(); + _context.moveTo( v1.positionScreen.x, v1.positionScreen.y ); + _context.lineTo( v2.positionScreen.x, v2.positionScreen.y ); + + if ( material instanceof THREE.LineBasicMaterial ) { + + setLineWidth( material.linewidth ); + setLineCap( material.linecap ); + setLineJoin( material.linejoin ); + + if ( material.vertexColors !== THREE.VertexColors ) { + + setStrokeStyle( material.color.getStyle() ); + + } else { + + var colorStyle1 = element.vertexColors[0].getStyle(); + var colorStyle2 = element.vertexColors[1].getStyle(); + + if ( colorStyle1 === colorStyle2 ) { + + setStrokeStyle( colorStyle1 ); + + } else { + + try { + + var grad = _context.createLinearGradient( + v1.positionScreen.x, + v1.positionScreen.y, + v2.positionScreen.x, + v2.positionScreen.y + ); + grad.addColorStop( 0, colorStyle1 ); + grad.addColorStop( 1, colorStyle2 ); + + } catch ( exception ) { + + grad = colorStyle1; + + } + + setStrokeStyle( grad ); + + } + + } + + _context.stroke(); + _elemBox.expandByScalar( material.linewidth * 2 ); + + } else if ( material instanceof THREE.LineDashedMaterial ) { + + setLineWidth( material.linewidth ); + setLineCap( material.linecap ); + setLineJoin( material.linejoin ); + setStrokeStyle( material.color.getStyle() ); + setDashAndGap( material.dashSize, material.gapSize ); + + _context.stroke(); + + _elemBox.expandByScalar( material.linewidth * 2 ); + + setDashAndGap( null, null ); + + } + + } + + function renderFace3( v1, v2, v3, uv1, uv2, uv3, element, material ) { + + _this.info.render.vertices += 3; + _this.info.render.faces ++; + + setOpacity( material.opacity ); + setBlending( material.blending ); + + _v1x = v1.positionScreen.x; _v1y = v1.positionScreen.y; + _v2x = v2.positionScreen.x; _v2y = v2.positionScreen.y; + _v3x = v3.positionScreen.x; _v3y = v3.positionScreen.y; + + drawTriangle( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y ); + + if ( ( material instanceof THREE.MeshLambertMaterial || material instanceof THREE.MeshPhongMaterial ) && material.map === null ) { + + _diffuseColor.copy( material.color ); + _emissiveColor.copy( material.emissive ); + + if ( material.vertexColors === THREE.FaceColors ) { + + _diffuseColor.multiply( element.color ); + + } + + if ( material.wireframe === false && material.shading === THREE.SmoothShading && element.vertexNormalsLength === 3 ) { + + _color1.copy( _ambientLight ); + _color2.copy( _ambientLight ); + _color3.copy( _ambientLight ); + + calculateLight( element.v1.positionWorld, element.vertexNormalsModel[ 0 ], _color1 ); + calculateLight( element.v2.positionWorld, element.vertexNormalsModel[ 1 ], _color2 ); + calculateLight( element.v3.positionWorld, element.vertexNormalsModel[ 2 ], _color3 ); + + _color1.multiply( _diffuseColor ).add( _emissiveColor ); + _color2.multiply( _diffuseColor ).add( _emissiveColor ); + _color3.multiply( _diffuseColor ).add( _emissiveColor ); + _color4.addColors( _color2, _color3 ).multiplyScalar( 0.5 ); + + _image = getGradientTexture( _color1, _color2, _color3, _color4 ); + + clipImage( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, 0, 0, 1, 0, 0, 1, _image ); + + } else { + + _color.copy( _ambientLight ); + + calculateLight( element.centroidModel, element.normalModel, _color ); + + _color.multiply( _diffuseColor ).add( _emissiveColor ); + + material.wireframe === true + ? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin ) + : fillPath( _color ); + + } + + } else if ( material instanceof THREE.MeshBasicMaterial || material instanceof THREE.MeshLambertMaterial || material instanceof THREE.MeshPhongMaterial ) { + + if ( material.map !== null ) { + + if ( material.map.mapping instanceof THREE.UVMapping ) { + + _uvs = element.uvs[ 0 ]; + patternPath( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uvs[ uv1 ].x, _uvs[ uv1 ].y, _uvs[ uv2 ].x, _uvs[ uv2 ].y, _uvs[ uv3 ].x, _uvs[ uv3 ].y, material.map ); + + } + + + } else if ( material.envMap !== null ) { + + if ( material.envMap.mapping instanceof THREE.SphericalReflectionMapping ) { + + _normal.copy( element.vertexNormalsModel[ uv1 ] ).applyMatrix3( _normalViewMatrix ); + _uv1x = 0.5 * _normal.x + 0.5; + _uv1y = 0.5 * _normal.y + 0.5; + + _normal.copy( element.vertexNormalsModel[ uv2 ] ).applyMatrix3( _normalViewMatrix ); + _uv2x = 0.5 * _normal.x + 0.5; + _uv2y = 0.5 * _normal.y + 0.5; + + _normal.copy( element.vertexNormalsModel[ uv3 ] ).applyMatrix3( _normalViewMatrix ); + _uv3x = 0.5 * _normal.x + 0.5; + _uv3y = 0.5 * _normal.y + 0.5; + + patternPath( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uv1x, _uv1y, _uv2x, _uv2y, _uv3x, _uv3y, material.envMap ); + + }/* else if ( material.envMap.mapping === THREE.SphericalRefractionMapping ) { + + + + }*/ + + + } else { + + _color.copy( material.color ); + + if ( material.vertexColors === THREE.FaceColors ) { + + _color.multiply( element.color ); + + } + + material.wireframe === true + ? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin ) + : fillPath( _color ); + + } + + } else if ( material instanceof THREE.MeshDepthMaterial ) { + + _near = _camera.near; + _far = _camera.far; + + _color1.r = _color1.g = _color1.b = 1 - smoothstep( v1.positionScreen.z * v1.positionScreen.w, _near, _far ); + _color2.r = _color2.g = _color2.b = 1 - smoothstep( v2.positionScreen.z * v2.positionScreen.w, _near, _far ); + _color3.r = _color3.g = _color3.b = 1 - smoothstep( v3.positionScreen.z * v3.positionScreen.w, _near, _far ); + _color4.addColors( _color2, _color3 ).multiplyScalar( 0.5 ); + + _image = getGradientTexture( _color1, _color2, _color3, _color4 ); + + clipImage( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, 0, 0, 1, 0, 0, 1, _image ); + + } else if ( material instanceof THREE.MeshNormalMaterial ) { + + if ( material.shading === THREE.FlatShading ) { + + _normal.copy( element.normalModel ).applyMatrix3( _normalViewMatrix ); + + _color.setRGB( _normal.x, _normal.y, _normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 ); + + material.wireframe === true + ? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin ) + : fillPath( _color ); + + } else if ( material.shading === THREE.SmoothShading ) { + + _normal.copy( element.vertexNormalsModel[ uv1 ] ).applyMatrix3( _normalViewMatrix ); + _color1.setRGB( _normal.x, _normal.y, _normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 ); + + _normal.copy( element.vertexNormalsModel[ uv2 ] ).applyMatrix3( _normalViewMatrix ); + _color2.setRGB( _normal.x, _normal.y, _normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 ); + + _normal.copy( element.vertexNormalsModel[ uv3 ] ).applyMatrix3( _normalViewMatrix ); + _color3.setRGB( _normal.x, _normal.y, _normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 ); + + _color4.addColors( _color2, _color3 ).multiplyScalar( 0.5 ); + + _image = getGradientTexture( _color1, _color2, _color3, _color4 ); + + clipImage( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, 0, 0, 1, 0, 0, 1, _image ); + + } + + } + + } + + // + + function drawTriangle( x0, y0, x1, y1, x2, y2 ) { + + _context.beginPath(); + _context.moveTo( x0, y0 ); + _context.lineTo( x1, y1 ); + _context.lineTo( x2, y2 ); + _context.closePath(); + + } + + function strokePath( color, linewidth, linecap, linejoin ) { + + setLineWidth( linewidth ); + setLineCap( linecap ); + setLineJoin( linejoin ); + setStrokeStyle( color.getStyle() ); + + _context.stroke(); + + _elemBox.expandByScalar( linewidth * 2 ); + + } + + function fillPath( color ) { + + setFillStyle( color.getStyle() ); + _context.fill(); + + } + + function onTextureUpdate ( event ) { + + textureToPattern( event.target ); + + } + + function textureToPattern( texture ) { + + var repeatX = texture.wrapS === THREE.RepeatWrapping; + var repeatY = texture.wrapT === THREE.RepeatWrapping; + + var image = texture.image; + + var canvas = document.createElement( 'canvas' ); + canvas.width = image.width; + canvas.height = image.height; + + var context = canvas.getContext( '2d' ); + context.setTransform( 1, 0, 0, - 1, 0, image.height ); + context.drawImage( image, 0, 0 ); + + _patterns[ texture.id ] = _context.createPattern( + canvas, repeatX === true && repeatY === true + ? 'repeat' + : repeatX === true && repeatY === false + ? 'repeat-x' + : repeatX === false && repeatY === true + ? 'repeat-y' + : 'no-repeat' + ); + + } + + function patternPath( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, texture ) { + + if ( texture instanceof THREE.DataTexture ) return; + + if ( texture.hasEventListener( 'update', onTextureUpdate ) === false ) { + + if ( texture.image !== undefined && texture.image.width > 0 ) { + + textureToPattern( texture ); + + } + + texture.addEventListener( 'update', onTextureUpdate ); + + } + + var pattern = _patterns[ texture.id ]; + + if ( pattern !== undefined ) { + + setFillStyle( pattern ); + + } else { + + setFillStyle( 'rgba(0,0,0,1)' ); + _context.fill(); + + return; + + } + + // http://extremelysatisfactorytotalitarianism.com/blog/?p=2120 + + var a, b, c, d, e, f, det, idet, + offsetX = texture.offset.x / texture.repeat.x, + offsetY = texture.offset.y / texture.repeat.y, + width = texture.image.width * texture.repeat.x, + height = texture.image.height * texture.repeat.y; + + u0 = ( u0 + offsetX ) * width; + v0 = ( v0 + offsetY ) * height; + + u1 = ( u1 + offsetX ) * width; + v1 = ( v1 + offsetY ) * height; + + u2 = ( u2 + offsetX ) * width; + v2 = ( v2 + offsetY ) * height; + + x1 -= x0; y1 -= y0; + x2 -= x0; y2 -= y0; + + u1 -= u0; v1 -= v0; + u2 -= u0; v2 -= v0; + + det = u1 * v2 - u2 * v1; + + if ( det === 0 ) return; + + idet = 1 / det; + + a = ( v2 * x1 - v1 * x2 ) * idet; + b = ( v2 * y1 - v1 * y2 ) * idet; + c = ( u1 * x2 - u2 * x1 ) * idet; + d = ( u1 * y2 - u2 * y1 ) * idet; + + e = x0 - a * u0 - c * v0; + f = y0 - b * u0 - d * v0; + + _context.save(); + _context.transform( a, b, c, d, e, f ); + _context.fill(); + _context.restore(); + + } + + function clipImage( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, image ) { + + // http://extremelysatisfactorytotalitarianism.com/blog/?p=2120 + + var a, b, c, d, e, f, det, idet, + width = image.width - 1, + height = image.height - 1; + + u0 *= width; v0 *= height; + u1 *= width; v1 *= height; + u2 *= width; v2 *= height; + + x1 -= x0; y1 -= y0; + x2 -= x0; y2 -= y0; + + u1 -= u0; v1 -= v0; + u2 -= u0; v2 -= v0; + + det = u1 * v2 - u2 * v1; + + idet = 1 / det; + + a = ( v2 * x1 - v1 * x2 ) * idet; + b = ( v2 * y1 - v1 * y2 ) * idet; + c = ( u1 * x2 - u2 * x1 ) * idet; + d = ( u1 * y2 - u2 * y1 ) * idet; + + e = x0 - a * u0 - c * v0; + f = y0 - b * u0 - d * v0; + + _context.save(); + _context.transform( a, b, c, d, e, f ); + _context.clip(); + _context.drawImage( image, 0, 0 ); + _context.restore(); + + } + + function getGradientTexture( color1, color2, color3, color4 ) { + + // http://mrdoob.com/blog/post/710 + + _pixelMapData[ 0 ] = ( color1.r * 255 ) | 0; + _pixelMapData[ 1 ] = ( color1.g * 255 ) | 0; + _pixelMapData[ 2 ] = ( color1.b * 255 ) | 0; + + _pixelMapData[ 4 ] = ( color2.r * 255 ) | 0; + _pixelMapData[ 5 ] = ( color2.g * 255 ) | 0; + _pixelMapData[ 6 ] = ( color2.b * 255 ) | 0; + + _pixelMapData[ 8 ] = ( color3.r * 255 ) | 0; + _pixelMapData[ 9 ] = ( color3.g * 255 ) | 0; + _pixelMapData[ 10 ] = ( color3.b * 255 ) | 0; + + _pixelMapData[ 12 ] = ( color4.r * 255 ) | 0; + _pixelMapData[ 13 ] = ( color4.g * 255 ) | 0; + _pixelMapData[ 14 ] = ( color4.b * 255 ) | 0; + + _pixelMapContext.putImageData( _pixelMapImage, 0, 0 ); + _gradientMapContext.drawImage( _pixelMap, 0, 0 ); + + return _gradientMap; + + } + + // Hide anti-alias gaps + + function expand( v1, v2, pixels ) { + + var x = v2.x - v1.x, y = v2.y - v1.y, + det = x * x + y * y, idet; + + if ( det === 0 ) return; + + idet = pixels / Math.sqrt( det ); + + x *= idet; y *= idet; + + v2.x += x; v2.y += y; + v1.x -= x; v1.y -= y; + + } + + // Context cached methods. + + function setOpacity( value ) { + + if ( _contextGlobalAlpha !== value ) { + + _context.globalAlpha = value; + _contextGlobalAlpha = value; + + } + + } + + function setBlending( value ) { + + if ( _contextGlobalCompositeOperation !== value ) { + + if ( value === THREE.NormalBlending ) { + + _context.globalCompositeOperation = 'source-over'; + + } else if ( value === THREE.AdditiveBlending ) { + + _context.globalCompositeOperation = 'lighter'; + + } else if ( value === THREE.SubtractiveBlending ) { + + _context.globalCompositeOperation = 'darker'; + + } + + _contextGlobalCompositeOperation = value; + + } + + } + + function setLineWidth( value ) { + + if ( _contextLineWidth !== value ) { + + _context.lineWidth = value; + _contextLineWidth = value; + + } + + } + + function setLineCap( value ) { + + // "butt", "round", "square" + + if ( _contextLineCap !== value ) { + + _context.lineCap = value; + _contextLineCap = value; + + } + + } + + function setLineJoin( value ) { + + // "round", "bevel", "miter" + + if ( _contextLineJoin !== value ) { + + _context.lineJoin = value; + _contextLineJoin = value; + + } + + } + + function setStrokeStyle( value ) { + + if ( _contextStrokeStyle !== value ) { + + _context.strokeStyle = value; + _contextStrokeStyle = value; + + } + + } + + function setFillStyle( value ) { + + if ( _contextFillStyle !== value ) { + + _context.fillStyle = value; + _contextFillStyle = value; + + } + + } + + function setDashAndGap( dashSizeValue, gapSizeValue ) { + + if ( _contextDashSize !== dashSizeValue || _contextGapSize !== gapSizeValue ) { + + _context.setLineDash( [ dashSizeValue, gapSizeValue ] ); + _contextDashSize = dashSizeValue; + _contextGapSize = gapSizeValue; + + } + + } + +}; + +/** + * Shader chunks for WebLG Shader library + * + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + * @author mikael emtinger / http://gomo.se/ + */ + +THREE.ShaderChunk = { + + // FOG + + fog_pars_fragment: [ + + "#ifdef USE_FOG", + + "uniform vec3 fogColor;", + + "#ifdef FOG_EXP2", + + "uniform float fogDensity;", + + "#else", + + "uniform float fogNear;", + "uniform float fogFar;", + + "#endif", + + "#endif" + + ].join("\n"), + + fog_fragment: [ + + "#ifdef USE_FOG", + + "float depth = gl_FragCoord.z / gl_FragCoord.w;", + + "#ifdef FOG_EXP2", + + "const float LOG2 = 1.442695;", + "float fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );", + "fogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );", + + "#else", + + "float fogFactor = smoothstep( fogNear, fogFar, depth );", + + "#endif", + + "gl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );", + + "#endif" + + ].join("\n"), + + // ENVIRONMENT MAP + + envmap_pars_fragment: [ + + "#ifdef USE_ENVMAP", + + "uniform float reflectivity;", + "uniform samplerCube envMap;", + "uniform float flipEnvMap;", + "uniform int combine;", + + "#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )", + + "uniform bool useRefract;", + "uniform float refractionRatio;", + + "#else", + + "varying vec3 vReflect;", + + "#endif", + + "#endif" + + ].join("\n"), + + envmap_fragment: [ + + "#ifdef USE_ENVMAP", + + "vec3 reflectVec;", + + "#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )", + + "vec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );", + + "if ( useRefract ) {", + + "reflectVec = refract( cameraToVertex, normal, refractionRatio );", + + "} else { ", + + "reflectVec = reflect( cameraToVertex, normal );", + + "}", + + "#else", + + "reflectVec = vReflect;", + + "#endif", + + "#ifdef DOUBLE_SIDED", + + "float flipNormal = ( -1.0 + 2.0 * float( gl_FrontFacing ) );", + "vec4 cubeColor = textureCube( envMap, flipNormal * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );", + + "#else", + + "vec4 cubeColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );", + + "#endif", + + "#ifdef GAMMA_INPUT", + + "cubeColor.xyz *= cubeColor.xyz;", + + "#endif", + + "if ( combine == 1 ) {", + + "gl_FragColor.xyz = mix( gl_FragColor.xyz, cubeColor.xyz, specularStrength * reflectivity );", + + "} else if ( combine == 2 ) {", + + "gl_FragColor.xyz += cubeColor.xyz * specularStrength * reflectivity;", + + "} else {", + + "gl_FragColor.xyz = mix( gl_FragColor.xyz, gl_FragColor.xyz * cubeColor.xyz, specularStrength * reflectivity );", + + "}", + + "#endif" + + ].join("\n"), + + envmap_pars_vertex: [ + + "#if defined( USE_ENVMAP ) && ! defined( USE_BUMPMAP ) && ! defined( USE_NORMALMAP )", + + "varying vec3 vReflect;", + + "uniform float refractionRatio;", + "uniform bool useRefract;", + + "#endif" + + ].join("\n"), + + worldpos_vertex : [ + + "#if defined( USE_ENVMAP ) || defined( PHONG ) || defined( LAMBERT ) || defined ( USE_SHADOWMAP )", + + "#ifdef USE_SKINNING", + + "vec4 worldPosition = modelMatrix * skinned;", + + "#endif", + + "#if defined( USE_MORPHTARGETS ) && ! defined( USE_SKINNING )", + + "vec4 worldPosition = modelMatrix * vec4( morphed, 1.0 );", + + "#endif", + + "#if ! defined( USE_MORPHTARGETS ) && ! defined( USE_SKINNING )", + + "vec4 worldPosition = modelMatrix * vec4( position, 1.0 );", + + "#endif", + + "#endif" + + ].join("\n"), + + envmap_vertex : [ + + "#if defined( USE_ENVMAP ) && ! defined( USE_BUMPMAP ) && ! defined( USE_NORMALMAP )", + + "vec3 worldNormal = mat3( modelMatrix[ 0 ].xyz, modelMatrix[ 1 ].xyz, modelMatrix[ 2 ].xyz ) * objectNormal;", + "worldNormal = normalize( worldNormal );", + + "vec3 cameraToVertex = normalize( worldPosition.xyz - cameraPosition );", + + "if ( useRefract ) {", + + "vReflect = refract( cameraToVertex, worldNormal, refractionRatio );", + + "} else {", + + "vReflect = reflect( cameraToVertex, worldNormal );", + + "}", + + "#endif" + + ].join("\n"), + + // COLOR MAP (particles) + + map_particle_pars_fragment: [ + + "#ifdef USE_MAP", + + "uniform sampler2D map;", + + "#endif" + + ].join("\n"), + + + map_particle_fragment: [ + + "#ifdef USE_MAP", + + "gl_FragColor = gl_FragColor * texture2D( map, vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y ) );", + + "#endif" + + ].join("\n"), + + // COLOR MAP (triangles) + + map_pars_vertex: [ + + "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP )", + + "varying vec2 vUv;", + "uniform vec4 offsetRepeat;", + + "#endif" + + ].join("\n"), + + map_pars_fragment: [ + + "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP )", + + "varying vec2 vUv;", + + "#endif", + + "#ifdef USE_MAP", + + "uniform sampler2D map;", + + "#endif" + + ].join("\n"), + + map_vertex: [ + + "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP )", + + "vUv = uv * offsetRepeat.zw + offsetRepeat.xy;", + + "#endif" + + ].join("\n"), + + map_fragment: [ + + "#ifdef USE_MAP", + + "vec4 texelColor = texture2D( map, vUv );", + + "#ifdef GAMMA_INPUT", + + "texelColor.xyz *= texelColor.xyz;", + + "#endif", + + "gl_FragColor = gl_FragColor * texelColor;", + + "#endif" + + ].join("\n"), + + // LIGHT MAP + + lightmap_pars_fragment: [ + + "#ifdef USE_LIGHTMAP", + + "varying vec2 vUv2;", + "uniform sampler2D lightMap;", + + "#endif" + + ].join("\n"), + + lightmap_pars_vertex: [ + + "#ifdef USE_LIGHTMAP", + + "varying vec2 vUv2;", + + "#endif" + + ].join("\n"), + + lightmap_fragment: [ + + "#ifdef USE_LIGHTMAP", + + "gl_FragColor = gl_FragColor * texture2D( lightMap, vUv2 );", + + "#endif" + + ].join("\n"), + + lightmap_vertex: [ + + "#ifdef USE_LIGHTMAP", + + "vUv2 = uv2;", + + "#endif" + + ].join("\n"), + + // BUMP MAP + + bumpmap_pars_fragment: [ + + "#ifdef USE_BUMPMAP", + + "uniform sampler2D bumpMap;", + "uniform float bumpScale;", + + // Derivative maps - bump mapping unparametrized surfaces by Morten Mikkelsen + // http://mmikkelsen3d.blogspot.sk/2011/07/derivative-maps.html + + // Evaluate the derivative of the height w.r.t. screen-space using forward differencing (listing 2) + + "vec2 dHdxy_fwd() {", + + "vec2 dSTdx = dFdx( vUv );", + "vec2 dSTdy = dFdy( vUv );", + + "float Hll = bumpScale * texture2D( bumpMap, vUv ).x;", + "float dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;", + "float dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;", + + "return vec2( dBx, dBy );", + + "}", + + "vec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {", + + "vec3 vSigmaX = dFdx( surf_pos );", + "vec3 vSigmaY = dFdy( surf_pos );", + "vec3 vN = surf_norm;", // normalized + + "vec3 R1 = cross( vSigmaY, vN );", + "vec3 R2 = cross( vN, vSigmaX );", + + "float fDet = dot( vSigmaX, R1 );", + + "vec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );", + "return normalize( abs( fDet ) * surf_norm - vGrad );", + + "}", + + "#endif" + + ].join("\n"), + + // NORMAL MAP + + normalmap_pars_fragment: [ + + "#ifdef USE_NORMALMAP", + + "uniform sampler2D normalMap;", + "uniform vec2 normalScale;", + + // Per-Pixel Tangent Space Normal Mapping + // http://hacksoflife.blogspot.ch/2009/11/per-pixel-tangent-space-normal-mapping.html + + "vec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {", + + "vec3 q0 = dFdx( eye_pos.xyz );", + "vec3 q1 = dFdy( eye_pos.xyz );", + "vec2 st0 = dFdx( vUv.st );", + "vec2 st1 = dFdy( vUv.st );", + + "vec3 S = normalize( q0 * st1.t - q1 * st0.t );", + "vec3 T = normalize( -q0 * st1.s + q1 * st0.s );", + "vec3 N = normalize( surf_norm );", + + "vec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;", + "mapN.xy = normalScale * mapN.xy;", + "mat3 tsn = mat3( S, T, N );", + "return normalize( tsn * mapN );", + + "}", + + "#endif" + + ].join("\n"), + + // SPECULAR MAP + + specularmap_pars_fragment: [ + + "#ifdef USE_SPECULARMAP", + + "uniform sampler2D specularMap;", + + "#endif" + + ].join("\n"), + + specularmap_fragment: [ + + "float specularStrength;", + + "#ifdef USE_SPECULARMAP", + + "vec4 texelSpecular = texture2D( specularMap, vUv );", + "specularStrength = texelSpecular.r;", + + "#else", + + "specularStrength = 1.0;", + + "#endif" + + ].join("\n"), + + // LIGHTS LAMBERT + + lights_lambert_pars_vertex: [ + + "uniform vec3 ambient;", + "uniform vec3 diffuse;", + "uniform vec3 emissive;", + + "uniform vec3 ambientLightColor;", + + "#if MAX_DIR_LIGHTS > 0", + + "uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];", + "uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];", + + "#endif", + + "#if MAX_HEMI_LIGHTS > 0", + + "uniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];", + "uniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];", + "uniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];", + + "#endif", + + "#if MAX_POINT_LIGHTS > 0", + + "uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];", + "uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];", + "uniform float pointLightDistance[ MAX_POINT_LIGHTS ];", + + "#endif", + + "#if MAX_SPOT_LIGHTS > 0", + + "uniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];", + "uniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];", + "uniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];", + "uniform float spotLightDistance[ MAX_SPOT_LIGHTS ];", + "uniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];", + "uniform float spotLightExponent[ MAX_SPOT_LIGHTS ];", + + "#endif", + + "#ifdef WRAP_AROUND", + + "uniform vec3 wrapRGB;", + + "#endif" + + ].join("\n"), + + lights_lambert_vertex: [ + + "vLightFront = vec3( 0.0 );", + + "#ifdef DOUBLE_SIDED", + + "vLightBack = vec3( 0.0 );", + + "#endif", + + "transformedNormal = normalize( transformedNormal );", + + "#if MAX_DIR_LIGHTS > 0", + + "for( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {", + + "vec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );", + "vec3 dirVector = normalize( lDirection.xyz );", + + "float dotProduct = dot( transformedNormal, dirVector );", + "vec3 directionalLightWeighting = vec3( max( dotProduct, 0.0 ) );", + + "#ifdef DOUBLE_SIDED", + + "vec3 directionalLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );", + + "#ifdef WRAP_AROUND", + + "vec3 directionalLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );", + + "#endif", + + "#endif", + + "#ifdef WRAP_AROUND", + + "vec3 directionalLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );", + "directionalLightWeighting = mix( directionalLightWeighting, directionalLightWeightingHalf, wrapRGB );", + + "#ifdef DOUBLE_SIDED", + + "directionalLightWeightingBack = mix( directionalLightWeightingBack, directionalLightWeightingHalfBack, wrapRGB );", + + "#endif", + + "#endif", + + "vLightFront += directionalLightColor[ i ] * directionalLightWeighting;", + + "#ifdef DOUBLE_SIDED", + + "vLightBack += directionalLightColor[ i ] * directionalLightWeightingBack;", + + "#endif", + + "}", + + "#endif", + + "#if MAX_POINT_LIGHTS > 0", + + "for( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {", + + "vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );", + "vec3 lVector = lPosition.xyz - mvPosition.xyz;", + + "float lDistance = 1.0;", + "if ( pointLightDistance[ i ] > 0.0 )", + "lDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );", + + "lVector = normalize( lVector );", + "float dotProduct = dot( transformedNormal, lVector );", + + "vec3 pointLightWeighting = vec3( max( dotProduct, 0.0 ) );", + + "#ifdef DOUBLE_SIDED", + + "vec3 pointLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );", + + "#ifdef WRAP_AROUND", + + "vec3 pointLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );", + + "#endif", + + "#endif", + + "#ifdef WRAP_AROUND", + + "vec3 pointLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );", + "pointLightWeighting = mix( pointLightWeighting, pointLightWeightingHalf, wrapRGB );", + + "#ifdef DOUBLE_SIDED", + + "pointLightWeightingBack = mix( pointLightWeightingBack, pointLightWeightingHalfBack, wrapRGB );", + + "#endif", + + "#endif", + + "vLightFront += pointLightColor[ i ] * pointLightWeighting * lDistance;", + + "#ifdef DOUBLE_SIDED", + + "vLightBack += pointLightColor[ i ] * pointLightWeightingBack * lDistance;", + + "#endif", + + "}", + + "#endif", + + "#if MAX_SPOT_LIGHTS > 0", + + "for( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {", + + "vec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );", + "vec3 lVector = lPosition.xyz - mvPosition.xyz;", + + "float spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - worldPosition.xyz ) );", + + "if ( spotEffect > spotLightAngleCos[ i ] ) {", + + "spotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );", + + "float lDistance = 1.0;", + "if ( spotLightDistance[ i ] > 0.0 )", + "lDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );", + + "lVector = normalize( lVector );", + + "float dotProduct = dot( transformedNormal, lVector );", + "vec3 spotLightWeighting = vec3( max( dotProduct, 0.0 ) );", + + "#ifdef DOUBLE_SIDED", + + "vec3 spotLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );", + + "#ifdef WRAP_AROUND", + + "vec3 spotLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );", + + "#endif", + + "#endif", + + "#ifdef WRAP_AROUND", + + "vec3 spotLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );", + "spotLightWeighting = mix( spotLightWeighting, spotLightWeightingHalf, wrapRGB );", + + "#ifdef DOUBLE_SIDED", + + "spotLightWeightingBack = mix( spotLightWeightingBack, spotLightWeightingHalfBack, wrapRGB );", + + "#endif", + + "#endif", + + "vLightFront += spotLightColor[ i ] * spotLightWeighting * lDistance * spotEffect;", + + "#ifdef DOUBLE_SIDED", + + "vLightBack += spotLightColor[ i ] * spotLightWeightingBack * lDistance * spotEffect;", + + "#endif", + + "}", + + "}", + + "#endif", + + "#if MAX_HEMI_LIGHTS > 0", + + "for( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {", + + "vec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );", + "vec3 lVector = normalize( lDirection.xyz );", + + "float dotProduct = dot( transformedNormal, lVector );", + + "float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;", + "float hemiDiffuseWeightBack = -0.5 * dotProduct + 0.5;", + + "vLightFront += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );", + + "#ifdef DOUBLE_SIDED", + + "vLightBack += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeightBack );", + + "#endif", + + "}", + + "#endif", + + "vLightFront = vLightFront * diffuse + ambient * ambientLightColor + emissive;", + + "#ifdef DOUBLE_SIDED", + + "vLightBack = vLightBack * diffuse + ambient * ambientLightColor + emissive;", + + "#endif" + + ].join("\n"), + + // LIGHTS PHONG + + lights_phong_pars_vertex: [ + + "#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )", + + "varying vec3 vWorldPosition;", + + "#endif" + + ].join("\n"), + + + lights_phong_vertex: [ + + "#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )", + + "vWorldPosition = worldPosition.xyz;", + + "#endif" + + ].join("\n"), + + lights_phong_pars_fragment: [ + + "uniform vec3 ambientLightColor;", + + "#if MAX_DIR_LIGHTS > 0", + + "uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];", + "uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];", + + "#endif", + + "#if MAX_HEMI_LIGHTS > 0", + + "uniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];", + "uniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];", + "uniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];", + + "#endif", + + "#if MAX_POINT_LIGHTS > 0", + + "uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];", + + "uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];", + "uniform float pointLightDistance[ MAX_POINT_LIGHTS ];", + + "#endif", + + "#if MAX_SPOT_LIGHTS > 0", + + "uniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];", + "uniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];", + "uniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];", + "uniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];", + "uniform float spotLightExponent[ MAX_SPOT_LIGHTS ];", + + "uniform float spotLightDistance[ MAX_SPOT_LIGHTS ];", + + "#endif", + + "#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )", + + "varying vec3 vWorldPosition;", + + "#endif", + + "#ifdef WRAP_AROUND", + + "uniform vec3 wrapRGB;", + + "#endif", + + "varying vec3 vViewPosition;", + "varying vec3 vNormal;" + + ].join("\n"), + + lights_phong_fragment: [ + + "vec3 normal = normalize( vNormal );", + "vec3 viewPosition = normalize( vViewPosition );", + + "#ifdef DOUBLE_SIDED", + + "normal = normal * ( -1.0 + 2.0 * float( gl_FrontFacing ) );", + + "#endif", + + "#ifdef USE_NORMALMAP", + + "normal = perturbNormal2Arb( -vViewPosition, normal );", + + "#elif defined( USE_BUMPMAP )", + + "normal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );", + + "#endif", + + "#if MAX_POINT_LIGHTS > 0", + + "vec3 pointDiffuse = vec3( 0.0 );", + "vec3 pointSpecular = vec3( 0.0 );", + + "for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {", + + "vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );", + "vec3 lVector = lPosition.xyz + vViewPosition.xyz;", + + "float lDistance = 1.0;", + "if ( pointLightDistance[ i ] > 0.0 )", + "lDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );", + + "lVector = normalize( lVector );", + + // diffuse + + "float dotProduct = dot( normal, lVector );", + + "#ifdef WRAP_AROUND", + + "float pointDiffuseWeightFull = max( dotProduct, 0.0 );", + "float pointDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );", + + "vec3 pointDiffuseWeight = mix( vec3 ( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );", + + "#else", + + "float pointDiffuseWeight = max( dotProduct, 0.0 );", + + "#endif", + + "pointDiffuse += diffuse * pointLightColor[ i ] * pointDiffuseWeight * lDistance;", + + // specular + + "vec3 pointHalfVector = normalize( lVector + viewPosition );", + "float pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );", + "float pointSpecularWeight = specularStrength * max( pow( pointDotNormalHalf, shininess ), 0.0 );", + + // 2.0 => 2.0001 is hack to work around ANGLE bug + + "float specularNormalization = ( shininess + 2.0001 ) / 8.0;", + + "vec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVector, pointHalfVector ), 0.0 ), 5.0 );", + "pointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * lDistance * specularNormalization;", + + "}", + + "#endif", + + "#if MAX_SPOT_LIGHTS > 0", + + "vec3 spotDiffuse = vec3( 0.0 );", + "vec3 spotSpecular = vec3( 0.0 );", + + "for ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {", + + "vec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );", + "vec3 lVector = lPosition.xyz + vViewPosition.xyz;", + + "float lDistance = 1.0;", + "if ( spotLightDistance[ i ] > 0.0 )", + "lDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );", + + "lVector = normalize( lVector );", + + "float spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - vWorldPosition ) );", + + "if ( spotEffect > spotLightAngleCos[ i ] ) {", + + "spotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );", + + // diffuse + + "float dotProduct = dot( normal, lVector );", + + "#ifdef WRAP_AROUND", + + "float spotDiffuseWeightFull = max( dotProduct, 0.0 );", + "float spotDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );", + + "vec3 spotDiffuseWeight = mix( vec3 ( spotDiffuseWeightFull ), vec3( spotDiffuseWeightHalf ), wrapRGB );", + + "#else", + + "float spotDiffuseWeight = max( dotProduct, 0.0 );", + + "#endif", + + "spotDiffuse += diffuse * spotLightColor[ i ] * spotDiffuseWeight * lDistance * spotEffect;", + + // specular + + "vec3 spotHalfVector = normalize( lVector + viewPosition );", + "float spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );", + "float spotSpecularWeight = specularStrength * max( pow( spotDotNormalHalf, shininess ), 0.0 );", + + // 2.0 => 2.0001 is hack to work around ANGLE bug + + "float specularNormalization = ( shininess + 2.0001 ) / 8.0;", + + "vec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVector, spotHalfVector ), 0.0 ), 5.0 );", + "spotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * lDistance * specularNormalization * spotEffect;", + + "}", + + "}", + + "#endif", + + "#if MAX_DIR_LIGHTS > 0", + + "vec3 dirDiffuse = vec3( 0.0 );", + "vec3 dirSpecular = vec3( 0.0 );" , + + "for( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {", + + "vec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );", + "vec3 dirVector = normalize( lDirection.xyz );", + + // diffuse + + "float dotProduct = dot( normal, dirVector );", + + "#ifdef WRAP_AROUND", + + "float dirDiffuseWeightFull = max( dotProduct, 0.0 );", + "float dirDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );", + + "vec3 dirDiffuseWeight = mix( vec3( dirDiffuseWeightFull ), vec3( dirDiffuseWeightHalf ), wrapRGB );", + + "#else", + + "float dirDiffuseWeight = max( dotProduct, 0.0 );", + + "#endif", + + "dirDiffuse += diffuse * directionalLightColor[ i ] * dirDiffuseWeight;", + + // specular + + "vec3 dirHalfVector = normalize( dirVector + viewPosition );", + "float dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );", + "float dirSpecularWeight = specularStrength * max( pow( dirDotNormalHalf, shininess ), 0.0 );", + + /* + // fresnel term from skin shader + "const float F0 = 0.128;", + + "float base = 1.0 - dot( viewPosition, dirHalfVector );", + "float exponential = pow( base, 5.0 );", + + "float fresnel = exponential + F0 * ( 1.0 - exponential );", + */ + + /* + // fresnel term from fresnel shader + "const float mFresnelBias = 0.08;", + "const float mFresnelScale = 0.3;", + "const float mFresnelPower = 5.0;", + + "float fresnel = mFresnelBias + mFresnelScale * pow( 1.0 + dot( normalize( -viewPosition ), normal ), mFresnelPower );", + */ + + // 2.0 => 2.0001 is hack to work around ANGLE bug + + "float specularNormalization = ( shininess + 2.0001 ) / 8.0;", + + //"dirSpecular += specular * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization * fresnel;", + + "vec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( dirVector, dirHalfVector ), 0.0 ), 5.0 );", + "dirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;", + + + "}", + + "#endif", + + "#if MAX_HEMI_LIGHTS > 0", + + "vec3 hemiDiffuse = vec3( 0.0 );", + "vec3 hemiSpecular = vec3( 0.0 );" , + + "for( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {", + + "vec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );", + "vec3 lVector = normalize( lDirection.xyz );", + + // diffuse + + "float dotProduct = dot( normal, lVector );", + "float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;", + + "vec3 hemiColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );", + + "hemiDiffuse += diffuse * hemiColor;", + + // specular (sky light) + + "vec3 hemiHalfVectorSky = normalize( lVector + viewPosition );", + "float hemiDotNormalHalfSky = 0.5 * dot( normal, hemiHalfVectorSky ) + 0.5;", + "float hemiSpecularWeightSky = specularStrength * max( pow( hemiDotNormalHalfSky, shininess ), 0.0 );", + + // specular (ground light) + + "vec3 lVectorGround = -lVector;", + + "vec3 hemiHalfVectorGround = normalize( lVectorGround + viewPosition );", + "float hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;", + "float hemiSpecularWeightGround = specularStrength * max( pow( hemiDotNormalHalfGround, shininess ), 0.0 );", + + "float dotProductGround = dot( normal, lVectorGround );", + + // 2.0 => 2.0001 is hack to work around ANGLE bug + + "float specularNormalization = ( shininess + 2.0001 ) / 8.0;", + + "vec3 schlickSky = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVector, hemiHalfVectorSky ), 0.0 ), 5.0 );", + "vec3 schlickGround = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 0.0 ), 5.0 );", + "hemiSpecular += hemiColor * specularNormalization * ( schlickSky * hemiSpecularWeightSky * max( dotProduct, 0.0 ) + schlickGround * hemiSpecularWeightGround * max( dotProductGround, 0.0 ) );", + + "}", + + "#endif", + + "vec3 totalDiffuse = vec3( 0.0 );", + "vec3 totalSpecular = vec3( 0.0 );", + + "#if MAX_DIR_LIGHTS > 0", + + "totalDiffuse += dirDiffuse;", + "totalSpecular += dirSpecular;", + + "#endif", + + "#if MAX_HEMI_LIGHTS > 0", + + "totalDiffuse += hemiDiffuse;", + "totalSpecular += hemiSpecular;", + + "#endif", + + "#if MAX_POINT_LIGHTS > 0", + + "totalDiffuse += pointDiffuse;", + "totalSpecular += pointSpecular;", + + "#endif", + + "#if MAX_SPOT_LIGHTS > 0", + + "totalDiffuse += spotDiffuse;", + "totalSpecular += spotSpecular;", + + "#endif", + + "#ifdef METAL", + + "gl_FragColor.xyz = gl_FragColor.xyz * ( emissive + totalDiffuse + ambientLightColor * ambient + totalSpecular );", + + "#else", + + "gl_FragColor.xyz = gl_FragColor.xyz * ( emissive + totalDiffuse + ambientLightColor * ambient ) + totalSpecular;", + + "#endif" + + ].join("\n"), + + // VERTEX COLORS + + color_pars_fragment: [ + + "#ifdef USE_COLOR", + + "varying vec3 vColor;", + + "#endif" + + ].join("\n"), + + + color_fragment: [ + + "#ifdef USE_COLOR", + + "gl_FragColor = gl_FragColor * vec4( vColor, 1.0 );", + + "#endif" + + ].join("\n"), + + color_pars_vertex: [ + + "#ifdef USE_COLOR", + + "varying vec3 vColor;", + + "#endif" + + ].join("\n"), + + + color_vertex: [ + + "#ifdef USE_COLOR", + + "#ifdef GAMMA_INPUT", + + "vColor = color * color;", + + "#else", + + "vColor = color;", + + "#endif", + + "#endif" + + ].join("\n"), + + // SKINNING + + skinning_pars_vertex: [ + + "#ifdef USE_SKINNING", + + "#ifdef BONE_TEXTURE", + + "uniform sampler2D boneTexture;", + "uniform int boneTextureWidth;", + "uniform int boneTextureHeight;", + + "mat4 getBoneMatrix( const in float i ) {", + + "float j = i * 4.0;", + "float x = mod( j, float( boneTextureWidth ) );", + "float y = floor( j / float( boneTextureWidth ) );", + + "float dx = 1.0 / float( boneTextureWidth );", + "float dy = 1.0 / float( boneTextureHeight );", + + "y = dy * ( y + 0.5 );", + + "vec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );", + "vec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );", + "vec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );", + "vec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );", + + "mat4 bone = mat4( v1, v2, v3, v4 );", + + "return bone;", + + "}", + + "#else", + + "uniform mat4 boneGlobalMatrices[ MAX_BONES ];", + + "mat4 getBoneMatrix( const in float i ) {", + + "mat4 bone = boneGlobalMatrices[ int(i) ];", + "return bone;", + + "}", + + "#endif", + + "#endif" + + ].join("\n"), + + skinbase_vertex: [ + + "#ifdef USE_SKINNING", + + "mat4 boneMatX = getBoneMatrix( skinIndex.x );", + "mat4 boneMatY = getBoneMatrix( skinIndex.y );", + "mat4 boneMatZ = getBoneMatrix( skinIndex.z );", + "mat4 boneMatW = getBoneMatrix( skinIndex.w );", + + "#endif" + + ].join("\n"), + + skinning_vertex: [ + + "#ifdef USE_SKINNING", + + "#ifdef USE_MORPHTARGETS", + + "vec4 skinVertex = vec4( morphed, 1.0 );", + + "#else", + + "vec4 skinVertex = vec4( position, 1.0 );", + + "#endif", + + "vec4 skinned = boneMatX * skinVertex * skinWeight.x;", + "skinned += boneMatY * skinVertex * skinWeight.y;", + "skinned += boneMatZ * skinVertex * skinWeight.z;", + "skinned += boneMatW * skinVertex * skinWeight.w;", + + "#endif" + + ].join("\n"), + + // MORPHING + + morphtarget_pars_vertex: [ + + "#ifdef USE_MORPHTARGETS", + + "#ifndef USE_MORPHNORMALS", + + "uniform float morphTargetInfluences[ 8 ];", + + "#else", + + "uniform float morphTargetInfluences[ 4 ];", + + "#endif", + + "#endif" + + ].join("\n"), + + morphtarget_vertex: [ + + "#ifdef USE_MORPHTARGETS", + + "vec3 morphed = vec3( 0.0 );", + "morphed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];", + "morphed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];", + "morphed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];", + "morphed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];", + + "#ifndef USE_MORPHNORMALS", + + "morphed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];", + "morphed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];", + "morphed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];", + "morphed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];", + + "#endif", + + "morphed += position;", + + "#endif" + + ].join("\n"), + + default_vertex : [ + + "vec4 mvPosition;", + + "#ifdef USE_SKINNING", + + "mvPosition = modelViewMatrix * skinned;", + + "#endif", + + "#if !defined( USE_SKINNING ) && defined( USE_MORPHTARGETS )", + + "mvPosition = modelViewMatrix * vec4( morphed, 1.0 );", + + "#endif", + + "#if !defined( USE_SKINNING ) && ! defined( USE_MORPHTARGETS )", + + "mvPosition = modelViewMatrix * vec4( position, 1.0 );", + + "#endif", + + "gl_Position = projectionMatrix * mvPosition;" + + ].join("\n"), + + morphnormal_vertex: [ + + "#ifdef USE_MORPHNORMALS", + + "vec3 morphedNormal = vec3( 0.0 );", + + "morphedNormal += ( morphNormal0 - normal ) * morphTargetInfluences[ 0 ];", + "morphedNormal += ( morphNormal1 - normal ) * morphTargetInfluences[ 1 ];", + "morphedNormal += ( morphNormal2 - normal ) * morphTargetInfluences[ 2 ];", + "morphedNormal += ( morphNormal3 - normal ) * morphTargetInfluences[ 3 ];", + + "morphedNormal += normal;", + + "#endif" + + ].join("\n"), + + skinnormal_vertex: [ + + "#ifdef USE_SKINNING", + + "mat4 skinMatrix = skinWeight.x * boneMatX;", + "skinMatrix += skinWeight.y * boneMatY;", + + "#ifdef USE_MORPHNORMALS", + + "vec4 skinnedNormal = skinMatrix * vec4( morphedNormal, 0.0 );", + + "#else", + + "vec4 skinnedNormal = skinMatrix * vec4( normal, 0.0 );", + + "#endif", + + "#endif" + + ].join("\n"), + + defaultnormal_vertex: [ + + "vec3 objectNormal;", + + "#ifdef USE_SKINNING", + + "objectNormal = skinnedNormal.xyz;", + + "#endif", + + "#if !defined( USE_SKINNING ) && defined( USE_MORPHNORMALS )", + + "objectNormal = morphedNormal;", + + "#endif", + + "#if !defined( USE_SKINNING ) && ! defined( USE_MORPHNORMALS )", + + "objectNormal = normal;", + + "#endif", + + "#ifdef FLIP_SIDED", + + "objectNormal = -objectNormal;", + + "#endif", + + "vec3 transformedNormal = normalMatrix * objectNormal;" + + ].join("\n"), + + // SHADOW MAP + + // based on SpiderGL shadow map and Fabien Sanglard's GLSL shadow mapping examples + // http://spidergl.org/example.php?id=6 + // http://fabiensanglard.net/shadowmapping + + shadowmap_pars_fragment: [ + + "#ifdef USE_SHADOWMAP", + + "uniform sampler2D shadowMap[ MAX_SHADOWS ];", + "uniform vec2 shadowMapSize[ MAX_SHADOWS ];", + + "uniform float shadowDarkness[ MAX_SHADOWS ];", + "uniform float shadowBias[ MAX_SHADOWS ];", + + "varying vec4 vShadowCoord[ MAX_SHADOWS ];", + + "float unpackDepth( const in vec4 rgba_depth ) {", + + "const vec4 bit_shift = vec4( 1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0 );", + "float depth = dot( rgba_depth, bit_shift );", + "return depth;", + + "}", + + "#endif" + + ].join("\n"), + + shadowmap_fragment: [ + + "#ifdef USE_SHADOWMAP", + + "#ifdef SHADOWMAP_DEBUG", + + "vec3 frustumColors[3];", + "frustumColors[0] = vec3( 1.0, 0.5, 0.0 );", + "frustumColors[1] = vec3( 0.0, 1.0, 0.8 );", + "frustumColors[2] = vec3( 0.0, 0.5, 1.0 );", + + "#endif", + + "#ifdef SHADOWMAP_CASCADE", + + "int inFrustumCount = 0;", + + "#endif", + + "float fDepth;", + "vec3 shadowColor = vec3( 1.0 );", + + "for( int i = 0; i < MAX_SHADOWS; i ++ ) {", + + "vec3 shadowCoord = vShadowCoord[ i ].xyz / vShadowCoord[ i ].w;", + + // "if ( something && something )" breaks ATI OpenGL shader compiler + // "if ( all( something, something ) )" using this instead + + "bvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );", + "bool inFrustum = all( inFrustumVec );", + + // don't shadow pixels outside of light frustum + // use just first frustum (for cascades) + // don't shadow pixels behind far plane of light frustum + + "#ifdef SHADOWMAP_CASCADE", + + "inFrustumCount += int( inFrustum );", + "bvec3 frustumTestVec = bvec3( inFrustum, inFrustumCount == 1, shadowCoord.z <= 1.0 );", + + "#else", + + "bvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );", + + "#endif", + + "bool frustumTest = all( frustumTestVec );", + + "if ( frustumTest ) {", + + "shadowCoord.z += shadowBias[ i ];", + + "#if defined( SHADOWMAP_TYPE_PCF )", + + // Percentage-close filtering + // (9 pixel kernel) + // http://fabiensanglard.net/shadowmappingPCF/ + + "float shadow = 0.0;", + + /* + // nested loops breaks shader compiler / validator on some ATI cards when using OpenGL + // must enroll loop manually + + "for ( float y = -1.25; y <= 1.25; y += 1.25 )", + "for ( float x = -1.25; x <= 1.25; x += 1.25 ) {", + + "vec4 rgbaDepth = texture2D( shadowMap[ i ], vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy );", + + // doesn't seem to produce any noticeable visual difference compared to simple "texture2D" lookup + //"vec4 rgbaDepth = texture2DProj( shadowMap[ i ], vec4( vShadowCoord[ i ].w * ( vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy ), 0.05, vShadowCoord[ i ].w ) );", + + "float fDepth = unpackDepth( rgbaDepth );", + + "if ( fDepth < shadowCoord.z )", + "shadow += 1.0;", + + "}", + + "shadow /= 9.0;", + + */ + + "const float shadowDelta = 1.0 / 9.0;", + + "float xPixelOffset = 1.0 / shadowMapSize[ i ].x;", + "float yPixelOffset = 1.0 / shadowMapSize[ i ].y;", + + "float dx0 = -1.25 * xPixelOffset;", + "float dy0 = -1.25 * yPixelOffset;", + "float dx1 = 1.25 * xPixelOffset;", + "float dy1 = 1.25 * yPixelOffset;", + + "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );", + "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;", + + "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );", + "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;", + + "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );", + "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;", + + "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );", + "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;", + + "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );", + "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;", + + "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );", + "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;", + + "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );", + "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;", + + "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );", + "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;", + + "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );", + "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;", + + "shadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );", + + "#elif defined( SHADOWMAP_TYPE_PCF_SOFT )", + + // Percentage-close filtering + // (9 pixel kernel) + // http://fabiensanglard.net/shadowmappingPCF/ + + "float shadow = 0.0;", + + "float xPixelOffset = 1.0 / shadowMapSize[ i ].x;", + "float yPixelOffset = 1.0 / shadowMapSize[ i ].y;", + + "float dx0 = -1.0 * xPixelOffset;", + "float dy0 = -1.0 * yPixelOffset;", + "float dx1 = 1.0 * xPixelOffset;", + "float dy1 = 1.0 * yPixelOffset;", + + "mat3 shadowKernel;", + "mat3 depthKernel;", + + "depthKernel[0][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );", + "depthKernel[0][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );", + "depthKernel[0][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );", + "depthKernel[1][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );", + "depthKernel[1][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );", + "depthKernel[1][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );", + "depthKernel[2][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );", + "depthKernel[2][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );", + "depthKernel[2][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );", + + "vec3 shadowZ = vec3( shadowCoord.z );", + "shadowKernel[0] = vec3(lessThan(depthKernel[0], shadowZ ));", + "shadowKernel[0] *= vec3(0.25);", + + "shadowKernel[1] = vec3(lessThan(depthKernel[1], shadowZ ));", + "shadowKernel[1] *= vec3(0.25);", + + "shadowKernel[2] = vec3(lessThan(depthKernel[2], shadowZ ));", + "shadowKernel[2] *= vec3(0.25);", + + "vec2 fractionalCoord = 1.0 - fract( shadowCoord.xy * shadowMapSize[i].xy );", + + "shadowKernel[0] = mix( shadowKernel[1], shadowKernel[0], fractionalCoord.x );", + "shadowKernel[1] = mix( shadowKernel[2], shadowKernel[1], fractionalCoord.x );", + + "vec4 shadowValues;", + "shadowValues.x = mix( shadowKernel[0][1], shadowKernel[0][0], fractionalCoord.y );", + "shadowValues.y = mix( shadowKernel[0][2], shadowKernel[0][1], fractionalCoord.y );", + "shadowValues.z = mix( shadowKernel[1][1], shadowKernel[1][0], fractionalCoord.y );", + "shadowValues.w = mix( shadowKernel[1][2], shadowKernel[1][1], fractionalCoord.y );", + + "shadow = dot( shadowValues, vec4( 1.0 ) );", + + "shadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );", + + "#else", + + "vec4 rgbaDepth = texture2D( shadowMap[ i ], shadowCoord.xy );", + "float fDepth = unpackDepth( rgbaDepth );", + + "if ( fDepth < shadowCoord.z )", + + // spot with multiple shadows is darker + + "shadowColor = shadowColor * vec3( 1.0 - shadowDarkness[ i ] );", + + // spot with multiple shadows has the same color as single shadow spot + + //"shadowColor = min( shadowColor, vec3( shadowDarkness[ i ] ) );", + + "#endif", + + "}", + + + "#ifdef SHADOWMAP_DEBUG", + + "#ifdef SHADOWMAP_CASCADE", + + "if ( inFrustum && inFrustumCount == 1 ) gl_FragColor.xyz *= frustumColors[ i ];", + + "#else", + + "if ( inFrustum ) gl_FragColor.xyz *= frustumColors[ i ];", + + "#endif", + + "#endif", + + "}", + + "#ifdef GAMMA_OUTPUT", + + "shadowColor *= shadowColor;", + + "#endif", + + "gl_FragColor.xyz = gl_FragColor.xyz * shadowColor;", + + "#endif" + + ].join("\n"), + + shadowmap_pars_vertex: [ + + "#ifdef USE_SHADOWMAP", + + "varying vec4 vShadowCoord[ MAX_SHADOWS ];", + "uniform mat4 shadowMatrix[ MAX_SHADOWS ];", + + "#endif" + + ].join("\n"), + + shadowmap_vertex: [ + + "#ifdef USE_SHADOWMAP", + + "for( int i = 0; i < MAX_SHADOWS; i ++ ) {", + + "vShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;", + + "}", + + "#endif" + + ].join("\n"), + + // ALPHATEST + + alphatest_fragment: [ + + "#ifdef ALPHATEST", + + "if ( gl_FragColor.a < ALPHATEST ) discard;", + + "#endif" + + ].join("\n"), + + // LINEAR SPACE + + linear_to_gamma_fragment: [ + + "#ifdef GAMMA_OUTPUT", + + "gl_FragColor.xyz = sqrt( gl_FragColor.xyz );", + + "#endif" + + ].join("\n") + + +}; +/** + * Uniform Utilities + */ + +THREE.UniformsUtils = { + + merge: function ( uniforms ) { + + var u, p, tmp, merged = {}; + + for ( u = 0; u < uniforms.length; u ++ ) { + + tmp = this.clone( uniforms[ u ] ); + + for ( p in tmp ) { + + merged[ p ] = tmp[ p ]; + + } + + } + + return merged; + + }, + + clone: function ( uniforms_src ) { + + var u, p, parameter, parameter_src, uniforms_dst = {}; + + for ( u in uniforms_src ) { + + uniforms_dst[ u ] = {}; + + for ( p in uniforms_src[ u ] ) { + + parameter_src = uniforms_src[ u ][ p ]; + + if ( parameter_src instanceof THREE.Color || + parameter_src instanceof THREE.Vector2 || + parameter_src instanceof THREE.Vector3 || + parameter_src instanceof THREE.Vector4 || + parameter_src instanceof THREE.Matrix4 || + parameter_src instanceof THREE.Texture ) { + + uniforms_dst[ u ][ p ] = parameter_src.clone(); + + } else if ( parameter_src instanceof Array ) { + + uniforms_dst[ u ][ p ] = parameter_src.slice(); + + } else { + + uniforms_dst[ u ][ p ] = parameter_src; + + } + + } + + } + + return uniforms_dst; + + } + +}; +/** + * Uniforms library for shared webgl shaders + */ + +THREE.UniformsLib = { + + common: { + + "diffuse" : { type: "c", value: new THREE.Color( 0xeeeeee ) }, + "opacity" : { type: "f", value: 1.0 }, + + "map" : { type: "t", value: null }, + "offsetRepeat" : { type: "v4", value: new THREE.Vector4( 0, 0, 1, 1 ) }, + + "lightMap" : { type: "t", value: null }, + "specularMap" : { type: "t", value: null }, + + "envMap" : { type: "t", value: null }, + "flipEnvMap" : { type: "f", value: -1 }, + "useRefract" : { type: "i", value: 0 }, + "reflectivity" : { type: "f", value: 1.0 }, + "refractionRatio" : { type: "f", value: 0.98 }, + "combine" : { type: "i", value: 0 }, + + "morphTargetInfluences" : { type: "f", value: 0 } + + }, + + bump: { + + "bumpMap" : { type: "t", value: null }, + "bumpScale" : { type: "f", value: 1 } + + }, + + normalmap: { + + "normalMap" : { type: "t", value: null }, + "normalScale" : { type: "v2", value: new THREE.Vector2( 1, 1 ) } + }, + + fog : { + + "fogDensity" : { type: "f", value: 0.00025 }, + "fogNear" : { type: "f", value: 1 }, + "fogFar" : { type: "f", value: 2000 }, + "fogColor" : { type: "c", value: new THREE.Color( 0xffffff ) } + + }, + + lights: { + + "ambientLightColor" : { type: "fv", value: [] }, + + "directionalLightDirection" : { type: "fv", value: [] }, + "directionalLightColor" : { type: "fv", value: [] }, + + "hemisphereLightDirection" : { type: "fv", value: [] }, + "hemisphereLightSkyColor" : { type: "fv", value: [] }, + "hemisphereLightGroundColor" : { type: "fv", value: [] }, + + "pointLightColor" : { type: "fv", value: [] }, + "pointLightPosition" : { type: "fv", value: [] }, + "pointLightDistance" : { type: "fv1", value: [] }, + + "spotLightColor" : { type: "fv", value: [] }, + "spotLightPosition" : { type: "fv", value: [] }, + "spotLightDirection" : { type: "fv", value: [] }, + "spotLightDistance" : { type: "fv1", value: [] }, + "spotLightAngleCos" : { type: "fv1", value: [] }, + "spotLightExponent" : { type: "fv1", value: [] } + + }, + + particle: { + + "psColor" : { type: "c", value: new THREE.Color( 0xeeeeee ) }, + "opacity" : { type: "f", value: 1.0 }, + "size" : { type: "f", value: 1.0 }, + "scale" : { type: "f", value: 1.0 }, + "map" : { type: "t", value: null }, + + "fogDensity" : { type: "f", value: 0.00025 }, + "fogNear" : { type: "f", value: 1 }, + "fogFar" : { type: "f", value: 2000 }, + "fogColor" : { type: "c", value: new THREE.Color( 0xffffff ) } + + }, + + shadowmap: { + + "shadowMap": { type: "tv", value: [] }, + "shadowMapSize": { type: "v2v", value: [] }, + + "shadowBias" : { type: "fv1", value: [] }, + "shadowDarkness": { type: "fv1", value: [] }, + + "shadowMatrix" : { type: "m4v", value: [] } + + } + +}; +/** + * Webgl Shader Library for three.js + * + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + * @author mikael emtinger / http://gomo.se/ + */ + + +THREE.ShaderLib = { + + 'basic': { + + uniforms: THREE.UniformsUtils.merge( [ + + THREE.UniformsLib[ "common" ], + THREE.UniformsLib[ "fog" ], + THREE.UniformsLib[ "shadowmap" ] + + ] ), + + vertexShader: [ + + THREE.ShaderChunk[ "map_pars_vertex" ], + THREE.ShaderChunk[ "lightmap_pars_vertex" ], + THREE.ShaderChunk[ "envmap_pars_vertex" ], + THREE.ShaderChunk[ "color_pars_vertex" ], + THREE.ShaderChunk[ "morphtarget_pars_vertex" ], + THREE.ShaderChunk[ "skinning_pars_vertex" ], + THREE.ShaderChunk[ "shadowmap_pars_vertex" ], + + "void main() {", + + THREE.ShaderChunk[ "map_vertex" ], + THREE.ShaderChunk[ "lightmap_vertex" ], + THREE.ShaderChunk[ "color_vertex" ], + THREE.ShaderChunk[ "skinbase_vertex" ], + + "#ifdef USE_ENVMAP", + + THREE.ShaderChunk[ "morphnormal_vertex" ], + THREE.ShaderChunk[ "skinnormal_vertex" ], + THREE.ShaderChunk[ "defaultnormal_vertex" ], + + "#endif", + + THREE.ShaderChunk[ "morphtarget_vertex" ], + THREE.ShaderChunk[ "skinning_vertex" ], + THREE.ShaderChunk[ "default_vertex" ], + + THREE.ShaderChunk[ "worldpos_vertex" ], + THREE.ShaderChunk[ "envmap_vertex" ], + THREE.ShaderChunk[ "shadowmap_vertex" ], + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform vec3 diffuse;", + "uniform float opacity;", + + THREE.ShaderChunk[ "color_pars_fragment" ], + THREE.ShaderChunk[ "map_pars_fragment" ], + THREE.ShaderChunk[ "lightmap_pars_fragment" ], + THREE.ShaderChunk[ "envmap_pars_fragment" ], + THREE.ShaderChunk[ "fog_pars_fragment" ], + THREE.ShaderChunk[ "shadowmap_pars_fragment" ], + THREE.ShaderChunk[ "specularmap_pars_fragment" ], + + "void main() {", + + "gl_FragColor = vec4( diffuse, opacity );", + + THREE.ShaderChunk[ "map_fragment" ], + THREE.ShaderChunk[ "alphatest_fragment" ], + THREE.ShaderChunk[ "specularmap_fragment" ], + THREE.ShaderChunk[ "lightmap_fragment" ], + THREE.ShaderChunk[ "color_fragment" ], + THREE.ShaderChunk[ "envmap_fragment" ], + THREE.ShaderChunk[ "shadowmap_fragment" ], + + THREE.ShaderChunk[ "linear_to_gamma_fragment" ], + + THREE.ShaderChunk[ "fog_fragment" ], + + "}" + + ].join("\n") + + }, + + 'lambert': { + + uniforms: THREE.UniformsUtils.merge( [ + + THREE.UniformsLib[ "common" ], + THREE.UniformsLib[ "fog" ], + THREE.UniformsLib[ "lights" ], + THREE.UniformsLib[ "shadowmap" ], + + { + "ambient" : { type: "c", value: new THREE.Color( 0xffffff ) }, + "emissive" : { type: "c", value: new THREE.Color( 0x000000 ) }, + "wrapRGB" : { type: "v3", value: new THREE.Vector3( 1, 1, 1 ) } + } + + ] ), + + vertexShader: [ + + "#define LAMBERT", + + "varying vec3 vLightFront;", + + "#ifdef DOUBLE_SIDED", + + "varying vec3 vLightBack;", + + "#endif", + + THREE.ShaderChunk[ "map_pars_vertex" ], + THREE.ShaderChunk[ "lightmap_pars_vertex" ], + THREE.ShaderChunk[ "envmap_pars_vertex" ], + THREE.ShaderChunk[ "lights_lambert_pars_vertex" ], + THREE.ShaderChunk[ "color_pars_vertex" ], + THREE.ShaderChunk[ "morphtarget_pars_vertex" ], + THREE.ShaderChunk[ "skinning_pars_vertex" ], + THREE.ShaderChunk[ "shadowmap_pars_vertex" ], + + "void main() {", + + THREE.ShaderChunk[ "map_vertex" ], + THREE.ShaderChunk[ "lightmap_vertex" ], + THREE.ShaderChunk[ "color_vertex" ], + + THREE.ShaderChunk[ "morphnormal_vertex" ], + THREE.ShaderChunk[ "skinbase_vertex" ], + THREE.ShaderChunk[ "skinnormal_vertex" ], + THREE.ShaderChunk[ "defaultnormal_vertex" ], + + THREE.ShaderChunk[ "morphtarget_vertex" ], + THREE.ShaderChunk[ "skinning_vertex" ], + THREE.ShaderChunk[ "default_vertex" ], + + THREE.ShaderChunk[ "worldpos_vertex" ], + THREE.ShaderChunk[ "envmap_vertex" ], + THREE.ShaderChunk[ "lights_lambert_vertex" ], + THREE.ShaderChunk[ "shadowmap_vertex" ], + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform float opacity;", + + "varying vec3 vLightFront;", + + "#ifdef DOUBLE_SIDED", + + "varying vec3 vLightBack;", + + "#endif", + + THREE.ShaderChunk[ "color_pars_fragment" ], + THREE.ShaderChunk[ "map_pars_fragment" ], + THREE.ShaderChunk[ "lightmap_pars_fragment" ], + THREE.ShaderChunk[ "envmap_pars_fragment" ], + THREE.ShaderChunk[ "fog_pars_fragment" ], + THREE.ShaderChunk[ "shadowmap_pars_fragment" ], + THREE.ShaderChunk[ "specularmap_pars_fragment" ], + + "void main() {", + + "gl_FragColor = vec4( vec3 ( 1.0 ), opacity );", + + THREE.ShaderChunk[ "map_fragment" ], + THREE.ShaderChunk[ "alphatest_fragment" ], + THREE.ShaderChunk[ "specularmap_fragment" ], + + "#ifdef DOUBLE_SIDED", + + //"float isFront = float( gl_FrontFacing );", + //"gl_FragColor.xyz *= isFront * vLightFront + ( 1.0 - isFront ) * vLightBack;", + + "if ( gl_FrontFacing )", + "gl_FragColor.xyz *= vLightFront;", + "else", + "gl_FragColor.xyz *= vLightBack;", + + "#else", + + "gl_FragColor.xyz *= vLightFront;", + + "#endif", + + THREE.ShaderChunk[ "lightmap_fragment" ], + THREE.ShaderChunk[ "color_fragment" ], + THREE.ShaderChunk[ "envmap_fragment" ], + THREE.ShaderChunk[ "shadowmap_fragment" ], + + THREE.ShaderChunk[ "linear_to_gamma_fragment" ], + + THREE.ShaderChunk[ "fog_fragment" ], + + "}" + + ].join("\n") + + }, + + 'phong': { + + uniforms: THREE.UniformsUtils.merge( [ + + THREE.UniformsLib[ "common" ], + THREE.UniformsLib[ "bump" ], + THREE.UniformsLib[ "normalmap" ], + THREE.UniformsLib[ "fog" ], + THREE.UniformsLib[ "lights" ], + THREE.UniformsLib[ "shadowmap" ], + + { + "ambient" : { type: "c", value: new THREE.Color( 0xffffff ) }, + "emissive" : { type: "c", value: new THREE.Color( 0x000000 ) }, + "specular" : { type: "c", value: new THREE.Color( 0x111111 ) }, + "shininess": { type: "f", value: 30 }, + "wrapRGB" : { type: "v3", value: new THREE.Vector3( 1, 1, 1 ) } + } + + ] ), + + vertexShader: [ + + "#define PHONG", + + "varying vec3 vViewPosition;", + "varying vec3 vNormal;", + + THREE.ShaderChunk[ "map_pars_vertex" ], + THREE.ShaderChunk[ "lightmap_pars_vertex" ], + THREE.ShaderChunk[ "envmap_pars_vertex" ], + THREE.ShaderChunk[ "lights_phong_pars_vertex" ], + THREE.ShaderChunk[ "color_pars_vertex" ], + THREE.ShaderChunk[ "morphtarget_pars_vertex" ], + THREE.ShaderChunk[ "skinning_pars_vertex" ], + THREE.ShaderChunk[ "shadowmap_pars_vertex" ], + + "void main() {", + + THREE.ShaderChunk[ "map_vertex" ], + THREE.ShaderChunk[ "lightmap_vertex" ], + THREE.ShaderChunk[ "color_vertex" ], + + THREE.ShaderChunk[ "morphnormal_vertex" ], + THREE.ShaderChunk[ "skinbase_vertex" ], + THREE.ShaderChunk[ "skinnormal_vertex" ], + THREE.ShaderChunk[ "defaultnormal_vertex" ], + + "vNormal = normalize( transformedNormal );", + + THREE.ShaderChunk[ "morphtarget_vertex" ], + THREE.ShaderChunk[ "skinning_vertex" ], + THREE.ShaderChunk[ "default_vertex" ], + + "vViewPosition = -mvPosition.xyz;", + + THREE.ShaderChunk[ "worldpos_vertex" ], + THREE.ShaderChunk[ "envmap_vertex" ], + THREE.ShaderChunk[ "lights_phong_vertex" ], + THREE.ShaderChunk[ "shadowmap_vertex" ], + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform vec3 diffuse;", + "uniform float opacity;", + + "uniform vec3 ambient;", + "uniform vec3 emissive;", + "uniform vec3 specular;", + "uniform float shininess;", + + THREE.ShaderChunk[ "color_pars_fragment" ], + THREE.ShaderChunk[ "map_pars_fragment" ], + THREE.ShaderChunk[ "lightmap_pars_fragment" ], + THREE.ShaderChunk[ "envmap_pars_fragment" ], + THREE.ShaderChunk[ "fog_pars_fragment" ], + THREE.ShaderChunk[ "lights_phong_pars_fragment" ], + THREE.ShaderChunk[ "shadowmap_pars_fragment" ], + THREE.ShaderChunk[ "bumpmap_pars_fragment" ], + THREE.ShaderChunk[ "normalmap_pars_fragment" ], + THREE.ShaderChunk[ "specularmap_pars_fragment" ], + + "void main() {", + + "gl_FragColor = vec4( vec3 ( 1.0 ), opacity );", + + THREE.ShaderChunk[ "map_fragment" ], + THREE.ShaderChunk[ "alphatest_fragment" ], + THREE.ShaderChunk[ "specularmap_fragment" ], + + THREE.ShaderChunk[ "lights_phong_fragment" ], + + THREE.ShaderChunk[ "lightmap_fragment" ], + THREE.ShaderChunk[ "color_fragment" ], + THREE.ShaderChunk[ "envmap_fragment" ], + THREE.ShaderChunk[ "shadowmap_fragment" ], + + THREE.ShaderChunk[ "linear_to_gamma_fragment" ], + + THREE.ShaderChunk[ "fog_fragment" ], + + "}" + + ].join("\n") + + }, + + 'particle_basic': { + + uniforms: THREE.UniformsUtils.merge( [ + + THREE.UniformsLib[ "particle" ], + THREE.UniformsLib[ "shadowmap" ] + + ] ), + + vertexShader: [ + + "uniform float size;", + "uniform float scale;", + + THREE.ShaderChunk[ "color_pars_vertex" ], + THREE.ShaderChunk[ "shadowmap_pars_vertex" ], + + "void main() {", + + THREE.ShaderChunk[ "color_vertex" ], + + "vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );", + + "#ifdef USE_SIZEATTENUATION", + "gl_PointSize = size * ( scale / length( mvPosition.xyz ) );", + "#else", + "gl_PointSize = size;", + "#endif", + + "gl_Position = projectionMatrix * mvPosition;", + + THREE.ShaderChunk[ "worldpos_vertex" ], + THREE.ShaderChunk[ "shadowmap_vertex" ], + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform vec3 psColor;", + "uniform float opacity;", + + THREE.ShaderChunk[ "color_pars_fragment" ], + THREE.ShaderChunk[ "map_particle_pars_fragment" ], + THREE.ShaderChunk[ "fog_pars_fragment" ], + THREE.ShaderChunk[ "shadowmap_pars_fragment" ], + + "void main() {", + + "gl_FragColor = vec4( psColor, opacity );", + + THREE.ShaderChunk[ "map_particle_fragment" ], + THREE.ShaderChunk[ "alphatest_fragment" ], + THREE.ShaderChunk[ "color_fragment" ], + THREE.ShaderChunk[ "shadowmap_fragment" ], + THREE.ShaderChunk[ "fog_fragment" ], + + "}" + + ].join("\n") + + }, + + 'dashed': { + + uniforms: THREE.UniformsUtils.merge( [ + + THREE.UniformsLib[ "common" ], + THREE.UniformsLib[ "fog" ], + + { + "scale": { type: "f", value: 1 }, + "dashSize": { type: "f", value: 1 }, + "totalSize": { type: "f", value: 2 } + } + + ] ), + + vertexShader: [ + + "uniform float scale;", + "attribute float lineDistance;", + + "varying float vLineDistance;", + + THREE.ShaderChunk[ "color_pars_vertex" ], + + "void main() {", + + THREE.ShaderChunk[ "color_vertex" ], + + "vLineDistance = scale * lineDistance;", + + "vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );", + "gl_Position = projectionMatrix * mvPosition;", + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform vec3 diffuse;", + "uniform float opacity;", + + "uniform float dashSize;", + "uniform float totalSize;", + + "varying float vLineDistance;", + + THREE.ShaderChunk[ "color_pars_fragment" ], + THREE.ShaderChunk[ "fog_pars_fragment" ], + + "void main() {", + + "if ( mod( vLineDistance, totalSize ) > dashSize ) {", + + "discard;", + + "}", + + "gl_FragColor = vec4( diffuse, opacity );", + + THREE.ShaderChunk[ "color_fragment" ], + THREE.ShaderChunk[ "fog_fragment" ], + + "}" + + ].join("\n") + + }, + + 'depth': { + + uniforms: { + + "mNear": { type: "f", value: 1.0 }, + "mFar" : { type: "f", value: 2000.0 }, + "opacity" : { type: "f", value: 1.0 } + + }, + + vertexShader: [ + + "void main() {", + + "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform float mNear;", + "uniform float mFar;", + "uniform float opacity;", + + "void main() {", + + "float depth = gl_FragCoord.z / gl_FragCoord.w;", + "float color = 1.0 - smoothstep( mNear, mFar, depth );", + "gl_FragColor = vec4( vec3( color ), opacity );", + + "}" + + ].join("\n") + + }, + + 'normal': { + + uniforms: { + + "opacity" : { type: "f", value: 1.0 } + + }, + + vertexShader: [ + + "varying vec3 vNormal;", + + THREE.ShaderChunk[ "morphtarget_pars_vertex" ], + + "void main() {", + + "vNormal = normalize( normalMatrix * normal );", + + THREE.ShaderChunk[ "morphtarget_vertex" ], + THREE.ShaderChunk[ "default_vertex" ], + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform float opacity;", + "varying vec3 vNormal;", + + "void main() {", + + "gl_FragColor = vec4( 0.5 * normalize( vNormal ) + 0.5, opacity );", + + "}" + + ].join("\n") + + }, + + /* ------------------------------------------------------------------------- + // Normal map shader + // - Blinn-Phong + // - normal + diffuse + specular + AO + displacement + reflection + shadow maps + // - point and directional lights (use with "lights: true" material option) + ------------------------------------------------------------------------- */ + + 'normalmap' : { + + uniforms: THREE.UniformsUtils.merge( [ + + THREE.UniformsLib[ "fog" ], + THREE.UniformsLib[ "lights" ], + THREE.UniformsLib[ "shadowmap" ], + + { + + "enableAO" : { type: "i", value: 0 }, + "enableDiffuse" : { type: "i", value: 0 }, + "enableSpecular" : { type: "i", value: 0 }, + "enableReflection": { type: "i", value: 0 }, + "enableDisplacement": { type: "i", value: 0 }, + + "tDisplacement": { type: "t", value: null }, // must go first as this is vertex texture + "tDiffuse" : { type: "t", value: null }, + "tCube" : { type: "t", value: null }, + "tNormal" : { type: "t", value: null }, + "tSpecular" : { type: "t", value: null }, + "tAO" : { type: "t", value: null }, + + "uNormalScale": { type: "v2", value: new THREE.Vector2( 1, 1 ) }, + + "uDisplacementBias": { type: "f", value: 0.0 }, + "uDisplacementScale": { type: "f", value: 1.0 }, + + "diffuse": { type: "c", value: new THREE.Color( 0xffffff ) }, + "specular": { type: "c", value: new THREE.Color( 0x111111 ) }, + "ambient": { type: "c", value: new THREE.Color( 0xffffff ) }, + "shininess": { type: "f", value: 30 }, + "opacity": { type: "f", value: 1 }, + + "useRefract": { type: "i", value: 0 }, + "refractionRatio": { type: "f", value: 0.98 }, + "reflectivity": { type: "f", value: 0.5 }, + + "uOffset" : { type: "v2", value: new THREE.Vector2( 0, 0 ) }, + "uRepeat" : { type: "v2", value: new THREE.Vector2( 1, 1 ) }, + + "wrapRGB" : { type: "v3", value: new THREE.Vector3( 1, 1, 1 ) } + + } + + ] ), + + fragmentShader: [ + + "uniform vec3 ambient;", + "uniform vec3 diffuse;", + "uniform vec3 specular;", + "uniform float shininess;", + "uniform float opacity;", + + "uniform bool enableDiffuse;", + "uniform bool enableSpecular;", + "uniform bool enableAO;", + "uniform bool enableReflection;", + + "uniform sampler2D tDiffuse;", + "uniform sampler2D tNormal;", + "uniform sampler2D tSpecular;", + "uniform sampler2D tAO;", + + "uniform samplerCube tCube;", + + "uniform vec2 uNormalScale;", + + "uniform bool useRefract;", + "uniform float refractionRatio;", + "uniform float reflectivity;", + + "varying vec3 vTangent;", + "varying vec3 vBinormal;", + "varying vec3 vNormal;", + "varying vec2 vUv;", + + "uniform vec3 ambientLightColor;", + + "#if MAX_DIR_LIGHTS > 0", + + "uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];", + "uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];", + + "#endif", + + "#if MAX_HEMI_LIGHTS > 0", + + "uniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];", + "uniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];", + "uniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];", + + "#endif", + + "#if MAX_POINT_LIGHTS > 0", + + "uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];", + "uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];", + "uniform float pointLightDistance[ MAX_POINT_LIGHTS ];", + + "#endif", + + "#if MAX_SPOT_LIGHTS > 0", + + "uniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];", + "uniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];", + "uniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];", + "uniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];", + "uniform float spotLightExponent[ MAX_SPOT_LIGHTS ];", + "uniform float spotLightDistance[ MAX_SPOT_LIGHTS ];", + + "#endif", + + "#ifdef WRAP_AROUND", + + "uniform vec3 wrapRGB;", + + "#endif", + + "varying vec3 vWorldPosition;", + "varying vec3 vViewPosition;", + + THREE.ShaderChunk[ "shadowmap_pars_fragment" ], + THREE.ShaderChunk[ "fog_pars_fragment" ], + + "void main() {", + + "gl_FragColor = vec4( vec3( 1.0 ), opacity );", + + "vec3 specularTex = vec3( 1.0 );", + + "vec3 normalTex = texture2D( tNormal, vUv ).xyz * 2.0 - 1.0;", + "normalTex.xy *= uNormalScale;", + "normalTex = normalize( normalTex );", + + "if( enableDiffuse ) {", + + "#ifdef GAMMA_INPUT", + + "vec4 texelColor = texture2D( tDiffuse, vUv );", + "texelColor.xyz *= texelColor.xyz;", + + "gl_FragColor = gl_FragColor * texelColor;", + + "#else", + + "gl_FragColor = gl_FragColor * texture2D( tDiffuse, vUv );", + + "#endif", + + "}", + + "if( enableAO ) {", + + "#ifdef GAMMA_INPUT", + + "vec4 aoColor = texture2D( tAO, vUv );", + "aoColor.xyz *= aoColor.xyz;", + + "gl_FragColor.xyz = gl_FragColor.xyz * aoColor.xyz;", + + "#else", + + "gl_FragColor.xyz = gl_FragColor.xyz * texture2D( tAO, vUv ).xyz;", + + "#endif", + + "}", + + "if( enableSpecular )", + "specularTex = texture2D( tSpecular, vUv ).xyz;", + + "mat3 tsb = mat3( normalize( vTangent ), normalize( vBinormal ), normalize( vNormal ) );", + "vec3 finalNormal = tsb * normalTex;", + + "#ifdef FLIP_SIDED", + + "finalNormal = -finalNormal;", + + "#endif", + + "vec3 normal = normalize( finalNormal );", + "vec3 viewPosition = normalize( vViewPosition );", + + // point lights + + "#if MAX_POINT_LIGHTS > 0", + + "vec3 pointDiffuse = vec3( 0.0 );", + "vec3 pointSpecular = vec3( 0.0 );", + + "for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {", + + "vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );", + "vec3 pointVector = lPosition.xyz + vViewPosition.xyz;", + + "float pointDistance = 1.0;", + "if ( pointLightDistance[ i ] > 0.0 )", + "pointDistance = 1.0 - min( ( length( pointVector ) / pointLightDistance[ i ] ), 1.0 );", + + "pointVector = normalize( pointVector );", + + // diffuse + + "#ifdef WRAP_AROUND", + + "float pointDiffuseWeightFull = max( dot( normal, pointVector ), 0.0 );", + "float pointDiffuseWeightHalf = max( 0.5 * dot( normal, pointVector ) + 0.5, 0.0 );", + + "vec3 pointDiffuseWeight = mix( vec3 ( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );", + + "#else", + + "float pointDiffuseWeight = max( dot( normal, pointVector ), 0.0 );", + + "#endif", + + "pointDiffuse += pointDistance * pointLightColor[ i ] * diffuse * pointDiffuseWeight;", + + // specular + + "vec3 pointHalfVector = normalize( pointVector + viewPosition );", + "float pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );", + "float pointSpecularWeight = specularTex.r * max( pow( pointDotNormalHalf, shininess ), 0.0 );", + + // 2.0 => 2.0001 is hack to work around ANGLE bug + + "float specularNormalization = ( shininess + 2.0001 ) / 8.0;", + + "vec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( pointVector, pointHalfVector ), 5.0 );", + "pointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * pointDistance * specularNormalization;", + + "}", + + "#endif", + + // spot lights + + "#if MAX_SPOT_LIGHTS > 0", + + "vec3 spotDiffuse = vec3( 0.0 );", + "vec3 spotSpecular = vec3( 0.0 );", + + "for ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {", + + "vec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );", + "vec3 spotVector = lPosition.xyz + vViewPosition.xyz;", + + "float spotDistance = 1.0;", + "if ( spotLightDistance[ i ] > 0.0 )", + "spotDistance = 1.0 - min( ( length( spotVector ) / spotLightDistance[ i ] ), 1.0 );", + + "spotVector = normalize( spotVector );", + + "float spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - vWorldPosition ) );", + + "if ( spotEffect > spotLightAngleCos[ i ] ) {", + + "spotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );", + + // diffuse + + "#ifdef WRAP_AROUND", + + "float spotDiffuseWeightFull = max( dot( normal, spotVector ), 0.0 );", + "float spotDiffuseWeightHalf = max( 0.5 * dot( normal, spotVector ) + 0.5, 0.0 );", + + "vec3 spotDiffuseWeight = mix( vec3 ( spotDiffuseWeightFull ), vec3( spotDiffuseWeightHalf ), wrapRGB );", + + "#else", + + "float spotDiffuseWeight = max( dot( normal, spotVector ), 0.0 );", + + "#endif", + + "spotDiffuse += spotDistance * spotLightColor[ i ] * diffuse * spotDiffuseWeight * spotEffect;", + + // specular + + "vec3 spotHalfVector = normalize( spotVector + viewPosition );", + "float spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );", + "float spotSpecularWeight = specularTex.r * max( pow( spotDotNormalHalf, shininess ), 0.0 );", + + // 2.0 => 2.0001 is hack to work around ANGLE bug + + "float specularNormalization = ( shininess + 2.0001 ) / 8.0;", + + "vec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( spotVector, spotHalfVector ), 5.0 );", + "spotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * spotDistance * specularNormalization * spotEffect;", + + "}", + + "}", + + "#endif", + + // directional lights + + "#if MAX_DIR_LIGHTS > 0", + + "vec3 dirDiffuse = vec3( 0.0 );", + "vec3 dirSpecular = vec3( 0.0 );", + + "for( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {", + + "vec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );", + "vec3 dirVector = normalize( lDirection.xyz );", + + // diffuse + + "#ifdef WRAP_AROUND", + + "float directionalLightWeightingFull = max( dot( normal, dirVector ), 0.0 );", + "float directionalLightWeightingHalf = max( 0.5 * dot( normal, dirVector ) + 0.5, 0.0 );", + + "vec3 dirDiffuseWeight = mix( vec3( directionalLightWeightingFull ), vec3( directionalLightWeightingHalf ), wrapRGB );", + + "#else", + + "float dirDiffuseWeight = max( dot( normal, dirVector ), 0.0 );", + + "#endif", + + "dirDiffuse += directionalLightColor[ i ] * diffuse * dirDiffuseWeight;", + + // specular + + "vec3 dirHalfVector = normalize( dirVector + viewPosition );", + "float dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );", + "float dirSpecularWeight = specularTex.r * max( pow( dirDotNormalHalf, shininess ), 0.0 );", + + // 2.0 => 2.0001 is hack to work around ANGLE bug + + "float specularNormalization = ( shininess + 2.0001 ) / 8.0;", + + "vec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( dirVector, dirHalfVector ), 5.0 );", + "dirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;", + + "}", + + "#endif", + + // hemisphere lights + + "#if MAX_HEMI_LIGHTS > 0", + + "vec3 hemiDiffuse = vec3( 0.0 );", + "vec3 hemiSpecular = vec3( 0.0 );" , + + "for( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {", + + "vec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );", + "vec3 lVector = normalize( lDirection.xyz );", + + // diffuse + + "float dotProduct = dot( normal, lVector );", + "float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;", + + "vec3 hemiColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );", + + "hemiDiffuse += diffuse * hemiColor;", + + // specular (sky light) + + + "vec3 hemiHalfVectorSky = normalize( lVector + viewPosition );", + "float hemiDotNormalHalfSky = 0.5 * dot( normal, hemiHalfVectorSky ) + 0.5;", + "float hemiSpecularWeightSky = specularTex.r * max( pow( hemiDotNormalHalfSky, shininess ), 0.0 );", + + // specular (ground light) + + "vec3 lVectorGround = -lVector;", + + "vec3 hemiHalfVectorGround = normalize( lVectorGround + viewPosition );", + "float hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;", + "float hemiSpecularWeightGround = specularTex.r * max( pow( hemiDotNormalHalfGround, shininess ), 0.0 );", + + "float dotProductGround = dot( normal, lVectorGround );", + + // 2.0 => 2.0001 is hack to work around ANGLE bug + + "float specularNormalization = ( shininess + 2.0001 ) / 8.0;", + + "vec3 schlickSky = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVector, hemiHalfVectorSky ), 5.0 );", + "vec3 schlickGround = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 5.0 );", + "hemiSpecular += hemiColor * specularNormalization * ( schlickSky * hemiSpecularWeightSky * max( dotProduct, 0.0 ) + schlickGround * hemiSpecularWeightGround * max( dotProductGround, 0.0 ) );", + + "}", + + "#endif", + + // all lights contribution summation + + "vec3 totalDiffuse = vec3( 0.0 );", + "vec3 totalSpecular = vec3( 0.0 );", + + "#if MAX_DIR_LIGHTS > 0", + + "totalDiffuse += dirDiffuse;", + "totalSpecular += dirSpecular;", + + "#endif", + + "#if MAX_HEMI_LIGHTS > 0", + + "totalDiffuse += hemiDiffuse;", + "totalSpecular += hemiSpecular;", + + "#endif", + + "#if MAX_POINT_LIGHTS > 0", + + "totalDiffuse += pointDiffuse;", + "totalSpecular += pointSpecular;", + + "#endif", + + "#if MAX_SPOT_LIGHTS > 0", + + "totalDiffuse += spotDiffuse;", + "totalSpecular += spotSpecular;", + + "#endif", + + "#ifdef METAL", + + "gl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * ambient + totalSpecular );", + + "#else", + + "gl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * ambient ) + totalSpecular;", + + "#endif", + + "if ( enableReflection ) {", + + "vec3 vReflect;", + "vec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );", + + "if ( useRefract ) {", + + "vReflect = refract( cameraToVertex, normal, refractionRatio );", + + "} else {", + + "vReflect = reflect( cameraToVertex, normal );", + + "}", + + "vec4 cubeColor = textureCube( tCube, vec3( -vReflect.x, vReflect.yz ) );", + + "#ifdef GAMMA_INPUT", + + "cubeColor.xyz *= cubeColor.xyz;", + + "#endif", + + "gl_FragColor.xyz = mix( gl_FragColor.xyz, cubeColor.xyz, specularTex.r * reflectivity );", + + "}", + + THREE.ShaderChunk[ "shadowmap_fragment" ], + THREE.ShaderChunk[ "linear_to_gamma_fragment" ], + THREE.ShaderChunk[ "fog_fragment" ], + + "}" + + ].join("\n"), + + vertexShader: [ + + "attribute vec4 tangent;", + + "uniform vec2 uOffset;", + "uniform vec2 uRepeat;", + + "uniform bool enableDisplacement;", + + "#ifdef VERTEX_TEXTURES", + + "uniform sampler2D tDisplacement;", + "uniform float uDisplacementScale;", + "uniform float uDisplacementBias;", + + "#endif", + + "varying vec3 vTangent;", + "varying vec3 vBinormal;", + "varying vec3 vNormal;", + "varying vec2 vUv;", + + "varying vec3 vWorldPosition;", + "varying vec3 vViewPosition;", + + THREE.ShaderChunk[ "skinning_pars_vertex" ], + THREE.ShaderChunk[ "shadowmap_pars_vertex" ], + + "void main() {", + + THREE.ShaderChunk[ "skinbase_vertex" ], + THREE.ShaderChunk[ "skinnormal_vertex" ], + + // normal, tangent and binormal vectors + + "#ifdef USE_SKINNING", + + "vNormal = normalize( normalMatrix * skinnedNormal.xyz );", + + "vec4 skinnedTangent = skinMatrix * vec4( tangent.xyz, 0.0 );", + "vTangent = normalize( normalMatrix * skinnedTangent.xyz );", + + "#else", + + "vNormal = normalize( normalMatrix * normal );", + "vTangent = normalize( normalMatrix * tangent.xyz );", + + "#endif", + + "vBinormal = normalize( cross( vNormal, vTangent ) * tangent.w );", + + "vUv = uv * uRepeat + uOffset;", + + // displacement mapping + + "vec3 displacedPosition;", + + "#ifdef VERTEX_TEXTURES", + + "if ( enableDisplacement ) {", + + "vec3 dv = texture2D( tDisplacement, uv ).xyz;", + "float df = uDisplacementScale * dv.x + uDisplacementBias;", + "displacedPosition = position + normalize( normal ) * df;", + + "} else {", + + "#ifdef USE_SKINNING", + + "vec4 skinVertex = vec4( position, 1.0 );", + + "vec4 skinned = boneMatX * skinVertex * skinWeight.x;", + "skinned += boneMatY * skinVertex * skinWeight.y;", + + "displacedPosition = skinned.xyz;", + + "#else", + + "displacedPosition = position;", + + "#endif", + + "}", + + "#else", + + "#ifdef USE_SKINNING", + + "vec4 skinVertex = vec4( position, 1.0 );", + + "vec4 skinned = boneMatX * skinVertex * skinWeight.x;", + "skinned += boneMatY * skinVertex * skinWeight.y;", + + "displacedPosition = skinned.xyz;", + + "#else", + + "displacedPosition = position;", + + "#endif", + + "#endif", + + // + + "vec4 mvPosition = modelViewMatrix * vec4( displacedPosition, 1.0 );", + "vec4 worldPosition = modelMatrix * vec4( displacedPosition, 1.0 );", + + "gl_Position = projectionMatrix * mvPosition;", + + // + + "vWorldPosition = worldPosition.xyz;", + "vViewPosition = -mvPosition.xyz;", + + // shadows + + "#ifdef USE_SHADOWMAP", + + "for( int i = 0; i < MAX_SHADOWS; i ++ ) {", + + "vShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;", + + "}", + + "#endif", + + "}" + + ].join("\n") + + }, + + /* ------------------------------------------------------------------------- + // Cube map shader + ------------------------------------------------------------------------- */ + + 'cube': { + + uniforms: { "tCube": { type: "t", value: null }, + "tFlip": { type: "f", value: -1 } }, + + vertexShader: [ + + "varying vec3 vWorldPosition;", + + "void main() {", + + "vec4 worldPosition = modelMatrix * vec4( position, 1.0 );", + "vWorldPosition = worldPosition.xyz;", + + "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform samplerCube tCube;", + "uniform float tFlip;", + + "varying vec3 vWorldPosition;", + + "void main() {", + + "gl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );", + + "}" + + ].join("\n") + + }, + + // Depth encoding into RGBA texture + // based on SpiderGL shadow map example + // http://spidergl.org/example.php?id=6 + // originally from + // http://www.gamedev.net/topic/442138-packing-a-float-into-a-a8r8g8b8-texture-shader/page__whichpage__1%25EF%25BF%25BD + // see also here: + // http://aras-p.info/blog/2009/07/30/encoding-floats-to-rgba-the-final/ + + 'depthRGBA': { + + uniforms: {}, + + vertexShader: [ + + THREE.ShaderChunk[ "morphtarget_pars_vertex" ], + THREE.ShaderChunk[ "skinning_pars_vertex" ], + + "void main() {", + + THREE.ShaderChunk[ "skinbase_vertex" ], + THREE.ShaderChunk[ "morphtarget_vertex" ], + THREE.ShaderChunk[ "skinning_vertex" ], + THREE.ShaderChunk[ "default_vertex" ], + + "}" + + ].join("\n"), + + fragmentShader: [ + + "vec4 pack_depth( const in float depth ) {", + + "const vec4 bit_shift = vec4( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0 );", + "const vec4 bit_mask = vec4( 0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0 );", + "vec4 res = fract( depth * bit_shift );", + "res -= res.xxyz * bit_mask;", + "return res;", + + "}", + + "void main() {", + + "gl_FragData[ 0 ] = pack_depth( gl_FragCoord.z );", + + //"gl_FragData[ 0 ] = pack_depth( gl_FragCoord.z / gl_FragCoord.w );", + //"float z = ( ( gl_FragCoord.z / gl_FragCoord.w ) - 3.0 ) / ( 4000.0 - 3.0 );", + //"gl_FragData[ 0 ] = pack_depth( z );", + //"gl_FragData[ 0 ] = vec4( z, z, z, 1.0 );", + + "}" + + ].join("\n") + + } + +}; + +/** + * @author supereggbert / http://www.paulbrunt.co.uk/ + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * @author szimek / https://github.com/szimek/ + */ + +THREE.WebGLRenderer = function ( parameters ) { + + console.log( 'THREE.WebGLRenderer', THREE.REVISION ); + + parameters = parameters || {}; + + var _canvas = parameters.canvas !== undefined ? parameters.canvas : document.createElement( 'canvas' ), + _context = parameters.context !== undefined ? parameters.context : null, + + _precision = parameters.precision !== undefined ? parameters.precision : 'highp', + + _buffers = {}, + + _alpha = parameters.alpha !== undefined ? parameters.alpha : false, + _premultipliedAlpha = parameters.premultipliedAlpha !== undefined ? parameters.premultipliedAlpha : true, + _antialias = parameters.antialias !== undefined ? parameters.antialias : false, + _stencil = parameters.stencil !== undefined ? parameters.stencil : true, + _preserveDrawingBuffer = parameters.preserveDrawingBuffer !== undefined ? parameters.preserveDrawingBuffer : false, + + _clearColor = new THREE.Color( 0x000000 ), + _clearAlpha = 0; + + // public properties + + this.domElement = _canvas; + this.context = null; + this.devicePixelRatio = parameters.devicePixelRatio !== undefined + ? parameters.devicePixelRatio + : self.devicePixelRatio !== undefined + ? self.devicePixelRatio + : 1; + + // clearing + + this.autoClear = true; + this.autoClearColor = true; + this.autoClearDepth = true; + this.autoClearStencil = true; + + // scene graph + + this.sortObjects = true; + this.autoUpdateObjects = true; + + // physically based shading + + this.gammaInput = false; + this.gammaOutput = false; + + // shadow map + + this.shadowMapEnabled = false; + this.shadowMapAutoUpdate = true; + this.shadowMapType = THREE.PCFShadowMap; + this.shadowMapCullFace = THREE.CullFaceFront; + this.shadowMapDebug = false; + this.shadowMapCascade = false; + + // morphs + + this.maxMorphTargets = 8; + this.maxMorphNormals = 4; + + // flags + + this.autoScaleCubemaps = true; + + // custom render plugins + + this.renderPluginsPre = []; + this.renderPluginsPost = []; + + // info + + this.info = { + + memory: { + + programs: 0, + geometries: 0, + textures: 0 + + }, + + render: { + + calls: 0, + vertices: 0, + faces: 0, + points: 0 + + } + + }; + + // internal properties + + var _this = this, + + _programs = [], + _programs_counter = 0, + + // internal state cache + + _currentProgram = null, + _currentFramebuffer = null, + _currentMaterialId = -1, + _currentGeometryGroupHash = null, + _currentCamera = null, + + _usedTextureUnits = 0, + + // GL state cache + + _oldDoubleSided = -1, + _oldFlipSided = -1, + + _oldBlending = -1, + + _oldBlendEquation = -1, + _oldBlendSrc = -1, + _oldBlendDst = -1, + + _oldDepthTest = -1, + _oldDepthWrite = -1, + + _oldPolygonOffset = null, + _oldPolygonOffsetFactor = null, + _oldPolygonOffsetUnits = null, + + _oldLineWidth = null, + + _viewportX = 0, + _viewportY = 0, + _viewportWidth = _canvas.width, + _viewportHeight = _canvas.height, + _currentWidth = 0, + _currentHeight = 0, + + _enabledAttributes = new Uint8Array( 16 ), + + // frustum + + _frustum = new THREE.Frustum(), + + // camera matrices cache + + _projScreenMatrix = new THREE.Matrix4(), + _projScreenMatrixPS = new THREE.Matrix4(), + + _vector3 = new THREE.Vector3(), + + // light arrays cache + + _direction = new THREE.Vector3(), + + _lightsNeedUpdate = true, + + _lights = { + + ambient: [ 0, 0, 0 ], + directional: { length: 0, colors: new Array(), positions: new Array() }, + point: { length: 0, colors: new Array(), positions: new Array(), distances: new Array() }, + spot: { length: 0, colors: new Array(), positions: new Array(), distances: new Array(), directions: new Array(), anglesCos: new Array(), exponents: new Array() }, + hemi: { length: 0, skyColors: new Array(), groundColors: new Array(), positions: new Array() } + + }; + + // initialize + + var _gl; + + var _glExtensionTextureFloat; + var _glExtensionTextureFloatLinear; + var _glExtensionStandardDerivatives; + var _glExtensionTextureFilterAnisotropic; + var _glExtensionCompressedTextureS3TC; + + initGL(); + + setDefaultGLState(); + + this.context = _gl; + + // GPU capabilities + + var _maxTextures = _gl.getParameter( _gl.MAX_TEXTURE_IMAGE_UNITS ); + var _maxVertexTextures = _gl.getParameter( _gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ); + var _maxTextureSize = _gl.getParameter( _gl.MAX_TEXTURE_SIZE ); + var _maxCubemapSize = _gl.getParameter( _gl.MAX_CUBE_MAP_TEXTURE_SIZE ); + + var _maxAnisotropy = _glExtensionTextureFilterAnisotropic ? _gl.getParameter( _glExtensionTextureFilterAnisotropic.MAX_TEXTURE_MAX_ANISOTROPY_EXT ) : 0; + + var _supportsVertexTextures = ( _maxVertexTextures > 0 ); + var _supportsBoneTextures = _supportsVertexTextures && _glExtensionTextureFloat; + + var _compressedTextureFormats = _glExtensionCompressedTextureS3TC ? _gl.getParameter( _gl.COMPRESSED_TEXTURE_FORMATS ) : []; + + // + + var _vertexShaderPrecisionHighpFloat = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.HIGH_FLOAT ); + var _vertexShaderPrecisionMediumpFloat = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.MEDIUM_FLOAT ); + var _vertexShaderPrecisionLowpFloat = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.LOW_FLOAT ); + + var _fragmentShaderPrecisionHighpFloat = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.HIGH_FLOAT ); + var _fragmentShaderPrecisionMediumpFloat = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.MEDIUM_FLOAT ); + var _fragmentShaderPrecisionLowpFloat = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.LOW_FLOAT ); + + var _vertexShaderPrecisionHighpInt = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.HIGH_INT ); + var _vertexShaderPrecisionMediumpInt = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.MEDIUM_INT ); + var _vertexShaderPrecisionLowpInt = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.LOW_INT ); + + var _fragmentShaderPrecisionHighpInt = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.HIGH_INT ); + var _fragmentShaderPrecisionMediumpInt = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.MEDIUM_INT ); + var _fragmentShaderPrecisionLowpInt = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.LOW_INT ); + + // clamp precision to maximum available + + var highpAvailable = _vertexShaderPrecisionHighpFloat.precision > 0 && _fragmentShaderPrecisionHighpFloat.precision > 0; + var mediumpAvailable = _vertexShaderPrecisionMediumpFloat.precision > 0 && _fragmentShaderPrecisionMediumpFloat.precision > 0; + + if ( _precision === "highp" && ! highpAvailable ) { + + if ( mediumpAvailable ) { + + _precision = "mediump"; + console.warn( "WebGLRenderer: highp not supported, using mediump" ); + + } else { + + _precision = "lowp"; + console.warn( "WebGLRenderer: highp and mediump not supported, using lowp" ); + + } + + } + + if ( _precision === "mediump" && ! mediumpAvailable ) { + + _precision = "lowp"; + console.warn( "WebGLRenderer: mediump not supported, using lowp" ); + + } + + // API + + this.getContext = function () { + + return _gl; + + }; + + this.supportsVertexTextures = function () { + + return _supportsVertexTextures; + + }; + + this.supportsFloatTextures = function () { + + return _glExtensionTextureFloat; + + }; + + this.supportsStandardDerivatives = function () { + + return _glExtensionStandardDerivatives; + + }; + + this.supportsCompressedTextureS3TC = function () { + + return _glExtensionCompressedTextureS3TC; + + }; + + this.getMaxAnisotropy = function () { + + return _maxAnisotropy; + + }; + + this.getPrecision = function () { + + return _precision; + + }; + + this.setSize = function ( width, height, updateStyle ) { + + _canvas.width = width * this.devicePixelRatio; + _canvas.height = height * this.devicePixelRatio; + + if ( this.devicePixelRatio !== 1 && updateStyle !== false ) { + + _canvas.style.width = width + 'px'; + _canvas.style.height = height + 'px'; + + } + + this.setViewport( 0, 0, width, height ); + + }; + + this.setViewport = function ( x, y, width, height ) { + + _viewportX = x * this.devicePixelRatio; + _viewportY = y * this.devicePixelRatio; + + _viewportWidth = width * this.devicePixelRatio; + _viewportHeight = height * this.devicePixelRatio; + + _gl.viewport( _viewportX, _viewportY, _viewportWidth, _viewportHeight ); + + }; + + this.setScissor = function ( x, y, width, height ) { + + _gl.scissor( + x * this.devicePixelRatio, + y * this.devicePixelRatio, + width * this.devicePixelRatio, + height * this.devicePixelRatio + ); + + }; + + this.enableScissorTest = function ( enable ) { + + enable ? _gl.enable( _gl.SCISSOR_TEST ) : _gl.disable( _gl.SCISSOR_TEST ); + + }; + + // Clearing + + this.setClearColor = function ( color, alpha ) { + + _clearColor.set( color ); + _clearAlpha = alpha !== undefined ? alpha : 1; + + _gl.clearColor( _clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha ); + + }; + + this.setClearColorHex = function ( hex, alpha ) { + + console.warn( 'DEPRECATED: .setClearColorHex() is being removed. Use .setClearColor() instead.' ); + this.setClearColor( hex, alpha ); + + }; + + this.getClearColor = function () { + + return _clearColor; + + }; + + this.getClearAlpha = function () { + + return _clearAlpha; + + }; + + this.clear = function ( color, depth, stencil ) { + + var bits = 0; + + if ( color === undefined || color ) bits |= _gl.COLOR_BUFFER_BIT; + if ( depth === undefined || depth ) bits |= _gl.DEPTH_BUFFER_BIT; + if ( stencil === undefined || stencil ) bits |= _gl.STENCIL_BUFFER_BIT; + + _gl.clear( bits ); + + }; + + this.clearColor = function () { + + _gl.clear( _gl.COLOR_BUFFER_BIT ); + + }; + + this.clearDepth = function () { + + _gl.clear( _gl.DEPTH_BUFFER_BIT ); + + }; + + this.clearStencil = function () { + + _gl.clear( _gl.STENCIL_BUFFER_BIT ); + + }; + + this.clearTarget = function ( renderTarget, color, depth, stencil ) { + + this.setRenderTarget( renderTarget ); + this.clear( color, depth, stencil ); + + }; + + // Plugins + + this.addPostPlugin = function ( plugin ) { + + plugin.init( this ); + this.renderPluginsPost.push( plugin ); + + }; + + this.addPrePlugin = function ( plugin ) { + + plugin.init( this ); + this.renderPluginsPre.push( plugin ); + + }; + + // Rendering + + this.updateShadowMap = function ( scene, camera ) { + + _currentProgram = null; + _oldBlending = -1; + _oldDepthTest = -1; + _oldDepthWrite = -1; + _currentGeometryGroupHash = -1; + _currentMaterialId = -1; + _lightsNeedUpdate = true; + _oldDoubleSided = -1; + _oldFlipSided = -1; + + this.shadowMapPlugin.update( scene, camera ); + + }; + + // Internal functions + + // Buffer allocation + + function createParticleBuffers ( geometry ) { + + geometry.__webglVertexBuffer = _gl.createBuffer(); + geometry.__webglColorBuffer = _gl.createBuffer(); + + _this.info.memory.geometries ++; + + }; + + function createLineBuffers ( geometry ) { + + geometry.__webglVertexBuffer = _gl.createBuffer(); + geometry.__webglColorBuffer = _gl.createBuffer(); + geometry.__webglLineDistanceBuffer = _gl.createBuffer(); + + _this.info.memory.geometries ++; + + }; + + function createMeshBuffers ( geometryGroup ) { + + geometryGroup.__webglVertexBuffer = _gl.createBuffer(); + geometryGroup.__webglNormalBuffer = _gl.createBuffer(); + geometryGroup.__webglTangentBuffer = _gl.createBuffer(); + geometryGroup.__webglColorBuffer = _gl.createBuffer(); + geometryGroup.__webglUVBuffer = _gl.createBuffer(); + geometryGroup.__webglUV2Buffer = _gl.createBuffer(); + + geometryGroup.__webglSkinIndicesBuffer = _gl.createBuffer(); + geometryGroup.__webglSkinWeightsBuffer = _gl.createBuffer(); + + geometryGroup.__webglFaceBuffer = _gl.createBuffer(); + geometryGroup.__webglLineBuffer = _gl.createBuffer(); + + var m, ml; + + if ( geometryGroup.numMorphTargets ) { + + geometryGroup.__webglMorphTargetsBuffers = []; + + for ( m = 0, ml = geometryGroup.numMorphTargets; m < ml; m ++ ) { + + geometryGroup.__webglMorphTargetsBuffers.push( _gl.createBuffer() ); + + } + + } + + if ( geometryGroup.numMorphNormals ) { + + geometryGroup.__webglMorphNormalsBuffers = []; + + for ( m = 0, ml = geometryGroup.numMorphNormals; m < ml; m ++ ) { + + geometryGroup.__webglMorphNormalsBuffers.push( _gl.createBuffer() ); + + } + + } + + _this.info.memory.geometries ++; + + }; + + // Events + + var onGeometryDispose = function ( event ) { + + var geometry = event.target; + + geometry.removeEventListener( 'dispose', onGeometryDispose ); + + deallocateGeometry( geometry ); + + }; + + var onTextureDispose = function ( event ) { + + var texture = event.target; + + texture.removeEventListener( 'dispose', onTextureDispose ); + + deallocateTexture( texture ); + + _this.info.memory.textures --; + + + }; + + var onRenderTargetDispose = function ( event ) { + + var renderTarget = event.target; + + renderTarget.removeEventListener( 'dispose', onRenderTargetDispose ); + + deallocateRenderTarget( renderTarget ); + + _this.info.memory.textures --; + + }; + + var onMaterialDispose = function ( event ) { + + var material = event.target; + + material.removeEventListener( 'dispose', onMaterialDispose ); + + deallocateMaterial( material ); + + }; + + // Buffer deallocation + + var deleteBuffers = function ( geometry ) { + + if ( geometry.__webglVertexBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglVertexBuffer ); + if ( geometry.__webglNormalBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglNormalBuffer ); + if ( geometry.__webglTangentBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglTangentBuffer ); + if ( geometry.__webglColorBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglColorBuffer ); + if ( geometry.__webglUVBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglUVBuffer ); + if ( geometry.__webglUV2Buffer !== undefined ) _gl.deleteBuffer( geometry.__webglUV2Buffer ); + + if ( geometry.__webglSkinIndicesBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglSkinIndicesBuffer ); + if ( geometry.__webglSkinWeightsBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglSkinWeightsBuffer ); + + if ( geometry.__webglFaceBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglFaceBuffer ); + if ( geometry.__webglLineBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglLineBuffer ); + + if ( geometry.__webglLineDistanceBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglLineDistanceBuffer ); + // custom attributes + + if ( geometry.__webglCustomAttributesList !== undefined ) { + + for ( var id in geometry.__webglCustomAttributesList ) { + + _gl.deleteBuffer( geometry.__webglCustomAttributesList[ id ].buffer ); + + } + + } + + _this.info.memory.geometries --; + + }; + + var deallocateGeometry = function ( geometry ) { + + geometry.__webglInit = undefined; + + if ( geometry instanceof THREE.BufferGeometry ) { + + var attributes = geometry.attributes; + + for ( var key in attributes ) { + + if ( attributes[ key ].buffer !== undefined ) { + + _gl.deleteBuffer( attributes[ key ].buffer ); + + } + + } + + _this.info.memory.geometries --; + + } else { + + if ( geometry.geometryGroups !== undefined ) { + + for ( var g in geometry.geometryGroups ) { + + var geometryGroup = geometry.geometryGroups[ g ]; + + if ( geometryGroup.numMorphTargets !== undefined ) { + + for ( var m = 0, ml = geometryGroup.numMorphTargets; m < ml; m ++ ) { + + _gl.deleteBuffer( geometryGroup.__webglMorphTargetsBuffers[ m ] ); + + } + + } + + if ( geometryGroup.numMorphNormals !== undefined ) { + + for ( var m = 0, ml = geometryGroup.numMorphNormals; m < ml; m ++ ) { + + _gl.deleteBuffer( geometryGroup.__webglMorphNormalsBuffers[ m ] ); + + } + + } + + deleteBuffers( geometryGroup ); + + } + + } else { + + deleteBuffers( geometry ); + + } + + } + + }; + + var deallocateTexture = function ( texture ) { + + if ( texture.image && texture.image.__webglTextureCube ) { + + // cube texture + + _gl.deleteTexture( texture.image.__webglTextureCube ); + + } else { + + // 2D texture + + if ( ! texture.__webglInit ) return; + + texture.__webglInit = false; + _gl.deleteTexture( texture.__webglTexture ); + + } + + }; + + var deallocateRenderTarget = function ( renderTarget ) { + + if ( !renderTarget || ! renderTarget.__webglTexture ) return; + + _gl.deleteTexture( renderTarget.__webglTexture ); + + if ( renderTarget instanceof THREE.WebGLRenderTargetCube ) { + + for ( var i = 0; i < 6; i ++ ) { + + _gl.deleteFramebuffer( renderTarget.__webglFramebuffer[ i ] ); + _gl.deleteRenderbuffer( renderTarget.__webglRenderbuffer[ i ] ); + + } + + } else { + + _gl.deleteFramebuffer( renderTarget.__webglFramebuffer ); + _gl.deleteRenderbuffer( renderTarget.__webglRenderbuffer ); + + } + + }; + + var deallocateMaterial = function ( material ) { + + var program = material.program; + + if ( program === undefined ) return; + + material.program = undefined; + + // only deallocate GL program if this was the last use of shared program + // assumed there is only single copy of any program in the _programs list + // (that's how it's constructed) + + var i, il, programInfo; + var deleteProgram = false; + + for ( i = 0, il = _programs.length; i < il; i ++ ) { + + programInfo = _programs[ i ]; + + if ( programInfo.program === program ) { + + programInfo.usedTimes --; + + if ( programInfo.usedTimes === 0 ) { + + deleteProgram = true; + + } + + break; + + } + + } + + if ( deleteProgram === true ) { + + // avoid using array.splice, this is costlier than creating new array from scratch + + var newPrograms = []; + + for ( i = 0, il = _programs.length; i < il; i ++ ) { + + programInfo = _programs[ i ]; + + if ( programInfo.program !== program ) { + + newPrograms.push( programInfo ); + + } + + } + + _programs = newPrograms; + + _gl.deleteProgram( program ); + + _this.info.memory.programs --; + + } + + }; + + // Buffer initialization + + function initCustomAttributes ( geometry, object ) { + + var nvertices = geometry.vertices.length; + + var material = object.material; + + if ( material.attributes ) { + + if ( geometry.__webglCustomAttributesList === undefined ) { + + geometry.__webglCustomAttributesList = []; + + } + + for ( var a in material.attributes ) { + + var attribute = material.attributes[ a ]; + + if ( !attribute.__webglInitialized || attribute.createUniqueBuffers ) { + + attribute.__webglInitialized = true; + + var size = 1; // "f" and "i" + + if ( attribute.type === "v2" ) size = 2; + else if ( attribute.type === "v3" ) size = 3; + else if ( attribute.type === "v4" ) size = 4; + else if ( attribute.type === "c" ) size = 3; + + attribute.size = size; + + attribute.array = new Float32Array( nvertices * size ); + + attribute.buffer = _gl.createBuffer(); + attribute.buffer.belongsToAttribute = a; + + attribute.needsUpdate = true; + + } + + geometry.__webglCustomAttributesList.push( attribute ); + + } + + } + + }; + + function initParticleBuffers ( geometry, object ) { + + var nvertices = geometry.vertices.length; + + geometry.__vertexArray = new Float32Array( nvertices * 3 ); + geometry.__colorArray = new Float32Array( nvertices * 3 ); + + geometry.__sortArray = []; + + geometry.__webglParticleCount = nvertices; + + initCustomAttributes ( geometry, object ); + + }; + + function initLineBuffers ( geometry, object ) { + + var nvertices = geometry.vertices.length; + + geometry.__vertexArray = new Float32Array( nvertices * 3 ); + geometry.__colorArray = new Float32Array( nvertices * 3 ); + geometry.__lineDistanceArray = new Float32Array( nvertices * 1 ); + + geometry.__webglLineCount = nvertices; + + initCustomAttributes ( geometry, object ); + + }; + + function initMeshBuffers ( geometryGroup, object ) { + + var geometry = object.geometry, + faces3 = geometryGroup.faces3, + + nvertices = faces3.length * 3, + ntris = faces3.length * 1, + nlines = faces3.length * 3, + + material = getBufferMaterial( object, geometryGroup ), + + uvType = bufferGuessUVType( material ), + normalType = bufferGuessNormalType( material ), + vertexColorType = bufferGuessVertexColorType( material ); + + // console.log( "uvType", uvType, "normalType", normalType, "vertexColorType", vertexColorType, object, geometryGroup, material ); + + geometryGroup.__vertexArray = new Float32Array( nvertices * 3 ); + + if ( normalType ) { + + geometryGroup.__normalArray = new Float32Array( nvertices * 3 ); + + } + + if ( geometry.hasTangents ) { + + geometryGroup.__tangentArray = new Float32Array( nvertices * 4 ); + + } + + if ( vertexColorType ) { + + geometryGroup.__colorArray = new Float32Array( nvertices * 3 ); + + } + + if ( uvType ) { + + if ( geometry.faceVertexUvs.length > 0 ) { + + geometryGroup.__uvArray = new Float32Array( nvertices * 2 ); + + } + + if ( geometry.faceVertexUvs.length > 1 ) { + + geometryGroup.__uv2Array = new Float32Array( nvertices * 2 ); + + } + + } + + if ( object.geometry.skinWeights.length && object.geometry.skinIndices.length ) { + + geometryGroup.__skinIndexArray = new Float32Array( nvertices * 4 ); + geometryGroup.__skinWeightArray = new Float32Array( nvertices * 4 ); + + } + + geometryGroup.__faceArray = new Uint16Array( ntris * 3 ); + geometryGroup.__lineArray = new Uint16Array( nlines * 2 ); + + var m, ml; + + if ( geometryGroup.numMorphTargets ) { + + geometryGroup.__morphTargetsArrays = []; + + for ( m = 0, ml = geometryGroup.numMorphTargets; m < ml; m ++ ) { + + geometryGroup.__morphTargetsArrays.push( new Float32Array( nvertices * 3 ) ); + + } + + } + + if ( geometryGroup.numMorphNormals ) { + + geometryGroup.__morphNormalsArrays = []; + + for ( m = 0, ml = geometryGroup.numMorphNormals; m < ml; m ++ ) { + + geometryGroup.__morphNormalsArrays.push( new Float32Array( nvertices * 3 ) ); + + } + + } + + geometryGroup.__webglFaceCount = ntris * 3; + geometryGroup.__webglLineCount = nlines * 2; + + + // custom attributes + + if ( material.attributes ) { + + if ( geometryGroup.__webglCustomAttributesList === undefined ) { + + geometryGroup.__webglCustomAttributesList = []; + + } + + for ( var a in material.attributes ) { + + // Do a shallow copy of the attribute object so different geometryGroup chunks use different + // attribute buffers which are correctly indexed in the setMeshBuffers function + + var originalAttribute = material.attributes[ a ]; + + var attribute = {}; + + for ( var property in originalAttribute ) { + + attribute[ property ] = originalAttribute[ property ]; + + } + + if ( !attribute.__webglInitialized || attribute.createUniqueBuffers ) { + + attribute.__webglInitialized = true; + + var size = 1; // "f" and "i" + + if( attribute.type === "v2" ) size = 2; + else if( attribute.type === "v3" ) size = 3; + else if( attribute.type === "v4" ) size = 4; + else if( attribute.type === "c" ) size = 3; + + attribute.size = size; + + attribute.array = new Float32Array( nvertices * size ); + + attribute.buffer = _gl.createBuffer(); + attribute.buffer.belongsToAttribute = a; + + originalAttribute.needsUpdate = true; + attribute.__original = originalAttribute; + + } + + geometryGroup.__webglCustomAttributesList.push( attribute ); + + } + + } + + geometryGroup.__inittedArrays = true; + + }; + + function getBufferMaterial( object, geometryGroup ) { + + return object.material instanceof THREE.MeshFaceMaterial + ? object.material.materials[ geometryGroup.materialIndex ] + : object.material; + + }; + + function materialNeedsSmoothNormals ( material ) { + + return material && material.shading !== undefined && material.shading === THREE.SmoothShading; + + }; + + function bufferGuessNormalType ( material ) { + + // only MeshBasicMaterial and MeshDepthMaterial don't need normals + + if ( ( material instanceof THREE.MeshBasicMaterial && !material.envMap ) || material instanceof THREE.MeshDepthMaterial ) { + + return false; + + } + + if ( materialNeedsSmoothNormals( material ) ) { + + return THREE.SmoothShading; + + } else { + + return THREE.FlatShading; + + } + + }; + + function bufferGuessVertexColorType( material ) { + + if ( material.vertexColors ) { + + return material.vertexColors; + + } + + return false; + + }; + + function bufferGuessUVType( material ) { + + // material must use some texture to require uvs + + if ( material.map || + material.lightMap || + material.bumpMap || + material.normalMap || + material.specularMap || + material instanceof THREE.ShaderMaterial ) { + + return true; + + } + + return false; + + }; + + // + + function initDirectBuffers( geometry ) { + + var a, attribute, type; + + for ( a in geometry.attributes ) { + + if ( a === "index" ) { + + type = _gl.ELEMENT_ARRAY_BUFFER; + + } else { + + type = _gl.ARRAY_BUFFER; + + } + + attribute = geometry.attributes[ a ]; + + attribute.buffer = _gl.createBuffer(); + + _gl.bindBuffer( type, attribute.buffer ); + _gl.bufferData( type, attribute.array, _gl.STATIC_DRAW ); + + } + + }; + + // Buffer setting + + function setParticleBuffers ( geometry, hint, object ) { + + var v, c, vertex, offset, index, color, + + vertices = geometry.vertices, + vl = vertices.length, + + colors = geometry.colors, + cl = colors.length, + + vertexArray = geometry.__vertexArray, + colorArray = geometry.__colorArray, + + sortArray = geometry.__sortArray, + + dirtyVertices = geometry.verticesNeedUpdate, + dirtyElements = geometry.elementsNeedUpdate, + dirtyColors = geometry.colorsNeedUpdate, + + customAttributes = geometry.__webglCustomAttributesList, + i, il, + a, ca, cal, value, + customAttribute; + + if ( object.sortParticles ) { + + _projScreenMatrixPS.copy( _projScreenMatrix ); + _projScreenMatrixPS.multiply( object.matrixWorld ); + + for ( v = 0; v < vl; v ++ ) { + + vertex = vertices[ v ]; + + _vector3.copy( vertex ); + _vector3.applyProjection( _projScreenMatrixPS ); + + sortArray[ v ] = [ _vector3.z, v ]; + + } + + sortArray.sort( numericalSort ); + + for ( v = 0; v < vl; v ++ ) { + + vertex = vertices[ sortArray[v][1] ]; + + offset = v * 3; + + vertexArray[ offset ] = vertex.x; + vertexArray[ offset + 1 ] = vertex.y; + vertexArray[ offset + 2 ] = vertex.z; + + } + + for ( c = 0; c < cl; c ++ ) { + + offset = c * 3; + + color = colors[ sortArray[c][1] ]; + + colorArray[ offset ] = color.r; + colorArray[ offset + 1 ] = color.g; + colorArray[ offset + 2 ] = color.b; + + } + + if ( customAttributes ) { + + for ( i = 0, il = customAttributes.length; i < il; i ++ ) { + + customAttribute = customAttributes[ i ]; + + if ( ! ( customAttribute.boundTo === undefined || customAttribute.boundTo === "vertices" ) ) continue; + + offset = 0; + + cal = customAttribute.value.length; + + if ( customAttribute.size === 1 ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + index = sortArray[ ca ][ 1 ]; + + customAttribute.array[ ca ] = customAttribute.value[ index ]; + + } + + } else if ( customAttribute.size === 2 ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + index = sortArray[ ca ][ 1 ]; + + value = customAttribute.value[ index ]; + + customAttribute.array[ offset ] = value.x; + customAttribute.array[ offset + 1 ] = value.y; + + offset += 2; + + } + + } else if ( customAttribute.size === 3 ) { + + if ( customAttribute.type === "c" ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + index = sortArray[ ca ][ 1 ]; + + value = customAttribute.value[ index ]; + + customAttribute.array[ offset ] = value.r; + customAttribute.array[ offset + 1 ] = value.g; + customAttribute.array[ offset + 2 ] = value.b; + + offset += 3; + + } + + } else { + + for ( ca = 0; ca < cal; ca ++ ) { + + index = sortArray[ ca ][ 1 ]; + + value = customAttribute.value[ index ]; + + customAttribute.array[ offset ] = value.x; + customAttribute.array[ offset + 1 ] = value.y; + customAttribute.array[ offset + 2 ] = value.z; + + offset += 3; + + } + + } + + } else if ( customAttribute.size === 4 ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + index = sortArray[ ca ][ 1 ]; + + value = customAttribute.value[ index ]; + + customAttribute.array[ offset ] = value.x; + customAttribute.array[ offset + 1 ] = value.y; + customAttribute.array[ offset + 2 ] = value.z; + customAttribute.array[ offset + 3 ] = value.w; + + offset += 4; + + } + + } + + } + + } + + } else { + + if ( dirtyVertices ) { + + for ( v = 0; v < vl; v ++ ) { + + vertex = vertices[ v ]; + + offset = v * 3; + + vertexArray[ offset ] = vertex.x; + vertexArray[ offset + 1 ] = vertex.y; + vertexArray[ offset + 2 ] = vertex.z; + + } + + } + + if ( dirtyColors ) { + + for ( c = 0; c < cl; c ++ ) { + + color = colors[ c ]; + + offset = c * 3; + + colorArray[ offset ] = color.r; + colorArray[ offset + 1 ] = color.g; + colorArray[ offset + 2 ] = color.b; + + } + + } + + if ( customAttributes ) { + + for ( i = 0, il = customAttributes.length; i < il; i ++ ) { + + customAttribute = customAttributes[ i ]; + + if ( customAttribute.needsUpdate && + ( customAttribute.boundTo === undefined || + customAttribute.boundTo === "vertices") ) { + + cal = customAttribute.value.length; + + offset = 0; + + if ( customAttribute.size === 1 ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + customAttribute.array[ ca ] = customAttribute.value[ ca ]; + + } + + } else if ( customAttribute.size === 2 ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + value = customAttribute.value[ ca ]; + + customAttribute.array[ offset ] = value.x; + customAttribute.array[ offset + 1 ] = value.y; + + offset += 2; + + } + + } else if ( customAttribute.size === 3 ) { + + if ( customAttribute.type === "c" ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + value = customAttribute.value[ ca ]; + + customAttribute.array[ offset ] = value.r; + customAttribute.array[ offset + 1 ] = value.g; + customAttribute.array[ offset + 2 ] = value.b; + + offset += 3; + + } + + } else { + + for ( ca = 0; ca < cal; ca ++ ) { + + value = customAttribute.value[ ca ]; + + customAttribute.array[ offset ] = value.x; + customAttribute.array[ offset + 1 ] = value.y; + customAttribute.array[ offset + 2 ] = value.z; + + offset += 3; + + } + + } + + } else if ( customAttribute.size === 4 ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + value = customAttribute.value[ ca ]; + + customAttribute.array[ offset ] = value.x; + customAttribute.array[ offset + 1 ] = value.y; + customAttribute.array[ offset + 2 ] = value.z; + customAttribute.array[ offset + 3 ] = value.w; + + offset += 4; + + } + + } + + } + + } + + } + + } + + if ( dirtyVertices || object.sortParticles ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglVertexBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, vertexArray, hint ); + + } + + if ( dirtyColors || object.sortParticles ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglColorBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, colorArray, hint ); + + } + + if ( customAttributes ) { + + for ( i = 0, il = customAttributes.length; i < il; i ++ ) { + + customAttribute = customAttributes[ i ]; + + if ( customAttribute.needsUpdate || object.sortParticles ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, customAttribute.buffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, customAttribute.array, hint ); + + } + + } + + } + + + }; + + function setLineBuffers ( geometry, hint ) { + + var v, c, d, vertex, offset, color, + + vertices = geometry.vertices, + colors = geometry.colors, + lineDistances = geometry.lineDistances, + + vl = vertices.length, + cl = colors.length, + dl = lineDistances.length, + + vertexArray = geometry.__vertexArray, + colorArray = geometry.__colorArray, + lineDistanceArray = geometry.__lineDistanceArray, + + dirtyVertices = geometry.verticesNeedUpdate, + dirtyColors = geometry.colorsNeedUpdate, + dirtyLineDistances = geometry.lineDistancesNeedUpdate, + + customAttributes = geometry.__webglCustomAttributesList, + + i, il, + a, ca, cal, value, + customAttribute; + + if ( dirtyVertices ) { + + for ( v = 0; v < vl; v ++ ) { + + vertex = vertices[ v ]; + + offset = v * 3; + + vertexArray[ offset ] = vertex.x; + vertexArray[ offset + 1 ] = vertex.y; + vertexArray[ offset + 2 ] = vertex.z; + + } + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglVertexBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, vertexArray, hint ); + + } + + if ( dirtyColors ) { + + for ( c = 0; c < cl; c ++ ) { + + color = colors[ c ]; + + offset = c * 3; + + colorArray[ offset ] = color.r; + colorArray[ offset + 1 ] = color.g; + colorArray[ offset + 2 ] = color.b; + + } + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglColorBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, colorArray, hint ); + + } + + if ( dirtyLineDistances ) { + + for ( d = 0; d < dl; d ++ ) { + + lineDistanceArray[ d ] = lineDistances[ d ]; + + } + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglLineDistanceBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, lineDistanceArray, hint ); + + } + + if ( customAttributes ) { + + for ( i = 0, il = customAttributes.length; i < il; i ++ ) { + + customAttribute = customAttributes[ i ]; + + if ( customAttribute.needsUpdate && + ( customAttribute.boundTo === undefined || + customAttribute.boundTo === "vertices" ) ) { + + offset = 0; + + cal = customAttribute.value.length; + + if ( customAttribute.size === 1 ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + customAttribute.array[ ca ] = customAttribute.value[ ca ]; + + } + + } else if ( customAttribute.size === 2 ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + value = customAttribute.value[ ca ]; + + customAttribute.array[ offset ] = value.x; + customAttribute.array[ offset + 1 ] = value.y; + + offset += 2; + + } + + } else if ( customAttribute.size === 3 ) { + + if ( customAttribute.type === "c" ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + value = customAttribute.value[ ca ]; + + customAttribute.array[ offset ] = value.r; + customAttribute.array[ offset + 1 ] = value.g; + customAttribute.array[ offset + 2 ] = value.b; + + offset += 3; + + } + + } else { + + for ( ca = 0; ca < cal; ca ++ ) { + + value = customAttribute.value[ ca ]; + + customAttribute.array[ offset ] = value.x; + customAttribute.array[ offset + 1 ] = value.y; + customAttribute.array[ offset + 2 ] = value.z; + + offset += 3; + + } + + } + + } else if ( customAttribute.size === 4 ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + value = customAttribute.value[ ca ]; + + customAttribute.array[ offset ] = value.x; + customAttribute.array[ offset + 1 ] = value.y; + customAttribute.array[ offset + 2 ] = value.z; + customAttribute.array[ offset + 3 ] = value.w; + + offset += 4; + + } + + } + + _gl.bindBuffer( _gl.ARRAY_BUFFER, customAttribute.buffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, customAttribute.array, hint ); + + } + + } + + } + + }; + + function setMeshBuffers( geometryGroup, object, hint, dispose, material ) { + + if ( ! geometryGroup.__inittedArrays ) { + + return; + + } + + var normalType = bufferGuessNormalType( material ), + vertexColorType = bufferGuessVertexColorType( material ), + uvType = bufferGuessUVType( material ), + + needsSmoothNormals = ( normalType === THREE.SmoothShading ); + + var f, fl, fi, face, + vertexNormals, faceNormal, normal, + vertexColors, faceColor, + vertexTangents, + uv, uv2, v1, v2, v3, v4, t1, t2, t3, t4, n1, n2, n3, n4, + c1, c2, c3, c4, + sw1, sw2, sw3, sw4, + si1, si2, si3, si4, + sa1, sa2, sa3, sa4, + sb1, sb2, sb3, sb4, + m, ml, i, il, + vn, uvi, uv2i, + vk, vkl, vka, + nka, chf, faceVertexNormals, + a, + + vertexIndex = 0, + + offset = 0, + offset_uv = 0, + offset_uv2 = 0, + offset_face = 0, + offset_normal = 0, + offset_tangent = 0, + offset_line = 0, + offset_color = 0, + offset_skin = 0, + offset_morphTarget = 0, + offset_custom = 0, + offset_customSrc = 0, + + value, + + vertexArray = geometryGroup.__vertexArray, + uvArray = geometryGroup.__uvArray, + uv2Array = geometryGroup.__uv2Array, + normalArray = geometryGroup.__normalArray, + tangentArray = geometryGroup.__tangentArray, + colorArray = geometryGroup.__colorArray, + + skinIndexArray = geometryGroup.__skinIndexArray, + skinWeightArray = geometryGroup.__skinWeightArray, + + morphTargetsArrays = geometryGroup.__morphTargetsArrays, + morphNormalsArrays = geometryGroup.__morphNormalsArrays, + + customAttributes = geometryGroup.__webglCustomAttributesList, + customAttribute, + + faceArray = geometryGroup.__faceArray, + lineArray = geometryGroup.__lineArray, + + geometry = object.geometry, // this is shared for all chunks + + dirtyVertices = geometry.verticesNeedUpdate, + dirtyElements = geometry.elementsNeedUpdate, + dirtyUvs = geometry.uvsNeedUpdate, + dirtyNormals = geometry.normalsNeedUpdate, + dirtyTangents = geometry.tangentsNeedUpdate, + dirtyColors = geometry.colorsNeedUpdate, + dirtyMorphTargets = geometry.morphTargetsNeedUpdate, + + vertices = geometry.vertices, + chunk_faces3 = geometryGroup.faces3, + obj_faces = geometry.faces, + + obj_uvs = geometry.faceVertexUvs[ 0 ], + obj_uvs2 = geometry.faceVertexUvs[ 1 ], + + obj_colors = geometry.colors, + + obj_skinIndices = geometry.skinIndices, + obj_skinWeights = geometry.skinWeights, + + morphTargets = geometry.morphTargets, + morphNormals = geometry.morphNormals; + + if ( dirtyVertices ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + face = obj_faces[ chunk_faces3[ f ] ]; + + v1 = vertices[ face.a ]; + v2 = vertices[ face.b ]; + v3 = vertices[ face.c ]; + + vertexArray[ offset ] = v1.x; + vertexArray[ offset + 1 ] = v1.y; + vertexArray[ offset + 2 ] = v1.z; + + vertexArray[ offset + 3 ] = v2.x; + vertexArray[ offset + 4 ] = v2.y; + vertexArray[ offset + 5 ] = v2.z; + + vertexArray[ offset + 6 ] = v3.x; + vertexArray[ offset + 7 ] = v3.y; + vertexArray[ offset + 8 ] = v3.z; + + offset += 9; + + } + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglVertexBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, vertexArray, hint ); + + } + + if ( dirtyMorphTargets ) { + + for ( vk = 0, vkl = morphTargets.length; vk < vkl; vk ++ ) { + + offset_morphTarget = 0; + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + chf = chunk_faces3[ f ]; + face = obj_faces[ chf ]; + + // morph positions + + v1 = morphTargets[ vk ].vertices[ face.a ]; + v2 = morphTargets[ vk ].vertices[ face.b ]; + v3 = morphTargets[ vk ].vertices[ face.c ]; + + vka = morphTargetsArrays[ vk ]; + + vka[ offset_morphTarget ] = v1.x; + vka[ offset_morphTarget + 1 ] = v1.y; + vka[ offset_morphTarget + 2 ] = v1.z; + + vka[ offset_morphTarget + 3 ] = v2.x; + vka[ offset_morphTarget + 4 ] = v2.y; + vka[ offset_morphTarget + 5 ] = v2.z; + + vka[ offset_morphTarget + 6 ] = v3.x; + vka[ offset_morphTarget + 7 ] = v3.y; + vka[ offset_morphTarget + 8 ] = v3.z; + + // morph normals + + if ( material.morphNormals ) { + + if ( needsSmoothNormals ) { + + faceVertexNormals = morphNormals[ vk ].vertexNormals[ chf ]; + + n1 = faceVertexNormals.a; + n2 = faceVertexNormals.b; + n3 = faceVertexNormals.c; + + } else { + + n1 = morphNormals[ vk ].faceNormals[ chf ]; + n2 = n1; + n3 = n1; + + } + + nka = morphNormalsArrays[ vk ]; + + nka[ offset_morphTarget ] = n1.x; + nka[ offset_morphTarget + 1 ] = n1.y; + nka[ offset_morphTarget + 2 ] = n1.z; + + nka[ offset_morphTarget + 3 ] = n2.x; + nka[ offset_morphTarget + 4 ] = n2.y; + nka[ offset_morphTarget + 5 ] = n2.z; + + nka[ offset_morphTarget + 6 ] = n3.x; + nka[ offset_morphTarget + 7 ] = n3.y; + nka[ offset_morphTarget + 8 ] = n3.z; + + } + + // + + offset_morphTarget += 9; + + } + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[ vk ] ); + _gl.bufferData( _gl.ARRAY_BUFFER, morphTargetsArrays[ vk ], hint ); + + if ( material.morphNormals ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphNormalsBuffers[ vk ] ); + _gl.bufferData( _gl.ARRAY_BUFFER, morphNormalsArrays[ vk ], hint ); + + } + + } + + } + + if ( obj_skinWeights.length ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + face = obj_faces[ chunk_faces3[ f ] ]; + + // weights + + sw1 = obj_skinWeights[ face.a ]; + sw2 = obj_skinWeights[ face.b ]; + sw3 = obj_skinWeights[ face.c ]; + + skinWeightArray[ offset_skin ] = sw1.x; + skinWeightArray[ offset_skin + 1 ] = sw1.y; + skinWeightArray[ offset_skin + 2 ] = sw1.z; + skinWeightArray[ offset_skin + 3 ] = sw1.w; + + skinWeightArray[ offset_skin + 4 ] = sw2.x; + skinWeightArray[ offset_skin + 5 ] = sw2.y; + skinWeightArray[ offset_skin + 6 ] = sw2.z; + skinWeightArray[ offset_skin + 7 ] = sw2.w; + + skinWeightArray[ offset_skin + 8 ] = sw3.x; + skinWeightArray[ offset_skin + 9 ] = sw3.y; + skinWeightArray[ offset_skin + 10 ] = sw3.z; + skinWeightArray[ offset_skin + 11 ] = sw3.w; + + // indices + + si1 = obj_skinIndices[ face.a ]; + si2 = obj_skinIndices[ face.b ]; + si3 = obj_skinIndices[ face.c ]; + + skinIndexArray[ offset_skin ] = si1.x; + skinIndexArray[ offset_skin + 1 ] = si1.y; + skinIndexArray[ offset_skin + 2 ] = si1.z; + skinIndexArray[ offset_skin + 3 ] = si1.w; + + skinIndexArray[ offset_skin + 4 ] = si2.x; + skinIndexArray[ offset_skin + 5 ] = si2.y; + skinIndexArray[ offset_skin + 6 ] = si2.z; + skinIndexArray[ offset_skin + 7 ] = si2.w; + + skinIndexArray[ offset_skin + 8 ] = si3.x; + skinIndexArray[ offset_skin + 9 ] = si3.y; + skinIndexArray[ offset_skin + 10 ] = si3.z; + skinIndexArray[ offset_skin + 11 ] = si3.w; + + offset_skin += 12; + + } + + if ( offset_skin > 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglSkinIndicesBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, skinIndexArray, hint ); + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglSkinWeightsBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, skinWeightArray, hint ); + + } + + } + + if ( dirtyColors && vertexColorType ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + face = obj_faces[ chunk_faces3[ f ] ]; + + vertexColors = face.vertexColors; + faceColor = face.color; + + if ( vertexColors.length === 3 && vertexColorType === THREE.VertexColors ) { + + c1 = vertexColors[ 0 ]; + c2 = vertexColors[ 1 ]; + c3 = vertexColors[ 2 ]; + + } else { + + c1 = faceColor; + c2 = faceColor; + c3 = faceColor; + + } + + colorArray[ offset_color ] = c1.r; + colorArray[ offset_color + 1 ] = c1.g; + colorArray[ offset_color + 2 ] = c1.b; + + colorArray[ offset_color + 3 ] = c2.r; + colorArray[ offset_color + 4 ] = c2.g; + colorArray[ offset_color + 5 ] = c2.b; + + colorArray[ offset_color + 6 ] = c3.r; + colorArray[ offset_color + 7 ] = c3.g; + colorArray[ offset_color + 8 ] = c3.b; + + offset_color += 9; + + } + + if ( offset_color > 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglColorBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, colorArray, hint ); + + } + + } + + if ( dirtyTangents && geometry.hasTangents ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + face = obj_faces[ chunk_faces3[ f ] ]; + + vertexTangents = face.vertexTangents; + + t1 = vertexTangents[ 0 ]; + t2 = vertexTangents[ 1 ]; + t3 = vertexTangents[ 2 ]; + + tangentArray[ offset_tangent ] = t1.x; + tangentArray[ offset_tangent + 1 ] = t1.y; + tangentArray[ offset_tangent + 2 ] = t1.z; + tangentArray[ offset_tangent + 3 ] = t1.w; + + tangentArray[ offset_tangent + 4 ] = t2.x; + tangentArray[ offset_tangent + 5 ] = t2.y; + tangentArray[ offset_tangent + 6 ] = t2.z; + tangentArray[ offset_tangent + 7 ] = t2.w; + + tangentArray[ offset_tangent + 8 ] = t3.x; + tangentArray[ offset_tangent + 9 ] = t3.y; + tangentArray[ offset_tangent + 10 ] = t3.z; + tangentArray[ offset_tangent + 11 ] = t3.w; + + offset_tangent += 12; + + } + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglTangentBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, tangentArray, hint ); + + } + + if ( dirtyNormals && normalType ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + face = obj_faces[ chunk_faces3[ f ] ]; + + vertexNormals = face.vertexNormals; + faceNormal = face.normal; + + if ( vertexNormals.length === 3 && needsSmoothNormals ) { + + for ( i = 0; i < 3; i ++ ) { + + vn = vertexNormals[ i ]; + + normalArray[ offset_normal ] = vn.x; + normalArray[ offset_normal + 1 ] = vn.y; + normalArray[ offset_normal + 2 ] = vn.z; + + offset_normal += 3; + + } + + } else { + + for ( i = 0; i < 3; i ++ ) { + + normalArray[ offset_normal ] = faceNormal.x; + normalArray[ offset_normal + 1 ] = faceNormal.y; + normalArray[ offset_normal + 2 ] = faceNormal.z; + + offset_normal += 3; + + } + + } + + } + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglNormalBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, normalArray, hint ); + + } + + if ( dirtyUvs && obj_uvs && uvType ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + fi = chunk_faces3[ f ]; + + uv = obj_uvs[ fi ]; + + if ( uv === undefined ) continue; + + for ( i = 0; i < 3; i ++ ) { + + uvi = uv[ i ]; + + uvArray[ offset_uv ] = uvi.x; + uvArray[ offset_uv + 1 ] = uvi.y; + + offset_uv += 2; + + } + + } + + if ( offset_uv > 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglUVBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, uvArray, hint ); + + } + + } + + if ( dirtyUvs && obj_uvs2 && uvType ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + fi = chunk_faces3[ f ]; + + uv2 = obj_uvs2[ fi ]; + + if ( uv2 === undefined ) continue; + + for ( i = 0; i < 3; i ++ ) { + + uv2i = uv2[ i ]; + + uv2Array[ offset_uv2 ] = uv2i.x; + uv2Array[ offset_uv2 + 1 ] = uv2i.y; + + offset_uv2 += 2; + + } + + } + + if ( offset_uv2 > 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglUV2Buffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, uv2Array, hint ); + + } + + } + + if ( dirtyElements ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + faceArray[ offset_face ] = vertexIndex; + faceArray[ offset_face + 1 ] = vertexIndex + 1; + faceArray[ offset_face + 2 ] = vertexIndex + 2; + + offset_face += 3; + + lineArray[ offset_line ] = vertexIndex; + lineArray[ offset_line + 1 ] = vertexIndex + 1; + + lineArray[ offset_line + 2 ] = vertexIndex; + lineArray[ offset_line + 3 ] = vertexIndex + 2; + + lineArray[ offset_line + 4 ] = vertexIndex + 1; + lineArray[ offset_line + 5 ] = vertexIndex + 2; + + offset_line += 6; + + vertexIndex += 3; + + } + + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglFaceBuffer ); + _gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, faceArray, hint ); + + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglLineBuffer ); + _gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, lineArray, hint ); + + } + + if ( customAttributes ) { + + for ( i = 0, il = customAttributes.length; i < il; i ++ ) { + + customAttribute = customAttributes[ i ]; + + if ( ! customAttribute.__original.needsUpdate ) continue; + + offset_custom = 0; + offset_customSrc = 0; + + if ( customAttribute.size === 1 ) { + + if ( customAttribute.boundTo === undefined || customAttribute.boundTo === "vertices" ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + face = obj_faces[ chunk_faces3[ f ] ]; + + customAttribute.array[ offset_custom ] = customAttribute.value[ face.a ]; + customAttribute.array[ offset_custom + 1 ] = customAttribute.value[ face.b ]; + customAttribute.array[ offset_custom + 2 ] = customAttribute.value[ face.c ]; + + offset_custom += 3; + + } + + } else if ( customAttribute.boundTo === "faces" ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + value = customAttribute.value[ chunk_faces3[ f ] ]; + + customAttribute.array[ offset_custom ] = value; + customAttribute.array[ offset_custom + 1 ] = value; + customAttribute.array[ offset_custom + 2 ] = value; + + offset_custom += 3; + + } + + } + + } else if ( customAttribute.size === 2 ) { + + if ( customAttribute.boundTo === undefined || customAttribute.boundTo === "vertices" ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + face = obj_faces[ chunk_faces3[ f ] ]; + + v1 = customAttribute.value[ face.a ]; + v2 = customAttribute.value[ face.b ]; + v3 = customAttribute.value[ face.c ]; + + customAttribute.array[ offset_custom ] = v1.x; + customAttribute.array[ offset_custom + 1 ] = v1.y; + + customAttribute.array[ offset_custom + 2 ] = v2.x; + customAttribute.array[ offset_custom + 3 ] = v2.y; + + customAttribute.array[ offset_custom + 4 ] = v3.x; + customAttribute.array[ offset_custom + 5 ] = v3.y; + + offset_custom += 6; + + } + + } else if ( customAttribute.boundTo === "faces" ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + value = customAttribute.value[ chunk_faces3[ f ] ]; + + v1 = value; + v2 = value; + v3 = value; + + customAttribute.array[ offset_custom ] = v1.x; + customAttribute.array[ offset_custom + 1 ] = v1.y; + + customAttribute.array[ offset_custom + 2 ] = v2.x; + customAttribute.array[ offset_custom + 3 ] = v2.y; + + customAttribute.array[ offset_custom + 4 ] = v3.x; + customAttribute.array[ offset_custom + 5 ] = v3.y; + + offset_custom += 6; + + } + + } + + } else if ( customAttribute.size === 3 ) { + + var pp; + + if ( customAttribute.type === "c" ) { + + pp = [ "r", "g", "b" ]; + + } else { + + pp = [ "x", "y", "z" ]; + + } + + if ( customAttribute.boundTo === undefined || customAttribute.boundTo === "vertices" ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + face = obj_faces[ chunk_faces3[ f ] ]; + + v1 = customAttribute.value[ face.a ]; + v2 = customAttribute.value[ face.b ]; + v3 = customAttribute.value[ face.c ]; + + customAttribute.array[ offset_custom ] = v1[ pp[ 0 ] ]; + customAttribute.array[ offset_custom + 1 ] = v1[ pp[ 1 ] ]; + customAttribute.array[ offset_custom + 2 ] = v1[ pp[ 2 ] ]; + + customAttribute.array[ offset_custom + 3 ] = v2[ pp[ 0 ] ]; + customAttribute.array[ offset_custom + 4 ] = v2[ pp[ 1 ] ]; + customAttribute.array[ offset_custom + 5 ] = v2[ pp[ 2 ] ]; + + customAttribute.array[ offset_custom + 6 ] = v3[ pp[ 0 ] ]; + customAttribute.array[ offset_custom + 7 ] = v3[ pp[ 1 ] ]; + customAttribute.array[ offset_custom + 8 ] = v3[ pp[ 2 ] ]; + + offset_custom += 9; + + } + + } else if ( customAttribute.boundTo === "faces" ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + value = customAttribute.value[ chunk_faces3[ f ] ]; + + v1 = value; + v2 = value; + v3 = value; + + customAttribute.array[ offset_custom ] = v1[ pp[ 0 ] ]; + customAttribute.array[ offset_custom + 1 ] = v1[ pp[ 1 ] ]; + customAttribute.array[ offset_custom + 2 ] = v1[ pp[ 2 ] ]; + + customAttribute.array[ offset_custom + 3 ] = v2[ pp[ 0 ] ]; + customAttribute.array[ offset_custom + 4 ] = v2[ pp[ 1 ] ]; + customAttribute.array[ offset_custom + 5 ] = v2[ pp[ 2 ] ]; + + customAttribute.array[ offset_custom + 6 ] = v3[ pp[ 0 ] ]; + customAttribute.array[ offset_custom + 7 ] = v3[ pp[ 1 ] ]; + customAttribute.array[ offset_custom + 8 ] = v3[ pp[ 2 ] ]; + + offset_custom += 9; + + } + + } else if ( customAttribute.boundTo === "faceVertices" ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + value = customAttribute.value[ chunk_faces3[ f ] ]; + + v1 = value[ 0 ]; + v2 = value[ 1 ]; + v3 = value[ 2 ]; + + customAttribute.array[ offset_custom ] = v1[ pp[ 0 ] ]; + customAttribute.array[ offset_custom + 1 ] = v1[ pp[ 1 ] ]; + customAttribute.array[ offset_custom + 2 ] = v1[ pp[ 2 ] ]; + + customAttribute.array[ offset_custom + 3 ] = v2[ pp[ 0 ] ]; + customAttribute.array[ offset_custom + 4 ] = v2[ pp[ 1 ] ]; + customAttribute.array[ offset_custom + 5 ] = v2[ pp[ 2 ] ]; + + customAttribute.array[ offset_custom + 6 ] = v3[ pp[ 0 ] ]; + customAttribute.array[ offset_custom + 7 ] = v3[ pp[ 1 ] ]; + customAttribute.array[ offset_custom + 8 ] = v3[ pp[ 2 ] ]; + + offset_custom += 9; + + } + + } + + } else if ( customAttribute.size === 4 ) { + + if ( customAttribute.boundTo === undefined || customAttribute.boundTo === "vertices" ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + face = obj_faces[ chunk_faces3[ f ] ]; + + v1 = customAttribute.value[ face.a ]; + v2 = customAttribute.value[ face.b ]; + v3 = customAttribute.value[ face.c ]; + + customAttribute.array[ offset_custom ] = v1.x; + customAttribute.array[ offset_custom + 1 ] = v1.y; + customAttribute.array[ offset_custom + 2 ] = v1.z; + customAttribute.array[ offset_custom + 3 ] = v1.w; + + customAttribute.array[ offset_custom + 4 ] = v2.x; + customAttribute.array[ offset_custom + 5 ] = v2.y; + customAttribute.array[ offset_custom + 6 ] = v2.z; + customAttribute.array[ offset_custom + 7 ] = v2.w; + + customAttribute.array[ offset_custom + 8 ] = v3.x; + customAttribute.array[ offset_custom + 9 ] = v3.y; + customAttribute.array[ offset_custom + 10 ] = v3.z; + customAttribute.array[ offset_custom + 11 ] = v3.w; + + offset_custom += 12; + + } + + } else if ( customAttribute.boundTo === "faces" ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + value = customAttribute.value[ chunk_faces3[ f ] ]; + + v1 = value; + v2 = value; + v3 = value; + + customAttribute.array[ offset_custom ] = v1.x; + customAttribute.array[ offset_custom + 1 ] = v1.y; + customAttribute.array[ offset_custom + 2 ] = v1.z; + customAttribute.array[ offset_custom + 3 ] = v1.w; + + customAttribute.array[ offset_custom + 4 ] = v2.x; + customAttribute.array[ offset_custom + 5 ] = v2.y; + customAttribute.array[ offset_custom + 6 ] = v2.z; + customAttribute.array[ offset_custom + 7 ] = v2.w; + + customAttribute.array[ offset_custom + 8 ] = v3.x; + customAttribute.array[ offset_custom + 9 ] = v3.y; + customAttribute.array[ offset_custom + 10 ] = v3.z; + customAttribute.array[ offset_custom + 11 ] = v3.w; + + offset_custom += 12; + + } + + } else if ( customAttribute.boundTo === "faceVertices" ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + value = customAttribute.value[ chunk_faces3[ f ] ]; + + v1 = value[ 0 ]; + v2 = value[ 1 ]; + v3 = value[ 2 ]; + + customAttribute.array[ offset_custom ] = v1.x; + customAttribute.array[ offset_custom + 1 ] = v1.y; + customAttribute.array[ offset_custom + 2 ] = v1.z; + customAttribute.array[ offset_custom + 3 ] = v1.w; + + customAttribute.array[ offset_custom + 4 ] = v2.x; + customAttribute.array[ offset_custom + 5 ] = v2.y; + customAttribute.array[ offset_custom + 6 ] = v2.z; + customAttribute.array[ offset_custom + 7 ] = v2.w; + + customAttribute.array[ offset_custom + 8 ] = v3.x; + customAttribute.array[ offset_custom + 9 ] = v3.y; + customAttribute.array[ offset_custom + 10 ] = v3.z; + customAttribute.array[ offset_custom + 11 ] = v3.w; + + offset_custom += 12; + + } + + } + + } + + _gl.bindBuffer( _gl.ARRAY_BUFFER, customAttribute.buffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, customAttribute.array, hint ); + + } + + } + + if ( dispose ) { + + delete geometryGroup.__inittedArrays; + delete geometryGroup.__colorArray; + delete geometryGroup.__normalArray; + delete geometryGroup.__tangentArray; + delete geometryGroup.__uvArray; + delete geometryGroup.__uv2Array; + delete geometryGroup.__faceArray; + delete geometryGroup.__vertexArray; + delete geometryGroup.__lineArray; + delete geometryGroup.__skinIndexArray; + delete geometryGroup.__skinWeightArray; + + } + + }; + + // used by renderBufferDirect for THREE.Line + function setupLinesVertexAttributes( material, programAttributes, geometryAttributes, startIndex ) { + + var attributeItem, attributeName, attributePointer, attributeSize; + + for ( attributeName in programAttributes ) { + + attributePointer = programAttributes[ attributeName ]; + attributeItem = geometryAttributes[ attributeName ]; + + if ( attributePointer >= 0 ) { + + if ( attributeItem ) { + + attributeSize = attributeItem.itemSize; + _gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer ); + enableAttribute( attributePointer ); + _gl.vertexAttribPointer( attributePointer, attributeSize, _gl.FLOAT, false, 0, startIndex * attributeSize * 4 ); // 4 bytes per Float32 + + } else if ( material.defaultAttributeValues ) { + + if ( material.defaultAttributeValues[ attributeName ].length === 2 ) { + + _gl.vertexAttrib2fv( attributePointer, material.defaultAttributeValues[ attributeName ] ); + + } else if ( material.defaultAttributeValues[ attributeName ].length === 3 ) { + + _gl.vertexAttrib3fv( attributePointer, material.defaultAttributeValues[ attributeName ] ); + + } + + } + + } + + } + + } + + function setDirectBuffers( geometry, hint ) { + + var attributes = geometry.attributes; + + var attributeName, attributeItem; + + for ( attributeName in attributes ) { + + attributeItem = attributes[ attributeName ]; + + if ( attributeItem.needsUpdate ) { + + if ( attributeName === 'index' ) { + + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, attributeItem.buffer ); + _gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, attributeItem.array, hint ); + + } else { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, attributeItem.array, hint ); + + } + + attributeItem.needsUpdate = false; + + } + + } + + } + + // Buffer rendering + + this.renderBufferImmediate = function ( object, program, material ) { + + if ( object.hasPositions && ! object.__webglVertexBuffer ) object.__webglVertexBuffer = _gl.createBuffer(); + if ( object.hasNormals && ! object.__webglNormalBuffer ) object.__webglNormalBuffer = _gl.createBuffer(); + if ( object.hasUvs && ! object.__webglUvBuffer ) object.__webglUvBuffer = _gl.createBuffer(); + if ( object.hasColors && ! object.__webglColorBuffer ) object.__webglColorBuffer = _gl.createBuffer(); + + if ( object.hasPositions ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglVertexBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, object.positionArray, _gl.DYNAMIC_DRAW ); + _gl.enableVertexAttribArray( program.attributes.position ); + _gl.vertexAttribPointer( program.attributes.position, 3, _gl.FLOAT, false, 0, 0 ); + + } + + if ( object.hasNormals ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglNormalBuffer ); + + if ( material.shading === THREE.FlatShading ) { + + var nx, ny, nz, + nax, nbx, ncx, nay, nby, ncy, naz, nbz, ncz, + normalArray, + i, il = object.count * 3; + + for( i = 0; i < il; i += 9 ) { + + normalArray = object.normalArray; + + nax = normalArray[ i ]; + nay = normalArray[ i + 1 ]; + naz = normalArray[ i + 2 ]; + + nbx = normalArray[ i + 3 ]; + nby = normalArray[ i + 4 ]; + nbz = normalArray[ i + 5 ]; + + ncx = normalArray[ i + 6 ]; + ncy = normalArray[ i + 7 ]; + ncz = normalArray[ i + 8 ]; + + nx = ( nax + nbx + ncx ) / 3; + ny = ( nay + nby + ncy ) / 3; + nz = ( naz + nbz + ncz ) / 3; + + normalArray[ i ] = nx; + normalArray[ i + 1 ] = ny; + normalArray[ i + 2 ] = nz; + + normalArray[ i + 3 ] = nx; + normalArray[ i + 4 ] = ny; + normalArray[ i + 5 ] = nz; + + normalArray[ i + 6 ] = nx; + normalArray[ i + 7 ] = ny; + normalArray[ i + 8 ] = nz; + + } + + } + + _gl.bufferData( _gl.ARRAY_BUFFER, object.normalArray, _gl.DYNAMIC_DRAW ); + _gl.enableVertexAttribArray( program.attributes.normal ); + _gl.vertexAttribPointer( program.attributes.normal, 3, _gl.FLOAT, false, 0, 0 ); + + } + + if ( object.hasUvs && material.map ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglUvBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, object.uvArray, _gl.DYNAMIC_DRAW ); + _gl.enableVertexAttribArray( program.attributes.uv ); + _gl.vertexAttribPointer( program.attributes.uv, 2, _gl.FLOAT, false, 0, 0 ); + + } + + if ( object.hasColors && material.vertexColors !== THREE.NoColors ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglColorBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, object.colorArray, _gl.DYNAMIC_DRAW ); + _gl.enableVertexAttribArray( program.attributes.color ); + _gl.vertexAttribPointer( program.attributes.color, 3, _gl.FLOAT, false, 0, 0 ); + + } + + _gl.drawArrays( _gl.TRIANGLES, 0, object.count ); + + object.count = 0; + + }; + + this.renderBufferDirect = function ( camera, lights, fog, material, geometry, object ) { + + if ( material.visible === false ) return; + + var linewidth, a, attribute; + var attributeItem, attributeName, attributePointer, attributeSize; + + var program = setProgram( camera, lights, fog, material, object ); + + var programAttributes = program.attributes; + var geometryAttributes = geometry.attributes; + + var updateBuffers = false, + wireframeBit = material.wireframe ? 1 : 0, + geometryHash = ( geometry.id * 0xffffff ) + ( program.id * 2 ) + wireframeBit; + + if ( geometryHash !== _currentGeometryGroupHash ) { + + _currentGeometryGroupHash = geometryHash; + updateBuffers = true; + + } + + if ( updateBuffers ) { + + disableAttributes(); + + } + + // render mesh + + if ( object instanceof THREE.Mesh ) { + + var index = geometryAttributes[ "index" ]; + + // indexed triangles + + if ( index ) { + + var offsets = geometry.offsets; + + // if there is more than 1 chunk + // must set attribute pointers to use new offsets for each chunk + // even if geometry and materials didn't change + + if ( offsets.length > 1 ) updateBuffers = true; + + for ( var i = 0, il = offsets.length; i < il; i ++ ) { + + var startIndex = offsets[ i ].index; + + if ( updateBuffers ) { + + for ( attributeName in programAttributes ) { + + attributePointer = programAttributes[ attributeName ]; + attributeItem = geometryAttributes[ attributeName ]; + + if ( attributePointer >= 0 ) { + + if ( attributeItem ) { + + attributeSize = attributeItem.itemSize; + _gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer ); + enableAttribute( attributePointer ); + _gl.vertexAttribPointer( attributePointer, attributeSize, _gl.FLOAT, false, 0, startIndex * attributeSize * 4 ); // 4 bytes per Float32 + + } else if ( material.defaultAttributeValues ) { + + if ( material.defaultAttributeValues[ attributeName ].length === 2 ) { + + _gl.vertexAttrib2fv( attributePointer, material.defaultAttributeValues[ attributeName ] ); + + } else if ( material.defaultAttributeValues[ attributeName ].length === 3 ) { + + _gl.vertexAttrib3fv( attributePointer, material.defaultAttributeValues[ attributeName ] ); + + } + + } + + } + + } + + // indices + + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, index.buffer ); + + } + + // render indexed triangles + + _gl.drawElements( _gl.TRIANGLES, offsets[ i ].count, _gl.UNSIGNED_SHORT, offsets[ i ].start * 2 ); // 2 bytes per Uint16 + + _this.info.render.calls ++; + _this.info.render.vertices += offsets[ i ].count; // not really true, here vertices can be shared + _this.info.render.faces += offsets[ i ].count / 3; + + } + + // non-indexed triangles + + } else { + + if ( updateBuffers ) { + + for ( attributeName in programAttributes ) { + + if ( attributeName === 'index') continue; + + attributePointer = programAttributes[ attributeName ]; + attributeItem = geometryAttributes[ attributeName ]; + + if ( attributePointer >= 0 ) { + + if ( attributeItem ) { + + attributeSize = attributeItem.itemSize; + _gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer ); + enableAttribute( attributePointer ); + _gl.vertexAttribPointer( attributePointer, attributeSize, _gl.FLOAT, false, 0, 0 ); + + } else if ( material.defaultAttributeValues && material.defaultAttributeValues[ attributeName ] ) { + + if ( material.defaultAttributeValues[ attributeName ].length === 2 ) { + + _gl.vertexAttrib2fv( attributePointer, material.defaultAttributeValues[ attributeName ] ); + + } else if ( material.defaultAttributeValues[ attributeName ].length === 3 ) { + + _gl.vertexAttrib3fv( attributePointer, material.defaultAttributeValues[ attributeName ] ); + + } + + } + + } + + } + + } + + var position = geometry.attributes[ "position" ]; + + // render non-indexed triangles + + _gl.drawArrays( _gl.TRIANGLES, 0, position.array.length / 3 ); + + _this.info.render.calls ++; + _this.info.render.vertices += position.array.length / 3; + _this.info.render.faces += position.array.length / 3 / 3; + + } + + // render particles + + } else if ( object instanceof THREE.ParticleSystem ) { + + if ( updateBuffers ) { + + for ( attributeName in programAttributes ) { + + attributePointer = programAttributes[ attributeName ]; + attributeItem = geometryAttributes[ attributeName ]; + + if ( attributePointer >= 0 ) { + + if ( attributeItem ) { + + attributeSize = attributeItem.itemSize; + _gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer ); + enableAttribute( attributePointer ); + _gl.vertexAttribPointer( attributePointer, attributeSize, _gl.FLOAT, false, 0, 0 ); + + } else if ( material.defaultAttributeValues && material.defaultAttributeValues[ attributeName ] ) { + + if ( material.defaultAttributeValues[ attributeName ].length === 2 ) { + + _gl.vertexAttrib2fv( attributePointer, material.defaultAttributeValues[ attributeName ] ); + + } else if ( material.defaultAttributeValues[ attributeName ].length === 3 ) { + + _gl.vertexAttrib3fv( attributePointer, material.defaultAttributeValues[ attributeName ] ); + + } + + } + + } + + } + + } + + var position = geometryAttributes[ "position" ]; + + // render particles + + _gl.drawArrays( _gl.POINTS, 0, position.array.length / 3 ); + + _this.info.render.calls ++; + _this.info.render.points += position.array.length / 3; + + } else if ( object instanceof THREE.Line ) { + + var primitives = ( object.type === THREE.LineStrip ) ? _gl.LINE_STRIP : _gl.LINES; + + setLineWidth( material.linewidth ); + + var index = geometryAttributes[ "index" ]; + + // indexed lines + + if ( index ) { + + var offsets = geometry.offsets; + + // if there is more than 1 chunk + // must set attribute pointers to use new offsets for each chunk + // even if geometry and materials didn't change + + if ( offsets.length > 1 ) updateBuffers = true; + + for ( var i = 0, il = offsets.length; i < il; i ++ ) { + + var startIndex = offsets[ i ].index; + + if ( updateBuffers ) { + + setupLinesVertexAttributes(material, programAttributes, geometryAttributes, startIndex); + + // indices + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, index.buffer ); + + } + + // render indexed lines + + _gl.drawElements( _gl.LINES, offsets[ i ].count, _gl.UNSIGNED_SHORT, offsets[ i ].start * 2 ); // 2 bytes per Uint16Array + + _this.info.render.calls ++; + _this.info.render.vertices += offsets[ i ].count; // not really true, here vertices can be shared + + } + + } + + // non-indexed lines + + else { + + if ( updateBuffers ) { + + setupLinesVertexAttributes(material, programAttributes, geometryAttributes, 0); + } + + var position = geometryAttributes[ "position" ]; + + _gl.drawArrays( primitives, 0, position.array.length / 3 ); + _this.info.render.calls ++; + _this.info.render.points += position.array.length; + } + + + + } + + }; + + this.renderBuffer = function ( camera, lights, fog, material, geometryGroup, object ) { + + if ( material.visible === false ) return; + + var linewidth, a, attribute, i, il; + + var program = setProgram( camera, lights, fog, material, object ); + + var attributes = program.attributes; + + var updateBuffers = false, + wireframeBit = material.wireframe ? 1 : 0, + geometryGroupHash = ( geometryGroup.id * 0xffffff ) + ( program.id * 2 ) + wireframeBit; + + if ( geometryGroupHash !== _currentGeometryGroupHash ) { + + _currentGeometryGroupHash = geometryGroupHash; + updateBuffers = true; + + } + + if ( updateBuffers ) { + + disableAttributes(); + + } + + // vertices + + if ( !material.morphTargets && attributes.position >= 0 ) { + + if ( updateBuffers ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglVertexBuffer ); + enableAttribute( attributes.position ); + _gl.vertexAttribPointer( attributes.position, 3, _gl.FLOAT, false, 0, 0 ); + + } + + } else { + + if ( object.morphTargetBase ) { + + setupMorphTargets( material, geometryGroup, object ); + + } + + } + + + if ( updateBuffers ) { + + // custom attributes + + // Use the per-geometryGroup custom attribute arrays which are setup in initMeshBuffers + + if ( geometryGroup.__webglCustomAttributesList ) { + + for ( i = 0, il = geometryGroup.__webglCustomAttributesList.length; i < il; i ++ ) { + + attribute = geometryGroup.__webglCustomAttributesList[ i ]; + + if ( attributes[ attribute.buffer.belongsToAttribute ] >= 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, attribute.buffer ); + enableAttribute( attributes[ attribute.buffer.belongsToAttribute ] ); + _gl.vertexAttribPointer( attributes[ attribute.buffer.belongsToAttribute ], attribute.size, _gl.FLOAT, false, 0, 0 ); + + } + + } + + } + + + // colors + + if ( attributes.color >= 0 ) { + + if ( object.geometry.colors.length > 0 || object.geometry.faces.length > 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglColorBuffer ); + enableAttribute( attributes.color ); + _gl.vertexAttribPointer( attributes.color, 3, _gl.FLOAT, false, 0, 0 ); + + } else if ( material.defaultAttributeValues ) { + + + _gl.vertexAttrib3fv( attributes.color, material.defaultAttributeValues.color ); + + } + + } + + // normals + + if ( attributes.normal >= 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglNormalBuffer ); + enableAttribute( attributes.normal ); + _gl.vertexAttribPointer( attributes.normal, 3, _gl.FLOAT, false, 0, 0 ); + + } + + // tangents + + if ( attributes.tangent >= 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglTangentBuffer ); + enableAttribute( attributes.tangent ); + _gl.vertexAttribPointer( attributes.tangent, 4, _gl.FLOAT, false, 0, 0 ); + + } + + // uvs + + if ( attributes.uv >= 0 ) { + + if ( object.geometry.faceVertexUvs[0] ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglUVBuffer ); + enableAttribute( attributes.uv ); + _gl.vertexAttribPointer( attributes.uv, 2, _gl.FLOAT, false, 0, 0 ); + + } else if ( material.defaultAttributeValues ) { + + + _gl.vertexAttrib2fv( attributes.uv, material.defaultAttributeValues.uv ); + + } + + } + + if ( attributes.uv2 >= 0 ) { + + if ( object.geometry.faceVertexUvs[1] ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglUV2Buffer ); + enableAttribute( attributes.uv2 ); + _gl.vertexAttribPointer( attributes.uv2, 2, _gl.FLOAT, false, 0, 0 ); + + } else if ( material.defaultAttributeValues ) { + + + _gl.vertexAttrib2fv( attributes.uv2, material.defaultAttributeValues.uv2 ); + + } + + } + + if ( material.skinning && + attributes.skinIndex >= 0 && attributes.skinWeight >= 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglSkinIndicesBuffer ); + enableAttribute( attributes.skinIndex ); + _gl.vertexAttribPointer( attributes.skinIndex, 4, _gl.FLOAT, false, 0, 0 ); + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglSkinWeightsBuffer ); + enableAttribute( attributes.skinWeight ); + _gl.vertexAttribPointer( attributes.skinWeight, 4, _gl.FLOAT, false, 0, 0 ); + + } + + // line distances + + if ( attributes.lineDistance >= 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglLineDistanceBuffer ); + enableAttribute( attributes.lineDistance ); + _gl.vertexAttribPointer( attributes.lineDistance, 1, _gl.FLOAT, false, 0, 0 ); + + } + + } + + // render mesh + + if ( object instanceof THREE.Mesh ) { + + // wireframe + + if ( material.wireframe ) { + + setLineWidth( material.wireframeLinewidth ); + + if ( updateBuffers ) _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglLineBuffer ); + _gl.drawElements( _gl.LINES, geometryGroup.__webglLineCount, _gl.UNSIGNED_SHORT, 0 ); + + // triangles + + } else { + + if ( updateBuffers ) _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglFaceBuffer ); + _gl.drawElements( _gl.TRIANGLES, geometryGroup.__webglFaceCount, _gl.UNSIGNED_SHORT, 0 ); + + } + + _this.info.render.calls ++; + _this.info.render.vertices += geometryGroup.__webglFaceCount; + _this.info.render.faces += geometryGroup.__webglFaceCount / 3; + + // render lines + + } else if ( object instanceof THREE.Line ) { + + var primitives = ( object.type === THREE.LineStrip ) ? _gl.LINE_STRIP : _gl.LINES; + + setLineWidth( material.linewidth ); + + _gl.drawArrays( primitives, 0, geometryGroup.__webglLineCount ); + + _this.info.render.calls ++; + + // render particles + + } else if ( object instanceof THREE.ParticleSystem ) { + + _gl.drawArrays( _gl.POINTS, 0, geometryGroup.__webglParticleCount ); + + _this.info.render.calls ++; + _this.info.render.points += geometryGroup.__webglParticleCount; + + } + + }; + + function enableAttribute( attribute ) { + + if ( _enabledAttributes[ attribute ] === 0 ) { + + _gl.enableVertexAttribArray( attribute ); + _enabledAttributes[ attribute ] = 1; + + } + + }; + + function disableAttributes() { + + for ( var attribute in _enabledAttributes ) { + + if ( _enabledAttributes[ attribute ] === 1 ) { + + _gl.disableVertexAttribArray( attribute ); + _enabledAttributes[ attribute ] = 0; + + } + + } + + }; + + function setupMorphTargets ( material, geometryGroup, object ) { + + // set base + + var attributes = material.program.attributes; + + if ( object.morphTargetBase !== -1 && attributes.position >= 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[ object.morphTargetBase ] ); + enableAttribute( attributes.position ); + _gl.vertexAttribPointer( attributes.position, 3, _gl.FLOAT, false, 0, 0 ); + + } else if ( attributes.position >= 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglVertexBuffer ); + enableAttribute( attributes.position ); + _gl.vertexAttribPointer( attributes.position, 3, _gl.FLOAT, false, 0, 0 ); + + } + + if ( object.morphTargetForcedOrder.length ) { + + // set forced order + + var m = 0; + var order = object.morphTargetForcedOrder; + var influences = object.morphTargetInfluences; + + while ( m < material.numSupportedMorphTargets && m < order.length ) { + + if ( attributes[ "morphTarget" + m ] >= 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[ order[ m ] ] ); + enableAttribute( attributes[ "morphTarget" + m ] ); + _gl.vertexAttribPointer( attributes[ "morphTarget" + m ], 3, _gl.FLOAT, false, 0, 0 ); + + } + + if ( attributes[ "morphNormal" + m ] >= 0 && material.morphNormals ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphNormalsBuffers[ order[ m ] ] ); + enableAttribute( attributes[ "morphNormal" + m ] ); + _gl.vertexAttribPointer( attributes[ "morphNormal" + m ], 3, _gl.FLOAT, false, 0, 0 ); + + } + + object.__webglMorphTargetInfluences[ m ] = influences[ order[ m ] ]; + + m ++; + } + + } else { + + // find the most influencing + + var influence, activeInfluenceIndices = []; + var influences = object.morphTargetInfluences; + var i, il = influences.length; + + for ( i = 0; i < il; i ++ ) { + + influence = influences[ i ]; + + if ( influence > 0 ) { + + activeInfluenceIndices.push( [ influence, i ] ); + + } + + } + + if ( activeInfluenceIndices.length > material.numSupportedMorphTargets ) { + + activeInfluenceIndices.sort( numericalSort ); + activeInfluenceIndices.length = material.numSupportedMorphTargets; + + } else if ( activeInfluenceIndices.length > material.numSupportedMorphNormals ) { + + activeInfluenceIndices.sort( numericalSort ); + + } else if ( activeInfluenceIndices.length === 0 ) { + + activeInfluenceIndices.push( [ 0, 0 ] ); + + }; + + var influenceIndex, m = 0; + + while ( m < material.numSupportedMorphTargets ) { + + if ( activeInfluenceIndices[ m ] ) { + + influenceIndex = activeInfluenceIndices[ m ][ 1 ]; + + if ( attributes[ "morphTarget" + m ] >= 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[ influenceIndex ] ); + enableAttribute( attributes[ "morphTarget" + m ] ); + _gl.vertexAttribPointer( attributes[ "morphTarget" + m ], 3, _gl.FLOAT, false, 0, 0 ); + + } + + if ( attributes[ "morphNormal" + m ] >= 0 && material.morphNormals ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphNormalsBuffers[ influenceIndex ] ); + enableAttribute( attributes[ "morphNormal" + m ] ); + _gl.vertexAttribPointer( attributes[ "morphNormal" + m ], 3, _gl.FLOAT, false, 0, 0 ); + + + } + + object.__webglMorphTargetInfluences[ m ] = influences[ influenceIndex ]; + + } else { + + /* + _gl.vertexAttribPointer( attributes[ "morphTarget" + m ], 3, _gl.FLOAT, false, 0, 0 ); + + if ( material.morphNormals ) { + + _gl.vertexAttribPointer( attributes[ "morphNormal" + m ], 3, _gl.FLOAT, false, 0, 0 ); + + } + */ + + object.__webglMorphTargetInfluences[ m ] = 0; + + } + + m ++; + + } + + } + + // load updated influences uniform + + if ( material.program.uniforms.morphTargetInfluences !== null ) { + + _gl.uniform1fv( material.program.uniforms.morphTargetInfluences, object.__webglMorphTargetInfluences ); + + } + + }; + + // Sorting + + function painterSortStable ( a, b ) { + + if ( a.z !== b.z ) { + + return b.z - a.z; + + } else { + + return a.id - b.id; + + } + + }; + + function numericalSort ( a, b ) { + + return b[ 0 ] - a[ 0 ]; + + }; + + + // Rendering + + this.render = function ( scene, camera, renderTarget, forceClear ) { + + if ( camera instanceof THREE.Camera === false ) { + + console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' ); + return; + + } + + var i, il, + + webglObject, object, + renderList, + + lights = scene.__lights, + fog = scene.fog; + + // reset caching for this frame + + _currentMaterialId = -1; + _lightsNeedUpdate = true; + + // update scene graph + + if ( scene.autoUpdate === true ) scene.updateMatrixWorld(); + + // update camera matrices and frustum + + if ( camera.parent === undefined ) camera.updateMatrixWorld(); + + camera.matrixWorldInverse.getInverse( camera.matrixWorld ); + + _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); + _frustum.setFromMatrix( _projScreenMatrix ); + + // update WebGL objects + + if ( this.autoUpdateObjects ) this.initWebGLObjects( scene ); + + // custom render plugins (pre pass) + + renderPlugins( this.renderPluginsPre, scene, camera ); + + // + + _this.info.render.calls = 0; + _this.info.render.vertices = 0; + _this.info.render.faces = 0; + _this.info.render.points = 0; + + this.setRenderTarget( renderTarget ); + + if ( this.autoClear || forceClear ) { + + this.clear( this.autoClearColor, this.autoClearDepth, this.autoClearStencil ); + + } + + // set matrices for regular objects (frustum culled) + + renderList = scene.__webglObjects; + + for ( i = 0, il = renderList.length; i < il; i ++ ) { + + webglObject = renderList[ i ]; + object = webglObject.object; + + webglObject.id = i; + webglObject.render = false; + + if ( object.visible ) { + + if ( ! ( object instanceof THREE.Mesh || object instanceof THREE.ParticleSystem ) || ! ( object.frustumCulled ) || _frustum.intersectsObject( object ) ) { + + setupMatrices( object, camera ); + + unrollBufferMaterial( webglObject ); + + webglObject.render = true; + + if ( this.sortObjects === true ) { + + if ( object.renderDepth !== null ) { + + webglObject.z = object.renderDepth; + + } else { + + _vector3.setFromMatrixPosition( object.matrixWorld ); + _vector3.applyProjection( _projScreenMatrix ); + + webglObject.z = _vector3.z; + + } + + } + + } + + } + + } + + if ( this.sortObjects ) { + + renderList.sort( painterSortStable ); + + } + + // set matrices for immediate objects + + renderList = scene.__webglObjectsImmediate; + + for ( i = 0, il = renderList.length; i < il; i ++ ) { + + webglObject = renderList[ i ]; + object = webglObject.object; + + if ( object.visible ) { + + setupMatrices( object, camera ); + + unrollImmediateBufferMaterial( webglObject ); + + } + + } + + if ( scene.overrideMaterial ) { + + var material = scene.overrideMaterial; + + this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst ); + this.setDepthTest( material.depthTest ); + this.setDepthWrite( material.depthWrite ); + setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); + + renderObjects( scene.__webglObjects, false, "", camera, lights, fog, true, material ); + renderObjectsImmediate( scene.__webglObjectsImmediate, "", camera, lights, fog, false, material ); + + } else { + + var material = null; + + // opaque pass (front-to-back order) + + this.setBlending( THREE.NoBlending ); + + renderObjects( scene.__webglObjects, true, "opaque", camera, lights, fog, false, material ); + renderObjectsImmediate( scene.__webglObjectsImmediate, "opaque", camera, lights, fog, false, material ); + + // transparent pass (back-to-front order) + + renderObjects( scene.__webglObjects, false, "transparent", camera, lights, fog, true, material ); + renderObjectsImmediate( scene.__webglObjectsImmediate, "transparent", camera, lights, fog, true, material ); + + } + + // custom render plugins (post pass) + + renderPlugins( this.renderPluginsPost, scene, camera ); + + + // Generate mipmap if we're using any kind of mipmap filtering + + if ( renderTarget && renderTarget.generateMipmaps && renderTarget.minFilter !== THREE.NearestFilter && renderTarget.minFilter !== THREE.LinearFilter ) { + + updateRenderTargetMipmap( renderTarget ); + + } + + // Ensure depth buffer writing is enabled so it can be cleared on next render + + this.setDepthTest( true ); + this.setDepthWrite( true ); + + // _gl.finish(); + + }; + + function renderPlugins( plugins, scene, camera ) { + + if ( ! plugins.length ) return; + + for ( var i = 0, il = plugins.length; i < il; i ++ ) { + + // reset state for plugin (to start from clean slate) + + _currentProgram = null; + _currentCamera = null; + + _oldBlending = -1; + _oldDepthTest = -1; + _oldDepthWrite = -1; + _oldDoubleSided = -1; + _oldFlipSided = -1; + _currentGeometryGroupHash = -1; + _currentMaterialId = -1; + + _lightsNeedUpdate = true; + + plugins[ i ].render( scene, camera, _currentWidth, _currentHeight ); + + // reset state after plugin (anything could have changed) + + _currentProgram = null; + _currentCamera = null; + + _oldBlending = -1; + _oldDepthTest = -1; + _oldDepthWrite = -1; + _oldDoubleSided = -1; + _oldFlipSided = -1; + _currentGeometryGroupHash = -1; + _currentMaterialId = -1; + + _lightsNeedUpdate = true; + + } + + }; + + function renderObjects( renderList, reverse, materialType, camera, lights, fog, useBlending, overrideMaterial ) { + + var webglObject, object, buffer, material, start, end, delta; + + if ( reverse ) { + + start = renderList.length - 1; + end = -1; + delta = -1; + + } else { + + start = 0; + end = renderList.length; + delta = 1; + } + + for ( var i = start; i !== end; i += delta ) { + + webglObject = renderList[ i ]; + + if ( webglObject.render ) { + + object = webglObject.object; + buffer = webglObject.buffer; + + if ( overrideMaterial ) { + + material = overrideMaterial; + + } else { + + material = webglObject[ materialType ]; + + if ( ! material ) continue; + + if ( useBlending ) _this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst ); + + _this.setDepthTest( material.depthTest ); + _this.setDepthWrite( material.depthWrite ); + setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); + + } + + _this.setMaterialFaces( material ); + + if ( buffer instanceof THREE.BufferGeometry ) { + + _this.renderBufferDirect( camera, lights, fog, material, buffer, object ); + + } else { + + _this.renderBuffer( camera, lights, fog, material, buffer, object ); + + } + + } + + } + + }; + + function renderObjectsImmediate ( renderList, materialType, camera, lights, fog, useBlending, overrideMaterial ) { + + var webglObject, object, material, program; + + for ( var i = 0, il = renderList.length; i < il; i ++ ) { + + webglObject = renderList[ i ]; + object = webglObject.object; + + if ( object.visible ) { + + if ( overrideMaterial ) { + + material = overrideMaterial; + + } else { + + material = webglObject[ materialType ]; + + if ( ! material ) continue; + + if ( useBlending ) _this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst ); + + _this.setDepthTest( material.depthTest ); + _this.setDepthWrite( material.depthWrite ); + setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); + + } + + _this.renderImmediateObject( camera, lights, fog, material, object ); + + } + + } + + }; + + this.renderImmediateObject = function ( camera, lights, fog, material, object ) { + + var program = setProgram( camera, lights, fog, material, object ); + + _currentGeometryGroupHash = -1; + + _this.setMaterialFaces( material ); + + if ( object.immediateRenderCallback ) { + + object.immediateRenderCallback( program, _gl, _frustum ); + + } else { + + object.render( function( object ) { _this.renderBufferImmediate( object, program, material ); } ); + + } + + }; + + function unrollImmediateBufferMaterial ( globject ) { + + var object = globject.object, + material = object.material; + + if ( material.transparent ) { + + globject.transparent = material; + globject.opaque = null; + + } else { + + globject.opaque = material; + globject.transparent = null; + + } + + }; + + function unrollBufferMaterial ( globject ) { + + var object = globject.object; + var buffer = globject.buffer; + + var geometry = object.geometry; + var material = object.material; + + if ( material instanceof THREE.MeshFaceMaterial ) { + + var materialIndex = geometry instanceof THREE.BufferGeometry ? 0 : buffer.materialIndex; + + material = material.materials[ materialIndex ]; + + if ( material.transparent ) { + + globject.transparent = material; + globject.opaque = null; + + } else { + + globject.opaque = material; + globject.transparent = null; + + } + + } else { + + if ( material ) { + + if ( material.transparent ) { + + globject.transparent = material; + globject.opaque = null; + + } else { + + globject.opaque = material; + globject.transparent = null; + + } + + } + + } + + }; + + // Objects refresh + + this.initWebGLObjects = function ( scene ) { + + if ( !scene.__webglObjects ) { + + scene.__webglObjects = []; + scene.__webglObjectsImmediate = []; + scene.__webglSprites = []; + scene.__webglFlares = []; + + } + + while ( scene.__objectsAdded.length ) { + + addObject( scene.__objectsAdded[ 0 ], scene ); + scene.__objectsAdded.splice( 0, 1 ); + + } + + while ( scene.__objectsRemoved.length ) { + + removeObject( scene.__objectsRemoved[ 0 ], scene ); + scene.__objectsRemoved.splice( 0, 1 ); + + } + + // update must be called after objects adding / removal + + for ( var o = 0, ol = scene.__webglObjects.length; o < ol; o ++ ) { + + var object = scene.__webglObjects[ o ].object; + + // TODO: Remove this hack (WebGLRenderer refactoring) + + if ( object.__webglInit === undefined ) { + + if ( object.__webglActive !== undefined ) { + + removeObject( object, scene ); + + } + + addObject( object, scene ); + + } + + updateObject( object ); + + } + + }; + + // Objects adding + + function addObject( object, scene ) { + + var g, geometry, material, geometryGroup; + + if ( object.__webglInit === undefined ) { + + object.__webglInit = true; + + object._modelViewMatrix = new THREE.Matrix4(); + object._normalMatrix = new THREE.Matrix3(); + + if ( object.geometry !== undefined && object.geometry.__webglInit === undefined ) { + + object.geometry.__webglInit = true; + object.geometry.addEventListener( 'dispose', onGeometryDispose ); + + } + + geometry = object.geometry; + + if ( geometry === undefined ) { + + // fail silently for now + + } else if ( geometry instanceof THREE.BufferGeometry ) { + + initDirectBuffers( geometry ); + + } else if ( object instanceof THREE.Mesh ) { + + material = object.material; + + if ( geometry.geometryGroups === undefined ) { + + geometry.makeGroups( material instanceof THREE.MeshFaceMaterial ); + + } + + // create separate VBOs per geometry chunk + + for ( g in geometry.geometryGroups ) { + + geometryGroup = geometry.geometryGroups[ g ]; + + // initialise VBO on the first access + + if ( ! geometryGroup.__webglVertexBuffer ) { + + createMeshBuffers( geometryGroup ); + initMeshBuffers( geometryGroup, object ); + + geometry.verticesNeedUpdate = true; + geometry.morphTargetsNeedUpdate = true; + geometry.elementsNeedUpdate = true; + geometry.uvsNeedUpdate = true; + geometry.normalsNeedUpdate = true; + geometry.tangentsNeedUpdate = true; + geometry.colorsNeedUpdate = true; + + } + + } + + } else if ( object instanceof THREE.Line ) { + + if ( ! geometry.__webglVertexBuffer ) { + + createLineBuffers( geometry ); + initLineBuffers( geometry, object ); + + geometry.verticesNeedUpdate = true; + geometry.colorsNeedUpdate = true; + geometry.lineDistancesNeedUpdate = true; + + } + + } else if ( object instanceof THREE.ParticleSystem ) { + + if ( ! geometry.__webglVertexBuffer ) { + + createParticleBuffers( geometry ); + initParticleBuffers( geometry, object ); + + geometry.verticesNeedUpdate = true; + geometry.colorsNeedUpdate = true; + + } + + } + + } + + if ( object.__webglActive === undefined ) { + + if ( object instanceof THREE.Mesh ) { + + geometry = object.geometry; + + if ( geometry instanceof THREE.BufferGeometry ) { + + addBuffer( scene.__webglObjects, geometry, object ); + + } else if ( geometry instanceof THREE.Geometry ) { + + for ( g in geometry.geometryGroups ) { + + geometryGroup = geometry.geometryGroups[ g ]; + + addBuffer( scene.__webglObjects, geometryGroup, object ); + + } + + } + + } else if ( object instanceof THREE.Line || + object instanceof THREE.ParticleSystem ) { + + geometry = object.geometry; + addBuffer( scene.__webglObjects, geometry, object ); + + } else if ( object instanceof THREE.ImmediateRenderObject || object.immediateRenderCallback ) { + + addBufferImmediate( scene.__webglObjectsImmediate, object ); + + } else if ( object instanceof THREE.Sprite ) { + + scene.__webglSprites.push( object ); + + } else if ( object instanceof THREE.LensFlare ) { + + scene.__webglFlares.push( object ); + + } + + object.__webglActive = true; + + } + + }; + + function addBuffer( objlist, buffer, object ) { + + objlist.push( + { + id: null, + buffer: buffer, + object: object, + opaque: null, + transparent: null, + z: 0 + } + ); + + }; + + function addBufferImmediate( objlist, object ) { + + objlist.push( + { + id: null, + object: object, + opaque: null, + transparent: null, + z: 0 + } + ); + + }; + + // Objects updates + + function updateObject( object ) { + + var geometry = object.geometry, + geometryGroup, customAttributesDirty, material; + + if ( geometry instanceof THREE.BufferGeometry ) { + + setDirectBuffers( geometry, _gl.DYNAMIC_DRAW ); + + } else if ( object instanceof THREE.Mesh ) { + + // check all geometry groups + + for( var i = 0, il = geometry.geometryGroupsList.length; i < il; i ++ ) { + + geometryGroup = geometry.geometryGroupsList[ i ]; + + material = getBufferMaterial( object, geometryGroup ); + + if ( geometry.buffersNeedUpdate ) { + + initMeshBuffers( geometryGroup, object ); + + } + + customAttributesDirty = material.attributes && areCustomAttributesDirty( material ); + + if ( geometry.verticesNeedUpdate || geometry.morphTargetsNeedUpdate || geometry.elementsNeedUpdate || + geometry.uvsNeedUpdate || geometry.normalsNeedUpdate || + geometry.colorsNeedUpdate || geometry.tangentsNeedUpdate || customAttributesDirty ) { + + setMeshBuffers( geometryGroup, object, _gl.DYNAMIC_DRAW, !geometry.dynamic, material ); + + } + + } + + geometry.verticesNeedUpdate = false; + geometry.morphTargetsNeedUpdate = false; + geometry.elementsNeedUpdate = false; + geometry.uvsNeedUpdate = false; + geometry.normalsNeedUpdate = false; + geometry.colorsNeedUpdate = false; + geometry.tangentsNeedUpdate = false; + + geometry.buffersNeedUpdate = false; + + material.attributes && clearCustomAttributes( material ); + + } else if ( object instanceof THREE.Line ) { + + material = getBufferMaterial( object, geometry ); + + customAttributesDirty = material.attributes && areCustomAttributesDirty( material ); + + if ( geometry.verticesNeedUpdate || geometry.colorsNeedUpdate || geometry.lineDistancesNeedUpdate || customAttributesDirty ) { + + setLineBuffers( geometry, _gl.DYNAMIC_DRAW ); + + } + + geometry.verticesNeedUpdate = false; + geometry.colorsNeedUpdate = false; + geometry.lineDistancesNeedUpdate = false; + + material.attributes && clearCustomAttributes( material ); + + + } else if ( object instanceof THREE.ParticleSystem ) { + + material = getBufferMaterial( object, geometry ); + + customAttributesDirty = material.attributes && areCustomAttributesDirty( material ); + + if ( geometry.verticesNeedUpdate || geometry.colorsNeedUpdate || object.sortParticles || customAttributesDirty ) { + + setParticleBuffers( geometry, _gl.DYNAMIC_DRAW, object ); + + } + + geometry.verticesNeedUpdate = false; + geometry.colorsNeedUpdate = false; + + material.attributes && clearCustomAttributes( material ); + + } + + }; + + // Objects updates - custom attributes check + + function areCustomAttributesDirty( material ) { + + for ( var a in material.attributes ) { + + if ( material.attributes[ a ].needsUpdate ) return true; + + } + + return false; + + }; + + function clearCustomAttributes( material ) { + + for ( var a in material.attributes ) { + + material.attributes[ a ].needsUpdate = false; + + } + + }; + + // Objects removal + + function removeObject( object, scene ) { + + if ( object instanceof THREE.Mesh || + object instanceof THREE.ParticleSystem || + object instanceof THREE.Line ) { + + removeInstances( scene.__webglObjects, object ); + + } else if ( object instanceof THREE.Sprite ) { + + removeInstancesDirect( scene.__webglSprites, object ); + + } else if ( object instanceof THREE.LensFlare ) { + + removeInstancesDirect( scene.__webglFlares, object ); + + } else if ( object instanceof THREE.ImmediateRenderObject || object.immediateRenderCallback ) { + + removeInstances( scene.__webglObjectsImmediate, object ); + + } + + delete object.__webglActive; + + }; + + function removeInstances( objlist, object ) { + + for ( var o = objlist.length - 1; o >= 0; o -- ) { + + if ( objlist[ o ].object === object ) { + + objlist.splice( o, 1 ); + + } + + } + + }; + + function removeInstancesDirect( objlist, object ) { + + for ( var o = objlist.length - 1; o >= 0; o -- ) { + + if ( objlist[ o ] === object ) { + + objlist.splice( o, 1 ); + + } + + } + + }; + + // Materials + + this.initMaterial = function ( material, lights, fog, object ) { + + material.addEventListener( 'dispose', onMaterialDispose ); + + var u, a, identifiers, i, parameters, maxLightCount, maxBones, maxShadows, shaderID; + + if ( material instanceof THREE.MeshDepthMaterial ) { + + shaderID = 'depth'; + + } else if ( material instanceof THREE.MeshNormalMaterial ) { + + shaderID = 'normal'; + + } else if ( material instanceof THREE.MeshBasicMaterial ) { + + shaderID = 'basic'; + + } else if ( material instanceof THREE.MeshLambertMaterial ) { + + shaderID = 'lambert'; + + } else if ( material instanceof THREE.MeshPhongMaterial ) { + + shaderID = 'phong'; + + } else if ( material instanceof THREE.LineBasicMaterial ) { + + shaderID = 'basic'; + + } else if ( material instanceof THREE.LineDashedMaterial ) { + + shaderID = 'dashed'; + + } else if ( material instanceof THREE.ParticleSystemMaterial ) { + + shaderID = 'particle_basic'; + + } + + if ( shaderID ) { + + setMaterialShaders( material, THREE.ShaderLib[ shaderID ] ); + + } + + // heuristics to create shader parameters according to lights in the scene + // (not to blow over maxLights budget) + + maxLightCount = allocateLights( lights ); + + maxShadows = allocateShadows( lights ); + + maxBones = allocateBones( object ); + + parameters = { + + map: !!material.map, + envMap: !!material.envMap, + lightMap: !!material.lightMap, + bumpMap: !!material.bumpMap, + normalMap: !!material.normalMap, + specularMap: !!material.specularMap, + + vertexColors: material.vertexColors, + + fog: fog, + useFog: material.fog, + fogExp: fog instanceof THREE.FogExp2, + + sizeAttenuation: material.sizeAttenuation, + + skinning: material.skinning, + maxBones: maxBones, + useVertexTexture: _supportsBoneTextures && object && object.useVertexTexture, + + morphTargets: material.morphTargets, + morphNormals: material.morphNormals, + maxMorphTargets: this.maxMorphTargets, + maxMorphNormals: this.maxMorphNormals, + + maxDirLights: maxLightCount.directional, + maxPointLights: maxLightCount.point, + maxSpotLights: maxLightCount.spot, + maxHemiLights: maxLightCount.hemi, + + maxShadows: maxShadows, + shadowMapEnabled: this.shadowMapEnabled && object.receiveShadow && maxShadows > 0, + shadowMapType: this.shadowMapType, + shadowMapDebug: this.shadowMapDebug, + shadowMapCascade: this.shadowMapCascade, + + alphaTest: material.alphaTest, + metal: material.metal, + wrapAround: material.wrapAround, + doubleSided: material.side === THREE.DoubleSide, + flipSided: material.side === THREE.BackSide + + }; + + material.program = buildProgram( shaderID, material.fragmentShader, material.vertexShader, material.uniforms, material.attributes, material.defines, parameters, material.index0AttributeName ); + + var attributes = material.program.attributes; + + if ( material.morphTargets ) { + + material.numSupportedMorphTargets = 0; + + var id, base = "morphTarget"; + + for ( i = 0; i < this.maxMorphTargets; i ++ ) { + + id = base + i; + + if ( attributes[ id ] >= 0 ) { + + material.numSupportedMorphTargets ++; + + } + + } + + } + + if ( material.morphNormals ) { + + material.numSupportedMorphNormals = 0; + + var id, base = "morphNormal"; + + for ( i = 0; i < this.maxMorphNormals; i ++ ) { + + id = base + i; + + if ( attributes[ id ] >= 0 ) { + + material.numSupportedMorphNormals ++; + + } + + } + + } + + material.uniformsList = []; + + for ( u in material.uniforms ) { + + material.uniformsList.push( [ material.uniforms[ u ], u ] ); + + } + + }; + + function setMaterialShaders( material, shaders ) { + + material.uniforms = THREE.UniformsUtils.clone( shaders.uniforms ); + material.vertexShader = shaders.vertexShader; + material.fragmentShader = shaders.fragmentShader; + + }; + + function setProgram( camera, lights, fog, material, object ) { + + _usedTextureUnits = 0; + + if ( material.needsUpdate ) { + + if ( material.program ) deallocateMaterial( material ); + + _this.initMaterial( material, lights, fog, object ); + material.needsUpdate = false; + + } + + if ( material.morphTargets ) { + + if ( ! object.__webglMorphTargetInfluences ) { + + object.__webglMorphTargetInfluences = new Float32Array( _this.maxMorphTargets ); + + } + + } + + var refreshMaterial = false; + + var program = material.program, + p_uniforms = program.uniforms, + m_uniforms = material.uniforms; + + if ( program !== _currentProgram ) { + + _gl.useProgram( program ); + _currentProgram = program; + + refreshMaterial = true; + + } + + if ( material.id !== _currentMaterialId ) { + + _currentMaterialId = material.id; + refreshMaterial = true; + + } + + if ( refreshMaterial || camera !== _currentCamera ) { + + _gl.uniformMatrix4fv( p_uniforms.projectionMatrix, false, camera.projectionMatrix.elements ); + + if ( camera !== _currentCamera ) _currentCamera = camera; + + } + + // skinning uniforms must be set even if material didn't change + // auto-setting of texture unit for bone texture must go before other textures + // not sure why, but otherwise weird things happen + + if ( material.skinning ) { + + if ( _supportsBoneTextures && object.useVertexTexture ) { + + if ( p_uniforms.boneTexture !== null ) { + + var textureUnit = getTextureUnit(); + + _gl.uniform1i( p_uniforms.boneTexture, textureUnit ); + _this.setTexture( object.boneTexture, textureUnit ); + + } + + if ( p_uniforms.boneTextureWidth !== null ) { + + _gl.uniform1i( p_uniforms.boneTextureWidth, object.boneTextureWidth ); + + } + + if ( p_uniforms.boneTextureHeight !== null ) { + + _gl.uniform1i( p_uniforms.boneTextureHeight, object.boneTextureHeight ); + + } + + } else { + + if ( p_uniforms.boneGlobalMatrices !== null ) { + + _gl.uniformMatrix4fv( p_uniforms.boneGlobalMatrices, false, object.boneMatrices ); + + } + + } + + } + + if ( refreshMaterial ) { + + // refresh uniforms common to several materials + + if ( fog && material.fog ) { + + refreshUniformsFog( m_uniforms, fog ); + + } + + if ( material instanceof THREE.MeshPhongMaterial || + material instanceof THREE.MeshLambertMaterial || + material.lights ) { + + if ( _lightsNeedUpdate ) { + + setupLights( program, lights ); + _lightsNeedUpdate = false; + + } + + refreshUniformsLights( m_uniforms, _lights ); + + } + + if ( material instanceof THREE.MeshBasicMaterial || + material instanceof THREE.MeshLambertMaterial || + material instanceof THREE.MeshPhongMaterial ) { + + refreshUniformsCommon( m_uniforms, material ); + + } + + // refresh single material specific uniforms + + if ( material instanceof THREE.LineBasicMaterial ) { + + refreshUniformsLine( m_uniforms, material ); + + } else if ( material instanceof THREE.LineDashedMaterial ) { + + refreshUniformsLine( m_uniforms, material ); + refreshUniformsDash( m_uniforms, material ); + + } else if ( material instanceof THREE.ParticleSystemMaterial ) { + + refreshUniformsParticle( m_uniforms, material ); + + } else if ( material instanceof THREE.MeshPhongMaterial ) { + + refreshUniformsPhong( m_uniforms, material ); + + } else if ( material instanceof THREE.MeshLambertMaterial ) { + + refreshUniformsLambert( m_uniforms, material ); + + } else if ( material instanceof THREE.MeshDepthMaterial ) { + + m_uniforms.mNear.value = camera.near; + m_uniforms.mFar.value = camera.far; + m_uniforms.opacity.value = material.opacity; + + } else if ( material instanceof THREE.MeshNormalMaterial ) { + + m_uniforms.opacity.value = material.opacity; + + } + + if ( object.receiveShadow && ! material._shadowPass ) { + + refreshUniformsShadow( m_uniforms, lights ); + + } + + // load common uniforms + + loadUniformsGeneric( program, material.uniformsList ); + + // load material specific uniforms + // (shader material also gets them for the sake of genericity) + + if ( material instanceof THREE.ShaderMaterial || + material instanceof THREE.MeshPhongMaterial || + material.envMap ) { + + if ( p_uniforms.cameraPosition !== null ) { + + _vector3.setFromMatrixPosition( camera.matrixWorld ); + _gl.uniform3f( p_uniforms.cameraPosition, _vector3.x, _vector3.y, _vector3.z ); + + } + + } + + if ( material instanceof THREE.MeshPhongMaterial || + material instanceof THREE.MeshLambertMaterial || + material instanceof THREE.ShaderMaterial || + material.skinning ) { + + if ( p_uniforms.viewMatrix !== null ) { + + _gl.uniformMatrix4fv( p_uniforms.viewMatrix, false, camera.matrixWorldInverse.elements ); + + } + + } + + } + + loadUniformsMatrices( p_uniforms, object ); + + if ( p_uniforms.modelMatrix !== null ) { + + _gl.uniformMatrix4fv( p_uniforms.modelMatrix, false, object.matrixWorld.elements ); + + } + + return program; + + }; + + // Uniforms (refresh uniforms objects) + + function refreshUniformsCommon ( uniforms, material ) { + + uniforms.opacity.value = material.opacity; + + if ( _this.gammaInput ) { + + uniforms.diffuse.value.copyGammaToLinear( material.color ); + + } else { + + uniforms.diffuse.value = material.color; + + } + + uniforms.map.value = material.map; + uniforms.lightMap.value = material.lightMap; + uniforms.specularMap.value = material.specularMap; + + if ( material.bumpMap ) { + + uniforms.bumpMap.value = material.bumpMap; + uniforms.bumpScale.value = material.bumpScale; + + } + + if ( material.normalMap ) { + + uniforms.normalMap.value = material.normalMap; + uniforms.normalScale.value.copy( material.normalScale ); + + } + + // uv repeat and offset setting priorities + // 1. color map + // 2. specular map + // 3. normal map + // 4. bump map + + var uvScaleMap; + + if ( material.map ) { + + uvScaleMap = material.map; + + } else if ( material.specularMap ) { + + uvScaleMap = material.specularMap; + + } else if ( material.normalMap ) { + + uvScaleMap = material.normalMap; + + } else if ( material.bumpMap ) { + + uvScaleMap = material.bumpMap; + + } + + if ( uvScaleMap !== undefined ) { + + var offset = uvScaleMap.offset; + var repeat = uvScaleMap.repeat; + + uniforms.offsetRepeat.value.set( offset.x, offset.y, repeat.x, repeat.y ); + + } + + uniforms.envMap.value = material.envMap; + uniforms.flipEnvMap.value = ( material.envMap instanceof THREE.WebGLRenderTargetCube ) ? 1 : -1; + + if ( _this.gammaInput ) { + + //uniforms.reflectivity.value = material.reflectivity * material.reflectivity; + uniforms.reflectivity.value = material.reflectivity; + + } else { + + uniforms.reflectivity.value = material.reflectivity; + + } + + uniforms.refractionRatio.value = material.refractionRatio; + uniforms.combine.value = material.combine; + uniforms.useRefract.value = material.envMap && material.envMap.mapping instanceof THREE.CubeRefractionMapping; + + }; + + function refreshUniformsLine ( uniforms, material ) { + + uniforms.diffuse.value = material.color; + uniforms.opacity.value = material.opacity; + + }; + + function refreshUniformsDash ( uniforms, material ) { + + uniforms.dashSize.value = material.dashSize; + uniforms.totalSize.value = material.dashSize + material.gapSize; + uniforms.scale.value = material.scale; + + }; + + function refreshUniformsParticle ( uniforms, material ) { + + uniforms.psColor.value = material.color; + uniforms.opacity.value = material.opacity; + uniforms.size.value = material.size; + uniforms.scale.value = _canvas.height / 2.0; // TODO: Cache this. + + uniforms.map.value = material.map; + + }; + + function refreshUniformsFog ( uniforms, fog ) { + + uniforms.fogColor.value = fog.color; + + if ( fog instanceof THREE.Fog ) { + + uniforms.fogNear.value = fog.near; + uniforms.fogFar.value = fog.far; + + } else if ( fog instanceof THREE.FogExp2 ) { + + uniforms.fogDensity.value = fog.density; + + } + + }; + + function refreshUniformsPhong ( uniforms, material ) { + + uniforms.shininess.value = material.shininess; + + if ( _this.gammaInput ) { + + uniforms.ambient.value.copyGammaToLinear( material.ambient ); + uniforms.emissive.value.copyGammaToLinear( material.emissive ); + uniforms.specular.value.copyGammaToLinear( material.specular ); + + } else { + + uniforms.ambient.value = material.ambient; + uniforms.emissive.value = material.emissive; + uniforms.specular.value = material.specular; + + } + + if ( material.wrapAround ) { + + uniforms.wrapRGB.value.copy( material.wrapRGB ); + + } + + }; + + function refreshUniformsLambert ( uniforms, material ) { + + if ( _this.gammaInput ) { + + uniforms.ambient.value.copyGammaToLinear( material.ambient ); + uniforms.emissive.value.copyGammaToLinear( material.emissive ); + + } else { + + uniforms.ambient.value = material.ambient; + uniforms.emissive.value = material.emissive; + + } + + if ( material.wrapAround ) { + + uniforms.wrapRGB.value.copy( material.wrapRGB ); + + } + + }; + + function refreshUniformsLights ( uniforms, lights ) { + + uniforms.ambientLightColor.value = lights.ambient; + + uniforms.directionalLightColor.value = lights.directional.colors; + uniforms.directionalLightDirection.value = lights.directional.positions; + + uniforms.pointLightColor.value = lights.point.colors; + uniforms.pointLightPosition.value = lights.point.positions; + uniforms.pointLightDistance.value = lights.point.distances; + + uniforms.spotLightColor.value = lights.spot.colors; + uniforms.spotLightPosition.value = lights.spot.positions; + uniforms.spotLightDistance.value = lights.spot.distances; + uniforms.spotLightDirection.value = lights.spot.directions; + uniforms.spotLightAngleCos.value = lights.spot.anglesCos; + uniforms.spotLightExponent.value = lights.spot.exponents; + + uniforms.hemisphereLightSkyColor.value = lights.hemi.skyColors; + uniforms.hemisphereLightGroundColor.value = lights.hemi.groundColors; + uniforms.hemisphereLightDirection.value = lights.hemi.positions; + + }; + + function refreshUniformsShadow ( uniforms, lights ) { + + if ( uniforms.shadowMatrix ) { + + var j = 0; + + for ( var i = 0, il = lights.length; i < il; i ++ ) { + + var light = lights[ i ]; + + if ( ! light.castShadow ) continue; + + if ( light instanceof THREE.SpotLight || ( light instanceof THREE.DirectionalLight && ! light.shadowCascade ) ) { + + uniforms.shadowMap.value[ j ] = light.shadowMap; + uniforms.shadowMapSize.value[ j ] = light.shadowMapSize; + + uniforms.shadowMatrix.value[ j ] = light.shadowMatrix; + + uniforms.shadowDarkness.value[ j ] = light.shadowDarkness; + uniforms.shadowBias.value[ j ] = light.shadowBias; + + j ++; + + } + + } + + } + + }; + + // Uniforms (load to GPU) + + function loadUniformsMatrices ( uniforms, object ) { + + _gl.uniformMatrix4fv( uniforms.modelViewMatrix, false, object._modelViewMatrix.elements ); + + if ( uniforms.normalMatrix ) { + + _gl.uniformMatrix3fv( uniforms.normalMatrix, false, object._normalMatrix.elements ); + + } + + }; + + function getTextureUnit() { + + var textureUnit = _usedTextureUnits; + + if ( textureUnit >= _maxTextures ) { + + console.warn( "WebGLRenderer: trying to use " + textureUnit + " texture units while this GPU supports only " + _maxTextures ); + + } + + _usedTextureUnits += 1; + + return textureUnit; + + }; + + function loadUniformsGeneric ( program, uniforms ) { + + var uniform, value, type, location, texture, textureUnit, i, il, j, jl, offset; + + for ( j = 0, jl = uniforms.length; j < jl; j ++ ) { + + location = program.uniforms[ uniforms[ j ][ 1 ] ]; + if ( !location ) continue; + + uniform = uniforms[ j ][ 0 ]; + + type = uniform.type; + value = uniform.value; + + if ( type === "i" ) { // single integer + + _gl.uniform1i( location, value ); + + } else if ( type === "f" ) { // single float + + _gl.uniform1f( location, value ); + + } else if ( type === "v2" ) { // single THREE.Vector2 + + _gl.uniform2f( location, value.x, value.y ); + + } else if ( type === "v3" ) { // single THREE.Vector3 + + _gl.uniform3f( location, value.x, value.y, value.z ); + + } else if ( type === "v4" ) { // single THREE.Vector4 + + _gl.uniform4f( location, value.x, value.y, value.z, value.w ); + + } else if ( type === "c" ) { // single THREE.Color + + _gl.uniform3f( location, value.r, value.g, value.b ); + + } else if ( type === "iv1" ) { // flat array of integers (JS or typed array) + + _gl.uniform1iv( location, value ); + + } else if ( type === "iv" ) { // flat array of integers with 3 x N size (JS or typed array) + + _gl.uniform3iv( location, value ); + + } else if ( type === "fv1" ) { // flat array of floats (JS or typed array) + + _gl.uniform1fv( location, value ); + + } else if ( type === "fv" ) { // flat array of floats with 3 x N size (JS or typed array) + + _gl.uniform3fv( location, value ); + + } else if ( type === "v2v" ) { // array of THREE.Vector2 + + if ( uniform._array === undefined ) { + + uniform._array = new Float32Array( 2 * value.length ); + + } + + for ( i = 0, il = value.length; i < il; i ++ ) { + + offset = i * 2; + + uniform._array[ offset ] = value[ i ].x; + uniform._array[ offset + 1 ] = value[ i ].y; + + } + + _gl.uniform2fv( location, uniform._array ); + + } else if ( type === "v3v" ) { // array of THREE.Vector3 + + if ( uniform._array === undefined ) { + + uniform._array = new Float32Array( 3 * value.length ); + + } + + for ( i = 0, il = value.length; i < il; i ++ ) { + + offset = i * 3; + + uniform._array[ offset ] = value[ i ].x; + uniform._array[ offset + 1 ] = value[ i ].y; + uniform._array[ offset + 2 ] = value[ i ].z; + + } + + _gl.uniform3fv( location, uniform._array ); + + } else if ( type === "v4v" ) { // array of THREE.Vector4 + + if ( uniform._array === undefined ) { + + uniform._array = new Float32Array( 4 * value.length ); + + } + + for ( i = 0, il = value.length; i < il; i ++ ) { + + offset = i * 4; + + uniform._array[ offset ] = value[ i ].x; + uniform._array[ offset + 1 ] = value[ i ].y; + uniform._array[ offset + 2 ] = value[ i ].z; + uniform._array[ offset + 3 ] = value[ i ].w; + + } + + _gl.uniform4fv( location, uniform._array ); + + } else if ( type === "m4") { // single THREE.Matrix4 + + if ( uniform._array === undefined ) { + + uniform._array = new Float32Array( 16 ); + + } + + value.flattenToArray( uniform._array ); + _gl.uniformMatrix4fv( location, false, uniform._array ); + + } else if ( type === "m4v" ) { // array of THREE.Matrix4 + + if ( uniform._array === undefined ) { + + uniform._array = new Float32Array( 16 * value.length ); + + } + + for ( i = 0, il = value.length; i < il; i ++ ) { + + value[ i ].flattenToArrayOffset( uniform._array, i * 16 ); + + } + + _gl.uniformMatrix4fv( location, false, uniform._array ); + + } else if ( type === "t" ) { // single THREE.Texture (2d or cube) + + texture = value; + textureUnit = getTextureUnit(); + + _gl.uniform1i( location, textureUnit ); + + if ( !texture ) continue; + + if ( texture.image instanceof Array && texture.image.length === 6 ) { + + setCubeTexture( texture, textureUnit ); + + } else if ( texture instanceof THREE.WebGLRenderTargetCube ) { + + setCubeTextureDynamic( texture, textureUnit ); + + } else { + + _this.setTexture( texture, textureUnit ); + + } + + } else if ( type === "tv" ) { // array of THREE.Texture (2d) + + if ( uniform._array === undefined ) { + + uniform._array = []; + + } + + for( i = 0, il = uniform.value.length; i < il; i ++ ) { + + uniform._array[ i ] = getTextureUnit(); + + } + + _gl.uniform1iv( location, uniform._array ); + + for( i = 0, il = uniform.value.length; i < il; i ++ ) { + + texture = uniform.value[ i ]; + textureUnit = uniform._array[ i ]; + + if ( !texture ) continue; + + _this.setTexture( texture, textureUnit ); + + } + + } else { + + console.warn( 'THREE.WebGLRenderer: Unknown uniform type: ' + type ); + + } + + } + + }; + + function setupMatrices ( object, camera ) { + + object._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + object._normalMatrix.getNormalMatrix( object._modelViewMatrix ); + + }; + + // + + function setColorGamma( array, offset, color, intensitySq ) { + + array[ offset ] = color.r * color.r * intensitySq; + array[ offset + 1 ] = color.g * color.g * intensitySq; + array[ offset + 2 ] = color.b * color.b * intensitySq; + + }; + + function setColorLinear( array, offset, color, intensity ) { + + array[ offset ] = color.r * intensity; + array[ offset + 1 ] = color.g * intensity; + array[ offset + 2 ] = color.b * intensity; + + }; + + function setupLights ( program, lights ) { + + var l, ll, light, n, + r = 0, g = 0, b = 0, + color, skyColor, groundColor, + intensity, intensitySq, + position, + distance, + + zlights = _lights, + + dirColors = zlights.directional.colors, + dirPositions = zlights.directional.positions, + + pointColors = zlights.point.colors, + pointPositions = zlights.point.positions, + pointDistances = zlights.point.distances, + + spotColors = zlights.spot.colors, + spotPositions = zlights.spot.positions, + spotDistances = zlights.spot.distances, + spotDirections = zlights.spot.directions, + spotAnglesCos = zlights.spot.anglesCos, + spotExponents = zlights.spot.exponents, + + hemiSkyColors = zlights.hemi.skyColors, + hemiGroundColors = zlights.hemi.groundColors, + hemiPositions = zlights.hemi.positions, + + dirLength = 0, + pointLength = 0, + spotLength = 0, + hemiLength = 0, + + dirCount = 0, + pointCount = 0, + spotCount = 0, + hemiCount = 0, + + dirOffset = 0, + pointOffset = 0, + spotOffset = 0, + hemiOffset = 0; + + for ( l = 0, ll = lights.length; l < ll; l ++ ) { + + light = lights[ l ]; + + if ( light.onlyShadow ) continue; + + color = light.color; + intensity = light.intensity; + distance = light.distance; + + if ( light instanceof THREE.AmbientLight ) { + + if ( ! light.visible ) continue; + + if ( _this.gammaInput ) { + + r += color.r * color.r; + g += color.g * color.g; + b += color.b * color.b; + + } else { + + r += color.r; + g += color.g; + b += color.b; + + } + + } else if ( light instanceof THREE.DirectionalLight ) { + + dirCount += 1; + + if ( ! light.visible ) continue; + + _direction.setFromMatrixPosition( light.matrixWorld ); + _vector3.setFromMatrixPosition( light.target.matrixWorld ); + _direction.sub( _vector3 ); + _direction.normalize(); + + // skip lights with undefined direction + // these create troubles in OpenGL (making pixel black) + + if ( _direction.x === 0 && _direction.y === 0 && _direction.z === 0 ) continue; + + dirOffset = dirLength * 3; + + dirPositions[ dirOffset ] = _direction.x; + dirPositions[ dirOffset + 1 ] = _direction.y; + dirPositions[ dirOffset + 2 ] = _direction.z; + + if ( _this.gammaInput ) { + + setColorGamma( dirColors, dirOffset, color, intensity * intensity ); + + } else { + + setColorLinear( dirColors, dirOffset, color, intensity ); + + } + + dirLength += 1; + + } else if ( light instanceof THREE.PointLight ) { + + pointCount += 1; + + if ( ! light.visible ) continue; + + pointOffset = pointLength * 3; + + if ( _this.gammaInput ) { + + setColorGamma( pointColors, pointOffset, color, intensity * intensity ); + + } else { + + setColorLinear( pointColors, pointOffset, color, intensity ); + + } + + _vector3.setFromMatrixPosition( light.matrixWorld ); + + pointPositions[ pointOffset ] = _vector3.x; + pointPositions[ pointOffset + 1 ] = _vector3.y; + pointPositions[ pointOffset + 2 ] = _vector3.z; + + pointDistances[ pointLength ] = distance; + + pointLength += 1; + + } else if ( light instanceof THREE.SpotLight ) { + + spotCount += 1; + + if ( ! light.visible ) continue; + + spotOffset = spotLength * 3; + + if ( _this.gammaInput ) { + + setColorGamma( spotColors, spotOffset, color, intensity * intensity ); + + } else { + + setColorLinear( spotColors, spotOffset, color, intensity ); + + } + + _vector3.setFromMatrixPosition( light.matrixWorld ); + + spotPositions[ spotOffset ] = _vector3.x; + spotPositions[ spotOffset + 1 ] = _vector3.y; + spotPositions[ spotOffset + 2 ] = _vector3.z; + + spotDistances[ spotLength ] = distance; + + _direction.copy( _vector3 ); + _vector3.setFromMatrixPosition( light.target.matrixWorld ); + _direction.sub( _vector3 ); + _direction.normalize(); + + spotDirections[ spotOffset ] = _direction.x; + spotDirections[ spotOffset + 1 ] = _direction.y; + spotDirections[ spotOffset + 2 ] = _direction.z; + + spotAnglesCos[ spotLength ] = Math.cos( light.angle ); + spotExponents[ spotLength ] = light.exponent; + + spotLength += 1; + + } else if ( light instanceof THREE.HemisphereLight ) { + + hemiCount += 1; + + if ( ! light.visible ) continue; + + _direction.setFromMatrixPosition( light.matrixWorld ); + _direction.normalize(); + + // skip lights with undefined direction + // these create troubles in OpenGL (making pixel black) + + if ( _direction.x === 0 && _direction.y === 0 && _direction.z === 0 ) continue; + + hemiOffset = hemiLength * 3; + + hemiPositions[ hemiOffset ] = _direction.x; + hemiPositions[ hemiOffset + 1 ] = _direction.y; + hemiPositions[ hemiOffset + 2 ] = _direction.z; + + skyColor = light.color; + groundColor = light.groundColor; + + if ( _this.gammaInput ) { + + intensitySq = intensity * intensity; + + setColorGamma( hemiSkyColors, hemiOffset, skyColor, intensitySq ); + setColorGamma( hemiGroundColors, hemiOffset, groundColor, intensitySq ); + + } else { + + setColorLinear( hemiSkyColors, hemiOffset, skyColor, intensity ); + setColorLinear( hemiGroundColors, hemiOffset, groundColor, intensity ); + + } + + hemiLength += 1; + + } + + } + + // null eventual remains from removed lights + // (this is to avoid if in shader) + + for ( l = dirLength * 3, ll = Math.max( dirColors.length, dirCount * 3 ); l < ll; l ++ ) dirColors[ l ] = 0.0; + for ( l = pointLength * 3, ll = Math.max( pointColors.length, pointCount * 3 ); l < ll; l ++ ) pointColors[ l ] = 0.0; + for ( l = spotLength * 3, ll = Math.max( spotColors.length, spotCount * 3 ); l < ll; l ++ ) spotColors[ l ] = 0.0; + for ( l = hemiLength * 3, ll = Math.max( hemiSkyColors.length, hemiCount * 3 ); l < ll; l ++ ) hemiSkyColors[ l ] = 0.0; + for ( l = hemiLength * 3, ll = Math.max( hemiGroundColors.length, hemiCount * 3 ); l < ll; l ++ ) hemiGroundColors[ l ] = 0.0; + + zlights.directional.length = dirLength; + zlights.point.length = pointLength; + zlights.spot.length = spotLength; + zlights.hemi.length = hemiLength; + + zlights.ambient[ 0 ] = r; + zlights.ambient[ 1 ] = g; + zlights.ambient[ 2 ] = b; + + }; + + // GL state setting + + this.setFaceCulling = function ( cullFace, frontFaceDirection ) { + + if ( cullFace === THREE.CullFaceNone ) { + + _gl.disable( _gl.CULL_FACE ); + + } else { + + if ( frontFaceDirection === THREE.FrontFaceDirectionCW ) { + + _gl.frontFace( _gl.CW ); + + } else { + + _gl.frontFace( _gl.CCW ); + + } + + if ( cullFace === THREE.CullFaceBack ) { + + _gl.cullFace( _gl.BACK ); + + } else if ( cullFace === THREE.CullFaceFront ) { + + _gl.cullFace( _gl.FRONT ); + + } else { + + _gl.cullFace( _gl.FRONT_AND_BACK ); + + } + + _gl.enable( _gl.CULL_FACE ); + + } + + }; + + this.setMaterialFaces = function ( material ) { + + var doubleSided = material.side === THREE.DoubleSide; + var flipSided = material.side === THREE.BackSide; + + if ( _oldDoubleSided !== doubleSided ) { + + if ( doubleSided ) { + + _gl.disable( _gl.CULL_FACE ); + + } else { + + _gl.enable( _gl.CULL_FACE ); + + } + + _oldDoubleSided = doubleSided; + + } + + if ( _oldFlipSided !== flipSided ) { + + if ( flipSided ) { + + _gl.frontFace( _gl.CW ); + + } else { + + _gl.frontFace( _gl.CCW ); + + } + + _oldFlipSided = flipSided; + + } + + }; + + this.setDepthTest = function ( depthTest ) { + + if ( _oldDepthTest !== depthTest ) { + + if ( depthTest ) { + + _gl.enable( _gl.DEPTH_TEST ); + + } else { + + _gl.disable( _gl.DEPTH_TEST ); + + } + + _oldDepthTest = depthTest; + + } + + }; + + this.setDepthWrite = function ( depthWrite ) { + + if ( _oldDepthWrite !== depthWrite ) { + + _gl.depthMask( depthWrite ); + _oldDepthWrite = depthWrite; + + } + + }; + + function setLineWidth ( width ) { + + if ( width !== _oldLineWidth ) { + + _gl.lineWidth( width ); + + _oldLineWidth = width; + + } + + }; + + function setPolygonOffset ( polygonoffset, factor, units ) { + + if ( _oldPolygonOffset !== polygonoffset ) { + + if ( polygonoffset ) { + + _gl.enable( _gl.POLYGON_OFFSET_FILL ); + + } else { + + _gl.disable( _gl.POLYGON_OFFSET_FILL ); + + } + + _oldPolygonOffset = polygonoffset; + + } + + if ( polygonoffset && ( _oldPolygonOffsetFactor !== factor || _oldPolygonOffsetUnits !== units ) ) { + + _gl.polygonOffset( factor, units ); + + _oldPolygonOffsetFactor = factor; + _oldPolygonOffsetUnits = units; + + } + + }; + + this.setBlending = function ( blending, blendEquation, blendSrc, blendDst ) { + + if ( blending !== _oldBlending ) { + + if ( blending === THREE.NoBlending ) { + + _gl.disable( _gl.BLEND ); + + } else if ( blending === THREE.AdditiveBlending ) { + + _gl.enable( _gl.BLEND ); + _gl.blendEquation( _gl.FUNC_ADD ); + _gl.blendFunc( _gl.SRC_ALPHA, _gl.ONE ); + + } else if ( blending === THREE.SubtractiveBlending ) { + + // TODO: Find blendFuncSeparate() combination + _gl.enable( _gl.BLEND ); + _gl.blendEquation( _gl.FUNC_ADD ); + _gl.blendFunc( _gl.ZERO, _gl.ONE_MINUS_SRC_COLOR ); + + } else if ( blending === THREE.MultiplyBlending ) { + + // TODO: Find blendFuncSeparate() combination + _gl.enable( _gl.BLEND ); + _gl.blendEquation( _gl.FUNC_ADD ); + _gl.blendFunc( _gl.ZERO, _gl.SRC_COLOR ); + + } else if ( blending === THREE.CustomBlending ) { + + _gl.enable( _gl.BLEND ); + + } else { + + _gl.enable( _gl.BLEND ); + _gl.blendEquationSeparate( _gl.FUNC_ADD, _gl.FUNC_ADD ); + _gl.blendFuncSeparate( _gl.SRC_ALPHA, _gl.ONE_MINUS_SRC_ALPHA, _gl.ONE, _gl.ONE_MINUS_SRC_ALPHA ); + + } + + _oldBlending = blending; + + } + + if ( blending === THREE.CustomBlending ) { + + if ( blendEquation !== _oldBlendEquation ) { + + _gl.blendEquation( paramThreeToGL( blendEquation ) ); + + _oldBlendEquation = blendEquation; + + } + + if ( blendSrc !== _oldBlendSrc || blendDst !== _oldBlendDst ) { + + _gl.blendFunc( paramThreeToGL( blendSrc ), paramThreeToGL( blendDst ) ); + + _oldBlendSrc = blendSrc; + _oldBlendDst = blendDst; + + } + + } else { + + _oldBlendEquation = null; + _oldBlendSrc = null; + _oldBlendDst = null; + + } + + }; + + // Defines + + function generateDefines ( defines ) { + + var value, chunk, chunks = []; + + for ( var d in defines ) { + + value = defines[ d ]; + if ( value === false ) continue; + + chunk = "#define " + d + " " + value; + chunks.push( chunk ); + + } + + return chunks.join( "\n" ); + + }; + + // Shaders + + function buildProgram( shaderID, fragmentShader, vertexShader, uniforms, attributes, defines, parameters, index0AttributeName ) { + + var p, pl, d, program, code; + var chunks = []; + + // Generate code + + if ( shaderID ) { + + chunks.push( shaderID ); + + } else { + + chunks.push( fragmentShader ); + chunks.push( vertexShader ); + + } + + for ( d in defines ) { + + chunks.push( d ); + chunks.push( defines[ d ] ); + + } + + for ( p in parameters ) { + + chunks.push( p ); + chunks.push( parameters[ p ] ); + + } + + code = chunks.join(); + + // Check if code has been already compiled + + for ( p = 0, pl = _programs.length; p < pl; p ++ ) { + + var programInfo = _programs[ p ]; + + if ( programInfo.code === code ) { + + // console.log( "Code already compiled." /*: \n\n" + code*/ ); + + programInfo.usedTimes ++; + + return programInfo.program; + + } + + } + + var shadowMapTypeDefine = "SHADOWMAP_TYPE_BASIC"; + + if ( parameters.shadowMapType === THREE.PCFShadowMap ) { + + shadowMapTypeDefine = "SHADOWMAP_TYPE_PCF"; + + } else if ( parameters.shadowMapType === THREE.PCFSoftShadowMap ) { + + shadowMapTypeDefine = "SHADOWMAP_TYPE_PCF_SOFT"; + + } + + // console.log( "building new program " ); + + // + + var customDefines = generateDefines( defines ); + + // + + program = _gl.createProgram(); + + var prefix_vertex = [ + + "precision " + _precision + " float;", + "precision " + _precision + " int;", + + customDefines, + + _supportsVertexTextures ? "#define VERTEX_TEXTURES" : "", + + _this.gammaInput ? "#define GAMMA_INPUT" : "", + _this.gammaOutput ? "#define GAMMA_OUTPUT" : "", + + "#define MAX_DIR_LIGHTS " + parameters.maxDirLights, + "#define MAX_POINT_LIGHTS " + parameters.maxPointLights, + "#define MAX_SPOT_LIGHTS " + parameters.maxSpotLights, + "#define MAX_HEMI_LIGHTS " + parameters.maxHemiLights, + + "#define MAX_SHADOWS " + parameters.maxShadows, + + "#define MAX_BONES " + parameters.maxBones, + + parameters.map ? "#define USE_MAP" : "", + parameters.envMap ? "#define USE_ENVMAP" : "", + parameters.lightMap ? "#define USE_LIGHTMAP" : "", + parameters.bumpMap ? "#define USE_BUMPMAP" : "", + parameters.normalMap ? "#define USE_NORMALMAP" : "", + parameters.specularMap ? "#define USE_SPECULARMAP" : "", + parameters.vertexColors ? "#define USE_COLOR" : "", + + parameters.skinning ? "#define USE_SKINNING" : "", + parameters.useVertexTexture ? "#define BONE_TEXTURE" : "", + + parameters.morphTargets ? "#define USE_MORPHTARGETS" : "", + parameters.morphNormals ? "#define USE_MORPHNORMALS" : "", + parameters.wrapAround ? "#define WRAP_AROUND" : "", + parameters.doubleSided ? "#define DOUBLE_SIDED" : "", + parameters.flipSided ? "#define FLIP_SIDED" : "", + + parameters.shadowMapEnabled ? "#define USE_SHADOWMAP" : "", + parameters.shadowMapEnabled ? "#define " + shadowMapTypeDefine : "", + parameters.shadowMapDebug ? "#define SHADOWMAP_DEBUG" : "", + parameters.shadowMapCascade ? "#define SHADOWMAP_CASCADE" : "", + + parameters.sizeAttenuation ? "#define USE_SIZEATTENUATION" : "", + + "uniform mat4 modelMatrix;", + "uniform mat4 modelViewMatrix;", + "uniform mat4 projectionMatrix;", + "uniform mat4 viewMatrix;", + "uniform mat3 normalMatrix;", + "uniform vec3 cameraPosition;", + + "attribute vec3 position;", + "attribute vec3 normal;", + "attribute vec2 uv;", + "attribute vec2 uv2;", + + "#ifdef USE_COLOR", + + "attribute vec3 color;", + + "#endif", + + "#ifdef USE_MORPHTARGETS", + + "attribute vec3 morphTarget0;", + "attribute vec3 morphTarget1;", + "attribute vec3 morphTarget2;", + "attribute vec3 morphTarget3;", + + "#ifdef USE_MORPHNORMALS", + + "attribute vec3 morphNormal0;", + "attribute vec3 morphNormal1;", + "attribute vec3 morphNormal2;", + "attribute vec3 morphNormal3;", + + "#else", + + "attribute vec3 morphTarget4;", + "attribute vec3 morphTarget5;", + "attribute vec3 morphTarget6;", + "attribute vec3 morphTarget7;", + + "#endif", + + "#endif", + + "#ifdef USE_SKINNING", + + "attribute vec4 skinIndex;", + "attribute vec4 skinWeight;", + + "#endif", + + "" + + ].join("\n"); + + var prefix_fragment = [ + + "precision " + _precision + " float;", + "precision " + _precision + " int;", + + ( parameters.bumpMap || parameters.normalMap ) ? "#extension GL_OES_standard_derivatives : enable" : "", + + customDefines, + + "#define MAX_DIR_LIGHTS " + parameters.maxDirLights, + "#define MAX_POINT_LIGHTS " + parameters.maxPointLights, + "#define MAX_SPOT_LIGHTS " + parameters.maxSpotLights, + "#define MAX_HEMI_LIGHTS " + parameters.maxHemiLights, + + "#define MAX_SHADOWS " + parameters.maxShadows, + + parameters.alphaTest ? "#define ALPHATEST " + parameters.alphaTest: "", + + _this.gammaInput ? "#define GAMMA_INPUT" : "", + _this.gammaOutput ? "#define GAMMA_OUTPUT" : "", + + ( parameters.useFog && parameters.fog ) ? "#define USE_FOG" : "", + ( parameters.useFog && parameters.fogExp ) ? "#define FOG_EXP2" : "", + + parameters.map ? "#define USE_MAP" : "", + parameters.envMap ? "#define USE_ENVMAP" : "", + parameters.lightMap ? "#define USE_LIGHTMAP" : "", + parameters.bumpMap ? "#define USE_BUMPMAP" : "", + parameters.normalMap ? "#define USE_NORMALMAP" : "", + parameters.specularMap ? "#define USE_SPECULARMAP" : "", + parameters.vertexColors ? "#define USE_COLOR" : "", + + parameters.metal ? "#define METAL" : "", + parameters.wrapAround ? "#define WRAP_AROUND" : "", + parameters.doubleSided ? "#define DOUBLE_SIDED" : "", + parameters.flipSided ? "#define FLIP_SIDED" : "", + + parameters.shadowMapEnabled ? "#define USE_SHADOWMAP" : "", + parameters.shadowMapEnabled ? "#define " + shadowMapTypeDefine : "", + parameters.shadowMapDebug ? "#define SHADOWMAP_DEBUG" : "", + parameters.shadowMapCascade ? "#define SHADOWMAP_CASCADE" : "", + + "uniform mat4 viewMatrix;", + "uniform vec3 cameraPosition;", + "" + + ].join("\n"); + + var glVertexShader = getShader( "vertex", prefix_vertex + vertexShader ); + var glFragmentShader = getShader( "fragment", prefix_fragment + fragmentShader ); + + _gl.attachShader( program, glVertexShader ); + _gl.attachShader( program, glFragmentShader ); + + // Force a particular attribute to index 0. + // because potentially expensive emulation is done by browser if attribute 0 is disabled. + // And, color, for example is often automatically bound to index 0 so disabling it + if ( index0AttributeName !== undefined ) { + + _gl.bindAttribLocation( program, 0, index0AttributeName ); + + } + + _gl.linkProgram( program ); + + if ( _gl.getProgramParameter( program, _gl.LINK_STATUS ) === false ) { + + console.error( 'Could not initialise shader' ); + console.error( 'gl.VALIDATE_STATUS', _gl.getProgramParameter( program, _gl.VALIDATE_STATUS ) ); + console.error( 'gl.getError()', _gl.getError() ); + + } + + if ( _gl.getProgramInfoLog( program ) !== '' ) { + + console.error( 'gl.getProgramInfoLog()', _gl.getProgramInfoLog( program ) ); + + } + + // clean up + + _gl.deleteShader( glFragmentShader ); + _gl.deleteShader( glVertexShader ); + + // console.log( prefix_fragment + fragmentShader ); + // console.log( prefix_vertex + vertexShader ); + + program.uniforms = {}; + program.attributes = {}; + + var identifiers, u, a, i; + + // cache uniform locations + + identifiers = [ + + 'viewMatrix', 'modelViewMatrix', 'projectionMatrix', 'normalMatrix', 'modelMatrix', 'cameraPosition', + 'morphTargetInfluences' + + ]; + + if ( parameters.useVertexTexture ) { + + identifiers.push( 'boneTexture' ); + identifiers.push( 'boneTextureWidth' ); + identifiers.push( 'boneTextureHeight' ); + + } else { + + identifiers.push( 'boneGlobalMatrices' ); + + } + + for ( u in uniforms ) { + + identifiers.push( u ); + + } + + cacheUniformLocations( program, identifiers ); + + // cache attributes locations + + identifiers = [ + + "position", "normal", "uv", "uv2", "tangent", "color", + "skinIndex", "skinWeight", "lineDistance" + + ]; + + for ( i = 0; i < parameters.maxMorphTargets; i ++ ) { + + identifiers.push( "morphTarget" + i ); + + } + + for ( i = 0; i < parameters.maxMorphNormals; i ++ ) { + + identifiers.push( "morphNormal" + i ); + + } + + for ( a in attributes ) { + + identifiers.push( a ); + + } + + cacheAttributeLocations( program, identifiers ); + + program.id = _programs_counter ++; + + _programs.push( { program: program, code: code, usedTimes: 1 } ); + + _this.info.memory.programs = _programs.length; + + return program; + + }; + + // Shader parameters cache + + function cacheUniformLocations ( program, identifiers ) { + + var i, l, id; + + for( i = 0, l = identifiers.length; i < l; i ++ ) { + + id = identifiers[ i ]; + program.uniforms[ id ] = _gl.getUniformLocation( program, id ); + + } + + }; + + function cacheAttributeLocations ( program, identifiers ) { + + var i, l, id; + + for( i = 0, l = identifiers.length; i < l; i ++ ) { + + id = identifiers[ i ]; + program.attributes[ id ] = _gl.getAttribLocation( program, id ); + + } + + }; + + function addLineNumbers ( string ) { + + var chunks = string.split( "\n" ); + + for ( var i = 0, il = chunks.length; i < il; i ++ ) { + + // Chrome reports shader errors on lines + // starting counting from 1 + + chunks[ i ] = ( i + 1 ) + ": " + chunks[ i ]; + + } + + return chunks.join( "\n" ); + + }; + + function getShader ( type, string ) { + + var shader; + + if ( type === "fragment" ) { + + shader = _gl.createShader( _gl.FRAGMENT_SHADER ); + + } else if ( type === "vertex" ) { + + shader = _gl.createShader( _gl.VERTEX_SHADER ); + + } + + _gl.shaderSource( shader, string ); + _gl.compileShader( shader ); + + if ( !_gl.getShaderParameter( shader, _gl.COMPILE_STATUS ) ) { + + console.error( _gl.getShaderInfoLog( shader ) ); + console.error( addLineNumbers( string ) ); + return null; + + } + + return shader; + + }; + + // Textures + + function setTextureParameters ( textureType, texture, isImagePowerOfTwo ) { + + if ( isImagePowerOfTwo ) { + + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, paramThreeToGL( texture.wrapS ) ); + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, paramThreeToGL( texture.wrapT ) ); + + _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, paramThreeToGL( texture.magFilter ) ); + _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, paramThreeToGL( texture.minFilter ) ); + + } else { + + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE ); + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE ); + + _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterFallback( texture.magFilter ) ); + _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterFallback( texture.minFilter ) ); + + } + + if ( _glExtensionTextureFilterAnisotropic && texture.type !== THREE.FloatType ) { + + if ( texture.anisotropy > 1 || texture.__oldAnisotropy ) { + + _gl.texParameterf( textureType, _glExtensionTextureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, _maxAnisotropy ) ); + texture.__oldAnisotropy = texture.anisotropy; + + } + + } + + }; + + this.setTexture = function ( texture, slot ) { + + if ( texture.needsUpdate ) { + + if ( ! texture.__webglInit ) { + + texture.__webglInit = true; + + texture.addEventListener( 'dispose', onTextureDispose ); + + texture.__webglTexture = _gl.createTexture(); + + _this.info.memory.textures ++; + + } + + _gl.activeTexture( _gl.TEXTURE0 + slot ); + _gl.bindTexture( _gl.TEXTURE_2D, texture.__webglTexture ); + + _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); + _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); + _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); + + var image = texture.image, + isImagePowerOfTwo = THREE.Math.isPowerOfTwo( image.width ) && THREE.Math.isPowerOfTwo( image.height ), + glFormat = paramThreeToGL( texture.format ), + glType = paramThreeToGL( texture.type ); + + setTextureParameters( _gl.TEXTURE_2D, texture, isImagePowerOfTwo ); + + var mipmap, mipmaps = texture.mipmaps; + + if ( texture instanceof THREE.DataTexture ) { + + // use manually created mipmaps if available + // if there are no manual mipmaps + // set 0 level mipmap and then use GL to generate other mipmap levels + + if ( mipmaps.length > 0 && isImagePowerOfTwo ) { + + for ( var i = 0, il = mipmaps.length; i < il; i ++ ) { + + mipmap = mipmaps[ i ]; + _gl.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); + + } + + texture.generateMipmaps = false; + + } else { + + _gl.texImage2D( _gl.TEXTURE_2D, 0, glFormat, image.width, image.height, 0, glFormat, glType, image.data ); + + } + + } else if ( texture instanceof THREE.CompressedTexture ) { + + for( var i = 0, il = mipmaps.length; i < il; i ++ ) { + + mipmap = mipmaps[ i ]; + if ( texture.format!==THREE.RGBAFormat ) { + _gl.compressedTexImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, mipmap.data ); + } else { + _gl.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); + } + + } + + } else { // regular Texture (image, video, canvas) + + // use manually created mipmaps if available + // if there are no manual mipmaps + // set 0 level mipmap and then use GL to generate other mipmap levels + + if ( mipmaps.length > 0 && isImagePowerOfTwo ) { + + for ( var i = 0, il = mipmaps.length; i < il; i ++ ) { + + mipmap = mipmaps[ i ]; + _gl.texImage2D( _gl.TEXTURE_2D, i, glFormat, glFormat, glType, mipmap ); + + } + + texture.generateMipmaps = false; + + } else { + + _gl.texImage2D( _gl.TEXTURE_2D, 0, glFormat, glFormat, glType, texture.image ); + + } + + } + + if ( texture.generateMipmaps && isImagePowerOfTwo ) _gl.generateMipmap( _gl.TEXTURE_2D ); + + texture.needsUpdate = false; + + if ( texture.onUpdate ) texture.onUpdate(); + + } else { + + _gl.activeTexture( _gl.TEXTURE0 + slot ); + _gl.bindTexture( _gl.TEXTURE_2D, texture.__webglTexture ); + + } + + }; + + function clampToMaxSize ( image, maxSize ) { + + if ( image.width <= maxSize && image.height <= maxSize ) { + + return image; + + } + + // Warning: Scaling through the canvas will only work with images that use + // premultiplied alpha. + + var maxDimension = Math.max( image.width, image.height ); + var newWidth = Math.floor( image.width * maxSize / maxDimension ); + var newHeight = Math.floor( image.height * maxSize / maxDimension ); + + var canvas = document.createElement( 'canvas' ); + canvas.width = newWidth; + canvas.height = newHeight; + + var ctx = canvas.getContext( "2d" ); + ctx.drawImage( image, 0, 0, image.width, image.height, 0, 0, newWidth, newHeight ); + + return canvas; + + } + + function setCubeTexture ( texture, slot ) { + + if ( texture.image.length === 6 ) { + + if ( texture.needsUpdate ) { + + if ( ! texture.image.__webglTextureCube ) { + + texture.addEventListener( 'dispose', onTextureDispose ); + + texture.image.__webglTextureCube = _gl.createTexture(); + + _this.info.memory.textures ++; + + } + + _gl.activeTexture( _gl.TEXTURE0 + slot ); + _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, texture.image.__webglTextureCube ); + + _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); + + var isCompressed = texture instanceof THREE.CompressedTexture; + + var cubeImage = []; + + for ( var i = 0; i < 6; i ++ ) { + + if ( _this.autoScaleCubemaps && ! isCompressed ) { + + cubeImage[ i ] = clampToMaxSize( texture.image[ i ], _maxCubemapSize ); + + } else { + + cubeImage[ i ] = texture.image[ i ]; + + } + + } + + var image = cubeImage[ 0 ], + isImagePowerOfTwo = THREE.Math.isPowerOfTwo( image.width ) && THREE.Math.isPowerOfTwo( image.height ), + glFormat = paramThreeToGL( texture.format ), + glType = paramThreeToGL( texture.type ); + + setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, isImagePowerOfTwo ); + + for ( var i = 0; i < 6; i ++ ) { + + if( !isCompressed ) { + + _gl.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, glFormat, glType, cubeImage[ i ] ); + + } else { + + var mipmap, mipmaps = cubeImage[ i ].mipmaps; + + for( var j = 0, jl = mipmaps.length; j < jl; j ++ ) { + + mipmap = mipmaps[ j ]; + if ( texture.format!==THREE.RGBAFormat ) { + + _gl.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, mipmap.data ); + + } else { + _gl.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); + } + + } + } + } + + if ( texture.generateMipmaps && isImagePowerOfTwo ) { + + _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP ); + + } + + texture.needsUpdate = false; + + if ( texture.onUpdate ) texture.onUpdate(); + + } else { + + _gl.activeTexture( _gl.TEXTURE0 + slot ); + _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, texture.image.__webglTextureCube ); + + } + + } + + }; + + function setCubeTextureDynamic ( texture, slot ) { + + _gl.activeTexture( _gl.TEXTURE0 + slot ); + _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, texture.__webglTexture ); + + }; + + // Render targets + + function setupFrameBuffer ( framebuffer, renderTarget, textureTarget ) { + + _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, textureTarget, renderTarget.__webglTexture, 0 ); + + }; + + function setupRenderBuffer ( renderbuffer, renderTarget ) { + + _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); + + if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) { + + _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_COMPONENT16, renderTarget.width, renderTarget.height ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); + + /* For some reason this is not working. Defaulting to RGBA4. + } else if( ! renderTarget.depthBuffer && renderTarget.stencilBuffer ) { + + _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.STENCIL_INDEX8, renderTarget.width, renderTarget.height ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); + */ + } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) { + + _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); + + } else { + + _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.RGBA4, renderTarget.width, renderTarget.height ); + + } + + }; + + this.setRenderTarget = function ( renderTarget ) { + + var isCube = ( renderTarget instanceof THREE.WebGLRenderTargetCube ); + + if ( renderTarget && ! renderTarget.__webglFramebuffer ) { + + if ( renderTarget.depthBuffer === undefined ) renderTarget.depthBuffer = true; + if ( renderTarget.stencilBuffer === undefined ) renderTarget.stencilBuffer = true; + + renderTarget.addEventListener( 'dispose', onRenderTargetDispose ); + + renderTarget.__webglTexture = _gl.createTexture(); + + _this.info.memory.textures ++; + + // Setup texture, create render and frame buffers + + var isTargetPowerOfTwo = THREE.Math.isPowerOfTwo( renderTarget.width ) && THREE.Math.isPowerOfTwo( renderTarget.height ), + glFormat = paramThreeToGL( renderTarget.format ), + glType = paramThreeToGL( renderTarget.type ); + + if ( isCube ) { + + renderTarget.__webglFramebuffer = []; + renderTarget.__webglRenderbuffer = []; + + _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, renderTarget.__webglTexture ); + setTextureParameters( _gl.TEXTURE_CUBE_MAP, renderTarget, isTargetPowerOfTwo ); + + for ( var i = 0; i < 6; i ++ ) { + + renderTarget.__webglFramebuffer[ i ] = _gl.createFramebuffer(); + renderTarget.__webglRenderbuffer[ i ] = _gl.createRenderbuffer(); + + _gl.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null ); + + setupFrameBuffer( renderTarget.__webglFramebuffer[ i ], renderTarget, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i ); + setupRenderBuffer( renderTarget.__webglRenderbuffer[ i ], renderTarget ); + + } + + if ( isTargetPowerOfTwo ) _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP ); + + } else { + + renderTarget.__webglFramebuffer = _gl.createFramebuffer(); + + if ( renderTarget.shareDepthFrom ) { + + renderTarget.__webglRenderbuffer = renderTarget.shareDepthFrom.__webglRenderbuffer; + + } else { + + renderTarget.__webglRenderbuffer = _gl.createRenderbuffer(); + + } + + _gl.bindTexture( _gl.TEXTURE_2D, renderTarget.__webglTexture ); + setTextureParameters( _gl.TEXTURE_2D, renderTarget, isTargetPowerOfTwo ); + + _gl.texImage2D( _gl.TEXTURE_2D, 0, glFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null ); + + setupFrameBuffer( renderTarget.__webglFramebuffer, renderTarget, _gl.TEXTURE_2D ); + + if ( renderTarget.shareDepthFrom ) { + + if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) { + + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderTarget.__webglRenderbuffer ); + + } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) { + + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderTarget.__webglRenderbuffer ); + + } + + } else { + + setupRenderBuffer( renderTarget.__webglRenderbuffer, renderTarget ); + + } + + if ( isTargetPowerOfTwo ) _gl.generateMipmap( _gl.TEXTURE_2D ); + + } + + // Release everything + + if ( isCube ) { + + _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, null ); + + } else { + + _gl.bindTexture( _gl.TEXTURE_2D, null ); + + } + + _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); + _gl.bindFramebuffer( _gl.FRAMEBUFFER, null ); + + } + + var framebuffer, width, height, vx, vy; + + if ( renderTarget ) { + + if ( isCube ) { + + framebuffer = renderTarget.__webglFramebuffer[ renderTarget.activeCubeFace ]; + + } else { + + framebuffer = renderTarget.__webglFramebuffer; + + } + + width = renderTarget.width; + height = renderTarget.height; + + vx = 0; + vy = 0; + + } else { + + framebuffer = null; + + width = _viewportWidth; + height = _viewportHeight; + + vx = _viewportX; + vy = _viewportY; + + } + + if ( framebuffer !== _currentFramebuffer ) { + + _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + _gl.viewport( vx, vy, width, height ); + + _currentFramebuffer = framebuffer; + + } + + _currentWidth = width; + _currentHeight = height; + + }; + + function updateRenderTargetMipmap ( renderTarget ) { + + if ( renderTarget instanceof THREE.WebGLRenderTargetCube ) { + + _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, renderTarget.__webglTexture ); + _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP ); + _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, null ); + + } else { + + _gl.bindTexture( _gl.TEXTURE_2D, renderTarget.__webglTexture ); + _gl.generateMipmap( _gl.TEXTURE_2D ); + _gl.bindTexture( _gl.TEXTURE_2D, null ); + + } + + }; + + // Fallback filters for non-power-of-2 textures + + function filterFallback ( f ) { + + if ( f === THREE.NearestFilter || f === THREE.NearestMipMapNearestFilter || f === THREE.NearestMipMapLinearFilter ) { + + return _gl.NEAREST; + + } + + return _gl.LINEAR; + + }; + + // Map three.js constants to WebGL constants + + function paramThreeToGL ( p ) { + + if ( p === THREE.RepeatWrapping ) return _gl.REPEAT; + if ( p === THREE.ClampToEdgeWrapping ) return _gl.CLAMP_TO_EDGE; + if ( p === THREE.MirroredRepeatWrapping ) return _gl.MIRRORED_REPEAT; + + if ( p === THREE.NearestFilter ) return _gl.NEAREST; + if ( p === THREE.NearestMipMapNearestFilter ) return _gl.NEAREST_MIPMAP_NEAREST; + if ( p === THREE.NearestMipMapLinearFilter ) return _gl.NEAREST_MIPMAP_LINEAR; + + if ( p === THREE.LinearFilter ) return _gl.LINEAR; + if ( p === THREE.LinearMipMapNearestFilter ) return _gl.LINEAR_MIPMAP_NEAREST; + if ( p === THREE.LinearMipMapLinearFilter ) return _gl.LINEAR_MIPMAP_LINEAR; + + if ( p === THREE.UnsignedByteType ) return _gl.UNSIGNED_BYTE; + if ( p === THREE.UnsignedShort4444Type ) return _gl.UNSIGNED_SHORT_4_4_4_4; + if ( p === THREE.UnsignedShort5551Type ) return _gl.UNSIGNED_SHORT_5_5_5_1; + if ( p === THREE.UnsignedShort565Type ) return _gl.UNSIGNED_SHORT_5_6_5; + + if ( p === THREE.ByteType ) return _gl.BYTE; + if ( p === THREE.ShortType ) return _gl.SHORT; + if ( p === THREE.UnsignedShortType ) return _gl.UNSIGNED_SHORT; + if ( p === THREE.IntType ) return _gl.INT; + if ( p === THREE.UnsignedIntType ) return _gl.UNSIGNED_INT; + if ( p === THREE.FloatType ) return _gl.FLOAT; + + if ( p === THREE.AlphaFormat ) return _gl.ALPHA; + if ( p === THREE.RGBFormat ) return _gl.RGB; + if ( p === THREE.RGBAFormat ) return _gl.RGBA; + if ( p === THREE.LuminanceFormat ) return _gl.LUMINANCE; + if ( p === THREE.LuminanceAlphaFormat ) return _gl.LUMINANCE_ALPHA; + + if ( p === THREE.AddEquation ) return _gl.FUNC_ADD; + if ( p === THREE.SubtractEquation ) return _gl.FUNC_SUBTRACT; + if ( p === THREE.ReverseSubtractEquation ) return _gl.FUNC_REVERSE_SUBTRACT; + + if ( p === THREE.ZeroFactor ) return _gl.ZERO; + if ( p === THREE.OneFactor ) return _gl.ONE; + if ( p === THREE.SrcColorFactor ) return _gl.SRC_COLOR; + if ( p === THREE.OneMinusSrcColorFactor ) return _gl.ONE_MINUS_SRC_COLOR; + if ( p === THREE.SrcAlphaFactor ) return _gl.SRC_ALPHA; + if ( p === THREE.OneMinusSrcAlphaFactor ) return _gl.ONE_MINUS_SRC_ALPHA; + if ( p === THREE.DstAlphaFactor ) return _gl.DST_ALPHA; + if ( p === THREE.OneMinusDstAlphaFactor ) return _gl.ONE_MINUS_DST_ALPHA; + + if ( p === THREE.DstColorFactor ) return _gl.DST_COLOR; + if ( p === THREE.OneMinusDstColorFactor ) return _gl.ONE_MINUS_DST_COLOR; + if ( p === THREE.SrcAlphaSaturateFactor ) return _gl.SRC_ALPHA_SATURATE; + + if ( _glExtensionCompressedTextureS3TC !== undefined ) { + + if ( p === THREE.RGB_S3TC_DXT1_Format ) return _glExtensionCompressedTextureS3TC.COMPRESSED_RGB_S3TC_DXT1_EXT; + if ( p === THREE.RGBA_S3TC_DXT1_Format ) return _glExtensionCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT1_EXT; + if ( p === THREE.RGBA_S3TC_DXT3_Format ) return _glExtensionCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT3_EXT; + if ( p === THREE.RGBA_S3TC_DXT5_Format ) return _glExtensionCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT5_EXT; + + } + + return 0; + + }; + + // Allocations + + function allocateBones ( object ) { + + if ( _supportsBoneTextures && object && object.useVertexTexture ) { + + return 1024; + + } else { + + // default for when object is not specified + // ( for example when prebuilding shader + // to be used with multiple objects ) + // + // - leave some extra space for other uniforms + // - limit here is ANGLE's 254 max uniform vectors + // (up to 54 should be safe) + + var nVertexUniforms = _gl.getParameter( _gl.MAX_VERTEX_UNIFORM_VECTORS ); + var nVertexMatrices = Math.floor( ( nVertexUniforms - 20 ) / 4 ); + + var maxBones = nVertexMatrices; + + if ( object !== undefined && object instanceof THREE.SkinnedMesh ) { + + maxBones = Math.min( object.bones.length, maxBones ); + + if ( maxBones < object.bones.length ) { + + console.warn( "WebGLRenderer: too many bones - " + object.bones.length + ", this GPU supports just " + maxBones + " (try OpenGL instead of ANGLE)" ); + + } + + } + + return maxBones; + + } + + }; + + function allocateLights( lights ) { + + var dirLights = 0; + var pointLights = 0; + var spotLights = 0; + var hemiLights = 0; + + for ( var l = 0, ll = lights.length; l < ll; l ++ ) { + + var light = lights[ l ]; + + if ( light.onlyShadow || light.visible === false ) continue; + + if ( light instanceof THREE.DirectionalLight ) dirLights ++; + if ( light instanceof THREE.PointLight ) pointLights ++; + if ( light instanceof THREE.SpotLight ) spotLights ++; + if ( light instanceof THREE.HemisphereLight ) hemiLights ++; + + } + + return { 'directional' : dirLights, 'point' : pointLights, 'spot': spotLights, 'hemi': hemiLights }; + + }; + + function allocateShadows( lights ) { + + var maxShadows = 0; + + for ( var l = 0, ll = lights.length; l < ll; l++ ) { + + var light = lights[ l ]; + + if ( ! light.castShadow ) continue; + + if ( light instanceof THREE.SpotLight ) maxShadows ++; + if ( light instanceof THREE.DirectionalLight && ! light.shadowCascade ) maxShadows ++; + + } + + return maxShadows; + + }; + + // Initialization + + function initGL() { + + try { + + var attributes = { + alpha: _alpha, + premultipliedAlpha: _premultipliedAlpha, + antialias: _antialias, + stencil: _stencil, + preserveDrawingBuffer: _preserveDrawingBuffer + }; + + _gl = _context || _canvas.getContext( 'webgl', attributes ) || _canvas.getContext( 'experimental-webgl', attributes ); + + if ( _gl === null ) { + + throw 'Error creating WebGL context.'; + + } + + } catch ( error ) { + + console.error( error ); + + } + + _glExtensionTextureFloat = _gl.getExtension( 'OES_texture_float' ); + _glExtensionTextureFloatLinear = _gl.getExtension( 'OES_texture_float_linear' ); + _glExtensionStandardDerivatives = _gl.getExtension( 'OES_standard_derivatives' ); + + _glExtensionTextureFilterAnisotropic = _gl.getExtension( 'EXT_texture_filter_anisotropic' ) || _gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) || _gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' ); + + _glExtensionCompressedTextureS3TC = _gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) || _gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) || _gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' ); + + if ( ! _glExtensionTextureFloat ) { + + console.log( 'THREE.WebGLRenderer: Float textures not supported.' ); + + } + + if ( ! _glExtensionStandardDerivatives ) { + + console.log( 'THREE.WebGLRenderer: Standard derivatives not supported.' ); + + } + + if ( ! _glExtensionTextureFilterAnisotropic ) { + + console.log( 'THREE.WebGLRenderer: Anisotropic texture filtering not supported.' ); + + } + + if ( ! _glExtensionCompressedTextureS3TC ) { + + console.log( 'THREE.WebGLRenderer: S3TC compressed textures not supported.' ); + + } + + if ( _gl.getShaderPrecisionFormat === undefined ) { + + _gl.getShaderPrecisionFormat = function() { + + return { + "rangeMin" : 1, + "rangeMax" : 1, + "precision" : 1 + }; + + } + } + + }; + + function setDefaultGLState () { + + _gl.clearColor( 0, 0, 0, 1 ); + _gl.clearDepth( 1 ); + _gl.clearStencil( 0 ); + + _gl.enable( _gl.DEPTH_TEST ); + _gl.depthFunc( _gl.LEQUAL ); + + _gl.frontFace( _gl.CCW ); + _gl.cullFace( _gl.BACK ); + _gl.enable( _gl.CULL_FACE ); + + _gl.enable( _gl.BLEND ); + _gl.blendEquation( _gl.FUNC_ADD ); + _gl.blendFunc( _gl.SRC_ALPHA, _gl.ONE_MINUS_SRC_ALPHA ); + + _gl.viewport( _viewportX, _viewportY, _viewportWidth, _viewportHeight ); + + _gl.clearColor( _clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha ); + + }; + + // default plugins (order is important) + + this.shadowMapPlugin = new THREE.ShadowMapPlugin(); + this.addPrePlugin( this.shadowMapPlugin ); + + this.addPostPlugin( new THREE.SpritePlugin() ); + this.addPostPlugin( new THREE.LensFlarePlugin() ); + +}; + +/** + * @author szimek / https://github.com/szimek/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.WebGLRenderTarget = function ( width, height, options ) { + + this.width = width; + this.height = height; + + options = options || {}; + + this.wrapS = options.wrapS !== undefined ? options.wrapS : THREE.ClampToEdgeWrapping; + this.wrapT = options.wrapT !== undefined ? options.wrapT : THREE.ClampToEdgeWrapping; + + this.magFilter = options.magFilter !== undefined ? options.magFilter : THREE.LinearFilter; + this.minFilter = options.minFilter !== undefined ? options.minFilter : THREE.LinearMipMapLinearFilter; + + this.anisotropy = options.anisotropy !== undefined ? options.anisotropy : 1; + + this.offset = new THREE.Vector2( 0, 0 ); + this.repeat = new THREE.Vector2( 1, 1 ); + + this.format = options.format !== undefined ? options.format : THREE.RGBAFormat; + this.type = options.type !== undefined ? options.type : THREE.UnsignedByteType; + + this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true; + this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : true; + + this.generateMipmaps = true; + + this.shareDepthFrom = null; + +}; + +THREE.WebGLRenderTarget.prototype = { + + constructor: THREE.WebGLRenderTarget, + + clone: function () { + + var tmp = new THREE.WebGLRenderTarget( this.width, this.height ); + + tmp.wrapS = this.wrapS; + tmp.wrapT = this.wrapT; + + tmp.magFilter = this.magFilter; + tmp.minFilter = this.minFilter; + + tmp.anisotropy = this.anisotropy; + + tmp.offset.copy( this.offset ); + tmp.repeat.copy( this.repeat ); + + tmp.format = this.format; + tmp.type = this.type; + + tmp.depthBuffer = this.depthBuffer; + tmp.stencilBuffer = this.stencilBuffer; + + tmp.generateMipmaps = this.generateMipmaps; + + tmp.shareDepthFrom = this.shareDepthFrom; + + return tmp; + + }, + + dispose: function () { + + this.dispatchEvent( { type: 'dispose' } ); + + } + +}; + +THREE.EventDispatcher.prototype.apply( THREE.WebGLRenderTarget.prototype ); + +/** + * @author alteredq / http://alteredqualia.com + */ + +THREE.WebGLRenderTargetCube = function ( width, height, options ) { + + THREE.WebGLRenderTarget.call( this, width, height, options ); + + this.activeCubeFace = 0; // PX 0, NX 1, PY 2, NY 3, PZ 4, NZ 5 + +}; + +THREE.WebGLRenderTargetCube.prototype = Object.create( THREE.WebGLRenderTarget.prototype ); + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.RenderableVertex = function () { + + this.position = new THREE.Vector3(); + this.positionWorld = new THREE.Vector3(); + this.positionScreen = new THREE.Vector4(); + + this.visible = true; + +}; + +THREE.RenderableVertex.prototype.copy = function ( vertex ) { + + this.positionWorld.copy( vertex.positionWorld ); + this.positionScreen.copy( vertex.positionScreen ); + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.RenderableFace = function () { + + this.id = 0; + + this.v1 = new THREE.RenderableVertex(); + this.v2 = new THREE.RenderableVertex(); + this.v3 = new THREE.RenderableVertex(); + + this.centroidModel = new THREE.Vector3(); + + this.normalModel = new THREE.Vector3(); + + this.vertexNormalsModel = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ]; + this.vertexNormalsLength = 0; + + this.color = null; + this.material = null; + this.uvs = [[]]; + + this.z = 0; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.RenderableObject = function () { + + this.id = 0; + + this.object = null; + this.z = 0; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.RenderableSprite = function () { + + this.id = 0; + + this.object = null; + + this.x = 0; + this.y = 0; + this.z = 0; + + this.rotation = 0; + this.scale = new THREE.Vector2(); + + this.material = null; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.RenderableLine = function () { + + this.id = 0; + + this.v1 = new THREE.RenderableVertex(); + this.v2 = new THREE.RenderableVertex(); + + this.vertexColors = [ new THREE.Color(), new THREE.Color() ]; + this.material = null; + + this.z = 0; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.GeometryUtils = { + + // Merge two geometries or geometry and geometry from object (using object's transform) + + merge: function ( geometry1, object2 /* mesh | geometry */, materialIndexOffset ) { + + var matrix, normalMatrix, + vertexOffset = geometry1.vertices.length, + uvPosition = geometry1.faceVertexUvs[ 0 ].length, + geometry2 = object2 instanceof THREE.Mesh ? object2.geometry : object2, + vertices1 = geometry1.vertices, + vertices2 = geometry2.vertices, + faces1 = geometry1.faces, + faces2 = geometry2.faces, + uvs1 = geometry1.faceVertexUvs[ 0 ], + uvs2 = geometry2.faceVertexUvs[ 0 ]; + + if ( materialIndexOffset === undefined ) materialIndexOffset = 0; + + if ( object2 instanceof THREE.Mesh ) { + + object2.matrixAutoUpdate && object2.updateMatrix(); + + matrix = object2.matrix; + + normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix ); + + } + + // vertices + + for ( var i = 0, il = vertices2.length; i < il; i ++ ) { + + var vertex = vertices2[ i ]; + + var vertexCopy = vertex.clone(); + + if ( matrix ) vertexCopy.applyMatrix4( matrix ); + + vertices1.push( vertexCopy ); + + } + + // faces + + for ( i = 0, il = faces2.length; i < il; i ++ ) { + + var face = faces2[ i ], faceCopy, normal, color, + faceVertexNormals = face.vertexNormals, + faceVertexColors = face.vertexColors; + + faceCopy = new THREE.Face3( face.a + vertexOffset, face.b + vertexOffset, face.c + vertexOffset ); + faceCopy.normal.copy( face.normal ); + + if ( normalMatrix ) { + + faceCopy.normal.applyMatrix3( normalMatrix ).normalize(); + + } + + for ( var j = 0, jl = faceVertexNormals.length; j < jl; j ++ ) { + + normal = faceVertexNormals[ j ].clone(); + + if ( normalMatrix ) { + + normal.applyMatrix3( normalMatrix ).normalize(); + + } + + faceCopy.vertexNormals.push( normal ); + + } + + faceCopy.color.copy( face.color ); + + for ( var j = 0, jl = faceVertexColors.length; j < jl; j ++ ) { + + color = faceVertexColors[ j ]; + faceCopy.vertexColors.push( color.clone() ); + + } + + faceCopy.materialIndex = face.materialIndex + materialIndexOffset; + + faceCopy.centroid.copy( face.centroid ); + + if ( matrix ) { + + faceCopy.centroid.applyMatrix4( matrix ); + + } + + faces1.push( faceCopy ); + + } + + // uvs + + for ( i = 0, il = uvs2.length; i < il; i ++ ) { + + var uv = uvs2[ i ], uvCopy = []; + + for ( var j = 0, jl = uv.length; j < jl; j ++ ) { + + uvCopy.push( new THREE.Vector2( uv[ j ].x, uv[ j ].y ) ); + + } + + uvs1.push( uvCopy ); + + } + + }, + + // Get random point in triangle (via barycentric coordinates) + // (uniform distribution) + // http://www.cgafaq.info/wiki/Random_Point_In_Triangle + + randomPointInTriangle: function () { + + var vector = new THREE.Vector3(); + + return function ( vectorA, vectorB, vectorC ) { + + var point = new THREE.Vector3(); + + var a = THREE.Math.random16(); + var b = THREE.Math.random16(); + + if ( ( a + b ) > 1 ) { + + a = 1 - a; + b = 1 - b; + + } + + var c = 1 - a - b; + + point.copy( vectorA ); + point.multiplyScalar( a ); + + vector.copy( vectorB ); + vector.multiplyScalar( b ); + + point.add( vector ); + + vector.copy( vectorC ); + vector.multiplyScalar( c ); + + point.add( vector ); + + return point; + + }; + + }(), + + // Get random point in face (triangle / quad) + // (uniform distribution) + + randomPointInFace: function ( face, geometry, useCachedAreas ) { + + var vA, vB, vC, vD; + + vA = geometry.vertices[ face.a ]; + vB = geometry.vertices[ face.b ]; + vC = geometry.vertices[ face.c ]; + + return THREE.GeometryUtils.randomPointInTriangle( vA, vB, vC ); + + }, + + // Get uniformly distributed random points in mesh + // - create array with cumulative sums of face areas + // - pick random number from 0 to total area + // - find corresponding place in area array by binary search + // - get random point in face + + randomPointsInGeometry: function ( geometry, n ) { + + var face, i, + faces = geometry.faces, + vertices = geometry.vertices, + il = faces.length, + totalArea = 0, + cumulativeAreas = [], + vA, vB, vC, vD; + + // precompute face areas + + for ( i = 0; i < il; i ++ ) { + + face = faces[ i ]; + + vA = vertices[ face.a ]; + vB = vertices[ face.b ]; + vC = vertices[ face.c ]; + + face._area = THREE.GeometryUtils.triangleArea( vA, vB, vC ); + + totalArea += face._area; + + cumulativeAreas[ i ] = totalArea; + + } + + // binary search cumulative areas array + + function binarySearchIndices( value ) { + + function binarySearch( start, end ) { + + // return closest larger index + // if exact number is not found + + if ( end < start ) + return start; + + var mid = start + Math.floor( ( end - start ) / 2 ); + + if ( cumulativeAreas[ mid ] > value ) { + + return binarySearch( start, mid - 1 ); + + } else if ( cumulativeAreas[ mid ] < value ) { + + return binarySearch( mid + 1, end ); + + } else { + + return mid; + + } + + } + + var result = binarySearch( 0, cumulativeAreas.length - 1 ) + return result; + + } + + // pick random face weighted by face area + + var r, index, + result = []; + + var stats = {}; + + for ( i = 0; i < n; i ++ ) { + + r = THREE.Math.random16() * totalArea; + + index = binarySearchIndices( r ); + + result[ i ] = THREE.GeometryUtils.randomPointInFace( faces[ index ], geometry, true ); + + if ( ! stats[ index ] ) { + + stats[ index ] = 1; + + } else { + + stats[ index ] += 1; + + } + + } + + return result; + + }, + + // Get triangle area (half of parallelogram) + // http://mathworld.wolfram.com/TriangleArea.html + + triangleArea: function () { + + var vector1 = new THREE.Vector3(); + var vector2 = new THREE.Vector3(); + + return function ( vectorA, vectorB, vectorC ) { + + vector1.subVectors( vectorB, vectorA ); + vector2.subVectors( vectorC, vectorA ); + vector1.cross( vector2 ); + + return 0.5 * vector1.length(); + + }; + + }(), + + // Center geometry so that 0,0,0 is in center of bounding box + + center: function ( geometry ) { + + geometry.computeBoundingBox(); + + var bb = geometry.boundingBox; + + var offset = new THREE.Vector3(); + + offset.addVectors( bb.min, bb.max ); + offset.multiplyScalar( -0.5 ); + + geometry.applyMatrix( new THREE.Matrix4().makeTranslation( offset.x, offset.y, offset.z ) ); + geometry.computeBoundingBox(); + + return offset; + + }, + + triangulateQuads: function ( geometry ) { + + var i, il, j, jl; + + var faces = []; + var faceVertexUvs = []; + + for ( i = 0, il = geometry.faceVertexUvs.length; i < il; i ++ ) { + + faceVertexUvs[ i ] = []; + + } + + for ( i = 0, il = geometry.faces.length; i < il; i ++ ) { + + var face = geometry.faces[ i ]; + + faces.push( face ); + + for ( j = 0, jl = geometry.faceVertexUvs.length; j < jl; j ++ ) { + + faceVertexUvs[ j ].push( geometry.faceVertexUvs[ j ][ i ] ); + + } + + } + + geometry.faces = faces; + geometry.faceVertexUvs = faceVertexUvs; + + geometry.computeCentroids(); + geometry.computeFaceNormals(); + geometry.computeVertexNormals(); + + if ( geometry.hasTangents ) geometry.computeTangents(); + + } + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.ImageUtils = { + + crossOrigin: undefined, + + loadTexture: function ( url, mapping, onLoad, onError ) { + + var loader = new THREE.ImageLoader(); + loader.crossOrigin = this.crossOrigin; + + var texture = new THREE.Texture( undefined, mapping ); + + var image = loader.load( url, function () { + + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture ); + + } ); + + texture.image = image; + texture.sourceFile = url; + + return texture; + + }, + + loadCompressedTexture: function ( url, mapping, onLoad, onError ) { + + var texture = new THREE.CompressedTexture(); + texture.mapping = mapping; + + var request = new XMLHttpRequest(); + + request.onload = function () { + + var buffer = request.response; + var dds = THREE.ImageUtils.parseDDS( buffer, true ); + + texture.format = dds.format; + + texture.mipmaps = dds.mipmaps; + texture.image.width = dds.width; + texture.image.height = dds.height; + + // gl.generateMipmap fails for compressed textures + // mipmaps must be embedded in the DDS file + // or texture filters must not use mipmapping + + texture.generateMipmaps = false; + + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture ); + + } + + request.onerror = onError; + + request.open( 'GET', url, true ); + request.responseType = "arraybuffer"; + request.send( null ); + + return texture; + + }, + + loadTextureCube: function ( array, mapping, onLoad, onError ) { + + var images = []; + images.loadCount = 0; + + var texture = new THREE.Texture(); + texture.image = images; + if ( mapping !== undefined ) texture.mapping = mapping; + + // no flipping needed for cube textures + + texture.flipY = false; + + for ( var i = 0, il = array.length; i < il; ++ i ) { + + var cubeImage = new Image(); + images[ i ] = cubeImage; + + cubeImage.onload = function () { + + images.loadCount += 1; + + if ( images.loadCount === 6 ) { + + texture.needsUpdate = true; + if ( onLoad ) onLoad( texture ); + + } + + }; + + cubeImage.onerror = onError; + + cubeImage.crossOrigin = this.crossOrigin; + cubeImage.src = array[ i ]; + + } + + return texture; + + }, + + loadCompressedTextureCube: function ( array, mapping, onLoad, onError ) { + + var images = []; + images.loadCount = 0; + + var texture = new THREE.CompressedTexture(); + texture.image = images; + if ( mapping !== undefined ) texture.mapping = mapping; + + // no flipping for cube textures + // (also flipping doesn't work for compressed textures ) + + texture.flipY = false; + + // can't generate mipmaps for compressed textures + // mips must be embedded in DDS files + + texture.generateMipmaps = false; + + var generateCubeFaceCallback = function ( rq, img ) { + + return function () { + + var buffer = rq.response; + var dds = THREE.ImageUtils.parseDDS( buffer, true ); + + img.format = dds.format; + + img.mipmaps = dds.mipmaps; + img.width = dds.width; + img.height = dds.height; + + images.loadCount += 1; + + if ( images.loadCount === 6 ) { + + texture.format = dds.format; + texture.needsUpdate = true; + if ( onLoad ) onLoad( texture ); + + } + + } + + } + + // compressed cubemap textures as 6 separate DDS files + + if ( array instanceof Array ) { + + for ( var i = 0, il = array.length; i < il; ++ i ) { + + var cubeImage = {}; + images[ i ] = cubeImage; + + var request = new XMLHttpRequest(); + + request.onload = generateCubeFaceCallback( request, cubeImage ); + request.onerror = onError; + + var url = array[ i ]; + + request.open( 'GET', url, true ); + request.responseType = "arraybuffer"; + request.send( null ); + + } + + // compressed cubemap texture stored in a single DDS file + + } else { + + var url = array; + var request = new XMLHttpRequest(); + + request.onload = function( ) { + + var buffer = request.response; + var dds = THREE.ImageUtils.parseDDS( buffer, true ); + + if ( dds.isCubemap ) { + + var faces = dds.mipmaps.length / dds.mipmapCount; + + for ( var f = 0; f < faces; f ++ ) { + + images[ f ] = { mipmaps : [] }; + + for ( var i = 0; i < dds.mipmapCount; i ++ ) { + + images[ f ].mipmaps.push( dds.mipmaps[ f * dds.mipmapCount + i ] ); + images[ f ].format = dds.format; + images[ f ].width = dds.width; + images[ f ].height = dds.height; + + } + + } + + texture.format = dds.format; + texture.needsUpdate = true; + if ( onLoad ) onLoad( texture ); + + } + + } + + request.onerror = onError; + + request.open( 'GET', url, true ); + request.responseType = "arraybuffer"; + request.send( null ); + + } + + return texture; + + }, + + loadDDSTexture: function ( url, mapping, onLoad, onError ) { + + var images = []; + images.loadCount = 0; + + var texture = new THREE.CompressedTexture(); + texture.image = images; + if ( mapping !== undefined ) texture.mapping = mapping; + + // no flipping for cube textures + // (also flipping doesn't work for compressed textures ) + + texture.flipY = false; + + // can't generate mipmaps for compressed textures + // mips must be embedded in DDS files + + texture.generateMipmaps = false; + + { + var request = new XMLHttpRequest(); + + request.onload = function( ) { + + var buffer = request.response; + var dds = THREE.ImageUtils.parseDDS( buffer, true ); + + if ( dds.isCubemap ) { + + var faces = dds.mipmaps.length / dds.mipmapCount; + + for ( var f = 0; f < faces; f ++ ) { + + images[ f ] = { mipmaps : [] }; + + for ( var i = 0; i < dds.mipmapCount; i ++ ) { + + images[ f ].mipmaps.push( dds.mipmaps[ f * dds.mipmapCount + i ] ); + images[ f ].format = dds.format; + images[ f ].width = dds.width; + images[ f ].height = dds.height; + + } + + } + + + } else { + texture.image.width = dds.width; + texture.image.height = dds.height; + texture.mipmaps = dds.mipmaps; + } + + texture.format = dds.format; + texture.needsUpdate = true; + if ( onLoad ) onLoad( texture ); + + } + + request.onerror = onError; + + request.open( 'GET', url, true ); + request.responseType = "arraybuffer"; + request.send( null ); + + } + + return texture; + + }, + + parseDDS: function ( buffer, loadMipmaps ) { + + var dds = { mipmaps: [], width: 0, height: 0, format: null, mipmapCount: 1 }; + + // Adapted from @toji's DDS utils + // https://github.com/toji/webgl-texture-utils/blob/master/texture-util/dds.js + + // All values and structures referenced from: + // http://msdn.microsoft.com/en-us/library/bb943991.aspx/ + + var DDS_MAGIC = 0x20534444; + + var DDSD_CAPS = 0x1, + DDSD_HEIGHT = 0x2, + DDSD_WIDTH = 0x4, + DDSD_PITCH = 0x8, + DDSD_PIXELFORMAT = 0x1000, + DDSD_MIPMAPCOUNT = 0x20000, + DDSD_LINEARSIZE = 0x80000, + DDSD_DEPTH = 0x800000; + + var DDSCAPS_COMPLEX = 0x8, + DDSCAPS_MIPMAP = 0x400000, + DDSCAPS_TEXTURE = 0x1000; + + var DDSCAPS2_CUBEMAP = 0x200, + DDSCAPS2_CUBEMAP_POSITIVEX = 0x400, + DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800, + DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000, + DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000, + DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000, + DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000, + DDSCAPS2_VOLUME = 0x200000; + + var DDPF_ALPHAPIXELS = 0x1, + DDPF_ALPHA = 0x2, + DDPF_FOURCC = 0x4, + DDPF_RGB = 0x40, + DDPF_YUV = 0x200, + DDPF_LUMINANCE = 0x20000; + + function fourCCToInt32( value ) { + + return value.charCodeAt(0) + + (value.charCodeAt(1) << 8) + + (value.charCodeAt(2) << 16) + + (value.charCodeAt(3) << 24); + + } + + function int32ToFourCC( value ) { + + return String.fromCharCode( + value & 0xff, + (value >> 8) & 0xff, + (value >> 16) & 0xff, + (value >> 24) & 0xff + ); + } + + function loadARGBMip( buffer, dataOffset, width, height ) { + var dataLength = width*height*4; + var srcBuffer = new Uint8Array( buffer, dataOffset, dataLength ); + var byteArray = new Uint8Array( dataLength ); + var dst = 0; + var src = 0; + for ( var y = 0; y < height; y++ ) { + for ( var x = 0; x < width; x++ ) { + var b = srcBuffer[src]; src++; + var g = srcBuffer[src]; src++; + var r = srcBuffer[src]; src++; + var a = srcBuffer[src]; src++; + byteArray[dst] = r; dst++; //r + byteArray[dst] = g; dst++; //g + byteArray[dst] = b; dst++; //b + byteArray[dst] = a; dst++; //a + } + } + return byteArray; + } + + var FOURCC_DXT1 = fourCCToInt32("DXT1"); + var FOURCC_DXT3 = fourCCToInt32("DXT3"); + var FOURCC_DXT5 = fourCCToInt32("DXT5"); + + var headerLengthInt = 31; // The header length in 32 bit ints + + // Offsets into the header array + + var off_magic = 0; + + var off_size = 1; + var off_flags = 2; + var off_height = 3; + var off_width = 4; + + var off_mipmapCount = 7; + + var off_pfFlags = 20; + var off_pfFourCC = 21; + var off_RGBBitCount = 22; + var off_RBitMask = 23; + var off_GBitMask = 24; + var off_BBitMask = 25; + var off_ABitMask = 26; + + var off_caps = 27; + var off_caps2 = 28; + var off_caps3 = 29; + var off_caps4 = 30; + + // Parse header + + var header = new Int32Array( buffer, 0, headerLengthInt ); + + if ( header[ off_magic ] !== DDS_MAGIC ) { + + console.error( "ImageUtils.parseDDS(): Invalid magic number in DDS header" ); + return dds; + + } + + if ( ! header[ off_pfFlags ] & DDPF_FOURCC ) { + + console.error( "ImageUtils.parseDDS(): Unsupported format, must contain a FourCC code" ); + return dds; + + } + + var blockBytes; + + var fourCC = header[ off_pfFourCC ]; + + var isRGBAUncompressed = false; + + switch ( fourCC ) { + + case FOURCC_DXT1: + + blockBytes = 8; + dds.format = THREE.RGB_S3TC_DXT1_Format; + break; + + case FOURCC_DXT3: + + blockBytes = 16; + dds.format = THREE.RGBA_S3TC_DXT3_Format; + break; + + case FOURCC_DXT5: + + blockBytes = 16; + dds.format = THREE.RGBA_S3TC_DXT5_Format; + break; + + default: + + if( header[off_RGBBitCount] ==32 + && header[off_RBitMask]&0xff0000 + && header[off_GBitMask]&0xff00 + && header[off_BBitMask]&0xff + && header[off_ABitMask]&0xff000000 ) { + isRGBAUncompressed = true; + blockBytes = 64; + dds.format = THREE.RGBAFormat; + } else { + console.error( "ImageUtils.parseDDS(): Unsupported FourCC code: ", int32ToFourCC( fourCC ) ); + return dds; + } + } + + dds.mipmapCount = 1; + + if ( header[ off_flags ] & DDSD_MIPMAPCOUNT && loadMipmaps !== false ) { + + dds.mipmapCount = Math.max( 1, header[ off_mipmapCount ] ); + + } + + //TODO: Verify that all faces of the cubemap are present with DDSCAPS2_CUBEMAP_POSITIVEX, etc. + + dds.isCubemap = header[ off_caps2 ] & DDSCAPS2_CUBEMAP ? true : false; + + dds.width = header[ off_width ]; + dds.height = header[ off_height ]; + + var dataOffset = header[ off_size ] + 4; + + // Extract mipmaps buffers + + var width = dds.width; + var height = dds.height; + + var faces = dds.isCubemap ? 6 : 1; + + for ( var face = 0; face < faces; face ++ ) { + + for ( var i = 0; i < dds.mipmapCount; i ++ ) { + + if( isRGBAUncompressed ) { + var byteArray = loadARGBMip( buffer, dataOffset, width, height ); + var dataLength = byteArray.length; + } else { + var dataLength = Math.max( 4, width ) / 4 * Math.max( 4, height ) / 4 * blockBytes; + var byteArray = new Uint8Array( buffer, dataOffset, dataLength ); + } + + var mipmap = { "data": byteArray, "width": width, "height": height }; + dds.mipmaps.push( mipmap ); + + dataOffset += dataLength; + + width = Math.max( width * 0.5, 1 ); + height = Math.max( height * 0.5, 1 ); + + } + + width = dds.width; + height = dds.height; + + } + + return dds; + + }, + + getNormalMap: function ( image, depth ) { + + // Adapted from http://www.paulbrunt.co.uk/lab/heightnormal/ + + var cross = function ( a, b ) { + + return [ a[ 1 ] * b[ 2 ] - a[ 2 ] * b[ 1 ], a[ 2 ] * b[ 0 ] - a[ 0 ] * b[ 2 ], a[ 0 ] * b[ 1 ] - a[ 1 ] * b[ 0 ] ]; + + } + + var subtract = function ( a, b ) { + + return [ a[ 0 ] - b[ 0 ], a[ 1 ] - b[ 1 ], a[ 2 ] - b[ 2 ] ]; + + } + + var normalize = function ( a ) { + + var l = Math.sqrt( a[ 0 ] * a[ 0 ] + a[ 1 ] * a[ 1 ] + a[ 2 ] * a[ 2 ] ); + return [ a[ 0 ] / l, a[ 1 ] / l, a[ 2 ] / l ]; + + } + + depth = depth | 1; + + var width = image.width; + var height = image.height; + + var canvas = document.createElement( 'canvas' ); + canvas.width = width; + canvas.height = height; + + var context = canvas.getContext( '2d' ); + context.drawImage( image, 0, 0 ); + + var data = context.getImageData( 0, 0, width, height ).data; + var imageData = context.createImageData( width, height ); + var output = imageData.data; + + for ( var x = 0; x < width; x ++ ) { + + for ( var y = 0; y < height; y ++ ) { + + var ly = y - 1 < 0 ? 0 : y - 1; + var uy = y + 1 > height - 1 ? height - 1 : y + 1; + var lx = x - 1 < 0 ? 0 : x - 1; + var ux = x + 1 > width - 1 ? width - 1 : x + 1; + + var points = []; + var origin = [ 0, 0, data[ ( y * width + x ) * 4 ] / 255 * depth ]; + points.push( [ - 1, 0, data[ ( y * width + lx ) * 4 ] / 255 * depth ] ); + points.push( [ - 1, - 1, data[ ( ly * width + lx ) * 4 ] / 255 * depth ] ); + points.push( [ 0, - 1, data[ ( ly * width + x ) * 4 ] / 255 * depth ] ); + points.push( [ 1, - 1, data[ ( ly * width + ux ) * 4 ] / 255 * depth ] ); + points.push( [ 1, 0, data[ ( y * width + ux ) * 4 ] / 255 * depth ] ); + points.push( [ 1, 1, data[ ( uy * width + ux ) * 4 ] / 255 * depth ] ); + points.push( [ 0, 1, data[ ( uy * width + x ) * 4 ] / 255 * depth ] ); + points.push( [ - 1, 1, data[ ( uy * width + lx ) * 4 ] / 255 * depth ] ); + + var normals = []; + var num_points = points.length; + + for ( var i = 0; i < num_points; i ++ ) { + + var v1 = points[ i ]; + var v2 = points[ ( i + 1 ) % num_points ]; + v1 = subtract( v1, origin ); + v2 = subtract( v2, origin ); + normals.push( normalize( cross( v1, v2 ) ) ); + + } + + var normal = [ 0, 0, 0 ]; + + for ( var i = 0; i < normals.length; i ++ ) { + + normal[ 0 ] += normals[ i ][ 0 ]; + normal[ 1 ] += normals[ i ][ 1 ]; + normal[ 2 ] += normals[ i ][ 2 ]; + + } + + normal[ 0 ] /= normals.length; + normal[ 1 ] /= normals.length; + normal[ 2 ] /= normals.length; + + var idx = ( y * width + x ) * 4; + + output[ idx ] = ( ( normal[ 0 ] + 1.0 ) / 2.0 * 255 ) | 0; + output[ idx + 1 ] = ( ( normal[ 1 ] + 1.0 ) / 2.0 * 255 ) | 0; + output[ idx + 2 ] = ( normal[ 2 ] * 255 ) | 0; + output[ idx + 3 ] = 255; + + } + + } + + context.putImageData( imageData, 0, 0 ); + + return canvas; + + }, + + generateDataTexture: function ( width, height, color ) { + + var size = width * height; + var data = new Uint8Array( 3 * size ); + + var r = Math.floor( color.r * 255 ); + var g = Math.floor( color.g * 255 ); + var b = Math.floor( color.b * 255 ); + + for ( var i = 0; i < size; i ++ ) { + + data[ i * 3 ] = r; + data[ i * 3 + 1 ] = g; + data[ i * 3 + 2 ] = b; + + } + + var texture = new THREE.DataTexture( data, width, height, THREE.RGBFormat ); + texture.needsUpdate = true; + + return texture; + + } + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.SceneUtils = { + + createMultiMaterialObject: function ( geometry, materials ) { + + var group = new THREE.Object3D(); + + for ( var i = 0, l = materials.length; i < l; i ++ ) { + + group.add( new THREE.Mesh( geometry, materials[ i ] ) ); + + } + + return group; + + }, + + detach : function ( child, parent, scene ) { + + child.applyMatrix( parent.matrixWorld ); + parent.remove( child ); + scene.add( child ); + + }, + + attach: function ( child, scene, parent ) { + + var matrixWorldInverse = new THREE.Matrix4(); + matrixWorldInverse.getInverse( parent.matrixWorld ); + child.applyMatrix( matrixWorldInverse ); + + scene.remove( child ); + parent.add( child ); + + } + +}; + +/** + * @author zz85 / http://www.lab4games.net/zz85/blog + * @author alteredq / http://alteredqualia.com/ + * + * For Text operations in three.js (See TextGeometry) + * + * It uses techniques used in: + * + * typeface.js and canvastext + * For converting fonts and rendering with javascript + * http://typeface.neocracy.org + * + * Triangulation ported from AS3 + * Simple Polygon Triangulation + * http://actionsnippet.com/?p=1462 + * + * A Method to triangulate shapes with holes + * http://www.sakri.net/blog/2009/06/12/an-approach-to-triangulating-polygons-with-holes/ + * + */ + +THREE.FontUtils = { + + faces : {}, + + // Just for now. face[weight][style] + + face : "helvetiker", + weight: "normal", + style : "normal", + size : 150, + divisions : 10, + + getFace : function() { + + return this.faces[ this.face ][ this.weight ][ this.style ]; + + }, + + loadFace : function( data ) { + + var family = data.familyName.toLowerCase(); + + var ThreeFont = this; + + ThreeFont.faces[ family ] = ThreeFont.faces[ family ] || {}; + + ThreeFont.faces[ family ][ data.cssFontWeight ] = ThreeFont.faces[ family ][ data.cssFontWeight ] || {}; + ThreeFont.faces[ family ][ data.cssFontWeight ][ data.cssFontStyle ] = data; + + var face = ThreeFont.faces[ family ][ data.cssFontWeight ][ data.cssFontStyle ] = data; + + return data; + + }, + + drawText : function( text ) { + + var characterPts = [], allPts = []; + + // RenderText + + var i, p, + face = this.getFace(), + scale = this.size / face.resolution, + offset = 0, + chars = String( text ).split( '' ), + length = chars.length; + + var fontPaths = []; + + for ( i = 0; i < length; i ++ ) { + + var path = new THREE.Path(); + + var ret = this.extractGlyphPoints( chars[ i ], face, scale, offset, path ); + offset += ret.offset; + + fontPaths.push( ret.path ); + + } + + // get the width + + var width = offset / 2; + // + // for ( p = 0; p < allPts.length; p++ ) { + // + // allPts[ p ].x -= width; + // + // } + + //var extract = this.extractPoints( allPts, characterPts ); + //extract.contour = allPts; + + //extract.paths = fontPaths; + //extract.offset = width; + + return { paths : fontPaths, offset : width }; + + }, + + + + + extractGlyphPoints : function( c, face, scale, offset, path ) { + + var pts = []; + + var i, i2, divisions, + outline, action, length, + scaleX, scaleY, + x, y, cpx, cpy, cpx0, cpy0, cpx1, cpy1, cpx2, cpy2, + laste, + glyph = face.glyphs[ c ] || face.glyphs[ '?' ]; + + if ( !glyph ) return; + + if ( glyph.o ) { + + outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) ); + length = outline.length; + + scaleX = scale; + scaleY = scale; + + for ( i = 0; i < length; ) { + + action = outline[ i ++ ]; + + //console.log( action ); + + switch( action ) { + + case 'm': + + // Move To + + x = outline[ i++ ] * scaleX + offset; + y = outline[ i++ ] * scaleY; + + path.moveTo( x, y ); + break; + + case 'l': + + // Line To + + x = outline[ i++ ] * scaleX + offset; + y = outline[ i++ ] * scaleY; + path.lineTo(x,y); + break; + + case 'q': + + // QuadraticCurveTo + + cpx = outline[ i++ ] * scaleX + offset; + cpy = outline[ i++ ] * scaleY; + cpx1 = outline[ i++ ] * scaleX + offset; + cpy1 = outline[ i++ ] * scaleY; + + path.quadraticCurveTo(cpx1, cpy1, cpx, cpy); + + laste = pts[ pts.length - 1 ]; + + if ( laste ) { + + cpx0 = laste.x; + cpy0 = laste.y; + + for ( i2 = 1, divisions = this.divisions; i2 <= divisions; i2 ++ ) { + + var t = i2 / divisions; + var tx = THREE.Shape.Utils.b2( t, cpx0, cpx1, cpx ); + var ty = THREE.Shape.Utils.b2( t, cpy0, cpy1, cpy ); + } + + } + + break; + + case 'b': + + // Cubic Bezier Curve + + cpx = outline[ i++ ] * scaleX + offset; + cpy = outline[ i++ ] * scaleY; + cpx1 = outline[ i++ ] * scaleX + offset; + cpy1 = outline[ i++ ] * -scaleY; + cpx2 = outline[ i++ ] * scaleX + offset; + cpy2 = outline[ i++ ] * -scaleY; + + path.bezierCurveTo( cpx, cpy, cpx1, cpy1, cpx2, cpy2 ); + + laste = pts[ pts.length - 1 ]; + + if ( laste ) { + + cpx0 = laste.x; + cpy0 = laste.y; + + for ( i2 = 1, divisions = this.divisions; i2 <= divisions; i2 ++ ) { + + var t = i2 / divisions; + var tx = THREE.Shape.Utils.b3( t, cpx0, cpx1, cpx2, cpx ); + var ty = THREE.Shape.Utils.b3( t, cpy0, cpy1, cpy2, cpy ); + + } + + } + + break; + + } + + } + } + + + + return { offset: glyph.ha*scale, path:path}; + } + +}; + + +THREE.FontUtils.generateShapes = function( text, parameters ) { + + // Parameters + + parameters = parameters || {}; + + var size = parameters.size !== undefined ? parameters.size : 100; + var curveSegments = parameters.curveSegments !== undefined ? parameters.curveSegments: 4; + + var font = parameters.font !== undefined ? parameters.font : "helvetiker"; + var weight = parameters.weight !== undefined ? parameters.weight : "normal"; + var style = parameters.style !== undefined ? parameters.style : "normal"; + + THREE.FontUtils.size = size; + THREE.FontUtils.divisions = curveSegments; + + THREE.FontUtils.face = font; + THREE.FontUtils.weight = weight; + THREE.FontUtils.style = style; + + // Get a Font data json object + + var data = THREE.FontUtils.drawText( text ); + + var paths = data.paths; + var shapes = []; + + for ( var p = 0, pl = paths.length; p < pl; p ++ ) { + + Array.prototype.push.apply( shapes, paths[ p ].toShapes() ); + + } + + return shapes; + +}; + + +/** + * This code is a quick port of code written in C++ which was submitted to + * flipcode.com by John W. Ratcliff // July 22, 2000 + * See original code and more information here: + * http://www.flipcode.com/archives/Efficient_Polygon_Triangulation.shtml + * + * ported to actionscript by Zevan Rosser + * www.actionsnippet.com + * + * ported to javascript by Joshua Koo + * http://www.lab4games.net/zz85/blog + * + */ + + +( function( namespace ) { + + var EPSILON = 0.0000000001; + + // takes in an contour array and returns + + var process = function( contour, indices ) { + + var n = contour.length; + + if ( n < 3 ) return null; + + var result = [], + verts = [], + vertIndices = []; + + /* we want a counter-clockwise polygon in verts */ + + var u, v, w; + + if ( area( contour ) > 0.0 ) { + + for ( v = 0; v < n; v++ ) verts[ v ] = v; + + } else { + + for ( v = 0; v < n; v++ ) verts[ v ] = ( n - 1 ) - v; + + } + + var nv = n; + + /* remove nv - 2 vertices, creating 1 triangle every time */ + + var count = 2 * nv; /* error detection */ + + for( v = nv - 1; nv > 2; ) { + + /* if we loop, it is probably a non-simple polygon */ + + if ( ( count-- ) <= 0 ) { + + //** Triangulate: ERROR - probable bad polygon! + + //throw ( "Warning, unable to triangulate polygon!" ); + //return null; + // Sometimes warning is fine, especially polygons are triangulated in reverse. + console.log( "Warning, unable to triangulate polygon!" ); + + if ( indices ) return vertIndices; + return result; + + } + + /* three consecutive vertices in current polygon, */ + + u = v; if ( nv <= u ) u = 0; /* previous */ + v = u + 1; if ( nv <= v ) v = 0; /* new v */ + w = v + 1; if ( nv <= w ) w = 0; /* next */ + + if ( snip( contour, u, v, w, nv, verts ) ) { + + var a, b, c, s, t; + + /* true names of the vertices */ + + a = verts[ u ]; + b = verts[ v ]; + c = verts[ w ]; + + /* output Triangle */ + + result.push( [ contour[ a ], + contour[ b ], + contour[ c ] ] ); + + + vertIndices.push( [ verts[ u ], verts[ v ], verts[ w ] ] ); + + /* remove v from the remaining polygon */ + + for( s = v, t = v + 1; t < nv; s++, t++ ) { + + verts[ s ] = verts[ t ]; + + } + + nv--; + + /* reset error detection counter */ + + count = 2 * nv; + + } + + } + + if ( indices ) return vertIndices; + return result; + + }; + + // calculate area of the contour polygon + + var area = function ( contour ) { + + var n = contour.length; + var a = 0.0; + + for( var p = n - 1, q = 0; q < n; p = q++ ) { + + a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y; + + } + + return a * 0.5; + + }; + + var snip = function ( contour, u, v, w, n, verts ) { + + var p; + var ax, ay, bx, by; + var cx, cy, px, py; + + ax = contour[ verts[ u ] ].x; + ay = contour[ verts[ u ] ].y; + + bx = contour[ verts[ v ] ].x; + by = contour[ verts[ v ] ].y; + + cx = contour[ verts[ w ] ].x; + cy = contour[ verts[ w ] ].y; + + if ( EPSILON > (((bx-ax)*(cy-ay)) - ((by-ay)*(cx-ax))) ) return false; + + var aX, aY, bX, bY, cX, cY; + var apx, apy, bpx, bpy, cpx, cpy; + var cCROSSap, bCROSScp, aCROSSbp; + + aX = cx - bx; aY = cy - by; + bX = ax - cx; bY = ay - cy; + cX = bx - ax; cY = by - ay; + + for ( p = 0; p < n; p++ ) { + + px = contour[ verts[ p ] ].x + py = contour[ verts[ p ] ].y + + if ( ( (px === ax) && (py === ay) ) || + ( (px === bx) && (py === by) ) || + ( (px === cx) && (py === cy) ) ) continue; + + apx = px - ax; apy = py - ay; + bpx = px - bx; bpy = py - by; + cpx = px - cx; cpy = py - cy; + + // see if p is inside triangle abc + + aCROSSbp = aX*bpy - aY*bpx; + cCROSSap = cX*apy - cY*apx; + bCROSScp = bX*cpy - bY*cpx; + + if ( (aCROSSbp >= -EPSILON) && (bCROSScp >= -EPSILON) && (cCROSSap >= -EPSILON) ) return false; + + } + + return true; + + }; + + + namespace.Triangulate = process; + namespace.Triangulate.area = area; + + return namespace; + +})(THREE.FontUtils); + +// To use the typeface.js face files, hook up the API +self._typeface_js = { faces: THREE.FontUtils.faces, loadFace: THREE.FontUtils.loadFace }; +THREE.typeface_js = self._typeface_js; + +/** + * @author zz85 / http://www.lab4games.net/zz85/blog + * Extensible curve object + * + * Some common of Curve methods + * .getPoint(t), getTangent(t) + * .getPointAt(u), getTagentAt(u) + * .getPoints(), .getSpacedPoints() + * .getLength() + * .updateArcLengths() + * + * This following classes subclasses THREE.Curve: + * + * -- 2d classes -- + * THREE.LineCurve + * THREE.QuadraticBezierCurve + * THREE.CubicBezierCurve + * THREE.SplineCurve + * THREE.ArcCurve + * THREE.EllipseCurve + * + * -- 3d classes -- + * THREE.LineCurve3 + * THREE.QuadraticBezierCurve3 + * THREE.CubicBezierCurve3 + * THREE.SplineCurve3 + * THREE.ClosedSplineCurve3 + * + * A series of curves can be represented as a THREE.CurvePath + * + **/ + +/************************************************************** + * Abstract Curve base class + **************************************************************/ + +THREE.Curve = function () { + +}; + +// Virtual base class method to overwrite and implement in subclasses +// - t [0 .. 1] + +THREE.Curve.prototype.getPoint = function ( t ) { + + console.log( "Warning, getPoint() not implemented!" ); + return null; + +}; + +// Get point at relative position in curve according to arc length +// - u [0 .. 1] + +THREE.Curve.prototype.getPointAt = function ( u ) { + + var t = this.getUtoTmapping( u ); + return this.getPoint( t ); + +}; + +// Get sequence of points using getPoint( t ) + +THREE.Curve.prototype.getPoints = function ( divisions ) { + + if ( !divisions ) divisions = 5; + + var d, pts = []; + + for ( d = 0; d <= divisions; d ++ ) { + + pts.push( this.getPoint( d / divisions ) ); + + } + + return pts; + +}; + +// Get sequence of points using getPointAt( u ) + +THREE.Curve.prototype.getSpacedPoints = function ( divisions ) { + + if ( !divisions ) divisions = 5; + + var d, pts = []; + + for ( d = 0; d <= divisions; d ++ ) { + + pts.push( this.getPointAt( d / divisions ) ); + + } + + return pts; + +}; + +// Get total curve arc length + +THREE.Curve.prototype.getLength = function () { + + var lengths = this.getLengths(); + return lengths[ lengths.length - 1 ]; + +}; + +// Get list of cumulative segment lengths + +THREE.Curve.prototype.getLengths = function ( divisions ) { + + if ( !divisions ) divisions = (this.__arcLengthDivisions) ? (this.__arcLengthDivisions): 200; + + if ( this.cacheArcLengths + && ( this.cacheArcLengths.length == divisions + 1 ) + && !this.needsUpdate) { + + //console.log( "cached", this.cacheArcLengths ); + return this.cacheArcLengths; + + } + + this.needsUpdate = false; + + var cache = []; + var current, last = this.getPoint( 0 ); + var p, sum = 0; + + cache.push( 0 ); + + for ( p = 1; p <= divisions; p ++ ) { + + current = this.getPoint ( p / divisions ); + sum += current.distanceTo( last ); + cache.push( sum ); + last = current; + + } + + this.cacheArcLengths = cache; + + return cache; // { sums: cache, sum:sum }; Sum is in the last element. + +}; + + +THREE.Curve.prototype.updateArcLengths = function() { + this.needsUpdate = true; + this.getLengths(); +}; + +// Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equi distance + +THREE.Curve.prototype.getUtoTmapping = function ( u, distance ) { + + var arcLengths = this.getLengths(); + + var i = 0, il = arcLengths.length; + + var targetArcLength; // The targeted u distance value to get + + if ( distance ) { + + targetArcLength = distance; + + } else { + + targetArcLength = u * arcLengths[ il - 1 ]; + + } + + //var time = Date.now(); + + // binary search for the index with largest value smaller than target u distance + + var low = 0, high = il - 1, comparison; + + while ( low <= high ) { + + i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats + + comparison = arcLengths[ i ] - targetArcLength; + + if ( comparison < 0 ) { + + low = i + 1; + continue; + + } else if ( comparison > 0 ) { + + high = i - 1; + continue; + + } else { + + high = i; + break; + + // DONE + + } + + } + + i = high; + + //console.log('b' , i, low, high, Date.now()- time); + + if ( arcLengths[ i ] == targetArcLength ) { + + var t = i / ( il - 1 ); + return t; + + } + + // we could get finer grain at lengths, or use simple interpolatation between two points + + var lengthBefore = arcLengths[ i ]; + var lengthAfter = arcLengths[ i + 1 ]; + + var segmentLength = lengthAfter - lengthBefore; + + // determine where we are between the 'before' and 'after' points + + var segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength; + + // add that fractional amount to t + + var t = ( i + segmentFraction ) / ( il -1 ); + + return t; + +}; + +// Returns a unit vector tangent at t +// In case any sub curve does not implement its tangent derivation, +// 2 points a small delta apart will be used to find its gradient +// which seems to give a reasonable approximation + +THREE.Curve.prototype.getTangent = function( t ) { + + var delta = 0.0001; + var t1 = t - delta; + var t2 = t + delta; + + // Capping in case of danger + + if ( t1 < 0 ) t1 = 0; + if ( t2 > 1 ) t2 = 1; + + var pt1 = this.getPoint( t1 ); + var pt2 = this.getPoint( t2 ); + + var vec = pt2.clone().sub(pt1); + return vec.normalize(); + +}; + + +THREE.Curve.prototype.getTangentAt = function ( u ) { + + var t = this.getUtoTmapping( u ); + return this.getTangent( t ); + +}; + + + + + +/************************************************************** + * Utils + **************************************************************/ + +THREE.Curve.Utils = { + + tangentQuadraticBezier: function ( t, p0, p1, p2 ) { + + return 2 * ( 1 - t ) * ( p1 - p0 ) + 2 * t * ( p2 - p1 ); + + }, + + // Puay Bing, thanks for helping with this derivative! + + tangentCubicBezier: function (t, p0, p1, p2, p3 ) { + + return -3 * p0 * (1 - t) * (1 - t) + + 3 * p1 * (1 - t) * (1-t) - 6 *t *p1 * (1-t) + + 6 * t * p2 * (1-t) - 3 * t * t * p2 + + 3 * t * t * p3; + }, + + + tangentSpline: function ( t, p0, p1, p2, p3 ) { + + // To check if my formulas are correct + + var h00 = 6 * t * t - 6 * t; // derived from 2t^3 − 3t^2 + 1 + var h10 = 3 * t * t - 4 * t + 1; // t^3 − 2t^2 + t + var h01 = -6 * t * t + 6 * t; // − 2t3 + 3t2 + var h11 = 3 * t * t - 2 * t; // t3 − t2 + + return h00 + h10 + h01 + h11; + + }, + + // Catmull-Rom + + interpolate: function( p0, p1, p2, p3, t ) { + + var v0 = ( p2 - p0 ) * 0.5; + var v1 = ( p3 - p1 ) * 0.5; + var t2 = t * t; + var t3 = t * t2; + return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1; + + } + +}; + + +// TODO: Transformation for Curves? + +/************************************************************** + * 3D Curves + **************************************************************/ + +// A Factory method for creating new curve subclasses + +THREE.Curve.create = function ( constructor, getPointFunc ) { + + constructor.prototype = Object.create( THREE.Curve.prototype ); + constructor.prototype.getPoint = getPointFunc; + + return constructor; + +}; + +/** + * @author zz85 / http://www.lab4games.net/zz85/blog + * + **/ + +/************************************************************** + * Curved Path - a curve path is simply a array of connected + * curves, but retains the api of a curve + **************************************************************/ + +THREE.CurvePath = function () { + + this.curves = []; + this.bends = []; + + this.autoClose = false; // Automatically closes the path +}; + +THREE.CurvePath.prototype = Object.create( THREE.Curve.prototype ); + +THREE.CurvePath.prototype.add = function ( curve ) { + + this.curves.push( curve ); + +}; + +THREE.CurvePath.prototype.checkConnection = function() { + // TODO + // If the ending of curve is not connected to the starting + // or the next curve, then, this is not a real path +}; + +THREE.CurvePath.prototype.closePath = function() { + // TODO Test + // and verify for vector3 (needs to implement equals) + // Add a line curve if start and end of lines are not connected + var startPoint = this.curves[0].getPoint(0); + var endPoint = this.curves[this.curves.length-1].getPoint(1); + + if (!startPoint.equals(endPoint)) { + this.curves.push( new THREE.LineCurve(endPoint, startPoint) ); + } + +}; + +// To get accurate point with reference to +// entire path distance at time t, +// following has to be done: + +// 1. Length of each sub path have to be known +// 2. Locate and identify type of curve +// 3. Get t for the curve +// 4. Return curve.getPointAt(t') + +THREE.CurvePath.prototype.getPoint = function( t ) { + + var d = t * this.getLength(); + var curveLengths = this.getCurveLengths(); + var i = 0, diff, curve; + + // To think about boundaries points. + + while ( i < curveLengths.length ) { + + if ( curveLengths[ i ] >= d ) { + + diff = curveLengths[ i ] - d; + curve = this.curves[ i ]; + + var u = 1 - diff / curve.getLength(); + + return curve.getPointAt( u ); + + break; + } + + i ++; + + } + + return null; + + // loop where sum != 0, sum > d , sum+1 maxX ) maxX = p.x; + else if ( p.x < minX ) minX = p.x; + + if ( p.y > maxY ) maxY = p.y; + else if ( p.y < minY ) minY = p.y; + + if ( v3 ) { + + if ( p.z > maxZ ) maxZ = p.z; + else if ( p.z < minZ ) minZ = p.z; + + } + + sum.add( p ); + + } + + var ret = { + + minX: minX, + minY: minY, + maxX: maxX, + maxY: maxY, + centroid: sum.divideScalar( il ) + + }; + + if ( v3 ) { + + ret.maxZ = maxZ; + ret.minZ = minZ; + + } + + return ret; + +}; + +/************************************************************** + * Create Geometries Helpers + **************************************************************/ + +/// Generate geometry from path points (for Line or ParticleSystem objects) + +THREE.CurvePath.prototype.createPointsGeometry = function( divisions ) { + + var pts = this.getPoints( divisions, true ); + return this.createGeometry( pts ); + +}; + +// Generate geometry from equidistance sampling along the path + +THREE.CurvePath.prototype.createSpacedPointsGeometry = function( divisions ) { + + var pts = this.getSpacedPoints( divisions, true ); + return this.createGeometry( pts ); + +}; + +THREE.CurvePath.prototype.createGeometry = function( points ) { + + var geometry = new THREE.Geometry(); + + for ( var i = 0; i < points.length; i ++ ) { + + geometry.vertices.push( new THREE.Vector3( points[ i ].x, points[ i ].y, points[ i ].z || 0) ); + + } + + return geometry; + +}; + + +/************************************************************** + * Bend / Wrap Helper Methods + **************************************************************/ + +// Wrap path / Bend modifiers? + +THREE.CurvePath.prototype.addWrapPath = function ( bendpath ) { + + this.bends.push( bendpath ); + +}; + +THREE.CurvePath.prototype.getTransformedPoints = function( segments, bends ) { + + var oldPts = this.getPoints( segments ); // getPoints getSpacedPoints + var i, il; + + if ( !bends ) { + + bends = this.bends; + + } + + for ( i = 0, il = bends.length; i < il; i ++ ) { + + oldPts = this.getWrapPoints( oldPts, bends[ i ] ); + + } + + return oldPts; + +}; + +THREE.CurvePath.prototype.getTransformedSpacedPoints = function( segments, bends ) { + + var oldPts = this.getSpacedPoints( segments ); + + var i, il; + + if ( !bends ) { + + bends = this.bends; + + } + + for ( i = 0, il = bends.length; i < il; i ++ ) { + + oldPts = this.getWrapPoints( oldPts, bends[ i ] ); + + } + + return oldPts; + +}; + +// This returns getPoints() bend/wrapped around the contour of a path. +// Read http://www.planetclegg.com/projects/WarpingTextToSplines.html + +THREE.CurvePath.prototype.getWrapPoints = function ( oldPts, path ) { + + var bounds = this.getBoundingBox(); + + var i, il, p, oldX, oldY, xNorm; + + for ( i = 0, il = oldPts.length; i < il; i ++ ) { + + p = oldPts[ i ]; + + oldX = p.x; + oldY = p.y; + + xNorm = oldX / bounds.maxX; + + // If using actual distance, for length > path, requires line extrusions + //xNorm = path.getUtoTmapping(xNorm, oldX); // 3 styles. 1) wrap stretched. 2) wrap stretch by arc length 3) warp by actual distance + + xNorm = path.getUtoTmapping( xNorm, oldX ); + + // check for out of bounds? + + var pathPt = path.getPoint( xNorm ); + var normal = path.getTangent( xNorm ); + normal.set( -normal.y, normal.x ).multiplyScalar( oldY ); + + p.x = pathPt.x + normal.x; + p.y = pathPt.y + normal.y; + + } + + return oldPts; + +}; + + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Gyroscope = function () { + + THREE.Object3D.call( this ); + +}; + +THREE.Gyroscope.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.Gyroscope.prototype.updateMatrixWorld = function ( force ) { + + this.matrixAutoUpdate && this.updateMatrix(); + + // update matrixWorld + + if ( this.matrixWorldNeedsUpdate || force ) { + + if ( this.parent ) { + + this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); + + this.matrixWorld.decompose( this.translationWorld, this.quaternionWorld, this.scaleWorld ); + this.matrix.decompose( this.translationObject, this.quaternionObject, this.scaleObject ); + + this.matrixWorld.compose( this.translationWorld, this.quaternionObject, this.scaleWorld ); + + + } else { + + this.matrixWorld.copy( this.matrix ); + + } + + + this.matrixWorldNeedsUpdate = false; + + force = true; + + } + + // update children + + for ( var i = 0, l = this.children.length; i < l; i ++ ) { + + this.children[ i ].updateMatrixWorld( force ); + + } + +}; + +THREE.Gyroscope.prototype.translationWorld = new THREE.Vector3(); +THREE.Gyroscope.prototype.translationObject = new THREE.Vector3(); +THREE.Gyroscope.prototype.quaternionWorld = new THREE.Quaternion(); +THREE.Gyroscope.prototype.quaternionObject = new THREE.Quaternion(); +THREE.Gyroscope.prototype.scaleWorld = new THREE.Vector3(); +THREE.Gyroscope.prototype.scaleObject = new THREE.Vector3(); + + +/** + * @author zz85 / http://www.lab4games.net/zz85/blog + * Creates free form 2d path using series of points, lines or curves. + * + **/ + +THREE.Path = function ( points ) { + + THREE.CurvePath.call(this); + + this.actions = []; + + if ( points ) { + + this.fromPoints( points ); + + } + +}; + +THREE.Path.prototype = Object.create( THREE.CurvePath.prototype ); + +THREE.PathActions = { + + MOVE_TO: 'moveTo', + LINE_TO: 'lineTo', + QUADRATIC_CURVE_TO: 'quadraticCurveTo', // Bezier quadratic curve + BEZIER_CURVE_TO: 'bezierCurveTo', // Bezier cubic curve + CSPLINE_THRU: 'splineThru', // Catmull-rom spline + ARC: 'arc', // Circle + ELLIPSE: 'ellipse' +}; + +// TODO Clean up PATH API + +// Create path using straight lines to connect all points +// - vectors: array of Vector2 + +THREE.Path.prototype.fromPoints = function ( vectors ) { + + this.moveTo( vectors[ 0 ].x, vectors[ 0 ].y ); + + for ( var v = 1, vlen = vectors.length; v < vlen; v ++ ) { + + this.lineTo( vectors[ v ].x, vectors[ v ].y ); + + }; + +}; + +// startPath() endPath()? + +THREE.Path.prototype.moveTo = function ( x, y ) { + + var args = Array.prototype.slice.call( arguments ); + this.actions.push( { action: THREE.PathActions.MOVE_TO, args: args } ); + +}; + +THREE.Path.prototype.lineTo = function ( x, y ) { + + var args = Array.prototype.slice.call( arguments ); + + var lastargs = this.actions[ this.actions.length - 1 ].args; + + var x0 = lastargs[ lastargs.length - 2 ]; + var y0 = lastargs[ lastargs.length - 1 ]; + + var curve = new THREE.LineCurve( new THREE.Vector2( x0, y0 ), new THREE.Vector2( x, y ) ); + this.curves.push( curve ); + + this.actions.push( { action: THREE.PathActions.LINE_TO, args: args } ); + +}; + +THREE.Path.prototype.quadraticCurveTo = function( aCPx, aCPy, aX, aY ) { + + var args = Array.prototype.slice.call( arguments ); + + var lastargs = this.actions[ this.actions.length - 1 ].args; + + var x0 = lastargs[ lastargs.length - 2 ]; + var y0 = lastargs[ lastargs.length - 1 ]; + + var curve = new THREE.QuadraticBezierCurve( new THREE.Vector2( x0, y0 ), + new THREE.Vector2( aCPx, aCPy ), + new THREE.Vector2( aX, aY ) ); + this.curves.push( curve ); + + this.actions.push( { action: THREE.PathActions.QUADRATIC_CURVE_TO, args: args } ); + +}; + +THREE.Path.prototype.bezierCurveTo = function( aCP1x, aCP1y, + aCP2x, aCP2y, + aX, aY ) { + + var args = Array.prototype.slice.call( arguments ); + + var lastargs = this.actions[ this.actions.length - 1 ].args; + + var x0 = lastargs[ lastargs.length - 2 ]; + var y0 = lastargs[ lastargs.length - 1 ]; + + var curve = new THREE.CubicBezierCurve( new THREE.Vector2( x0, y0 ), + new THREE.Vector2( aCP1x, aCP1y ), + new THREE.Vector2( aCP2x, aCP2y ), + new THREE.Vector2( aX, aY ) ); + this.curves.push( curve ); + + this.actions.push( { action: THREE.PathActions.BEZIER_CURVE_TO, args: args } ); + +}; + +THREE.Path.prototype.splineThru = function( pts /*Array of Vector*/ ) { + + var args = Array.prototype.slice.call( arguments ); + var lastargs = this.actions[ this.actions.length - 1 ].args; + + var x0 = lastargs[ lastargs.length - 2 ]; + var y0 = lastargs[ lastargs.length - 1 ]; +//--- + var npts = [ new THREE.Vector2( x0, y0 ) ]; + Array.prototype.push.apply( npts, pts ); + + var curve = new THREE.SplineCurve( npts ); + this.curves.push( curve ); + + this.actions.push( { action: THREE.PathActions.CSPLINE_THRU, args: args } ); + +}; + +// FUTURE: Change the API or follow canvas API? + +THREE.Path.prototype.arc = function ( aX, aY, aRadius, + aStartAngle, aEndAngle, aClockwise ) { + + var lastargs = this.actions[ this.actions.length - 1].args; + var x0 = lastargs[ lastargs.length - 2 ]; + var y0 = lastargs[ lastargs.length - 1 ]; + + this.absarc(aX + x0, aY + y0, aRadius, + aStartAngle, aEndAngle, aClockwise ); + + }; + + THREE.Path.prototype.absarc = function ( aX, aY, aRadius, + aStartAngle, aEndAngle, aClockwise ) { + this.absellipse(aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise); + }; + +THREE.Path.prototype.ellipse = function ( aX, aY, xRadius, yRadius, + aStartAngle, aEndAngle, aClockwise ) { + + var lastargs = this.actions[ this.actions.length - 1].args; + var x0 = lastargs[ lastargs.length - 2 ]; + var y0 = lastargs[ lastargs.length - 1 ]; + + this.absellipse(aX + x0, aY + y0, xRadius, yRadius, + aStartAngle, aEndAngle, aClockwise ); + + }; + + +THREE.Path.prototype.absellipse = function ( aX, aY, xRadius, yRadius, + aStartAngle, aEndAngle, aClockwise ) { + + var args = Array.prototype.slice.call( arguments ); + var curve = new THREE.EllipseCurve( aX, aY, xRadius, yRadius, + aStartAngle, aEndAngle, aClockwise ); + this.curves.push( curve ); + + var lastPoint = curve.getPoint(1); + args.push(lastPoint.x); + args.push(lastPoint.y); + + this.actions.push( { action: THREE.PathActions.ELLIPSE, args: args } ); + + }; + +THREE.Path.prototype.getSpacedPoints = function ( divisions, closedPath ) { + + if ( ! divisions ) divisions = 40; + + var points = []; + + for ( var i = 0; i < divisions; i ++ ) { + + points.push( this.getPoint( i / divisions ) ); + + //if( !this.getPoint( i / divisions ) ) throw "DIE"; + + } + + // if ( closedPath ) { + // + // points.push( points[ 0 ] ); + // + // } + + return points; + +}; + +/* Return an array of vectors based on contour of the path */ + +THREE.Path.prototype.getPoints = function( divisions, closedPath ) { + + if (this.useSpacedPoints) { + console.log('tata'); + return this.getSpacedPoints( divisions, closedPath ); + } + + divisions = divisions || 12; + + var points = []; + + var i, il, item, action, args; + var cpx, cpy, cpx2, cpy2, cpx1, cpy1, cpx0, cpy0, + laste, j, + t, tx, ty; + + for ( i = 0, il = this.actions.length; i < il; i ++ ) { + + item = this.actions[ i ]; + + action = item.action; + args = item.args; + + switch( action ) { + + case THREE.PathActions.MOVE_TO: + + points.push( new THREE.Vector2( args[ 0 ], args[ 1 ] ) ); + + break; + + case THREE.PathActions.LINE_TO: + + points.push( new THREE.Vector2( args[ 0 ], args[ 1 ] ) ); + + break; + + case THREE.PathActions.QUADRATIC_CURVE_TO: + + cpx = args[ 2 ]; + cpy = args[ 3 ]; + + cpx1 = args[ 0 ]; + cpy1 = args[ 1 ]; + + if ( points.length > 0 ) { + + laste = points[ points.length - 1 ]; + + cpx0 = laste.x; + cpy0 = laste.y; + + } else { + + laste = this.actions[ i - 1 ].args; + + cpx0 = laste[ laste.length - 2 ]; + cpy0 = laste[ laste.length - 1 ]; + + } + + for ( j = 1; j <= divisions; j ++ ) { + + t = j / divisions; + + tx = THREE.Shape.Utils.b2( t, cpx0, cpx1, cpx ); + ty = THREE.Shape.Utils.b2( t, cpy0, cpy1, cpy ); + + points.push( new THREE.Vector2( tx, ty ) ); + + } + + break; + + case THREE.PathActions.BEZIER_CURVE_TO: + + cpx = args[ 4 ]; + cpy = args[ 5 ]; + + cpx1 = args[ 0 ]; + cpy1 = args[ 1 ]; + + cpx2 = args[ 2 ]; + cpy2 = args[ 3 ]; + + if ( points.length > 0 ) { + + laste = points[ points.length - 1 ]; + + cpx0 = laste.x; + cpy0 = laste.y; + + } else { + + laste = this.actions[ i - 1 ].args; + + cpx0 = laste[ laste.length - 2 ]; + cpy0 = laste[ laste.length - 1 ]; + + } + + + for ( j = 1; j <= divisions; j ++ ) { + + t = j / divisions; + + tx = THREE.Shape.Utils.b3( t, cpx0, cpx1, cpx2, cpx ); + ty = THREE.Shape.Utils.b3( t, cpy0, cpy1, cpy2, cpy ); + + points.push( new THREE.Vector2( tx, ty ) ); + + } + + break; + + case THREE.PathActions.CSPLINE_THRU: + + laste = this.actions[ i - 1 ].args; + + var last = new THREE.Vector2( laste[ laste.length - 2 ], laste[ laste.length - 1 ] ); + var spts = [ last ]; + + var n = divisions * args[ 0 ].length; + + spts = spts.concat( args[ 0 ] ); + + var spline = new THREE.SplineCurve( spts ); + + for ( j = 1; j <= n; j ++ ) { + + points.push( spline.getPointAt( j / n ) ) ; + + } + + break; + + case THREE.PathActions.ARC: + + var aX = args[ 0 ], aY = args[ 1 ], + aRadius = args[ 2 ], + aStartAngle = args[ 3 ], aEndAngle = args[ 4 ], + aClockwise = !!args[ 5 ]; + + var deltaAngle = aEndAngle - aStartAngle; + var angle; + var tdivisions = divisions * 2; + + for ( j = 1; j <= tdivisions; j ++ ) { + + t = j / tdivisions; + + if ( ! aClockwise ) { + + t = 1 - t; + + } + + angle = aStartAngle + t * deltaAngle; + + tx = aX + aRadius * Math.cos( angle ); + ty = aY + aRadius * Math.sin( angle ); + + //console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty); + + points.push( new THREE.Vector2( tx, ty ) ); + + } + + //console.log(points); + + break; + + case THREE.PathActions.ELLIPSE: + + var aX = args[ 0 ], aY = args[ 1 ], + xRadius = args[ 2 ], + yRadius = args[ 3 ], + aStartAngle = args[ 4 ], aEndAngle = args[ 5 ], + aClockwise = !!args[ 6 ]; + + + var deltaAngle = aEndAngle - aStartAngle; + var angle; + var tdivisions = divisions * 2; + + for ( j = 1; j <= tdivisions; j ++ ) { + + t = j / tdivisions; + + if ( ! aClockwise ) { + + t = 1 - t; + + } + + angle = aStartAngle + t * deltaAngle; + + tx = aX + xRadius * Math.cos( angle ); + ty = aY + yRadius * Math.sin( angle ); + + //console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty); + + points.push( new THREE.Vector2( tx, ty ) ); + + } + + //console.log(points); + + break; + + } // end switch + + } + + + + // Normalize to remove the closing point by default. + var lastPoint = points[ points.length - 1]; + var EPSILON = 0.0000000001; + if ( Math.abs(lastPoint.x - points[ 0 ].x) < EPSILON && + Math.abs(lastPoint.y - points[ 0 ].y) < EPSILON) + points.splice( points.length - 1, 1); + if ( closedPath ) { + + points.push( points[ 0 ] ); + + } + + return points; + +}; + +// Breaks path into shapes + +THREE.Path.prototype.toShapes = function( isCCW ) { + + function isPointInsidePolygon( inPt, inPolygon ) { + var EPSILON = 0.0000000001; + + var polyLen = inPolygon.length; + + // inPt on polygon contour => immediate success or + // toggling of inside/outside at every single! intersection point of an edge + // with the horizontal line through inPt, left of inPt + // not counting lowerY endpoints of edges and whole edges on that line + var inside = false; + for( var p = polyLen - 1, q = 0; q < polyLen; p = q++ ) { + var edgeLowPt = inPolygon[ p ]; + var edgeHighPt = inPolygon[ q ]; + + var edgeDx = edgeHighPt.x - edgeLowPt.x; + var edgeDy = edgeHighPt.y - edgeLowPt.y; + + if ( Math.abs(edgeDy) > EPSILON ) { // not parallel + if ( edgeDy < 0 ) { + edgeLowPt = inPolygon[ q ]; edgeDx = -edgeDx; + edgeHighPt = inPolygon[ p ]; edgeDy = -edgeDy; + } + if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) continue; + + if ( inPt.y == edgeLowPt.y ) { + if ( inPt.x == edgeLowPt.x ) return true; // inPt is on contour ? + // continue; // no intersection or edgeLowPt => doesn't count !!! + } else { + var perpEdge = edgeDy * (inPt.x - edgeLowPt.x) - edgeDx * (inPt.y - edgeLowPt.y); + if ( perpEdge == 0 ) return true; // inPt is on contour ? + if ( perpEdge < 0 ) continue; + inside = !inside; // true intersection left of inPt + } + } else { // parallel or colinear + if ( inPt.y != edgeLowPt.y ) continue; // parallel + // egde lies on the same horizontal line as inPt + if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) || + ( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) ) return true; // inPt: Point on contour ! + // continue; + } + } + + return inside; + } + + var i, il, item, action, args; + + var subPaths = [], lastPath = new THREE.Path(); + + for ( i = 0, il = this.actions.length; i < il; i ++ ) { + + item = this.actions[ i ]; + + args = item.args; + action = item.action; + + if ( action == THREE.PathActions.MOVE_TO ) { + + if ( lastPath.actions.length != 0 ) { + + subPaths.push( lastPath ); + lastPath = new THREE.Path(); + + } + + } + + lastPath[ action ].apply( lastPath, args ); + + } + + if ( lastPath.actions.length != 0 ) { + + subPaths.push( lastPath ); + + } + + // console.log(subPaths); + + if ( subPaths.length == 0 ) return []; + + var solid, tmpPath, tmpShape, shapes = []; + + if ( subPaths.length == 1) { + + tmpPath = subPaths[0]; + tmpShape = new THREE.Shape(); + tmpShape.actions = tmpPath.actions; + tmpShape.curves = tmpPath.curves; + shapes.push( tmpShape ); + return shapes; + + } + + var holesFirst = !THREE.Shape.Utils.isClockWise( subPaths[ 0 ].getPoints() ); + holesFirst = isCCW ? !holesFirst : holesFirst; + + // console.log("Holes first", holesFirst); + + var betterShapeHoles = []; + var newShapes = []; + var newShapeHoles = []; + var mainIdx = 0; + var tmpPoints; + + newShapes[mainIdx] = undefined; + newShapeHoles[mainIdx] = []; + + for ( i = 0, il = subPaths.length; i < il; i ++ ) { + + tmpPath = subPaths[ i ]; + tmpPoints = tmpPath.getPoints(); + solid = THREE.Shape.Utils.isClockWise( tmpPoints ); + solid = isCCW ? !solid : solid; + + if ( solid ) { + + if ( (! holesFirst ) && ( newShapes[mainIdx] ) ) mainIdx++; + + newShapes[mainIdx] = { s: new THREE.Shape(), p: tmpPoints }; + newShapes[mainIdx].s.actions = tmpPath.actions; + newShapes[mainIdx].s.curves = tmpPath.curves; + + if ( holesFirst ) mainIdx++; + newShapeHoles[mainIdx] = []; + + //console.log('cw', i); + + } else { + + newShapeHoles[mainIdx].push( { h: tmpPath, p: tmpPoints[0] } ); + + //console.log('ccw', i); + + } + + } + + if ( newShapes.length > 1 ) { + var ambigious = false; + var toChange = []; + + for (var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx++ ) { + betterShapeHoles[sIdx] = []; + } + for (var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx++ ) { + var sh = newShapes[sIdx]; + var sho = newShapeHoles[sIdx]; + for (var hIdx = 0; hIdx < sho.length; hIdx++ ) { + var ho = sho[hIdx]; + var hole_unassigned = true; + for (var s2Idx = 0; s2Idx < newShapes.length; s2Idx++ ) { + if ( isPointInsidePolygon( ho.p, newShapes[s2Idx].p ) ) { + if ( sIdx != s2Idx ) toChange.push( { froms: sIdx, tos: s2Idx, hole: hIdx } ); + if ( hole_unassigned ) { + hole_unassigned = false; + betterShapeHoles[s2Idx].push( ho ); + } else { + ambigious = true; + } + } + } + if ( hole_unassigned ) { betterShapeHoles[sIdx].push( ho ); } + } + } + // console.log("ambigious: ", ambigious); + if ( toChange.length > 0 ) { + // console.log("to change: ", toChange); + if (! ambigious) newShapeHoles = betterShapeHoles; + } + } + + var tmpHoles, j, jl; + for ( i = 0, il = newShapes.length; i < il; i ++ ) { + tmpShape = newShapes[i].s; + shapes.push( tmpShape ); + tmpHoles = newShapeHoles[i]; + for ( j = 0, jl = tmpHoles.length; j < jl; j ++ ) { + tmpShape.holes.push( tmpHoles[j].h ); + } + } + + //console.log("shape", shapes); + + return shapes; + +}; + +/** + * @author zz85 / http://www.lab4games.net/zz85/blog + * Defines a 2d shape plane using paths. + **/ + +// STEP 1 Create a path. +// STEP 2 Turn path into shape. +// STEP 3 ExtrudeGeometry takes in Shape/Shapes +// STEP 3a - Extract points from each shape, turn to vertices +// STEP 3b - Triangulate each shape, add faces. + +THREE.Shape = function () { + + THREE.Path.apply( this, arguments ); + this.holes = []; + +}; + +THREE.Shape.prototype = Object.create( THREE.Path.prototype ); + +// Convenience method to return ExtrudeGeometry + +THREE.Shape.prototype.extrude = function ( options ) { + + var extruded = new THREE.ExtrudeGeometry( this, options ); + return extruded; + +}; + +// Convenience method to return ShapeGeometry + +THREE.Shape.prototype.makeGeometry = function ( options ) { + + var geometry = new THREE.ShapeGeometry( this, options ); + return geometry; + +}; + +// Get points of holes + +THREE.Shape.prototype.getPointsHoles = function ( divisions ) { + + var i, il = this.holes.length, holesPts = []; + + for ( i = 0; i < il; i ++ ) { + + holesPts[ i ] = this.holes[ i ].getTransformedPoints( divisions, this.bends ); + + } + + return holesPts; + +}; + +// Get points of holes (spaced by regular distance) + +THREE.Shape.prototype.getSpacedPointsHoles = function ( divisions ) { + + var i, il = this.holes.length, holesPts = []; + + for ( i = 0; i < il; i ++ ) { + + holesPts[ i ] = this.holes[ i ].getTransformedSpacedPoints( divisions, this.bends ); + + } + + return holesPts; + +}; + + +// Get points of shape and holes (keypoints based on segments parameter) + +THREE.Shape.prototype.extractAllPoints = function ( divisions ) { + + return { + + shape: this.getTransformedPoints( divisions ), + holes: this.getPointsHoles( divisions ) + + }; + +}; + +THREE.Shape.prototype.extractPoints = function ( divisions ) { + + if (this.useSpacedPoints) { + return this.extractAllSpacedPoints(divisions); + } + + return this.extractAllPoints(divisions); + +}; + +// +// THREE.Shape.prototype.extractAllPointsWithBend = function ( divisions, bend ) { +// +// return { +// +// shape: this.transform( bend, divisions ), +// holes: this.getPointsHoles( divisions, bend ) +// +// }; +// +// }; + +// Get points of shape and holes (spaced by regular distance) + +THREE.Shape.prototype.extractAllSpacedPoints = function ( divisions ) { + + return { + + shape: this.getTransformedSpacedPoints( divisions ), + holes: this.getSpacedPointsHoles( divisions ) + + }; + +}; + +/************************************************************** + * Utils + **************************************************************/ + +THREE.Shape.Utils = { + + triangulateShape: function ( contour, holes ) { + + function point_in_segment_2D_colin( inSegPt1, inSegPt2, inOtherPt ) { + // inOtherPt needs to be colinear to the inSegment + if ( inSegPt1.x != inSegPt2.x ) { + if ( inSegPt1.x < inSegPt2.x ) { + return ( ( inSegPt1.x <= inOtherPt.x ) && ( inOtherPt.x <= inSegPt2.x ) ); + } else { + return ( ( inSegPt2.x <= inOtherPt.x ) && ( inOtherPt.x <= inSegPt1.x ) ); + } + } else { + if ( inSegPt1.y < inSegPt2.y ) { + return ( ( inSegPt1.y <= inOtherPt.y ) && ( inOtherPt.y <= inSegPt2.y ) ); + } else { + return ( ( inSegPt2.y <= inOtherPt.y ) && ( inOtherPt.y <= inSegPt1.y ) ); + } + } + } + + function intersect_segments_2D( inSeg1Pt1, inSeg1Pt2, inSeg2Pt1, inSeg2Pt2, inExcludeAdjacentSegs ) { + var EPSILON = 0.0000000001; + + var seg1dx = inSeg1Pt2.x - inSeg1Pt1.x, seg1dy = inSeg1Pt2.y - inSeg1Pt1.y; + var seg2dx = inSeg2Pt2.x - inSeg2Pt1.x, seg2dy = inSeg2Pt2.y - inSeg2Pt1.y; + + var seg1seg2dx = inSeg1Pt1.x - inSeg2Pt1.x; + var seg1seg2dy = inSeg1Pt1.y - inSeg2Pt1.y; + + var limit = seg1dy * seg2dx - seg1dx * seg2dy; + var perpSeg1 = seg1dy * seg1seg2dx - seg1dx * seg1seg2dy; + + if ( Math.abs(limit) > EPSILON ) { // not parallel + + var perpSeg2; + if ( limit > 0 ) { + if ( ( perpSeg1 < 0 ) || ( perpSeg1 > limit ) ) return []; + perpSeg2 = seg2dy * seg1seg2dx - seg2dx * seg1seg2dy; + if ( ( perpSeg2 < 0 ) || ( perpSeg2 > limit ) ) return []; + } else { + if ( ( perpSeg1 > 0 ) || ( perpSeg1 < limit ) ) return []; + perpSeg2 = seg2dy * seg1seg2dx - seg2dx * seg1seg2dy; + if ( ( perpSeg2 > 0 ) || ( perpSeg2 < limit ) ) return []; + } + + // i.e. to reduce rounding errors + // intersection at endpoint of segment#1? + if ( perpSeg2 == 0 ) { + if ( ( inExcludeAdjacentSegs ) && + ( ( perpSeg1 == 0 ) || ( perpSeg1 == limit ) ) ) return []; + return [ inSeg1Pt1 ]; + } + if ( perpSeg2 == limit ) { + if ( ( inExcludeAdjacentSegs ) && + ( ( perpSeg1 == 0 ) || ( perpSeg1 == limit ) ) ) return []; + return [ inSeg1Pt2 ]; + } + // intersection at endpoint of segment#2? + if ( perpSeg1 == 0 ) return [ inSeg2Pt1 ]; + if ( perpSeg1 == limit ) return [ inSeg2Pt2 ]; + + // return real intersection point + var factorSeg1 = perpSeg2 / limit; + return [ { x: inSeg1Pt1.x + factorSeg1 * seg1dx, + y: inSeg1Pt1.y + factorSeg1 * seg1dy } ]; + + } else { // parallel or colinear + if ( ( perpSeg1 != 0 ) || + ( seg2dy * seg1seg2dx != seg2dx * seg1seg2dy ) ) return []; + + // they are collinear or degenerate + var seg1Pt = ( (seg1dx == 0) && (seg1dy == 0) ); // segment1 ist just a point? + var seg2Pt = ( (seg2dx == 0) && (seg2dy == 0) ); // segment2 ist just a point? + // both segments are points + if ( seg1Pt && seg2Pt ) { + if ( (inSeg1Pt1.x != inSeg2Pt1.x) || + (inSeg1Pt1.y != inSeg2Pt1.y) ) return []; // they are distinct points + return [ inSeg1Pt1 ]; // they are the same point + } + // segment#1 is a single point + if ( seg1Pt ) { + if (! point_in_segment_2D_colin( inSeg2Pt1, inSeg2Pt2, inSeg1Pt1 ) ) return []; // but not in segment#2 + return [ inSeg1Pt1 ]; + } + // segment#2 is a single point + if ( seg2Pt ) { + if (! point_in_segment_2D_colin( inSeg1Pt1, inSeg1Pt2, inSeg2Pt1 ) ) return []; // but not in segment#1 + return [ inSeg2Pt1 ]; + } + + // they are collinear segments, which might overlap + var seg1min, seg1max, seg1minVal, seg1maxVal; + var seg2min, seg2max, seg2minVal, seg2maxVal; + if (seg1dx != 0) { // the segments are NOT on a vertical line + if ( inSeg1Pt1.x < inSeg1Pt2.x ) { + seg1min = inSeg1Pt1; seg1minVal = inSeg1Pt1.x; + seg1max = inSeg1Pt2; seg1maxVal = inSeg1Pt2.x; + } else { + seg1min = inSeg1Pt2; seg1minVal = inSeg1Pt2.x; + seg1max = inSeg1Pt1; seg1maxVal = inSeg1Pt1.x; + } + if ( inSeg2Pt1.x < inSeg2Pt2.x ) { + seg2min = inSeg2Pt1; seg2minVal = inSeg2Pt1.x; + seg2max = inSeg2Pt2; seg2maxVal = inSeg2Pt2.x; + } else { + seg2min = inSeg2Pt2; seg2minVal = inSeg2Pt2.x; + seg2max = inSeg2Pt1; seg2maxVal = inSeg2Pt1.x; + } + } else { // the segments are on a vertical line + if ( inSeg1Pt1.y < inSeg1Pt2.y ) { + seg1min = inSeg1Pt1; seg1minVal = inSeg1Pt1.y; + seg1max = inSeg1Pt2; seg1maxVal = inSeg1Pt2.y; + } else { + seg1min = inSeg1Pt2; seg1minVal = inSeg1Pt2.y; + seg1max = inSeg1Pt1; seg1maxVal = inSeg1Pt1.y; + } + if ( inSeg2Pt1.y < inSeg2Pt2.y ) { + seg2min = inSeg2Pt1; seg2minVal = inSeg2Pt1.y; + seg2max = inSeg2Pt2; seg2maxVal = inSeg2Pt2.y; + } else { + seg2min = inSeg2Pt2; seg2minVal = inSeg2Pt2.y; + seg2max = inSeg2Pt1; seg2maxVal = inSeg2Pt1.y; + } + } + if ( seg1minVal <= seg2minVal ) { + if ( seg1maxVal < seg2minVal ) return []; + if ( seg1maxVal == seg2minVal ) { + if ( inExcludeAdjacentSegs ) return []; + return [ seg2min ]; + } + if ( seg1maxVal <= seg2maxVal ) return [ seg2min, seg1max ]; + return [ seg2min, seg2max ]; + } else { + if ( seg1minVal > seg2maxVal ) return []; + if ( seg1minVal == seg2maxVal ) { + if ( inExcludeAdjacentSegs ) return []; + return [ seg1min ]; + } + if ( seg1maxVal <= seg2maxVal ) return [ seg1min, seg1max ]; + return [ seg1min, seg2max ]; + } + } + } + + function isPointInsideAngle( inVertex, inLegFromPt, inLegToPt, inOtherPt ) { + // The order of legs is important + + var EPSILON = 0.0000000001; + + // translation of all points, so that Vertex is at (0,0) + var legFromPtX = inLegFromPt.x - inVertex.x, legFromPtY = inLegFromPt.y - inVertex.y; + var legToPtX = inLegToPt.x - inVertex.x, legToPtY = inLegToPt.y - inVertex.y; + var otherPtX = inOtherPt.x - inVertex.x, otherPtY = inOtherPt.y - inVertex.y; + + // main angle >0: < 180 deg.; 0: 180 deg.; <0: > 180 deg. + var from2toAngle = legFromPtX * legToPtY - legFromPtY * legToPtX; + var from2otherAngle = legFromPtX * otherPtY - legFromPtY * otherPtX; + + if ( Math.abs(from2toAngle) > EPSILON ) { // angle != 180 deg. + + var other2toAngle = otherPtX * legToPtY - otherPtY * legToPtX; + // console.log( "from2to: " + from2toAngle + ", from2other: " + from2otherAngle + ", other2to: " + other2toAngle ); + + if ( from2toAngle > 0 ) { // main angle < 180 deg. + return ( ( from2otherAngle >= 0 ) && ( other2toAngle >= 0 ) ); + } else { // main angle > 180 deg. + return ( ( from2otherAngle >= 0 ) || ( other2toAngle >= 0 ) ); + } + } else { // angle == 180 deg. + // console.log( "from2to: 180 deg., from2other: " + from2otherAngle ); + return ( from2otherAngle > 0 ); + } + } + + + function removeHoles( contour, holes ) { + + var shape = contour.concat(); // work on this shape + var hole; + + function isCutLineInsideAngles( inShapeIdx, inHoleIdx ) { + // Check if hole point lies within angle around shape point + var lastShapeIdx = shape.length - 1; + + var prevShapeIdx = inShapeIdx - 1; + if ( prevShapeIdx < 0 ) prevShapeIdx = lastShapeIdx; + + var nextShapeIdx = inShapeIdx + 1; + if ( nextShapeIdx > lastShapeIdx ) nextShapeIdx = 0; + + var insideAngle = isPointInsideAngle( shape[inShapeIdx], shape[ prevShapeIdx ], shape[ nextShapeIdx ], hole[inHoleIdx] ); + if (! insideAngle ) { + // console.log( "Vertex (Shape): " + inShapeIdx + ", Point: " + hole[inHoleIdx].x + "/" + hole[inHoleIdx].y ); + return false; + } + + // Check if shape point lies within angle around hole point + var lastHoleIdx = hole.length - 1; + + var prevHoleIdx = inHoleIdx - 1; + if ( prevHoleIdx < 0 ) prevHoleIdx = lastHoleIdx; + + var nextHoleIdx = inHoleIdx + 1; + if ( nextHoleIdx > lastHoleIdx ) nextHoleIdx = 0; + + insideAngle = isPointInsideAngle( hole[inHoleIdx], hole[ prevHoleIdx ], hole[ nextHoleIdx ], shape[inShapeIdx] ); + if (! insideAngle ) { + // console.log( "Vertex (Hole): " + inHoleIdx + ", Point: " + shape[inShapeIdx].x + "/" + shape[inShapeIdx].y ); + return false; + } + + return true; + } + + function intersectsShapeEdge( inShapePt, inHolePt ) { + // checks for intersections with shape edges + var sIdx, nextIdx, intersection; + for ( sIdx = 0; sIdx < shape.length; sIdx++ ) { + nextIdx = sIdx+1; nextIdx %= shape.length; + intersection = intersect_segments_2D( inShapePt, inHolePt, shape[sIdx], shape[nextIdx], true ); + if ( intersection.length > 0 ) return true; + } + + return false; + } + + var indepHoles = []; + + function intersectsHoleEdge( inShapePt, inHolePt ) { + // checks for intersections with hole edges + var ihIdx, chkHole, + hIdx, nextIdx, intersection; + for ( ihIdx = 0; ihIdx < indepHoles.length; ihIdx++ ) { + chkHole = holes[indepHoles[ihIdx]]; + for ( hIdx = 0; hIdx < chkHole.length; hIdx++ ) { + nextIdx = hIdx+1; nextIdx %= chkHole.length; + intersection = intersect_segments_2D( inShapePt, inHolePt, chkHole[hIdx], chkHole[nextIdx], true ); + if ( intersection.length > 0 ) return true; + } + } + return false; + } + + var holeIndex, shapeIndex, + shapePt, holePt, + holeIdx, cutKey, failedCuts = [], + tmpShape1, tmpShape2, + tmpHole1, tmpHole2; + + for ( var h = 0, hl = holes.length; h < hl; h ++ ) { + + indepHoles.push( h ); + + } + + var counter = indepHoles.length * 2; + while ( indepHoles.length > 0 ) { + counter --; + if ( counter < 0 ) { + console.log( "Infinite Loop! Holes left:" + indepHoles.length + ", Probably Hole outside Shape!" ); + break; + } + + // search for shape-vertex and hole-vertex, + // which can be connected without intersections + for ( shapeIndex = 0; shapeIndex < shape.length; shapeIndex++ ) { + + shapePt = shape[ shapeIndex ]; + holeIndex = -1; + + // search for hole which can be reached without intersections + for ( var h = 0; h < indepHoles.length; h ++ ) { + holeIdx = indepHoles[h]; + + // prevent multiple checks + cutKey = shapePt.x + ":" + shapePt.y + ":" + holeIdx; + if ( failedCuts[cutKey] !== undefined ) continue; + + hole = holes[holeIdx]; + for ( var h2 = 0; h2 < hole.length; h2 ++ ) { + holePt = hole[ h2 ]; + if (! isCutLineInsideAngles( shapeIndex, h2 ) ) continue; + if ( intersectsShapeEdge( shapePt, holePt ) ) continue; + if ( intersectsHoleEdge( shapePt, holePt ) ) continue; + + holeIndex = h2; + indepHoles.splice(h,1); + + tmpShape1 = shape.slice( 0, shapeIndex+1 ); + tmpShape2 = shape.slice( shapeIndex ); + tmpHole1 = hole.slice( holeIndex ); + tmpHole2 = hole.slice( 0, holeIndex+1 ); + + shape = tmpShape1.concat( tmpHole1 ).concat( tmpHole2 ).concat( tmpShape2 ); + + // Debug only, to show the selected cuts + // glob_CutLines.push( [ shapePt, holePt ] ); + + break; + } + if ( holeIndex >= 0 ) break; // hole-vertex found + + failedCuts[cutKey] = true; // remember failure + } + if ( holeIndex >= 0 ) break; // hole-vertex found + } + } + + return shape; /* shape with no holes */ + } + + + var i, il, f, face, + key, index, + allPointsMap = {}; + + // To maintain reference to old shape, one must match coordinates, or offset the indices from original arrays. It's probably easier to do the first. + + var allpoints = contour.concat(); + + for ( var h = 0, hl = holes.length; h < hl; h ++ ) { + + Array.prototype.push.apply( allpoints, holes[h] ); + + } + + //console.log( "allpoints",allpoints, allpoints.length ); + + // prepare all points map + + for ( i = 0, il = allpoints.length; i < il; i ++ ) { + + key = allpoints[ i ].x + ":" + allpoints[ i ].y; + + if ( allPointsMap[ key ] !== undefined ) { + + console.log( "Duplicate point", key ); + + } + + allPointsMap[ key ] = i; + + } + + // remove holes by cutting paths to holes and adding them to the shape + var shapeWithoutHoles = removeHoles( contour, holes ); + + var triangles = THREE.FontUtils.Triangulate( shapeWithoutHoles, false ); // True returns indices for points of spooled shape + //console.log( "triangles",triangles, triangles.length ); + + // check all face vertices against all points map + + for ( i = 0, il = triangles.length; i < il; i ++ ) { + + face = triangles[ i ]; + + for ( f = 0; f < 3; f ++ ) { + + key = face[ f ].x + ":" + face[ f ].y; + + index = allPointsMap[ key ]; + + if ( index !== undefined ) { + + face[ f ] = index; + + } + + } + + } + + return triangles.concat(); + + }, + + isClockWise: function ( pts ) { + + return THREE.FontUtils.Triangulate.area( pts ) < 0; + + }, + + // Bezier Curves formulas obtained from + // http://en.wikipedia.org/wiki/B%C3%A9zier_curve + + // Quad Bezier Functions + + b2p0: function ( t, p ) { + + var k = 1 - t; + return k * k * p; + + }, + + b2p1: function ( t, p ) { + + return 2 * ( 1 - t ) * t * p; + + }, + + b2p2: function ( t, p ) { + + return t * t * p; + + }, + + b2: function ( t, p0, p1, p2 ) { + + return this.b2p0( t, p0 ) + this.b2p1( t, p1 ) + this.b2p2( t, p2 ); + + }, + + // Cubic Bezier Functions + + b3p0: function ( t, p ) { + + var k = 1 - t; + return k * k * k * p; + + }, + + b3p1: function ( t, p ) { + + var k = 1 - t; + return 3 * k * k * t * p; + + }, + + b3p2: function ( t, p ) { + + var k = 1 - t; + return 3 * k * t * t * p; + + }, + + b3p3: function ( t, p ) { + + return t * t * t * p; + + }, + + b3: function ( t, p0, p1, p2, p3 ) { + + return this.b3p0( t, p0 ) + this.b3p1( t, p1 ) + this.b3p2( t, p2 ) + this.b3p3( t, p3 ); + + } + +}; + + +/************************************************************** + * Line + **************************************************************/ + +THREE.LineCurve = function ( v1, v2 ) { + + this.v1 = v1; + this.v2 = v2; + +}; + +THREE.LineCurve.prototype = Object.create( THREE.Curve.prototype ); + +THREE.LineCurve.prototype.getPoint = function ( t ) { + + var point = this.v2.clone().sub(this.v1); + point.multiplyScalar( t ).add( this.v1 ); + + return point; + +}; + +// Line curve is linear, so we can overwrite default getPointAt + +THREE.LineCurve.prototype.getPointAt = function ( u ) { + + return this.getPoint( u ); + +}; + +THREE.LineCurve.prototype.getTangent = function( t ) { + + var tangent = this.v2.clone().sub(this.v1); + + return tangent.normalize(); + +}; +/************************************************************** + * Quadratic Bezier curve + **************************************************************/ + + +THREE.QuadraticBezierCurve = function ( v0, v1, v2 ) { + + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + +}; + +THREE.QuadraticBezierCurve.prototype = Object.create( THREE.Curve.prototype ); + + +THREE.QuadraticBezierCurve.prototype.getPoint = function ( t ) { + + var tx, ty; + + tx = THREE.Shape.Utils.b2( t, this.v0.x, this.v1.x, this.v2.x ); + ty = THREE.Shape.Utils.b2( t, this.v0.y, this.v1.y, this.v2.y ); + + return new THREE.Vector2( tx, ty ); + +}; + + +THREE.QuadraticBezierCurve.prototype.getTangent = function( t ) { + + var tx, ty; + + tx = THREE.Curve.Utils.tangentQuadraticBezier( t, this.v0.x, this.v1.x, this.v2.x ); + ty = THREE.Curve.Utils.tangentQuadraticBezier( t, this.v0.y, this.v1.y, this.v2.y ); + + // returns unit vector + + var tangent = new THREE.Vector2( tx, ty ); + tangent.normalize(); + + return tangent; + +}; +/************************************************************** + * Cubic Bezier curve + **************************************************************/ + +THREE.CubicBezierCurve = function ( v0, v1, v2, v3 ) { + + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + +}; + +THREE.CubicBezierCurve.prototype = Object.create( THREE.Curve.prototype ); + +THREE.CubicBezierCurve.prototype.getPoint = function ( t ) { + + var tx, ty; + + tx = THREE.Shape.Utils.b3( t, this.v0.x, this.v1.x, this.v2.x, this.v3.x ); + ty = THREE.Shape.Utils.b3( t, this.v0.y, this.v1.y, this.v2.y, this.v3.y ); + + return new THREE.Vector2( tx, ty ); + +}; + +THREE.CubicBezierCurve.prototype.getTangent = function( t ) { + + var tx, ty; + + tx = THREE.Curve.Utils.tangentCubicBezier( t, this.v0.x, this.v1.x, this.v2.x, this.v3.x ); + ty = THREE.Curve.Utils.tangentCubicBezier( t, this.v0.y, this.v1.y, this.v2.y, this.v3.y ); + + var tangent = new THREE.Vector2( tx, ty ); + tangent.normalize(); + + return tangent; + +}; +/************************************************************** + * Spline curve + **************************************************************/ + +THREE.SplineCurve = function ( points /* array of Vector2 */ ) { + + this.points = (points == undefined) ? [] : points; + +}; + +THREE.SplineCurve.prototype = Object.create( THREE.Curve.prototype ); + +THREE.SplineCurve.prototype.getPoint = function ( t ) { + + var v = new THREE.Vector2(); + var c = []; + var points = this.points, point, intPoint, weight; + point = ( points.length - 1 ) * t; + + intPoint = Math.floor( point ); + weight = point - intPoint; + + c[ 0 ] = intPoint == 0 ? intPoint : intPoint - 1; + c[ 1 ] = intPoint; + c[ 2 ] = intPoint > points.length - 2 ? points.length -1 : intPoint + 1; + c[ 3 ] = intPoint > points.length - 3 ? points.length -1 : intPoint + 2; + + v.x = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].x, points[ c[ 1 ] ].x, points[ c[ 2 ] ].x, points[ c[ 3 ] ].x, weight ); + v.y = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].y, points[ c[ 1 ] ].y, points[ c[ 2 ] ].y, points[ c[ 3 ] ].y, weight ); + + return v; + +}; +/************************************************************** + * Ellipse curve + **************************************************************/ + +THREE.EllipseCurve = function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise ) { + + this.aX = aX; + this.aY = aY; + + this.xRadius = xRadius; + this.yRadius = yRadius; + + this.aStartAngle = aStartAngle; + this.aEndAngle = aEndAngle; + + this.aClockwise = aClockwise; + +}; + +THREE.EllipseCurve.prototype = Object.create( THREE.Curve.prototype ); + +THREE.EllipseCurve.prototype.getPoint = function ( t ) { + + var angle; + var deltaAngle = this.aEndAngle - this.aStartAngle; + + if ( deltaAngle < 0 ) deltaAngle += Math.PI * 2; + if ( deltaAngle > Math.PI * 2 ) deltaAngle -= Math.PI * 2; + + if ( this.aClockwise === true ) { + + angle = this.aEndAngle + ( 1 - t ) * ( Math.PI * 2 - deltaAngle ); + + } else { + + angle = this.aStartAngle + t * deltaAngle; + + } + + var tx = this.aX + this.xRadius * Math.cos( angle ); + var ty = this.aY + this.yRadius * Math.sin( angle ); + + return new THREE.Vector2( tx, ty ); + +}; + +/************************************************************** + * Arc curve + **************************************************************/ + +THREE.ArcCurve = function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { + + THREE.EllipseCurve.call( this, aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); +}; + +THREE.ArcCurve.prototype = Object.create( THREE.EllipseCurve.prototype ); +/************************************************************** + * Line3D + **************************************************************/ + +THREE.LineCurve3 = THREE.Curve.create( + + function ( v1, v2 ) { + + this.v1 = v1; + this.v2 = v2; + + }, + + function ( t ) { + + var r = new THREE.Vector3(); + + + r.subVectors( this.v2, this.v1 ); // diff + r.multiplyScalar( t ); + r.add( this.v1 ); + + return r; + + } + +); + +/************************************************************** + * Quadratic Bezier 3D curve + **************************************************************/ + +THREE.QuadraticBezierCurve3 = THREE.Curve.create( + + function ( v0, v1, v2 ) { + + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + + }, + + function ( t ) { + + var tx, ty, tz; + + tx = THREE.Shape.Utils.b2( t, this.v0.x, this.v1.x, this.v2.x ); + ty = THREE.Shape.Utils.b2( t, this.v0.y, this.v1.y, this.v2.y ); + tz = THREE.Shape.Utils.b2( t, this.v0.z, this.v1.z, this.v2.z ); + + return new THREE.Vector3( tx, ty, tz ); + + } + +); +/************************************************************** + * Cubic Bezier 3D curve + **************************************************************/ + +THREE.CubicBezierCurve3 = THREE.Curve.create( + + function ( v0, v1, v2, v3 ) { + + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + + }, + + function ( t ) { + + var tx, ty, tz; + + tx = THREE.Shape.Utils.b3( t, this.v0.x, this.v1.x, this.v2.x, this.v3.x ); + ty = THREE.Shape.Utils.b3( t, this.v0.y, this.v1.y, this.v2.y, this.v3.y ); + tz = THREE.Shape.Utils.b3( t, this.v0.z, this.v1.z, this.v2.z, this.v3.z ); + + return new THREE.Vector3( tx, ty, tz ); + + } + +); +/************************************************************** + * Spline 3D curve + **************************************************************/ + + +THREE.SplineCurve3 = THREE.Curve.create( + + function ( points /* array of Vector3 */) { + + this.points = (points == undefined) ? [] : points; + + }, + + function ( t ) { + + var v = new THREE.Vector3(); + var c = []; + var points = this.points, point, intPoint, weight; + point = ( points.length - 1 ) * t; + + intPoint = Math.floor( point ); + weight = point - intPoint; + + c[ 0 ] = intPoint == 0 ? intPoint : intPoint - 1; + c[ 1 ] = intPoint; + c[ 2 ] = intPoint > points.length - 2 ? points.length - 1 : intPoint + 1; + c[ 3 ] = intPoint > points.length - 3 ? points.length - 1 : intPoint + 2; + + var pt0 = points[ c[0] ], + pt1 = points[ c[1] ], + pt2 = points[ c[2] ], + pt3 = points[ c[3] ]; + + v.x = THREE.Curve.Utils.interpolate(pt0.x, pt1.x, pt2.x, pt3.x, weight); + v.y = THREE.Curve.Utils.interpolate(pt0.y, pt1.y, pt2.y, pt3.y, weight); + v.z = THREE.Curve.Utils.interpolate(pt0.z, pt1.z, pt2.z, pt3.z, weight); + + return v; + + } + +); + + +// THREE.SplineCurve3.prototype.getTangent = function(t) { +// var v = new THREE.Vector3(); +// var c = []; +// var points = this.points, point, intPoint, weight; +// point = ( points.length - 1 ) * t; + +// intPoint = Math.floor( point ); +// weight = point - intPoint; + +// c[ 0 ] = intPoint == 0 ? intPoint : intPoint - 1; +// c[ 1 ] = intPoint; +// c[ 2 ] = intPoint > points.length - 2 ? points.length - 1 : intPoint + 1; +// c[ 3 ] = intPoint > points.length - 3 ? points.length - 1 : intPoint + 2; + +// var pt0 = points[ c[0] ], +// pt1 = points[ c[1] ], +// pt2 = points[ c[2] ], +// pt3 = points[ c[3] ]; + +// // t = weight; +// v.x = THREE.Curve.Utils.tangentSpline( t, pt0.x, pt1.x, pt2.x, pt3.x ); +// v.y = THREE.Curve.Utils.tangentSpline( t, pt0.y, pt1.y, pt2.y, pt3.y ); +// v.z = THREE.Curve.Utils.tangentSpline( t, pt0.z, pt1.z, pt2.z, pt3.z ); + +// return v; + +// } +/************************************************************** + * Closed Spline 3D curve + **************************************************************/ + + +THREE.ClosedSplineCurve3 = THREE.Curve.create( + + function ( points /* array of Vector3 */) { + + this.points = (points == undefined) ? [] : points; + + }, + + function ( t ) { + + var v = new THREE.Vector3(); + var c = []; + var points = this.points, point, intPoint, weight; + point = ( points.length - 0 ) * t; + // This needs to be from 0-length +1 + + intPoint = Math.floor( point ); + weight = point - intPoint; + + intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / points.length ) + 1 ) * points.length; + c[ 0 ] = ( intPoint - 1 ) % points.length; + c[ 1 ] = ( intPoint ) % points.length; + c[ 2 ] = ( intPoint + 1 ) % points.length; + c[ 3 ] = ( intPoint + 2 ) % points.length; + + v.x = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].x, points[ c[ 1 ] ].x, points[ c[ 2 ] ].x, points[ c[ 3 ] ].x, weight ); + v.y = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].y, points[ c[ 1 ] ].y, points[ c[ 2 ] ].y, points[ c[ 3 ] ].y, weight ); + v.z = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].z, points[ c[ 1 ] ].z, points[ c[ 2 ] ].z, points[ c[ 3 ] ].z, weight ); + + return v; + + } + +); +/** + * @author mikael emtinger / http://gomo.se/ + */ + +THREE.AnimationHandler = (function() { + + var playing = []; + var library = {}; + var that = {}; + + + //--- update --- + + that.update = function( deltaTimeMS ) { + + for( var i = 0; i < playing.length; i ++ ) + playing[ i ].update( deltaTimeMS ); + + }; + + + //--- add --- + + that.addToUpdate = function( animation ) { + + if ( playing.indexOf( animation ) === -1 ) + playing.push( animation ); + + }; + + + //--- remove --- + + that.removeFromUpdate = function( animation ) { + + var index = playing.indexOf( animation ); + + if( index !== -1 ) + playing.splice( index, 1 ); + + }; + + + //--- add --- + + that.add = function( data ) { + + if ( library[ data.name ] !== undefined ) + console.log( "THREE.AnimationHandler.add: Warning! " + data.name + " already exists in library. Overwriting." ); + + library[ data.name ] = data; + initData( data ); + + }; + + + //--- get --- + + that.get = function( name ) { + + if ( typeof name === "string" ) { + + if ( library[ name ] ) { + + return library[ name ]; + + } else { + + console.log( "THREE.AnimationHandler.get: Couldn't find animation " + name ); + return null; + + } + + } else { + + // todo: add simple tween library + + } + + }; + + //--- parse --- + + that.parse = function( root ) { + + // setup hierarchy + + var hierarchy = []; + + if ( root instanceof THREE.SkinnedMesh ) { + + for( var b = 0; b < root.bones.length; b++ ) { + + hierarchy.push( root.bones[ b ] ); + + } + + } else { + + parseRecurseHierarchy( root, hierarchy ); + + } + + return hierarchy; + + }; + + var parseRecurseHierarchy = function( root, hierarchy ) { + + hierarchy.push( root ); + + for( var c = 0; c < root.children.length; c++ ) + parseRecurseHierarchy( root.children[ c ], hierarchy ); + + } + + + //--- init data --- + + var initData = function( data ) { + + if( data.initialized === true ) + return; + + + // loop through all keys + + for( var h = 0; h < data.hierarchy.length; h ++ ) { + + for( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) { + + // remove minus times + + if( data.hierarchy[ h ].keys[ k ].time < 0 ) + data.hierarchy[ h ].keys[ k ].time = 0; + + + // create quaternions + + if( data.hierarchy[ h ].keys[ k ].rot !== undefined && + !( data.hierarchy[ h ].keys[ k ].rot instanceof THREE.Quaternion ) ) { + + var quat = data.hierarchy[ h ].keys[ k ].rot; + data.hierarchy[ h ].keys[ k ].rot = new THREE.Quaternion( quat[0], quat[1], quat[2], quat[3] ); + + } + + } + + + // prepare morph target keys + + if( data.hierarchy[ h ].keys.length && data.hierarchy[ h ].keys[ 0 ].morphTargets !== undefined ) { + + // get all used + + var usedMorphTargets = {}; + + for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) { + + for ( var m = 0; m < data.hierarchy[ h ].keys[ k ].morphTargets.length; m ++ ) { + + var morphTargetName = data.hierarchy[ h ].keys[ k ].morphTargets[ m ]; + usedMorphTargets[ morphTargetName ] = -1; + + } + + } + + data.hierarchy[ h ].usedMorphTargets = usedMorphTargets; + + + // set all used on all frames + + for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) { + + var influences = {}; + + for ( var morphTargetName in usedMorphTargets ) { + + for ( var m = 0; m < data.hierarchy[ h ].keys[ k ].morphTargets.length; m ++ ) { + + if ( data.hierarchy[ h ].keys[ k ].morphTargets[ m ] === morphTargetName ) { + + influences[ morphTargetName ] = data.hierarchy[ h ].keys[ k ].morphTargetsInfluences[ m ]; + break; + + } + + } + + if ( m === data.hierarchy[ h ].keys[ k ].morphTargets.length ) { + + influences[ morphTargetName ] = 0; + + } + + } + + data.hierarchy[ h ].keys[ k ].morphTargetsInfluences = influences; + + } + + } + + + // remove all keys that are on the same time + + for ( var k = 1; k < data.hierarchy[ h ].keys.length; k ++ ) { + + if ( data.hierarchy[ h ].keys[ k ].time === data.hierarchy[ h ].keys[ k - 1 ].time ) { + + data.hierarchy[ h ].keys.splice( k, 1 ); + k --; + + } + + } + + + // set index + + for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) { + + data.hierarchy[ h ].keys[ k ].index = k; + + } + + } + + // done + + data.initialized = true; + + }; + + + // interpolation types + + that.LINEAR = 0; + that.CATMULLROM = 1; + that.CATMULLROM_FORWARD = 2; + + return that; + +}()); + +/** + * @author mikael emtinger / http://gomo.se/ + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Animation = function ( root, name ) { + + this.root = root; + this.data = THREE.AnimationHandler.get( name ); + this.hierarchy = THREE.AnimationHandler.parse( root ); + + this.currentTime = 0; + this.timeScale = 1; + + this.isPlaying = false; + this.isPaused = true; + this.loop = true; + + this.interpolationType = THREE.AnimationHandler.LINEAR; + +}; + +THREE.Animation.prototype.play = function ( startTime ) { + + this.currentTime = startTime !== undefined ? startTime : 0; + + if ( this.isPlaying === false ) { + + this.isPlaying = true; + + this.reset(); + this.update( 0 ); + + } + + this.isPaused = false; + + THREE.AnimationHandler.addToUpdate( this ); + +}; + + +THREE.Animation.prototype.pause = function() { + + if ( this.isPaused === true ) { + + THREE.AnimationHandler.addToUpdate( this ); + + } else { + + THREE.AnimationHandler.removeFromUpdate( this ); + + } + + this.isPaused = !this.isPaused; + +}; + + +THREE.Animation.prototype.stop = function() { + + this.isPlaying = false; + this.isPaused = false; + THREE.AnimationHandler.removeFromUpdate( this ); + +}; + +THREE.Animation.prototype.reset = function () { + + for ( var h = 0, hl = this.hierarchy.length; h < hl; h ++ ) { + + var object = this.hierarchy[ h ]; + + object.matrixAutoUpdate = true; + + if ( object.animationCache === undefined ) { + + object.animationCache = {}; + object.animationCache.prevKey = { pos: 0, rot: 0, scl: 0 }; + object.animationCache.nextKey = { pos: 0, rot: 0, scl: 0 }; + object.animationCache.originalMatrix = object instanceof THREE.Bone ? object.skinMatrix : object.matrix; + + } + + var prevKey = object.animationCache.prevKey; + var nextKey = object.animationCache.nextKey; + + prevKey.pos = this.data.hierarchy[ h ].keys[ 0 ]; + prevKey.rot = this.data.hierarchy[ h ].keys[ 0 ]; + prevKey.scl = this.data.hierarchy[ h ].keys[ 0 ]; + + nextKey.pos = this.getNextKeyWith( "pos", h, 1 ); + nextKey.rot = this.getNextKeyWith( "rot", h, 1 ); + nextKey.scl = this.getNextKeyWith( "scl", h, 1 ); + + } + +}; + + +THREE.Animation.prototype.update = (function(){ + + var points = []; + var target = new THREE.Vector3(); + + // Catmull-Rom spline + + var interpolateCatmullRom = function ( points, scale ) { + + var c = [], v3 = [], + point, intPoint, weight, w2, w3, + pa, pb, pc, pd; + + point = ( points.length - 1 ) * scale; + intPoint = Math.floor( point ); + weight = point - intPoint; + + c[ 0 ] = intPoint === 0 ? intPoint : intPoint - 1; + c[ 1 ] = intPoint; + c[ 2 ] = intPoint > points.length - 2 ? intPoint : intPoint + 1; + c[ 3 ] = intPoint > points.length - 3 ? intPoint : intPoint + 2; + + pa = points[ c[ 0 ] ]; + pb = points[ c[ 1 ] ]; + pc = points[ c[ 2 ] ]; + pd = points[ c[ 3 ] ]; + + w2 = weight * weight; + w3 = weight * w2; + + v3[ 0 ] = interpolate( pa[ 0 ], pb[ 0 ], pc[ 0 ], pd[ 0 ], weight, w2, w3 ); + v3[ 1 ] = interpolate( pa[ 1 ], pb[ 1 ], pc[ 1 ], pd[ 1 ], weight, w2, w3 ); + v3[ 2 ] = interpolate( pa[ 2 ], pb[ 2 ], pc[ 2 ], pd[ 2 ], weight, w2, w3 ); + + return v3; + + }; + + var interpolate = function ( p0, p1, p2, p3, t, t2, t3 ) { + + var v0 = ( p2 - p0 ) * 0.5, + v1 = ( p3 - p1 ) * 0.5; + + return ( 2 * ( p1 - p2 ) + v0 + v1 ) * t3 + ( - 3 * ( p1 - p2 ) - 2 * v0 - v1 ) * t2 + v0 * t + p1; + + }; + + return function ( delta ) { + if ( this.isPlaying === false ) return; + + this.currentTime += delta * this.timeScale; + + // + + var vector; + var types = [ "pos", "rot", "scl" ]; + + var duration = this.data.length; + + if ( this.loop === true && this.currentTime > duration ) { + + this.currentTime %= duration; + this.reset(); + + } else if ( this.loop === false && this.currentTime > duration ) { + + this.stop(); + return; + + } + + this.currentTime = Math.min( this.currentTime, duration ); + + for ( var h = 0, hl = this.hierarchy.length; h < hl; h ++ ) { + + var object = this.hierarchy[ h ]; + var animationCache = object.animationCache; + + // loop through pos/rot/scl + + for ( var t = 0; t < 3; t ++ ) { + + // get keys + + var type = types[ t ]; + var prevKey = animationCache.prevKey[ type ]; + var nextKey = animationCache.nextKey[ type ]; + + if ( nextKey.time <= this.currentTime ) { + + prevKey = this.data.hierarchy[ h ].keys[ 0 ]; + nextKey = this.getNextKeyWith( type, h, 1 ); + + while ( nextKey.time < this.currentTime && nextKey.index > prevKey.index ) { + + prevKey = nextKey; + nextKey = this.getNextKeyWith( type, h, nextKey.index + 1 ); + + } + + animationCache.prevKey[ type ] = prevKey; + animationCache.nextKey[ type ] = nextKey; + + } + + object.matrixAutoUpdate = true; + object.matrixWorldNeedsUpdate = true; + + var scale = ( this.currentTime - prevKey.time ) / ( nextKey.time - prevKey.time ); + + var prevXYZ = prevKey[ type ]; + var nextXYZ = nextKey[ type ]; + + if ( scale < 0 ) scale = 0; + if ( scale > 1 ) scale = 1; + + // interpolate + + if ( type === "pos" ) { + + vector = object.position; + + if ( this.interpolationType === THREE.AnimationHandler.LINEAR ) { + + vector.x = prevXYZ[ 0 ] + ( nextXYZ[ 0 ] - prevXYZ[ 0 ] ) * scale; + vector.y = prevXYZ[ 1 ] + ( nextXYZ[ 1 ] - prevXYZ[ 1 ] ) * scale; + vector.z = prevXYZ[ 2 ] + ( nextXYZ[ 2 ] - prevXYZ[ 2 ] ) * scale; + + } else if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM || + this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) { + + points[ 0 ] = this.getPrevKeyWith( "pos", h, prevKey.index - 1 )[ "pos" ]; + points[ 1 ] = prevXYZ; + points[ 2 ] = nextXYZ; + points[ 3 ] = this.getNextKeyWith( "pos", h, nextKey.index + 1 )[ "pos" ]; + + scale = scale * 0.33 + 0.33; + + var currentPoint = interpolateCatmullRom( points, scale ); + + vector.x = currentPoint[ 0 ]; + vector.y = currentPoint[ 1 ]; + vector.z = currentPoint[ 2 ]; + + if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) { + + var forwardPoint = interpolateCatmullRom( points, scale * 1.01 ); + + target.set( forwardPoint[ 0 ], forwardPoint[ 1 ], forwardPoint[ 2 ] ); + target.sub( vector ); + target.y = 0; + target.normalize(); + + var angle = Math.atan2( target.x, target.z ); + object.rotation.set( 0, angle, 0 ); + + } + + } + + } else if ( type === "rot" ) { + + THREE.Quaternion.slerp( prevXYZ, nextXYZ, object.quaternion, scale ); + + } else if ( type === "scl" ) { + + vector = object.scale; + + vector.x = prevXYZ[ 0 ] + ( nextXYZ[ 0 ] - prevXYZ[ 0 ] ) * scale; + vector.y = prevXYZ[ 1 ] + ( nextXYZ[ 1 ] - prevXYZ[ 1 ] ) * scale; + vector.z = prevXYZ[ 2 ] + ( nextXYZ[ 2 ] - prevXYZ[ 2 ] ) * scale; + + } + + } + + } + + }; + +})(); + + + + + +// Get next key with + +THREE.Animation.prototype.getNextKeyWith = function ( type, h, key ) { + + var keys = this.data.hierarchy[ h ].keys; + + if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM || + this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) { + + key = key < keys.length - 1 ? key : keys.length - 1; + + } else { + + key = key % keys.length; + + } + + for ( ; key < keys.length; key++ ) { + + if ( keys[ key ][ type ] !== undefined ) { + + return keys[ key ]; + + } + + } + + return this.data.hierarchy[ h ].keys[ 0 ]; + +}; + +// Get previous key with + +THREE.Animation.prototype.getPrevKeyWith = function ( type, h, key ) { + + var keys = this.data.hierarchy[ h ].keys; + + if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM || + this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) { + + key = key > 0 ? key : 0; + + } else { + + key = key >= 0 ? key : key + keys.length; + + } + + + for ( ; key >= 0; key -- ) { + + if ( keys[ key ][ type ] !== undefined ) { + + return keys[ key ]; + + } + + } + + return this.data.hierarchy[ h ].keys[ keys.length - 1 ]; + +}; + +/** + * @author mikael emtinger / http://gomo.se/ + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * @author khang duong + * @author erik kitson + */ + +THREE.KeyFrameAnimation = function ( root, data ) { + + this.root = root; + this.data = THREE.AnimationHandler.get( data ); + this.hierarchy = THREE.AnimationHandler.parse( root ); + this.currentTime = 0; + this.timeScale = 0.001; + this.isPlaying = false; + this.isPaused = true; + this.loop = true; + + // initialize to first keyframes + + for ( var h = 0, hl = this.hierarchy.length; h < hl; h ++ ) { + + var keys = this.data.hierarchy[h].keys, + sids = this.data.hierarchy[h].sids, + obj = this.hierarchy[h]; + + if ( keys.length && sids ) { + + for ( var s = 0; s < sids.length; s++ ) { + + var sid = sids[ s ], + next = this.getNextKeyWith( sid, h, 0 ); + + if ( next ) { + + next.apply( sid ); + + } + + } + + obj.matrixAutoUpdate = false; + this.data.hierarchy[h].node.updateMatrix(); + obj.matrixWorldNeedsUpdate = true; + + } + + } + +}; + +// Play + +THREE.KeyFrameAnimation.prototype.play = function ( startTime ) { + + this.currentTime = startTime !== undefined ? startTime : 0; + + if ( this.isPlaying === false ) { + + this.isPlaying = true; + + // reset key cache + + var h, hl = this.hierarchy.length, + object, + node; + + for ( h = 0; h < hl; h++ ) { + + object = this.hierarchy[ h ]; + node = this.data.hierarchy[ h ]; + + if ( node.animationCache === undefined ) { + + node.animationCache = {}; + node.animationCache.prevKey = null; + node.animationCache.nextKey = null; + node.animationCache.originalMatrix = object instanceof THREE.Bone ? object.skinMatrix : object.matrix; + + } + + var keys = this.data.hierarchy[h].keys; + + if (keys.length) { + + node.animationCache.prevKey = keys[ 0 ]; + node.animationCache.nextKey = keys[ 1 ]; + + this.startTime = Math.min( keys[0].time, this.startTime ); + this.endTime = Math.max( keys[keys.length - 1].time, this.endTime ); + + } + + } + + this.update( 0 ); + + } + + this.isPaused = false; + + THREE.AnimationHandler.addToUpdate( this ); + +}; + + + +// Pause + +THREE.KeyFrameAnimation.prototype.pause = function() { + + if( this.isPaused ) { + + THREE.AnimationHandler.addToUpdate( this ); + + } else { + + THREE.AnimationHandler.removeFromUpdate( this ); + + } + + this.isPaused = !this.isPaused; + +}; + + +// Stop + +THREE.KeyFrameAnimation.prototype.stop = function() { + + this.isPlaying = false; + this.isPaused = false; + + THREE.AnimationHandler.removeFromUpdate( this ); + + // reset JIT matrix and remove cache + + for ( var h = 0; h < this.data.hierarchy.length; h++ ) { + + var obj = this.hierarchy[ h ]; + var node = this.data.hierarchy[ h ]; + + if ( node.animationCache !== undefined ) { + + var original = node.animationCache.originalMatrix; + + if( obj instanceof THREE.Bone ) { + + original.copy( obj.skinMatrix ); + obj.skinMatrix = original; + + } else { + + original.copy( obj.matrix ); + obj.matrix = original; + + } + + delete node.animationCache; + + } + + } + +}; + + +// Update + +THREE.KeyFrameAnimation.prototype.update = function ( delta ) { + + if ( this.isPlaying === false ) return; + + this.currentTime += delta * this.timeScale; + + // + + var duration = this.data.length; + + if ( this.loop === true && this.currentTime > duration ) { + + this.currentTime %= duration; + + } + + this.currentTime = Math.min( this.currentTime, duration ); + + for ( var h = 0, hl = this.hierarchy.length; h < hl; h++ ) { + + var object = this.hierarchy[ h ]; + var node = this.data.hierarchy[ h ]; + + var keys = node.keys, + animationCache = node.animationCache; + + + if ( keys.length ) { + + var prevKey = animationCache.prevKey; + var nextKey = animationCache.nextKey; + + if ( nextKey.time <= this.currentTime ) { + + while ( nextKey.time < this.currentTime && nextKey.index > prevKey.index ) { + + prevKey = nextKey; + nextKey = keys[ prevKey.index + 1 ]; + + } + + animationCache.prevKey = prevKey; + animationCache.nextKey = nextKey; + + } + + if ( nextKey.time >= this.currentTime ) { + + prevKey.interpolate( nextKey, this.currentTime ); + + } else { + + prevKey.interpolate( nextKey, nextKey.time ); + + } + + this.data.hierarchy[ h ].node.updateMatrix(); + object.matrixWorldNeedsUpdate = true; + + } + + } + +}; + +// Get next key with + +THREE.KeyFrameAnimation.prototype.getNextKeyWith = function( sid, h, key ) { + + var keys = this.data.hierarchy[ h ].keys; + key = key % keys.length; + + for ( ; key < keys.length; key++ ) { + + if ( keys[ key ].hasTarget( sid ) ) { + + return keys[ key ]; + + } + + } + + return keys[ 0 ]; + +}; + +// Get previous key with + +THREE.KeyFrameAnimation.prototype.getPrevKeyWith = function( sid, h, key ) { + + var keys = this.data.hierarchy[ h ].keys; + key = key >= 0 ? key : key + keys.length; + + for ( ; key >= 0; key-- ) { + + if ( keys[ key ].hasTarget( sid ) ) { + + return keys[ key ]; + + } + + } + + return keys[ keys.length - 1 ]; + +}; + +/** + * @author mrdoob / http://mrdoob.com + */ + +THREE.MorphAnimation = function ( mesh ) { + + this.mesh = mesh; + this.frames = mesh.morphTargetInfluences.length; + this.currentTime = 0; + this.duration = 1000; + this.loop = true; + + this.isPlaying = false; + +}; + +THREE.MorphAnimation.prototype = { + + play: function () { + + this.isPlaying = true; + + }, + + pause: function () { + + this.isPlaying = false; + }, + + update: ( function () { + + var lastFrame = 0; + var currentFrame = 0; + + return function ( delta ) { + + if ( this.isPlaying === false ) return; + + this.currentTime += delta; + + if ( this.loop === true && this.currentTime > this.duration ) { + + this.currentTime %= this.duration; + + } + + this.currentTime = Math.min( this.currentTime, this.duration ); + + var interpolation = this.duration / this.frames; + var frame = Math.floor( this.currentTime / interpolation ); + + if ( frame != currentFrame ) { + + this.mesh.morphTargetInfluences[ lastFrame ] = 0; + this.mesh.morphTargetInfluences[ currentFrame ] = 1; + this.mesh.morphTargetInfluences[ frame ] = 0; + + lastFrame = currentFrame; + currentFrame = frame; + + } + + this.mesh.morphTargetInfluences[ frame ] = ( this.currentTime % interpolation ) / interpolation; + this.mesh.morphTargetInfluences[ lastFrame ] = 1 - this.mesh.morphTargetInfluences[ frame ]; + + } + + } )() + +}; + +/** + * Camera for rendering cube maps + * - renders scene into axis-aligned cube + * + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.CubeCamera = function ( near, far, cubeResolution ) { + + THREE.Object3D.call( this ); + + var fov = 90, aspect = 1; + + var cameraPX = new THREE.PerspectiveCamera( fov, aspect, near, far ); + cameraPX.up.set( 0, -1, 0 ); + cameraPX.lookAt( new THREE.Vector3( 1, 0, 0 ) ); + this.add( cameraPX ); + + var cameraNX = new THREE.PerspectiveCamera( fov, aspect, near, far ); + cameraNX.up.set( 0, -1, 0 ); + cameraNX.lookAt( new THREE.Vector3( -1, 0, 0 ) ); + this.add( cameraNX ); + + var cameraPY = new THREE.PerspectiveCamera( fov, aspect, near, far ); + cameraPY.up.set( 0, 0, 1 ); + cameraPY.lookAt( new THREE.Vector3( 0, 1, 0 ) ); + this.add( cameraPY ); + + var cameraNY = new THREE.PerspectiveCamera( fov, aspect, near, far ); + cameraNY.up.set( 0, 0, -1 ); + cameraNY.lookAt( new THREE.Vector3( 0, -1, 0 ) ); + this.add( cameraNY ); + + var cameraPZ = new THREE.PerspectiveCamera( fov, aspect, near, far ); + cameraPZ.up.set( 0, -1, 0 ); + cameraPZ.lookAt( new THREE.Vector3( 0, 0, 1 ) ); + this.add( cameraPZ ); + + var cameraNZ = new THREE.PerspectiveCamera( fov, aspect, near, far ); + cameraNZ.up.set( 0, -1, 0 ); + cameraNZ.lookAt( new THREE.Vector3( 0, 0, -1 ) ); + this.add( cameraNZ ); + + this.renderTarget = new THREE.WebGLRenderTargetCube( cubeResolution, cubeResolution, { format: THREE.RGBFormat, magFilter: THREE.LinearFilter, minFilter: THREE.LinearFilter } ); + + this.updateCubeMap = function ( renderer, scene ) { + + var renderTarget = this.renderTarget; + var generateMipmaps = renderTarget.generateMipmaps; + + renderTarget.generateMipmaps = false; + + renderTarget.activeCubeFace = 0; + renderer.render( scene, cameraPX, renderTarget ); + + renderTarget.activeCubeFace = 1; + renderer.render( scene, cameraNX, renderTarget ); + + renderTarget.activeCubeFace = 2; + renderer.render( scene, cameraPY, renderTarget ); + + renderTarget.activeCubeFace = 3; + renderer.render( scene, cameraNY, renderTarget ); + + renderTarget.activeCubeFace = 4; + renderer.render( scene, cameraPZ, renderTarget ); + + renderTarget.generateMipmaps = generateMipmaps; + + renderTarget.activeCubeFace = 5; + renderer.render( scene, cameraNZ, renderTarget ); + + }; + +}; + +THREE.CubeCamera.prototype = Object.create( THREE.Object3D.prototype ); + +/** + * @author zz85 / http://twitter.com/blurspline / http://www.lab4games.net/zz85/blog + * + * A general perpose camera, for setting FOV, Lens Focal Length, + * and switching between perspective and orthographic views easily. + * Use this only if you do not wish to manage + * both a Orthographic and Perspective Camera + * + */ + + +THREE.CombinedCamera = function ( width, height, fov, near, far, orthoNear, orthoFar ) { + + THREE.Camera.call( this ); + + this.fov = fov; + + this.left = -width / 2; + this.right = width / 2 + this.top = height / 2; + this.bottom = -height / 2; + + // We could also handle the projectionMatrix internally, but just wanted to test nested camera objects + + this.cameraO = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, orthoNear, orthoFar ); + this.cameraP = new THREE.PerspectiveCamera( fov, width / height, near, far ); + + this.zoom = 1; + + this.toPerspective(); + + var aspect = width/height; + +}; + +THREE.CombinedCamera.prototype = Object.create( THREE.Camera.prototype ); + +THREE.CombinedCamera.prototype.toPerspective = function () { + + // Switches to the Perspective Camera + + this.near = this.cameraP.near; + this.far = this.cameraP.far; + + this.cameraP.fov = this.fov / this.zoom ; + + this.cameraP.updateProjectionMatrix(); + + this.projectionMatrix = this.cameraP.projectionMatrix; + + this.inPerspectiveMode = true; + this.inOrthographicMode = false; + +}; + +THREE.CombinedCamera.prototype.toOrthographic = function () { + + // Switches to the Orthographic camera estimating viewport from Perspective + + var fov = this.fov; + var aspect = this.cameraP.aspect; + var near = this.cameraP.near; + var far = this.cameraP.far; + + // The size that we set is the mid plane of the viewing frustum + + var hyperfocus = ( near + far ) / 2; + + var halfHeight = Math.tan( fov / 2 ) * hyperfocus; + var planeHeight = 2 * halfHeight; + var planeWidth = planeHeight * aspect; + var halfWidth = planeWidth / 2; + + halfHeight /= this.zoom; + halfWidth /= this.zoom; + + this.cameraO.left = -halfWidth; + this.cameraO.right = halfWidth; + this.cameraO.top = halfHeight; + this.cameraO.bottom = -halfHeight; + + // this.cameraO.left = -farHalfWidth; + // this.cameraO.right = farHalfWidth; + // this.cameraO.top = farHalfHeight; + // this.cameraO.bottom = -farHalfHeight; + + // this.cameraO.left = this.left / this.zoom; + // this.cameraO.right = this.right / this.zoom; + // this.cameraO.top = this.top / this.zoom; + // this.cameraO.bottom = this.bottom / this.zoom; + + this.cameraO.updateProjectionMatrix(); + + this.near = this.cameraO.near; + this.far = this.cameraO.far; + this.projectionMatrix = this.cameraO.projectionMatrix; + + this.inPerspectiveMode = false; + this.inOrthographicMode = true; + +}; + + +THREE.CombinedCamera.prototype.setSize = function( width, height ) { + + this.cameraP.aspect = width / height; + this.left = -width / 2; + this.right = width / 2 + this.top = height / 2; + this.bottom = -height / 2; + +}; + + +THREE.CombinedCamera.prototype.setFov = function( fov ) { + + this.fov = fov; + + if ( this.inPerspectiveMode ) { + + this.toPerspective(); + + } else { + + this.toOrthographic(); + + } + +}; + +// For mantaining similar API with PerspectiveCamera + +THREE.CombinedCamera.prototype.updateProjectionMatrix = function() { + + if ( this.inPerspectiveMode ) { + + this.toPerspective(); + + } else { + + this.toPerspective(); + this.toOrthographic(); + + } + +}; + +/* +* Uses Focal Length (in mm) to estimate and set FOV +* 35mm (fullframe) camera is used if frame size is not specified; +* Formula based on http://www.bobatkins.com/photography/technical/field_of_view.html +*/ +THREE.CombinedCamera.prototype.setLens = function ( focalLength, frameHeight ) { + + if ( frameHeight === undefined ) frameHeight = 24; + + var fov = 2 * THREE.Math.radToDeg( Math.atan( frameHeight / ( focalLength * 2 ) ) ); + + this.setFov( fov ); + + return fov; +}; + + +THREE.CombinedCamera.prototype.setZoom = function( zoom ) { + + this.zoom = zoom; + + if ( this.inPerspectiveMode ) { + + this.toPerspective(); + + } else { + + this.toOrthographic(); + + } + +}; + +THREE.CombinedCamera.prototype.toFrontView = function() { + + this.rotation.x = 0; + this.rotation.y = 0; + this.rotation.z = 0; + + // should we be modifing the matrix instead? + + this.rotationAutoUpdate = false; + +}; + +THREE.CombinedCamera.prototype.toBackView = function() { + + this.rotation.x = 0; + this.rotation.y = Math.PI; + this.rotation.z = 0; + this.rotationAutoUpdate = false; + +}; + +THREE.CombinedCamera.prototype.toLeftView = function() { + + this.rotation.x = 0; + this.rotation.y = - Math.PI / 2; + this.rotation.z = 0; + this.rotationAutoUpdate = false; + +}; + +THREE.CombinedCamera.prototype.toRightView = function() { + + this.rotation.x = 0; + this.rotation.y = Math.PI / 2; + this.rotation.z = 0; + this.rotationAutoUpdate = false; + +}; + +THREE.CombinedCamera.prototype.toTopView = function() { + + this.rotation.x = - Math.PI / 2; + this.rotation.y = 0; + this.rotation.z = 0; + this.rotationAutoUpdate = false; + +}; + +THREE.CombinedCamera.prototype.toBottomView = function() { + + this.rotation.x = Math.PI / 2; + this.rotation.y = 0; + this.rotation.z = 0; + this.rotationAutoUpdate = false; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * based on http://papervision3d.googlecode.com/svn/trunk/as3/trunk/src/org/papervision3d/objects/primitives/Cube.as + */ + +THREE.BoxGeometry = function ( width, height, depth, widthSegments, heightSegments, depthSegments ) { + + THREE.Geometry.call( this ); + + var scope = this; + + this.width = width; + this.height = height; + this.depth = depth; + + this.widthSegments = widthSegments || 1; + this.heightSegments = heightSegments || 1; + this.depthSegments = depthSegments || 1; + + var width_half = this.width / 2; + var height_half = this.height / 2; + var depth_half = this.depth / 2; + + buildPlane( 'z', 'y', - 1, - 1, this.depth, this.height, width_half, 0 ); // px + buildPlane( 'z', 'y', 1, - 1, this.depth, this.height, - width_half, 1 ); // nx + buildPlane( 'x', 'z', 1, 1, this.width, this.depth, height_half, 2 ); // py + buildPlane( 'x', 'z', 1, - 1, this.width, this.depth, - height_half, 3 ); // ny + buildPlane( 'x', 'y', 1, - 1, this.width, this.height, depth_half, 4 ); // pz + buildPlane( 'x', 'y', - 1, - 1, this.width, this.height, - depth_half, 5 ); // nz + + function buildPlane( u, v, udir, vdir, width, height, depth, materialIndex ) { + + var w, ix, iy, + gridX = scope.widthSegments, + gridY = scope.heightSegments, + width_half = width / 2, + height_half = height / 2, + offset = scope.vertices.length; + + if ( ( u === 'x' && v === 'y' ) || ( u === 'y' && v === 'x' ) ) { + + w = 'z'; + + } else if ( ( u === 'x' && v === 'z' ) || ( u === 'z' && v === 'x' ) ) { + + w = 'y'; + gridY = scope.depthSegments; + + } else if ( ( u === 'z' && v === 'y' ) || ( u === 'y' && v === 'z' ) ) { + + w = 'x'; + gridX = scope.depthSegments; + + } + + var gridX1 = gridX + 1, + gridY1 = gridY + 1, + segment_width = width / gridX, + segment_height = height / gridY, + normal = new THREE.Vector3(); + + normal[ w ] = depth > 0 ? 1 : - 1; + + for ( iy = 0; iy < gridY1; iy ++ ) { + + for ( ix = 0; ix < gridX1; ix ++ ) { + + var vector = new THREE.Vector3(); + vector[ u ] = ( ix * segment_width - width_half ) * udir; + vector[ v ] = ( iy * segment_height - height_half ) * vdir; + vector[ w ] = depth; + + scope.vertices.push( vector ); + + } + + } + + for ( iy = 0; iy < gridY; iy++ ) { + + for ( ix = 0; ix < gridX; ix++ ) { + + var a = ix + gridX1 * iy; + var b = ix + gridX1 * ( iy + 1 ); + var c = ( ix + 1 ) + gridX1 * ( iy + 1 ); + var d = ( ix + 1 ) + gridX1 * iy; + + var uva = new THREE.Vector2( ix / gridX, 1 - iy / gridY ); + var uvb = new THREE.Vector2( ix / gridX, 1 - ( iy + 1 ) / gridY ); + var uvc = new THREE.Vector2( ( ix + 1 ) / gridX, 1 - ( iy + 1 ) / gridY ); + var uvd = new THREE.Vector2( ( ix + 1 ) / gridX, 1 - iy / gridY ); + + var face = new THREE.Face3( a + offset, b + offset, d + offset ); + face.normal.copy( normal ); + face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone() ); + face.materialIndex = materialIndex; + + scope.faces.push( face ); + scope.faceVertexUvs[ 0 ].push( [ uva, uvb, uvd ] ); + + face = new THREE.Face3( b + offset, c + offset, d + offset ); + face.normal.copy( normal ); + face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone() ); + face.materialIndex = materialIndex; + + scope.faces.push( face ); + scope.faceVertexUvs[ 0 ].push( [ uvb.clone(), uvc, uvd.clone() ] ); + + } + + } + + } + + this.computeCentroids(); + this.mergeVertices(); + +}; + +THREE.BoxGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @author hughes + */ + +THREE.CircleGeometry = function ( radius, segments, thetaStart, thetaLength ) { + + THREE.Geometry.call( this ); + + this.radius = radius = radius || 50; + this.segments = segments = segments !== undefined ? Math.max( 3, segments ) : 8; + + this.thetaStart = thetaStart = thetaStart !== undefined ? thetaStart : 0; + this.thetaLength = thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2; + + var i, uvs = [], + center = new THREE.Vector3(), centerUV = new THREE.Vector2( 0.5, 0.5 ); + + this.vertices.push(center); + uvs.push( centerUV ); + + for ( i = 0; i <= segments; i ++ ) { + + var vertex = new THREE.Vector3(); + var segment = thetaStart + i / segments * thetaLength; + + vertex.x = radius * Math.cos( segment ); + vertex.y = radius * Math.sin( segment ); + + this.vertices.push( vertex ); + uvs.push( new THREE.Vector2( ( vertex.x / radius + 1 ) / 2, ( vertex.y / radius + 1 ) / 2 ) ); + + } + + var n = new THREE.Vector3( 0, 0, 1 ); + + for ( i = 1; i <= segments; i ++ ) { + + var v1 = i; + var v2 = i + 1 ; + var v3 = 0; + + this.faces.push( new THREE.Face3( v1, v2, v3, [ n.clone(), n.clone(), n.clone() ] ) ); + this.faceVertexUvs[ 0 ].push( [ uvs[ i ].clone(), uvs[ i + 1 ].clone(), centerUV.clone() ] ); + + } + + this.computeCentroids(); + this.computeFaceNormals(); + + this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius ); + +}; + +THREE.CircleGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +// DEPRECATED + +THREE.CubeGeometry = THREE.BoxGeometry; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.CylinderGeometry = function ( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded ) { + + THREE.Geometry.call( this ); + + this.radiusTop = radiusTop = radiusTop !== undefined ? radiusTop : 20; + this.radiusBottom = radiusBottom = radiusBottom !== undefined ? radiusBottom : 20; + this.height = height = height !== undefined ? height : 100; + + this.radialSegments = radialSegments = radialSegments || 8; + this.heightSegments = heightSegments = heightSegments || 1; + + this.openEnded = openEnded = openEnded !== undefined ? openEnded : false; + + var heightHalf = height / 2; + + var x, y, vertices = [], uvs = []; + + for ( y = 0; y <= heightSegments; y ++ ) { + + var verticesRow = []; + var uvsRow = []; + + var v = y / heightSegments; + var radius = v * ( radiusBottom - radiusTop ) + radiusTop; + + for ( x = 0; x <= radialSegments; x ++ ) { + + var u = x / radialSegments; + + var vertex = new THREE.Vector3(); + vertex.x = radius * Math.sin( u * Math.PI * 2 ); + vertex.y = - v * height + heightHalf; + vertex.z = radius * Math.cos( u * Math.PI * 2 ); + + this.vertices.push( vertex ); + + verticesRow.push( this.vertices.length - 1 ); + uvsRow.push( new THREE.Vector2( u, 1 - v ) ); + + } + + vertices.push( verticesRow ); + uvs.push( uvsRow ); + + } + + var tanTheta = ( radiusBottom - radiusTop ) / height; + var na, nb; + + for ( x = 0; x < radialSegments; x ++ ) { + + if ( radiusTop !== 0 ) { + + na = this.vertices[ vertices[ 0 ][ x ] ].clone(); + nb = this.vertices[ vertices[ 0 ][ x + 1 ] ].clone(); + + } else { + + na = this.vertices[ vertices[ 1 ][ x ] ].clone(); + nb = this.vertices[ vertices[ 1 ][ x + 1 ] ].clone(); + + } + + na.setY( Math.sqrt( na.x * na.x + na.z * na.z ) * tanTheta ).normalize(); + nb.setY( Math.sqrt( nb.x * nb.x + nb.z * nb.z ) * tanTheta ).normalize(); + + for ( y = 0; y < heightSegments; y ++ ) { + + var v1 = vertices[ y ][ x ]; + var v2 = vertices[ y + 1 ][ x ]; + var v3 = vertices[ y + 1 ][ x + 1 ]; + var v4 = vertices[ y ][ x + 1 ]; + + var n1 = na.clone(); + var n2 = na.clone(); + var n3 = nb.clone(); + var n4 = nb.clone(); + + var uv1 = uvs[ y ][ x ].clone(); + var uv2 = uvs[ y + 1 ][ x ].clone(); + var uv3 = uvs[ y + 1 ][ x + 1 ].clone(); + var uv4 = uvs[ y ][ x + 1 ].clone(); + + this.faces.push( new THREE.Face3( v1, v2, v4, [ n1, n2, n4 ] ) ); + this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv4 ] ); + + this.faces.push( new THREE.Face3( v2, v3, v4, [ n2.clone(), n3, n4.clone() ] ) ); + this.faceVertexUvs[ 0 ].push( [ uv2.clone(), uv3, uv4.clone() ] ); + + } + + } + + // top cap + + if ( openEnded === false && radiusTop > 0 ) { + + this.vertices.push( new THREE.Vector3( 0, heightHalf, 0 ) ); + + for ( x = 0; x < radialSegments; x ++ ) { + + var v1 = vertices[ 0 ][ x ]; + var v2 = vertices[ 0 ][ x + 1 ]; + var v3 = this.vertices.length - 1; + + var n1 = new THREE.Vector3( 0, 1, 0 ); + var n2 = new THREE.Vector3( 0, 1, 0 ); + var n3 = new THREE.Vector3( 0, 1, 0 ); + + var uv1 = uvs[ 0 ][ x ].clone(); + var uv2 = uvs[ 0 ][ x + 1 ].clone(); + var uv3 = new THREE.Vector2( uv2.x, 0 ); + + this.faces.push( new THREE.Face3( v1, v2, v3, [ n1, n2, n3 ] ) ); + this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv3 ] ); + + } + + } + + // bottom cap + + if ( openEnded === false && radiusBottom > 0 ) { + + this.vertices.push( new THREE.Vector3( 0, - heightHalf, 0 ) ); + + for ( x = 0; x < radialSegments; x ++ ) { + + var v1 = vertices[ y ][ x + 1 ]; + var v2 = vertices[ y ][ x ]; + var v3 = this.vertices.length - 1; + + var n1 = new THREE.Vector3( 0, - 1, 0 ); + var n2 = new THREE.Vector3( 0, - 1, 0 ); + var n3 = new THREE.Vector3( 0, - 1, 0 ); + + var uv1 = uvs[ y ][ x + 1 ].clone(); + var uv2 = uvs[ y ][ x ].clone(); + var uv3 = new THREE.Vector2( uv2.x, 1 ); + + this.faces.push( new THREE.Face3( v1, v2, v3, [ n1, n2, n3 ] ) ); + this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv3 ] ); + + } + + } + + this.computeCentroids(); + this.computeFaceNormals(); + +} + +THREE.CylinderGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @author zz85 / http://www.lab4games.net/zz85/blog + * + * Creates extruded geometry from a path shape. + * + * parameters = { + * + * curveSegments: , // number of points on the curves + * steps: , // number of points for z-side extrusions / used for subdividing segements of extrude spline too + * amount: , // Depth to extrude the shape + * + * bevelEnabled: , // turn on bevel + * bevelThickness: , // how deep into the original shape bevel goes + * bevelSize: , // how far from shape outline is bevel + * bevelSegments: , // number of bevel layers + * + * extrudePath: // 3d spline path to extrude shape along. (creates Frames if .frames aren't defined) + * frames: // containing arrays of tangents, normals, binormals + * + * material: // material index for front and back faces + * extrudeMaterial: // material index for extrusion and beveled faces + * uvGenerator: // object that provides UV generator functions + * + * } + **/ + +THREE.ExtrudeGeometry = function ( shapes, options ) { + + if ( typeof( shapes ) === "undefined" ) { + shapes = []; + return; + } + + THREE.Geometry.call( this ); + + shapes = shapes instanceof Array ? shapes : [ shapes ]; + + this.shapebb = shapes[ shapes.length - 1 ].getBoundingBox(); + + this.addShapeList( shapes, options ); + + this.computeCentroids(); + this.computeFaceNormals(); + + // can't really use automatic vertex normals + // as then front and back sides get smoothed too + // should do separate smoothing just for sides + + //this.computeVertexNormals(); + + //console.log( "took", ( Date.now() - startTime ) ); + +}; + +THREE.ExtrudeGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +THREE.ExtrudeGeometry.prototype.addShapeList = function ( shapes, options ) { + var sl = shapes.length; + + for ( var s = 0; s < sl; s ++ ) { + var shape = shapes[ s ]; + this.addShape( shape, options ); + } +}; + +THREE.ExtrudeGeometry.prototype.addShape = function ( shape, options ) { + + var amount = options.amount !== undefined ? options.amount : 100; + + var bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 6; // 10 + var bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 2; // 8 + var bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3; + + var bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true; // false + + var curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12; + + var steps = options.steps !== undefined ? options.steps : 1; + + var extrudePath = options.extrudePath; + var extrudePts, extrudeByPath = false; + + var material = options.material; + var extrudeMaterial = options.extrudeMaterial; + + // Use default WorldUVGenerator if no UV generators are specified. + var uvgen = options.UVGenerator !== undefined ? options.UVGenerator : THREE.ExtrudeGeometry.WorldUVGenerator; + + var shapebb = this.shapebb; + //shapebb = shape.getBoundingBox(); + + + + var splineTube, binormal, normal, position2; + if ( extrudePath ) { + + extrudePts = extrudePath.getSpacedPoints( steps ); + + extrudeByPath = true; + bevelEnabled = false; // bevels not supported for path extrusion + + // SETUP TNB variables + + // Reuse TNB from TubeGeomtry for now. + // TODO1 - have a .isClosed in spline? + + splineTube = options.frames !== undefined ? options.frames : new THREE.TubeGeometry.FrenetFrames(extrudePath, steps, false); + + // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length); + + binormal = new THREE.Vector3(); + normal = new THREE.Vector3(); + position2 = new THREE.Vector3(); + + } + + // Safeguards if bevels are not enabled + + if ( ! bevelEnabled ) { + + bevelSegments = 0; + bevelThickness = 0; + bevelSize = 0; + + } + + // Variables initalization + + var ahole, h, hl; // looping of holes + var scope = this; + var bevelPoints = []; + + var shapesOffset = this.vertices.length; + + var shapePoints = shape.extractPoints( curveSegments ); + + var vertices = shapePoints.shape; + var holes = shapePoints.holes; + + var reverse = !THREE.Shape.Utils.isClockWise( vertices ) ; + + if ( reverse ) { + + vertices = vertices.reverse(); + + // Maybe we should also check if holes are in the opposite direction, just to be safe ... + + for ( h = 0, hl = holes.length; h < hl; h ++ ) { + + ahole = holes[ h ]; + + if ( THREE.Shape.Utils.isClockWise( ahole ) ) { + + holes[ h ] = ahole.reverse(); + + } + + } + + reverse = false; // If vertices are in order now, we shouldn't need to worry about them again (hopefully)! + + } + + + var faces = THREE.Shape.Utils.triangulateShape ( vertices, holes ); + + /* Vertices */ + + var contour = vertices; // vertices has all points but contour has only points of circumference + + for ( h = 0, hl = holes.length; h < hl; h ++ ) { + + ahole = holes[ h ]; + + vertices = vertices.concat( ahole ); + + } + + + function scalePt2 ( pt, vec, size ) { + + if ( !vec ) console.log( "die" ); + + return vec.clone().multiplyScalar( size ).add( pt ); + + } + + var b, bs, t, z, + vert, vlen = vertices.length, + face, flen = faces.length, + cont, clen = contour.length; + + + // Find directions for point movement + + var RAD_TO_DEGREES = 180 / Math.PI; + + + function getBevelVec( inPt, inPrev, inNext ) { + + var EPSILON = 0.0000000001; + var sign = THREE.Math.sign; + + // computes for inPt the corresponding point inPt' on a new contour + // shiftet by 1 unit (length of normalized vector) to the left + // if we walk along contour clockwise, this new contour is outside the old one + // + // inPt' is the intersection of the two lines parallel to the two + // adjacent edges of inPt at a distance of 1 unit on the left side. + + var v_trans_x, v_trans_y, shrink_by = 1; // resulting translation vector for inPt + + // good reading for geometry algorithms (here: line-line intersection) + // http://geomalgorithms.com/a05-_intersect-1.html + + var v_prev_x = inPt.x - inPrev.x, v_prev_y = inPt.y - inPrev.y; + var v_next_x = inNext.x - inPt.x, v_next_y = inNext.y - inPt.y; + + var v_prev_lensq = ( v_prev_x * v_prev_x + v_prev_y * v_prev_y ); + + // check for colinear edges + var colinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x ); + + if ( Math.abs( colinear0 ) > EPSILON ) { // not colinear + + // length of vectors for normalizing + + var v_prev_len = Math.sqrt( v_prev_lensq ); + var v_next_len = Math.sqrt( v_next_x * v_next_x + v_next_y * v_next_y ); + + // shift adjacent points by unit vectors to the left + + var ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len ); + var ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len ); + + var ptNextShift_x = ( inNext.x - v_next_y / v_next_len ); + var ptNextShift_y = ( inNext.y + v_next_x / v_next_len ); + + // scaling factor for v_prev to intersection point + + var sf = ( ( ptNextShift_x - ptPrevShift_x ) * v_next_y - + ( ptNextShift_y - ptPrevShift_y ) * v_next_x ) / + ( v_prev_x * v_next_y - v_prev_y * v_next_x ); + + // vector from inPt to intersection point + + v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x ); + v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y ); + + // Don't normalize!, otherwise sharp corners become ugly + // but prevent crazy spikes + var v_trans_lensq = ( v_trans_x * v_trans_x + v_trans_y * v_trans_y ) + if ( v_trans_lensq <= 2 ) { + return new THREE.Vector2( v_trans_x, v_trans_y ); + } else { + shrink_by = Math.sqrt( v_trans_lensq / 2 ); + } + + } else { // handle special case of colinear edges + + var direction_eq = false; // assumes: opposite + if ( v_prev_x > EPSILON ) { + if ( v_next_x > EPSILON ) { direction_eq = true; } + } else { + if ( v_prev_x < -EPSILON ) { + if ( v_next_x < -EPSILON ) { direction_eq = true; } + } else { + if ( sign(v_prev_y) == sign(v_next_y) ) { direction_eq = true; } + } + } + + if ( direction_eq ) { + // console.log("Warning: lines are a straight sequence"); + v_trans_x = -v_prev_y; + v_trans_y = v_prev_x; + shrink_by = Math.sqrt( v_prev_lensq ); + } else { + // console.log("Warning: lines are a straight spike"); + v_trans_x = v_prev_x; + v_trans_y = v_prev_y; + shrink_by = Math.sqrt( v_prev_lensq / 2 ); + } + + } + + return new THREE.Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by ); + + } + + + var contourMovements = []; + + for ( var i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { + + if ( j === il ) j = 0; + if ( k === il ) k = 0; + + // (j)---(i)---(k) + // console.log('i,j,k', i, j , k) + + var pt_i = contour[ i ]; + var pt_j = contour[ j ]; + var pt_k = contour[ k ]; + + contourMovements[ i ]= getBevelVec( contour[ i ], contour[ j ], contour[ k ] ); + + } + + var holesMovements = [], oneHoleMovements, verticesMovements = contourMovements.concat(); + + for ( h = 0, hl = holes.length; h < hl; h ++ ) { + + ahole = holes[ h ]; + + oneHoleMovements = []; + + for ( i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { + + if ( j === il ) j = 0; + if ( k === il ) k = 0; + + // (j)---(i)---(k) + oneHoleMovements[ i ]= getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] ); + + } + + holesMovements.push( oneHoleMovements ); + verticesMovements = verticesMovements.concat( oneHoleMovements ); + + } + + + // Loop bevelSegments, 1 for the front, 1 for the back + + for ( b = 0; b < bevelSegments; b ++ ) { + //for ( b = bevelSegments; b > 0; b -- ) { + + t = b / bevelSegments; + z = bevelThickness * ( 1 - t ); + + //z = bevelThickness * t; + bs = bevelSize * ( Math.sin ( t * Math.PI/2 ) ) ; // curved + //bs = bevelSize * t ; // linear + + // contract shape + + for ( i = 0, il = contour.length; i < il; i ++ ) { + + vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); + //vert = scalePt( contour[ i ], contourCentroid, bs, false ); + v( vert.x, vert.y, - z ); + + } + + // expand holes + + for ( h = 0, hl = holes.length; h < hl; h++ ) { + + ahole = holes[ h ]; + oneHoleMovements = holesMovements[ h ]; + + for ( i = 0, il = ahole.length; i < il; i++ ) { + + vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); + //vert = scalePt( ahole[ i ], holesCentroids[ h ], bs, true ); + + v( vert.x, vert.y, -z ); + + } + + } + + } + + bs = bevelSize; + + // Back facing vertices + + for ( i = 0; i < vlen; i ++ ) { + + vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; + + if ( !extrudeByPath ) { + + v( vert.x, vert.y, 0 ); + + } else { + + // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x ); + + normal.copy( splineTube.normals[0] ).multiplyScalar(vert.x); + binormal.copy( splineTube.binormals[0] ).multiplyScalar(vert.y); + + position2.copy( extrudePts[0] ).add(normal).add(binormal); + + v( position2.x, position2.y, position2.z ); + + } + + } + + // Add stepped vertices... + // Including front facing vertices + + var s; + + for ( s = 1; s <= steps; s ++ ) { + + for ( i = 0; i < vlen; i ++ ) { + + vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; + + if ( !extrudeByPath ) { + + v( vert.x, vert.y, amount / steps * s ); + + } else { + + // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x ); + + normal.copy( splineTube.normals[s] ).multiplyScalar( vert.x ); + binormal.copy( splineTube.binormals[s] ).multiplyScalar( vert.y ); + + position2.copy( extrudePts[s] ).add( normal ).add( binormal ); + + v( position2.x, position2.y, position2.z ); + + } + + } + + } + + + // Add bevel segments planes + + //for ( b = 1; b <= bevelSegments; b ++ ) { + for ( b = bevelSegments - 1; b >= 0; b -- ) { + + t = b / bevelSegments; + z = bevelThickness * ( 1 - t ); + //bs = bevelSize * ( 1-Math.sin ( ( 1 - t ) * Math.PI/2 ) ); + bs = bevelSize * Math.sin ( t * Math.PI/2 ) ; + + // contract shape + + for ( i = 0, il = contour.length; i < il; i ++ ) { + + vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); + v( vert.x, vert.y, amount + z ); + + } + + // expand holes + + for ( h = 0, hl = holes.length; h < hl; h ++ ) { + + ahole = holes[ h ]; + oneHoleMovements = holesMovements[ h ]; + + for ( i = 0, il = ahole.length; i < il; i ++ ) { + + vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); + + if ( !extrudeByPath ) { + + v( vert.x, vert.y, amount + z ); + + } else { + + v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z ); + + } + + } + + } + + } + + /* Faces */ + + // Top and bottom faces + + buildLidFaces(); + + // Sides faces + + buildSideFaces(); + + + ///// Internal functions + + function buildLidFaces() { + + if ( bevelEnabled ) { + + var layer = 0 ; // steps + 1 + var offset = vlen * layer; + + // Bottom faces + + for ( i = 0; i < flen; i ++ ) { + + face = faces[ i ]; + f3( face[ 2 ]+ offset, face[ 1 ]+ offset, face[ 0 ] + offset, true ); + + } + + layer = steps + bevelSegments * 2; + offset = vlen * layer; + + // Top faces + + for ( i = 0; i < flen; i ++ ) { + + face = faces[ i ]; + f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset, false ); + + } + + } else { + + // Bottom faces + + for ( i = 0; i < flen; i++ ) { + + face = faces[ i ]; + f3( face[ 2 ], face[ 1 ], face[ 0 ], true ); + + } + + // Top faces + + for ( i = 0; i < flen; i ++ ) { + + face = faces[ i ]; + f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps, false ); + + } + } + + } + + // Create faces for the z-sides of the shape + + function buildSideFaces() { + + var layeroffset = 0; + sidewalls( contour, layeroffset ); + layeroffset += contour.length; + + for ( h = 0, hl = holes.length; h < hl; h ++ ) { + + ahole = holes[ h ]; + sidewalls( ahole, layeroffset ); + + //, true + layeroffset += ahole.length; + + } + + } + + function sidewalls( contour, layeroffset ) { + + var j, k; + i = contour.length; + + while ( --i >= 0 ) { + + j = i; + k = i - 1; + if ( k < 0 ) k = contour.length - 1; + + //console.log('b', i,j, i-1, k,vertices.length); + + var s = 0, sl = steps + bevelSegments * 2; + + for ( s = 0; s < sl; s ++ ) { + + var slen1 = vlen * s; + var slen2 = vlen * ( s + 1 ); + + var a = layeroffset + j + slen1, + b = layeroffset + k + slen1, + c = layeroffset + k + slen2, + d = layeroffset + j + slen2; + + f4( a, b, c, d, contour, s, sl, j, k ); + + } + } + + } + + + function v( x, y, z ) { + + scope.vertices.push( new THREE.Vector3( x, y, z ) ); + + } + + function f3( a, b, c, isBottom ) { + + a += shapesOffset; + b += shapesOffset; + c += shapesOffset; + + // normal, color, material + scope.faces.push( new THREE.Face3( a, b, c, null, null, material ) ); + + var uvs = isBottom ? uvgen.generateBottomUV( scope, shape, options, a, b, c ) : uvgen.generateTopUV( scope, shape, options, a, b, c ); + + scope.faceVertexUvs[ 0 ].push( uvs ); + + } + + function f4( a, b, c, d, wallContour, stepIndex, stepsLength, contourIndex1, contourIndex2 ) { + + a += shapesOffset; + b += shapesOffset; + c += shapesOffset; + d += shapesOffset; + + scope.faces.push( new THREE.Face3( a, b, d, null, null, extrudeMaterial ) ); + scope.faces.push( new THREE.Face3( b, c, d, null, null, extrudeMaterial ) ); + + var uvs = uvgen.generateSideWallUV( scope, shape, wallContour, options, a, b, c, d, + stepIndex, stepsLength, contourIndex1, contourIndex2 ); + + scope.faceVertexUvs[ 0 ].push( [ uvs[ 0 ], uvs[ 1 ], uvs[ 3 ] ] ); + scope.faceVertexUvs[ 0 ].push( [ uvs[ 1 ], uvs[ 2 ], uvs[ 3 ] ] ); + + } + +}; + +THREE.ExtrudeGeometry.WorldUVGenerator = { + + generateTopUV: function( geometry, extrudedShape, extrudeOptions, indexA, indexB, indexC ) { + var ax = geometry.vertices[ indexA ].x, + ay = geometry.vertices[ indexA ].y, + + bx = geometry.vertices[ indexB ].x, + by = geometry.vertices[ indexB ].y, + + cx = geometry.vertices[ indexC ].x, + cy = geometry.vertices[ indexC ].y; + + return [ + new THREE.Vector2( ax, ay ), + new THREE.Vector2( bx, by ), + new THREE.Vector2( cx, cy ) + ]; + + }, + + generateBottomUV: function( geometry, extrudedShape, extrudeOptions, indexA, indexB, indexC ) { + + return this.generateTopUV( geometry, extrudedShape, extrudeOptions, indexA, indexB, indexC ); + + }, + + generateSideWallUV: function( geometry, extrudedShape, wallContour, extrudeOptions, + indexA, indexB, indexC, indexD, stepIndex, stepsLength, + contourIndex1, contourIndex2 ) { + + var ax = geometry.vertices[ indexA ].x, + ay = geometry.vertices[ indexA ].y, + az = geometry.vertices[ indexA ].z, + + bx = geometry.vertices[ indexB ].x, + by = geometry.vertices[ indexB ].y, + bz = geometry.vertices[ indexB ].z, + + cx = geometry.vertices[ indexC ].x, + cy = geometry.vertices[ indexC ].y, + cz = geometry.vertices[ indexC ].z, + + dx = geometry.vertices[ indexD ].x, + dy = geometry.vertices[ indexD ].y, + dz = geometry.vertices[ indexD ].z; + + if ( Math.abs( ay - by ) < 0.01 ) { + return [ + new THREE.Vector2( ax, 1 - az ), + new THREE.Vector2( bx, 1 - bz ), + new THREE.Vector2( cx, 1 - cz ), + new THREE.Vector2( dx, 1 - dz ) + ]; + } else { + return [ + new THREE.Vector2( ay, 1 - az ), + new THREE.Vector2( by, 1 - bz ), + new THREE.Vector2( cy, 1 - cz ), + new THREE.Vector2( dy, 1 - dz ) + ]; + } + } +}; + +THREE.ExtrudeGeometry.__v1 = new THREE.Vector2(); +THREE.ExtrudeGeometry.__v2 = new THREE.Vector2(); +THREE.ExtrudeGeometry.__v3 = new THREE.Vector2(); +THREE.ExtrudeGeometry.__v4 = new THREE.Vector2(); +THREE.ExtrudeGeometry.__v5 = new THREE.Vector2(); +THREE.ExtrudeGeometry.__v6 = new THREE.Vector2(); + +/** + * @author jonobr1 / http://jonobr1.com + * + * Creates a one-sided polygonal geometry from a path shape. Similar to + * ExtrudeGeometry. + * + * parameters = { + * + * curveSegments: , // number of points on the curves. NOT USED AT THE MOMENT. + * + * material: // material index for front and back faces + * uvGenerator: // object that provides UV generator functions + * + * } + **/ + +THREE.ShapeGeometry = function ( shapes, options ) { + + THREE.Geometry.call( this ); + + if ( shapes instanceof Array === false ) shapes = [ shapes ]; + + this.shapebb = shapes[ shapes.length - 1 ].getBoundingBox(); + + this.addShapeList( shapes, options ); + + this.computeCentroids(); + this.computeFaceNormals(); + +}; + +THREE.ShapeGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * Add an array of shapes to THREE.ShapeGeometry. + */ +THREE.ShapeGeometry.prototype.addShapeList = function ( shapes, options ) { + + for ( var i = 0, l = shapes.length; i < l; i++ ) { + + this.addShape( shapes[ i ], options ); + + } + + return this; + +}; + +/** + * Adds a shape to THREE.ShapeGeometry, based on THREE.ExtrudeGeometry. + */ +THREE.ShapeGeometry.prototype.addShape = function ( shape, options ) { + + if ( options === undefined ) options = {}; + var curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12; + + var material = options.material; + var uvgen = options.UVGenerator === undefined ? THREE.ExtrudeGeometry.WorldUVGenerator : options.UVGenerator; + + var shapebb = this.shapebb; + + // + + var i, l, hole, s; + + var shapesOffset = this.vertices.length; + var shapePoints = shape.extractPoints( curveSegments ); + + var vertices = shapePoints.shape; + var holes = shapePoints.holes; + + var reverse = !THREE.Shape.Utils.isClockWise( vertices ); + + if ( reverse ) { + + vertices = vertices.reverse(); + + // Maybe we should also check if holes are in the opposite direction, just to be safe... + + for ( i = 0, l = holes.length; i < l; i++ ) { + + hole = holes[ i ]; + + if ( THREE.Shape.Utils.isClockWise( hole ) ) { + + holes[ i ] = hole.reverse(); + + } + + } + + reverse = false; + + } + + var faces = THREE.Shape.Utils.triangulateShape( vertices, holes ); + + // Vertices + + var contour = vertices; + + for ( i = 0, l = holes.length; i < l; i++ ) { + + hole = holes[ i ]; + vertices = vertices.concat( hole ); + + } + + // + + var vert, vlen = vertices.length; + var face, flen = faces.length; + var cont, clen = contour.length; + + for ( i = 0; i < vlen; i++ ) { + + vert = vertices[ i ]; + + this.vertices.push( new THREE.Vector3( vert.x, vert.y, 0 ) ); + + } + + for ( i = 0; i < flen; i++ ) { + + face = faces[ i ]; + + var a = face[ 0 ] + shapesOffset; + var b = face[ 1 ] + shapesOffset; + var c = face[ 2 ] + shapesOffset; + + this.faces.push( new THREE.Face3( a, b, c, null, null, material ) ); + this.faceVertexUvs[ 0 ].push( uvgen.generateBottomUV( this, shape, options, a, b, c ) ); + + } + +}; + +/** + * @author astrodud / http://astrodud.isgreat.org/ + * @author zz85 / https://github.com/zz85 + * @author bhouston / http://exocortex.com + */ + +// points - to create a closed torus, one must use a set of points +// like so: [ a, b, c, d, a ], see first is the same as last. +// segments - the number of circumference segments to create +// phiStart - the starting radian +// phiLength - the radian (0 to 2*PI) range of the lathed section +// 2*pi is a closed lathe, less than 2PI is a portion. +THREE.LatheGeometry = function ( points, segments, phiStart, phiLength ) { + + THREE.Geometry.call( this ); + + segments = segments || 12; + phiStart = phiStart || 0; + phiLength = phiLength || 2 * Math.PI; + + var inversePointLength = 1.0 / ( points.length - 1 ); + var inverseSegments = 1.0 / segments; + + for ( var i = 0, il = segments; i <= il; i ++ ) { + + var phi = phiStart + i * inverseSegments * phiLength; + + var c = Math.cos( phi ), + s = Math.sin( phi ); + + for ( var j = 0, jl = points.length; j < jl; j ++ ) { + + var pt = points[ j ]; + + var vertex = new THREE.Vector3(); + + vertex.x = c * pt.x - s * pt.y; + vertex.y = s * pt.x + c * pt.y; + vertex.z = pt.z; + + this.vertices.push( vertex ); + + } + + } + + var np = points.length; + + for ( var i = 0, il = segments; i < il; i ++ ) { + + for ( var j = 0, jl = points.length - 1; j < jl; j ++ ) { + + var base = j + np * i; + var a = base; + var b = base + np; + var c = base + 1 + np; + var d = base + 1; + + var u0 = i * inverseSegments; + var v0 = j * inversePointLength; + var u1 = u0 + inverseSegments; + var v1 = v0 + inversePointLength; + + this.faces.push( new THREE.Face3( a, b, d ) ); + + this.faceVertexUvs[ 0 ].push( [ + + new THREE.Vector2( u0, v0 ), + new THREE.Vector2( u1, v0 ), + new THREE.Vector2( u0, v1 ) + + ] ); + + this.faces.push( new THREE.Face3( b, c, d ) ); + + this.faceVertexUvs[ 0 ].push( [ + + new THREE.Vector2( u1, v0 ), + new THREE.Vector2( u1, v1 ), + new THREE.Vector2( u0, v1 ) + + ] ); + + + } + + } + + this.mergeVertices(); + this.computeCentroids(); + this.computeFaceNormals(); + this.computeVertexNormals(); + +}; + +THREE.LatheGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @author mrdoob / http://mrdoob.com/ + * based on http://papervision3d.googlecode.com/svn/trunk/as3/trunk/src/org/papervision3d/objects/primitives/Plane.as + */ + +THREE.PlaneGeometry = function ( width, height, widthSegments, heightSegments ) { + + THREE.Geometry.call( this ); + + this.width = width; + this.height = height; + + this.widthSegments = widthSegments || 1; + this.heightSegments = heightSegments || 1; + + var ix, iz; + var width_half = width / 2; + var height_half = height / 2; + + var gridX = this.widthSegments; + var gridZ = this.heightSegments; + + var gridX1 = gridX + 1; + var gridZ1 = gridZ + 1; + + var segment_width = this.width / gridX; + var segment_height = this.height / gridZ; + + var normal = new THREE.Vector3( 0, 0, 1 ); + + for ( iz = 0; iz < gridZ1; iz ++ ) { + + for ( ix = 0; ix < gridX1; ix ++ ) { + + var x = ix * segment_width - width_half; + var y = iz * segment_height - height_half; + + this.vertices.push( new THREE.Vector3( x, - y, 0 ) ); + + } + + } + + for ( iz = 0; iz < gridZ; iz ++ ) { + + for ( ix = 0; ix < gridX; ix ++ ) { + + var a = ix + gridX1 * iz; + var b = ix + gridX1 * ( iz + 1 ); + var c = ( ix + 1 ) + gridX1 * ( iz + 1 ); + var d = ( ix + 1 ) + gridX1 * iz; + + var uva = new THREE.Vector2( ix / gridX, 1 - iz / gridZ ); + var uvb = new THREE.Vector2( ix / gridX, 1 - ( iz + 1 ) / gridZ ); + var uvc = new THREE.Vector2( ( ix + 1 ) / gridX, 1 - ( iz + 1 ) / gridZ ); + var uvd = new THREE.Vector2( ( ix + 1 ) / gridX, 1 - iz / gridZ ); + + var face = new THREE.Face3( a, b, d ); + face.normal.copy( normal ); + face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone() ); + + this.faces.push( face ); + this.faceVertexUvs[ 0 ].push( [ uva, uvb, uvd ] ); + + face = new THREE.Face3( b, c, d ); + face.normal.copy( normal ); + face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone() ); + + this.faces.push( face ); + this.faceVertexUvs[ 0 ].push( [ uvb.clone(), uvc, uvd.clone() ] ); + + } + + } + + this.computeCentroids(); + +}; + +THREE.PlaneGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @author Kaleb Murphy + */ + +THREE.RingGeometry = function ( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) { + + THREE.Geometry.call( this ); + + innerRadius = innerRadius || 0; + outerRadius = outerRadius || 50; + + thetaStart = thetaStart !== undefined ? thetaStart : 0; + thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2; + + thetaSegments = thetaSegments !== undefined ? Math.max( 3, thetaSegments ) : 8; + phiSegments = phiSegments !== undefined ? Math.max( 3, phiSegments ) : 8; + + var i, o, uvs = [], radius = innerRadius, radiusStep = ( ( outerRadius - innerRadius ) / phiSegments ); + + for ( i = 0; i <= phiSegments; i ++ ) { // concentric circles inside ring + + for ( o = 0; o <= thetaSegments; o ++ ) { // number of segments per circle + + var vertex = new THREE.Vector3(); + var segment = thetaStart + o / thetaSegments * thetaLength; + + vertex.x = radius * Math.cos( segment ); + vertex.y = radius * Math.sin( segment ); + + this.vertices.push( vertex ); + uvs.push( new THREE.Vector2( ( vertex.x / outerRadius + 1 ) / 2, ( vertex.y / outerRadius + 1 ) / 2 ) ); + } + + radius += radiusStep; + + } + + var n = new THREE.Vector3( 0, 0, 1 ); + + for ( i = 0; i < phiSegments; i ++ ) { // concentric circles inside ring + + var thetaSegment = i * thetaSegments; + + for ( o = 0; o <= thetaSegments; o ++ ) { // number of segments per circle + + var segment = o + thetaSegment; + + var v1 = segment + i; + var v2 = segment + thetaSegments + i; + var v3 = segment + thetaSegments + 1 + i; + + this.faces.push( new THREE.Face3( v1, v2, v3, [ n.clone(), n.clone(), n.clone() ] ) ); + this.faceVertexUvs[ 0 ].push( [ uvs[ v1 ].clone(), uvs[ v2 ].clone(), uvs[ v3 ].clone() ]); + + v1 = segment + i; + v2 = segment + thetaSegments + 1 + i; + v3 = segment + 1 + i; + + this.faces.push( new THREE.Face3( v1, v2, v3, [ n.clone(), n.clone(), n.clone() ] ) ); + this.faceVertexUvs[ 0 ].push( [ uvs[ v1 ].clone(), uvs[ v2 ].clone(), uvs[ v3 ].clone() ]); + + } + } + + this.computeCentroids(); + this.computeFaceNormals(); + + this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius ); + +}; + +THREE.RingGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.SphereGeometry = function ( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) { + + THREE.Geometry.call( this ); + + this.radius = radius = radius || 50; + + this.widthSegments = widthSegments = Math.max( 3, Math.floor( widthSegments ) || 8 ); + this.heightSegments = heightSegments = Math.max( 2, Math.floor( heightSegments ) || 6 ); + + this.phiStart = phiStart = phiStart !== undefined ? phiStart : 0; + this.phiLength = phiLength = phiLength !== undefined ? phiLength : Math.PI * 2; + + this.thetaStart = thetaStart = thetaStart !== undefined ? thetaStart : 0; + this.thetaLength = thetaLength = thetaLength !== undefined ? thetaLength : Math.PI; + + var x, y, vertices = [], uvs = []; + + for ( y = 0; y <= heightSegments; y ++ ) { + + var verticesRow = []; + var uvsRow = []; + + for ( x = 0; x <= widthSegments; x ++ ) { + + var u = x / widthSegments; + var v = y / heightSegments; + + var vertex = new THREE.Vector3(); + vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); + vertex.y = radius * Math.cos( thetaStart + v * thetaLength ); + vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); + + this.vertices.push( vertex ); + + verticesRow.push( this.vertices.length - 1 ); + uvsRow.push( new THREE.Vector2( u, 1 - v ) ); + + } + + vertices.push( verticesRow ); + uvs.push( uvsRow ); + + } + + for ( y = 0; y < this.heightSegments; y ++ ) { + + for ( x = 0; x < this.widthSegments; x ++ ) { + + var v1 = vertices[ y ][ x + 1 ]; + var v2 = vertices[ y ][ x ]; + var v3 = vertices[ y + 1 ][ x ]; + var v4 = vertices[ y + 1 ][ x + 1 ]; + + var n1 = this.vertices[ v1 ].clone().normalize(); + var n2 = this.vertices[ v2 ].clone().normalize(); + var n3 = this.vertices[ v3 ].clone().normalize(); + var n4 = this.vertices[ v4 ].clone().normalize(); + + var uv1 = uvs[ y ][ x + 1 ].clone(); + var uv2 = uvs[ y ][ x ].clone(); + var uv3 = uvs[ y + 1 ][ x ].clone(); + var uv4 = uvs[ y + 1 ][ x + 1 ].clone(); + + if ( Math.abs( this.vertices[ v1 ].y ) === this.radius ) { + + uv1.x = ( uv1.x + uv2.x ) / 2; + this.faces.push( new THREE.Face3( v1, v3, v4, [ n1, n3, n4 ] ) ); + this.faceVertexUvs[ 0 ].push( [ uv1, uv3, uv4 ] ); + + } else if ( Math.abs( this.vertices[ v3 ].y ) === this.radius ) { + + uv3.x = ( uv3.x + uv4.x ) / 2; + this.faces.push( new THREE.Face3( v1, v2, v3, [ n1, n2, n3 ] ) ); + this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv3 ] ); + + } else { + + this.faces.push( new THREE.Face3( v1, v2, v4, [ n1, n2, n4 ] ) ); + this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv4 ] ); + + this.faces.push( new THREE.Face3( v2, v3, v4, [ n2.clone(), n3, n4.clone() ] ) ); + this.faceVertexUvs[ 0 ].push( [ uv2.clone(), uv3, uv4.clone() ] ); + + } + + } + + } + + this.computeCentroids(); + this.computeFaceNormals(); + + this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius ); + +}; + +THREE.SphereGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @author zz85 / http://www.lab4games.net/zz85/blog + * @author alteredq / http://alteredqualia.com/ + * + * For creating 3D text geometry in three.js + * + * Text = 3D Text + * + * parameters = { + * size: , // size of the text + * height: , // thickness to extrude text + * curveSegments: , // number of points on the curves + * + * font: , // font name + * weight: , // font weight (normal, bold) + * style: , // font style (normal, italics) + * + * bevelEnabled: , // turn on bevel + * bevelThickness: , // how deep into text bevel goes + * bevelSize: , // how far from text outline is bevel + * } + * + */ + +/* Usage Examples + + // TextGeometry wrapper + + var text3d = new TextGeometry( text, options ); + + // Complete manner + + var textShapes = THREE.FontUtils.generateShapes( text, options ); + var text3d = new ExtrudeGeometry( textShapes, options ); + +*/ + + +THREE.TextGeometry = function ( text, parameters ) { + + parameters = parameters || {}; + + var textShapes = THREE.FontUtils.generateShapes( text, parameters ); + + // translate parameters to ExtrudeGeometry API + + parameters.amount = parameters.height !== undefined ? parameters.height : 50; + + // defaults + + if ( parameters.bevelThickness === undefined ) parameters.bevelThickness = 10; + if ( parameters.bevelSize === undefined ) parameters.bevelSize = 8; + if ( parameters.bevelEnabled === undefined ) parameters.bevelEnabled = false; + + THREE.ExtrudeGeometry.call( this, textShapes, parameters ); + +}; + +THREE.TextGeometry.prototype = Object.create( THREE.ExtrudeGeometry.prototype ); + +/** + * @author oosmoxiecode + * @author mrdoob / http://mrdoob.com/ + * based on http://code.google.com/p/away3d/source/browse/trunk/fp10/Away3DLite/src/away3dlite/primitives/Torus.as?r=2888 + */ + +THREE.TorusGeometry = function ( radius, tube, radialSegments, tubularSegments, arc ) { + + THREE.Geometry.call( this ); + + var scope = this; + + this.radius = radius || 100; + this.tube = tube || 40; + this.radialSegments = radialSegments || 8; + this.tubularSegments = tubularSegments || 6; + this.arc = arc || Math.PI * 2; + + var center = new THREE.Vector3(), uvs = [], normals = []; + + for ( var j = 0; j <= this.radialSegments; j ++ ) { + + for ( var i = 0; i <= this.tubularSegments; i ++ ) { + + var u = i / this.tubularSegments * this.arc; + var v = j / this.radialSegments * Math.PI * 2; + + center.x = this.radius * Math.cos( u ); + center.y = this.radius * Math.sin( u ); + + var vertex = new THREE.Vector3(); + vertex.x = ( this.radius + this.tube * Math.cos( v ) ) * Math.cos( u ); + vertex.y = ( this.radius + this.tube * Math.cos( v ) ) * Math.sin( u ); + vertex.z = this.tube * Math.sin( v ); + + this.vertices.push( vertex ); + + uvs.push( new THREE.Vector2( i / this.tubularSegments, j / this.radialSegments ) ); + normals.push( vertex.clone().sub( center ).normalize() ); + + } + + } + + + for ( var j = 1; j <= this.radialSegments; j ++ ) { + + for ( var i = 1; i <= this.tubularSegments; i ++ ) { + + var a = ( this.tubularSegments + 1 ) * j + i - 1; + var b = ( this.tubularSegments + 1 ) * ( j - 1 ) + i - 1; + var c = ( this.tubularSegments + 1 ) * ( j - 1 ) + i; + var d = ( this.tubularSegments + 1 ) * j + i; + + var face = new THREE.Face3( a, b, d, [ normals[ a ].clone(), normals[ b ].clone(), normals[ d ].clone() ] ); + this.faces.push( face ); + this.faceVertexUvs[ 0 ].push( [ uvs[ a ].clone(), uvs[ b ].clone(), uvs[ d ].clone() ] ); + + face = new THREE.Face3( b, c, d, [ normals[ b ].clone(), normals[ c ].clone(), normals[ d ].clone() ] ); + this.faces.push( face ); + this.faceVertexUvs[ 0 ].push( [ uvs[ b ].clone(), uvs[ c ].clone(), uvs[ d ].clone() ] ); + + } + + } + + this.computeCentroids(); + this.computeFaceNormals(); + +}; + +THREE.TorusGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @author oosmoxiecode + * based on http://code.google.com/p/away3d/source/browse/trunk/fp10/Away3D/src/away3d/primitives/TorusKnot.as?spec=svn2473&r=2473 + */ + +THREE.TorusKnotGeometry = function ( radius, tube, radialSegments, tubularSegments, p, q, heightScale ) { + + THREE.Geometry.call( this ); + + var scope = this; + + this.radius = radius || 100; + this.tube = tube || 40; + this.radialSegments = radialSegments || 64; + this.tubularSegments = tubularSegments || 8; + this.p = p || 2; + this.q = q || 3; + this.heightScale = heightScale || 1; + this.grid = new Array( this.radialSegments ); + + var tang = new THREE.Vector3(); + var n = new THREE.Vector3(); + var bitan = new THREE.Vector3(); + + for ( var i = 0; i < this.radialSegments; ++ i ) { + + this.grid[ i ] = new Array( this.tubularSegments ); + var u = i / this.radialSegments * 2 * this.p * Math.PI; + var p1 = getPos( u, this.q, this.p, this.radius, this.heightScale ); + var p2 = getPos( u + 0.01, this.q, this.p, this.radius, this.heightScale ); + tang.subVectors( p2, p1 ); + n.addVectors( p2, p1 ); + + bitan.crossVectors( tang, n ); + n.crossVectors( bitan, tang ); + bitan.normalize(); + n.normalize(); + + for ( var j = 0; j < this.tubularSegments; ++ j ) { + + var v = j / this.tubularSegments * 2 * Math.PI; + var cx = - this.tube * Math.cos( v ); // TODO: Hack: Negating it so it faces outside. + var cy = this.tube * Math.sin( v ); + + var pos = new THREE.Vector3(); + pos.x = p1.x + cx * n.x + cy * bitan.x; + pos.y = p1.y + cx * n.y + cy * bitan.y; + pos.z = p1.z + cx * n.z + cy * bitan.z; + + this.grid[ i ][ j ] = scope.vertices.push( pos ) - 1; + + } + + } + + for ( var i = 0; i < this.radialSegments; ++ i ) { + + for ( var j = 0; j < this.tubularSegments; ++ j ) { + + var ip = ( i + 1 ) % this.radialSegments; + var jp = ( j + 1 ) % this.tubularSegments; + + var a = this.grid[ i ][ j ]; + var b = this.grid[ ip ][ j ]; + var c = this.grid[ ip ][ jp ]; + var d = this.grid[ i ][ jp ]; + + var uva = new THREE.Vector2( i / this.radialSegments, j / this.tubularSegments ); + var uvb = new THREE.Vector2( ( i + 1 ) / this.radialSegments, j / this.tubularSegments ); + var uvc = new THREE.Vector2( ( i + 1 ) / this.radialSegments, ( j + 1 ) / this.tubularSegments ); + var uvd = new THREE.Vector2( i / this.radialSegments, ( j + 1 ) / this.tubularSegments ); + + this.faces.push( new THREE.Face3( a, b, d ) ); + this.faceVertexUvs[ 0 ].push( [ uva, uvb, uvd ] ); + + this.faces.push( new THREE.Face3( b, c, d ) ); + this.faceVertexUvs[ 0 ].push( [ uvb.clone(), uvc, uvd.clone() ] ); + + } + } + + this.computeCentroids(); + this.computeFaceNormals(); + this.computeVertexNormals(); + + function getPos( u, in_q, in_p, radius, heightScale ) { + + var cu = Math.cos( u ); + var su = Math.sin( u ); + var quOverP = in_q / in_p * u; + var cs = Math.cos( quOverP ); + + var tx = radius * ( 2 + cs ) * 0.5 * cu; + var ty = radius * ( 2 + cs ) * su * 0.5; + var tz = heightScale * radius * Math.sin( quOverP ) * 0.5; + + return new THREE.Vector3( tx, ty, tz ); + + } + +}; + +THREE.TorusKnotGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @author WestLangley / https://github.com/WestLangley + * @author zz85 / https://github.com/zz85 + * @author miningold / https://github.com/miningold + * + * Modified from the TorusKnotGeometry by @oosmoxiecode + * + * Creates a tube which extrudes along a 3d spline + * + * Uses parallel transport frames as described in + * http://www.cs.indiana.edu/pub/techreports/TR425.pdf + */ + +THREE.TubeGeometry = function( path, segments, radius, radialSegments, closed ) { + + THREE.Geometry.call( this ); + + this.path = path; + this.segments = segments || 64; + this.radius = radius || 1; + this.radialSegments = radialSegments || 8; + this.closed = closed || false; + + this.grid = []; + + var scope = this, + + tangent, + normal, + binormal, + + numpoints = this.segments + 1, + + x, y, z, + tx, ty, tz, + u, v, + + cx, cy, + pos, pos2 = new THREE.Vector3(), + i, j, + ip, jp, + a, b, c, d, + uva, uvb, uvc, uvd; + + var frames = new THREE.TubeGeometry.FrenetFrames( this.path, this.segments, this.closed ), + tangents = frames.tangents, + normals = frames.normals, + binormals = frames.binormals; + + // proxy internals + this.tangents = tangents; + this.normals = normals; + this.binormals = binormals; + + function vert( x, y, z ) { + + return scope.vertices.push( new THREE.Vector3( x, y, z ) ) - 1; + + } + + + // consruct the grid + + for ( i = 0; i < numpoints; i++ ) { + + this.grid[ i ] = []; + + u = i / ( numpoints - 1 ); + + pos = path.getPointAt( u ); + + tangent = tangents[ i ]; + normal = normals[ i ]; + binormal = binormals[ i ]; + + for ( j = 0; j < this.radialSegments; j++ ) { + + v = j / this.radialSegments * 2 * Math.PI; + + cx = -this.radius * Math.cos( v ); // TODO: Hack: Negating it so it faces outside. + cy = this.radius * Math.sin( v ); + + pos2.copy( pos ); + pos2.x += cx * normal.x + cy * binormal.x; + pos2.y += cx * normal.y + cy * binormal.y; + pos2.z += cx * normal.z + cy * binormal.z; + + this.grid[ i ][ j ] = vert( pos2.x, pos2.y, pos2.z ); + + } + } + + + // construct the mesh + + for ( i = 0; i < this.segments; i++ ) { + + for ( j = 0; j < this.radialSegments; j++ ) { + + ip = ( this.closed ) ? (i + 1) % this.segments : i + 1; + jp = (j + 1) % this.radialSegments; + + a = this.grid[ i ][ j ]; // *** NOT NECESSARILY PLANAR ! *** + b = this.grid[ ip ][ j ]; + c = this.grid[ ip ][ jp ]; + d = this.grid[ i ][ jp ]; + + uva = new THREE.Vector2( i / this.segments, j / this.radialSegments ); + uvb = new THREE.Vector2( ( i + 1 ) / this.segments, j / this.radialSegments ); + uvc = new THREE.Vector2( ( i + 1 ) / this.segments, ( j + 1 ) / this.radialSegments ); + uvd = new THREE.Vector2( i / this.segments, ( j + 1 ) / this.radialSegments ); + + this.faces.push( new THREE.Face3( a, b, d ) ); + this.faceVertexUvs[ 0 ].push( [ uva, uvb, uvd ] ); + + this.faces.push( new THREE.Face3( b, c, d ) ); + this.faceVertexUvs[ 0 ].push( [ uvb.clone(), uvc, uvd.clone() ] ); + + } + } + + this.computeCentroids(); + this.computeFaceNormals(); + this.computeVertexNormals(); + +}; + +THREE.TubeGeometry.prototype = Object.create( THREE.Geometry.prototype ); + + +// For computing of Frenet frames, exposing the tangents, normals and binormals the spline +THREE.TubeGeometry.FrenetFrames = function(path, segments, closed) { + + var tangent = new THREE.Vector3(), + normal = new THREE.Vector3(), + binormal = new THREE.Vector3(), + + tangents = [], + normals = [], + binormals = [], + + vec = new THREE.Vector3(), + mat = new THREE.Matrix4(), + + numpoints = segments + 1, + theta, + epsilon = 0.0001, + smallest, + + tx, ty, tz, + i, u, v; + + + // expose internals + this.tangents = tangents; + this.normals = normals; + this.binormals = binormals; + + // compute the tangent vectors for each segment on the path + + for ( i = 0; i < numpoints; i++ ) { + + u = i / ( numpoints - 1 ); + + tangents[ i ] = path.getTangentAt( u ); + tangents[ i ].normalize(); + + } + + initialNormal3(); + + function initialNormal1(lastBinormal) { + // fixed start binormal. Has dangers of 0 vectors + normals[ 0 ] = new THREE.Vector3(); + binormals[ 0 ] = new THREE.Vector3(); + if (lastBinormal===undefined) lastBinormal = new THREE.Vector3( 0, 0, 1 ); + normals[ 0 ].crossVectors( lastBinormal, tangents[ 0 ] ).normalize(); + binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ).normalize(); + } + + function initialNormal2() { + + // This uses the Frenet-Serret formula for deriving binormal + var t2 = path.getTangentAt( epsilon ); + + normals[ 0 ] = new THREE.Vector3().subVectors( t2, tangents[ 0 ] ).normalize(); + binormals[ 0 ] = new THREE.Vector3().crossVectors( tangents[ 0 ], normals[ 0 ] ); + + normals[ 0 ].crossVectors( binormals[ 0 ], tangents[ 0 ] ).normalize(); // last binormal x tangent + binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ).normalize(); + + } + + function initialNormal3() { + // select an initial normal vector perpenicular to the first tangent vector, + // and in the direction of the smallest tangent xyz component + + normals[ 0 ] = new THREE.Vector3(); + binormals[ 0 ] = new THREE.Vector3(); + smallest = Number.MAX_VALUE; + tx = Math.abs( tangents[ 0 ].x ); + ty = Math.abs( tangents[ 0 ].y ); + tz = Math.abs( tangents[ 0 ].z ); + + if ( tx <= smallest ) { + smallest = tx; + normal.set( 1, 0, 0 ); + } + + if ( ty <= smallest ) { + smallest = ty; + normal.set( 0, 1, 0 ); + } + + if ( tz <= smallest ) { + normal.set( 0, 0, 1 ); + } + + vec.crossVectors( tangents[ 0 ], normal ).normalize(); + + normals[ 0 ].crossVectors( tangents[ 0 ], vec ); + binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ); + } + + + // compute the slowly-varying normal and binormal vectors for each segment on the path + + for ( i = 1; i < numpoints; i++ ) { + + normals[ i ] = normals[ i-1 ].clone(); + + binormals[ i ] = binormals[ i-1 ].clone(); + + vec.crossVectors( tangents[ i-1 ], tangents[ i ] ); + + if ( vec.length() > epsilon ) { + + vec.normalize(); + + theta = Math.acos( THREE.Math.clamp( tangents[ i-1 ].dot( tangents[ i ] ), -1, 1 ) ); // clamp for floating pt errors + + normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) ); + + } + + binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); + + } + + + // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same + + if ( closed ) { + + theta = Math.acos( THREE.Math.clamp( normals[ 0 ].dot( normals[ numpoints-1 ] ), -1, 1 ) ); + theta /= ( numpoints - 1 ); + + if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ numpoints-1 ] ) ) > 0 ) { + + theta = -theta; + + } + + for ( i = 1; i < numpoints; i++ ) { + + // twist a little... + normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) ); + binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); + + } + + } +}; + +/** + * @author clockworkgeek / https://github.com/clockworkgeek + * @author timothypratley / https://github.com/timothypratley + * @author WestLangley / http://github.com/WestLangley +*/ + +THREE.PolyhedronGeometry = function ( vertices, faces, radius, detail ) { + + THREE.Geometry.call( this ); + + radius = radius || 1; + detail = detail || 0; + + var that = this; + + for ( var i = 0, l = vertices.length; i < l; i ++ ) { + + prepare( new THREE.Vector3( vertices[ i ][ 0 ], vertices[ i ][ 1 ], vertices[ i ][ 2 ] ) ); + + } + + var midpoints = [], p = this.vertices; + + var f = []; + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + var v1 = p[ faces[ i ][ 0 ] ]; + var v2 = p[ faces[ i ][ 1 ] ]; + var v3 = p[ faces[ i ][ 2 ] ]; + + f[ i ] = new THREE.Face3( v1.index, v2.index, v3.index, [ v1.clone(), v2.clone(), v3.clone() ] ); + + } + + for ( var i = 0, l = f.length; i < l; i ++ ) { + + subdivide(f[ i ], detail); + + } + + + // Handle case when face straddles the seam + + for ( var i = 0, l = this.faceVertexUvs[ 0 ].length; i < l; i ++ ) { + + var uvs = this.faceVertexUvs[ 0 ][ i ]; + + var x0 = uvs[ 0 ].x; + var x1 = uvs[ 1 ].x; + var x2 = uvs[ 2 ].x; + + var max = Math.max( x0, Math.max( x1, x2 ) ); + var min = Math.min( x0, Math.min( x1, x2 ) ); + + if ( max > 0.9 && min < 0.1 ) { // 0.9 is somewhat arbitrary + + if ( x0 < 0.2 ) uvs[ 0 ].x += 1; + if ( x1 < 0.2 ) uvs[ 1 ].x += 1; + if ( x2 < 0.2 ) uvs[ 2 ].x += 1; + + } + + } + + + // Apply radius + + for ( var i = 0, l = this.vertices.length; i < l; i ++ ) { + + this.vertices[ i ].multiplyScalar( radius ); + + } + + + // Merge vertices + + this.mergeVertices(); + + this.computeCentroids(); + + this.computeFaceNormals(); + + this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius ); + + + // Project vector onto sphere's surface + + function prepare( vector ) { + + var vertex = vector.normalize().clone(); + vertex.index = that.vertices.push( vertex ) - 1; + + // Texture coords are equivalent to map coords, calculate angle and convert to fraction of a circle. + + var u = azimuth( vector ) / 2 / Math.PI + 0.5; + var v = inclination( vector ) / Math.PI + 0.5; + vertex.uv = new THREE.Vector2( u, 1 - v ); + + return vertex; + + } + + + // Approximate a curved face with recursively sub-divided triangles. + + function make( v1, v2, v3 ) { + + var face = new THREE.Face3( v1.index, v2.index, v3.index, [ v1.clone(), v2.clone(), v3.clone() ] ); + face.centroid.add( v1 ).add( v2 ).add( v3 ).divideScalar( 3 ); + that.faces.push( face ); + + var azi = azimuth( face.centroid ); + + that.faceVertexUvs[ 0 ].push( [ + correctUV( v1.uv, v1, azi ), + correctUV( v2.uv, v2, azi ), + correctUV( v3.uv, v3, azi ) + ] ); + + } + + + // Analytically subdivide a face to the required detail level. + + function subdivide(face, detail ) { + + var cols = Math.pow(2, detail); + var cells = Math.pow(4, detail); + var a = prepare( that.vertices[ face.a ] ); + var b = prepare( that.vertices[ face.b ] ); + var c = prepare( that.vertices[ face.c ] ); + var v = []; + + // Construct all of the vertices for this subdivision. + + for ( var i = 0 ; i <= cols; i ++ ) { + + v[ i ] = []; + + var aj = prepare( a.clone().lerp( c, i / cols ) ); + var bj = prepare( b.clone().lerp( c, i / cols ) ); + var rows = cols - i; + + for ( var j = 0; j <= rows; j ++) { + + if ( j == 0 && i == cols ) { + + v[ i ][ j ] = aj; + + } else { + + v[ i ][ j ] = prepare( aj.clone().lerp( bj, j / rows ) ); + + } + + } + + } + + // Construct all of the faces. + + for ( var i = 0; i < cols ; i ++ ) { + + for ( var j = 0; j < 2 * (cols - i) - 1; j ++ ) { + + var k = Math.floor( j / 2 ); + + if ( j % 2 == 0 ) { + + make( + v[ i ][ k + 1], + v[ i + 1 ][ k ], + v[ i ][ k ] + ); + + } else { + + make( + v[ i ][ k + 1 ], + v[ i + 1][ k + 1], + v[ i + 1 ][ k ] + ); + + } + + } + + } + + } + + + // Angle around the Y axis, counter-clockwise when looking from above. + + function azimuth( vector ) { + + return Math.atan2( vector.z, -vector.x ); + + } + + + // Angle above the XZ plane. + + function inclination( vector ) { + + return Math.atan2( -vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) ); + + } + + + // Texture fixing helper. Spheres have some odd behaviours. + + function correctUV( uv, vector, azimuth ) { + + if ( ( azimuth < 0 ) && ( uv.x === 1 ) ) uv = new THREE.Vector2( uv.x - 1, uv.y ); + if ( ( vector.x === 0 ) && ( vector.z === 0 ) ) uv = new THREE.Vector2( azimuth / 2 / Math.PI + 0.5, uv.y ); + return uv.clone(); + + } + + +}; + +THREE.PolyhedronGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @author timothypratley / https://github.com/timothypratley + */ + +THREE.IcosahedronGeometry = function ( radius, detail ) { + + this.radius = radius; + this.detail = detail; + + var t = ( 1 + Math.sqrt( 5 ) ) / 2; + + var vertices = [ + [ -1, t, 0 ], [ 1, t, 0 ], [ -1, -t, 0 ], [ 1, -t, 0 ], + [ 0, -1, t ], [ 0, 1, t ], [ 0, -1, -t ], [ 0, 1, -t ], + [ t, 0, -1 ], [ t, 0, 1 ], [ -t, 0, -1 ], [ -t, 0, 1 ] + ]; + + var faces = [ + [ 0, 11, 5 ], [ 0, 5, 1 ], [ 0, 1, 7 ], [ 0, 7, 10 ], [ 0, 10, 11 ], + [ 1, 5, 9 ], [ 5, 11, 4 ], [ 11, 10, 2 ], [ 10, 7, 6 ], [ 7, 1, 8 ], + [ 3, 9, 4 ], [ 3, 4, 2 ], [ 3, 2, 6 ], [ 3, 6, 8 ], [ 3, 8, 9 ], + [ 4, 9, 5 ], [ 2, 4, 11 ], [ 6, 2, 10 ], [ 8, 6, 7 ], [ 9, 8, 1 ] + ]; + + THREE.PolyhedronGeometry.call( this, vertices, faces, radius, detail ); + +}; + +THREE.IcosahedronGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @author timothypratley / https://github.com/timothypratley + */ + +THREE.OctahedronGeometry = function ( radius, detail ) { + + var vertices = [ + [ 1, 0, 0 ], [ -1, 0, 0 ], [ 0, 1, 0 ], [ 0, -1, 0 ], [ 0, 0, 1 ], [ 0, 0, -1 ] + ]; + + var faces = [ + [ 0, 2, 4 ], [ 0, 4, 3 ], [ 0, 3, 5 ], [ 0, 5, 2 ], [ 1, 2, 5 ], [ 1, 5, 3 ], [ 1, 3, 4 ], [ 1, 4, 2 ] + ]; + + THREE.PolyhedronGeometry.call( this, vertices, faces, radius, detail ); +}; + +THREE.OctahedronGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @author timothypratley / https://github.com/timothypratley + */ + +THREE.TetrahedronGeometry = function ( radius, detail ) { + + var vertices = [ + [ 1, 1, 1 ], [ -1, -1, 1 ], [ -1, 1, -1 ], [ 1, -1, -1 ] + ]; + + var faces = [ + [ 2, 1, 0 ], [ 0, 3, 2 ], [ 1, 3, 0 ], [ 2, 3, 1 ] + ]; + + THREE.PolyhedronGeometry.call( this, vertices, faces, radius, detail ); + +}; + +THREE.TetrahedronGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @author zz85 / https://github.com/zz85 + * Parametric Surfaces Geometry + * based on the brilliant article by @prideout http://prideout.net/blog/?p=44 + * + * new THREE.ParametricGeometry( parametricFunction, uSegments, ySegements ); + * + */ + +THREE.ParametricGeometry = function ( func, slices, stacks ) { + + THREE.Geometry.call( this ); + + var verts = this.vertices; + var faces = this.faces; + var uvs = this.faceVertexUvs[ 0 ]; + + var i, il, j, p; + var u, v; + + var stackCount = stacks + 1; + var sliceCount = slices + 1; + + for ( i = 0; i <= stacks; i ++ ) { + + v = i / stacks; + + for ( j = 0; j <= slices; j ++ ) { + + u = j / slices; + + p = func( u, v ); + verts.push( p ); + + } + } + + var a, b, c, d; + var uva, uvb, uvc, uvd; + + for ( i = 0; i < stacks; i ++ ) { + + for ( j = 0; j < slices; j ++ ) { + + a = i * sliceCount + j; + b = i * sliceCount + j + 1; + c = (i + 1) * sliceCount + j + 1; + d = (i + 1) * sliceCount + j; + + uva = new THREE.Vector2( j / slices, i / stacks ); + uvb = new THREE.Vector2( ( j + 1 ) / slices, i / stacks ); + uvc = new THREE.Vector2( ( j + 1 ) / slices, ( i + 1 ) / stacks ); + uvd = new THREE.Vector2( j / slices, ( i + 1 ) / stacks ); + + faces.push( new THREE.Face3( a, b, d ) ); + uvs.push( [ uva, uvb, uvd ] ); + + faces.push( new THREE.Face3( b, c, d ) ); + uvs.push( [ uvb.clone(), uvc, uvd.clone() ] ); + + } + + } + + // console.log(this); + + // magic bullet + // var diff = this.mergeVertices(); + // console.log('removed ', diff, ' vertices by merging'); + + this.computeCentroids(); + this.computeFaceNormals(); + this.computeVertexNormals(); + +}; + +THREE.ParametricGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @author sroucheray / http://sroucheray.org/ + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.AxisHelper = function ( size ) { + + size = size || 1; + + var geometry = new THREE.Geometry(); + + geometry.vertices.push( + new THREE.Vector3(), new THREE.Vector3( size, 0, 0 ), + new THREE.Vector3(), new THREE.Vector3( 0, size, 0 ), + new THREE.Vector3(), new THREE.Vector3( 0, 0, size ) + ); + + geometry.colors.push( + new THREE.Color( 0xff0000 ), new THREE.Color( 0xffaa00 ), + new THREE.Color( 0x00ff00 ), new THREE.Color( 0xaaff00 ), + new THREE.Color( 0x0000ff ), new THREE.Color( 0x00aaff ) + ); + + var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors } ); + + THREE.Line.call( this, geometry, material, THREE.LinePieces ); + +}; + +THREE.AxisHelper.prototype = Object.create( THREE.Line.prototype ); + +/** + * @author WestLangley / http://github.com/WestLangley + * @author zz85 / http://github.com/zz85 + * @author bhouston / http://exocortex.com + * + * Creates an arrow for visualizing directions + * + * Parameters: + * dir - Vector3 + * origin - Vector3 + * length - Number + * hex - color in hex value + * headLength - Number + * headWidth - Number + */ + +THREE.ArrowHelper = function ( dir, origin, length, hex, headLength, headWidth ) { + + // dir is assumed to be normalized + + THREE.Object3D.call( this ); + + if ( hex === undefined ) hex = 0xffff00; + if ( length === undefined ) length = 1; + if ( headLength === undefined ) headLength = 0.2 * length; + if ( headWidth === undefined ) headWidth = 0.2 * headLength; + + this.position = origin; + + var lineGeometry = new THREE.Geometry(); + lineGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ) ); + lineGeometry.vertices.push( new THREE.Vector3( 0, 1, 0 ) ); + + this.line = new THREE.Line( lineGeometry, new THREE.LineBasicMaterial( { color: hex } ) ); + this.line.matrixAutoUpdate = false; + this.add( this.line ); + + var coneGeometry = new THREE.CylinderGeometry( 0, 0.5, 1, 5, 1 ); + coneGeometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, - 0.5, 0 ) ); + + this.cone = new THREE.Mesh( coneGeometry, new THREE.MeshBasicMaterial( { color: hex } ) ); + this.cone.matrixAutoUpdate = false; + this.add( this.cone ); + + this.setDirection( dir ); + this.setLength( length, headLength, headWidth ); + +}; + +THREE.ArrowHelper.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.ArrowHelper.prototype.setDirection = function () { + + var axis = new THREE.Vector3(); + var radians; + + return function ( dir ) { + + // dir is assumed to be normalized + + if ( dir.y > 0.99999 ) { + + this.quaternion.set( 0, 0, 0, 1 ); + + } else if ( dir.y < - 0.99999 ) { + + this.quaternion.set( 1, 0, 0, 0 ); + + } else { + + axis.set( dir.z, 0, - dir.x ).normalize(); + + radians = Math.acos( dir.y ); + + this.quaternion.setFromAxisAngle( axis, radians ); + + } + + }; + +}(); + +THREE.ArrowHelper.prototype.setLength = function ( length, headLength, headWidth ) { + + if ( headLength === undefined ) headLength = 0.2 * length; + if ( headWidth === undefined ) headWidth = 0.2 * headLength; + + this.line.scale.set( 1, length, 1 ); + this.line.updateMatrix(); + + this.cone.scale.set( headWidth, headLength, headWidth ); + this.cone.position.y = length; + this.cone.updateMatrix(); + +}; + +THREE.ArrowHelper.prototype.setColor = function ( hex ) { + + this.line.material.color.setHex( hex ); + this.cone.material.color.setHex( hex ); + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.BoxHelper = function ( object ) { + + // 5____4 + // 1/___0/| + // | 6__|_7 + // 2/___3/ + + var vertices = [ + new THREE.Vector3( 1, 1, 1 ), + new THREE.Vector3( - 1, 1, 1 ), + new THREE.Vector3( - 1, - 1, 1 ), + new THREE.Vector3( 1, - 1, 1 ), + + new THREE.Vector3( 1, 1, - 1 ), + new THREE.Vector3( - 1, 1, - 1 ), + new THREE.Vector3( - 1, - 1, - 1 ), + new THREE.Vector3( 1, - 1, - 1 ) + ]; + + this.vertices = vertices; + + // TODO: Wouldn't be nice if Line had .segments? + + var geometry = new THREE.Geometry(); + geometry.vertices.push( + vertices[ 0 ], vertices[ 1 ], + vertices[ 1 ], vertices[ 2 ], + vertices[ 2 ], vertices[ 3 ], + vertices[ 3 ], vertices[ 0 ], + + vertices[ 4 ], vertices[ 5 ], + vertices[ 5 ], vertices[ 6 ], + vertices[ 6 ], vertices[ 7 ], + vertices[ 7 ], vertices[ 4 ], + + vertices[ 0 ], vertices[ 4 ], + vertices[ 1 ], vertices[ 5 ], + vertices[ 2 ], vertices[ 6 ], + vertices[ 3 ], vertices[ 7 ] + ); + + THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: 0xffff00 } ), THREE.LinePieces ); + + if ( object !== undefined ) { + + this.update( object ); + + } + +}; + +THREE.BoxHelper.prototype = Object.create( THREE.Line.prototype ); + +THREE.BoxHelper.prototype.update = function ( object ) { + + var geometry = object.geometry; + + if ( geometry.boundingBox === null ) { + + geometry.computeBoundingBox(); + + } + + var min = geometry.boundingBox.min; + var max = geometry.boundingBox.max; + var vertices = this.vertices; + + vertices[ 0 ].set( max.x, max.y, max.z ); + vertices[ 1 ].set( min.x, max.y, max.z ); + vertices[ 2 ].set( min.x, min.y, max.z ); + vertices[ 3 ].set( max.x, min.y, max.z ); + vertices[ 4 ].set( max.x, max.y, min.z ); + vertices[ 5 ].set( min.x, max.y, min.z ); + vertices[ 6 ].set( min.x, min.y, min.z ); + vertices[ 7 ].set( max.x, min.y, min.z ); + + this.geometry.computeBoundingSphere(); + this.geometry.verticesNeedUpdate = true; + + this.matrixAutoUpdate = false; + this.matrixWorld = object.matrixWorld; + +}; + +/** + * @author WestLangley / http://github.com/WestLangley + */ + +// a helper to show the world-axis-aligned bounding box for an object + +THREE.BoundingBoxHelper = function ( object, hex ) { + + var color = ( hex !== undefined ) ? hex : 0x888888; + + this.object = object; + + this.box = new THREE.Box3(); + + THREE.Mesh.call( this, new THREE.BoxGeometry( 1, 1, 1 ), new THREE.MeshBasicMaterial( { color: color, wireframe: true } ) ); + +}; + +THREE.BoundingBoxHelper.prototype = Object.create( THREE.Mesh.prototype ); + +THREE.BoundingBoxHelper.prototype.update = function () { + + this.box.setFromObject( this.object ); + + this.box.size( this.scale ); + + this.box.center( this.position ); + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + * + * - shows frustum, line of sight and up of the camera + * - suitable for fast updates + * - based on frustum visualization in lightgl.js shadowmap example + * http://evanw.github.com/lightgl.js/tests/shadowmap.html + */ + +THREE.CameraHelper = function ( camera ) { + + var geometry = new THREE.Geometry(); + var material = new THREE.LineBasicMaterial( { color: 0xffffff, vertexColors: THREE.FaceColors } ); + + var pointMap = {}; + + // colors + + var hexFrustum = 0xffaa00; + var hexCone = 0xff0000; + var hexUp = 0x00aaff; + var hexTarget = 0xffffff; + var hexCross = 0x333333; + + // near + + addLine( "n1", "n2", hexFrustum ); + addLine( "n2", "n4", hexFrustum ); + addLine( "n4", "n3", hexFrustum ); + addLine( "n3", "n1", hexFrustum ); + + // far + + addLine( "f1", "f2", hexFrustum ); + addLine( "f2", "f4", hexFrustum ); + addLine( "f4", "f3", hexFrustum ); + addLine( "f3", "f1", hexFrustum ); + + // sides + + addLine( "n1", "f1", hexFrustum ); + addLine( "n2", "f2", hexFrustum ); + addLine( "n3", "f3", hexFrustum ); + addLine( "n4", "f4", hexFrustum ); + + // cone + + addLine( "p", "n1", hexCone ); + addLine( "p", "n2", hexCone ); + addLine( "p", "n3", hexCone ); + addLine( "p", "n4", hexCone ); + + // up + + addLine( "u1", "u2", hexUp ); + addLine( "u2", "u3", hexUp ); + addLine( "u3", "u1", hexUp ); + + // target + + addLine( "c", "t", hexTarget ); + addLine( "p", "c", hexCross ); + + // cross + + addLine( "cn1", "cn2", hexCross ); + addLine( "cn3", "cn4", hexCross ); + + addLine( "cf1", "cf2", hexCross ); + addLine( "cf3", "cf4", hexCross ); + + function addLine( a, b, hex ) { + + addPoint( a, hex ); + addPoint( b, hex ); + + } + + function addPoint( id, hex ) { + + geometry.vertices.push( new THREE.Vector3() ); + geometry.colors.push( new THREE.Color( hex ) ); + + if ( pointMap[ id ] === undefined ) { + + pointMap[ id ] = []; + + } + + pointMap[ id ].push( geometry.vertices.length - 1 ); + + } + + THREE.Line.call( this, geometry, material, THREE.LinePieces ); + + this.camera = camera; + this.matrixWorld = camera.matrixWorld; + this.matrixAutoUpdate = false; + + this.pointMap = pointMap; + + this.update(); + +}; + +THREE.CameraHelper.prototype = Object.create( THREE.Line.prototype ); + +THREE.CameraHelper.prototype.update = function () { + + var vector = new THREE.Vector3(); + var camera = new THREE.Camera(); + var projector = new THREE.Projector(); + + return function () { + + var scope = this; + + var w = 1, h = 1; + + // we need just camera projection matrix + // world matrix must be identity + + camera.projectionMatrix.copy( this.camera.projectionMatrix ); + + // center / target + + setPoint( "c", 0, 0, -1 ); + setPoint( "t", 0, 0, 1 ); + + // near + + setPoint( "n1", -w, -h, -1 ); + setPoint( "n2", w, -h, -1 ); + setPoint( "n3", -w, h, -1 ); + setPoint( "n4", w, h, -1 ); + + // far + + setPoint( "f1", -w, -h, 1 ); + setPoint( "f2", w, -h, 1 ); + setPoint( "f3", -w, h, 1 ); + setPoint( "f4", w, h, 1 ); + + // up + + setPoint( "u1", w * 0.7, h * 1.1, -1 ); + setPoint( "u2", -w * 0.7, h * 1.1, -1 ); + setPoint( "u3", 0, h * 2, -1 ); + + // cross + + setPoint( "cf1", -w, 0, 1 ); + setPoint( "cf2", w, 0, 1 ); + setPoint( "cf3", 0, -h, 1 ); + setPoint( "cf4", 0, h, 1 ); + + setPoint( "cn1", -w, 0, -1 ); + setPoint( "cn2", w, 0, -1 ); + setPoint( "cn3", 0, -h, -1 ); + setPoint( "cn4", 0, h, -1 ); + + function setPoint( point, x, y, z ) { + + vector.set( x, y, z ); + projector.unprojectVector( vector, camera ); + + var points = scope.pointMap[ point ]; + + if ( points !== undefined ) { + + for ( var i = 0, il = points.length; i < il; i ++ ) { + + scope.geometry.vertices[ points[ i ] ].copy( vector ); + + } + + } + + } + + this.geometry.verticesNeedUpdate = true; + + }; + +}(); + +/** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + * @author WestLangley / http://github.com/WestLangley + */ + +THREE.DirectionalLightHelper = function ( light, size ) { + + THREE.Object3D.call( this ); + + this.light = light; + this.light.updateMatrixWorld(); + + this.matrixWorld = light.matrixWorld; + this.matrixAutoUpdate = false; + + size = size || 1; + var geometry = new THREE.PlaneGeometry( size, size ); + var material = new THREE.MeshBasicMaterial( { wireframe: true, fog: false } ); + material.color.copy( this.light.color ).multiplyScalar( this.light.intensity ); + + this.lightPlane = new THREE.Mesh( geometry, material ); + this.add( this.lightPlane ); + + geometry = new THREE.Geometry(); + geometry.vertices.push( new THREE.Vector3() ); + geometry.vertices.push( new THREE.Vector3() ); + + material = new THREE.LineBasicMaterial( { fog: false } ); + material.color.copy( this.light.color ).multiplyScalar( this.light.intensity ); + + this.targetLine = new THREE.Line( geometry, material ); + this.add( this.targetLine ); + + this.update(); + +}; + +THREE.DirectionalLightHelper.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.DirectionalLightHelper.prototype.dispose = function () { + + this.lightPlane.geometry.dispose(); + this.lightPlane.material.dispose(); + this.targetLine.geometry.dispose(); + this.targetLine.material.dispose(); +}; + +THREE.DirectionalLightHelper.prototype.update = function () { + + var v1 = new THREE.Vector3(); + var v2 = new THREE.Vector3(); + var v3 = new THREE.Vector3(); + + return function () { + + v1.setFromMatrixPosition( this.light.matrixWorld ); + v2.setFromMatrixPosition( this.light.target.matrixWorld ); + v3.subVectors( v2, v1 ); + + this.lightPlane.lookAt( v3 ); + this.lightPlane.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity ); + + this.targetLine.geometry.vertices[ 1 ].copy( v3 ); + this.targetLine.geometry.verticesNeedUpdate = true; + this.targetLine.material.color.copy( this.lightPlane.material.color ); + + } + +}(); + + +/** + * @author WestLangley / http://github.com/WestLangley + */ + +THREE.EdgesHelper = function ( object, hex ) { + + var color = ( hex !== undefined ) ? hex : 0xffffff; + + var edge = [ 0, 0 ], hash = {}; + var sortFunction = function ( a, b ) { return a - b }; + + var keys = [ 'a', 'b', 'c' ]; + var geometry = new THREE.BufferGeometry(); + + var geometry2 = object.geometry.clone(); + + geometry2.mergeVertices(); + geometry2.computeFaceNormals(); + + var vertices = geometry2.vertices; + var faces = geometry2.faces; + var numEdges = 0; + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + var face = faces[ i ]; + + for ( var j = 0; j < 3; j ++ ) { + + edge[ 0 ] = face[ keys[ j ] ]; + edge[ 1 ] = face[ keys[ ( j + 1 ) % 3 ] ]; + edge.sort( sortFunction ); + + var key = edge.toString(); + + if ( hash[ key ] === undefined ) { + + hash[ key ] = { vert1: edge[ 0 ], vert2: edge[ 1 ], face1: i, face2: undefined }; + numEdges ++; + + } else { + + hash[ key ].face2 = i; + + } + + } + + } + + geometry.addAttribute( 'position', Float32Array, 2 * numEdges, 3 ); + + var coords = geometry.attributes.position.array; + + var index = 0; + + for ( var key in hash ) { + + var h = hash[ key ]; + + if ( h.face2 === undefined || faces[ h.face1 ].normal.dot( faces[ h.face2 ].normal ) < 0.9999 ) { // hardwired const OK + + var vertex = vertices[ h.vert1 ]; + coords[ index ++ ] = vertex.x; + coords[ index ++ ] = vertex.y; + coords[ index ++ ] = vertex.z; + + vertex = vertices[ h.vert2 ]; + coords[ index ++ ] = vertex.x; + coords[ index ++ ] = vertex.y; + coords[ index ++ ] = vertex.z; + + } + + } + + THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: color } ), THREE.LinePieces ); + + this.matrixAutoUpdate = false; + this.matrixWorld = object.matrixWorld; + +}; + +THREE.EdgesHelper.prototype = Object.create( THREE.Line.prototype ); + +/** + * @author mrdoob / http://mrdoob.com/ + * @author WestLangley / http://github.com/WestLangley +*/ + +THREE.FaceNormalsHelper = function ( object, size, hex, linewidth ) { + + this.object = object; + + this.size = ( size !== undefined ) ? size : 1; + + var color = ( hex !== undefined ) ? hex : 0xffff00; + + var width = ( linewidth !== undefined ) ? linewidth : 1; + + var geometry = new THREE.Geometry(); + + var faces = this.object.geometry.faces; + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + geometry.vertices.push( new THREE.Vector3() ); + geometry.vertices.push( new THREE.Vector3() ); + + } + + THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: color, linewidth: width } ), THREE.LinePieces ); + + this.matrixAutoUpdate = false; + + this.normalMatrix = new THREE.Matrix3(); + + this.update(); + +}; + +THREE.FaceNormalsHelper.prototype = Object.create( THREE.Line.prototype ); + +THREE.FaceNormalsHelper.prototype.update = ( function ( object ) { + + var v1 = new THREE.Vector3(); + + return function ( object ) { + + this.object.updateMatrixWorld( true ); + + this.normalMatrix.getNormalMatrix( this.object.matrixWorld ); + + var vertices = this.geometry.vertices; + + var faces = this.object.geometry.faces; + + var worldMatrix = this.object.matrixWorld; + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + var face = faces[ i ]; + + v1.copy( face.normal ).applyMatrix3( this.normalMatrix ).normalize().multiplyScalar( this.size ); + + var idx = 2 * i; + + vertices[ idx ].copy( face.centroid ).applyMatrix4( worldMatrix ); + + vertices[ idx + 1 ].addVectors( vertices[ idx ], v1 ); + + } + + this.geometry.verticesNeedUpdate = true; + + return this; + + } + +}()); + + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.GridHelper = function ( size, step ) { + + var geometry = new THREE.Geometry(); + var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors } ); + + this.color1 = new THREE.Color( 0x444444 ); + this.color2 = new THREE.Color( 0x888888 ); + + for ( var i = - size; i <= size; i += step ) { + + geometry.vertices.push( + new THREE.Vector3( - size, 0, i ), new THREE.Vector3( size, 0, i ), + new THREE.Vector3( i, 0, - size ), new THREE.Vector3( i, 0, size ) + ); + + var color = i === 0 ? this.color1 : this.color2; + + geometry.colors.push( color, color, color, color ); + + } + + THREE.Line.call( this, geometry, material, THREE.LinePieces ); + +}; + +THREE.GridHelper.prototype = Object.create( THREE.Line.prototype ); + +THREE.GridHelper.prototype.setColors = function( colorCenterLine, colorGrid ) { + + this.color1.set( colorCenterLine ); + this.color2.set( colorGrid ); + + this.geometry.colorsNeedUpdate = true; + +} + +/** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.HemisphereLightHelper = function ( light, sphereSize, arrowLength, domeSize ) { + + THREE.Object3D.call( this ); + + this.light = light; + this.light.updateMatrixWorld(); + + this.matrixWorld = light.matrixWorld; + this.matrixAutoUpdate = false; + + this.colors = [ new THREE.Color(), new THREE.Color() ]; + + var geometry = new THREE.SphereGeometry( sphereSize, 4, 2 ); + geometry.applyMatrix( new THREE.Matrix4().makeRotationX( - Math.PI / 2 ) ); + + for ( var i = 0, il = 8; i < il; i ++ ) { + + geometry.faces[ i ].color = this.colors[ i < 4 ? 0 : 1 ]; + + } + + var material = new THREE.MeshBasicMaterial( { vertexColors: THREE.FaceColors, wireframe: true } ); + + this.lightSphere = new THREE.Mesh( geometry, material ); + this.add( this.lightSphere ); + + this.update(); + +}; + +THREE.HemisphereLightHelper.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.HemisphereLightHelper.prototype.dispose = function () { + this.lightSphere.geometry.dispose(); + this.lightSphere.material.dispose(); +}; + +THREE.HemisphereLightHelper.prototype.update = function () { + + var vector = new THREE.Vector3(); + + return function () { + + this.colors[ 0 ].copy( this.light.color ).multiplyScalar( this.light.intensity ); + this.colors[ 1 ].copy( this.light.groundColor ).multiplyScalar( this.light.intensity ); + + this.lightSphere.lookAt( vector.setFromMatrixPosition( this.light.matrixWorld ).negate() ); + this.lightSphere.geometry.colorsNeedUpdate = true; + + } + +}(); + + +/** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.PointLightHelper = function ( light, sphereSize ) { + + this.light = light; + this.light.updateMatrixWorld(); + + var geometry = new THREE.SphereGeometry( sphereSize, 4, 2 ); + var material = new THREE.MeshBasicMaterial( { wireframe: true, fog: false } ); + material.color.copy( this.light.color ).multiplyScalar( this.light.intensity ); + + THREE.Mesh.call( this, geometry, material ); + + this.matrixWorld = this.light.matrixWorld; + this.matrixAutoUpdate = false; + + /* + var distanceGeometry = new THREE.IcosahedronGeometry( 1, 2 ); + var distanceMaterial = new THREE.MeshBasicMaterial( { color: hexColor, fog: false, wireframe: true, opacity: 0.1, transparent: true } ); + + this.lightSphere = new THREE.Mesh( bulbGeometry, bulbMaterial ); + this.lightDistance = new THREE.Mesh( distanceGeometry, distanceMaterial ); + + var d = light.distance; + + if ( d === 0.0 ) { + + this.lightDistance.visible = false; + + } else { + + this.lightDistance.scale.set( d, d, d ); + + } + + this.add( this.lightDistance ); + */ + +}; + +THREE.PointLightHelper.prototype = Object.create( THREE.Mesh.prototype ); + +THREE.PointLightHelper.prototype.dispose = function () { + + this.geometry.dispose(); + this.material.dispose(); +}; + +THREE.PointLightHelper.prototype.update = function () { + + this.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity ); + + /* + var d = this.light.distance; + + if ( d === 0.0 ) { + + this.lightDistance.visible = false; + + } else { + + this.lightDistance.visible = true; + this.lightDistance.scale.set( d, d, d ); + + } + */ + +}; + + +/** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + * @author WestLangley / http://github.com/WestLangley +*/ + +THREE.SpotLightHelper = function ( light ) { + + THREE.Object3D.call( this ); + + this.light = light; + this.light.updateMatrixWorld(); + + this.matrixWorld = light.matrixWorld; + this.matrixAutoUpdate = false; + + var geometry = new THREE.CylinderGeometry( 0, 1, 1, 8, 1, true ); + + geometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, -0.5, 0 ) ); + geometry.applyMatrix( new THREE.Matrix4().makeRotationX( - Math.PI / 2 ) ); + + var material = new THREE.MeshBasicMaterial( { wireframe: true, fog: false } ); + + this.cone = new THREE.Mesh( geometry, material ); + this.add( this.cone ); + + this.update(); + +}; + +THREE.SpotLightHelper.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.SpotLightHelper.prototype.dispose = function () { + this.cone.geometry.dispose(); + this.cone.material.dispose(); +}; + +THREE.SpotLightHelper.prototype.update = function () { + + var vector = new THREE.Vector3(); + var vector2 = new THREE.Vector3(); + + return function () { + + var coneLength = this.light.distance ? this.light.distance : 10000; + var coneWidth = coneLength * Math.tan( this.light.angle ); + + this.cone.scale.set( coneWidth, coneWidth, coneLength ); + + vector.setFromMatrixPosition( this.light.matrixWorld ); + vector2.setFromMatrixPosition( this.light.target.matrixWorld ); + + this.cone.lookAt( vector2.sub( vector ) ); + + this.cone.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity ); + + }; + +}(); + +/** + * @author mrdoob / http://mrdoob.com/ + * @author WestLangley / http://github.com/WestLangley +*/ + +THREE.VertexNormalsHelper = function ( object, size, hex, linewidth ) { + + this.object = object; + + this.size = ( size !== undefined ) ? size : 1; + + var color = ( hex !== undefined ) ? hex : 0xff0000; + + var width = ( linewidth !== undefined ) ? linewidth : 1; + + var geometry = new THREE.Geometry(); + + var vertices = object.geometry.vertices; + + var faces = object.geometry.faces; + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + var face = faces[ i ]; + + for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) { + + geometry.vertices.push( new THREE.Vector3() ); + geometry.vertices.push( new THREE.Vector3() ); + + } + + } + + THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: color, linewidth: width } ), THREE.LinePieces ); + + this.matrixAutoUpdate = false; + + this.normalMatrix = new THREE.Matrix3(); + + this.update(); + +}; + +THREE.VertexNormalsHelper.prototype = Object.create( THREE.Line.prototype ); + +THREE.VertexNormalsHelper.prototype.update = ( function ( object ) { + + var v1 = new THREE.Vector3(); + + return function( object ) { + + var keys = [ 'a', 'b', 'c', 'd' ]; + + this.object.updateMatrixWorld( true ); + + this.normalMatrix.getNormalMatrix( this.object.matrixWorld ); + + var vertices = this.geometry.vertices; + + var verts = this.object.geometry.vertices; + + var faces = this.object.geometry.faces; + + var worldMatrix = this.object.matrixWorld; + + var idx = 0; + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + var face = faces[ i ]; + + for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) { + + var vertexId = face[ keys[ j ] ]; + var vertex = verts[ vertexId ]; + + var normal = face.vertexNormals[ j ]; + + vertices[ idx ].copy( vertex ).applyMatrix4( worldMatrix ); + + v1.copy( normal ).applyMatrix3( this.normalMatrix ).normalize().multiplyScalar( this.size ); + + v1.add( vertices[ idx ] ); + idx = idx + 1; + + vertices[ idx ].copy( v1 ); + idx = idx + 1; + + } + + } + + this.geometry.verticesNeedUpdate = true; + + return this; + + } + +}()); + +/** + * @author mrdoob / http://mrdoob.com/ + * @author WestLangley / http://github.com/WestLangley +*/ + +THREE.VertexTangentsHelper = function ( object, size, hex, linewidth ) { + + this.object = object; + + this.size = ( size !== undefined ) ? size : 1; + + var color = ( hex !== undefined ) ? hex : 0x0000ff; + + var width = ( linewidth !== undefined ) ? linewidth : 1; + + var geometry = new THREE.Geometry(); + + var vertices = object.geometry.vertices; + + var faces = object.geometry.faces; + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + var face = faces[ i ]; + + for ( var j = 0, jl = face.vertexTangents.length; j < jl; j ++ ) { + + geometry.vertices.push( new THREE.Vector3() ); + geometry.vertices.push( new THREE.Vector3() ); + + } + + } + + THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: color, linewidth: width } ), THREE.LinePieces ); + + this.matrixAutoUpdate = false; + + this.update(); + +}; + +THREE.VertexTangentsHelper.prototype = Object.create( THREE.Line.prototype ); + +THREE.VertexTangentsHelper.prototype.update = ( function ( object ) { + + var v1 = new THREE.Vector3(); + + return function( object ) { + + var keys = [ 'a', 'b', 'c', 'd' ]; + + this.object.updateMatrixWorld( true ); + + var vertices = this.geometry.vertices; + + var verts = this.object.geometry.vertices; + + var faces = this.object.geometry.faces; + + var worldMatrix = this.object.matrixWorld; + + var idx = 0; + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + var face = faces[ i ]; + + for ( var j = 0, jl = face.vertexTangents.length; j < jl; j ++ ) { + + var vertexId = face[ keys[ j ] ]; + var vertex = verts[ vertexId ]; + + var tangent = face.vertexTangents[ j ]; + + vertices[ idx ].copy( vertex ).applyMatrix4( worldMatrix ); + + v1.copy( tangent ).transformDirection( worldMatrix ).multiplyScalar( this.size ); + + v1.add( vertices[ idx ] ); + idx = idx + 1; + + vertices[ idx ].copy( v1 ); + idx = idx + 1; + + } + + } + + this.geometry.verticesNeedUpdate = true; + + return this; + + } + +}()); + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.WireframeHelper = function ( object, hex ) { + + var color = ( hex !== undefined ) ? hex : 0xffffff; + + var edge = [ 0, 0 ], hash = {}; + var sortFunction = function ( a, b ) { return a - b }; + + var keys = [ 'a', 'b', 'c' ]; + var geometry = new THREE.BufferGeometry(); + + if ( object.geometry instanceof THREE.Geometry ) { + + var vertices = object.geometry.vertices; + var faces = object.geometry.faces; + var numEdges = 0; + + // allocate maximal size + var edges = new Uint32Array( 6 * faces.length ); + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + var face = faces[ i ]; + + for ( var j = 0; j < 3; j ++ ) { + + edge[ 0 ] = face[ keys[ j ] ]; + edge[ 1 ] = face[ keys[ ( j + 1 ) % 3 ] ]; + edge.sort( sortFunction ); + + var key = edge.toString(); + + if ( hash[ key ] === undefined ) { + + edges[ 2 * numEdges ] = edge[ 0 ]; + edges[ 2 * numEdges + 1 ] = edge[ 1 ]; + hash[ key ] = true; + numEdges ++; + + } + + } + + } + + geometry.addAttribute( 'position', Float32Array, 2 * numEdges, 3 ); + + var coords = geometry.attributes.position.array; + + for ( var i = 0, l = numEdges; i < l; i ++ ) { + + for ( var j = 0; j < 2; j ++ ) { + + var vertex = vertices[ edges [ 2 * i + j] ]; + + var index = 6 * i + 3 * j; + coords[ index + 0 ] = vertex.x; + coords[ index + 1 ] = vertex.y; + coords[ index + 2 ] = vertex.z; + + } + + } + + } else if ( object.geometry instanceof THREE.BufferGeometry && object.geometry.attributes.index !== undefined ) { // indexed BufferGeometry + + var vertices = object.geometry.attributes.position.array; + var indices = object.geometry.attributes.index.array; + var offsets = object.geometry.offsets; + var numEdges = 0; + + // allocate maximal size + var edges = new Uint32Array( 2 * indices.length ); + + for ( var o = 0, ol = offsets.length; o < ol; ++ o ) { + + var start = offsets[ o ].start; + var count = offsets[ o ].count; + var index = offsets[ o ].index; + + for ( var i = start, il = start + count; i < il; i += 3 ) { + + for ( var j = 0; j < 3; j ++ ) { + + edge[ 0 ] = index + indices[ i + j ]; + edge[ 1 ] = index + indices[ i + ( j + 1 ) % 3 ]; + edge.sort( sortFunction ); + + var key = edge.toString(); + + if ( hash[ key ] === undefined ) { + + edges[ 2 * numEdges ] = edge[ 0 ]; + edges[ 2 * numEdges + 1 ] = edge[ 1 ]; + hash[ key ] = true; + numEdges ++; + + } + + } + + } + + } + + geometry.addAttribute( 'position', Float32Array, 2 * numEdges, 3 ); + + var coords = geometry.attributes.position.array; + + for ( var i = 0, l = numEdges; i < l; i ++ ) { + + for ( var j = 0; j < 2; j ++ ) { + + var index = 6 * i + 3 * j; + var index2 = 3 * edges[ 2 * i + j]; + coords[ index + 0 ] = vertices[ index2 ]; + coords[ index + 1 ] = vertices[ index2 + 1 ]; + coords[ index + 2 ] = vertices[ index2 + 2 ]; + + } + + } + + } else if ( object.geometry instanceof THREE.BufferGeometry ) { // non-indexed BufferGeometry + + var vertices = object.geometry.attributes.position.array; + var numEdges = vertices.length / 3; + var numTris = numEdges / 3; + + geometry.addAttribute( 'position', Float32Array, 2 * numEdges, 3 ); + + var coords = geometry.attributes.position.array; + + for ( var i = 0, l = numTris; i < l; i ++ ) { + + for ( var j = 0; j < 3; j ++ ) { + + var index = 18 * i + 6 * j; + + var index1 = 9 * i + 3 * j; + coords[ index + 0 ] = vertices[ index1 ]; + coords[ index + 1 ] = vertices[ index1 + 1 ]; + coords[ index + 2 ] = vertices[ index1 + 2 ]; + + var index2 = 9 * i + 3 * ( ( j + 1 ) % 3 ); + coords[ index + 3 ] = vertices[ index2 ]; + coords[ index + 4 ] = vertices[ index2 + 1 ]; + coords[ index + 5 ] = vertices[ index2 + 2 ]; + + } + + } + + } + + THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: color } ), THREE.LinePieces ); + + this.matrixAutoUpdate = false; + this.matrixWorld = object.matrixWorld; + +}; + +THREE.WireframeHelper.prototype = Object.create( THREE.Line.prototype ); + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.ImmediateRenderObject = function () { + + THREE.Object3D.call( this ); + + this.render = function ( renderCallback ) { }; + +}; + +THREE.ImmediateRenderObject.prototype = Object.create( THREE.Object3D.prototype ); + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.LensFlare = function ( texture, size, distance, blending, color ) { + + THREE.Object3D.call( this ); + + this.lensFlares = []; + + this.positionScreen = new THREE.Vector3(); + this.customUpdateCallback = undefined; + + if( texture !== undefined ) { + + this.add( texture, size, distance, blending, color ); + + } + +}; + +THREE.LensFlare.prototype = Object.create( THREE.Object3D.prototype ); + + +/* + * Add: adds another flare + */ + +THREE.LensFlare.prototype.add = function ( texture, size, distance, blending, color, opacity ) { + + if( size === undefined ) size = -1; + if( distance === undefined ) distance = 0; + if( opacity === undefined ) opacity = 1; + if( color === undefined ) color = new THREE.Color( 0xffffff ); + if( blending === undefined ) blending = THREE.NormalBlending; + + distance = Math.min( distance, Math.max( 0, distance ) ); + + this.lensFlares.push( { texture: texture, // THREE.Texture + size: size, // size in pixels (-1 = use texture.width) + distance: distance, // distance (0-1) from light source (0=at light source) + x: 0, y: 0, z: 0, // screen position (-1 => 1) z = 0 is ontop z = 1 is back + scale: 1, // scale + rotation: 1, // rotation + opacity: opacity, // opacity + color: color, // color + blending: blending } ); // blending + +}; + + +/* + * Update lens flares update positions on all flares based on the screen position + * Set myLensFlare.customUpdateCallback to alter the flares in your project specific way. + */ + +THREE.LensFlare.prototype.updateLensFlares = function () { + + var f, fl = this.lensFlares.length; + var flare; + var vecX = -this.positionScreen.x * 2; + var vecY = -this.positionScreen.y * 2; + + for( f = 0; f < fl; f ++ ) { + + flare = this.lensFlares[ f ]; + + flare.x = this.positionScreen.x + vecX * flare.distance; + flare.y = this.positionScreen.y + vecY * flare.distance; + + flare.wantedRotation = flare.x * Math.PI * 0.25; + flare.rotation += ( flare.wantedRotation - flare.rotation ) * 0.25; + + } + +}; + + + + + + + + + + + + + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.MorphBlendMesh = function( geometry, material ) { + + THREE.Mesh.call( this, geometry, material ); + + this.animationsMap = {}; + this.animationsList = []; + + // prepare default animation + // (all frames played together in 1 second) + + var numFrames = this.geometry.morphTargets.length; + + var name = "__default"; + + var startFrame = 0; + var endFrame = numFrames - 1; + + var fps = numFrames / 1; + + this.createAnimation( name, startFrame, endFrame, fps ); + this.setAnimationWeight( name, 1 ); + +}; + +THREE.MorphBlendMesh.prototype = Object.create( THREE.Mesh.prototype ); + +THREE.MorphBlendMesh.prototype.createAnimation = function ( name, start, end, fps ) { + + var animation = { + + startFrame: start, + endFrame: end, + + length: end - start + 1, + + fps: fps, + duration: ( end - start ) / fps, + + lastFrame: 0, + currentFrame: 0, + + active: false, + + time: 0, + direction: 1, + weight: 1, + + directionBackwards: false, + mirroredLoop: false + + }; + + this.animationsMap[ name ] = animation; + this.animationsList.push( animation ); + +}; + +THREE.MorphBlendMesh.prototype.autoCreateAnimations = function ( fps ) { + + var pattern = /([a-z]+)(\d+)/; + + var firstAnimation, frameRanges = {}; + + var geometry = this.geometry; + + for ( var i = 0, il = geometry.morphTargets.length; i < il; i ++ ) { + + var morph = geometry.morphTargets[ i ]; + var chunks = morph.name.match( pattern ); + + if ( chunks && chunks.length > 1 ) { + + var name = chunks[ 1 ]; + var num = chunks[ 2 ]; + + if ( ! frameRanges[ name ] ) frameRanges[ name ] = { start: Infinity, end: -Infinity }; + + var range = frameRanges[ name ]; + + if ( i < range.start ) range.start = i; + if ( i > range.end ) range.end = i; + + if ( ! firstAnimation ) firstAnimation = name; + + } + + } + + for ( var name in frameRanges ) { + + var range = frameRanges[ name ]; + this.createAnimation( name, range.start, range.end, fps ); + + } + + this.firstAnimation = firstAnimation; + +}; + +THREE.MorphBlendMesh.prototype.setAnimationDirectionForward = function ( name ) { + + var animation = this.animationsMap[ name ]; + + if ( animation ) { + + animation.direction = 1; + animation.directionBackwards = false; + + } + +}; + +THREE.MorphBlendMesh.prototype.setAnimationDirectionBackward = function ( name ) { + + var animation = this.animationsMap[ name ]; + + if ( animation ) { + + animation.direction = -1; + animation.directionBackwards = true; + + } + +}; + +THREE.MorphBlendMesh.prototype.setAnimationFPS = function ( name, fps ) { + + var animation = this.animationsMap[ name ]; + + if ( animation ) { + + animation.fps = fps; + animation.duration = ( animation.end - animation.start ) / animation.fps; + + } + +}; + +THREE.MorphBlendMesh.prototype.setAnimationDuration = function ( name, duration ) { + + var animation = this.animationsMap[ name ]; + + if ( animation ) { + + animation.duration = duration; + animation.fps = ( animation.end - animation.start ) / animation.duration; + + } + +}; + +THREE.MorphBlendMesh.prototype.setAnimationWeight = function ( name, weight ) { + + var animation = this.animationsMap[ name ]; + + if ( animation ) { + + animation.weight = weight; + + } + +}; + +THREE.MorphBlendMesh.prototype.setAnimationTime = function ( name, time ) { + + var animation = this.animationsMap[ name ]; + + if ( animation ) { + + animation.time = time; + + } + +}; + +THREE.MorphBlendMesh.prototype.getAnimationTime = function ( name ) { + + var time = 0; + + var animation = this.animationsMap[ name ]; + + if ( animation ) { + + time = animation.time; + + } + + return time; + +}; + +THREE.MorphBlendMesh.prototype.getAnimationDuration = function ( name ) { + + var duration = -1; + + var animation = this.animationsMap[ name ]; + + if ( animation ) { + + duration = animation.duration; + + } + + return duration; + +}; + +THREE.MorphBlendMesh.prototype.playAnimation = function ( name ) { + + var animation = this.animationsMap[ name ]; + + if ( animation ) { + + animation.time = 0; + animation.active = true; + + } else { + + console.warn( "animation[" + name + "] undefined" ); + + } + +}; + +THREE.MorphBlendMesh.prototype.stopAnimation = function ( name ) { + + var animation = this.animationsMap[ name ]; + + if ( animation ) { + + animation.active = false; + + } + +}; + +THREE.MorphBlendMesh.prototype.update = function ( delta ) { + + for ( var i = 0, il = this.animationsList.length; i < il; i ++ ) { + + var animation = this.animationsList[ i ]; + + if ( ! animation.active ) continue; + + var frameTime = animation.duration / animation.length; + + animation.time += animation.direction * delta; + + if ( animation.mirroredLoop ) { + + if ( animation.time > animation.duration || animation.time < 0 ) { + + animation.direction *= -1; + + if ( animation.time > animation.duration ) { + + animation.time = animation.duration; + animation.directionBackwards = true; + + } + + if ( animation.time < 0 ) { + + animation.time = 0; + animation.directionBackwards = false; + + } + + } + + } else { + + animation.time = animation.time % animation.duration; + + if ( animation.time < 0 ) animation.time += animation.duration; + + } + + var keyframe = animation.startFrame + THREE.Math.clamp( Math.floor( animation.time / frameTime ), 0, animation.length - 1 ); + var weight = animation.weight; + + if ( keyframe !== animation.currentFrame ) { + + this.morphTargetInfluences[ animation.lastFrame ] = 0; + this.morphTargetInfluences[ animation.currentFrame ] = 1 * weight; + + this.morphTargetInfluences[ keyframe ] = 0; + + animation.lastFrame = animation.currentFrame; + animation.currentFrame = keyframe; + + } + + var mix = ( animation.time % frameTime ) / frameTime; + + if ( animation.directionBackwards ) mix = 1 - mix; + + this.morphTargetInfluences[ animation.currentFrame ] = mix * weight; + this.morphTargetInfluences[ animation.lastFrame ] = ( 1 - mix ) * weight; + + } + +}; + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.LensFlarePlugin = function () { + + var _gl, _renderer, _precision, _lensFlare = {}; + + this.init = function ( renderer ) { + + _gl = renderer.context; + _renderer = renderer; + + _precision = renderer.getPrecision(); + + _lensFlare.vertices = new Float32Array( 8 + 8 ); + _lensFlare.faces = new Uint16Array( 6 ); + + var i = 0; + _lensFlare.vertices[ i++ ] = -1; _lensFlare.vertices[ i++ ] = -1; // vertex + _lensFlare.vertices[ i++ ] = 0; _lensFlare.vertices[ i++ ] = 0; // uv... etc. + + _lensFlare.vertices[ i++ ] = 1; _lensFlare.vertices[ i++ ] = -1; + _lensFlare.vertices[ i++ ] = 1; _lensFlare.vertices[ i++ ] = 0; + + _lensFlare.vertices[ i++ ] = 1; _lensFlare.vertices[ i++ ] = 1; + _lensFlare.vertices[ i++ ] = 1; _lensFlare.vertices[ i++ ] = 1; + + _lensFlare.vertices[ i++ ] = -1; _lensFlare.vertices[ i++ ] = 1; + _lensFlare.vertices[ i++ ] = 0; _lensFlare.vertices[ i++ ] = 1; + + i = 0; + _lensFlare.faces[ i++ ] = 0; _lensFlare.faces[ i++ ] = 1; _lensFlare.faces[ i++ ] = 2; + _lensFlare.faces[ i++ ] = 0; _lensFlare.faces[ i++ ] = 2; _lensFlare.faces[ i++ ] = 3; + + // buffers + + _lensFlare.vertexBuffer = _gl.createBuffer(); + _lensFlare.elementBuffer = _gl.createBuffer(); + + _gl.bindBuffer( _gl.ARRAY_BUFFER, _lensFlare.vertexBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, _lensFlare.vertices, _gl.STATIC_DRAW ); + + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, _lensFlare.elementBuffer ); + _gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, _lensFlare.faces, _gl.STATIC_DRAW ); + + // textures + + _lensFlare.tempTexture = _gl.createTexture(); + _lensFlare.occlusionTexture = _gl.createTexture(); + + _gl.bindTexture( _gl.TEXTURE_2D, _lensFlare.tempTexture ); + _gl.texImage2D( _gl.TEXTURE_2D, 0, _gl.RGB, 16, 16, 0, _gl.RGB, _gl.UNSIGNED_BYTE, null ); + _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_MAG_FILTER, _gl.NEAREST ); + _gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_MIN_FILTER, _gl.NEAREST ); + + _gl.bindTexture( _gl.TEXTURE_2D, _lensFlare.occlusionTexture ); + _gl.texImage2D( _gl.TEXTURE_2D, 0, _gl.RGBA, 16, 16, 0, _gl.RGBA, _gl.UNSIGNED_BYTE, null ); + _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_MAG_FILTER, _gl.NEAREST ); + _gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_MIN_FILTER, _gl.NEAREST ); + + if ( _gl.getParameter( _gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ) <= 0 ) { + + _lensFlare.hasVertexTexture = false; + _lensFlare.program = createProgram( THREE.ShaderFlares[ "lensFlare" ], _precision ); + + } else { + + _lensFlare.hasVertexTexture = true; + _lensFlare.program = createProgram( THREE.ShaderFlares[ "lensFlareVertexTexture" ], _precision ); + + } + + _lensFlare.attributes = {}; + _lensFlare.uniforms = {}; + + _lensFlare.attributes.vertex = _gl.getAttribLocation ( _lensFlare.program, "position" ); + _lensFlare.attributes.uv = _gl.getAttribLocation ( _lensFlare.program, "uv" ); + + _lensFlare.uniforms.renderType = _gl.getUniformLocation( _lensFlare.program, "renderType" ); + _lensFlare.uniforms.map = _gl.getUniformLocation( _lensFlare.program, "map" ); + _lensFlare.uniforms.occlusionMap = _gl.getUniformLocation( _lensFlare.program, "occlusionMap" ); + _lensFlare.uniforms.opacity = _gl.getUniformLocation( _lensFlare.program, "opacity" ); + _lensFlare.uniforms.color = _gl.getUniformLocation( _lensFlare.program, "color" ); + _lensFlare.uniforms.scale = _gl.getUniformLocation( _lensFlare.program, "scale" ); + _lensFlare.uniforms.rotation = _gl.getUniformLocation( _lensFlare.program, "rotation" ); + _lensFlare.uniforms.screenPosition = _gl.getUniformLocation( _lensFlare.program, "screenPosition" ); + + }; + + + /* + * Render lens flares + * Method: renders 16x16 0xff00ff-colored points scattered over the light source area, + * reads these back and calculates occlusion. + * Then _lensFlare.update_lensFlares() is called to re-position and + * update transparency of flares. Then they are rendered. + * + */ + + this.render = function ( scene, camera, viewportWidth, viewportHeight ) { + + var flares = scene.__webglFlares, + nFlares = flares.length; + + if ( ! nFlares ) return; + + var tempPosition = new THREE.Vector3(); + + var invAspect = viewportHeight / viewportWidth, + halfViewportWidth = viewportWidth * 0.5, + halfViewportHeight = viewportHeight * 0.5; + + var size = 16 / viewportHeight, + scale = new THREE.Vector2( size * invAspect, size ); + + var screenPosition = new THREE.Vector3( 1, 1, 0 ), + screenPositionPixels = new THREE.Vector2( 1, 1 ); + + var uniforms = _lensFlare.uniforms, + attributes = _lensFlare.attributes; + + // set _lensFlare program and reset blending + + _gl.useProgram( _lensFlare.program ); + + _gl.enableVertexAttribArray( _lensFlare.attributes.vertex ); + _gl.enableVertexAttribArray( _lensFlare.attributes.uv ); + + // loop through all lens flares to update their occlusion and positions + // setup gl and common used attribs/unforms + + _gl.uniform1i( uniforms.occlusionMap, 0 ); + _gl.uniform1i( uniforms.map, 1 ); + + _gl.bindBuffer( _gl.ARRAY_BUFFER, _lensFlare.vertexBuffer ); + _gl.vertexAttribPointer( attributes.vertex, 2, _gl.FLOAT, false, 2 * 8, 0 ); + _gl.vertexAttribPointer( attributes.uv, 2, _gl.FLOAT, false, 2 * 8, 8 ); + + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, _lensFlare.elementBuffer ); + + _gl.disable( _gl.CULL_FACE ); + _gl.depthMask( false ); + + var i, j, jl, flare, sprite; + + for ( i = 0; i < nFlares; i ++ ) { + + size = 16 / viewportHeight; + scale.set( size * invAspect, size ); + + // calc object screen position + + flare = flares[ i ]; + + tempPosition.set( flare.matrixWorld.elements[12], flare.matrixWorld.elements[13], flare.matrixWorld.elements[14] ); + + tempPosition.applyMatrix4( camera.matrixWorldInverse ); + tempPosition.applyProjection( camera.projectionMatrix ); + + // setup arrays for gl programs + + screenPosition.copy( tempPosition ) + + screenPositionPixels.x = screenPosition.x * halfViewportWidth + halfViewportWidth; + screenPositionPixels.y = screenPosition.y * halfViewportHeight + halfViewportHeight; + + // screen cull + + if ( _lensFlare.hasVertexTexture || ( + screenPositionPixels.x > 0 && + screenPositionPixels.x < viewportWidth && + screenPositionPixels.y > 0 && + screenPositionPixels.y < viewportHeight ) ) { + + // save current RGB to temp texture + + _gl.activeTexture( _gl.TEXTURE1 ); + _gl.bindTexture( _gl.TEXTURE_2D, _lensFlare.tempTexture ); + _gl.copyTexImage2D( _gl.TEXTURE_2D, 0, _gl.RGB, screenPositionPixels.x - 8, screenPositionPixels.y - 8, 16, 16, 0 ); + + + // render pink quad + + _gl.uniform1i( uniforms.renderType, 0 ); + _gl.uniform2f( uniforms.scale, scale.x, scale.y ); + _gl.uniform3f( uniforms.screenPosition, screenPosition.x, screenPosition.y, screenPosition.z ); + + _gl.disable( _gl.BLEND ); + _gl.enable( _gl.DEPTH_TEST ); + + _gl.drawElements( _gl.TRIANGLES, 6, _gl.UNSIGNED_SHORT, 0 ); + + + // copy result to occlusionMap + + _gl.activeTexture( _gl.TEXTURE0 ); + _gl.bindTexture( _gl.TEXTURE_2D, _lensFlare.occlusionTexture ); + _gl.copyTexImage2D( _gl.TEXTURE_2D, 0, _gl.RGBA, screenPositionPixels.x - 8, screenPositionPixels.y - 8, 16, 16, 0 ); + + + // restore graphics + + _gl.uniform1i( uniforms.renderType, 1 ); + _gl.disable( _gl.DEPTH_TEST ); + + _gl.activeTexture( _gl.TEXTURE1 ); + _gl.bindTexture( _gl.TEXTURE_2D, _lensFlare.tempTexture ); + _gl.drawElements( _gl.TRIANGLES, 6, _gl.UNSIGNED_SHORT, 0 ); + + + // update object positions + + flare.positionScreen.copy( screenPosition ) + + if ( flare.customUpdateCallback ) { + + flare.customUpdateCallback( flare ); + + } else { + + flare.updateLensFlares(); + + } + + // render flares + + _gl.uniform1i( uniforms.renderType, 2 ); + _gl.enable( _gl.BLEND ); + + for ( j = 0, jl = flare.lensFlares.length; j < jl; j ++ ) { + + sprite = flare.lensFlares[ j ]; + + if ( sprite.opacity > 0.001 && sprite.scale > 0.001 ) { + + screenPosition.x = sprite.x; + screenPosition.y = sprite.y; + screenPosition.z = sprite.z; + + size = sprite.size * sprite.scale / viewportHeight; + + scale.x = size * invAspect; + scale.y = size; + + _gl.uniform3f( uniforms.screenPosition, screenPosition.x, screenPosition.y, screenPosition.z ); + _gl.uniform2f( uniforms.scale, scale.x, scale.y ); + _gl.uniform1f( uniforms.rotation, sprite.rotation ); + + _gl.uniform1f( uniforms.opacity, sprite.opacity ); + _gl.uniform3f( uniforms.color, sprite.color.r, sprite.color.g, sprite.color.b ); + + _renderer.setBlending( sprite.blending, sprite.blendEquation, sprite.blendSrc, sprite.blendDst ); + _renderer.setTexture( sprite.texture, 1 ); + + _gl.drawElements( _gl.TRIANGLES, 6, _gl.UNSIGNED_SHORT, 0 ); + + } + + } + + } + + } + + // restore gl + + _gl.enable( _gl.CULL_FACE ); + _gl.enable( _gl.DEPTH_TEST ); + _gl.depthMask( true ); + + }; + + function createProgram ( shader, precision ) { + + var program = _gl.createProgram(); + + var fragmentShader = _gl.createShader( _gl.FRAGMENT_SHADER ); + var vertexShader = _gl.createShader( _gl.VERTEX_SHADER ); + + var prefix = "precision " + precision + " float;\n"; + + _gl.shaderSource( fragmentShader, prefix + shader.fragmentShader ); + _gl.shaderSource( vertexShader, prefix + shader.vertexShader ); + + _gl.compileShader( fragmentShader ); + _gl.compileShader( vertexShader ); + + _gl.attachShader( program, fragmentShader ); + _gl.attachShader( program, vertexShader ); + + _gl.linkProgram( program ); + + return program; + + }; + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.ShadowMapPlugin = function () { + + var _gl, + _renderer, + _depthMaterial, _depthMaterialMorph, _depthMaterialSkin, _depthMaterialMorphSkin, + + _frustum = new THREE.Frustum(), + _projScreenMatrix = new THREE.Matrix4(), + + _min = new THREE.Vector3(), + _max = new THREE.Vector3(), + + _matrixPosition = new THREE.Vector3(); + + this.init = function ( renderer ) { + + _gl = renderer.context; + _renderer = renderer; + + var depthShader = THREE.ShaderLib[ "depthRGBA" ]; + var depthUniforms = THREE.UniformsUtils.clone( depthShader.uniforms ); + + _depthMaterial = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms } ); + _depthMaterialMorph = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, morphTargets: true } ); + _depthMaterialSkin = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, skinning: true } ); + _depthMaterialMorphSkin = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, morphTargets: true, skinning: true } ); + + _depthMaterial._shadowPass = true; + _depthMaterialMorph._shadowPass = true; + _depthMaterialSkin._shadowPass = true; + _depthMaterialMorphSkin._shadowPass = true; + + }; + + this.render = function ( scene, camera ) { + + if ( ! ( _renderer.shadowMapEnabled && _renderer.shadowMapAutoUpdate ) ) return; + + this.update( scene, camera ); + + }; + + this.update = function ( scene, camera ) { + + var i, il, j, jl, n, + + shadowMap, shadowMatrix, shadowCamera, + program, buffer, material, + webglObject, object, light, + renderList, + + lights = [], + k = 0, + + fog = null; + + // set GL state for depth map + + _gl.clearColor( 1, 1, 1, 1 ); + _gl.disable( _gl.BLEND ); + + _gl.enable( _gl.CULL_FACE ); + _gl.frontFace( _gl.CCW ); + + if ( _renderer.shadowMapCullFace === THREE.CullFaceFront ) { + + _gl.cullFace( _gl.FRONT ); + + } else { + + _gl.cullFace( _gl.BACK ); + + } + + _renderer.setDepthTest( true ); + + // preprocess lights + // - skip lights that are not casting shadows + // - create virtual lights for cascaded shadow maps + + for ( i = 0, il = scene.__lights.length; i < il; i ++ ) { + + light = scene.__lights[ i ]; + + if ( ! light.castShadow ) continue; + + if ( ( light instanceof THREE.DirectionalLight ) && light.shadowCascade ) { + + for ( n = 0; n < light.shadowCascadeCount; n ++ ) { + + var virtualLight; + + if ( ! light.shadowCascadeArray[ n ] ) { + + virtualLight = createVirtualLight( light, n ); + virtualLight.originalCamera = camera; + + var gyro = new THREE.Gyroscope(); + gyro.position = light.shadowCascadeOffset; + + gyro.add( virtualLight ); + gyro.add( virtualLight.target ); + + camera.add( gyro ); + + light.shadowCascadeArray[ n ] = virtualLight; + + console.log( "Created virtualLight", virtualLight ); + + } else { + + virtualLight = light.shadowCascadeArray[ n ]; + + } + + updateVirtualLight( light, n ); + + lights[ k ] = virtualLight; + k ++; + + } + + } else { + + lights[ k ] = light; + k ++; + + } + + } + + // render depth map + + for ( i = 0, il = lights.length; i < il; i ++ ) { + + light = lights[ i ]; + + if ( ! light.shadowMap ) { + + var shadowFilter = THREE.LinearFilter; + + if ( _renderer.shadowMapType === THREE.PCFSoftShadowMap ) { + + shadowFilter = THREE.NearestFilter; + + } + + var pars = { minFilter: shadowFilter, magFilter: shadowFilter, format: THREE.RGBAFormat }; + + light.shadowMap = new THREE.WebGLRenderTarget( light.shadowMapWidth, light.shadowMapHeight, pars ); + light.shadowMapSize = new THREE.Vector2( light.shadowMapWidth, light.shadowMapHeight ); + + light.shadowMatrix = new THREE.Matrix4(); + + } + + if ( ! light.shadowCamera ) { + + if ( light instanceof THREE.SpotLight ) { + + light.shadowCamera = new THREE.PerspectiveCamera( light.shadowCameraFov, light.shadowMapWidth / light.shadowMapHeight, light.shadowCameraNear, light.shadowCameraFar ); + + } else if ( light instanceof THREE.DirectionalLight ) { + + light.shadowCamera = new THREE.OrthographicCamera( light.shadowCameraLeft, light.shadowCameraRight, light.shadowCameraTop, light.shadowCameraBottom, light.shadowCameraNear, light.shadowCameraFar ); + + } else { + + console.error( "Unsupported light type for shadow" ); + continue; + + } + + scene.add( light.shadowCamera ); + + if ( scene.autoUpdate === true ) scene.updateMatrixWorld(); + + } + + if ( light.shadowCameraVisible && ! light.cameraHelper ) { + + light.cameraHelper = new THREE.CameraHelper( light.shadowCamera ); + light.shadowCamera.add( light.cameraHelper ); + + } + + if ( light.isVirtual && virtualLight.originalCamera == camera ) { + + updateShadowCamera( camera, light ); + + } + + shadowMap = light.shadowMap; + shadowMatrix = light.shadowMatrix; + shadowCamera = light.shadowCamera; + + shadowCamera.position.setFromMatrixPosition( light.matrixWorld ); + _matrixPosition.setFromMatrixPosition( light.target.matrixWorld ); + shadowCamera.lookAt( _matrixPosition ); + shadowCamera.updateMatrixWorld(); + + shadowCamera.matrixWorldInverse.getInverse( shadowCamera.matrixWorld ); + + if ( light.cameraHelper ) light.cameraHelper.visible = light.shadowCameraVisible; + if ( light.shadowCameraVisible ) light.cameraHelper.update(); + + // compute shadow matrix + + shadowMatrix.set( 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0 ); + + shadowMatrix.multiply( shadowCamera.projectionMatrix ); + shadowMatrix.multiply( shadowCamera.matrixWorldInverse ); + + // update camera matrices and frustum + + _projScreenMatrix.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse ); + _frustum.setFromMatrix( _projScreenMatrix ); + + // render shadow map + + _renderer.setRenderTarget( shadowMap ); + _renderer.clear(); + + // set object matrices & frustum culling + + renderList = scene.__webglObjects; + + for ( j = 0, jl = renderList.length; j < jl; j ++ ) { + + webglObject = renderList[ j ]; + object = webglObject.object; + + webglObject.render = false; + + if ( object.visible && object.castShadow ) { + + if ( ! ( object instanceof THREE.Mesh || object instanceof THREE.ParticleSystem ) || ! ( object.frustumCulled ) || _frustum.intersectsObject( object ) ) { + + object._modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld ); + + webglObject.render = true; + + } + + } + + } + + // render regular objects + + var objectMaterial, useMorphing, useSkinning; + + for ( j = 0, jl = renderList.length; j < jl; j ++ ) { + + webglObject = renderList[ j ]; + + if ( webglObject.render ) { + + object = webglObject.object; + buffer = webglObject.buffer; + + // culling is overriden globally for all objects + // while rendering depth map + + // need to deal with MeshFaceMaterial somehow + // in that case just use the first of material.materials for now + // (proper solution would require to break objects by materials + // similarly to regular rendering and then set corresponding + // depth materials per each chunk instead of just once per object) + + objectMaterial = getObjectMaterial( object ); + + useMorphing = object.geometry.morphTargets !== undefined && object.geometry.morphTargets.length > 0 && objectMaterial.morphTargets; + useSkinning = object instanceof THREE.SkinnedMesh && objectMaterial.skinning; + + if ( object.customDepthMaterial ) { + + material = object.customDepthMaterial; + + } else if ( useSkinning ) { + + material = useMorphing ? _depthMaterialMorphSkin : _depthMaterialSkin; + + } else if ( useMorphing ) { + + material = _depthMaterialMorph; + + } else { + + material = _depthMaterial; + + } + + if ( buffer instanceof THREE.BufferGeometry ) { + + _renderer.renderBufferDirect( shadowCamera, scene.__lights, fog, material, buffer, object ); + + } else { + + _renderer.renderBuffer( shadowCamera, scene.__lights, fog, material, buffer, object ); + + } + + } + + } + + // set matrices and render immediate objects + + renderList = scene.__webglObjectsImmediate; + + for ( j = 0, jl = renderList.length; j < jl; j ++ ) { + + webglObject = renderList[ j ]; + object = webglObject.object; + + if ( object.visible && object.castShadow ) { + + object._modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld ); + + _renderer.renderImmediateObject( shadowCamera, scene.__lights, fog, _depthMaterial, object ); + + } + + } + + } + + // restore GL state + + var clearColor = _renderer.getClearColor(), + clearAlpha = _renderer.getClearAlpha(); + + _gl.clearColor( clearColor.r, clearColor.g, clearColor.b, clearAlpha ); + _gl.enable( _gl.BLEND ); + + if ( _renderer.shadowMapCullFace === THREE.CullFaceFront ) { + + _gl.cullFace( _gl.BACK ); + + } + + }; + + function createVirtualLight( light, cascade ) { + + var virtualLight = new THREE.DirectionalLight(); + + virtualLight.isVirtual = true; + + virtualLight.onlyShadow = true; + virtualLight.castShadow = true; + + virtualLight.shadowCameraNear = light.shadowCameraNear; + virtualLight.shadowCameraFar = light.shadowCameraFar; + + virtualLight.shadowCameraLeft = light.shadowCameraLeft; + virtualLight.shadowCameraRight = light.shadowCameraRight; + virtualLight.shadowCameraBottom = light.shadowCameraBottom; + virtualLight.shadowCameraTop = light.shadowCameraTop; + + virtualLight.shadowCameraVisible = light.shadowCameraVisible; + + virtualLight.shadowDarkness = light.shadowDarkness; + + virtualLight.shadowBias = light.shadowCascadeBias[ cascade ]; + virtualLight.shadowMapWidth = light.shadowCascadeWidth[ cascade ]; + virtualLight.shadowMapHeight = light.shadowCascadeHeight[ cascade ]; + + virtualLight.pointsWorld = []; + virtualLight.pointsFrustum = []; + + var pointsWorld = virtualLight.pointsWorld, + pointsFrustum = virtualLight.pointsFrustum; + + for ( var i = 0; i < 8; i ++ ) { + + pointsWorld[ i ] = new THREE.Vector3(); + pointsFrustum[ i ] = new THREE.Vector3(); + + } + + var nearZ = light.shadowCascadeNearZ[ cascade ]; + var farZ = light.shadowCascadeFarZ[ cascade ]; + + pointsFrustum[ 0 ].set( -1, -1, nearZ ); + pointsFrustum[ 1 ].set( 1, -1, nearZ ); + pointsFrustum[ 2 ].set( -1, 1, nearZ ); + pointsFrustum[ 3 ].set( 1, 1, nearZ ); + + pointsFrustum[ 4 ].set( -1, -1, farZ ); + pointsFrustum[ 5 ].set( 1, -1, farZ ); + pointsFrustum[ 6 ].set( -1, 1, farZ ); + pointsFrustum[ 7 ].set( 1, 1, farZ ); + + return virtualLight; + + } + + // Synchronize virtual light with the original light + + function updateVirtualLight( light, cascade ) { + + var virtualLight = light.shadowCascadeArray[ cascade ]; + + virtualLight.position.copy( light.position ); + virtualLight.target.position.copy( light.target.position ); + virtualLight.lookAt( virtualLight.target ); + + virtualLight.shadowCameraVisible = light.shadowCameraVisible; + virtualLight.shadowDarkness = light.shadowDarkness; + + virtualLight.shadowBias = light.shadowCascadeBias[ cascade ]; + + var nearZ = light.shadowCascadeNearZ[ cascade ]; + var farZ = light.shadowCascadeFarZ[ cascade ]; + + var pointsFrustum = virtualLight.pointsFrustum; + + pointsFrustum[ 0 ].z = nearZ; + pointsFrustum[ 1 ].z = nearZ; + pointsFrustum[ 2 ].z = nearZ; + pointsFrustum[ 3 ].z = nearZ; + + pointsFrustum[ 4 ].z = farZ; + pointsFrustum[ 5 ].z = farZ; + pointsFrustum[ 6 ].z = farZ; + pointsFrustum[ 7 ].z = farZ; + + } + + // Fit shadow camera's ortho frustum to camera frustum + + function updateShadowCamera( camera, light ) { + + var shadowCamera = light.shadowCamera, + pointsFrustum = light.pointsFrustum, + pointsWorld = light.pointsWorld; + + _min.set( Infinity, Infinity, Infinity ); + _max.set( -Infinity, -Infinity, -Infinity ); + + for ( var i = 0; i < 8; i ++ ) { + + var p = pointsWorld[ i ]; + + p.copy( pointsFrustum[ i ] ); + THREE.ShadowMapPlugin.__projector.unprojectVector( p, camera ); + + p.applyMatrix4( shadowCamera.matrixWorldInverse ); + + if ( p.x < _min.x ) _min.x = p.x; + if ( p.x > _max.x ) _max.x = p.x; + + if ( p.y < _min.y ) _min.y = p.y; + if ( p.y > _max.y ) _max.y = p.y; + + if ( p.z < _min.z ) _min.z = p.z; + if ( p.z > _max.z ) _max.z = p.z; + + } + + shadowCamera.left = _min.x; + shadowCamera.right = _max.x; + shadowCamera.top = _max.y; + shadowCamera.bottom = _min.y; + + // can't really fit near/far + //shadowCamera.near = _min.z; + //shadowCamera.far = _max.z; + + shadowCamera.updateProjectionMatrix(); + + } + + // For the moment just ignore objects that have multiple materials with different animation methods + // Only the first material will be taken into account for deciding which depth material to use for shadow maps + + function getObjectMaterial( object ) { + + return object.material instanceof THREE.MeshFaceMaterial + ? object.material.materials[ 0 ] + : object.material; + + }; + +}; + +THREE.ShadowMapPlugin.__projector = new THREE.Projector(); + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.SpritePlugin = function () { + + var _gl, _renderer, _texture; + + var vertices, faces, vertexBuffer, elementBuffer; + var program, attributes, uniforms; + + this.init = function ( renderer ) { + + _gl = renderer.context; + _renderer = renderer; + + vertices = new Float32Array( [ + - 0.5, - 0.5, 0, 0, + 0.5, - 0.5, 1, 0, + 0.5, 0.5, 1, 1, + - 0.5, 0.5, 0, 1 + ] ); + + faces = new Uint16Array( [ + 0, 1, 2, + 0, 2, 3 + ] ); + + vertexBuffer = _gl.createBuffer(); + elementBuffer = _gl.createBuffer(); + + _gl.bindBuffer( _gl.ARRAY_BUFFER, vertexBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, vertices, _gl.STATIC_DRAW ); + + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, elementBuffer ); + _gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, faces, _gl.STATIC_DRAW ); + + program = createProgram(); + + attributes = { + position: _gl.getAttribLocation ( program, 'position' ), + uv: _gl.getAttribLocation ( program, 'uv' ) + }; + + uniforms = { + uvOffset: _gl.getUniformLocation( program, 'uvOffset' ), + uvScale: _gl.getUniformLocation( program, 'uvScale' ), + + rotation: _gl.getUniformLocation( program, 'rotation' ), + scale: _gl.getUniformLocation( program, 'scale' ), + + color: _gl.getUniformLocation( program, 'color' ), + map: _gl.getUniformLocation( program, 'map' ), + opacity: _gl.getUniformLocation( program, 'opacity' ), + + modelViewMatrix: _gl.getUniformLocation( program, 'modelViewMatrix' ), + projectionMatrix: _gl.getUniformLocation( program, 'projectionMatrix' ), + + fogType: _gl.getUniformLocation( program, 'fogType' ), + fogDensity: _gl.getUniformLocation( program, 'fogDensity' ), + fogNear: _gl.getUniformLocation( program, 'fogNear' ), + fogFar: _gl.getUniformLocation( program, 'fogFar' ), + fogColor: _gl.getUniformLocation( program, 'fogColor' ), + + alphaTest: _gl.getUniformLocation( program, 'alphaTest' ) + }; + + var canvas = document.createElement( 'canvas' ); + canvas.width = 8; + canvas.height = 8; + + var context = canvas.getContext( '2d' ); + context.fillStyle = '#ffffff'; + context.fillRect( 0, 0, canvas.width, canvas.height ); + + _texture = new THREE.Texture( canvas ); + _texture.needsUpdate = true; + + }; + + this.render = function ( scene, camera, viewportWidth, viewportHeight ) { + + var sprites = scene.__webglSprites, + nSprites = sprites.length; + + if ( ! nSprites ) return; + + // setup gl + + _gl.useProgram( program ); + + _gl.enableVertexAttribArray( attributes.position ); + _gl.enableVertexAttribArray( attributes.uv ); + + _gl.disable( _gl.CULL_FACE ); + _gl.enable( _gl.BLEND ); + + _gl.bindBuffer( _gl.ARRAY_BUFFER, vertexBuffer ); + _gl.vertexAttribPointer( attributes.position, 2, _gl.FLOAT, false, 2 * 8, 0 ); + _gl.vertexAttribPointer( attributes.uv, 2, _gl.FLOAT, false, 2 * 8, 8 ); + + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, elementBuffer ); + + _gl.uniformMatrix4fv( uniforms.projectionMatrix, false, camera.projectionMatrix.elements ); + + _gl.activeTexture( _gl.TEXTURE0 ); + _gl.uniform1i( uniforms.map, 0 ); + + var oldFogType = 0; + var sceneFogType = 0; + var fog = scene.fog; + + if ( fog ) { + + _gl.uniform3f( uniforms.fogColor, fog.color.r, fog.color.g, fog.color.b ); + + if ( fog instanceof THREE.Fog ) { + + _gl.uniform1f( uniforms.fogNear, fog.near ); + _gl.uniform1f( uniforms.fogFar, fog.far ); + + _gl.uniform1i( uniforms.fogType, 1 ); + oldFogType = 1; + sceneFogType = 1; + + } else if ( fog instanceof THREE.FogExp2 ) { + + _gl.uniform1f( uniforms.fogDensity, fog.density ); + + _gl.uniform1i( uniforms.fogType, 2 ); + oldFogType = 2; + sceneFogType = 2; + + } + + } else { + + _gl.uniform1i( uniforms.fogType, 0 ); + oldFogType = 0; + sceneFogType = 0; + + } + + + // update positions and sort + + var i, sprite, material, fogType, scale = []; + + for( i = 0; i < nSprites; i ++ ) { + + sprite = sprites[ i ]; + material = sprite.material; + + if ( sprite.visible === false ) continue; + + sprite._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, sprite.matrixWorld ); + sprite.z = - sprite._modelViewMatrix.elements[ 14 ]; + + } + + sprites.sort( painterSortStable ); + + // render all sprites + + for( i = 0; i < nSprites; i ++ ) { + + sprite = sprites[ i ]; + + if ( sprite.visible === false ) continue; + + material = sprite.material; + + _gl.uniform1f( uniforms.alphaTest, material.alphaTest ); + _gl.uniformMatrix4fv( uniforms.modelViewMatrix, false, sprite._modelViewMatrix.elements ); + + scale[ 0 ] = sprite.scale.x; + scale[ 1 ] = sprite.scale.y; + + if ( scene.fog && material.fog ) { + + fogType = sceneFogType; + + } else { + + fogType = 0; + + } + + if ( oldFogType !== fogType ) { + + _gl.uniform1i( uniforms.fogType, fogType ); + oldFogType = fogType; + + } + + if ( material.map !== null ) { + + _gl.uniform2f( uniforms.uvOffset, material.map.offset.x, material.map.offset.y ); + _gl.uniform2f( uniforms.uvScale, material.map.repeat.x, material.map.repeat.y ); + + } else { + + _gl.uniform2f( uniforms.uvOffset, 0, 0 ); + _gl.uniform2f( uniforms.uvScale, 1, 1 ); + + } + + _gl.uniform1f( uniforms.opacity, material.opacity ); + _gl.uniform3f( uniforms.color, material.color.r, material.color.g, material.color.b ); + + _gl.uniform1f( uniforms.rotation, material.rotation ); + _gl.uniform2fv( uniforms.scale, scale ); + + _renderer.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst ); + _renderer.setDepthTest( material.depthTest ); + _renderer.setDepthWrite( material.depthWrite ); + + if ( material.map && material.map.image && material.map.image.width ) { + + _renderer.setTexture( material.map, 0 ); + + } else { + + _renderer.setTexture( _texture, 0 ); + + } + + _gl.drawElements( _gl.TRIANGLES, 6, _gl.UNSIGNED_SHORT, 0 ); + + } + + // restore gl + + _gl.enable( _gl.CULL_FACE ); + + }; + + function createProgram () { + + var program = _gl.createProgram(); + + var vertexShader = _gl.createShader( _gl.VERTEX_SHADER ); + var fragmentShader = _gl.createShader( _gl.FRAGMENT_SHADER ); + + _gl.shaderSource( vertexShader, [ + + 'precision ' + _renderer.getPrecision() + ' float;', + + 'uniform mat4 modelViewMatrix;', + 'uniform mat4 projectionMatrix;', + 'uniform float rotation;', + 'uniform vec2 scale;', + 'uniform vec2 uvOffset;', + 'uniform vec2 uvScale;', + + 'attribute vec2 position;', + 'attribute vec2 uv;', + + 'varying vec2 vUV;', + + 'void main() {', + + 'vUV = uvOffset + uv * uvScale;', + + 'vec2 alignedPosition = position * scale;', + + 'vec2 rotatedPosition;', + 'rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;', + 'rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;', + + 'vec4 finalPosition;', + + 'finalPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );', + 'finalPosition.xy += rotatedPosition;', + 'finalPosition = projectionMatrix * finalPosition;', + + 'gl_Position = finalPosition;', + + '}' + + ].join( '\n' ) ); + + _gl.shaderSource( fragmentShader, [ + + 'precision ' + _renderer.getPrecision() + ' float;', + + 'uniform vec3 color;', + 'uniform sampler2D map;', + 'uniform float opacity;', + + 'uniform int fogType;', + 'uniform vec3 fogColor;', + 'uniform float fogDensity;', + 'uniform float fogNear;', + 'uniform float fogFar;', + 'uniform float alphaTest;', + + 'varying vec2 vUV;', + + 'void main() {', + + 'vec4 texture = texture2D( map, vUV );', + + 'if ( texture.a < alphaTest ) discard;', + + 'gl_FragColor = vec4( color * texture.xyz, texture.a * opacity );', + + 'if ( fogType > 0 ) {', + + 'float depth = gl_FragCoord.z / gl_FragCoord.w;', + 'float fogFactor = 0.0;', + + 'if ( fogType == 1 ) {', + + 'fogFactor = smoothstep( fogNear, fogFar, depth );', + + '} else {', + + 'const float LOG2 = 1.442695;', + 'float fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );', + 'fogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );', + + '}', + + 'gl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );', + + '}', + + '}' + + ].join( '\n' ) ); + + _gl.compileShader( vertexShader ); + _gl.compileShader( fragmentShader ); + + _gl.attachShader( program, vertexShader ); + _gl.attachShader( program, fragmentShader ); + + _gl.linkProgram( program ); + + return program; + + }; + + function painterSortStable ( a, b ) { + + if ( a.z !== b.z ) { + + return b.z - a.z; + + } else { + + return b.id - a.id; + + } + + }; + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.DepthPassPlugin = function () { + + this.enabled = false; + this.renderTarget = null; + + var _gl, + _renderer, + _depthMaterial, _depthMaterialMorph, _depthMaterialSkin, _depthMaterialMorphSkin, + + _frustum = new THREE.Frustum(), + _projScreenMatrix = new THREE.Matrix4(); + + this.init = function ( renderer ) { + + _gl = renderer.context; + _renderer = renderer; + + var depthShader = THREE.ShaderLib[ "depthRGBA" ]; + var depthUniforms = THREE.UniformsUtils.clone( depthShader.uniforms ); + + _depthMaterial = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms } ); + _depthMaterialMorph = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, morphTargets: true } ); + _depthMaterialSkin = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, skinning: true } ); + _depthMaterialMorphSkin = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, morphTargets: true, skinning: true } ); + + _depthMaterial._shadowPass = true; + _depthMaterialMorph._shadowPass = true; + _depthMaterialSkin._shadowPass = true; + _depthMaterialMorphSkin._shadowPass = true; + + }; + + this.render = function ( scene, camera ) { + + if ( ! this.enabled ) return; + + this.update( scene, camera ); + + }; + + this.update = function ( scene, camera ) { + + var i, il, j, jl, n, + + program, buffer, material, + webglObject, object, light, + renderList, + + fog = null; + + // set GL state for depth map + + _gl.clearColor( 1, 1, 1, 1 ); + _gl.disable( _gl.BLEND ); + + _renderer.setDepthTest( true ); + + // update scene + + if ( scene.autoUpdate === true ) scene.updateMatrixWorld(); + + // update camera matrices and frustum + + camera.matrixWorldInverse.getInverse( camera.matrixWorld ); + + _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); + _frustum.setFromMatrix( _projScreenMatrix ); + + // render depth map + + _renderer.setRenderTarget( this.renderTarget ); + _renderer.clear(); + + // set object matrices & frustum culling + + renderList = scene.__webglObjects; + + for ( j = 0, jl = renderList.length; j < jl; j ++ ) { + + webglObject = renderList[ j ]; + object = webglObject.object; + + webglObject.render = false; + + if ( object.visible ) { + + if ( ! ( object instanceof THREE.Mesh || object instanceof THREE.ParticleSystem ) || ! ( object.frustumCulled ) || _frustum.intersectsObject( object ) ) { + + object._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + + webglObject.render = true; + + } + + } + + } + + // render regular objects + + var objectMaterial, useMorphing, useSkinning; + + for ( j = 0, jl = renderList.length; j < jl; j ++ ) { + + webglObject = renderList[ j ]; + + if ( webglObject.render ) { + + object = webglObject.object; + buffer = webglObject.buffer; + + // todo: create proper depth material for particles + + if ( object instanceof THREE.ParticleSystem && !object.customDepthMaterial ) continue; + + objectMaterial = getObjectMaterial( object ); + + if ( objectMaterial ) _renderer.setMaterialFaces( object.material ); + + useMorphing = object.geometry.morphTargets.length > 0 && objectMaterial.morphTargets; + useSkinning = object instanceof THREE.SkinnedMesh && objectMaterial.skinning; + + if ( object.customDepthMaterial ) { + + material = object.customDepthMaterial; + + } else if ( useSkinning ) { + + material = useMorphing ? _depthMaterialMorphSkin : _depthMaterialSkin; + + } else if ( useMorphing ) { + + material = _depthMaterialMorph; + + } else { + + material = _depthMaterial; + + } + + if ( buffer instanceof THREE.BufferGeometry ) { + + _renderer.renderBufferDirect( camera, scene.__lights, fog, material, buffer, object ); + + } else { + + _renderer.renderBuffer( camera, scene.__lights, fog, material, buffer, object ); + + } + + } + + } + + // set matrices and render immediate objects + + renderList = scene.__webglObjectsImmediate; + + for ( j = 0, jl = renderList.length; j < jl; j ++ ) { + + webglObject = renderList[ j ]; + object = webglObject.object; + + if ( object.visible ) { + + object._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + + _renderer.renderImmediateObject( camera, scene.__lights, fog, _depthMaterial, object ); + + } + + } + + // restore GL state + + var clearColor = _renderer.getClearColor(), + clearAlpha = _renderer.getClearAlpha(); + + _gl.clearColor( clearColor.r, clearColor.g, clearColor.b, clearAlpha ); + _gl.enable( _gl.BLEND ); + + }; + + // For the moment just ignore objects that have multiple materials with different animation methods + // Only the first material will be taken into account for deciding which depth material to use + + function getObjectMaterial( object ) { + + return object.material instanceof THREE.MeshFaceMaterial + ? object.material.materials[ 0 ] + : object.material; + + }; + +}; + + +/** + * @author mikael emtinger / http://gomo.se/ + */ + +THREE.ShaderFlares = { + + 'lensFlareVertexTexture': { + + vertexShader: [ + + "uniform lowp int renderType;", + + "uniform vec3 screenPosition;", + "uniform vec2 scale;", + "uniform float rotation;", + + "uniform sampler2D occlusionMap;", + + "attribute vec2 position;", + "attribute vec2 uv;", + + "varying vec2 vUV;", + "varying float vVisibility;", + + "void main() {", + + "vUV = uv;", + + "vec2 pos = position;", + + "if( renderType == 2 ) {", + + "vec4 visibility = texture2D( occlusionMap, vec2( 0.1, 0.1 ) );", + "visibility += texture2D( occlusionMap, vec2( 0.5, 0.1 ) );", + "visibility += texture2D( occlusionMap, vec2( 0.9, 0.1 ) );", + "visibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) );", + "visibility += texture2D( occlusionMap, vec2( 0.9, 0.9 ) );", + "visibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) );", + "visibility += texture2D( occlusionMap, vec2( 0.1, 0.9 ) );", + "visibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) );", + "visibility += texture2D( occlusionMap, vec2( 0.5, 0.5 ) );", + + "vVisibility = visibility.r / 9.0;", + "vVisibility *= 1.0 - visibility.g / 9.0;", + "vVisibility *= visibility.b / 9.0;", + "vVisibility *= 1.0 - visibility.a / 9.0;", + + "pos.x = cos( rotation ) * position.x - sin( rotation ) * position.y;", + "pos.y = sin( rotation ) * position.x + cos( rotation ) * position.y;", + + "}", + + "gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 );", + + "}" + + ].join( "\n" ), + + fragmentShader: [ + + "uniform lowp int renderType;", + + "uniform sampler2D map;", + "uniform float opacity;", + "uniform vec3 color;", + + "varying vec2 vUV;", + "varying float vVisibility;", + + "void main() {", + + // pink square + + "if( renderType == 0 ) {", + + "gl_FragColor = vec4( 1.0, 0.0, 1.0, 0.0 );", + + // restore + + "} else if( renderType == 1 ) {", + + "gl_FragColor = texture2D( map, vUV );", + + // flare + + "} else {", + + "vec4 texture = texture2D( map, vUV );", + "texture.a *= opacity * vVisibility;", + "gl_FragColor = texture;", + "gl_FragColor.rgb *= color;", + + "}", + + "}" + ].join( "\n" ) + + }, + + + 'lensFlare': { + + vertexShader: [ + + "uniform lowp int renderType;", + + "uniform vec3 screenPosition;", + "uniform vec2 scale;", + "uniform float rotation;", + + "attribute vec2 position;", + "attribute vec2 uv;", + + "varying vec2 vUV;", + + "void main() {", + + "vUV = uv;", + + "vec2 pos = position;", + + "if( renderType == 2 ) {", + + "pos.x = cos( rotation ) * position.x - sin( rotation ) * position.y;", + "pos.y = sin( rotation ) * position.x + cos( rotation ) * position.y;", + + "}", + + "gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 );", + + "}" + + ].join( "\n" ), + + fragmentShader: [ + + "precision mediump float;", + + "uniform lowp int renderType;", + + "uniform sampler2D map;", + "uniform sampler2D occlusionMap;", + "uniform float opacity;", + "uniform vec3 color;", + + "varying vec2 vUV;", + + "void main() {", + + // pink square + + "if( renderType == 0 ) {", + + "gl_FragColor = vec4( texture2D( map, vUV ).rgb, 0.0 );", + + // restore + + "} else if( renderType == 1 ) {", + + "gl_FragColor = texture2D( map, vUV );", + + // flare + + "} else {", + + "float visibility = texture2D( occlusionMap, vec2( 0.5, 0.1 ) ).a;", + "visibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) ).a;", + "visibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) ).a;", + "visibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) ).a;", + "visibility = ( 1.0 - visibility / 4.0 );", + + "vec4 texture = texture2D( map, vUV );", + "texture.a *= opacity * visibility;", + "gl_FragColor = texture;", + "gl_FragColor.rgb *= color;", + + "}", + + "}" + + ].join( "\n" ) + + } + +}; + diff --git a/Three.js/js/peer.js b/Three.js/js/peer.js new file mode 100644 index 0000000..c7c5d27 --- /dev/null +++ b/Three.js/js/peer.js @@ -0,0 +1,2657 @@ +/*! peerjs.js build:0.3.7, development. Copyright(c) 2013 Michelle Bu */ +(function(exports){ +var binaryFeatures = {}; +binaryFeatures.useBlobBuilder = (function(){ + try { + new Blob([]); + return false; + } catch (e) { + return true; + } +})(); + +binaryFeatures.useArrayBufferView = !binaryFeatures.useBlobBuilder && (function(){ + try { + return (new Blob([new Uint8Array([])])).size === 0; + } catch (e) { + return true; + } +})(); + +exports.binaryFeatures = binaryFeatures; +exports.BlobBuilder = window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder || window.BlobBuilder; + +function BufferBuilder(){ + this._pieces = []; + this._parts = []; +} + +BufferBuilder.prototype.append = function(data) { + if(typeof data === 'number') { + this._pieces.push(data); + } else { + this.flush(); + this._parts.push(data); + } +}; + +BufferBuilder.prototype.flush = function() { + if (this._pieces.length > 0) { + var buf = new Uint8Array(this._pieces); + if(!binaryFeatures.useArrayBufferView) { + buf = buf.buffer; + } + this._parts.push(buf); + this._pieces = []; + } +}; + +BufferBuilder.prototype.getBuffer = function() { + this.flush(); + if(binaryFeatures.useBlobBuilder) { + var builder = new BlobBuilder(); + for(var i = 0, ii = this._parts.length; i < ii; i++) { + builder.append(this._parts[i]); + } + return builder.getBlob(); + } else { + return new Blob(this._parts); + } +}; +exports.BinaryPack = { + unpack: function(data){ + var unpacker = new Unpacker(data); + return unpacker.unpack(); + }, + pack: function(data){ + var packer = new Packer(); + packer.pack(data); + var buffer = packer.getBuffer(); + return buffer; + } +}; + +function Unpacker (data){ + // Data is ArrayBuffer + this.index = 0; + this.dataBuffer = data; + this.dataView = new Uint8Array(this.dataBuffer); + this.length = this.dataBuffer.byteLength; +} + + +Unpacker.prototype.unpack = function(){ + var type = this.unpack_uint8(); + if (type < 0x80){ + var positive_fixnum = type; + return positive_fixnum; + } else if ((type ^ 0xe0) < 0x20){ + var negative_fixnum = (type ^ 0xe0) - 0x20; + return negative_fixnum; + } + var size; + if ((size = type ^ 0xa0) <= 0x0f){ + return this.unpack_raw(size); + } else if ((size = type ^ 0xb0) <= 0x0f){ + return this.unpack_string(size); + } else if ((size = type ^ 0x90) <= 0x0f){ + return this.unpack_array(size); + } else if ((size = type ^ 0x80) <= 0x0f){ + return this.unpack_map(size); + } + switch(type){ + case 0xc0: + return null; + case 0xc1: + return undefined; + case 0xc2: + return false; + case 0xc3: + return true; + case 0xca: + return this.unpack_float(); + case 0xcb: + return this.unpack_double(); + case 0xcc: + return this.unpack_uint8(); + case 0xcd: + return this.unpack_uint16(); + case 0xce: + return this.unpack_uint32(); + case 0xcf: + return this.unpack_uint64(); + case 0xd0: + return this.unpack_int8(); + case 0xd1: + return this.unpack_int16(); + case 0xd2: + return this.unpack_int32(); + case 0xd3: + return this.unpack_int64(); + case 0xd4: + return undefined; + case 0xd5: + return undefined; + case 0xd6: + return undefined; + case 0xd7: + return undefined; + case 0xd8: + size = this.unpack_uint16(); + return this.unpack_string(size); + case 0xd9: + size = this.unpack_uint32(); + return this.unpack_string(size); + case 0xda: + size = this.unpack_uint16(); + return this.unpack_raw(size); + case 0xdb: + size = this.unpack_uint32(); + return this.unpack_raw(size); + case 0xdc: + size = this.unpack_uint16(); + return this.unpack_array(size); + case 0xdd: + size = this.unpack_uint32(); + return this.unpack_array(size); + case 0xde: + size = this.unpack_uint16(); + return this.unpack_map(size); + case 0xdf: + size = this.unpack_uint32(); + return this.unpack_map(size); + } +} + +Unpacker.prototype.unpack_uint8 = function(){ + var byte = this.dataView[this.index] & 0xff; + this.index++; + return byte; +}; + +Unpacker.prototype.unpack_uint16 = function(){ + var bytes = this.read(2); + var uint16 = + ((bytes[0] & 0xff) * 256) + (bytes[1] & 0xff); + this.index += 2; + return uint16; +} + +Unpacker.prototype.unpack_uint32 = function(){ + var bytes = this.read(4); + var uint32 = + ((bytes[0] * 256 + + bytes[1]) * 256 + + bytes[2]) * 256 + + bytes[3]; + this.index += 4; + return uint32; +} + +Unpacker.prototype.unpack_uint64 = function(){ + var bytes = this.read(8); + var uint64 = + ((((((bytes[0] * 256 + + bytes[1]) * 256 + + bytes[2]) * 256 + + bytes[3]) * 256 + + bytes[4]) * 256 + + bytes[5]) * 256 + + bytes[6]) * 256 + + bytes[7]; + this.index += 8; + return uint64; +} + + +Unpacker.prototype.unpack_int8 = function(){ + var uint8 = this.unpack_uint8(); + return (uint8 < 0x80 ) ? uint8 : uint8 - (1 << 8); +}; + +Unpacker.prototype.unpack_int16 = function(){ + var uint16 = this.unpack_uint16(); + return (uint16 < 0x8000 ) ? uint16 : uint16 - (1 << 16); +} + +Unpacker.prototype.unpack_int32 = function(){ + var uint32 = this.unpack_uint32(); + return (uint32 < Math.pow(2, 31) ) ? uint32 : + uint32 - Math.pow(2, 32); +} + +Unpacker.prototype.unpack_int64 = function(){ + var uint64 = this.unpack_uint64(); + return (uint64 < Math.pow(2, 63) ) ? uint64 : + uint64 - Math.pow(2, 64); +} + +Unpacker.prototype.unpack_raw = function(size){ + if ( this.length < this.index + size){ + throw new Error('BinaryPackFailure: index is out of range' + + ' ' + this.index + ' ' + size + ' ' + this.length); + } + var buf = this.dataBuffer.slice(this.index, this.index + size); + this.index += size; + + //buf = util.bufferToString(buf); + + return buf; +} + +Unpacker.prototype.unpack_string = function(size){ + var bytes = this.read(size); + var i = 0, str = '', c, code; + while(i < size){ + c = bytes[i]; + if ( c < 128){ + str += String.fromCharCode(c); + i++; + } else if ((c ^ 0xc0) < 32){ + code = ((c ^ 0xc0) << 6) | (bytes[i+1] & 63); + str += String.fromCharCode(code); + i += 2; + } else { + code = ((c & 15) << 12) | ((bytes[i+1] & 63) << 6) | + (bytes[i+2] & 63); + str += String.fromCharCode(code); + i += 3; + } + } + this.index += size; + return str; +} + +Unpacker.prototype.unpack_array = function(size){ + var objects = new Array(size); + for(var i = 0; i < size ; i++){ + objects[i] = this.unpack(); + } + return objects; +} + +Unpacker.prototype.unpack_map = function(size){ + var map = {}; + for(var i = 0; i < size ; i++){ + var key = this.unpack(); + var value = this.unpack(); + map[key] = value; + } + return map; +} + +Unpacker.prototype.unpack_float = function(){ + var uint32 = this.unpack_uint32(); + var sign = uint32 >> 31; + var exp = ((uint32 >> 23) & 0xff) - 127; + var fraction = ( uint32 & 0x7fffff ) | 0x800000; + return (sign == 0 ? 1 : -1) * + fraction * Math.pow(2, exp - 23); +} + +Unpacker.prototype.unpack_double = function(){ + var h32 = this.unpack_uint32(); + var l32 = this.unpack_uint32(); + var sign = h32 >> 31; + var exp = ((h32 >> 20) & 0x7ff) - 1023; + var hfrac = ( h32 & 0xfffff ) | 0x100000; + var frac = hfrac * Math.pow(2, exp - 20) + + l32 * Math.pow(2, exp - 52); + return (sign == 0 ? 1 : -1) * frac; +} + +Unpacker.prototype.read = function(length){ + var j = this.index; + if (j + length <= this.length) { + return this.dataView.subarray(j, j + length); + } else { + throw new Error('BinaryPackFailure: read index out of range'); + } +} + +function Packer(){ + this.bufferBuilder = new BufferBuilder(); +} + +Packer.prototype.getBuffer = function(){ + return this.bufferBuilder.getBuffer(); +} + +Packer.prototype.pack = function(value){ + var type = typeof(value); + if (type == 'string'){ + this.pack_string(value); + } else if (type == 'number'){ + if (Math.floor(value) === value){ + this.pack_integer(value); + } else{ + this.pack_double(value); + } + } else if (type == 'boolean'){ + if (value === true){ + this.bufferBuilder.append(0xc3); + } else if (value === false){ + this.bufferBuilder.append(0xc2); + } + } else if (type == 'undefined'){ + this.bufferBuilder.append(0xc0); + } else if (type == 'object'){ + if (value === null){ + this.bufferBuilder.append(0xc0); + } else { + var constructor = value.constructor; + if (constructor == Array){ + this.pack_array(value); + } else if (constructor == Blob || constructor == File) { + this.pack_bin(value); + } else if (constructor == ArrayBuffer) { + if(binaryFeatures.useArrayBufferView) { + this.pack_bin(new Uint8Array(value)); + } else { + this.pack_bin(value); + } + } else if ('BYTES_PER_ELEMENT' in value){ + if(binaryFeatures.useArrayBufferView) { + this.pack_bin(new Uint8Array(value.buffer)); + } else { + this.pack_bin(value.buffer); + } + } else if (constructor == Object){ + this.pack_object(value); + } else if (constructor == Date){ + this.pack_string(value.toString()); + } else if (typeof value.toBinaryPack == 'function'){ + this.bufferBuilder.append(value.toBinaryPack()); + } else { + throw new Error('Type "' + constructor.toString() + '" not yet supported'); + } + } + } else { + throw new Error('Type "' + type + '" not yet supported'); + } + this.bufferBuilder.flush(); +} + + +Packer.prototype.pack_bin = function(blob){ + var length = blob.length || blob.byteLength || blob.size; + if (length <= 0x0f){ + this.pack_uint8(0xa0 + length); + } else if (length <= 0xffff){ + this.bufferBuilder.append(0xda) ; + this.pack_uint16(length); + } else if (length <= 0xffffffff){ + this.bufferBuilder.append(0xdb); + this.pack_uint32(length); + } else{ + throw new Error('Invalid length'); + return; + } + this.bufferBuilder.append(blob); +} + +Packer.prototype.pack_string = function(str){ + var length = utf8Length(str); + + if (length <= 0x0f){ + this.pack_uint8(0xb0 + length); + } else if (length <= 0xffff){ + this.bufferBuilder.append(0xd8) ; + this.pack_uint16(length); + } else if (length <= 0xffffffff){ + this.bufferBuilder.append(0xd9); + this.pack_uint32(length); + } else{ + throw new Error('Invalid length'); + return; + } + this.bufferBuilder.append(str); +} + +Packer.prototype.pack_array = function(ary){ + var length = ary.length; + if (length <= 0x0f){ + this.pack_uint8(0x90 + length); + } else if (length <= 0xffff){ + this.bufferBuilder.append(0xdc) + this.pack_uint16(length); + } else if (length <= 0xffffffff){ + this.bufferBuilder.append(0xdd); + this.pack_uint32(length); + } else{ + throw new Error('Invalid length'); + } + for(var i = 0; i < length ; i++){ + this.pack(ary[i]); + } +} + +Packer.prototype.pack_integer = function(num){ + if ( -0x20 <= num && num <= 0x7f){ + this.bufferBuilder.append(num & 0xff); + } else if (0x00 <= num && num <= 0xff){ + this.bufferBuilder.append(0xcc); + this.pack_uint8(num); + } else if (-0x80 <= num && num <= 0x7f){ + this.bufferBuilder.append(0xd0); + this.pack_int8(num); + } else if ( 0x0000 <= num && num <= 0xffff){ + this.bufferBuilder.append(0xcd); + this.pack_uint16(num); + } else if (-0x8000 <= num && num <= 0x7fff){ + this.bufferBuilder.append(0xd1); + this.pack_int16(num); + } else if ( 0x00000000 <= num && num <= 0xffffffff){ + this.bufferBuilder.append(0xce); + this.pack_uint32(num); + } else if (-0x80000000 <= num && num <= 0x7fffffff){ + this.bufferBuilder.append(0xd2); + this.pack_int32(num); + } else if (-0x8000000000000000 <= num && num <= 0x7FFFFFFFFFFFFFFF){ + this.bufferBuilder.append(0xd3); + this.pack_int64(num); + } else if (0x0000000000000000 <= num && num <= 0xFFFFFFFFFFFFFFFF){ + this.bufferBuilder.append(0xcf); + this.pack_uint64(num); + } else{ + throw new Error('Invalid integer'); + } +} + +Packer.prototype.pack_double = function(num){ + var sign = 0; + if (num < 0){ + sign = 1; + num = -num; + } + var exp = Math.floor(Math.log(num) / Math.LN2); + var frac0 = num / Math.pow(2, exp) - 1; + var frac1 = Math.floor(frac0 * Math.pow(2, 52)); + var b32 = Math.pow(2, 32); + var h32 = (sign << 31) | ((exp+1023) << 20) | + (frac1 / b32) & 0x0fffff; + var l32 = frac1 % b32; + this.bufferBuilder.append(0xcb); + this.pack_int32(h32); + this.pack_int32(l32); +} + +Packer.prototype.pack_object = function(obj){ + var keys = Object.keys(obj); + var length = keys.length; + if (length <= 0x0f){ + this.pack_uint8(0x80 + length); + } else if (length <= 0xffff){ + this.bufferBuilder.append(0xde); + this.pack_uint16(length); + } else if (length <= 0xffffffff){ + this.bufferBuilder.append(0xdf); + this.pack_uint32(length); + } else{ + throw new Error('Invalid length'); + } + for(var prop in obj){ + if (obj.hasOwnProperty(prop)){ + this.pack(prop); + this.pack(obj[prop]); + } + } +} + +Packer.prototype.pack_uint8 = function(num){ + this.bufferBuilder.append(num); +} + +Packer.prototype.pack_uint16 = function(num){ + this.bufferBuilder.append(num >> 8); + this.bufferBuilder.append(num & 0xff); +} + +Packer.prototype.pack_uint32 = function(num){ + var n = num & 0xffffffff; + this.bufferBuilder.append((n & 0xff000000) >>> 24); + this.bufferBuilder.append((n & 0x00ff0000) >>> 16); + this.bufferBuilder.append((n & 0x0000ff00) >>> 8); + this.bufferBuilder.append((n & 0x000000ff)); +} + +Packer.prototype.pack_uint64 = function(num){ + var high = num / Math.pow(2, 32); + var low = num % Math.pow(2, 32); + this.bufferBuilder.append((high & 0xff000000) >>> 24); + this.bufferBuilder.append((high & 0x00ff0000) >>> 16); + this.bufferBuilder.append((high & 0x0000ff00) >>> 8); + this.bufferBuilder.append((high & 0x000000ff)); + this.bufferBuilder.append((low & 0xff000000) >>> 24); + this.bufferBuilder.append((low & 0x00ff0000) >>> 16); + this.bufferBuilder.append((low & 0x0000ff00) >>> 8); + this.bufferBuilder.append((low & 0x000000ff)); +} + +Packer.prototype.pack_int8 = function(num){ + this.bufferBuilder.append(num & 0xff); +} + +Packer.prototype.pack_int16 = function(num){ + this.bufferBuilder.append((num & 0xff00) >> 8); + this.bufferBuilder.append(num & 0xff); +} + +Packer.prototype.pack_int32 = function(num){ + this.bufferBuilder.append((num >>> 24) & 0xff); + this.bufferBuilder.append((num & 0x00ff0000) >>> 16); + this.bufferBuilder.append((num & 0x0000ff00) >>> 8); + this.bufferBuilder.append((num & 0x000000ff)); +} + +Packer.prototype.pack_int64 = function(num){ + var high = Math.floor(num / Math.pow(2, 32)); + var low = num % Math.pow(2, 32); + this.bufferBuilder.append((high & 0xff000000) >>> 24); + this.bufferBuilder.append((high & 0x00ff0000) >>> 16); + this.bufferBuilder.append((high & 0x0000ff00) >>> 8); + this.bufferBuilder.append((high & 0x000000ff)); + this.bufferBuilder.append((low & 0xff000000) >>> 24); + this.bufferBuilder.append((low & 0x00ff0000) >>> 16); + this.bufferBuilder.append((low & 0x0000ff00) >>> 8); + this.bufferBuilder.append((low & 0x000000ff)); +} + +function _utf8Replace(m){ + var code = m.charCodeAt(0); + + if(code <= 0x7ff) return '00'; + if(code <= 0xffff) return '000'; + if(code <= 0x1fffff) return '0000'; + if(code <= 0x3ffffff) return '00000'; + return '000000'; +} + +function utf8Length(str){ + if (str.length > 600) { + // Blob method faster for large strings + return (new Blob([str])).size; + } else { + return str.replace(/[^\u0000-\u007F]/g, _utf8Replace).length; + } +} +/** + * Light EventEmitter. Ported from Node.js/events.js + * Eric Zhang + */ + +/** + * EventEmitter class + * Creates an object with event registering and firing methods + */ +function EventEmitter() { + // Initialise required storage variables + this._events = {}; +} + +var isArray = Array.isArray; + + +EventEmitter.prototype.addListener = function(type, listener, scope, once) { + if ('function' !== typeof listener) { + throw new Error('addListener only takes instances of Function'); + } + + // To avoid recursion in the case that type == "newListeners"! Before + // adding it to the listeners, first emit "newListeners". + this.emit('newListener', type, typeof listener.listener === 'function' ? + listener.listener : listener); + + if (!this._events[type]) { + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + } else if (isArray(this._events[type])) { + + // If we've already got an array, just append. + this._events[type].push(listener); + + } else { + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + } + return this; +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.once = function(type, listener, scope) { + if ('function' !== typeof listener) { + throw new Error('.once only takes instances of Function'); + } + + var self = this; + function g() { + self.removeListener(type, g); + listener.apply(this, arguments); + }; + + g.listener = listener; + self.on(type, g); + + return this; +}; + +EventEmitter.prototype.removeListener = function(type, listener, scope) { + if ('function' !== typeof listener) { + throw new Error('removeListener only takes instances of Function'); + } + + // does not use listeners(), so no side effect of creating _events[type] + if (!this._events[type]) return this; + + var list = this._events[type]; + + if (isArray(list)) { + var position = -1; + for (var i = 0, length = list.length; i < length; i++) { + if (list[i] === listener || + (list[i].listener && list[i].listener === listener)) + { + position = i; + break; + } + } + + if (position < 0) return this; + list.splice(position, 1); + if (list.length == 0) + delete this._events[type]; + } else if (list === listener || + (list.listener && list.listener === listener)) + { + delete this._events[type]; + } + + return this; +}; + + +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + + +EventEmitter.prototype.removeAllListeners = function(type) { + if (arguments.length === 0) { + this._events = {}; + return this; + } + + // does not use listeners(), so no side effect of creating _events[type] + if (type && this._events && this._events[type]) this._events[type] = null; + return this; +}; + +EventEmitter.prototype.listeners = function(type) { + if (!this._events[type]) this._events[type] = []; + if (!isArray(this._events[type])) { + this._events[type] = [this._events[type]]; + } + return this._events[type]; +}; + +EventEmitter.prototype.emit = function(type) { + var type = arguments[0]; + var handler = this._events[type]; + if (!handler) return false; + + if (typeof handler == 'function') { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + var l = arguments.length; + var args = new Array(l - 1); + for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; + handler.apply(this, args); + } + return true; + + } else if (isArray(handler)) { + var l = arguments.length; + var args = new Array(l - 1); + for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; + + var listeners = handler.slice(); + for (var i = 0, l = listeners.length; i < l; i++) { + listeners[i].apply(this, args); + } + return true; + } else { + return false; + } +}; + + + +/** + * Reliable transfer for Chrome Canary DataChannel impl. + * Author: @michellebu + */ +function Reliable(dc, debug) { + if (!(this instanceof Reliable)) return new Reliable(dc); + this._dc = dc; + + util.debug = debug; + + // Messages sent/received so far. + // id: { ack: n, chunks: [...] } + this._outgoing = {}; + // id: { ack: ['ack', id, n], chunks: [...] } + this._incoming = {}; + this._received = {}; + + // Window size. + this._window = 1000; + // MTU. + this._mtu = 500; + // Interval for setInterval. In ms. + this._interval = 0; + + // Messages sent. + this._count = 0; + + // Outgoing message queue. + this._queue = []; + + this._setupDC(); +}; + +// Send a message reliably. +Reliable.prototype.send = function(msg) { + // Determine if chunking is necessary. + var bl = util.pack(msg); + if (bl.size < this._mtu) { + this._handleSend(['no', bl]); + return; + } + + this._outgoing[this._count] = { + ack: 0, + chunks: this._chunk(bl) + }; + + if (util.debug) { + this._outgoing[this._count].timer = new Date(); + } + + // Send prelim window. + this._sendWindowedChunks(this._count); + this._count += 1; +}; + +// Set up interval for processing queue. +Reliable.prototype._setupInterval = function() { + // TODO: fail gracefully. + + var self = this; + this._timeout = setInterval(function() { + // FIXME: String stuff makes things terribly async. + var msg = self._queue.shift(); + if (msg._multiple) { + for (var i = 0, ii = msg.length; i < ii; i += 1) { + self._intervalSend(msg[i]); + } + } else { + self._intervalSend(msg); + } + }, this._interval); +}; + +Reliable.prototype._intervalSend = function(msg) { + var self = this; + msg = util.pack(msg); + util.blobToBinaryString(msg, function(str) { + self._dc.send(str); + }); + if (self._queue.length === 0) { + clearTimeout(self._timeout); + self._timeout = null; + //self._processAcks(); + } +}; + +// Go through ACKs to send missing pieces. +Reliable.prototype._processAcks = function() { + for (var id in this._outgoing) { + if (this._outgoing.hasOwnProperty(id)) { + this._sendWindowedChunks(id); + } + } +}; + +// Handle sending a message. +// FIXME: Don't wait for interval time for all messages... +Reliable.prototype._handleSend = function(msg) { + var push = true; + for (var i = 0, ii = this._queue.length; i < ii; i += 1) { + var item = this._queue[i]; + if (item === msg) { + push = false; + } else if (item._multiple && item.indexOf(msg) !== -1) { + push = false; + } + } + if (push) { + this._queue.push(msg); + if (!this._timeout) { + this._setupInterval(); + } + } +}; + +// Set up DataChannel handlers. +Reliable.prototype._setupDC = function() { + // Handle various message types. + var self = this; + this._dc.onmessage = function(e) { + var msg = e.data; + var datatype = msg.constructor; + // FIXME: msg is String until binary is supported. + // Once that happens, this will have to be smarter. + if (datatype === String) { + var ab = util.binaryStringToArrayBuffer(msg); + msg = util.unpack(ab); + self._handleMessage(msg); + } + }; +}; + +// Handles an incoming message. +Reliable.prototype._handleMessage = function(msg) { + var id = msg[1]; + var idata = this._incoming[id]; + var odata = this._outgoing[id]; + var data; + switch (msg[0]) { + // No chunking was done. + case 'no': + var message = id; + if (!!message) { + this.onmessage(util.unpack(message)); + } + break; + // Reached the end of the message. + case 'end': + data = idata; + + // In case end comes first. + this._received[id] = msg[2]; + + if (!data) { + break; + } + + this._ack(id); + break; + case 'ack': + data = odata; + if (!!data) { + var ack = msg[2]; + // Take the larger ACK, for out of order messages. + data.ack = Math.max(ack, data.ack); + + // Clean up when all chunks are ACKed. + if (data.ack >= data.chunks.length) { + util.log('Time: ', new Date() - data.timer); + delete this._outgoing[id]; + } else { + this._processAcks(); + } + } + // If !data, just ignore. + break; + // Received a chunk of data. + case 'chunk': + // Create a new entry if none exists. + data = idata; + if (!data) { + var end = this._received[id]; + if (end === true) { + break; + } + data = { + ack: ['ack', id, 0], + chunks: [] + }; + this._incoming[id] = data; + } + + var n = msg[2]; + var chunk = msg[3]; + data.chunks[n] = new Uint8Array(chunk); + + // If we get the chunk we're looking for, ACK for next missing. + // Otherwise, ACK the same N again. + if (n === data.ack[2]) { + this._calculateNextAck(id); + } + this._ack(id); + break; + default: + // Shouldn't happen, but would make sense for message to just go + // through as is. + this._handleSend(msg); + break; + } +}; + +// Chunks BL into smaller messages. +Reliable.prototype._chunk = function(bl) { + var chunks = []; + var size = bl.size; + var start = 0; + while (start < size) { + var end = Math.min(size, start + this._mtu); + var b = bl.slice(start, end); + var chunk = { + payload: b + } + chunks.push(chunk); + start = end; + } + util.log('Created', chunks.length, 'chunks.'); + return chunks; +}; + +// Sends ACK N, expecting Nth blob chunk for message ID. +Reliable.prototype._ack = function(id) { + var ack = this._incoming[id].ack; + + // if ack is the end value, then call _complete. + if (this._received[id] === ack[2]) { + this._complete(id); + this._received[id] = true; + } + + this._handleSend(ack); +}; + +// Calculates the next ACK number, given chunks. +Reliable.prototype._calculateNextAck = function(id) { + var data = this._incoming[id]; + var chunks = data.chunks; + for (var i = 0, ii = chunks.length; i < ii; i += 1) { + // This chunk is missing!!! Better ACK for it. + if (chunks[i] === undefined) { + data.ack[2] = i; + return; + } + } + data.ack[2] = chunks.length; +}; + +// Sends the next window of chunks. +Reliable.prototype._sendWindowedChunks = function(id) { + util.log('sendWindowedChunks for: ', id); + var data = this._outgoing[id]; + var ch = data.chunks; + var chunks = []; + var limit = Math.min(data.ack + this._window, ch.length); + for (var i = data.ack; i < limit; i += 1) { + if (!ch[i].sent || i === data.ack) { + ch[i].sent = true; + chunks.push(['chunk', id, i, ch[i].payload]); + } + } + if (data.ack + this._window >= ch.length) { + chunks.push(['end', id, ch.length]) + } + chunks._multiple = true; + this._handleSend(chunks); +}; + +// Puts together a message from chunks. +Reliable.prototype._complete = function(id) { + util.log('Completed called for', id); + var self = this; + var chunks = this._incoming[id].chunks; + var bl = new Blob(chunks); + util.blobToArrayBuffer(bl, function(ab) { + self.onmessage(util.unpack(ab)); + }); + delete this._incoming[id]; +}; + +// Ups bandwidth limit on SDP. Meant to be called during offer/answer. +Reliable.higherBandwidthSDP = function(sdp) { + // AS stands for Application-Specific Maximum. + // Bandwidth number is in kilobits / sec. + // See RFC for more info: http://www.ietf.org/rfc/rfc2327.txt + + // Chrome 31+ doesn't want us munging the SDP, so we'll let them have their + // way. + var version = navigator.appVersion.match(/Chrome\/(.*?) /); + if (version) { + version = parseInt(version[1].split('.').shift()); + if (version < 31) { + var parts = sdp.split('b=AS:30'); + var replace = 'b=AS:102400'; // 100 Mbps + if (parts.length > 1) { + return parts[0] + replace + parts[1]; + } + } + } + + return sdp; +}; + +// Overwritten, typically. +Reliable.prototype.onmessage = function(msg) {}; + +exports.Reliable = Reliable; +exports.RTCSessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription; +exports.RTCPeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection || window.RTCPeerConnection; +exports.RTCIceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate; +var defaultConfig = {'iceServers': [{ 'url': 'stun:stun.l.google.com:19302' }]}; +var dataCount = 1; + +var util = { + noop: function() {}, + + CLOUD_HOST: '0.peerjs.com', + CLOUD_PORT: 9000, + + // Browsers that need chunking: + chunkedBrowsers: {'Chrome': 1}, + chunkedMTU: 16300, // The original 60000 bytes setting does not work when sending data from Firefox to Chrome, which is "cut off" after 16384 bytes and delivered individually. + + // Logging logic + logLevel: 0, + setLogLevel: function(level) { + var debugLevel = parseInt(level, 10); + if (!isNaN(parseInt(level, 10))) { + util.logLevel = debugLevel; + } else { + // If they are using truthy/falsy values for debug + util.logLevel = level ? 3 : 0; + } + util.log = util.warn = util.error = util.noop; + if (util.logLevel > 0) { + util.error = util._printWith('ERROR'); + } + if (util.logLevel > 1) { + util.warn = util._printWith('WARNING'); + } + if (util.logLevel > 2) { + util.log = util._print; + } + }, + setLogFunction: function(fn) { + if (fn.constructor !== Function) { + util.warn('The log function you passed in is not a function. Defaulting to regular logs.'); + } else { + util._print = fn; + } + }, + + _printWith: function(prefix) { + return function() { + var copy = Array.prototype.slice.call(arguments); + copy.unshift(prefix); + util._print.apply(util, copy); + }; + }, + _print: function () { + var err = false; + var copy = Array.prototype.slice.call(arguments); + copy.unshift('PeerJS: '); + for (var i = 0, l = copy.length; i < l; i++){ + if (copy[i] instanceof Error) { + copy[i] = '(' + copy[i].name + ') ' + copy[i].message; + err = true; + } + } + err ? console.error.apply(console, copy) : console.log.apply(console, copy); + }, + // + + // Returns browser-agnostic default config + defaultConfig: defaultConfig, + // + + // Returns the current browser. + browser: (function() { + if (window.mozRTCPeerConnection) { + return 'Firefox'; + } else if (window.webkitRTCPeerConnection) { + return 'Chrome'; + } else if (window.RTCPeerConnection) { + return 'Supported'; + } else { + return 'Unsupported'; + } + })(), + // + + // Lists which features are supported + supports: (function() { + if (typeof RTCPeerConnection === 'undefined') { + return {}; + } + + var data = true; + var audioVideo = true; + + var binaryBlob = false; + var sctp = false; + var onnegotiationneeded = !!window.webkitRTCPeerConnection; + + var pc, dc; + try { + pc = new RTCPeerConnection(defaultConfig, {optional: [{RtpDataChannels: true}]}); + } catch (e) { + data = false; + audioVideo = false; + } + + if (data) { + try { + dc = pc.createDataChannel('_PEERJSTEST'); + } catch (e) { + data = false; + } + } + + if (data) { + // Binary test + try { + dc.binaryType = 'blob'; + binaryBlob = true; + } catch (e) { + } + + // Reliable test. + // Unfortunately Chrome is a bit unreliable about whether or not they + // support reliable. + var reliablePC = new RTCPeerConnection(defaultConfig, {}); + try { + var reliableDC = reliablePC.createDataChannel('_PEERJSRELIABLETEST', {}); + sctp = reliableDC.reliable; + } catch (e) { + } + reliablePC.close(); + } + + // FIXME: not really the best check... + if (audioVideo) { + audioVideo = !!pc.addStream; + } + + // FIXME: this is not great because in theory it doesn't work for + // av-only browsers (?). + if (!onnegotiationneeded && data) { + // sync default check. + var negotiationPC = new RTCPeerConnection(defaultConfig, {optional: [{RtpDataChannels: true}]}); + negotiationPC.onnegotiationneeded = function() { + onnegotiationneeded = true; + // async check. + if (util && util.supports) { + util.supports.onnegotiationneeded = true; + } + }; + var negotiationDC = negotiationPC.createDataChannel('_PEERJSNEGOTIATIONTEST'); + + setTimeout(function() { + negotiationPC.close(); + }, 1000); + } + + if (pc) { + pc.close(); + } + + return { + audioVideo: audioVideo, + data: data, + binaryBlob: binaryBlob, + binary: sctp, // deprecated; sctp implies binary support. + reliable: sctp, // deprecated; sctp implies reliable data. + sctp: sctp, + onnegotiationneeded: onnegotiationneeded + }; + }()), + // + + // Ensure alphanumeric ids + validateId: function(id) { + // Allow empty ids + return !id || /^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(id); + }, + + validateKey: function(key) { + // Allow empty keys + return !key || /^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(key); + }, + + + debug: false, + + inherits: function(ctor, superCtor) { + ctor.super_ = superCtor; + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }, + extend: function(dest, source) { + for(var key in source) { + if(source.hasOwnProperty(key)) { + dest[key] = source[key]; + } + } + return dest; + }, + pack: BinaryPack.pack, + unpack: BinaryPack.unpack, + + log: function () { + if (util.debug) { + var err = false; + var copy = Array.prototype.slice.call(arguments); + copy.unshift('PeerJS: '); + for (var i = 0, l = copy.length; i < l; i++){ + if (copy[i] instanceof Error) { + copy[i] = '(' + copy[i].name + ') ' + copy[i].message; + err = true; + } + } + err ? console.error.apply(console, copy) : console.log.apply(console, copy); + } + }, + + setZeroTimeout: (function(global) { + var timeouts = []; + var messageName = 'zero-timeout-message'; + + // Like setTimeout, but only takes a function argument. There's + // no time argument (always zero) and no arguments (you have to + // use a closure). + function setZeroTimeoutPostMessage(fn) { + timeouts.push(fn); + global.postMessage(messageName, '*'); + } + + function handleMessage(event) { + if (event.source == global && event.data == messageName) { + if (event.stopPropagation) { + event.stopPropagation(); + } + if (timeouts.length) { + timeouts.shift()(); + } + } + } + if (global.addEventListener) { + global.addEventListener('message', handleMessage, true); + } else if (global.attachEvent) { + global.attachEvent('onmessage', handleMessage); + } + return setZeroTimeoutPostMessage; + }(this)), + + // Binary stuff + + // chunks a blob. + chunk: function(bl) { + var chunks = []; + var size = bl.size; + var start = index = 0; + var total = Math.ceil(size / util.chunkedMTU); + while (start < size) { + var end = Math.min(size, start + util.chunkedMTU); + var b = bl.slice(start, end); + + var chunk = { + __peerData: dataCount, + n: index, + data: b, + total: total + }; + + chunks.push(chunk); + + start = end; + index += 1; + } + dataCount += 1; + return chunks; + }, + + blobToArrayBuffer: function(blob, cb){ + var fr = new FileReader(); + fr.onload = function(evt) { + cb(evt.target.result); + }; + fr.readAsArrayBuffer(blob); + }, + blobToBinaryString: function(blob, cb){ + var fr = new FileReader(); + fr.onload = function(evt) { + cb(evt.target.result); + }; + fr.readAsBinaryString(blob); + }, + binaryStringToArrayBuffer: function(binary) { + var byteArray = new Uint8Array(binary.length); + for (var i = 0; i < binary.length; i++) { + byteArray[i] = binary.charCodeAt(i) & 0xff; + } + return byteArray.buffer; + }, + randomToken: function () { + return Math.random().toString(36).substr(2); + }, + // + + isSecure: function() { + return location.protocol === 'https:'; + } +}; + +exports.util = util; +/** + * A peer who can initiate connections with other peers. + */ +function Peer(id, options) { + if (!(this instanceof Peer)) return new Peer(id, options); + EventEmitter.call(this); + + // Deal with overloading + if (id && id.constructor == Object) { + options = id; + id = undefined; + } else if (id) { + // Ensure id is a string + id = id.toString(); + } + // + + // Configurize options + options = util.extend({ + debug: 0, // 1: Errors, 2: Warnings, 3: All logs + host: util.CLOUD_HOST, + port: util.CLOUD_PORT, + key: 'peerjs', + path: '/', + config: util.defaultConfig + }, options); + this.options = options; + // Detect relative URL host. + if (options.host === '/') { + options.host = window.location.hostname; + } + // Set path correctly. + if (options.path[0] !== '/') { + options.path = '/' + options.path; + } + if (options.path[options.path.length - 1] !== '/') { + options.path += '/'; + } + + // Set whether we use SSL to same as current host + if (options.secure === undefined && options.host !== util.CLOUD_HOST) { + options.secure = util.isSecure(); + } + // Set a custom log function if present + if (options.logFunction) { + util.setLogFunction(options.logFunction); + } + util.setLogLevel(options.debug); + // + + // Sanity checks + // Ensure WebRTC supported + if (!util.supports.audioVideo && !util.supports.data ) { + this._delayedAbort('browser-incompatible', 'The current browser does not support WebRTC'); + return; + } + // Ensure alphanumeric id + if (!util.validateId(id)) { + this._delayedAbort('invalid-id', 'ID "' + id + '" is invalid'); + return; + } + // Ensure valid key + if (!util.validateKey(options.key)) { + this._delayedAbort('invalid-key', 'API KEY "' + options.key + '" is invalid'); + return; + } + // Ensure not using unsecure cloud server on SSL page + if (options.secure && options.host === '0.peerjs.com') { + this._delayedAbort('ssl-unavailable', + 'The cloud server currently does not support HTTPS. Please run your own PeerServer to use HTTPS.'); + return; + } + // + + // States. + this.destroyed = false; // Connections have been killed + this.disconnected = false; // Connection to PeerServer killed manually but P2P connections still active + this.open = false; // Sockets and such are not yet open. + // + + // References + this.connections = {}; // DataConnections for this peer. + this._lostMessages = {}; // src => [list of messages] + // + + // Initialize the 'socket' (which is actually a mix of XHR streaming and + // websockets.) + var self = this; + this.socket = new Socket(this.options.secure, this.options.host, this.options.port, this.options.path, this.options.key); + this.socket.on('message', function(data) { + self._handleMessage(data); + }); + this.socket.on('error', function(error) { + self._abort('socket-error', error); + }); + this.socket.on('close', function() { + if (!self.disconnected) { // If we haven't explicitly disconnected, emit error. + self._abort('socket-closed', 'Underlying socket is already closed.'); + } + }); + // + + // Start the connections + if (id) { + this._initialize(id); + } else { + this._retrieveId(); + } + // +}; + +util.inherits(Peer, EventEmitter); + +/** Get a unique ID from the server via XHR. */ +Peer.prototype._retrieveId = function(cb) { + var self = this; + var http = new XMLHttpRequest(); + var protocol = this.options.secure ? 'https://' : 'http://'; + var url = protocol + this.options.host + ':' + this.options.port + + this.options.path + this.options.key + '/id'; + var queryString = '?ts=' + new Date().getTime() + '' + Math.random(); + url += queryString; + + // If there's no ID we need to wait for one before trying to init socket. + http.open('get', url, true); + http.onerror = function(e) { + util.error('Error retrieving ID', e); + var pathError = ''; + if (self.options.path === '/' && self.options.host !== util.CLOUD_HOST) { + pathError = ' If you passed in a `path` to your self-hosted PeerServer, ' + + 'you\'ll also need to pass in that same path when creating a new' + + ' Peer.'; + } + self._abort('server-error', 'Could not get an ID from the server.' + pathError); + } + http.onreadystatechange = function() { + if (http.readyState !== 4) { + return; + } + if (http.status !== 200) { + http.onerror(); + return; + } + self._initialize(http.responseText); + }; + http.send(null); +}; + +/** Initialize a connection with the server. */ +Peer.prototype._initialize = function(id) { + var self = this; + this.id = id; + this.socket.start(this.id); +} + +/** Handles messages from the server. */ +Peer.prototype._handleMessage = function(message) { + var type = message.type; + var payload = message.payload; + var peer = message.src; + + switch (type) { + case 'OPEN': // The connection to the server is open. + this.emit('open', this.id); + this.open = true; + break; + case 'ERROR': // Server error. + this._abort('server-error', payload.msg); + break; + case 'ID-TAKEN': // The selected ID is taken. + this._abort('unavailable-id', 'ID `' + this.id + '` is taken'); + break; + case 'INVALID-KEY': // The given API key cannot be found. + this._abort('invalid-key', 'API KEY "' + this.options.key + '" is invalid'); + break; + + // + case 'LEAVE': // Another peer has closed its connection to this peer. + util.log('Received leave message from', peer); + this._cleanupPeer(peer); + break; + + case 'EXPIRE': // The offer sent to a peer has expired without response. + this.emit('error', new Error('Could not connect to peer ' + peer)); + break; + case 'OFFER': // we should consider switching this to CALL/CONNECT, but this is the least breaking option. + var connectionId = payload.connectionId; + var connection = this.getConnection(peer, connectionId); + + if (connection) { + util.warn('Offer received for existing Connection ID:', connectionId); + //connection.handleMessage(message); + } else { + // Create a new connection. + if (payload.type === 'media') { + var connection = new MediaConnection(peer, this, { + connectionId: connectionId, + _payload: payload, + metadata: payload.metadata + }); + this._addConnection(peer, connection); + this.emit('call', connection); + } else if (payload.type === 'data') { + connection = new DataConnection(peer, this, { + connectionId: connectionId, + _payload: payload, + metadata: payload.metadata, + label: payload.label, + serialization: payload.serialization, + reliable: payload.reliable + }); + this._addConnection(peer, connection); + this.emit('connection', connection); + } else { + util.warn('Received malformed connection type:', payload.type); + return; + } + // Find messages. + var messages = this._getMessages(connectionId); + for (var i = 0, ii = messages.length; i < ii; i += 1) { + connection.handleMessage(messages[i]); + } + } + break; + default: + if (!payload) { + util.warn('You received a malformed message from ' + peer + ' of type ' + type); + return; + } + + var id = payload.connectionId; + var connection = this.getConnection(peer, id); + + if (connection && connection.pc) { + // Pass it on. + connection.handleMessage(message); + } else if (id) { + // Store for possible later use + this._storeMessage(id, message); + } else { + util.warn('You received an unrecognized message:', message); + } + break; + } +} + +/** Stores messages without a set up connection, to be claimed later. */ +Peer.prototype._storeMessage = function(connectionId, message) { + if (!this._lostMessages[connectionId]) { + this._lostMessages[connectionId] = []; + } + this._lostMessages[connectionId].push(message); +} + +/** Retrieve messages from lost message store */ +Peer.prototype._getMessages = function(connectionId) { + var messages = this._lostMessages[connectionId]; + if (messages) { + delete this._lostMessages[connectionId]; + return messages; + } else { + return []; + } +} + +/** + * Returns a DataConnection to the specified peer. See documentation for a + * complete list of options. + */ +Peer.prototype.connect = function(peer, options) { + if (this.disconnected) { + util.warn('You cannot connect to a new Peer because you called ' + + '.disconnect() on this Peer and ended your connection with the' + + ' server. You can create a new Peer to reconnect.'); + this.emit('error', new Error('Cannot connect to new Peer after disconnecting from server.')); + return; + } + var connection = new DataConnection(peer, this, options); + this._addConnection(peer, connection); + return connection; +} + +/** + * Returns a MediaConnection to the specified peer. See documentation for a + * complete list of options. + */ +Peer.prototype.call = function(peer, stream, options) { + if (this.disconnected) { + util.warn('You cannot connect to a new Peer because you called ' + + '.disconnect() on this Peer and ended your connection with the' + + ' server. You can create a new Peer to reconnect.'); + this.emit('error', new Error('Cannot connect to new Peer after disconnecting from server.')); + return; + } + if (!stream) { + util.error('To call a peer, you must provide a stream from your browser\'s `getUserMedia`.'); + return; + } + options = options || {}; + options._stream = stream; + var call = new MediaConnection(peer, this, options); + this._addConnection(peer, call); + return call; +} + +/** Add a data/media connection to this peer. */ +Peer.prototype._addConnection = function(peer, connection) { + if (!this.connections[peer]) { + this.connections[peer] = []; + } + this.connections[peer].push(connection); +} + +/** Retrieve a data/media connection for this peer. */ +Peer.prototype.getConnection = function(peer, id) { + var connections = this.connections[peer]; + if (!connections) { + return null; + } + for (var i = 0, ii = connections.length; i < ii; i++) { + if (connections[i].id === id) { + return connections[i]; + } + } + return null; +} + +Peer.prototype._delayedAbort = function(type, message) { + var self = this; + util.setZeroTimeout(function(){ + self._abort(type, message); + }); +} + +/** Destroys the Peer and emits an error message. */ +Peer.prototype._abort = function(type, message) { + util.error('Aborting. Error:', message); + var err = new Error(message); + err.type = type; + this.destroy(); + this.emit('error', err); +}; + +/** + * Destroys the Peer: closes all active connections as well as the connection + * to the server. + * Warning: The peer can no longer create or accept connections after being + * destroyed. + */ +Peer.prototype.destroy = function() { + if (!this.destroyed) { + this._cleanup(); + this.disconnect(); + this.destroyed = true; + } +} + + +/** Disconnects every connection on this peer. */ +Peer.prototype._cleanup = function() { + if (this.connections) { + var peers = Object.keys(this.connections); + for (var i = 0, ii = peers.length; i < ii; i++) { + this._cleanupPeer(peers[i]); + } + } + this.emit('close'); +} + +/** Closes all connections to this peer. */ +Peer.prototype._cleanupPeer = function(peer) { + var connections = this.connections[peer]; + for (var j = 0, jj = connections.length; j < jj; j += 1) { + connections[j].close(); + } +} + +/** + * Disconnects the Peer's connection to the PeerServer. Does not close any + * active connections. + * Warning: The peer can no longer create or accept connections after being + * disconnected. It also cannot reconnect to the server. + */ +Peer.prototype.disconnect = function() { + var self = this; + util.setZeroTimeout(function(){ + if (!self.disconnected) { + self.disconnected = true; + self.open = false; + if (self.socket) { + self.socket.close(); + } + self.id = null; + } + }); +} + +/** + * Get a list of available peer IDs. If you're running your own server, you'll + * want to set allow_discovery: true in the PeerServer options. If you're using + * the cloud server, email team@peerjs.com to get the functionality enabled for + * your key. + */ +Peer.prototype.listAllPeers = function(cb) { + cb = cb || function() {}; + var self = this; + var http = new XMLHttpRequest(); + var protocol = this.options.secure ? 'https://' : 'http://'; + var url = protocol + this.options.host + ':' + this.options.port + + this.options.path + this.options.key + '/peers'; + var queryString = '?ts=' + new Date().getTime() + '' + Math.random(); + url += queryString; + + // If there's no ID we need to wait for one before trying to init socket. + http.open('get', url, true); + http.onerror = function(e) { + self._abort('server-error', 'Could not get peers from the server.'); + cb([]); + } + http.onreadystatechange = function() { + if (http.readyState !== 4) { + return; + } + if (http.status === 401) { + var helpfulError = ''; + if (self.options.host !== util.CLOUD_HOST) { + helpfulError = 'It looks like you\'re using the cloud server. You can email ' + + 'team@peerjs.com to enable peer listing for your API key.'; + } else { + helpfulError = 'You need to enable `allow_discovery` on your self-hosted' + + ' PeerServer to use this feature.'; + } + throw new Error('It doesn\'t look like you have permission to list peers IDs. ' + helpfulError); + cb([]); + } else if (http.status !== 200) { + cb([]); + } else { + cb(JSON.parse(http.responseText)); + } + }; + http.send(null); +} + +exports.Peer = Peer; +/** + * Wraps a DataChannel between two Peers. + */ +function DataConnection(peer, provider, options) { + if (!(this instanceof DataConnection)) return new DataConnection(peer, provider, options); + EventEmitter.call(this); + + this.options = util.extend({ + serialization: 'binary', + reliable: false + }, options); + + // Connection is not open yet. + this.open = false; + this.type = 'data'; + this.peer = peer; + this.provider = provider; + + this.id = this.options.connectionId || DataConnection._idPrefix + util.randomToken(); + + this.label = this.options.label || this.id; + this.metadata = this.options.metadata; + this.serialization = this.options.serialization; + this.reliable = this.options.reliable; + + // Data channel buffering. + this._buffer = []; + this._buffering = false; + this.bufferSize = 0; + + // For storing large data. + this._chunkedData = {}; + + if (this.options._payload) { + this._peerBrowser = this.options._payload.browser; + } + + Negotiator.startConnection( + this, + this.options._payload || { + originator: true + } + ); +} + +util.inherits(DataConnection, EventEmitter); + +DataConnection._idPrefix = 'dc_'; + +/** Called by the Negotiator when the DataChannel is ready. */ +DataConnection.prototype.initialize = function(dc) { + this._dc = this.dataChannel = dc; + this._configureDataChannel(); +} + +DataConnection.prototype._configureDataChannel = function() { + var self = this; + if (util.supports.sctp) { + this._dc.binaryType = 'arraybuffer'; + } + this._dc.onopen = function() { + util.log('Data channel connection success'); + self.open = true; + self.emit('open'); + } + + // Use the Reliable shim for non Firefox browsers + if (!util.supports.sctp && this.reliable) { + this._reliable = new Reliable(this._dc, util.debug); + } + + if (this._reliable) { + this._reliable.onmessage = function(msg) { + self.emit('data', msg); + }; + } else { + this._dc.onmessage = function(e) { + self._handleDataMessage(e); + }; + } + this._dc.onclose = function(e) { + util.log('DataChannel closed for:', self.peer); + self.close(); + }; +} + +// Handles a DataChannel message. +DataConnection.prototype._handleDataMessage = function(e) { + var self = this; + var data = e.data; + var datatype = data.constructor; + if (this.serialization === 'binary' || this.serialization === 'binary-utf8') { + if (datatype === Blob) { + // Datatype should never be blob + util.blobToArrayBuffer(data, function(ab) { + data = util.unpack(ab); + self.emit('data', data); + }); + return; + } else if (datatype === ArrayBuffer) { + data = util.unpack(data); + } else if (datatype === String) { + // String fallback for binary data for browsers that don't support binary yet + var ab = util.binaryStringToArrayBuffer(data); + data = util.unpack(ab); + } + } else if (this.serialization === 'json') { + data = JSON.parse(data); + } + + // Check if we've chunked--if so, piece things back together. + // We're guaranteed that this isn't 0. + if (data.__peerData) { + var id = data.__peerData; + var chunkInfo = this._chunkedData[id] || {data: [], count: 0, total: data.total}; + + chunkInfo.data[data.n] = data.data; + chunkInfo.count += 1; + + if (chunkInfo.total === chunkInfo.count) { + // We've received all the chunks--time to construct the complete data. + data = new Blob(chunkInfo.data); + this._handleDataMessage({data: data}); + + // We can also just delete the chunks now. + delete this._chunkedData[id]; + } + + this._chunkedData[id] = chunkInfo; + return; + } + + this.emit('data', data); +} + +/** + * Exposed functionality for users. + */ + +/** Allows user to close connection. */ +DataConnection.prototype.close = function() { + if (!this.open) { + return; + } + this.open = false; + Negotiator.cleanup(this); + this.emit('close'); +} + +/** Allows user to send data. */ +DataConnection.prototype.send = function(data, chunked) { + if (!this.open) { + this.emit('error', new Error('Connection is not open. You should listen for the `open` event before sending messages.')); + return; + } + if (this._reliable) { + // Note: reliable shim sending will make it so that you cannot customize + // serialization. + this._reliable.send(data); + return; + } + var self = this; + if (this.serialization === 'json') { + this._bufferedSend(JSON.stringify(data)); + } else if (this.serialization === 'binary' || this.serialization === 'binary-utf8') { + var blob = util.pack(data); + + // For Chrome-Firefox interoperability, we need to make Firefox "chunk" + // the data it sends out. + var needsChunking = util.chunkedBrowsers[this._peerBrowser] || util.chunkedBrowsers[util.browser]; + if (needsChunking && !chunked && blob.size > util.chunkedMTU) { + this._sendChunks(blob); + return; + } + + // DataChannel currently only supports strings. + if (!util.supports.sctp) { + util.blobToBinaryString(blob, function(str) { + self._bufferedSend(str); + }); + } else if (!util.supports.binaryBlob) { + // We only do this if we really need to (e.g. blobs are not supported), + // because this conversion is costly. + util.blobToArrayBuffer(blob, function(ab) { + self._bufferedSend(ab); + }); + } else { + this._bufferedSend(blob); + } + } else { + this._bufferedSend(data); + } +} + +DataConnection.prototype._bufferedSend = function(msg) { + if (this._buffering || !this._trySend(msg)) { + this._buffer.push(msg); + this.bufferSize = this._buffer.length; + } +} + +// Returns true if the send succeeds. +DataConnection.prototype._trySend = function(msg) { + try { + this._dc.send(msg); + } catch (e) { + this._buffering = true; + + var self = this; + setTimeout(function() { + // Try again. + self._buffering = false; + self._tryBuffer(); + }, 100); + return false; + } + return true; +} + +// Try to send the first message in the buffer. +DataConnection.prototype._tryBuffer = function() { + if (this._buffer.length === 0) { + return; + } + + var msg = this._buffer[0]; + + if (this._trySend(msg)) { + this._buffer.shift(); + this.bufferSize = this._buffer.length; + this._tryBuffer(); + } +} + +DataConnection.prototype._sendChunks = function(blob) { + var blobs = util.chunk(blob); + for (var i = 0, ii = blobs.length; i < ii; i += 1) { + var blob = blobs[i]; + this.send(blob, true); + } +} + +DataConnection.prototype.handleMessage = function(message) { + var payload = message.payload; + + switch (message.type) { + case 'ANSWER': + this._peerBrowser = payload.browser; + + // Forward to negotiator + Negotiator.handleSDP(message.type, this, payload.sdp); + break; + case 'CANDIDATE': + Negotiator.handleCandidate(this, payload.candidate); + break; + default: + util.warn('Unrecognized message type:', message.type, 'from peer:', this.peer); + break; + } +} +/** + * Wraps the streaming interface between two Peers. + */ +function MediaConnection(peer, provider, options) { + if (!(this instanceof MediaConnection)) return new MediaConnection(peer, provider, options); + EventEmitter.call(this); + + this.options = util.extend({}, options); + + this.open = false; + this.type = 'media'; + this.peer = peer; + this.provider = provider; + this.metadata = this.options.metadata; + this.localStream = this.options._stream; + + this.id = this.options.connectionId || MediaConnection._idPrefix + util.randomToken(); + if (this.localStream) { + Negotiator.startConnection( + this, + {_stream: this.localStream, originator: true} + ); + } +}; + +util.inherits(MediaConnection, EventEmitter); + +MediaConnection._idPrefix = 'mc_'; + +MediaConnection.prototype.addStream = function(remoteStream) { + util.log('Receiving stream', remoteStream); + + this.remoteStream = remoteStream; + this.emit('stream', remoteStream); // Should we call this `open`? + +}; + +MediaConnection.prototype.handleMessage = function(message) { + var payload = message.payload; + + switch (message.type) { + case 'ANSWER': + // Forward to negotiator + Negotiator.handleSDP(message.type, this, payload.sdp); + this.open = true; + break; + case 'CANDIDATE': + Negotiator.handleCandidate(this, payload.candidate); + break; + default: + util.warn('Unrecognized message type:', message.type, 'from peer:', this.peer); + break; + } +} + +MediaConnection.prototype.answer = function(stream) { + if (this.localStream) { + util.warn('Local stream already exists on this MediaConnection. Are you answering a call twice?'); + return; + } + + this.options._payload._stream = stream; + + this.localStream = stream; + Negotiator.startConnection( + this, + this.options._payload + ) + // Retrieve lost messages stored because PeerConnection not set up. + var messages = this.provider._getMessages(this.id); + for (var i = 0, ii = messages.length; i < ii; i += 1) { + this.handleMessage(messages[i]); + } + this.open = true; +}; + +/** + * Exposed functionality for users. + */ + +/** Allows user to close connection. */ +MediaConnection.prototype.close = function() { + if (!this.open) { + return; + } + this.open = false; + Negotiator.cleanup(this); + this.emit('close') +}; +/** + * Manages all negotiations between Peers. + */ +var Negotiator = { + pcs: { + data: {}, + media: {} + }, // type => {peerId: {pc_id: pc}}. + //providers: {}, // provider's id => providers (there may be multiple providers/client. + queue: [] // connections that are delayed due to a PC being in use. +} + +Negotiator._idPrefix = 'pc_'; + +/** Returns a PeerConnection object set up correctly (for data, media). */ +Negotiator.startConnection = function(connection, options) { + var pc = Negotiator._getPeerConnection(connection, options); + + if (connection.type === 'media' && options._stream) { + // Add the stream. + pc.addStream(options._stream); + } + + // Set the connection's PC. + connection.pc = connection.peerConnection = pc; + // What do we need to do now? + if (options.originator) { + if (connection.type === 'data') { + // Create the datachannel. + var config = {}; + // Dropping reliable:false support, since it seems to be crashing + // Chrome. + /*if (util.supports.sctp && !options.reliable) { + // If we have canonical reliable support... + config = {maxRetransmits: 0}; + }*/ + // Fallback to ensure older browsers don't crash. + if (!util.supports.sctp) { + config = {reliable: options.reliable}; + } + var dc = pc.createDataChannel(connection.label, config); + connection.initialize(dc); + } + + if (!util.supports.onnegotiationneeded) { + Negotiator._makeOffer(connection); + } + } else { + Negotiator.handleSDP('OFFER', connection, options.sdp); + } +} + +Negotiator._getPeerConnection = function(connection, options) { + if (!Negotiator.pcs[connection.type]) { + util.error(connection.type + ' is not a valid connection type. Maybe you overrode the `type` property somewhere.'); + } + + if (!Negotiator.pcs[connection.type][connection.peer]) { + Negotiator.pcs[connection.type][connection.peer] = {}; + } + var peerConnections = Negotiator.pcs[connection.type][connection.peer]; + + var pc; + // Not multiplexing while FF and Chrome have not-great support for it. + /*if (options.multiplex) { + ids = Object.keys(peerConnections); + for (var i = 0, ii = ids.length; i < ii; i += 1) { + pc = peerConnections[ids[i]]; + if (pc.signalingState === 'stable') { + break; // We can go ahead and use this PC. + } + } + } else */ + if (options.pc) { // Simplest case: PC id already provided for us. + pc = Negotiator.pcs[connection.type][connection.peer][options.pc]; + } + + if (!pc || pc.signalingState !== 'stable') { + pc = Negotiator._startPeerConnection(connection); + } + return pc; +} + +/* +Negotiator._addProvider = function(provider) { + if ((!provider.id && !provider.disconnected) || !provider.socket.open) { + // Wait for provider to obtain an ID. + provider.on('open', function(id) { + Negotiator._addProvider(provider); + }); + } else { + Negotiator.providers[provider.id] = provider; + } +}*/ + + +/** Start a PC. */ +Negotiator._startPeerConnection = function(connection) { + util.log('Creating RTCPeerConnection.'); + + var id = Negotiator._idPrefix + util.randomToken(); + var optional = {}; + + if (connection.type === 'data' && !util.supports.sctp) { + optional = {optional: [{RtpDataChannels: true}]}; + } else if (connection.type === 'media') { + // Interop req for chrome. + optional = {optional: [{DtlsSrtpKeyAgreement: true}]}; + } + + var pc = new RTCPeerConnection(connection.provider.options.config, optional); + Negotiator.pcs[connection.type][connection.peer][id] = pc; + + Negotiator._setupListeners(connection, pc, id); + + return pc; +} + +/** Set up various WebRTC listeners. */ +Negotiator._setupListeners = function(connection, pc, pc_id) { + var peerId = connection.peer; + var connectionId = connection.id; + var provider = connection.provider; + + // ICE CANDIDATES. + util.log('Listening for ICE candidates.'); + pc.onicecandidate = function(evt) { + if (evt.candidate) { + util.log('Received ICE candidates for:', connection.peer); + provider.socket.send({ + type: 'CANDIDATE', + payload: { + candidate: evt.candidate, + type: connection.type, + connectionId: connection.id + }, + dst: peerId + }); + } + }; + + pc.oniceconnectionstatechange = function() { + switch (pc.iceConnectionState) { + case 'disconnected': + case 'failed': + util.log('iceConnectionState is disconnected, closing connections to ' + peerId); + connection.close(); + break; + case 'completed': + pc.onicecandidate = util.noop; + break; + } + }; + + // Fallback for older Chrome impls. + pc.onicechange = pc.oniceconnectionstatechange; + + // ONNEGOTIATIONNEEDED (Chrome) + util.log('Listening for `negotiationneeded`'); + pc.onnegotiationneeded = function() { + util.log('`negotiationneeded` triggered'); + if (pc.signalingState == 'stable') { + Negotiator._makeOffer(connection); + } else { + util.log('onnegotiationneeded triggered when not stable. Is another connection being established?'); + } + }; + + // DATACONNECTION. + util.log('Listening for data channel'); + // Fired between offer and answer, so options should already be saved + // in the options hash. + pc.ondatachannel = function(evt) { + util.log('Received data channel'); + var dc = evt.channel; + var connection = provider.getConnection(peerId, connectionId); + connection.initialize(dc); + }; + + // MEDIACONNECTION. + util.log('Listening for remote stream'); + pc.onaddstream = function(evt) { + util.log('Received remote stream'); + var stream = evt.stream; + provider.getConnection(peerId, connectionId).addStream(stream); + }; +} + +Negotiator.cleanup = function(connection) { + util.log('Cleaning up PeerConnection to ' + connection.peer); + + var pc = connection.pc; + + if (!!pc && (pc.readyState !== 'closed' || pc.signalingState !== 'closed')) { + pc.close(); + connection.pc = null; + } +} + +Negotiator._makeOffer = function(connection) { + var pc = connection.pc; + pc.createOffer(function(offer) { + util.log('Created offer.'); + + if (!util.supports.sctp && connection.type === 'data' && connection.reliable) { + offer.sdp = Reliable.higherBandwidthSDP(offer.sdp); + } + + pc.setLocalDescription(offer, function() { + util.log('Set localDescription: offer', 'for:', connection.peer); + connection.provider.socket.send({ + type: 'OFFER', + payload: { + sdp: offer, + type: connection.type, + label: connection.label, + connectionId: connection.id, + reliable: connection.reliable, + serialization: connection.serialization, + metadata: connection.metadata, + browser: util.browser + }, + dst: connection.peer + }); + }, function(err) { + connection.provider.emit('error', err); + util.log('Failed to setLocalDescription, ', err); + }); + }, function(err) { + connection.provider.emit('error', err); + util.log('Failed to createOffer, ', err); + }, connection.options.constraints); +} + +Negotiator._makeAnswer = function(connection) { + var pc = connection.pc; + + pc.createAnswer(function(answer) { + util.log('Created answer.'); + + if (!util.supports.sctp && connection.type === 'data' && connection.reliable) { + answer.sdp = Reliable.higherBandwidthSDP(answer.sdp); + } + + pc.setLocalDescription(answer, function() { + util.log('Set localDescription: answer', 'for:', connection.peer); + connection.provider.socket.send({ + type: 'ANSWER', + payload: { + sdp: answer, + type: connection.type, + connectionId: connection.id, + browser: util.browser + }, + dst: connection.peer + }); + }, function(err) { + connection.provider.emit('error', err); + util.log('Failed to setLocalDescription, ', err); + }); + }, function(err) { + connection.provider.emit('error', err); + util.log('Failed to create answer, ', err); + }); +} + +/** Handle an SDP. */ +Negotiator.handleSDP = function(type, connection, sdp) { + sdp = new RTCSessionDescription(sdp); + var pc = connection.pc; + + util.log('Setting remote description', sdp); + pc.setRemoteDescription(sdp, function() { + util.log('Set remoteDescription:', type, 'for:', connection.peer); + + if (type === 'OFFER') { + Negotiator._makeAnswer(connection); + } + }, function(err) { + connection.provider.emit('error', err); + util.log('Failed to setRemoteDescription, ', err); + }); +} + +/** Handle a candidate. */ +Negotiator.handleCandidate = function(connection, ice) { + var candidate = ice.candidate; + var sdpMLineIndex = ice.sdpMLineIndex; + connection.pc.addIceCandidate(new RTCIceCandidate({ + sdpMLineIndex: sdpMLineIndex, + candidate: candidate + })); + util.log('Added ICE candidate for:', connection.peer); +} +/** + * An abstraction on top of WebSockets and XHR streaming to provide fastest + * possible connection for peers. + */ +function Socket(secure, host, port, path, key) { + if (!(this instanceof Socket)) return new Socket(secure, host, port, path, key); + + EventEmitter.call(this); + + // Disconnected manually. + this.disconnected = false; + this._queue = []; + + var httpProtocol = secure ? 'https://' : 'http://'; + var wsProtocol = secure ? 'wss://' : 'ws://'; + this._httpUrl = httpProtocol + host + ':' + port + path + key; + this._wsUrl = wsProtocol + host + ':' + port + path + 'peerjs?key=' + key; +} + +util.inherits(Socket, EventEmitter); + + +/** Check in with ID or get one from server. */ +Socket.prototype.start = function(id) { + this.id = id; + + var token = util.randomToken(); + this._httpUrl += '/' + id + '/' + token; + this._wsUrl += '&id='+id+'&token='+token; + + this._startXhrStream(); + this._startWebSocket(); +} + + +/** Start up websocket communications. */ +Socket.prototype._startWebSocket = function(id) { + var self = this; + + if (this._socket) { + return; + } + + this._socket = new WebSocket(this._wsUrl); + + this._socket.onmessage = function(event) { + var data; + try { + data = JSON.parse(event.data); + } catch(e) { + util.log('Invalid server message', event.data); + return; + } + self.emit('message', data); + }; + + // Take care of the queue of connections if necessary and make sure Peer knows + // socket is open. + this._socket.onopen = function() { + if (self._timeout) { + clearTimeout(self._timeout); + setTimeout(function(){ + self._http.abort(); + self._http = null; + }, 5000); + } + self._sendQueuedMessages(); + util.log('Socket open'); + }; +} + +/** Start XHR streaming. */ +Socket.prototype._startXhrStream = function(n) { + try { + var self = this; + this._http = new XMLHttpRequest(); + this._http._index = 1; + this._http._streamIndex = n || 0; + this._http.open('post', this._httpUrl + '/id?i=' + this._http._streamIndex, true); + this._http.onreadystatechange = function() { + if (this.readyState == 2 && this.old) { + this.old.abort(); + delete this.old; + } + if (this.readyState > 2 && this.status == 200 && this.responseText) { + self._handleStream(this); + } + }; + this._http.send(null); + this._setHTTPTimeout(); + } catch(e) { + util.log('XMLHttpRequest not available; defaulting to WebSockets'); + } +} + + +/** Handles onreadystatechange response as a stream. */ +Socket.prototype._handleStream = function(http) { + // 3 and 4 are loading/done state. All others are not relevant. + var messages = http.responseText.split('\n'); + + // Check to see if anything needs to be processed on buffer. + if (http._buffer) { + while (http._buffer.length > 0) { + var index = http._buffer.shift(); + var bufferedMessage = messages[index]; + try { + bufferedMessage = JSON.parse(bufferedMessage); + } catch(e) { + http._buffer.shift(index); + break; + } + this.emit('message', bufferedMessage); + } + } + + var message = messages[http._index]; + if (message) { + http._index += 1; + // Buffering--this message is incomplete and we'll get to it next time. + // This checks if the httpResponse ended in a `\n`, in which case the last + // element of messages should be the empty string. + if (http._index === messages.length) { + if (!http._buffer) { + http._buffer = []; + } + http._buffer.push(http._index - 1); + } else { + try { + message = JSON.parse(message); + } catch(e) { + util.log('Invalid server message', message); + return; + } + this.emit('message', message); + } + } +} + +Socket.prototype._setHTTPTimeout = function() { + var self = this; + this._timeout = setTimeout(function() { + var old = self._http; + if (!self._wsOpen()) { + self._startXhrStream(old._streamIndex + 1); + self._http.old = old; + } else { + old.abort(); + } + }, 25000); +} + +/** Is the websocket currently open? */ +Socket.prototype._wsOpen = function() { + return this._socket && this._socket.readyState == 1; +} + +/** Send queued messages. */ +Socket.prototype._sendQueuedMessages = function() { + for (var i = 0, ii = this._queue.length; i < ii; i += 1) { + this.send(this._queue[i]); + } +} + +/** Exposed send for DC & Peer. */ +Socket.prototype.send = function(data) { + if (this.disconnected) { + return; + } + + // If we didn't get an ID yet, we can't yet send anything so we should queue + // up these messages. + if (!this.id) { + this._queue.push(data); + return; + } + + if (!data.type) { + this.emit('error', 'Invalid message'); + return; + } + + var message = JSON.stringify(data); + if (this._wsOpen()) { + this._socket.send(message); + } else { + var http = new XMLHttpRequest(); + var url = this._httpUrl + '/' + data.type.toLowerCase(); + http.open('post', url, true); + http.setRequestHeader('Content-Type', 'application/json'); + http.send(message); + } +} + +Socket.prototype.close = function() { + if (!this.disconnected && this._wsOpen()) { + this._socket.close(); + this.disconnected = true; + } +} + +})(this);