From 18ea153d9598c8de76f88aaf3d1ec5fa10d51f89 Mon Sep 17 00:00:00 2001 From: Nicolas Raynaud Date: Sat, 26 Sep 2015 16:14:31 -0400 Subject: [PATCH] update three.js to latest version of three-depthtextures.js - split some GLSL shader to separate files --- images/IO_interface_schem.jpg | Bin 0 -> 580587 bytes images/IO_interface_schem_thumb.jpeg | Bin 0 -> 53424 bytes various_tests/test_3D.html | 5 +- .../test_3D_conservative_rendering.html | 3 - webapp/cnc/app/view.js | 12 +- webapp/cnc/cam/3D/modelProjector.js | 354 +- webapp/cnc/gcode/simulation.js | 2 - webapp/cnc/ui/threeDView.js | 12 +- .../libs/threejs/postprocessing/ShaderPass.js | 56 +- webapp/libs/threejs/three-depthtextures.js | 42262 ++++++++-------- webapp/shaders/conservative_model_proj.frag | 29 + webapp/shaders/conservative_model_proj.vert | 59 + webapp/shaders/model_proj.frag | 3 + webapp/shaders/model_proj.vert | 5 + webgcode.xcodeproj/project.pbxproj | 10 + 15 files changed, 22426 insertions(+), 20386 deletions(-) create mode 100644 images/IO_interface_schem.jpg create mode 100644 images/IO_interface_schem_thumb.jpeg create mode 100644 webapp/shaders/conservative_model_proj.frag create mode 100644 webapp/shaders/conservative_model_proj.vert create mode 100644 webapp/shaders/model_proj.frag create mode 100644 webapp/shaders/model_proj.vert diff --git a/images/IO_interface_schem.jpg b/images/IO_interface_schem.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2dcbf99d0af58ede9bc296f02b2fc00b11639332 GIT binary patch literal 580587 zcmeFZ2~<;Aw=Nn>5djhD5rmW?eG?TBL?k5^AW(qxJt;(_6JrAsBqSwW=qW`&L5WBw zZKPMkL`Z;ARHT=Hq(MbG*+B?f(%oI>zIX0D|2gBn_s+fJjXU1^Ggu>py|VY3Ypprw zH@`U-q7Nb_Y_Fr8gB@%e^xCs+4-6)nhMoSGPk;aAKl}RoCjZ&*{_FQdFJZE~#MlzM zw{JTL`$=}&cG+zr8VnADZ4>*iePI8`YuiuTcZls2-z6a_1$AiO3;Su?_U%9I*e)iv zV+Yhb3Azs3AuA?#;K-?+@@^sG2d^j`y?LvAm!?f~kD~i9aLDA`)no}tC1sU;s#@AQ zx_bJiX2;Aej$5Ao)z;4bjDw?xrnub+SD`LGM&5s^{XV&ksIV-jwpq^6}4GBUGn z7u+c>uQkh2;8a$Bykg#Q&CS+fVUO+b+99?7)$o za;MzHL$1glJbH7Ng3Yb+<{k-66L&!I+|^-8r9-AHEzaMf{flJ(_XJD+Pf7N#g8etS z=rCyYpvBuR3q!($LfuDMuz$Ayxj8Vn$I0hsm*fkciC_y!jj`izzyYD{TYKS*2)0h1 z=_P zs;c6NVFP$}BG_N6Rrn9}p(0pK(p8EPCnfXytTap2%7L1Ydy!=E8Z010hg|Ut8g*69 z&AoU`JBQfP$83&$_5^&RanOXye)7H0@|Lq(BVp^C#H%$(ILq2QUq1L_-t08df2%i9 z;pSKUnDxT7WOz;M|8=R#Le@fPLb5*?XknuJ8X zH8upc+-dz-t_9J+tF=zQGyIEUY#CVSqV{Wik3XlnTOsAOVMK^@a=KK>caUA!>Hs><#*2#`ry}#jPKyu)4J7fs07`B*8MWl zQd0NPAOCFsoP&Se17EO^ejfZ%A%b1PZ=s@t%&u7<5y5U6j+@BwHHwrDWa{25T6zn`W05n>kaAl3o z?ZQb?Xb1^J-?Q#RR=rp^Wor8PK+bvK>)zKc9VJIb#S67Bk?p8LJ?v%B2slQicGdJ# zvPCc{vP@(df@74Q%-4C$cjt&;sY19frlQ~SzPY(7I+cGSp8Yo9Oy>mO>2bi+$Dz>j z$d+}zL9vF+9SxnTUKhUho7TPdEviV*i5$DrELZ3-oJH+7{lwYhRXbLpc*^He zyXBqRaUqsxGd*h*{a#mDG+17x9fy7=WL2rY^O~tqJf&}vc44VY&+Wd!v06v7utig! zbZoewMg+S+oa#L(ho-~11aKdq1eZTK7OYl0hwLN4_{RA(Ip4aI%Qp^{S#WlycC}75 zG@Fh1s-<`O?R~x1vS0>l237$BbLJ^+lnlJMP!l81ru<w_{NRvhjHP+wHw&R^yZi5iF%EKE=wl2|Rb^ z9LG4DwW<(|d95z*Ki+-Ph$g#Ka=kR^T0-S3D?PmQ^D{TesbCUEjkVfG+7&_BOIF0J z_7LT*wzHisbOWn-)^JS4uN-2k8SmGkGa5^_90UQ;Zlvth;m0mgD(mNBihMuiJc$ic z)K{F_=i+J5zqh37`0Lz=Ud6yrEyrtxy3jA-68hKfM_Ig+^PCBnIKypwHja#TqB}VE z6WeN54%NPkT{Ls0C)(AHRe3+KdpK@+r>^&se2u#TPha9`I3#kt<0 z?P{V;zKZ621|^ z)P$x>6yT~WUm=|7@{E(n#En zw0@P6<6gUsZj41Zj8yIOtlb}^rFyH-(Q`VxK4PqTejszejXGTD`}#q`X?Ousf4Is& zyWTP3U6_Yqg?FWwW1eh{R;p*J6uYrHs*VqpBN8-z?O|`!h-3%S!@PGSmJ*%g*Q1gJ)5%`33Di zbY6|FL|W$Eu6G{yd7dqyJ_T^2fI1D<`UNjr zakKE|`@!Y}R6*}Gj3tVm!py<1Y6L@}~B7~ZE$nIiAwDEG@`;dX+N<(OI6>g3jW)sJ~E z!ts%D6~Pgc-r4w=divs@j$Cuqu)gf7Ps+eMZh^n$jJ<(P&ztcI|K$7i!;Lnk^jrs@ zD({@gSHoMqIUl1uJr4mZ=M*lmbWYVR8mDlnY5rbeDkJqSl{Lz`b`Komv@H7TD)ekj z7d`LHslIi)Q?bv^tmM(Bx^{?DmbGtJS$C;V3TA8y3LFXoP zm(i8!XJS7(IdLTo=AMT32xXZwo5pMxwMN>#;46g_UQeFZS=nYs)N8tC~Mj{~0v?&jpSDUz@-G6+v+R=K`VBLV2)o0N)l>uvNLu`a-z9Wkc7N}r<8T~4YgT>qbJzD!v^-|Dm!t1==yHH_ zG4IAuxCkaDFM=hn<9kj}&V7{o#-LHeDCdT@eZogX3Z?Yr1opf!+AKD2V++T*c?dTgxCqqa!d)>^n>%lVt+ogkb>U-LV;&JaglKuav(ej1%`ecI91ajWrL zdw>C2Y}Rzhu=q5~uJGE`^DlVL<3m%IznIAE?&vU(MR4Cq)ZV+`XKz-uf}iL%`%THi z#)yy5VH56uSw9eY)sEDbT=}RP`z^r^ExY^VE-lw=;OWKuqj%tj<@@uM?bYv{(^47D zy8Xt&dH7YmC%5jByW8VB{Y-c z*h+kfmgLKTQh3fhtFuRD2iAfUPjlVzf{??PX~5i`q|%LRn-*sGjhdmQjXsjg#Rf8s zg<6{c9QhhE+V1rc-#jwgrxoBplM7i?fUf&Iy$S}DA64-PvP(Hdb4%E zObt=Z3#TDJObaEU*nP(b=qZ`F5oCulU*3bspfaZAxt9e`&888m*f>E+w;#vJ4h);i z--UIRAw|)NS*ETEm`$4Mv23wBWrmYx4^Z|KZOe0qk|>Kh3bA&m}qM7vHT5^ zP-2O5O(+?|$v+FeVxvG^<>gkOnCk>o8Zk?eH{mi^WsaI7STQ%<@5Uy8DfJ+)PeYZ1 z&%~M--AU`#lu3_SUKhcnqhw9}hsmR1xs8`9mlM)2%h-?4r+%l2k>06 zKM88_5k$HN)8`MN6=q1Nil9N0$Uj+wbscWt`>;s6Fdo#|J%DTWx7Aq0p2eLpmZP*1 zZtdJX&F7r`6Z!QYrXsqMI#Lp=tNL5aWV$HKRE^zjf;>o{tC4^&8q~3;CWU)H6+93s zV@6q%eT*~>g|YVZ$pgS|v&Jo`6=_R`TiKd$Yi$E-LIMqX{j1AsgM*@#-}8|psNg%% zM`qFdMrci8AWtYc&O0G|i{FnsgzHDPkf04Z9nMVviM&&o)n8wLA$U$JRRr5f*5vDf zZ&`3@@WP4J{VznYJoz*4&GL6*TB%*6G*iK8+Y$7}iuc+Q^%z<~xLYFeN|SIN+L(qS z*m)G)hpPx6_zJE<$!N&XsE%Wes)S?2w)W~5oK7|}8EaDiAv#zUbO1J4J_LTyoQ2hK z+$FmtXY1(?u!xdPUn9v)@&YeZ?=$;bInlHp^Yo~|aH$)xM>+-AK=P__j|C}{Re7Dt=ANk{l zRV0T@XqbEV&xPdsMmpv^abpJ?_lDLD9*r|{EYdf-d@u0wZI|F_3HRc$uOn6agEhBK zlfFC`?s))?&xv5^M8KJ`2YVF6vjc`C26^tdVa6UD3>@?v9T8mmW({9b70>MsVN1&* zOnJ}!m~E4ny45QeZmit<&YI*OxzYsQ_%iF{?9P;dmV=o}Y@1)nNdaNCgUK`Cmf!K#>?e{KetgdBWD5XtM zeIfG2!?$SlWrxA12s$d=up1c~QWbzp6uw5SwQcehw+r9YGx5Sl@Rqb2%FDw%Cwxzz zPJn<&-o=lHwBs6f*J!LjZ9upi=p10sM?|nW2KBA$=}BwZuUmq`&KztEYc(xc4y?qU zikuUK_Z)Gxz84p5+*lO|zFt6o0u#y?q_43)YED8C597liw*au`D^GGcf(|P;;8P^N z7158LN@4j>N$C_hJnJx^wS+0|x84shmIWb=3z|J$@tLbuNfp^rl^Xf6_#*KaV4i1>6}J8ryFXK-V|B(do$2~nW5&<3le*vy{_Q7tGI~sfZ zN^`Z+dL7f1x`nmw$vq=m`IJv~55x=3Qdw7L>}2Hak1)+LDmveD>$t z0|J{PG@rh6MkSw0j>H6W?=r!CGx{x%+hVoQl=Sdhj(hU>E^k9s&=EV9O5BWBzu_SO}g0 z?g$x73Kb>Q9nCuj0U2&4n9qr4A~LLO*$?DBK`+iIO_h|4H4-b%Z9l^|ZvxSNKnH;3ddDGK z=N#c~MN%hwLYgN&%W#yvkrLkR>(4z&G@>E_y4lD;$1}HB@ zFu+*^1C3ny@~0?}X*cc{F4;d7RFV&2PSdW_mHQ1AuwH5(FdcCEiI-i4#WWd-h5W=? z|Ak@*d34)0@Wn^hV~!;*k<=r~)SegHGz)e?lQn^RXKf0`K~pC$C`2+J;w%J&b_A$v z&zFDBLN2ey02Ten?R;4&uxpg1T^?Q!j&RW#+gZQ4F(NEGQe+5N`-f3oG5u8)fuNL! zwSuKX@~w-xs?q`&|+BiWU(u_}fsV1?7r5`K&1!=O~+ zW30hgoYRmvc-n3Ro2a1Lur=8OK}Xh4oU+D;i{7hj>3Dx&V`>!Y90RTN*-0xI2wC*- zJbkCEH@3l%58La;+p-B)OqFe6Lru zSrzS11~@n1KQ1K3p)YZV12N%qaW_k}OQh85BWhgr2QF+pO;pPzc^JHH=_VYf)!oar zTH?X?^>S+OhTrqr-&1A3&xIs0@%HH5i3zz{53w{_QdI0B&n5HRmAktYuJNtqN(VkA z^oJ++jQhGSmptp2u`Ev?JlYT!Xk>Y`NTZHjvQOjp+K*S=dwd!Rmj!hEcsO6~1lJz* zAw?)L#WNNTb#`4I#fCn@9v|fgaIEEC9hzg;&?2=slld#*&umfc#@TH38>`eJlVfK_ zVq>hutOIq$zX}w5`o3Vco;8K$zo~!R--J}o=yTZ{(_7q8uV<5Mj!2ve`C6wE6*#-u zLiM=1`T3Y&rc-QpC!!~8{nPw@4b?n2-9h(t8%u`abKm2(2hFEUQ`WRfYad%nQO|Dg z#Yb5NsVmlZp|`C+_*Va0F#t_!{U!ZXl~5uD^64`I;tKrYcgnRL&-tX5ANVD9!6n8K z;kTJ@KU#0*sR`xbvLe`fdl5{_5cxxi>|`=$*s4F^^Ad8|HU5Ow`9jrLEoYwN*?J8h zt6vYT?5kW=T-7cdFWuU$yeG67^!M#DyWCvkLo71W@p8WE;`wnb(gCeBUb)^ebb977 zNgBd812p_P^pIChGD6#me|X?iB>TW8%DVJDXxrX57s1TbQH$viBt!xz@Mxz9rhD>r z7Qg_1!$KoG1?S&+B7%MLN3xD^;DR0aHFpU0--2={8HC%A5XZtHy}ZoekHPoMetJ5hu1#Lj9J)F#z z$hp|7_b939#b)nN7ET=`vBoFbym>|fQsK@Q4~O($B|So&Y4AR-Vu+7=(zW)vz-e4F z%K}}V=ZR@Vuf$1Pzh0xrTA)mazFzJ=($X{kp}UJ5^T%K_r^co!^k(ll>&Be2pQ0KT z&PD&eXyHj?9ni{3SBl=JYIgZO$wRv6L~3ipPCqMltY*fbF1wc5Y|(A-z>GVr`2`P5iiq(?65H^|g}b1nJTJlPhfv^M`Yew z!tLXCq0eb>G&U}mLA!}Hs&Ey=-=oYMK5E ztD`i}`?JO0YH4%JnQQ!ZXX1Wek6N z+<%m`{Co8ekDM&sM^E!A)=GuDHlexs#DVkWrMMv+{4$S?&nnUBTN1(Ku~#{izOUWR z4?%ActXJuJ5gG9wt5(@W(7yLL`o{WbpzjbXJCnYFmOIYsP7~`!OSKWJ%B%t96l*<< zP9I)ccn}P?=Mewsj(ZqYrpU=p?LHN*R8fv8rj~rH>`*0VHu(Ogs`k;`y`?NVV9?L& zQJF-ro~`?ZzDQm9pukv7IdaTP^!A;taFyBDfBTBp5tSLwRXvB-O^>p^+@E*9TKg|Q*Y3~jxm>$ndiBn{j-I1?%rmHUUY|DJn`UA6 zz{2kS0Yw!X*_;Er25eP&^^fGZ58EAah&=VG2)&Yn!rYN4Qt4HO``NS z;h1G%+l9pmIV42#Gw%`sM|B@A94z4diW?Qd+GJLgn}m}hSV#EEX?%?iEk92cLOzXF zYSC2~*E12-tw4P0x^8>W)o3-T`qCJ~#b9S4vD~&2gX+;x0~l#m=9Qqk))@Z$b!#x(`$}=jQ=wo^CCDWwmCD73T~(|aTd!~;q115*RWL15cknASpr;QO;Xid)8PAXCfHxWQWi4P+Uh-4 z6F{E=@){pcfe4m$GcZVwHhi8pnw|_4_bH|5@2d2u7iC`6yf@eseKtM!pX)alBa#!=I z@i;|vh6vUYz7h|Rs2chp0T^Hx8Ia-R~&kbI2chCUD*z5p2xx_*M;so&PF@dS4O2Hng9(*($EhKUHwEh5bKiSIL$T zMAqy_aiQ4~!JL%~0pv~o6*huUcCeLWo{qL2#7Ez)PCq;1yLkQRQL}f&bpZwg+yod{VQqy`X=#>~_CU3Lq9I^UZpA_GVU8N$?6MLg?dy$H6Zj@K3(@Yx&kv^CiTr(X5oG`ukE z?i+9ABd?x3D-5*Tu4Zpl5g_ATri17?_aY6m_Pxi&?OOGsooD^oTK@;AscsYDo5tIm zo#LiNKlxs~pr57+1}0rR6nXCQ#?c?6^%Cy1$%+-d=WDO}to?M`E#nTjoE2OJ{Wz{v ziXz{#Ya)vgMrmJrIN7%Evmc_(X+be-KvOJ##alTzuqE^5PM6#W_WFM8$0)UG#0mJf1ra<5e_77e1|^akVZwxf0$$IDAB z5EpCODeY~~Q#Ozg54CrFNCk6!$p3j;H_*txm*V~&YrnrFhD%J6D=)L~-!ee180`G6 zzYopCX@S=?6bF*<2WakZz(h5_I@$89 z^4$I~ubQ`7$$eCr44O|pA<}6Pvl^!5F3VMU=F#Wjbp)4UtKhSK^M@{E8esoy3fq3L zIGWry9DFjKS&eTsUp5J2$w<#)4CaW-Q=G}U>z#7}^|@|Xk4H-=3H~Y8Nx0K++w+Jf zWd{f|AK*yS=&8lmM}jJgfb0ZWry-2z#l$vv6yPF-izrRyg5~85?>G zVF3?#R;(*syq6-*0$KtjAamzR_%agrV%8gB$ z%4R_s)_NyF5rq}IXf4UXr|@Ox7&*{gA#nA<`bZQ`4MaZ%2Y-e5>5nzZSY@U_1Fx_& ztD16l*tF59&+<~?6)fUV=Kk$dW-`+O+)f~$uLx(xF9&flh)Z|8i2YA=`n&33MROTi{edB@h-@3{V ziU;alfk{F&d6=xqpf+{1#0jL z*Pm@ybz{%v)h#+c(F*b^uF(BC3wHZ|bO7K#y7r8y0@?d#&=!~`3XAEJ&Vf3#x9H!1 z`Q*44;eK5_jnZ&IDYulU&7K?N*^xq`WxmCnD`RcWxGLbEXLfv>nRfFdWYaKNDlQsB zSA6Tno_x4?%=MlR?VQ0_n!bXrd(T9zcY9R({t%Z!3p)=8U%91v+$nKr9e%ao*;pEO zCt~!0fBUuF>Xx3h{m|t~j>6n78(9~R`=7=_RjNPH9gYnheAG=6Du+|yTl*+$xojDs zmm5@0+{(%q>}LG1<3PSjzy61b0LMga8G=3{n9L7|Dm8<5i(vKG(;}FSA(W1kemW#{ zzfq<584oiK^?7-%w4PnCt)){0O;oDdq&+f%|uj*MVeYd-%NK!apgY$gS=X^gM6mKwh<}>>87l1Zf-i0UB!Ufx_fkOoYZ2_nW8^sb z0MtVX^3CZp;UbvcN%yQZ%0CLgHy%nyuAAULtI^>6x{80C81b!68gl*MJ0vg-sn2Jf zI=5hGjQ@5%AraE1fB|7TnqPrQ5y1*Q@uV3k5zLk-32BN5W*~xnc_@N;g9Kr!-+)l< z3XxyA;b;I2^4|;b?m0EcDK^A5if797Bh<0GIsG*;tVVC{lE-M8BC=~1&6PTierzwK zd_9RZgW|_E5$v`&q&GZk6sWf;>?U#B9`5mgDwpP0G&v^JqO!x_6`pD&a<7#Z-nu+G zgbmdpO)^^Nv-rmo<0=_53;vDm)twI)mAAs)Y+a}gPpDqpOO0LgL(v1LW*Xx6I;o;- zRy28muO81t$_jgx5%vClev3Z-{>L_n+y!)?D z0*7iCr+4RTRW89<48FWQ_ZLAkCDir9AQQdf%2UT6g2%1!a|qCPlhNVBmmiwj+5Q-7 zI)Xb9=*JHRZ`MR0m8|zhkuB2=5I5m%vqa`Ja?ir5aeH(%|0q<%A?3Vhx$43ebSeq$WG>0)j)&cu~ zxdDc>)p6F47ziIILs||c3#F!@8i=CWFpj3%*vbJYMb8fUmi^FA%ILJmvuCgsz69@A zaFmVaD~_@dJt9~pV|yIsCgc}zkiF@#phTfEO(G|u0@QGtxKqAN|;G3BSWCbZ31virx!MJ5_uP7`ob7U?2UxbLA~iVF#{4jtCEm0q{AW6?2{+ zZipg5Hpcaq9en{FsKO12VC~AZ)u|yH@D}ehUms*C&gXSo4Kg2~oky`AbJ0*Pdv9#a zg%c7FliY)(>?7qI4m?SgU2eg@9#EoiQwK#b!KJsy+Mxgy;>l%^zdJ#X!|Ds};iE}Y zL^fijmE$zG=Xotyz`Y^33zdMF2Xb6jq>~yk`f9NgLLH1It!y7h=jMiMN{M|6bG+Oc z&ptnk1THGk!}`n7iWBwd=z70jxBVD~?&|>ok2t=iAP*lx$weuXRfHcYfM+y1-(#W6 z{0|Cb#rG^B0XOIvN6<6Q>HsAp_`pcFCNSA^Lpk*`C(y^*cpS=7d#BqC8!GvUV68E6 z(l%jP{k`AV@EJU)yZ+IkOWlM5NmKK)`h7H5j}1qd(k>X@Q$PSQv!?q{g%r6TadTd}uyIsNx`D90eyA#*6U zM+7Ut0(e0g0jg&d!Ge$Q49-Fk>wg!KVV!v>u#-Jb%%+@oO+!lHbWvFK=OB;w3t00~ z!;teOn}ra=4A3m&)2$46hlTGMs+w)@*BKd>@JuU&Sbq&0oi?l9LVnRbjJTvZ5;k_A zB|6~V$QMMG2o_DsCTj`^E8XJp9H=77i5KigK{fYs9=7&i-= zWqKG>P6KjajH1=)>ZEknz1pQ3b#JeCg$8vHaYn*df?6n#G6&^Mk=*M_1RSEyH))O=eWv4NnT#TS(JxF|@OuZd6Pvj6@o&}2?T$7>w zTvSjA5`S~nGs``B$qLDimU3Fk4mnm>XG@mJ#fRmCx=^|?KVTe>CMF9ZC!d_l*O};s z{5q87e}psO2Y^`e2{4i4!z8B1s)=A(@Ycy*#LXK%DW=fb5v(ULPv|}{xAZs;nU11& zbd{wvPIYu%Ax&fAe;YYJwo(D{qZHvutn4`c0v`N%zF%gF8ax3N1IkAeTQ~V~0RzY# zaYVUDrOjg{J{e}HG-S)pDyFl2z^D-#Icn4<@c(|QMEH7kNuaG3ol!yDtQ*_5Ac8^8 zKiYL#dCt6-NPw$iWqI;gD~+GPMqm>%TnbhPAfh3fC#~kEv6nNMzP_pyA-B|Z5xH&S zM38}p>ap60tp_>&W#wYI4C0RX%_}tHLyHtvIWop|%Y>1P1WUId9CBK44cV&3Ka?$$ zkm8*jgbLMrR~vl>3SD-I~f%=BvM0>-a%AFKBPLgnhy5NGNk%26^VoNza>~M~9wpv5JRW zmI8$qtPj?6Ho3TQ$~h4%e`{a>F;z;l-aafuV6HK*KL?2H{N!~hcotD+9iIUiG_wGd zIym3Y*l}TOn&z)m@pyVBa-vKrvMtw%O21=%wY_aC?4tfg7TZia4|{*@c=X8(=&1kbM;6iQWzV4+C9e2oa^XU+Y?dH}+s zuQ;0m73Gyz5CI95`iw)k^|(F%m}@@e>r2WdSH2FE7$ylbU-KiN&HgL`-!eYg&&VL} z2IG0ZK&x^_sEzMALq>o~+kjT?Q2_xN4bSyABc;Zbx^7!2+XuMP5E<}2Sgkq49=;JM z4Rp=2;W8Lf|EwXH)`0Pz4DRS2=R1YQkOG507V+l=#0}!qU=SKSBrJxiI2nx2cp3@R zC1fL`kW+G_+&V!KAI*yp4woGtBg6f$2ucfwu+me(M}zWg)ZX@*)NTV78VX&*S#Ry8 z`zEeLlq3gE2Sge)EEMBHVR)ZeMM2xcahwh`n1H#VsS56WQ1dbW0tcR9r4I0DL`CdL z;GUIE1LipuzXyT`!JE|EvwUlCfV16ubhQRBrxH~bCw!FYhN_-J7411CIp&i^N}H&t zNh+!x#lH^~5g~*_GHkr?`4E>)dKLkDl%@OM8renA03GnmpF(hzfXOtg5S~9D4b(vK zq%>we@HsG_QDHM;mQaK-RBXCGq;MqR_q0Dd2T_nh&`u)( zTgKKInVuoOGT#OQ_Qro8FMK7b2+E+y8l4QGgYaINU3{#d4EJ*cLxC)V+2rAcvNGx* z?xdZOW)QUCDB9L!3IrQl?CYi?Czk~G*!g>wZkP=a!}nZj`%!{8D!-q!ybg2oEz{yC z<+5b7Idfe_+ACQm8iBj@TXj z=OO>x#=LU0b!pRrY98^5^ydfNkDNmFHKzx6x4;sE6u%GAkaP++9`xXJG6$Afob7BP zfqxt#pVLsXuYhpY9bj>itj(FxkZrXjWL{dIjzPGPlSc;6G&?kXBef^yTD}ROqn!4y zX{9Zsuu)k^AP}l`o9sbn2rWTdjuUqV^kOnHk-PbRB#hbsPHt=&dwDBAo$trWb(}jh z+2%Cg0zicgK?_}lRehB!G1ship1Ip+nwkDHyg|XI2QPzULR$6&*_S^x#3rtwfmQB3 zKqre5E|cuv-KE@z%2rJxWsXwGQsc}vV1wUbS?O~_tfS$(R!tWF@m03()#2zn)6wd}3UZ3RTR(2xMhhv`+hx}<_XQa!Ai8-3J`7r+^ zi>Gzr2X^qI%j{vtWM<2!?HeOmLnCo92EiliE7xqs)&=$LkOgx4s7 z=~S<7?=oiisbplUe~s?jWMsTjT{O#v2cLsPtprKX#ztWqYTIofyQM-|Zr;+VmKO&|Y~KAs1y%QZa~E)I&*=(7}3;E~SJmE}j- zgC&b=6B1YxZnxlxPzy&>-AwLQ=Zp#^6`4E1o1E?TlU5!qsBRU~EoKQ`NqCHJTiT1;&YYzcsACBCTE|89m&p5K1}#$N{(< zG%gE13Pf;FOj55aU5)D|c(_b9!|o#s9wFkSPcVn_R@;&36j_`+xXKF8H&d63US=8BaHp<`Y{nM5)!x0tdPJf){vZ z860rQ6=$yrhg15- zV(Z2I&N2-SF0-k$-YG`u`Hj(G-7jBf$Hn9G+2Dg7G9@l!>e;0qC2F&;Pf= zvNGZAgocTKh5tvx7+;a+@<91|Og&UOI%Z`8(cN;J1G5Ihd}&s0D7$kxjOUNl24Wc5 zSX+Z>4Bn=jGf6|ISnmUH`AJQ|bqibWi(4PFf)`B!rhZwuV&&HPeUqa}g;LOcL(4%; z_?}RH0`D|#iYSGQ62VlkF`y4ejTWzXf3tRGRg{<_Vl3n2#D&?(2Yp*WEX65uLh#*xvcgY3>S?RYY^4AiwpK;5L3E zIx=?Krj~-+FB;(-W#R84*e1jzPOE;mTp#*38BZoZ9I7B!G&_CP(Eeg1YKzsrZ zjr$-}bERKnZT3?%A~cQw_O$1&;^22~c;|bp4(G;q;a=CNp`iC|*(cyXfyy2u{3F#7 zs!c>eW_;t4(j-o64C}y#?_HeLeD=I!ZS6ND_|dnQpYHZP=zGIA%kTbJxr9rV5jA5`d`nl^k8b-V6aIDvZZ@Yww;nC|@xTXz>*dp|$r$LQ*}U%ms3r0JQftg%gY z@05(IF}kmtbiIV@P*)grPWQEu8}-}_C4s`MqyT405KG)VxdO$eJ4m1uFhoV|Hlv8+ zW{_?046;1dcdR?E93**h%nAIHl}i+H)8|eMLUD5g$n1vS zI<^9DMLsUtyvz^|_;BD*xVr)xL1+udG3W0+%|!R0=qK2CT0UKzN@+v%yNYAA1(o=S zj~>tt+bcZuYL20dwGljc2p}@aKQE~g($5cf|0a|nbN1Y;Ih|AoqR(~_ZQnPXADi?Z zS4teLM*$x2sU_|VWU2H)6K*W@{>R_6aWWA{XS6kbyTuvpu@)Cp;?!{OQL-d{0E8*G z@YykDNueIk-($s->Rro0eD94rvl?SqW3oJj&}aE2*~+zYDOi!ybn`KE;m%*)vj`o4&K!K-oeNAbZ!n z#?ox^WE9TDKs~>Aj?xG475#d2q9LE4}JU!{Uu>FnurpNUz#0+y|XLFz*l6Aoz`MB$<4- zlc8_^ZQ-x}3rGw(fu712N~zB=Kz*O(DV`_R{=M}M+MnLjg&bu;ADI4F5JFOA2b`6?2wFC_P)?kIzNE2LKzr8#)D=UZNbs!zt zrLRl+q{&Sf6~QjzLF4e1L|!!Cc?^$srI&J#2q3o>k@d0I^d_4kFfvngI2vu2B#pxs8Q3d-iDbzb$Bo{^5|kv_I&YK3D% zg?`}?i`U0-a;b(j8GREK8MLQ*ug4ybR;2cxyW+LvbvsATBPT?1Jj&*tAaNM=hQXI3 za&HP6DB%cDPiDmn(ou|{5n_%g+=fxI8ur*IJ_0#a!%YQm#nK71w;rr7HCI@6<5qfe zxp?>S_LF#8C&Y#~^A6G9eyPP*Ma?R3E?(~{z@W|o6Qvcx7<`Wjc`v@_7bNpB5~87) z8scR+%wkg#FmV|QQiqIyJV@bxtbrkA7K z{VOelTBXCa`)xWjn)Z-`YuUBzgSGV3&&S=}tMpBT?C2h$9E7m8<1E3sxngR5s?e&F zlb?gUz)<(&n}XxjeqYP9pL7DKJW}kP2{y1W(Vna9^1l0Y!XDz!mZ@Zgv5jeGeNNcO z#)(pA7m0>B;S~zAn5X~|W9VJDDLmlD01v_;mOT)A9;DVoNrez3jRcvX_U9AD?jV#O z%NN5eQhT+z9BkO!%{k~4n`@;XHU!Xq&dRXzEYs-(YBJi;a=srNl2|E^BDdmuA%tl| z1a?GbT#~Zy=b3wF&TsCS`Tcii&dm8kWkPU~ zZ>{fr-|u;!=XnLdDx{^T8$fcupv1lo#(VoSa3s5ngn)xN_gf*kjHZ==EJ@;U)WNOG zC5WDm=)Se5Z-QC&`9A}EFLZm1hXji2Jsv4RbZ;otM;!4?Y5>SV)z3)DxleEL=4UDO zvG2Z{ANIAEz=|x#T*w+bBB~hMd+94$P6wkF6frI(>av0lI7+ZWR1cTkDVp#jCBHf1 zVK?@yv#(B3t9Slh#mQGrCC6CM`i|7jd9fJh{Un^o!yxyb6AWr=p7u{9^tFlWN9qhh z(aWhV2=Ecbh|_@2Mp|{@N|HJ)%|inrKLCWh2+Yi9ka|(6MUs{?EVRgq>3ls~=tM`m ziEN9C8f21cBtP}#nze7AKm_R;n;mJ_@XwVtzR2Zj# zJ+YPm#W8a`&A@R`mE!@wVwbqTWFz`!cJCoaRIiZtz|`Khj?J%4%!3cm&`{RKG#qMV z;Wc)cApiHutkj6PV#&(ZMB=J`N%vULaEV-cG5ToF7iZ>KV>hj16<&21C8dVbbE)Yn zvY7_u4)T{bo@Yxfbe0xs$;>G@PfI;HCFUYMt55@ZC@qNHyTE4G#=BOs*!%O232C|d z;@o@vuTK`LBXTd?kXCz-mT-2?RH;=ksyelmLLNi_Z8J~W{v-&=@k>RGGn@>l#RJBg z0CnbB{C7_1SnVcSY7$?Ll)>T;XbXGyMn$|YzxUL(!fW~DM%HS6zUN%yw^0D;_})%G zz()GljzKb*7p}%hSeiO${u0nhwM+uB}G2;H9!3_PVeIv8^aR3uE|P%d*F})G2O*`xsqxogX^+xx*6`oAqb!dSnmkSbHQhd0c3XB%)L2>c; z+%6RjX#O#iCblxAJR_(KAmhWlM*PPOGrZ3jV}%3rotnlB2lAr_5HG9@TW+A`?kQy* zsN9Mch@S#IXFUP%{n`ry2&+~YDx_KhZ!{{b8@hIc0s5|-b1)j{S~{gbJw(xe0WEZ* z?*f*AAvMKIsB1`m0AlqTo1$X@RqGW}8KStK3TuuftEG2d=ZbF1&C{e^{vH(JqF->5v*qSuXp^ z_F8y7N-RE^zTz@JSRX2c(M;=c?PT0Iek~@w7gKU-C8Esx@cWzXMhZ9neh#FbQC@Jxx#IR%wy6=x0YvRG8st8(4y)&oxk%W>j{ksikod6>#jlUhlZK-^Q3!K6 zpyL#SyY1M4NfV5zN!YhBzi2N$(Kpb2T+i}%tMkvN6;EA?D0}xp=goB$8Y{mDKKA_- zjAS817D>>#U%LCX2CgvPq%W#RK_!D7)=v>`XJ>Uk)4SY~w>I_fwsVai+K*NR{Wdr} z#hm|lnm$o5qF?2CcMqG`RJFO|R3ixdR!Tb-kn z!iP>_TYd$i0w`y4Tp;|9| z>LV{1|KxW^$!k?c!OSH)&EXQQpF~MFP5(>COCq1L0u?`22=mblJy{srKPv-GWD+Ep z%8I?Sd)Ya6OYzlRep=9c8LkFT>Y*sGcNc|LEk+5Fsy*y2GAm1i0JvXQT)r$D<`8^4 z@P=5AWV^FAGL=Ms3Xs{Be&nZqIyOn5P1w%Q0PY{33y+QoG-2#(69D!RWtNEthIzJ6 z;m96SRI1)J;_sgw7+Hx}nN7{`7&-Tk;l3gqb!e<9&z>3V{i-&o&&*$pwt(R&?qEyx z(DtF|Sw+9;^3bv zJWC@+YB!w9C-;O;n`vLsC(-9g8B}?}Z-VZk-$0f#VJ`I0;w*JQ9J;0#OOzeuIY1Tt zg_`hf@-lJ%C{L7~y6ysvmsP{|Zo_)(Jd0;DOL-yV#ZP@dJCyr<#&M4NObw`&y|S6K z`MpBiv=jPlQFti9PwnluDXd>aUe_r0_ijBgz<$GlGlz*6(A^Ya*(Tt#*oK5}q zCK-kBpU)px_gg0MEU&Pf`Z?|uD54vsk6=>RiNK_jT zUwKRVsNDDh9TIBzys5F_g!BvV*3Xdb!Ju`~=i6jcQmlX9o2|a142^xO$CG1m{)Q_d z76)+M05CE*tHXamJWlMy)|>RLpps3C`e;+4bm=x)JTLg)ZNk^?>NZj0%HFfpyFb3K z!$r9zj148$7-`!}$)*%92}~&Lq>RQPNnD=aW0j08e^AgtdOG z>4I&uoBv3lML>;QB`UxeR$#C1QPylmDgMqfXJSHNIZV`_UWw|UG$PM7Bv}uKXNfHN zNe-`$I89tL8f7c5PMyv}-a~80f9uV6?>i-G*ZBR6{}sNAa!UHjrI%$1TV+0Od)_9B zQx2EpciKp=J1405HCFaLZ8^smy|oh0*boANi?3Ow(`i`~Pla>{tSl1?n?Ich`SKO#5&18t)Fc+k=8( ztR(d^_AauaB3Yn99B~r#PX+d1r(Ni|U5GSf%Kd8eI(+=oUV`cn8W+Pm{LP$&ow-GD;eoGZ;x)MK+xa+*?%%}&yrz%DGu`3q_5MK|)kp_3IUNoY%7 z`adx?{_C$jR5aMiS@Dh9>-(0Se1|So@N)-Kd;hqHkE86npkG~Dc!*qZ6P|KD_YcY2DbDmIJk3;hL_s zUN3XHCryu{n5nXcr^JKn>0lhB5A(AJ|uo6e#@T@g%O+ z@>a-i__>@G~G@N*#Kr4951cW#KH0u5H|JI45|Bj?fN%|J%ZY5kc=iunVw9k>P#>ZtSqkI--DWq@bf&T3%r8qUXJzcF z$w!~gaKj;~UxhQMlwsm#F@8s>0yU|rK-;ta^0xQ>&%8csOd33RllD%3|1P1-K00Jq zeZQ2A&)a{=sZ-s^VWQgBq1W!P-C;*9+r|fH8GZAQFMCzC_aAwEX}RJ4Q}(BK#u9GV zox)sa4>id+b%J+_J8C(F92`MCp-wdjL~d{;2p%Bl)C8d7*SI1$*({%>JYZz2BAIaG+~ZkLI)H3hl*P?@0+)SJb;*vz4qva?VvNhB?tF`vmd{^(rB(yFvkDjGuc!a5Gd>^h(PdMXe;B0pe z_&5d|(lTrTZ&%3+oP4DKXt?>xaB6E1H5uJ63~qvBJHPP|daobvXgmK-BY85jvwZX# ziD3ZIvcij|RK4MfO5>tZ<0s<|45~an%x}cSbxd_E;8mJ4DC7eV73s)He70h@=gnpV z2HvUZYr63hkA+_-P>PaBS%(7#q9l+1v>ojGT=99rx#vrIXu8uH@q{DrFB zM397H=LrCkkB!EYn552p>Xe*7xHFJ@kzmc%i_Ps;k>QtE>aI)_385~cYXiSN`IJmtx(%LUr^O+?)3zH{jXl>u+I;qTs|?rKdI_)|Lmr9 zJZE>Ujhc+QjANme%gwnUpZfUH5r>nQ)BeD_{zgPhq8#eY5u?vfWRxsJa>i@spO(`r zo%;q$D+bgKpXqYBf$NiOLtUiOD~lS)SwQ0ltGBFSxyA(D59Yc|G+^uOr^p0Zib0M= zEDN72mMTqbWlmYSLpeTi!QD9b>EQP+jO+@tR-SivKG@5+NN7rjM{DXdSz8M*e#|xC znT}l33mpAw2yPNrAeJX5Y%`WkFX~eRgVd$#Pp6IuWJsO5?fWB0h-J?Bu-f~YsNA(UcGq_%DZ?Bs z{n$IigJ%vQx~hX02_rjRtH-St4JWNlGmG{HleQD2is4AtEVqt04&c$p-lACr1S-Gp z{WxN7#3d5nwQ=ghRXt4Js5~ylnI=};dMj*iubuMzq=NI%i(_xPJO^Fm`+PDb-#vHA z%Olhsmh9;Zvh!O>i@hIptm5yop3ldH+rpT8@5`vF3%lfmEe{9o@UPeMLcll6^Ffji_@(EzRQ#{_h_IqS zWHK7ce@$zm@+3<@xQGlaX<0o2y<$eL$%n9AdJ4_z9WeBT?yRY!zqY$Mz}w$|r=ED& zb7q=$Wy&w1jON9|KN`F+^`<2k=tUQ~~5$o$0M&2(MQbG?^auPL6Fy@8#F2FGd^K7ueehfQp#udSfW_u3(D@H!{)h$P zmxf4ILGb@pbPCP%0~J{83An$TCTHDnGc@8Q<-j7XUQkg1Q^an;1E}fE>5!YV9GVwZ z%C2Rq6ccH$lcpc=Q;2Ip9E+mGMv$|9LEHdIH=gF~f_+(t$uuUq2bK7Xp7IC5f?rFz zj7$<3Koc33DtH?!HYy_omvFtb zS8^OJB_nStnQ^gnBs}lggE;3Km}EFAUfr1zbN^$7qn(U8#(1Ez>&e`S3jy+P%MB#r z3l*GtG~QH#pK&cXZXJs41hf+?C{(b=o`Fc4-IryX-&-U?rQgpJ>d6xAB`^II>?KV( z0GX^t;+Qd#7QxnS2d3SJvChM5xy~bp_rwa`y$?R}Z7zs62B)wU1)?~B{xc?4McLbb zB}-a^__LxGn?CYgq>#==DgW+T%{1c)Rgofzr!h+{e{XF^ArIT61wfyPdt)RW479Q0 z%~e~e%c7);+RBEPd=LJkb`m7lje_jfCs!Q##StixUZ@4M2O%^qejKZ07^;8^3lgYg zy;eZ4F8qCht#_7vIrXaEgv&>l?&htrx>sfSX1yKta_)&!bo)6}cbq_^gPt`N)=l~C z1C^dnf*h&x#7};^;ozFZ2>_x{On6s^dCFW3z(GMYVMTBze)7;zIz<7W%Tl=)!wzuj zfD#`(^TW9#pb>gi5U0&zZTxXr5GUtH?)z)3f{Wiu%uk11t#j2JG+f4bCJKK{uC3cA&m+(A?Ytw<|qDgOgQU$^j)4ukV}Hr%jEgi=wip(Gp!>PrZgXp zQzZ~kC77Ua8tTV7dynfqL(vP1Wd7Y2g5lQ!`5a=>hSLYHKtr6kc7B050*E1Kul-En z5XcmH*I0mA+g`yVb+8XVuEO6$DdIWy3k@i-cJrLv`@wi0U&C7;V~TTHQJ+u(=Q)sV zhC$C4n9YIbw5BN9ub(S%h1!Tp06fMOR?8Zhh4|ElN<%7LwUoTlJgdaNFHpcwTQ@;N zNfen=?A;c|-TeUcIqbf5N_s7sdI`Fd78zr;ir}R_)xnu?cO9Ya;%`oi*v7p z)agp1H_qye={;?LXqkjl+}}RqZ9E^Ix!MF1Ot?ltfX!DrGIu&uA7{FmG5;WHzv`*z z5W~GD)}MOWa)|N=LpLwZ5naFgbR72#Gt;Y`nG9Mj$f^5hfq?0H)2Ek$kFmozp-tb8 zg>jZj z?5`b+M}Y83-A!;}>-8ejiRS6emCRZpyWxl<^!43o-`YqCjmX5jvZmDhv<%w-9vAlH z-V#iApW{FN_0S6p_<*_;F!UR-kJ=KrrVRzqh2nVID`540Q_R{>DG$jnA&`W+v6IuR zz${Dc=9dCtw%dAglf|h{f!Ty${}lcNV}z)@k|SEWLatl-abK^FNHaz86j|&v7JqGh zZIa!h1=93ND?kw;Hy)hXS(xiIHwQ?a*y zyYrJ^mp(?IwJX0d_+;ZHYu8pam#MzS3yW=jg9eprZh`3g21Siu9Br0b)-eBWQ%Q|}R)6*4@%SY?G%%>(fZlFwC}`Q+R2!#pJ$YxwHvKQj^z=Ipe#@8`QP}fLQQx`IJqPx z{t)Y3@$qw&?nfSEwLkq*Hc(sp?0ka~)gXTP(kGs*Wx?^P2(9z=9t2<02&LPj>gE-pbzK0N zV$J^btSrBf8c@By7jFQysEx3je;j6>DYv4pz+-?tLgeoj~8QPq)?>PBwYT{gb z>u`8}h^+C%hS>lu$J~OMB_?=7gTiTR!em~|L^ydH-kY<+kF`|s5DcoAuw(djX$+DC zp3KN8hN#`M>Tp0A9Ov}1d6>nv=qVEConr1NHU~g<1^k=h2Ao7|O zSg>Cxup)7==r`V}5>VrWu8y&fvU1ln*^GA8O3#%dsFEl*g`3SVS`!qVcFFkTZQ+J&1aq|%TP;oy)u_-g~>cKW7mFD^|!jr z(rWGBYD#1G9K?Lz0d{~$BKp$;rxINJIjQr&=B;QkMb1rr35#Dwx;$gB^r)TIKwgt6 z|FrDomtU&%^CpfW`XY|;bF6UN?^tO(%gt)?SZhm*;P2p{e}ezx1Hl$hFadpPml;2e zN+;{%z7iD(f5N+f14%EfF}ta1AXF&XNLlK&iZ=Y7Ylh1Wy;ia2Rgp@#T`G#n`SY5$ z0~}fI(dB*V)#RN>>mdl^81v-j1uAi4fwjY^T z*5fHY<{HCUbQ7K=mx0a_92EXaG$u|G4*@+uHT!`VCGB!qE&PBrv#LN#2qmXg4dzPm zKo?MVY^|o#s<}S0S|-&v&w&P55s-ZrXoRhv;$A1xu#jKTKH@mGA+mcYGjHM!iCzX& zbfH2)md4GaUk{t3%2q6O;DYEXPY*>gyH0n-=~rgBc}bmgKk2Er+js8R6cUK)lP)iT zIBFB3)sD2+|ff)AT7BKlYzQ`n#@5+SOj(lOGuuoK;|Pxv_`5Z%tzCJrI% zX}o<{frt#o06M}wP%fzZZ*O=6Km~;Ux%e%?1CT=H~3=@>O62x~n@(D7a4kxpBnoMC{ zlXqH0KlQ-d4Y{d8S)PD(+4p&B~I#}kH;wUqDA@1Lfo2foHjeEW%Sli<0N_(-?`WR#zZAXgL&h&lurCi*vm2EVaA zny!}ss`!GUG{c2q5W-}6(y)3NQkb0ebz}0>?OO`-xeRJjUPLLU!DkY&Z;@g+vbL^A zANji@f9t{jcYNRfO=Ksx0`zUV4_`|#;u_5iGR;EFfX_g#k=f5=HC;n4Hbqu{DEhjq zlyYO(jF-2($1W4ht(>bi9NUmH%HDW>Q!B{Zi zC-D*ZC7=lsFi^jC5>?nrmU<7-l8MKN29RQuijpZZBY+S^{Da?Sc~tPm1w3!z z<`IF6EO74|u&>FPd7(W!`Ee-!Nh9SjPq>9G#KOiIp+$YJtU_hsKvMUwG%GWh2 z=4?{%H-oMzow)bjC9c}3+hpKk~QOKRgUF zF?4*atSR${{maZ*CR4`t3uxoVYQIs!LiVAPvYx&NQuVgOg|-LpWvgZy!uMF0BO@=! zNJz_Gz98c&dFHo{3Hz}oL=x@AtqyuoIUE=?GF{TX-qMN{K_-Q#+7+uK@O?@@k%gQwx5ZVD*#VlN2Z z31rwVohq$Q@rr#@eyt4q#fN?|%`~QZ=F?Xn$hu!2dX7D4deUDV33;eP zs_{V)v1_06^FDv3PPN9YMf=gbJIG~g!AsGLfr6&%R1>|eH{HDP`8mPV+n=qW1Mfr0 zexGO8{^Jc^e99brgnN!>4r8A2_VHhmE+gVmje)BZ?4d4HW7aB{RkR=e{uu|9^$MKDOHBL>b1Qvlc71#E`px{zY13&FLv6iJ>kjHFZd!?}H139L6P zByi?sf-ayfi>88a)}9G-A5X|geAs53Uyl1WGxd3D3h3^JR99#XLTid_Z#P(-x&Fys zu;(Y(eC7=?H(4h>gi^0&eUf9)ddXAvOljjUM$N5I;(8xe$WlVki;-9{D;(^iIl>ie z*11@Ya2^^lb6WR4cS{WX*4M!f&?lWAg4In@c!BIRIxRhdq{zDnw=hHzqWBSJ;)_BN zC=u{wU4w2F9&}LbMC}pmfmInroq9X*mqRX(PdGo1>n_}-kbbUTNw;p+%4m9K4Rh?R zhV_$`%dH_AZdpc}qy2P-NWJ&lhjYoU%cUFF3hbVcZEtpn=S>Q%k}@=zM{AzOYxfQ& z*tHh^R$%(U>$p>;xpSG9=90Qyt(#nFE3)k>K_75^=7}Rzjz4t|YBQkQ?>#pFuPP8p zV(KYmU^u35Jy#kZz_8Ax^DQ@EJx(I9rM6*4W`zn4EJfyxt&ItC$zVWPOPryigTeBL zMcFCcnzp0MvI-9s5=PtN>wP+vsY4{OcBj9+!IA|1ny@G5Q03jv=s*J+_@%#bTiv&d zF1{LX%u>P0ZPn$A?mRpZ6_Zn6I^$>6UN1{Az#-t_(^0O(smr%0i7Ql%cUiW1uB@vFbZO-*)@s3pD+A`)#=ppz$ zmxnF?VOvRgV|{En$+gU!==+$Pu3=BgtK%QU2>)(Vty}5~D|#_#@b<}H6&hWo^Q_E9RpG&XPbxI}3?2p?DpQmD zi7qDs{VC*i3`H*}5~9+}qFVaE5VgY%P(RcC9@k>!8$s;@K-NeZITSM- z`PmEI0*bhugK3;hD65xmCJ+XRc30y^fN<80urLV%$rYNg;D8PHAdFxUOPHEQMX=8; z)b`8#zrRux@R9B#=C;p1!!>U%F~&cH`<{7IwM0a=6wdC-+L8q+9Lu0eETI~#^NW`` zQw3WB5cOWnmX0@fxj*s%w^L$uGsFw3wx%v_UCs@>U5Oni$jOT-8~ZY+TRVSG%X8LV z9yo{_WNS7S%RQd^@gT%XqbwtP`r*_2ADf3Utn@(QE*h@~P4(mVnvd8UET!=i{S*~www|d?5{0vRR212>5B8eId-;~meFbSu@W(Wq+HKX0ibo3&6KHKlg>4_{7d`uu04(atn|FKPpvCRx35(5mDfULdWnwv4&~Sk z%n8Po0yl5DW&;fk>I1KstM*U&*P|>425=mHK|Aph~4HceD;1 zxKow-w^r31X)w#%W#v<4A!GEV*D)V4FOO?-z~|zver;Mt)Tb)@6ntQ^e_XZKF|U0I zr(Z_lZBjDiWZ6}1-M#)D*@P>MU>k{yUZ%ElH5S?Z$tROsU#w-wazW&>{dO^z`XZ|B z?+2$8J0H94@D}Ox(D!yb#u)h%M6NhsD1qF!h?NIgE)?V(oHd!|z$a6-69-tX5N0g^ zawqNb;{Exu1GZtR20rt~=ltk0&GW-445l$)J>I1Vv9Hw^^Gp)WL2TO7T*eg6Oo_N1 zQT876DL<2-Yoz}5DHF~u*oJ$E*ohQ3zr^lms%M1DZ-|}hLOrkOUKd6~Xy*}C%iq)b zRgSV9>z`h+Io$*iOZ=}KnrKJdVCIZ$2G+N|co}_3@F8pKO;yggtnt+~+$CHPKoe_% zgYoBamX|}H^rT|vFNZ!Vdi-ef;1lTaXAMSie9wno*NUXRd5?DopC2z&)v2O3X^-}~ zcL%7SaJca7W4|dr-N>UaQ|qD3Rjp@>V~$0#>KX;M*0mxVs$RD*V{$I|9o=ECUwrJ+ zG5q;w7p|P^LFXd8%6Ea`b(&#lQD3`+poaxlLAAUBzu>2%g3(DuQ0V$v7&n^uwy1>` zZ|)0K)3eru*x!4RAdz=)Z9ozjN+tNxfuUr8d(IU^{Gm|MPb;P)GdKmYp@b0Ck;Z(n z3n*c@P)`;oP1B2uTBH0r zg(kf~BWION6OsCdfc%=R^+~5I6Bk=`{|Sz{$hh%H9?X7psy#^&MW%F*B^jAM2S26l zQtePx^tOd32@o45nkQ`|5LxG9NS#<6iZVFAhjLZ3?gE6omwRNEXZx|PQ7n*|{V)>r z-A8$D#b9vQ2Q*{lI8TkK9N?7q{@O-xj7`6(dq&7JUpVPcw*Ctq0NP?tfFR>e*$e(Y zMnsW19m7GN10eBIJUOlf0RyycnzO+5Ea8Kt0-n}K6>p@a2~^l7>HR6ds5O<_tw5~$ zh%fPg;%61vDoFt)OD%2j#>S=@K5lyS=Y1M(M1JB&tw!67m%eYx@I|sJj zwjDcCdHcZiWd(1MD!^AD10fJmy!6YvW{ju|v<6H*6}TXshi?Qk!P9F+aD%@0+}g6? z$fc|W3>!K4nc)wYH9jnBF5x&>3uSnDpGk3!sSyv1Zw|C#{9&)#1>KTU>td++9qE~< zP|@rTsCL5-T`X}is!r>@(BRdi$>0|}tkluDu75u(JFD}8&&{pDmZo<{2!0JZ)`xPC zIf=?6jH|bHh*PTNZTq*|i^g})Wlg(BsOy}n)IpCgoPmK!Z)YTnHoYPcY2`+POs~t^ z&Y~p&qrXgw!Xm&7R_U{-`doAen{%%W4b9VBCN`4&mYLgw%e;B2O{1HOjVXHOD1Rm@ zSWBT+|ZS844e&;7J3Q)H0?-rm8)Gj|W(m&iRh|H5-q&Cifha;@?11tycuDp)K3>=9$ExInW zsIa+^Pz<5c4KqR=HuXN~^{Z%sXAvlXuLI$WmU=fj33Sl@1HHgDls9qBcye$qYfaAu z8bvA)d?PFI%Y6YFfy+b1$@RJWA=!x?Drsf^Zu1&B zv8!v(*FRG6-P-YahS@uc_h!m;AO3P)-do;z`%Z=M8~uB4bnnGUJ00kkc6wTRqc2rP z((oKOhV61YC*!(p+iz7#`%XU8xS)PF;wV8KVW0T?6X3XyX6&JtmhEngXHS4tm z4!g1Ogqv^%V}-m6zZ)TYCD%*2kF1w4*OWJ}x6d<3HmW1Bgi-xiPq%L4Wr$drAYUze zbM^f*?x7YL7DMQd^s1-u3e!lFr^ftG#EMk@2q=hYreycU*Bk-is ztFSFYtN(5T)ubZGwlSy>S{Ah-Vj?}%d`AVogwfw!-OMH@Tr=0KQBUTXu}A2atDkA% zhJ9vBIlFjg0UUjf*PaN7qNsaEzls>$71!0(VSZ>9n#5rkHe^iOzfej97lEzg%`AcvL zMF&UF9gOu(V2%MA!9ZZLxjBGNM2ZVg_;n^N_0WjngUaA$io~!{)+DtDGu&CK_6{2~ z`um8}m*73u(ud7&+Jr9rBY{-qyajiB4G;X;^&oUVss0B~;ycHg|BQHy;0XUHUPg)V zPOx8j!?!46a41+nZNgq>?iw+=`msEY07j$avis!9okW zM;!IB@~{q;T2&K~jGuE(FirbDEfDtRN`uzK&P{$C2_WN>80Ya?o)vTbH+xZ0zUqSTkzj1jHUn>G-E5o8+ zeLE!(?{EinBHHRkRoP=I{cq%U8MhNU?}$i3=yun=_icaQw%eAD*5Z|wKs?kXJXb`BP`tATE3B2=C?J_&I1fA;eH65~pf2&L~KuDR;1RCWN@IKw<& z?t%YwnZ3mQ%Renl8~9&9mmuiN&pwQf+v!$|Muv+?WqRe#B`&Tne6&*QUN))+1#8Li zq#Fdn)c`PB=%Utx{anJp0-OOFU?IAu(Wz0PY35kS7@3aTPQC_sOESThN?0+pN}j-%{}l=Xt1{z@FLHl1vZ`yT)?eWh^nix% zwt`ld0$G}?wVTMjQ$zgx8P7@D>GPDP=Jq?!1H?upd`^23SN6}_I1D_9Yf-JQ{3B8E z%6(D%h1mVMSFd*~SV;$&YFXcPl8(~YF;%kt$+-dO<=gP*5Gr^JL>6Hc~Rl3%590M_LK&Z2eTX>+EjE4f$JIMaGgx`#x6R zX|GT^Y%+b)RWl&SxLzXLw7Gk`wnBktIWw6tWphIE$pg7mf!9e&=ixo~1JXS*xhAux~&LHWCe$ zb&zS`LOD<>9s&cWTFm03{ah> zKQhb%%6Z4f%}>$wL_or31*RB^QdQuVE_czteV7#*FSwPUd%4x(7=e`P$qUeVoztK4{cGP%ztvth&<)2 zw+Brs^{8)$>&441Mc+=l;m=pcu=Sqtu;60dmMV)|3mKMrcr(roXyLB8$TZVk&OKXy2dm_ep`j@-A(v{O-n*T9F0Cip^v zEcOn%O(H1}wV!ue8{5Bo znAiVvA>8$wN@T3(=bO8ifjYo`hfbIFq+pK0(CEW<%n#!REmtyIUWAkn3A1H=UZ$Gs z&ydz^=NjH$=l~vGT_X?wK+(c*PRILau46hLgcws>qO@U9;Kerx5MaTd25`Y4ncmH@ z`FGnwf(=it2#OlwOCcwdK$JngK>fSTTx)YETrlyB{0;%D?Jfa%-fol{NR~~`>n-8z7z=W%=z5j4cW{`euYgRNMi=##yxyv7qBo8sQWSe`d@7LSIZkPLkb=Fs z3AIK;OZ@*IbA0T35H?i>;EFnQ&vwFsKQvtPV`h!A8H?EzbCBn!yC9>qs0io z=RrOinMDv{P?JramoP+4xRYUV4bwDi?i7G~CfXtLK4A3xtIWx|kQ~LzJE&5y2J>JR z)da9rq-}&yTaW??>UPxD?_ewa7ccJM%0H!MhQb-q0^wRP2Vt6f1MmVAApojB70>#w zU%rE?&-IGsJ?TxxOYCE@CQhc&;V! zr)6iQ=ki}aC1<8)r|VYIH11;!rhu_aYQ9oU%7l~8?M&?5_uqK8+#WM<0XJ6%gi~Gz zKoO&5r{rRLKNjqDRA}^femJAG5#(54rO^0=btwA`>UOyQ4UcqjZA3=rcp*|Qo@_6b z*yD_IElw^zHdf-5|1eO-G(uIr_oaiT8ve}{!5N^TI0}Qve_|Z~B=nJP8gIbq-)(yX z!K9?PSqpF|u1KQ(%pun-3e~3Zi3$;gs%!EdezU$iXjue6KZ2@aR5UUHpuRQNX zX?dCj=hAjk7e7H20?BzKPk{KJ-|K(Y>;L8R^;@b2`BPTh|6%F<$G^NPwoNxx0w39H zsRn<9vd*+j5S%RQ4N+1=>Cc~+#!bKrGzxQ7_{0S~yymcM}?ayw}}wFFUa$ zvK$}QAEjHC)kRJ7%E(GuvsB{Q!N{#@tWG!dooV#21u#WN8gIp}pKI&DHevtBCUEw< z%=sMHd*INYGl?@v{^`U(rizeUTJ2x1-E%MBz0FbaY-vf9+Rd(eP@B}*m0smCrR5So zdU3Pu*7DFl<}L&EFIR2$x^>O%=S^kGdeX-I?+(nKUu?IQ+KxtOJ*4cf94xSsw{S_% zTOJx9DS_BvLZv^UHlH=dDgi!zUf><#LF_bKDY|^U~(FwiV%f}W5@=ZKJTKq zLUDeRxeFJI_wFHyU#n|JL4YS%1l-lolEAR;TGBbTOhS1Qdx$x-whQ_OJU$PB3mpfm zsRW0%s(+4oGeh2$^CnkApEr@wXIW>MCpCeD_yuYz8lwnc=Se?=}UV6+4^Xl#y7$ zLdXZ{=Svs48%O!&GdlFnHr5aGEI=6C1BZ3{?SPEq7^EbD3{;V@5&B+ZzBSQIIrU9o zck9}1f6dWYk0*JF2CgO6uBt|k8Q72v;KCk7!aS)snfXVwO=3Jte)^k^W#r>|&zz%4 z6?rX@Frc^se=)(k8H%xpyOXLaFav9rz zx$8gLj-v0`43#_d>h?hasvYjh%gIIdPE*s;F4exK%6>Kav~e0*Ej)BaHzfQRPluoh zWWe{4NwVX#9sY1I^0gVBC|dzaW;w!fE|FjdXIBt(;6Sb|aS+uIni7uO=3`IBNA^)) zT&2nbwa}kS<9T1ky^{Ju6BO!-+0k7Spr&TM7;itq7)rtJZUXwQ6oELN@@;s?lP#A< z5M_}PNK!;KVyQ1r11^bR>FV`hC-Z(+xRtN=Jalb;{NX{P{k#UbDFsI9Z!l%;jqDh= zx38Z`InI-0_h<0-epogQDKDtThq*Pf(Q)RNz{mqLggnoJomsSNt}quMy+T0-M)a1k z;c4elbJQ~rAJ1h|WNN!EVE*FO`=ZoS%!QeuQgA5d`?t@#10RrTZYOHTf4}YQS#E~3 zHp1Vy5TJAL`u<&b=c=;(TsW#d$LBd=1dA(lcgk6i-@*Jmq4@ge#v&=?^6^3iqnD1y zUJmWp+n}9gWNK8RVe{jW*Rrrpj-+#?^8Cw=uL&-E8MGiMrhZw@?ml# zEq2SX(p=$Mac%IAJhk_nI3_uZqCI@Lu5n!+eq~D?YuAGox~DSBpd>n{x)LUxgG<;L z)_QLdJahDT;_Z5l1!)yL)$e>G@AFlq{mA1Asq@9j1f#q#mjW=@`Q97-F==(jvOShQ z-FuctdYodN!k1mh?gcXPeMPG2%;?7Ndl%9T;0r~20(~~BFrCBI;>w-l=~~X!32}4p z^$lw6Ws=HEgzRdS9D-b>gHGliEOu7^7$wKMvk}VOg`;iRiUp178c(intXU2^XXw^u z*EVLztI5-2k3A{A-@GfUGi_}@Wh6rtU;ojm~e%Bw^(BQ(;w-@N!?veQ5^vq zZ}l)$Q602RKXJ*5xXzI$K7nZA_zGY^a?JnwhbO-Im~@6lhhKeE$E20C`@)%D)AGIz zttr#7B0T$+W(b{O*ia2edN(!70RmT$mvu4jcEy{Tpf=;yzgM=@=JS~dQMkI>YbLXR zsV5Blr@hJ_Fthf+`S!yEdqx4)cA~M?%&D387ciO^;>EyYOmsZpIFK^{7)aVr9HNPB zMrSD)Q`uqk^#qvye(`Wew6cZS8YM6&(>ZQAbA?TmXN`t(v6_;SA zg7c6mJTvkVut%(e1A1r0C&Fh;dMV=U(8MNP?Os1gwtGqm{Gh9i@_?zUcXl*{D$dk7 zj1udSAGbU%9*`beyP0<^Pu?(0#JOy**ugo+2vukC>*34C>O7-OQykwqwUeH_B=35B z9xh1^I|dDgJaX~Ks%5YrT|*`YTf5@s8eOurvi;p93ywQi(OVBzy4gKVeHe=DMWi4h zYf>Ugn1^JWo#5wDuZE@-J;$tOvE{mUk%G{10?8L}D2PBJ_*Ztr?P_uf1T1^U8MusH zV#CH{QV{S;@9gf8+W*Dgdj~bO{{5nC8{Me%8WjZvBq|_^NVc#60Ria{N<>7O7!i;t zfo$oWtq3Spq}NDS1R{j85f$l8NI|5_ia=OGl6@Y3_q})CbIZ;bxiMA=L?!KEER^Ajy-4poI@LoKQ7we2 zs2H|7#QTjjKE)scR}m`f5&Qj20*=YSp`x(~M_NmzBdQ9#&~mCPkRn+3FD;!%vm1(#~^35_>$e{W^Y5QlN}O z_9Puex0n(_f^dA!?I1$6K$6e%XaQy?BTZoV^m-0vq~@8Hxr6dxiCyFZ<*O-aj|Ft6 zmrbOj_)KTX5pC*2L-sHgayR>o?2`TzQ@VWLTPv>ZEL#d{Z7tgT{x-Ox&p)V~a2GyU z;OJt(G&kNv$CLRr;)q`SI{q|J&iU3aNo+qxzK%*5as=Y1zZEWHYuAzqo<-hm3Sg1$ z`G;p*iQ%#JE+OyG!uH2XOk)OPPc0nak81Ca&2(+`4c=EGUc?~of~)(S>zdVnZrNo< zwZGvw4~8Mxmahiu`$UQs4o@ty>>@AysEfe)$sW6C6g*ca{Op#+U)gCtuaLUYQwWBA zH+n*1cB=y&iQQk70s@UGeTHFvK6Ts+Gxt;*Mi{JtVk>aOdRXe&deX%q5 zQUb%48cXn?Gxu;sMs*ciNu3id7kQIfoR}tC4A;7@Z_b)&v?Ys}fNk?4Aw}_eg*1ww zN#WvkZ}Y-4BAp-Ie8{Ya?{icQl!rR){0j{Cw>lVx4ENk(A9*sj>DfWBG#Kfi-Nnb> z?Jc`rYFnnSnr~HkCg@C>LO7_EPWW~w3E4_-p0WJ`TDdI-5AT*y*}Gux9cR307gCJu znH0<-V<(A}%)pubAV`kdVW1ung$evjCDy&cn>C|2KwF%*9Md^+hwvM-Zni(6{*-CE zHYwu!8e6!~k$v)W(Q7z`NxZ^5x(qU5z*NC5gvzk+(D17zOfbbX4Gx&aMs8Qlvz5s* zW;o)kl*Ug@7bDxO&Sr^DUo&^d2zvlC#>Ls&-ETsRZ=eprxyXa~9*6KAhT`*CK#B5! z`XA{kx(dXemCSGwcYEW(?@Wq!(2CNJnjU_3!@`3cXyKQEKIW7~mTv z6}0T%PPC8_&*ak6kC{fMqg7{a`=7pW_`&TZ!TMVlTz#M2a=!S?itao+Zr$mAMl!=9 z#$8W2T}d%bdB2Ufp6Hr#UDVhy_fMRcCRWZ9wdWnQCY|8y(6Q3F@HmvlU) zT(<0j*bDR`olBC|g5D+xZ zpm6uVMKn|#Qi#C6j$Uork>U1Z(j#_sd|dYc1|yk1--(UMYoM;IF+nL7NlA%t0{A4J1Wb>Xrd6krQuaG z_gzDC{N#FcuX!Sc|Er9|LvJ(MuOOz(ukz!a>`n*mDbQyO<0B+0LvcxfS(z z7y9gaV3xzs2tRq%S4H$mFzVIOS2jl)*WTS%ZrDf*tDt`q6pNsE;zTkSwZC*ME@4Fd z@%9nfi#@9OfcdIaf@+9lOwO3PxRM)S`;6JslIBP8EhL#VBe86Hg;z0LYgos{)Fl2qW|XK3#jk zRRrqqBE^e0NmG{F&V42b7kX_=L~cx*PtIHA8AZ#9TmkaoGIm27Qzu^H+m1Q zy@=PbBrVh|YXeTbj=qQY$=7v5nj|E?-;Sx2w0TMX(_HRSzhMh=&!lb9@Dt~$i7uTd zSEzmizwae1?_2|_>_AMt2ztks;*{x%;C*LN6eN5l5a8(|TdqxJ+g*cG^^#d~N{)?h z1~!FJRt;@+5thTJo;dlAxAZwJzjcgE-#P?j4-71SmwG54SBUpD7yy#L9PJ^a&jfVQ z;4BS>-50H~KbvD`(K zX?XxR7|Ag-qy%>mdEic)G8;Fhk(J~cLW(6Gz$pz^-)XU^R4ggOI<<#tY_}CD7!^_6 zZb^SPYkapya6?%1w>hFr7(YRUQ@|C&kI`bf*Y^)a|M}>b?*5WYI0j-X7>|d0udT1&Qr)ZSweV3gDT6ZwcJ~x}|2n z&R{$DuFU;a%-tr&jEgUyU=i?+BlFy&d+xWj1*^__IEvjAkQ0kp&Jq31)NCkl6>0Hm zP$NO`l#0pV%4+?p`qd%7>or3lV|XvxSXc9^wjo)?*Yu((zM!eGp(+0S;b@KcV^;=j zTwccW{C5IW?|^pielJ%P)FLm=G)#4`1~PuTLKQFrn|2(H4F8lBE4A-qn@g2~@RjZUQ%599$G?Zsfm2-njXfN?fq9 zq=E8HJQtVnYLn@FA@dsLT18xyw#}Opmc>gW<@s@xPySJwmDFRN-$`*Uj>Y{?W-HYc zWICRIlHrf8N+elD$zMGupIU{fjYbJt-7PWDSTY`pe6AD%;Sg5Kb!Q(xZ1!0l5T#Qjvbq;tD4K<94k{<4r$ovR11 zC>FIF%g^>?tntGs6kR2V7^!1M=L8rIYph}^+r^rK!mjVX0iC?=Z@twilZX6&L|!#P z`s(#67m((N?KRBQS@fzPGlgZv{F0dhv?0PiV0tQN(g>O+VJ;XjTO7Mn=zYh@X zXQ{COys68_zVx}lmn4J-@jXkLjp3APdff>+XU2pBN2-3d_&+D#E`q4aEsh_isLe3T1Da za~BF)g8606O1(eyXR*D&C!YIXE1rDYJfDI{CY;J{QSPm&SMI&3e0qDtxnIy#aKgYa zXZnPVcj<94e3#*=f@m_rGY#J)_`FS!!5f}}_$KKy=zO-oVtHgeQCu-I@Mylt_bBZu zW9#@t9A9bs>%8cMo!wzm$a*b`7pRf}CO81#l0bJudcq__`?y;?Dsf&5c0gs24#l6Id z;A#-eei_M34s{Y`Hi5GO|IiA-jHc2b872&4cP@l|_>1cMmk-3Ydj%UoYxWWJq!^&* z={?#siGIXI;;|jMQe0|Nre8oO!ksRs&05-8Ax}xzz`Z-C33_l40~x&?M2hj`6o+ZC zFTe_nx5By$z4S7IEpr#es&=sL^yU2b>rTeA{fMcB)86(!CL4-K$@K{#a;VN;VgNm9 z|CaI27N~i)TS((Y-c8l}`a&owHz`81`O!;7GzxCeE=eeGVpU(Tl@OjqHM)}R2u%fZs(v+VR zVA7<_tC7NbVyDx@j4#uD7~Vq_iInm*D9AH9$&N69+tI=wZ%8s~*Ot#Gc>%qw=p*td zsHc+|w7+b284;Ut%`2%Cp0^ne;yYHyl-r7xU-~}Ql__53fr@G|pzrjN_+7ke@Ykoj z?OJ%>ks|L-0j~j>HVwA%aA1zetVIIZGbIAa*N_r*UsuYB$h+(+0bicYi+{)=^`N4- ziftons358kzHOb+sf~&yCuR13BoPTfK4 zp(mvY=d#RS7VDIcck6X39#T0xG?J5Pr>E$W6&DoqiK_#ss1b~QfpH%}H_rmNuH#_~ zh8j-5(ce;)x^($QCuc$&H7^~VE6$bj(-gemG0<}P#gg4trbPUP>2m-6W$n<-;{2~; zkJD)Z&Z(z9G0%UJ=yx}pUH@V;D;sU=X^pB%EQ%|VtS~P9q*2y;Sw%m6@kE)txJ8Di z^M$AW@+f3`wy6M4lX3g@?0(6#hiUgJ4Rw>%B9B&`D>(lPJsLk(To5EC`S&y&$Dr`BGAB-mPM;snPoi`b<|n?G7I;bwzMwCrcQ4;c0?Y)7#}nZc_tyz0l2uO8R#@3J zVt}{{Xy0DVmKD(-?*3Jwjc04*N;e|6G>==zM3<>v!UUI@6qd~x4F~=UayG&HoNe$A z&oeZq6V2*w`P8cZ-!qS3wA4R5y~fV~y{Lqn@76zZ9>@J-9K8cJ^)k)yc$J@jgMB!g zJA-VBi6i1t9uXlEB&>L=iq;=RmT3usAod-21lU1chLWGp%Ax2+lvvW{5uA9^OMDj4 zQHm~6?D@TgJNagqaD<^Wq6wNx>J-f0(TFlN8ufIJ+r4_RqceTSio1Y@-Hz= z5>k_|R+ozJv`KAHJtxAiSADc)^}>l0-J(l2*@qvFXB)^WN*qk=w3zrz=}yg>dpfWE zEb-SlgkiSA$&pS56IhntzI39f->#%p`)-tR50Gyp3vp$PK^_SDIpKJ`3X9AsLDLHI znvejF=m-4Q=!li|(Al*Zt~6x-4jV(=!#zsu2&M;AENib;t>!(_;V<-N*-7W-f%z3; z@VbTRnQ*;zSaV`+t$s~l3Lp3dAF)2f@_z%`-ic@&apFE`d9A|4ogjh;=h=0*%C{T_ zXYO%Q{D72fB#?&W*3nFj6%#0pu1Dd9*%V$lraKn_O8p8vbJwp9*MIa` zw}x6CJ|3*s9Z74iw8gFJyxl?X*57R}^}Hkr?|jL*1K$wSu5G$^bNe;b{m-sh$}h+|N&vB>Q$JMx(@HyFRzmls2z0&fI-B&|%x@e)1z@=1}@Rksh~K z6(aGS^x>KyImFHEdvxg*4e%7?ZY9Y|oS=L=r~SOO=8 zaQfqyRi9$$*p$3|ZNPn5<8zDc3p}y=e&$Gav&YxqTydrfumw2{M^j8j@@#w@uCoTr`)Rzf^>zc*1e8{6rKWGr#XklEr?UJ6%^>7NT^AAZ zC#|;c>{_HY8AXzD=m<0q>K1HKxI*ow%bRTeTfl&Qm(pI zy1Oaj&%m<5uE9=^^GgrzxJ7sAwfbm4o2TLT@rgy+HAN?z{jC#gL`ArrUZnYc7N8RV z)f-nu@FL@jglw?MGvc3dZAS?}ZY8-MEr~a62U09Ufk;U}_vuf@(t93NGLp|XB#`(g z33`Bhz1PLpVh)SJ-$Eb2Qk}XdU5Z-t*U^sfI_L-P@ut2~B|^6TI|S~B!G$$is1hqT zl_fa@a@4iR6#Cy`JWJ>9hp9+o*{T&EMY!VbOX6IiH!cB|&@*NV``BV((MVEC7eCu7 zo%Q77)d-20&TkrL(x7MBj;?6_^c(&SC9Zf!_Znu2J+EFkg^tEH4VZ z#{5+gpQh^}ZvC;$zI|@|gBnsK?m?Aom;!Hfaj9N?nTOxd2RTd{5Tk$ERuyZ(?|lQ? z*vb7Kp4UnAd}_b`XvYnZPYAX&EPQNhC2z-_3*t_pz_kNmgUBY=0vhm)+ovn%b7mSY zptv4@-!M)DIicm8X0je^7`K=qB6SSttGEuXcwPGJ)jU_`-uam7%~Lww{>ye}w({y? zR!HCu?Z)F6N-IF2U=pyU;~+Fc=}&Z%Ul4ZElomu<>4FmU#a-n>MQn13La+pbH?mE{ z3p1b4aD2=>rP!1(Mie^Q&`45Er1Fb8U+5oYKc;~c%*D9%M|vU z1zJHS(5_Wp&0}VbyWTp;5h>gA5t=9RFwB$~wsFA{1{Dn!&~&gmG!fgK$+yWXh>Yhajc~NF?tlDOd>zLSI3>Oo%^NGh^+16s7Z-4k%LE)9HP1bg_HF*++ z5v|E{bgcJGs}#Lk+u1KAAxXM9>$p(oBl|4*VNe+Z`k5n?Lz5}67PCgib*R3NQnt#s zs}{Fe@-Vw~uabGX#B^Wb(qzMh&nYXO9l*=!2wVp4k}0PYqAT~r^yoOJxaH)|sfFA100q{N1v~6W$!ZXtm?E#v$>yGfgIQj1UqM(u$Z}CF z7ZhMLWAqKg?PCPxMP?4-ahl$AU@=w(_%1U=p)cD@y_@aw!-8bY5SA^Cqqhb$i)y$Z z-Vlt#JHEoV!;4OSF0GMOO20M!#YG^k+|6IJf^j}-^uytKi%AMXM^TU;W3xyvu*%W7 zh(Mo151Vn~}ZrierM`y5P<%jfUK2{wbM-x`Nc`F-1~@|rYzktkgL*^8?3zHxmg-CvEg<^Q?O@{G-_GR$=`$x$sAJ054A`gBx9?zvvFlj**ihGmdk zM+KRR2;piO@8&JL=01P$aVsB;R+5Gs1pL32>4bv5?DVyQ==jpC3jMw5CXMb^wG_8f z^K|8^lf&&T$I6K|V7rdBcL@Mxl)h}M`i2l~2I(EY_uKdU@<^i0z2OAq~vtkcc2m#oSJo}Q7sZ6L7WV^iP? zB;QQ}d~<#WGJdg3{}~{t`wX1g{*$fqpKH1QJAVDslr{4YPpSh<1#FH!29o=V&8YCO3#1-(}gECG6ZpEzaZf{Ty_p?oV*b zHUc*P`p?4o|HId>k%D;cR42&MV|7sI7=jT!CxFr%7FYSj0glo*JC=!z{l2*)Ct+Wg z_4`=R!qI5fGDZ3W@%r%GP{Uid+fQ7*?=!#r{v&vp$DVl*c#z{mx{%Pr;MLu33>RJl zrdTAp&OGXYSo+(d@uktnE*HLO#GI0#!kv$cn>xPww?1edR$C&z1 zKiA%olTTb3547OiBt9Aj7Cgj`eAw(CoF63~!1I90Rz^lrH%z&5pg|`_CvoinKwBV&uEhah>=+_!fCv4;w6uk%*LZV4!t!76 zZ5=1?)D-&KDcB4E#}T_BY17yCmP5EI+zQ4|cPT^#j+(BloqDcH@~ie!`)u4ixik~< zAly_m_JyqH|JuEqTzpuaQSHsjY)mL~@Y%LytN6Q>Ee5A=N9Tpy`8jc^Xtgla$+=_v zqDQp7VLL_E9*ycm{zjQyMnzfUhdLe;%!gZ!loKuwwaN7|giq@D6KR)3FoQZ5D&fKY zj;i~beR%@jx<{D%BJ!794@Y-xRLEg5gY{EcuSuGMS3G#$gBGY-hvIqG$*>KYpS+O4z6c(+3I)rq1l=q|2X6Z~C%eCzl$auYpO7Q{tDBsW>#3lyneK*nk`a!`Q=hd#iEw>Rr_G|$>WpGi^dO+Hn$8p zsQ2u9Ir@1L8_S(2HV!Ga@FoUK{D;-OSofm*`N1HPB;3?4-k3Qo-4qc>6CP{5 zUflMWBgKDXinV2LpPC+z+w{uMqDW`kXQaP6mz^5f*wp&wO*~8H_xsfwuY- z-RK<(K9R8k_!WCrM>nJzEabz++Qi{JMldrT4j0xkgPo938mDkpu(xN`!r-EoS+y}U z&k?fkL<{0?Q!2eh9DL`Ir?1P~F+t&K)NVK(rh_sqHHT9@$ zvB~Rt)YHyCJIw@Rd7uUXL^9wIOb{a*!rU2%OzxCJ&&cq z-OQMjQ}rat1=j-rYs|QKlc~8fX&A)_j=2JmyxObe7)_$M*-N9jWfX(&d%@>GIai44 zj1Q9&>hm}rzSLv+M8^2{i_O5UKtLrK0N#@|@?zuahXgq7zV zhQoS2_hi_spK5r0kg~V6UftPwW~Se}Wb(4i$Kc>S?m_o8cr0BXFav;POB)|S8O~tl zG31Lgwf+RM1iW3KQ`+KNapjs?79G1AKzu>-G9*r3<5tt)MqinwAnDmsDW9s!Tpq00 zzuHSD!6)Wde4!LpGc*>yrYntzo$b~Rj@1RG^c%XzM|F`f>0RyxCi0PvK0U|DKF0X7 z@N}m=S1#0W)N$j|QAVhW}LZAa(`7_)7h%v=!w^21u;vy5QskD z+y~O%RbzT1II5>l)&R85d*zW7;Ez~3-a)Dpg@@>s;#xI1W zRn|#dez3#u6^Du7XvBFEh@2$RL5P6k%1`;|YQu4~OCj&1tKT4EH*o?!ZGz0>AFH2a zrdQz#7ap5}r?}e{f3c_6EAzH*(G+CmYJsGVy=X@B8j1}3WUak=l7 z_%brJV4=W*J48-*EQmVN;OBI0F!1lGz(-#*K@3-0A1?aD`}>U~sc6rL7W)!-?Dr<- zzARj*r%+S#r;Gr%vM!u>eu>YF+~_89zi+b#t^Q4G?UPU~r@z{Xa# z##VPtA`%~A2U>~eu}~l{`x&YOwF5Rh9w7eq3?vBHfmM@4n<7ogvR(rdlQULNl78A0 zzAQQ}(`I|p*l;b)@cWPay(i7|1OhCrGb3%jwJcabFa5s${>SUfkS9%Wudu=y0l_l@`+vLP{{%QYk>&+K zZU>^_z3^MGPY`Fg7_l7_HSTD4Xb&)dHk^z zq}cAlg?q;-egXs!4oTsPA4BQp8D%4BRsZsrFOUAYs}wmf^6N$!So}njV6_rX4kv?K zO`PPC3nsxYXXKk7DA^mLmwkj!kV%J+fNl|o*e>u7&&t{waWjDXx2XAnOv~&e=S!rH zpV#v?QDZ)_P8@vdTJyxj@?p2-!^p*<(x<@m;7XaHiRKZ3$5s*FBA@!``CBDsXxw}P zu6ycQvJqk+YAa6oHCy<1b0v|incKnxK;ySQaUeTD_A^12`SkPEnDRcMfx?SdZa%6>q+zpT8!0Gy4S3-Myh8 zMBmH*>oEg$&y~+;Zyd*3UX%0}nifv}5w|vB!ClVd)@O7}=UxfeF~1UG?rP!Pi}3CZ z`4RAA{Rh_7Jj`jiFZ2p4@F#~<7t=8JmDTbWD?8)cS5|iJP-@l;c+H;KI=+d~OLe&&jg)y4!7cgfq|8624r-nWwb7OXsOFm4bRRca~ zB67!c>z4>{s~Qk9+!+<%AQSPrpnwMy=g0Uqd?a-A%!{jSpuPq)uue`AEu0r9Y^80tu zd&2oNwolgo%Ms|8UC=L6pkH1=8biQ3$UO5u?1F$x(7*ou`p^0yvyz{C!5EUdmCEHe z+EKMokWzY0Og?L&{$%jO;*KHP<&nI%3`(K$K$k#Wx{9b}H{T!E&{*Feyj(u8qn*g* zfKKdp(DGY0Cb*HtmMjz9#P|$PN7@!h8lFzHYX%3f8VsH>wJ)w-IuLcfTa+$vH@@D@ z{9xqKhWm*}B=wY67bUxVHb%a@j8~5T|Dy?+6gEke{6*Q_Gx@6n8Fqj>L9EYdETf|* z0$2r{$3?K9AM=HytBpMuJO2uk+@pyu7~7MdOmuTE3e~)pH5VSvk+Mq{XqAR6Qw!qz0))GE8~B)anJha+Oz_2q`y{XHpD>Z~*lC>7Jn0n1@^b4QBXNtA05*)bpl zCa|uLw7Bs`he`TqHv&{I{MF`c;$NV_3ryNBia@s=_~%4?6fP*_PcnY1O6tBcd!S!E%&AQL4DAzSswN@t0YpNDq zkET+p_9$E(Zn>~8wc2Z;`Bv5K^{_@_d-J(Q&;&BXju-iz@>>-Li~`H%oKr6y(tY2i zBojrxzG;4nqP}i^T0eHE5o7FGpcoN(CG^E9G21_ zP2Jyi09MzB1E9Qjx&p8pv_(1X?1aa?%R}g#%7<0jbLAV~w!Rs@Q?-E>gncMi2Q;3k z1qt@`?Zj59`psstmcA_+c+aOBEq8m?pSj=~kUrRMDkv1D6dhI|X%}?z`YTz`bUj-R z&}$VZ?K|x%V#*@3D*`l?hnebm%#)7ReanW$wwUUpV!m!?2=>q1i_4lVyWH{lbDQPE zl!3?`ZPA4rci}b%vrnc7yTp^d7R$0!%Ge6>A0Fi$u&v<1UjLPN8_BUF@~Ld!0K@+( z1~UQhwCEeLw^v^@L;+f}#V(f?J7%F}!)QK@*hF=%eZkZJASA#)IN*rPw>P1QbK(ra(Ik-5Q3B??n^<6zD*= z^61ON{dls~nCixkpxKR`PVGAdL1j}86YU|YOeej##TijZE1bYj<`re*VUQ!vDUI`P%8S19If*c|4QdSUC++e9I1 z4QgeTDZv5=*9fqzcv(bJ`Ro0RI>`s}7$A+8heIgR=*hW}maDp=?CX$3XO$;q+c3|~ zqtw+G1LSW(YU4BdQ2deK?J?yCmyfA`=~2E*eYMtsgEdtc{VO=&X-8ZY0eNjk z2Q`U{1R4JZF~yaixwVSeF@;0ANz99SpKs>_t71Bl*DDwOs0I51UtpC?JOGEy(my4X zdMEgm8{~5mOvf}h`j>~uZ&6XmeQgLfFBFMHwDA&70`7+AJG-W_$)cukAUP}ng2Vmn zBe=4qd#aP^1-#KMM~W7A`&}pK;vHK zHcD1c@?#718njNDG_)woqO~Pu(-YDKMdIFj-5NKhh?pK!-LHjsek*9*#QF2LG-q9f zy{;vvR1LJPo-Q!X1p1t8|Lo88BGYQn`@#+oe!<8+iS5e2TusOI>0@z#^DfN=scSv( z2%f|lL7eo0*-J)_buiFC{i&T>ez%loKpRM?{eufIzP}n1rmDkquyOO3;FFML+>~hr!D}rk$J- zQepHG?(b;SmK!aBzA^X$m*4s(^vi?J|M8%`O*ILY>5PAP%uUIIfxx(M7TSi*F9{l2 z2rB-iquVBJrq)Z)ZRV@v_DdgK=;IlR{!118*)a1|)L(T#Lg)t}?@5~z><8kC9ZsaK zFzBTM4rnS11r*99*#-l1Z}JS8{eOn>5LBR)1P#YEg>;!W^)AHAP(yS{p{f6#A*YXu zSphyjoXk$WJriz})ROU>=6|jG^M}Q6&*FiMpUymQT=Ll{=l2%sl+XG0!q0DiRAK+ZmMN<18XogA5RxPq=;ZOHj#2uAF&A!>JnCln2cD5s zmVD6BCV$N_-9yje!ElarP41z>BE!Ina_T4h_LRvi8gd0>3cR2q-|nCUj7XI5rZi+s z+gXAbjHo7DWM*`7ZuNBJBABwS(r^%z_+@+?j$R_7t?ITz3I`TsCrV z_tRaA_TpSwrea2!xA>U0gRQh*r}*QKZVh3*$IeSm?amwQusGIfD(uL(_a(YXa8^tJ zDll)eRd#r9X%r9PhYo$3n)UX$j;;i4{9g!ad^l~rJ#wGS2vWt?ztVuFOD0~wjS8xAV*`CV5U1Yartr&cl8@h%V`a$h784Sz^ie<;<_;k1cvrxh+21KI zR~LHm@>tj@_F)LodCK$3(9P^=V?fX;iLaNl+8#4a|C$vSI9}o;rKD*8sPK5XXEuH5 zMupBF>xmTJ_`iK_Hh4aBKVjQL_fVZo4z@U$@fuY=RA3<&U9@^SjZ^4hs+yKnLkgU?ti7BY~3-IBinTFsd=t@zgTRQ@-rb1yj6ukJKa-AJHE<9 zB!TNdrlnvTf+^T3ckcdQpsviY0V406Z^OTQ>shyl0#UnsGqhc1610G#L`?1xPM)s3 z0V8E)+Q4nN%1_5QfF6>AGfl9mAKJ7XL{&=I4r#0!+^KxQ?IKuu`-5)4L5In1kLFgu zWH9x5WP8@x*}+*~$n9Cw-mgK@dI;YX|T%abia@1nW*!lHVcLhgeOU=a!XBig{PO5k|b?2RF^*6lvoGE-uOOQQ3K@911AfpMMU zT3TD_+N6Y+W8VYE3to>9kY(PJ#7#sAw+mbIEtLsolGmyV%bt^~(LebcKs!C)YAyoV zCoa;FZS6<2t$j1pV1|dkQz=aN>2hXnUdRLYQeWgGdeXizBlz5wmK8;(bejxz)|AL$>Q!K1iyh=g0JCy)){LZq&)<^HfXbTr|Z;aTTCdUO2p>vpdYC)oFC=4 zqSzGv{mPM$*soshDvOM|FS=T1yT92&X$q?AC!$?)28*n+Y>Vj}4=g=O^94npVT<4fo4 zuH{GcK~S2{M_Urcw*1rSZ;?kq5q3W3|5iTzrLOj0K>hUpQf)={ORy!}xdKWI^u(AR zRI-u-C0IZp{prk0YU2TOMOVR>i>hY|4-(G7*42wMMQA0-w+`vZ(M?#62tl(Ji&DM%s)IItC0WxwPBhSpI#>TcIoV1ESDsn>b>+d5Uss;7AyWR$ zNuK}7&;Q%s^lyItzv7!V^sx27ikltwj*Mv8J(~ig1u4$I>^(uFO`)Bu$Rx6l5D{Uz z1W6_;r3DLe;n9SUN)UdbXvF=*j!+K{c~+RXf}PNn8ql5a=2TRD55J&6#AaP$P@BCc-jA0rYF!cs$A6OCOmxQgE?2?RyyV6^2cxNBrx`_ z0uvw-8%2d3un>uh03Q*={ePj^>SUG&Xa*!;p-dvk!2tBLV``m9KMgAH?cmQLvaf-8 zoyJMvilNybs8Bw!0UdxH38oNXW&0H@Q|q0)FdS7#u&Egg{D(&h#u+hXwbkCLr6FTA zct=$8o8Lfdv8x+NP7Xt=Y-9RPnc@J!z|FmU$!HHO+~dsO1~y>T?>a^RONs8tbjdAZq#XFtb*_QoVn;QvF|&3ilZSQt zc01SmGUoH9sP+Y15=Vd8bz+COD!7BJ*ttN^Y5Rw#fz{GDxAow`Pj37A7Xxlxs6B#P z0bE8x1C|lX$jKI}{WhsKqrG>h-*5HJg$JQC=3&HFend(q`X+K)H4G$=WYDN*0;CTE zS&QI^qXB$}3IlfU91aXVe(rnhhJh}UNSC3bxt5jysjswik~km=hyLpQ0(|FB11vz< z$)LDpDeP)O4BKyQyMXv(0mA(T5TLagk!(xGn%ELiK1{|Ugs~9VMIK^{(G?-xq@dFA zX`eBBUB%JLY8@xWn+B5jl5bg;M+oWCbavh1wqI0LrwAvP|L7(O#>cEg02Y4=g}lW@ zb1yi7G15V-&EOogh@rxUumxxqfkozj{ILJ=v5?Fr@)8)8eFeL@@<(rxQ!l~(u>NTqMHO*lYLNMsqgr|mem{~jFDo~zXuPp_}?v;(z0GE{MSbcOcz zKo(lN&ldVt#G8PXi*_ZN{a*uK{bVZgQj?CocWBT?-dFbYs13k34k*?efT!6+{fU)YQ^)DLJ z4k$Pl=}ZYFa$L4k8cR0GZmS&f$ahXjOZ2QHPHd*g zvX!8<6wON)U7Cjp)^zfL#b(!{7d;HbUN{+yK<(E4n4q{Swg1N4n@2T~y<5ZBiYSN? z5l|4)iik3$RR)DXv{4Ze)5;_uq!E=NLYf97N=TG>5YkSdAVfq!gb|F3Vu|V)th_h!L#}dd@b%EHp&4WgS<~b$Js13 zzJ}QSu5<@b2brM!G?aalP+lWoS_$0VzV;4I`z?Ou5Y{*(=r^S4Iabk_4tB;9RyS%!^Q}*HsW(mBf{l@c$t>k-==3U zfI-$D5*N7k%bWzcc;Egns8utgKJj|S;-o~US4A9$L9;T^&; ze40(n@V+>3L$Ntg@Tc>Nyr;sR&A?r;xHSu!Xbief)J{q)H@9`*Ejk+djQ zjj&_654o{DJF_Cv>WW!>ON2_0m3L8IW?FcpgxIxFH2-qgLo3U}%IZ_i;N@I;Z1o%L zdSv+^E0V79anaf`|Ne}J-Ogt|c=y3C&yL&VL^sBqT==m@%UfjjuoCsIAm*>g79Wn|cTNpbjeKQh;;3W`Df38R4<-)T4EoEHC$u#;dP&r~M%2)W>)56w442z$mV<$`?rDR0MhjIWwUAnloq0B5_U1+GZ zgg6>bCO8ZRNVVdzD#VgmtL%8I(JOjC4pxtyim=sG>9);mb*N8Wy$kCm^@W)L;?O0v zHYmkKy3|{W9GQE#__N2q?Ni_Y7?HN-X|8`vpvzoL9tXMr9 ze7rU*k-8HTtKNFzW3O)+M_}SIz1Tocy&xQLaTqXtAKTv+=6Ei|B1@w2>}8gRcCyl; zQl#H^M?bP{CU0Fotb&4p|aTt-FU}f-vf}6Fp8bk6 zQ}jmaPBK@rU5uA$YDw0>#63Yg^=3ECgLnwAH^~YI~kf(GSUd}IM#ar-xfPKm-o6aM<2V{CS?E?0mb^%h!|1_)5`{%wFMql9S7 zjNoX2DM6srF`qiz?T1SarO+<%X*x5SOTYzNXWY9*>xWYdF~(XPn%I>-8ayR21>(3A zXCYmcZ0`<(cu(8^(bh^({MFgGM0Mss{khk-ck?9xH1cB=4bjtIR5tiO6P{YhE^yNcU7uZWB=YFJ5; zv^PBN<*YQeKPkDlQ))TWY%9-|sh3i0AyjKj0Dt9m%|QbMclXi2nx|h$k~(mgXm62Myp=Mvn89}gtkAoV;bbwF0q-y<7Ntwi z0a?Z~nP_bwx|u{+k2MAh9FTApW7_BmdK-Ev+C$kjNvXI%Oe{~ehP8RrUR*3lr^kt! z4hd{Xt0*?TGNTC{I)73r8-xZ&fg3rvluEp48N_l2t!+r(I&v4HtSg?EY?7xU7G#3t z%z(^klDC1f7jovsg?PmovU)5!I+rQ*MwC!R%AWT;| zwDU%1`QTo8h@&LMP?o4bKp6S;J5ET0vgW)YqNJb~7(!~v>tt$>3%|>U%xhj|k;>lGaiHmsp^YdTHzcFlJ|-`xUq!>S}&C-zUR7 zCahvE;>jP(!CT_D?HkfQ7heN(^Aun0T2Pk)szrHf%>Y}Q zPn=i<6h;h(YjQ6wfC)aGJ)BqrI0OxP=_Wzu?hUyaj0e75nssG(N|uMZr+2sCp6}$W%(FY3n1R$6_4InvKae&SFejrtRTV?-=|0s&$<>7q7qhQ_IrIsVKOP zXz5X zOYgKdyGukx)7eES#cv6xJ-ex-kea(xZcz;5_&LVO(3@>&-Gvx52V|8CcOsm32ia-J z&WIg2KD&1YG@M%S!L?r|v^0KQFcz#LlR_TCIkXLG2actL;)fJtck=~n% zss;?6^@ZAX`qZVe129R=KMl(cl+R?@VPesmMBj~&RZ5dhAzXU0zX?-LE^g_3V9)2N z%~+eS*_w0mNv(JyA=Lk~mUAQ0+5Z>I#0(=co!3_=6%*TOYBIbKpCLg^2e4M*pkI>O82U#f)|%L1%qz;UgifylRws>awztI$*-|^g~X%;<-^2- zX@aS_&yc;q@TwVQOKI?;byh<0Yb?4bxQ`s&7Ns>bRKeqcdIq_u@2}yu%SO|ZPFf*h zRCkvqQKd7KVgp}?){nJQO)I~3u3{g z?nC#*_^a4Mip*LjpRHv2r83uOXoh#Zk2(73;K+h~zzwQA9i&};HDtkw9E+y)xgV8Cz8oC)z^{ABX?I=l58pnBV#0?DIvM)AjD&=qUx-4xTuoCkfes z3^Ji2maYJmE_L}W_QzC;9oY7*T4}Netn1zEVG_#?X-8U-SMLhvsEwlLs-C&wTFJd; z(X+y@ro{#gZ{gFQk*$W3+oT1W+zWZ}u3!#VZCA0-OgsCGUGr`+M8ul=8u9M^Z1{gs z=mN#Ne_Ry}*@%~%1Py*W#Zr*{@BoA9Qz{+y!;Ls0$$+CH)Bhgty)gPr6N0ks;4aYP z*<*-UF3L}^zeZfjOp%6D|J1fh0lpFkATDQEsYA;1L2RfS&bC}CA6 zQ(vFrkltl}s_mlxw7?TXs1YS34V^@rm(gUugII|VDv9SXOIsNzwm4l3Sf4w9l)yjk zQ2}B}0G$k9Pve9n+8_w^vkoiCu=bJz-~zaO9=HO)@7v6p(wB6D@CzvYyw)D>$YCl; zw$qY))nSme2{aNOko!CA^!l;kS#@aOu<(}&XbpJ6!q4a!{!eQlcw5CA_5lPO+U^gJGz9vmh5UbKwgVK%HPNnag&v7 z0MZ0uu3W~a+gacrQZ~Xs=Wo#t2ut$MHaa$$mcTiVebqk430f4}?5Pt?LoO|L22X}f zNOR_FH3m?34^BEIE(G^FW%4zXa!!CNEMte#MtN4_wTI*LEFYv}YHwO0bumyx;)eq2?cHt)}PhlkK`Vb{h0!eUzVwMKeVRc^T+E^x%HuTNvfgCD=L99^rD-=7NF zwjVe7V1(Iw$&-8@owQ?ox|EO7ja`G{Zop^ z3$mX-?ol(!JbEMT=84n~p(jIUZ(E!ntt+^WS?BAAUPwK?x3c|cX*N)LBbT_nKbjhs zbK;t7&aL;x<*vf>@0g$PA`fXeS!a?fQ}-9JWgC{aj3(5w_%^7K7-S!1dtPMvBy_w~ z$Xe+cj8m*;ruKD+glG+NR?Cvfks`OC$g^(i-x*U)kakAZc93zb$dxl`leZf5>Lh74 z__1gS8cLeWk{5k+fYft_NfBTu4D#!hT+0EHIT2o31qK=jsc3c4QdL1x}Q-6wb-EDa+?MxK)b;+kSJ$o^UDsn45t z!{g!}ycGbY*nW&;^cpsa5ce^si=j^K$}^1P<{9@7?Y%u!ueS|$M7PmbKfC9&<(Ut8 zw3BpcL#5~27(dCBBKSHlwD>ixT1%#mK~@Wi$lZq&viy;{Bp3c%>@Tv8>6x37u01s( z^Bs`k1ktXYX%6sJD0_Z#KGEdEofQoY8GlW@RZ5L6-3FGU-RffNF^LfT6-HoGc1jhg z!+HCvV7G_6Fb2hQCDLE2dDTgC^O$>Y_Bi;52yvUqyKy^oyiZK_Jn{Yt*tnXfaWeHr zfmH&H)C2ES5*2{qsfpc|;G2_Kt+Y78GMpcNB(3eqZKCPX;Zx#?6saqPYxYVz6q;uCu<9Y7xm6;H|3fh-eY5I_G^$@{*gCJmt@WHYjC1byOE0#({&E?_ z5T%PzKpNr1d@4`u#TpIZPiIfHgJ+575aQUCJ#+g6rq+8*-zSMwrTWAMlxPHEOV{qX z71wsTxZ@O}OVQ7k{7`xN6krKj-2|@W4n(<{Y^S^yQIQ>xUq_Zvhex?7<~=GDTX=P4ZW)-v!LYi3 zew86UP9H%~DHYW`ZK~BYb&I?NvEz+D_ZwKiU1QWJB`@I9eJ$vx9ZbAk#In5I)#3|d z+0M0yPF;L=*!!=aXm$kE`OqdD#F-??)}34(&_Cb2lGfr&HInV`uddF1rw&fl+Xr|L zITD`OmmZBh*}*D=lTN}nA5u0y-CefiSJh`AZu;#3!7}$8=PoT!?w}^Wxj+)>9g%y_ z-s8DXN&fgCY06u8#;ATj-P09=va&)3k?wZdnHap(;|9)U>6-#>aG#uMM+=#g&qkw( zT_O9vV)Es-|6B$?1>r(SBMr3FPT?$T)Smx-G3mmW_k9sKWghuB;)CbZW?3d%bfxRJ z{YI9I9rN)f3_OVS9(P}CSSXPfhWt{5B;x`&wP|?tYn#VtW6GN=l8B>B*7#A6RbJx<9+>>mt>bqq z_RlvXMvm&(kf}Vd`NjrQU;3)%xMoYA1CFYpq;PKOT7@s{u|IKqL4|w1skx~x8tfJ< z1A>vo)7vNA)l9NTm%?<*hS<6}4qVm(V_T-19eDWe<64_X?`5uk@tEZ{m>ZQ$m*o83 z+F-0u+4;%D)kuEXuDzc$8UCkyJ8jr(bDon+EN;;7=Mz0U9e;i5b}prC*0rrm3nfaN zip|v9ccydd&x$8Zc_FIw#l7tB#g{4hAK1uAyf$pmycdo>M%V~DwdHA8|8Z)5Z64OX zU}AbZ?4{FqA|ga_YE-e}X`T8V8Ou(0;>-g3e;VnrD!^mR0*{f4c-Tq*6>w60K;7<` zofS}3gyBnK9I|>*Y70NcZx}(|5UQg%m}N+>*J9GdjM^k`#O3*7zsw63ZW%JQ;ug;! z7NEj*mD#KpWLbwNX_k@%xxV88pY^??>HEQRpuHLSUY!=7#KSuobH=2gko(}x4XqH* zBg!sPn?L~nk}hEPKr_!N82Lk!>e4`W8=@*`_0U5$w?7)29y(E2QvrRvo1aK5&Yb_k zeGWW|0_GOQ!nV<Xw9yYXR$AUHg-eVhTYzGs!6N9i{PhxQ}CmW;yg@DO|>Rj zqLhaRf+lfltLjinJBCi^C715jNJAJ%W#R0N?dvnv$+qM-hDkIM<1?xetAB* z$UAGR?Vew18`gJ+n0Ty*9c1ekj}A2dh`hpWqJ{Qoyczc*a}nhO(qCkL^2egBgMQ?N zT&>Bn>=!bOJO`_ftz4!*eHqt60H$|C5#%=vGu(#LPgigIbOX_AB8IZptER$`P-=a+dQuc^%klhZL1s=Z#90&Cabj<7e7OJ&iAQmU z2=Rd10|Wu~i96sCN|3w`8zvr>tcCYfH~cw)k(bC$gox+cv?@APwn^ssJqcL^HLSPf zPcN$~J+zf`SioldCDNBVF5yiYpVj_-0Bva4a3bBG_>f{b#6CC19%+0&9j=Y_)u`xg z*5gj{lC1ko!Y)V&aW$%YIHRJRvNfeK4_h!d_>?Mmr&NjB>-#-4c|h%Fiyg<+xoQNb zN3<3!&YzuvtHuK~XXaA
3s5bMk1Wn1pU4+gI#9fy3A9?NXx`Bam@-;Q^iSbF7U zJ$l&hV0vp<0Ub+Yg!6@kwY8SfdyH18p;F0xX4!unt zViacyHFsUQpL~(4vKbVIh9XF+6ci+3u32TU&Ubv`(T%r`dW@w=0b{IKa#~DCj-)BW zHw8e-OB7|_l*w^@a${y?X5U|%Fa2?B{h2e_*sB&z4{XvdSse)9u!p-1*tEzrSJ1EI zfuJ$=63__?IZ|0I&!7=WD2g52U$*bl+?V+gf61%F{_mFVk7n+0M@>O71(>(vu9*tz zw~vkr_gmd>4_o76m_uvPM^?(r<>jFIMbf2?us31V3dpTO$y0d=JBXLgRuRcu9*z*h zVf#rpPzPT})}I79Zu%S&Q0-eHx>?t z>U#=mymP|$?C(3q+S+E??F}9@x@o4emG2d zDk_GuJ9RR+irY?B9{hB0TK{+7?*LjT_0Q%V&-z)Bs5so(&|&90-K2A+%2Uq9n+=^1W-W!RC{( zoKMk_cXqliafm3)nT&E}Ujq}44J8trKY6eJg{JG1yv{>I45I=gqrHQx_%Vzj_rO9= zLCz`97#}bY)N1)v+DDjn7A12e#i3=Rz5n%RYbD>0U0tk7bG*m;tJhuhGV!yK9do*#0i%cXLld1au4y?j2>*kD*@RW{qK2bll^ zCh><{K{~Lfi%LQzU;>8cEL3S6ua#_p6C=rbDi-Tf!b%BYA86KtRa$AvO<t-ywM#HR*cz(BCX*p zkCRR|S1xMA325s^$R0lr)QpLHR_2D7wj^2}$`0QUvL*G$ccCH4pZ4zm>j~E+O=r$1 zsx%WSzkaTq?BUzb8q8iD(fGksul=sF_ff}G2Zv90`zdB0m)|*eTO;d|1vs}B-sENQ zuet^qph?fQrD5ia`}U2e4}P>R^=@&E-+c49+0c!!-|^kA0x}A3ov-=5?(n|bCUt0+ zWhO_TtrG5>gxr4WD=V9Kb}v@i)}MB?|M*(RZwj^`?hM&kDV~~(grZtZX(2QUEr_iN zUpEu^*u50bpp~hr(_Lrt?^XG<%ad$75At#@2Ka5fwh32|Ed!;YSYz0xbrkb_J|#ON z;r_IjI<0kLP?TOoFFqAiyJi;9(J*7q%-ph7m{B!}d&9=-%&^e+4~`7t0*$BkqAa?@ zn>Ri`|Fku=T^Nv7vZ^r2O+PWisL%vUlsh}D77Ya9bNR#iX+`5xuX=8Yd#qopY= zXYGE;u==Saoq2qxpT?<4;Weg%%I2fb5AVg49X2=G*<|GU)@S>?OD?dnBAki&2y8)t zzSQ48%Vd07iw5o;!k%nkNTMW!rKezTSwiT!XBy4+EPLWQ<|iIu`N@0a1VlZtm)Sbgu}2vgjXa zEkD!Vwa`M*QghU(_xFS%o%jy*y(Rxaw8Z6`Lc`QI1*QR%*vtm^5$txIvB+DTBoJL) z2MDd&!+)a12MAveF3uHyQ-}o27cVEFssfe$AFk$KSM}fe?ePvZ7xYbGryn>O__hga z_CgNj1c5d9b-7fV_Tm(6vg(_{Vg^foo(FnNW*5F+!XG%X2kmPIN?s_H7=!=G`ulJY z9Y%dqxc4hiS0%8|u;tT{OY;4S|9<>GhD>FHGt1U_ASZ04AXXX&FY?e0m_uL{6* z4>UE}2M}6db;s1Dkvljm`Z;2$5I3ST&`^G)HpB?mdg$Ku(=p`|jk?>K-h|nYx~rHD$=v_m#N#qVI|goWk+5L@*DShl3>y8Rqc1!rEv9&vLGs#AwEHP zQ!J77Q4W+Me}c>_p86AjbawcW)IVOl2<(}%(y_K7;bFFgd4S5aoV$cdz=jolTwJuQ zTNgAs$V+Skv@BX{gWxlYrv#BUB}@`(_mzX$H|7ouH*gY-F6dB zjq;z7Pw$0w)s>YsiQYM-ci&}f{4vAvT;##?=uWGa9wmwy$e|`@7<#_cj_j2QeEf3C z7Ws9HZX3z#oBY~@b46s73tYjJKkgGA8a#YkH!m(k5VR2D%{P73-0*v6bhHq)0;Wrk zcGrv>w?{pZgIrKC4PF41i5^%g<{_(yQZ#(`OOGxktg;#^7V#h_`|L|S&oLvueLj0m z49aXC*(R&C2vErl=icY|2G%Xs8bAIxS{EnNC^@)bNa8ng-35a{x$fQe7y_DjaQ4BUhKQ} zt&S5V};Z%*F`Ogm8}GujUA?Bt8Ss_^ZApvlH(#?F0_>4&{V zC^zxBq*P2qm#yLH{+fL?Onv(S^K(ybe3%z~P{o~yCX`T>DMZ*qdYED&4(RBK^)>Hn z-Xb%B6AEjqCg(Q=mR{1!Wlr}__yqMkuhC9tX5@#M*2r(aPaiLo=Q7_Hj#o>DEg@(1 z`^ahV+(zd|g#7WIx$mjJhx}G#AV%gSF2auyggtX|no@G!i&?i}><@OEKcZ3FeS*Dh zY;^5n@V~rXX7$+_)!W7 z?j(M+&L!sdCysVH==`r!IoSoh3OVb>-XW?V_mS@_TX__u+-LT=OUl1h&J z><7+=y~ybX@{7G>qY(hP{3bc`=yRuESiSO_H}B1Ik6$|MTCw}ZXRKn-3tV&iFPxzV z(r2f>Gh@QNdvQxT=h}Nvu!Hq_7^-v7%gcf{AVtQHVJBZyvA_|n|X!#ZRI)!g<5b8xqY1PD05FZ0Hc`2h6~!R)=fZ|v-0#9FG#krsdjI`AAT_fiGFwAKRt}mRTjDA z$G1-nj$de09xXPyb=#y$=SGi?(c{oO!%c3V`iq@=lZq2r9%?DC1P_{-nIHBgE?3#n zQC!<*lC`QJ+cm5z(!gX`HOKF_i0Qug#}0}c&1bp?2#>#bOk!jjctk~2NElH;R)3XZ zr~`~jZY%{bssvM_OOIv7w0JvZC@(=UuNjyHc%%$Y4l89se+ZvQQ4=s$lUeRrM|*|+ z=4pXt7H{l2OJ;}EC%BOt@I*_3Xb+gW_aIDb54>fHgMxQXaY9u1=S!e0L0x>a@*B`l zVP{YUnK8vLftNHP<$^gXbwX47JRPk1D7btcFHQ@jspG0l^&yJ&t@Y%~BMUlR@9P>< z-*;_&DRfdhc`7=_JDJ~lyCl5Wchg%)W#tp(#jbm~7U?8Kqv=m&Ubk2Em24~AQE+#% zbg1Q*V$X2lCr3zCX`4_Z(vlDRB%@{ubZhu>ryw!oERLm4h9=ONs2|Uuj$zo+`x#l(>P7^MLm6@w`qWhJy7ac zOw8Mw`nk zD0bM%qMV))fcMLF0t@a-0zjgprL@0gslov33T8w?jY3w7JPLRoQ{0LIk@Z=8cLtw? zVk8rzi{UiyCg>UcnkmKJP-g7&+nDFHui{X|qW3d{!a+Yz(yQFXNZNY9EJbGk7?$*$ z0h@sqG<9Pxq5!X+WT!J~eZjL`vD7D1@)MId)ZK&T>;w>#H&6{`aiH;2=5UE9U=p0F zhcMKQWUW(!3oJF~$TIY59|9R?e`EI4aFdbU@S_p=ApiUFq9xPtme_tTikxMQ5D?{? zVtfch;_ay{&?JC^y;ZmkY_LN#g zWne?_sXI*!v&8MU@{cE?)q9-#${>xx#QfV7!QNw`#5K+)CtLk~YO=~mOn&k@kQzL*;0bwbZ{k7@ z=%DHiR^K)E@ki8kwU<5#Ihj0z#C#KH^6T`Y8&2nY6_lnj*VwK)oPZu(O|LBg4;%;2*3I6op!;MO@4=OM3%Z1 zJBQ=mj2eT={pu}XH8PPG;fU;1TCLSwuJi(B9ULu=mXyfTWS)E`rehJOM5?)qXp!p< zLWMmk{=H_^rodZVA4;q%#uJHrX~@qu(^$9C5%{L#aJA^Diq8+^@`ytDBgpP1jYTH5 zS?k>v=(@C87ggoYj2_Psbg$Jln(`92BL#k80RaMCJ)TehLd^OYmqYd0wodpY&%Q`o zBk#sQmlOT-A=<MegO2Q|@ew|2b=;_uop`?}o^ay1~Ro=hM=a=As^QSTd8 zJU6qmwd~CFtKQhQ-j1iA8tQ5O>C7wb4jMl*wd=j$;DM4e8o^q*hf9o}ls(H~)E4YG zns&2f1w{xcNhakt?KF_rI91#^ydLGE*ep(ZNJa?s0MoT~`K3!GaL+04!g}KB`3R9- z*DnNBubBt0z61{E?aVn;GnC!&)x3nI_hS0Yl0VoTX=?+QSjiOo$I}g(SM!7FMmy@5 zvsOb5=dTw`YoDvGYsdupYaW#?0maj!G56Gccm$tr)tbWU-96ZfcAa`;GZy46D_)B| zWaR`l0x|dTp&9G(5@ck%g-g`_M>EJ4Xgf2q#K*Y=&Wy3tMg9C5_bYAkBRkF481?~QPKOH$$#~bB zhZ#bxHeLd5l||QV%+Dd#P?Irp)`PkOD92t9qjt~eUgoy++@E#VxnbP1Hu&;OE4`4X znX6+c=kW*p;K*}iF1>B)j?CS)w8{49UMqTW-n*~5`}_h2A}VgQaMW{te?iVlVkMvQ zy6yVYt(lhoch7~v!`u%8VXm6miGkJfhi^i0cvu4zb;knu6Zw$3$1peHOFSERKBG`% zeUIU@PcvQ&agV|k0{jm_Xk`oi5??2ca(cMGFi2#PlX52MXnoU-gYHWJZ3o$%;;Qdm z-YZ67+sU4E#^$D75U!o70rhmCSIO4*U9j5#merveDM^Fee;9JFE1a>;t-IC~GHJUT zwYG)GS3PR_^CzB1eiu;ZzS;1Knux7`FuqSVUGL}!wW1^keAebA3kySlKbUJ}BY_}Om zFkgjBOZg+HhB=$@~{WhU*vy`#hZTm-m4Qp~!fx7jr!owPFCXuTTohUinh|CPe zJ$~}%n*1xa`}OtrZVSl!4L_7i08-%dAPSSj0Y9z{hJ3&~0bpkOBt0ItfocYyrmqLh zvzw6JAMLClLQBs`SBqNvufx`km10zeogQzNYTchd5VCy$OpW+l^N-j=kI3Qtww+d~ z76f(B`@t(*qhWtLKzVs($)WSymu@Qi+zhwvj56A5(souBazW$r#$%e6#Z?KLJ1ibg z-k-sgxwoDuHQL2B!ZNvv$)(3@O!T(9)S8{TaO`7$M9|{}Ti_`dxoht=?cX!?71tPb z{NC~9kw?2<$(e$L+(U+nX^DC@%J4O#XU9yj$2WPnK6XprFtg)9{i}aDWGtUf-SL3^ z2*vpyX~_OhbZ7s+_@7c=9|!-S$Y`U}jvBeuizm53#PFD?Z5JW)^2}QIFnrXd-(Dp6 z)zl;sb5fMsSDZXLli0msnV2oYL+Jg`)Eo;c3R%f*?;SlY+YBKUL63;dN>Ef{#Mqb$ zD`{d&bP$k!S{FDPQaEt4J$wV?-D3PoOuFOno~v(+o6JFfIuABe_ACdaF#XK&%P3LJ zH-+sr5F6>FwbA4r$PO0ZlEz-3b>_&nep6tnx&Vc=y2!5pD0nyh8lVL(>faQKf17$Y zhy3F7UT z@?ycbp&+qf5;plzbt@Q;p2l^s;l0K!Y!y3m+42zc*xmc2> zZ6z8LO4IcPKvz|h;sSSzODDg0xi+IoO0vDcfvv2&h-`DZIqNh?mVwrjDP+M;#v1Cr zNy(;?JBYLB7s`+4^BZjq6_UrZ|jbhByJvYO|gwE$F5d<3sq;{is`Db>+cE7!iJwz7FYMs(a2( zVpQ!1@wh1!q{~(GIp;FiNr+Cd+u8~71cVHB;Ob_D7Ns`4BH6xmXVLT!Rjq%!{C;(m zxH$1cmgi9O)* z+{cP6tb!9t0iE`U81VT}W!ssm!lotPoFIXZ*A! z*CNA&ItNicAFoS&rKcl*+Ju6_+juB;0QwE^3ZK&{r}pD&4CyAcfOJ(mO&PipMnQ20 zArH=c3U965W@LD|Qk(!5YpmNOZAA}cCvDf}rbphQ`@}iR?|zq3U-41?pxJ<8 zFE0fG!VF2Fm^U{+2|Bu>T1iRN^&olNJWKdN9YiyjRd8A>O{oaBh+%>X6O#lHpHub3 z85O1EADpdPmmW@7!a#+NtuO6d&~Wz4%wHyYNVYvATRh(< zI!5USQoa+69?HN(m3jvYVUa==eHT?qn$}yM^OxhtGzvi|Lm+2*lpBL;W#bknKuv3H2`^>49*-)B_f6> zEMl3ZPh#M0IPi6ow=Orrq!xb;b?ZZ}d*xHZYZme1JXX=DZ0DV6s>)4g zNFi2AsB0o^{Hb@1b~$>k6Wre}_`SGPs5EyDauGU+T*R}2?YBX1=X#hSwWa97{$iTs z43G*@pDgA5KshWfzS4vNSiQOmI~}b)UkGPiWUr-IM3wWV*9YaTx-r@vF^?jnIl~^> z&2^<$C`_pym~n@2AKdt~>^FF%fT9DBL!Tw>u#`VPGA}vZUk%bSGN;AsVhN-r zz_;VCfp#kf*C$kXU|5UOtO&^5V4y_mwu%}!I-fY&z8KfE5o@~vH`b8;Or|G?>=9+6 zbU)G!IG~gF5j2=FYBIf>k~{Jg+BupkGK{QGru(%bi;|QtS_W}nV0OkiZ zTZ#|Y@PU-9F_aY7Kx; zb&2NPW)r>7aR&ff0J8z%cTZV0(c@=Wl%_f6p!`Brw#46=hZ`Gxg$3qy>@NcYWy-31t?cu1c8@4Zryx*t3jkZ??G#7VN| z->|+ZT=x05O;dx1gvEikj4B7Tp~b@2{|4*)y}{;9tN^F_9dw8)$u`i1AQa>{4q4o# zn#jUorAgLRulae#w#Rw`$%T&XOXtCe$Neui1w6?PcCQAsVc7tP%_6wrFJq z7tZ|%=@1-49(#MA4qT#twAi5Vr!Zqj-;V2BQ@5QGW>~mp?Ct&e!PXth$*Dg)ex&?Z zL17tT8Ahhg=4;bhj_DxE7qg2VFf`uDZ{|o&FhKz1?&{T=0RFfM~e- zao~5%rfJ975Zfl>_w5w{$$O|*{!bwEcR)m;Nht`|ZnYegiq7KF*_DQEsx&uoRuXj= z9Oc~a(Y~a6I|D#G4sCimkXZ_7Z6y~tR}Ad0X?*&c;=%9Mak1=(-dXNfn>jHC++++g zmUwf@JC=7hs{)}})^m0|P8nGZYpxDyQWw+sJ+V|R^3M}t7pgdXx&ll*CN7WDeO^^p zUHy63sgHT^!%*Mo+pZ=KY0Wu*!XLOmJZy+*VUJ=!s-J=jWoZ#+G-+zMGfuQ??#M>5 zygaAKCvXBsNVVITWd9nUI&U_}5;?Sv`_%v|V)LN$mV|Y|b?QEi2a}V&Y)w@#{>+nr zRPp=Fl&4V5sc#uN)XhZgSET~X7(@6PelPSkb>|+bsuoj7$DwpI7)lgued@ffdgY*`JmV(dINjJJR&F!BS7JdD)c(hEy>a zx8!IM?vHy5F(j)2Tsm;tv6KEy;WsYr%~~t4Y(PoxxH7c@#CcH=AkRz>0NNe|WslkS zRIvKBXye}TsM~2HfkU(f4RIm0*`t#j34FcW!2&ZbTfgeqWV3(Pr*IOv+WT z7lNa2;i)fZivYt~cnDf%he7f57sl702xCb|_ww&Pw}ZQcUS9zcWBi{zChnt#L8^G= zKVH{=`)}D*c{=XB2~G@Z=~Au-QbkHNURVN79sc;QPC#**hCtw2%f}CF=!! zG!9t>ChCgk@+!MMaE>Hi{*ZlsWAj$ouh8w{q_QSw;VNVog(mi%8A3E(o6oC!{2>O# zUu-8RlY{p~$d@1Kk7x-e71)P|sopb>wHaRsl2_d;cY0*-P07)pmS5|+CV%jk z?!NrMpxV1sgK2HN_(q}cRf*$(Q+6;~8(eEo((kzDNJMFteFY@Jw({ap+*X=}cmlJh zsX~AqAZI-xdO)spfjn<&;MkSc09ZTo9ih-oAPY_bjb2ZN3R8)jFZsriF)W4fCoDpB zU#S5wS?^~}Q-tQAz64x0 z1M|S_IN=3O%T}87N}oNP$&!tk267CMyXk}S%#(e*lT-WJpS5Kop|*!$&^(t5$L_W? z-Oial|fAw=eSMV%6-_&5HRC*8T*yS%Itr7z@BlCFR0Bb~3 zJA)?>6)R85ovKnPIb*Z^?a)WWpwdI$;r5r^*E;eKO%t_rclD0E2-g1Xr=_2OM=sYe zxca1$6U|h^M>X}&`i$&m^CkC2OaE*2IRiisuHN8UngA^CZ6okgv2-A20L#4gTC6I5 za|87hl}?XX&0$jblsa6ve-~ZNT2jEoeShwK5FwId6d%%uUzH7lEyf1IIGPst`tIWc zjk?9Q%{po6CKDINe*jVB)-cf@C>km%iKX)d#lq)sWHliZB@do@M}R`IFgy57);8u3 zTS4_AEK)kO2$j5s)|w0J$a!B-0Jce!La4Z}{d3ueamCp_l1b?#^Ur|JXJ|T=0(`lx zMGwdqkJ5}zY#t{b#ipGFP`{Ro$|3LGpVuE`DX=3Hazio;woVaGXYsI#aCWGEeA`=t z&>(enOqx+L;qj>PNsGU*!XD8t^oy9;`4bQ-m5e7R`Ns~(sd5T4bp?gDjL}3l9(~8l zN6Vir$TOesYOo|=pm4QO3b_5v4mAtv{gY)6!;K@u%!T)(%u5=6t(_-0eQEQ`sJ&H| zI#hOd?C+)zY!#DvMwh*EU;cdGJ1Wg{Dfn{pZfIEE>6nW_YmMct7e9qwFsS=jpJi57 zc;OIi^ron9sy?f>c=FB1S5K*qPPylkhU-G&PVJAmbgR}iS6j*Q%3RCO&obSq*#oWx z>VzAQ^KD(e#dF}cCj{HlEgb20)J}63;}*ImVX9X~(8;~%!89(dpqarmkA8mDl0ahb zQqXQ_Vm=j7I(+Z`_k_y_B09gPGU;us7e_JSxS1^=ktwO#wL^RuQGoNJQ>!henD5C2`?;F1y{z6G)QjoZWQR9nON)m zt>ugD4}>hP?sEtz0IGLd9_q&%F5-4l#XDJke23dti8f7sJ1UBX-F6Xq&|Q0hLpIG; z{CBYJMb2kE*hBn<*fbWlaeK`CQG&`AaKoz`)iCzcoi@Y5y-9wwcp zkJ~kurvOv>t87di1?^dg<0AqZ=ztN1`uximNRnej^G98uN6HuZ+bC7`Av*y=*$Rnr zt&ea11SbhUkBDWanWRaeMP*`Eg6B31^7@=_+qAM$%rIN#uz0pJT@?$BIZZ?Y=a|>p zC%dN1DBGr&eQw`wgN9n7 z$dFJ(7h4m-`!eYBP4l~4Mr|mbJ&W4yk6^}MzZ48+ItZJda$FqzQ)0Y*&D9FtyzoZ= zDhUIJbpXs@kfF1AY6K6iDnEGB7CeRuU2*L0ZCL40(%~^{yg-99pPrX8CysDO%j4Da z7-Us~D^4-YTRmrD{jZz|u0kF8Cu|)aT}CSrwby5{bob0jBc^)>mDMhtGwtufgsFm zfVr`spT}5xS-pCIiCr)H&1oF^lvJx`=UF$A*-}HfjcJv&(n|+`1ROD}>s`CIV59K? z?lHD6kZd<@6iJ*tcJa=b@VT?gDJFOH)GhI3g2oJTN6R_vPIQSgKYMZz*}FHL98Q6M z*vxn5iKSiGTX%Y@Y!@GgEdoyl@AG}6a`xwb&tILgNmHR@2pb9Niwuf7n7JIWLifNc zq{ActYPUuMFgnEc$(kC(DgKH3p15bUC5LSH7`j5*`%G|ePD!x69Vk6F&E)MOQ0=CO z?u(?0g#mGmjR)FSFrgT5M)Dm-|cqJk}I9hr=FX`#+1x zsZHisU-ILIc;~45W_xRfATGbT)!JnrRt_8JKC>;Xux`B4-)xavV2~X?QF$tdc*4lU zq}x_EpByYb^Vi9nLp2^L;rX@an?0X-Y`M$bXK<&|MdS@A!=8(tDJJpLA+kR%xHoze z;>lPa0iz85#2ZGG@nY$6fYlX6C1O)BVj{LB^@6%AeZ`XdrOObSVz;l12nXbDa)-0j-&Yl00@*)I2HG=ush{9BV|C_nO$%PX6`2vK zNx(WznI}Y8X(R%nNsZY()+cTAL#o3Htr{k+g6>C#aQ~QY(30Qd-FD<&<(?1liiN6z zw5PyhsChcC&dBz>?9P3IGFkcC-TK`>)a$rs_FGN+s4o}U#HAQK%a(=K)N}lTk8G@? z-7A&TdpLF6kn9&7YaWgE7beb`#{4i&o>8`{i>`a6#{uLHjZzk<;&48&Am2`)wu-XV zC5_``*Y4YHS4hj=%2sBem zwiVXFL4)=kqy9XXJClD#u7uO%Fx|Mf+M*aMlaYp9xA|X#J_6i(YkBYqa6KFhW(V+KA$@|>SwFw4GyQ&Y&sz` zLDEgqY9GALa|LmwXp}S|Bt2jKP`}3u|Kv-V`A#2&ri_W|VfRW$&+K5omkE&}C%*uk zd#elNEn##vi~fxX+~Om?OVptP1(2jip#1@EGNOp#<~DK?)t0Bok|bTC5jEBylBw~3kisbK?N{!Q;lKMbj>LTHKU%W#af>lQ1bqm{u z2duE^R~jnXJ~bHgFPCy1X(^M@5!!wJr|QAY-4*74qx0c%M~pD9aiOR`r>(jIuYRgJ z!nmNaa;Yd{#bahk{YF`c^<-h2&(A{uR+3E6Xi=WaGv{pkx>mD|7-E+@U*7)Exjat8-jcYyLc zxo&XNQ9KmS>di~F-340Sc`;nDA!mG*P$9}#-a>b6ol|QYzcj%Ws4_ZVhj>31%;k<` zQDvv$5dUZ6*@yk^2M9n5-vPo6ptfSzGbmUmwwE97g6kz&6bpY5Q4ojd7<@uj;Pqbh z_Wt4T#UqqmoLk(H#+B8zZXbr!z~dKf14qpR_kZjO_8U9fqPk_{Fcgbuu)sP`gwXx< zs2nYnD4VDQqT!AE++&De)uKNumt;hsa9-y=x9MvqUBO8coq4YVI9(l=D*IM-3pt%s znRkQ`hnesq*St%bsig&UGHHU++`clqc@@DxKskY##Cbx*La1cY4OcG>`=-!5&`S&KmLx%NFyFbXv!VN&jD)?=AieNbpHo_=tSkKNQtc zH&R*T*%SP%U7IV*lh;YBHnIq~Gu7`|ng3y3pPSgGt?*Ss`Z)+QP1-x4pS0C_>l)MZ zZi`BaJbMS_(@VES8gp!Q|Nik6rK_g-eku7jRyQv7d|G`P!Cx&hHkQB4rb-iSL=7YbKOz`2zS-p^pVNg>MRbCWLl&)^VMRvu`$cl(DA5m^?YG@2 zxK!aW6XdA*SFu)`S)-lr6%%U1Rg<-Div(dvRx<--+*K{O(@iF! zD0?vI(77ShbciE}Ev>|BqPyQDLT3%_1gL(>^!&hQ@vc#t4#69>%j|l%`tZspBgJJc z=?D|hDFw4vCGx#@!R{8|9$~`cTrG?Zj;$pyWje`A#X^Zr(R0KH8tNyj&?C37SzF;% zo_scL01W8SFHiSOw&yK!@2>9L#O> zIr|nlEZ26kdd8mtVUWExo6>}KFs7RN2TX?!;ua~bt1EikVjQzm7%s3(dnDC=dx<4q zG!+O$rCavg(f=!G6Zh84nY?R3J)+@=OC53oa`=UR`eWCN7btn;1UPMXAa)^EAzA@t zN=xoMqUykvAEW~XU__D&l%?sMot>MQ6P(v*u2h<}&ZGCs;(bugTziDT*{|3otzUf` zrH*hdV0;Yi(%}E}olIP|ogN3s`+*^K0H=VNY$_?{UkAw4;wahyDb z{sq(iZSWd^G{}BcO+2ZkZlMxCm`lDGnNx%Vd<@y?(|*>e2B==U#P6T5Gvlnn!ZG5^&v`B=7t-^AF^Lg>E2H=%1lL8AMQ+xfKN} zzxq}U_M}fFHJb3nPmrg}j=J~v{lMx@D=|}EuqB+0( z8EutH9&)?9>&5ucc)r6*sa6HYGTs|sa_vAx(U^DfxIl+7bLCkoMd_%QU!`?hlV?@E z^R%_G|F5+s;TBQCjInIbG5_$f0xE=Ki@(h)T!S1C)c|1vIHnlpx4U(SG(}T|-KtBEI^BAc67p zhv$dF8v#C#Sx06;64eHt#K=CZH0cdl8N)88w7kRCkfv~ur@YpC@rah`1e#xw`Uz|& z+hd$XSWg(sNjx(5;V+cq$a-fW-a{bDr372wXeI>ok+!|nYs=}{7y9d02}k8+t?7Tk zN%`)}mah`WK7Ezw0&Tt3`KjggZ~eVDMDWcn%;uN%f1kDbN1K4W8v~%`hZqV|Rs1^b zM=)9C0Bhoy>;LlozoD4lhPwWvzwlqbCY!P#Cot0`bq2_Y;%;SZo(AU*J~LADxUd(l z1n4JAy{UXtgBTCTML@^d;@TP~>vK;Du^c?SDzt~MHvn+!2m7%%yU~3kzOW3BoVB|4 z+T%o_|3o=$#K>GjQe1$xof0}zS3X@qi(g}=bJ+KoGIV{~?^ly0FI)6RloC>stBiY( zptjL}SYMr)7jB9g#HOT|>Vy19_Ueo(o6<|4QR49_e0^Zz4fa00ergUz!S*oG`I`$F zilEfvf9ie&Z}k_tWD7MJwUdcW@5t@sE?l0Z9Uv42y6hkBee8I?Iw)va=@%l295KvH z9R_B^TvQ5Q02(M9&A(2U(sWg&>oQ;w8NZ-6LViNdq z-EgEh0ddTum%H#$Rb|gGBUO1nZFt|UT)Y;py3tZ}wdB%y-TJzry&F|k!W$z2K#9~v z_N=|RX!+FDNS(~>!FRZhn)`E3pMbx_f*t>jpdX}H9kW8vzepbLp+iI3_Ra8zI4JJV zDUAUS(pS;I=hm!CDXUMLaTam9C5Cj~-;4Er zDLqGD8^q0jkm_OOt!|=7;F`7QW!f4N&%$68Zha3&fuK+9VK}q5~8uY8aC;c!e2>21J20V2|6{LfNVGcFPdJ9MHs)#MaV~~g&2|`E zP0`u|cwuLO5}9(B4MgXix(R(6Xs7QID-#Na21znw;vWzTfB`|`JD7$TaupsDhI^-m zZxC&`f!_}YL2Hv$v+i+vC1q`F4Cq+E`XLO=%rG0XGJ;+iz77p?DgDEK9(B{#YOjD(R72nUJImtrVp&srW6%lkY)M!g5}CXJX-d}6?l^hsL-@%;%{JQjhsl`4AGfSo{PS`9Pk(=-hJ~|c zV}*gDbfOKLx?4PsQ8uQjhSC+fyv0(`j~@v>gF)yHfnJu9WA~KCk7F%V2{^dl|7oC{ zo@;lvoN@M9HU4|=V13UJ(knWM=zx%a6^`u_9|Yd3#|UViZZSJ2NN&r(WT{rM+Zf`= z?!NHX@r)~1ta0pBg~bhYR@Y&RhXt{NE>?e~o!5HABvllkf8K~$>iMz{JZxivkesd^ zf^zZp3GP>kG?er@J!kINTMYY=BbfvEf_nv)Nkok%!m20sCRyo@v5bqrp-(e}F()%( zqU#W6|2e(25rfUy7ZZmJt`m#h6Lff*V9SFmS`GW3tzW`6GVAcGjL~L z``sml(1)lv*g-4ynlX$xW?knbyTa7yFiGH9B4d0N^nPjZJC>X-ifm8M;ME@{Kzf5oN$^q~AltOD=J zYlt2Zl>umTc!A5N%Zp`*F63KZB~-2P(9i(D2VsJ%Hv2W>#2PT~93&E#aHxB#ZXZ7o z%jbyvxl``D()`F#wC{CKFMpB5KtD#p$1r+g%cnJD&Zy{-)FRmKS5Z#lsrq5D5nSCL zv<>zXpadCRD{RiBvcFFV7#Cvl9B}nh9#ORF}dBlp`;_y|%H2=bwc>8Ve z{(hbJPeBgdbA2_kL4~W*-F%mu$Cn+M^Eb-88gbDFnz1X~m6nq9edW2-trtB3gzdpV zK{2<-$vxe;776JU3BY7?-1K$+MeP^sX|YoJA1}U26r_KTnLE2XGX(h&z&pu>uM(B< zKwVfKF~hV{zDihSX8xMvAEW;U$=84Lpkd`pxLx#zkVcBJqu6-q&%YATVj13r7B#lllT2P?8oFC78F8o-L z=zu7qgv+9K;93UMnl1?B`Bbo;5W(k_eT}eGf6$&$Ye_t>9;E>%()1RKCi83OfGKQ9 ziddTv!mu@bqP44|dF)ZwByBjR4DZla&{lMDIk9Z{Fm+iJFIBVK&0gZ4um6d_A^w@p z&ZK-KMP!E-37`LwzG1jwL!Flq%lLei&|3$`3m6zr>7c$bF}(T1 zS*f)Ika0@^m4p97x&{@TGP?}(@h5t_yr@WuZ~RrFE_k5zqtt&F7x){BI{u%|>7VEJ zfAY8TzmP-l`GQC);iDA?$$gosxUfNp#L?(a54-%bI@8~hid-M@qO|4z#LM}^_r z5Bv)~1R%r!t`S&y|93*m^7=pSO&pk}{F3xl;@!!w5?AoQFOS*o2eyeQF&DKN929~} z5>SwP_wz)r$V)1FMINbudG7-L_cOq}xa$iBAY{nwM-=EB3jB&5D>|OOMLeWsA))}6 z+EbAk^oWYYZDZLjCw-%swoQhd~=2a_gHf8RgnWI1XAnb&mLF>7F=n$tJ zW%_) zSvS$>Bf5Gt3N`?WFUhQC>>y>$sX~|)DvXHb@m8++oV=8F)b40pPLK`!jA&oA zkj982obv+_c#=}N#QdDGK5ea!uUz_A;UDf!?gV|9DI0Ul#ibL@sc}s^OVnfLjqGy#33X7_W8awzK^l_o zyPFB^$+_yjzx}k}8irN1I{c7ZebA{QZ2S*#*+J?cs0@Z6%BAG#P2l|ea`TvYnDS&5 z>>m+yW{B{~Sj_NlqCOxGQ}!;<<+_Yx*m*mdqRz7dP2iUJI{1X(QgTF8b$=}oUKPg6 z{aL&%qYSS;n9<6TozBq;3~2#+%~$DwI+qI%3AEW$d~nggUf#Va|Bj&A(4S&TZ0otr#c0AU+Nl^hsTN%9peZ^Ia?JY>PQ@)_&UxtHP9CbDdvrOLREV zKJldT963V8`V5& zy>om|`rl7gED4X+^s&khzWE?A$6z`$d{4fm&v#b&HSYDN9vr^sPPMyUQ)Z=@a+NUl z;3A;Oz$XOr>`1;8M}+OCONdQ@K@E!&s2bP-wKa1ilTixR`viuJ4Qxh3!-}~E3(VmS zCzaQQ8H&9-vHayei0`MwvzTUwo?%!=m49h7 z6wwj=+De|$W2g1X=g{{cSkPcqO|mtD>B4k@RE~8p2k5yTS6XIDrPF?35-xLlH$g>8 zYtJ{f;LA66IcT{YD~;vX-VB$!?zFavZ(fIzkHN3WBK{4yzfyBRg0?G-_g5?6mYg5A zHpC>60AEX33}JJv@ij0QDlPaG4j3rsXtwvdpaaLE6I}=0#@1setg#J*>pensuc7eI zb4QZwQd)1UMeRMF9ao_DT~ej_df3Gl1M4B>{GUp!gJKGPGL9=if<<#j(%xZ0IV_c;q=CFg$p61?6e&N(ZO6)ERhB$@$NSzEsJD_APi2a2ft@o2a%aK%mN)6_R!sBi3u+Rj`K!c$A!`S$ zKzniR?!CK@OQMjtH6Cc+y$?4kiN>B~LV`^mNd(x@0)$^v6J> zy8vHR)frnw@bDE}y??11%+ZgsAa6Wo8aQA9aRm?YG~lV{i4}WE&?C%|Y5n2m_cMbPSmpsx~t>v+57DM*>h9PK^U5bc6>9ZgGX>WWCLf}^WvOBJyF>Sr zA3u9pr#m`$Hq}z&R?WN%`_Q<+_ksOsCpD+&OE+`R`^OwU64aY-WUKY_)mH!8DG5G+ zj5gLgSu7$yq8EXV4UDD?vS#K%32#d)8rDZ6isd}jmBYYxiU9h?n1|xGSfGMW(O9Ey zCnWi1GMAm6z=dqS5{Ggt1}Bj`RzaIs&(Y|M&(G^Kv_S(hSu{lg|1p%=r%q;qQWgd_ zl4oFGo%Sru#TuGEV$~_2fHsP=8{Dj#A>O~BWpZcA3y!ED9vLM{)%Y)|OWj;SP+Wam zJr>-yI(WnXQtExx4C_4+RV^|#b4&cnmkF^|%GdTz-h2F(5IV8({))G%ype^TOm?-0 z>doxx%$|fZnncgwoJzIobw_fGrw|MkOMjK{eKcRPcT6(K{DW)5)WqZ6pPZWO)A~5` zq7r|%6ziCS7Y?my63wd33djh^=ZM9HM6krav2P|SQEHixyFwioE> zldIoP`{LkvZgmdO%J|$fX~}GS<+li&Idla#sw+a=%0Gz+j9H2p=f&bT>&+cs{i zbrSm2t6t4$!~q|ZO-&#F(U|wxv)xaM{aTuH4b#(v$)(4^T880bNR^|9DEV;7;I@Kk z(#NDp%#L6ItJmdt0DOpah`?Zb;H^?V=dM-0T@Mv&!%D6b*3K35v~^#8t7%9`_=Sl( zs$8rjNn7htj`3iw+i+o?y<|iViW(X@4L$=nU&c}|4IYGvrRw7v`@W! zaQfL))FP)xH^g{FV>2OpV(W66{Ill9bF zG#$@(SK4o<&lsC}jKy?Yx`I~dsX?ES^#lzUtm8z>P-lY3(qk-=&+BX+k6vDO)M@Q* z?rtqGEOzb;yVU-?z2I2cZC*@pJRQIqb_E&oq5>|>0eT2|MxN{Pc?B?JiFR;+z@vXo z8V-oz4DWzHPT5U~Rp6boFY2j7b@0`xbC9t5l0e4$tu>~qjZ#4f@exS<(%3rmEMgP{ z4J#uL=U6b@s{lTq1Liq|H%7(ENVup0e4j_xqK6Dm&vFN|8=`GAguhJK9)o@QwaiC| z$EfzPiJY9se){(fw(5%;)k%nb-2&8K=fb9_z^uP@%xcw#JOsuqU1PxjOLgqgvQ+k_ zj@N!u&uSIix78wa1qCM8duXKf8(AN^<`oqk+7)3RAe(PxuIJS1=uHTJQtoP6sQCKv z_+!|QTh-ltrF_Q&&*~%wui4Gl3O>y>xUgtfs$QkTW^>ZRV`_T4^iSnY>f5!TBp$$kG=kqPm=$JEVnD9M ztOgZO0a=J{c?w4)2s9jF`gK7ugQ7`N3^zPq?;H9N_Ff(gQh}tByL9k8nV}r_H;JYU zb%%7KJ@;5N+-l?FHqz2<=!*aHrRKxY4LJO_zmFsz^e8yL^U5i0x#SYR4?(XlyD{AQ zCmECd&QeylD+A9jj(=#XPRnbdI#sGgV-NM(RfHKdHGFWE3={?BU-1kZ`Td;tw#G0{ z?PQ8Hq4J<-xj~bGr`}$N&a}dITm?>+W(5YbCiJrR!Ipw74z9lzsQ6_9)lu6lPGKK< z2jL@j3*0tR=}eV@doeQM8}&dg!Ta^r<)}w^yCIL1vU`roH%g4rd!@bVb_Vj|`RrS0=TgdUI9njq*Lz^NM1bP4r6la7sqES@BPHD`@)f=RQw zG;Wnzd1FrUtzx7c9KSEhxMZy1O*1mgrou-}h54`u6gixExRo@3neM@y%^$@K%yw&^ z#7A(RFm+69xDccun98s|{;(o8NFTV@(`4{kJBmF7SF0-)?RZ}8kWX5dLu2@@ZKI7{ zUNr=?+r-7EW6^$r;p_QE2mEu4D7vqs@|#;9`&!$ol;Ur`m`w41R`gr?`GxaQY7OUV zFR^^$-X*xyGF_kVQ=& zILkPnSY>t)=@_+BZ^*&&O1U zsirfZwU4hhL|jVcy`(>H&$)`C(g4^@726=EAnE`)kJUVdDEjay{9~KSM3sq{W#a%L zrRy7#*8>Ii!qZUF@5BHueLK;J27B<(3P6Q&!@i`8*+ng1Vy0GCh8@=twB2WSF08IC z%SHK_=;j_~bEI6WR1ID>KYt*|ho=42q`#!%3b6{Z-Ui+NN6(p?7hQ-VSeu1=Kd6E} zCSURzX*@S1Q{>i|R-1WEc&~2WZ!(~u!B`#6gg1O;8JegQ`x@5K&h6f%gRhj5dHScK?yfSp2e}LGeVnCJtECue)n z6PjK)kTRt16~8cDu-$@9qU`~(HIZ;!`1rBF%s(Vum3xk*V0}`sc@FkU06koJZk)BT z-YqU9yf4XbqR1yTuJ=s_;WLQ#Hd(q9)M5nLl4K()6HAcZV4!RAePm_8-``7=QMtQDz2~gG6P>K7srIO%|#u zu7{QSX?j_KNe*y?K$}zgTxZQHEscNv*O&Eke-mCCR-f6yJf02?DHjEjJ$4nUnF{0* zqr_vtfa6;$gPcJ+1fwABKsX<=w1>4JG7tE3na$}zRfW`WtJm%Q2|%_bQIK29HQt6$ z(|BXX#C?qf%d@S!mQms3O>*yVKSB85VzP)AMY;5_WboxFxCzB1b1rvpnht@OYCdXx zSfB?al)zRKU=IzaSgT6r^-#3}@sF^1pbdS!N{`WvJKZ{NHe1~71o&6F^%W;-{C@we z@-j}w@tt@|y$}IBEd~X0K!c0Zy9zLU7c|UA>x&9W#)QoQt60QRi@F&C4>NARC)lEQ z_9U28b6HuK-G~PVE$cd|^7h1e$;sk}j&(1iqV`I;bw9szR^D}j9*%`gA{nTegp;Dy zbxIlra+POBa_lKRR5^q!Xn!%BWQ!72k#-Ohy5Phb!l}VQ+XG`@qe&ZJDbUObC_h1L zTG#RG*JHL7k}K>smKQ%ULnl9Xv~1kCOWshSYuZ`^7UL7v$_K%wc*jeD6x;G6i!=D=hgJwuhW3({?W=p4=Tz6lM!2(p6XA{&s?qePdyo%l)84{%lmO#ZRh zby7C6aag$p{blhwxT+-4En7Kqj~Pjg5ZQ}qDeQ^N16?qk`IVc{XU;q$ZzhS2&Fex7 zp7J95Fxl~WJ@2&{S{iUjBq-^(e$KLX3$6(Vpk7YNztC@lf2HN%)Y)S1(1KTl`d$RH z`6D4qy!{w>kOAJgMIgPVD@2MlLdk8Y{)9knb~|G;GAHQB6R5o>FPWt%)+glo!+F4G zn|Mty-u`$|h?2%!UXI+mRX`P5RO$?;ubRC#WRUiBI7fuVyf@pjc2_7R0@b`Qc}?*+ zy^X%IZ!^FFO6#YneU%8V$KA9=j{)P~?GP&ik!NEw$vZKj^0N53AE0660BWbT7nhX| zh|2WzpuJ@Ia{?xs?=Z3ULR=*m%umMg@lrEU#tuRa4`_LIoQuU=cZlcMLkw-LE)8-_f2mm(P%riNSX|q07Ide_| zeHW8Xh9VqN$X6nnUnRKaX?##sl{^NcH(iKs0LM|2fpfX#ipHir>8*Y_P~_>-;Dikd z$yrTAIkp^Wj6pjhh#iRBGwoQ_I?p#0<@1*FcEKShIw4^z&qm7o$=F~f{V_QMB>{xr zeFY8-V5;c{2e47;Bpui?wh51sZKY@t5}XAFh|R?MbW7g^;>;eHJ$3CU*z5ydxrz07 zl@67BE=s|;L2-H^t9Hq$E)=b)#xh1)^xU^`+y z)e@+Io1z&ZQSehLDjnA{#WxphZ(Qw&Spf@=-FL1)G4|KNBm=0%;)XTS%jR^EdkL5z%_$irElou*uje z5|Vu2DBi-VnTx4+VsK9fEr%1S;2R!qbR9QT?Qo;0*!Aj_rVCvyW2^h0HMdJ3xnp#m z3n-(5BEi8`L}}0ynB67_4ZzB!h+qH1moAJlJ@xxn345shrtR?n!~_(Jb?MU4P5Z~p z==|*s=9F#BMRDRlnQk!$pfUe=dm&YkVmjZM=nUAox0)8|kR1lz@AC_F)r7Pa*(1s# zUu4ayilvQf7p6?@<>)D9=v#AAoe;K{@BZ=4>81v>xy_b7^~5LI#_wyUXlBSUfkeht zYt*&3mSSnv=0)Nu@Ub+aHtYp4L1_Gz?5K6RFw~9|xSbKPoo{oV|8KZe^ zcU2KZ!06ZkIHB%9zD0h=%sViRI6C;zT{cHFq*lT`&2wypy<2sR(Ksu{yZD-@G^cG{Xt{k z^F;Vu*5Bj)@_qUYT5g(*Fp_^+`@H-!twNv9hXqbj}hx=fq#2zg<2vX2w@_ z8Cy%#>K%3(jV{)3F?Aj@TMoU_B0BE+_}1|NLm7?Z(YbS@Z{9EuHu4)E<{tg&mRaTj zPkXl;^atdbDY49~pq|%0NKVFVGpEA)r;yz&j_zH716MVrOQx3SE}-|Y6n$ZB&T9%F zOat=Wpq4ousFs$^gw4rAEwRRpglhLfkCZjhwxP4#?U^6B+Z&oN<(X^4=l&IJLv8}K z=PyA1`3LgnPyaTxI&Vwe>dDEn?v~+g6zGAs`$);xK9>*s=MiU=2fxg;w)x>OA zX$ky$BXR;?9X4ckqjZn6H>f8(ZdndE7~PqOSrOEUav&9X6@MZM(#*@@$8=&87dH-% zMH(Z{=H0{TccxsrJDW#l1E05b&{F4p3hmp+s>ZLX2>I1rcfu|`FZGG@-7*#Wzd^#{ z|3TOR*DsG~<%5xIQ}GVK5DBs2vP37>GR0H>7!3Xgt%v$|6l_RGcv9j~(IzOZtUCX5 zGPo1;J`a)kXjU^$yw>q=v&RzDH#u8`SzyUsB&x#vSg;vwK;Pg(2v!z}hxxq7bgdr* z4l4|HLZF_v0F{Bn@u)kNC`w>*gQbM%a1t?|Pu`kS)d_ZkX9eERV+6ZHhq)0Jp?b!N zqS_0EV0_u*Rkp@fbz;;@i-}r3fiu z3@k&ggMHY8sa;V}-f2Nl7P~u=JJ%VUSO=T3=NWw9`XcU3;G)yjh4(|q=p-?Ukl;4i zx?Ey5H(2?mP03llg4}a-4F~IXB8LQIz_Vl*ktAVFHju#57x=v;>70NPIPHi*qCZBH zpsUYMOQY$*N*>T^GIEJSR)yoo{O+7yLu^uERV|i7s@2Y1=5sVu&T-M0Sp<|D9KV8##wB-3k?ETRS8? z4E#NJ(9BcIa+<+tN%j@$db6?{}Nufog<-)QYk| zNqCbylh8wH$xAjL&Ythr(uOQ3kL)W5Z>D;I@Z@|Fw}`9^OZCu|BN5XusCYnZ1HHS^ zo)!RMldYo~;p#I9ns$b-b@yCBE6N9mYRFfHXOe_ys33=jWnSl@QfNTQPj6!2CP%~A z%{(HqpJp*-H+Z1P@+P20*;wKPQ@Q2=gv#86ZcJ~`-pQ*gjTO$t&&qBlr+w)aH6hv@ zaxw}Ek6n4qqo+)&SYfhlw~B|!kPC*1We=ve)M%%9jL>Y}BhDg<^-sjuN3J!mBe;2L zaP^>;!Diyd7W3a}M<%y+(xwh#t|I6WfZzZ(HYtZ5ZsWi+?g&?(2Ynj7ycdBdh1R33 z69M&PIpjE;!D~!#g*SQRtuyCL{r&NU6ou$ntR$S)A0N>!Y$>wuBWXMvzw7*)@0ss5 zZf+Wh=g3bi`BkOHS;BC5;vr%;iEN3Ah~QDT&V`DNAgR7#$>Is`$Ik`0UV26l3IeQ} zor}y|ZqeoXdf0#X%y?xcS3{#Sqp?X5-Lwn=p%BS(Y6^zkf`RPiHHimq0Z}oZzRuNI zB@VfNGe_}oWCD*qgJR2#Rjujfglv=`PHz;Os1;1(UuxS@&WuKcA(QuO22_y2{ zNJ)9waLZWr{Ny6#vb4S@z@WM<$-VgZ7H4uZKflt|*+F!3E$@5aY|!PrNhI!vi|Y`} zLY`%B<8cm^?*iElGrWr^H%XXfEjdRX>?OD$M3y#UhLgny;qPBvg4hCC^&6ck&)I(z zpSUyR`iw@OXd+R7y!$m=9!Aa_mzp66;aT?ht{3z7|G7GdlRz%vp-t{NmH5l$lAx8&Qeem1mDXvt)n#g2`HA4B9IJ$6AenB!FYn6x2w3m|g5bU=a*qb!YzM&YJgO$q zX`)N7i8O1)%CwFYsP;4GbBVFkFqAwUk7XKc*PZGLJfsd@SARR4@#wV_}1Ju(OpCe(gQh6 z3^|o{pjAM=1uWYVxK$|vjehU=!duZ_B^1LtyvyqsfIEE@w81-vSG(V;q4DfW9t3$O(1KQgFY!?6WSRB69mt8XE(P{%C~{t~ z#bbns=JL0(`u!K7S$s3jFU4++dBgaZdd<4ti(4!9? zv%Ph7CaI65f;iR@c>Qz;6z8u{Q)y}fhZjIf>qkLQ(ou|lQ3@LD1EyVVdHfy>vRk{~R&t~ZUr(B{QU!8{ zX(yjTlzvf&LsHPQr%)QGl0Ss0E}{}+u}moKz;i2`!rtdJ)!`}}L2)|NGM0Zm#-iqL zn79S%?%uB+F%l+M4xR!!>WkpD4?!{)fxO|CA?wm%~NxTA(RH z;!ZAIamkx$nMO1%4=>#}wVp7kA5t0GVSI0+puRS+qy9*M@AND-=D6gA<{$si*H%VW z0koG2BKIinFABXYyeoL$Yj1SBu@CcQfUL z*8Yqr7I4Fwz#_PHRm|WUeSB%wfVqrHN>_J0*ZKG~@K;|Ate*P-)=YBd!N`4xK_4QWB04W&3iCh5l$xo#EhN$T@bx1yw2jQ~s zdO@+-*76Mt>Y{=Bkqt8^W6BA`_9N)(r{mGH_ntkTFuKq=c2&!8$i73hO?7e-!6C^(KE%E8K1!(YAJo3$nn@m&zU+3aM0fiDFptG^PaVk1w{-29V=G|jK zAAkPyeCm*?WmbR3?Ml&o%K0(_&-Z=}uE&3Pb4X41QKibA0p6h|FD23MarO9>6_Myr#$#4`kx_uUG%7!TGTrH7d~9sW=f>Pq1{Ae!&X z0C4~ua3q52Xb$5e`j1G{ELmz-Lj8$~aP`N~UulWZC+=KIFf}ks?P37lFz~wfSVJE3 za%-*-NKB2j%*GXMe4N}-gM@%ju>#}afUUSX(`c(n1gCmyX@RVcxlGMqqHlGD0gbFp z8ANjwvE&-dI6ogHhOxWJfD@f$s~zg!;vao&W--3Cajv*ApeHu`kzsk%C2MF%WPl{V zGEkPV6vpsazfd$0g-W75>>@C=D`d z2;@F19WkW|PB`~?Y`t5_?eWAFV-pUteGEYCDatepJIaA1L=Au?_Y{$6pIU8wM!#Gw zD!0Z+^_<7ogpcB5Z8dE*p2FuNqRrx{RlmJ^*I(1mb#5iMo?HGH&bO$wyzE?b{@^9b z%x@3uUW}JhtasJ+B8t9jr#Tz$%mu4 zAFLi1wveVNko}%-b<|XP^QlINE^HM5X8Y!51gQtJou~oLb1-}z_IsPIU^c8BMCi=6Pu{o6%79>4^N+B4OOYd+l`QZx)`V4SaMcr3{(oQZ8 za*BlYwQhsf8mmPL5Oz9Z2Qc!a5scJ3}}D(!$xWIaG5*fNvIe=j~bm zdhfDVC2VS%C(PdCdLI8CVeU+!(y!uRZF&!OHjU@Qpfe4Yg*Qb_;?qqSWL9qU1HQ9k2!e|diC#NxekO$Qrsr2(HRV zDoqfQqlg?qh)5NLh=?>HDj-2ZqO?#1UPVAbh=@ohlmLp52%#KBr1z47jV3z+aZAdX z$9H}+_jlhpzdN&L-MMSlUE@D$DA_yvdA`rLe9E3j-#C#z*0SclM^Cspa(zUSeL}KG zNizb{{AC%P|o1V`#FIG^=6JV~oERw7N=N@sFeiu_*G((1LyNQWNCD_9U zn&sSlY<`ft!1{ELVn<;HAeEp5s^+Wk`a!CSo`wGJ^ptj*I&^M&EcYlDv zLv=6onL*hB-*`KDa)hRg^K%%~S;L4eobB0flGW5Vyvl#0)77I2`WmqzsLQ41e!wRd zMU`4I;YW(bkvm}L3ipK>30Rl!KJKh)%?<0!DY6;e@@T{E+KOxvv}YpB zy~D9I<{jluPqo{243$xKxZRr@(mZ#NeZS*k&AC>WqV48om-2l(-KMr`NmMHnMj;wi(ahsbUCa&tD?OpoA7XUfPvwFBu z;0&&_Quqj|O<<_12=4$Y5a>uVsf`SMNxRSrH-RlYIy)|~;zGCqjP!y?lM3Ia?>4MN zS<)K0H!~tQo{p!hTN-MT2R^rj_OK;y0od`^f{K83a(d>KF`b6&ZL_BHy|Rq+wD5E1 zZ!q2nvAXY99Le4_xtU zcTIux?Ci}{PWRFg6=ElSCvxi-%=U^9Eb!D)aqTq5$l?!kQvO^$Pz+osc=NrlC&ca= zDSgoy5l>4AE|rPcZ1r6PKoYHIn>R+43qV~I>r7J-xYoYNbwMZPZ|ZN3d>(jc($nsG zl|PSIQ!}zU?LPC}uD;>L=`|09iZ!<@1HmV&Tiy{bHD20>27QeV%Ja7*?jEik+>S?}N?M_G)4gZ7{VPcpKnw`sIqqC#!%ew^j{pJ|H6yAQA05k^% z%_S;v9=6bEqPcbXJaWs>XDUqcqX6PgQ&lC<=7YXl$?;~CM)2ylYAyr@85bPRAFdFd zNjH6SL+g>}o=Y`uZ+FAF((QZRId&J3vJ~_5l;0guekFAR=)VPyQcPU{L@M;|TwkG! zp*JZLQ~vhphfY?gc^BWM#b)=R;j(L|d>C#vhTO8|!){ZfM_ymSm1X+s7#;P79=NDB zNbFcRRe!pzL8IP1Y&cvVnD3YoAy9e?3T9`;gqi3&i1nJ)98roks6QrGcyjP*umKc= zx%kt3T|o#}z8x!pM{%ta&DA*;mTs_XIqY|md)H(}a%5?3>4>(4Wd}`~+nJ*B?0Z&! z7a;?_#R&*bDQf1`Wc`Du!3v^5AoeP9ZmtBWo~DBHzrqbs5kBxlFDXx4?ccy zG3SO|v|)WwatvI?3RN~g7IntnGo5kHZZ)Mm@mZ0s)1{9ZIVosJO|(NAnt3#6jFX`_ef!?U6CxfDBGFKBfoHN3vSqRK(11jHa?$}Fx)zqN(&}U0~ zzu)D%QeRL@U}Q$HGAN%ahrnZ78Tvv`xQ) z*s4xs6&c_)Kv>)eA^Ee|)@2}I5_U6Z2L&usvAL$y4{`(WG6NW>%4OUzvh`4LJe>;c zqGIt5Y$37jJGRVvc1gZHhj=%b@I_b+t}BhX33c@+rB2u8`z&P%14(rOLI7hF_JjsG z5sRO>7#lcZHnLg4A}ii661ej@WVbf;v`o%4lEPk1=H~4hWUN?x*wZ`KFs@DXmV(tZ z7aRFvpa(=>QhSOU1;_i60^b(qTjw%zy1JMX85}2+3}K%e95Sz~sx&Qc^{N_-&l3#4 zoa5<=*~HAc4~Gf;I)c1I)^#a~!Of`9>(AcITN`%=Hh*8hJ-T#g&pSU^4y0glq1M@t z)OjT`!Hnkk(082FP%O+et7?!^$r-o+m3$d)b86#gz0E&(!&;}apYZ`=uuC1@U5kv^ zc2WBim0LzOme*zY4%J=QYO<~L=qIOA$HMeh-*Am$tsq%tXGPd;r4Iulr=3u(y$Qqw z8Ew&yQ~l#9vCg<2Cd%o+EEL46$?(>Ke$x67u`)K$o z_i&b#oP6x*A*wLsQxGrd$^dpT{G=NCRYT}^`l2n^ZQp@K{nj}q6$ndaw?mNXI5N)S zj(lz(z-hb2S5*}Ef8<^ry@h;K;uVm;YZ!0hEtiRXeuHu3HRq5*mV;MwH8oVZ?#_e5 zy<(2?iVEoxmoeiEN}Qw z+0*%*{9A?Ml(k704QiZt(B2I5DSnYIQu(>>2@`SF~ zAH&R60-}G_Lt8}+xRP0Rhz{kQtb=1a6_=(GScFjcyGXK9n7oEa1_%{({3iG_KakMT zK$a^u$w$Efd`-cPK_C=zWQ2lo7^*k*(%s|+p_1z*R1B8PI*Wv4_8&%ttX|31YYnQq zps#m|IdN$uU^Mb}<$6}W( zgDviPs9WWvkwc+8*#XK4$>>b*fdlD^nCtTh$d){A4YVk+G?Pg|lr1HNW&o%9a`@;i z7jRoA?PA;AUE_flrlj=~^ek58DUDnT2a3LN=9w<_X&$Mk+@1}3H^p#1Ew9emlv17KI2`6^-(EUBm!@Skv{!A(93+ZD@ov85lGPG+Sdb()djD;BLm%M8k>jK z(gD1}WheyM0o+1%D&52S`UC~R(bxId zeeeSp(Rm)Pk-aOj&wrVF|Fdn1I~g6@CjG+%D?J&WS8(E-5)(R$}8ilR`QJ| z3GE1Z+SVX1nSLY0T^FFp1@r=lW40IIz3iaLU+Y!GfIQhxks5VW|XOT|kO zYI%xM@CiEm+zj5e1vc77NhDGU13%l1;_-1GSFwxtewjO_Pp6)_8%$aOO`OejT)!n< z6M+n>9Rax@CS-UoghhaijoKsF1%vc#%P(9M12fM1-UT4hQ0xV$t2I55Ow~=zBy~)0 zosn3=&LY~6wfM9}WX;HNLta%{1+&Ov=kX|r5+^V%o`U_HN6gP`6zbqiSj1K=$t-07 zAw}4U*M~GH`}Ko0N?FVeICR_k-K=ju@9r#9?KC<+vhk8!!y=_kHy*oC(g<>bBXHhU zau8RWJ$eVZcQRnt6uQU*fWDCk?q$Wx-%l$?I0kTo)(HhF4;l|Mwp}=VwZ{C-?jW@z zPwxo+0rEi=kPmtt8~W)lMWx3yn}D0T3>VFkC&W1Nt*Kbv)ClsZe%ni+VCtnN%qRM* zY~_6K0wUEa5TC)^Rc{6R6$+Gxn@_U=&T~IkKDJq!trF@EX_D#>2qIL3o#n_3cOn86 zhyshaOjtr^J@-@U-Tv7ky#s1ugkYx>yOQ4V*9Ij*2N^*OazpBUq1QK%3j(qtPCvuto)X@F0whvi z8AG}~!2YBju?zNa1$A>NbPtaX;-Izzp(e1QcFs>Ak_?yjEuR`9`~>Ajp5_Jt_ubut zJfSebs24D05`@o?5;!TA-(orUJG-HfkH(+M(|XJ4PF%E}bK8 zTNp(u4y;9Cr0k1NL&A4kRel*nCwy89xsCD{Y2nWSqOdial14j(cjdZdvxo`w?E>ve zc(ehey!8I?;)B4}$Sgg<Copk zvSPDB0Zi-|rEPB+6de=D!1o|The6;?MLkkgFR>Ws%1I{)OseL+RH9Hj&$nVZ+Q$xt zrKFykbOz^TRFBsB0RkG*6v&POutLqOC+Z|@0S)qA0mH8@XE-SssR_@?)!NmcpLfDdPo1$j&_6e6)T!S-%%SeT}g!kWgSb2c(dOb>-MaUAE zOhr@x=p_OE@z?Jiv|k1YQiL&LjVpsPiQ$F7-OmIDT;tM8oCYF^C`tQuK4*#2L5%g< zM{lHZ4?tW4o&$-^m<1a zE#D#R^PiAPTD)4EW1x{275cm@xYRwfsqVg;!UcgT{|ezxME&QxKV@)Yi49$g-rOi& zBt!%sC8`bxj(~#`1yvCPFbRmAf?#N+YXa4Q*@L&>R=&$tRR>Zhh*so&$Zq<10W^EM zmgEd%i`}a&qav=Y8-32o0`nJcu4o&GKD_%NR%{nd(Cg_x5ew3UcXVnxz+;g>-=Cqc zJm4CHwn%i38Cj z+9r6mQ*MN~ohD}e{7o?H)iNn3J)Txwa-DoqS49x&N%1|7Z)*=r?SU zVdS{a-Kr|$pqVK6z>dO577;!O++nP{kg|@5WDuhwc=fOtCox!7?InO%>njs_avIa| z)*Y&3k(mC*|v%QY8W zx!tVv#c-4Ou=F&hl0aXxO+~6xWWrVNiU;!0gA63{UKk<~wKH7T@yuX7MOva~)9XgB z-!PI^>|hzUQBn%lP$@U+=#nl4dliUQ(pi99cD1pU4g>5*q{9)7Ol-WL~%)1l=EH+{8!<_6D$}6z0i0 z1*XBzo|$1%>*>;sDfQT04CO(cru+iVkw1n*uDq1d$$0X|+2VVf9V~a9%$Ilqbf!ri zn0TTjSmJoM0gYeZb z3CLdl865Bu+{8x2xgbCXB^B>8n?63to*rEcD!^MIC5CAG@LCyc*HmvVCMAS$40jHT zXe|wBN_bfk(6;=0f5JUvA$D_6s4zcU9#K1VR_rrz$&z%{n7GgH=e#I@6(X^pi2 zBXF|hiBy~ZR`3xi6V%=OiCa*v8ZWYnn(XnLlX1m}*&$$UBavNnkK$$jSNCH#=T-+`*8DMAe zy;{@{m}uz}L9^*g3<%AmL!Xy=3k2!|v<)~QAORCus2zm2UdluGP|dbftUa`r-if-S zst5OKcJvq%6$BRWOXGWyWuESE%4?Hu*7OwsnS@%vnlv}?EuSLL9DYaqBa8g=EquU9 z8-SLXj^E~j@>aDl9h?8|xVbs5X-hoy~EI$#NW=mIQqGtwW=Knq`dNNL4De~R%=4I}qW&A;I`@jD7TN;zK$ zzq#Y3nljqt5nFvATR01obXKhK5mkI3T)0{I6e%Y7LwJ`M@-x$12W6}pCwoZ^Nzp=&RpKXvQCrXO`+z51e*1(w1*I;#pnEe5_H`v`ri>Z3$fndlH? zlAsGz@6oZ}1nML700aqBM`1`MxJ$iGzhgF2C!YcYWgOWdN!*N+jdmWhsATE^+P;Nb z*w`w>>6)}z53XcZ<+H|U05*9H7SA&rptq@)rNiFnRG`xO25R~`$-4pv7s7W1dV|eq zu2kCumbBWU)P`u#OcW>uaM51S`#cL0>9bu>Cw*J6NS*n-l6&mpd`}e;B5ECM(iGRiJPa=lCiB*ZEbZ^vrzwEmgwKnUjFX2 z|40R`Uce@{%A!7q=&U9(FM@|zfBDyjpmkBY|Azli0nK$a3iDoGAV(3Q7ev3hf^?M# z7F#))_xls6FT9`nh5z_DG5a6UJHuc=WI93o&`JzJ@C~IVJpS6?)Rh_pZ1aAj2%bjq z9D#(z=f5^os&f-=qcA1^lQ*yZO^jy<Ln5c~(gCsD_Ay%%m6eI57(YE(N|5f7po$pZOo4p7^MwZ#@^`Fa8q zSd)(l!fd6f65Bqd1DY#c}tzURBmebZLru@Jg$9K z%jQ{Q@_mNZ?j;3`bP}VKucb%Rlvv;dS%RA%BnCa(k=N!qhOR1x2hfs5C{K27r}v}jkq6DEif z^~}2{2#Z~6nPp1iFAB*7nJ=Iy>`YJcGJ!_BunDUBp@kM-LC}+q4kBYut?Ea;^l2J96Z3cvQro85wGV8lVsW>Uo4m)5Cb(|Z^`p!UrataI< z>-dH^3@>IlIE)+vx?o_-6Yop_?QVd?iwZQ^V>W79a)O; zW)HZ#Qt|f2@p!vixWI6((R`QWLH2>sXT?UJT;5mXt~$G2((W&eNkeCR8anK3(X3(< z8CO>_t9jHWevW-;SNFC4^5i@%!xD23w}}@Ki%#@{x7r-3$S0r@6hn zhg{?G;xCO&PoU*kb0xOQ5yVdXNx(OLUiV^1hJpOGPfvd}S1Fh68)VTlQ(U!RCYBKP zFwU+rXdOGTDC*~?FP4IIjw!qcpnf2qvt@u>->VU#@M?t_1T%Q{cTziMyFiKWjaL{% zO80Wj2KJP@BO#ICJfh!_qD0dDWte=gYD5; z!w}B=lt!w!OT7C7@18wF40e`*w7tn=b%1u-UzkKeODWsg82Tz^dfVA(d5%@wl=@|^ zOChN+A-vGuNl1O{RN%3*`oZ&wxI+893um%a^K5|pO%djX?Eaz0#2|l)Is6KmJOS+N z$s8i6>~|Xt;QTm^j9x{7F;`}6s@L~clYCmUNhyqR4La6+e(y_alPAjQh4FXe6)SU@ z%~=Y&PP!o8cL|i#xW!%lgkB)Rqq!*0edYCH1ZfOBxu~DhY03+_C4q`*g+!A$b4Lzh z=LL7q=th?aT1!0w0M@uCloZAm?SA_*zsnE|fTVt@d2wc<#jMixZS{*Yc<5{1FkTXm zzp-d{D||Oa;bQq{s|{6s$9ad2!3X<4DwH(4>}C6Atyt!yp%5N=nNEwKi_BGrM z(ctOP;`G+0@0~=sfQk5AxA~^v%7OOvC!;`P=BZMAmdyU|h)tIoUSVrnRhpTyr^B6XQ`5mo>?#*L>SjYkk^R+%O~5WM;`ZMKF-L(8oqy`w=A zExkYgLcZc))|+6}_Y(fiLvG^#sK4cQCcy|=J#17{7N^;^c9bHs$9wwL!>hCl$8FVK zcV!u|%ALDTv?zCK?Rq}vT{h^LZs2FEdDzJpDD@qw)X|I1ggz)gFKMIsroFvB{``pf zdjpvY&83bgC6Ku|my{tR;EA>k{OU+;t~$7FFCqHsdUTrn$nIl3Ii*l|G> zLsR;qs{SIw0h&(BVAG}Fu>TC7K6U;agOP6njg)So4W+vf4>~{1ch2tJlKO5Y;Jhz< zVRB1GTUoc(`jW-+eaMpTX9_MExU#jTsA%C~X{FT(2*^y2k z=w+43nDFM|LR-nKD-XBVJ>nFRK4#jiygTURG<`tJ#^7A(fvV7T&Qp3G9sUioG|fLN zxLNyCvg(rlB-sHgOBg{_Bii|(OQi|l&pHdxX3a8XfvW0#)MG6kjtz~29IjMim<6`g zeO0aFzJHEvS4y)&8LZ|u6ugGrjgSp$BCVdjppcKzX_Q|hRxDM*#J%T75+fp;;;7FyvIz9+Ds-z&`b7G@Q2P1?y4Km|XP1xBmGjqO300_0f`Epr8;tU)IFUT2g~i`{V&q) z?(-U=&r7ZmC~x4yo`Km2TJ7$Qi!Bwfb{l;o3QL^Se~qY32WO7*+>8-KS-d^U{KXst z3TUfA>at96vUOXuHYXyDAdbtJoymjjzg^c$k7Riele$ZYZKjb?&&J@V9lpspGi(YI zI@68X2|P1U!ZP9&%mf8svJ+`X1pfU3{ik?B6Rd)TLOb$*fv55%sulI)&C#=%1hNWJ zPT&ZE+8HAWM?{dqu&TJgLCayzfC_s^#xXO;XPpRGj@lgTm`N+m1Wx5|#k+AwJ9_WZ zOyN8HjWdFi!_Cqh%I-jdI5;nKgUB*8885*;F7g#O%})FEghg6GDf$^^D(D(ev63u? z<=S*3Gqc(QY?|Db1hRYt7@C?a-Q#xg#s0$-+eHgPK-%wr%2yo#9H|q3PhI;Ky0wrM*z&EVH#&{tJlTA}RRC)jfsGF@Ks7^fepI z^al_I3T97a@pVb)_nCgzPvX+p$@bai5P2qb_< z`EnZaSFUCf-u5|sG05E$G`W7-4d?}>9%}_UQDt-nQwDfmB!r(26W-zq?4C^<7M`K< zLA3yv^Q?uj1aUBf*er;IJQxPJM$QAGLUS;86Nk7Y2F#CjWF}gAf1yBIolg0#tOZd(0KsDd+@*n820q zP8h_~(PH+ENNz@O1ga`!S>SMmz-FNt!_^vOPd9eyCj%V294kbtV3f z5xr(Bp!Nfhk0}D{r}H^C8@Zc0RDYwl4A-li0|@JHbN9N<&IhbpWM%ky7rToxz;4`) z_Z7yA6a>L3Jgt=W{Da(?hxF>+PBMw9Oi;+%vCQBi0G5{B3s&DQyxD*uT_u{%_|92A z(F_^|QIVxqY%W`PeC>d)*XY^$`ty}n=8!u?T}&y_Hqj8O!DPvU-7Agja^&_dRYpNO z_#EV&%Be5+VKn0ip%*2=WTWE-Ta9^&>IpT0Ih}EzKoj25bHwOVuFEfWM$~TeadO3% zCsJ=fX%&MM_0EA#33ZUj%CO{UcB(;|wxafLctW`T7Gm2(249SqR4Xjf#ZOy{c2Z!= z^xRpcJ=F6&oT&l3`2Megma|nQGUa|XSlHIsi2laNKq==U( zVsjA#foAWEX6b3SPzjT>qg^P)ni?wUWOD<=ODzJOwovv%z53IYt8s89C(q{7Zf;tN zukZKz-T%tK+{j&Cnx@Q|nJjj2y#~)VCrh*tU(Xo#t>#_{lbONHdr6Ki?v*Y)Le@A~ z#WFF7{xh?m5b&un0dox8tPe~&Jxsq$CQaDIPeqvIt0c@%o|8V(y)Acgvb_9sLB0kK zP`p)o5btXrI2fF4SefWS9S1ih1MSF|rHuP=a6_{*P5QC;ppvE^qlbjWb}6kBAK#Z3 zi#29$zrs^Gdi&wQyfgchqEa;SzH-mUYJ+-j%O;U++~) z+o>Dm5%pwe7d_Lmf&@1++(GY|t>-$-G4(C7f=LhgE)>@Q*4cl^l(?StLP}QaVXvQj z(skS(r%ZGuT1m0L%leobe&6eJ-9_<{SBq0|qaHbN#6SF>CJXyS_y3C;+`s=DC9l`) z$>syOt!l1L&RzXNGOW(QoARA>YV`&Cm-**G;5`xiT5{$0Cc>0|Sha92T~B80l17PB zWv@qmJo1lFf)AU%`#MUdH<>AY@tWHnUxmeQ6`QYBe+cCvF02oKTzhQWewL&sHb3^V z!pL1TD7|!OsO7O~@`0x`Sa|ej&Z8|@K?N>PPO{JfnFtZk*)F69U?+^sg8?Y14UETb zo68bIS^84$aG^)-Fy58FAFrHZ{UZC17sIQO0Q=Nb%UAw3-V++SdDLaVsDLW(YRvrN zhcU)m3PFD21HwnX?oe|y7sERz%tdPA7S7x5SxZ;MTfz#U@%>aORYR&VcSmF z4>c53U^McI#?Q|+91M@tns9UXI6s-V#7Z{YAR?kThX568RJa+`mFa>b;(u-ENkwq? z=nDWTBZf%r5-EVMeCni!(%I7(2u)c(j2>fx^C*Qy{%gbG-9$zc0Ow#Z4P3NK62!?UJ_Rrwjd`i)=#E4MQlZm#eC(GOIHDcK9Wh z#&(Bp_te7#CA40u+5K|V(4t6DR?9NSB;7Fbeu#;y&FJYmE|zy2aC_oGnG2-NXc`x+ zxZhc%j#t>E-`!>~y_KT(1ep;EC3)gJ#_d92gbVTrt!~6!`&hSTOwhRg$-VJSX5CoB zL(CMB)CRDL%vyI}V-Wc^jO~Y?IatZR9xF^fPX=W&4d4606kafOTNxF3|WM9n_{um~RP#%KQ;f_U~I$(!a_TKiakUV`;!Ts0Kk101Md7sV&Q)f zn0`kj0Fi-z4Ug&YU3t;3AY2_sO@Q~_9z$I5!MtQbUn#KWsBlm5A{M-P$4{fLhsmhF z4Wt|Y&o%$w4uJn@e1hu`E^M~bmr?d!LclD zj99?U39rg${yD0-SU*#)YH>s~G2-UnpEZ`gJY4YNSJhAR;$8zvl+{s68rW zdXi+F3~nby4nC$A}}{Na01;g8=G`MYla*ZfFu z1^j#yj%1VJ4E}NBOZN>r7Ah9Vs_`u0I~DDdu8Sh&zVvhv(=6+v`$hewoXvYLy_nYO zFtDh9YsojjBq1PYeUbDY;%c%$> z2_)=3#-)6!RJC!WH~{OXgS{(EnHMQ2c8FYlDC5Niq`s2%;qEUkMTtdCRNIw3o9Kc<$L%>1(dpbFx{rC!u`T`^HOuchMnqtj zYUju9+I!w^=gbWjrnFzYhP!L0c;vy{xpvn!?{e27n;YMY?ep9RkPMrTN zq%5pk0Bgm^`mioWKpqwBqyQV%?|*G@MJ?XJ^e_GNZ*r&qKvCem7-61pSyV+F|K&ES zZ~`cpAb$tHDDJNfZ(>AKD4sup+9)bbR~!WvOvf=lPJUl329~Fwx9)B`D;fP$rUhhU z8C!%eh>Lxw0Ibka)rbNnk}>peNz3DC(E?dS&F)nEH;K9bnGXo4*o_T}&N09{fqb$# zoVZ{NKmF&l?{CJ8PozfY!7ZTv2Q4UpOyNIw*w63(mn#+$e#+~?@4}~Q8MTF4u?&; z;Wq3vS8Q75yz^srXJgu6oqjER zf6<;$hwd8byE#F5gDJHc_XS|A5SxfZ;D{W`($6Gb9FE|0>mGB0=qW&&G2BA(*DTfNAC@1JZ=2+1qCaPrR`Jsk0d9}SRMbUYVy zasldRw&4}{4fSv=cbPXegwzyxfYlx@xX3N;lu{HNA3|;pQ1oOjAxd#|U0&kc==@Y+ zW&zNAh;f&|UJm>O_}W_0!!Ol`KnKwhAS zfE}3JP++4hFOng`u1p9Zq&}eSyiwpA*`UD_#{?gbAdXmHCoB-sMM*q-bizW9kU3y& z(0UO=TD|t;0ie6ix_x??rY(V1u*o!6*E_^&zIuH%x?a&}UNgh@$E&+))(c*psb|bf z4|4qb%DWZA598jA$|km%gpr|b)73Gf59<*Fw3hZSd5)*7>r7PoJbuhO9+WGELZ!YOIc0!EZ zZ%b6c>vH4H&dnJuJm)s|S}#F4nA8XBN2XcN1&5)WZgy=62#|%a=Yr^yWTvwlC5es@WFyIXO#7UVkP~ zgdzrjUVsI6s~rcqNw5ML=?;?ozO8N*2c$rCbWjHO|91NaEPd zZ#lE=?j7G5^euB{XEb|W4FUPLnW!L*{HVdJ^f6h21$YWRMAm&(@Kj?O@T!0^{OvR6 z17@SZ4=V1OASH((lC0UOjUDHoL0yS>)5xbVP)cXd0o1v$pq9I}KrGJu#KePJe50&l zR*#0;qQ>Ln;l3-8E*eKK;MLDPEA_tq;!Ll`JFPbJmBMVl$eU^#6Wa%4?><<#2q1}m zS8)DE-wBAO@}h~40U}NPm?lWIAr2ZJo{p) zWS{1fD6&%vtNH0x}45AGoudQ<35i*vd}(|Hpf#gs2q(**z1IL`ZPMztc?9@Ah)R z^9!M;2XkLGA7M(lwgy2j?de=8PcZoN#s_l>XI)&lf~?{TW|+8Gy{!8`1=l@mJ%swa zPdiL|WoNIgStKapjlUrki?y7!EXRC*G)l0KJk}|3N!us=yjD3^8k3=*|N6r-%9{CnhF61d32xqrAMyu3D?jJ5Q$T4B9LxI}{$PExUgh#d?ORNf($S zc=|v}V=uAo4eD1q8|#5HW{Tu?LOAZSKn#Bd8a}B$pXh?1U(SU%<#vc<=6TX2$4i6-xYsa_^27hwnMxQ0>yYQ!=JFj zEf6W*Z&)6^mC@GJP;i}_!Z~%1^5y2bdWGE1Ilw_CCsv|DWjoqUjyy5yvQ)7={3i5_ zs`o>0=;)NIjPH1}7J8H8m{XhgQTy||9~|(-1v;@^j54(yoxW(5$&{Mq4^+Ajn_WK| z5!-$_6LNDOYuckjLA@Sf47OWO4wgUS$~opbJTCvVC+Xn1Jac31M|_)a&!*HA10Nsk zzW|Vyjh%`nOCZJB0_GSoqoOhJpqMVhgkNz$8g?UmorPBXy$2O5xUQF12FCyZzS6l9 zS+@#ppTPhFrID*rJB5CZ3+=q+$h0UK(Gg7x{axQ6YNZw}`R0T+Ox@Q!zOm??mlTmi z+f*0qcy7o@otEv$%}&PqRJnUh{N#9GI6?I$Ip6Pw|GK!ikB?uF&B}g0mxCfvB~gDs z?@3R;G!fs>p+9B_*F&9P{Tl z^K7j*GbU1amyl}nv8yNNJ7dGDcRjAzv)Fm49E{Hu9~?@4Hs2YN)8^R8{=t>KMlDKu zzcST*l#kH$$nR6q*J?A-~*~K0GOt{wz&5HkU(#m zO9l>}B~pyG|Bht#FaJA#{RLeGZV@OHy3fn4TXeRR1iF7pVNYw?#(e9xv(0n$qNkv1 zJzCIb?;o>34%r@Hn&)4w{qs^?ZNn>iQ+<6d^?7aiZ58L+z8Cjw-FPpyaKmw>d&8b+ zXW#9={-(G=AObWWoiSV^3%#_F6-C^$Q3Hh(a^YbaB0GWqdw>jAaY~ffTi^B1`+uO+8dSu>sX!d%|1P z%a>EW@L|&zgaodxjb_?+)!dmy*##3jRrd_Lu&H!u*yH?Q^YJJ1wgC|q@q=zbigrCS zWL(4ONTZAE))~%m$!RKYAUH=s8c%E|t1g-AilrH)y0)%QsY*efQx#u{VCT$3D`#b^vcx8n9gScAzz)1exnw5+E~AOeI6dz4^C@3>CsV zv95IfKE${mUxxF2+_mj6-WQV&&MlybSj1IZr29u-xdMmt(6B9SAY}9PA517M@)rYP z@h3%5?B&MB98-4Auf7R$o_bI3@5g;T_eb23{am2Hr`<4v^11~R0 z;1$~lEuWQs82`O>6KvkdivoZaBJpDW0f5Wrf_!q^r?XCQ5dOipC-f0j!Vxj3 zO@`^IIlfeRf*Nc9ePgsCw$rq6D;)h~yt>NJP1@n%rPVxnD~MJpKNey&T~%B#e=8f* zTsuTZh^@!hDC6Hblthsl0W`hE1=4&w*t+8oE`m*J>(S>?YXQChbyrcn4d)TjehO~H zLTQsgj+%N*Y*=sxi$6@kA084o?}JV6cPf$p6MOF-59QwfjcTccPzoWYgHnlU6{#@O zN=Oz{shna~Ne;7^EM#QNRpgv>q{U*AQzfP_6k`VCuu3K85MyRmCrqwOj4Ly9^?bXZ z-?R7qyMOn+_x-$H&+B>Zy6IpFG7adr0$d_r|wqEdh1oegikwsoH#HWO3~nw2T%8>B+5u z@KR)Kc!`dSEHu{bHJuW;xV~tDf$IUBd%t#*Id6xTZ5j98jI>jKTky3?M#M&#)kE0u zd1z8c7J|JZt8#VaZa_UyCL#?Hh4>cU;D8vgc`ZS?5SYl(7n^G}aks(ADdo0N@C~6# z2FFM=qBhmy?K|5rIS;gp8qN>7r2IOoPI{T8i@5I$V(Shqy8}71MLxO z4M)2xYIyt6-_M;=4!%@H)K|HcSN)B zUhB!R24x{R0R)y-^X;w-nP>l0MMTAc+2;yJRtFO`p~`cvWP|ii4}_1|^#f+J1{~ubzA0TNg7(KFqN3x_SLRaQ zUkO5%Opm3oI|@dd^Ityy-=08E6{fBEx&yQ16+zy5sdQpFg`w8_aE7cOH1Aeb8nTCV ziJjr(*u!xu{Y2QRDJ_bDV?*G{2mIiusKSt(bN3gL{NqAxIi~Gvk+<^g zh>{{FSGZ1o`b#7gbs)8yAnEe$pdk|q;vak!YTnCTK$9O0Agspv46LOQmkb2bn1+`r zPgP)b=`u39C0V``)Z;4!+8|3NK2bd41+vhf%S|!|c@Mke~7FZQtMs8yzA3>|EDuG!6jZ32Kfb)65?L~;4BuG<3&zs&?%lhOop(3}K*M%r zPq=-fM>o?oT7NRx{A*y4$<-YfoOeC5`ffG2$f7oW7zjN1>QpZqZe!S?e*UYM7achB z6miIS`J#CJF?aJ;pkZWtDJ(1abk@FClkGzJez562dH^jH6Nr@xJ73X38~Qrl2)r;z z#2&)Q6Xz1YDXp{|uUpIVOKE;$ae_oA!)<#7xxMW+f_y4xg$10NTC z;hWOgMsUMvs)HUGe9~d1hjafwBP0I1uQ4E0hN*o&ng;>7gY1VI0_N_RB8O{4_kt^) za19V(tQCQHG-E6KZS7dLXAjn$sa4d+oeVBMal0Z^pf|8>jaT7@HX*YN+awP|iLi^1 z@f4{NeP|H??*!CI*syWuAb*|r%M_}cTYQy>dcvTZ&lTFw{~DjuJ9jR}yrJmE z+X;_>n&W$pT4!fxy>^k4cNt(V~$tvJ|8OB4D9?UX)1qyDxM6EX|nAzAWW&{J)~Zx=$o41_4^g(|6B zL#=dUEtGmvV=C1)x|D8=^jF=b@9mOnm42G9O}X1uU1ol&`_+d-aOdqixN3405g5*G z!`_UpM)cBnXGj?Yb+oC7Mm%9`h@3~yOush|Z1$dLl9t6;w-WUGB$1ukv~!~Q9_#ag z(pNVEjPD1rqDt-h>TJ_z%Ap-Zz~$>)N)G_1ND{*snAw{01!<$YXaK^(R1?J`M3MDn z#yYww(%JYP?TCO`;^<@+T}S*Uwwitf9!Owp@bg3$la{@u+r0)m!fH0Ks;T=MZ&Zyu zKChWn3!dVi|D~$>f`3KCq5P{h`|Ab&f`!BSDE;SM_Pw|+2_S0X)&LuXe~WX&<$?R- z-|M?8F?jzW5cH3TB_)q$vLWag^I#HwuwHbf5cUwyNp8ppawD|7kY9LW99xX0B8L0m ziuz(7TCy9vZ^otkpoaJRAscmH7ng?WUh|5u;6Dw+M^0-Jgf2fLXObvGsonMxGqVuH~iE@71T+T2ue*NU`?0u5mX zuu58#mafn6+Ys(5V0`f-wJ0dxlsjOl*wf=ybA3j6{&Th{o4<;&I`i@lx+82tRMkpa zhde@y@#!2Qj(=n9+2jrPQk%f0j6lkw82(47Znm#{&UBu?JnnrD$U+1Ro63piUR zq{WInA_c#cRtP$GRn~2p$xI`<&X{2JI9Y@4AjSb2$|EHDlEfBV1H%Z}SbWEacH_Wx zv14Cp^=rA0aAcoLxv>;$Y<$g~3*x|Q)iR_pMeS0(WRro=&!M(nFv!BYXx#!nV`cBuul)jV7)vi6 zPYnn*dU5WN{401s9eW}rxWnLA^MS=iVir){Vp2!K$t&uWyGioBU>h#Y3 zbH~qhwNfb*sAh`eL1ot%}O;vZK1y0)0Hc zJdhtfYLU3rmWV7JF;pJIt)Mlmg%+ZewomsK_XNQC4I*=!mSy9?8}lskvmZbE0gLw# zX3KTWj>3XAAXJ72mqucALR#_-z!v7A;;AKY?0|pXQ8O9JoMVM5v7u z19CgUjx(9{ps+_i5i$lOz&9ug!)tTX4Wq1WY0qrlN<7f{E3ElT|Vsx})NE z+nt~?eazU38cGEysBlDa75Rab+zy-0OFZ_*T$uSY!sS%`>|fo(Ss{MMvXQC zT)Ih5gkz8vmnL1~)iq|8l}&1jl5^Ru zF}lk?%%JJ{m2LJG_p@Y#Aerjn`?~;Rto`?+CsNH^ZhfBZlFvM$DqENqw=#N`RjI`s zJ5e|o;UFyDSAHImW0kH@pk(7e2!2_WVL!AGGBWcVm~0S%zQ8}UEL=`gVB z@?mOtO=uu2VJhsKRT?fzvASQb63_7|q~rt3q)gGHJsRVb2%2?kUH3;pP}hfEhlZMl zLQ>qk%i-3Qha6Tfb6UNPZ{V=>$93&14?7mDzq0G~Lh!L@{~?{Diw-IMP{azFUv~FD zG)b-jBS@_u+EeU`q!+fQSM(eV0%ox4BQUa6nC-Bz<39BmW9hr1{fw3g9kz9H4`g_ zX~V+V($uJ@B|a28T=S?2nl2=%qCTyAN{W9&hJy{DWOl>BDbFX@cUeBEco%68vVYiX z3BxGFZx7hU)3jJ=!Ja_*wkteYXgX;q9;dsV)%=pw($8E(0EWetqv=SQWMNRZd~+`^ zh(c1yIMdU%R<05&T4HM+Dk`FQt!W<>u!(%=4FT{|TaT03f&_3snGpx6&M-$((Sdll z0GdB6;Lk63eC-aWi*1HZO6YdbQk7Tm`$A!QnZ~(()cY;%H<7us`DrENTG$5kHu$U| z`=-&&z!b>!$d@Bs^3A|maIPuCZNjsQHFJXsd~CB@w3gHH$jH@7j_#b^`Ez+<;4@!S zlXGcl&3ySEKaLLm{L7Oxd-=O@!4l%w=dV`2uE3zP6jLP|=g%ijym12hWCr#>cv-zj z>RXe1NRbb&>!jyW%4(0g48_|Z9Sdq$iIy$%9?(`zfdYb{lcCG7Htu@G9l00mC>94w za52FwMz0U4Efx?&{pi0U#qBLIwg;y_dGBOfJwYDkyzcsdM5Upk&>fTAv{#f&?VZIt zu*ZPdPX^2rXuG;b3P7xO}?ksh7~A`NoU-Ej85LeCqE{Rb=rGbo0*m zxDsBR5cr-LJ)s^;YZ>rZ@z%L6xaiy{XjR#Pc_9Pp>W7-lAvg~q0rS~V$W5Tl*aBn? ztc2!H?E*b)J#B^1<6`6m!(#}uB~yVcf-*AH-lco%+}5xTY5JdY3xC_b zQjDw+N6S}(vUcGKbbV}0;Y*p%Pl$(aWwdi-XFQO7SSNR^ym8X3uCp!6*no{jFs?LS zqTuyOPfq>3&#NynX5M?+XF*D?H^+IOl*l5I>vwqs?P&i^UC{&aCIM1K3T1 zI`$g(121T5312)lC=;84R!|04HLoy(ehe;a_tQr{C$640y=`t01s;|2dF;IDO((4q z-(K|_u5(@qJDG%~Pp~JAk!!-40sq&L2jqtd*-@g?-Il$G&Z}fxp6;OBB{aKV|M0Ho)ek{+1Fn%@aTuM(#oa*mZztm`IC`9lWw_m^09Y!b>}f(v#494o2> z2m4@Z!y@7p1I=7fM#SF)Caz-uAEM3xU9M*5z`$523Lb7;@rJ$tR6WV~NR=&JUs)|rs)MXO)7J#2oje-_B z-3xI$%>h>8%~L6(J5m3!;CHt!WCcLVD3M>HTCpSA()eg?*c{&Mk;;jDP<6%=!4t6B z4JhGx_CjYm!I+?hx3c=_Rw@U`tBF*GfDD-9>qynf57~S!uAi_Lz0tzc;henlagI1x zt4(un{t~jsH>2fzf_CFV~%d~`X%2gYqQcX50%@V zH*QZ-8oNMa@tNqZfhIqVUfx(Huhu#v3a3g7JcMK?b2Wm`pSdU+9#J`1hY*rW#>qMa z<7f9i%{w;O73Eaq`rmV2ijNHrh+fpt(`raseL2mk4!i?zUNA9TP^lubq!aHlnGM5R0yiHTfEgL|Kr_FECYm1WHeTY#~>D>^sjoD>_#&x}0ElxS! zID9` zuQRkGG!KbBzdy3%$|~Q^A9%T18N^l4SS4`*k23XUhw!L?O`4n)$AJ#|F%^lijM@T11V$k;X z6Qn*frDK@6ND8T9)>wZ$gj!@yTFH*eV=EQ+iWS#*XL!keJAp1<88ks z?z2+QjkP(jGGXiVUF93(;#+bF)TF|&W%L+yPJ;Y+;TQDqcgPL_j0K#=F5{{K<0s8| z6ma5Y2&y#8$Lb|`dw3Bz#RV(cVZJ?@3n5%}szFGNL0Dp`=Ly<6JF;V4zl0_KDY!Ja z#yg|0cE)AzeKZjLc4fb{^^ew{@E7R|L?l%jzx8~6 zvDlEek_I~J{QPCF>86y2RwJD$Gn6bkfLJX6T8`QoulNv@ik>;vEu_g*M4GY!YK zZf7Rt??69@I@5^N)I`a{3T!Q^0$>2(9MAyV94}#mH6OP$2h#xk@%11wQdv=Uj#Exg zVa44yf)jDs5?-=_o?J)I9$k;5+oMcSlN%$T4Wgo3UOWu`zh#`=@US@g0(aG08Y7}) zBm2JmEE<8&l>4xDHWVlKS5)Vj38$`1R=@n!zUb3HPJVok-}qz94r;;^gPHMU*i|sJ z9#aF^!9rBQ20_tGNs*Y=NSHpv361KEJqF7)Na)F0=It zFQ$=NjPdZh(TtrpL@yUv4O-ExRExT1hU4~$n;DyZakDOBqFkp_YKZ#FHUi(G8kKm$ zP5|PI2QuaBy@I9CvWoBgU{|E=Rver&xfTjeG)8z*@tvjlY`F(C6zwzLJT%;XoDGtEW%7NyGeqNPG| z%TzpWG5sX0-iA}7N6YrmBJ2^~Wm`LO=b{Oo9%dN~0&+A9rTE74n~lwC`XlXv<<3j{ zbMguu*Exe(g4tJa(8PPogA`W@1o}C);y&X?Y#6GfO293&$VO4bQJS|-CTN1$-2iBE zwAC;xc}*@x1es{1pvtt7s1}^_PFnvPFd-_Z0znhLXnAe6H<~DFSbw}ftf&%h`@Pi5 zNwNI(0-%+r^5v!MlpH)y30Xjj*Isn4Os4xi6?Fru@r)PN=kxynCluWpvEg$qh|6Zm zms{FxH~VONa=qLDX<0FWuP7D*1PC`kxVY+Jq9D0)eguW&nWy5PxN7O zw|GjAwqYZJYs_#F{sw|)7nlbcx4C%9?c&6qxnoHw7Bicy7V24lvb4VU+VSy*^5+fD zsulW*0^*nuLf4m0_Ql2((gdJH4-ic)_q2WFe21=Gg^=3QlQ@@dH3pXZ+rqqIK zuhwzgTgz$5KQD93q}sHfwS6E*`al>5+lp?`V0R4B{F|6Hfh{gcbxE(<@QdkMaBB9k zr};-665NtoiMky8ju&gUkH+XNA9j=W_k2D4?CiRDvgw}Z+rGXhyIZvMmL1GE_~d+L zo}SJT^!)4cm4Ec!z4?cB*nrEiIA&_gZe6|9?R8JxwwH%knjLTMzX^XID!${X%+dMz^-&?ffx*?Qge**Q{4yU}DGrE=MJyU+QPwm%gFS1udhck&`3 zZlYyeL%_5v!~6Cmsx&W_-LGcps%qqdp5i>%Y+ z%GQZR@E4jHf2fvE)S%mC^TQ|1D<-yw483PY2;azE3ux`6Dp>M4`Ju+1FK$%{hq@(G zFFWJOm2(n_%o1yXG*I8g)9s4oos^pO9dG@RT4C-KC{IG{K?YnYw zH&j(SX(%4C7k0L44Gv4s0^tfT*$Kw$QKRVCsS7v_Iq_~t{u-17LwpbE=WB21R1g{M z?}k23a=AULr9#*1q6dhNKfWGHZQene?*j_ias5CF4H~Q$X2&N>@G=tPu(qIL;O<^v)>t37jQPBg#=$PD_b%09wsI)R4*o18IfBUN?6 zgJD(;CV#Qb=cm%?F``cZxv`8U!alTN-$!ShlVudbhJkh6M@BpMKkn95@9aL0Me@e- zd5Z|!2EiHSj1gSnIVq;#fYv2z5NQ9w#Z!%}RI2PaJqTvDOf8i=D=O(EYvlpmfF-T1CfCiYkI#rY>#jD6@^Q@pYs^Sx9W^ zRB57?(Y~~et{r$-4K4k6HaN%iDyJ_>L(h?;gk|4jHBm2*TB9SfIPgYDgl*unom>Z* z6RS+t%eFJx$Uk5!>4y{ra+1sjEnpbQqtQMjT0F?>!}L)VC$yYeGCBbOCj@<3VACGo z(R$Vhx@E!{A*IX?nsmMArn~vX)-TO7C8HpUE_@CmRR*I?P&qOsS%HY9cN75HGgfep zXvo0{W3`4T?a|~_gl&vhP_;@EbFnAg{t@prwtb@Czugay`XYEfE*PH4*Ff^`iwp%z z(d&I2`w~jl`Pk9m+A$n&23VT^ePdfElT|JY8oJP9LYs1U8sxpL?bPyH@2_a2u2IkGvfs zdq$x%2g;2Lq_n?eE6U6}Z#G`Mb>43@Bg7)2r0mFjmm&8X83$qy99#`w4{i&8|32N- ztGsX6Q?%)!qcPX$Rq+PD3~!6_&AX13N`hZ8t4l3=E(<$f>gI$T_Aaa8DFcNBq9%7O zN|JKHN+r{Bk1_deooxdB;y&tF&ymk&t_f6r-!|q910yxTx}Z zvrBAb4*uhD9uBPOALQw9sJ2v*r7ln$3kI=dYDQs$0dzgAW_H78UEYh0gtvB9HInq(z`P@;7pl4j9l0KuZkJ zldhW0+x=_F!%wCYfitn+Gax`ZI>h|CjXa!;y;PQF0U_ld#ok|)|C!DosDl1Y9mM~T zv;F(q<9~du^R;hG3pJG{++n;S!-w%hq;~Fx*W3*>WlU`W+zFKGeMF@AoX+^>Pjw(7 zsnB;LQ+$^8ry;B=s$zU+{*G>Jeeme+Co;!BMvY&KyUW+)v;|v*M}M%NHzQvL9v!@) zdNC=PW5~kVnUAR;^%5E>$~bYblZe#DGnOB#Ng1`5nKC+Cin;k8<>ZY)qxO{LHk^Hzj5I2K4vPV8rDB4;?I`?}#bsiL~?U@I%96 z!-Z1z>$|nxGb5%4YuVER)D)W3k_9r_h$|UHG+jiJgu;4a93MXvAx%ZuAh>zLR4}>@ zpz6tgM5uBLQW~)9rY47FCeTCgGrEb(A|)z~L*6vK{ZG^RIhmKN6i3d*qMfP!ddsbN z#f+DuYCv727=(qZipw*+Rah6Zn%K-t;py}Isc+`m^S3JsxGJ;{2SD_0Lqg|(5-GtB zAv`@PCa3uSnnkbY1KrEQ(D+XxIIKIawZtswQYCt}rU>pb6>}FhLJ6{Ws6H*CVT#)sm&MUT5 zuo`2EIt=*rFc)$bhXSql1W|2z6ftFV6&s%8Ly0+BsdD>*VBPt^WXr;Pv;;3s9~*>u znpuSW(U} zmv8E|jKhl=W$*;18^>!BC;=^_!{Wh*Rukl;`LC>2!2Aev0*Y3AQ_8kNtz@f* zsomv24jIXbahxPITU6K1Hb6Z4fkJz%;ZjyC*u={#k z6=0+f4k(QuzbPFdVJDahVIgMVj}EDpH>KP3xo=8&1t4jiOaG?y z5!@2lgVEoVtZ+vUlg++E!lB+9;5C3mqXhxHIB3q@2VYx|Fu3c#Tmn&Kj6e!CXj*0g zAk2m1X=d-Bzh65X+=H7n`KI&^JdUG4l3_Or4iyI?cJhk=JC`iCAH{!DDj;Fx0D-(W z?*H=rJIS4h>d?nhz^EmFzTvCaKnfuigy0#l9vzeQ-;{DPA8caX0*7S@aoica2Hpe5 z68eXClk!1GRe-ARYjP*_bqI%O(Rf%Q@oQW*^yToZgU&SXKP}nc-2^yfwH!=q?t-EI zwy#s)lx*y=wNrZElwN}i!dFgVA%uH(xgP&zx&G5;u4g9lX25d4$ANj@hajlMP6VK} z;A-AUeSu((Q{PGtkn#TK75mTcrX>Qd#nK=-{{v4h(fa>)|9|oBht5ez{w3ZWvG-}- zJ*3Knz`W=<(VwB_C%>2+@c$BK+OEKD+s(i&A%*u>9o&9%z5Zr!M5aKkoL9KP@DGRG&tk7TcJ~^F zyPuu^uqWYq_>r@Re{PfDtm{|C zhN&7(f69;MMcyVWDEQw(yIT!DHfl}x#RbZ2{6ed}9Zm?iLVXlD|6{Q(Jt4zL?X~?HHJZOFXao_`23kU#p>4F@%9hkdA@TCG8$1+(gdK2Oe**ff2l$J zMV8>LmXWdNdYILZ=pnh)4QWJrYp~elLc2%{b)*hFVm%baYokAUwliHhOS4CPS zMv3n^Z@nHoq4%l$CrT70juIN{A3==?h*{`yJo{XgQ#d6m)RwYUPj_HhRjcia{Szkc z`z5QdRo3m;ys-0c10~}Xpm7R3H;y6JVrz*CyrS8gTM&(`m;O?Zo(w=B*ZMBDx(3mK z2?FQE0g>s&w!DYF_C`rhvPboz>N#;cgR@ROH)71$!ZtSGj+b+IG?k@b zHD~*6d2M37{nGv zUk3{&M}*+e`i&lp%t^Gwp;2L0zUzfNe&;3F-WJ0nyOSO25tHp%kE-C8+yv`1D!W|U zXiW&nb6~{8#wtFGQdC+oS4at;|+-YSlDC4p6{J~nW2gPE)RxfEdn*}I{GeP9zLQu_cJhoV`j)zMK6yu z(Vc}xjvcLB?Y^;|s0RJd^z?GV7tsU>l)&&=$Eq@yu}x3_gl-rBweP~?qA#N3rLsfx zP{bqQ*Y$ZVuH|^Xk)g2d2BQ19Yo~JrwRmJaXUFK`q!$yqtPdyJ7R=%0EACPUAx}k) z2$`Bp6_J6$U!go*n`kb)K7RDjoR$tJyvgwSt7l7twXdc;P$FjGfqnL`?aA0K zTA3>f-t(d?1&go?YH}plxM05%FAle1o6tC!EzQEE^2R6{+wAR@98rjJUA2ckTkT92 z`mio>^`9mdxnw3^&EBODq52@i&M6XkXDEg?xC>CF-sA_k{NZ73JD+Cq68ds~QS6OX zYB$EY_&5Wq4#nf*v7-Wt!OL0kmV3c}9#ftK>aIxi@%@>YrV)BJ z%tJbcs_7ityWr^G2z-q?^HxZe)Io6z3bxxb;i9+$P&`u?nFf=eMM1^nG@=@3xi~h3 zzxl9v@mxxzi)=#u9#_GZWV~#6_wYfL4{7DS3Zl(yY#`7b`%KdlxpNMG%;LeWxD?)k zMUT&f?t}DbxzZvu7FqmUwCk3E)#JMo+&>x#1D? zn3$=DW(qZH!{5hMxUO#*)$Zf$we|A98#1uGxVWErm-dWf75Dlj0_oQ zbby?AtPuBjPO|&^z*_?LxloZiYZu)L>?nOp={psFKw-SWs^)C&X3()^wPg30*Dxt! zElrw-6PsPt8>aPp^h01t3C|_WcB+1A>v0D*avEBEM_C)VY22f{V{L5iZ+&0Ku6deG zdTO1e-MP@+*`L;5yVEu5%=CII_Or?cPW6uFBkq*wh%oZ={G$wU_tq|ll?%`U1P^R^ zWbP=roym@40xJ0lIYE)f{edycFz;uqXLK2>BejjW;Bn0-peK>QHq4+$ZirAt1CuKg)M0m|QIY z7%K*tx@~+>=b+dN?GW3=K0|QH9H%>@v>h#3xYZ0@Pnxxi5UMfOyM_mj-Za27?Us0N zuKu!MMF0boSdT~&eVO0)!-zm&@QKyY5+dB;iDn?#u8-jUY@}USI{E1#s?IlF3Fl|B zXgG5BRw`jdh``7;G)|=9XPBuze$6igkvn(&QlIHrJv~$JUVI=5*EhdOd8{*&`Pj9+ zO)$jARNu*5+Yw`&MypJfmM+E`!pGX--0^NY3AdWCcvNn1r60EppEA=2hr^($-n{<{ zAp^)oM`p@4pXzY|9tNy@Pzx@E>oTR6K=`~vj|6x;_5#pgv4Zhq(M`EzQ2RG)t${9W zryHUUh`o@4rp1BPZ|3eHE#tLB_4gdi{25LUb}6j+Bg$yMAbptF-*#(P;1Z+(sx4Cs27M!%_Z=JL>L|?z(4)7CxJ~tR}5?)Z-7!_XAe$ zq4gcsRpOZWlK&1S{!hB}tQ}|vK#^3nLsAO?^l@Fob`DjH6pybyRCz2b85+CBn-1ASR$TT2jk(^;6Rh44IbiX`@1pUY5aJ}e*yYY!Lcv`@h@VMs zWE^QS`Wz*C5%9uxDh^pB=DNgQqOmt^ti57gohofId9iebw+nG1gJ!(?)csQ^etntq z(X>RhH|K9o9J#-by#=MadLO&jUwF*1cuV!TseW_tLA+C7B$wR$Yv}6e|B>*s+wMIzkDCpxZ3}CMu^oZ;gP)k#d?KRbq*^JlfxL7|5@F`u*Zf4 zwb7HRLL!be@53;w_(Xe2K}0@P|PwaN5`dQW_PgeNQ#vmw3zZFt3@k zg3)Qb8)VpW-?2Q?X5bQC{Gk~FfIVLqoDNF$eLdq!Xhi+yoO}1zF?c1uK7*Fq4~DWt z>jIor{z{fF_g>l0rlMPGO2stElsxbO)D%ek6{Kvm#tECS&!>SSH9$9(I-ul2Up5j` z0+(m=-HS^uE_xCCOI_T%A@8Qu8tXKGm4&wKI2UZ(9>(*HnCShN1CRTlpaxuHbad^C zw<9FDXFo9N-SLU2Rc`c`iv^0F>I*M$LlbESfkt|eq-8j%TRQ#$Ium-WhQrZf~o=J{J ziTC)P4?reVsPO~M%CQ>jxgA$xqR@r2QzHciG!H)E$F9W2Zd)&*!my?85GVDQdV{d~ zlHri7b>oY!A4?kZ9&z~4Qu*iOj;9N+DEWU42WJN;#BSPxaAXfqJ43eMl{-TohT!#8 z=q&&ypOY`^5SM~lDACUn4F5pXR8^$XttwFaHcYc0&mjVp{fV8U+VF$n@tlq9l9yDd zM9^HhD{Z_#=-u8)og`N!#*2RIlv#;QGZ%mmNe`V(yq%I}Lf!rPHYAzmd(KFV+v4`k zAGmW_f8#at@^`0G7sRYuc=$;g=1$l}w@}n#OXy)>;YlG}P`kzFGI|G?puaO#KrlnH zn|MQerT``_PsF@b1D=MP;cSq2!*2LDENQg@v@L=JL+9I%)G2ajz9|{em4R_|p-?5wGIaZABfKY? zNEMlfwUa=JP9ruCC5}|*DB9x}@e%ya?Pj3&*z}> z6xiFwA?4F45qF^7s)(MuBoX#AWT&W z$k1^*NoGf(pdMQk*i(Y&U4h23kN0mMZjXRFFf|HnCeDAX)5F;})eI7bD5Sp85gfc~@1$L<>(6?eGl zY-(tkWtWd)5iT*!982((zi9= zbYXJFolS!_7oV}3NkfI02B^K_1L+aH*?`MKRz=&TOVPQak)13dE}dCPN{zxT@cw8Z zUpwr^!D(dl=Bqxcs``E8ugu2dkQRHe(0?hxr21&$T`bw$;>dLW(Da%Koj#8Bj}I@X z>X>E(>=k<5+;x0Ge3jB~%FCZU_6@%oxayV0!4;PS_05a*+jd;eN_TcVe7!5EKRsYX zwXJCF5$z+12lf@2)Z8>(^-9~rdFap|r>ni4gKEg_zY1!AG8YkB;!yS}$6CHJ_a`2> z^em{!=EjlAuB}igfD-#;W)f8+JJN4YKC~cM1kF{Jx7~d=YLbsnsY?`%4_zsw1Q*HU z`e%2EU=D8}5YUr~P+1dK8yUT5%+gF0lb3!ZZ0$OQEKVq=dHAI}BgWa3hrwMFj8({n zD`15*Lp}w@sEvE;Hk1Spy}Cx**fVQm=rSBLmU(XadFSEkr;mEg%~_N9U$XkApHDBT zD7TOVI;O=^a)Y~s`KpCyu6PDGUDom0O}17mu02cwp#8US@u>lG!+i>Gu%N2od`h zhF&JWe<(2C)>=4BO~DNfIVDuv8Sd?|qTT5c`_Bg=CjsT*4HZ+h5?i90ylPVkBs)yE zLiJ+<;QO{QMV|!P*e;N3Cug$51BFoo)uiOnC2(F+LlJ#ruiQUmakjtE$gZ5Qnx<*! zdT#pt`yTg!&(KleDsdSC*qcgr@}w_7*{Ue=kBEFdS_#Ww-;(=?M{D#A_UCf3AR~WA z4t1400FkoqyPC|M&ngBjrqn$jQlm=i8tOPtTqwCdMD|OKB6IQ!s(eMg)J9Rs8~cNy z0;t4e1^Gtk0t6Cq%gf1F7gQB3VanE0Mwj-^Qis$VaX)Yl%fdl(0nAp&S8$ct$+PJJ zh~rhMvnW)rjj2u0q|t{#Z)zYZ%7wi8r&4@M0i+Ww7+SP)f&4FYPDIS$es41d&yX^S zKS2IKEG?oP1Ef2SDqJQ#taxOVOCBt2n-6S`vkdm=XRJYuKuKdS3YDMzR4c`4Y?b@m zr|(7wgo0(Q!mrgNwGC~o5{GaRUNyvDV$u&)z93 zbC(uB+uqN%L~3y9$82Vo?>|^(kO_<~GT4qD*?Jn;9$`uB5J0_Xgj8wSe#W|>3Q6H^ zqOn}ytZNTx8P&28&!D=v&t#}hY` zuKwD7m*{)m`+VqsE>Rh%J&ueFpYT3-+Oy(L&ZuLWW7^@>UDaX!x0cZUQfnznv0s1k zX8a=Bs|Li&doxuG!+T;cJ?;Gq+jv(DA@*U{@Tc)u@x zI3LGRj|h&4IrF%EQIooaogm88q4_srM*VcMn7sTWxEq=rPp;N=SphG=P-7tnHX= z0XZFl!KiT+%|~R~${Qw%v@Tjn@U7g%NOnshi$-d#TlDsPHYfrMLkMdTU&)oy!jPKQ z$yr0QGMik=y}+8R{jssRF&C#lq%x(~xhj0p*A1H>U|0lzHN_EMa7&=SQu*JM-h4;_ zYA;b~YTu1a+0ZmZhWGJgHZp1Udc9$TS4VnF1%%L`WJv$NTPNdRk2;o)OR6 z0e+=2FcsZIlHUe?R-l1$QRV`T-$u&m7r}w41JXPa<2f`3e%EXfciX@KNJsunkk5a} z`~Fh^rhKL1G3cgkr;uR*D0M$Hv4)ADZUzlGU?j}qlXNFWHvvFmZ98*eAneql5!i%N zjbbgOTcU@9?@o}bGE=d&zBFUAD8lM4jv+-{^W-AkzT6@sHsC&G$4KpzFX$>DG8I%$ z>@>LnypKTC-Rd(_wqG8HA~28&hp8=b#4(SSVKZN<$S)DGKcL8yn?;l|o-@>dV1-;y z=o@$d1{U`LcT4$GYBaLf;{n@~cec*$#MU5=tcZo#;pZW_wxKi>;Q@b9E&9$}@lk`D zOtzLpBeoo%<=GXFMe4Z8DV-1|5R95gk%x<2ty}sNJtlK9`4fgOwS>M6mx7JBdBrq6 z(&KoO1G^QwfE03uAd&cJPLe0vL62431LnTCC77ywA9@2J3034Pfn>^V#eLaYbi)3r z9)yEVQ8RffY&WyU$$HRfAF6iaBbtx+*2{dRqFl&aiF!Tk5kmeDDnbC$i$E=u>h|GZ&xL0wLuy*4`F>#P64y!Mw zAL^d0{-&f$w*NI>3l+$000ZTuqTpUtrxz8Zr}gUL>f~t!w(KN7Nugxg(;>6ha#I4=3M zkw-F}B%+k#9cpEM<>!Amajd*vq5cGOkmab;la)p~Y z(YI&A(;jMKO;rI!*EqEN#iN%Nd6E6oE-TO2e+5wu`g86gZ!!i<(SKe)je(eS{RXhL z*fb6hgWHK`KLX<*CBRxux^D61ehro;5Ym? z+uy(aIN0^)rn~^6tOA69eO4eXUb}cvsuqbQ(B9mzlP6#Q1n5wQMUneiriT`{++$?Klh>Y zr`IU$nLMD(%kR@3Izc^|bj5YO$CU$sMRxD@41ep27OUN-J+qvn9F`9GK5DHdazh(C zyOQ}X%UAzg=%L|y@@!A5>!}}Cemq!x@<@iYi@HH7$8Doqb-+?9o1?dO9lysk-~9Wr zhxhmVc($Av>TP-I(~&&So|^?n=(B(PH}>8<9?JjU8`r9mCd3pm6-Bm*Yzfm!hzZ$a z(#Dw3LZ*zFB80|Nin2`-Lzc;2!ep-`*+YyO$vT+{-rtaMn@HK>d8wmA#KlYJBD57`)jC0F<7_^exz_dsK02GBU9_ zQy#(b(nVR9O}+i*JgZl?x{{ALd}X>HYZGOzxWDN`N0ra6dAjs_njLFsxz~=h0l`m# zy)^A~d#~x3SETXx7(SHSYj>qh+o)ON+u6(ebPJAmQqy1Mo4c}8A!C=C8t!mBjJ&;D zm2#|8HA9Z(g>AOmV{7)4OmypYPVjPwsK~o`H30U*^OD@+((#nL11?W==__5T%BsTC zQnCxyD@p|%edeTs&fNLodW1uW`qoJO6^9)EG zJ#$?EVu!jzNSbeDm&Fc9iS-dE>Y>gz*OKF`gYv|k%rZBK#Bzyp3$$&QoFE!Sf0qcn zDDGNuVD37i8Sw>#l{Bzb?cx^Ht`ijlIJ=}66c3jzxZq+SbMEMfeWU|dEB$eCGzHP|5$SWVLx7^u`e?=dBewY#oS+9_^ED4wA;B4tEl?xXibD_QN~62OT5d4oh*N zo_p9!{_UjIQO{a@mYgtY46Z#L(xlCRU%bki8$_L+*)ZwQB#yJmwHwAPv_v$f(Us`U!rYiF$DVdAyt7s~ETxw=q;x@!BD4F7=_ zK-@vV#h{RcGs6fj5jz>nN#_Irn$3tLt^)G(F>fhKfcX`}vE#<@3mH5~$~-AZ<^rtSQwnvP6pn%jH5r6H9rL$K_dH_?6S zH(J&At$RLx(1B%QD4LXM`8SeKvfM4C~-GHZZIyOfPKZlC?#y#MJ2~;XMP8(Mo%{<(owW!|*hakRqeDA{kg)?K5}Xlt0u=#RLs0)5KcK7J z&!7g=M3OdW)Bo`W$Jm0x!eCg}s}lp1NHc^AjfGx?&j>J@_UaZM%`D32OThfW5cveP zIC1GWLELVOjzw*(MGCiyB7yw-&u}@u2v~IQT5P(3pqQz0?*adKa$f^ky-{T^SH8TD zVzE`Y1vJES&0s9lU7{KHPFJ~|as5(+U!V83&t>S1{(a@YZoMFWL~;EvG1a>=^t*^U z#0K%^nohRqaPlZ7hFMujb{Y-5pI09nvifz_Fdj3yFx!+zaK@P={MZ%{6ylD&QmK#( z)BfBlQXuwWDi&v+2&5t7h73c{VAjJ2jkHIYU8T2=w8N%}TAf0x+)2~EJE)SP1#GbP zgrZ7?>g&{FC5$)6kY{h{1{4Mgvyk`o>7z;Ue+DopIG!~#WJAR5_)s=#2KE%cH2g#P z5VJ)QInx!8jjxpwTnByL8H=68(B9SM@?YUA+oeBv7jzsyo;x)jA`$*u0;B~uc|px6 zMPk#yu3{dh5hY(8#Clb$Nw@$+LKv|qm9o|$^-)?Aq91MpMHRY>+n6#$-GQkpBDv@k z)ZT=UKHC|j)>m!y%x`!nFEGv^NCApLQ{4K3&ZjP{uH6hC4_-X@546%vaeGQK2R>4> z-=>VKGv0z2kYh1!8hXK<89il8MM#Q;Xhv=(?Bn`2;;x67s~)K>Mr^D$W(^rPak9R; ztu?sNyCJ1(Na?H@^(vs7%6|*JD!fGX=B-yePi+VXit;yZ>p|D%TuntBK0U{l*5AIf zrxV-vIbJJ2;E?U$+QR1*N6uA5PYq)qd@I<7cw)LJ%r33N@U1|Rd;!S|Aij4Ubt(T& zY8WtseV@?>ExMFfbL)L>UjTZ&1b4>iGj6+PIO}a!HCr1BwEladxqsrb|MFED60obq zvzTh%ecGKvb6m6C*0Ra{$o(XjbyHiCzq?pj;gOi53-wOwrSba(dJ8OYH8=U z94+g@T(zcyodepw{xaqdgZctpwjBtF7gjb$h6ji4E$J73rpgCToVC24aUkO;`E~fu z{qH7jr8P;Hoglt-{RR_mbQL?83wHmO7y`Ld?*pg-L+{CeOGVe6h*pw3l%xc+HnbZqYDEQY3d@@cmE?kn|cwLUmr zNem5mQ!!Hj2qpbJ1cj@{lsL|AO-s^}&q#4wnn)5PUsOMh<=coRGfP_PXZvGXP;07Px#AYUERRIqEDvyPU`QGa ziqyfLHX4p|=&C*%&Spes>?*5u;SJxB5P$|jsUwV=eWcYh!Jr$EA{qB7f><{i#)v~m)@n@g zC!b0yu!614lyV{a(^Wr;MR~h&Nu~DbtxQs#aWEP#u4ir`ou=N!fZW><6tHHS(Hp!W z%~3YGasO;Y1w(-#J>p50Y*3U22Hl*ty}EN)_Nzzc_k#6(eRExIuhJ$fpo`)0nXgQe~l`BW8L<_@8r@3?4C? zM)C(Ty#t2R?Jz&0Qb!h6#s`Xr`s_4_1&5SkZI-4^zV9O7imU>iG zQWd4!alUIoBWU1V?PKI`2_4i(mg*@LbJ3lcSS9Q5ATgXCMf^IT=xvp)0GP}+QDP;l@mdB*}jIzn85s8 z(ZQIJr;#Sr4D;PR;KG~pL4s9S=90Cve_tOcBYowP_-jF*tNWDRA|AH59wxdu!dQT< z2F>Zq>Q~sVIRjRb+0DYp^Xb)^F4; zSL#2Q(D?zT|NZ*`$Ia=lo=Pif(04;)JO&Fh)5ncutGCd}Vq_^7UXO`Vfp=G*AD!Kd zarYlcQ-sHqsUN1uq#xYu(db!7kn9?pYZ&@% zb^F9hHNC;D0hV1SKmV9L-DeQ)YF{d`n4%T^2PgCvn2T5_f{Iuj&lnR~Pn8yLf)Gt4 zS&MD>XHx@WK4`42<6117OZ%}d$xGSa0A4KzgL#k5aT}h3Yb}fT(D{9g9mN6{ zX7v`c)dDUwh1$8LjVJJLSkxx!3cM2=-N4*5=;3clP}#wCC710-$l}j>P|&^F^PIEx zib>==_n~x@Ygd`br77Deg-p(#**n<0jM;JYG@jB#gmc3qrfPM0yKb=pbbiQo7$Rk+ z6|cpfUcAzxUF=c#opu!$*e82*WvAMX!>TRoi(a@GZ(Bi(I|uBV9op94Z(HB-yx3N* z;J)pqIF)^>_t#pHKSx_RIzNndJh++)!flsp=>=F*#$ZC?3+%wtE<>qwRY%hhPWddK znD0B5+NLj<>1^fO9qP7~d0j$neI5Cc(iUr6bO1$0LFQTF<8VHRc}p3Ue+Zg9ONK0C z-1=uCYm|z{v_R-|G?*kXieLZ`M zbPR@&=<>T;bdftPyME4jWgk$dQRx8ko+{gZBP!klWM6t z8b!UcuHs_R3OB7)N_kYP4(D{Sk ziL>`1O5Gj4M9P&+LtZwJe9s7F(yQg z(APk_>IZGOF);?D^^-}=28r-IF)jZOtaD{lf_I`Rt$tcKwrNqpPq&c3Dw@CUFmwY4i}(wOeO5evxsd!-^B&{ zNj!UvMBu0EF;dl1qnOwXR4<3HcLW{dEz3hBRO8sTW3R%HFvzyv)O`GonN{KN&gaH( zH17GtQaDFcKj_(h>GR;L=q=|o$?d?t{yn#g9}U@Z$46;(40#WT$#QL?N=|s2gHGe1 zt&Z#Q7z~#ltG&IujC^GB1}8eXXmW2W&Qz*K{S=_|`r1^vmx)}_C5(z!6?ay2`Ozf- z>E8|%xzm8pL>0Q3e#ay9mLYOLebg!n@4|)rlBsRH5Li_n?klg5R2?`L4x&T{4IMQWE zryxpy+~STlAvRkmH_@gmKrU*4jQ0r4%!igQh1&>h%0C-kF2bf7WTg6r=1##~_(lo}UBgW8p*H23!u0e4w0T?4$Dbul(!i&{kYL2!z1J^Z$o9>MN0+q|iTGH4C3y_AC-htr1C1^S~ZlV%q zAp^QY*E|K%_PrV6<+0Rm+d1EUlToHD&d&2U-sA3uve^qWUNe*qVMM~w_3`ol&@ zoIms477FD8Q4%h|t#WB6fPB1bqH?B;Uy6DxouHa|u*mP4iaMp?Mg`?69lEA- zruP$A2pUWuH8n>T8m1`>^ip%(Vs*~_mhfUM13V-|J$;QI3Rb%r(yAwIs9weLTr&>} zSz2$0l{?MUStuCx<%2UXJIu^8J>0OSUTNo_Bn^M)Tobw1sO_V*1aLTEj65PKqHwJO zgc7L^gTm&w4;TZj&;X2`C`PQHGgQwI6oFeZ& z2;-cI(A3uDZ|2{+D&X|L@lwtIDfxF&4*%Y}sp~V-Vv*#+lf|-V=?RtX z0yveANXk$WX*Kg>EF-XgPZv}gNB6|bYc=r`Yib3kb z*U!IF!5>_g&mxKBNXtkp1Z!68>hPB%u3<;7l3tkY{4H^Wx{k!wX#ldRDF7|!OP$~U ziNT#qBh7soVX&MDAlG>oG=dbW3c)=3FopW-p;^Bykpm04qy{+w`feb=txjA-RqkzH zQXa=Z*FT|t&0NB8U(|v*-C@jyi)F-s0zDRj`0Jt2G6!)kH(4Z00IR^AN&u*yH$n_P z85eDsqYKT(5kkHR5^~0X2Y@9W@b}P6CX3Kq4-#Bh|lvFkqN4-(nyuDD1byqwc>R z>KX2^TFR=&%v*D5;vHZ20^af$(*GSyQUAIC{Er+=r-AL?1X}xlYD@9Yb!!1Z9gTKQ zAy1OO2~rN=g|hl)aDv0c0gsyA*SciXErxHKin;e(eMPa~KvL1LCU3Cc1Bz@iMMZv| z{QhD56nF7fwYRqJE{fpLz-L=5pTPiysV67I_-enwQxMg`CApGirG}@_G zO$!%r=frDn305_+b&%{(jwJ05!Z#v9==$+YlZI>?sMlFWd{hb z&jrt3-4&&@AB4`*TFw-=-~!)ES6`-qX27cLqIlGKSUhIE8IH#UtCa*%5rCb ztqvF6Y+|+#mq~EyFr=)(`?~N}-^^l5#xP2%U2`XTQz|I5Q)pJYrwfDIo2C4+tb0DS zb>FVa!~xVuT4iMwWroB}nmh5Ps`gDXn&GZX*WFq5>b_66?zSlJ>qBFgH_5cD_j!DW zO;$a%Ljz#|Md0FhbNhOHX2u^ne-yvw_;NN)cGD1NXRhzts+h-{%iC3f?I+xR_3Sx* zIC+i8e#4{V(G`~$W`j_EG@C+=Fj&3-U_CW@7-jr8e+S6p8s;qiYJ>My?zNa4=xn_O zqQgvwRin_OT}Fc8uiCu)3>0cUnfStVc``={XqkOr0%w3bq+$-8V|bOD>cCrUKue2T8Ov*tlbt{*UaWu* zV|mBsB4fq7Eod)Rzv?pB7vw!q-(_1K=Vj@3E^hLvEsclietx^M!qcQwhD|Fbcb+~xxhPy?ey-WHY=oqOx&Vl^pn`six>_BIQYq0DFDC?Z(Htr6&&9*F z6)#_x@)mE+gDu(b+Dm46Pjsg8Y)m;*8vmwIyqTw)q#EI6d#Ddt=u=brWb`$qAH`Dn zjGB=4}AY z@r)ap3WUoWs5{FOsLDlA@Hm3|3#+G-Ia}1af*+NYJov#G!Nr;n{JZ4=FTOF9e~DD* zZl)Y!*yu2LnEPz1-o2j@qTCPEX1~_Yl3zHS+H#7jyVc~;=ay}l{sH0U?pm3eaiIe9 z7&o@B=()IE6oDAk)-!AybaPi@l;NO5Q|G(JFx%;~>y5atqDSP*6|T{OU3TLc)eFBE zF|Pu*)wjpFTu*92Ca9X{k>9?0jl!X2i}mx+PkM6mwN0bpqMS6WW1;qutaaFqC%=$U zk1clBb&nq7?Q&ROw7GU?{^RpS`VVW$w|X>vDA&F2lE3v(eRIaz@W6+DpXp|&vg{vv zt2_gcEygjF$NxfZ{qHEQzkdBks3wB(Gj_HgEuN-|%aWTF{|!a-H$iIdKXJRB{SDvi zKaK%uL1=gOSTR&6*m|H;)z*hsS#O+tKgX+(({jh454r8cr9(a+&+5Ih(% z7zEdq64P!>L7hgxbBeFh-j5<74cJUSH%_wr)$4Mj_E7|6dpbp_yuvkX$U;15{|LEb z*QTVewpi`^YR|tC;IW%8PH$=0TmQ{u^C+*pbp31lhYx8p-1%wyvZ)uB@-455#-GhbHp$B@J>6)BM*#RtvU_*tSr|jkbR}Jw03WQbZeEH?3!(nksb@6 zI4LKVX@_A=5ov*_(f}7`oAwXS4YQG|!k-VBFF&$W&eYd+-e>)mQpr`exPcib=zOBl zN-`?yhUPQtlPB{JJ>S%6W3#*83y3m%`Cj^OVBY^p*U9Btem=Hp=fkRm!t2uHhb#2I z+BZ2!ta_Zb`izYH*u59`w>rGn;=~z;bkG6AVIN&y6DQ z&ddh4w!V?d)g|a~1>U@s#x9{1>ys-K(+g5OYEyIjU%f7QlKynJRzb)0{u%;NNxOxu zU#+IsPu{BI@z`vqb=4*Mox4G6?5&$my?Ng(OSaAxm|k&8+cM_+IIUeW@w;0=>MFE; zo8Rea=T@$vzv)SyNpAD}WOv#r-}hl3PS7-Zax-TpZ45OJQXR2GBW^;u)F~`ct>XvF z>dU9(&)X%_luN1uPu%-ZvP$FAgB`{AZ_X@>-PjbXc!PH7)U{^&NCkeFjkSun-ac33 zfkQ*s0hbt_QvHp^*b61quOFSOywG6~PfL=*DtPi%UVD*vtizI&fr56<&PcI-OAPq( zRUmaf-eoLs(8$pdIAe+Xy z#WUtBgT3El5+FrtWkk7ReFo^TSNkpQ3iCwu!}-U(%+psVr|eeM!F9Adk&^`Dx6`p}M>B@a zkt0QgBj}ayeN9!}Tb`vlSCqx6l~f8!=Svr)ngD~&LJ`_Kq897oyv11Wk*Ju;9Gc9}jUz1}iV%_wD{h1Hy@@p?@#9cpAyJZMqc z!LbjNVi-_(m~vtD+5kf`%^cCl+)6kA{9b#>OKCa0T(62wV4cUk9s*3{u_2>Q$j5 zs4vN&!&43_X0T;!fP>iO3PPy}RE6s;Au97gQUocX)}WY}RdhNQVmBc)QF4RZcB}D@-6z#;q|<9R6V{8K zGEO69y}q`xDUiA($CVevzxvtrWQKKljT4{5+m%CAL|s714{P83lw?Y)N8A|zhVg}s zRQ0;KrrOJE-n<^GMzF&r`2*nU`muofTR{t8Ga`VT1LI5Tpt8=2oRQCKfw0eSCumSx)1%X867a>Pw1HLCO&T0j60j#w`Z- z1oMkJ+bfx}u%^dj)gP^ikqlFdQr>=a-va!>v4GC;699PbGa9fTU76%|fq{U1sME5- znM7O|+iZV=Vm7+=!^SF#S-0Bg2H)q`zh(XT_&VeMxszAOrVnnLKL%BK8vcha!bkfo0OQlI0?msOoXpxDJYP5FXw z5xroX#Q++>6R3ly#IB42qNJY@>*q0dZ%|`|#-kc#Lt1tnwWfVFxojaQ2E(-nYI{K?`si}oIf7pk?<)OqUiB95jDE+sD z<>*>4g8-cTXXJu$eQ3s(PLP-s?yBPiODQ7{bd*i&NS)}JPfZmmbhg@8R8({Z1zt4t za%^VL+DEIEzRqZM>JHeM0)H?p{-IzSyJwbYNZ8#~KE`dDrI?uq6&d8MDaN(6gq_nf zUPJaPu6J~SOWMq>px-w#!4v|e-UTqN!<*Wh(v=!r zP;iUwmC*$Tzh-tkK*O=qtIE^AR6m&THF8=Znmu+vO@;~fl?|_;*yXmYHiUI=HA%OUHEnJrI2O?n9Lgm{ zmOa83YG=C@#PFjNbJhD(Ow!gg4tu}9pE^edL;HU1@(UnuifZLGh3W<5^Hx}{d#%R# zQy(2+SzcO=-@J+W*FYebBEu8hJ@pDMy~U*I18Pt|Sv$qv;rmI)WUWB6_d9jH&pmix z%TaA2YM3;&He6Iw8-;6-3iOlJ($0)+@MCqKZ&lZrJF3x|oU+@}8rg`1%#V@7_)?-r z0rUEXsdq@Y{OdW9HmHHUM`6O<^jO}n&ZEBLrblZ!%$iT6_Pg#o4I2#-Zn4_*5-=jc znhvQa9J>(1*Ww*(j{U`e@w3viL&?LMetWb$PHe+GjVK8mQmAp%^4>31JcXG^@8Rq2f`h2Dw z{=nb$e)t3Sci77xj{>&~zknXizUng|PX+uPW`!~Nd2vP8IiTBcYgF26<|4}$XX{%9 zXh=FK`#s(IM@uW#uZ{S$ZB9yeZr0mb9TDN zaIapl*ScuYYl(vGe1wVQ*uYU)|^6 zZ2Jvb{rjB3*$t2Kt@n!5bAjh~7H9b%Y~J5H;bJ;Cf7{u;iOL!POdE?oTNA+5ypIrn zMe+R*WtHMQieev-em;o#b!2e{1ZE+i(CT43DjiM&_Y%zS#8skKq?yksY>>!F#gKjv z^{>gCgoyYbN!h=DUS7!I(H>-yGb+3+mLc%Qm^JLmQ8ZP=W+o2=SXnhqW=D~z} zd$3Glk&?wpC*5z-`UO|a?rUE;Hhk)W@%?&ax@?k<@HrR_ihv&1wwK>4dO`^0Spjzl z3vKsPeh8In-;sx=zT}Utt`8_{gdh+SEzB%2ZV@!Lr5WOT(u(7%Xf6+H3!~jkI_utd zH98hLDHbO;d_uV9uULwM8gj4}#@r3`XZ>K_JGsfa5BuT5!k=GgcZirWzzyzCeIMaA zRFrNgdR3KhEXQckSmA>}SIugEmNO(Ljx==@uMa>$3q)%W-9P)&k~x|>N)>Na2HFnk zz_5CPv5eGl0U8IZwxlH}dtW|9orWVw9nzq{($EOxJRKVTva&w`Bc=n&a)yezwj)5q zVvrSGZo>Nw+}o2!nYU5C2;k=)IC-H*xLsg4sp8v69cXCDdZ&Lx+n_yCMWNShd#G}q z!)0TN_Q)af_KbB*?$K)DMlO>B!u6Hkx|KOUNF!8Id3z&bEijGvp>}$zP;{811$N;W~5?;9RB4@ya6IEFG83mNvQ-JF5I)WFM+N>0`1;5xd zyptR3QDCYBbv2s0)F>pS^w7SP8^5&0Ncy>6YP;@R_34n|&WfMe*qrj$Nm7@nrowY? z%0-D_&8(o7Qk%8%9~+>fm|TaT{!pN%T*Fi3yPxeU(?U|HC24(36$>dmtuf^`N4aFm zdmCg|Bs{}4y61auZQ`Nw3ajXoar>Ds-d*nhgp&5i34M52z41_0l;S2H#?v`hpz*%v z%H{Y-=dF1^%et(s&D+aaX)Sm5YVX=XmpOOx=Xz%|8`@!*tYNR+O}D{BGE0-=&v%;jTHX{|p?9(bC;MD}OLWf8umlJ3d-?Z7Da3W+ zuG%ec%1gMYW=v$QBvg0Su*l2p6=4Gv8q zttDNY1_J4QGo+_xKZ}a5f-cNDANDjtLHtp%Fnu7=ER?tf3IGi9vy&yBF(C7K$eHe*ZO12&rA*Uc)9@sj=0g5MZH6kW|Xk+ z+%#m$c?wNa>#rPBb4`Pu%^QA1?|Y3-9~`AcF-B-45b{Dh*Q=~L-)*r@xKZ>V1hkQ1 zl=bl2=UAV}9~s&*SnF{Fv_5sKA%JSzWsvdg`KN)m9azsx!UMLxqWIUWo3_Jok5 ztK)fL2YLh9Utju%?rFN@J{~K{k-F6!W5D`nh%_Jqf z6D$2kCbnS0LN~=zI9vZ^CQdWUU*s{JoEY9IBx*}4x_`*$ zD5wsc^_Ka3c`TSCzjj|9O{*r+rDOId0iD9LbYC!TNHacsM?UCcv3H*y9v*#ZEVxkK zKJZy?8eRR_<*|-g(K4YG?^=Ei-d;TV_Sy|h9O1;z{y}UnVnD1Et1a#b+2pD?+EfHZ zHdH85_{ycWc@58vDzmNgzYf;r6lLe=v`!htbNhwnEar0j*~3xe9j>-rvK?i>(Q#S@ zUe(v@_brhFzfmpT(z6h-+?HcM(wf&UN+jt#sQLabt9)I+HX*2b#~g*E#PP;0D4~fd zNW=HXp;#S6(A0g_#sj*x?PBODtWN`%>BbHYFE_8RE!U8QMPt*++w{_QE14c3T392 zrkhn{dJK?p-p1VqJ?F8kz^Vhw-yf^(MSn+fA!?cf|k87&eTN=6n6s2**Nsf2c=(3E8EZKW8nV7#u0~iW|d#b%(a1`9ROX9kL zc8(ScBZp_OFBJOlY+3D*@Or?qGK@!W#=F>(6uhQRpu=~Ob0&t@ybhbz^DFedV%U9b z%h#I1x2NILq+VSMFrt*C!EgiV@6W6sj9!KuW8$k1iu+pvB|bsgL*inU3wx4<3h}I( z^06|0vL0b0H22-}YlE6x3$SwA5ftZaEk_6}dh;;Q(q)Uw%HI+NJ4s1aLZ!7lo5fLM zAw01GBZHhLs~(~@qSlc7aI-Tt>skf-?aJZAEw5`rDof^mL{*jbIhHi><719!CVF{) z)vl~WOa)=WBlGU$-)?Yj`kVkf!N~jAdp_rJ&K@dzmNscwm~0wJsO}vy_|X+KKeOtT zGeJSNV!!lO75yTWN5@@c<-@D@^o+04e0bKQgCCbhMH)UDzz*f~0|ja8-aq1|j5J&l{Xz8q zB`zm2{tv+K|0jU?|Bt`t)OeiGi^W`th0*a@j&@qswrwc1aZpfbNR`03Cepqu;A}+m z@b~ZTv-BSUGn}jhI5to{Zs)3@gA-HbV~;Yj0(JtfH7?xOsNJ;p=de~jmhf7^P54gT zDwb~(yCcx4;)v@h{RjyQEl~k+8{VmzTJl?><(1jKTrcldU~R?TRB3zp*>u$jvN{(Q z*SUk9KUuL>?*h5bP0{lBa@OJ92BeWx#7~fguVG9#oCM&(!l4^5PC3BDmh%|m_oPOW z$W^RC2a_b?BI$jqaK~>6y0p`83H!>BUmzC$Hu)I{fH;B0mUe0UTMz2nyboh0wwW|3 z+`i}qsP9?S0?>&02YvH@XB{E#q2{P4K_g5o4hKVS)SkvxUI8X_rT*FD5lX=@^-NF)h41=9{@e$JsT7)Xl#Tml-y{Vo)rHS zA9kSQBCWeT{pzXSklmazj=EK6@H8$}gmz-Qt}J<3QkgL|g?}1ny=lcy-@nJO39;Z` zS_Z`&=>3BTEX_&H!lhUg=0a9(P(N!{{tGrG{gG%k+E74EAjuG112UoL-rnoJ9U<`) zDLl2oduQbF?yQQM04A`dTA7WyjVg!Vp0|CYtmTZWv1;^Q2~;|W zRY%y(${wfOwfGiNST;ZD_Y4=%#7%%)<2o zd}y6*!BA77AGIPT-T=KuxRystm=W$)c|2ewc0MSiuPxW4Qo66k5zOwhPP*sf1 za;Z1&CMdPSx}pkjQ9j@XDFOz1tvXog!IU@`v{9%JEwH_$nn7ct`ZCYcgC}{n8k32G zXKXL4;-YQ>Pa#qSW_g*G7w$x78E!JTO>U&G0V(;_1Doh}P(%(b@V|CE{)(@tE;?tedU#7`Sy<~Ap!#a|p+mi2Cue9!iLTp5Qy>oV!et;S z{rXkXdEZ=jFK_IXrqwiTK*-8P)8NI8JG(bB>o$>f(n>w8l^Y$tss*XZs=X^z%2z7; zv&t+J`jF~42r#@$Vnpd82B=rX8A2VPl#V8Suw-Bv0(}wy8%`}ia12N-H_8qwW22Y@lKae6+}+&0<+7iiME^^1<-i z!lQr6F&ZP-6;t%#ScKufHB;Ei{D|m@TXMt*JTSt zND=G)Su4hG#lq6RvX1{$ISm1qGDm>#t_TI8-QEjbx4&97^yco;Rfd=IL&7Hd?V}vVM>3i#!PA0xMZ`aMgacb|-JNFdNCp(j@La$vD#zP{o3cuuG zny8RAkYFtq5GR2igb%a=tXHgV#l;rb3NIWyX@C~b@S{ZBKs3XE3-PcC8fKN%j?14? zQRqPV1u0l#3+xK|N#_oZwBka^-ron9Zlcy_H@B!=ZdGd1R4cY2Z@vHS1@FC5o~xDR zDld>)xvX-xUbzv+9Tcwtfyl}N9!eBZq4Bq?GO zRr-5x5yKa~(jT2WnQ|2(o$64BB6$+i-dnnegIQk;Dx`gLhb2WVKhAGj_6wwmtFDX4 zgaE-cVjXI;#VVm%O~-st0>83$1w<2|@Ts0XyFM3I@8u=3)DsL4DtKFm_)`%rj)|63 z2RkJP;QB}TGQ2-5fH=~s^ZfW?(r*a|VheTIMKDe2{fGG%MX3eJ_WeMLI;eg&Ds=Cp zzMT=3^a8J1mtt|N(7$&PZj=N!KI`5y6IAYw3@j-4LElyVVCwt8+V2l`E}8x>y%?=u z0NeamDh4ChU+hg9nWg?ZOo=1Suq{9Zb+Xm_DQt9FEDg%a58M7&M3-#mf!fcB4$L$h zGYazM)hvP>D2TSSL%SDm0cYZTehic-&>>h?0FEN5No5Vx#%*qGNcmm}v;*;^284fk zIZrZDi1En;OK`&A?F3ToLLY^9I>SgPV>h_H@9W9i`T^p z_BHW*M|7h$Q7(h~EG>!#I}~y=q?#3l?k1QGuXOu~BMHDJ=aY!tN#bZ%ofR-+u_w<>G7 zeX)k*Gt);!k3mZhAl12v>=w{W3=h5P!_o@cIQg!sk0X=gx5tBgX0b6@jJ!7$I=rUS zpvG3TxnNeuP|4QoFuJOu_GN8F#Lo{)pYTP6NFCj0?wq2+z|+3b>;X31`e_Cn6wP~& z_20j>P2tay^0$-*`rnZs{?Fv5{?Ru1*9GSPXWzr_p#lT;w|Ml;FnP#BP&n(onJZg4 zsIFG8vy8c6aM)vV(SuLVg*;teb{cG^(|0Be*BOT8=ai?2N5T`{+k9oV%HIv$fv%@V zpkz@a42C3e3m(Ust)qM)@d-S1RN3ZTah?Y`dxd(e2v<-01mw%~Jsz^u!$nW9!SuT9=O+Z|(~A z+I=XkcZWe%OrG&pFE?8$$1(uHDfeVnt8!2G8FT&%=mdFnavqe`En8IBXEK zFGzf!BiKe_;aF&Kf+E-%=_M9Iu%oiF86#tiC9BGrTc8U|k3ub(00eY2SkEHCYf^<- zA}Lm=-2^BeEU=fwN&F9c3yuObtI#0wZN26VY@^48dnhw37QF%TK|?% zB|&c9AT7Qv5Gyc%*3uqu3$uaEc#KdR3Av-Vi9mLeN~#YRfh}yUx=o1e;dZgX<}#ISLnft@k`71ts?3#X#hMb|0kplx1qU2kibO%ML! z=l~`dw4GvICL;2Hya!dhc9cDvur$t5XEXX;S&Ue*Hbiyn79M7Mwr`}$;JjD!HZThP z<|YO{y)Ggscb5-RBdaQk^LMs=D$Sb;6)%5P!{9w?6q{FNV3bWJ~IniDAdihAOK^dqDMZ5kUi}|4DzghsxTAB{BolrRx(zo7jEY3 zz@^6ri;$i@GxHP$=*p}uPxo#Ld2rIS@L}oxG(G3hsD?9od+%niH&-kl-#nP&kS)1S zW6u4mL3&YVO``wesV^Uo?{PcsqW}E7iO;2lV!69I?HUe|c^WNGa_3B%e$E-t&kfj5 z3&nu<&jgZ7BTrH5QP{8=gm~=&3ki96oAP2J7h(xv+?d8&Y7m;uSPNb0oU%p92(42D zO&KdYgdjawbD=o@?eGR(b_=;$HO~_WAVzN}_eX6!#>TX<&$x@oblrtcIq|9F>n?0`lt-}M!y%6oMNePkUNxa(6|bbJ zs(%>D+P9Nd{b_5=zW;~4H-Ty@U9*OrQ4k{_1VM<3ii!{i5XF!{c8Dh>+h^NR<-)r6O@9+PX_kMl5 zzwW=+zwTNt6f>Rk<{h4AKl|C6cV?F7g_qw&jCS$@hn>1tNe^ujq!FXH#T@R;-Lu={ zdbMeHwy)jE_94B0==|9;@ejTNXubYJpYYG;|NRKy|JwL)iKNqPPy{8DEyF<40X&wA zlnXlK;|dT`Sd3$Yjt%t-z}~PZjAS;BF;u&ut}%B9XvasTZS6eJ31z1Ck__PX#vSR2 z7_fFtZ071qk?^b}y4SMr!zP&thpG8V65`2izpEtxmf0B6zEtI)*StE97 z68ZO^nEDFYv=`0I06a(_H&~%7>@FPZ@Qk9|JWCe|!^6zYPwh!dN`iSB-q&n@^w zhCS*pOIyEc7e}BqKt3lr;C0dTPQ8m)W3#2PR)=cS^)8rKa(>BJow;@8bT~!}NLC;h zKdXt?Ze}!)XAxI1UX!L1^i_u$olq67(PNsLp}9?>kWw?Jw~)*uML7C*uy=+m&6RGB z?2|?zWL_@5F>_MGj#p!GJYdu2oz2L-H9T2R%5lo&_xw_qr8Zz5MSNNnfZL`NYA%x~ z9;Rr!Y_+pVS#I#Wc5H5Mgq5B{l)z5@faU4rw_4#yor_oR!LI zDi1rq@o5n)Yv1FJik!qA>=8q<;|=weEUjJ;xP=}~#tO}KqKUReG!wXqoOc`>5^rgr z$@qm_vEqtq@33e#-K`Yll65J=B0Y{X60Bq(5Eq)-aaNrjsP)(j)ex&wOL; z*3N;%fB`i}Ye}E`#qfOQRn!$`@k|(V(MYiey0YhGlEh=~VGk%W?Cx+ejFBvVcH#7d zTdijA2iMc5?d(q5P3ZO4z9_lz_Q}&HnG}omabt2EAdo=dhbQ+3K-;#K$4EthLR8cM z=>S;03ZjHV%4CF;N-$P^8=&5f;2Te-h_&$&3ru4Tbm5)mrrHX}pISmoX*};?sd1m=(4=*!jp?O)N;R<1d)A98q8GBH`nTq zY3_gn8LGtd*-R2vyBP?f+`dKMfle=>FYT%zC-e?(N<93i*8y{xHBTL6S$rNu9?B~x zBDx>L;qv?kSVRBV`yaX=tXN`GG*Pf?3PdEqXI#eO!O+gp)RTfpQ?<{?k8~0_`my5n z%k_>N1S7XDrIkxhvtT7cCy7n%OT#2kT&XRP?B4Hwb}P^vJ?UYLrj-(EN4Ibj0~U2< zg=cbOqXX67;81%Bf-ZDra1KPy>6##`)57inhck(*zIrm!|DTmY| zMbr*12IB!I_*Du)x9Ox{ySbEzg7fi}8 zXK1PCR=v9+$6n?ij0|%+Oz$_?9q)L+)wSjN%9fi(wfkMqZkhR2cIV&J?TIa(G|*Uf z02ux2#iOl%Mq$H7L0t6j^b>fcGdl6tHOMa)2+k{j!t;UB{O?n6z>WZ&`Y*iHKR>V) z*CGGwxA-EOn8tz+P=#z|%0o>)DG-lZT#@?*GiAcU!M;;7+l8_Rq917yI2em+|_m(?h`HGn|>%9XdXbn$7PL(h3 zrAOa>e@K;&fHCKYATHnVE}#VxXV$-4{$;{1{7`0*SAQ9wjDMau<$5i?sIi zC1|;7H!5mnOaq&Eg9t;n(PR_nqN8r$*A(ln2f!~hQ1Us65QE^G^MGQkvrbchE=*3O z?W9bdE@i2Vt3q>=gjvomn>w7T!B&wcidP1961AVbZ$K8+;IxJuuB;>COUc_z9{ z(85jk{Q66 z6b)|cg&L<3jp(5$Sn@L-YOYma(nRy=fzr}IPc%s49>@!riL~;l`gDh0V2S7JuCwoP z6}q+8cVo((uxz!jNq$gqXi>AJbNZ(iAEU4XeWF3WMN z%sv5a+-cfA+V+=XnGsjqCla33&H>WeG!Nw?v;Z89I%tXnkD(LMD`FLME;>yhof}}4 z_o<}@t8`E6#!K;YhtGdoRgv?#dfSIyFWls{b5d?7m|C^CsnBT_ScfwZ<`_>kbC`&! zL0Vf`3qpWU<pDrF}+@$AyVi&`>v#Eg`v#f@gJyK%YzjPS&- z$t|>kJjFiERe(}F$7`3&1M}q>kSo{;1vgC_JF1TC$T_a~v~Ho(x^?Q`P8D`WZL*#R z=2RF2Zx7=N*1YEb9#SM z;5yS#)3M3Yx#6oF7Oj%rd*Ix&8gac}(G`BrZRfsPa?gJ-6q|Ye z^o@eyjo8VulH$Q5!!_a82PV2rJ~B2#&HEsDhsPwb%?kK%JIdVvy^{C?DSGrdV>9T* zz@g0FryS2K1qiboOisjzrJmoVWtQ&&q?t@4&*XE4{hw?&-e8et}IZe{EO9|5T{Z+{{#gr4c}$VxnU z@_ezXeHUZyR|k%}3py@BS2D*y$}tlIAY4u`FeE$sO6w4U!$iISj>oiUPX?@&AnLJbdxdMl&Kj}BME{hH{{!NP-ohvW0F zJe>c`-~SC9o`1bOHzbLXqW5UzAqqIL2bh5;Rm#!5-^LoBCt zMNoC#uWQq3#$eG$4yD_>ngezfX62{gmWBuamZ(XoU#@Kx&DDa~Y#DJeh!{2y0?TOa zTD=(#iXT|IL?_TsY((FOmfk|y;_B3i=q}7cBLuQFWD(Z*=w9sJWDg@pU9Ou{xgjAwGD&g0ZiOOffbTH8)gvf1$t zxySj`i6cbZUExKFkICbZnq&1V1RvTj0Wb%8?We4S6|Dl)8MuOr%ot{%W`5~>Rx4>? zh|s&TRBh1(dj0jS_*v)UFXcJccIf;-2(?v{B>oHqKO3Lmz?|Q9o3j3}<7N4SAb71&+P<N*o3#f&(C?G(6n*?YOOuADSp_nP4u9m_0WjOsHs5Br@=wPzZH8 z<-9Yu8E+nK4J_WDcLuX&Z_vu(|;xRxUeciemPU_GYY1UL0`gbg%hn?zwc*ynO< zgkjJ_h%6egD=vON=+I4f`1R-;TAW8k_rwboSC5u)u>0F+wxV=Gf4j)LobxoU*`%hv zVBYmo3Tt=o?z+;~w6lBadnZ<0KlNbgk>&GSg0fw%9=tgFc&hO%zm%lo7dKe7>Jlyt zWt)1tdaa(gC%M<#e0=rxG~EUFy-R)XKBaUP>@_StaQD})x0jYI+`I4PvydDL;f!gr z={L^wfYp@*qPhO=GAo0C2iI14-rjxleox5(cL!6iqB;Bi@?5u%eqT3FybRzk#pUzn z+D_obTWe$l+}j2~6kz|EvVYe}5quR*4>?t47F7M_GfrsWyVYxhTkKoEf=3%RS7|ux z7aX(pkLgRr_X5|`Pptdt{E^=1?RWEaD~k59m6t|$Zzu@JHG6burFVWn>Hu+XMQB&? zz5}T&yo}_cwf^}DoA*3AVH>mPr(+?Aj^VMAefFcTa#z1_D88H;y1%*Oc7Se~xj|y{ ztDD8EBjJhRLj{{#+_$np5kwE{7S~*siRJ% z)pBRJRapUZ6Bi>xVOUJ`u(hA&k?TS#5Aw+%8u|W&?0!?%X6D{l*4aDcHz+vViZtTEEg`R(^9 zI%e9Y`ejwsXr<~LeOZR`oTh+8!3hE4kBmF0V$d@6*?! z24L`-1B|)o21$PsT}L9HwfO_bQs9j}Uw@l6RdpNQqSf7^I)tjk)fWgcD(siNhqxJi zq1;jlr^g4wj?D}Q&0Q#1aS>-oh=lWZb=~fw+e%q8=?l;c>=EK(OKcB;6iB&;It{f1 ztbbL>G5A+FEXunQlHgSj6;^83}e*z zHmZ=|cyw4IA55lP3k(U=_wr8dIGmx!Zu zBlY1PvzyuRWK-@*_75VXdY#bo#8No11)4+Gd7vgdeN}idd?QA$O;Sg6cHFz`LZC^M z?;=m=_=xY(SAPD(X4m5fvtlX1F~QCiWP-L&^e{G2o(KR_P-wx%b1AqqQMpG8sV_k$ z%`At>(x`?B)T88qBY7cVNg>U#d5CX`L)mb@ba$LI&2Wg9Ijd7oZ)Zoqo=Edvr?@S` z7ok(-!0Mz0pGGog-~dYlm8({x3gtHB97|@ggEtw)^SDf&uQgKd1>e{_-;jFC?o;YB z;brPY2sxFoj#4mkKCh(Y{aT76aBvvI2AI`xY96I{aJ#IZ{+1$jQ5)mL&;^qCF0Ndl zHrlWke9_6AC?refeO+ej(_*D`tlbF0Sk%dk zSjI&cz9>E1cUZd~^#eU56(nFV4fd=?i*fQo@Yg18*r-Tox|1yx12XnxI;)rl2sJ-Q_fsuir+ zuBBnV;g#1>o$)@^A?=`$-AGO+&EuZn_NkQT5eQV)j;LMWSx=E9#sZYuQa>N{mv|(S zX40L@`cdCcQWn2GIbdV-c)Jb?`5^v{a5bRT4Cplr70C`N=j~1H-z1Jb1N>Nb?Mtp9 zajn`Do}pcg4ym?`vdXQQr#LS6L$XNoD%qC@r=Z0agSkO7FDZ#O9fTlwXn$P?yTUe1 zp0O)vRzCF(da*Z5@7{#}yhhFyYq5i(ft*ZQ2#Lkyes*INsUnL{?W^X2AOM8wLAZq= zEfHUVSqa*;>aDP4oRnln+x_tc@Vg;UB#;?90g!t|Nm-1=bqEjLv9%nTRV1aTF@d0h zw3}HV9If$4(t@qz;%+q+RUmphHC2EJ8--rKN2hf|$65U`z?+6ErV4d@yXd4sT9K5= z`vPDEHVm$Z$I0^rO*IUrq|HSNUATqZh7;2&>u|d|L;KuFK zey}iU67jT--4XJ0WP@R|>eNa-es`s_O>3raRO%^?`?BnpY0;$~7KzE(gU@ZkAfxx6 zO}?K~-9K>HA9!WI_lBkdbKs9xM8aBagCzh>yIuCOU#IlH2q(T&?B`@Wt9VqE_i@Dk zs_R=nznz~vtg{R`?8nPa_L&jV((XNIo{svdR?!c@<1BbTH+8b$@zJCG^gT?V{8DbT zSA-xj)s*60BT6Lfx_i|Ad*{4@qJU*TKDWvr*pQ^H)}brxt=IwTlM) zw6npqpOGpk^h-OdoQq-~99^;9{XQl8t-9oC;O_qAy|p&_&*tvgvw78m(C;7JT1bK= zAR%RHth$smAOjxd1v4~ekL!|S zC>QtKA6b@kv8H71+8LCNtqayfl;XeXl6FQ6Y~*=#-ij|3cda=km)oP?hWFn}em=NK zo*!Sm@qMSmj;D_~*~NEgR9^kUi!s!BA!hhQd0E-A+NaeW_9Ygp^-`W5uBhI?KQ@zd z_UN8p+Lm?N>=|n=H*_t%zw(Qc>~x!NUPU+?Ow5T0H+St^)}sglK3Dc_PA?lLoOe?v zgbBBl51R$mpl|GjUrENAkI;gI$`C*hYU_>2agN%{?UteI1Is^S!T03v{pDZ;BZ4x*3qZ z;nL7TyZasoJI_3?GuxEy@bGo={!(CdShBs&?21>BcN#D}1S(*4%XP1f>@)O;v_6@g znvY%qi?SD{CXys~NvFOO(sH!MAjt?D!0-~@W>U)=^4241HAi2lDn5OXG51!46l8su zE@AX87;p)<$5lWa)7aDsAa>kLffPQ?!<`Zf5dOCZV_&M!p(e%WI{ds5d z4&@S@9WWHncZXILcV~QG?AdhPDT4B;JG*FiuG7KaC{61~I2gD1*^?;VpMU^6;jHKMFskLL6kiizH z4{!wY5oji!bL^hl7U&lui85+q-C4k@B6e2DF)fCDVXPTiE4ow9hnCv>Vej)nZ+r{n zxhdk~_k+%2>p*=-w|~KHOUdh!=hc_A+M& zTQoAb0w`=dyco7r_#&jFi8OLRPq6T_Fjaf>m@PQ8l;_aRh>Ntnft()T1e^u|i@J~L zgT|MsF^G$LndPc3l4N!dV>QiaKN>9SXOFq#{JPXzq-42bMrhWg^%becid@yH255<` znZ*{LWOGzg223Zrlm(iLq>~CE00~e?=8-JKrrVQ1f~TA1Q-y-GQOxBtleklf8)#m; z5knVT5@1ZI_oL2I_AIoCJR413`D3WonA=0Ow@K=8m_2r^TbiIGku`bTVA4eZH?y1C zFyht*(zIb9!BTG-f^{m)r`{C~j!eGHFCNXFP)}B0rh7_R3uqbpdkKeRcr401^z;d^ z9F~eq!hGA490s;i>?YjzeF)^lfQCI^<;(v4<+WW3t52o)(xUR36#A9lss!r@8(QD( ze72gJ)ECtTYSt{#otv^GCYivU(%` z=j2NqCP{fH@BpiK?YPa#mV%Y8jGm5N z#*-4&?Jptwzo$!|%>F~+VCvrr@&Cvc{v_A&CoP75tIP1ufB#vl;{P-zmO)6FLRy#D z$W3t+O|9VSjdqvF97SxHF=e#Xe}Q;coZ>+ZY3s&A0^x7T_Fne|vMa-6cU zM#C>{t88{USbbo}nHkBCix9C0r2G?UZOlGLCAmeGR&S-bdQN6-9;NM6DP@I<99+yz zf$}M!D=r|xPr*}JllGt{0G2O+!GM%a05@NOF3(CLi3Li*xd6+Cp=E}SGWuYd>m z<0dr&EhW0zAOG>e`9BTbS0ATe01IHwpF|zmqhAdh|FHY}npnhtWd0lHmIwpL!%hY1 zxy5aFMzA%J@`8kNDm6Tcm=S$8e9gw1n6mZ*+0NvUeRvYN}w7!v1I1n z08RntbYrw#rTgA@BJDgZ`kQUt%iGZeL#U}mbI+c)dkt z;S-M@w)cfEi=pKopQ^l3`Czm0(WYQ6?)JkK3IMq;wi zb5N*4d;Z!~)%Cv-L!sPml6a7fuF+53@P-cf6c>M?2_&Q>hgzf<74ZY+A|m<1yJ5sF z+*MniMN7l#%v;PH-W5mLY6jlj`RsK4t-5gJgLHe_n$Eqixd=v}#+GO^jZlCYB?f@3BM7 zad>`qQC9rQZ#NuW1Jn37hGXKGy@9{8&h3=4(D(brcp`JD7QAeu(B1b^n3P$tNf3-mAlrQBTedxuvmb&N-ibQ{&g=Nz_eJ9&3JnwV9wl#jM zhkMUu4*GKfC|{qSno=KW(HNMdt86sI(Ad#V(s+ktM7{s(-{AYxT=Ad?wc*#*7u~1r z(CT5qS3N_$N3bkT6(!BQAY%!awzfF+JTS`gbN2Fih@50SAa7GfR+k^F>3O>=LAcmz zWs(`)#rQ2v&2tb>q6GWVju+*xk3XukT7Bj)oe&u1O`ll&CZh88!e_P1&C5TPZG2Wv z=-MJ)vv2wwg8ST}q*b!BW)H8gY}JcTHNPI3T;TitnfKQ>b!VL0iw#zu-32Z9`eJbT z>Z0}OXWe1dvf%%>z78;TS37{*rx6c{3rUY0?y8s+MO~P@SOm&@vZ62gIOQ|%V zdW-|`($z)kh34|rA709BslFed2c8mae8h_ix4(s34|ap45S5FrPL#(ZMp8in zm)ef5nD{(K5raGozgIxOAi5*1t(FVOhUnwbqsdLsEPR4?@jyVRZ@)+Wv*|ecbb`U1(Wp#IQzRSBY4lAiO27;F z=j~0-qltT~_l!5*gHF8c9C+Oh$QS+*`*n~VAjRc^*>;sQJj1tDYt}Br1Hk?u#8ZID z`73jBo5>@9jh8D^0RG&=xUcPC!Dj(fdsazChY$}X4ZV&yO2GvhF!X6%erSyBXSEF+ z#-DtbSVU{(IZ_MJj9nEyLYtKn3+>$l^y*e9M>sM5W&|a4-2C*#sPf@daCrFZj1phw8@)ib!Ctsb@TsI&-2ijIh=o)J|S+!?`G#60xFeuO&RIOB-#7ThN z^iJZsqr)Tv0@jixt#kV9nz-{7k&hUDRN@y{#M&vsTUfWFoig)|X?veUb(Nm}Q9}mj z*CZI`AuzJe;7ntga6Dj=(ii!hUP(^MGlne`&9x#7tzS}<#4)L^pVwL0rpK$PF9MbA zOB*OY>xa*?lhmY{y)4_e^|zP#-i_MmEjZVkomvGwwErW){bxe_&&2&J9auB}Kg<=s zTn=z*qN3K;Y9m2Q{ZaveEW^JhDK{Y06uQTKQCR&l#3hLf7~mJ3J_F3JbR^3V?944s zOGoN;YP&1LeLQP3{YvE9weO@KK1h#PKgo9W|4DNLIM{<-WL_P3T?o{h>&Jq-VJDL2ZGJDY9_G#+Ty|_33vz;0KD-MD$b(UY5IBf&zVIrRign#GPuhQ~y zk6oJCI9m2wJl$0W9t9|{4j~An9P>*#8Wy#TXz`J*oO4J2xs8jWJcL7%#J{9gc9e#y@}Dql#T!wi#Ogru=%gNeXb_~S?v*~5$cPRh#EaC)E=70+Kn`xWL{Pz zGRn7ff>}Rs)@V)8GbQwGW`(zl>CUeRp3?c%Yu^ht?(}97wUT1eMNJd&VOtQ2p+kz^(cp-T=P3(?gQD41+|^jYzH6H8}U zkwUOK6YWsR5yt3KdBR-Ixz8DHbJq>DQqRoR>0>?gkV_D*tW`c$<;Q4YRWcjer_mQ` z3ITjHn}I>&nri@Hn`BnR$6pi~(WfKa?Y))lv(WNJC&6VPJIcj|IC}H8a~}5+9Hj%g zB2Q-9V@~yT^^1PzEqO2`?@}Emsg3bFGzs-hdRG37|1xOyBK=8q6F^K@X+C59zt;cN5;XS)_tQHRFg8(#V9Z~!gSyoAy#`xP_> zJoja^Llqu!L}kk8it;T2hI9ocB!dT92fDlF4kxTMw5B?lbei0k=$Spb12951(au!H z)Ta0w@qoOe^Lf@B`Zl<=F$o*Tz|zvE2Om!#MkRA?e{+-}*YR449VAr$M;c`IIF z9)gxfa=)Go@c8%BS8L+9$C)~G9O5D0EMr|FuF~8E1URnphi>|@a5zfL9Medk1&7t7 zF3v8gzPaSnHW>+vR!e=F-M4X;i^=nb%`(%%4MPG}2`bZE*F-=|q3xOrq*G)=Mvvt} zv`gHcGN|*a-UJjZA7{0sMJZ!7`C1&!?`SR9n*$NX^}Fx2)^K|>K0#PtLMa@bOa%V2 z^}5Kc6scmShtvY^(L0tI>%$XXi}J~yI~hO3H)|V6Y$T(b4)nK>3@gKM`m`O&5^&o3 zzou_M@lvXRNJz=vSBtobv2tt)aA(6dmFj-@c-ShV8Tchg=OrPtY*#HDs_!~3u#=ca zJiGP+#J~A8#!9Ua2m6wQFB+qM1nBJyhP_sRo6Rs*4$Jd_;Wf*Q*14uZ>tYEemFb`ZB*F~jJaB8t{mJWCv1M;w$b$NdcWC0 zCrBYNrx)=yy!d!u&*(#hSW2B$w9c$@l8ZjwKHAMkuxzWm+0 z8!bLpC9;&ZPjgrJC*&U9AAia}q~p@)!2@TCur^QKGL3RPQ}!3_D{y_jK(dNGJS#bQ zj`Zn8^PkM=js`9Sd}S8>z^$trg%bZLtLc^34+qbk`g3tu)%pX;*L<&?xR$@H>p<#N zKosgI-t9>#H3NT~O`mT_4PLlv_Zh$rs*tUGzS{GeZwFu#1({TYx|W*#SHQA?|0M+X zFZLD}?=t#8=L~ebJ)Tuw^$5l_K>CcuU^mT&PALF4bvxXWgPiq8ToMU7`JpUJ1%1~Y zO27(1C3)7cu=Vn{4?8AG9lr_RIy||%_`B?*pFjCg1l4E!`rU>^k6q+%RqiNLDMmBE zG#u4bGp2#gP_V3dxbl-}DnB&|y`XZUdm*G|MI70%elBQx(`j`RwbB7-h;!PIN04CpWcvbg}8%KC#t{@Dh0-pF0aUkJm4NKc`lGUUcYK2ZDYC0 zRm&eVvy+Ll62@Z=f${r+a7;x)e;&nZFemrrxgs z1q4u0NWj5f)YqSsB*bIv6C4BD09o=tDI_HEwE8HA3wCcADJ9RU5}mFhjEGFB4gxgt zm>8Oi`ba9%^QHJPDyH9eB)}exyH6gj67svNoE8W+VlaJB#V2jQN0_)4|GEX!_x+&u zo#JSQ6bIC7p4Hm~3=4_5)LFSe)M$bm2QvLqdsQ3c*<<&vhg5mmfoVcJ*R`keab-+M zWwgTAocsdFy5O5d3Bq8t30^zLeUuyW`rSMX zQc}>D36s}O-M3+a&`A1Qn9GwGr*n_NEJFiL@?v>R%>n(m(ww<*6-b= zkS;9|>~m9#LHj*hYD?msJ7ZU39ZxtSiS>RF(4%$?)=7#e$z_Q4VQUVD4d}-1oSFIa z&r`)*;Ck6_C9RfIBxIo*53*!f&~Obnb8x~kfw%&8tQgrYlkDb}(O42pI$?)6T&GcM zozlXc7l}6%hL8%~uC3Mx2g@MPYs8KP!U1Dwh98Id=Akwo zYW7`#6~X)5i67N|Z_wZ`inq(?Tf2>Su9tVviK6+tK88E4`?)%Zy7;PjY??zewSI8( z#bO@=#^$|uO!V&A5yyA$SassNg-3Q?Azaxr3q5glqYHHlCg<%> zoO5_vUf{zX1U04y80PL;-Ho;X_KhU`_U5Ks7c43Gz;{JnSHJfpJg|$&keyw0+Vj#c z4{53Gc0R4&QoK_inFh7ZcI}SO{IU2z?&>nb>;mrsr$e#1t}V0D7I+3Ejj;azk|2;* zFaVDQ!}V~-&S+-B zp;eQa^_WJrMomE!K+qf!C{Jd9Wu;f)PLlo-K!QD*Jq*wisda+}D-|`bgJck>%g^Qn z77Itt;}fMB{tyN|O*Zhz_x*wK$&#uA!0vKH1aRj-2sO7qeFL$Gy2 zh?W=%==@&M$CgcQ%q{Fk@yb)2YVNpl#wAn2=uGn5pxL{?mH!uW+eQyqi&Ni76&2bBpei7}fpiAm!e zk=p9<18fBh8}+o57Lw^3~0wBbLOamNNH^C6m>Yl@9DYOF&HAO zzQ{3>K$oG4$hxcpAsX~5Dq>1_o8xmiDz=H1yuPURYK2z2}{^y1`@%;ZtQ=tj$_ z=<>oHCrn}9-qRq?Z^vtiC}dz$>4 zSV@)+fZZzY$#vro+5q+=kmct)Ri^`y&QZongq^1L0&ArUNiUT;`|zuUr7ugQxCOLC zKCs3viG=Ql)g)Bf4i?`>FRDDBa=6_qSo@!HvZDjeRRc|T4V|KimunrN#=YE=(CZ<7 zwmE9OPjicIgJ8wnxC#hE>vH$wUSQ18+yG5Oic2+#sRdlbI&r)=Z=!z5nn_0O3T_lG z)j@t90p#*No4PX`c3q^GfUEL~;YWmV%R4egP+jnx#N)!p%!IvKtB4NbJ+w`p& zp2*uA%ByXX5}ThnTcG;|Eu|qJ-wbErD3qGuMphqNvg|w~;}|RwyEL?4A{su)+~Q^q z>8m%Q{;;6Y(y}7+#9={-kNUWx2s^|#syUW{(^YRk^ARI4 zwN(_)om#&Pz546VRlbfFv_v%^ssa(a!Ns*b0UNN1LO~036509KuT^qmBB7?rP+R6R zgjE|en{YY~BtR^a_e;3RH71 zjm%Ose}B|X8pbv}3eLiAfq=L&Q=a{SykMXIa%5T7snfsq4r)Gd87FRGp4|JZeqkF} zq1APVOB$U3=`~6#)fVlbqS9R zTHeDEn4P>GZZVj1CV0JH9d|^OLR?A9aYbU=FA;2EHChW;2R=(-=*=@c>-^k|*evtQ3rR)XX6)Qm0tv0G|cK)=gST z-0f7!>MQJpF*gc5o;gLX_AHs{XW@YfSXZ+5yJFMhYbQb_qm6EvrMi`?+&4XAT}1^y z@B&e@iggA#%IKFn#zqd5`e1wwnomYN{OPCdm4LwCl4mm~pdM@F?!LR;tkT!P=+dt& z`OLJNW|6s-X^-Uz!evS8=Whtd*k(yNQv!q{zH@`b&M-pUJEmsYP==4Xk?BA z@abv3A1Gz?&C=54;SK}yq(@G6;pHc;yjJ%}sL8m8B6*N%Cw(g@%>c&cSy`6c0;~o# zvM(-Nn$qSb(PSmleN+xKfwVX6zVl7uJoJJTGq)`mys;52(G6yl6r#65qIEUg%k9al zwf_LoHk!J?hvkif`RuLB4t^e2arvG9`Xt=A&P3p@V~$T(0=7;)Ukq=Y8rNaOhgl zPk^7XbTEt;leGg-dG#0zN#n%it9PQds(Fq)k-?}hmdTaaqeHUxg!*}!0*;T$h5ijv zUn?c)QNACbeey+IQkkiopb|8#;&$oC;L;m}%@+LY73a=E$t~b zDmR**l88`S!>6}sm%*)kaR2n3;IkuXwNiXa89iKF@|)U5x&Xm2*`$*s1Dw$tijVKI z>~DKB$UP5E+paklqrY9|;TP@E$2H+l!627v$|51u+NA+XVwDS;Si;~&QI$0~by7g_ z20HHV!6#4QESF*u2aV7(FjF$bV=hGR54kd1&j(zQGA}8!!N*UCruT_eYo_ci>YIGySJ^(jiOa#o;#${ULuVD=4o-FzJ<n@DaSwN&>_O zAtgYIE;_4YSdziYSeUI0)PV0rd|ziPdJL}Sb-r*4nZz_u`*=-xbK%Uiz{0*aM}VS^ ziI;mmMaaBN{WgV{HxuSy>zDT4t?q~jtKN!vKPmyoBhhh0wKH^;_$kK+oR{__+)301kz)D+sKy{|2OiMS$ySwD8{9?;Rz7yBp43HI zT9d_a%o_tU+J&3(q#Qs^NP+r^c+@di`3x;bu(BrlQ6RhcsgW@U<;!fC7ae^q1Wnv{ zT)Co|olKs4S{)0=iC2_Jo-6@Kk&l?$_wn~Bd@}}HrkFpXUD8v(bhrzx=3~!y4Vx8a z6jHc#=K;e<)OUd!Xd5UgeU~J@%ndBi+@x|GiD@%Tf+cEP`LEEtyVXI}yKyHhEvjKN zP2u3ERnYzA;%j?m+?qaI>sITh@(UX^&xz@`>8G6@Q`oLZf7kn=_nkxb;5(tzgce+# zhjl%cZB2Wd*3)t0u+ed@8Q|j9ijpURb#jJsW)0eY_Q3%9OluHYT=*ueeZ$Cl`nqb+ z)ENd{Tvo^9lzz*dc?X^r@@wsqPLtN>fhNy2S${D3ru}ax7@Jm*XF2-9)t7C7&n`&z z{Au;?hfSPH6&Vls1#Yti4!^=x2~`{0ib4%Ty&sN#&v#5=U+Z^~cwjd5`Z{}4Y{#E9 zkZeGTsGS0Ts4LdCfm-E^tnYbRvz?QzNP}C%u71;(UBXz+?zu6re>LaoCW<*C z(CTiMmT<8&Ncmm2gY9b3&Yp@d=*=$p-6PPSkyZ+f;MfThEYtVZrG;VZDMCU`CW}zD zTiW|B*7}ZkMqqhG(|dS)+BHhh&*_g27H{e; zo>=zl$f0Y;CbB$?l9UJEZuTmexVg(Ia#Xd+Mc8vG*wz#MtP`|pEaS_xZPhskNbQy@KVFXR*(MamhNG3$%HCK$fssOtv zcRqG-w550}P+bMr0$nqmMO760!shMs)9lQzGgbtFUegkOWtgV3ri%`1VEZ_)0;Ny{ z7$$`SHPfX zdCvt1@s8RjtRYB!%Zl31%W*kB zpmdDc2CsE7wlCfBdBY-amC@5~tzvC(_8bDQ1xDv#-BQ6^)n-lb)j3KqJ-NYD+)#D{ zLTPZTu&n>zVRg&y0C=Stz$w< z&n%tuiF!Ckv+KS2yYTJZPs>gC3i~Vl>SiSq5XiR)10rwmtv)5Pg*@CU(k>9PNQoR( zq39g#ZB^D*}}7AD|tcSm~HV_^T9X|ICL>AzPA0RwW&VQm3m#amc7}5_Y{TK39$;w#2csBE*5!UC@~A;~snJ zy>Sal26;V5q9q&km%>Zn4B@C#{^udvp2C9};ri_9xH`HOx9ZyePr+ayH6Lb7}&O~JbpEbAr&`2R4n}U&vdTB_&etu6fhM0R8%t!BPEeCHuDKd>O5z3h! zqW>^ly$I562X^F47Bvf3XI3h57awX!?3kBHqnzl*psW#K2PJTo4{4xZB>D0b)4(=m z47>TVO*4lIQyTI=QClqLF=rM(&yymcmBylLR%yxy2>+jMbV^=SF$U z4vx{4@()OY>2i}s`3=@s;b|cF-HlX$B0U{xgad?|B>9v8j506-Xx0yl!%vL~hOn!M zm7Ut=h&41OSScx)HZ$)_fX^}zVMwz~sRXDv+g-Z0b2`u;>Pl~gn$orpFni;oVE z$0k~7uJvE@&ZV4cU7T;oxZb-wzlE?76Y}&r)dh&}|E1fk#&>G3D`1i3U`j^#^k^fs z)n5&kNn6jj!P!||1_CIa*;~e*+U=)vV>(|qYDu2g<4e15E8NNWC1pXC2bXjj!zgbQ z7)tLe5NaY?1l31XAwEZQJ7HWWeWTtz{GQ-%cg(J;6q{aes9dRjZ}EjNiORi2+jjU$ zpOaF)cy-an-{Ss|+b(^3<7frFa|YRR;9RT7jr@GOorawTQRH_NMA7X#T#YEp-n*@T z@q^To1cJzq(z2U*D(3^it{3!0!QGkjy2&I*0nHho%9V{W2zLP2MFZV3QJ^fz?%a=r zL5VEYhVC43Ky>`Q>rw3uy|a2I@tR#}YM%YGAJ)EQ7NMAWNezh7z-ZVgpzp|AC0A-G zA7*%JE%a5bRV3pc;7H2e@5=ENoWF9L*5d#MBc@*eq@uFcdYAkC@t)Tq?vyHRf(beK?|0@^+QWElCRak$o*ZNNjx#)kOO6W@sEDO&W6ShgJwl)Rc53^uTCzO!i z_-NPjhN|>?+0M{0N(~_ij83ifN1fhpXvm*&r>wRejn90u$7s6B(>0%^UX{f$=0RiPCbtD>cVEvfiM7=0h zPEZ*ho*pP1m5)431tX;Ri>C{UH|!5`wo@{2XXa2I=_XQwF`P!>9?3lr)1tRulG~&U z4CTylDdxp%duzF}B-b8FX!jzb@pEn68u;kVYn@X4s*$qiY;_oZaGO~g550yoso!|$t&7_KXbm3GD7TRbB1@0#7eD# zC@`CKDZ-)4n}Af1kmye0&@I5_ee>4s2AkjwobCfKzzZUX+u)9&zVk zE&;y-^&~q2pD7o@RYcct28p)&`$*f%_b3z`j}iqRsC#6;(e7K1Lvzo+wj{!j6~ex# z;sHQ>(k2~7av`)&xB=0r@F8k;j%%U9-D-pt zSx!7j?>kqP_OWb6w5C4foAsu^ug|oKQ^!cV3Id=%&vklD*()kJ$Yi50=Zp2r%r(^M zLH6&hlkGVbQzN;b{3m-o;?#ow#hw3uJxuCfj;nutbp5~gdn@yWXN-$Jy(py**i25xRAoRE+%a-qC@Mgl$GV}hVtVseb#Nr4>cKTUE#Ir71| z9W#H6*v#Dd8uxMR6i(^juaJ~)yFT^!eA;NH z`oTLXUg6EuqT$1KWF1ghZO8E{8#)1t){Q(uT^gMuPjqc%Dxz{kR+qozirD4OhcL67 z1)bPIt}BuS2k)??WgDr4Hq@vpa;(fwkHpighfZ~ z#zkzQgA$Hcim5%ue55vI2@I|Kx+7$2uiY5HrhV4c2|YqI?wsjxqQmy5&3=q2Z{rsw>Qc{Iob5eai7x{VcS;it-Z6) z%)Q>58hUiQ)G<;IF`qSf8U02wK*D1{a_%>17DFyY5+w@73^Y}6?Jl+2QxG%--e!q!KQk1M=(~znI}6qNqZC(!a1|)0Ux+PmL0I*WIJ0Lb z1d3?|m=)Dg-Kmt?kJ_6D<}fQKY%s90*a{gE z3wra~LYNagVD3{y6~?zqs8zu+FL#*E=AXZra4lQAfpgyl6AJ3wTTh9IX)V z8MSua2a+trTZ*;SEF3PE4?Oq`uf56GriDAyXCfbfudk`?UBwZAv+YfN4^6G!TutMY z>dDWYQ>jOE_?9;O8 z&Fjjgl_3TW1i~stz4DX24_j6-uD_^C-|;sVBdKi0 z5ep9i9_B&P-o|2;`sSywOgmLgQt8<{9lb0d9c#4@HNEOvSp1W$ zPi2MNL5|P6L?oJ^Y2S5Z~b-H2B z6b$d^JTNe2N=?Trk!75DuJY&yZT0uj3AXs=cceYI)^3d011e9wGl)dm_5}4kh{7lx z3yln0#i+WA-Y54^HAw~{Q<4-i2+SD-b^wxJY@PsoWMtTYI9bV{JA@k`) zcNf+ji;^<}ZvK_(U<;Xx=w{L`xVfrKUX^VRW7DkMszV<8BbzBpiTR(|huF1ibf|cW z{?WQ^?HIt>Z3zL|XaDu!m5+;UB-crYc{#Ggm`?Hrt%PQ?o_LbGo6XRxvU1?%EFEvy-52e*FU-Oy?_70fdk+G?69frYh_tl)V zKKQEpNMCi;3`4SaU}U~yzBl-*A$IFmf#Y;URm6s%jQIGpjgBQZ&4L$@myCzHa99f! za!>GV$3eb?G%j>t&`rA;q)Q2HZTIsNdh|&Qw7~X+ED{bv4>jK zEOb-RMPg6rEi?X(E&pQmxQ{UB{u;;@?v z=|Pg@yCHFN*F zX+vx+!36+32Hm8vQ5R>{P3DC&h9s+oYU-$>N+-X{)ic*4g=H;0t!nOR_IrDvFs!l4 z=j%7$p#9UpRG(jaNhla|OW|+b8of?&FC98BDQABCND0P?41uW$$d0mNPf7$~Vl8F} z{pT(WM-lC!v{NLmrt_dBaK{dIjw*^(2*O@TF8Mv9dC_z;^yiOJ%IC4=o zwHBW;JEM*E6k9TS#EN%9%zb`LeHZBtfid}UVu16r;k4l`w37B_ zwctzekLmvq%pLBQ`44q488g#i6>W)axZ0U%H$ez_yEx|QAF;Hl5v2js#wc(e zv9f;qaO{{*2*BGt!DW;guWN9#EC17Q_K*R!f^rJ`6L=m`&^KHj4XwnlVE6pXK=A+5 zZdhbJavv4)aZRK=rgRLVDy038OxtuYhe&3(c^23#?QOz|8t5`MUnr$?}&0)@3Z|dyZy>GSlJYZNc*q#bCR^uJqY-*O5Sg9uFP8WMY)5GJ!Pi zvCB90l)ZR3W9#u{1IC{gD_zL@$;FHsPpKSK&3DQ6aw6Um<||?p0%d$u1~4C7Uh*eJ zPF^u+q-?6VquCI3%VyjxN^2P1PgGYDfXX9`5jb-ifq@{b|EBmh% ztEo)iJ8^9L@AQj!&-sZqx5V~CN1w7^_m7V zXP1Toeo0X!z3>N9djpU<$EUO%!aT%ER$(~{c8bd|i5Osx_XQL?w{geu^BeKCzogKWxTUzCzogi} zLQx9%LjLV1K~FXB1nUD3B0vzhYO~D*PD5-iawHu`+)KwtwlxlrHA(uw5?lEoq?kz3 z4Hj!Hl9lx?k4x$KvS)N-p(F2t?@35Fj+4{h!38;tgtdZYf za_Oe5_U!Btt(wOWJqe&*W#ZlJSglL)AuQ#4)|(?M69k zF)PE#E0Gxfk2JD|GkwU}l)<4U62w7pe50Q4bif-$nplXsD)Q2ajFj-DOG&5rxghLaZWd zl_Sa=>urjKHxIS?B>WK4GEA{5#EfpIo~CnU6Fv@?PD4TIQ6F;BN}eU6^0S~axFini z1z91QxlEVnC)lQchwR;HNAkQ*>W#+geLET<>lSq%AlRTriK8+8GcM-; zC;prN;;6eF4oamjT3&Trp3=$s^hffNwEcHH^BvZa9(7#)dUhe3;OCN2I$}`3BBTIR z-ZiKBx5*K8cmOcKrM$-L=ZEVOOMP9-TM3n$7()2NL=55h4^QEF&j!0WIOrgMQ|JNZ*VDYZICQ#&gG{MpF9YVG^y76u)KApzqaj-a0ahBt7*4m66Rpm_y_EN4On3Cy$gaJ-k&UITd1pV|uiai7#xKdBdF=2C z+#CJydzsthleq0EJ50C9maDLf^|SB~>`wgYT8raDOex%#q6JajZl#eNUV#`SRUS$W=xXkHTj6Y=23eDc@jGOecnqc78p+-S%l+ z1g@I$!ADoJh(Q;t`B31SxQ7hadFc>5VgzP6qo(bWEP;$fH5QNRTw5?;AEO*6mj04* zkOrM~n^VCrDHAHN6pj(A{LTjXH26-;Z{*h?SN+Nz@;iSEBt*bmOj-{>z1c=ToFl}} zZo-#!^WP}8D}zkN`7HP;^yY23>GY0)k+(t2_a-7s)o;C@KIgo!@)cPUEz#xSa2L7^ zgcI|79cbch@0!+8cvqQgqwlTv(uM7FVNt89_AX?-;7v@hS9tS#k(=0t>M?*VACwNm zs~GhhFy`*PO0j4Do6jeffUWcqfPdK2t^hXuDxr~;h)TM32sIFb#4di4v^1T5Q-;>V2&<=G<++eQoV3to;ju zL!=Wsl!?k-5iRkj+(zOsS=xYya_IuvJrH?Cc9^t9@|ZE%n3G}Qn!43f$(I-jdWY#X zfV*q9L4EZef_Y|mzUuNVjK@DgN>(@AL}@vI3z^|0qO!F%`9gms9H>A8XFinj0vSC` z^0(#H_@E&S!E)+dGW0a;^4G&$K}QaM`#oRBtueNIR;%gzF3v^`a)DSCKWbesR*n>2 zmHYwDp$9HyuJJtP3X%l~ObWnqRNu081vbID$<6DNNt{kB&9Ow>HKe)BI=t5o1eD3_ZdA4tu0-W%0?F<@@wY>mvL;+)<1eYH#_B(D zxoW9Rkr2+Fkj+$Wa$J5=$KWo?);5GzRY@3W8l(KZzQ_DDGnJO!_|g3wi|A=zxd-+I zbrRsrEm?WPKtp8_V+KZc|sj6XbG3`fhcX=iyh_EX?6W`xGWoawv`@j z@WA?pu*RXCS^@ccat?v(y%+8F2ie9HUJ}l!FJAI`0w` z5<`bZg#d!QR16TxXIsNRCG&fYm?sFDL`oZ`HfZ`VZyMW$Io-=}F&18y6wP%n_VU?L zmDMWVoBz^kuGm^#D!u^hr9nAL3mq&jh=Q!onJyc}G3;Or|3}9Vbt!q6Bbk?TE_|km z%67$C&$M?dk@hg%vz;d?$ruJb9aVjHrGYhAe%%ZiqJgBB5d-j9 ztsNl9&O0#u!WD`V4mhA{HaUXZr!6VLg%FS}n6_uCQ=u3|CQ$w`g-Q=IJs)i=qqcW1 zC4ufFnJ+r|d_LhdF-3^U!YD<_Z#s2V9kf<83`>(H$vZ|0_vPEOgUtrZHDZkdZ4KJ* zL$@C7ysXjb7PUH5|Jt*pGL<*GEW(d|KGu{&4%5c0JYoE2#8MMtym{-XC!f-2y8^a* zE&F`5EcJ4zK0{UG$fdCgy(8B`BXt!|&yogkb{JGn23pedj%1CzPR_e#L5#OoNWQ4~ zM;+tUn#jCOUZ+-E+DW%W&Vi3(Eh!>vgcMlV6oJLcvBW=oa(#FsS+_@wiM4+A@rP$U zr?^BoY%ba?{3UgSjirBjHc)GLdep7CAFC2k_C)z*=}TtHL2~NKB!58fMn}69vfzYO7D39SbvyzZ?Krc;$r>k}5Y#kqK>JCpBw{ z4`nClFUQV|F?&o(e$V%4+<4X6rr$60kX2uVoawOm@VL;d((1>Nq1~JMqrRrHxR94? zQflk`v0{BB8s0y-iqpRF+xShDiy2YG(yMy3x~6A+Mh^&UHp<0>7-lgG&R$iy#6I`9 zR$>nqu$YUG8D~F(Htn^&Wc!qPUEq{SV5-i6H=f&<@rQ4_JmXL+ zrt=*izve{8&ZbxNwAiD_`SRaP?i~jeb8b@T?{1fa9kvS3(HxW^GH2<<)+^)6+A-dNE(^XF~1KKN7`U{j)ZJb18SiTD1+EB7q^O(ARw_o!(%m^v)D{I&f~ z{Uv2&^Iaqju*RKh%+U=;nLqD>sTw{D^Qa&jcdA>Pv<|OHtYq12pth5@zk8EJR&FT% zwsSpWz>xIgS=hbl2*W?0*j;ofN#U(I5VK^u>@wwvA?1?8rP`u^!*KV-+x@0~PV=i_ zYD%CN0b5;t$J^73VRt2<*mKW1hiC<<3=9Gx7~jxWccwyxH$yO8kv?Z@zEor|UnNGFu)vd*2Nx z$ya8p?zov)b`ZLDjmjWP<{kr9@@&qr1#a?BX;c<0g3WTGvw)9wo7@YMCal61Qd$0D zBR>ZwE~|CX18PEWPil5c%9wKphy%^wqFn;G>?>VnAWl?GneWA`2TRY*{|jdw*p4my zo_Pz4n1Wf$0X4ylr0ADa4*1_BDLQa3`P;v^{ZILoFL9vFgWP9Qn2T^Ba;_I_Hbb6S zGqrxMEr*G0r>DJuB=^SXhm%EJZMkD;U(rE%CwALrSB20Y8*~}D`%({NtBC?T$^~zR zfnYotSp?=&lcTz*GRNVU6xINEKOE$|!SP0!A8@U|y&7Z?l*nD6dt3T6NdKjYPk99< znR!O&II|(&;nH#X-H^*lcRGf)tlw4dwj9l*{M%~tXR+pbc!T<_1gBd;7gDrqW2~%Y zpW0c4l|QTA^eXBjKb072x}{rY+3}x`WRsw|K#lM2wPK14XgB-XKSzfW6Z385(%N2? zE6e+?FT390JQT1=ueUHoiND3Y#BI?*b%irWT92|kubnf_*1^AcBlnL*bJV26!G)Sx zE8d?8Yk@&o$(L_qNBZ-JjSk@8U3eq;x}Ml`q#=dTCu5hpLGq~=H3dSXJH7#BU=XZ5 zDX=0d!J5S1xa}G?)FA$ircjgkZp5w>)*3FajE)Jr-TYLj^x&9}&#<2@P4k9f7460m zT?s%CdT?Ll%P72tKA=VF`OBKCo|z)7#C@rG^-Bs~3l?Elj`P1=NT@qd3GGxwHqT4} z(0*~SMYxB<<}NY#Sja0wv55KaT}HeVt5}m7~td$s;*V$a`4v zd9z+GuJ)kF(3}5Pg!Rd5tMnrUPP@6{INj|h&TY3LvzdZseN=z|zx zh9rOPV@-W;v$=Q;%x(2`t`IEc?j8Wovckw92p&Atc5fmVaciRtYOjAdr2N1$&b=}1 z*5+0*2&4c}mqacq8z?%4sDU{0in1^dFrtK9Tt8?&$3J3coFTb{Sw&)}2Vp{6%sKQu z*{IL|`1w#X|CNXyZ&>a83{OknSpcm z0dz8_Spi87lRSl#g~pP^kCN1@1Hq$>j|f@Z`hj%w>}IR5X3UfIw_PGzaf@aN3pNe` ziycp#6L_DvQ9Sk|j#5Ht2{g4N4A!)PNv?SHi}Wf6{X>DUytipR-07ZTp0||}h#C5= zL-7Jmojzm8Zz~w*GgT&-vQ{SoLpGnt{@yAWx2n8ea>J5|tY=>$IZ+>eqE8#c$_EOZ zkw@U$Tnf^$gM>mPHRRm)u0XYEd4c_VROQRN7o)E=&R>KKoDtukPaY}@)!4lGWR}~WRb#naxbg)radquEzS&=sbN(isyN8sIuhiMVq2!&_AzdKX_jG&0|bq(gFxP(R57x{ z9=3i!~X`B7())`o6dW>a%=IkPrH&^iTN&OfGn?hQ-SM)J^VeE`g23C7x}ki*Oe73OhVx~fLg@5g+poKjE8UGfH! z8LXRmGXIOxy$PSx=QD%&7Pe>15t%1h;&YTuBih0NdlUcy|8{4GuZ!tfVFBA!&aQPY z;6ZNpKBchUzbHwo*qiIrKj!!TV5ti(j>G-P#J&DTSoug@?0$xx^?TD&{E^ChWf|X; z3c&>bORcs~WSa#Xed=azWQhMSsZB^bkAmm{c?ZwMwoPq7Lv4C}B`rPL#o4$2Otb{k z!u~_c_h>_Q?99N|ey=b!Wm|%LbKdcW?1}{Wl+3WeyBs_DyyR;h^C%9iu@|H*eO4HH zn6@&=^4S`yVOErI{}>2T4W75YPYLq&^80hRHYuyhart{4rHysYF^dO`zMO12nqj+v z+)6nSOr2fY%Y^*E3hxLjgz>F-X{2v|!9G|f)ugde?T>?LTc`hMkGj&e7PL1yD|&rz z-1NHBnDM#)^`oY`Z&QbC;%)dds~^SMoJe^^oH1a1Hof;dKMYjctqP;K5PP?zbK8mO zCxU#Ewy)L`l%~?RnmR#k_E`?j*M9nM2IVO{s1TC$LhSVNp?59Ge6ySq`k-|g9~Yil zwBL5k@{_bn6?pj-9vb;(>cln*KQgW+#Ob{X|72TtgqPe{$yRwp)KR4t={2}LumPO!8#zQw1Lg0@E+i5V$!I4 za}O_^q4lY?BHXHw5&P8AwxwkOgxK&!wWY>;NZ*N47p#!8$aYAcZA>$> zZPmElK#Um7gi~2!DNx=`$u@zwVWcsg%aY?3r7(oFr z%JJXW6ZK3(7#kfgC-2$5BlKM7ecxlYa-Gq#VZ!ZB>l~*&_C57B8od&JvJuFGnE$5r zq9n1W?e0$$P#LbBla|k%(1~6U>8r9rwO*}2d5Coq?oQiqxqb0tlf%~qr?X_YY`Og6 z$&o$tR!coQ80W6XJwf%m;OkqC9%{X=Snh3yosRYM)7(FfJJzd~=him6N}8De9ynI{&YS{J;^smT z7L>e=;?+PV9>%{pzoGfHMCzaQdg6;+OGuU!rOJ4h2a5g@-DG#X^SM_Lkg3tCCN&cL5;zsidACrdfis z`v?3gKI`w&-}s^M`EYIIMUeqMi{mLSqqlK|4uDuNw+)hzDV@yk&Xhsd3lhaH@qu4b zH?e5f)|y$|DZpBlNJ)18^CdJG1DCf`m;>RaaXi2UM%d3a@EMFOgXh$>WJ|Hs`)M&g z;BLr#a_41c5IqAmKr8_f;$Wsd+f)?NWLg2-*yoFWq+lZ=rskZ7W#cugJnef3pPuv? zR-(O>8gioOAko|o@=UYQLwhjG!%bY3eUUp_@xn8J-=ZTV!TBZU2xXxSPU9NvOq(@f zajLbI$Z4oPW=vy$%vlT7P4mSdZV?>6cvaH--hfjwYE2} z1u!|q;pzLYCGdvr1GCsQIZEAh?~vZmtGML5j~@0;M?^J7-t!}7cYcc*l|$~h4s{DR z=XOktk2=~YjxaNO-TQj?!L}rGb|Kzd+41eQ>V`*+3JoXFZjZ&@l+tCBKQw?WITZWT zF?9+E1W(3XX3njI{mAD8G2-+cOp)FWO808A%cz<-_o=xgZ%`4<%fbbI0rz#Sqjp1ZkO86XQWfK4`JD4+*opiDP<)w zp}H0$O{}qpX{kY}$Dr!UJH*t3X7gVYkF#xE-k$-BlrhlUQ!9P$?u4f>24j18x6Hx3 z?;T|zAbjzAU<~OhPv&UkTnMjbhTxWw)(lXBz^s7ekn72Q&52w-$YAM!6x}8*f>fBT z5}i>@2RDt$aA>pC^b2Uycm{}+k%FpEd1F~5=f!bo<$a^{@OxUuE_IKg&lgO9g00R& zb>OC1PJnC!^Bn_3eUk!25$KI<4zk(IHs*z>kD-`pj)g&9QmJM zN(U|*uOfE-T%`P*qR?dYqanO@So~;@_2;tt6Z6G+x4#T*`&lr5Ehfe%Jnxs(3m?D% zi|oKFG#dahb)YDo%wn!(o{-lOX2O7H)W^%|z^aq%5FNONH&74T`lf$^*ep=#Ywth| zNAXJatQ;+*sg#2_xWXPcqY~nT>?0{$|NXOFjY(3H%sb7>U`iB<<@oX)a!Me~)B*)( zu?TrD@@Nm@RVr!{b35nN+OZd?GR?lE)5G1U{??`othp$6{(gaoQNd|{p?v^<1JroT!-=U zEEJ#mC3V#n_C#MYvng}x?+tnl-k^U&>m|!Kr5=P+lTGC=8tgeCaFP8MeM$F8QAzE4 z!=OKG7w;*%c_fXsv(zIN6S{8eb5GG-^fj}#69`EI#g_F*r3gz3h15Fe(yw}KV=IWp z-r19fAc?r=hNm5zlTtm<*M=^GSJ|(~MIzhh&C;ufHfxL?%-XLj-bThyUPyR3B2BUB zFDY83K2Y@GIFaabW6=)r??_^_$d0t5pA;=pN7)ZMO@!RqoGB{39ij`2s*L6ut_}$X zQp3V=tutBZ+UDYs_ofnE0X11{8+~uwY`SbH%;M-OpvswZ=$IKY7oL{fvN0gPr7R|! ziM`&T7+^>JpvjkxsPPn2O$)6<3ObARp!dZ$Vk*WR9}I>R_$XX7aH&Qp4!s{?8(OJd zHImb&p_^*r&~IAh#A!#1=`d(CF4cp+ZMO{$Rs`&gAn(U#2x);`z9_s;BlI?+#)t1Bun9WKhPsh_Q|TkW3z zAEZ|Nf%|JcIST=&j9>rv1U%avjSz$J=%0D;Me_wkLp4({S)k^OCPa)R@y$8gT=&iJ%U3PZXK@!fx_h>@WB ze0VsPyYKLBZBr4z8bO!B3A5M;xX3r{V)xk&<5+acYcc# zy&+*qSy-Ikrz*hJp35&On=p&XFUWuAy5iN#?h>LU%VsU0-(X}B#TL#{3$-MYGJx?} zHmW^_cc0{Q9iXw4*)Bg#+uo{AjW-1o`=cZ~UJdl>83h&ddsRZpukL+2thIX4cV=78 zpfeBK&dkBp(5Fpd>E~xB0T&1th(UZ(dAQ45A6Pz`CAXNT6bB2Q|B_nUyp5!a zI6_>$Ym595#pM)M??6;{qKz>^sU$Xzsh`xm^}N%IV3#rc4oX~_-6LL zc};Zr@F+F|^LR|WI(32edFe2bDWJCzvY1Pn0J)rBFr6t{Pb?i(%&5g_i;p5P5Di48 z6!K@}FAFWu}*iqKFt&Hldt2*1=u%75JS}kRPIGG3Fh*Zi1@Vyp34XZlj#q zLSBp%RTA;M&JI@vTS(*tHJ8v4;}%1~kQ`UhwgS*l>`FDU(}!>mUr8DK4OjY?6!9iF zcL|&@^q_+<5y0nM7|>GP-jFZg2I=VXXwgX!&QeR6x&rN?1kGGVmm^my#XAvmsJ?}} zillRQ;v~D1sz7oF^RXpg8no#p#P82<-uEIlvSR8uwaK6E8diN7INv}W93LIi+taoy;!6Nkm_MfN3n+4 z@2&*xyyZ9UV#ZX!r<#@2GJ`2O+}iR|;F9Yrx&HtcM)-}~0=BU%R1vt2GXtQmeTZxH zC|7BCLc>Yy$QGaAGi5_L^kh@2Mlfd}ivV!|%-675L-nG-&yqh^_pChFht%*%eDe<( z(Tcncf^Y4M<0W!-}OBsl^Ht6J3(dT|1p)GehbeXTm`V+d$8&e1*es6bt zwYJOadxsVH$#B2B-Yd7H(7kT7uIcq_SZ=h}bWF&yw{EFB2CSfkpSlj+$ESUhwk>C=OI5IjN5MvT&~fwxZ}`Y} zz0&fX+(e2pGnjxF)KAOs@HrTHs}sQ69iWbvj*{PUm8Puxpn7lU6WR!U7tqD4;{~Y~ zqjPq2wp(1jQiSt^xKn|A$;p?aHPG5SGo53nuP5Di)_M(_`5MJKyhG+F34pU z*Miq9B!kbqloINT0XTzw!Dx}M>l4TmkN}o_VKU5f^aFu^aPYH?V!5#;GlIA;uCl7FU58X1L=U0ogBJQclnd zq)}LVswX(9DdDpTn|n8fVbU;Cv-oYTx4S-})DY&kNA%0g>E*zW2shWzyp^qBY54pz z-edl&wt7y5AY^pgpS=~B!1OZ6-T&>`&*xC@?|a6HjoJ|b*8-o`_k$wr?8Ej1+cnOZ zq_I3N(ON$Ye;}u(!u3Yy&!BD@nu%7?lWn*YxO6D==)(qm^HvTo5nc_eX9LhKof87tJU(fhu#(hRuh4*dU#4H%|A3w zEqP|PP|LpiaR1gaOkaU`Wvg%no5?H2KnHP&xTWa2V-swOM$=g%@=kG_MMr7uy+6mf zD$P3MaqF9*JDQD|S0bzy@m~mK1tH|9jPz4%9M_A9FJFoZ2@Rc zyPaq`rDe5QSh4;Pn#(7onz!%m3N{^Os-zKg^!#NJ?i9A@&0DC(#l$sVMe;Ir`c2{~z~E^(~gAIE+$o5PW2 zJs$VC2{IuYhbc$u`!H>|<R-=YcNZ77r$6Wo^u zG8dGt6dUbdQs1-b6U|IjN`x!26D-1H(UM7xoQnX;h{C8g8;`SHY0i*=@b|v;$WvHb z{=_DcWw1{_C!&$KO6y|`Z%g0 zQXTdW(wPEmcbDvvhA|bpI4cD~S!htlFwaS?rvP@1?PKN)6nkQX%%`bF#HyL;0p+i)}g0dI+0m%u& zVa0`+?c!m?5B3#a?P=alY~-6=Gy@LMO2A(q^V}N4YGAe%0+;DTCrP=L-`$V`kGc_S zh;h^R0mr*PFzkBx%{{B9)s_b2@r*-Fpr6?q>i10^tv(mU-*q%2MMvS|xck8(r{9~_ zmX${AI1+j|Wc>5#r)J*8C&o`i9@DZ4msNjYW07ZZwNhn;hw70$*_XYQPRHIwQX4+< zH*GFku1f<2P#cB|xhT#XQ9*lyVgQj)JPIgye{-?jLvXxO3KWT*n$-lz6q5(6rzyes zWrHMk4D+#Khx)XYUHoFoq)%(B84$gI29L7F<-D@O75atJ?9Y2Q)Z1;!$HqzuDZDW* zqRVdUUfT}J$oub502o@3ty)c=WmoWUobS)sBl6Iv>WZ@;RHxzJmW_0^*6G#lp095= z)a2)tBY~HH8}Te0i3>{o+wZ+{J_y+bHMVl4Q6(`J!SXe^N)!Gnh%@w~g(*)my@xPC zAP+8MhJler;txUtS7O2K0QFIM*v;uTDK{2#sw>Z~a-L)t^E%c4uz!{Vy0_cJ8rxoy z3*Rr&0mi1xP1~?N-Zk0sB?r5_-_2DS&2K91I?@{*ZV_K7@8H^Wu{X@O%QC&{>xSs7 zdRpq+*|!xlap9ODE>CyLfZr{}75fs_A~l^#J`Tk!K45hjqd<*+o_gQv#Xw>kdDqpq35w4TzeMxrhm&8|VHfB3S}q){w<1i2 z9nj@i*qC2;y)qWA9M$cZ84Tbvr@Bf8ovsjR{>Ws4$X0B@HgQ&5^3c)9t!+Evx(Cks zaP{@ZG3WaJs)VzM7b+Akrxy# z)|3EWRo%?5O?LcnO6!Ozgk1T`n5MKej1&p1_1Ap0Dtu8DQGJB)GCTlX)^|I!*a9&S z1(RDa_4sV^2JtBb6DIH_Bo<{F{mJxGg;QZ4>Bgh z^%c|b%ftyGKo%VpV@A09e^K}DVKKIU-)Mx8jIMQJ-XAvH4G zq@ig>`<=|KDC(|}R7i%lXftZsAQ^2(6U`23GrBHnx-^@6ot|gC@9}%z-|sorv(|dv zcOC1G4s(Q=YtHLBzvp-J*^F6pZ4%X|P_kiVRYMGL-}vRu8_FFF`|Tah9O!m|;4jc3 zIWRb=CF4C^$DwiI3`eO|+~as$eozrpU!6WSz6Ws@8hL-&R?S8EcVN?Sn&LG0hyeRaz?po)NTmPH9?S<>an(pN_0S{5uKo&JvK}%cCbywY8tFc)>7UT9#YD}qiTC8Qhm-NvsW@C*xx0AsK-9Zo_T@(k9Nzr5) z6FNZWmkUu~EWhC(2*!?!pmK2^b1_?Z8?%glY!FL-vWDSP4+)Y~EB$HRg%~&~C4Q)t z7kw!?qYO!rJNq@JhR-~&o-s%~*{v$18g;Wnx$n({#016qJBm~9Kwd&`<(C$T@$)EE z5}p+MfFe@(MTtoi+h@n$;WkiD!Lr7Olx)J&Cz~S-ju$XeS=I7jheJ%yWb=K_n>F~; z?moSzq6+K}^w_$eWS#HwZJ+e$JN0EACpt8CUQXSGp34Vsmeu9#y{i^~=;b_PQYu1* z=_31fk4^Gm#2I!L3EDh#-20Yh-ya8JT9G2ws_=jd4=istI2@N1i82WXtE>Cwmvq&1 zS3S+!aV_%fJQeYV#i^k@P@bYi6{MD7Ll~2xz>2_uPWd8}?}t1SD#m9$Z&Xk?-n|z2 z-hH57sTm$UbB0KXpCR;Ko1Cn$xoI_0UFrM0aGTI)wVL8d6~G}7 z4pOJ#O{}GrNl#SwI<#HS!*}}=$6Gbh54w8qG`xPyK)C}cfkrs=6||KgN8KmEO3BD> z+(zJ-4iIeAL&~^{h!8^_U1SKeQ>MWa#(dwjw)>H#ZKk@fs<)6|7INIMb^{Uzv&1PJc7ked??^nT;z5C>+W?j(MWy}Z*>i!66vJJP zRQdGSkix%FpE;(MHtsWhuL`Ig;L!9HN05DRN6QmrTnIcFQ}8Psb!l%7tD*+w!Tcq6Tr#$7rj~k%l9r` zGd6uDt7Cpm9p8$6lAhtczdInXjdVJZq5WW!GkM8@Hr?=5o7)VWmz-@)&G?*6Z{EN@ zI*F{Gk-EywT;#?Gx>YrwoIzjKuOOWRzGpBwlT6m(d1p?q;?H7js@sif%^u}~!SY_7Xm8P+fqi3^Go-Kk|{nspH z_A~xg^SzFGLj_=?EHjWmh8BX^a{qv?viMHA`6Q#40UFOm`0u~7>rl!(jbAO^2chjX zV1)S3mY^V^Yu-iw)J#8(0j*&^m<=B6$RvTo=bwa;FaMW1=K(eUaPqH#<09#=^G!ko z1IN<_DhBCi3z#c?KF&tW>cF*U(x-KmPf4>*{}P6Q8gUnzB?F8K1~Cz|0=7Z9$TbBY z`irbm%bf6m$7M-KhTKcVrCBR;uuZxV0$th?3PYlslgGN+qz-%lj=)$khNGD+iqvVX z^$r1MkqZ(kNCT|9#AgF3vKxJQzG)zPg!W;2`aqKEyv-&+s?qQ}F6|Xp3OtBAu!>$0`mgHeV+ND-HYKj3e z?Pi*BU81wZC_r;ObtDeOjLkle?VET+NPL? zq=}5qi5YF&6ihWClWGX<;$685Un$A82K)xlF63=o+r^b_TS z4zTp?#JRRk)*v5BWVPa}N>ZdN!KH|qi8U{)1M68R*D^||IGGJ=wy3c0Shg>v=LI_@Uo@U7W5i(f!!Cn=L)+MM#w=)O*+@FKi zhlIhWJ0mw(K~$4x@9t)|IwYlVSo4wk7O|1L2W)!N+dXzqH-FarQ{>ixkCvbQQlELw zULPeu)}R&3HCy?^Rbr1A*?a``fYh|QKT7LdK;O6Og0sH}?Y{MSU?$)6^f>R({+Z}p zoglYEhxTV}S-e0^sf|p+9io(pekfXO-@&?~+E@vNwM-_3==CX%NU++((@|ADQ!nc@ zN#=VKg2wU&$}hJ zspwwNJnnK9no0d(bKxgCYC6^}8b&3m3@JpBee+~S07yLBGRlWKGc4`JddV5^oh z|9ed>tZQS>CoozoXsh7A(->RI5P7I`hIM)rTDK2tp<4KulvC7=cSbL{bba~DCR2TX z@6qn5A+4?7d&K5)?XuB#&gA3gBBS&^v$Ay`e^~dj5;X#{L}4!zS*vXR5rvBXTMcdN z-rgBR1e)ZoZMRrfUoM2@h`;QHm+b8&m^buPpWTybUOzARJ?jqp1e@QfTAU=0qL}Rg z`pqY}>a*6il2J~i235Nf$8NaRCwOEB3)nm=;#Z+vIF_?a}+NWzaSXfR4sUb z-(9KD0{PKeg(DmmPYo2)GYYld*;@J$k!upH;XY&LMY?I(_TJpq=OweFxsg$#f^`|4 zSX0r_^pn0jjo!QlasMjlovd~;R5b@gtFoLyp9=COul!o**i8?!jOJ7sR&2zdoJ57| z&vaVel|7H^w#Dj)XGJ)~cHQXu*f?-2c70XQC~w~ed;LE+)~-6+GwH|AV}H*1Dv&Mi zWlv=pC;-5a{i#MmmY4jkHhPyvY!Q4v)3Ez*wdeM#o3;QS@(UKG0Cma#UPN6(y@=e- zO=KJX4)^m~2&p0ZND3M102a+EE$Ytv7XDp>I~2UQ>~dI5EnIdv>dR&3V@f{99$owA zxWTcfg>T(cbAHoYJZz=6} z%O0KaO1PN^5=+R3fS66DsMJAQ`St*t#ErWYHt|EkG6+#PK=gKV?yAv05zSVbL+_D+ zZ&-!--SIbIOV$rmtcHdgEo5F#uIcP+I=V$&0w+iqU=$|+ZeC(;x&iA2chJmR$x`g3 zgLsvV>?<2nE$+o_QmjWju*pD)Ap&ps0i<<4rd4o^PtR1a8@`r9x(g28@a%mLPWS1L zcgoi{axEI3on0GauFlLXtw?S?1mdhqdhZNN*{$rq3VC4beb& zvaY#5S_`%Eezb(F&RV5zsmeHe_Q=^U)T}L9zhNpcRXl|*UZ$gbNOd_YoE)r@)hi2W zw#eIox3O;I`BXg6*3*hTZ7YwOQfT*QjXu z9@=|MC3_-8B**9g!euqSS#UWI_b69-`xQu7HIfyWSn-LTwyFqUl}`6`>@I;n9Q-+b zIKZ}_kz!%%{!)MJ!>hSz#g+)zDjFtWSt0@ba~XX|AfhKyw+ONP8JP_tmG7YXD4Dno z2%a+=k|r7AE+1zaf~oi4YQd1+H`d+J&T>j_OQAW$&4{`<&C-a_iY+EtkLqt7)3;y7 zf0;Q2)@jvzA$cB>pjxy>eij)If$&K+SVM}B>Ne1fr+7cY_uQLkoWOw zoLDns-?XKHk;jC^o^t&g7s(55%k(qai+r>WM1Absp-) z9$mV&p|{5HB+KGa@$yZ3O`g`~Z9L#$l63ajp~?kG7wOHYpKApR=@YpKI48Y4OdEIo zgTD2@i-fK%q*(r*_vOn;FjyC7#bl&?+WUL_!UL}?dfnF?7N>9MFWhHzLtD3M*==z1 zZLk?YiWn$LsWfkZJ>r3M%73SIfmgN}lH=TKZ_L&6Xe+ z%f?nO?mW1`+Ig$R6A#@7-g`?jIqR;yyz=tv*rC1~zwcbjW}*-aR|OiJ5hIG_C<$wV z2SvBzHK&IDLHn&TO0S3)Up*D6Av*m8z){}}T(ScDd{M_$aI>^W z(fZGmi%K6ci;&QOe#?=k4e=fBeV6g>>zxo!|^3Ky(>w{O@zo}l?8ZP*gKU`8U zEq(BzZPU=l4=u~X6s{mfk!hVxR?1>Wq6mYS)1`G{=3Hu!vbf0tF|x%D0`@2eh)Ny? z>dL4IR|wTEJ1YnW*gD&XgYM_T6AtL*|da$wq2r}urLpg?WBA+P> z9*CreGY4ac(9bME+ga)^*dQmwOs^n6G3t89A4XwiD{cw2PF*VC)Rq_z5-m(UkxngHkv?rcVX% zesqW?k#%9xqT4F|7TUY#_7i6z2l`O48=}NZy|5EeQLwcuaWHWI(^xmUcDTJ=u9w0$ zX??d1KK)Wbi7Z=$M-DUQ*Iqx;eXKIqx$Y_2*pu{^@H_opY@{!pzin)LEGpFFV9h1L zk_RDYK9^R1$@-GjbnQ~1aa8MyB`I?I@y=yne1C4!$`He`sT+Yu&tm317%suNxqF6N zT{IunxUb9wrxZYb3JZC7$P-kP0uCXCw!OBHz&8;~+d4Y9{OF**&oNz>W+WP|q(Jj= z!P2qW8ys%bMLb8jzzE%p-fG(?>i>*2Ru(G z3ye~~MqVM;Kssd~;E@UV4h3ZQS`g@+yqM(gqAx@81pW}FnXcCbiq{2!$WhQJ=!BX| zv7ca6hPVm{G-0<)P7M+Dk@STFJ7TIXhMkSUWt7ZzJdMr%KLJh}Kgz)4wo%Ez3t0l_ z0GFRLE(-;~at#sjeHD<~(1=%HD=0%^L5tDRFF$1Ii0IxJ{DF^jh@zC$ewR+v7u6*7 z-{>!Y9~c7AKwaF#DCrJgctJ7j?3?xDg|U&mO1AJL8LdhoZJEPypQt2n7F&oUPP155<~(zbnHq~nKJLo>3!dK6oq zJXc=*)+@=4Z?MIr?V7G9ekhh+He2H9_btT_5p*6@g_e?tqb@ffo`tG(9EB04r zwR9hP5F2W^>Dt}TXy2^N4f2hz&w+n_{m4ce#^9x&3|X=A3jR1@DDXwL!cO@B7zf!S z0+DO7*n9<*B$AUV$ehYxvs@>zC#b>UGCZ1M;&<$|RgFmN-0uG3T`zMVJbIzr?h>QX zKsQyPz_Pl4W!;AtW-&1hT4Fk4!tHpfvZl4smo=H5G>C2|yeG<7@bwB2I~j+8agAh+ zw5HXFX1j~FxEJutqM{#!j1}tkP6^VW=F;%95wqBiv$BG%_;HhQ75#?p0gV=R9Si93 zn$!4fEyZ>Xsf{gRVHXtI8V-OEu{^@tFAAXVqBj+`GE&ggNr^3xk-gM$iAbVJCq`uR6b@=v3DZ`(8Ev4 zB6`FI+G^_N9uaJpKo^w@v=1ZJqLd_@AwZet)pVrPxGNnxT4})8lo)%WZfk@?)CYrF zTkY>;Y1GuVdhYm!}Qksia-EgYcfWnZx21|p&E(^ z7)mxLz!li~X?07hKcd`u-=>GUH1}85^qpR-hS{ll$A@W3FH zkqY{WQ35n2MYIGD#=a*{I&Kj$<9SUu=n8E<{E)c#e43;7eGid2UYFv3yr<^gYW82l zL%|?ExiE zuY91?%G4w8k5>i-@`Hjt3i5e1Nmj)H9E1a6*L_+6KJ7{d{)Iy^L5xbfY? z1{X1TvB!f^*=$l_X)S=L?(l!-=n@`j+n@P-m~*5&ptSh7EYnT_^nG%3vQ_3_^_qq? zpj-|nfev{QlZWTLJLPC8B&4vR!(3zoHcp`@%Ct0gvgQ;nhnSfPU^jejXK(SfPgy&( zOk8^>rsOsk*G1LUW%>`s9i1cp>_Xp)Tled(gE!vmNPxhQKu`XgVyQBVej*!+RxNra zcTw?%)eztin!(m-h`9qa25}f#XQ63>yHrQm0dzykNIYNqGAUJ~Sb#9vaaj2Cmki<4 zsEWYfppit!9pdmJSa%j*6HbQ?vWEf*NRfOF)f4%J%UXqy<;&>JE-GNMCuFo}nM%Zd zX||If5Wjd{*E*T}e1@UY2Zk)gC8GiRnHs06u?Ehf`tete04;egkf+#kJ0zUNB0&^} zRAp49ru@pWiC?-?7v z@w;bZMSsO~gWm zK(sFf=dMh7&Cy-%vma#3uRx*kh=8KC)6sO&5#h-pnF~lm68o(_A^y;KN2gFnyob2h zX3-;~4d5#;IMM3@LDPW4fPch_D_n{M3sDJdt%mgwupFavu!(%XgehNyF4VGa;TdaG zLCoYD!@biU_&r38;K9+E!WDS~JH%KU^C8V0AGg9~)eSZAV;heiK`YOeYah|3L^@bT2`;dhXZHaou4=R$^39mFCJb|{0NFJmCN zasYZzW=&6CHXyeW-*-wFnKY{cv@E?3*l89nhIfZ-Ku8ae1w)=XK;@I0TEoj4oDOG| z?cxViIxp{_v=x}Q;6OvH*%W6^^#%16Kn+Tw6}mv#u32DAk*gNZfizo)l_Tl26{^k< zBbbq>(g9dm7m*Q|msr*q>9i7;V{Hj5kz%pU3_s~nRVgJup!Xqwk?HrHXIp!?*P9Oo z?{HR|p})tf7DdaosHVzv)nbJcTbbXqfEvJtnJvuuNI6Jm0V;WkLI>W_#x!hNP3h!Z zFkTI_uQt%dj1^y>50r`PSvuphr6u>AjZ31=g_a!-8XCQHsPWM)Qe-41mEH_`8ljf- zR);c$j@5`TcfwEzI=~=(i&kF2t);r=cHk?6D|jzZC`u4@Q-d-O=ta{-Z&O6vh13{W zBbjD6u1{Ocvn6PaT|y;y{B;V=(RcMs?->&i@*K&7lR}~b9V*)i>tp#h;9I7LQz2 zSpekhQhdBW-wFY!XXxioU|om=p*8Hp!%s&qD;mM3-XvPL{P`pNJpgkQBaC39K9!@h*Ux$~!1Ns$yErMkaC$SC3h2$labGzf;#*#Z64mz;X z%bw>+e0w6_fUXTuY6d!MSY?LFR6$mzJ4QhnzXUIEMpOp`1DCfo*(-|_XcTBf)N`It zk0Te$73*MTq78n*hp$ZrNEj|fvSEs~YMh3F1HKO1=kRvdWV;6Y_W1TsOcei)`V-8s zK!e)smyqy$NsnqNd!mFcC-GIU@sI6pz&nJ*7mYD~kY6HgDJ4*%(N0eT+8IDS=OiJu z5R3eotJ3eCv?0$yqB$6?25rmSWJQ?xrY&QcfD05|AJ+k?L&f%4aK_GiuPnwbQe1!- z9o(e~N9C;wd?@n@W_}Zn1Zq7%m}3uqc61V2u9X8drL~YC`8W~nAx0&?+bJTZJtt%~ zXrYp<9I>!NKXr0BLU(^sb1Ye~w6@ZaJ(a zyK&Kq5e49WId;7)(z)--Ql<_f*{*;}^V5xw}g?u=z4AQ49AX z_#-jHa0@*)oOC4Sl4TJ(I>&B#NN4Ers6TYxKCQ0G_31laQ+sRJ@S?3IFG>5+$X1#o zZTOOLrijh=0b|S}BxYZ4wG-MC9%x*^ygRDzJ9{BN6c`}IefoZCqQWGd&&O{02&T!Y zD{m&Pw3V9?@V6Q+)^uABLmeS0OZ+yyITxKS4q4T*E1BpKkbgCFRj}L5E4%Oa zl{x2KH7K!IXSVdz3A^)Wu76(S>BE=|)=LrHnhs>5PSzQG2=T12yA@_yv~o$7W^SBj zHEdgvJNW3>%dD}k$etbMxj8-ddE0!$?agHtPRCO@?^^fhZS2tAl}YKX(D1C;Hc;YL znG`?{i2D7(y}pD2>np|Yls`06uVOWm+x8R$c(omYkefX$j~c63q`#J^{SW&C1+{ zSQr4Am=hy%S0qD}n~XD{aGP=ZNS%BYrT^P`AT?uGh=*gDiZz=*91hPWZHdr#&}#pa z=a7;pow%zs&X<)wMZCZ}ZUzUH2MEnR^i%Z3d=;0u0Dqc}tZT}YZ=@8r@+Z&%ejM!J zh%6g6%e0x~2XJ=7$u$r6R)FsK9=N3N0o7dO&ee0D*FESQt9)i#!AxoJxF3%1(VLub zm#>TnacV;D-&4I5ROx}fh?WoWDePMYubmN^wUo1r$8a0!>+NSk)-}P2D&6c>`%B6$ zx#3XeQHfW!SKdVaEh~egA&uo?6o-+*)NfexP=P}L26h2;KZH)L0BlO*g-Dzz7S$fI zj2G#>`<7)5a0j~D;oV)cZTVrzjU$bfg|^qUTapj0R)2Gz2{=8R#5xc6`a^lI^kd)(R?-L{7fC4-I z98~`K%Alcj0xn?Maz*rsUXZnzAkFevS~Jn0!4H6TxyUjKyNtRP*$9n@MpA7x_XQVBuxhxAk#p|- zrzfJV=T#PdcRzf6B3<`4T^H~T-;-qKP;Ps$pDZ0>Ph`rik!XqR*`u-Hjr}UyuS|8^ z%5b_~4N8|1KlV-DiZgu%#n-89z zELA6fN*uuQi4yyXY%wa%z-%Hxf*%wJaSsG58=`fm8 zB__&i)q=Q$w(yb;QT@${(>TmWst!5D)g4B~o92rbG~OMXEiHaPc`KJcekVPB+Ty%Z zXsbg=ZY1cyPH4IZ2avW&{u&JW=o}XHF#Li;LK#2MUDX@sA3FTxZDd8+wI!)ZR2=sG zxNKj7B#~YH24AHy@e?gw=~ze=80!c}IQ>ZT`d3wv+gXIxvm%jrvhG!Bo$OSd#21}4 zT4b@VU_!Fj@9V6Z;uN^+w@(6Bl~X_u68~F|P1kbZX{pna_g7AUM`kVa(X|BxmhirBThFJ&gM<zTj-4? z+Xmxe7hS(^8dFDpf*S4r4x0DB0whga{Hrq^A4zX^Wgo}t(fVCwzbhNSD44ti^n*+3 zr^tK_*^B%ZfiC_i9oTrl_DNJ{c%-#K;aA+rs&8Vk?soB zTi+akCXl+0A8Gcch9Gqk{CxgS2|ks$j2b15#UE@kHtvtQufT|Q_a3XNt?GYhyW*71 zWz@YUpabYYKb^rC3UgQ1tLCftc(o>D>hA;Wqf9X4t@LXvz;`e!327=5#JMdXHvu?+ zQ(a(JMuW-SQ9(61-b!HvNS1mLXPdG``5y_b-x0c7UCg5Ck0<8qF71~^kyXQ3I?&MJ>+T7u-RPc&W$O{U@2Q?22p3;-4^U}lCm zZBbm5ed(*|=qdcTb2dZf4sLG+g&^xj$Q9H-K*edQ;>`foK{a=9(gztVQh4{{jFDXf zJb;y$^4Q_tXUiU(6rqzN8)7et$*Dwr&?xWnYavIKjKb7yeGL~ybZtuDPI!Q2nKkS- z=ZigXUBdFMf#_jA6w?mSCiJ1uJ<3wt3hDx&unvl!SUI2_EB^wY*D0)J0nk7^0db}h z;nU*>kXtal1F!qAVdL4eXYYRhG$6r4Um(B6mWn zWJeIZd>0tmqo8{6NPL5bkeTU?{1D>PaGNRYfX984Hkyr#B`+KXGT~y;g#uw`Ch+Mz znsI*Yi2XbEBB8X%c={H><1T2IBoVeQ!X z+P~F|dch^%q549^I9nqV0(|4qt%u-zV#6WaU&wJjDUWvl0fKMM3ZJQczMC15_4#<- zYyOWBJR0OqQM6_LBtcC|B2I_)idajQYDqBfbHyWVM1a+!r9sRWc+_bN#T*ea@zMq6 zeG(pRMFmvWG!+Kw7bH;wSmHozvP!qMA0sZyz*5yE`_@Co*Y1X&iQrwvln^UjIc&bQ z$gjm5KNnDk^?~8g37C;GDV<_322ctNC?1z7^xmCP8IOjngoYVKU-P)hj_$xXL;0m# z924u~$*VDPZLJ+%+cSZEIPGCiNa5sokNOBA=$^bU9c?UR6L~@c4av?cbMYq$nkPYN zj@#BHKaIaU0VZASc=kf7<&g)9aHPLnu@h>b**t#Squ4I?h~^lWB}Y19MJc)dNQM}* z63GTl>$nQb6Df!L(cVvcZIrt}iVCt!TZq*`6E z%qi?Lit9c#rS%=Q5!D)e&TRxEW@rORKt0wA2H&ky<^rfcO+48jh?o3g1bF<(s0o3D zo<7S=kHXGHsQ!oBU^{xI1A#--r;P7sHskb=XJSm^tZQ5iWBIGo8TWS<1r3e&sM^kt z|2AJwF%*u6yqHEn6adh~W9ZG1(@vyA@P5oPM(YavF?=HbQSk-bD#doh89xZh>d37Dw zcp!IzSLmC|s*!2=*jAKro_Ntnin_ZqP!aTgLSnt|#P6j=;>Ncsz^5=cb9 zpEBFnOQsk}!`d)2B`VlXW&R0j8>opTE#{IbY05jY-k-5Et$4%&$N_n100x4++>*Ll zd7Vv~%!p22uo)ra$zHl&WU5PX45Kk_rx_~+;`8-pzEgV?I zgzfF2y3p@ZJ z!ze+ec?%lmirb&b9g$v{sj{I7L+i=w;CwU`vnOEi%WRQ-{5PQ5)(g}{x!Pk*D;NQg zDf2efZQwk14p$^E&^$cQ5LKo)$y2TB6tWhN*74ixEd1>Jo(DhHm@sQ$CbBhE^Qkxp zs6oX4l4~-A%0;;e7EDj5>~a$ZIp+qIB~EE>e^l1=EW`a>bzqL!vbyBD^ zW^m*4m*NeyA4&3q5kPF1`~IAa0ShKt*aqz5bkG#{LGHt5&F(^0s#mxJ z{)%b^ZGc%tV;&b5L0qxjEfH}!lHE*SrqZW;;~nl%Y!Q!du+0qP3~z+cnYJ!9kvm#1 z*zp`(hv`p<(q9s(Ls*%cvVa}ISn}Qo;|^M0;D2i$d$*r;X4r55D8dbsms9+|6>iJ? zP>6;N#+oo$aOmMlj0frFIo*4EHG2Dzk0tci0SZurANxZ9${7@jH_E=EFoDs&13*fv zg}7`zG;>WZ?XQP*^OPNFLiwEjV|D!&j~Z;rWf2CR6_NK&`UULH z&4Gdf3HQVE1fO%`rBQ|$WK-0~9M%O}tpKjjE|c_tSiYm;TQONoSi*_9P_fq4L^J<- z&hK1PTM6rDpU^p&GO;zt*|PX$ZY|IL z=oe=iyz^(c+|9qE3>>g~0UXkB;j!m=hM2jO2X8l$SynZMgQ2yMU;3JTSX<4}M_AUQ zf&;c~pFg+G`1zm<#o;HSFDN9flRuXqr^~#L{jFBGL6P5%{b}|GS#Dm5QJNdj-gWoP#K;ggK*5R8PqvJ2wvL6nfX#I`oW z>X|6hTD-4Y;c-OBS_Qb(j1(KfDvH&lAEa!Ld$@y?hpUf{$R7RSk|Z~#bpp4JD4j5=sXl|Jyf$yzr#|G?}ch6{mgX ziJ$jM;x*lGmQ^d{AfoK2g85l|oa&9Sg|bYsUcQ&c1waEJohZAZd`yoZ4(%eR{(=WY z1c!<7sR|vIf(GNV#ZFk!S}ML#$!yTpNVBlPR(=@#pxBMGes`>K(5MA5?(N##A;00G zMq7#i)tN5fg$f4dpTQx1MR~Jk5p^zYz3KzFBWz(QQ(LvLk)1|f2YsR$DJv{&%gIS5 z?U%|=rs3l5t;>bzEnXzjk#f_kcbv(%;_3yHDtlEk(FkBUewCG02>Q3|W|KfAh$`WP zK{GMC!#P%LSWemq%5*;=`U(KLzoD3lY%Vfe4e}|BkS7jK1$_>?qxqD=vA_(!E58A7 z@c9E8pJyiS%hxFU5qofrps@;Mu=|@f(-+sZ2!^%*oZ1D%G0(H=Y>fCAri%T-h%TyY z#USqI(`<$g+jtC{^i{kCLfftfRXx3}xjwOJeZL9r*?9ei0jm7}IU-II8+_(}Zc432 zWB(T$n9unsmxt4ahAosi4Rnx#tVKeB*4b`lInGkm%P(sS#=jU=M`9&lc+b@OX1N;K zDbhr(qOcD1DO_P@+wdaj%7M~^i%xCT%Sn%eJ~&;kmBB$lUF3Ox<+c%!4XzpVFw7vS zg_g6#D5Bx8G?1-v+d~%j zIck=WRBPTHADHUG5Sgtx29fd)T}QJ5Z5s-h(i-_D#av~vEjNC66H*M10AK47Fj~2z z@P;*-8EI?{mCY!9fg(oK4@W0YF_uvG^q;b-$#C-9&#NAaoIlm+5i;c4T@$=?K;XT1 zI%c2cwBA)hI=JDM&TRmc0Yn|vM@#2JBv~n(!~&W`6hXV7W@aKyB;fDr;5gVNkO8H2 z4e&T<@PURzWm4;+ud_?+LRK*R`lJ<+wlN+Aj=s#nMzsm58k;@l`$(`_W0W8blOF^| z-UlE$$0OPoYJVo->MBlv$U~N$LHvantq<@0$8i?G9xtOUXLkJ^*NgXG8D-|&5aE{$ zfmtgiom^&78=?bUcy|5)cX|43A^SSu#If1|9~4M1066<$D6AtG>zC4ofm{f2cx~J& zRj>Jj=8H1IG7xZ`0kVc=NP8V()IajHZ_U--)@TR(gdEBizzS zq|bMfl!t+BGC;`v|NV#fFZbg3?7tp8{M*+3C+;=zPy9clMrdpRAAF0R08E>I6J6T{ zpd*f;A%3rH!U>43{ReDqHQKP2HH`M_RZ0Gv!t zidH)_L@-$`{BAC@W3K`N6~ymRY>2>?Z*btMN|;N=g^v5L9B+xbUc7RB-Y0K2FYlo4 zt~1}SRIFc5d9~4n4`+F2T*)GR|66T^$x3L?8qY0R{yeRH8~1E=JHXvsnV5C`*!7}y zB`eo=tz5t1^;S1`87_STi;X{k15xuo8Lj@`U}^x=L!|{UIoW`Q1t!#V$)wT;qh>zq z^soP7gZiu!fZ?eB2W-P%z>%DPJ;nJaDE)tfi2UQ9cmvScsd&>FE_aMP5iCGE+Er9v zd$#}5@S(?h4hoMMqs|gpvuMTTDYATixL|gl77b5t4#G=%WuWy`*(hQu(G|RjrJ$4b z55%5)=+n`sf2&=+TdcS7NagA94J8}@cwNb`$?^=kUt=6*b&${46p4RD@!k@$fs(y` z#eKpe&WG<_>DPa*PrY*S)14Bl!*wfuxCK2qz2$R8_}H(kXXkA9{}(+-x;6cYUuM%r z5McBk(zdBu*@pCE0=`6onSjIKAL5QS!F!%8PMO^_mog-~4$0V1F6eEio~A~kT8L?d z7$lrr0kW9MHvD`_nOFZ*?K9q#2cAQ-aEPn#wW_d@n@73_s%~w#jrzh9rc8+B?o=ww zQ0TE#iwSV9kbIT4h{BMul-FxegBh}2%3F#f;gQ%5BNJg6XEK?#aTy7K^la}4NZKcj zoj}WefvV$R56!6K4?Ztx`J(Cy5A2DC%EqTMraC1?_8HLt$Y+?k0b))xL*vAE?9+l| z`eRHBfRNaWc&XGKks1J&4x?P5=%n)k^m|tkMmI1vMrg zne=7UbH$3oNUsRB$Sk?$BRC<`9WRQvtJy6)G-Gq2OtR0LpgZ?598 z=Yz~$fY>u+Pob2FULYVLXaz1YbuDy50Q^W)W}Tm$ATFem>5FOI?5ejnpp3S>nIs!j zssk(8rd)tgn6bNs^~OusebtSDevCuUE91Ys9gH%(8LKYTxI)9y1_V$rF|%ncQU@8f zfjyK|m&z8VouzJv*q5m!F;f$i$1AI`^8jb55nle)*c{0(qo5A)sAiyW?{Mf+vNEQf ztY2IrUZHwLmrz1FIS!?)hyLRMO+`gtnN67Lk%=}DC>xtqtDxL8+&V=p;)PG721iQh zO93?gJTfR@#})2)JRq%(Byqm%U}z&~pU2q&-B#7iWf3_%pbs#=sp9sqYUyb?G~D=O=s)xB%jQsi2UM0@yu&Jwu$|JC^$rn7D$M!p!RKCXoFMS!&+k5;v? zAzwl&-Uv5e;R3k&MkHZMD{9D9%$l%=y&BN)8lY~LFCoW0qGI5tA=x?PqUh@cTeK4P zWr;AWMnmQ{LFCkjQIuoQ>_>jJv;4r*c=Pg$(lEkS&6~&Lk9ArhrF*XK{!ro{ZK8eS zch2^Cs|@nMoj=&Ll-6c!t}LbtF9WlAg?5wSN4$p;lyS!^oW+8PN&4;a%AwI~5g}HP zMu$0zze7fZj|n2O?0Xv)!9y>?L}97tG3YLxaEL$7Z+Mbl&R!aE;Yl=eImje6REzBd zkURadK#P8a&d-n?fjcI$fnZ}7v6|k&)TN$47Ku>__(k{=4(LkgB_$Z z-D!?rqzGz4GS1l*0G!A`E)+pVBQgFE?M~B1*zGF~)BDCO zud}>z#_s;56Qf_%A$_?I%u?upc9pgK0x)U>(GCQr^U8dnqA`npflX6sf!|d@?%)B& zL;>0*9iv)E#Wf;c5@yn{13-HX1ru8N^YA)TsfzUy%sk47U+=>PJu8-J4778h;ub$Pnzj*HN@o;q z>EUsRn$>tsin-kV5L}l*T?0vWLeYwK4)_$FcVl(Ggm*p@FF!DPrYl5HZ& z(KH@-BY%7X6LijB009ySZ(exM2->QFt?yK##ZxzRm{k6A7t_i+`v0wrI7?Gdi`l~tw7pl>uH72Ns|m7 z2YMnKwwGWz`GUEWc+?7jxr-JN_+=7wx}%4LxC)^K>h)2#iwZP2m)dh5Mey72lgY=A zmyGG>)-AuQ(=L_IXHnN6#b-fU1_Qm3VNA-fWv;M2A58u7kz3Fsxj5^jLjt1TDGn)- z`y=XY0ra@jj&7?Rf^eG}p{`6>edL|S*MhFQ{0|{_O^5Bz%2B_TUQp%vOw0nu2uguI z2fDMPLh$&acMs5oMa*h8cz6;(iyxAem4;BmdH{SeL)ZaO6&=v)fiL|h;44hsCX=B+ zDjwS09oj%#hCKRaWc;G*eEXJJb8um$%3D-|9MsFeZ8MZPSkf)p#u?+R zpmu;|Kz28Hlu~**nygXZfa*)AjH%tvJYYM8MTDNl+JxgHR={Qoxk%;$NC>; zdj|fkw$;6E=c=xwo%fhu7qB&}iAlo?fV5WC!_g;SeTyt;#yeTfJB9{5{&+(@QjG3- zWMx*BBOc#-?a}&+55JlINwCg;R_OkPRH|@pzvOr%AZX!JKfMIb&a;Q~7!>A_HxKp+ zamBt~tUavS7u{ZOhIeT0I(YVQiqC?3zNI2N`gY~&$BLL#W^zXSlbcJ{nY2HRy48uE zK7HJmY~deyu|p??HGQQXwPFOdrCC&x(k?CtYT8D1vG=^2`z=$~i&)3(j{C>ziI^a$ z$!()+gtMW8guyZn&*92O@nNc^WZT23UEdAsZytr18Qx!JMMZUmMs}?h=&XPbg06Xf zxfo96jqdu|rs%uN!IJh6jSH=~NHe*VTbH`lXIL8;$nGeeSC88ahqh(C>4-#wc|7MQPy6%XCC2+wi&H9+yY7XKJ+Ca$uuUoU zKay1tap?AaXFXrrEnZjj0-m09D_FVb?)BU+xJ_r+%it64Ilpg<)&mZBEn$>XZyU

V*eH${E*i!WXhb<(%JW%Hr(-$&nGH9k{r3usz<9~1Q6Ec@_cNc1#)yD0Wy zz4*qwYqh+^y=<-z={45oE@_8(qnJ!0z`rMVz7x1 z(@y{*BQPXS0c>@!l`xuMW|*7;V>?*TB*(zMnY@T{u9YtuIPxwEqaE}y;vCWme8Ci3 zN=Kpa*;7T{^yPFC(ZfvPZc{!UvjdwmNtu z*OVEi+F!KJw{#o+^1f^7m+vK8$c%HRj%5Fd66XTI_%CCXc^GBs8mNseH3F|^ly`9W zW?^|fXfQ6|z$}@c@@mt@HqnlceiP~k>!Ci{c4bji|GP&~TS~%7_qs|l{IR*7j{{3@ zL|V4e*kFR*Jc5Dtu@-;L^Gj~P%D1XMG#Nr)0On=X!8q==0;9~Eiq+ZE^KgxOjFGil zPc;C0UmEW4?46KGu4)GE*tVv&E@}CO(A|f;lte(0PeN7#?QWt7uQFtTwElY`13AD} z$A8UGn1RVq6gXT|KJPTXmqSd|(obNG-97>}>TA@k$}BM6(xeo3@Do*A;ij@;g|70Z zqiw7tmd57~v;@br1r+AW-JH*gEvbiH$){{RP6w9vhMmf389#QWHJZ8)OmD!s(7dI= z{1KQ>_T$uO!zl-Pz68dwtEjfh!V_^Pwz!(FbraMqIWAi?*Log5I55;9@`CajxQM?L zMu_WeRXbkS))FNO<766orFcPeUFang496jA0?5}W>{pc=^%v$Y`C7T>P1dHD>cx7$ zAG}7~N$VueYg!Tk%(7n8gTXkOqd7jrPNFR=9Q*#`1y1+*^JjS;b6XpDXq{24P0zE> zpRNtQ|Gv-md*8V$#}KaJpPlcO&Zs|+SUX%OiTGR1pOsOt=!fsfK<+x9D^7XU`P43d z4B8pj(Fdz<7Tu3QO?Ej~pUfVYj^j7UGEEdEkFA^a;`zOFByD)q;XIIB$j zz?TA{yjxA#1oqH-)!V<-l)BgHt>7a-(l@&rQzuXvVg|vKf@K5m6B{Dn${Aj!F?RDoPbH ziqwc11SCkvQc$Xp!2%;H5fBh55g|k=2@uLCgLIIfgaivMvZ4^ykm7xreeV72y~o+* z>~r@0-E+?$U?9k1y=$%KdEV#wmP=n0&JkmH@KIiWNG+DZti5L-(7X%}3p73?mJ7Gr zAUXUuSdR7fF1l1t5ad*Uv8N)}JA0h90hZNoDR^-54nd~aQ2G9j)#lPWKTl@WJSmrK zE1N1d0;8z$-3I%+LS<;IK;;~)K9U+;wB@pD|M--o<8Bii4`JbK@l6vWN1$c9{60_35-;z#o*d8iC% zP9ACl4#-1Avw=KR+`~ULbN&|pN%2#T_!T%-Kak)B+QV3la$Yo=kuFrH=t-S=tPJu= zW0=JOcoh~#y05YvW|c-21k4`JdW0ui!sx`%ds{pYez5CyEV&Im-1~@8T#2{$(Yy67 zxv{UACekpHF6YTpz9^govID?D^NYf-Z7yRpp(+S(p8+L-fYltOSj+OTXV{nUy4Ars z{znk+L!!6_dMrgtWSdZIwKFIp;vsirV@aSTx$?%SX83GMPJ~asLx^GVt(Q3u9SQ2& z@SdhD5RaCD>0q?Y7lqE9>@zs&Hr6uWejTPjJ_^JPjzM+ch$EBJZBiQ^7qr6eP6M#% zaj5PBcIYQ;In>1K#|h%NhWJSo;|KeH2QI`SOHAO*-f+a@{9a-+iChX|Gybbo9l$)5$7!MF-b7eY)oIGT6)B zBmR8gZJ#Am@U&4&qiY>xMT9{B-sWRu3z$nqo=vSh(b;7uf1=HO`Z**QRL} zAG+X@bfT~+BroSub4TOLWu~%^0Zn(Vc|D)3B=BiS;tlyC03ou4N`Qo`9Q0YGA0C3v zQ2}-c=!8v$&wh{_5QW$;3NJ6RKzjbl*-(-5MCnQkd1|XWovD-CjR?cN+(wPI&wHzp`(Z3gL!!U3>+e`P)Q2{!;VG8coJs0#6u z=uvvHE97W-9rg(?1}I4a!jeBSISjN$syF}#R&A6wKBy@Qze>?W3O!+7{M2rn!MZJ` zGVgu8XE!r;Z<|%N7h;0Ge|j~|t|zV-T(G}vSbx=SwhY znn-i_8ni5%q7KGwvUFJ-*jXD%joC_4ZNRpf5gUn9N@Y~p+(#C8n+{wmWjPZxuEijZ z>mz$JN7h?sD}5`GcgiGz(4^^qls?$Iz)R?>==eX9iu3Hj3EH>XZ zek?%v=%fceFC*RCv0Hm!Dw_oo7W)+a)dw-V3OG&xb-7;)=QY(XQc)OQYX+q zY110Z)P2(Pwt{y2ai^Jydy`hLK4Z3bf}~766$A=o&H;H*@ub% z#|5gD=rM4uzhx6V@K^Q?VT6kxk$ypp1m-vBq#sX`wUIY`>f?ZmXTwi_;$!)nkcezq zX|}wlw85N0{uI&Nk}NH_5Y-a^Y7)wMF+?lcfvacg&$6aO;DL?+vY5(I{w2qd5Bb<6O4R`dhkelzEj~ztOGM|4Z zF^V)#Z}@s(KL)A-TX0XAZ@w>orOl1rvzb>$6z(~(cgN;B%Cha>hwVCXNAarBd{*yQ zqsv`3ydPZhb?3J|1=r@h{OA3J3+KrfleTu>T65{=S%N`{?WVC8oNq2KNSwVf@lxfV zOq>5``u>-Gj{dT3z(u~KO0odl6V?p63SukHLO$mq4g#ZO6Kpxz1g6(hG(j^HlW19T z(wm15Zf9TS5C-5-lE6QM1VmhoLl{0>?$aj_rQ*^))p~3fVLvcY zFP^Gk&kMkRN@J@*{klxR^5Jl%B?kY3rRHxSntyU2bsse%{saHSIjZ#UnbrUGBA|cH zS^CeumV&urG$@>&Unhe(`ePcWP3G#g8mRf5nf%opcL@^PSA*K-p)8r=3GTV`*r3Tv z__F4S3F}2jAD-1?y+!(cHQZawr^aQjWIpbxw#`TOaWXen^q8RlRZgYV$)N(Cxl)1& z)uE~SyM#0GTx%oIikb>|Ful+f@owT$ugbyBF#5C{JhpQg{HO#&ng^JYdrCz%3^>0I zkCN(%Z0o}{fu6B|r$EYtmh``F%p0H8FD3+pm?XE<6}$?d7(+v^RHQ=bM!UgCTal^*T6CqEz9@G9?{ot zH}%*!ZQ^&b&+Oj8$e>;J9aHx#x$gmNt$Xrg&_vPZ;t!RTnH$z#KLFsuV56@z1C|#e zUqmGN^0q;pZs6h+;wr*jGqF;F2bw7uBoHPk3*wT@*xCX5OW+GP_!rMtt>F+{QKP_=^}6eK+>sGntPO#&LA~#3z(@UVhMOEdp?n(L*4L~Pt8V-y|go7KVIoT=Uv>Oh-85( z8^c--wkEbvz@*eby7h}ft07vXG*geBwVM>~28bXi7W?Z1`YVtCjeGi}TE4tS{K97G zN~1%F8Z_h#YT5)`qZo<7aLPq@DJI&?a(xBR*UV61|1}c0chELx`YBc=X{iGUAP#KPU^87w6xPD9A?Xxeg^gy z0|MM7$~T~BR$3)1`f#U~wGHMaWygHiMc&O1@{O?CRmcr|zVNodA;|m4({}oL!d_kN za!rd6*D96x`>DexcH2&fz?PzJh$!L>eUe^ihkQ23msm7VmFbPo%r*k>LFrlAvSn3g zto+9)dgE%OS6=iucKNal^3J7|apHK!fKKE40y%5s-`Gn0ZJ)g#dWmBTF}#X=`R9Xw zQqA~x6ykF*3*fi8LzF~|#*Q7lhOS8~qs_d?=U?)Idxvhj3*=T5pZh?heTOdWU z4i>g^Kt<2UHxkuJ56I{6i7QDaW+PMIyiZ;PxUqA z&A7+r)wPN+RlbIW?E9i%uP4iA!&JB#h#-RYlNL}_95rB<&&XHESA9`JHz+#eHBy%M_g7+TQnXd z$a=8Is!fPSY;G|opG5HbmxQ6pqUX_0fljEm^qVr)lPXX{Qsq4x#U53W_r!qu(|GmM zuWIY6is)=+iFNaZst{thI6~g`t$p?F3ulQF1NF1&6ipZim2Y2`TQt6v6!KlC=>`UM zMIfq>6yr>_>k>{)bu->L@3%MG?fYv`V{iULyZD*(CvE3Pioa1jjoS4*PH27P>8G5} z4NAYP4T@>~bm-E~B+BIGr^}plF6}JvSNx>>e_%-}T?1muLae)fhlY2#Q{W1fNIp+ z)oiUO`^j1x;I%_5oVnrx_*|E`NECMspiW3JjkBAzOHvDwK7Mn|vS(Syxgg(W$vKkL zj_L0CP#MI|Iy>ucpgIGqy^=HXx-_)bSvqT-oXf=ma_e??^3HBBHNP$0{s_qYM?MTh zueQ?jfjd8oOZ%pT$6mG_lHEl$=EED)IbJ)fszHXP8{X=x2i>BL?johW9a7o0sRJS5IW4fV(4G%Gvdo7CdBq_Tr$D@#)K8+ddW+H1c5t*< zmKov)Gl!GY?r8jy{YxOI;mQY-P(sBbp69EG;IRx`Jubn2G}y4Vi~fwkdOXd0g>A&c z=h`mIjSl1^5Jw+{?3DPn1QWvO;GtJChj|TH6?wmHww*HcRUZc$we5^=9d-a|g?ETjF!zgC}XayXw8hE5_-m zcg{py2%km?wVeuOcc?kHPwg~5p7T5@OS%)+4>UVfgif?7V`E0&H5rvXR0_c-@X5d! z5;qUoC0+vKfz-~>;jF>{b2QAwmjUk2q*xz0nW>k4l*#C|I$jwr%o-}=^K=6JwQ^%TLcxvx38;y%q!>)LS(eqI;rzdku* z=czPvmlV7H)Q%WqjWyjpqxw((95w9n&)UcCL3arMfU5fc5tQ>>2|Z7$3Dwo}09jLm zj~Un}U<*TxIB&dxygaWl3e7LcCy0#eFKCz4+$SxYsRzGfUX+e$7A#xZIEniOrI8?^EjiO!L0gDNQc093b$eGW>|pNFK2_JIes{DTlo zDi(Z%G4Vhd$)g2`F4oU#rA*-v|6=p;1AXLuL4sXD+xLUw4RYMzbN!xM|R`!AlU3?1ILF`lfdzz zAMCo0H%C953+%cDk|?M%3=3*MsUp$_<}XpU(w$CX&_8=HHV5@UltnZ!a+w8^6?4!@ zkexPBc=)M%IM4wG%CU0L8Jxm{Ui7l#G8TK(lEwfzah?>^sUs|)e*rK-Kd|{PXv{vD zk&~DW5F^-YV6LOp4RLN^iS*gS`9kQ6!X*d{;ASy}0H1jY^aPRf`NC;k%MvhfZtL0=!XXw-S5=-L$~!K3iwUdFO)DZ~%Ly1GsM zJdIfdh{vkdVE(rFR_tZ{vJ#T*;Yga6Kh&ZHgfmtF&=>68?3Lz7MH0=0D#(o|H|`^ z{Yqr%0d{74(7-kQYe5VQdcG1`0Mz~n@Js({49odiZ~?G=kSh3Ji(&tJ68}$(|KBU| z`;_=U;&!4xry-l;q-tH_yRsWtU+jFKY19Sc(K-;1PLn*`fqNWP$rS{VETU+kiQ;#C z)95&!y4e6;ub-JH-z7cLl~S4)FsRc*#-4jTq8Xmo(lC3O>dL%yvOf_0MsMy}4^;*> zi1k3!;Wr!4v7Zpolt)+b%KGX7HV1rP^|C|7T*12k;f5-fj%yhRRrJXdbW zHF(ie_(c#AIt}@cqe0mm^YIc#A5kXhw_nKvYFbbFtzW?tU z2Y^$MFJAFQVW%}HXW0}kP10&SJZ$0@Hzp+xGs1!|3e}yL{$wZiN5kEJ$_H%j z{J!>q`np;BAFu!4yZrz1`}9mU!P6T^T3~%9xoQpUGRKm859RHY)rf@%e5q6(<+10f z$mX5^m5`|qSL7EVyErZsblJ1=>YlZtEleN3`?8Eq{+6V`O=5w4ssZ#I6YDh(Abu}X z49Mn4lxWGYxJ4E#RSh0yDs(ceo@eePJMJqAt!ogL-+xi^>4bJ;;U0{z?v&0rokk0hzj zxRo9^U0`;nRi)vGYar&-naNj9Lz6<$5QJ=`3HJ`;2O=B2mLOFj0@N!AP!pS@Dci}b z5TjxI723jcr`jnyJtZroHkirM;wF4#claXszPt@OZ7{U4 zHwgk5vwD7zi+sx|Q4`Gc?x?7|mxuzl#USS_$l zLC+A-;9elPBfvGP5{8XZtWgO45RwWHaMb#K_QLeU=_qCXOw7BU z>f;%kMz$^p14TjUjdR zlT=a9BY8)TxI|)LQLBzzU@xI2;p;mCcOak?RM91wl1*Pea)j{R^FF5wS4i-n;q$~c z4&rZ;W`I7SJmLRhJ$;IqKwaem3y6D5FoRlL}M$7R{=_f=mYf zDu0X`SrPg5(HPV6O*c?!s>ff&GKrVY(brc-eP>tnk+OT_hBO4e*D7oIiB3MihfxpPWWyyZBg-Gd&dre8f!%^#RpUDbXYu-q8+NWEp>`F`v z6Il9EMg3I_hXSp`T^|^3ES}7*@108>gqT=J)v5$DNfUBS0#Cm(6wBGl|-tgshgp&nFiqLnr*|dJdQPZz6GmQNiED^!x+w&-F1>dVYgf7h*rgLI7N?PY11op-kSV z@7a%M0BCTIef!9}?gSWx|D(;3@!5ZLF!+!Bob>YVlKG!fq&MzY2NY;K$wlA{iUtIU z|Hb0*b6nVEmwqG30>GIcG=v5uS@igyNV30wW90ASDu26N|GgUSKcioD$&Z0rPKgHU zhA?Q{9LfHzhWh)1P%B08X1-1I_qkS- z>Y$!T*NuNMGPc_8dsS!|^!Nd<%q5wu0wTH(=wU_sN)G`pJqp>hPkKOhkL)K2<-lw~ zTQhK2Hn5Rpm_EHL?*TYLMoN+ZwUU5d7>ZkVC_Q6KjQ0Z-Pa~i{mlSr-_yC++opH5$ zxyy{3t|9tQlO)kufL62a9Eg7#T-Ht6wLQq-i@ zuzu-~O4}+D@em9NmEk+iF^4;WY;g;1xis=r6!z+*>2BY4djX_K$`ebDbyaU1HrYI; ztvl-@XRfdMe%_RpR5u8?`>>aTC+H_EL#xRI@&9rNr%-p5e1|en=+A0eUJqw+`akAv=E%hrp{tUn=&U4S3m;O2b0FUQhwWV*T&Ep zck`MDZ#)7o+T>_dWSt6D?WsP7dDkY@bgF|YFv49DMd^N7I@=ehyq!SO1#MjFkhqZF zrWt5SZ<>teq4>yR`tH6M20_obp`CQ6VbpKY%oyj(WuA^P~ zw2x3f4~+lHZSo~MJjWpowoepLFl?Mu9J!d1#Aio`L#tR?$kt(`I!8lHpt`pCZ;;OM zIxLEXl|P>ij|Ee|wTMK(Ok5k|CQAe6A2nW%xMsXswY`9+O$gVf#!t)qTrz{ay=weF zbQ{KYd-OLem1En@AS@vD(-(uwzn7v1db3O;KmHTJSMhCyeIh(xD}{Ou2js1= zBrZNiJG>s#KZ!#EzZaomrG^4yA=LPpL~F3tV|WZ;3?r-}&9q{cMUzH#r zUj~mRIO5-R!kE1yUt(xoWi)}8IE-j7q{*_v1#KW2X-U8B6^es3xA9Pn%M)%+MZ7C#MFRB+hrt_4+4nxu-e|j|H}J8{-6x$>#Yt+L0+Y2_ahJwb?<@hT> zQpYG5`VK)qptSTFM+jC+y>kM%QSVbsw?`QE#O9Wmd=O`W3r_zBocEU4w4%FgACo{4HB z!Dtw?1oyB`mz$Xct=fuyPQ3!bb~rAz9uM22`fWKZovvyN*iXJBOQJ;fl4Y~~vHi3L zATg*q?tW-?G8xp2yre%%%3g09S{|f#Rjo{ zU~iA8T67XXzt;IHsj%C!!N#)sNs&&ubM z@&)*M$4HKLY;FjUXB!uRlQ4>#!l(xdgZu|I*`5w3I&)%^xJ?N6>_Q_(=|E)^ z?vgJyl&FvoBdLNYOud&f3~D_lU^vrLtWF7by)Ems+Rdi}Ut!k5Uh-c0xaAdLg^I~8 z9HWv|{q)D{1_=Z2&nhB(n9~H6`!&-M<4LI+PTY;qi;xfe$$B<7y_UvIO9xZwUo5Mc z7x(MWm))kUmKwsqTk3-#yn(*V*OWT!(k!%X;?f)1R*v=+hG3(mnqL%_R>V=(v?vsS-OZ3pEn_sSjk@w8pCDMsCKfWpQsRr>>Z*i z1q5)1om&M;`vD7uNL z;z%UlPIMiKvx9dX;J>-pv?~&okU=TUR^8O0fX%7=YQ06@4z{O4r&`wh-s+OQ<4 zO&o3GwmM-H#lVajEje zb|3}B%DRsyUF6kvk!_a03QQ|=8>}-g1A?R%B~3wj73Kab_xCpTI}umDNH_m%J6|(x z@`&tFi8Z?Ihm-cVI$Iwv$>#JFCuRlSO@8#$YCqg_)9+xIe^0(}%^S<^z{9s(%`8K1 zMt!KOmpY`2&N=Se)uM6YYB63X_8`6N{E5e_9{ptXUGhOQ<0EtjZr8cAwO8G58h2eU zIQW1+xeZ9T_|Go?2g>gMY}u~Z1Q_Wl`f9S9N{6WJD46s*HCfDZ6c(Y}-L+stnfNU{?d=0mZ-(hT%7 zNRfiRhBj3Xp2JYpz4npq;~8NeuhKDb^2SGFmUv;C&`pB%$1i;pwr~x=W3ph?r7M53vZSmDZv40i!*xKAtGu1uxIPEQN-O(Jt*)#)4#>=SY;= zmfY+SuDmSYUFSxya{si-)wID!#&9kju`$X~k;)o*YcfhfP5U^yV*VSZ02S0cd=rlNHtxKFw^5%TD4=ZVoA_hH7KJYQy zwS2(oT`tI}Fb{OUa3a)jTf?N{C^z<&0ME5;Vy!}wM90?m(9eZBIyJG>kr1GO z;_S7ibISe!L3Ih3Yu8a0!Hn3-*|SHsFv1?}Ex>PPPFj5eDS<83N|!G#d_cgL(d#g; z98SWZK$k{}+QenD+j1of&f7T$PX8|omj`u9K@E&a0?eC;VVqV!0VtDggeU8Q$K~qq zM@I0Yku=M+(pBBWpfzP49*y4TpzK5uJ()I-qDyIax4l%3O~|;ARRGF)Rn~UkA27|g zn-aRu*j?`vVCzu20odho^q9rASI>0@M`xjgDG!x9!u3Opw1Oa={y`h~;8;}JCG0%{ z581&PLN!6j?#zigaJgVa2UrWdlx3N=3?!R-q8nM;kqC*d)Uu+wyfRW}m29Cb7pSB` zfHL!Q=Q66~D5Vv>P;Rl-L&rf6-|V%pBMhTC^5c&j>(!!$3%&AfaCzV>=ras0%@~{Xl&JTt0F84nCGw)yjIE{Lf$pnO>eYG zQf%G`mZ4cg7^W)rzkXyIa+2gU?0YYA0hV3Hv{13&?ygyFdN38TPaTiV(F zsyaiU9qC=!YZ4kkv?BUR?8s*A6pevFk9}4dB?V!8EZ4~${$(-Yd*)#c@iM84W@Mez}!0 zUqxo*Ao5GDHsD9$yuy?Abpa(jN1grb-Urt%ue<1&mm3)r{IGJRV(#6LZPyj&rS8!S zQ%`*HVzr&m_p9CPHodUfyWoety3sofQXTdv{Q(;4&%cibdU0NK;4&x#Bc9Ohb1s8J z({qYw3&g+et>W0gWzd%fTn3lS^;>H2pAsgyW%3a}d3k!>7zky+g6NCFdKC!@c*{^) zv9ozr8hg|A z1^(viKNtl5Ni^^4Bf>ufq5Z{y{$Tq`AOGs4PhsxIf3Nf~K26yJlF46kD$QnPdz2`9 z1(mxnku3D?1}?0(QSdOh;w}I#H*mO^GQvL&^=mvf0`D+v^ofy;~)Uk(nS1f zu{zU}af8MkEOpX{CQF}}3_f)sGUL=yG6Ae??h)+DQz96~yA_XT@QxEOWp56g7Z+$W z=Jr%M5HX*2f}G<6g6dsLGo%g;CDF?6r91;GHc`!jXM|3oJ10PO(~7OSGzjyvVeRtt^N1X;K^+B^fmYqD z67vc!&Vy6x!41@oYAVTSuqHY@SgTigghzey{xq}qqin@6xL zPA=CLQ*+t`4pGZ=JZe-&*A;?Unx&k8UPUm~4*eUI!-?C0P_dC8W0 zi=|s-wDKrJvWvup?A(4kriw7Afmmb8gH-RkmTV5gYq}Gh`z|ou4=t6ufGyiNLKvdP z0JbiX`#GhqZ zWV`@Jg;aqW@teTlJhGLn1n6q>79H3DyF{~=Rur11fuj%0(#YoLLaEyFHgA{iNitTw zGQHvFY5J4m*?yF|`_q-0h7}IWY2JOAeFN>Uc}pL6}$6rop9fcyp8@KQFg< zjJE{108kSKHLSNGwxTNj?!JUmxaF@%Mgn4-aN78xm7zeT*r_3NSyk7AF2K6MLdm@?GqS@4<9+y7v23%2W>Xlg-fbA3A=1 zG{U(R=6r0`d@Dcs;=7Nq-uSD_G*0AF@zXelA}T7e14m}?u$Z+Hb}$|w38`N!WFE*$R}*)H!q*= z+L{RDhq}GI?VlEW`n-WvV2|1IMIrt4XcuMc+aAvymPwaF{5ReGQJjde$bNnN*NAS9 z!A$`XM5ub?H<|VQe-1nN_u(syX`(X}qdWD3hH-Tk?q=!N-of%R^c5o6mKYwvAIGUZ z&p%mmIjn^1>Yy~;n@oIk$3Dy~~}nF?Eh_6p2tOYv7HPFIN5Aotk} z=4F-zsp+Fw_^t!XUqPJDu1D#S%uTEZ7r8WeH#pGOXBqx-!$04V;1fNClh(?+peK-2 z!$53~Y?rJAdTeq)K?khGeE??wj&T|~LrJ9{isMc&a>WQ8pr@lt7*?vBVtln=m@SFM(2n4~28r^=_mZ*su`ltD84$;y*3CBS?*bX-2RK&!u5uY@;Mt1!jQx+>DiOS4fYG7}wpvzeXqn9Bn4m zl&D9#U}xo!??2;;#XdzP#N&on?elBnAmSAuh3=b9A3Yc ze9=*fFQpYy3CfGBLo0)Xe4EDzzY^L~)mr>$d5}<)4-%X=@agSnFnPxCPD@^30*M>R3f*J;^D(o?u+qX z$`GGmSg{_hRIU6FCs{!9vtwO<-@-R)Bq|fyuqo3IO{VP!#3|j39n~kocC+I*1LknRU@&z8OlSj%JK0yp2IJ!0K>QFSI`TnB2!}B9{BKAj5ECFqr%ESWM+^kE zwX{`;#Zj7k1+;3*?P6ePSeb~VAJD#)7b8&ba1t++WkJ65fkL^;PKh&x%UcTiXqUMk z?+i`$gAUqb+=LJww?m4DjN7i^HKlq(s6-}^Y_+x`2NSQP%}&vlhvH)^!yE3|I>68+ zDX!4pykc-$$Js4X7av_Oy_$A2RaeDXyT9&L(sv*=>LI``>u!k6^(L-q!-M#eD@Cc6 z*Nc{1FM0IBj(_P?^t@u4-Ut^6^=>TmA4K=8>tO5>`7Ob7pr{8gEA|~Jzv0ZN=$UGa z!r4CS4!v2^Rm?~>gUU^Bf6VDVoA}0KKj#A|oXOkzq~ByAR9Bo1n4h_y$nFixV=Ed)9e`>w>yF^mzl?_|}p($66` zb8q^l0=?~)(u9I_T{zTH(=JET-el7*chfc_>@}a%4_j{@&)H1Z2XfmRo?Dq5@6G}L zn&lieG0SoECxg3kLHr5q6Yk-oR-@z-^u<1l9(`Ig9d*b?@8o5CG_Bd>{bn_OU00uUJ zw$geHLT|$BSTFS$t%TCLfR#A*NmP}?&iDbh{tcB*VrT*@0d3^E<#H_EAFC@zBW-&@ zKDa^ls4UlN0d^;~z$1#vtr9e>H+pi*s9UNfij62o$8zu8YyEgv^&2GeIlU1nv&MSdt~S zJ0sXy%Hf`18-9XV*pcHQaoAD+uBV1sKC_`0`GD(x zfvt9#Ir(dLagk>mRq%0b;}0`prwi+{xqCd7nDvN7Qa42Kf0Abe+W&?fVOLzul{S zKt1qb{3C_J@}u}eQJ4M)vc={hsX+Bc4|<`-CekEE`ip>gV;kJda*$Gi3>#^`z=rY?My2fO$+DuVFaEFoHoL8^in}^#L zN_UhdqBTe48a?Z2Hnfv5C*s_?%uKXrjy!qxo0wm+yjcY#@%geuYimj~QKZ3A!!)Cp z`DdcBE41qOH9PXD6U5aBp^-<>SF>IZ|Io|oB=3^Z-lA8Idf^7F`#v~`p)yC%U97-J z)wbceD6s1y3JqXmakR*2qEHebf8nLi0#z1$hszW#B+jubOtxf_V_88n;axVxqmqxk82^r4)pekRt|!%O z|HZv`hoS3R{5yhp9TJl(jgX~6{&Zv`QwWN#1=l;VL-(1yl@t?72X8r8)1)s7+C+*H zfwC2*)j_f7zbP;2=05*n;nJWok&iJn1CjrpXw#Bim+=^ij5m zw%k0YH5-Je!ob{TdoC~#=3T>r2X(-2h(@(WBNtj+E2@!Qu$FiqbmfBnPF79FWN52} zvxwG!j>qcA&5*n|p^*bg#N`8{`by>S$?apkM?C_yW@e4sa$7zv1(aGaxx@mRFQ^Tu z{ddG}va)I&*h6d}%d2(~IW@Y^aPTYTtxw1YkUn@VDrJTnn=7K#vCCW<3D|3y%CJi; z*;vpbi)=_AT#1jTEwVQ7B5$qf&41c?$J4*KVGp_xDobxbUHPJ*0h01$G+NDCkx>9J zvXfVMD6%JnsZ(hm{Y|V)aw8-@xkHL*8#`&h_zNUn^k2? zc1J*U=$JtzStd(Wx)H1j$MKFW5)6Cu`(h|sB&|cwDzDFeY^kR#fW0*?KOUI_2ib-dADE9Ui~UKEl6R-g$Kq=&VSz`J{VeBXP zghCYb=bE|y>g%ue<|yp1cR%-_>D_M0cz%zEQ1kusF@f~QMwx- zH^)Vzm$FtO=myIM;ILM`v*>J>0dhk?Rd;nI#k%=;s_oDCe6VL?^RK+5j0^Vx&@7lX z5%tJ0d3f|ksBSCPf53iPgK@9uR8{)0-_>=AT#v`GgS!$8oJstM1u2)F6dZA!lO$a5 zqwKMK0aW*mToH+rFTM)Sd6g)cIdM5@K z-5P1JYz@Coz=2imD>sH8&BdKN3K4UYE&R`L&>%hqobl1 zT*R3em$~MK1kZ8bd~6)wFaPHHj03QXmQ{njN4~YDtODzc?pGf6o475}GtU+A$+%(c zDr_y08!pIifK~y8Z?&Nlu_#S)fCvBpX_u_$wNFu1%-K=7B%^viCy^2$c2^T0?YmE+ z^v-&ioDSMPWn7PC@+MkE2EZJ;P2wrtCZaHae*j)_3X0F@iKhx-m^8t3Au!DfZos1U^atmWs8jimhWx# zC=IYOSpU|oLUte+4d=e3iU7!>gd5N`#G4p6o6Z3$hMCnH&J_y{OX0Q{iq4p`{M+8R zSDT59HZ(HX=-yN#mhEp0Rls^b`~zx?@f-;_=05gq9c2Z;$sx*fWa8%8-*h^^_J#sr z>Hfj5`imaNS9|aON3d+3n4wT3mJD%n!C2+`)PgZD-KtE$`sT)F<@FEp{9XE-Zv@O( zRGPi{W!uSs@Ni8nElsD_ubwaHa>tpKr?kHq;Z7Y{s5cX9WmYhL5uFf*LHS>di| znL#4pS)FIMOtVqz8E4bnUyNKVZmdq9Jvi1IoST)CuBpcmMJ zmU?0+V1QRH%-23p;D1qm+oxz`_R7&#y@p_XK*lvKbC=*|tF4C*>t((l=gd^TkIM_}yQtCn)|eO(ob#H;eeIZs3qO~j z^h+;p!HxPt^`en1TSGobIZD+A);S@3>=mqvbZbcAs71!Y;$Z%y0?FwB&8eNCd6l86 zbu`_vGw4B0CoyC24c&AgE$!v#{VQ!MKiwccJi13e5)=)Lz>$) zuQN%yr(Rt-qJZt*k|;k84YjdXlWqWgh=XWd!k~r7`I*3B9MAotFuVX&gcP1+_ihoE zkK^L7N*@=PJRF;G*}l)`tk2V*Y^(^JHCpPimWQ8?&Q-+TczhW%tRc5?FvhhiY%jbCAy!5elF>J^NB z=J=46;~6+*#GK<<9$9Q$-^n#Sh{elw$mTGy9=mWvJ`Y~@V+Q(a=!OSLhq}De0(uvu zIcI$5bMI}M+q%^LcWE{Oez!6s20apd(r{^oH->us#P)PiPY+4>|-5mTied7YgdhVS*-u13v|k26$q%s+4} z=EK9Wrq1%?`X9o8e|k5~_s{yt$I3Rk4XZn@D+WCe!~5 zW3z=ebiPcvK_!1nO?H4bcVxCEF5dKN&(y$f`_P)mgFm+Bj$iztCiawf;pmexhf@U} zi|Cu*o)&+$X~;-&ftzEi_l9H-f-ZRbbXA+`n0*m)VbZ1ZteNpWW!Dt*#N93jGR1=d zp*8kv!;>*ZUMjiC{hCLL_P)-(v|%j6>vr*IWo%M$K(8f>TKMV>m{i<&aedU+Ml2zAU z1OtcHv)14>N#1-c2YB5Tczrq`GLIPouF>)}Zf2|AB^?Rt4V7);9YsZ{e{Lq#e+cUi zId~)8O?NAw2nIjOWYxki3Pb=i1pi%moV8!f9strsMpvt`g|f7ff@c--&d$(6hqkpt z*{k`VtDhaj0M}6O&XK<&`T?RLC<=FD1h@tq7;NxLfvS^L)G%&Q|De2e(C}F`18zN*Ue$=G#7wr?%51d|>Zfbh+g1+Y3f~D&7@L3S-Lrj(i3`6}f;=W2zevi9VDaK`I!hx@D=H&Z#_abJnwd*`uu=+wTuO{%Wn^h!0?7C zz6FXnCAx=D6Y%>`6UG7)zk0bQyDM+$=?<{)eTj@M?^_<;EO(;K?plS@Ao9{ho7S|rr>Et#0OH__?0%rCCs zT1&`M!!3A1Lfz@dQ_pytcjpFbY~*A(r*DqPIP84kusC4*AbaRX-lxEJ=vAKpEwc(d zF-!AugJXu8Iqb{U%Na0+W1=Eo0ft@u&o`%u*MIsb-}Bkg>=(M3P}--(ykQ?4Z1u#` z>-9;`pU?EK6RIDv)hg&qum9E8$cQhcZ*H>0sa@enHVrmWI@7UM?>;&<>Aq+sykJE7 zx9P-eyXLj89;ig7gnD7-T{%vWdF2$^(zwUdKaTBi`rv%M@497`hYg4oksrFQ{#Qa0 z{>Jap-vgwgrk{sF%LCj2(JL|IHAS~Kul*y#!-0BL``o>VU=2p@nCECIpeLloJbDwr zbaoM^J2R8>zI*Z@Z*4({YQqYO@95XmMR8cznXZ=lGd`wwq~SaWdi9?C_gvhb0lT35 zdk=KKkHOP|r>=idIN=nqGuUSS)+&eF{kv?0A-mR3luX=V+#r0KRLfoUMWNu}v1=!v zwi#bLp1bPV!M=Bi=MNuyLQLB<_vtx9VgN$yh|$0r-6Tb6(}9Dug`0O0KRL3jq(7Q!6k`Y-VoU(>Mu z^FRMT`C9ocu-W;CM05&4*@|(IW=D#Pz9>|#{-RK92w(iotu+#Q9xdna#Qx|qoVYvy({&}MN+G~*feh0vDnZu;~9($rbZ&votEX2{4rQ09c3le=?>`(Y4pE+@T zJxcfT$>sVM^Ezr@UsK$;K=Ihozm0GD{}XcO#K&{Ov+WKMAw+`Oj^s1XGIp%E8~gp` zcRt77o%m_P&Tm{VUkW~*dvMFK=$j`6+3z$qJUehuT5|N*`NUf^&BoTt#4wjs zyr(WcAROoI9aJ`Blv{W{BxGkZCGUko%4#uNA7k5SxWe#lyq#a%QdixpH9qb!{X~jg z^G>0MZ;YeW<0P<}7*k&LV+OuerD?=q4+y11n9{9?$;Y#o(h8qH=8K~esnUEcNYHMC zf6&jB?=;KoUv{K{0fUI_#XU;d6qu~5tJSBJ3l#p)));dxK^rcO4m{7o#co(yq1ug9 zK5CQ2eDwRCJzNg45^{MJWk0xK>1@57;<2!K(!KStfVNgyP_{tJu0n#_49a(OjO{2J z`-g^gq36zFu^C^+mR|OsRG01}NFPl`%ICV%1=ktcVMz~4l-8{4xHw#^I@SPV-Ux#8 zTCOGUm4SCzIkjevD=qtt{S4uC<^J{!JqNWbt(J!&! zv%}lYQo8X~z=d(k2e><-eDl5_*m{2P563%aaTWsc(go;jS!jcVOh!}#ec^>U7)1m%f_Ak8m%_+N(vW4 zGdH(xgRj^cey!MP=0cWKR=s0pzedY+;joygVat0E+c6u>#jCZdEc-02he*Ak&gONE z%^BC8gw?yB_had+_E!x$T8mcxkMtjb$hyqL4RoV#8pkjFdft1IhLT_#Fz zD!7>oC?=qKNMj(yxWUjHRv!Q{?N18gI}h>My4g_V1h=f5ONmZhKM1{a(AM2 zVB5iM4Wp40X7-b&l5cb2v;D#gB-Mf22fIGxdwoQU?peujVy;@o)z8)iD%J(|Q!i}A31`~wJUcuz z_T3HFI*a<66>ys0cbZ1#7k~i8pJ&WJSoaMu=O0jBGzC~Si_mq4UAXT9v!QwY`%QX9 zv%EvoIcX=Sz%s$yobmd;>G&P}&!_S06B!AFbivARqEjc+_t++1p0El-dB2GOCe0$!o^XtLss-ibi&Bpc3Qnw$;FH!rm$5>qz%oh+7zAv2^ykdeqiP0FTAYv}ux-o?D zFIsfLNN+_#en-04c5d@-ZPa%{}!63>~@hFpfNu<#v2=0(}QKP%IN zzC}4!;0N7o-=ZO_grdiPKa}lT@Nb8*1HfxVIqbcJh~kB<8+R;AP!ql^x0wmw(Lt*F zd0&OD7bR}pUIeFKzrB;~4aeQr*3ERN%FJ8*;zWx)tNiHl6!Eh^fB){BS;C4XpmGN; zKjLWkYb}t@@#6V=xtD;6ev3KZQeEd?mOl%h*wR3@qpFeZO)@X8mJu<(LNR(B8YbG| z30L#NVf&o@F25}B#og?}+1FWxC>U<+34TJZ32>PCK()dD=2|RwIS$#Bw@4Y}yB_-T z<=nZl9QDcYa6-b>>~PEC{Lu87D@Y|?4p_C0?58|Db~pc{qTrnwQP8MIc#Dw4AYj=w z=DtM`n5^cVaA>G9_reN!u;$^~h&FAb!+*A02cixOs0p9yr+ZhZVbup-fb3PyWQ zNiD_EpCEihDC)CF?t!&7F%J=~ZsZ6_5DXyl1|a=5G~r^iTWbUX^gomu>|}MfZW&H# z7AwDgoj8S*=)ZS&8xJ>fPS_MV5)ktxE5f_P`RVFP%t*|zbY|C=w$*T{9RAc$7*mqPwxAeh7$3(jOY-epi zQR5h>*z7hsy`ZOeqf=q6ojj>RcSvFRJJk(}*?pOrge=+7br6d_?Xq<$dRXe+Sm-6gfb8k*> zyvB3KY@d&nj^6?SwjhZH(EQ3Zn#cs`!c5~J7Q=L5QV8EZ$5|whg89;YS_me<@51c~ zAtx%z3~l%@gf&vgDP*4tBn=h29=>TRQrxZY+Igf^dlX-ddWBliDdJDys^S23)yB7I z`UW(@3~V*h8e6XT^}swy@AcrdYAA~Ss??%9Pny-272kYLKUFG1(?ou+z!oJ@{R=s@ zu|B$SvwcmZ7gy;+Iz}2<-mrv=yP%-bep4_UGvA-0!G3ekK-M7Nd?aK#^jt7=G^Ged znKswf&nwjV3fu1r_`X+R7B(g}X?8f^0~2&Q4TH`&Zr@#RC#<*|(8 zf4Kd6G5u2U#g7Mqlx`;s6RcY8ORV=7jOhD?92T_Aw@oKlS>Fj!JJWX8exYPID7xVJ ztjw7?{n`?Jzd{%LX}`n_N!tqj+ZE22Z0$?bGE^Ju+^(Hfv(KVqr!U;&o@Hjfp{r}yGQx)ItZuH| z&<*+o_Twt*D;hYfEM5*GfxO}CAF>4pdfdjicjf#k1RKRy0N88e>*pfNb~M=XXQmw6 z8M;@%J<5B=7e#NUk=r9Ac2zUe+WUolNpgdq8c%ggY!wxpIFvf;G6uhBwv|UN@5fav z)(S&d_@Uzg$>%^Y}x)ITE!b~L6_tS+6ur%=~{_ik?Cv59}ezW3vK3{dXD8Idd# z(Fi!>otIcQ<5aNc6Hn#je0lAXPWxy4iqNT96^n93Dg3LBK@Un~Njy71zptDAy8riT zl3t$4iGInH_HL!_e%OVB=7Wi4wm7?{yCpLEIt$H`@G{1{a(uhmYvPTe$|)QQl%TeT zRxkXc($~Mxu>tC|f2}I^e^Y(>7xNtc31iUtS*(^&`B|(6ygN{=E+m6u_2}P=)qDSA zo3GEm)@Kv>jN=RGfO6;*kBGa9pB$p$mgP9|*lv;(j{t%IVt!x}a~#=oRy@%Kss{3Zs-4g-SuxPjgefqZhn1OAxL-{&{PO@a0Q4rusB z1{i~GMDG=V*!3yEm>fI==AQk{su)Kv=e)95dK-C7ff8?Zh#y4398~%LJCU19XbD z-HlgBl12-9u;SvDwUdYk$dNjLoozlAB7DT8Wu#nZ+ly(BCB6)Ec+90RItcZy(8W*x z*W6nhu77^!=0L8TeYn<1pl4!qkb4#)(t&7$-twF`y+2`Q2Q#k2@Rxp9fawR+<+zJ? z<-vxy=7p|)>`_EjXrWhNNvmhcc}cF+gA`u5qyLG}&4$aRNR>$x^FEyW5^&?$XxOeF z0vm!*D{5eK8SKtu?wrE_4JC57fjq{Bt}np9Or}%F%&>l*GMRl9z`~QApyOhS>k@S1;;cbfyJ=9jN|@uN5Y{{FwX27kk=06{efWCvp^B?yd;ddf`ZP?@zbfC zlRz&Rm>+(m8n6*yLniSRK-ESN12&{9aQGrzjb_vjfdLa!?m0*P{_kMdH}K!XAXhZ` zhrshhavhKfhh73HUq7(EsEGAUaK%x86d5QDjEtxa5ZML3If{f^0ElvpAYW<*xY~Io z;!5aG>+-+**NN^=gH?gYL*N)<_CtNF%`Pg_0Y34~mKpeVEe0lPbQ0ZQ%_JvbjKdk= zVeG?bGh?e}tseBY*FPXE`@URgc(5{>@zTAuVA4;&>#?UHH)yFYTMoNE$7N9|jZa@f+ziod!n>>G}zW-%4Nazm464JK~aIL02c8vA!gFgIlpf~=c*B6Y(4z1Ul@*{bZjB=PxR% zGI!PB=t~Kjo-IuSn!&vlmdL>nnabL-Ln2dtK;;CnA4huwtCa?0T%%|r4~>ohM!^<( z?^*{!N*f{Yj`*&~c`+S;pnP#wTq03pouhC8a2CwNCm77%`gLY1t_2g2uNxyN5_vNU zNQ>2FBOu)A(3w$O&m&+I2$S*l#@-<>(0LqLxH1(o`N^>pf6rBd5S*I5fRhOAQ&S=e{x8eRFU z-LpBD44v4x5uXny2{9q3_GjO&@&CHq-d0Z3Vw}KUVgppDnd( zvr8{gv&*$u_cax?qP_Z-qVJ^QD5+*)OH_q-gJLhR8(<@B=@YjDe%LuWj(@cn7&aKV z-`7DrQ_4i}#Yw=xeA!FBdb|{LW`_U%L*T{Vr=K%Sfb~=QA;6IG!mr!0=J?7W)c=am zCVRog4YBfEObZ-Y0wlQh0sj19r!6%dC7Z5^q!dh&`S7oQ* zd9HF1&f7QJB)1|n<)!P?l<$^9Mu4}hPWLPUd?e5$p#FlRQK_l;9h0`NM-jJ>6Q*7G z0|;80qGWx;lh*Pj6i6oM*8%&qCT*EjhK^9k4u49eBRgO*RIz1mqg!E_`VpVG@Gzej zpZS&Q-y@+4&M%lC7N|#Y-e!MzN&^(oDSj&!$RB|KVy8M(K{TcJIUwR>!+@mDNr3C+ zaI_)8wvXscbz^!j>L8doM2sd&t*#4|0oje;(HFj7y5mbXYd?7mfTw-X{ds^*^Luw25EIU8juR2 z3~XpH=>@Ubo)H81tb!P)opflZV5*Yj&VF1xJ!$m$TD@JywO0OpGHoVsLI|MdGKG9F zsTXl8SE#cwu(MwXBGVg|MVOIGU3s#j+Iw^YC+yf_%-=k=L%y@b?W8vWfkp%xE^+1% z3z67t<;xJ(SzX6T8a?$bANP}HqpoM1OS`z`d8K<9a8-7WF=vXN`%*;^w2~hJ)tWEp zuC!;}#rzS}tBR?aLj$XoQ;g%sMuBI>$Lp`e6x=cynTH-=@U_O?Y7@4=Fkko0#;qVg zpxz383xtb0yZVzvp#{!GOO6~@+Uml=4Gn2qUJmYhpCVY9*7>DATGmAQJXr|O%6Eg> zD;Gn{8Wk?MT6({Bf8;tj>4R&%PddQ8&+=aOr1w*{v(1=UNtghR8@3an$d}*`;$-Fq$ScJW#P@QH(;Nj}feTSZCT3IZBe|UStLEtMYy8!)zB%&ff|6y+1He z8xaIThI8bba=7tcwmdHjaZ|@$zo88kH6#8J;~ggVy{wFDIlK(6@+d@LY(F-qg^1@L z1!>Y)L0G!8!iJRZv8TNjG*}c>fb55q#kK;Y0n`%p*lt60Ene6&;OvHo+mnIgaJpf! zRAW|hvp)@)f|6}kF6O_>&dtjzD`#f~`BQiCwb8iW25r9z#Vge?FTDsmp=WHB?n|~L z(@nuSoXGQw7!g2aTLZrDdnDLhx5(ovv+!gbtqE6!>Y(m~P6KhVr*X;n{lOyp#+SlE!xWWTp!*sh zG8>e#%V&XL zPXe?+Ch=3t-hv%!4T1RX54ExgrV4*=-969nfH2UOtYTaLx~8$=_0W(pv!ZFH-9XGl zxzu`QSAysLNALamw2#ZgF1!Tv_E`B zyOVVyJxjyf`E^~H)tNUc-(HjVXg#|rEgoIgX#n`I<%Sp?b34XVu-c_Or4)NNbVUriSIWa)-(dYTNU ze={c4zE0nBJ3u(Md{m z58FulKD%jT_G;#L8h9nyb@Yp>rJc>&yQe32@2M1nW1_vn<{SeZRfd|K4KS4YQ0Y7S z^1<(^d$$lbo=HDJsND6{f+@Hj_u77r zc7G(u2Oe{6h=jN}Xm^hme}=Jew@2%9)`3}huioRmnU>{Vy}in#TIq`s$p)IehtD8+ zUb7`%t;Ij#C1wY8rucXqtq=D7VnYgf`J2&6fR??6j-W=b z;Es`*H`BN;UAw64`}l*n4L6J^aY>EDhHh*{6D?^LRQ1kUg1PZaIPdT6?+Kd z1zFCjFmparSH9<9(JQ z9(hX7yk%T;d_Dx{%dQ`rl^uTe{4*?sD41|}ue{ac>(#fcRV1Se6EbDrv$wH6Krps56-;EA{1^klh3Mn&l7!9!q-@rY-iZCem zXN~N@dboO@V0*P$HZBEox)4eq`FI;67p`q%zQ3TncvDO|;9G)tv zxyxw3(8pRQ+z z)BBry%X9Ug40c>Ab&<@Myz=2xW@1ou?spq*4;CP4b;1O$_-X#0C9pc2AsL2wej2EC zN1AZ2K;H{6vIBLxdeRTZT8Sy_yWCJ-{H@jGf~Vr0dSr=P*nZFQm{N1F>qeq ze3<+5KR$q#=m@Ud1YP7|o_Oph>tYEA^YO}v;^@)HOe&=c4?|l8gi=#L4J2wI32V~|VOxb3< zs<<|gnMX!V&CiI9>EO1tA!yG?hQkOSDliw!I*jQxU)>Zs8FKlCj-8}C#^M@oAL38t zv@aF;dR$qCtK*P0#*!Vjtp+yzJ6O%9jkQiZvXV%&H7iVMKlmHN$2Vgy`PO4-5SP`4UitIgTucsIr9zVF@7ig3I$k5-bS{ z6BV8vq=Umcu09n8=Dw2nWWES`TeI-QqB__M_i=dn~Jr9jK=CKk2wJob&gl=EmL=6f&n)W>xJoHDr zNT8$_@pA#=T8#{RnU7mNPO?l}$rKe=ru6azCjt?NIalh(ml?wD#P;KA1KUMFWwNVk zwy95-5-$8p&zchx)seP~f_~hA47ib%r!=@ObbmEaC)`kX4lf;>dRi#H?L(p8sScZx zOIPlUcwK7Iu{Ed+_UoP@*q-b1xBuW4Dt3^osJ>JGb>e}}%O!Ccx0HRav9$9YG&O_O z+h+UXVGNUZLT*Dj4_=f%lTww>BtoP&L>D zo7|WW4s|3+^G)Y)EFrD_Ema5reqznI@36G=pAzSh#GW(>d>t|}`R15F{Y&=JI-*kPL_4L z1;ms+zJA0(l#plL>~H5(eoUuLjOa@%YlPHJZmmjkaHYeY*SutGE5Bo5iKNu0A`)LP zhS?6YfubivzV5U4Z_|FAT6FA8qNUu!_X_%VJ6(!U3Fv2y(TLiU$R&hbS#qm|czyx~ z4}v^qK1dA9QR^WdARlZCYXg;>R$v(Z#r<>sr~5aB>p0Pim?zBkb1hh)?*jL1jw_G5 zPbWuHCHSIH7=N$IEj$dXi;klq6Dvu3p`rF51$8Ut%~*_8nX|EP$@v$PKDyGvu*9gv z)WBNdpzNvdUdr)c>H|1il(NSlastm@eT+;rI{{-SGLKB0S3TwwAoo@}AV4De-6<{8 zJs0oZe3H52sXVv)Ti9QeMTsAAkd_?Z%KK++sv3OZH9!L%=?wo46S?+PB|iA{zFCEh z3@b~H=)#c&=w8g7W^EQyINN(7ZFEE#vC=##`n1&J-8*7g zY44`BTYqjh!~8ye5{&lr_`Xj6uK>9J!Pfn|Vvwj{gW7?@PK8|Vva9%4#l zdoAB@su?EKr4PE2Ysj=3qocgvn-R=j_SrhHLmr_O`?~-+5P(8~h!_v?p|nH;WB?r> z%@=_(N-#&EZvZA$z}%Cj)Tb3KQFn6@*9c2<9s2vLf`Xp+mQ2sP;NyciJQ2&`fE(*b z0KD0Qm6!tHZ|>=QNF6+^74W2XY%$?cm~-BWx!}Ma^tsv1w?MbU`@#_8Xs2iXyuM2O za&U##2QBkdO3&c#1CQ%=SqWf0K@uvc&ozSlwH*=l2(Gv|Kw&wG&?R#*^|W}r82Kj8 zhAaJY^{UI9N8`vG6I-|Q*&T6pQre59tb*UG#`Wzkg35Cbw@nb1NdZ>jh5VrR`g@uCCOh-mr-K zYA-DP&p8#oe)BZ_pyz#e``=xO*$6-Qkln*8%G$K3j6wAKUg%!ACJ52E9n-(aDMid*Dcpd33hZ@En z>(hC!e1tS``+TNI9>NR2jZ6!qF!$FuHdtYiiP&Bk(}fJFsjOUi0Q7=@?65CsM@BKzc9sB<1-j>VK+HtxgLT8*defd0KL7QvcLO0GYw!UG~mFQ_6Q*#*<1S9 zm>2U~sAVEE=OU9GL%5(+Q(aR%w7!;hXTtZvmFUA_=fcnV(CQkJPn?YTwT@yAw!(Sr z`|x*&8pKLCA#)qQy_9jC*&z|n&l8-`{_P%nS&EO!D@);;b(1(r z(1C3IMo;!uXi#@S_qF@~dWB!RY{vm+(Lc>_%Xgz-HW8nTQfCIyHb1%U}K*{YMf~y1q<3~K@ zkEp&+WM+_Ho8^4)v=g=;@O%I$q3yh8jX%zkZb$8yrz9bdt)#zWuO&3*7TBn@8&s*8 zDVn{=82=kMQI;lxHU}Gb{Vk(hComdSmoOX+4ULa-?YFViyG^yN!s4~>K%vcG$=)k1$pjTuH66bT&z`+N zK5ZIe|IONegpLqJqf&zIr;5k1&OH0A`rUAhU)`&Wlk0vxF)qQUV2&B)J$Cl$BLs`v z!SmlN+=g2!v=`y?Gl_r}!Z^>Zbs-BC? zv&n~Wb4Iw$co>iSv)H8cJMSTG{t0q5B?CC`yqhZjcbPxH;~Z%Zmw<`R~=75 z@FfGeC*!zUKt4)@s|4GJqk~)^n!}d`RZ%jV%tmoFzzIx&bQ(Au#uMTg^TmiO;Lkb^ zGUd1b5D=FY;P)Q?IpF87KGBqGvW@c#xNIdJ5$vyr(5W8+{rsN;pZ@z_@V|2x|2{zo z&~%{0?d+XkaJdAHEv{QUvIMq&J2_;LAu9@}{(+#>`(O4rjO6h!Pm z>?g@h%_|rmOWQ~$`RQtI+QWBKJEN|xc6An)OIt$9B$%mtZZ>d0>*o+LnGlw-sNT1-ca%5&wMX=a{Ge-@f7QH6D-e5;(aXF%I3=vHkRQUZG1mZ!-i0AFYVgEYdTfjfZj zpME#g#4-=025na5++$EpKHA%MR0kfjm04BdH*<1~D?Mwqy>-jFkO7xMBRcbx zhU+4b?RiIrv{O7g^Tb&&q%=%?H-sHXy;fHsSQj%BrzmtoLF0~=P1y)zYFVm1)2z5!pC)*aV`1 zOh;3>G8|L(++-yV#1nLoZAU+T$L}7-)dSWdclrp&4xs&iVdb`gS;#+2>VIVjDnN#4 z2CF6}S5#!t$*ojM>eA&LyQ(3;VCIT5tF2t^hjmo@3iI&SybCcdEOi+|?riFA#hpzp zuQO-lLXcXb;h*PZNJ2;6UU?t%wM5=f)h?#Qm|~x6Q5_V0SkdRw>R`Z!!ZVjnJ}Ep? zfSzE7XEA4-U3K1nb~xj5y3ndrn^)nIp>EZdtAA&Xpm=lk>{;sywV;@@Jt0aN{qsGE z++^T|BAdBzSD#BSn26c;4NXo{x>j?!K8pL^Uel;@+f(X%NfhV zXtPp@v+mLk2wdt%*({@Za@ml5nx}fbTUtY<3GkOlbkO2>Ta@gqr+%}_MEkC}_qp?uAZv(! zKl-%}C?}n`8+~ubM+=E=rPDY`{K0*=x;U2ixua0{H^_DNpL_VxVMrNTzu!nef zbLerYeh+(SMOZ>+W1qiSw-#{K$@Phg4(!Vv?eBLb2@ts`K`VDYf z7zXk+`yR_7VNod${_ysArf!!=w9x)+n+r7BbZMnSO_#yD3n-zSyWcuIU_?TD{=AJGK}mGIclS`T2@9KUw9Bue9w@bf8XEhR{GSG zi5%Y#W8>}tkE2Qj%b{Q%kXqya*%I%OVHCdgr=YN{KDT*$#C*6{PIjN@%W^I*?>u(9ozgb06=Wwmj5%LR#*V z;%^_#<#XI+F2!KOpx7=m(h(?%ZD%&gIl|EE>KDfNB<_=pF*@MaZUa4HDgHV8SvNba zlPKVyCpUZ~`jUvyg1*m`EQ4-wz8?)F(2|G7Fw-iviD%e6i zM-JRF@&1u;o-yaNFLv?hBe-Lz^J6SmEA@#fOd2OACKdUJgnBXyfuKA}4e@Cb7Z}JnyCwwc zjXO4YHW*nZ@ECP#iCG9S{y`|L-9rQdOw4V5I3ez_8M&d_J}Fp&)-*cJbTkAdd7iNF zYVrhMl=*%}A?*^=y6jFWWw^0BDI`GWQdCHxpUW5vgzdX}xVHhYe-$@@pRFYs4q(o+ ze&r9~C2BsZp`PS}} zA6T`_gJ&HHu~!x^)R{GIc-q=8P-0|Z*~_Tqn8 zBI%~NGnf78NI~u~mP+T2k(Ig|pdpmOd|sF3Se0h(Z&q>Zu&7)7iONbHbCt%X*tx!% znp#W$oDT##F}c#f_r>(gqRMY^PD2}dLv<-CR`o3993RI3!opU8zSp|qiI2^Ol{)?7 zI_l@<;kUJ|bcG#%Yk4(tq9T3ps}fEvgbJyNEPrRX_HtV8vBP;U>$aCd#?DO5`~Y~I zfl+2do(yZpE6Xw`%Y7cFlD{l1FUaI7sF?MZ-)^cgjRvz5z??;l%&s!UTqctJd{}d% z&q^o4+i|QmSIB9k?%X_xB8}@azkHqR{IpX0aXsQRGV-`*;%SBFub+C6-ZVG8`A@g% zJjne#+X0aN3J+O5#eHk$J$*@5M$p%AyUFu0{ErC80`dkVb_y{J7~1=g$LNqSf`;q< zgBV)U5MbACq?p)Izdlvdtaw7J_|#gvQkCs?NiSQ%3DbmT;g}b$;;xA2C6^2J>*Vi@ zf45A3=2qA%8D(wj>vGEKtZv50>a|2xTsuZlPBe+wdGLy<@TI~Bm1lnIx%-JAt$MfB zYvF^H`XyC@s*c3SD%owGIA@5X%>qKEUF&E=6a(2NLW%9a+%OziJ5f6cn4fSf6mmFt zsi3-L2(F=nNaV{;1_hqXSvl~B!^lv;Hz}B;k*JIO)9lOP=TN8RnNu zREO$ZZs(fW7Y#mBk^7)g>0@q@>+&k-nM?7S=W_Kv4xI5@eWrh=b@{6iZ{QkjgZ+le zEG%?XKNyT0qI`*dQsPiyZBrsBTgm=!C#J;9Y~(!qzn7XWfsFcpOs>I^WeT2T5U?nN zPz{^QQEA0TKENd#Nn(3k!q`R?TldWj8#YAXB{wW8=E?cuO^ z)Gr1mIA6)>wOFjIj|)GIMwP_qj&ZGmDm3$P`B1nGE9~J>$f~gZAx<;uIU?EMEF$4n zvj)JAooS<$n_B|Ht2n@9~hD9#FdyWX_1#Za((8 zI?~Gfw*jtY3C4q&n$J*BY`5{xaX;-wW)foIA^yjolN*`rB*TvEfcoygT%7-l(G3_& zLU0+lql-ywGDt23Y27~r5L-W6kpABQX#kq=Ps61DB^reRuva7#`zL-cIe-cshpnhG zU~!1rh`EP2zct7U4mm!sPN9p$>H$klrkik&j$(E)sF2l(KoWd`VwB6vFZVm?2HoR` zxU*oC_2J9_Bu8n%w(l*Xy61l(z69Krd?!a7di-IKtHH#xi=vz}XB_KtJeb~XavDau zmnHSAd|6Fbvii~;6OY?Z^gR6ZP4+Ce${%qIA!n@7dc6uNu{d)}VejkguhGi3e!0iL zS*9PbwtGByD$((7`hiBk0%I7oY!tc|v45I>Mf zDz0#i)mugBQi;AtFRFey{j|9ZY4(?HoWaafVP4WqDwAK7@RF^Yzmgx{Hhb751!L%J zQ(^HkR<_Q_$3DrC;83odpJJ|F^KD8bIZwf}SpOqK7JdH$>4>naNMh^k0^=m~Oxc&t zlJM)w6Kl~^3%NSmXVKz!*Wpf?PAInajhCsH@e#Eur{QAnt9y0}Wn6Y*3Z1-c?ey?5 z>f{o+c7GMHIZ>c!u(1}kjPKU>+U(2`g8wqUR6R5CCsuMPPpxXSPN)UF5L%giDJLJg z?rvv4jHtPmusT=!@x%^FW<`N<80)XHQV(+m96F9z&S9(x`*}#ROqZ)zw&1N^!&_y}xH{IqI(1T4x623*#Ww@A z#dukdzq=dvO?2#^9C_DEvQjyixJcd^+@x-(XT`(w5et;iT>n5%!UEe!n3-Fck&;oM z8t4z?umc&zwEDXG$TReYhDhz$gEmJ}Bqi-Z4*r)hSbS0|YTc+EM@t4#!6`wmNty^^ zzNigH*Y)}#@ZsCn8or1!;E$Q1{rUadX;l7mz=%-aQsl+sKJ}7sAUHZ;I5J|Z)IbH* z+rX9I#_9%PNaP>bwg=wJYfNf;o(5#X5Z0S*_?>jDmqAR?m68>V`aEQyK_@CMgt=UF z+&V@ex(na@iV}0R@{*_zqSQ)drRfYG(-z^K!*+d~L}A-;)x2j74YCnLuGU=Z%CR}y zMD%jyiNP^?VBGp-87DmVtEu0>A6{=7Uf)zYRMkV1^im|*bjs|HaHx~dKUJ7gb|JgW zL1KY%h;D8B$@X^LUR4goMWe3C!EiY#`L%aKnPdAW=Q{bWdf6xbjxlOZrMGj>BGa`3 zMPpw`J*%yC%bqUti+}~r!gsrqY9xt?&3gA#;-qe}_JwYJd3t)@GVt{WN{?x9Ut#$+ z`S*d*zi6a1R}RHrV^JeDPr!97aWZI!h||{L=ndgZumcNX<4*5FznpVggNv~$K-c%L zw$mz)WW9HK?rWF)ZZ{htHc2BdbHG514_o~YNs?_z=QWf&O^v%RWIe!mykI4@KKK&D zl(VR60<#v4OaUHQi(Jl?%!=hJOuD1P+C0?3(D=Allm#P0624vb$pD2qWzCj$tO5aq zkav;$%=>KXmz?t3hTj&_2L0tY3f7}?Jl(HDm#f~`vNtpJ4op)%JWR}rWeBFr*lSg1 znqGTj_TjvwAL#@yMSHyBFtib_8%1A>bJ_SI&^7Hq?Eq8SmCTuAZ557S3PudqaL0oi zv(^K&kuiqiqat6jcz66V#*ofYxI5HYZzrrKHy^Ucn`8ag@j->;%Ck*rhrhW`T7_I6 zsMx5r>s0Q{al5F};96`$u?)@I<@`ciP3HPFrCk? zaO?U}9fSlr%z6;+bzovKYilOQo8bQ5txKt%^!iOI<^FGpe;!G6yuRot_fS%^f`Q-X zbHI7ppLAp(*fqdpP{U}}VPu`BE3|NQ@5gR$ozESorkVg{_F?WH`WZV;_Ja^)w~;7U z{UcT%u*K-ev|5}nIvW{H-bE6GENLH7W-HEF4l|V5Qd22 z!d4$Sb))IHcT2Xro^kCc9P`jqC>SS#QDEkG;%nh68iialX8*&G71uUndS(o>zn!G| zaa{vx0R&HFNRnue)puO+0>Apf@o_ZJ3SK558pdvGCpoPg8`drko?Ix1vKV?9{YyVr zQXm#=c#v1LUIa4+1IJz2^?*UQ8zF!_bl|-F0nuaKpWeFu3%q&W5?7QlazLAX`~MuVfpy~re_@YT^-W>6Lj4yMJEX{%2OOL=?q zhB~)8WMc|bTKEm6K92pxPTewTmy;Kk_T9}m(5IC<^6fiW@($nFBU4WAeaY3b(fpMK zyAn%rJ$<*Ygb%s$hCY*dVoBB(j;_u{nELRpW_Y_(_8Egsz5J}}{sUWmvR8ZL4VXd>ihDQ{~KI?w9*_Ghv4VQ1wV39=5` zBtM0uSndkFvLo#s9tx+n!M6_MYLR>~k__k*s!#jpL(Tu$S1@q@DLd|L<~`qVh3|u| z&)P8g8TsgZ6u29GN9=yL1UlM2MskCs18A}flNhg)`M4c>Fvo55`nU36!qEj6<&2j4 z7xzo4R40mWfbiJxN#xGq15=Y$lAU$$ZTbU)b|jrR-Kqr?vScn-n@dzxH{U%X8GN+^a)o#$!Q+e(?!B@Kc8XWQf=dZPtggb*gGI`w#1N9xX$$%* z{Hoxu6T5!-_+sdG@OKUPdWHV#d4!G>tkVYIl_P%S55y9en_ zc+-k!w~JTqv)j=gGj5&U`{9bCa?Pi}6a;J8?3@oJ4o?;bUs$B59VGfX)cL-)BM7?0 zDki&SKmJnY@kHt6R8cKi7v2Ti&wP?^!;+N!R^pPXn6mfat-v~Be23Lgz6B^b3SH2& zO{N;&GLf~p{5ZpG0k@Qx$S_iXnxWaY?67!`vE_TyyIga;1bY&__I^HmGu<>@C)I51 z{ogLF(kI^~SQM`;Ex$Urtn2--?dBP^ys9B{vnxF!^do#7 zDTelT9NJjf?o{>Uk-}<5xv!)2gU}bghaL9Z^S!67X&-Yh3e`8q6&s_WT0yD=6!jrh z?Yw6INxcN}RXS=n80t3zuNmPY zXg>rFKrh!kMnd{wi(4KUUM1*Q{sOV+DWbmaVR0zFYArfgWZauCMBte7TVVphkbw+G zh5r%u$`ss3+Ve)!Rt13^TpW3)kz)Kis2wl1EQ=6dYCM)EoEaOE zWTn|}UR5;|ykgWgu$?^zYSEy+I>dr?HIRi#idX<^Za28klD3u(C`Jpm;-#9mGty+o zHDYHmVC3YXlYNQ1ALzO6b%HN4}g0Ah>gM0l)evf@#Hp zN8y>5;W2q9nQ1HSjJ1i`R$yqb;uy0mx;|e(xmLgtU@ZGvQ3!P1|g;r3YkhaV`36AF=ons6SLZ&h^ZuGGs&)m z$$ph&Pe#l}JDbTpHq)Ki^!=>o|9tQJthJu^`<~-_zvHOLahtj4zJK@iyRP#(uk(zc zLm^b*21Bv5sUVQtXg6(T2n^1iwWoqru`7jt3HD%3Se(9(%a!aaQEFi*(s|2gTzehq zi9(NulU7mb4W$JRTG4Muo{j#J?C0~_FH(S+u&Px^7SYMRWLO8C+@ML+6Wyvrjp*|C zl7ZsCJ3P}!T`5G1;sjWtDK9MZnplCJM094&EQcE+4HAAzs1sEpACQ~&Y2090N?{;+ z3t@p*YT-N?fjc?UE|LaAaS*2K{jFEzr%K%2fI6)RSFr2D$E6x+(x<_wndqLC_Jn)a zRk0}nH$IfK6(d8CY(&X7tb!j14S2eT`*sQgBk?*ZGt*op2lCplq;361$n|3f3)HIS z5U}xg{yv~dat;COC?z+nBMp$(EOf)nimx^?h)-% z@~e5oNf%xt9tt@vcE>So+6f6$wtDu=;hfr!^HE#X$J;5psj8q*l@E&T!9yU!z=j%Z%XoI*n~<2DI`{luNnP%Bw>A}fQE zN66j&v9fQ0vMs>16Km*x+B)>9(p3w$-##3mt*B_U2+nUYv0u6|Qni21C;L-(XW_?7t=7I(kV}Z_Nh|xh zxyECKg##@+lCvM?Ntf<#b8&I1Y1{DL3wCQBBB>3OTDY<12lT=KvG=s=IQq2Z zE7W~=-5<$I%=?wZ0g|P-9gsBDo!MQjTur{-0GU-V3)3-hJ8$vZ_vYoO;R@E5=}~Ch zjmP9(ATBE-%#Hz$z@V2=re{wkyQ>+k2;Z_E&@6ZKoSevS<%IXRA_4V#Yy1O&A)GtT zDWePa{9*lp8VNbr<8}a|q*=)FM#g;-TI+4uwn^jJNBi}{x~93|uYmnvK1(p8;-1}d zctP-5xVdB)yI;8Z@u{uZ#COXU>j8^f>PHp0Mt_mT$^(J2Q0OY6TdfiU5W5QBLLVmc zLqXl^NtAsG22@^RQmHFS>Ow)x=yR=&^^(rwG`G=L7}GcC3U~2Js~#umE#m01LYFYM zDAI%8H}7wuNwW?v#I(>u$c;lt$YEw=gg+}P03+gRq~Dnp;VBI!up0Kqol~ybeSSAu!dALBY1w}M7olp5U?pjY{1h|& zLvRwd<-Y`mg{WvNO`xiQUP+)dsk$XDpkyXPrn0u)W!^%BN%Q=VLdp4uQ(~Eyf@L10cl1hBTaH_M#85LOiCm%6+!@k_k*A->P*i1^;4B$= z%?wdo$ua+2$(Jxt!FV>Q8ehzzY?#zQLK&GH#F{P+b!959#r?z{0%fB*9i)Pd^>oOy z!vNORX~HWLWX1zv!$x{_LX&Q)gFBBJOVkbwen{{GK(fq&W6)utF%R*|UBBQ7PW8H? zc#|n3Ji6{mes$54*ZH{Z4+zn?Y!8D?6A?ZMAeGZt3FdHg__uhp={{iV`;PjQ+J?X0 z2O|#D!HuBGV<6e>(=W#p|_j5lR5o!$cQ|8f~22iE`*=m-7s+gIwIvxUjO+ z{-=a19DYUn<)piOiqdSVKuP=#1-W&-r}J}QTY7lw%eSm)L3)~uU>@``538}dpQJu@10Hd&<*8Rw!}0=D57-rTm2?!6v3EvVOtOMX z&o5iNE7(tNtOo%bpNd=|)*a z{k)#}sVFPpqh;fh`q?8`^I^pfLxr`!uA&>#eP-Yt?bF8N_~AT$IB3fwN1FL&J*2IK zmn=*ZdZX}APZg8l$4W~oov<<=5-wwl+*%pF+W`7T zm(uh#!6`3WERWXYrCQAnrNlR&Y6n7pH%Pnn*c$a-PiQ)hST!L_)~j<5Z$!vBt=K^J zG&(BPK`!@erzasRXQn%Vonb;NbGV&jI-SWwwyRz)HwBwKK+%$0scmD+`a|&>a8??# z_>b8R-T23S3(t-25N+9lROjX~WdFBHSr5k-^*tB1-fpp?h?{y)UuD5^t?dJ=Xs}cv z$KEJlgwd1dmLY5yOy5ygQ}rXNbjQ7#XLLQ9Pvo%2j>>IrpZN3JTtCqbsvp?Y=y6Gy zKpUFgG_9i}Qg`glFy_6xj%ZmWO{nBf=4GLtR-zXru^3n%)Mn$xcMAz4P3CTiq0BXt zUYb4AM28ZsOV}*E&iVC|N-29^Dvq64$FA{GM;-N024-HJ1AuEfM+Th@O}4KfQDjEnf^m*&FpFH#_b2BpU+x6 z_$*s!(QDCJ-WJDiRrYE!>(x5^@KD;e9`7`>{w+3%%O7spCSCYs`IEwBze)fOS@JzA zNy*qEuj-8}W10qAo_B0?lZ|aYJ9$o9ZMJsq!GY_=W}~yUH*?CkxR-58J3B5K2b*12 zJbks%t<8Q<>tNHFr+3_+7ab7ZZ0T=4Tj=yMj;UL`BhB=3)yRLr97{mNmo70+@}~ry zilO~6X}=c)rn+B$O3ZxemYQ1lvkwO_9QZpgihtwnW3)t!{gVc#+dB+-IUX!sM=~l!ICiV~!0PBd57Yr@r}Y zWVM$g-_fM)T^qBbsw!G*(JEO2G+@=af6(wLq~DQn>e47da^Bzkj7Ixr$R}!zWgPf1SAy9%? zdN=Q&8yNVYj?0q88&YOsQL@H(S&v}?nvF;Ss>8PJ8j{)VHJQh8sx;wFq_U21iz=y# zrQ6Y|+nj}OF16c$JCq7u?#+#M)$M-amp31i+wbeU*=4`hRo#0K{kl+h+{2-m zDUGWoy$tjY-AU6b&wguCgT~>bdK<-88E#^kQXs+*&|}*Vk>{lVwAge6 z;0%UR{kxF!lHeXU?L>lBC$&{cC@&*Ih}VfgVdyeZP5oLMEXpG8jR+NxKb%r)A_w|A zGKbHNNHLE5a>#F1*tcFT^SuO(t9i7$($VXSUEM%vl&*Y#vz=RB?aDOMdBbAjkaJ#{#Duv(pXk z7vs!Ezx}{A-3yv7`w_$~_OmuWQP_Snbw}v^Y~t;rzI>T_kB|9I_w*TV#G1=)L2o`- zl|voun#8cLbuKia6=d^*L(uasPiTo~k26w7pN@aCeQmkBb@AAHbfI?T@mpF)^H9$fVG@W@t9jn3rw)I6Dafmvu7hE}NS@qmVkh)d&Yzg;W(Ynm=;J_6URhGh9*bU3JB%`-rE*Mu!znwYQH-A@EQ;yQ&~5 zi0FrfzBej1?5eLnZ9muQDwgj)%hMiwm@#C6E%e0Qc&}ezJUKjeno;^8)6uND_W4!2 zPmWtEU76AiO2at!gv4p_Cc?VD7W4YiPH{2KgK)4J!kOnRd`f%p*vktuc>~)Kc_l3D z@g$>+8Ul=>Zg%+;O`1G=*%*20=J(2@QduyD)zt_9%*o<1FidOPjFFnct{5yTfibTd zlpyhw>YB~gtQ0Ki6nAv;y|UPh%<526(`61<@Mo2(FV~ziU_>_C zY!1WAzPkRJrTJM|*dLY6-zs`OS{{;)`AIJm0KZdP3OhDof)}H&?uj-#c!Tq4(HYCRl!t`h)&&!;BsW_GS>X|D3K9W_`Ai&JuPKv zk5<{qrH@}MyI%IB`BL)nS1do31B9LOdE1`9+0)3@R{oTZB0z$Cpdy|WR;R^bW@Mx@#eGJjyE5Dlzk~6N7ZPeFFLUL$C)zG z25b3a9yv?qSyFQ@pywe&dW8b;65V$OWS=#a5&Gq9An(B@QJ|rNwF7lJ;rZAssa7=u zIiV|zgE&p(bdW&S>jT*+Kf8Z^&)nU z=Rr0e;V)iK<@hDmq1aPo*{?w1Z}6Jp9im4eXlGVud({H0;ZIoJ|Dp-=(x8+V4d`of zPyLh#HC)oyWG(z*wEP#ttzVHPV;T_u8e3iRAXE@%WQlCnP=o+$kPzAIr3&%y`aJxq zq0w#RvCU{y?O^wOpovW2KD(az9CHSv`AFYRz0&EDouw?0iEZ1GX8iu&c+dhwlWzp_ zlBb}k1U15pI%cu6VcQ>=rYIHlX(tK-Cn{)n6IbAq{||J!d)M)at|Lj^VZ~2cOvaTNwtie?W zpPjv4u*OE2sPB@_E_8Nuevr^{Y0(H7uxYlvYjro6k3aD!iS zckB7mbaj+q)mw)!4_WI@xVEKjGhd z$!>J(MDJq;`b0L?@;KVr{Up(H)#xTGjg*`APN~xJo;SmnzB<`i9=~-Hed1RC{E}`2 z0w~1GB3nr65VZ*zCsqdK^7TO9a^B?x5cDKqXPX|Ovv=@Xe%~;Ga-^>^J5n#G*CR4J z`-)zbS-S(4RKqAZ_#%f)~i@rKdh{C4Cg$g<``*|xzDG3&m?9=PsO?g43 zivhXHgWm!}JGTz5Ss~b%SHQ7}qobb9kMlqF>0Ni**dDOO?vZTi zpVE_mcC=lsXiH|t*D5oID)o>aQIaPsd5W=&hPysRCMg`5)!#Ok4;f|DM|&6G6pxOM zT4Rfb@8AQ+UpznTXPx!y2nfK-#_1q6l_70wJuB7H6+gBdPFR<&b#o$^Fy5qrrB=+DycekZ#>d$BvHtgKj+veFHPD7oQ5Jf^6on|Ea77S;_oJ0lCOs5t@I(& zcne#_xIpF9<0H0|lf5$=kJPv%Yp*}G`kc%rT*d195vS#sLG42Mui0R<6F?d}8MiJ3 z($7*DzmMEO+&r^OxSaPRw#l1uZnpwv=i||Ax1e3Rz=T$IYt0g>7QGiV}xQoMDfo79KJ?eh)h&~ z#{oPK3Y0gh#ATpJ9UpHOKjNpS+sp5$ob1kXX*E`IO;s)1{<1R1-bK}MvKMPw>^SM| zhDa#uD@|w~40g00d~l#5Ifx6sy0n@W9}9Mz?2DlGA^zvbvhQ@dLeB6pIfs2Oq@O%l zpuUW&#t#B1IsIFwoQxa~CP-u(esEKTk zbXR%Eloy?vQes%QavvA0$~MWZak#?QO*QB1CY@nQ8&@X0Wh}Ew`k$+lyZ>5owGGrs zvPv(ugkjeD55;dv?0Da-BJm}D*40vQX~gu~?DyB7xO3-6hmM_c?b!3daqqDfpW&zo z!y4lq@7F50U;2bC^FPsKuiv?~tTG~RlMVXzxvw3FlsA{^T-J=_rfKSRO!ZQAk5i1z zpQ|)^KC>%s9WH)Ynx6hR8;ke$#w(QOlsL%N*X>X=%RAOoIBB=n*H=3yWnYEihQMRi zmk+EjSnXV}y6K*#@`3f&&#pcocPw`E>cY0&ponbSoqYWqs3C8RI^ElTK)&dJ{6t?Y zwy(fxcbs`BTe)d{?B=C^G?km={y6&jal0+a+n((Fr{3b>#Rv11lMTrrbl8%xq4 z9_?A6$Pka3N99(H)7QWS&6ibka?8C7Xz^8N&)j@P1&R)hW<*<_pAGn|Y2qG?JV@8_ zP^%jD3PGk3okr@5SPdIOkxo4gE8(tIWI5_8KuRbvJ(IbH-4<&g;4=lOe{aFdObv>4 zo>wPu3|eK$oX15_g)3ea?Q`FqpmV4{zbeKHqNbv%sY3Z=P*SogP?dhsDAs+}-X9tQprUMt~>#{>^HIZnLUpSsqdVe=uP4$mH`L_9dK;0GDR zWjhR31qOWg=%0TXc|IdDc9*C_a7fH#R=c(6VZ>`5fTmApEdqpAKlF$*ABJW91c!(b zyqXrwnwqrvuJ;w}@c0G|Kx`#%7;9#?)m%BNbiHAV-FS}2arg==%Aj?_hp$fq7FT}M z*#0clcy-ljtz{@IYS>RK4dT{Hg`Nf(5|D$hLSIFlqYsa>bfQV-!p(3WuYI4Y`t)& zZkmNUPm={$?hR^iCi@E?ah+y_-1xcxjT@Jdb$00aMl^fvZ>%`5S@;n?#gpRy5IrN~ z&?5qNdo$>L>!Rc6K818Y+K5aWSyBx8W!3a&U#9DMn6^xeY@I0t{O!^L>jElZ#%bMl zMlLn@2|Utbeh}k19FXxuyr~w&n6*>ythysLf6JQ46gZN4sR*(+et5hMqd_tZV5B{G zNRT|(_du{NEZVM#KU>SU_GX@8Cu)E`GT!Utf_0VXNOjsGtB!jD0 ze>-G!{|}IetpLUo!Bz4^b-om>W4pM3IWehnr zGD5_d!0jpJV75!21xml-eC{s|y<2$t)F)pz1Sr-zd%jI6nuxgWyW+WGwVQdud-g@d zHW7{faS-K?7iiMglNVI`b=y!Zz&M)q#jHV1#nXXj@~S*%aW8inQe8Lg7=eR`O1F%nF|+;I??V=`>WJO0+QUqyTj4( zQPc*ZQ--7ehON())(dYzbYRuQ=}Kl+NhQ!(xu}|&X{jR8JJKG}Ly{cfJ1EQ7a2Ke_ zNEvbf>KbYjQJp1UiBB9;=wGmuvh4w(JG4|^tbSmsnWPKh+`X_H7=9lVF5FOz;Z4)kiTfaf12LSjB96KwKNH${LwE@O1cf)7 zpUFM}r{W>E@$`ZOtIdS)U!lQl^7y_{C3FpM74=-rqU>1LOY->~)CmzN`j!~RK=6q5 z5Gfn}xj~24rjFDD~ZMU(3g2z`- zNL@}*&!s&28b97tPPjZdK21$wLPybzjePLjMiYraB0y%vQr^eoRvE{igZ0hu~?fQM_W2Rzb zslTf$Nfy5v)@*WLRg|C4r;y8Vel6jqmnQh7_x0~;S9k^tK5GT92n;gd9bL@^uy%N( zr4C1f4hlpsc${b3`aL2BMzF$T7VgaJg`a|Gv4d}bNC*aS@^CW`;ZzGv=sSBXCF=a* zP*U0aJ^{AQwv6D=)MKkYlsH~jAt7>m*kB-fLB24vj?{{j7V2_-2&Fif>nM3(uy+DN zzrqa9vHYTE=A(34$tqSRLYo)02~7>_$_It}EgKTpsas?$QLa3tl)AGeFR{4_(Bq`y zsf~@17K*iDlccv)1#%El4Zh{kL&hP7W!RWkvKtVxtz`s!8(Eqj@JMvuh>Jd%eXRPL zP@&AH>nX2v!fF^ACjw)d(SLSa$ zDZSIr%4+Az)pU(nJ%#Cy7?X=m6~#I!RvPZGQrkwy@ilJydlv+q?vY(G`f-3ze6Vk4 zWykg^HM6{-FXcmZ+Y2TbmJ6jV)mde6kAPpT7p~zlc<{E|>k_xZT_W4UE2n0JNrn`G zqBqzB6pLnOgd2c}r#k|~z=J;}3R3Oq&Qr6^;x)2BiMv!vluFwCQ=+deA5*lzf|P_e zekh5q{gn9Tp70j-`6;1=lqJ8fCr?a1u$FflqF(+xyZHa)XQYcDY@#bMuny2~*L-CT zBsHfFLFh!Ql>sP?292)`l6}PR0(j&^pD)bi;;SC|%=#ZZuSBVFVw}6Et62P@?KAag90!1u`NDyxaBWPeg6v3{c5S{gU zPhl4k2|_?ZT*XB@?^xN_xeM)gA zZjx>T^YtDr6;q~e7bndtZ;^foRbTKNzceA@+;aDx8fvQ{%ht(;jIVrg>gFe+Gb-Nb z1@YIKH5XS*m<{Ak+gDuM^Skn4$@u$-!~dw*iV9DTOkP&14P@P61Sn>jm>w9LoNQx_ zj5hJ-5)Cf+*3?(rY1Ld^R`Tta+M1|mBc1YvY4?%8F>8R4yR&#r7x*yWvII>S1=1dC zp6>WPL$colV0tFJI(Y#wdAw@}KL*QIriXGq?@B;R^4@Zhu=LnPuSEsEkc`Y?0P0=%E&qr#qnVV~Y--Ge;ZPetPd z)bMPM+qGXZ4*T$|?mRohRg1f6d-~1cedp6m(yYzi)Lr;}@}TCP-_WxHs81{l5aZ}y zi1rXYR=k6#&68>)C+4o=sn^pIiE86?Nsx4-Q-qs2y2{_y)bv-P*8AtG_ZtOzb#P5) z5BXJq=3>X%rxp0LLl|!)gtHZHoYX#CYW0h__p9FPF>_X$Gm+Z0?r>^CdwF}Ak6s(D$nQU#vEUp z;@msJXNl-E&NQ-rNc!WQwWIy~E|r~^z0oVD<*&FXI2mt>DzN|~E$o+CLG}Jtl<(Bvjf1&E-Vf}AH5A{!PUENgc<38wKHbO}?Z5iV3oM#)4 zy`)*D)bULiGQ(42Y*3SKY$VSks{jecjVz>Kx`M!f)v7umZ(fprMW2Wj9Kx;UO`kMI zsu9j@dl>j;EI?hSF~YO$$nrZG*e#5=#CrCr%P5XA9oE5%R)EGQ`wAYQgAqUIT+9ag z*YwvQWmpoq$Utg~YB)0@xl>YNHBS1P=C!}=U$M%~W~EkYQ(aK$ zrsT4cydNKd`5dTdq8>G$eN1p@BP&%#ZF#p4wgH`DR1{`I{T|15NOfp(AI)l--Sh5z zqzdwq?S_XoH?yC(Q-o@p1*<2AZUvYpfXax$o+(SV3+0dPg^gDr%u+YlZMi01{;oUk zS*G0G{Nb1Wmn4jtO?MO2#)I!ZGAq_@^f7Zc*4ZT4G#H1+Dfbts9#8c@hZCJMF08Ez zcyj{kLLA?fr#x^y$9m$T6ONC9XFNVR>%MO#1$k&1@N z_*VvSM5fThO}HI>9SP;3*uSCJw;MY6G;rQc??EaBl2n;I@kSFMRD6-NqewY2rmu(L6@}ga0B>?i>W?WDuLH z(`}SP^4LD!Q)L;_?2d0juAa2o+%s*gSY6vczYn1RpZo6mEICpJv;J$v2iqbKvYES| zP!5P(?=L3xcx@Kt1d=4Ct{Z73Z$bq`;aW`=t?o)Cg;5cN6t+&hk*o(M`OWHu9P~kG zFds^2Wl$0-BSqFhL6x2BeoC~Gbn1IAC=AxY=GN7Bo7e!mNZ4I!~f6}5iDtn zm`VRrQ)Dw)1f*um!~jZwF+@;#eOYGv_bc(gq){+H&kD1sO9%H&ti%L-v{%Q#?nf5` zWm`8g4m|$`4M5rU>XNYSAL2ToI|k%)lV_IXbK|}P`P_^0-mC=fYZgU<(A-LuMERkl#)yb$LK$}+_)Q*xa`U}; zw+`|Utp@7|k)BV>#mxegP(RSk1;RvI9E*B9>7~<0)XOtSuENJN3Q~+M4I5E3=HFis zu@~JR4QnH~c8~|3(?>sxlAcgSwaz4wEP56U)P!>Vm6qhDh7Hf)e5kh%&C9J&O4gyY zX>9GQoLLTxM8cav$I=}JWy{u~n;z=qX7`-TgfNVfe`bRs&mie4SSxHq>r6ZqX{Aaq z9am~(s`!ghz|T#jVHwhttK0oCy`)NaYX;8aj!t!L{K&E1uvDF6)pb2}2iaWTF7%Ml zm^K0QoQ3-zW3$HYlFtTCC%4VbQ-Xr#$iBtkqwUJzNDL`|p?2|Jnyx1SGVT zYyQBYo|6F%g~bCL%9!>WiWNIjK%FH%Z;cHP5o%I2%_e@s8bi2TYh=mG3 zCPY2TLpLF>A(a}o5dxZ9tW03L@cS5?SOSjXAQiq2MtmyEh7Q{G2ZcuF*xFSmIM+NK z2<;dy$t7^ZWVSQ*-4PIU0}XXVNn4Da*$)nMc#*}IJUx_6mF-^D=!1s zJ0EuH6R-a%kwUVB6z_^|d2^7l8ruhdnVToeV85F(FAU-9M(uI0OT`^HEyc?$u%>zRSD$A!HJMEFTGl;!@ojFE6!MHoi*w%T=%-bmc?wbldI+*$TmuehpV`BNo-2VAk=)k6ZN3(-(bB_n-JIlouxL-{6 ztSm@wZgX;cQ{t^ioKZM8H@(g_rRCxvW>c4G%A=?7sU0Cfdvc!i$EiLqQP^p1f6=Zf z^@y+H#!EqAdCG?0-*j+*wv5b4{LY2vU#fB(4(=JA+pTpfHdX{T{}<<2l}{!Y)K_Y&xa4^H5=5dPMNMZO@yE zqeHG^sxp!tAKiO2)bvS5k@@P(+>FYTv%3a%-;-_b4-R=?e=_T67xYbj>Wy63X2mHx zhGC%ACmm$m8z+7A{NPl3u6pv|XAg(3-@fJ5WI5p6k{5^dgEpA>2Qpai-{=ium$&sP zf0SFLf2@gXWebs8OchPWwhmhT(umC`bhVn-H&m33p29d3V{7(2&cQ!Eg^e(}ycoI% zyQq^8+99K7(yCuGI=M$gbFbGt+E244Is&Fbi0Cfrt0@n#JfvKi!US94-Y))maJ`r< zXra0Xc~%`SD(1L}HoQQIHMRk)y*W>SZ)GlSd4>Qc8o*NV?BW%Gb#}$kzwV&JYrwAa zdnLIkgpFU6riqt>*fy(20*5t&qRJP&C@VY!(29YYpAwsU{^q^;v?R>D{YS7p67qgZ zbV&Z`onSRVr1e&?6G~kMudj%Di z?r&Xw)BnLnl^k!>Ad*7*1qKKq2bo-lVAE7!Z+3W`ScM?@D=6(-K#`^0TWXw8jaKx6 zpNt&pt^B%h+QY895~q$rLqS!gFaH#twKZIJ1UE!gaMJONkgZKhm1YXYmc>XdEn1TD|qr%w#Yxj zO9yf-4J`R;8u9sg?d9SEIODx>F6|07%8!uZKS;;i7$N%s_B*Y4xvll3srNTHWMhkR-HQQ^XU&>)U(~76)$EHWpO0_1Fmf^Q zc6B-P;lvjm7qhKgW$TxFPx26(NZlGb5U@!rgF{+L>)|EL@>qAnRzfz1mSTZCi`!}b zC6*{x3>*I1U+2I^Ob2>a$+BF{Qejg)SmU))5kC~>iw-2aqbSm7g9V=m2 z$5XCggU@0M979U#M|0k&rF^_O+Sw(UPkOzFfN9dug=%6+TZ}XLls3vTp$8P$R?l`F z;##QrKy0D1g$`?PNuUt@@H8Q2fA3DBQh|T9sjW+|!I>V#!Pj-eB@IKt(;B={7~e*h zpYKwEXV^N4IuQAKolibN(gBWx1T8mCOE2iTEJDIi62#LSnwrqCa>ybuEZ+;xMJf}Wp}~SH4<>piR2_eZ z(5+}y!k#xcb(6?Zo5;iG`zmE@W((+b)GNMOpRp>Ba7ct$X%dJjm?jaU#*s>$<1{2( zaHJRg~bN#v`V76}2^+w*wjzafS+Jl8*YCn9dpGK;6De&;Ebkzx@^a73^M^&*> zCI-PVh*rD=yrXHdscV1Ckv@G37tU#=16CV&=SeEORmFI}s4Kz7^A1n0M4WD;;zQFI z#Mz0i;E;J_3lKxJGS6%>pGgp+QPILMmTbV3JCvDp%b{kdHww^PQcX)I%YLfPDV$)GrVm`dAolYUSiv{N-!VgVIG&WvfY6bHPNGURY-WCd z{a%IVDs{6d?3LnM;RRnaBCPVqX^Kou-1>=gGM&exBF3j@-vw>%%h6PPyX@U0veGHP z%zbpKL3S4dYalQbo`7?+d`r)i7Jo&pw~#5BFyFbj>V-S%V%6LDTbSFJnmCFr@!bh4_boYY*?x;V`k!q!=PH60??U^R+B<|v2t}P&B zQn%{Xo7(bBulL#23<<*uju^|hZV1R=tV-FMe?M_^^mo^q`a!2Wh0nzi=er^5s~Ls& zleudKg<-rsBdy<=GV?NAO?E?09zokelbs`Tf)35W?s&xj#`!TFo=GW3J<4cJP-t<8 zL1IbhOPnS8l;YwAH`9rb0Evb*0J!PgHx6QlY@ORU0wLW03?&;x4CJVdZfpdhIT^7W z*9&JJ>8&$-_|TSyqixL`mo-N#cwsZLS_j6ef-KZSE-mH^)PDwn{3ZehbP(64-DF?d z$YVz@zh{(VqQ^o@?UdK}OH*O$HP^=0e2i^fdo3^RLKEiO-Q$NF4dj#QLpqJ*Sn>ud zWlttTj_3$c(*lh|RE=bS| z}EPTv9Ci8>|Mr8@Cxu_wZ&>X-Y$b3H&g)lnp96FG>6=YwZ0s zL>+_Jq2qC3&mSfqHmEY&O7RZZ-HO*^AWgWwb=YqOyty|&29YbCoHyHKayE@W{}6&p zj$Eu9)UYq8DtuyB_@eO%c7IjacH_$>1Pj|F&f2Y>PoP1M*v=4SNl)z4=N~*ThGwYR z)2fSAD+2T{-h6ah&F%Wffe#nRAx!8V>>-XW2BW2!p#Ds;{cD2t8Tpqi0{NFbY8J_8 z&?fij0)iZH@%SmR(`w_N_IrMN3b$@IjZo}0;FIDsFs~lM)=3d66i--PE_yhM`@YB> z{H^$`Ri58D{gQ|Ey`kUxFsIU-!wT-#>_U8KudroXz1SL)562_rac<=d5IRAnMLh>7@9~6#BgNc_d5%wd!p1{G50r zRfgQ{LWVXhC80M0N@oDzh~-y!dEtT^am(Ygd@K6x+w#*TcR82@zPxjOTzb#j1JSmh z@qf9Lwx&Y45q@x*iiZ-)qC5sL1EjA?FRiw8A4yPcy~=?&N^O@+%9{i*x$W3 z8*WMTy?fXucvGRLaHT`A!Wxa@KU3Mx5ZCOtiMPa|y6RMm6231L7|?&jxRD%xvD- z366(P79Q}tar4CFi9Cmmsrd;-$4onIGE9|L7jCzyyxseMiyZ&8ME_e5hIVn$aMAMN zlFHWK0*U_)9s+!xzv^@VToOoq{F$dAd`)gVLTAMQ`#_y(AAub4f+*igNoXU=TbNNN znt}*{7bQG`zqbN=)iCs$QGf}a<>6m>EeGMds)T3>#WXQm#5NR8&VkSE(-B-sdeK2Y z<90@=cka9KF_aG#HbwEZVWwcW_#Ii52y~VC1${ziUL?!537M>+{S^A1+OWahU(p<} z@5D$#WNSocZUU}|xPG8YEl^|R*g)CYKE0WR(@sFgE_z?-0e;xc&%Yujd;eo_>Oa4X zVhZke3yk1^a5)?`J|iRC#+zwJ#E4hJ`QzkiG<-m;Mxb*r(L&?#8GxWPIh1 z$$Sv2CCCdW2u@e6?*9!r^uM!`BM#RW^7|FpfzBuxbG2YOsIB$SdASakBA3>fs zM+2y?G3V$pV&=M6DwUV4h!mvDzM{yIn_^8fqW`ocA1!!$R;F*K5F23hP0ce zvqj`1^><4+yX}HsfyXk(Y6nbi%m5WK5y5#uJS8xJD>yPI2~+I2%=pTe2FbZd7&BfE z>e=Hwbb^I8PxCnMbXr^_tJo6bViJ)?DT3{Y9zGCf?&Px`3yeuYKK9~h1S*k!4%kTV zt$-UNd6Laz>v`lBL~=w;;!G$rsYblAJG%`dJ1?LjCd3MmNhT}m+%$8NVIb7)RI=&$ z-1{xG-uZ|FZG9t|vzlu(l6st=De(nJ+>}=`AxAvC&9N@hVubcShF?}$<95?5Hq(^6 z+~P`L;Od*MhX+1C&oJ};@J7|v<=tUg8_=`-EbeP$Lbl9hUmLXUU~BJ@H#O|_v@EjI zPmCXLsVfc~7Xr(Q5fWkT{G5C$>o>kc_?m1bMW5zx#J7@6o$@9K1&InW> zmlMGlU8*;sLN@8aI^^r8DjBLLHWd%FdOwIOLd$W_*WbgwJN7x@*{7_m`Vs7KJ~@SR zp*d|Aq0Yh#^Gk;zaTD~8kPLr#mn&Vm6J34VWm{j-wz-4v3mu(I>+U_A=G+ojpxAq% z8qrEOpos%O_#cXQbpV0K=+Z%xeED*F%BFHV?+D&&wIa8)L%0KA#I87RdC8;b)mmVL$8cuAq?&WRd0>il$wcvEoH?F9mKy#TcbR8dmx0R* zxbbp)QuqI2?@i#LZu`DrtwNe4OT<))WSJIA*`{(L2_bt-%UPD8sU#C)rpO-763&Pz zLPC>$H!+q<$T}Hi%m~>sQ;dIR=HGibuj_iB`@YW3`*UCS^M3B6cQh&nP2gN?|0O>`d!MZ ziE7u%86Na7(=gB9)$sVj6`Yh=bu7AaS5AMT?AeD@jre|xwG%f-2=@X%5_ER$am|%ea!OFn z!M90A=Y*x8S8Vlg+l)JScI*!AS$x@)MJk4&biko_K-0h6c>lOzyTmKOko(ih8xAei zzgcd%VBcMAZ}*Zh9d-28o7M%=p+zRguWjbg^Vc$PEjNf)1oz{M@z=~zJ_YVMXpr=GUHzZT75-udR?|89UrDC^<-hY^o3#n-0JKX=@g-Wk zPVEUK!6FGZ%faFq9Ola!#1Qp`KyC!Zpqg<=x94%Q*2llOg8Zw5aELW`d+B;&9?5%J zA>nZWCN!ZFSw7`VPo*ng=E$b4V*r>}$j^S|kl z%edg5%wt}YL8NY!rTQ0SkWN=~@&t0CJbE`l>q?<-0E9K?0?0Y8c?nK>WEEnRruX1E zKGj;7M^MSbN4dP{O=Hj<=$c0AtT-(OG6=p@2EHnZ@MerewM2TJ8rBKST=1JnG+s`=A|p0x~c#bbveI_8%h7=vmV8 zkPYaiAi<9*tpIpJ31E{4SQanQOSvX!`h3Xp@B8UD{=IG1-z8U&jruL|im=#1HXyce z+=iHp09IQoSYnraNPf7jcsfs<%Y2CoN0&uX_(vx*wU#xJkK!V0x*C}=m~}?TkVoEK zh!*$Ncz;0CutKf(Jl%G!=408?uU}(`5cm0GK9&fcI9LiJi9lbB$zYDmHB+P0QX@#` zB}qI-aMmXIAU6O&85@df^BcmOR^C&|I_&OzmDOv3O!Zpn1zfz!~c5qfJ# zqA0ZL1ZOc)mn!Y)%ao}LDug7=IZ?C_Y*JRDA05rb}$&Ui5P0VWX zJ@K_XEhz61JZOM36e%WlNx~=iedL14at38j>?D`Rx6O)zP5Pi4?TuvoDlq3H z-a&f<-A`4nZW^)EO|&VT3lPSYd?Uh|9vvn_^f3JzWwWpJXE&$#k+=}52stWE!Pj67 z{Nx@4njo}iL*?-#t7|7kIRPxP1pFEa7XAnrae_a05?Fi_mp8trKrd4ra?U1f>LE%( z3Ez0(+|=0vSgtllAScWch;p|Rw6z0~5x?R62}+~>$WbF@?Glbx1W2UqAvf#uCwj_W zmFdHy4O@IXHQqHS5{%yoZWO9T69TgF$fjWDI}6a@uyH_3@b&PsK5VVC%7;t#`vy1b zr#|a^Jm|IN<~g$cFXp#Tn)=|QsWjPqW!;Kn)RzY6k0Is zUi|*ogdhbS4gVdLsc&Dr%I>sE$Ls?a%>{3xbbxo31Lnl`;{bzmyN2(UsiC3+tPf@a znJR7?#=Vm1HM^t0 zJaAKsO<~?b!UZS!F)Ok4n;NSCFv=w=cEausDZThXn-cm7Y?titYHz?u8WDLu+uBm_cU*ohUuOa`6IZknfgO(sGq!OiUv|c2vH zjceLAyafOQnF*xg@w=S3rt8Sf0=wML=nW>)s3@Z76iK{6Yu6n&V*O}_3QIqnxD!wa zysx@XXYQjL>E~EK`A9lSQifLukiw@XQh)?!(blzc60-@G5*#V-^{cxu5_-wsoM}Zb zx)vI6f~%S?`GH~mCF5xp0}F!Z(^h}Vne)02eaG1Q&3}mK!EdRY6Ug_gcn7(yd0ayt zR+!u%OV|q~7fjL(jOUu&l?+dU&fwalU^~iuJ4zm3YtI#pE~`h|?>nq(XnerFw6ZT{ zco3Ie7u$ z#AH86Rg;UK!iTXm5dcLmEK$ucqzJ?Ci|8%HFeyEvdn8+H!`5CcW2^agrs$ zvWWogF)fS}=<@essl}Ar4QsqT#e@&*Y>RwgH7?>NAW$F_A<(#W<$9*FaT}o4-d2Ug zX2d!?*$hez9No^gT?b+9tGU-2_Tn~S^y1EAL!X@Dli9?Dqo+}=)BAf zXw)$}&p$!15WZ*-!;*?+QfKduptcY$W7hhD_GH6KD6DJLk<#R01r*E#H%7>kcoW1{ zylv}1%>ynbq8u&rnaN4^Daut%#_=+AQMw^>Gzz2+XMhMG2{fOVKb{w6Q?ZGVT1pOR zJb1NH#RdhtIE%9@aC`^2i@1$Ln(5~6z~jJl712bx4UjQDIoPzyMBEv$Ovd8cFtX(y z2F7T6F1^`ylf^S%1Do_L$l_>m?+=mZOdw&hMR*@LRM?FIq`nvWwgTpsZdZ(cu*XER zt>P507)otM$21tHnpT7uZ$NJW1FH-+dJACKnq@}ebep_uu2-_trzLj|V=VKA+^s_K z-g6pP(1|>AFi^>S28G-}BA3 zPikBLwOWaOJ^F9^QU+HYF!zvO^&O;qVfn5EJoK9D1+TN7$3{2tL3=3ChpLDiqaIZwA&opDK@PZcyke$E>f9O!f-O z9U?Xe@X#ub7cUcXWhaJ_kgyUS%g$?JZgFoEjV2)3=@wNlYC;TurmwQ76UjTJN?z>R zGJsF9@__^H_>NyYXd8q5wJP{v9V5je%~p$cPHpD1bE}1zU3Z>SP<*z!$KabiSMR7- zA31e3@%5EizfnZvRu4?ni{A;?Lo6b{r9H4g$TBp|z;?}C~c3uKiBA$S9+Y>#u}U)qRjvX#NfpI&@4kB_{EVD7wXkoe)v ztCHK&?q$@1atIL6SD%6Wp8$EQlxns$A}}{OsBqdqH}ipJrJO@klxwW*2pss2giMw>7q{`F&Tyll<0)yWcz)PcbRmEB~x2 z7+>gbq)V`oOUdywHgd$56&Q>C z&8C{I15G`x7dOAa-|$I%Ej`)= zl<{>1o4Omci5$1I8G99ULlxyS-)*Lq9y{tNIQZ2TZg=roY4+6H|UBJqG+l9#@jbs2bZd?L}b23qu za2QIyz?T=;3)9i(>J4dbe&{tO>cn?V^d_o2Y>aQ6c0lA~gY}!zt=d~T6q=I$k&{cf zXl=^E5J|?9>2CF8TFN+~IL9?ql9LXLm4ekc4@o_D!)K2$!G2d2=x{x5$7v ztJPiSesOZgn|Lfc4zpgBiW_@Vu=AXf*-C?gZR;N;mj4f$FGU6V$dMRHoYep~%L#Ji z9~P)VByKvZv!c3>pvVPmS#;brbv2hy36e*LyIjg@JkZW1o9faomFA^;IG?D}2+SLH zKb>{Fq3FsLoEOgc;|C4{h&*YvbaXW5uc@!ETi4TJ^zC^2*1a@^vU}|+d(2}yXEtv_ zlWm8ApqC7O~=Xe%vEM>>_qhOKo2=2Z6gIn3;J|2C1wssT`o* ze$%`Xv=hi-*Wv(et*j@#IdgkI!TI^yK(y3UaA)n))4s&*17M6bK5?oabx8qRX_kB@ zK|XuY;5!bKaRvM%f=j{&L|Jh6@~K|kd@Vu%H!`EpS6K&=C7ksd%_m50HQ6=bSEr+0 z&R4`9YxzQDS$QCu={35|V)^(>)2^vBM#=NMT#qcF<>56l-v%<+lAc4OGejgt7US!= zb)ZgT)M~6}e85P*$gQC?%eMg$ai+YdEMtGWGv~IaOakArXDkyQUsAf{e`H6$s=phf z)zT#5(aL|C$^7YaFrEAhzkr_;%$LFIe=#-!a;f4VX;zisUk3KIkBB|YrCcK{W*ez- z4GARfC1fET4HEY|y32X{X0_$Ko%KiFokdt{&~iBcQoBz_u)3$>U1M-&6BJtxxsQDm*v1;$i*UtKcZ$Jp+lSBV5G$&5GbhAKyJ>+tQ3RK!BO{dqQX>u{S-)}*YST+mn3 zt?zsT+@};eTFPv(Sw8f{>?ysPz|K75>VA~w+_v8HwOGl+j3viEl|6qee>_)#o}?)- zjMXx23MQtX9Sz+KE%orey-bUWHQG|-%}~3e0(_j}J-=!pPJ4UxT&#;bR$ABZ)WrV6 z;DwrWewi8;%c}hSkto%fYYNCsr%10W;9({&U|0^gQ2>XT%^bNF>MDYDUmYeFLOG;^ z%XVAiWJ#~C{}Dfw)_g%h+-J44E!T{Xj_-a%*IZFieRC?!hisFA-{tOJ5MjR{2n5-2 zGV-fGwX#z9a4`a~ZC3`1!1O=HmXwuT=U9Oy7+=H$FU&EFEZFrMkhk5sV#=6N4CT9P z#+uU_;+}gu?X`H)k~>LP7=Qe1>MQ00O(&@<&>y!tg+1K#!R_N|j~2cm)P~`bqUz4U zYW=zy7}p+?ZPScviw2b21k;iC{_K!YgYP;WDxeID%kou!;&bogY1c`vN6VFG?t>P7 zX)SL70j%d{=$e09DKD45KnGc_VA+t#iS%*(AAU^67=RoACGS7GX8_rrKaFP_s9-sT z0P7`~1`IxsrMAF&Y0XJd`uyKvbIZW-Mo=Mqf~g}BBwo-Wb*UOOJ;lM)`wy9(elR!% zIz3=f&dkk`fKbn?%|8)N{#n#t7I6O46aSlz^FJ5n%kAhtT8qxV5q1k76IT%MFmS$z zP~y_SjcLU}W~7p3!Bp@iJ1(jbfTmfB@CUdXZeD)aZBw@abTR1Jpo0^ym+Eb7+bH#U@rv1#+>G48jwiZ>n=*E^c1~*f%k~VjSLl@P z6&>hXJ|}jgIX6&(EK?eWhexgji&hkB#JpRa0eeK%gAXdcL5g4n@Y%9s8Nhhb$XCqw zU!o$ZGaM61&>=x^!>Er*@1IW%xAArg@6F-VVi`SMJQoNN7#MIM8aS6$LRKtSuS{3@ zh+|nX3YJRI;?G$B%7 zPU@|4OmqppwwWYpWcuhcV=m%aG%GWteogtYBFe71>hh|D#S=Z{R>m3*eTBbNRydWq z4SlfQDB9EKER`cT${DU4B})^~J-cfA0=w2uGOQP4pKJLmTPq%|p*w!j$ymvEd{v>m ztG4$f7MFGOLVa%KspUeSeN%RI)r;bHpt~6GX$m5Kd)^mP0S# zwcM42-PPPvX|FjS-26Osuyz;y#<}_ehwJbzyhBrv!Kf-3N3qhm8)f9mcLZKw&anh6 zi@MQ=x$$fX6j0!;fJzYfpB*s{apkDpo&)IR*;OqHK6Jv7Cr3hsN!>~s=|+Dy1sX~-bp$*(Ml*YEqVs&0)nhbX&Cb;w z5p7cxs*X16R}!~2Kse>n^DKHr@8jP2xM^!5-8yATj8*P&eCo@*)#b(g!%XXyzxWr; zYWdg2_`LjjOUj*{qfB*U)UeN#R1i$_*repd4tHv-xSlAjnN)p}!Qy|c-qX_Ig z-9e(;4ERCXkTtv*3$~l)fJPtHpYmOo5)bH`Rxr2~Q&g_m4V*w)8a`3W(&rCrfGb<- zLg`ckpnaPt;l1C=7FJ=Gj32H17Z5xqKZ_C0^o6fL zr|2hb(f?o{0Zr89M6s@G6eYOVr36S+wV{CY5j+3p8~P6QtCdj@P|2BHgu^sCJJFvU zz(D^!bqna)E+90F0eVsO3i$S05}ehKHlmJz_Ssi#0C>nWg*17pf2kC69{@rC+(8#a zsF^@Ib^e*J(3K*3llm#~6?F#jL*#0oi5Cs^eH(;@Tc*LKcLaJ#4R!n%;HLlcBKTK6 zU&TU0yY<`1L(;q(kS;563*1aC?twwQq-z^fgN`&h!YOLlgAZbD1WY}CmpIh!*l)6p z)MUx_zWAcS024ir5eF{mn`GpI(46GZ2EC1wrQyq-PzaE!hTr-jGKq2dA;Q&f0Ik5U zYe;5fX?Pz&1=7yLMS|{;*OI=MJwmjgK}c^5 zA^F(eBuj&isXi?J8ti}ysFc4#pbb@yYN1$*5mcX9eRgCNw{irq7QHC@Jmf5))ItF0 zcXN6$QY%q^?-k=2l3ERKn$S0|HiEc1RWk=W;;4VL7 z$4!OBy17=oJ8p@@j_sLT(jzN3^BT(mj zYXpWP%Fk3oZskuikdgBpDCfR4&9{Di=;%c-f1`;hYcx+)*K#fcYpAxygF^SRh`o}gF^qSu_8Z2 z)Dt1CiPkRYprJGO2~i%TT914{FHnVVfc4ywK)2;knuh#wHp1xgI6bP5md;177SifK z+!kMxg9*HYEgiWrT$NVOUC`qvgC9}JdETivTX0LDm|0D(0>7&nuQGC>p&opiBfJZA zQC01Br#3Ep%IQ@Qzf9VrqSc{I(@eF9-7BH-TM8r>z#uyQL?8} z7TvDroDJBr?}O{`kTq`qrhDdZjscTTUMg6wz9g?WO0+&2bj+m9C0JQi(v0{$=NH9@bBwIMykJ)_d2iMIG)*geYdnH7b0G8NnC2u@+EKnLD)yN^V}( zBg+97;ea4N6!iiH+I6r*e`6fYpAFQ%d}MRY_(v+gp}lFy%pJq$zOAr5aB0MOM+zwp zSU(mVpGD6!^7k4|tPSeXZH_vA<4yR1$#+cn-EW>|7klQg_%GWdPenf~vl~4JLvTyO zMqvR#6nGeG0dvY~uER^DBHHI8!e&4dRu$L+&Q=?T5-xB>3wEZx&D>gF(%qTJs><%$ z&bgB&nOPg?#Ue|-XJS(r@gGy-F8J&CGVPr^y##2Z#i=)k2K?_A>X-RnNcM4X{AlAC z+|aG}VY9!YaeDV9*fs`o%iAQhHjNH=8-Lcg6suNbey`b}F5dti(5kdHe^**!{ki-q z>{e$uN`(&XSF&3UAhHP9FbgfwN4x@Wb6NXE$nvFH_f7Z|8lP=#AJ z=m8wVTjURsZKQp{Q7sq<)$(M?6lxn0otewWW^sODn8!Swin2_!P7g3<0M*4iyz9GN z&u(njVf?1bHgoBVJ38R;iCIPxY)$gt9)3)C6LhY%Oi!5E7JFiq_jA&GlMZ(mb1Hk!{VDSU z_pztqy^c8W4*HmYB z+qoMGkh5C~dUHCu&3$%W|mN1u-kd9TY z)TLPB2OSX-&5G36PMS}|4Xy&J0~m4A{N-*y)6oL_P4L!ZO#Kxh&?2aVwYEG7-n;@7 zxc^80NDk28EJFgBry}B8QrX3LZXQogm;&_Pf}9{JZpaCM90me=wKRQzy^l0ezz9sZ zl9U92>Eodz{BuxdNc?#-PFcxnd!Xs@-@RpD(;Gm8ps~@dKO-j!WU1$=gV`_b zJunRSq2Wol;f(yritBV~Be}GkK2K#ZncwDHX?0&zKab7X6-)c}^@u^N(Q8<1vSA;{B1+OF z9DIOhOZ-@^S&d=6gFW|9eU)FcNL6tx1HPFx+qXZbDZXucA?ji zIOo}9_=YZD%ZzYQju!mhPj|iHC{ercb9bI)e|{|OP#=(&u72Hc@k3G24a?aM_2XYt zU!D={YCgO7cRvlTTtY#evG^&E;HnQQ`HsD}&rDgDtBwJ8(VmAD;}6F=4iR65-FElk}zf0oIoOt@CBRyLJJ9Uhy;x-8^H4^{~_{u zVzpxFpAc2TY+_ef*`IDbG1(s(eC!YG%cAu9-; zTe=I4Bv=Sh6%Xj>^vKuGq_PhcOP!RUrysGwb#in09iu}8RE(nSem9?EI=jE9`5y)~ z3u%jq6u}0X<4U((XP5vz8i^)Y8oFw-6_0b^l4!z!=B@%LuJx-AaX(i-u1+SzYZSM} zH0LyQ)dRuek#nKAnTFkAN&!{_v$BKAtxKKL-^0KQiy*yWVK~RBd_4rn20qUXc~Z+6 zOM=ARDOO?lLl+MUx@#7&La%-K;>q-~Q$0CytF!V=?{VFS18@O=(5;e+xX`u2Xy`zv zd-FhZXn8%Fc64tFtvbfIh06pWw){z)Sj z-DEJ4Smvq|_iBRE@{xQ~07$`M*w1gb&z8A+5T|E&ZV#Ib4GTg(ny|NsV~$X#(sonM zEQZBujDW&xq6suUk??fBp11vbt^CqUqY2-B|fS(KEoe^eIy>Zd0 z1{Q6&Fp|7BSp3OOBv5;K1G!WaU4psr$tU4+lRbm@E?TA(*LfCnJhcqjl`*V9#@G@S`whe1g#jVDF`30(Fb)dj3e;$Ltn>H;5i695HWCUN(v*nz2SS(}2HXbbr ztK+4eAQ`^Jl!4kyNvoG&JI%WDeWsC$bzmDbla&{ku_Oe%6=JgU+9=TtyRA{I`@;x2 zEDwymy=JAJO!9(F;<6ya!)c^QXV?VADpK8G%R_QollbOv6PaU&h+Ianj$|wEhI&VB zBgkc#HuOfQV6?3f7jGgC)EK)q;#C1?UXQr52X`$LcdUjRCqP12Zu)7?MB)z43Fi7S zP9vw`mJbD7h_wVu*6TWb*_>Pvk|mIk_Tjd>LAOS`25Im*l}A>O2-l%${TAUWTB+bT zaIP!m#Q-l{QZ!ONw+t-u%LFU#NHrbSH03(8bl=WpL$#L0P{h-U%F$^{-B43gHHDO% zD>6Cst^@c~c4e%I)H027t>6fjZ%gnT&?Joj!QS%4MhX&cH}IOpj8Y^B*EzUwK)X_f z=|~x(65QcwAS^5=#bvJOgEgp4*0QKbCMerk2m4I+P6wl+=Y{&_-8vXqJWHoz&$N>W zT;@$_$3Z)((FBjD1Tm;%gsC(QX4k;VQw)to#FI8$GLaE@Krw9G9iZTa_R`v*!Xj^h zkR#4<@Iid3GgmdAdyh|pS8&M@0{63{zt>?#J@$GN^?K@1IZ#`3il;)2WZH5+oP|J-Hh~q@TudRH`}E8et(|T=Kfud2K$qLVeQd1&Y4g`>TK)n z&R$gc7vh4(C-k36EU`b8Sj7DxYB+Dpc2bB+3=lxU}O61u-dm@PQ01jm;}q3 za}^B=?u+_da8lW(pu)unWO7jl-awX|>u5dm6pS{%d2;;t$l4TZ>ZY~d<@ZEhZ{NFW z=ZN!6KTzN@22sZ>RR*j-8vtFui}+7Py6k*>8!Y}tQ#+nXLq_UdpO&4}p0BrRX7fJU z^)p=J1ZK&n_T+!tl6B2xam(RoLj>cv#(L+-h2n$0P++UT!)zei{$7EtOr%5Z)KNQ! zxYI=kho^etm??J2Im!gD+f{cwrKKZ0cX0bfqaQoPQmhnjJE{ZIBAH`4S$zO{m~t{j zP~@yp+BI!QkVb;@G%{{+kM*cp4hpPK9IPkfS3*5CY6~bqOfcOh~A%C9dl-l5#`weQ}2AG^m3cQ7v=TdTdcns$khJ5+9$w;9a5|ApMMp7y!xUn@x; zAx3Rs@eABGSR!T=@jhL^AT&W_?UXra?KUGd^?-k8=Gw|Iz%bGu#=EbShp<9Q5LW{5?E;9y6H83s`nt z0QbDTEfOW{it-3%S@(;?!|NjI3@4ZTd?cj9ZzwEoTrkNWxW5z9| zkt+~0R{(Z%k1#_Q34w})Q*0D6g4zrQ!Q6}CJli(BG@S3g@XIzMZ40HFLb=!DF1OiaXZPTOvV z+cMQT)Y)*pI!MDbK&N_mFK1U}v#p$9D@X=NtqS*lh@7RL8xZWu!dHy9jnZ3(D57|i z#WpwR$V;}dJGzZf6HrN86LBL+t_>N>Y=YGVV#4S^G(*EoSkxebMVH{B$`j>?@6v3W z;}H%(*2j8Bs1pMvJ^Ws$(v2Ba=MeA&n<<~kWHpqtyvsZE*@cS@yMW1`9LYsroe zu;lbi(RpCr7QjX#yp^@z>2`?&ef4gEEej|8d5Iha2C0;dlPPWkw}4fFTM5~E;F@uO zdEcYz-g1AWQ}6Rje!9l{VFvpR{DvLk{&ZKEs|pUlnN<)k5n-8PfRnYkmmPK6L>@xZ zJPmq*+BZo1P%kB=Pj~CYg&M+AF+$}{ z$}Y9>f+A)Yik~u|15F#f&nSbAz)yQn7C+tYB#Sa&5ec9u|J(fxW*tG~Z>zoMhloED z40fV{txX%UG%6avE~p^z($q;bd%T1z*CJ|!L#6?dgxhHyiH#`e=$xwX*)>J?Ip~Cu zgzDo$C~=0U!<`!p+`S`q6qfQ?*K-kW|G5|ABLvW+{c{@@R1AL^`9;9Y^#7S3@s*%5 z(C~)+RmD`>EyN~L$y^k{M01RPDUa193)##$-g)J;?w$k=@{13)ia_lS3RBw8wz?&M zv&HDDr9XN;#_aMqUjO&8_cN=n$XveHdKSydj(Nl=I5Lp8drukTK2k@q{{G0A-0p8Q za+=9?+|3kh^2I}bR~%p1w~Z7ZJT|&_wzWaQCDuN-!2Zg8q^`ckpv?xmfUt*$zOx{8 zPE;F`<7`L~W7y_{$(@~C%r4t9eE4yzu1&ODpGrCVW4!CU&nDX=L!N5dD`LNo)Hal+ zENK+uZUp)$-OuhP{YB$L$pmug8!&%{d8nwD8lE#|UG~bbfXK|`-476Gy zB518yIY;AKoIl+r#i*%vwUVI|q>&=#{PAT9*hpIoTJY7k8h|Wh?liwc-bSuHfjV&7 zNOi8c!NI0in4bskIqUq4Pui#Sl;6U}114nm695myEa*?!urcyKl@9;UG=4I`+5Dag zvDmoxsW#vodE%xP*`Ne>#c)esf+1Qxhi8>8B-+O8!w0oVu;LFDElZ~lS?lQ={MxYN z-gSdt4v6{B_6I7uFixc}-4ezRgJ$a4xxvu6G9I-wisuO#ZAka>7iMc`=a_P8Y197x zYkt2+syywh;CnQS9-tVL_w~3sNsB~ALPn{u-F6m*Z-fXZErkotP(Fw7Kxj(R7XAk6 zH87h4$LUi}M#y(j$-Y+w=k~N4cLrRD&J!oL?=Lu>V_+kF)lvP~m6~+xmWrM+Txe=9 zr)^z7s|I@3%Lc?(!vxj)>LKfg>&45SR#|sAKFK%JwaXnD^0`{s5JN}`;_zGHgTU3m zHVm_B)&%vMV`~q|6u3j~O-yAYC3e>;Qx-DD?Naq)vKRryQ7-4AXrM9kJby%sL~~+{ zCl$6mqsG)3v*e4e9lK^tlu(>3dtYBL8r5t?Nx+mMX4j06#RXarJsX+=YXldsf)U4O zv*Z%yx7bvMWLcgUjnuKe^v%?EJ-*#Nr1r5)IW;Vhys;4Z%4wx3Fe|e)Zg(xv` z6_V>T*tWNocR;r|0WPI1zkbxdQtXTY98o*Tm!{= z-2t|%m+YdP7*?A)0_I7Q7352EtXY3eIQK80FY5Qy9&EiEg>TQ^c6Z%|^!v}}ckbO# z{pQ}2m1~b|b@==U@!G@GzJH!Nh-oCfXrPRs6}?Ho_9JVOyrN8z|3gH4>N;sdpfl&2 zSUECg3Dx|XjalQH@&eNTrH{DN?2!vr-{g?KQ;*M8$VYeGC8K9lpl-~rH)YGs26;Wf zhw#Gs>fA>$@`6MC4KjEP4PMW+o&$Z7#bDb;+Xw~gCnTwbS~4Ors?u|liAFcB46SHd z09Bqy)(O?)ecb3_x-MzKq1kgYrz`GJuYaB0qe%mSf?h2U$37MZGW-UQ1I?buIxC@^ z`wRXrm;6Dn{i2)F^L5o$?!{xY@AhvJ-<=d~L38Z-$^i$0$-k;vy}P34lQ5C$-OSzj zPOGk9?QKx@2~HbXYHAP~e#z3>#;n5{Txhygd1ECt0|D*u%;Eon$MK{%ouIq0dX59; zZYJA^9Wh*KwkE?iS`ebUkQ$xC^<$JtvWHwBad_1uMSI=dn9-vZ-t)UZ{krpYz}3s6 zOG=;;`A@rKeyv_yZ)?>2uGzw*vn1I({L$yJ@_dJEr-cg+j80u?B7OLizv02%%u@u6 z^yNSsl@=EFB>U3#*Rsg|uFsu^j53+6ub=QCookWKBibrE#+enTt#JUS>qgIokG&d-G$$KMe*2(l}s>?6;*E$pZz57F5mvY84qOkk4YgD9T@q4eR)7WNiy_?Lh1*ft- zBV4)*6iOaPXWKMv3iNieJ6W)ft$xT173z*L)a8UoJvcKggO6(6TTjsSv5;~{#L%20 zmFuuplCiX&<}a1x;$3V0MepZ{nrpYnn5SdqomcL@xA!S%to$@J10zM5C7V^^92ZU1 z4USXnoCum7tu;QW^bUciI4uFs*1To?T#G=_r@Ro5INnx_iB5|%!ZXNY0&F4 zfIXyHj8E-|Zm!w<*m|HyO})|}LEHLG;I3yA`A&p|JwTuPCzaq|X7+&@ZU0+lk^f)I zivPP_kLM9^93F-|K;E%}Lz-RShZ24f<}|Fq(?(Kn5f6aQf%~X!>`C8DNm~S7)KmBv zMLfX7W^RsSSws_#Tq(pV>v4!t*?=?{=ELZsf%LVlag%pVW_Tz&QEH0rMxo8x@RuI` z&1Db`8W;wv0KbAICj^UVRx^#_{1PNg!8 z6Us-mR@tU~S#x)+Zz57s)QKM)D+2GPvhvX*^XR3$$iZ;X&}Wv;{SYx%LUQ`ZoM5C- z9x(&vbm6!`(1OX{KrKKI1@M|6XZ}jKog_=elH=1z@xWdHvv$5-+JUQ@%9%J^!PSq~ zm1d)5y-mcs3$JPI_#xu>uI%+CZ7@mPG7umYRd?_X{Pfd%=2@T1&2+<^$6B9hvylt6{9o!+z7*+gODK(E2 zm{Y(CsszxW75q?;Nf$5Q*(LSe;NRzU_&-yUXWl{csIBGgWfrLi2nTg>g>ytnHqH`ytxibvjQS z^KG)9vPV0gSq!%B=@}zQm(~ygfKw7gBhklTgnAQxi-h!qWYW^!&)|AJ0XOL#xMD9! z7ZCzQVi#2?A2?0^={2Q%HigC7z%JMbfyVb5v@oq~j*-M8yo1&7*q1@Wd%3o^F1~}c z@m|1E=#CBupm=kuU47YUL$u+Y+^EO}39-k0ewX}!ZDR9MOVW*=_aCi%3f}t$`=^$L zih@;D`n zk*L^ly4IJn0u2qUmMN{r#aH3sVsLUr5UWOg-z=p6Ok9~>`V*#{!{S>KHlxe3;a96#yO9iS^sQpJ$Cfk7muQBs z3O1*=^$7MRwlXBia$d+eYRD8zpqvhG?Cv%9eM;Zg2}P5p@Vhgqc~~ zI?++lCThg69MnBJx6U#Px(5bgk)AsJM%LMQY>PDZn|C*Kj;Ca0&k2gr>P*j>c4qYd z)~gU|YF$=YU6L~K)yc^n1szHmaIbLpUYLWn-%lgn`L;Jc&~R3iw*aoDb7`rj1v&8S z`2wHCq?Zqp{k^_5{hpniR0C=KL8KoO)@skZs;YQL8p$LVE5{UeNUcx1RP+q6ymNFj z(6+VP#x>IG_vlk2^|oNoj)(SCi!3g?LvoyO@8j60=0t#JSg&d1&vj^IP+N=q@LnUN z7E}1qm1OTJh6;I1J&P3-EzL1-q=O%h;Z9<-@ik?KX3OTi?rzn>K-1h02Tr^~GWv`|dwp@1$Iz z91u}Gs?axP{l@5ZTiPDy2GS@GFtq71^Jb%u8lhs0yke-dKDx(t??oo9exu^j|H;`N5%Tl%dYX=A>yn^dRN+R% z5Qw&ti8J7Rx245j1Q?AZwKtzXRbQ0WpmpXPKWO0j=Hb*=zNJN`{bKc|c)D)c=jRK4 ztXEh4(1MW;x+rVVqhiP?5mrco5vCxRuMqwY zOvU&n;HsUa&mjey)Y#4fnK`xvjTuKLahyQX!dz#v7HYTR21OOY>xT&Y28>Xl`S?_t zzJm2kre-zQI4TTQ6F9!p)GF(+H{O9nd;o3D$(o8s^k4?Yo6B(VNnM2FK+LQG=iGB* z$MSBZ-Zn9UUi0!6~L8xgcbX>Y26(JmHq{dD)V|D#EwN@aC#E+^=YfW(To zvZ?9~#9nn8YA$*K0+n#XN@H$RXg z4{0QaDIRDWj}#?f_zr|!93%~~(p%3&t3R-vnb>fsN8r!Gti?bmiopiN*>0s;YXmr+h2m;tV-$*7Z`#3^4q8vfJ-sB;2=a=aHDQh zFOuI+@R~t|9$uapy-Z;UBaj-+z_E`Q5*<|wD?7I4BkwUMk@Y2qGC91nU#qbFS%+To z)9IS60UzF&ynf=NkU%M#x;o_J=D*Mkpg5(rv#xvt_&r%-9&v|UY5}(JYQkCIvZE+r zI30>6;%I55ZZ#>%(X5;uR&G8?E)u^G1r7Bj~?8PwBD01Dyw3T^{x8m zetMbYGXl8rzmOtL_6w`7bq(6GCK|OwS)~xk1{hDxt;dVDkmO8u;P=zp8g}*=t?)95 z%g4%@=x{5-bEksaVy<-{UCFcq43$A|RP@>k*wn}g+A zkV7cmTdrsmGmI&U_QHw!x{;zAws$=PYGX!n1?_C)Mtp#6RaKvnZcdATc6%OlebF`S zE!GoN-(5DVW4$%FX!wCp5=e$#Sfl|kn>egIm3ntdfqctE5kfYm#S3h{nuUh4Nb7xq zcYXYH_1N8G+z%gIUhg+-Q!J^v_{+IdBEe>Vup=Ftgf4aeZMZH z#u#BoDN>)|O&oI6JoL)BVE~9A54-p%cx09CNsk+<+Ohsk?c!chh%9UXcjlXIxTkCt z6$${ic^t9_=e6$Z=k6zvWtiDDIZ$d7dfmL;&f&v!V6KyAb_~|QciF^lSu;K-g^Y4b ze)!w|M2YFCc7N13@+Q@r3hlzorm}^I(V%7ZJBcIQ5FU-IN+#3{#DQ_wRse)@C+riZ z)Ja@rC6|4+iQ!l@xg|V}_t{obThBp95I0Y!N?sx}hIeyMU6}ZNl~4&5CtQKh{9}OH zM{mX4q;4|W&c(5u@5nje*5mmv)B5wGjShe@{JE(`7&)L)+&A;&o|tOSLWhQZkFMfX zrw}dk)~QbR!HKY6FsCTvK_fwet!F`n!Uby5K_lc_BoU(FBvXR81#iOSOE;nsby9o{ zxW@#^8Ba({$i5PKwW8uW$d{9Ya@Q-nwQ~F%>6+ZCnaL@x*$y|{^+=hbc z07sr>h=qL9{_BbRt!q`6A12HKnv5O5aZsIu4j(V0_LY+rr&^nK&(B9Zem7%Q^eMk^ zI>1U3>+cnK!?)%lc{R}=&bhW|Kj?1l^#rT0ryP&zSd0csp&H4tL@D5DR@|1wR#Z%y zT=caf94)PnE^C0CRRMO&FF7p@1vp?}=q4M-Dt#Omf}6#@RydMtrbG6vi?vjZv+Fxw z#p{BiYr(|;)l0TB2&#@;Q0-)d;(v%t&x!sJdAA7OC6m6pg05V%^$2=N{pt^q2JnzQ z=s<5uvmy#YQD;`LWsAY&N_y_s9__pk^L~_x0^#(T#gGA=NDWnv3He%V!m)d<&RCH# zk*}k2CNU_1TsSO-xuD|a%PgCoOuH}IUiX3&MlLfOtayk>+ZS+wlMv$5`&;+xtx;F^ zryhI4+!Xlfp$F}QMRW_R#IH%7QKfc}emUp=V(-o4q3qxOVO=dqQrU`_7LvWPW}B-8 zAx+30a}}~osE{dRrU)UFwTQ`1XtI|uF_t39&d4$&*)vlaCo^;Qd|c1>dEL+Vs{47q z*Y9`V_v`n2p8KD9@o_rm{G6ZdINrx`yx$HY!?~6sML`Nl@{OWNoBQOuif5_{`yO9V zth&xvl)6$acCmEu45H)0!~XV3OYMj=vHQ{wO!wuV*gmglle-xk7EZEB+HxX?n(J^i zU-51B(rnb4eIBtyXpkdM&mq5U!gr%NQw2Xa70n1e15Dl|P$xmKUDG>`E&suD$bCT; zC|KT%`$gMYVdDQUt}a?>{6&f7nuqiAiz18snGS!Nd{Grp!d*qW#0~ROh?2+(f;%b< zT@DBlHiC_l$S8r8sf0hEatm;5E?Bs@1JAcnHh5eSE#;LFFwhjYv6;0XNzsW-L$Kbj z`0mixwF%lLG+*|w4raj`-NnB+@c;W=yY}i2qJVR`K>5?TG$YJDWR~E-Jl`PtC*fZk zVfj2L;LWK&H1Hl#h2)5p!~981Th>YLQv-1b_`dKc;2?_|qD;iysuj-!?>-gi%aa#~ zVps|-B}z}9)*;76*+bU(9;<<@*wTWbA?_=iUM2rwl(x9W%BA2N-*r78?V9QX5PjPWCXeiBN7!~OXM@q_3TPX=&;KS(MQwX$H{!E zJ>7%iPudf!#V#{4+c?u(Q58e$M4YdX(LHFBG4^^vpmg)jmCJ4X3cGH7 zbOZYu3IB%pT^Ec$O>s?q?xDj&eG(_{AOIjn#Uj>@HsQMRbcq zRl?Kr4%plE8c>$hA&=3WUA3z2o_W=gdb&mC8whS)6Y-95hel7wVe-v%XB;#m-ScPQ zdIo_jbcL`2&da1U!e5R>=V?+0PzPR~kNm-NqOud-LEesB3Co71>)87U9~OY&c8Ao2 zf-iXd;6Y=NaKsBFrwGvRP~d&O>wrsWmcm^kuUK%V!2CK+p1cC;Jki`VUI1l$4_qLj zsS6-E31JIMq`+I9K!-5!5Q3k7z(4@nPkR=|2yJrs^q?cp#GO7IBt#{i3%Zay_h3eP ze-e%)g$=+DNZ;}g@!fo&c-g|tgV^1=;K>wnj&2!6Fcw26pwPfV+&%^!cO|3qwewcy zK=hveT9-{P1@mul1nEV@O&&L!^J21~B0$R9)NXzP-r;$<;OuS(Gd>o1-GH~ZUfOBSA*FdSN&*?Sq#7pC zpF}6@I3^T|Q{K1~{wj!R;G{WV>m+{c!}nk{E89NO#t^d!$+;NehH74`WVjz_1{QtL z|DCi8nqa6l5)d54TnN=XDho;i6oZAEsxp+i13E*Drp5o5*17$38f*mL&Lq-KkxK*}B;mnx| zK+9NXsW;~8q}R4qzavO}#vIH~Vasx~%bC~Hc|$nvOy1MXxQ04+mfx!$gQGZisGa%k z%m=dj*!JkdbdaefPyxN2=#)G$PL9xF^r#06d`AeXr0N}$zD;`=@OEjZo+|Clck)XR zPJf8!!MFb40bB1S;0KG(5x4p;N_)Wy%uymuU!bX88+yqk&^ue>q(`x+*NP>iTeaJL zCwyb1)tO_F-xoJv!QSka~yir~Nz z=cv-iTk45fxIkt~oF0LByoc@X_`-J^lux%w_Xhm?XQMvPS)@)G#aWeBgzPN#H>qF? zY8RR04Lg{ASy)g%%iA)xo$yEJP!HXmGBz|p%PYzU>R!)4Lf-I96`5xV9fp*`t8=87 zW0bjIRa!V4I^T?%TaQH@6#;6@1;K&4^S<#;2eIK8$wwtBwMSBSzNml)Eyod0 zD>-Muw)0o=C%CYFJA^)71$ENR9ciBZ!)b>p2YBCfa%{lR*132Wov9b;u05&VQhUP^dpW?$1*hy5V&(ft z<`j<8snK4BJJS3a`_xWPt!T4CiL;NbO;*|+aV6J9S^mnh3gXWX~zRSQu zZRMo(++g4htxAKBuOK|6XyEeCd-t;bSd(})Goy6;p`P-y^JAX%TV64PW7y`|_A~W+ zG*iT`mrbvp8@Tz{GPljVz`j`n&fl7z)2iON!@14d*SvzfRWDemNgrX^OYb3+51S15 zs`HPIS(SoeJx+h$-vStEkaWSrsqK#4ugPY+p3zP@4A__TRir(1sp)YST_G0{8q5f; z$|ssEu}?ec*GqCSisM|XMoqtl@iB>!6YkQv1%qCpmSO{ALOy$6ysUcJLpv895M;m4 zbH}N6(E!po0+N@T4<5%j1tV#0vpUVoksL$S$fcl1dO{P*3U4*|{bM9gY@5+BN@B36 z?WdoM>4+wl9VdIdsOin^)zED(ihi|{5x2A)3c^!C;G>PqDMr!{Fc4gkmxM7#aBLz< z)NMbfaP)&%;4NoT!D+{jzDwjZ_F91nXiIiwZYXh~zbazqob7TD+KOEr%9HIgEnb%9 zwX?W&k@C*X@^{O&t+aHokEjZnmfZY!EF?@pebO?ATHJeq@1+S<{c(EyO~a#Ft_j|9 zvHP1*LeDD{FJhQR=riHciF4Kk+*fDw&!|`>WgA&0#b3YYB5yKeq+%|&>Sl;PI*n#H z5ugJr-Dh$q5OMjGDEm8?$$a%k$DX)iu(=_pDo7CywQuP56CSSKAs*-;if`qXWipM6 zW|%Cl>U44%h!rV6c#h7l>XFo6PwmupDO-K^O!``){%XL%zI@BAJ-H4sc8w(x&uc#C zTPm2C=vx_7A9#Olp1mG*xNWWm@``_~ziNGDsn9xa!7_Hgkh93#*^*5I8Vb)XO)YNb z0fwzLN^qD2$t-iF+~Geusb)WT9xow&Zo0`^@HejJK`@+2D<_v1>_I)b+7BKFv+<>1 z0x;ZJe+q#|0Mc;eB_a?4-%M&$g%k-KQ8LWGz&-;;OrPw5xIMNWd;&fodF&W0isI~p z#X(w)74sDZO(ig5a6XVXo$CwB;Z-5;C)n%l^UhKqG=h!c13Gp2RQy|(8OmTa2;*5F z$BjjfR}vYgNnvQ9uSQ~!TgesGETPzXN%%cteeK2xb0Jt{U}4=;0#FTV05P6gRodeA z86*|Zbe0$!_Y%r0V|2cDE>O>82Iq~eAU#TJYbxXAKdxUD%mO?=KMxf0=O!L5{_k81 z=ssWpU=GTG_`wE6K(9!y1Kd)b>^-&vz@PbPsPG5DJ3C7l&qJ4rF9@@NA=hxw;2>@{ zGz8W`#BIVQBGq83l`N^zxt0$v#sS&Ve71K!0S$0%Y?y}ce5VK`aI#;|I34lYS*eyS zbfIHGg-n3Y zE?2z(JeCbC?h`yOZY%c*fu^;lp$Azbfmj|IEU=bEK?0~QYSdu9qE$yPk)d>mOsl$0 z8wQ0nfxq`HA)tq|ETSh{Zb(^=J+n}f#{=gd{971Q(a)?~VB-E29xM750rP`E&qffL z3kkvjyfFOB8u17FckpmvEeNE+n~`i3=O*M0=5qeTb-+i(FV{!_zz#SX8l~uDC=Zsz zAO?T%3?l|t<2a`(7w5@4W{eU2+&5~Co-6@5WDE-^viX3Yw^<9kAw{Tlf{cJD4QPHO@c*DoozS_l zZ2`XFqpE%WBSG-4VcM5WMm3u49>GE6W7Aq45S#-84vkRtYxJMdk(+Zr_*q7TfJ3=rW11-b8bu}uiyGO~Zw_(W)6_CM(QO!D|E0^_uxk^BNj zk$vhdUC_g^L-|=MaTC%*0HM})ym>U(mUb!4v+|U+UB^drrP#Z!A^TwW+!71n;puC`UmLH!VRxBht> zg^I+S!n4`DItf?rxXG&*C%TBsn_5KK#;-xEZHV=RhCj%zo#NA)@IJKoY5(-&W|?_j zhafY$Y;B;4yPtAQTV*0Fl)5Sw`-Msq{d}t5I_7T7HbdD?7eFoM%zc2IUYnVXwsbiF zZAQC- zSHcFzWX)O7Pb-Od4P@FR?~7FxyqkHKVx6?b2(dk)(seM&e@^vadH45+wG!}e{--wmK1)7nzdaTx`gEFY|nADbPCc+HGd)^7c_c)eS zLXJ=8ZlWU>Nng$s8pI~tpq|uZX=+kqdQgQJ3Gc_%VyrA6317By;wQ`j)2yL7+{4m* z*`vJRPrN665C~0s($SF)-1@qFNVhpft818J)2}gtZkEt`P{>iQE7goNsEYRt+%&de zlXBDGfuDMcg>kTFaMwS9An9nPFjfeTI!|5%+gDrMpp&rObLQ5V9L(yumWk5?>g|Fq z=8v=YW>Bmuqgd}R-dn#neiZvLo2UKN23Lt5QacyOcEzzJAc9f1zm#H&mgefE$UG1P z^M1)7H@P3O^QMSZ=#X^i-D^kkZKVCdHIM~<5HWv0P@kb zZUHkiw;f&`;}mdwfamd+Fu6k-)b^LZhQO*UK(=g#BghvKD@PWGgy0UsxDfm_j(^~r zD2OEO#Cb3f91(YzpPPf@;id%~#aS^oQ&^!;AX6;(h`FxKhgQ!13<*YS6Oi(Co9-ayxQJmlQez3-IR(RaVQw$S2=lCFxGr1ut?DZIM^Q4{w{Bwa9RPu-UI z_*`nD3-+6$Ch1aNU#fd$JQKZ@DQf!t+WBFDht`zi8j4m=raCwKCEU5xOOtGUbge3_ z+-tT&ZI-I|)nX~ek>w46fFC6gFVxQwW}8ESEPTJ7nOw8gggJ(;6=ra7Mn^373FOy2 zw#$FG17nvlD-}qgj*|1Ce8&%*3$!{_>n@{QPxlLc&#<_UrorWaRD!q;C&f)ddiVm9 zC8engdXD2^j^w~c$;_#H8X&6-^UnW3FS5E)-`H}1w5NVRMOBGb7vXry^6Li^{rVJ* z^ssmRqy@zBU=WS^zbWAS^fTTpT|$yP*bYLem3;$uITpZ*a;Ggyax5#EM9u+&gWuH2 zk`SROwcT?}z?-8l_Ppjd?D5fWr>md~e&Z**W77$j<-%wCgRkGv*t9C}JHsXv5UgM3 zW{yKO8g(`UwCiLrU=7Z^3_hmq-S6i6DLT^C3{nHEM`m909F^+({z6sn1RW)eO>^kU zW@^02SZ(QSOs=_hKSqb3#p`@6OQaXWN=QGMSNm+meyE%@TrOA9l_bf0+&Yv_IzPsl#paDE1`=-pHm$8!P7SYgO zWHxM-Z%a%)_j1G_pq*dQuf<|?8D%&yrIw*WtCw!FY@D*M7guev&0dN+S5))3CwoEG zjH+U1a*cheO?Q-eQ-wSrD!P*U%SB2VAiJVgrJfNONo#uE@04#4|2f_WUd=xPs|K?v zt+qDAZT@5HXQg+NE2;)AFGUlDpxXgb3bV>5!PaQ|$Ptrc88V#j2F!3gKg~SCmF4N8qapdZO5t!qNpg{OargP8LQlg?8qkD z5{C*uTZO<@0iojZ1H=bD+D9PXA~;6;{>fzOC&@JXdV}tcBxBDQBehv zLy(t7u=$=-jpHuYshyP zUA;5WvFpHu_VKHM_h`FXEaLv5%P0iOoI+59u71MjE!-UPN~Z;wbboELnKJpG5B&V+ zgp>Yr?%D^;Z~-L|Mx;Swnb(kJ;ZvoGTSq$=a%O88I zPYg3qZygsBnU&BzRyb_zOP3UsnEL%yUEJ9o?|`F~QgYUnWjtKotToMmh&%9W@^>5( z@JGm0H^jl-z@GiNZT;M;BA^_@qUOPa7*ha+Al(cH3Qa!u$Nj4DmI3*HnVSQ%dMhv- z5;j7Hz!M>iBe+%w#N4Vr=~E}XRg?Ri?6yWhQzq9y4g(n~fU5{NGduMHv=$Iq5DSn? z8OP!Dnfuvljd)?mjj2nazAqtp;RWjZzZ15)*fAHwmA=1{BAo3BPz7w87B3h}8HE_y z*!IqxA3Roo!YGJ_jH+M9KsF1l^BNl--3UDx2S^9B+;DShFhE(A?cRt#)OES`1g*o7 zt!7`|QyHVn2H3~tMFUEaCrc+OKb;jcr|VcSd7DQU@cRCnukwqZ<-K%#(u?h!Jsm2; zk_&TZHGAx5C490m8<)bC(i_!n#j2A754ZaUce#@nwah-Eb0l7|8L&8{uv{7L z5lbX72*iS4$8E;iQf|6^sivz6V^bQ4Lf=(|rX@7f){l7U)K?bIrg}e1&z(sYD>d;8 zA_V(wEGgbz+ta?=KL~dacdI*gZUeG!4YVL4Kep4fCm{I5 zvEIC%yo^!S=){?R^_NfTdX@xrEy~M!i+%QAzvtv|vigvdLq&i0EnEahu|$F03nZ&# z$AG;7c#^bHZZdf#GaHs5!hDlpHcEoOcY=(UI<_+5+X-|cnaPLXZ0~`?I;jBs5=x)? z!K0Q)Xxc+y7^7Cjksh5KgFqtZ{aN1c>&B5h__t^gj%+rTO^XWwdQNvoH!^n%pNE24 zGS)N3C@68r%&DrnCZjDVMzRTyDC_kPw&YXw_VGTD-0~GWQB$2UGUOI@5*^T`t}H~+ zae5X(tCKWkz|nt{$;Lj&^6XEwNx2l?F56Uu$sau7>*-U5QYt_7yz%yAyva`S>n`6^ z=PlS`xQ(E1c)*q+v}QwG8WFJnU?7H7_}1i*m+nhf-8O3=KR~J^h|ZMGHc3x7$02W$hXja4GG%j1Ds;xe15)~M zx&?eL@e^jA#t$z-yV&c25NP>g2wnqwqT!}*lVG3?H)qEuTo6>kRHJt`5rCz$+d#=o zF8o=o%G4Wf8BKt&D= zhdK8y*ohqDB2J8d&Sg7d<&Httr0LOp4C7nhJBNv^R{(W!%X9KbUB#x3p@_6%^3Jga ziIfPEMnHTphT;$&=9h^n9T^+;>K9L59=O`EO8ZAD?K!^_f=x$;KN{|4eQT9bYf#51 zshv|VQy`Y^ZORQ&f4DHM@ zU>KI9C#4so^!r9edRI?gaddP%CJ9uAbPTtg`g&?M>J-#R_CiDZv()y(T||cYMmJe| zNBZJ?RdI3tMmxl|5WYPjd>gihqHp3XAQ1b#H@2mIAE4cc&PflVEjJT>!@i=CV!t20 z5u7jO-cYZ@fHP?X;hFtsdsLJ)6xSC_r{*p_tnK--;9GKfG&r|OE$5o*w~J#84>`{E zccuF&=n97rlg;Gai&X`3dyN)_)c)|7e3T|-qW6J!wvz*tp0~Hdv05-c5gcVfEC+B* zh1VxQZZy(h>jXON0&}2&Bm{H=#5lr!H*VxRLansqBt}tdy(sZ)&xi}TG@{8sG}O9V zwzl*^m#VTQGrua(`q8%MybK`py&kM19m0o~Y$F2gTP?WtHL?d`oQPzci5rTB`5!RM zY@v;81#D0gRs43XA#=SwM5N0Dz26SA79F^H0uXjE79&)abn8%OzXCEg<@Jqt>2B}K z>g~D$JD>E}&`Lh_s*n~{ZW+8|EQZypjdL~pefhD0#~Jna%gwIU*hcurAW!z#(68m) z=x;q;U0Er7+WK_a-OxAIW~Hx7sQ=)i|9=xM`hWPlkSMea*i!ug8QW2fPHF_bvjKGn zu|>}jKgyG02Q5!@{sygojX%)4#6{S>B5ZhDfIHFWPZYw~t`0y7cyYKJl3_8U;6HziVx|dM_SXu;w z*r?-orw6JNN}?N{*%j}|ZT)rvxON-%akI!5-#R$ig1|lal?sNu2`Akn)DcJnxl2NW z39Q#T{=7R-Dsf_kL2M-Ecv};1RJ*zPiDrX6bzyWhnn+9@r_1St2Q7a3ETWVd`oi5>j(lJB^+b4AXa6S^**Rwk&A8s zhy+|-;JLg^bz^1v<$bKnC736tTu%+lE#Zds)h)y<)M{O~tx7QV^9=O#lj!gbz+4s3 z(-K?WR%5oMOSe_Yr_AVGF+Y3=7WP6jE*q3iNDAFSSH^LK^G|?o`QorO1{M&SeVro? zauP5CLl++GsabQHn;p|JXCdnX=yIowvH3uNuNc;UwklzG)Ny7;vZk5)aJp*MJTE4P zI=!#UU)TFcps26|=^D*L7R5)G{?ikxzD>*T;V~R2=+mDkg8l1bS zFuj_TS|QONS|FfoY|c@Qa^K3FasZ$c`bex~Q_lrphmPQAmqaC>U9r;q7o(Igy+(TWT1w%D%h%jAbI5A^)h^fQYvd%E2fc)mHaB3s4jYK0fbLQJV2}RIxo*Hc_LlI9}MV^Pd|d2ocw$YL>DfDp!-8qVx{jOfpxZj zL)Uo@FLW_k%i@IKmj{RtJ=Fuz1v26`%O9R)=ZwsV+lY6y-f(xne&6Ysa+wp=aU@64 zKiuW<7y}hID9do;Mj~BreB8G;c?$5v>H6v3?Q%Jx+}s-Kn&Hm(bW8l3FN5ue^#%rd zttVHc%@g~YZQH&UlT}J$QVXLCLmJOg%(kn?TNce<*eK^p@@Qe%o@t}H;f_*PWG`RZ zf8gzr;-~c&G;F18RbJB=e$D4hWOYUxAB*IAH0v7ie{Fqa+BUX_K&d1DfnGhlxW>@E zJ%7CQ2hZViziPBz;)>2@bOD_a-C-86{IaD1BN|fzzlHEk)c6-|Xf&9Pf9v)l>;6A> zbM$R=1!BIOeR!J8xI&m$WjGpfAHy3wk-Ru3wi3`*0tEo(dAPk$@C8Os(<-+*oP61S zp|W!uv}1tMg>`i3SDs?DhUbM^^;Q(64dyaf5i)*7Gn#_FTnAty|H9Dyz&p46i?R6c z@mW!0`>&BEma5(%Ttp9xH{(UPv1GTnE!cbtqO5g+myQqwg=S;rQ;p{0Xau(y5)J0(Xd5#K#ZJ4RTa5MLsuXzXjotvt5V_~h0T6|w*$MlJRsF7lLPt;2W;oN zCdQ)HbRlTW{jCnm92L|;rv!F+JdlFei7mH>+W}arFimx5Ro~^DpamP-fsBEQU|m%A zW&F?$ts(VcNnJ^TN`F6Kbwr`oH37i@|Ep0nkVJnSPef{~N z!;9Um{1L5x%*i~Zm7bfhi7^!S&p%@>Z(rT#)8CQb@W}1Pb-$33luLuVRa|a09efJu zx$PXF^jYA#tmRE|WzxL8sxj8`@>uWu`A){B_oIp)@OEg2VdQU*c9lJ#?^H zrk*wIO$O1M@GB62olZso>kAgvaR>NJ_AkD*T)hrsJg_%@!mzKR!+BKx^p4KlG3xBL z=0k?}DKb^@?;;Nx8y_y`<(nO({D$E=zd=qLCjdNwv<~3bz)k+?WIFh-esdJ^pAI?1 zZ1omG*U|d~TACto5QL#jZZ!{smm~EE7?Tx{{rG)7MIf^~E*HVM5Hy~?fp7`(S!~`h z3=iG(uUUAXf|(pf73#;P=MQT%6})pPtCaGlD{_gRc+n5)PA*NOdwTqrs{8d^g7VQ& zJf}0@s%EZV&WzYi>9hxadRl ziNLxn4PcY5v8p-(rgHki^M~g}Rb4_GX^g}hxk$ILOfw;~44coy^6WE@kLI>ZdtQ5~ zt61%tQW*MrylfR|`}z;Y3yIxd3+E4Ey>>bnbYGt$mQlL1Q5ok;Kc__}zI3^v9*@>n zoiAmW3cMis1EU#AIQcTvsE4C34EfOs&8RI|9uB9Mt95Md?*&#`jQ=25x^F8LKldzb zxg>cwKdGX zt*j1nt$Ch`*`Lo=Q3v`=5N#Yqj- zqk`X$J2l`hpt$RI!HuMF&I$U6?eDktSbWmE>DBI?thu=TQYRDm^P8>#zC@x3`4Vy1 zMm17mQ`$s9tsMK5hlG3hex`NJoF{ET^IL;?U_ib{J7FqDvoR=>Rj9PE!R9k9huHqj z^+6i@&ae5P4dnR4TDn=wMZqW0kfm+UH-bi?BR*2&;2AGa@HvC8w}E+DpD7t#3X#>aYcn zE0$Vd{{}XZI6O#;ia~Bdu0&=3qK!nFenzSNJC_5dj&@kU9!RRZA#Ns*KSEcKp>$xrlui(Vh%7rU5k!O0JD>;_ z4l*ghz?<0CkAI^5C;%%UA_xzHE-q`N-TP4>=yI{(7i~pzfQTLhU4Ai~aldK=V|>ft_g~$J zp$KnYU@O7z2!aG}zy}Mm0(Qgi3BT#BqxnW5EA_`B>JMNs4QM_*0&Dv_6#YcCnKa~L z?j+QDQ!|V|acXe`k{X$#pt9_FR>_b9qg1>TVCi1^9msA?OWe^Lx}o)c&=SC?nwVEmbfbT%PdL|G5p2M{hP~z45TcQ zfoIY>AIao}rIo=^vp8P@a&5=pZE=u7;6eanxq-k);cSImfH>nxz4bH_WIM))#Ug-1 zGi)GU2ft?EZ?lFw=TTc`kgi?+q{@%n@HQa>kVW{xd#%>6VIOizZj6<(IBfNU=SD8i zT3+>57WXz`x^+!~%ZM0?;537y?WZ=|f$t!sGRd~Vx01`MLqRwfbdF(+5W)%oc}*mv z-MAl+&!C~@^W~7mxT+9N0hDXHzg`T({|03;h&BVy45S93OLL&a?~YungMP-#+`-wN zQO`2pUjOzv-8zJQzdCzyS`#EjUnZ}Z330ZE!e4>;Sv*X;$;JPhcxnAMkRBOw0rUh} z85e`s=Vt1iV9VhUO!sEh$g#^QEvpeRdIqpMBcrs?%T~hta}iy;UL1Nl2NYhiA&Omn z*LZILQNjbir3YtU?Bx(~cS#v#UsKO?k5bWydj*P;0A=wnR_reu|D)#XWg-LRJE%_4 zvl%P(ozKmCt7o(~C;MYr#ZxUoJn%9-j{zSSLeai$8x(4q9axpaG_^RLd%R$rq7Brj1{=v1;A7-x4e#3)*em$>fqP&);5ge>CbL zEq^CoygKgJ_-sd?bO%C$+MTMD+Ki57jGkeUyZCATcM7Dgh*{DT=r@pseEhFMJBFupQ zMgN!YBq1-I%Eh}CyYv)`(AdQm15JoXxJt86pM1fn&;qs8#`N}ALR*|peKsQtd46-4 z9`GsE??^1!B6~=^bI5l*`2lTF6l>H;abQEnEzTnK41;o-zA+uu#C2(8KxUS96SW^W zy0is!i!BuyTAUkokbaCNpuE*?uVMJ;sVqswX&uFi?MC7|Ez6``ibRi zO$mBMjV?YPH?Fz^OQh6mGyE;E#!U|BW_@w`C34otd2EV35PQW8sthg7)S55~ac23@ z0bk*fM{#$*$L*rdXpZjd;rQiwzCL2>jqKpY+otK*=42zewEFA!;HZ?4!$Z3R|!PngPI$_A; ziBLFsJTKlL4LIx|Z#?AodHcg5!Ky3-_o>gk><=D0)_{ZW>o}>8gXfL9SMGFRu5M7H z?Eeywtezm-f?=w{g6<4L7+#yuAX_UqF?LT{stcCEzMq<~Z7DfI5QfO8s{YDH{#>;t zo98pEYiOrdnGRof;hhU~cyUqhz8Py#G$lS zO~G7j^!UK7MgNy4FV2}KP#pBA!P6nwFD1>BNqsRL6(FeiGE!yl$hKh}Lq(r>f$PyT zsfxRe1+KhTNyv@_`*Ee*L6gQC&YM5>e#+;U+oFH8y zm^eksi>Mum2WEQTmd*Kfrxu>^>}l+N?9QKi&{Xuo!M9E(2!t`1Xe@5%$wP*IY%#da zA@TmneOfiu^GrsxRj(XHts+IMx?U)ienaNLsIfcF_d50Ii?fUWju%k(RNeE!j8*3G z#|OKz`7CoyAD@%&$v>lT=+1tTM3QkA8o%^H{-!PW_gtL+zWIOsJ1=@;0M=K-Uh;S< zdKdXQfu_oh#S2Y?VIPp8%aD6f=0MiL7YeY#?fLB!f_kN>x34l$%Wh)8j^Rq%2}H6If0vBJnE&>lFd!e zp6S0%@ad}*STJ3D%Qjq^?CXtx&kg$@r>^@iPsw5$b3{@oRNhG7a}beL{b_V3A!Fv& zbZ@p2E`TXm8U8ZQ-mBjRB#7O?OG42go-?oq2no>nD%WB|FYr0_jEP; z9`~x=%gR0%+0OM~!wtFfgXe7s{1M6WKvY!2g>e!e0Gj@bLQ~Ql*hGHGjru2V=EgtS z*ew3je@0{=7*=RVKJc|KBx^grkcaOYtJoUKlxudDV#+ne2-C#x=qWRbj&%q8;1Pu) z=t7ZtY7+=C52{1!vpHCzTYW^weE;hj&T?uA>uI#lAK{Wgj&tp47!&ei-7yG6sD{|S z=QZLQ;;7;<|6cGa?m#CBg(q>w+!8>rNFJJ)uw6JsyG_P80`SshGpmyD~lfCN8;ks#X|abHMdJ@X6$=KofH&>(A0tP8@ED{3g&H6><;xLupc4eZ zezuEnahNo;JT6_Ro(%53aRawXNm<=ov{>TA`nj9{IT!=Xe7k?5yTX zRWC8qU+BHHenrsYDdy+uJjdqWn(6w&P1VxMeU%4KQjIBgRJ)RWz1&Fu&Rc|vh)5bF z$fUpm?ck}vMiCMO_twZZSBEbFst#5;R6cPlelk`{t>M~%{ym84b!;(?9K>ZHS(hM328ETy&GaF75n^G9p2|4L4!Qdo zPA(5C1Ch`6)tVH~-3%_4*pz5qd)-;2t5QmW4o+~9)cIRo&U-zvL#zz;aCXeSq-(TYU zwD_LhO_-$;<201doQn}tkN?4Q79pm*adKE8RR48l^^P}Yd@dUft^mOa_4i*FiXt%*o96tMHZysPf=v`~trV>diTuXgRD2S@Dh*<1`Xe0{NaHj`!YygKQ? zXw=ai=S(j8$IEYbjXgD}b1HWI{_H}9H*F^r<@es+utSib4c5{u_4G6*6St(MEjOHKHO6gVb|4vbgJ?dX2`N(UggUO zp+St`D;fDIqX!lsu4fFAezc-fO zaNfM-#+M6^X%I0~;Yz*uM$5CWt&1G2GZYRNnyP;)3Ua78Y$j|PfcbXe#{N7084@+= z_AS1v8d-pe9J-dju=In+q?bzqwv^BW5){z^Qiw^^@En{_$OewhI2ZvqdX^pf-DOq( zZ~Y4o9Oto?_`SQ39ZcvBKw>~}Z#~oru$y3G&QU8CFamOB@dF@~&&wo&fZggXWHwcC z1)!r53ivVf_jD9UhWx?91A_?Ock~+0d5&98p3xyzg3$arjyovOtpAg4eNBa|jaWUv zm9t`zfAG9Uf*K31IH-?FLi&APa@3z#l|Uw3-~ z%{4^&NFaalkbNs^%pjP5hJ6NW3vd1g;wEj>PL4>z1Ub6?FwPX(PDe;UR;(Aj3-F$} zNP<9xfS@qzUhPYUTtrq4v{(q`Rs|c@eND?R*<4^WH%PofM&#T9GQz@mB{%N#_|GUTzNNefsBhiQX`^47=)&;D3M%; zRWTUxm+K|*Aj{hR3)WR}ovmYc#VMN!>$<4-a;lX%JC5#CA5)x*XNEzIqB{e5~(y{(1_U*G-x~AKXTtNNMWXEY$YP+iau1F=rGbV55sh#PSA{+BF z*V9oh$Ip_StfI(e+1kBn@fBH#IrV{2<$42hZ9cpu)qLKsf zr0~awwZlvmzQjsjDbmuWUWrPa;O=OqBZW{B?VDa@n+s1}e&CgS#Aw*A$lL#MA?3IH z4Yb?5_q|XHBf@Z~jxD^!8woAp4MDD1gBt!&v~EBGs{`uFdw!4b#12brdNAw|Vs_(H z>0E}*eFhpBNh^WqU19Wky^`=u8*;?MRMDpv z`8pNOZsGX0oXlNk(nl5GIq#tUq~e^!#CT{6LK2&T<_|B4WEAZRZ?eub&AqJK!9Wf6 zJ>D2}OFZ zn$AfX1y{GzYsF+dd!@H^Ys3j^G&iS&sJk-05XU2N7ktN+go^vtqNEdQiEtfFf z(({8S&f>53$6FM1J+OIkRi7M7gsdTO#-#WYDCPC4gsue+NEB)$HGw1PRW{ds0;mRD zMM77>+4Joz;hDic@WrLT@uv;1|H0D{K@;bQ2yoTltzf4Jv7WUd&Ny7*rs9se1_%(w zh6G*{ygg&`saYeu)RQu%e|*#H+P*_%@EB>%aU!Kv9>0Coc|&P=*B=A>`!#k2kau^; zzPpd$!yW7b$|%Q~?y-k=ym(L{Wz`Hzrk8x#a^<6Ebn#os3KnJ;Yzix>{Yi&^)t4h2 z!8*%4hLq6roe%=){vr#oJ~T7g3ph$Tg-|*8o>VWaq;Vg~wWd$_RP1s?iq^%qi)T`i zG?~6!GsD_|hd?@GRE3+SE)w=}B6Q>yD*xDO3nKH)d>sr+r za-mJhqdWUJ`Yy#I*o3B<{!CVpptZ)r;R(D9m-B9DDh~JM zJx?H=sZHOhfJ=Hg^FGJ2U{XEn)7Z-(nQnu{2({f4YV8?vx^ER+clJe=nEc^&(et94 z!nSjD8T&J0OiQDlPFWm0`cl>XUAN__SIk#*VqCqr8s?^TKE)e#B1B4ire3HwV8Kf@ zlDI8^f00&6?!qDRhV29n1{j*%b?Ks=;uv!Cv2((=c{|BA*Z4 z`Q7e3cO*V>LDPmfUvH(rI}r#QgS2k$>_>W z<;hvBd(&^eTOEykr{umG1N6*p%-$Z~L9BjIS6f-J1MV4>*e+Rg*SiVGCm{|Qf3EQz zs?%C{KQi~&XBS*iE6HeYRc3m%(j%h$5#iG7ymcF4xeozSy0lopVjf7`Rw*RxbyMs~ z4f)=Zxmg3uhu(iISYkF9VV>NIu8li8pdp$0>~)Nu_*e^qBdO2GZbDX}<~x{es_UVN zbKFM+Fc~aAz|t5qM$|W}5B~>y?;a23-uH`Zl_Y7(F~qcrgragPr&%S0Br0d7k{qTn z37IlxRtX`jQmlwc$k~J$%4uQ-tApe?88K#5gvm@YuFRp|*LwD|_rCAD_3ZoEul;-W z^ZUJ?KfJt>!*%#x-|O>vf8K{v5b?5kB7!`@r=|w>SI-tT^AA1i)+&gOF`aR>s`ERr ztJyf!k^r~r{D@trK)Un6_;p0U*j@AM30I=DOS?}@R`s|?aR$vd=_+fBg~LU?<9w~a z`$6|E(%;|HIRdajzDcN+yp_vNlfr{xBr-j6sLW~aiTipb(x$rH{kOw*!AIu=pTQ<} zAkP3?D>I_#O2*&59-Mx(CrLE(Kw+4HnJAGc<3ZE&VHB6N)YAzn;a@le-b4bJK@_qP z4ERRE)LRZH-MLr6S7>txMXxEJ7!du16^zgtgHv{4TGwMU92q-j!>z2X0Ta$tF3#0H z`v@=dQ56A=iefCGtGLB?>kCcPP0*21;X3i$7Djp?%!1?68`W- z259a7u>B9=%|QnsqX>RrGwA3{WtJv5Mq-W4NQvfSgy=0E97FgN@vB-Nl@}QLU8<{m zKhA+YwJkK55fyUgNce4k_s)HcNNNAq^9gEal+NC)k-BJyKk|bMTi(Fp9bw(v{+b;q zuEU#z9SKBCsY-PD+F6WWHjEvw%$dCVv|f}SzHL`ROq$n=-_JZDW$a*A3>L6IZf^;T z3OjmN`QGVlO4%l-V>ZYhnG zSDlQz^bJ%~?EJ$d7tnv!MI*Gh;0J^@pYKwk#Dq)sO9GT>KgmWL8-E-nm-@RlncO%S z1!pQgs~HJog8zPVfI`S#4_noZGeh;c@q)2)ci$vb|&!ZF2eW8B2H_Q3gfV9O6YIhb}{i5-wi z)(!FSI)9=hrMi}tnjU#11MbIF5 z2kB0XV3KO-!n?*;Rj)H}ebMlQsuK zHJV>FG3y6#6)d#75OO$5t>;O8A5vePboM+MlCREqbl}7AFDt}8ZvO#&)^g|PL;Z#z zK|dqKdEz-qHDUOtANO&1u=BMY1gDixBu_pgFe4?h4ZKN?`Jb&TkYx~=u!)wgYprUc zBm62VeG|YKhH1zQ4(wT)Y;(-G9RqI(G2Ak}GTr#qS2?SZuo;PTix6`)>Oyp4UL`5& z0_<~o%741P{y(6J`u_#O@ep2d@q%Und4IUvFzfZtCe77d9-TddztB( zm7%~`z66XT%wBRv8)y!U@hq3ElYWgupIc3EC?c(~GnpV?;Uy&*;%p<#YNA1}lelD4RwCNZ^Y8x9nkLAQg^t_ju-YXXDOf9`qBz1Y(s*!2 zf5*|oQbZfV_&P4yncuN18m|Axcx zTxnw)w6-y9wuoZH)iydJ7=LwU#G{cgRf!(jmS(d_e5Z9!$fZ`z-l!QblA?chCu%2O zqqDJpr^4XbCts)uES_7M^D%JNVIqBTi;bB`mH14{9D2@1!#yCBa6e?wp_*?724z$g zoI4+=DwcKENmQ6=Tx5Ixnt=s^0xr5g8n$9a{e(}$^(b35*2+6Nl5-esk|B>$uXHE0 z9XH9i!BV$T0l)u7t40Xk_%0$GmU;z5F90qgZz!|M>@HB-y7!(pvWgYDpc)rbW~)XH z^EwvOTh*~w7RQP46GKLopG}EMIQNAt~n4?F*wJ|AzwONHLBZ^jsC5Vv|tSb3ri3~LnM|QyAN9x zBGCmIO?hV;v<%X}yjhu22QEhe4$Y^|0y^?4E(9nUKw*mJU@{QAV~Eton0DlP(H4F- zr~@}(joFh|KX=jK8Av572hucLDGGBvz=E-`OZQNp&FV4Vf_muu?a}V)5zExG4K^4m z&QJW95Q-UQ3Gh%T33pnYhgS^14}O=@q=q16fP0EGevQab{J_eAV|0Cqm4@F^Uq)b$ zB0J$4&E7mjn+14oMCJupTbhy(tXR;KNG>fw>n>On$d0#RcF>aI? zpa9OqQ9{Q{gU_w_ZiYmg*K>WL#b7n{oVyA>kY)`vEf?1v)R)N59D z6UZcq*$|uWkWNN9e~R)oS~;z*RZ#rti@#yr#hUIZ)=zjDk!iCy4UGNMJSW6d?k%3D zUG+B3vieD z!=i(WGN9(Wo`*Y@Zwd_ZS7pdm-+7G1O;tUjrmQDNYo740zG+esST50&yaom{TK={G z%-jQ~i3k2K+^$3Bwg^ zAb60&9pV|$1pJL7SFn(W4o_8rv7>Psi zF|&4H5I8Z{-UI2LD-;@I5zc&-1b$!}5#FkbGv;S=V}YlX!LjCgz%eEyQps7w2hhtS zNe?`mNgd*0Qrmp)d&ZWX1GJ~0WF}_-kJb8wQ4FSwOgIs=g3OVB^tZhetIph6p zO`wX`U|5~3>RyLG-y5o>)o)0Nb2|L+#kbo!dG}x4`s_dZ)B9l4K2M{Gj5VJE5IoOR zkxX@Rdh!HD4vKHLc-9c)9AAvhbZqLStXNswSj0NplKbME!^ya!3aJB9-v}dJcg{4$9Fl8Q`?Ct`IbC~luV}~ zk!>Jf!@?puzeIngvzL>}xw9?^E z&j8nfl4ro4E30+WfMioh{tn*a4IOCNKA(S%R0c8V#8uk;pmzN(^$ReKn(Qm=w^5{V ze8p==IxO<*a~5yn5TyZF4`F+a|I~35(}763EVRIdGQPr65i$$@DE6aNUJQrPk#WOj z4XMQ>6 zs0@LvXmzIFfrsFowpp|?u+nvX<`t?Y-r|p;D|eDlN7X#%plmm%VWN7S6b`)D<5Icb z-45RezS%LH8%2e530y|`s5dPxr~u!^A}6*)KI&F@i7EA&g3C0YuIPUBawGN*ji8+B z{lu`mY>)N^GvcJLWK9Im7Aby8Jw=cc#X+Hb*G_otT*3k(8LnVu3~MJOEG2xt%C^+# zSvSq|4nTP0is>2-47dwi=^Q~oBj+??scYEsaKfkOo(F@va>S>SgYEZy5Y?o=!3^Ldjk@C1Q6$kOCV@XKy7SNW**xms zH`;Jh$rC6QD7d-+#F}&-_Fc*h9?_#one57dVUt|@rCYNLX&h{Hi<5-a#ypy_E+;=3 zjb2SHLNNo1tn4`cuAcdQG{@Y-gX1mzT=Lf^1BR-H2cq%}E$1F08lqg6)_p=kdjP$0 zV(^Km zokr;u^4I0-@K-UeU~8Kk-#4t4==75gsk&j(*5{Uc`N^uinx|wlO~lTryE&h2XzaWr zh@`j41JNPJ(VQ%OlhDD6O(*Hj^6_+WSaZmM#@odQ2X#vfJkHP81z7McW#gkSwDhv?s7EAP^HyC9L`{pgO*hx6jsl*WeEhQ!Z(XYuHB8Peg~jKN%gQV(_ht0BxP)vZCb+y$Nk7LKsQrm#j8bTB8oB z5&J1H1iYd8dcq(pSpm(8KBADbMi4+ zpL3yVls)tMj*iFO5zM>X+fJ@I`n^P|TJh3H&l??QYTvEa)l9%{4&Nq|2yAW|Uh$r! z+zEY6k*jBYo_2C`a?RK8EffCHZ8RnL_cV7Em7o82fPsJCANh}&4F6XL`JZJ*|6h0F zzwX43sV@Inc`JWuqS*UGYD_gvA`4poRq+sj;a5#SLjHVLt2OiuG@$CZUHp=G5GJfT zPF+D%2MtC$N`O>9Pzn4JFm%>>)b7w$O&;;4l`fE>&pW<}(%e4JiFr$42V*~Wc6C|A zr4x6ynn_9GmrL{?lU*bks2_%UE?GbLX&DLnM=~#iu>g#OlyoIWR0W`N;F`aU}ED3xJw&uOM0na z@6RzottFq74!B5$J5~yP9#T_~(DiHqXlIKi@dHTcyj2XCJx4f*4mt|~P}LVn4Kos% zX;}iY(~}1PwcqfgR zffJo2eBX&$D4h9X90KFjmu_jU{3c2lynhpM9lp3=`)k*Zxd(pdP}M1WWHJK zrSDQl8~?{m`8Q-*%Tld>=EH!^`jyb&dKC~0k<1yvp!hZpK8{o$38U7?FSez@yQqq! zba0*5g63AT6?#4%+8hDW$K>x0c+z$Kcr8_w!R#B`a_!M(dZ!&#j~|Z?UiDon!t=|F z@B}bn<9T7?i4-`O&w)eRZHk3^#&CdJbM3)_ad3TaXjwpj0HS~myP{7Xx&Jmnn*JlFIhIg@nMvxadL4_P6 zIXs<8?xBOmW$szeO`d2q)B##|C0>-7Cv{D;?nD$LinK0lsXmW=r!Od*j$hGB=|&w% znV<{C49TsHH3LzxO)%KI9u9oHH@H)M+`HWT!r)Ng-{-M7q$+q5@OF&pqS;;iA#cFZ znle~L-^q8yM665RX?%#gxu&tv*x)CE6cr59pzY@Al68c(RX8xgSk)p(sX}HdFq0au4 z;LlY76-of$S+G1~jz3`RX^ta_qIF-D-Xyj?&Jl8r`8h5*Mi>TsacX1%0IOrF$c zAApGC(EGiIsk|i75#D>IXBv5p%7>2k%#-s>wA20hoR9j{}%K1P7e$4S-HXLaB= zHAcuy<5h>qajvtxDrRw<<1f<6^OGr(9ViQjjwXU^v-;hn_$Uz2R@C^Ka=Iwg=bAr? zs*DTdz*l2YM+OZ|HN$$2^?ywoG(j0?Kjn?ZlLAa7)eLpY(ADD5y)LC>Z9fb>lobJNM6WPQfC9iO@<2 zygRNX5MHp6JSVEq3?>gY`MHq*DupT8K^-qZet807!by_ucp72JQotb1vgmkYUc>cs zybsjm=uw0nv14p&9WOiaVQcP-xL|tDP1P7P3+vx#nC8aB&QA|Ln3ZR~EPl&S-zVc@ zcTr-Rrc#DF%j{?=->5@x3}*JNsoPl`DE+HUHG>bp%GD)EJ5TLKp+NWXM|=+ zovlhw_z80~Vcey5jM}G0c~bHM3}*Lp=CRs0B#icB@<8~l6J4u3RU@n-9JEs{tA`H- zI2Waf&Vr}PRO-^#SeXzMm#6ie3X013)%Q(+QQ3@eSI-{R~hTGlVk zJNL7k9ybDn7TJ}r!|Nss8lxH-Z`Mb=3@-Ae-ZCtR-MR;}cRJ?P%@vPjAV`HvKxIYuumciej!Z>mWkiHkRl>Ow1EVSz!w2G3^99k znQHs@PsYFX?>~?KZ!IeRqsCt@aYFdZk&kF}?tC47{iqO+ZmMb8JhAU%t2eQWxRJ<~ z7POeBMEPQFc-UA#M7I3a9y{LccWo0H3Rp~+)*dhe5E&FsG1Dm#*2VP?mrOIm`mIHb zOG|e3h2^6&KD&w^6;%5C8s}zbdp=ygc{TTpbKyGs4_nS*6~-Ul%TkSYz-GC$n?-q? zm8vb%mMvZR?Z7`fTw~0Gxq%GOc)ZZIG6lEgDh~tn9)z39Vi+)!{Sw5gg^P0li5)IJ zsipV;R=R_v4#fx$id3MQ(nlFQAxA5ov&#pqw=m*jxV`D|K(yw1y$20MpT0_pudiI5 zuVX|0^pHlLvrGEt*G%W8L-`@h>35D=nZiRNWk$1W^tf1&v-(ygD(m9RrTOZ6>r3K_ z<$C0O{YRlB*HrqYp}o+>3HM&~tP$~e|E>jZ%xqe5yX*Pe2d5rr_1j&@@uag>pc=H+ zF4N3P!aR{1al3TWM5^d>!8vg;ZG7gZ>BC2|n7fmF`jn8Ys;VZxX%7!4ZfvA- zQ%uxfk2M2~CSXj1@r++_j)?2H)BJ6;MlEWfR-Rc+Xrw9qH|#}Cq>OWJM^0W9c?o&_ z)WEUI`s3J~MaPtnGU*)M5bbpPnh9bCaSe!t%L>ro6K|rwR>d6aeaM&3zqdZSdPenl zNaUEGmu)%v&DR#kKyucXzK>)3gU-UV4OO?T`V|LCX6b_<+&+ZRc8D+wYB3Z>dFqW*v&*(05w%2NI-CQVXr&21)+SELt6%&Hvi7OHh zY~HYD>K9XJ5^QkE;9jfaR(zKVnwaR46bbIE5L^EFszVz4;$mtCsUawM0yE}@XxsW! zbP@E@sip7SKqV}70i!Vm=cTfo1!!xM%9oc8{ZV3vV|5f*NO*Mj!IyMT+09qZC4|P7 z#TozY>+IipX#ZdMKlp1^?k=F710SFvn|))20{K`aTpF^M`WBRgMS7W;{dnr z6cRF@m%Nl|cNK>bq#1&if4H9T4kM8kcu=)LQ(Xm~T0731*r8b$$s8moQF)iCqD&aH zy+NF(no3>J;^h%gC#2kv2<&Q0XV-Lu`gvbB#-qj!SleU$QKPmjF|S!ee;Y)Zde zw4)?k;EXK}>CM$o+v4`K<4-Dko;RFny$C&BcN}80-*Q!oJhXIDGWEgkwiz@0Ud*;@ zCF@qMi%*nF|Amy#xyfm@yYmvBSZq(hRG-@YI3dQ(9qHGx)AD{x?r~i5ymk)t9gd_H z{5D_XkI8%N9mnWrY^YhM3S1SY^$mo!8k-Hu5J6!X!)1{>Ix41{w7P)zQ9~kPO82|e zZJ(s?Qd>PO0LFla(RKSX2;gO z&E^{%wvH=2*+TqP6jU9CRJ3xf5N&}D=m6uIWUEW?P59k!Lz&U-I7{bLYiZ6~&LgZ4svOyO##IGd+b2BU$|J;!yulk%hH%zs*wcgOQry`hGzS!g zqlNMV0@rFRjU5wj707X(s^l~z>O}*aj6(at<^xqMi)vw&tJlCBMPnu@>$6~*GaY)4 zaG(9z@YVd0>2%LD+Nqz9*2`(8r1=@p%R0Rr5}bB)CMZ zdEGiK@1t4U3|~dmR9(Y%xGO$$UUf%j^C4jMQ!+GG-hDA9xSzmlvRMbZeLpA=jMnE8 z2;a~SBEz0|#8*$7O`yAuWg{iNp7*+i>XOcS-phyxKsA%0%LL7$26T(fxGx#amoqrZMP9u336Kdw?(f@Pj6Mx zpPB|zH~kj}R>dP9!ppU?!FaZAyhp6ulZb!hq;uvgZ+nGxzry0nAWp zD+PUpY8`^%)OTB5j8dr(Kz` zg08_<PzIbF%fU0sps8o z5Tx}iDk8gyTrdb+B^Ec^Y{riu$-&$v@Xxd2YHD!RN{(t*;JAj-h@!w&{BV~xU$?zy z19t9N>*hFBTEHI4kkvL62d;qK*0I>AURm|P-wW$BJuR3+4xZ&#E(JW!0d2)35H0Hp zq(Ey$2F8ag7KqGIz*B65*SmHZ0mw*Z0U?J%kX5gKjWa|ff^gaMHcHc4I=#)-96&TO z7{Z9+CV&b$lRl==FtolgUy*N<$IjIlwLIFCY<0wtxrrs*%TuW+o@tfl1vC`QG-&Dd zMiXVB$dkPym*^&Lz}CJ6O|92+u8i;)JGr<*PjZgtLG+vUu^`&H9j}iO42c`4<3NhS zLmEMPoxf56>>yGq5u}OJ3Fk82GW>>c)|dGi&)_xLNzFmkI1lJ$0XD|JPjmnt4-eq? zg_(^h_fjld4Apdo(72$j%mAI{#_VfWCrb`7eF?`UT5n7UYeUrG3C-50*JnS#q8$27 zU3OWP~i5G z%b4Iu>qogt0zvJx>hAV02Ld&_M>S(^v*_QYR3TsCf!WDicX3JURzMve z6x@xFeeyW9leHc#QsYf}lJgBJMz{IYoSl~dVEbCIuZSQ{!|i8dZi0>oCelD~kW5%t z{+4>zjiJbT8M^C;Y8y*abeU5|()^Iwp(V%fC}|tE+Q`mmc-4Q;)F>ebk@)&y#@1=i zoI8&kiNYcA1M0bq;YC3e6kCeZ6Bo70i>#nw-lF^17YNBQN;K(F@M8LtUDk#*nl!v| zjAe^24f;_MsRjO_jbnL+!?Q-*M~7|^axv8v5MtvD zpH@k_s9Mo2uAJc6=M%A4)0ojMV|9U?*eZRGnk1pQYb&3cV6z3Azu)kEtkI1z#bl3< z8`dYXBywPPlI=sHP-sVT${4O>5Mi8Q>9D+#hX{?*B)2 z{kGZuQrO0`KWBVZ6{-LOQZnCj8F2CwKW&AJ%%Ic4K2!O=4bS(7E^rGW=vp7ejb^iOeq9L!~YdtmJ z_7nUFY#hl$SP#e8pOQ2$YNTd#i)^U9v2>C;r`t!$M>}<#$}7Hqr9osJ3nb{@r4A=SU>``b>G>{| z7e{bW^1Lc3nWTR8no}7DDT?=5{O{Q z>Oc83heoi|jJjg7En4WRF@m9uqu#a;L9~b`quTuCBq8{d5BfuQRi(M=7dtKD>2g1< z6b!Tr#^nb7Oi7ijNd`$wi97CERfWGs-mI@@-gi}huKey2U-I1ZR@cXE)qG7_=h=0C z)aY&0{pufmIxx15n{_E0?=fe@_29j`94nX`{#ZE6sxe1*ax3n0H&Og?duVE{cIn-F zyfc&n=cm0s?LR#=vCV|{>Gl1-+tFce0qwi*Zbl52y>Tye$0(E!xs^6yE#vFPG+x!+ zKZok{CMv#pb?!q#!SD7F*pqsFIX5#&o-lZY{r$%frmT-T{e2EI= z;lFwQDGv_@^6=|G+q^&k0_-iMKLyx5;87K?F4JM^e~XUR_GB7iNnEyvLV8FEocxcj1lW;(aG?RD^yeJ#2aCYuhu0Yeq#?f|01EY^TMGZd`T|x0z^NI; zi~it-I{a|CPz#wmR(@;$4>*lGR3=EP=L*p{C-LuKCe@Q<18UuC#`pnH;x_M^0ILhG z+N=yBv-5v$g6f$vw5pXINLaIN0m{abq3SNqva&8Q%V29-NG9p(Ijor_&0Dqold3+Q z_hjzH%EW_x$e1u*ilDG&xx$S&MQA^dDv>=PbddCoN%IJzof)%;wEUl*LV57(BCyb9 zWjd%@P|0!0b5|JtwU%`+2%83R;H6k6;VgdVU}RZ{dE&&ZxqX~j&k`CO==HyEwi%!` zKY;vpXmPgWyHqrIXndR}n72R9ryj1j>4b-SmR5^;>$F>$w64y4^T|8s_J4FpeYv-C$lJ_#!>5aZg_{rE zIW0SXq8L%`+0L0bTK?g9mMRFP4cXC2Rw~wkx=#zEBusAT3gp}Qj+`3i2&vznQs?;g z@c5kutyQN|;x~?4{&u3G<~M6!FtD8z*Au2*G=pXVcHhPeh;gNEDX8F`Y#Ah7r=E)Qq-8PsyCP!N zXHGv`BanDzEZmFdls;8uE+879L`04>nUV{|~z0Y1Fc&RX; zx7KwK*-AhYSXc-)es+^A_)_o$LS&BMf@0bJBxg>f$0dA=FO+By1;gOm{`-M2W;>J! zW%nHc!*Jz3FvQ;pUJ)iq|M<)J4t%SOG$ilBGTNRbxDNGZTmyzRWZ1eX)_RdC&)}p{ z*$jRM?~nzYYhKnhvBvc9>Aqdu*?>uUfbLD-=j`!LSGk_FpjXe&Ac}$Vyj2m(z3%%qByXCsBAd*eikV&hda(R>o&oG;fksh-J zQq+Pr1*rZ@Z+I6oT$dsN@2(#Vf4G-gsoP#g+zXIp*9_O|T2#*rRxG?asUKCYA9-RR zWXJLQ3>_OslXtn{MPx_qIM({0{fREwR$4*%XP|fmzG=lyyjhyQa-h@0qJpHGdPj>c z>s@qmIvRfTq)w6hVNng#&B;hcD&y3G`0g|8?W9u3X2cP4M$o3XI2U%T7wC_wP#yU7 zxt#3!LZP!4Z?xNewcaEV3)=fR=ompSV=MJC64tu?O*b+bX!`xX>DkR6z z^#4j4zG$=#`)FjsA=wJf?ol;C%2s_H(Ug@aPwTQ{Qn^)>s>n|Dvu6z8dwC-`D>J-_ zg3e2*U+B~=ExA)9X#^^qeB#Pk{8r!~7jz$Q0)Ou(Ba(d0q_AwjYvH>yEb92@l>esX7eXCp3# zZq2RS>*dGXONjj!Jo4}7SN!=`4Qe^3vNTrap1J5V-J(zRYws)ZC@hh7bBa%JY`b`3 zG8OG;@~O){*CC#M`RM8UA7092P5#~rr&qeD1BHH}Q;WCzqw+HLhqZMjT29UU%-cD) z@9z7g%|t{{|9qB-OM6MG1hy-9cf8Vs^8lb$-y>G@Or`QlKsEJoJ@woU(r+MPcweR@ zeRTwwb$Jmap*D(=pFuZA=>;+_X~;g}hIh>hP9$ zuQN}!r5La)dW$(`(Fd!1O!fM!Hm`^0+?kZwG>}%~SfbK@beAJW_0_}339J1RR`!aD zNrjxQ4eSb!0*|{p5AB`RwO*Tfy>M+!{T|1Cj*ss9+`;d#3872SQLd1|BA3gKwG{f2 zAZ-R$gC8J~KPc(VZf_*f{m< zL>Zgj`X~4gq>|T2;%dlQoRX=J@M>Uzw(Jwai^l-cG+RWaWyZev5JGXo0 zoLh(l6!(*UH54r!;a&a6_O$(hHJh5F%G{4S!PHVaJe_dq&FnSCe^sS1uH#HWeKJI7 zJFr~WNN*A6Nmk%~5hqhJKrJF~V}w$7P1J^@IV@?>VKVd*9*@IB7l!&|ccsp$LD}s& zs|zatG_i&PUkw49aJ`s^I1^sO)gtH;aif*_#P*hgFC=PAcZdvIF{RE(USKIRMV3$uiqjH9zmkz4 z(SVS^Dxd;(y;)~{MMTg;Z$GPu=DL@Wk%$FLp(3Ge`R(>#DR{Plm+D9bcQ<5L`eE`G#BZ~=UaDphve%s$W>;S=bBB0rVz;Pj?P#cI&`U3nh(1Xj`Qbe66jz| znrLbr+i@kYDnw5t3F()Bq%#*r>*}eAqvD$AjFIDX1UVrwi9^uB z9(~F2pz^5W!HFVIw*19dd; zWTaRNiUr=L*Q4ZN?fFV?z7p19eQD}8_t-!ggrYHnvQi&lV}I#A|DZn#4YTbfzuiUV z>N@mA&7e^7#z!94H`G3^ZMX(9;vS^z8(8@bbeR9uVu2+9*l){lfeFY_4HV1|jyKhT z0`r<7elUO4)vBOizG0Par)93CLd53NL&#xYyTi@T&)zhdJeE*+<8UBWTQ3s0mi@1` zXg}|C^?GxoPWEo7{C9om{w|QSaEDTsSmtrRaP6DnG5N1yj{EnSnyzjZjmRK(znEXQ zURBN0kUp^+c$Ke;>XFLJolec^=txEyaGy zsI9<+Um24_c8N8MzswOU%FE8|Jy@zMb*2L!kJ&>q%laHN#Th?13tAC<6xyqyYVQ$%e$5Fh?U2JeC zcAe7h!Di2%3YU}g6N?vsQQ%Dj&oYmEm4qXg0&jHoz!I)c*x%$w&zuQb)8JA0{xkJo z3;(|<7B2WOxCTNeC`Hb|AGqDkmrm~vZLDsne(MlXIeezao3GP7_?cQZqQp}-@yzKM zS3G=$$h zf*)bk-_4`66q9CMIDeq4PNgu%l=!BP4?nnOnzG|J_=?*q`v%<(=>EFD><0x6{*{}< z-~N%qfB!cKYE+&-<^}=O4yc~DxksUs!jq!?E#hn&H6Pr09wL?7s)Nn>I>SVFrF_M# zhd4cldF`JBS7PGB)hADq2Pw`^id?+^aM^o8zh_JZX~+q^&TeKh}Mk^@_gv}r@;zcJ-a9Sgk@Do(?ZNOLNFDEb0)}1UU_JlF=lUTx(Yn`)pvz^ z!vZvUqpa&}-W;79+3t+4hCDfj#j(}cl=&0<%{DM{KZPU{He`%-wjW>|;a9waLQSpV zcDGwAfwv=yZ9L&=$=bYT?D7xaOkK6`;Xr)*J7taxvzD^HIv0iQCvwv7gO{}CRZs*$XA z+fH}Eyld|3L6Yowqi$yx*&T|sEGB7~!DVsD)4k0!naBKEYtxw)eB%j4ueQ$oDN@bLge zO@Vnh82BcSRHP1td^lJpd$H{VV?Ga5HAkRgzO%qpe7)J2M-U)E)Hl8#zIG>mE6$E_ z9$0l@-24@fv<^{5edMZ84$g9UMxBZdW*HYNFveySLjh)C!-f;zrEFRMY*0n3r_CRc z=SLI|)+6bV=ysm|`nyMLRAKfMi$Uwk67C1R!4e%Ck21=fft|@vnFy(P;dM+NhX>sx z-Hf!^Thw)GBcX)tm(;_7!h_68b z0B32>2Qd5Z7GJ2TNZv8RNCqum?a81C>upb$@wCcngN`@kIb|OoR+vi7<=QceQKM@K z1G(rePn`YDk31e&*maQCg+`q=snzMum*Y4Bm16^PLaA^6p{Pt51}97TP!K548_fWPQZ?#aVde zLW6A1U*50`BK>uOs z+VTg|Qc_Y%OF%YBJJ|-(PMn~769%Ur4}_fTI6)=TPm~Ai z-6^l(2Gnv7U?9CTmMRXEk*pO1F%f9`Ck!+G;VV>R>3@Ca{}-+jXO2$g9dlX9Srl!C z1AnRGe4}TWbTBslI=6C4qA1SV)x3fq+ddSa1ie@S2o!al=vc3n9*3lcLr@3My_*Q~i!X@EaoM*cjfNeNvZ2X!Ag{5V?Ao5OVk960xo4x&?y{3iHq*yt2rAuyqgC z!GTBvK{a({5S*q8+($%v9{6=sABZe~V!L8|{95&TqhfDZxpKUM`%OkYm@mAVZL1x$ z*X2-_-d(YiQvR{7hPv!{U4ZyO6?j;#o= z1Uz*=*I(gvi(Rf63ecs=&W$24@#LFx-sdaS4xqoL_9*Gn2+}D0Uc!227~{8h%+MGM z=2X{_c(=xe{IM6lMNjk9&LrPzNofU?!fQ7OFF=_UP7oSXnE*pPK}suK`6YH741Sa+ zC2KG98Ega%nTA{kP^oyNaeumNSU{Z_fd=JM>NptL`-m`3&h)VT#Fw*H7u$hlMiLKBI#aKIb( z9ES4_!@t=Xi4;+iSAhaA0P*RKH;Ppsz}jKZWw=PWUvgIh0XQW2n-IX*U+mz#F9^t> zL~J1D6TV9+&I`Z=eV-y`0W@+Ag8iPr^K@PE6mmeHtUI~v^z~ecK>GunMm6y(X%SE| zMXo)=V48N@U=a_7^BWN3Pv9Hz*7%W2@LJa-1jhAkKON35&=bZE;4czBIMh;JE6^#Iy<#IwJcq{J|pxYC3j;3ExRW8o0@eueY|{5t1h+V zYC%Gw$KCZ__P_|8w0hvaU)77Mn5HH|elSjxemOrlx$6{GxbOL6$~#S~Ci!^UP+`)C zk~NOU%Z_h6>f+=U*|4=PbF@JhssL#4s4#-i`#ZJ{5bhNT^U5bE@DY-Lb^8SyE;z&w zNY7BSACMn>)d^bsEo&6pc;xWe7peMxR6nI3$uzmB%Wc_vxbFnE_z(z%D7?L234}td zZM4`8LLr=LAOC!H%QboI+t;f(H;;lbE znVIVDhMMF{H27IY(y8se{NNjpDG{5l41{t*KfJYbzdn7S({$_59uv+OKg`hyj0NmS zADa$VA zeB=4r2lrQ`Dhuvg-sOBbPn69LtW$sbX#2JD=|KHPZudZieoCHsN>y@)g~^GhdU1gp z18UTXin``L4 z#bjDko~Ns%51JpBPO^RTwL6AQx>~(+j5Fhs-{-0_YHOZk5}H>0=m|r;y5*tw44hev zwC;A)7Tab znq>RHCxg8 zHg|uJ`n@bQ+l|7q_61(}osxs5cl`{aryIZOKk0LW2&%Qs0SMhcYBu3M*1I_yab9n( zxN#SOdbwk;n-`I&7ps0}gg&mRHb?_8ALH-&%K5tTzF8?r6D%4Wyj3>XXb{>qH+u>V zpi6>&7xCj(bzBHvRit2afOwSbV)Jo+f|1gyBAg6M!>gY)dE8{gu4a+2C^h+IH|PNld~q{AL12r8kg z9FnZafH!%~%1G#r?V{x*-LQ(_P}heneQt{0ewA(DNZLtWkaRT+EgarO7E(8cm@OKW z9+=bvT|W=d{DP^FJ_gXU!bpmuW5g5U30DKmhnnTb5y?wK#oI|nsZpd=kZ;#sKkRE4nNEr2v9LNM##C=%qb`;9GiC~bmbCyZp7(0m&+u4OE*Dzu&9UstWxd1?&3aK$uK=yy)NHORto-*XS# zExLQkd*|#mCA!*053}|XjjV~=@53{mSiqx?B1)M`fYf~wBXWgwI+LS zhF!C`ks69z2W3iR6NK85Kj2b?;3-ohCQGnpz-CX#B66dZ?L;?>oiRHP1COJu2-aLv zpGBiNS7M1B0W11I7?8YYyN`Lo`LnhB?G+ryW+lVs(*;wHvGHzJ)a%so-w~A78t@oN zNAo@DsJUU|XLvBRx|SiKX>AbS$EyM^xL(A>OV>pBPgW>4GLc9wq4~PN0{F^{G=Iet ze9w3vSc=UWzI_RQn=|iR9%=f@P{Blt1%rJqqZk`WGvXl2ZPX-f1l49Wp$oW3<`R>z zd~FC5!6v+2l*oSLuN1gJ*{4reK>7Xze-ND3f*6;7lvjmSADU=aX}nZC{>NG9BuAxX zz*KykX7N5Wws4b`>n^WXm_kc@rO9EcRJQtV9c7h@SOrD_~p4W1_Ko#i9`|XoGQbS}5Wq_Sa+ADNX zy=j=*){}Gzzq^LVO2Y4jCVUuQfuF}znEtFlpRrC?b$uW$W8K^wm`!iy2?O!3my2HfHsMR@Q|; zL^Ip^GtZ+UHWd9}Izanzoce_8licIimVEOp7#ze%s=x^xeU2zf`~u4{CjFTDH=0bOv`l`pz@~-nt!&Vs|}*-B^O9HwTT!n|@B^1!&sR z>!t#Js(tdz0YHQbpaa#}(A|Rk3L`6huEEGi`b_4_jqzh5mNE0H`A_TGp z=^zRS3PP0LBVB0`LfL|d)JPCQf=ZVag|H-L-|4>Ro^$TE{m%W)KmPw8_nh%LMuuZ7 z)>>I>X3qJx=Y5{C@z;4?4`9PKdX08^4le0b4V(-7-B#Vc=rXQp0rzyYL#??S95`ju z9Ou~4>_zGpM#WAX;5{NwK1M^1N%t_%TgX6Zf|sa;P7Fp+lHmQkPuZ1BVV-QO5YrKP zmt0NkT8bxD^Z5Bi6PMlTn>AVt;LJ|GB7EAHv3QQ&#&#e@1z^SCYqUO-#;6eDASm-> zvTuWjZ{SnuRiw702pN#je%|+607(+}t=%$=7Pbi_|GScCjubFwiXn5eraFQUcNc`{ z;33&~zzC2F6PQk!PNVoh92d!u3!FL`+oZrS-!7u+Ow}O1%jexkdm(9s?1Q{q^i|T> zanJ`4wD28D??P_WOo~MQlFy69`?4as4smrG2fTf3DH-`oku8e&eK0EDea)O=>Uai{ zs-9d#P;9B&JY9@i2C$juYVVEQ{>|VRYkJ{0M+dAYuqWOCq$&tdM88fH#K5YIq{Yf2 zd>Gh(ZlE@GF$t3MQpB3TJ4O6n=VNd@aF$~+;)^Rz*o*P@a47@OT_7%ckzziN>KO*) zz)Kw&PV78?PN`ja6XFCfkFU<%0mf(-H<%ZH1AmfG7B))%1;8Q^p!h4RstvaSUHrk`ss{G7J|yINo8y=*^)1_1XY| zi(toBLxP~J!aOZaK_t*co7RcDXDb8MK5?lby+f?%Rs(HlHBlpnMUDyp+uNxnJ>{w6 z$Iz8{^V_a2emMX^vDI8IE=?yMON<2#O_d4 z%d~gvdkwj}rlI)tXvhQ!t35von4Z5;Hms)$@}&>_zWS=~DQVX5)UcIPzhbokN858709wL_OFoqLJ@Xs> zvb56 z{mcs|TuY+aGPJS_OJ)WS9(G4=H`sfU`9n-1jFq*)vIh|Z=1!7>--Rd>5uLW6Gll|! ztBX(Jh?6Xv2QEy+R;Ksn^&K4 z#@;3Q@af!jBuosw%5V{E(i_xxv1H(%L?f%F7sfcaur7elG=bg@UaEqK(XPBgy*rfqU+o#=5`S>N(?{eS9^oBW~xDINfwDrk+9Z}bk z_+<0mmFoJ5mIH87K5S^$Uz2EihIQ$|d`8`!XQN)aVX=x|Vj2o#wa(Wh>W+l`Uhqxf z*O!kKTYP<3??;7D6+6|hWZUt!kd?56$x;yEFG<0DZ88hQI>T1Y0X;FP<`PHu)fP?Y zO9s_Ib+|0|T}>}`F*uFnCft<+2Xi_Cg1`wE*QsAnDEUA;$ylP< zPPUYb^ys-+5@Pux)dM3}evie3;VA+ zZlbkQL@b8b-o}TLKYvV6T9`h1id6ORtZ`oMmU|MO`@ic*-K1}0`aZglqWkLeqlD%c z%-PAghL+cz7Iz1T)sec{-FXbN>RjEDMElMx+bxMFc@by-1ilOL?hs~R!mkLf5jFzJ zrTr+l{OvJnU8v9s??-w~Pl>$|0FxQOoU;^Lwi3R18UEzh2_&W;u$WQW2v;y`2FTI% zU~M=~b^!|3nx7tA_h0$upnc|Nv_N6xpS@}=EXg^^AEH;w*Mg2sK|XL}W&E+UBa`RK zp`bQ&Rag9vcFlNQw~nP~kogeNJ4E^bd$Bvh$4;{l_ZIP|QIR!H*O;KK+jECzuZeYC za~fvbjBnR^gYql&0?6U~|J;w{EC!L;1-wUmo%z-5?XWuM9DXz0%*;X?zF>)f0JBA! z>kNlZ@xr=J)HDw+kCos0b`C0x3GhBQW>_~A;H}a&WtB0i_N;kmnPabHzvP{pGCuPs z_U{M&?_WOafoSzV$YC^s=1PGI{72Y>qZro7*9V$|PO5e+bUT?wXOKIV!s%P_M+(&H zw1Ia+z3T#zlEQZ5qT4OskI&KqlYonA#+pbB*%T}D!HbWgu9zZD4SK8j8{w^{P=a`n zeEd@n{hYxzxO9?A0f;&ufR&IJgF7IqAxvf_Z2_t?BYFq%axB9WWH>g2B{OXdK|p|} zZ#$_y(Ex#j)8+^pJX&N~BI(@SbkW*Ax!24hG19Wg*7a+VIdM=@9I{qtp$jlCS3`xJ zMDV);b(HOMGWz708+ux-)67-Ml7QDLZR7ZtX9cwRv|yb$?&-Wa9bNsmxp{MSQfp7N z`&_lYJT7wRI&SXjx9f`h(m%dR-w9HeZ9#_%M0_vJPl{%r0G&~3FbLav%l)$V17Gby z7^Oif8?{WPHL`tJx5^;O&Fr1<64Yp8VsS*$<^|E5rS;L`U~5Xx{byf?65DQ#&=sA}boJRrU&hELGi?F?7r2R&vz&oWN6MtaI(1bp%EOaKlh* z>I4J<0xVH~2%x-v&+pu&?)*@9=tYD|mdf);V?VnW4i}$Xno{)7u1pT}&?@oJ9Drtg zr#CX)2ih+(6Vmc!mKhGf$0+{#HtT%n&Ey7CrJYLGQ_N=b22JKwV-uU3zQtUBaqzTS z+r+r_7o|fAN24}eKYKfWtV+@Ctefk`RB=PGNqUSfAz`V>u)uMqMe*GVQrNV>agLq6 zckoIj?(^`%*u|PCWpyle@#7P_@6th1{97L!$0pvJtC>hHR==JkLgR$0beDxgBEppOeJMEJ^1f_=!NCj58|h0ayuDgr_S8hWH` z-UAAufGcVc(ZTe4O$I%saL@&ozc#t{bN>Fs+Z7&B-_C~fyzxfQUOo>!n<)W%AxOyOm}&3k9kAbR++`RbO?K?esFO?tK6}RAg0;K!?f=W;rwFx zxyxwKn}O@4Yx^%|a_G{b)a{>4hbcpeb}IKRZbhU9re4aS1=EZc z`n*tYzH(_19j=c)Xrs55n5B>+OJ#$LF|@w8fJISO;q`me zjvj_;NNBWk!_{=bSpI%GSCE&a7e*8dFwv%7=RIPwI`X8nlRd+9hXCCkZWF~m-I@Bu z;dY8~c8}$V!?z2AH_9pk*F$qT*;#)z6$^NArHAnnC|DIVGEUJ0y9>rc8PeTgh=;b?oHeHT>xuLz2^M zU(w7rC!_|A-+#S6>r}7oqy6>C&KG>p8`CZ962erXL?fPfl)Q+0kS}QIX?D;u?Q3#E zhKb|Z<_8oYK^A6p@>;c?kL4oPxM3nxjxwR&3l0G*ka(B;J;d2|jY>f;>vw}ZK?U){U}2CNaWa#I2pSpL9F)s?J?KS;AD`HjH`hTxp&??04Zj2-0>B@n5r@jOV&1a6TGk z{`Ji-0iWd=R+n1;xi$VaP=J|Ibux35b+WzC^lk;S;N*9XdPCL>P{^6q^d4~xA7PKZ z$x!<+(=hU&Wb8H8*H^+}ZrdjR7xt6U3?dRB->L{nKHPIubPbZiGw5k2Y|+C&odFRY z2HP^`2KnNp@y7^tPm|5X$~4%*H#(0S0b0y_;qIcHvM#UCwMz?B#%n!HDVzfGaMq=` z_|A!a^(@ii{qanMsE3R`@x7ipgS>gvZkC-sacFpHE8!|MBu$mLOgB-W)5r6!9d}=x zvf=?LvNicn1lZP`8^H@?Al8&`VYwQ4p{r-{Nk19XiI6KT_P+rn?9 z<}EDbZXfG!RF??Wn+H~gF?)%Gi92k3$$c=(p;jp=@0rz_VPIip9l*07YhQr{wFKg5Ut6iLu`rx8MQR&Y8NGXpBCay-DX&y@&*<~F0qZC+ zRJw52bdy!1W1;!a9QD%l>$t6SA#=Gj=GFMN;ae9=H(DAj%M}U@mKrBI_{G<2s#q3k z1>i;tLhQ}3Y11!c?C)8{7bdpa_$8dF$0 z-gWcH+=#I6hhW1MkN=EE1l%EK5@`5cwEsiEADBf4lgX7(P<8&n;0a{0qXDZI`Z^KF zVs8P>=&(9yp6cSr!l3jlL;@9bB85ncpEPWz$DqXmlJ4pv@8XdVnHhaIOwkL-XGF82 zUriVc7d8zx375d(p~8mGq56mb&KPK1zvJJ$4tr&Ac~5Z`!ReiUv-!$#zECYwZarWM zfy{~T(=DNqm$`7XSsgFUU?VR0Gcegh3$k7g zfecIZ@xJ(woO!@Xk?VQU(NkO6re7eG^uIv9f2X77zXL1c|D;j<|3?(;ul5uOhS_#F zXg`=jC1s<*-0%%xVVUNXW>p5Z9mn4e3!Z{+m&`UO0wi()UJo*l4?xMl_l{@n<>IQX zhHD8j4Wl5p=h(M(%strVNpZf3Z%9ZG>g`RgHwbLvmqs(bb;&EhV+EtQMs^5HWTGVP z!rnF*CZLwI$VeO-2svRk8#28T6T&pFV}IX?RMnT&UemqX~|reu5dt&{TK zfPZ!BAEo(|XGt>5ZWX>^yvir?GGIkd|lmD6wH9T0BN}nnfaRF4cP~^P_OriZy`H)Olbw%ynLvWyo z<BdMK3T?K6ZrnC z$!lKmbkPae(Uet~pwI*{;b1w1F8O`(2iaZ!b3MG&?^aM745Q_RG-sA$6^8UP zf99|MZ;y@mcMrYNA^8<_lw%h10mm7MvBZ}O69*h;i+=JdAOegDd(D>unZ%kefa&@d zlcS&O?|orocj1hCOe;+u573lRTFP3n>Q7O=Y8E-?dtN+uYO`0*c|norY*M)AI0N^kPTCCi(QSm&n7-70;MGCikCJJz{_(H+%qD?LVAd z`w!ywpWgik6Yf86{+ArbKZoU?v*o8F@Y93;oGt(N56jPGQ<7ku`GPQLGoo;I6PPA= zP?P^W(gKp5;o+d49(^^Q{@#V%@@^STBxw;cbE08_V zZaV!GM+E(#9Pv448LB2>+w2Ivs_PXAahX07oRwDYiY387$sii9%xo47DHZy%JKhfJ zit~4Jqd4yw-1h+M??eEazTU>d-sjrpXsFtGRv~}0AczYbzzbOO-)8tER=BU#MJ<4)w(T5R_lfv(7%=H=61)$=%>nFvAZK{}5J!zLmkK$B#s51*HAsC5 z0+j7*kSz@NB8>L`ptG=85~8weF&%%8p7>vxL?u7-HQ@m1gaB0$qm|5F6(@uK#jBiM z#=jc;nyh#4`r%xSPrf^>Exs;&c!Lz4-EOXZDOgtqo;#SdA_I3!04Rr^}<4D z6Rmtck>H^@QeS6#vBx{WTkW~|_6O&?@x~Ok{P6N|_vJpJuu*ay@=g=li&KtjYms8v zw!U0em+IcVr{?+d7p(eLqztZNPSxc6WSumaIdK8Kmh-Q_Ed>Z?#_oIA!pdn{sE@4L zl`FaOp%HZtDY;N`(K=f}*4g->t)Qit-IMEk#jN}pZ>A*o=RE9FIxzWm=kJw7+D;qW zT^dK;TT@h)q#s>#laUS({!yQkVRu8{t)vqT;bdzFibed z2+NuvUA5I?ioAvM7w(jJd@-=OPPxN_%hrWUmO|NB24Bz@Ls`)!d7RY=E!jn1qX4`v@g1-QkJB z9#&epNXHb$X{_UVy?Eydv|Q6|Xq7K)&H%`Y>3Ha#r9yG*d{H;=CT_uRm9FTU4E9I3pOxYbXaR~$E@xu zyC0&q0U`_7ZxXR#&Y`y}W(6q-DF0dj7Uy&TdC_5UDSBB|WdFSV%WpeBa?Ta&=cNx~ z0||!%KfJkR4vl+(h<6&iVgkSS4*{`qK7y;vD@0$T!y0vk9=ApE`y$EL>7#?}EQq=& zR^kx))bVtY9WH*-tu54+?V&wb((R)-7=V^`dMviDF5K>S9rQRhT1$4^g+h}e6?^!( z2j45s^$1Sn$KQHOl{ve(k}9p^;6yPIsY%o(>Qy-y4>*p!OqN1zHBG%JlJSFRHhgYm zWNqYgH=8EhbNu)HwT(3mQODyQOC4iqI~OawKikIPlwv28pFNa0=5pQ8#%6clSpk9q zp&rYXk>!YTy|b5?s0CA!GryBAwOFAvq6VgJp%E(`5HL%X3>pP{uOvl>9d8^CNZRV? ze7TC2gTp$#gnVMJbRNebFlG}@p_M4AcO)OD%2>LKKQxKn zG-F}DDVl-WJn{l( z=ihd@?sUFB+tjr#va>6D)*MEd>NT;N4wOp&G z1}vVN5IX$y_PdTAPe%LbS|+us#Gxg3nV_vbHXj*68R8`hI%65rF=p)!G0YUr8(;ugF% zv_wggGulrqwx4^~>&8-GG^gKfBILf-A5S<$&yr}*@buJm9G|g$?fVSlJX?>%^*emR z>)ikND8X4(xf^;MiuVH8T6g0uxlOM7pU#>!CccVPWL z&;Kz#qz>dgG<1RfKE4&?u`ipypJE$8)2--*rO`nqX@S8^l809rpop9lA4C|NP#bJQ zb1q@*=jc!x9mk&Ua=@enK=jsIyy$Z*?W}^=yu>O;og|ClWDZ9S&_(@F%XkPrli75( zyF73$hpgC)rw9^oIoimO(bAe0n){Ye14{k|fSLC%hj?M2hio_DuYdUOa&8J5Rxaeq zk{J4iZDB|_7R=vK$eb;hpoyrOnK;ZN))HfaAAZT48rNR6^p#H3lb#~QPiQprU$LU@ zu*b6%py^mWM^^?~3^walT@vYeildy!G=zx&+9iIRzu97HDL&hv5e5UkQ2-X3LTu#s z(FhSMN$?p!d*0O^Or;=^_*1Nvk=g)8BNjH&lglwG;95e&2(FS#0UY5>x>nKNeCo@%((B=bS>TrE$hFMBcbVANt zLui7`lY>3JB3a{kwheI;{IdSbJF{(ndwVnB;LbFeqh*S=LT0IjHpFJL%kNS>-<6)K zJ$80tAm1UijP|+O=$`M3$ML5bxM`y z@4MaYIu)&!0h&I6Y0yRgit}dPn`WGg57pf*K5q8-Hm3BTrEJfI>1#f>N)x}D?78jS z#XVmA1KpEzobiVMhmD5gIG%A|i=dz_@AAze<&oXR1NoZ=3pX?NxVH(NJ7k*@tk~#X zA2{ZntuFe@WSROFyBlcXkEul$3%6V z{~-|81w1svzuXl(H+tCY2hKza;7snOf_)f0l>Gx|;#Q^t^WnqwKk0XJv`5eBGM zjHWPTjV9^g*#}_#r(9E(V7TV%eMuLFn_Tn@7rhINN}NyaIIAj4*n$iA?U11Otaj4p ztJ=hQdIx>uBHnx|9gZ%TB1OMy^*0T&2`xH!*hcITRGh zl`I^G!*EyPri>Z}7xa)ho`KR{YO8?`%Ob^N<`QaRC&sW5VG^})2sFib;^(DAAqyA6 zUcQW%&R;*w-FgjGd^2o;ejnC2dp~N{HS^!Weyf0WVdYJ>{yu@-o%w~%WEQx=mnpa14A5Sz6HIfnO zwEOi*Y_uyA9nKeFq1yX|@lH?XXxMvticjZTb^&3~`1j@4ng;_K`+~%Uzz9G$ad*lCqg~r1R!!G`j7Bpib!gE*Ll$@@vMoMnbwt( zlSbz}57R6xlTE!mw5pH#ZLLQ27O82tg;QLh?mU%}9VHg!rqS1iw0 ztVS1^!$EEfcm5DK69lkUs|y4cXq;|D|B6^sXaVUOvdfPgS6vZFm3Ik1B*a_^u#q{p zPnu^0FcSN#u-rX)coWc|wE(caGxuNupO?hnoAkH0lw=QYCzL!n0x0U$fRzwZ2F=lg zV!b`!TSq`k+g;71Si;|K71kjnSjli9Nvo-H41b{ihMWIh)glRm#EOl|FW+OIU`>Cw z_p3UeC!`YEan{W-;)PAKN~-EjzP05;JL9bLCPKo-5@v_vYf^^w2ThM_4xaBOue)iZ z5_wr$A@Zwnjp|^QO3kM`g+00fd#4UGY7P&MYl4zZ#s&CvD;+XLzmIQYr`UUjAJJ!V`UTswa$HH)NDThjen!)sr9 zZOX^d>nDH9PjlUI{>pPrSIbK?rvm0$f~%&BkBzib0j=m;%i3v2F^51SzpovN!2TW| z`x9C%{{vbr69%Bwl7HeT7OFXiX2^fyC?LmkyeIq(9%w;tG|Uyd$!ckPIJ3le|0|4U7;jQO^?f%y!6aY(6`OnNNd zgem7|nS30lI0E<~vb-EvQiYq+UGiq6bQ<#5^0M!i)qfc??JojB08zk!?o7s&bV z{!apd$v#8wM-G30$MGaAX@Y2i3l?ek8Nyq0@*;{}SIGZnEC5_rAe{PDklh1H_|Jj8 z+ksx%OJ3`3k^b|1UKJ?fs-dBC2+r>$el<37er4@{s;K=)JNyS9bSe_oOXpkcVURd_ zhzP=31osMQCWH+ZY>Ec&K6q&=UjV#W5zuziLqoSw+?>-C3>$~s1Zv@ea4Y8B>cVM2 zhvD>tyJ3{XT?gnLEEu{1YvKQgwk$+ym?poMmL8u2$1{m{(c*YFD2Zu)Z`tIiRyxFg zq1Gff6>4?*xv7pJVTZjzdhuMw**9yN+i=&HI{51wq3QTVe_WXyYAN&VV7YC769Ul5 zW$$PKi12v1E$Gd20R6;AeBde$;1!>F7Ft5tcNmL4-&%Fw7Unx#9`~qoxL2`uJ$Ev6 zf1l~d7pXSK#zR5AA%&qO%d%K^V5z{{gg>4zF90Kblz<(#>7P&?N3zv;MJwe7?w_)S zb`SXz!VQiBT>ITLnw?wX;*$67#}3LaH~SL~l3J5sZTb%c`4|B8L{8EZK}8)KrPiW= zzc3W0!F7Tbr(47yPOx9-M+{B>$s?Z%uCkO;WE1 z>~TEUa}(EH!&qQgiJg*ElEJ^l$C+EpU;pHt;$1#E^;r-7;PZ+HwfFFoO;;TrqFv1@ zP$FA~_n2f2u>l|G#p%@io~jgYpT5z)zunP)Y9s(K!U_N*&OQW@mm>Px4`75607hg0 zU_{h|f6zU&;FsL&<87`YbN3bUul^r=!^`P$z)62Z0Ip@+mAYCY!#qmrti0&3Y* z=koK6&vHy5T9;M0qm_zUvV!LQHx7pt%+|9UcV8jP(9gTN`a)m94`|tK}5A2pg1=_Vu>2R=8~$IC8jM@j&FGtgm{>2GKv;(?Ms<*(l1zR>sWLgEh}ii$npJ zeT(Mc#eCz`OM5m?SOz9o+NS33Us5)gsLDag)q|Z|C%XRNgdybi;mD0Up9%54MoO z68q=P|8}hYIV}I2E&p6Zf3x5Gx7=Hr=zuy6NZbptfRa^VLd#A%|26p+y>)`|%eOUu z=VGB~1|a*2B8YPpgR3Y(1#on0ixxzURIim&vKt7H^>Tba`O@k1iIHnj-ohQh$QZnO z@w~2R?i%7O&@1AIg|%ZgqOYLB6QLuMF1WT;N`1>gYEuXkxeZ=*uBUSCnMr^i6lm}o zl$V+pc!03`2I$rpUrz~>sjP&kQOhDA^{_E(aWQKth7{22X9MiPzcuXr2vx(s19j(D zWC%OWOGDfxt_|^pNsGbcI3$cBa?8NZwuw&|y#gEkAy95U33wxl1W2IXI+=s~ngr-1 zi2OH8>HDC>e$@+VwV!y<3K{hCS zls`bOqb~qbhF1nMyoi?F0C=m^iJ%=}FF?pwnUBI$VUr6iLkeKzSzD!$Vx4!_K(_Y^ zGS7|ZnSvgI`Ru*Wmv~V!6BSL$g_q2p+bh1SEwzyt8=vL(q9J?477YGUKjBvbe1P1h z4+y}i9M8FpH=6V(>2VFU`KzyxNhxR50x@IcYPUrp#KTE~?C4y2@|dAFL*MG+V3UPg ziRU)h`4wDfTS?A1h@jC_zoxT&c*O>q_%q<~D+#S8pofhp2`BelgjG4mxchlAfTJyi zatKE_RoM)EsRvvHL@`jFORqQBQ}sLoLp?_()!AP@uT7Xd8bG5{6Gt~EdY7a21v%aO zy6JrQ?sGONhc~~d{2nE&N-^1))DC=}yR(B2?Tc&s9KznFir;lpQtlg%v+yRr{Qf=L zBmLw1V^iPsE*yMx{!*^7b%}Df!9cUQ>$xqO49i;&jk6bAcGOK|r7RVZB%0vi0uOe& z?dqC!M`Gf`rq%TN`oy9!2L}hoGlQ4Moi6w0?$c(oq1+z5uMKCnpC35iU2^_AyN|sF zT`%6;{4VU5u#uL-{hzO`VdL=YIePF^#{`19u8| zp3LeGAVW+kOX3fK1PjozG7OeuKj{Z{B91JEhC4!{`-!(KFd{$NhTI9uUi5k;{$5hM zIscF$SH}hxs3b7k2+=B~Bi{MUa*pD&Wbh^aT2JqLdzB_b9 z)Q7?sZUs($t8)2bm@p73eoj~bq+6|PA|G@^K*!Dn1dYgcWND=9zw)n63zcOb9Rba8 z=mh6ogJmpjoItKA(EbU5#yue^WcngXfR>|`EfJ?GzJaoDc=VlDw@nWibt~{$7ST?D* z%Qc_BV-5bDD*3IXM8NvuLCF)Z0A;nn7150X>VIN!to2XH_QVU%XmUCRNz>IYqq;7J zST~!UfXPtMMx{rq3UhL;; zCV{&P!xCiYv+7<#!u-v?94R2E*c7(!{pBnrYu+=w-%~G)2-!7Bz^SjJuC&Zk_alG% zaDb{p)EF|c=`3`e2Hp0X2~LV}`L#at`%N%E$l%wLBTqIuOTkYG?ikuw7aC|UKSOWj z>X(ksy=Fx)wg(Yqy~E^iew?$U!o&QY3|@WY6eORleuY0oZ92}tY%zi2%`EgQfLv4U zxE}c#9WqJuVt?UPw8+C4z+D8&H-cNk`jA3cf01Lk&w0reU4y0&qGP?gr8DTXtRVryO+uw>;bMR*=P*}2g1tE0BH@Lu zwHU08Pl~H2Zn&{>92GSi5WqCOK2Hgh(F`uT*{9A=pZwm0E&yHjw9WpdB+y;>ZKg*f zl^rAj)OUOI$y^C@zUWDl7GTY9BJ_6Z4k=M3ej)b`9YU??W#=nEdB7ZP28 zGIZ0%I78JtFSzWGCsSXGucis_>6%ul@zhOj?H0SVOHp!1UzYy$%;aJB8goZHN!;yw zwvpPtTYQ-}FYa7Zu(5Vcf17Hvb1?Oiixo{N$ik>w+v{vja4lz?Wr>z}%{d8cd~Op7 zs43kCTW^aSK}j}u%sJtvI(%_))2*uwmrWT?Dq|*t6`CAU!Xey4tP-_JWK+89RbE7Z zTOr1i>|*=17oh2sq`A^n158G(Ijrn+4=ce6Jmt|O0hYzAiL9=ivImOaphYqdS;A; zJ``C!Lt%Fs*CI-++Yj^?9%W|9 ze9_DwZEbSRW@)%(P5L=+?LB%e%3f=i7QW5;SC&ck6BlJ;yI?zy(CbIi6hu~3;1_L? z%3V9v3>4cE$XAMfBynAUWEq0Dp8$lVhJR2TezW+2G2}`{^HYFF`ecrmN%&qwszaYa z1Axgtdj3-cbxsvPOLhQ)7z)UR6aNr+8xYn@<_-bFztsZ_$<;pu^7=rTTo+*3_?~|X zS^Y>KM`?nQ26^HEH}E7`m}=}F0$L!G2b8<*2(zF%^ZhMpv>qrh#Qn%DlL00o5j|Ok zUVaSdbEj<=#sDb<;Nb-$Z-ZRB0NNe>O4;6EP5}7C|HltcPC*0qiDq7gCIUX%$Tnkr zsbnR!vJE!E(h$KO7G{NXCVv>OzspDB?Wi?an%}wTwrmB~uE3;R8o|X2DT$ZNg9n`) zU+ZZ>9LuJW8~C6y+^zXm{^9r&JhPvVizNRxS-NE#kPsGRp*k^wVTW)!c938@S-?x& zJxmoUc)80Yq+p`#;<%&v#@;fjZeV%t94(+XyPC1IZHAf=b%*J+{^FqCNe0i{s$c84 zFy}sEy|%9D1Z3^l74^YmcjYonVRHDYrja>`wa}T{c_& zVU|{xRO&(1rmkHtyz1xcN^tevI9&F%Rqd8`2jGx-j#{t=Ket~cFUerN{Tuo9vIBxgD>l8XuZ?|juIh5{uFY>x zA3X83s`ysPo9(;qmz{WZlpFhZ=JRI^2n}4&u*%&vr{?l5?sib5VurqMzu$xektI^F z!=3eJ;8A2nG*d_&bJ);D)d};xTG{+?|MlbzCK%z^BR9;9Qm^^#l6P{{Y`5^wH5WQ} zG*)NP^MG;w*}D`b%k1HS2`f9idhlD8MO0Pt_ZIcOLr;!Bxc2Ras5RzT$Hj-6tzNym zr{d#GtMTZrJwJK#5!WmCoci0GwkwrdpT2Z%UrEj7JlGYhvaeFtXnRbR;l3Mk`wZ7T z=pTCctLvVJsrO%n3vD7|;(=No*y=Y*I|^IZ^9ubrtIBMhkb04!80dix-M41( z=zK@d;qD8i4?f>b$9oX?JI(o9fRQnDdP-m*7)ZXgZTHoqV=;$zT-FbMvYvPm&{wMf znyb{*VzZkG`J3`3Q2}-7hb`+-n%BOhw@>2FFcWNqgVU`qpiRd zjAMhzsh65Zjq-bU^rS!RK9u5%dG1hF)tWzNbrKsCq^B*NO!2zH>I(H!3;e1V$i8*K z=D?TFH!H`Q$|}p=Bz!!uzr}`^yr6fwz+HY{4K-)zeHBjQ^YPK;K56#`eEkx`PPA;?Pk_;=4G^^+v!j@XuqJZ=`OAZE10ku zu%?$J!LDfdz!Fa`5bXYU(RT^ot(}nbQ6Zr4u6-ML(+3C-hWRbNN<7&x1z^u#V85b& zbtiQ${q-_K?qMQ#dl3I#BgoLVA=}X0l3d=+KEg~?9~yE+d`0|kZU77n%uktKF!G1t z(&AZz!_kbsJ9=WbS%RIf88dp0ZVRi(-t;&o83RH&ItO@RH)aZ_Y#hy_WQ| z1IxWimzrM28x5m0q}~qnU(ft52^&Jb_DAdowcfOmOA)6nPUob_#wBJOx=8 z+nP=m<0=BNtLZ2`p(!i^7t*O6Cd6uq4V@f6-SKT!h90fuZDI=z=f0ls>L(oF z+|wierHg1Y^1!DTUYW$|P#e=n%xXQ^_Nl{7HWt=4#YR_sZ{02F-geQq?1|>b%qKZ1 zMib)W_CycL_X?e(uk@Uc@9+F*J+il4GQ7+9NvUy`mT6~$|1vvZ*QXr?Db2@x%6f@X zVehta^{ECTGxBICFqhFoj7y%c%P9Zg(2`Z^K|p+2ZUmvfdxO_}Lu5vb#&WM;yS&Cb zO~+8n4;0D7w}oCB&$b`TjH=Vau$FN*;je?HP{2rIDXhcnqoD8Fq=}R}%wC*tm~Q7O zypkzM!J`VQUz%!ncAFzd9h|9FxiDEcgrxmtg67%%vAe7;_$qPWO&HBBBdIPhxDrs3XG|I#dtji#sZ zfhlOk0d>zzsNPv#NdG`I1a36b=i+u){zM4>h3h(KyfBGJ1 zz(WCLffJ|}D)(3k;LR}1_mbaCq}*~sijUDTGqmg^wuRT#68pK4L~lYNjiR5`+h+o0 zGLc+$^!icU?=ayaR93G`Pgs^EA?E6Bq#N!%4xJ>)fHbTvH2z`M@AB+`iQ-ap;gfjP z(nk08oC6hK9&BuXVn|uq0#Dn(xlCe$fx){4Jvpt_@MT1FwgePVP%9V{psFrmG7>-( z^K78T2AJ${8Hg$_oeM5+Zq826`{1da5tx;4sG0GwV<>3*g*LtKMmu@4!(y*M=GdBe;sWy;nVgN}L^LsllDap&>(Z#ShbxJReMH>!RC=6&G*@#7PncSYlzEu-DLRg;&v=hV zF_N}>WUICeFRbtkFAP#``oCPMdhYeOJewb2D#h#^yx7uE8RB)ZC&0s3gWLhYLAcK8 zcCv8Gei&g~$-f_IF&NVsEY@H9s3z2TPT8M>-sX`WNHZue++WQ0 z*5ywOUL15I57NG>zThet!d9%~Rw?c2Neql6&gIOI!EyXAENV1Yaf%Ro5J$J}_eX7j z<4+HBLkN=JU~&gr7cVVAbDon5*3K$NXB6EZvp+Rq{X$@MoG?X-_J6rQ-MGd;VL*10 z5NB{~He)0+baUk}7tNp!Y?TUh&G94K)sLuuDMuL{G888smQp*EIoWs5;a+7qPW2>< zOewd06@(G5@yc>x@bfbG@yt-c_FL~~=+J`+IFwZ8(9JPo?Q;;ww2SJp8hBdOQu%z? zxz8%YNf#F#ZNf3YpM0n1#tJfqKIKO)wT(MYqB{s%xoQb>HfgKbNEV`f+{$?P3tzrD z?`!9~8JX*ig>xAo&mvM>IGbV6?9Et&D9^^C7V%mPWE@|4*g(h`SN|%IF9-q2(M0We zhX}JqmQ8d8F1?d~6fL?!h;H!C$_w6_p0PigF{Ua`1%~l!(Xt7JnJ^;`m(=^Pn+|kNW_~^2! ztRY{MJW0cD1ZsnH&JjG2{8()V5m|LVq|~7dw~g4K z{h8R1UuCog%9BS#^t=a|aVCz(9b=oGOdkTwUs|!PpKxUprR8QRZ4AZD!wLHfKH0yV zeJ!}TBQLXQs;W`XV0W+YOM`XSpkvxP`W3A0MNLwhE=@UT&Z*q99xE=aE@13EQZ z&^3?{a>;fo=qFb8XWq*n0u?)#c-uy&`CFgy4Z-M;o`9OlIeHAp0Q$)O5BAWwx1yK-Cs;EdA5fzXqAycI;vlIbEAu0kQMWjYRA|$|4 zRHTf65RxoK>KTPFC53mm_SyTKvzL3necyM^yZ8Iv`JoVIW{NS#9HaNvTW?K7p+o*2 z1`f6GRL0}?dT&d5w-eJ}wKwe==V7qN`73D;+EB5f$$BER7xWlvvfvcfT`n=)GVNmD zwRo<}<0TEbGWE9IrE-oYXHjI%d9x6;>UD;)$YgVx(lb-<6~W{3?_Xrz>$v^mz)jEi zK^v*)&afNt+bCq;7~pAB0U-;|;OT=P#M%`qcvpOs{}elObgg3$hR=A)&i zZQiKJdc7dSCrM@GPlpX3Y`8SP;_>&-F= zzPGj6Qr~mtVd}@E+_u-eb=F(xeTjrRH|CDM4+DdTO1%8_@=`)v794*-ymM=}v$MAS zes9f_N$+V4gJ^asBvRZpU|*MDfNbpfzWVh4?8w-%XE7zoKOrPAC#yznQ`;)}qrRV%mV8Yuknd?$^>KCT53bjkqm7Xvy`tN;nI&OuI zY2@C24kGIc|tO3MJ`T<4H7-D5Ry7Yh5Fq5{!FDx1>Yxjd7DG}c-RR+Qy@xJ}Bggu#BBtuI@4eBF63ltkO| z`m3(~BKb`2`L!#Y^<&(ks(UZ=oS*S^SnImJ-=y;1<y#d2V$4VMuo#XK2 z7bM2+3ab@b&xJ`t8#*LfMAnJyO-Lj!q(lGg^vs@fm2o26x}bN=&HxvF*Tcr|oRJD4rXoJ?x`fM~TQkJi=r@%YIk)3&nJJ{9 z7vRE+{Uh!OlBCcNfb1UKgb@|z{IPW#1Jnfn0OH7UP!i{kf3kIp-T^q_CG>H%nlgx~ zULfGa$JmzTk{D2^JE0LP&=%jwP3zqK5V(jhc==;qrx+qQj^!UyTj8%$TjCB4*PE?G z%7uH#Op`DW6$DPJx=O%HFP$iXPk~lVE&U#zPv9*nGQtz4w znGEO~UIB0KUhNY9`}I}W#|G~Q>?tndIIQ=i{x=5bCj@-x#sT#}%1uX`ZZaAn>-goU zZ%Mky$ZKEWR{y+BTS;$YTlKRWBezApl~;}rslC4FZF4Wcu`ns*qkVbs$F$uwCkh^Q z-z>K`WmTp^R*ZXAcSfA}hQTY9-jvC7GX}3E7C^3E7AO_SsK}# zjp;UA7oU+ z%Y7VRzXn*YHG`sp^3ctZmsx55?v7%d5LDkn`%8F9pP&wkbDW!OacnA ztykfyln;Y;Ibxe@^_P&(q@ag{evc9gbRoC-iKRgBLJe%7|GLs&tYo?E1p)|B7W2im z)6?9YqqSiZknk`-C7yivaKh*lB~OImM+<0Y^o!LqKP?K+8yIw5wlTqKX;96J8~C@t zAr|!l4^{#{+NES%)qfz$CSv&34=8Q|(>Ybr}8~93j{q6CS z=PLbEkJX*Iqn?h}-cS>NY_-m(;QG1kuSymChLPayZ?|^mO!~$(zP;t&Nv*iGFX`5_ zKKa-aTIN@#VNV%3d=cC$b#g0n zFHfcw)1TVt@EJc@<9@!+fE3GpCwqQldAn%cK@kO1-0-_oCCNYNBEuhyzhN$Xc0?Mw z1X27&0kubZxIHAN&^-UIgG79@Pmqkrq#E(g8*^5kiskuNYQbl(=;!d<)`iI7fUD9W zlb(^d*|tds-m!E!&Ax-P-sIi?@>cMlvD{ywze@w(&(ng#2tL={+U^3sLndL<>ki;| zxN|W|M>|8isL}l8a^{Lfl-9qX#B8xD83a~Q22t2=Zl2!!odU2IT~8Zx^?sg`Ds((? z@Ud+uLHEimg@KhB@AUuG&0TkH+0x`~T{ENlo3}SLT)N+B*-?7@^v-70!C2}Y{>ZA6 z+k%;n!|SJtdTb0kZ{?eW^aPaVYxm?_Z{qULC9kbENkqjIJC!)C-CKA0a(g`KX!=R5 zgj^0q-Z`@{_R+16CbtT-XLD?hC%|n_iK39lM-tO)3S}diE`+oLck8OBUSHIFe3Ln< z>7f2{{f(y~H`Xt;vT|^`aVy5z;ow3UP2~vkE3JAhZL8eWCkvu?~pf_>|7Mt%wimC z>}7HuvkIdMi+|Z?Q^DJFG^-nLs6sg2;#_U$rGJL*^ok!^`t95zmAEFS7pJ!qe=$?9 zT3-I#UC*`q!Jc;o#OAM#w=(B)*15hqnsn>o4|#!qwfbAasQWIog|NNxV;HgzA~K9dv908{2&=xX`}W z)Cgd>c51w{QXk!>g#Y|S@fLKWxW+197hldvxeCWfDehb!%Jg>!8o=c#>F4}BrD-vkzO3S+-T~! zNqe*KjhjN z%a98k!$GU>Vk^+ANf!YET#q-OOhI&TlOUX4{Eanp_q)`4S+2_j3=QYeD_G36s`UoK zw;MO6*2OA)HzboE?+ZitM z5_7202|5iSTO&r82FRw?tJnZwCa9zLU8)5uEpi31*ss8jJdl%&8_t+WhFJnv z9NGvQFhlBqCo3gRBG1oAFv#IM-{l-8Sv^E9oD2C>(NS4^uC>`*f2u--phE4s(_joW z?!XSmEAh##^itdl`YLhoQ_ee=8Gslbg=h_OVDDF&nBH{AzGmM;T?XO0{c+KpXg z051^s68dQ!q+T|dzxO!835FRj8nmmCH(Y~g{4yWl9VK(Wo8~yaI$bIHh5Ql=8c2rApbDUWN0lrO=lWvQRGVbSa%pHH)!A;% zME3el!@VJ5P(X={dG$E@;@xA@A@n06)%!?Q7Z;m6+pgqyg#8i<3nOqS z>o%AB8Zxt1iEW@mg$q3#ga>WR<_o>ab45zYI$AVswWBe%Xh9)SZFQf!fh4pa1kjKN>c7J01dSR@C>O>TqzSj z*13&@trC{d*6+%r<YptQeP0NGA=6LH%-b*)K?%iazYbPaGWQJbj znGs8}iz~2Y(n2s#rr1f%AW%lfFDt=^Rv@7Zs18fkPs9`|Z_ zmVa0D@m6$f(`lZ4z4{Z!6dlZxi6C+x zY|(ZabI5mH)yg@)`}0xef#sE3ytWa~{&Kp%&=Qq;cXpnxh8-@$SB9lPUZ_5(m0n@h z6mAeZKh31YR#8_@koG+k9Zoz>hr`?X(meDv^7Y#@4f6A?v&mi{}~Q(v@E7W8!8zPoaBlbq_7DUVf!N9mNgMBQa+Q^G?Q@(;vpdf}(Ejou%Cm`9&Fr}NXoT-+ z;8Rmnw}m6xw<5DD$Z8u`qMVa(tjAa8K=y^GTjzm8N#XG%sImYjr+et>`z`hRLgSk` z-yWM#FN)0gBa<#%%3`9B*Dg{>ZKao`UlXnnZG&5Ayw7=v1}x;oMt($c37k6>;sdDl zB8yHE1Yn@J%xoIGl#NSt>Z!o3=pUUo%jc_q8L;X#%F=Q~g7va866uE_U(|q5u$zx< z0$!g(n>xv6h=F7kyU**$O^AY76}r9H4=ZOXrzooZAiqOo%c{sSe&M(Hu^ zyr(ZWCshxD|uM7$Fk^nCc5!0#&x&k#D=d z%XCf7fV&4VrH#>m?sRFMn_rR}J;8GIjSkXGc*P~&Kv5YAa2Z!uO?&mlLS3~BG5HgI zR?A|JjdH#`nbp!>lNHd=^XfvB>y!JPX_mCMMQ?`ZkMFtQxkzQ%9_Q>u*d~YLB-8W9 zEo`#UnoOsAa96fEz3SGO2l7r)^LG~H6*eCk6+xzsy{jm0indoR)$dQ$i>8k(4~=!^Ku%kzD-AnZSFlj9U-`- z#PZf+juGf60!uu>Bz@br(dsv7LjELo_mCfRcTKUE_NNV zw;8`mno(6>(_63Oox>hS&wE4;wy8@z`w%PZ4*GD1L0s!E?}{glRBc_59G>my#?oW- zdcH@sNTJ&GSt)-Bqv!xP!nM&bOVv;~D(l|Y?{DI+^hHx?R~u$*WHNR}o{K_sq$fH! zps~>nYBV(}q^@!k9mK_|$?$g)%UDg+RkO)!MF)^%z9LDQgN<%dMZGY|rY@+tmg>=C zwjn@Gy%e^LA7MPik8Ld&0H$K%a()+ZXwVYwK(yPr*w*QIGiBuHunK!E5&OcQhgs`k zolm^TYF69F3N$t1xT(50uWIvs*dJi>X121ZzqeE`B{uiabVN~rYedwVK`Y>|!bkB? z41>O}l9ke(p+U(FhVLY6{pc0YkC1VA+$6-suO;pjgdv0XMf>>~S7)B@WvXq4qiPKjMeDeQL9*j$ zhdzI^9bKe7yy=yQ+X+7UTIJr~>?&hZO!s&f5^cU*4m@4mdnSCq94ifjUVyv8g`|^6 zM-I82jl%i?G@3wHG{qGXGxoz|3O1%;gP$3OKSUq?Jgvnyi-d(-RGdiT5y^xvd6wfg z%9=C2f{iqFDG5;TfBWgvUjGd-*66RwBZB+L3E>%VbDY5X(kb+(p7}vS3qMSm@q~uZ zTWfM1FDO>&%1y-Ucz1(#0DjpyvK0AEEj>x!a3I_ep+xX-3z6#PZ6a)~vGPdF>4dps z-67#y(}xW{J{4DrE(p(!0s4Rygdw&Iev9QGgcix?BY3&^dFdzVih( zkDUa;&J;uodVx_u#$SAw0=hi@G+APB3x0(uE}UZ!Z>CfRw=!``1W;9;Ro%<*ROCkp za(O`Mtzjj9$#jgVY7hrFt|9>fT%1YwF3&|@X`i|>n8^jBnUz_TdXcxd9P+030nrn5 zl-XX93&I+1UkMD+6SZPaAp~xrY$6*Cs%mea0jo4|HOCekql~#;RByq>#iJz~-Z92& zono&WMqK&w^Dt-FV-QKO$L)=`E>HSypMFAnEK6i>|!emMC*3 z1GFttgsVjQHC-X$FE!&Tr|$TNa;ft5yKWk$k8Y+t4&2Y$86=A8(j#vqt`Xosbq>%2 z0`$TK(R|iK0la8w?HOd!#Cw4bQ_^+aXo2&=*tgZ|LAL3-NdM+54TbsY~t_ z0!xB8Wg(Ty8TKcUW@3Y!^{>*r{5ydO$FY zGz+nk{`ve#HbHm*v{z$K;nYNqNTKvKk>W6I87j#&XmX!W&2MOnntYc;8L?_oTik;_ z3inh;PMca41G~7|u5*=-^}XupsqU4%4`b(v;;I>Z-!zk5hMkn>K>ZBIj6>qPBo{az z_`@L`#V`81cltFfCTc(T@~24_F>+3z71(J|LwE;qvXX%?feQQmP>yoX*=H_QF*|&yuRn2+>W=OrVJR= zXpE}FjNA!<)Oh#-9kvhZixgDB`67i_&Jxghr^Ef7T~3=!9-dw~>bagj!k{FXX~U9g zIG@9wDCDPdU9Q^2Hh!1Fhz_x4WXFS)h(WCl>$^y6cy{`22YY4>*88m)`rto*r=11) zWBa}esu2*~EcO;tpfk4dm6-ngVXeud!i5N%E8QpuZrF_-WOZ_j=Th-)!#X0}PdJ^T ztSCv&Gj0mU$|86i-=K+*Wy~xewdG4Y^PTczO${41bXDfxyGouSg@NJ8B!kGT9M_x( zU4zzP{jexvF!%TL%%u+=6W3V4Yzlb;fvohDf2<7^LlcpxE9{YDU!M(q9 z1e(fms0sM8kw}m)_Jn%iab=QzzMY_sH+aSA-B~S3Z|^ySP+8OpaIfc(19pQ9(;AMh-ns*rJi_9OPPg=lVxg`MLSaKK) zOuFPfgg&Ke$ zE2*8-j~h z-jw?ascL2RUy9w8ff6+DU-Hj{`yp+7IW7TvhQ1JL3Q|m-xH@Nr3XSrcyaFG;C%0drr)0`4WI0msF*5ZR{5j!|N#+G(Rad%Na8wcH)OJQDG$~xA`tGd-cv_I9ZZU{Qdeh;SA52lSwM~K}T0Rthx zdwEtO4S5eY#EYq%kqDdxLpg=EtkpyBi4a%oTK!QDMa{3@<6~26=;cSF67@*i`q~)T zyM=r5W3=|8e#0Vr&|G8DnpQy8!?EMk6z?*>OD(Jq7y6C^W#Bf{`_@B!I60|q=-0{8Q#QYPeOu1@eQpArYm_1Eg&x^261C<=(OJlp=1kIq287s%p5}3-rN<%CLWmMwo^m|Ol0Nu zb6*DJ+?w9`%|EEh=1SN5P5GX{`W)@FaIti;ataAK`RelvMemb`y-%7uFTO6bfqPZ; zZ$^vBP&He$Dp<0t3yiJw^XL3_iewEItbLT}i$)&Cf>d#KNgx?5a;xSq-jR6ME|`f{ z4vhwb9r2hErdSbql;0j}GDI`kOFB>S7q`JAupq4UHS+YofJqDSr?6)9CQ8H<~f!TCXWPJcNdZLa_tHT$}M^G z_KBy1LUry0SC83jxxeuLL8b4H<27Pm&h%B16}No}GRHKNhRM%)v=|V}0a&N0j-OY~ z$wD9F`(CAk4*$~~^=<&&!^ce*<@cHbL~|KXRaV9Uut!1{&Z=#q=%qB;=d{5(KVS%f?U7c?4r#lgcibm1-(*d#tr4|vim`z2dIk30TLjmr;Voi~vTXnwDN zMVq|zPxTL%|I}KT(DR2aP8#M*AicU;d~Xg^%V!r}kpeO{5C2^satrt!Xhe#0=^woT z+Ep#ilum|b6~r}!`F;Obto*a!@5h{e&j8WGi>NHoDw^c2WbV6^3wGifzGD)!eZsEv zZ&(_#5iiaXPfJRmzJ*t@IsN}c(D6@Vk3ZTb|Ff8+nEWTAAAyv z%creplFB$q7fpN#PYp`k4)fculgx_(ven*^PH_T*@bB7^2?sk(%^!`qwxoEao%%H( z(>v<}tM;;W*69mji#K_DCqJE7@GeLmxokN8#$$GJone;Am#?^PBg-CL7yD9r*EHLP zURwWrXClF^P1Rwm;Xs@JN9$r5FMQ7pefbqin20n@rZio5@zDpfLF_()j zfEfP}APCBtNqv_p zy&C%RMLfG_=2^)sX7$*-M{*Wb5K0>`oavn^eq@m2t}{RRw@#zKb{zem`l%@5K#hvn z-zD_nTB;ySfS(8Rk1#Kx5dM&J)rNkkxrPEqf(behb7jpe5UX|kzpj=2F@itN2Jg}M z4nj5TBOOD2)TKI+aG@cQ(|`dkt{_InHj$MOH?D2qxw*#vJPIG}hHU3w8EDFExWk(? zQ?;CHbh&gfaojENQoAL+zH@q3MebZbVUOWS{VPF;Mt&<0NS>Z}P=cK><{9)QdV=c= zQrJdwl9lF8ktOUqXkSt@{o{Eg> zo|4K!Xepu^(zdsaF+VB{kTmZgv$A)IV@Y@n^ANmNoI*bvv{2F$rewfL=gaeF>DTKq zJnBl+;)5pRncLvs`;$i!CZ^is)bs-O+7eW5V@sztJxe&g&sI<&P6P&CC}1w+J>A$2 z;`QmM1_PoxC+}I8I;$91b)%vx{ZFr=$P-a5%I;MlJ1~w{Pm<~KfJ;hq1MHRx_l#t| z@}JC=61@;7gUP)Q1G}Px8!@mQUhGU)Ak2~JYw5$8RMoV!R?6++E9Q3lI)x7-=J64K{6jkm7ar2+>2~m9eClAi5Z9TndUG1tYe8eWjHr2raXV&jE1L`e&wS%VR28Gc% z3G)({?$x}fazff_D9p`~V^|ZnVg7=l}i6J^$eM{*v{h!|QCRiEo+?nN#>W2#rxBvF6|8vPY zs~V`O9L9)cO&|%VweosCUZG8d2{p(2lRuSmLD@nO&R41|aBmMUAduA&WB4l|p6av} zWOF}FAMG`wtQ;D1ncHcn@ltax%+cm`t3lF&uO#E96K`FzI$wWXVdDzGq9p^nii<5Xo{1d&mE8tMzjrD4t+}neJH7d?SR>C zA-)a4jiRkURl}Zs80)(4s6?NncB`e6WRDOu4r<+%#U`8Tf7*I!$K@O8-SH`QDWlKN z52$ABK8o(v)%w#jBa|Qw^rXBvN9(uo5xfIA(L!eha9_xjfH^leFf;twai7Gks z`6nO#KFaCGU}&t+K&WuvqP>S30iQN{#Z=hds=;5p2jg{HH&HZG4^z=*sFqI98q%Pg z^NZw6QMMsmTTvExjJ?t0FghYG{#n_8lgsWS(rKU=zna{Fo$;fWqayFeG=xnVGJgoEq5wL z^EfZTK$+;dn`g21*de>a+Ui#qKDjEjR%$sU(#J$WGsha%gQO~mLzSFhfdcTq{3Qy| z&Gg83S{c&J>6oW2>)va68p8E{%2vLY`ZIs^csucSWSy_iX=}}{*yiad?bqY{gPfYM zf#S5>3>qrcJt$0w3l9#@P+!bFM|EaWn9#Q=Z-XfQ{I)z4P*HF;a|s7Te8tU z33kb9NL#?6oNwnwpZ#TC$%5~6UZepOe{$gwFx=1zpeyjUp8B&H`}1*x4F(uo0|vC3 zq>UVW_*r5CThbqUN%7=E>qn7vFZ$e%;Xy(*3P8$6d}TNZuzfPn&rS~u_OVm^TucN4eBGm*yhp zSVy}`cq^Lr-nEVM+5cLx#V%sWKTXpyZ^A_D*)Vh%5ek1O8|^kj5px)s^aPR|A1#rU z6RILQ+|8*Evz%25%rHn@BUO!L!2OhZwBwg6DT(Li&B`yFoqzMZxhOLvLmsEty(aD+ z*o&E4@bDqZ{4TZ{#62Vm1$XrnJ?F`mMcQqI36<-*Z|&+8IOzuah*Zv(7gVQ zQyj61^*h6wOu3y0yL1hcP0;Dq*(!3odUbSmXZoM`n3hw0-O%6r(#-?1bvswvXMt$# zFIM+bE^pX!bOKQ8=8k|w%nwu-WVzlkg&e34$E7#pu45{c>K=b4Yr_p$Fq`Y!sHgJ_ z%WSKka&l|JjGlZb0bqb8EmSCs3qX^zh`nIhy-|}-_j_5k(w#;KK#&*j1lcMT(WQv< zy#(LBOLe(4nn;ON#Oc^`K<3?7(A{mO>rXGYVm^~-yn(Hb!&TS#>Mk&riCH@ExwblK z#=72`Q=|Jke=eG&PcjdD;wQhEMkpjk7}+TkR0{Vz=+;Eb=#2M^KoomD7=2#x*or0stv|M|&tu}Ki3Z#=OMq~~XQ0Z{W4UzQ4{J3q8g1ADXHdwRq}@HC`wWi28O^p^D+a_X=l$&7 z*QF2&_ugIGIRcszVk$ty@RmNRMlF|?EdAlQ_qhcmqX|+VP33@c>sQ*mRsIA31+LK- zL-X4o|IZo!jbl|fi9?Kl+HSk>G^lEVD3#jpYauQ-TW1buv|`Yp3BywtW<_IK%$5Lq z%U#zV5_h!OJY$8*Qe(**`MB7M&qk`QpQeVIn(RD6?P??5G{4RA_chBh8kt@5?-~n# z&3HftLhfXLDPxP)3`pKAYN5|3kUtpxVHzQ{g5CwWB#r z-=(&Ko%H%PXvhMZy-P9xzFdD>r2e{C{WVGYwg@aN;PhKK@+)Z09Y89NfGY9$>>r zsgsXf(7yp*MfU6%E;q8!7T{S;bjje|7P~^FYRQn@p4p2=oRf@}dPnJ&ox&ZG&Woe- zRE8mP8tYH#*O=Ue&cq~~;qwk)33vtBj-|!Yai`(=SMxD*mHvhZh@A#T^F&MD!Mo{t$$4RtjMwu#SeP8j6hw9WTFEZt0nkKN6!0`uf7d8pKc>EiOk zS;P;wq9OxL@LKW&Ddz;mi?*@8dKjX0*1MkXc_X^^Ku6uJ!h(zXl>em*Oq;{v>QFYK z)jqM2L+xv|2e~vQskhdJiH~d0#jA+S;O3FUdgF@{ou2y*5oU4$auB1mEja(SsPMMs z?HDC*N8M+?NghMP`BQu_^?^LZgQ=w7MujURy(KA0P93aaG;?85k&O1M{n{_C%Pd~c z;!;G)cn*FA@vcOMKdk4%%wGSFWc~!(ic9&mAs2XXlZ(kPgX?Ayv$>vLT~p)7b#UJO zI@-%qVS%R-;p*G%p#|;wZ?WYCmKmC(5w1qh#)6-P+xtG5Q>N&*rH@}C=f1e-!WlBp z@Y?nP)Mn>-l;RX8_*tx=hg3KA9$V;`JBvMK_t>U~=RmLQbr9SutJVyf2B52u4sM~2 zBGM1hHm?HcYnYYDY`Pi9{9@4H==(9eXpl86Ez-fq;%7D>Isk$7ko_+8>lV;UO@PNG zQJ5tVN9O_bWyxjg`wvIIOIcV+jx$7*8W5*YhJ(aw+eQ5s@S+>YvOuss-tGwmq8~^e zrVqOXajmX>C9-*6hXRTFv zkOULPrOz8t5&^mOY^Z_c-1uCiM3Kgz&95SX?+{f2P}ScQpr%SB6xcCFfZ8i+o|6wxb$xtF!ZNa9Sg@Jdija^=K?2zL^wauculj+ z7m3nXs`Z^S$6GU&Bd{SY{hOD=x`E{~nl4lA|H9B<{=>*bO4PB}fCC2htA8`}#e%(r zm?2q%Sq>ymBy9~H^s2KC)55(++}+JHh;tf~l*wH#X88V!0k$@#dbG=R=-aBx5}Qzk zqj--cTbktWi68M~3c5cY8M&F@K-F|hy8P(gGe>{VHm`4A6&<48?bSG`Da!6zXXHd zf(1PYkB<`gh||eNl6UMCW{MVkIw&zMF-1r4cZ_q)`70!fbUrOov{6^NohgUi&nlk5 z%RkRg&=tuMxT;>GvBcT@F)=!@pvesy2B8UBIe=Oj%`uD2`wA_ z^0tb>1$T(9mPkn`065e2ZRCFTN zOy#sEG_33pxhXb)^?9v-?>08kk*k=J#-*)v4VWuOK8+lTY{~RV%iUi^*C&QcxYk1&Y zfZS~Yym(qvy`GVXtcpbEwRs0}H{CJC1U__kZ76;uIv|eY8#96sEo0Gof>2Ymf=@{} z$(K%$sFJoJgAYaKd=#Va9Ob&14yeKA3>OdiNJ&r!Jg z(hQF-^$wCX2fHdFO=Qe?Iu(Zb1^=8k8if`4L?n0cuj+`Z=AUm%Zuf>qKE=^uv#=Hei~o zMQTCcr8F6mWyK%}wBSyQt?IS#{XLKZ9llH#rJ|6{)#5C4B7Ig3xN{MX{iDF^zvAx; zey6YJAmbO{YUVr18xu;ChZfK6<$b70?^4=_7Fv#ulzj-L1Ia~DMlmq{8kux0rZVty;9l^K*^OkGXZA@=~9 ztC^Xk6U??SCOx()bGor@FFg@o5!h|jJi;+*NaQBtS}-eTf@DAUnrx07es0fd2~*SV z%I!GGS1c*W#kU3e=1G!3)Or^SiX+3f=w-AC7QfI*e7`|yoL+ehzeZ$Nbb=r;?J@Nh zo&^H?@)b~mYfBnuj|lZ(41+e+;k74uZiL&L-6$&&&oiAQV=bnf4pWcwS+}C4M+f0mLM{-yNs zpQZ;8{sHT0;ZKb1bU;z_(^rYCUa)VGgpGwQzIt5_Qb!gE3M0Il4QegzOx^;~sfmIe z=lpVR8FnyohT07ATa#Da#ARIgo$pn?e(4E&#q#y%&`WHJkH5=zRCLW+YhTfIAzylr zzJKL9o0n$Co8Q{6vbSx29elQYORjwb;r7WdV@8DsQ8p#U^0uhuX=9r?HmeJ^e0|fh zBkdtoS7Ym&Ci!4|r>eqlzgr*O6yJIHk;TnD8Yf59cdtlEc~q#ebw$ymn8m9O8_rMI z8D{$DrP>*G*=^4Bujm55_~&H?8nUnWKLQWHu}pt(4E&#W_m5+*wmiJu^snrcKO}5! zV7{hK3vK5n3qX0ala4P0_0MG>)Q=kT zpa0d+;;8bb>)@Qm?2SkyUz%y+8(pu#3EVN~(mVUyQ^3NNmX&r*`qbBEh8Mmpo_vs^ zLbzPAIg{%bI9bfe92@h~+mKDilh2M_L@yB{SbLSvlZqfx|h04c~gp+u(bn zUHSIKt`&a0q94Kjp9{V8<Uzl<5WAL<7b0r7^`EyBX)lyBT2!@~Gd0);=KA*u zX#8r>-!p?4oVxZY+`q(hA2$%?sKip2tt{`JFi;yjTPbJqX5`gI2kmR_FV9NTt)WM{3@HQ}A zQ~^wrztRymU>WfFVh03oq$7hExT-+~diN3Jn!^Vnel50Y2X%hkM-X+K0D%9;YvQ__ z&^w+k$V5d3-=*}hxAFh7DdZlIbcFeo=aOu|6+cFV7?Q=^dZuH-Mvpg;47ia` zu?Oxt&9|q5FmJc3GZuY#QMMh*cUhVq#r*hP=D6vsl|9`?xL^nRC#TzHoR)>cZ} zP>NV^!V|BS-c!2a6%XHA%?MgryAAoEruXvHh2v*!uiFPiUvY9*xO?>Ckz4m1{U7z0 z`xmkg7Rpgx@Z5V*DpAd~0s0Rar?;@qI|N$1alCi$Sf>B3qW*xnck@*V$U#nPGo&=$ zOI_5kiF6dWENH;PB4XlxKK2@UNi&DGyw&SR2vIs(t)>De)8l3fHuK4qyv^mT3K=6i zyluCxp@22*UDYKr)$Q-WWkU=n60XBkM+nGMPe!aOS@qi=NqW`v#rI5YSEdmZlRw73 zh`_gGD?f*Qiv88^+Bq_ZRy=!0+QTha@dUKT$st*N4{ssCCo*g8%V%CCAPpn#$T--F z=xan;!`K8@-1TBIhO`qg;(DNfP0^AZV$Ob+=|SEsk~AO3*X2g&FgyHX0I*xC?RVu9 zbzi+P+M&tQT-l{wv;s+IuWeXG+K1fLPdBusuSI+qPTdtDw?8m)$Aqq|6HlvZy)Em? zD|!N_H*+7}cJU5@Fp?xKb6eXx4^ z*1&N$+fZ!!9hFC49tI}fc=mM1&P-$_n@epSL=RK0{ZXowN2d2T!0h<@NH7mCPg1Oc zeeMK3{7jW2Zz3ow);lYsk~Bar8ubKPPjq1nZ7%OQ7wTdEWo|!fHbVQWedPX5-B}s^ zq8HeMezbN-Ny2&udNjqQA<2*jy_%oVG`$+B#vv}M6b4O+kQta2O?yF$9ny#A1Ns}H zImNb&_6_{J>f-j0&_T;(r%G=wAaCqiQc;ZO;#+b3S_k=IFNmg_hTljt%`m+)_?1K| zbH0W}a?1mOJP(iRBd)JxcO5OP+k2&td@$RmJKs&`Wx4ySWbN!%xo4N+6Pr8pUD9xUZ*!OLj0(7D zWM-xBt@NnVfnq#+*@7eneF;Z@n6r1-c4A>^0Qh|bKEXX0uuzE=o`XV@RA{@z$r;~& zB1|kj4Azl63UHDQ`z}>*53Fs6Xz~yzV>_s%1a}YfN21QvJBAKFHflRzYUiWs+jYSG zv0GeG(d?cHjpu%79M!3+b5>T)Vfyg(DLaQQ+vgSC=K|HsY7)Z63%^Qm3DhNdNOLZ+ zmc>oEn~PvA_(}!5XGQh}w4v4#V!JcEWP~x2&1OV4K84P>0g_ArD+4{Z6UmmK>0N=F zyF&I_hh_!LJ_k+7$~giEz!Mpdpy8FzK(_h>TTwPWk7@+fGdqmr7i{-iu!KHziKD}9 zw?XZ4&y1Yn*Ex}q71$mC*kW)PG*PJrf0s%-_Pe-%yx{QYdT6y_f#xMpXvBK>hMivc zM0?cY&h1|n_iNsiT9+fV)&DMaUxbk;bO81NL;MR!73Vh*0i?3x85pgd+2A$J1A_y@ z1A{L6URW}i`$68lf6&c#y@0i=tEH^*LXhb%e7nURCNC9A%Yn{%NzyCxoveU=5cNpT z9lhnf<<*X)oUN8eZ@GJe-4Z;w<-HC(0{?-(Z+W%-mN!@j6lDG!mHaGH`S1DLU!0l$ zp2{=(Z@$cnkNsQtItKNz`yk-TO#A#7SLQ|k7-OLSLtx_HX>PRWdVsMl79fgnt6-2n zJvj)MOt)fIG-#5%5WuqsEg`0`=QFQdk|M$>9vDkkro@`*k`BQt{%eXynrX7;L61kT z1SM4@+gd%VFJ*;Z>OIv>Gg7r26Mk)z$ll@W971e)6hxyvMT7^92 zvn6`sQfT;*YePO*9%S0!G8TiqRD)jk&eR(mv=--^ZYuozU22h`Per!vzT@QA7@66` zz4$efcb+;=&(u(GtkA4otsKeSlJ|PC@gaR79uBRZ8!!#o#;{c+XcXMrj6s2}6Z`=_ z?HY;jARv1!ABr-?<@Swxssf*j(84`ujRMiGok85}wxeC4DqBBQ)m1!;Ih>bZY2_dn zj9JpKg0ut32`C`4!%!n-Sw9iY!81@Rk0Z7(vR9SX_ZZ3IP)N9)vzy*7*-21)bnKbx zTs0{aTOFyd{k7EJ_eKigTNhYo_mbN^H;FT#VUQ}rF4QRUPDIQP7Tn_(QyT#{lbv81 z%%}0pN-`wd`D`F9$-yQ{HueUkWcjuJ0*}PZ)Flc!Oi>)2rL~oCSk&2&c2qxmsJMug zf$tiW`UeTOlL#zRjXX8g-g-y)>;Gc!-2C`h?f129HW4p$KCcA zBpo?=r{~dhcZtP7fn4WU-`mgmCMP=^&yrTl<&@^y=EXO3=dA3yZFeJA({%9I)!Wu@ z_i1NRVQf5^$hL_lNa^{V{$f?yZLC)4>TQrJnixJeki_3JG!f{ennP%$a=fc|1{E*{ zKfHo+N!G*llJ2JE)kTrKwiOWpCd47%m!&j0foO-oG@q1h!OckX$o2hV6_B@IA>R1= zE6RCO(fMvKdRijXd$IL>3uSMd0v?_6>8sRJy2IYPrr-L!?hhYy=359iQbY6m1657n z`4nmS0hP~sw|A_F9kIrXGT_*Tl_vNy z1~OS<&GZ{k39XSH4;1R4X6j9<7l`PIOdY3DP5^FQP@wdQiEkq3sw+yV<}k=Nj2abO zH^2&lErZG7AkS8>$?3Haq@N=JH`(NQ+W^U&lkLfAa zl>I}ev}&KO9HUF8p4|m%yxe}B`q5J=5s?~0O7I^~F|-@BRqV4cDb=T3^&<6I zBWtHz(Yx65aKVB639CzO7HHc$Ct+-JTPu{tv$PB$?mpkXdm4W1%sU^N#ydY$Q$D7g zrfBf=5&hT=HS2ZRqF#14H0xZd4y=pcT=?lj=Bm8%p|T-AkdS+1uj(UY~%N@!0uWMB!P61gHitNhFrz z(gd<%JP3B=?B%NoP9vrxUvhwdN%&6ZL~E97O$4W$#>yKC4g8)S_EGx#tzC0@$||qzxFjv69MEVb%;$6#s*iQNo7GNKk7SfeY^3aEp~eSJ+myO zq)ex~M~?UU=s)d!(lg6D<O2l0T~EeujyU1cU0`tg6}#xdJ^f@trq3wUFQNU9+iZ>)+~H4R#jX5qPmt|S=pmr znslVA6xVBmTrr#mj?UQF5m=11v~MD>KY4oRiP)rH$5J$kv#ez&j%6N5P?GiPcz7T|OV8U(>O7Qo z!E@yWhV+GD=f(~Dle^n4zZp&cp+WEM&N(gnCik88J|TvCD=GB}T35z90Ca?q@mNe` zgIm$8*U`*X`zxu*Sa{rjz!@cxz_qPb0d6w+IMncv?&1HH#ik3k1wiOY$H`E+S?r{O z*f)`mIPm2z^wn(+%U$yvgU003KY@?eYSW5Y;*%0s9gJrc0+>rgWsT^dN|-l?k0tcz zVgM*2558yt+S*G{&GdK)&02v47T}LMLCOsH0{!KTCD zX56nuM9N#jici^*p||rOAF>|&;o}=!<5Kge(4JcnXGXVcf7+|@brh6}me9(LqMyco z-8D0SLEl1544RuL-k)<99yY(-o9C?E`t--#z9%{M>AE3pskc+(Z11NuCe_foCS1`r ztNK%W8+!9j?>dzynbOy2d)(JLs@BMrRcTzpD4N#dnE zQSs9#q6rVR)Gp`JZjdE&5oK#S*Y%bMJkpdJ7+0;$%!8tj`j)E(MwEg{Ev<*2DMdx# z=6<;%;Ts~ZpBCfBcRqM~Dvw0m=g}jx{J)f5~jO4Kmn=mm(3ODsQMgsvhC%rB5$z#-^jFVDV#!zn z%{X)fvmU{XoX>+lb+@1rutFHw&hHVTeV|8RmQ4AMDA(lv6!B~v{P=M$`8vpZ*}}uh zK<_p$&4OL}qC)s9!LSCAsRNT>{F&_%B(Zu2b8EOgyi{j*L#0aEkuR-l)3cmRz5RG` z_bdSPc_`X4FmSitd>~Gz@lDt3>EOyG8YJI0>yu)S@% zd50GFm-t`CU*>6U43i{_7ou^dFvN1WH=^Rq#!lBfB#kwcFMt-<>Gm}fs)jJ* z23bE;z6J>RTK5?HLD2cy9f&8BA?0l(dh-Q~Z?fBxTQ(E1o#WR=W1k7P>nUXRKw;&!^FR}?pz_h6GwFzqhx)zg zHzmWb&c43&vO+op$o8Ir$D;Hw81sED<`2dz9pY1L*2jFZvn&p4@+93&clSy6 zFR!I|?bYC;Yj~ib5ccT6GpNo0^3O|B@3hFI09`03hU2tc1gZck&e%9(t86vlqv1*U zFiYXq!F`WO8(y93dbIvS@*&in&J1T1Io8i0nqmT(v3f@7Sn~(-qh;LTVy+?E|Ay|P z&d7(p9>R2%4UTTs7Cfc4%5(F&xNDoESn^M*yZz2e=O4Ne1H3KE}cW z%0|Z#z{UXfbDK9TCQM08#4aQaI^IouwJX0XU)vo%Uaark%{!WI9nwtQP1X8%+hNp? z6H<{ge_xXgsls>(nUo9JGG`TaB@|;9K50ls@YbfEax99(4z&(;=4>%EOE)^-RdV!Z z%!1xVq?@NT>}!5&niV9S6tOi4i>Z zoQ-wMLvGq=o%r~9c%;z{+j?PWL(|BfUst~yY~HJOLHyn1J2!reDRwe(E!2^RnFkL0 z*9LT}Otb$2u7p}8{!QdfiwTP$G2yxfj1gfaNNHFZT3lycd;g|iVOMpxWkA5?i<+wo zOg_B-^5IBPi)87gA36tzhKAISns;=XhOXJ9bRl7nDTp`RRy8^8jJJ(HopR{51x~lJ z;_FH^DWXC&Ew;%%hbR+M;3ySMbFdEZbu6&8_vy{qnsjIK`2B1RkE^p5>W@IcY_{pI zBaMIM>qXX`l-(lhP9kTKbq9P0VBOLG#Jc+})!|R9J8Tn1pwNywhyqg&5vGgC>VP4a z4ZA>p>LMfq-0MdoV)zAZ6|1)q`aq)nyqfPt$rnKeE^D(J6`$t^n7Mj2Qj1$Sdm=4^ z#>$>22G$Xj3lZId$FZY>9&O{<70H^c3(rh9AI@!<4n}6s)tkU(05BQ=pr+dvoBM^f zwFVC`b)Y0XBTxou!-_kLJmVFJFdDB!;SEfBC-s8Fi@+ulAMXs!5l{ghQ&*84;L#X12IEz$d7@Os*)rV8 zz=#+#7u{{fT@ze>UB9t!zQX8Kf3p1UAwKz5i=Myi)7wp2dY8;M=e4Mx&vH>&FLxzi z%gGaR9_s7%KfsIxGFzrW4SmIH@KIhtb>OKnpAy!SgC2V^%5e3E(N$0#Uf>RAwqV!d z_X^zL^VfA|Vo(``R-d8$ldC`lNn{4;`aeu$eTsN6iDJ3ZuL4=2p8I>wo!6^Tb4hZ& z3HA6Rkr2A77|as71tw5i+6GsE_iZvOM>z&m^4Yy{RSPYNju)x2RajP zTUPKlUn7KJ5gcwhioYGIQ=BEd`_e)v@~oGjMmqJk(BddM2oVexmd3==*Hg5Id}V{J@b-8Hz`y?<(dyrbok5Va3O_-SS{` z?G@kdbgiNqkGJB5C$)3IXeq0GPm=ojDAwBbiI|JeeUKk)BtFjVKvL};Ihp?P zC(!e+8td_MidR_Cj>o=LzK5Aj0EBLcfS5^)Ss&=@ePDQSC>d~dO+!`f?l3R?AiAMK zH`_KtJ}z%hZtv7-U+XNN+j&j}=gc-=E^sP1a{E%c#cNL1BTHhzFQ1L8qAm?8TH7Bf z*siK-pDjm}iEcivW#4FjH+ZgieZKvXpW{t^de4s87Fb-icshQ^{+kFGQ(RAnEoK(< z34%7LH07HJUWxGAw}57Ljanb*W&Z7tmOgs1@A?0o#|nytd30t1u_y{aK43TqY-JOk zu>uy0HeTZI_{Prg1i%4oy}DE6`)8~m(i>|m9aNWn3_E|?DJ(`}P|B*o~BQebmjTF`cjE!^EqNA01eewMW;Bfq@yOzh=*sh_pwB-0<~-rjLoELH^YbN_2H*T1-Fg18GE2n5{)y7&0H z0$;i?rqpH`lElj`gI#*4qE`qpfyV}r-N(3|L)&{F_I%RC%~pw4xoj%idquW)&V;Z( zt#Kz9@H$^!xjp4e_GfS=2^@+!2|7=M7duY_`Jn##h5G=*39il_p$cvRz~q|oXfkM z${@qNS+?cJQjcYcAJ22tZ3bi_iPX#Y;a*v{^`Aa!JhtsEzuo&7pgc=qT`+@*k$~z3 zsU<0X&LWKB!mMP(bhLJ|Va%Uy=Tpx)Z^OwcM)T&t^Jir zw{35f#d&9+O)9Z|J^pnDv*YLc7|{zn1;qn}3Nn_<o27Fm;Yzb87LKcHBzof3ysrG%`0*5hJl!wfr56(y7 zr17@l&1p@y1pBzeIDxWczEV+fVuQbb&i=wT?S+LU?kbOsw$Br6L+>rH7D3q0z=Me0 z26(Hwu|A0LuEYM{M2- zRcd_ivm4x{*kI8pkbdVPCCf*D1oyZ+Qksh zm&1ysRXubM<*6z_l(sZ|2BsSO<_!qUzPO&+|73#KX%(=$xj0m_1~8t(_dB#695D9hDGvX=Z=;u0rW&UgCX z(=Xn*f6C3t#vxh?g3N>HYG8cM;`q+c~@QumLSZv_K%;q<{bOmPT8(=6u2x;A1|}-zD+@j1vX{83$u!J|6AOSP##KQM3TqN@J3+a5Hxkk?tl8&xrt6&GAPqSUS{vZ3;TM;;-2IeO(%{rb%Xan}j7$`L z`)lx<;DUso#XkA{Egs}wdCa1A&~KVB{#Z3*#!^2+MA*fKzmJa-0$NU)Py{kB1w=1M zmH-n8Ur#8Yk^&$sQwM|S(urCZ0D=(iqE-3=VEXG2lhMxKjuj`&

mrgL5KsT!ujQ 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 z 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 Ge-i+8%|ku_ literal 0 HcmV?d00001 diff --git a/various_tests/test_3D.html b/various_tests/test_3D.html index 426e8901..b68a1cfa 100644 --- a/various_tests/test_3D.html +++ b/various_tests/test_3D.html @@ -256,15 +256,14 @@ if (points) return new THREE.PointCloud(geometry, terrainMaterial); else - return new THREE.Line(geometry, terrainMaterial, THREE.LinePieces); + return new THREE.LineSegments(geometry, terrainMaterial); } var grid = createGrid(pipeline.toolPosBuffer.width, pipeline.toolPosBuffer.height, false, false, true); grid.frustumCulled = false; scene2.add(grid); scene2.add(new THREE.Mesh(clonedGeometry, new THREE.MeshLambertMaterial({ - color: 0xFEEFFE, - shading: THREE.SmoothShading + color: 0xFEEFFE }))); document.documentElement.addEventListener('keydown', function (event) { diff --git a/various_tests/test_3D_conservative_rendering.html b/various_tests/test_3D_conservative_rendering.html index 83286c5c..d0e57f8f 100644 --- a/various_tests/test_3D_conservative_rendering.html +++ b/various_tests/test_3D_conservative_rendering.html @@ -235,13 +235,11 @@ scene2.add(grid); scene2.add(new THREE.Mesh(clonedGeometry, new THREE.MeshLambertMaterial({ color: 0xFEEFFE, - shading: THREE.SmoothShading, side: THREE.DoubleSide }))); scene2.add(new THREE.PointCloud(pipeline.modelStage.inputGeometry.clone(), new THREE.ShaderMaterial({ depthTest: true, - sizeAttenuation: false, uniforms: pipeline.modelStage.shaderUniforms, attributes: pipeline.modelStage.shaderAttributes, vertexShader: pipeline.modelStage.vertexShader, @@ -253,7 +251,6 @@ scene2.add(new THREE.Line(pipeline.modelStage.inputGeometry.clone(), new THREE.ShaderMaterial({ depthTest: true, - sizeAttenuation: false, uniforms: pipeline.modelStage.shaderUniforms, attributes: pipeline.modelStage.shaderAttributes, vertexShader: pipeline.modelStage.vertexShader, diff --git a/webapp/cnc/app/view.js b/webapp/cnc/app/view.js index 8ed16323..0e729daf 100644 --- a/webapp/cnc/app/view.js +++ b/webapp/cnc/app/view.js @@ -221,16 +221,13 @@ define(['Ember', 'cnc/svgImporter', 'cnc/gerberImporter', 'cnc/excellonImporter' }); this.set('nativeComponent', threeDView); this.set('travelDisplay', threeDView.createDrawingNode(threeDView.rapidMaterial, new THREE.MeshLambertMaterial({ - color: 0xFEEFFE, - shading: THREE.SmoothShading + color: 0xFEEFFE }))); this.set('outlinesDisplay', threeDView.createDrawingNode(threeDView.outlineMaterial, new THREE.MeshLambertMaterial({ - color: 0xFEEFFE, - shading: THREE.SmoothShading + color: 0xFEEFFE }))); this.set('highlightDisplay', threeDView.createOverlayNode(threeDView.highlightMaterial, new THREE.MeshLambertMaterial({ - color: 0xdd4c2f, opacity: 0.5, - shading: THREE.SmoothShading + color: 0xdd4c2f, opacity: 0.5 }))); this.synchronizeCurrentOperation(); @@ -241,8 +238,7 @@ define(['Ember', 'cnc/svgImporter', 'cnc/gerberImporter', 'cnc/excellonImporter' return ShapeWrapper.create({ shape: shape, outlineDisplay: threeDView.createDrawingNode(threeDView.outlineMaterial, new THREE.MeshLambertMaterial({ - color: 0xFEEFFE, - shading: THREE.SmoothShading + color: 0xFEEFFE })) }); } diff --git a/webapp/cnc/cam/3D/modelProjector.js b/webapp/cnc/cam/3D/modelProjector.js index e1e7883f..8e08bba3 100644 --- a/webapp/cnc/cam/3D/modelProjector.js +++ b/webapp/cnc/cam/3D/modelProjector.js @@ -1,234 +1,138 @@ "use strict"; -define([], function () { - - function Projector() { - var scene = new THREE.Scene(); - this.scene = scene; - this.angle = 0; - this.normalVertexShader = [ - 'attribute vec3 prevPoint;', - 'attribute vec3 nextPoint;', - 'void main() {', - ' gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);', - '}'].join('\n'); - - this.normalFragmentShader = [ - 'void main() {', - ' gl_FragData[0] = vec4(1.0 - gl_FragCoord.z, 0.0, 0.0, 1.0);', - '}'].join('\n'); - this.vertexShader = [ - 'attribute vec3 prevPoint;', - 'attribute vec3 nextPoint;', - 'uniform vec2 hPixel;', - 'uniform vec2 hPixelWorld;', - 'varying vec3 AABB_min;', - 'varying vec3 AABB_max;', - 'varying vec3 positionK;', - - 'float cross2d(vec2 v1, vec2 v2) {', - ' return v1.x * v2.y - v1.y * v2.x; ', - '}', - - // http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter42.html - 'void main() {', - ' vec3 eyeDirection = vec3(0.0, 0.0, -1.0);', - ' vec3 p1 = prevPoint, p2 = position, p3 = nextPoint;', - ' vec2 e1 = normalize(p2.xy - p1.xy);', - ' vec2 e2 = normalize(p2.xy - p3.xy);', - // project the side on the bisector - // http://stackoverflow.com/a/32515402/72637 - ' float halfsine = sqrt((1.0 - dot(e1, e2)) / 2.0);', - ' vec2 resultPoint2D = p2.xy + length(hPixelWorld) / halfsine * normalize(e1 + e2);', - - // project the 2D point to the triangle plane in 3D - // grab the triangle normal - ' vec3 normal = normalize(cross(p2.xyz - p1.xyz, p3.xyz - p2.xyz));', - // grab the Z for (x=0, y=0) - ' float d = dot(normal, p2.xyz);', - // the new Z is the distance from the 2D projected point to its projection on the triangle plane - ' float t = (dot(normal, vec3(resultPoint2D, 0.0)) - d) / (dot(normal, eyeDirection));', - - //shift the whole triangle up because Z is sampled at pixel center, but the maximum Z is at a corner. - //A mostly vertical triangle might send the Z very high or very low, we'll clamp that in the fragment shader - ' float cornerPessimization = sqrt(1.0 - normal.z * normal.z) * length(hPixelWorld);', - ' vec4 shiftedPosition = vec4(resultPoint2D, t + cornerPessimization, 1.0);', - ' vec4 projectedShiftedPosition = projectionMatrix * modelViewMatrix * shiftedPosition;', - - //compute the Axis Aligned bounding box - ' vec4 prevPos = projectionMatrix * modelViewMatrix * vec4(p1, 1.0);', - ' vec4 currPos = projectionMatrix * modelViewMatrix * vec4(p2, 1.0);', - ' vec4 nextPos = projectionMatrix * modelViewMatrix * vec4(p3, 1.0);', - ' vec3 minBounds = prevPos.xyz;', - ' minBounds = min(currPos.xyz, minBounds);', - ' minBounds = min(nextPos.xyz, minBounds);', - ' vec3 maxBounds = prevPos.xyz;', - ' maxBounds = max(currPos.xyz, maxBounds);', - ' maxBounds = max(nextPos.xyz, maxBounds);', - // extend the box by one pixel - ' minBounds = minBounds - vec3(hPixel, 0.0);', - ' maxBounds = maxBounds + vec3(hPixel, 0.0);', - - ' AABB_min = minBounds;', - ' AABB_max = maxBounds;', - ' gl_PointSize = 10.0;', - ' positionK = projectedShiftedPosition.xyz;', - ' gl_Position = projectedShiftedPosition;', - '}'].join('\n'); - - this.fragmentShader = [ - '#extension GL_EXT_frag_depth : require', - 'varying vec3 AABB_min;', - 'varying vec3 AABB_max;', - 'varying vec3 positionK;', - // depth encoding : http://aras-p.info/blog/2009/07/30/encoding-floats-to-rgba-the-final/ - 'highp float factor = (exp2(24.0) - 1.0) / exp2(24.0);', - 'vec3 EncodeFloatRGB(highp float v) {', - ' vec3 enc = fract(vec3(1.0, 255.0, 255.0 * 255.0) * factor * v);', - ' enc -= enc.yzz * vec3(1.0 / 255.0, 1.0 / 255.0, 0.0);', - ' return enc;', - '}', - 'highp float DecodeFloatRGB(vec3 rgb) {', - ' return dot(rgb, vec3(1.0, 1.0 / 255.0, 1.0 / 255.0 / 255.0)) / factor;', - '}', - 'void main() {', - ' vec2 pos = positionK.xy;', - //let's destroy the fragments that are really out there between the input corner and the dilated corner - ' if(any(bvec4(lessThan(pos, AABB_min.xy), greaterThan(pos, AABB_max.xy))))', - ' discard;', - // ok, we were pessimistic, but one thing still holds: - // the true Z value can never ever be higher or lower than any Z value of the input vertices, - // so we clip to get back to some reality - ' float z = clamp(positionK.z, AABB_min.z, AABB_max.z);', - // go back to fragment world - ' z = (0.5 * z + 0.5);', - // update the depth buffer, since what was a nice triangle is now a triangle with 2 bent corners (flattened by the Z clamp). - ' gl_FragDepthEXT = z;', - ' gl_FragData[0] = vec4(1.0 - z, 0.0, 0.0, 1.0);', - '}'].join('\n'); - this.shaderAttributes = {prevPoint: {type: 'v3', value: []}, nextPoint: {type: 'v3', value: []}}; - this.shaderUniforms = {hPixel: {type: 'v2'}, hPixelWorld: {type: 'v2'}}; - this.meshMaterial = new THREE.ShaderMaterial({ - doublesided: true, - depthTest: true, - sizeAttenuation: false, - linewidth: 1, - uniforms: this.shaderUniforms, - attributes: this.shaderAttributes, - vertexShader: this.vertexShader, - fragmentShader: this.fragmentShader - }); - this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1); - scene.add(this.camera); - } - - Projector.prototype = { - render: function (renderer, buffer) { - var extension = renderer.getContext().getExtension('EXT_frag_depth'); - if (!extension && this.meshMaterial.vertexShader != this.normalVertexShader) { - console.log('EXT_frag_depth webgl extension is not supported, the projection won\'t be conservative'); - this.meshMaterial.vertexShader = this.normalVertexShader; - this.meshMaterial.fragmentShader = this.normalFragmentShader; - } - this.meshMaterial.uniforms.hPixel.value = new THREE.Vector2(1 / buffer.width, 1 / buffer.height); - var w = (this.camera.right - this.camera.left) / buffer.width; - var h = (this.camera.top - this.camera.bottom) / buffer.height; - this.meshMaterial.uniforms.hPixelWorld.value = new THREE.Vector2(w / 2, h / 2); - renderer.render(this.scene, this.camera, buffer, true); - }, - setGeometry: function (meshGeometry) { - this.inputGeometry = meshGeometry; - if (this.model) { - this.scene.remove(this.model); - this.model = null; - } - this.model = new THREE.Object3D(); +define(['THREE', 'text!shaders/model_proj.vert', 'text!shaders/model_proj.frag', 'text!shaders/conservative_model_proj.vert', + 'text!shaders/conservative_model_proj.frag'], + function (THREE, normalVertexShader, normalFragmentShader, vertexShader, fragmentShader) { + + function Projector() { + var scene = new THREE.Scene(); + this.scene = scene; + this.angle = 0; + this.normalVertexShader = normalVertexShader; + this.normalFragmentShader = normalFragmentShader; + this.vertexShader = vertexShader; + this.fragmentShader = fragmentShader; + this.shaderAttributes = {prevPoint: {type: 'v3', value: []}, nextPoint: {type: 'v3', value: []}}; + this.shaderUniforms = {hPixel: {type: 'v2'}, hPixelWorld: {type: 'v2'}}; + this.meshMaterial = new THREE.ShaderMaterial({ + side: THREE.DoubleSide, + depthTest: true, + linewidth: 1, + uniforms: this.shaderUniforms, + vertexShader: this.vertexShader, + fragmentShader: this.fragmentShader + }); + this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1); + scene.add(this.camera); + } - var originalPosition = meshGeometry.attributes.position; - var attribute = originalPosition.clone(); - var triangleCount = attribute.length / attribute.itemSize / 3; + Projector.prototype = { + render: function (renderer, buffer) { + var extension = renderer.getContext().getExtension('EXT_frag_depth'); + if (!extension && this.meshMaterial.vertexShader != this.normalVertexShader) { + console.log('EXT_frag_depth webgl extension is not supported, the projection won\'t be conservative'); + this.meshMaterial.vertexShader = this.normalVertexShader; + this.meshMaterial.fragmentShader = this.normalFragmentShader; + } + this.meshMaterial.uniforms.hPixel.value = new THREE.Vector2(1 / buffer.width, 1 / buffer.height); + var w = (this.camera.right - this.camera.left) / buffer.width; + var h = (this.camera.top - this.camera.bottom) / buffer.height; + this.meshMaterial.uniforms.hPixelWorld.value = new THREE.Vector2(w / 2, h / 2); + renderer.render(this.scene, this.camera, buffer, true); + }, + setGeometry: function (meshGeometry) { + this.inputGeometry = meshGeometry; + if (this.model) { + this.scene.remove(this.model); + this.model = null; + } + this.model = new THREE.Object3D(); - for (var triangleIndex = 0; triangleIndex < triangleCount; triangleIndex++) { - attribute.copyAt(triangleIndex * 3 + 0, originalPosition, triangleIndex * 3 + 2); - attribute.copyAt(triangleIndex * 3 + 1, originalPosition, triangleIndex * 3 + 0); - attribute.copyAt(triangleIndex * 3 + 2, originalPosition, triangleIndex * 3 + 1); - } - meshGeometry.addAttribute('prevPoint', attribute); - attribute = originalPosition.clone(); - for (triangleIndex = 0; triangleIndex < triangleCount; triangleIndex++) { - attribute.copyAt(triangleIndex * 3 + 0, originalPosition, triangleIndex * 3 + 1); - attribute.copyAt(triangleIndex * 3 + 1, originalPosition, triangleIndex * 3 + 2); - attribute.copyAt(triangleIndex * 3 + 2, originalPosition, triangleIndex * 3 + 0); - } - meshGeometry.addAttribute('nextPoint', attribute); - meshGeometry.needsUpdate = true; + var originalPosition = meshGeometry.attributes.position; + var attribute = originalPosition.clone(); + var triangleCount = attribute.count / 3; - var mesh = new THREE.Mesh(meshGeometry, this.meshMaterial); - this.model.add(mesh); - this.modelBbox = this.computeCameraBoundingBox(); - this.scene.add(this.model); - this.model.updateMatrixWorld(); - this.resetCamera(); - }, - resetCamera: function () { - var bbox = this.modelBbox; - var bboxSize = bbox.size(); - this.aspectRatio = bboxSize.x / bboxSize.y; - this.camera.lookAt(new THREE.Vector3(0, 0, bbox.min.z)); - this.camera.near = 1; - this.camera.position.set(0, 0, bbox.max.z + 10 + this.camera.near); - this.camera.far = this.camera.position.z - bbox.min.z; - this.zRatio = 1 / (this.camera.far - this.camera.near); - this.setCamera(bbox.min.x, bbox.max.x, bbox.min.y, bbox.max.y); - }, - setCamera: function (left, right, bottom, top) { - this.camera.bottom = bottom; - this.camera.top = top; - this.camera.left = left; - this.camera.right = right; - this.camera.updateProjectionMatrix(); - this.camera.updateMatrixWorld(); - }, - pushZInverseProjOn: function (matrix) { - var pos = new THREE.Vector3().setFromMatrixPosition(matrix); - pos.z = this.camera.position.z - this.camera.far; - matrix.scale(new THREE.Vector3(1, 1, this.camera.far - this.camera.near)); - matrix.setPosition(pos); - }, - setAngle: function (angleDeg) { - this.angle = angleDeg; - this.camera.up.set(0, 1, 0).applyAxisAngle(new THREE.Vector3(0, 0, 1), angleDeg * Math.PI / 180); - this.camera.lookAt(this.camera.position.clone().sub(new THREE.Vector3(0, 0, 1))); - this.camera.updateProjectionMatrix(); - this.camera.updateMatrixWorld(); - this.modelBbox = this.computeCameraBoundingBox(); - }, - computeCameraBoundingBox: function () { - var vector = new THREE.Vector3(); - var m = new THREE.Matrix4(); - m.lookAt(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, -1), new THREE.Vector3(0, 1, 0).applyAxisAngle(new THREE.Vector3(0, 0, 1), -this.angle * Math.PI / 180)); - var boundingBox = new THREE.Box3(); - var positions = this.inputGeometry.attributes.position.array; - if (positions) { - var bb = boundingBox; - bb.makeEmpty(); - for (var i = 0, il = positions.length; i < il; i += 3) { - vector.set(positions[i], positions[i + 1], positions[i + 2]); - vector.applyMatrix4(m); - bb.expandByPoint(vector); + for (var triangleIndex = 0; triangleIndex < triangleCount; triangleIndex++) { + attribute.copyAt(triangleIndex * 3 + 0, originalPosition, triangleIndex * 3 + 2); + attribute.copyAt(triangleIndex * 3 + 1, originalPosition, triangleIndex * 3 + 0); + attribute.copyAt(triangleIndex * 3 + 2, originalPosition, triangleIndex * 3 + 1); + } + meshGeometry.addAttribute('prevPoint', attribute); + attribute = originalPosition.clone(); + for (triangleIndex = 0; triangleIndex < triangleCount; triangleIndex++) { + attribute.copyAt(triangleIndex * 3 + 0, originalPosition, triangleIndex * 3 + 1); + attribute.copyAt(triangleIndex * 3 + 1, originalPosition, triangleIndex * 3 + 2); + attribute.copyAt(triangleIndex * 3 + 2, originalPosition, triangleIndex * 3 + 0); + } + meshGeometry.addAttribute('nextPoint', attribute); + meshGeometry.needsUpdate = true; + + var mesh = new THREE.Mesh(meshGeometry, this.meshMaterial); + this.model.add(mesh); + this.modelBbox = this.computeCameraBoundingBox(); + this.scene.add(this.model); + this.model.updateMatrixWorld(); + this.resetCamera(); + }, + resetCamera: function () { + var bbox = this.modelBbox; + var bboxSize = bbox.size(); + this.aspectRatio = bboxSize.x / bboxSize.y; + this.camera.lookAt(new THREE.Vector3(0, 0, bbox.min.z)); + this.camera.near = 1; + this.camera.position.set(0, 0, bbox.max.z + 10 + this.camera.near); + this.camera.far = this.camera.position.z - bbox.min.z; + this.zRatio = 1 / (this.camera.far - this.camera.near); + this.setCamera(bbox.min.x, bbox.max.x, bbox.min.y, bbox.max.y); + }, + setCamera: function (left, right, bottom, top) { + this.camera.bottom = bottom; + this.camera.top = top; + this.camera.left = left; + this.camera.right = right; + this.camera.updateProjectionMatrix(); + this.camera.updateMatrixWorld(); + }, + pushZInverseProjOn: function (matrix) { + var pos = new THREE.Vector3().setFromMatrixPosition(matrix); + pos.z = this.camera.position.z - this.camera.far; + matrix.scale(new THREE.Vector3(1, 1, this.camera.far - this.camera.near)); + matrix.setPosition(pos); + }, + setAngle: function (angleDeg) { + this.angle = angleDeg; + this.camera.up.set(0, 1, 0).applyAxisAngle(new THREE.Vector3(0, 0, 1), angleDeg * Math.PI / 180); + this.camera.lookAt(this.camera.position.clone().sub(new THREE.Vector3(0, 0, 1))); + this.camera.updateProjectionMatrix(); + this.camera.updateMatrixWorld(); + this.modelBbox = this.computeCameraBoundingBox(); + }, + computeCameraBoundingBox: function () { + var vector = new THREE.Vector3(); + var m = new THREE.Matrix4(); + m.lookAt(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, -1), new THREE.Vector3(0, 1, 0).applyAxisAngle(new THREE.Vector3(0, 0, 1), -this.angle * Math.PI / 180)); + var boundingBox = new THREE.Box3(); + var positions = this.inputGeometry.attributes.position.array; + if (positions) { + var bb = boundingBox; + bb.makeEmpty(); + for (var i = 0, il = positions.length; i < il; i += 3) { + vector.set(positions[i], positions[i + 1], positions[i + 2]); + vector.applyMatrix4(m); + bb.expandByPoint(vector); + } } - } - if (positions === undefined || positions.length === 0) { - boundingBox.min.set(0, 0, 0); - boundingBox.max.set(0, 0, 0); - } + if (positions === undefined || positions.length === 0) { + boundingBox.min.set(0, 0, 0); + boundingBox.max.set(0, 0, 0); + } - if (isNaN(boundingBox.min.x) || isNaN(boundingBox.min.y) || isNaN(boundingBox.min.z)) { - console.error('THREE.BufferGeometry.computeBoundingBox: Computed min/max have NaN values. The "position" attribute is likely to have NaN values.'); + if (isNaN(boundingBox.min.x) || isNaN(boundingBox.min.y) || isNaN(boundingBox.min.z)) { + console.error('THREE.BufferGeometry.computeBoundingBox: Computed min/max have NaN values. The "position" attribute is likely to have NaN values.'); + } + return boundingBox; } - return boundingBox; - } - }; + }; - return Projector; -}); \ No newline at end of file + return Projector; + }); \ No newline at end of file diff --git a/webapp/cnc/gcode/simulation.js b/webapp/cnc/gcode/simulation.js index 598dafeb..470c831f 100644 --- a/webapp/cnc/gcode/simulation.js +++ b/webapp/cnc/gcode/simulation.js @@ -241,8 +241,6 @@ define(['cnc/util', 'cnc/gcode/geometry'], function (util, geometry) { function discretize(segment) { var type = COMPONENT_TYPES[segment.type]; - - console.log(segment.type, type); var steps = type.simulationSteps(segment); var startTime = currentTime; for (var j = 1; j <= steps; j++) { diff --git a/webapp/cnc/ui/threeDView.js b/webapp/cnc/ui/threeDView.js index 9acc9545..5c26c2f3 100644 --- a/webapp/cnc/ui/threeDView.js +++ b/webapp/cnc/ui/threeDView.js @@ -74,7 +74,7 @@ define(['THREE', 'TWEEN', 'cnc/util', 'libs/threejs/OrbitControls', 'cnc/ui/cube var res = earcut(rawVertices, [], 3); var bufferedGeometry = new THREE.BufferGeometry(); bufferedGeometry.addAttribute('position', new THREE.BufferAttribute(rawVertices, 3)); - bufferedGeometry.addAttribute('index', new THREE.BufferAttribute(new Uint16Array(res), 1)); + bufferedGeometry.setIndex(new THREE.BufferAttribute(new Uint16Array(res), 1)); this.node.add(new THREE.Mesh(bufferedGeometry, this.meshMaterial)); } }, @@ -113,14 +113,14 @@ define(['THREE', 'TWEEN', 'cnc/util', 'libs/threejs/OrbitControls', 'cnc/ui/cube if (this.bufferedGeometry == null) { this.bufferedGeometry = new THREE.BufferGeometry(); this.bufferedGeometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3)); - this.bufferedGeometry.addAttribute('index', new THREE.BufferAttribute(newIndices, 1)); - this.node.add(new THREE.Line(this.bufferedGeometry, this.lineMaterial, THREE.LinePieces)); + this.bufferedGeometry.setIndex(new THREE.BufferAttribute(newIndices, 1)); + this.node.add(new THREE.LineSegments(this.bufferedGeometry, this.lineMaterial)); } else { var attributes = this.bufferedGeometry.attributes; attributes.position.array = typedArrayConcat(attributes.position.array, vertices, Float32Array); - attributes.index.array = typedArrayConcat(attributes.index.array, newIndices, Uint16Array); + this.bufferedGeometry.index.array = typedArrayConcat(this.bufferedGeometry.index.array, newIndices, Uint16Array); attributes.position.needsUpdate = true; - attributes.index.needsUpdate = true; + this.bufferedGeometry.index.needsUpdate = true; } } }; @@ -247,7 +247,7 @@ define(['THREE', 'TWEEN', 'cnc/util', 'libs/threejs/OrbitControls', 'cnc/ui/cube this.requestAnimationFrameCallback = this.actuallyRender.bind(this); $container.prepend(cubeManipulator(this)); $container.prepend($('

')); - this.rapidToolpathNode = this.createDrawingNode(this.rapidMaterial); + this.rapidToolpathNode = this.createOverlayNode(this.rapidMaterial); this.normalToolpathNode = this.createDrawingNode(this.normalMaterial, new THREE.MeshBasicMaterial({ color: 0x6622BB, opacity: 0.5, diff --git a/webapp/libs/threejs/postprocessing/ShaderPass.js b/webapp/libs/threejs/postprocessing/ShaderPass.js index fdd7fb68..d85b1324 100644 --- a/webapp/libs/threejs/postprocessing/ShaderPass.js +++ b/webapp/libs/threejs/postprocessing/ShaderPass.js @@ -2,57 +2,57 @@ * @author alteredq / http://alteredqualia.com/ */ -THREE.ShaderPass = function (shader, textureID) { +THREE.ShaderPass = function ( shader, textureID ) { - this.textureID = ( textureID !== undefined ) ? textureID : "tDiffuse"; + this.textureID = ( textureID !== undefined ) ? textureID : "tDiffuse"; - this.uniforms = THREE.UniformsUtils.clone(shader.uniforms); - this.material = new THREE.ShaderMaterial({ + this.uniforms = THREE.UniformsUtils.clone( shader.uniforms ); - defines: shader.defines || {}, - uniforms: this.uniforms, - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader + this.material = new THREE.ShaderMaterial( { - }); + uniforms: this.uniforms, + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader - this.renderToScreen = false; + } ); - this.enabled = true; - this.needsSwap = true; - this.clear = false; + this.renderToScreen = false; + this.enabled = true; + this.needsSwap = true; + this.clear = false; - this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); - this.scene = new THREE.Scene(); - this.quad = new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 2), null); - this.scene.add(this.quad); + this.camera = new THREE.OrthographicCamera( -1, 1, 1, -1, 0, 1 ); + this.scene = new THREE.Scene(); + + this.quad = new THREE.Mesh( new THREE.PlaneGeometry( 2, 2 ), null ); + this.scene.add( this.quad ); }; THREE.ShaderPass.prototype = { - render: function (renderer, writeBuffer, readBuffer, delta) { + render: function ( renderer, writeBuffer, readBuffer, delta ) { - if (this.uniforms[ this.textureID ]) { + if ( this.uniforms[ this.textureID ] ) { - this.uniforms[ this.textureID ].value = readBuffer; + this.uniforms[ this.textureID ].value = readBuffer; - } + } - this.quad.material = this.material; + this.quad.material = this.material; - if (this.renderToScreen) { + if ( this.renderToScreen ) { - renderer.render(this.scene, this.camera); + renderer.render( this.scene, this.camera ); - } else { + } else { - renderer.render(this.scene, this.camera, writeBuffer, this.clear); + renderer.render( this.scene, this.camera, writeBuffer, this.clear ); - } + } - } + } }; diff --git a/webapp/libs/threejs/three-depthtextures.js b/webapp/libs/threejs/three-depthtextures.js index a526a607..bfb411d3 100644 --- a/webapp/libs/threejs/three-depthtextures.js +++ b/webapp/libs/threejs/three-depthtextures.js @@ -4,46 +4,102 @@ * @author mrdoob / http://mrdoob.com/ */ -var THREE = {REVISION: '72dev'}; +var THREE = { REVISION: '73dev' }; -// browserify support +// + +if ( typeof define === 'function' && define.amd ) { -if (typeof module === 'object') { + define( 'three', THREE ); - module.exports = THREE; +} else if ( 'undefined' !== typeof exports && 'undefined' !== typeof module ) { + + module.exports = THREE; } + // polyfills -if (Math.sign === undefined) { +if ( self.requestAnimationFrame === undefined || self.cancelAnimationFrame === undefined ) { + + // Missing in Android stock browser. + + ( 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; + + }; - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign + } - Math.sign = function (x) { + if ( self.cancelAnimationFrame === undefined && self.clearTimeout !== undefined ) { - return ( x < 0 ) ? -1 : ( x > 0 ) ? 1 : +x; + self.cancelAnimationFrame = function ( id ) { - }; + self.clearTimeout( id ); + + }; + + } + + }() ); } +if ( Math.sign === undefined ) { -// set the default log handlers -THREE.log = function () { - console.log.apply(console, arguments); -}; -THREE.warn = function () { - console.warn.apply(console, arguments); -}; -THREE.error = function () { - console.error.apply(console, arguments); -}; + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign + + Math.sign = function ( x ) { + + return ( x < 0 ) ? - 1 : ( x > 0 ) ? 1 : + x; + + }; + +} + +if ( Function.prototype.name === undefined && Object.defineProperty !== undefined ) { + + // Missing in IE9-11. + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name + + Object.defineProperty( Function.prototype, 'name', { + + get: function () { + + return this.toString().match( /^\s*function\s*(\S*)\s*\(/ )[ 1 ]; + } + + } ); + +} // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent.button -THREE.MOUSE = {LEFT: 0, MIDDLE: 1, RIGHT: 2}; +THREE.MOUSE = { LEFT: 0, MIDDLE: 1, RIGHT: 2 }; // GL STATE CONSTANTS @@ -71,7 +127,6 @@ THREE.DoubleSide = 2; // shading -THREE.NoShading = 0; THREE.FlatShading = 1; THREE.SmoothShading = 2; @@ -92,7 +147,7 @@ THREE.CustomBlending = 5; // custom blending equations // (numbers start from 100 not to clash with other -// mappings to OpenGL constants defined in Texture.js) +// mappings to OpenGL constants defined in Texture.js) THREE.AddEquation = 100; THREE.SubtractEquation = 101; @@ -211,48 +266,49 @@ THREE.RGB_PVRTC_2BPPV1_Format = 2101; THREE.RGBA_PVRTC_4BPPV1_Format = 2102; THREE.RGBA_PVRTC_2BPPV1_Format = 2103; +// Loop styles for AnimationAction + +THREE.LoopOnce = 2200; +THREE.LoopRepeat = 2201; +THREE.LoopPingPong = 2202; // DEPRECATED THREE.Projector = function () { - THREE.error('THREE.Projector has been moved to /examples/js/renderers/Projector.js.'); + console.error( 'THREE.Projector has been moved to /examples/js/renderers/Projector.js.' ); - this.projectVector = function (vector, camera) { + this.projectVector = function ( vector, camera ) { - THREE.warn('THREE.Projector: .projectVector() is now vector.project().'); - vector.project(camera); + console.warn( 'THREE.Projector: .projectVector() is now vector.project().' ); + vector.project( camera ); - }; + }; - this.unprojectVector = function (vector, camera) { + this.unprojectVector = function ( vector, camera ) { - THREE.warn('THREE.Projector: .unprojectVector() is now vector.unproject().'); - vector.unproject(camera); + console.warn( 'THREE.Projector: .unprojectVector() is now vector.unproject().' ); + vector.unproject( camera ); - }; + }; - this.pickingRay = function (vector, camera) { + this.pickingRay = function ( vector, camera ) { - THREE.error('THREE.Projector: .pickingRay() is now raycaster.setFromCamera().'); + console.error( 'THREE.Projector: .pickingRay() is now raycaster.setFromCamera().' ); - }; + }; }; THREE.CanvasRenderer = function () { - THREE.error('THREE.CanvasRenderer has been moved to /examples/js/renderers/CanvasRenderer.js'); + console.error( 'THREE.CanvasRenderer has been moved to /examples/js/renderers/CanvasRenderer.js' ); - this.domElement = document.createElement('canvas'); - this.clear = function () { - }; - this.render = function () { - }; - this.setClearColor = function () { - }; - this.setSize = function () { - }; + this.domElement = document.createElement( 'canvas' ); + this.clear = function () {}; + this.render = function () {}; + this.setClearColor = function () {}; + this.setSize = function () {}; }; @@ -262,1077 +318,1053 @@ THREE.CanvasRenderer = function () { * @author mrdoob / http://mrdoob.com/ */ -THREE.Color = function (color) { +THREE.Color = function ( color ) { - if (arguments.length === 3) { + if ( arguments.length === 3 ) { - return this.setRGB(arguments[0], arguments[1], arguments[2]); + return this.setRGB( arguments[ 0 ], arguments[ 1 ], arguments[ 2 ] ); - } + } - return this.set(color) + return this.set( color ); }; THREE.Color.prototype = { - constructor: THREE.Color, + constructor: THREE.Color, - r: 1, g: 1, b: 1, + r: 1, g: 1, b: 1, - set: function (value) { + set: function ( value ) { - if (value instanceof THREE.Color) { + if ( value instanceof THREE.Color ) { - this.copy(value); + this.copy( value ); - } else if (typeof value === 'number') { + } else if ( typeof value === 'number' ) { - this.setHex(value); + this.setHex( value ); - } else if (typeof value === 'string') { + } else if ( typeof value === 'string' ) { - this.setStyle(value); + this.setStyle( value ); - } + } - return this; + return this; - }, + }, - setHex: function (hex) { + setHex: function ( hex ) { - hex = Math.floor(hex); + hex = Math.floor( hex ); - this.r = ( hex >> 16 & 255 ) / 255; - this.g = ( hex >> 8 & 255 ) / 255; - this.b = ( hex & 255 ) / 255; + this.r = ( hex >> 16 & 255 ) / 255; + this.g = ( hex >> 8 & 255 ) / 255; + this.b = ( hex & 255 ) / 255; - return this; + return this; - }, + }, - setRGB: function (r, g, b) { + setRGB: function ( r, g, b ) { - this.r = r; - this.g = g; - this.b = b; + this.r = r; + this.g = g; + this.b = b; - return this; + return this; - }, + }, - setHSL: function (h, s, l) { + setHSL: function () { - // h,s,l ranges are in 0.0 - 1.0 + function hue2rgb ( p, q, t ) { - if (s === 0) { + 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; - this.r = this.g = this.b = l; + } - } else { + return function ( h, s, l ) { - var hue2rgb = function (p, q, t) { + // h,s,l ranges are in 0.0 - 1.0 + h = THREE.Math.euclideanModulo( h, 1 ); + s = THREE.Math.clamp( s, 0, 1 ); + l = THREE.Math.clamp( l, 0, 1 ); - 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; + if ( s === 0 ) { - }; + this.r = this.g = this.b = l; - var p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s ); - var q = ( 2 * l ) - p; + } else { - this.r = hue2rgb(q, p, h + 1 / 3); - this.g = hue2rgb(q, p, h); - this.b = hue2rgb(q, p, h - 1 / 3); + 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; + } - }, + return this; - setStyle: function (style) { + }; - // rgb(255,0,0) + }(), - if (/^rgb\((\d+), ?(\d+), ?(\d+)\)$/i.test(style)) { + setStyle: function ( style ) { - var color = /^rgb\((\d+), ?(\d+), ?(\d+)\)$/i.exec(style); + var parseAlpha = function ( strAlpha ) { - 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; + var alpha = parseFloat( strAlpha ); - return this; + if ( alpha < 1 ) { - } + console.warn( 'THREE.Color: Alpha component of color ' + style + ' will be ignored.' ); - // rgb(100%,0%,0%) + } - if (/^rgb\((\d+)\%, ?(\d+)\%, ?(\d+)\%\)$/i.test(style)) { + return alpha; - 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; + var m; - } + if ( m = /^((?:rgb|hsl)a?)\(\s*([^\)]*)\)/.exec( style ) ) { - // #ff0000 + // rgb / hsl - if (/^\#([0-9a-f]{6})$/i.test(style)) { + var color; + var name = m[ 1 ]; + var components = m[ 2 ]; - var color = /^\#([0-9a-f]{6})$/i.exec(style); + switch ( name ) { - this.setHex(parseInt(color[1], 16)); + case 'rgb': - return this; + if ( color = /^(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*$/.exec( components ) ) { - } + // rgb(255,0,0) + 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; - // #f00 + return this; - 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); + if ( color = /^(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*$/.exec( components ) ) { - this.setHex(parseInt(color[1] + color[1] + color[2] + color[2] + color[3] + color[3], 16)); + // rgb(100%,0%,0%) + 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; + return this; - } + } - // red + break; - if (/^(\w+)$/i.test(style)) { + case 'rgba': - this.setHex(THREE.ColorKeywords[style]); + if ( color = /^(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([0-9]*\.?[0-9]+)\s*$/.exec( components ) ) { - return this; + // rgba(255,0,0,0.5) + 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; + parseAlpha( color[ 4 ] ); - } + return this; + } - }, + if ( color = /^(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*([0-9]*\.?[0-9]+)\s*$/.exec( components ) ) { - copy: function (color) { + // rgba(100%,0%,0%,0.5) + 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; + parseAlpha( color[ 4 ] ); - this.r = color.r; - this.g = color.g; - this.b = color.b; + return this; - return this; + } - }, + break; - copyGammaToLinear: function (color, gammaFactor) { + case 'hsl': - if (gammaFactor === undefined) gammaFactor = 2.0; + if ( color = /^([0-9]*\.?[0-9]+)\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*$/.exec( components ) ) { - this.r = Math.pow(color.r, gammaFactor); - this.g = Math.pow(color.g, gammaFactor); - this.b = Math.pow(color.b, gammaFactor); + // hsl(120,50%,50%) + var h = parseFloat( color[ 1 ] ); + var s = parseInt( color[ 2 ], 10 ) / 100; + var l = parseInt( color[ 3 ], 10 ) / 100; - return this; + return this.setHSL( h, s, l ); - }, + } - copyLinearToGamma: function (color, gammaFactor) { + break; - if (gammaFactor === undefined) gammaFactor = 2.0; + case 'hsla': - var safeInverse = ( gammaFactor > 0 ) ? ( 1.0 / gammaFactor ) : 1.0; + if ( color = /^([0-9]*\.?[0-9]+)\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*([0-9]*\.?[0-9]+)\s*$/.exec( components ) ) { - this.r = Math.pow(color.r, safeInverse); - this.g = Math.pow(color.g, safeInverse); - this.b = Math.pow(color.b, safeInverse); + // hsla(120,50%,50%,0.5) + var h = parseFloat( color[ 1 ] ); + var s = parseInt( color[ 2 ], 10 ) / 100; + var l = parseInt( color[ 3 ], 10 ) / 100; + parseAlpha( color[ 4 ] ); - return this; + return this.setHSL( h, s, l ); - }, + } - convertGammaToLinear: function () { + break; - var r = this.r, g = this.g, b = this.b; + } - this.r = r * r; - this.g = g * g; - this.b = b * b; + } else if ( m = /^\#([A-Fa-f0-9]+)$/.exec( style ) ) { - return this; + // hex color - }, + var hex = m[ 1 ]; + var size = hex.length; - convertLinearToGamma: function () { + if ( size === 3 ) { - this.r = Math.sqrt(this.r); - this.g = Math.sqrt(this.g); - this.b = Math.sqrt(this.b); + // #ff0 + this.r = parseInt( hex.charAt( 0 ) + hex.charAt( 0 ), 16 ) / 255; + this.g = parseInt( hex.charAt( 1 ) + hex.charAt( 1 ), 16 ) / 255; + this.b = parseInt( hex.charAt( 2 ) + hex.charAt( 2 ), 16 ) / 255; - return this; + return this; - }, + } else if ( size === 6 ) { - getHex: function () { + // #ff0000 + this.r = parseInt( hex.charAt( 0 ) + hex.charAt( 1 ), 16 ) / 255; + this.g = parseInt( hex.charAt( 2 ) + hex.charAt( 3 ), 16 ) / 255; + this.b = parseInt( hex.charAt( 4 ) + hex.charAt( 5 ), 16 ) / 255; - return ( this.r * 255 ) << 16 ^ ( this.g * 255 ) << 8 ^ ( this.b * 255 ) << 0; + return this; - }, + } - getHexString: function () { + } - return ( '000000' + this.getHex().toString(16) ).slice(-6); + if ( style && style.length > 0 ) { - }, + // color keywords + var hex = THREE.ColorKeywords[ style ]; - getHSL: function (optionalTarget) { + if ( hex !== undefined ) { - // h,s,l ranges are in 0.0 - 1.0 + // red + this.setHex( hex ); - var hsl = optionalTarget || {h: 0, s: 0, l: 0}; + } else { - var r = this.r, g = this.g, b = this.b; + // unknown color + console.warn( 'THREE.Color: Unknown color ' + style ); - 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) { + return this; - hue = 0; - saturation = 0; + }, - } else { + clone: function () { - var delta = max - min; + return new this.constructor( this.r, this.g, this.b ); - saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min ); + }, - switch (max) { + copy: function ( color ) { - 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; + this.r = color.r; + this.g = color.g; + this.b = color.b; - } + return this; - hue /= 6; + }, - } + copyGammaToLinear: function ( color, gammaFactor ) { - hsl.h = hue; - hsl.s = saturation; - hsl.l = lightness; + if ( gammaFactor === undefined ) gammaFactor = 2.0; - return hsl; + this.r = Math.pow( color.r, gammaFactor ); + this.g = Math.pow( color.g, gammaFactor ); + this.b = Math.pow( color.b, gammaFactor ); - }, + return this; - getStyle: function () { + }, - return 'rgb(' + ( ( this.r * 255 ) | 0 ) + ',' + ( ( this.g * 255 ) | 0 ) + ',' + ( ( this.b * 255 ) | 0 ) + ')'; + copyLinearToGamma: function ( color, gammaFactor ) { - }, + if ( gammaFactor === undefined ) gammaFactor = 2.0; - offsetHSL: function (h, s, l) { + var safeInverse = ( gammaFactor > 0 ) ? ( 1.0 / gammaFactor ) : 1.0; - var hsl = this.getHSL(); + this.r = Math.pow( color.r, safeInverse ); + this.g = Math.pow( color.g, safeInverse ); + this.b = Math.pow( color.b, safeInverse ); - hsl.h += h; - hsl.s += s; - hsl.l += l; + return this; - this.setHSL(hsl.h, hsl.s, hsl.l); + }, - return this; + convertGammaToLinear: function () { - }, + var r = this.r, g = this.g, b = this.b; - add: function (color) { + this.r = r * r; + this.g = g * g; + this.b = b * b; - this.r += color.r; - this.g += color.g; - this.b += color.b; + return this; - return this; + }, - }, + convertLinearToGamma: function () { - addColors: function (color1, color2) { + this.r = Math.sqrt( this.r ); + this.g = Math.sqrt( this.g ); + this.b = Math.sqrt( this.b ); - this.r = color1.r + color2.r; - this.g = color1.g + color2.g; - this.b = color1.b + color2.b; + return this; - return this; + }, - }, + getHex: function () { - addScalar: function (s) { + return ( this.r * 255 ) << 16 ^ ( this.g * 255 ) << 8 ^ ( this.b * 255 ) << 0; - this.r += s; - this.g += s; - this.b += s; + }, - return this; + getHexString: function () { - }, + return ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 ); - multiply: function (color) { + }, - this.r *= color.r; - this.g *= color.g; - this.b *= color.b; + getHSL: function ( optionalTarget ) { - return this; + // h,s,l ranges are in 0.0 - 1.0 - }, + var hsl = optionalTarget || { h: 0, s: 0, l: 0 }; - multiplyScalar: function (s) { + var r = this.r, g = this.g, b = this.b; - this.r *= s; - this.g *= s; - this.b *= s; + var max = Math.max( r, g, b ); + var min = Math.min( r, g, b ); - return this; + var hue, saturation; + var lightness = ( min + max ) / 2.0; - }, + if ( min === max ) { - lerp: function (color, alpha) { + hue = 0; + saturation = 0; - this.r += ( color.r - this.r ) * alpha; - this.g += ( color.g - this.g ) * alpha; - this.b += ( color.b - this.b ) * alpha; + } else { - return this; + var delta = max - min; - }, + saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min ); - equals: function (c) { + switch ( max ) { - return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b ); + 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; - }, + } - fromArray: function (array) { + hue /= 6; - this.r = array[0]; - this.g = array[1]; - this.b = array[2]; + } - return this; + hsl.h = hue; + hsl.s = saturation; + hsl.l = lightness; - }, + return hsl; - toArray: function (array, offset) { + }, - if (array === undefined) array = []; - if (offset === undefined) offset = 0; + getStyle: function () { - array[offset] = this.r; - array[offset + 1] = this.g; - array[offset + 2] = this.b; + return 'rgb(' + ( ( this.r * 255 ) | 0 ) + ',' + ( ( this.g * 255 ) | 0 ) + ',' + ( ( this.b * 255 ) | 0 ) + ')'; - return array; - }, + }, - clone: function () { + offsetHSL: function ( h, s, l ) { - return new THREE.Color().setRGB(this.r, this.g, this.b); + 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 ( array, offset ) { + + if ( array === undefined ) array = []; + if ( offset === undefined ) offset = 0; + + array[ offset ] = this.r; + array[ offset + 1 ] = this.g; + array[ offset + 2 ] = this.b; + + return array; + + } -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 }; +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 }; + // File:src/math/Quaternion.js /** * @author mikael emtinger / http://gomo.se/ * @author alteredq / http://alteredqualia.com/ * @author WestLangley / http://github.com/WestLangley - * @author bhouston / http://exocortex.com + * @author bhouston / http://clara.io */ -THREE.Quaternion = function (x, y, z, w) { +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; + this._x = x || 0; + this._y = y || 0; + this._z = z || 0; + this._w = ( w !== undefined ) ? w : 1; }; THREE.Quaternion.prototype = { - constructor: THREE.Quaternion, + constructor: THREE.Quaternion, - _x: 0, _y: 0, _z: 0, _w: 0, + get x () { - get x() { + return this._x; - return this._x; + }, - }, + set x ( value ) { - set x(value) { + this._x = value; + this.onChangeCallback(); - this._x = value; - this.onChangeCallback(); + }, - }, + get y () { - get y() { + return this._y; - return this._y; + }, - }, + set y ( value ) { - set y(value) { + this._y = value; + this.onChangeCallback(); - this._y = value; - this.onChangeCallback(); + }, - }, + get z () { - get z() { + return this._z; - return this._z; + }, - }, + set z ( value ) { - set z(value) { + this._z = value; + this.onChangeCallback(); - this._z = value; - this.onChangeCallback(); + }, - }, + get w () { - get w() { + return this._w; - return this._w; + }, - }, + set w ( value ) { - set w(value) { + this._w = value; + this.onChangeCallback(); - this._w = value; - this.onChangeCallback(); + }, - }, + set: function ( x, y, z, w ) { - set: function (x, y, z, w) { + this._x = x; + this._y = y; + this._z = z; + this._w = w; - this._x = x; - this._y = y; - this._z = z; - this._w = w; + this.onChangeCallback(); - this.onChangeCallback(); + return this; - return this; + }, - }, + clone: function () { - copy: function (quaternion) { + return new this.constructor( this._x, this._y, this._z, this._w ); - this._x = quaternion.x; - this._y = quaternion.y; - this._z = quaternion.z; - this._w = quaternion.w; + }, - this.onChangeCallback(); + copy: function ( quaternion ) { - return this; + this._x = quaternion.x; + this._y = quaternion.y; + this._z = quaternion.z; + this._w = quaternion.w; - }, + this.onChangeCallback(); - setFromEuler: function (euler, update) { + return this; - if (euler instanceof THREE.Euler === false) { + }, - throw new Error('THREE.Quaternion: .setFromEuler() now expects a Euler rotation rather than a Vector3 and order.'); - } + setFromEuler: function ( euler, update ) { - // http://www.mathworks.com/matlabcentral/fileexchange/ - // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ - // content/SpinCalc.m + if ( euler instanceof THREE.Euler === false ) { - 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); + throw new Error( 'THREE.Quaternion: .setFromEuler() now expects a Euler rotation rather than a Vector3 and order.' ); - 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; + // http://www.mathworks.com/matlabcentral/fileexchange/ + // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ + // content/SpinCalc.m - } else if (euler.order === 'YXZ') { + 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 ); - 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; + var order = euler.order; - } else if (euler.order === 'ZXY') { + if ( 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; + 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') { + } else if ( 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; + 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') { + } else if ( 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; + 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') { + } else if ( 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; + 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 ( order === 'YZX' ) { - if (update !== false) this.onChangeCallback(); + 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; - return this; + } else if ( 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; - setFromAxisAngle: function (axis, angle) { + } - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm + if ( update !== false ) this.onChangeCallback(); - // assumes axis is normalized + return this; - 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); + setFromAxisAngle: function ( axis, angle ) { - this.onChangeCallback(); + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm - return this; + // assumes axis is normalized - }, + var halfAngle = angle / 2, s = Math.sin( halfAngle ); - setFromRotationMatrix: function (m) { + this._x = axis.x * s; + this._y = axis.y * s; + this._z = axis.z * s; + this._w = Math.cos( halfAngle ); - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm + this.onChangeCallback(); - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + return this; - 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], + setFromRotationMatrix: function ( m ) { - trace = m11 + m22 + m33, - s; + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm - if (trace > 0) { + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - s = 0.5 / Math.sqrt(trace + 1.0); + var te = m.elements, - this._w = 0.25 / s; - this._x = ( m32 - m23 ) * s; - this._y = ( m13 - m31 ) * s; - this._z = ( m21 - m12 ) * s; + 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 ], - } else if (m11 > m22 && m11 > m33) { + trace = m11 + m22 + m33, + s; - s = 2.0 * Math.sqrt(1.0 + m11 - m22 - m33); + if ( trace > 0 ) { - this._w = ( m32 - m23 ) / s; - this._x = 0.25 * s; - this._y = ( m12 + m21 ) / s; - this._z = ( m13 + m31 ) / s; + s = 0.5 / Math.sqrt( trace + 1.0 ); - } else if (m22 > m33) { + this._w = 0.25 / s; + this._x = ( m32 - m23 ) * s; + this._y = ( m13 - m31 ) * s; + this._z = ( m21 - m12 ) * s; - s = 2.0 * Math.sqrt(1.0 + m22 - m11 - m33); + } else if ( m11 > m22 && m11 > m33 ) { - this._w = ( m13 - m31 ) / s; - this._x = ( m12 + m21 ) / s; - this._y = 0.25 * s; - this._z = ( m23 + m32 ) / s; + s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); - } else { + this._w = ( m32 - m23 ) / s; + this._x = 0.25 * s; + this._y = ( m12 + m21 ) / s; + this._z = ( m13 + m31 ) / s; - s = 2.0 * Math.sqrt(1.0 + m33 - m11 - m22); + } else if ( m22 > m33 ) { - this._w = ( m21 - m12 ) / s; - this._x = ( m13 + m31 ) / s; - this._y = ( m23 + m32 ) / s; - this._z = 0.25 * s; + 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; - this.onChangeCallback(); + } else { - return this; + 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; - setFromUnitVectors: function () { + } - // http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final + this.onChangeCallback(); - // assumes direction vectors vFrom and vTo are normalized + return this; - var v1, r; + }, - var EPS = 0.000001; + setFromUnitVectors: function () { - return function (vFrom, vTo) { + // http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final - if (v1 === undefined) v1 = new THREE.Vector3(); + // assumes direction vectors vFrom and vTo are normalized - r = vFrom.dot(vTo) + 1; + var v1, r; - if (r < EPS) { + var EPS = 0.000001; - r = 0; + return function ( vFrom, vTo ) { - if (Math.abs(vFrom.x) > Math.abs(vFrom.z)) { + if ( v1 === undefined ) v1 = new THREE.Vector3(); - v1.set(-vFrom.y, vFrom.x, 0); + r = vFrom.dot( vTo ) + 1; - } else { + if ( r < EPS ) { - v1.set(0, -vFrom.z, vFrom.y); + r = 0; - } + if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) { - } else { + v1.set( - vFrom.y, vFrom.x, 0 ); - v1.crossVectors(vFrom, vTo); + } else { - } + v1.set( 0, - vFrom.z, vFrom.y ); - this._x = v1.x; - this._y = v1.y; - this._z = v1.z; - this._w = r; + } - this.normalize(); + } else { - return this; + v1.crossVectors( vFrom, vTo ); - } + } - }(), + this._x = v1.x; + this._y = v1.y; + this._z = v1.z; + this._w = r; - inverse: function () { + this.normalize(); - this.conjugate().normalize(); + return this; - return this; + } - }, + }(), - conjugate: function () { + inverse: function () { - this._x *= -1; - this._y *= -1; - this._z *= -1; + this.conjugate().normalize(); - this.onChangeCallback(); + return this; - return this; + }, - }, + conjugate: function () { - dot: function (v) { + this._x *= - 1; + this._y *= - 1; + this._z *= - 1; - return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w; + this.onChangeCallback(); - }, + return this; - lengthSq: function () { + }, - return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; + dot: function ( v ) { - }, + return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w; - length: function () { + }, - return Math.sqrt(this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w); + lengthSq: function () { - }, + return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; - normalize: function () { + }, - var l = this.length(); + length: function () { - if (l === 0) { + return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w ); - this._x = 0; - this._y = 0; - this._z = 0; - this._w = 1; + }, - } else { + normalize: function () { - l = 1 / l; + var l = this.length(); - this._x = this._x * l; - this._y = this._y * l; - this._z = this._z * l; - this._w = this._w * l; + if ( l === 0 ) { - } + this._x = 0; + this._y = 0; + this._z = 0; + this._w = 1; - this.onChangeCallback(); + } else { - return this; + l = 1 / l; - }, + this._x = this._x * l; + this._y = this._y * l; + this._z = this._z * l; + this._w = this._w * l; - multiply: function (q, p) { + } - if (p !== undefined) { + this.onChangeCallback(); - THREE.warn('THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.'); - return this.multiplyQuaternions(q, p); + return this; - } + }, - return this.multiplyQuaternions(this, q); + multiply: function ( q, p ) { - }, + if ( p !== undefined ) { - multiplyQuaternions: function (a, b) { + console.warn( 'THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' ); + return this.multiplyQuaternions( q, p ); - // 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; + return this.multiplyQuaternions( this, q ); - 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.onChangeCallback(); + multiplyQuaternions: function ( a, b ) { - return this; + // 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; - multiplyVector3: function (vector) { + 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; - THREE.warn('THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.'); - return vector.applyQuaternion(this); + this.onChangeCallback(); - }, + return this; - slerp: function (qb, t) { + }, - if (t === 0) return this; - if (t === 1) return this.copy(qb); + multiplyVector3: function ( vector ) { - var x = this._x, y = this._y, z = this._z, w = this._w; + console.warn( 'THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' ); + return vector.applyQuaternion( this ); - // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ + }, - var cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; + slerp: function ( qb, t ) { - if (cosHalfTheta < 0) { + if ( t === 0 ) return this; + if ( t === 1 ) return this.copy( qb ); - this._w = -qb._w; - this._x = -qb._x; - this._y = -qb._y; - this._z = -qb._z; + var x = this._x, y = this._y, z = this._z, w = this._w; - cosHalfTheta = -cosHalfTheta; + // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ - } else { + var cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; - this.copy(qb); + if ( cosHalfTheta < 0 ) { - } + this._w = - qb._w; + this._x = - qb._x; + this._y = - qb._y; + this._z = - qb._z; - if (cosHalfTheta >= 1.0) { + cosHalfTheta = - cosHalfTheta; - this._w = w; - this._x = x; - this._y = y; - this._z = z; + } else { - return this; + this.copy( qb ); - } + } - var halfTheta = Math.acos(cosHalfTheta); - var sinHalfTheta = Math.sqrt(1.0 - cosHalfTheta * cosHalfTheta); + if ( cosHalfTheta >= 1.0 ) { - if (Math.abs(sinHalfTheta) < 0.001) { + this._w = w; + this._x = x; + this._y = y; + this._z = z; - 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; - return this; + } - } + var halfTheta = Math.acos( cosHalfTheta ); + var sinHalfTheta = Math.sqrt( 1.0 - cosHalfTheta * cosHalfTheta ); - var ratioA = Math.sin(( 1 - t ) * halfTheta) / sinHalfTheta, - ratioB = Math.sin(t * halfTheta) / sinHalfTheta; + if ( Math.abs( sinHalfTheta ) < 0.001 ) { - 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._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 ); - this.onChangeCallback(); + return this; - return this; + } - }, + var ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta, + ratioB = Math.sin( t * halfTheta ) / sinHalfTheta; - equals: function (quaternion) { + 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 ); - return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); + this.onChangeCallback(); - }, + return this; - fromArray: function (array, offset) { + }, - if (offset === undefined) offset = 0; + equals: function ( quaternion ) { - this._x = array[offset]; - this._y = array[offset + 1]; - this._z = array[offset + 2]; - this._w = array[offset + 3]; + return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); - this.onChangeCallback(); + }, - return this; + fromArray: function ( array, offset ) { - }, + if ( offset === undefined ) offset = 0; - toArray: function (array, offset) { + this._x = array[ offset ]; + this._y = array[ offset + 1 ]; + this._z = array[ offset + 2 ]; + this._w = array[ offset + 3 ]; - if (array === undefined) array = []; - if (offset === undefined) offset = 0; + this.onChangeCallback(); - array[offset] = this._x; - array[offset + 1] = this._y; - array[offset + 2] = this._z; - array[offset + 3] = this._w; + return this; - return array; + }, - }, + toArray: function ( array, offset ) { - onChange: function (callback) { + if ( array === undefined ) array = []; + if ( offset === undefined ) offset = 0; - this.onChangeCallback = callback; + array[ offset ] = this._x; + array[ offset + 1 ] = this._y; + array[ offset + 2 ] = this._z; + array[ offset + 3 ] = this._w; - return this; + return array; - }, + }, - onChangeCallback: function () { - }, + onChange: function ( callback ) { - clone: function () { + this.onChangeCallback = callback; - return new THREE.Quaternion(this._x, this._y, this._z, this._w); + return this; - } + }, + + onChangeCallback: function () {} }; -THREE.Quaternion.slerp = function (qa, qb, qm, t) { +THREE.Quaternion.slerp = function ( qa, qb, qm, t ) { - return qm.copy(qa).slerp(qb, t); + return qm.copy( qa ).slerp( qb, t ); }; @@ -1345,442 +1377,451 @@ THREE.Quaternion.slerp = function (qa, qb, qm, t) { * @author zz85 / http://www.lab4games.net/zz85/blog */ -THREE.Vector2 = function (x, y) { +THREE.Vector2 = function ( x, y ) { - this.x = x || 0; - this.y = y || 0; + this.x = x || 0; + this.y = y || 0; }; THREE.Vector2.prototype = { - constructor: THREE.Vector2, + constructor: THREE.Vector2, - set: function (x, y) { + set: function ( x, y ) { - this.x = x; - this.y = y; + this.x = x; + this.y = y; - return this; + return this; - }, + }, - setX: function (x) { + setX: function ( x ) { - this.x = x; + this.x = x; - return this; + return this; - }, + }, - setY: function (y) { + setY: function ( y ) { - this.y = y; + this.y = y; - return this; + return this; - }, + }, - setComponent: function (index, value) { + setComponent: function ( index, value ) { - switch (index) { + switch ( index ) { - case 0: - this.x = value; - break; - case 1: - this.y = value; - break; - default: - throw new Error('index is out of range: ' + 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) { + getComponent: function ( index ) { - switch (index) { + switch ( index ) { - case 0: - return this.x; - case 1: - return this.y; - default: - throw new Error('index is out of range: ' + index); + case 0: return this.x; + case 1: return this.y; + default: throw new Error( 'index is out of range: ' + index ); - } + } - }, + }, - copy: function (v) { + clone: function () { - this.x = v.x; - this.y = v.y; + return new this.constructor( this.x, this.y ); - return this; + }, - }, + copy: function ( v ) { - add: function (v, w) { + this.x = v.x; + this.y = v.y; - if (w !== undefined) { + return this; - THREE.warn('THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead.'); - return this.addVectors(v, w); + }, - } + add: function ( v, w ) { - this.x += v.x; - this.y += v.y; + if ( w !== undefined ) { - return this; + console.warn( 'THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' ); + return this.addVectors( v, w ); - }, + } - addScalar: function (s) { + this.x += v.x; + this.y += v.y; - this.x += s; - this.y += s; + return this; - return this; + }, - }, + addScalar: function ( s ) { - addVectors: function (a, b) { + this.x += s; + this.y += s; - this.x = a.x + b.x; - this.y = a.y + b.y; + return this; - return this; + }, - }, + addVectors: function ( a, b ) { - sub: function (v, w) { + this.x = a.x + b.x; + this.y = a.y + b.y; - if (w !== undefined) { + return this; - THREE.warn('THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.'); - return this.subVectors(v, w); + }, - } + addScaledVector: function ( v, s ) { - this.x -= v.x; - this.y -= v.y; + this.x += v.x * s; + this.y += v.y * s; - return this; + return this; - }, + }, - subScalar: function (s) { + sub: function ( v, w ) { - this.x -= s; - this.y -= s; + if ( w !== undefined ) { - return this; + console.warn( 'THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' ); + return this.subVectors( v, w ); - }, + } - subVectors: function (a, b) { + this.x -= v.x; + this.y -= v.y; - this.x = a.x - b.x; - this.y = a.y - b.y; + return this; - return this; + }, - }, + subScalar: function ( s ) { - multiply: function (v) { + this.x -= s; + this.y -= s; - this.x *= v.x; - this.y *= v.y; + return this; - return this; + }, - }, + subVectors: function ( a, b ) { - multiplyScalar: function (s) { + this.x = a.x - b.x; + this.y = a.y - b.y; - this.x *= s; - this.y *= s; + return this; - return this; + }, - }, + multiply: function ( v ) { - divide: function (v) { + this.x *= v.x; + this.y *= v.y; - this.x /= v.x; - this.y /= v.y; + return this; - return this; + }, - }, + multiplyScalar: function ( s ) { - divideScalar: function (scalar) { + this.x *= s; + this.y *= s; - if (scalar !== 0) { + return this; - var invScalar = 1 / scalar; + }, - this.x *= invScalar; - this.y *= invScalar; + divide: function ( v ) { - } else { + this.x /= v.x; + this.y /= v.y; - this.x = 0; - this.y = 0; + return this; - } + }, - return this; + divideScalar: function ( scalar ) { - }, + if ( scalar !== 0 ) { - min: function (v) { + var invScalar = 1 / scalar; - if (this.x > v.x) { + this.x *= invScalar; + this.y *= invScalar; - this.x = v.x; + } else { - } + this.x = 0; + this.y = 0; - if (this.y > v.y) { + } - this.y = v.y; + return this; - } + }, - return this; + min: function ( v ) { - }, + if ( this.x > v.x ) { - max: function (v) { + this.x = v.x; - if (this.x < v.x) { + } - this.x = v.x; + if ( this.y > v.y ) { - } + this.y = v.y; - if (this.y < v.y) { + } - this.y = v.y; + return this; - } + }, - return this; + max: function ( v ) { - }, + if ( this.x < v.x ) { - clamp: function (min, max) { + this.x = v.x; - // This function assumes min < max, if this assumption isn't true it will not operate correctly + } - if (this.x < min.x) { + if ( this.y < v.y ) { - this.x = min.x; + this.y = v.y; - } else if (this.x > max.x) { + } - this.x = max.x; + return this; - } + }, - if (this.y < min.y) { + clamp: function ( min, max ) { - this.y = min.y; + // This function assumes min < max, if this assumption isn't true it will not operate correctly - } else if (this.y > max.y) { + if ( this.x < min.x ) { - this.y = max.y; + this.x = min.x; - } + } else if ( this.x > max.x ) { - return this; - }, + this.x = max.x; - clampScalar: (function () { + } - var min, max; + if ( this.y < min.y ) { - return function (minVal, maxVal) { + this.y = min.y; - if (min === undefined) { + } else if ( this.y > max.y ) { - min = new THREE.Vector2(); - max = new THREE.Vector2(); + this.y = max.y; - } + } - min.set(minVal, minVal); - max.set(maxVal, maxVal); + return this; - return this.clamp(min, max); + }, - }; + clampScalar: function () { - })(), + var min, max; - floor: function () { + return function clampScalar( minVal, maxVal ) { - this.x = Math.floor(this.x); - this.y = Math.floor(this.y); + if ( min === undefined ) { - return this; + min = new THREE.Vector2(); + max = new THREE.Vector2(); - }, + } - ceil: function () { + min.set( minVal, minVal ); + max.set( maxVal, maxVal ); - this.x = Math.ceil(this.x); - this.y = Math.ceil(this.y); + return this.clamp( min, max ); - return this; + }; - }, + }(), - round: function () { + floor: function () { - this.x = Math.round(this.x); - this.y = Math.round(this.y); + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); - return this; + return this; - }, + }, - roundToZero: function () { + ceil: 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.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); - return this; + return this; - }, + }, - negate: function () { + round: function () { - this.x = -this.x; - this.y = -this.y; + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); - return this; + return this; - }, + }, - dot: function (v) { + roundToZero: function () { - return this.x * v.x + this.y * v.y; + 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; - lengthSq: function () { + }, - return this.x * this.x + this.y * this.y; + negate: function () { - }, + this.x = - this.x; + this.y = - this.y; - length: function () { + return this; - return Math.sqrt(this.x * this.x + this.y * this.y); + }, - }, + dot: function ( v ) { - normalize: function () { + return this.x * v.x + this.y * v.y; - return this.divideScalar(this.length()); + }, - }, + lengthSq: function () { - distanceTo: function (v) { + return this.x * this.x + this.y * this.y; - return Math.sqrt(this.distanceToSquared(v)); + }, - }, + length: function () { - distanceToSquared: function (v) { + return Math.sqrt( this.x * this.x + this.y * this.y ); - var dx = this.x - v.x, dy = this.y - v.y; - return dx * dx + dy * dy; + }, - }, + lengthManhattan: function() { - setLength: function (l) { + return Math.abs( this.x ) + Math.abs( this.y ); - var oldLength = this.length(); + }, - if (oldLength !== 0 && l !== oldLength) { + normalize: function () { - this.multiplyScalar(l / oldLength); - } + return this.divideScalar( this.length() ); - return this; + }, - }, + distanceTo: function ( v ) { - lerp: function (v, alpha) { + return Math.sqrt( this.distanceToSquared( v ) ); - this.x += ( v.x - this.x ) * alpha; - this.y += ( v.y - this.y ) * alpha; + }, - return this; + distanceToSquared: function ( v ) { - }, + var dx = this.x - v.x, dy = this.y - v.y; + return dx * dx + dy * dy; - lerpVectors: function (v1, v2, alpha) { + }, - this.subVectors(v2, v1).multiplyScalar(alpha).add(v1); + setLength: function ( l ) { - return this; + var oldLength = this.length(); - }, + if ( oldLength !== 0 && l !== oldLength ) { - equals: function (v) { + this.multiplyScalar( l / oldLength ); - return ( ( v.x === this.x ) && ( v.y === this.y ) ); + } - }, + return this; - fromArray: function (array, offset) { + }, - if (offset === undefined) offset = 0; + lerp: function ( v, alpha ) { - this.x = array[offset]; - this.y = array[offset + 1]; + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; - return this; + return this; - }, + }, - toArray: function (array, offset) { + lerpVectors: function ( v1, v2, alpha ) { - if (array === undefined) array = []; - if (offset === undefined) offset = 0; + this.subVectors( v2, v1 ).multiplyScalar( alpha ).add( v1 ); - array[offset] = this.x; - array[offset + 1] = this.y; + return this; - return array; + }, - }, + equals: function ( v ) { - fromAttribute: function (attribute, index, offset) { + return ( ( v.x === this.x ) && ( v.y === this.y ) ); - if (offset === undefined) offset = 0; + }, - index = index * attribute.itemSize + offset; + fromArray: function ( array, offset ) { - this.x = attribute.array[index]; - this.y = attribute.array[index + 1]; + if ( offset === undefined ) offset = 0; - return this; + this.x = array[ offset ]; + this.y = array[ offset + 1 ]; - }, + return this; - clone: function () { + }, - return new THREE.Vector2(this.x, this.y); + toArray: function ( array, offset ) { - } + if ( array === undefined ) array = []; + if ( offset === undefined ) offset = 0; + + array[ offset ] = this.x; + array[ offset + 1 ] = this.y; + + return array; + + }, + + fromAttribute: function ( attribute, index, offset ) { + + if ( offset === undefined ) offset = 0; + + index = index * attribute.itemSize + offset; + + this.x = attribute.array[ index ]; + this.y = attribute.array[ index + 1 ]; + + return this; + + } }; @@ -1795,856 +1836,858 @@ THREE.Vector2.prototype = { * @author WestLangley / http://github.com/WestLangley */ -THREE.Vector3 = function (x, y, z) { +THREE.Vector3 = function ( x, y, z ) { - this.x = x || 0; - this.y = y || 0; - this.z = z || 0; + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; }; THREE.Vector3.prototype = { - constructor: THREE.Vector3, + constructor: THREE.Vector3, - set: function (x, y, z) { + set: function ( x, y, z ) { - this.x = x; - this.y = y; - this.z = z; + this.x = x; + this.y = y; + this.z = z; - return this; + return this; - }, + }, - setX: function (x) { + setX: function ( x ) { - this.x = x; + this.x = x; - return this; + return this; - }, + }, - setY: function (y) { + setY: function ( y ) { - this.y = y; + this.y = y; - return this; + return this; - }, + }, - setZ: function (z) { + setZ: function ( z ) { - this.z = z; + this.z = z; - return this; + return this; - }, + }, - setComponent: function (index, value) { + setComponent: function ( index, value ) { - switch (index) { + 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); + 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) { + getComponent: function ( index ) { - switch (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); + 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) { + clone: function () { - this.x = v.x; - this.y = v.y; - this.z = v.z; + return new this.constructor( this.x, this.y, this.z ); - return this; + }, - }, + copy: function ( v ) { - add: function (v, w) { + this.x = v.x; + this.y = v.y; + this.z = v.z; - if (w !== undefined) { + return this; - THREE.warn('THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead.'); - return this.addVectors(v, w); + }, - } + add: function ( v, w ) { - this.x += v.x; - this.y += v.y; - this.z += v.z; + if ( w !== undefined ) { - return this; + console.warn( 'THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' ); + return this.addVectors( v, w ); - }, + } - addScalar: function (s) { + this.x += v.x; + this.y += v.y; + this.z += v.z; - this.x += s; - this.y += s; - this.z += s; + return this; - return this; + }, - }, + addScalar: function ( s ) { - addVectors: function (a, b) { + this.x += s; + this.y += s; + this.z += s; - this.x = a.x + b.x; - this.y = a.y + b.y; - this.z = a.z + b.z; + return this; - return this; + }, - }, + addVectors: function ( a, b ) { - sub: function (v, w) { + this.x = a.x + b.x; + this.y = a.y + b.y; + this.z = a.z + b.z; - if (w !== undefined) { + return this; - THREE.warn('THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.'); - return this.subVectors(v, w); + }, - } + addScaledVector: function ( v, s ) { - this.x -= v.x; - this.y -= v.y; - this.z -= v.z; + this.x += v.x * s; + this.y += v.y * s; + this.z += v.z * s; - return this; + return this; - }, + }, - subScalar: function (s) { + sub: function ( v, w ) { - this.x -= s; - this.y -= s; - this.z -= s; + if ( w !== undefined ) { - return this; + console.warn( 'THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' ); + return this.subVectors( v, w ); - }, + } - subVectors: function (a, b) { + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; - this.x = a.x - b.x; - this.y = a.y - b.y; - this.z = a.z - b.z; + return this; - return this; + }, - }, + subScalar: function ( s ) { - multiply: function (v, w) { + this.x -= s; + this.y -= s; + this.z -= s; - if (w !== undefined) { + return this; - THREE.warn('THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.'); - return this.multiplyVectors(v, w); + }, - } + subVectors: function ( a, b ) { - this.x *= v.x; - this.y *= v.y; - this.z *= v.z; + this.x = a.x - b.x; + this.y = a.y - b.y; + this.z = a.z - b.z; - return this; + return this; - }, + }, - multiplyScalar: function (scalar) { + multiply: function ( v, w ) { - this.x *= scalar; - this.y *= scalar; - this.z *= scalar; + if ( w !== undefined ) { - return this; + console.warn( 'THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.' ); + return this.multiplyVectors( v, w ); - }, + } - multiplyVectors: function (a, b) { + this.x *= v.x; + this.y *= v.y; + this.z *= v.z; - this.x = a.x * b.x; - this.y = a.y * b.y; - this.z = a.z * b.z; + return this; - return this; + }, - }, + multiplyScalar: function ( scalar ) { - applyEuler: function () { + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; - var quaternion; + return this; - return function (euler) { + }, - if (euler instanceof THREE.Euler === false) { + multiplyVectors: function ( a, b ) { - THREE.error('THREE.Vector3: .applyEuler() now expects a Euler rotation rather than a Vector3 and order.'); + this.x = a.x * b.x; + this.y = a.y * b.y; + this.z = a.z * b.z; - } + return this; - if (quaternion === undefined) quaternion = new THREE.Quaternion(); + }, - this.applyQuaternion(quaternion.setFromEuler(euler)); + applyEuler: function () { - return this; + var quaternion; - }; + return function applyEuler( euler ) { - }(), + if ( euler instanceof THREE.Euler === false ) { - applyAxisAngle: function () { + console.error( 'THREE.Vector3: .applyEuler() now expects a Euler rotation rather than a Vector3 and order.' ); - var quaternion; + } - return function (axis, angle) { + if ( quaternion === undefined ) quaternion = new THREE.Quaternion(); - if (quaternion === undefined) quaternion = new THREE.Quaternion(); + this.applyQuaternion( quaternion.setFromEuler( euler ) ); - this.applyQuaternion(quaternion.setFromAxisAngle(axis, angle)); + return this; - return this; + }; - }; + }(), - }(), + applyAxisAngle: function () { - applyMatrix3: function (m) { + var quaternion; - var x = this.x; - var y = this.y; - var z = this.z; + return function applyAxisAngle( axis, angle ) { - var e = m.elements; + if ( quaternion === undefined ) quaternion = new THREE.Quaternion(); - 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; + this.applyQuaternion( quaternion.setFromAxisAngle( axis, angle ) ); - return this; + return this; - }, + }; - applyMatrix4: function (m) { + }(), - // input: THREE.Matrix4 affine matrix + applyMatrix3: function ( m ) { - var x = this.x, y = this.y, z = this.z; + var x = this.x; + var y = this.y; + var z = this.z; - var e = m.elements; + 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]; + 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; + return this; - }, + }, - applyProjection: function (m) { + applyMatrix4: function ( m ) { - // input: THREE.Matrix4 projection matrix + // input: THREE.Matrix4 affine matrix - var x = this.x, y = this.y, z = this.z; + 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 + var e = m.elements; - 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; + 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; + return this; - }, + }, - applyQuaternion: function (q) { + applyProjection: function ( m ) { - var x = this.x; - var y = this.y; - var z = this.z; + // input: THREE.Matrix4 projection matrix - var qx = q.x; - var qy = q.y; - var qz = q.z; - var qw = q.w; + var x = this.x, y = this.y, z = this.z; - // calculate quat * vector + var e = m.elements; + var d = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); // perspective divide - 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; + 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; - // calculate result * inverse quat + return this; - 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; + applyQuaternion: function ( q ) { - }, + var x = this.x; + var y = this.y; + var z = this.z; - project: function () { + var qx = q.x; + var qy = q.y; + var qz = q.z; + var qw = q.w; - var matrix; + // calculate quat * vector - return function (camera) { + 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; - if (matrix === undefined) matrix = new THREE.Matrix4(); + // calculate result * inverse quat - matrix.multiplyMatrices(camera.projectionMatrix, matrix.getInverse(camera.matrixWorld)); - return this.applyProjection(matrix); + 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; - }(), + }, - unproject: function () { + project: function () { - var matrix; + var matrix; - return function (camera) { + return function project( camera ) { - if (matrix === undefined) matrix = new THREE.Matrix4(); + if ( matrix === undefined ) matrix = new THREE.Matrix4(); - matrix.multiplyMatrices(camera.matrixWorld, matrix.getInverse(camera.projectionMatrix)); - return this.applyProjection(matrix); + matrix.multiplyMatrices( camera.projectionMatrix, matrix.getInverse( camera.matrixWorld ) ); + return this.applyProjection( matrix ); - }; + }; - }(), + }(), - transformDirection: function (m) { + unproject: function () { - // input: THREE.Matrix4 affine matrix - // vector interpreted as a direction + var matrix; - var x = this.x, y = this.y, z = this.z; + return function unproject( camera ) { - var e = m.elements; + if ( matrix === undefined ) matrix = new THREE.Matrix4(); - 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; + matrix.multiplyMatrices( camera.matrixWorld, matrix.getInverse( camera.projectionMatrix ) ); + return this.applyProjection( matrix ); - this.normalize(); + }; - return this; + }(), - }, + transformDirection: function ( m ) { - divide: function (v) { + // input: THREE.Matrix4 affine matrix + // vector interpreted as a direction - this.x /= v.x; - this.y /= v.y; - this.z /= v.z; + var x = this.x, y = this.y, z = this.z; - return this; + 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; - divideScalar: function (scalar) { + this.normalize(); - if (scalar !== 0) { + return this; - var invScalar = 1 / scalar; + }, - this.x *= invScalar; - this.y *= invScalar; - this.z *= invScalar; + divide: function ( v ) { - } else { + this.x /= v.x; + this.y /= v.y; + this.z /= v.z; - this.x = 0; - this.y = 0; - this.z = 0; + return this; - } + }, - return this; + divideScalar: function ( scalar ) { - }, + if ( scalar !== 0 ) { - min: function (v) { + var invScalar = 1 / scalar; - if (this.x > v.x) { + this.x *= invScalar; + this.y *= invScalar; + this.z *= invScalar; - this.x = v.x; + } else { - } + this.x = 0; + this.y = 0; + this.z = 0; - if (this.y > v.y) { + } - this.y = v.y; + return this; - } + }, - if (this.z > v.z) { + min: function ( v ) { - this.z = v.z; + if ( this.x > v.x ) { - } + this.x = v.x; - return this; + } - }, + if ( this.y > v.y ) { - max: function (v) { + this.y = v.y; - if (this.x < v.x) { + } - this.x = v.x; + if ( this.z > v.z ) { - } + this.z = v.z; - if (this.y < v.y) { + } - this.y = v.y; + return this; - } + }, - if (this.z < v.z) { + max: function ( v ) { - this.z = v.z; + if ( this.x < v.x ) { - } + this.x = v.x; - return this; + } - }, + if ( this.y < v.y ) { - clamp: function (min, max) { + this.y = v.y; - // This function assumes min < max, if this assumption isn't true it will not operate correctly + } - if (this.x < min.x) { + if ( this.z < v.z ) { - this.x = min.x; + this.z = v.z; - } else if (this.x > max.x) { + } - this.x = max.x; + return this; - } + }, - if (this.y < min.y) { + clamp: function ( min, max ) { - this.y = min.y; + // This function assumes min < max, if this assumption isn't true it will not operate correctly - } else if (this.y > max.y) { + if ( this.x < min.x ) { - this.y = max.y; + this.x = min.x; - } + } else if ( this.x > max.x ) { - if (this.z < min.z) { + this.x = max.x; - this.z = min.z; + } - } else if (this.z > max.z) { + if ( this.y < min.y ) { - this.z = max.z; + this.y = min.y; - } + } else if ( this.y > max.y ) { - return this; + this.y = max.y; - }, + } - clampScalar: (function () { + if ( this.z < min.z ) { - var min, max; + this.z = min.z; - return function (minVal, maxVal) { + } else if ( this.z > max.z ) { - if (min === undefined) { + this.z = max.z; - min = new THREE.Vector3(); - max = new THREE.Vector3(); + } - } + return this; - min.set(minVal, minVal, minVal); - max.set(maxVal, maxVal, maxVal); + }, - return this.clamp(min, max); + clampScalar: function () { - }; + var min, max; - })(), + return function clampScalar( minVal, maxVal ) { - floor: function () { + if ( min === undefined ) { - this.x = Math.floor(this.x); - this.y = Math.floor(this.y); - this.z = Math.floor(this.z); + min = new THREE.Vector3(); + max = new THREE.Vector3(); - return this; + } - }, + min.set( minVal, minVal, minVal ); + max.set( maxVal, maxVal, maxVal ); - ceil: function () { + return this.clamp( min, max ); - this.x = Math.ceil(this.x); - this.y = Math.ceil(this.y); - this.z = Math.ceil(this.z); + }; - return this; + }(), - }, + floor: function () { - round: function () { + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); + this.z = Math.floor( this.z ); - this.x = Math.round(this.x); - this.y = Math.round(this.y); - this.z = Math.round(this.z); + return this; - return this; + }, - }, + ceil: function () { - roundToZero: function () { + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); + this.z = Math.ceil( this.z ); - 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; - return this; + }, - }, + round: function () { - negate: function () { + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); + this.z = Math.round( this.z ); - this.x = -this.x; - this.y = -this.y; - this.z = -this.z; + return this; - return this; + }, - }, + roundToZero: function () { - dot: function (v) { + 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.x * v.x + this.y * v.y + this.z * v.z; + return this; - }, + }, - lengthSq: function () { + negate: function () { - return this.x * this.x + this.y * this.y + this.z * this.z; + this.x = - this.x; + this.y = - this.y; + this.z = - this.z; - }, + return this; - length: function () { + }, - return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + dot: function ( v ) { - }, + return this.x * v.x + this.y * v.y + this.z * v.z; - lengthManhattan: function () { + }, - return Math.abs(this.x) + Math.abs(this.y) + Math.abs(this.z); + lengthSq: function () { - }, + return this.x * this.x + this.y * this.y + this.z * this.z; - normalize: function () { + }, - return this.divideScalar(this.length()); + length: function () { - }, + return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); - setLength: function (l) { + }, - var oldLength = this.length(); + lengthManhattan: function () { - if (oldLength !== 0 && l !== oldLength) { + return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); - this.multiplyScalar(l / oldLength); - } + }, - return this; + normalize: function () { - }, + return this.divideScalar( this.length() ); - 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; + setLength: function ( l ) { - return this; + var oldLength = this.length(); - }, + if ( oldLength !== 0 && l !== oldLength ) { - lerpVectors: function (v1, v2, alpha) { + this.multiplyScalar( l / oldLength ); - this.subVectors(v2, v1).multiplyScalar(alpha).add(v1); + } - return this; + return this; - }, + }, - cross: function (v, w) { + lerp: function ( v, alpha ) { - if (w !== undefined) { + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; + this.z += ( v.z - this.z ) * alpha; - THREE.warn('THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.'); - return this.crossVectors(v, w); + return this; - } + }, - var x = this.x, y = this.y, z = this.z; + lerpVectors: function ( v1, v2, alpha ) { - this.x = y * v.z - z * v.y; - this.y = z * v.x - x * v.z; - this.z = x * v.y - y * v.x; + this.subVectors( v2, v1 ).multiplyScalar( alpha ).add( v1 ); - return this; + return this; - }, + }, - crossVectors: function (a, b) { + cross: function ( v, w ) { - var ax = a.x, ay = a.y, az = a.z; - var bx = b.x, by = b.y, bz = b.z; + if ( w !== undefined ) { - this.x = ay * bz - az * by; - this.y = az * bx - ax * bz; - this.z = ax * by - ay * bx; + console.warn( 'THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.' ); + return this.crossVectors( v, w ); - return this; + } - }, + var x = this.x, y = this.y, z = this.z; - projectOnVector: function () { + this.x = y * v.z - z * v.y; + this.y = z * v.x - x * v.z; + this.z = x * v.y - y * v.x; - var v1, dot; + return this; - return function (vector) { + }, - if (v1 === undefined) v1 = new THREE.Vector3(); + crossVectors: function ( a, b ) { - v1.copy(vector).normalize(); + var ax = a.x, ay = a.y, az = a.z; + var bx = b.x, by = b.y, bz = b.z; - dot = this.dot(v1); + this.x = ay * bz - az * by; + this.y = az * bx - ax * bz; + this.z = ax * by - ay * bx; - return this.copy(v1).multiplyScalar(dot); + return this; - }; + }, - }(), + projectOnVector: function () { - projectOnPlane: function () { + var v1, dot; - var v1; + return function projectOnVector( vector ) { - return function (planeNormal) { + if ( v1 === undefined ) v1 = new THREE.Vector3(); - if (v1 === undefined) v1 = new THREE.Vector3(); + v1.copy( vector ).normalize(); - v1.copy(this).projectOnVector(planeNormal); + dot = this.dot( v1 ); - return this.sub(v1); + return this.copy( v1 ).multiplyScalar( dot ); - } + }; - }(), + }(), - reflect: function () { + projectOnPlane: function () { - // reflect incident vector off plane orthogonal to normal - // normal is assumed to have unit length + var v1; - var v1; + return function projectOnPlane( planeNormal ) { - return function (normal) { + if ( v1 === undefined ) v1 = new THREE.Vector3(); - if (v1 === undefined) v1 = new THREE.Vector3(); + v1.copy( this ).projectOnVector( planeNormal ); - return this.sub(v1.copy(normal).multiplyScalar(2 * this.dot(normal))); + return this.sub( v1 ); - } + } - }(), + }(), - angleTo: function (v) { + reflect: function () { - var theta = this.dot(v) / ( this.length() * v.length() ); + // reflect incident vector off plane orthogonal to normal + // normal is assumed to have unit length - // clamp, to handle numerical problems + var v1; - return Math.acos(THREE.Math.clamp(theta, -1, 1)); + return function reflect( normal ) { - }, + if ( v1 === undefined ) v1 = new THREE.Vector3(); - distanceTo: function (v) { + return this.sub( v1.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); - return Math.sqrt(this.distanceToSquared(v)); + } - }, + }(), - distanceToSquared: function (v) { + angleTo: function ( v ) { - var dx = this.x - v.x; - var dy = this.y - v.y; - var dz = this.z - v.z; + var theta = this.dot( v ) / ( this.length() * v.length() ); - return dx * dx + dy * dy + dz * dz; + // clamp, to handle numerical problems - }, + return Math.acos( THREE.Math.clamp( theta, - 1, 1 ) ); - setEulerFromRotationMatrix: function (m, order) { + }, - THREE.error('THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.'); + distanceTo: function ( v ) { - }, + return Math.sqrt( this.distanceToSquared( v ) ); - setEulerFromQuaternion: function (q, order) { + }, - THREE.error('THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.'); + distanceToSquared: function ( v ) { - }, + var dx = this.x - v.x; + var dy = this.y - v.y; + var dz = this.z - v.z; - getPositionFromMatrix: function (m) { + return dx * dx + dy * dy + dz * dz; - THREE.warn('THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition().'); + }, - return this.setFromMatrixPosition(m); + setEulerFromRotationMatrix: function ( m, order ) { - }, + console.error( 'THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.' ); - getScaleFromMatrix: function (m) { + }, - THREE.warn('THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale().'); + setEulerFromQuaternion: function ( q, order ) { - return this.setFromMatrixScale(m); - }, + console.error( 'THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.' ); - getColumnFromMatrix: function (index, matrix) { + }, - THREE.warn('THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn().'); + getPositionFromMatrix: function ( m ) { - return this.setFromMatrixColumn(index, matrix); + console.warn( 'THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition().' ); - }, + return this.setFromMatrixPosition( m ); - setFromMatrixPosition: function (m) { + }, - this.x = m.elements[12]; - this.y = m.elements[13]; - this.z = m.elements[14]; + getScaleFromMatrix: function ( m ) { - return this; + console.warn( 'THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale().' ); - }, + return this.setFromMatrixScale( m ); - 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(); + getColumnFromMatrix: function ( index, matrix ) { - this.x = sx; - this.y = sy; - this.z = sz; + console.warn( 'THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn().' ); - return this; - }, + return this.setFromMatrixColumn( index, matrix ); - setFromMatrixColumn: function (index, matrix) { + }, - var offset = index * 4; + setFromMatrixPosition: function ( m ) { - var me = matrix.elements; + this.x = m.elements[ 12 ]; + this.y = m.elements[ 13 ]; + this.z = m.elements[ 14 ]; - this.x = me[offset]; - this.y = me[offset + 1]; - this.z = me[offset + 2]; + return this; - return this; + }, - }, + setFromMatrixScale: function ( m ) { - equals: function (v) { + 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(); - return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); + this.x = sx; + this.y = sy; + this.z = sz; - }, + return this; - fromArray: function (array, offset) { + }, - if (offset === undefined) offset = 0; + setFromMatrixColumn: function ( index, matrix ) { - this.x = array[offset]; - this.y = array[offset + 1]; - this.z = array[offset + 2]; + var offset = index * 4; - return this; + var me = matrix.elements; - }, + this.x = me[ offset ]; + this.y = me[ offset + 1 ]; + this.z = me[ offset + 2 ]; - toArray: function (array, offset) { + return this; - if (array === undefined) array = []; - if (offset === undefined) offset = 0; + }, - array[offset] = this.x; - array[offset + 1] = this.y; - array[offset + 2] = this.z; + equals: function ( v ) { - return array; + return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); - }, + }, - fromAttribute: function (attribute, index, offset) { + fromArray: function ( array, offset ) { - if (offset === undefined) offset = 0; + if ( offset === undefined ) offset = 0; - index = index * attribute.itemSize + offset; + this.x = array[ offset ]; + this.y = array[ offset + 1 ]; + this.z = array[ offset + 2 ]; - this.x = attribute.array[index]; - this.y = attribute.array[index + 1]; - this.z = attribute.array[index + 2]; + return this; - return this; + }, - }, + toArray: function ( array, offset ) { - clone: function () { + if ( array === undefined ) array = []; + if ( offset === undefined ) offset = 0; - return new THREE.Vector3(this.x, this.y, this.z); + array[ offset ] = this.x; + array[ offset + 1 ] = this.y; + array[ offset + 2 ] = this.z; - } + return array; + + }, + + fromAttribute: function ( attribute, index, offset ) { + + if ( offset === undefined ) offset = 0; + + index = index * attribute.itemSize + offset; + + this.x = attribute.array[ index ]; + this.y = attribute.array[ index + 1 ]; + this.z = attribute.array[ index + 2 ]; + + return this; + + } }; @@ -2658,701 +2701,704 @@ THREE.Vector3.prototype = { * @author WestLangley / http://github.com/WestLangley */ -THREE.Vector4 = function (x, y, z, w) { +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; + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; + this.w = ( w !== undefined ) ? w : 1; }; THREE.Vector4.prototype = { - constructor: THREE.Vector4, + constructor: THREE.Vector4, - set: function (x, y, z, w) { + set: function ( x, y, z, w ) { - this.x = x; - this.y = y; - this.z = z; - this.w = w; + this.x = x; + this.y = y; + this.z = z; + this.w = w; - return this; + return this; - }, + }, - setX: function (x) { + setX: function ( x ) { - this.x = x; + this.x = x; - return this; + return this; - }, + }, - setY: function (y) { + setY: function ( y ) { - this.y = y; + this.y = y; - return this; + return this; - }, + }, - setZ: function (z) { + setZ: function ( z ) { - this.z = z; + this.z = z; - return this; + return this; - }, + }, - setW: function (w) { + setW: function ( w ) { - this.w = w; + this.w = w; - return this; + return this; - }, + }, - setComponent: function (index, value) { + setComponent: function ( index, value ) { - switch (index) { + 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); + 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) { + getComponent: function ( index ) { - switch (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); + 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) { + clone: function () { - this.x = v.x; - this.y = v.y; - this.z = v.z; - this.w = ( v.w !== undefined ) ? v.w : 1; + return new this.constructor( this.x, this.y, this.z, this.w ); - return this; + }, - }, + copy: function ( v ) { - add: function (v, w) { + this.x = v.x; + this.y = v.y; + this.z = v.z; + this.w = ( v.w !== undefined ) ? v.w : 1; - if (w !== undefined) { + return this; - THREE.warn('THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead.'); - return this.addVectors(v, w); + }, - } + add: function ( v, w ) { - this.x += v.x; - this.y += v.y; - this.z += v.z; - this.w += v.w; + if ( w !== undefined ) { - return this; + console.warn( 'THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' ); + return this.addVectors( v, w ); - }, + } - addScalar: function (s) { + this.x += v.x; + this.y += v.y; + this.z += v.z; + this.w += v.w; - this.x += s; - this.y += s; - this.z += s; - this.w += s; + return this; - return this; + }, - }, + addScalar: function ( s ) { - addVectors: function (a, b) { + this.x += s; + this.y += s; + this.z += s; + this.w += s; - 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; - return this; + }, - }, + addVectors: function ( a, b ) { - sub: function (v, w) { + this.x = a.x + b.x; + this.y = a.y + b.y; + this.z = a.z + b.z; + this.w = a.w + b.w; - if (w !== undefined) { + return this; - THREE.warn('THREE.Vector4: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.'); - return this.subVectors(v, w); + }, - } + addScaledVector: function ( v, s ) { - this.x -= v.x; - this.y -= v.y; - this.z -= v.z; - this.w -= v.w; + this.x += v.x * s; + this.y += v.y * s; + this.z += v.z * s; + this.w += v.w * s; - return this; + return this; - }, + }, - subScalar: function (s) { + sub: function ( v, w ) { - this.x -= s; - this.y -= s; - this.z -= s; - this.w -= s; + if ( w !== undefined ) { - return this; + console.warn( 'THREE.Vector4: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' ); + return this.subVectors( v, w ); - }, + } - subVectors: function (a, b) { + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; + this.w -= v.w; - 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; - return this; + }, - }, + subScalar: function ( s ) { - multiplyScalar: function (scalar) { + this.x -= s; + this.y -= s; + this.z -= s; + this.w -= s; - this.x *= scalar; - this.y *= scalar; - this.z *= scalar; - this.w *= scalar; + return this; - return this; + }, - }, + subVectors: function ( a, b ) { - applyMatrix4: function (m) { + this.x = a.x - b.x; + this.y = a.y - b.y; + this.z = a.z - b.z; + this.w = a.w - b.w; - var x = this.x; - var y = this.y; - var z = this.z; - var w = this.w; + return this; - 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; + multiplyScalar: function ( scalar ) { - return this; + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; + this.w *= scalar; - }, + return this; - divideScalar: function (scalar) { + }, - if (scalar !== 0) { + applyMatrix4: function ( m ) { - var invScalar = 1 / scalar; + var x = this.x; + var y = this.y; + var z = this.z; + var w = this.w; - this.x *= invScalar; - this.y *= invScalar; - this.z *= invScalar; - this.w *= invScalar; + var e = m.elements; - } else { + 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; - this.x = 0; - this.y = 0; - this.z = 0; - this.w = 1; + return this; - } + }, - return this; + divideScalar: function ( scalar ) { - }, + if ( scalar !== 0 ) { - setAxisAngleFromQuaternion: function (q) { + var invScalar = 1 / scalar; - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm + this.x *= invScalar; + this.y *= invScalar; + this.z *= invScalar; + this.w *= invScalar; - // q is assumed to be normalized + } else { - this.w = 2 * Math.acos(q.w); + this.x = 0; + this.y = 0; + this.z = 0; + this.w = 1; - var s = Math.sqrt(1 - q.w * q.w); + } - if (s < 0.0001) { + return this; - this.x = 1; - this.y = 0; - this.z = 0; + }, - } else { + setAxisAngleFromQuaternion: function ( q ) { - this.x = q.x / s; - this.y = q.y / s; - this.z = q.z / s; + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm - } + // q is assumed to be normalized - return this; + this.w = 2 * Math.acos( q.w ); - }, + var s = Math.sqrt( 1 - q.w * q.w ); - setAxisAngleFromRotationMatrix: function (m) { + if ( s < 0.0001 ) { - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm + this.x = 1; + this.y = 0; + this.z = 0; - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + } else { - 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 + this.x = q.x / s; + this.y = q.y / s; + this.z = q.z / s; - 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]; + return this; - 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 + setAxisAngleFromRotationMatrix: function ( m ) { - if (( Math.abs(m12 + m21) < epsilon2 ) - && ( Math.abs(m13 + m31) < epsilon2 ) - && ( Math.abs(m23 + m32) < epsilon2 ) - && ( Math.abs(m11 + m22 + m33 - 3) < epsilon2 )) { + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm - // this singularity is identity matrix so angle = 0 + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - this.set(1, 0, 0, 0); + 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 - return this; // zero angle, arbitrary axis + 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 ]; - // otherwise this singularity is angle = 180 + if ( ( Math.abs( m12 - m21 ) < epsilon ) + && ( Math.abs( m13 - m31 ) < epsilon ) + && ( Math.abs( m23 - m32 ) < epsilon ) ) { - angle = Math.PI; + // singularity found + // first check for identity matrix which must have +1 for all terms + // in leading diagonal and zero in other terms - 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 ( ( Math.abs( m12 + m21 ) < epsilon2 ) + && ( Math.abs( m13 + m31 ) < epsilon2 ) + && ( Math.abs( m23 + m32 ) < epsilon2 ) + && ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) { - if (( xx > yy ) && ( xx > zz )) { // m11 is the largest diagonal term + // this singularity is identity matrix so angle = 0 - if (xx < epsilon) { + this.set( 1, 0, 0, 0 ); - x = 0; - y = 0.707106781; - z = 0.707106781; + return this; // zero angle, arbitrary axis - } else { + } - x = Math.sqrt(xx); - y = xy / x; - z = xz / x; + // otherwise this singularity is angle = 180 - } + angle = Math.PI; - } else if (yy > zz) { // m22 is the largest diagonal term + 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 (yy < epsilon) { + if ( ( xx > yy ) && ( xx > zz ) ) { - x = 0.707106781; - y = 0; - z = 0.707106781; + // m11 is the largest diagonal term - } else { + if ( xx < epsilon ) { - y = Math.sqrt(yy); - x = xy / y; - z = yz / y; + x = 0; + y = 0.707106781; + z = 0.707106781; - } + } else { - } else { // m33 is the largest diagonal term so base result on this + x = Math.sqrt( xx ); + y = xy / x; + z = xz / x; - if (zz < epsilon) { + } - x = 0.707106781; - y = 0.707106781; - z = 0; + } else if ( yy > zz ) { - } else { + // m22 is the largest diagonal term - z = Math.sqrt(zz); - x = xz / z; - y = yz / z; + if ( yy < epsilon ) { - } + x = 0.707106781; + y = 0; + z = 0.707106781; - } + } else { - this.set(x, y, z, angle); + y = Math.sqrt( yy ); + x = xy / y; + z = yz / y; - return this; // return 180 deg rotation + } - } + } else { - // as we have reached here there are no singularities so we can handle normally + // m33 is the largest diagonal term so base result on this - var s = Math.sqrt(( m32 - m23 ) * ( m32 - m23 ) - + ( m13 - m31 ) * ( m13 - m31 ) - + ( m21 - m12 ) * ( m21 - m12 )); // used to normalize + if ( zz < epsilon ) { - if (Math.abs(s) < 0.001) s = 1; + x = 0.707106781; + y = 0.707106781; + z = 0; - // 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 + } else { - this.x = ( m32 - m23 ) / s; - this.y = ( m13 - m31 ) / s; - this.z = ( m21 - m12 ) / s; - this.w = Math.acos(( m11 + m22 + m33 - 1 ) / 2); + z = Math.sqrt( zz ); + x = xz / z; + y = yz / z; - return this; + } - }, + } - min: function (v) { + this.set( x, y, z, angle ); - if (this.x > v.x) { + return this; // return 180 deg rotation - this.x = v.x; + } - } + // as we have reached here there are no singularities so we can handle normally - if (this.y > v.y) { + var s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) + + ( m13 - m31 ) * ( m13 - m31 ) + + ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize - this.y = v.y; + 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 - if (this.z > v.z) { + this.x = ( m32 - m23 ) / s; + this.y = ( m13 - m31 ) / s; + this.z = ( m21 - m12 ) / s; + this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 ); - this.z = v.z; + return this; - } + }, - if (this.w > v.w) { + min: function ( v ) { - this.w = v.w; + if ( this.x > v.x ) { - } + this.x = v.x; - return this; + } - }, + if ( this.y > v.y ) { - max: function (v) { + this.y = v.y; - if (this.x < v.x) { + } - this.x = v.x; + if ( this.z > v.z ) { - } + this.z = v.z; - if (this.y < v.y) { + } - this.y = v.y; + if ( this.w > v.w ) { - } + this.w = v.w; - if (this.z < v.z) { + } - this.z = v.z; + return this; - } + }, - if (this.w < v.w) { + max: function ( v ) { - this.w = v.w; + if ( this.x < v.x ) { - } + this.x = v.x; - return this; + } - }, + if ( this.y < v.y ) { - clamp: function (min, max) { + this.y = v.y; - // This function assumes min < max, if this assumption isn't true it will not operate correctly + } - if (this.x < min.x) { + if ( this.z < v.z ) { - this.x = min.x; + this.z = v.z; - } else if (this.x > max.x) { + } - this.x = max.x; + if ( this.w < v.w ) { - } + this.w = v.w; - if (this.y < min.y) { + } - this.y = min.y; + return this; - } else if (this.y > max.y) { + }, - this.y = max.y; + clamp: function ( min, max ) { - } + // This function assumes min < max, if this assumption isn't true it will not operate correctly - if (this.z < min.z) { + if ( this.x < min.x ) { - this.z = min.z; + this.x = min.x; - } else if (this.z > max.z) { + } else if ( this.x > max.x ) { - this.z = max.z; + this.x = max.x; - } + } - if (this.w < min.w) { + if ( this.y < min.y ) { - this.w = min.w; + this.y = min.y; - } else if (this.w > max.w) { + } else if ( this.y > max.y ) { - this.w = max.w; + this.y = max.y; - } + } - return this; + if ( this.z < min.z ) { - }, + this.z = min.z; - clampScalar: (function () { + } else if ( this.z > max.z ) { - var min, max; + this.z = max.z; - return function (minVal, maxVal) { + } - if (min === undefined) { + if ( this.w < min.w ) { - min = new THREE.Vector4(); - max = new THREE.Vector4(); + this.w = min.w; - } + } else if ( this.w > max.w ) { - min.set(minVal, minVal, minVal, minVal); - max.set(maxVal, maxVal, maxVal, maxVal); + this.w = max.w; - return this.clamp(min, max); + } - }; + return this; - })(), + }, - floor: function () { + clampScalar: 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); + var min, max; - return this; + return function clampScalar( minVal, maxVal ) { - }, + if ( min === undefined ) { - ceil: function () { + min = new THREE.Vector4(); + max = new THREE.Vector4(); - 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; + min.set( minVal, minVal, minVal, minVal ); + max.set( maxVal, maxVal, maxVal, maxVal ); - }, + return this.clamp( min, max ); - 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; + 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 ); - roundToZero: function () { + return this; - 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; + 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 ); - negate: function () { + return this; - this.x = -this.x; - this.y = -this.y; - this.z = -this.z; - this.w = -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 ); - dot: function (v) { + return this; - return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; + }, - }, + roundToZero: function () { - lengthSq: 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.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; + return this; - }, + }, - length: function () { + negate: function () { - return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w); + this.x = - this.x; + this.y = - this.y; + this.z = - this.z; + this.w = - this.w; - }, + return this; - lengthManhattan: function () { + }, - return Math.abs(this.x) + Math.abs(this.y) + Math.abs(this.z) + Math.abs(this.w); + dot: function ( v ) { - }, + return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; - normalize: function () { + }, - return this.divideScalar(this.length()); + lengthSq: function () { - }, + return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; - setLength: function (l) { + }, - var oldLength = this.length(); + length: function () { - if (oldLength !== 0 && l !== oldLength) { + return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w ); - this.multiplyScalar(l / oldLength); + }, - } + lengthManhattan: function () { - return this; + return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w ); - }, + }, - lerp: function (v, alpha) { + normalize: function () { - 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.divideScalar( this.length() ); - return this; + }, - }, + setLength: function ( l ) { - lerpVectors: function (v1, v2, alpha) { + var oldLength = this.length(); - this.subVectors(v2, v1).multiplyScalar(alpha).add(v1); + if ( oldLength !== 0 && l !== oldLength ) { - return this; + this.multiplyScalar( l / oldLength ); - }, + } - equals: function (v) { + return this; - return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) ); + }, - }, + lerp: function ( v, alpha ) { - fromArray: function (array, offset) { + 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; - if (offset === undefined) offset = 0; + return this; - this.x = array[offset]; - this.y = array[offset + 1]; - this.z = array[offset + 2]; - this.w = array[offset + 3]; + }, - return this; + lerpVectors: function ( v1, v2, alpha ) { - }, + this.subVectors( v2, v1 ).multiplyScalar( alpha ).add( v1 ); - toArray: function (array, offset) { + return this; - if (array === undefined) array = []; - if (offset === undefined) offset = 0; + }, - array[offset] = this.x; - array[offset + 1] = this.y; - array[offset + 2] = this.z; - array[offset + 3] = this.w; + equals: function ( v ) { - return array; + return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) ); - }, + }, - fromAttribute: function (attribute, index, offset) { + fromArray: function ( array, offset ) { - if (offset === undefined) offset = 0; + if ( offset === undefined ) offset = 0; - index = index * attribute.itemSize + offset; + this.x = array[ offset ]; + this.y = array[ offset + 1 ]; + this.z = array[ offset + 2 ]; + this.w = array[ offset + 3 ]; - this.x = attribute.array[index]; - this.y = attribute.array[index + 1]; - this.z = attribute.array[index + 2]; - this.w = attribute.array[index + 3]; + return this; - return this; + }, - }, + toArray: function ( array, offset ) { - clone: function () { + if ( array === undefined ) array = []; + if ( offset === undefined ) offset = 0; - return new THREE.Vector4(this.x, this.y, this.z, this.w); + array[ offset ] = this.x; + array[ offset + 1 ] = this.y; + array[ offset + 2 ] = this.z; + array[ offset + 3 ] = this.w; - } + return array; + + }, + + fromAttribute: function ( attribute, index, offset ) { + + if ( offset === undefined ) offset = 0; + + index = index * attribute.itemSize + offset; + + this.x = attribute.array[ index ]; + this.y = attribute.array[ index + 1 ]; + this.z = attribute.array[ index + 2 ]; + this.w = attribute.array[ index + 3 ]; + + return this; + + } }; @@ -3361,1050 +3407,1051 @@ THREE.Vector4.prototype = { /** * @author mrdoob / http://mrdoob.com/ * @author WestLangley / http://github.com/WestLangley - * @author bhouston / http://exocortex.com + * @author bhouston / http://clara.io */ -THREE.Euler = function (x, y, z, order) { +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; + 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.RotationOrders = [ 'XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ', 'ZYX' ]; THREE.Euler.DefaultOrder = 'XYZ'; THREE.Euler.prototype = { - constructor: THREE.Euler, + constructor: THREE.Euler, - _x: 0, _y: 0, _z: 0, _order: THREE.Euler.DefaultOrder, + get x () { - get x() { + return this._x; - return this._x; + }, - }, + set x ( value ) { - set x(value) { + this._x = value; + this.onChangeCallback(); - this._x = value; - this.onChangeCallback(); + }, - }, + get y () { - get y() { + return this._y; - return this._y; + }, - }, + set y ( value ) { - set y(value) { + this._y = value; + this.onChangeCallback(); - this._y = value; - this.onChangeCallback(); + }, - }, + get z () { - get z() { + return this._z; - return this._z; + }, - }, + set z ( value ) { - set z(value) { + this._z = value; + this.onChangeCallback(); - this._z = value; - this.onChangeCallback(); + }, - }, + get order () { - get order() { + return this._order; - return this._order; + }, - }, + set order ( value ) { - set order(value) { + this._order = value; + this.onChangeCallback(); - this._order = value; - this.onChangeCallback(); + }, - }, + set: function ( x, y, z, order ) { - set: function (x, y, z, order) { + this._x = x; + this._y = y; + this._z = z; + this._order = order || this._order; - this._x = x; - this._y = y; - this._z = z; - this._order = order || this._order; + this.onChangeCallback(); - this.onChangeCallback(); + return this; - return this; + }, - }, + clone: function () { - copy: function (euler) { + return new this.constructor( this._x, this._y, this._z, this._order); - this._x = euler._x; - this._y = euler._y; - this._z = euler._z; - this._order = euler._order; + }, - this.onChangeCallback(); + copy: function ( euler ) { - return this; + this._x = euler._x; + this._y = euler._y; + this._z = euler._z; + this._order = euler._order; - }, + this.onChangeCallback(); - setFromRotationMatrix: function (m, order, update) { + return this; - var clamp = THREE.Math.clamp; + }, - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + setFromRotationMatrix: function ( m, order, update ) { - 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]; + var clamp = THREE.Math.clamp; - order = order || this._order; + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - if (order === 'XYZ') { + 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 ]; - this._y = Math.asin(clamp(m13, -1, 1)); + order = order || this._order; - if (Math.abs(m13) < 0.99999) { + if ( order === 'XYZ' ) { - this._x = Math.atan2(-m23, m33); - this._z = Math.atan2(-m12, m11); + this._y = Math.asin( clamp( m13, - 1, 1 ) ); - } else { + if ( Math.abs( m13 ) < 0.99999 ) { - this._x = Math.atan2(m32, m22); - this._z = 0; + this._x = Math.atan2( - m23, m33 ); + this._z = Math.atan2( - m12, m11 ); - } + } else { - } else if (order === 'YXZ') { + this._x = Math.atan2( m32, m22 ); + this._z = 0; - this._x = Math.asin(-clamp(m23, -1, 1)); + } - if (Math.abs(m23) < 0.99999) { + } else if ( order === 'YXZ' ) { - this._y = Math.atan2(m13, m33); - this._z = Math.atan2(m21, m22); + this._x = Math.asin( - clamp( m23, - 1, 1 ) ); - } else { + if ( Math.abs( m23 ) < 0.99999 ) { - this._y = Math.atan2(-m31, m11); - this._z = 0; + this._y = Math.atan2( m13, m33 ); + this._z = Math.atan2( m21, m22 ); - } + } else { - } else if (order === 'ZXY') { + this._y = Math.atan2( - m31, m11 ); + this._z = 0; - this._x = Math.asin(clamp(m32, -1, 1)); + } - if (Math.abs(m32) < 0.99999) { + } else if ( order === 'ZXY' ) { - this._y = Math.atan2(-m31, m33); - this._z = Math.atan2(-m12, m22); + this._x = Math.asin( clamp( m32, - 1, 1 ) ); - } else { + if ( Math.abs( m32 ) < 0.99999 ) { - this._y = 0; - this._z = Math.atan2(m21, m11); + this._y = Math.atan2( - m31, m33 ); + this._z = Math.atan2( - m12, m22 ); - } + } else { - } else if (order === 'ZYX') { + this._y = 0; + this._z = Math.atan2( m21, m11 ); - this._y = Math.asin(-clamp(m31, -1, 1)); + } - if (Math.abs(m31) < 0.99999) { + } else if ( order === 'ZYX' ) { - this._x = Math.atan2(m32, m33); - this._z = Math.atan2(m21, m11); + this._y = Math.asin( - clamp( m31, - 1, 1 ) ); - } else { + if ( Math.abs( m31 ) < 0.99999 ) { - this._x = 0; - this._z = Math.atan2(-m12, m22); + this._x = Math.atan2( m32, m33 ); + this._z = Math.atan2( m21, m11 ); - } + } else { - } else if (order === 'YZX') { + this._x = 0; + this._z = Math.atan2( - m12, m22 ); - this._z = Math.asin(clamp(m21, -1, 1)); + } - if (Math.abs(m21) < 0.99999) { + } else if ( order === 'YZX' ) { - this._x = Math.atan2(-m23, m22); - this._y = Math.atan2(-m31, m11); + this._z = Math.asin( clamp( m21, - 1, 1 ) ); - } else { + if ( Math.abs( m21 ) < 0.99999 ) { - this._x = 0; - this._y = Math.atan2(m13, m33); + this._x = Math.atan2( - m23, m22 ); + this._y = Math.atan2( - m31, m11 ); - } + } else { - } else if (order === 'XZY') { + this._x = 0; + this._y = Math.atan2( m13, m33 ); - this._z = Math.asin(-clamp(m12, -1, 1)); + } - if (Math.abs(m12) < 0.99999) { + } else if ( order === 'XZY' ) { - this._x = Math.atan2(m32, m22); - this._y = Math.atan2(m13, m11); + this._z = Math.asin( - clamp( m12, - 1, 1 ) ); - } else { + if ( Math.abs( m12 ) < 0.99999 ) { - this._x = Math.atan2(-m23, m33); - this._y = 0; + this._x = Math.atan2( m32, m22 ); + this._y = Math.atan2( m13, m11 ); - } + } else { - } else { + this._x = Math.atan2( - m23, m33 ); + this._y = 0; - THREE.warn('THREE.Euler: .setFromRotationMatrix() given unsupported order: ' + order) + } - } + } else { - this._order = order; + console.warn( 'THREE.Euler: .setFromRotationMatrix() given unsupported order: ' + order ) - if (update !== false) this.onChangeCallback(); + } - return this; + this._order = order; - }, + if ( update !== false ) this.onChangeCallback(); - setFromQuaternion: function () { + return this; - var matrix; + }, - return function (q, order, update) { + setFromQuaternion: function () { - if (matrix === undefined) matrix = new THREE.Matrix4(); - matrix.makeRotationFromQuaternion(q); - this.setFromRotationMatrix(matrix, order, update); + var matrix; - return this; + return function ( q, order, update ) { - }; + if ( matrix === undefined ) matrix = new THREE.Matrix4(); + matrix.makeRotationFromQuaternion( q ); + this.setFromRotationMatrix( matrix, order, update ); - }(), + return this; - setFromVector3: function (v, order) { + }; - return this.set(v.x, v.y, v.z, order || this._order); + }(), - }, + setFromVector3: function ( v, order ) { - reorder: function () { + return this.set( v.x, v.y, v.z, order || this._order ); - // WARNING: this discards revolution information -bhouston + }, - var q = new THREE.Quaternion(); + reorder: function () { - return function (newOrder) { + // WARNING: this discards revolution information -bhouston - q.setFromEuler(this); - this.setFromQuaternion(q, newOrder); + var q = new THREE.Quaternion(); - }; + return function ( newOrder ) { - }(), + q.setFromEuler( this ); + this.setFromQuaternion( q, newOrder ); - equals: function (euler) { + }; - return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order ); + }(), - }, + equals: function ( euler ) { - fromArray: function (array) { + return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order ); - this._x = array[0]; - this._y = array[1]; - this._z = array[2]; - if (array[3] !== undefined) this._order = array[3]; + }, - this.onChangeCallback(); + fromArray: function ( array ) { - return this; + this._x = array[ 0 ]; + this._y = array[ 1 ]; + this._z = array[ 2 ]; + if ( array[ 3 ] !== undefined ) this._order = array[ 3 ]; - }, + this.onChangeCallback(); - toArray: function (array, offset) { + return this; - if (array === undefined) array = []; - if (offset === undefined) offset = 0; + }, - array[offset] = this._x; - array[offset + 1] = this._y; - array[offset + 2] = this._z; - array[offset + 3] = this._order; + toArray: function ( array, offset ) { - return array; - }, + if ( array === undefined ) array = []; + if ( offset === undefined ) offset = 0; - toVector3: function (optionalResult) { + array[ offset ] = this._x; + array[ offset + 1 ] = this._y; + array[ offset + 2 ] = this._z; + array[ offset + 3 ] = this._order; - if (optionalResult) { + return array; - return optionalResult.set(this._x, this._y, this._z); + }, - } else { + toVector3: function ( optionalResult ) { - return new THREE.Vector3(this._x, this._y, this._z); + if ( optionalResult ) { - } + return optionalResult.set( this._x, this._y, this._z ); - }, + } else { - onChange: function (callback) { + return new THREE.Vector3( this._x, this._y, this._z ); - this.onChangeCallback = callback; + } - return this; + }, - }, + onChange: function ( callback ) { - onChangeCallback: function () { - }, + this.onChangeCallback = callback; - clone: function () { + return this; - return new THREE.Euler(this._x, this._y, this._z, this._order); + }, - } + onChangeCallback: function () {} }; // File:src/math/Line3.js /** - * @author bhouston / http://exocortex.com + * @author bhouston / http://clara.io */ -THREE.Line3 = function (start, end) { +THREE.Line3 = function ( start, end ) { - this.start = ( start !== undefined ) ? start : new THREE.Vector3(); - this.end = ( end !== undefined ) ? end : new THREE.Vector3(); + this.start = ( start !== undefined ) ? start : new THREE.Vector3(); + this.end = ( end !== undefined ) ? end : new THREE.Vector3(); }; THREE.Line3.prototype = { - constructor: THREE.Line3, + constructor: THREE.Line3, - set: function (start, end) { + set: function ( start, end ) { - this.start.copy(start); - this.end.copy(end); + this.start.copy( start ); + this.end.copy( end ); - return this; + return this; - }, + }, - copy: function (line) { + clone: function () { - this.start.copy(line.start); - this.end.copy(line.end); + return new this.constructor().copy( this ); - return this; + }, - }, + copy: function ( line ) { - center: function (optionalTarget) { + this.start.copy( line.start ); + this.end.copy( line.end ); - var result = optionalTarget || new THREE.Vector3(); - return result.addVectors(this.start, this.end).multiplyScalar(0.5); + return this; - }, + }, - delta: function (optionalTarget) { + center: function ( optionalTarget ) { - var result = optionalTarget || new THREE.Vector3(); - return result.subVectors(this.end, this.start); + var result = optionalTarget || new THREE.Vector3(); + return result.addVectors( this.start, this.end ).multiplyScalar( 0.5 ); - }, + }, - distanceSq: function () { + delta: function ( optionalTarget ) { - return this.start.distanceToSquared(this.end); + var result = optionalTarget || new THREE.Vector3(); + return result.subVectors( this.end, this.start ); - }, + }, - distance: function () { + distanceSq: function () { - return this.start.distanceTo(this.end); + return this.start.distanceToSquared( this.end ); - }, + }, - at: function (t, optionalTarget) { + distance: function () { - var result = optionalTarget || new THREE.Vector3(); + return this.start.distanceTo( this.end ); - return this.delta(result).multiplyScalar(t).add(this.start); + }, - }, + at: function ( t, optionalTarget ) { - closestPointToPointParameter: function () { + var result = optionalTarget || new THREE.Vector3(); - var startP = new THREE.Vector3(); - var startEnd = new THREE.Vector3(); + return this.delta( result ).multiplyScalar( t ).add( this.start ); - return function (point, clampToLine) { + }, - startP.subVectors(point, this.start); - startEnd.subVectors(this.end, this.start); + closestPointToPointParameter: function () { - var startEnd2 = startEnd.dot(startEnd); - var startEnd_startP = startEnd.dot(startP); + var startP = new THREE.Vector3(); + var startEnd = new THREE.Vector3(); - var t = startEnd_startP / startEnd2; + return function ( point, clampToLine ) { - if (clampToLine) { + startP.subVectors( point, this.start ); + startEnd.subVectors( this.end, this.start ); - t = THREE.Math.clamp(t, 0, 1); + var startEnd2 = startEnd.dot( startEnd ); + var startEnd_startP = startEnd.dot( startP ); - } + var t = startEnd_startP / startEnd2; - return t; + if ( clampToLine ) { - }; + t = THREE.Math.clamp( t, 0, 1 ); - }(), + } - closestPointToPoint: function (point, clampToLine, optionalTarget) { + return t; - var t = this.closestPointToPointParameter(point, clampToLine); + }; - var result = optionalTarget || new THREE.Vector3(); + }(), - return this.delta(result).multiplyScalar(t).add(this.start); + closestPointToPoint: function ( point, clampToLine, optionalTarget ) { - }, + var t = this.closestPointToPointParameter( point, clampToLine ); - applyMatrix4: function (matrix) { + var result = optionalTarget || new THREE.Vector3(); - this.start.applyMatrix4(matrix); - this.end.applyMatrix4(matrix); + return this.delta( result ).multiplyScalar( t ).add( this.start ); - return this; + }, - }, + applyMatrix4: function ( matrix ) { - equals: function (line) { + this.start.applyMatrix4( matrix ); + this.end.applyMatrix4( matrix ); - return line.start.equals(this.start) && line.end.equals(this.end); + return this; - }, + }, - clone: function () { + equals: function ( line ) { - return new THREE.Line3().copy(this); + return line.start.equals( this.start ) && line.end.equals( this.end ); - } + } }; // File:src/math/Box2.js /** - * @author bhouston / http://exocortex.com + * @author bhouston / http://clara.io */ -THREE.Box2 = function (min, max) { +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); + 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, + constructor: THREE.Box2, - set: function (min, max) { + set: function ( min, max ) { - this.min.copy(min); - this.max.copy(max); + this.min.copy( min ); + this.max.copy( max ); - return this; + return this; - }, + }, - setFromPoints: function (points) { + setFromPoints: function ( points ) { - this.makeEmpty(); + this.makeEmpty(); - for (var i = 0, il = points.length; i < il; i++) { + for ( var i = 0, il = points.length; i < il; i ++ ) { - this.expandByPoint(points[i]) + this.expandByPoint( points[ i ] ) - } + } - return this; + return this; - }, + }, - setFromCenterAndSize: function () { + setFromCenterAndSize: function () { - var v1 = new THREE.Vector2(); + var v1 = new THREE.Vector2(); - return function (center, size) { + return function ( center, size ) { - var halfSize = v1.copy(size).multiplyScalar(0.5); - this.min.copy(center).sub(halfSize); - this.max.copy(center).add(halfSize); + var halfSize = v1.copy( size ).multiplyScalar( 0.5 ); + this.min.copy( center ).sub( halfSize ); + this.max.copy( center ).add( halfSize ); - return this; + return this; - }; + }; - }(), + }(), + + clone: function () { - copy: function (box) { + return new this.constructor().copy( this ); - this.min.copy(box.min); - this.max.copy(box.max); + }, - return this; + copy: function ( box ) { - }, + this.min.copy( box.min ); + this.max.copy( box.max ); - makeEmpty: function () { + return this; - this.min.x = this.min.y = Infinity; - this.max.x = this.max.y = -Infinity; + }, - return this; + makeEmpty: function () { - }, + this.min.x = this.min.y = Infinity; + this.max.x = this.max.y = - Infinity; - empty: function () { + return this; - // 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 ); + empty: function () { - }, + // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes - center: function (optionalTarget) { + return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ); - var result = optionalTarget || new THREE.Vector2(); - return result.addVectors(this.min, this.max).multiplyScalar(0.5); + }, - }, + center: function ( optionalTarget ) { - size: function (optionalTarget) { + var result = optionalTarget || new THREE.Vector2(); + return result.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); - var result = optionalTarget || new THREE.Vector2(); - return result.subVectors(this.max, this.min); + }, - }, + size: function ( optionalTarget ) { - expandByPoint: function (point) { + var result = optionalTarget || new THREE.Vector2(); + return result.subVectors( this.max, this.min ); - this.min.min(point); - this.max.max(point); + }, - return this; - }, + expandByPoint: function ( point ) { - expandByVector: function (vector) { + this.min.min( point ); + this.max.max( point ); - this.min.sub(vector); - this.max.add(vector); + return this; - return this; - }, + }, - expandByScalar: function (scalar) { + expandByVector: function ( vector ) { - this.min.addScalar(-scalar); - this.max.addScalar(scalar); + this.min.sub( vector ); + this.max.add( vector ); - return this; - }, + 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) { + expandByScalar: function ( scalar ) { - return false; + this.min.addScalar( - scalar ); + this.max.addScalar( scalar ); - } + return this; - return true; + }, - }, + containsPoint: function ( point ) { - containsBox: function (box) { + if ( point.x < this.min.x || point.x > this.max.x || + point.y < this.min.y || point.y > this.max.y ) { - 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 false; - return true; + } - } + return true; - return false; + }, - }, + containsBox: function ( box ) { - getParameter: function (point, optionalTarget) { + 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 can potentially have a divide by zero if the box - // has a size dimension of 0. + return true; - 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 ) - ); + return false; - }, + }, - isIntersectionBox: function (box) { + getParameter: function ( point, optionalTarget ) { - // using 6 splitting planes to rule out intersections. + // This can potentially have a divide by zero if the box + // has a size dimension of 0. - 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) { + var result = optionalTarget || new THREE.Vector2(); - return false; + 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 ) + ); - } + }, - return true; + isIntersectionBox: function ( box ) { - }, + // using 6 splitting planes to rule out intersections. - clampPoint: function (point, optionalTarget) { + 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 ) { - var result = optionalTarget || new THREE.Vector2(); - return result.copy(point).clamp(this.min, this.max); + return false; - }, + } - distanceToPoint: function () { + return true; - var v1 = new THREE.Vector2(); + }, - return function (point) { + clampPoint: function ( point, optionalTarget ) { - var clampedPoint = v1.copy(point).clamp(this.min, this.max); - return clampedPoint.sub(point).length(); + var result = optionalTarget || new THREE.Vector2(); + return result.copy( point ).clamp( this.min, this.max ); - }; + }, - }(), + distanceToPoint: function () { - intersect: function (box) { + var v1 = new THREE.Vector2(); - this.min.max(box.min); - this.max.min(box.max); + return function ( point ) { - return this; + var clampedPoint = v1.copy( point ).clamp( this.min, this.max ); + return clampedPoint.sub( point ).length(); - }, + }; - union: function (box) { + }(), - this.min.min(box.min); - this.max.max(box.max); + intersect: function ( box ) { - return this; + this.min.max( box.min ); + this.max.min( box.max ); - }, + return this; - translate: function (offset) { + }, - this.min.add(offset); - this.max.add(offset); + union: function ( box ) { - return this; + this.min.min( box.min ); + this.max.max( box.max ); - }, + return this; - equals: function (box) { + }, - return box.min.equals(this.min) && box.max.equals(this.max); + translate: function ( offset ) { - }, + this.min.add( offset ); + this.max.add( offset ); - clone: function () { + return this; - return new THREE.Box2().copy(this); + }, - } + equals: function ( box ) { + + return box.min.equals( this.min ) && box.max.equals( this.max ); + + } }; // File:src/math/Box3.js /** - * @author bhouston / http://exocortex.com + * @author bhouston / http://clara.io * @author WestLangley / http://github.com/WestLangley */ -THREE.Box3 = function (min, max) { +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); + 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, + constructor: THREE.Box3, - set: function (min, max) { + set: function ( min, max ) { - this.min.copy(min); - this.max.copy(max); + this.min.copy( min ); + this.max.copy( max ); - return this; + return this; - }, + }, - setFromPoints: function (points) { + setFromPoints: function ( points ) { - this.makeEmpty(); + this.makeEmpty(); - for (var i = 0, il = points.length; i < il; i++) { + for ( var i = 0, il = points.length; i < il; i ++ ) { - this.expandByPoint(points[i]) + this.expandByPoint( points[ i ] ); - } + } - return this; + return this; - }, + }, - setFromCenterAndSize: function () { + setFromCenterAndSize: function () { - var v1 = new THREE.Vector3(); + var v1 = new THREE.Vector3(); - return function (center, size) { + return function ( center, size ) { - var halfSize = v1.copy(size).multiplyScalar(0.5); + var halfSize = v1.copy( size ).multiplyScalar( 0.5 ); - this.min.copy(center).sub(halfSize); - this.max.copy(center).add(halfSize); + this.min.copy( center ).sub( halfSize ); + this.max.copy( center ).add( halfSize ); - return this; + return this; - }; + }; - }(), + }(), - setFromObject: function () { + 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 + // Computes the world-axis-aligned bounding box of an object (including its children), + // accounting for both the object's, and children's, world transforms - var v1 = new THREE.Vector3(); + var v1 = new THREE.Vector3(); - return function (object) { + return function ( object ) { - var scope = this; + var scope = this; - object.updateMatrixWorld(true); + object.updateMatrixWorld( true ); - this.makeEmpty(); + this.makeEmpty(); - object.traverse(function (node) { + object.traverse( function ( node ) { - var geometry = node.geometry; + var geometry = node.geometry; - if (geometry !== undefined) { + if ( geometry !== undefined ) { - if (geometry instanceof THREE.Geometry) { + if ( geometry instanceof THREE.Geometry ) { - var vertices = geometry.vertices; + var vertices = geometry.vertices; - for (var i = 0, il = vertices.length; i < il; i++) { + for ( var i = 0, il = vertices.length; i < il; i ++ ) { - v1.copy(vertices[i]); + v1.copy( vertices[ i ] ); - v1.applyMatrix4(node.matrixWorld); + v1.applyMatrix4( node.matrixWorld ); - scope.expandByPoint(v1); + scope.expandByPoint( v1 ); - } + } - } else if (geometry instanceof THREE.BufferGeometry && geometry.attributes['position'] !== undefined) { + } else if ( geometry instanceof THREE.BufferGeometry && geometry.attributes[ 'position' ] !== undefined ) { - var positions = geometry.attributes['position'].array; + var positions = geometry.attributes[ 'position' ].array; - for (var i = 0, il = positions.length; i < il; i += 3) { + for ( var i = 0, il = positions.length; i < il; i += 3 ) { - v1.set(positions[i], positions[i + 1], positions[i + 2]); + v1.set( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] ); - v1.applyMatrix4(node.matrixWorld); + v1.applyMatrix4( node.matrixWorld ); - scope.expandByPoint(v1); + scope.expandByPoint( v1 ); - } + } - } + } - } + } - }); + } ); - return this; + return this; - }; + }; - }(), + }(), - copy: function (box) { + clone: function () { - this.min.copy(box.min); - this.max.copy(box.max); + return new this.constructor().copy( this ); - return this; + }, - }, + copy: function ( box ) { - makeEmpty: function () { + this.min.copy( box.min ); + this.max.copy( box.max ); - this.min.x = this.min.y = this.min.z = Infinity; - this.max.x = this.max.y = this.max.z = -Infinity; + return this; - return this; + }, - }, + makeEmpty: function () { - empty: function () { + this.min.x = this.min.y = this.min.z = Infinity; + this.max.x = this.max.y = this.max.z = - Infinity; - // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes + return this; - return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z ); + }, - }, + empty: function () { - center: function (optionalTarget) { + // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes - var result = optionalTarget || new THREE.Vector3(); - return result.addVectors(this.min, this.max).multiplyScalar(0.5); + return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z ); - }, + }, - size: function (optionalTarget) { + center: function ( optionalTarget ) { - var result = optionalTarget || new THREE.Vector3(); - return result.subVectors(this.max, this.min); + var result = optionalTarget || new THREE.Vector3(); + return result.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); - }, + }, - expandByPoint: function (point) { + size: function ( optionalTarget ) { - this.min.min(point); - this.max.max(point); + var result = optionalTarget || new THREE.Vector3(); + return result.subVectors( this.max, this.min ); - return this; + }, - }, + expandByPoint: function ( point ) { - expandByVector: function (vector) { + this.min.min( point ); + this.max.max( point ); - this.min.sub(vector); - this.max.add(vector); + return this; - return this; + }, - }, + expandByVector: function ( vector ) { - expandByScalar: function (scalar) { + this.min.sub( vector ); + this.max.add( vector ); - this.min.addScalar(-scalar); - this.max.addScalar(scalar); + return this; - return this; + }, - }, + expandByScalar: function ( scalar ) { - containsPoint: function (point) { + this.min.addScalar( - scalar ); + this.max.addScalar( scalar ); - 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 this; - return false; + }, - } + containsPoint: function ( point ) { - return true; + 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; - 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 true; + }, - } + containsBox: function ( box ) { - return false; + 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; - getParameter: function (point, optionalTarget) { + } - // This can potentially have a divide by zero if the box - // has a size dimension of 0. + return false; - 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 ) - ); + getParameter: function ( point, optionalTarget ) { - }, + // This can potentially have a divide by zero if the box + // has a size dimension of 0. - isIntersectionBox: function (box) { + var result = optionalTarget || new THREE.Vector3(); - // using 6 splitting planes to rule out intersections. + 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 ) + ); - 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; + isIntersectionBox: function ( box ) { - } + // using 6 splitting planes to rule out intersections. - return true; + 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; - clampPoint: function (point, optionalTarget) { + } - var result = optionalTarget || new THREE.Vector3(); - return result.copy(point).clamp(this.min, this.max); + return true; - }, + }, - distanceToPoint: function () { + clampPoint: function ( point, optionalTarget ) { - var v1 = new THREE.Vector3(); + var result = optionalTarget || new THREE.Vector3(); + return result.copy( point ).clamp( this.min, this.max ); - return function (point) { + }, - var clampedPoint = v1.copy(point).clamp(this.min, this.max); - return clampedPoint.sub(point).length(); + distanceToPoint: function () { - }; + var v1 = new THREE.Vector3(); - }(), + return function ( point ) { - getBoundingSphere: function () { + var clampedPoint = v1.copy( point ).clamp( this.min, this.max ); + return clampedPoint.sub( point ).length(); - var v1 = new THREE.Vector3(); + }; - return function (optionalTarget) { + }(), - var result = optionalTarget || new THREE.Sphere(); + getBoundingSphere: function () { - result.center = this.center(); - result.radius = this.size(v1).length() * 0.5; + var v1 = new THREE.Vector3(); - return result; + return function ( optionalTarget ) { - }; + var result = optionalTarget || new THREE.Sphere(); - }(), + result.center = this.center(); + result.radius = this.size( v1 ).length() * 0.5; - intersect: function (box) { + return result; - this.min.max(box.min); - this.max.min(box.max); + }; - return this; + }(), - }, + intersect: function ( box ) { - union: function (box) { + this.min.max( box.min ); + this.max.min( box.max ); - this.min.min(box.min); - this.max.max(box.max); + return this; - return this; + }, - }, + union: function ( box ) { - applyMatrix4: function () { + this.min.min( box.min ); + this.max.max( box.max ); - 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 this; - 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 + applyMatrix4: function () { - this.makeEmpty(); - this.setFromPoints(points); + 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 this; + 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 ); - translate: function (offset) { + return this; - this.min.add(offset); - this.max.add(offset); + }; - return this; + }(), - }, + translate: function ( offset ) { - equals: function (box) { + this.min.add( offset ); + this.max.add( offset ); - return box.min.equals(this.min) && box.max.equals(this.max); + return this; - }, + }, - clone: function () { + equals: function ( box ) { - return new THREE.Box3().copy(this); + return box.min.equals( this.min ) && box.max.equals( this.max ); - } + } }; @@ -4413,316 +4460,296 @@ THREE.Box3.prototype = { /** * @author alteredq / http://alteredqualia.com/ * @author WestLangley / http://github.com/WestLangley - * @author bhouston / http://exocortex.com + * @author bhouston / http://clara.io */ THREE.Matrix3 = function () { - this.elements = new Float32Array([ + this.elements = new Float32Array( [ - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 - ]); + ] ); - if (arguments.length > 0) { + if ( arguments.length > 0 ) { - THREE.error('THREE.Matrix3: the constructor no longer reads arguments. use .set() instead.'); + console.error( 'THREE.Matrix3: the constructor no longer reads arguments. use .set() instead.' ); - } + } }; THREE.Matrix3.prototype = { - constructor: THREE.Matrix3, + constructor: THREE.Matrix3, - set: function (n11, n12, n13, n21, n22, n23, n31, n32, n33) { + set: function ( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { - var te = this.elements; + 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; + 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; + return this; - }, + }, - identity: function () { + identity: function () { - this.set( - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 - ); + this.set( - return this; + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 - }, + ); - copy: function (m) { + return this; - var me = m.elements; + }, - this.set( - me[0], me[3], me[6], - me[1], me[4], me[7], - me[2], me[5], me[8] - ); + clone: function () { - return this; + return new this.constructor().fromArray( this.elements ); - }, + }, - multiplyVector3: function (vector) { + copy: function ( m ) { - THREE.warn('THREE.Matrix3: .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.'); - return vector.applyMatrix3(this); + var me = m.elements; - }, + this.set( - multiplyVector3Array: function (a) { + me[ 0 ], me[ 3 ], me[ 6 ], + me[ 1 ], me[ 4 ], me[ 7 ], + me[ 2 ], me[ 5 ], me[ 8 ] - THREE.warn('THREE.Matrix3: .multiplyVector3Array() has been renamed. Use matrix.applyToVector3Array( array ) instead.'); - return this.applyToVector3Array(a); + ); - }, + return this; - applyToVector3Array: function () { + }, - var v1; + multiplyVector3: function ( vector ) { - return function applyToVector3Array(array, offset, length) { + console.warn( 'THREE.Matrix3: .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.' ); + return vector.applyMatrix3( this ); - if (v1 === undefined) v1 = new THREE.Vector3(); - if (offset === undefined) offset = 0; - if (length === undefined) length = array.length; + }, - for (var i = 0, j = offset; i < length; i += 3, j += 3) { + multiplyVector3Array: function ( a ) { - v1.x = array[j]; - v1.y = array[j + 1]; - v1.z = array[j + 2]; + console.warn( 'THREE.Matrix3: .multiplyVector3Array() has been renamed. Use matrix.applyToVector3Array( array ) instead.' ); + return this.applyToVector3Array( a ); - v1.applyMatrix3(this); + }, - array[j] = v1.x; - array[j + 1] = v1.y; - array[j + 2] = v1.z; + applyToVector3Array: function () { - } + var v1; - return array; + return function ( array, offset, length ) { - }; + if ( v1 === undefined ) v1 = new THREE.Vector3(); + if ( offset === undefined ) offset = 0; + if ( length === undefined ) length = array.length; - }(), + for ( var i = 0, j = offset; i < length; i += 3, j += 3 ) { - applyToBuffer: function () { + v1.fromArray( array, j ); + v1.applyMatrix3( this ); + v1.toArray( array, j ); - var v1; + } - return function applyToBuffer(buffer, offset, length) { + return array; - if (v1 === undefined) v1 = new THREE.Vector3(); - if (offset === undefined) offset = 0; - if (length === undefined) length = buffer.length / buffer.itemSize; + }; - for (var i = 0, j = offset; i < length; i++, j++) { + }(), - v1.x = buffer.getX(j); - v1.y = buffer.getY(j); - v1.z = buffer.getZ(j); + applyToBuffer: function () { - v1.applyMatrix3(this); + var v1; - buffer.setXYZ(v1.x, v1.y, v1.z); + return function applyToBuffer( buffer, offset, length ) { - } + if ( v1 === undefined ) v1 = new THREE.Vector3(); + if ( offset === undefined ) offset = 0; + if ( length === undefined ) length = buffer.length / buffer.itemSize; - return buffer; + for ( var i = 0, j = offset; i < length; i ++, j ++ ) { - }; + v1.x = buffer.getX( j ); + v1.y = buffer.getY( j ); + v1.z = buffer.getZ( j ); - }(), + v1.applyMatrix3( this ); - multiplyScalar: function (s) { + buffer.setXYZ( v1.x, v1.y, v1.z ); - 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 buffer; - return this; + }; - }, + }(), - determinant: function () { + multiplyScalar: function ( s ) { - var te = this.elements; + 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]; + 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 a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g; + return this; - }, + }, - getInverse: function (matrix, throwOnInvertible) { + determinant: function () { - // input: THREE.Matrix4 - // ( based on http://code.google.com/p/webgl-mjs/ ) + var te = this.elements; - var me = matrix.elements; - 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 ]; - 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]; + return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g; - var det = me[0] * te[0] + me[1] * te[3] + me[2] * te[6]; + }, - // no inverse + getInverse: function ( matrix, throwOnInvertible ) { - if (det === 0) { + // input: THREE.Matrix4 + // ( based on http://code.google.com/p/webgl-mjs/ ) - var msg = "Matrix3.getInverse(): can't invert matrix, determinant is 0"; + var me = matrix.elements; + var te = this.elements; - if (throwOnInvertible || false) { + 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 ]; - throw new Error(msg); + var det = me[ 0 ] * te[ 0 ] + me[ 1 ] * te[ 3 ] + me[ 2 ] * te[ 6 ]; - } else { + // no inverse - THREE.warn(msg); + if ( det === 0 ) { - } + var msg = "Matrix3.getInverse(): can't invert matrix, determinant is 0"; - this.identity(); + if ( throwOnInvertible || false ) { - return this; + throw new Error( msg ); - } + } else { - this.multiplyScalar(1.0 / det); + console.warn( msg ); - return this; + } - }, + this.identity(); - transpose: function () { + return this; - 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; + this.multiplyScalar( 1.0 / det ); - return this; + return this; - }, + }, - flattenToArrayOffset: function (array, offset) { + transpose: function () { - var te = this.elements; + var tmp, m = this.elements; - array[offset] = te[0]; - array[offset + 1] = te[1]; - array[offset + 2] = te[2]; + 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; - array[offset + 3] = te[3]; - array[offset + 4] = te[4]; - array[offset + 5] = te[5]; + return this; - array[offset + 6] = te[6]; - array[offset + 7] = te[7]; - array[offset + 8] = te[8]; + }, - return array; + flattenToArrayOffset: function ( array, offset ) { - }, + var te = this.elements; - getNormalMatrix: function (m) { + array[ offset ] = te[ 0 ]; + array[ offset + 1 ] = te[ 1 ]; + array[ offset + 2 ] = te[ 2 ]; - // input: THREE.Matrix4 + array[ offset + 3 ] = te[ 3 ]; + array[ offset + 4 ] = te[ 4 ]; + array[ offset + 5 ] = te[ 5 ]; - this.getInverse(m).transpose(); + array[ offset + 6 ] = te[ 6 ]; + array[ offset + 7 ] = te[ 7 ]; + array[ offset + 8 ] = te[ 8 ]; - return this; + return array; - }, + }, - transposeIntoArray: function (r) { + getNormalMatrix: function ( m ) { - var m = this.elements; + // input: THREE.Matrix4 - 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]; + this.getInverse( m ).transpose(); - return this; + return this; - }, + }, - fromArray: function (array) { + transposeIntoArray: function ( r ) { - this.elements.set(array); + var m = this.elements; - return this; + 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; - toArray: function () { + }, - var te = this.elements; + fromArray: function ( array ) { - return [ - te[0], te[1], te[2], - te[3], te[4], te[5], - te[6], te[7], te[8] - ]; + this.elements.set( array ); - }, + return this; - clone: function () { + }, - return new THREE.Matrix3().fromArray(this.elements); + 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 ] + ]; + + } }; @@ -4737,1773 +4764,1722 @@ THREE.Matrix3.prototype = { * @author alteredq / http://alteredqualia.com/ * @author mikael emtinger / http://gomo.se/ * @author timknip / http://www.floorplanner.com/ - * @author bhouston / http://exocortex.com + * @author bhouston / http://clara.io * @author WestLangley / http://github.com/WestLangley */ THREE.Matrix4 = function () { - this.elements = new Float32Array([ + this.elements = new Float32Array( [ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 - ]); + ] ); - if (arguments.length > 0) { + if ( arguments.length > 0 ) { - THREE.error('THREE.Matrix4: the constructor no longer reads arguments. use .set() instead.'); + console.error( 'THREE.Matrix4: the constructor no longer reads arguments. use .set() instead.' ); - } + } }; THREE.Matrix4.prototype = { - constructor: THREE.Matrix4, + constructor: THREE.Matrix4, - set: function (n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44) { + set: function ( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { - var te = this.elements; + 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; + 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; + return this; - }, + }, - identity: function () { + identity: function () { - this.set( - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - ); + this.set( - return this; + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 - }, + ); - copy: function (m) { + return this; - this.elements.set(m.elements); + }, - return this; + clone: function () { - }, + return new THREE.Matrix4().fromArray( this.elements ); - extractPosition: function (m) { + }, - THREE.warn('THREE.Matrix4: .extractPosition() has been renamed to .copyPosition().'); - return this.copyPosition(m); + copy: function ( m ) { - }, + this.elements.set( m.elements ); - copyPosition: function (m) { + return this; - var te = this.elements; - var me = m.elements; + }, - te[12] = me[12]; - te[13] = me[13]; - te[14] = me[14]; + extractPosition: function ( m ) { - return this; + console.warn( 'THREE.Matrix4: .extractPosition() has been renamed to .copyPosition().' ); + return this.copyPosition( m ); - }, + }, - extractBasis: function (xAxis, yAxis, zAxis) { + copyPosition: function ( m ) { - var te = this.elements; + var te = this.elements; + var me = m.elements; - xAxis.set(te[0], te[1], te[2]); - yAxis.set(te[4], te[5], te[6]); - zAxis.set(te[8], te[9], te[10]); + te[ 12 ] = me[ 12 ]; + te[ 13 ] = me[ 13 ]; + te[ 14 ] = me[ 14 ]; - return this; + return this; - }, + }, - makeBasis: function (xAxis, yAxis, zAxis) { + extractBasis: function ( xAxis, yAxis, zAxis ) { - this.set( - xAxis.x, yAxis.x, zAxis.x, 0, - xAxis.y, yAxis.y, zAxis.y, 0, - xAxis.z, yAxis.z, zAxis.z, 0, - 0, 0, 0, 1 - ); + var te = this.elements; - return this; + xAxis.set( te[ 0 ], te[ 1 ], te[ 2 ] ); + yAxis.set( te[ 4 ], te[ 5 ], te[ 6 ] ); + zAxis.set( te[ 8 ], te[ 9 ], te[ 10 ] ); - }, + return this; - extractRotation: function () { + }, - var v1; + makeBasis: function ( xAxis, yAxis, zAxis ) { - return function (m) { + this.set( + xAxis.x, yAxis.x, zAxis.x, 0, + xAxis.y, yAxis.y, zAxis.y, 0, + xAxis.z, yAxis.z, zAxis.z, 0, + 0, 0, 0, 1 + ); - if (v1 === undefined) v1 = new THREE.Vector3(); + return this; - 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(); + extractRotation: function () { - te[0] = me[0] * scaleX; - te[1] = me[1] * scaleX; - te[2] = me[2] * scaleX; + var v1; - te[4] = me[4] * scaleY; - te[5] = me[5] * scaleY; - te[6] = me[6] * scaleY; + return function ( m ) { - te[8] = me[8] * scaleZ; - te[9] = me[9] * scaleZ; - te[10] = me[10] * scaleZ; + if ( v1 === undefined ) v1 = new THREE.Vector3(); - return this; + 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; - makeRotationFromEuler: function (euler) { + te[ 4 ] = me[ 4 ] * scaleY; + te[ 5 ] = me[ 5 ] * scaleY; + te[ 6 ] = me[ 6 ] * scaleY; - if (euler instanceof THREE.Euler === false) { + te[ 8 ] = me[ 8 ] * scaleZ; + te[ 9 ] = me[ 9 ] * scaleZ; + te[ 10 ] = me[ 10 ] * scaleZ; - THREE.error('THREE.Matrix: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.'); + return this; - } + }; - 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); + makeRotationFromEuler: function ( euler ) { - if (euler.order === 'XYZ') { + if ( euler instanceof THREE.Euler === false ) { - var ae = a * e, af = a * f, be = b * e, bf = b * f; + console.error( 'THREE.Matrix: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.' ); - 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; + var te = this.elements; - te[2] = bf - ae * d; - te[6] = be + af * d; - te[10] = a * c; + 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 ); - } else if (euler.order === 'YXZ') { + if ( euler.order === 'XYZ' ) { - var ce = c * e, cf = c * f, de = d * e, df = d * f; + var ae = a * e, af = a * f, be = b * e, bf = b * f; - te[0] = ce + df * b; - te[4] = de * b - cf; - te[8] = a * d; + te[ 0 ] = c * e; + te[ 4 ] = - c * f; + te[ 8 ] = d; - te[1] = a * f; - te[5] = a * e; - te[9] = -b; + te[ 1 ] = af + be * d; + te[ 5 ] = ae - bf * d; + te[ 9 ] = - b * c; - te[2] = cf * b - de; - te[6] = df + ce * b; - te[10] = a * c; + te[ 2 ] = bf - ae * d; + te[ 6 ] = be + af * d; + te[ 10 ] = a * c; - } else if (euler.order === 'ZXY') { + } else if ( euler.order === 'YXZ' ) { - var ce = c * e, cf = c * f, de = d * e, df = d * f; + 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[ 0 ] = ce + df * b; + te[ 4 ] = de * b - cf; + te[ 8 ] = a * d; - te[1] = cf + de * b; - te[5] = a * e; - te[9] = df - ce * b; + te[ 1 ] = a * f; + te[ 5 ] = a * e; + te[ 9 ] = - b; - te[2] = -a * d; - te[6] = b; - te[10] = a * c; + te[ 2 ] = cf * b - de; + te[ 6 ] = df + ce * b; + te[ 10 ] = a * c; - } else if (euler.order === 'ZYX') { + } else if ( euler.order === 'ZXY' ) { - var ae = a * e, af = a * f, be = b * e, bf = b * f; + var ce = c * e, cf = c * f, de = d * e, df = d * f; - te[0] = c * e; - te[4] = be * d - af; - te[8] = ae * d + bf; + te[ 0 ] = ce - df * b; + te[ 4 ] = - a * f; + te[ 8 ] = de + cf * b; - te[1] = c * f; - te[5] = bf * d + ae; - te[9] = af * d - be; + te[ 1 ] = cf + de * b; + te[ 5 ] = a * e; + te[ 9 ] = df - ce * b; - te[2] = -d; - te[6] = b * c; - te[10] = a * c; + te[ 2 ] = - a * d; + te[ 6 ] = b; + te[ 10 ] = a * c; - } else if (euler.order === 'YZX') { + } else if ( euler.order === 'ZYX' ) { - var ac = a * c, ad = a * d, bc = b * c, bd = b * d; + var ae = a * e, af = a * f, be = b * e, bf = b * f; - te[0] = c * e; - te[4] = bd - ac * f; - te[8] = bc * f + ad; + te[ 0 ] = c * e; + te[ 4 ] = be * d - af; + te[ 8 ] = ae * d + bf; - te[1] = f; - te[5] = a * e; - te[9] = -b * e; + te[ 1 ] = c * f; + te[ 5 ] = bf * d + ae; + te[ 9 ] = af * d - be; - te[2] = -d * e; - te[6] = ad * f + bc; - te[10] = ac - bd * f; + te[ 2 ] = - d; + te[ 6 ] = b * c; + te[ 10 ] = a * c; - } else if (euler.order === 'XZY') { + } else if ( euler.order === 'YZX' ) { - var ac = a * c, ad = a * d, bc = b * c, bd = b * d; + 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[ 0 ] = c * e; + te[ 4 ] = bd - ac * f; + te[ 8 ] = bc * f + ad; - te[1] = ac * f + bd; - te[5] = a * e; - te[9] = ad * f - bc; + te[ 1 ] = f; + te[ 5 ] = a * e; + te[ 9 ] = - b * e; - te[2] = bc * f - ad; - te[6] = b * e; - te[10] = bd * f + ac; + te[ 2 ] = - d * e; + te[ 6 ] = ad * f + bc; + te[ 10 ] = ac - bd * f; - } + } else if ( euler.order === 'XZY' ) { - // last column - te[3] = 0; - te[7] = 0; - te[11] = 0; + var ac = a * c, ad = a * d, bc = b * c, bd = b * d; - // bottom row - te[12] = 0; - te[13] = 0; - te[14] = 0; - te[15] = 1; + te[ 0 ] = c * e; + te[ 4 ] = - f; + te[ 8 ] = d * e; - return this; + 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; - setRotationFromQuaternion: function (q) { + } - THREE.warn('THREE.Matrix4: .setRotationFromQuaternion() has been renamed to .makeRotationFromQuaternion().'); + // last column + te[ 3 ] = 0; + te[ 7 ] = 0; + te[ 11 ] = 0; - return this.makeRotationFromQuaternion(q); + // bottom row + te[ 12 ] = 0; + te[ 13 ] = 0; + te[ 14 ] = 0; + te[ 15 ] = 1; - }, + return this; - makeRotationFromQuaternion: function (q) { + }, - var te = this.elements; + setRotationFromQuaternion: function ( q ) { - 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; + console.warn( 'THREE.Matrix4: .setRotationFromQuaternion() has been renamed to .makeRotationFromQuaternion().' ); - te[0] = 1 - ( yy + zz ); - te[4] = xy - wz; - te[8] = xz + wy; + return this.makeRotationFromQuaternion( q ); - 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 ); + makeRotationFromQuaternion: function ( q ) { - // last column - te[3] = 0; - te[7] = 0; - te[11] = 0; + var te = this.elements; - // bottom row - te[12] = 0; - te[13] = 0; - te[14] = 0; - te[15] = 1; + 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; - return this; + 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; - lookAt: function () { + te[ 2 ] = xz - wy; + te[ 6 ] = yz + wx; + te[ 10 ] = 1 - ( xx + yy ); - var x, y, z; + // last column + te[ 3 ] = 0; + te[ 7 ] = 0; + te[ 11 ] = 0; - return function (eye, target, up) { + // bottom row + te[ 12 ] = 0; + te[ 13 ] = 0; + te[ 14 ] = 0; + te[ 15 ] = 1; - if (x === undefined) x = new THREE.Vector3(); - if (y === undefined) y = new THREE.Vector3(); - if (z === undefined) z = new THREE.Vector3(); + return this; - var te = this.elements; + }, - z.subVectors(eye, target).normalize(); + lookAt: function () { - if (z.length() === 0) { + var x, y, z; - z.z = 1; + return function ( eye, target, up ) { - } + if ( x === undefined ) x = new THREE.Vector3(); + if ( y === undefined ) y = new THREE.Vector3(); + if ( z === undefined ) z = new THREE.Vector3(); - x.crossVectors(up, z).normalize(); + var te = this.elements; - if (x.length() === 0) { + z.subVectors( eye, target ).normalize(); - z.x += 0.0001; - x.crossVectors(up, z).normalize(); + if ( z.length() === 0 ) { - } + z.z = 1; - y.crossVectors(z, x); + } + x.crossVectors( up, z ).normalize(); - 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; + if ( x.length() === 0 ) { - return this; + z.x += 0.0001; + x.crossVectors( up, z ).normalize(); - }; + } - }(), + y.crossVectors( z, x ); - multiply: function (m, n) { - if (n !== undefined) { + 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; - THREE.warn('THREE.Matrix4: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead.'); - return this.multiplyMatrices(m, n); + return this; - } + }; - return this.multiplyMatrices(this, m); + }(), - }, + multiply: function ( m, n ) { - multiplyMatrices: function (a, b) { + if ( n !== undefined ) { - var ae = a.elements; - var be = b.elements; - var te = this.elements; + console.warn( 'THREE.Matrix4: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead.' ); + return this.multiplyMatrices( m, n ); - 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]; + return this.multiplyMatrices( this, m ); - 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; + multiplyMatrices: function ( a, b ) { - 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; + var ae = a.elements; + var be = b.elements; + var te = this.elements; - 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; + 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 ]; - return this; + 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; - multiplyToArray: function (a, b, r) { + 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; - var te = this.elements; + 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; - this.multiplyMatrices(a, b); + 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; - 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; - return this; + }, - }, + multiplyToArray: function ( a, b, r ) { - multiplyScalar: function (s) { + var te = this.elements; - var te = this.elements; + this.multiplyMatrices( a, b ); - 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; + 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; + return this; - }, + }, - multiplyVector3: function (vector) { + multiplyScalar: function ( s ) { - THREE.warn('THREE.Matrix4: .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) or vector.applyProjection( matrix ) instead.'); - return vector.applyProjection(this); + 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; - multiplyVector4: function (vector) { + return this; - THREE.warn('THREE.Matrix4: .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.'); - return vector.applyMatrix4(this); + }, - }, + multiplyVector3: function ( vector ) { - multiplyVector3Array: function (a) { + console.warn( 'THREE.Matrix4: .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) or vector.applyProjection( matrix ) instead.' ); + return vector.applyProjection( this ); - THREE.warn('THREE.Matrix4: .multiplyVector3Array() has been renamed. Use matrix.applyToVector3Array( array ) instead.'); - return this.applyToVector3Array(a); + }, - }, + multiplyVector4: function ( vector ) { - applyToVector3Array: function () { + console.warn( 'THREE.Matrix4: .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.' ); + return vector.applyMatrix4( this ); - var v1; + }, - return function applyToVector3Array(array, offset, length) { + multiplyVector3Array: function ( a ) { - if (v1 === undefined) v1 = new THREE.Vector3(); - if (offset === undefined) offset = 0; - if (length === undefined) length = array.length; + console.warn( 'THREE.Matrix4: .multiplyVector3Array() has been renamed. Use matrix.applyToVector3Array( array ) instead.' ); + return this.applyToVector3Array( a ); - for (var i = 0, j = offset; i < length; i += 3, j += 3) { + }, - v1.x = array[j]; - v1.y = array[j + 1]; - v1.z = array[j + 2]; + applyToVector3Array: function () { - v1.applyMatrix4(this); + var v1; - array[j] = v1.x; - array[j + 1] = v1.y; - array[j + 2] = v1.z; + return function ( array, offset, length ) { - } + if ( v1 === undefined ) v1 = new THREE.Vector3(); + if ( offset === undefined ) offset = 0; + if ( length === undefined ) length = array.length; - return array; + for ( var i = 0, j = offset; i < length; i += 3, j += 3 ) { - }; + v1.fromArray( array, j ); + v1.applyMatrix4( this ); + v1.toArray( array, j ); - }(), + } - applyToBuffer: function () { + return array; - var v1; + }; - return function applyToBuffer(buffer, offset, length) { + }(), - if (v1 === undefined) v1 = new THREE.Vector3(); - if (offset === undefined) offset = 0; - if (length === undefined) length = buffer.length / buffer.itemSize; + applyToBuffer: function () { - for (var i = 0, j = offset; i < length; i++, j++) { + var v1; - v1.x = buffer.getX(j); - v1.y = buffer.getY(j); - v1.z = buffer.getZ(j); + return function applyToBuffer( buffer, offset, length ) { - v1.applyMatrix4(this); + if ( v1 === undefined ) v1 = new THREE.Vector3(); + if ( offset === undefined ) offset = 0; + if ( length === undefined ) length = buffer.length / buffer.itemSize; - buffer.setXYZ(v1.x, v1.y, v1.z); + for ( var i = 0, j = offset; i < length; i ++, j ++ ) { - } + v1.x = buffer.getX( j ); + v1.y = buffer.getY( j ); + v1.z = buffer.getZ( j ); - return buffer; + v1.applyMatrix4( this ); - }; + buffer.setXYZ( v1.x, v1.y, v1.z ); - }(), + } - rotateAxis: function (v) { + return buffer; - THREE.warn('THREE.Matrix4: .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.'); + }; - v.transformDirection(this); + }(), - }, + rotateAxis: function ( v ) { - crossVector: function (vector) { + console.warn( 'THREE.Matrix4: .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.' ); - THREE.warn('THREE.Matrix4: .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead.'); - return vector.applyMatrix4(this); + v.transformDirection( this ); - }, + }, - determinant: function () { + crossVector: function ( vector ) { - var te = this.elements; + console.warn( 'THREE.Matrix4: .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead.' ); + return vector.applyMatrix4( this ); - 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 ) + determinant: function () { - 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 - ) + 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 ) - transpose: function () { + 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 + ) - 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; + transpose: function () { - return this; + 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; - flattenToArrayOffset: function (array, offset) { + 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; - var te = this.elements; + return this; - array[offset] = te[0]; - array[offset + 1] = te[1]; - array[offset + 2] = te[2]; - array[offset + 3] = te[3]; + }, - array[offset + 4] = te[4]; - array[offset + 5] = te[5]; - array[offset + 6] = te[6]; - array[offset + 7] = te[7]; + flattenToArrayOffset: function ( array, offset ) { - array[offset + 8] = te[8]; - array[offset + 9] = te[9]; - array[offset + 10] = te[10]; - array[offset + 11] = te[11]; + var te = this.elements; - array[offset + 12] = te[12]; - array[offset + 13] = te[13]; - array[offset + 14] = te[14]; - array[offset + 15] = te[15]; + array[ offset ] = te[ 0 ]; + array[ offset + 1 ] = te[ 1 ]; + array[ offset + 2 ] = te[ 2 ]; + array[ offset + 3 ] = te[ 3 ]; - return array; + array[ offset + 4 ] = te[ 4 ]; + array[ offset + 5 ] = te[ 5 ]; + array[ offset + 6 ] = te[ 6 ]; + array[ offset + 7 ] = te[ 7 ]; - }, + array[ offset + 8 ] = te[ 8 ]; + array[ offset + 9 ] = te[ 9 ]; + array[ offset + 10 ] = te[ 10 ]; + array[ offset + 11 ] = te[ 11 ]; - getPosition: function () { + array[ offset + 12 ] = te[ 12 ]; + array[ offset + 13 ] = te[ 13 ]; + array[ offset + 14 ] = te[ 14 ]; + array[ offset + 15 ] = te[ 15 ]; - var v1; + return array; - return function () { + }, - if (v1 === undefined) v1 = new THREE.Vector3(); - THREE.warn('THREE.Matrix4: .getPosition() has been removed. Use Vector3.setFromMatrixPosition( matrix ) instead.'); + getPosition: function () { - var te = this.elements; - return v1.set(te[12], te[13], te[14]); + var v1; - }; + return function () { - }(), + if ( v1 === undefined ) v1 = new THREE.Vector3(); + console.warn( 'THREE.Matrix4: .getPosition() has been removed. Use Vector3.setFromMatrixPosition( matrix ) instead.' ); - setPosition: function (v) { + var te = this.elements; + return v1.set( te[ 12 ], te[ 13 ], te[ 14 ] ); - var te = this.elements; + }; - te[12] = v.x; - te[13] = v.y; - te[14] = v.z; + }(), - return this; + setPosition: function ( v ) { - }, + var te = this.elements; - getInverse: function (m, throwOnInvertible) { + te[ 12 ] = v.x; + te[ 13 ] = v.y; + te[ 14 ] = v.z; - // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm - var te = this.elements; - var me = m.elements; + return this; - 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; + getInverse: function ( m, throwOnInvertible ) { - var det = n11 * te[0] + n21 * te[4] + n31 * te[8] + n41 * te[12]; + // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm + var te = this.elements; + var me = m.elements; - if (det == 0) { + 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 ]; - var msg = "THREE.Matrix4.getInverse(): can't invert matrix, determinant is 0"; + 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; - if (throwOnInvertible || false) { + var det = n11 * te[ 0 ] + n21 * te[ 4 ] + n31 * te[ 8 ] + n41 * te[ 12 ]; - throw new Error(msg); + if ( det === 0 ) { - } else { + var msg = "THREE.Matrix4.getInverse(): can't invert matrix, determinant is 0"; - THREE.warn(msg); + if ( throwOnInvertible || false ) { - } + throw new Error( msg ); - this.identity(); + } else { - return this; + console.warn( msg ); - } + } - this.multiplyScalar(1 / det); + this.identity(); - return this; + return this; - }, + } - translate: function (v) { + this.multiplyScalar( 1 / det ); - THREE.error('THREE.Matrix4: .translate() has been removed.'); + return this; - }, + }, - rotateX: function (angle) { + translate: function ( v ) { - THREE.error('THREE.Matrix4: .rotateX() has been removed.'); + console.error( 'THREE.Matrix4: .translate() has been removed.' ); - }, + }, - rotateY: function (angle) { + rotateX: function ( angle ) { - THREE.error('THREE.Matrix4: .rotateY() has been removed.'); + console.error( 'THREE.Matrix4: .rotateX() has been removed.' ); - }, + }, - rotateZ: function (angle) { + rotateY: function ( angle ) { - THREE.error('THREE.Matrix4: .rotateZ() has been removed.'); + console.error( 'THREE.Matrix4: .rotateY() has been removed.' ); - }, + }, - rotateByAxis: function (axis, angle) { + rotateZ: function ( angle ) { - THREE.error('THREE.Matrix4: .rotateByAxis() has been removed.'); + console.error( 'THREE.Matrix4: .rotateZ() has been removed.' ); - }, + }, - scale: function (v) { + rotateByAxis: function ( axis, angle ) { - var te = this.elements; - var x = v.x, y = v.y, z = v.z; + console.error( 'THREE.Matrix4: .rotateByAxis() has been removed.' ); - 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; + scale: function ( v ) { - }, + var te = this.elements; + var x = v.x, y = v.y, z = v.z; - getMaxScaleOnAxis: function () { + 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; - var te = this.elements; + return this; - 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))); + getMaxScaleOnAxis: function () { - }, + var te = this.elements; - makeTranslation: function (x, y, z) { + 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 ]; - this.set( - 1, 0, 0, x, - 0, 1, 0, y, - 0, 0, 1, z, - 0, 0, 0, 1 - ); + return Math.sqrt( Math.max( scaleXSq, Math.max( scaleYSq, scaleZSq ) ) ); - return this; + }, - }, + makeTranslation: function ( x, y, z ) { - makeRotationX: function (theta) { + this.set( - var c = Math.cos(theta), s = Math.sin(theta); + 1, 0, 0, x, + 0, 1, 0, y, + 0, 0, 1, z, + 0, 0, 0, 1 - this.set( - 1, 0, 0, 0, - 0, c, -s, 0, - 0, s, c, 0, - 0, 0, 0, 1 - ); + ); - return this; + return this; - }, + }, - makeRotationY: function (theta) { + makeRotationX: function ( theta ) { - var c = Math.cos(theta), s = Math.sin(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 - ); + this.set( - return this; + 1, 0, 0, 0, + 0, c, - s, 0, + 0, s, c, 0, + 0, 0, 0, 1 - }, + ); - makeRotationZ: function (theta) { + return this; - 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 - ); + makeRotationY: function ( theta ) { - return this; + var c = Math.cos( theta ), s = Math.sin( theta ); - }, + this.set( - makeRotationAxis: function (axis, angle) { + c, 0, s, 0, + 0, 1, 0, 0, + - s, 0, c, 0, + 0, 0, 0, 1 - // 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; + return this; - 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; + makeRotationZ: function ( theta ) { - }, + var c = Math.cos( theta ), s = Math.sin( theta ); - makeScale: function (x, y, z) { + this.set( - this.set( - x, 0, 0, 0, - 0, y, 0, 0, - 0, 0, z, 0, - 0, 0, 0, 1 - ); + c, - s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 - return this; + ); - }, + return this; - compose: function (position, quaternion, scale) { + }, - this.makeRotationFromQuaternion(quaternion); - this.scale(scale); - this.setPosition(position); + makeRotationAxis: function ( axis, angle ) { - return this; + // 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; - decompose: function () { + this.set( - var vector, matrix; + 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 function (position, quaternion, scale) { + ); - if (vector === undefined) vector = new THREE.Vector3(); - if (matrix === undefined) matrix = new THREE.Matrix4(); + return this; - 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(); + makeScale: function ( x, y, z ) { - // if determine is negative, we need to invert one scale - var det = this.determinant(); - if (det < 0) { + this.set( - sx = -sx; + x, 0, 0, 0, + 0, y, 0, 0, + 0, 0, z, 0, + 0, 0, 0, 1 - } + ); - position.x = te[12]; - position.y = te[13]; - position.z = te[14]; + return this; - // scale the rotation part + }, - matrix.elements.set(this.elements); // at this point matrix is incomplete so we can't use .copy() + compose: function ( position, quaternion, scale ) { - var invSX = 1 / sx; - var invSY = 1 / sy; - var invSZ = 1 / sz; + this.makeRotationFromQuaternion( quaternion ); + this.scale( scale ); + this.setPosition( position ); - matrix.elements[0] *= invSX; - matrix.elements[1] *= invSX; - matrix.elements[2] *= invSX; + return this; - matrix.elements[4] *= invSY; - matrix.elements[5] *= invSY; - matrix.elements[6] *= invSY; + }, - matrix.elements[8] *= invSZ; - matrix.elements[9] *= invSZ; - matrix.elements[10] *= invSZ; + decompose: function () { - quaternion.setFromRotationMatrix(matrix); + var vector, matrix; - scale.x = sx; - scale.y = sy; - scale.z = sz; + return function ( position, quaternion, scale ) { - return this; + if ( vector === undefined ) vector = new THREE.Vector3(); + if ( matrix === undefined ) matrix = new THREE.Matrix4(); - }; + 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(); - makeFrustum: function (left, right, bottom, top, near, far) { + // if determine is negative, we need to invert one scale + var det = this.determinant(); + if ( det < 0 ) { - var te = this.elements; - var x = 2 * near / ( right - left ); - var y = 2 * near / ( top - bottom ); + sx = - sx; - 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; + position.x = te[ 12 ]; + position.y = te[ 13 ]; + position.z = te[ 14 ]; - return this; + // scale the rotation part - }, + matrix.elements.set( this.elements ); // at this point matrix is incomplete so we can't use .copy() - makePerspective: function (fov, aspect, near, far) { + var invSX = 1 / sx; + var invSY = 1 / sy; + var invSZ = 1 / sz; - var ymax = near * Math.tan(THREE.Math.degToRad(fov * 0.5)); - var ymin = -ymax; - var xmin = ymin * aspect; - var xmax = ymax * aspect; + matrix.elements[ 0 ] *= invSX; + matrix.elements[ 1 ] *= invSX; + matrix.elements[ 2 ] *= invSX; - return this.makeFrustum(xmin, xmax, ymin, ymax, near, far); + matrix.elements[ 4 ] *= invSY; + matrix.elements[ 5 ] *= invSY; + matrix.elements[ 6 ] *= invSY; - }, + matrix.elements[ 8 ] *= invSZ; + matrix.elements[ 9 ] *= invSZ; + matrix.elements[ 10 ] *= invSZ; - makeOrthographic: function (left, right, top, bottom, near, far) { + quaternion.setFromRotationMatrix( matrix ); - var te = this.elements; - var w = right - left; - var h = top - bottom; - var p = far - near; + scale.x = sx; + scale.y = sy; + scale.z = sz; - var x = ( right + left ) / w; - var y = ( top + bottom ) / h; - var z = ( far + near ) / p; + return this; - 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; + }(), - }, + makeFrustum: function ( left, right, bottom, top, near, far ) { - fromArray: function (array) { + var te = this.elements; + var x = 2 * near / ( right - left ); + var y = 2 * near / ( top - bottom ); - this.elements.set(array); + 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 ); - return this; + 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; - toArray: function () { + }, - var te = this.elements; + makePerspective: function ( fov, aspect, near, far ) { - 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] - ]; + 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 ); - clone: function () { + }, - return new THREE.Matrix4().fromArray(this.elements); + 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; + + }, + + equals: function ( matrix ) { + + var te = this.elements; + var me = matrix.elements; + + for ( var i = 0; i < 16; i ++ ) { + + if ( te[ i ] !== me[ i ] ) return false; + + } + + return true; + + }, + + 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 ] + ]; + + } }; // File:src/math/Ray.js /** - * @author bhouston / http://exocortex.com + * @author bhouston / http://clara.io */ -THREE.Ray = function (origin, direction) { +THREE.Ray = function ( origin, direction ) { - this.origin = ( origin !== undefined ) ? origin : new THREE.Vector3(); - this.direction = ( direction !== undefined ) ? direction : new THREE.Vector3(); + this.origin = ( origin !== undefined ) ? origin : new THREE.Vector3(); + this.direction = ( direction !== undefined ) ? direction : new THREE.Vector3(); }; THREE.Ray.prototype = { - constructor: THREE.Ray, + constructor: THREE.Ray, - set: function (origin, direction) { + set: function ( origin, direction ) { - this.origin.copy(origin); - this.direction.copy(direction); + this.origin.copy( origin ); + this.direction.copy( direction ); - return this; + return this; - }, + }, - copy: function (ray) { + clone: function () { - this.origin.copy(ray.origin); - this.direction.copy(ray.direction); + return new this.constructor().copy( this ); - return this; + }, - }, + copy: function ( ray ) { - at: function (t, optionalTarget) { + this.origin.copy( ray.origin ); + this.direction.copy( ray.direction ); - var result = optionalTarget || new THREE.Vector3(); + return this; - return result.copy(this.direction).multiplyScalar(t).add(this.origin); + }, - }, + at: function ( t, optionalTarget ) { - recast: function () { + var result = optionalTarget || new THREE.Vector3(); - var v1 = new THREE.Vector3(); + return result.copy( this.direction ).multiplyScalar( t ).add( this.origin ); - return function (t) { + }, - this.origin.copy(this.at(t, v1)); + recast: function () { - return this; + var v1 = new THREE.Vector3(); - }; + return function ( t ) { - }(), + this.origin.copy( this.at( t, v1 ) ); - closestPointToPoint: function (point, optionalTarget) { + return this; - 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); + closestPointToPoint: function ( point, optionalTarget ) { - } + var result = optionalTarget || new THREE.Vector3(); + result.subVectors( point, this.origin ); + var directionDistance = result.dot( this.direction ); - return result.copy(this.direction).multiplyScalar(directionDistance).add(this.origin); + if ( directionDistance < 0 ) { - }, + return result.copy( this.origin ); - distanceToPoint: function () { + } - var v1 = new THREE.Vector3(); + return result.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin ); - return function (point) { + }, - var directionDistance = v1.subVectors(point, this.origin).dot(this.direction); + distanceToPoint: function ( point ) { - // point behind the ray + return Math.sqrt( this.distanceSqToPoint( point ) ); - if (directionDistance < 0) { + }, - return this.origin.distanceTo(point); + distanceSqToPoint: function () { - } + var v1 = new THREE.Vector3(); - v1.copy(this.direction).multiplyScalar(directionDistance).add(this.origin); + return function ( point ) { - return v1.distanceTo(point); + var directionDistance = v1.subVectors( point, this.origin ).dot( this.direction ); - }; + // point behind the ray - }(), + if ( directionDistance < 0 ) { - distanceSqToSegment: function () { + return this.origin.distanceToSquared( point ); - var segCenter = new THREE.Vector3(); - var segDir = new THREE.Vector3(); - var diff = new THREE.Vector3(); + } - return function (v0, v1, optionalPointOnRay, optionalPointOnSegment) { + v1.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin ); - // 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 + return v1.distanceToSquared( point ); - segCenter.copy(v0).add(v1).multiplyScalar(0.5); - segDir.copy(v1).sub(v0).normalize(); - diff.copy(this.origin).sub(segCenter); + }; - var segExtent = v0.distanceTo(v1) * 0.5; - 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) { + distanceSqToSegment: function () { - // The ray and segment are not parallel. + var segCenter = new THREE.Vector3(); + var segDir = new THREE.Vector3(); + var diff = new THREE.Vector3(); - s0 = a01 * b1 - b0; - s1 = a01 * b0 - b1; - extDet = segExtent * det; + return function ( v0, v1, optionalPointOnRay, optionalPointOnSegment ) { - if (s0 >= 0) { + // 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 - if (s1 >= -extDet) { + segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 ); + segDir.copy( v1 ).sub( v0 ).normalize(); + diff.copy( this.origin ).sub( segCenter ); - if (s1 <= extDet) { + var segExtent = v0.distanceTo( v1 ) * 0.5; + 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; - // region 0 - // Minimum at interior points of ray and segment. + if ( det > 0 ) { - var invDet = 1 / det; - s0 *= invDet; - s1 *= invDet; - sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c; + // The ray and segment are not parallel. - } else { + s0 = a01 * b1 - b0; + s1 = a01 * b0 - b1; + extDet = segExtent * det; - // region 1 + if ( s0 >= 0 ) { - s1 = segExtent; - s0 = Math.max(0, -( a01 * s1 + b0 )); - sqrDist = -s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + if ( s1 >= - extDet ) { - } + if ( s1 <= extDet ) { - } else { + // region 0 + // Minimum at interior points of ray and segment. - // region 5 + var invDet = 1 / det; + s0 *= invDet; + s1 *= invDet; + sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c; - s1 = -segExtent; - s0 = Math.max(0, -( a01 * s1 + b0 )); - sqrDist = -s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + } else { - } + // region 1 - } else { + s1 = segExtent; + s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - if (s1 <= -extDet) { + } - // region 4 + } else { - 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; + // region 5 - } else if (s1 <= extDet) { + s1 = - segExtent; + s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - // region 3 + } - s0 = 0; - s1 = Math.min(Math.max(-segExtent, -b1), segExtent); - sqrDist = s1 * ( s1 + 2 * b1 ) + c; + } else { - } else { + if ( s1 <= - extDet ) { - // region 2 + // 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; + 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 - } else { + s0 = 0; + s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent ); + sqrDist = s1 * ( s1 + 2 * b1 ) + c; - // Ray and segment are parallel. + } else { - s1 = ( a01 > 0 ) ? -segExtent : segExtent; - s0 = Math.max(0, -( a01 * s1 + b0 )); - sqrDist = -s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + // 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; - if (optionalPointOnRay) { + } - optionalPointOnRay.copy(this.direction).multiplyScalar(s0).add(this.origin); + } - } + } else { - if (optionalPointOnSegment) { + // Ray and segment are parallel. - optionalPointOnSegment.copy(segDir).multiplyScalar(s1).add(segCenter); + s1 = ( a01 > 0 ) ? - segExtent : segExtent; + s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - } + } - return sqrDist; + if ( optionalPointOnRay ) { - }; + optionalPointOnRay.copy( this.direction ).multiplyScalar( s0 ).add( this.origin ); - }(), + } + if ( optionalPointOnSegment ) { - isIntersectionSphere: function (sphere) { + optionalPointOnSegment.copy( segDir ).multiplyScalar( s1 ).add( segCenter ); - return this.distanceToPoint(sphere.center) <= sphere.radius; + } - }, + return sqrDist; - intersectSphere: function () { + }; - // from http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-7-intersecting-simple-shapes/ray-sphere-intersection/ + }(), - var v1 = new THREE.Vector3(); - return function (sphere, optionalTarget) { + isIntersectionSphere: function ( sphere ) { - v1.subVectors(sphere.center, this.origin); + return this.distanceToPoint( sphere.center ) <= sphere.radius; - var tca = v1.dot(this.direction); + }, - var d2 = v1.dot(v1) - tca * tca; + intersectSphere: function () { - var radius2 = sphere.radius * sphere.radius; + // from http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-7-intersecting-simple-shapes/ray-sphere-intersection/ - if (d2 > radius2) return null; + var v1 = new THREE.Vector3(); - var thc = Math.sqrt(radius2 - d2); + return function ( sphere, optionalTarget ) { - // t0 = first intersect point - entrance on front of sphere - var t0 = tca - thc; + v1.subVectors( sphere.center, this.origin ); - // t1 = second intersect point - exit point on back of sphere - var t1 = tca + thc; + var tca = v1.dot( this.direction ); - // test to see if both t0 and t1 are behind the ray - if so, return null - if (t0 < 0 && t1 < 0) return null; + var d2 = v1.dot( v1 ) - tca * tca; - // test to see if t0 is behind the ray: - // if it is, the ray is inside the sphere, so return the second exit point scaled by t1, - // in order to always return an intersect point that is in front of the ray. - if (t0 < 0) return this.at(t1, optionalTarget); + var radius2 = sphere.radius * sphere.radius; - // else t0 is in front of the ray, so return the first collision point scaled by t0 - return this.at(t0, optionalTarget); + if ( d2 > radius2 ) return null; - } + var thc = Math.sqrt( radius2 - d2 ); - }(), + // t0 = first intersect point - entrance on front of sphere + var t0 = tca - thc; - isIntersectionPlane: function (plane) { + // t1 = second intersect point - exit point on back of sphere + var t1 = tca + thc; - // check if the ray lies on the plane first + // test to see if both t0 and t1 are behind the ray - if so, return null + if ( t0 < 0 && t1 < 0 ) return null; - var distToPoint = plane.distanceToPoint(this.origin); + // test to see if t0 is behind the ray: + // if it is, the ray is inside the sphere, so return the second exit point scaled by t1, + // in order to always return an intersect point that is in front of the ray. + if ( t0 < 0 ) return this.at( t1, optionalTarget ); - if (distToPoint === 0) { + // else t0 is in front of the ray, so return the first collision point scaled by t0 + return this.at( t0, optionalTarget ); - return true; + } - } + }(), - var denominator = plane.normal.dot(this.direction); + isIntersectionPlane: function ( plane ) { - if (denominator * distToPoint < 0) { + // check if the ray lies on the plane first - return true; + var distToPoint = plane.distanceToPoint( this.origin ); - } + if ( distToPoint === 0 ) { - // ray origin is behind the plane (and is pointing behind it) + return true; - return false; + } - }, + var denominator = plane.normal.dot( this.direction ); - distanceToPlane: function (plane) { + if ( denominator * distToPoint < 0 ) { - var denominator = plane.normal.dot(this.direction); - if (denominator == 0) { + return true; - // line is coplanar, return origin - if (plane.distanceToPoint(this.origin) == 0) { + } - return 0; + // ray origin is behind the plane (and is pointing behind it) - } + return false; - // Null is preferable to undefined since undefined means.... it is undefined + }, - return null; + distanceToPlane: function ( plane ) { - } + var denominator = plane.normal.dot( this.direction ); + if ( denominator === 0 ) { - var t = -( this.origin.dot(plane.normal) + plane.constant ) / denominator; + // line is coplanar, return origin + if ( plane.distanceToPoint( this.origin ) === 0 ) { - // Return if the ray never intersects the plane + return 0; - return t >= 0 ? t : null; + } - }, + // Null is preferable to undefined since undefined means.... it is undefined - intersectPlane: function (plane, optionalTarget) { + return null; - var t = this.distanceToPlane(plane); + } - if (t === null) { + var t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator; - return null; - } + // Return if the ray never intersects the plane - return this.at(t, optionalTarget); + return t >= 0 ? t : null; - }, + }, - isIntersectionBox: function () { + intersectPlane: function ( plane, optionalTarget ) { - var v = new THREE.Vector3(); + var t = this.distanceToPlane( plane ); - return function (box) { + if ( t === null ) { - return this.intersectBox(box, v) !== null; + return null; - }; + } - }(), + return this.at( t, optionalTarget ); - intersectBox: function (box, optionalTarget) { + }, - // http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-7-intersecting-simple-shapes/ray-box-intersection/ + isIntersectionBox: function () { - var tmin, tmax, tymin, tymax, tzmin, tzmax; + var v = new THREE.Vector3(); - var invdirx = 1 / this.direction.x, - invdiry = 1 / this.direction.y, - invdirz = 1 / this.direction.z; + return function ( box ) { - var origin = this.origin; + return this.intersectBox( box, v ) !== null; - if (invdirx >= 0) { + }; - tmin = ( box.min.x - origin.x ) * invdirx; - tmax = ( box.max.x - origin.x ) * invdirx; + }(), - } else { + intersectBox: function ( box, optionalTarget ) { - tmin = ( box.max.x - origin.x ) * invdirx; - tmax = ( box.min.x - origin.x ) * invdirx; - } + // http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-7-intersecting-simple-shapes/ray-box-intersection/ - if (invdiry >= 0) { + var tmin, tmax, tymin, tymax, tzmin, tzmax; - tymin = ( box.min.y - origin.y ) * invdiry; - tymax = ( box.max.y - origin.y ) * invdiry; + var invdirx = 1 / this.direction.x, + invdiry = 1 / this.direction.y, + invdirz = 1 / this.direction.z; - } else { + var origin = this.origin; - tymin = ( box.max.y - origin.y ) * invdiry; - tymax = ( box.min.y - origin.y ) * invdiry; - } + if ( invdirx >= 0 ) { - if (( tmin > tymax ) || ( tymin > tmax )) return null; + tmin = ( box.min.x - origin.x ) * invdirx; + tmax = ( box.max.x - origin.x ) * invdirx; - // These lines also handle the case where tmin or tmax is NaN - // (result of 0 * Infinity). x !== x returns true if x is NaN + } else { - if (tymin > tmin || tmin !== tmin) tmin = tymin; + tmin = ( box.max.x - origin.x ) * invdirx; + tmax = ( box.min.x - origin.x ) * invdirx; - if (tymax < tmax || tmax !== tmax) tmax = tymax; + } - if (invdirz >= 0) { + if ( invdiry >= 0 ) { - tzmin = ( box.min.z - origin.z ) * invdirz; - tzmax = ( box.max.z - origin.z ) * invdirz; + tymin = ( box.min.y - origin.y ) * invdiry; + tymax = ( box.max.y - origin.y ) * invdiry; - } else { + } else { - tzmin = ( box.max.z - origin.z ) * invdirz; - tzmax = ( box.min.z - origin.z ) * invdirz; - } + tymin = ( box.max.y - origin.y ) * invdiry; + tymax = ( box.min.y - origin.y ) * invdiry; - if (( tmin > tzmax ) || ( tzmin > tmax )) return null; + } - if (tzmin > tmin || tmin !== tmin) tmin = tzmin; + if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null; - if (tzmax < tmax || tmax !== tmax) tmax = tzmax; + // These lines also handle the case where tmin or tmax is NaN + // (result of 0 * Infinity). x !== x returns true if x is NaN - //return point closest to the ray (positive side) + if ( tymin > tmin || tmin !== tmin ) tmin = tymin; - if (tmax < 0) return null; + if ( tymax < tmax || tmax !== tmax ) tmax = tymax; - return this.at(tmin >= 0 ? tmin : tmax, optionalTarget); + if ( invdirz >= 0 ) { - }, + tzmin = ( box.min.z - origin.z ) * invdirz; + tzmax = ( box.max.z - origin.z ) * invdirz; - intersectTriangle: function () { + } else { - // 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(); + tzmin = ( box.max.z - origin.z ) * invdirz; + tzmax = ( box.min.z - origin.z ) * invdirz; - return function (a, b, c, backfaceCulling, optionalTarget) { + } - // from http://www.geometrictools.com/LibMathematics/Intersection/Wm5IntrRay3Triangle3.cpp + if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null; - edge1.subVectors(b, a); - edge2.subVectors(c, a); - normal.crossVectors(edge1, edge2); + if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin; - // 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 ( tzmax < tmax || tmax !== tmax ) tmax = tzmax; - if (DdN > 0) { + //return point closest to the ray (positive side) - if (backfaceCulling) return null; - sign = 1; + if ( tmax < 0 ) return null; - } else if (DdN < 0) { + return this.at( tmin >= 0 ? tmin : tmax, optionalTarget ); - sign = -1; - DdN = -DdN; + }, - } else { + intersectTriangle: function () { - return null; + // 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 ) { - diff.subVectors(this.origin, a); - var DdQxE2 = sign * this.direction.dot(edge2.crossVectors(diff, edge2)); + // from http://www.geometrictools.com/LibMathematics/Intersection/Wm5IntrRay3Triangle3.cpp - // b1 < 0, no intersection - if (DdQxE2 < 0) { + edge1.subVectors( b, a ); + edge2.subVectors( c, a ); + normal.crossVectors( edge1, edge2 ); - return null; + // 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 ) { - var DdE1xQ = sign * this.direction.dot(edge1.cross(diff)); + if ( backfaceCulling ) return null; + sign = 1; - // b2 < 0, no intersection - if (DdE1xQ < 0) { + } else if ( DdN < 0 ) { - return null; + sign = - 1; + DdN = - DdN; - } + } else { - // b1+b2 > 1, no intersection - if (DdQxE2 + DdE1xQ > DdN) { + return null; - return null; + } - } + diff.subVectors( this.origin, a ); + var DdQxE2 = sign * this.direction.dot( edge2.crossVectors( diff, edge2 ) ); - // Line intersects triangle, check if ray does. - var QdN = -sign * diff.dot(normal); + // b1 < 0, no intersection + if ( DdQxE2 < 0 ) { - // t < 0, no intersection - if (QdN < 0) { + return null; - return null; + } - } + var DdE1xQ = sign * this.direction.dot( edge1.cross( diff ) ); - // Ray intersects triangle. - return this.at(QdN / DdN, optionalTarget); + // b2 < 0, no intersection + if ( DdE1xQ < 0 ) { - }; + return null; - }(), + } - applyMatrix4: function (matrix4) { + // b1+b2 > 1, no intersection + if ( DdQxE2 + DdE1xQ > DdN ) { - this.direction.add(this.origin).applyMatrix4(matrix4); - this.origin.applyMatrix4(matrix4); - this.direction.sub(this.origin); - this.direction.normalize(); + return null; - return this; - }, + } - equals: function (ray) { + // Line intersects triangle, check if ray does. + var QdN = - sign * diff.dot( normal ); - return ray.origin.equals(this.origin) && ray.direction.equals(this.direction); + // t < 0, no intersection + if ( QdN < 0 ) { - }, + return null; - clone: function () { + } - return new THREE.Ray().copy(this); + // 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 ); + + } }; // File:src/math/Sphere.js /** - * @author bhouston / http://exocortex.com + * @author bhouston / http://clara.io * @author mrdoob / http://mrdoob.com/ */ -THREE.Sphere = function (center, radius) { +THREE.Sphere = function ( center, radius ) { - this.center = ( center !== undefined ) ? center : new THREE.Vector3(); - this.radius = ( radius !== undefined ) ? radius : 0; + this.center = ( center !== undefined ) ? center : new THREE.Vector3(); + this.radius = ( radius !== undefined ) ? radius : 0; }; THREE.Sphere.prototype = { - constructor: THREE.Sphere, + constructor: THREE.Sphere, - set: function (center, radius) { + set: function ( center, radius ) { - this.center.copy(center); - this.radius = radius; + this.center.copy( center ); + this.radius = radius; - return this; - }, + return this; - setFromPoints: function () { + }, - var box = new THREE.Box3(); + setFromPoints: function () { - return function (points, optionalCenter) { + var box = new THREE.Box3(); - var center = this.center; + return function ( points, optionalCenter ) { - if (optionalCenter !== undefined) { + var center = this.center; - center.copy(optionalCenter); + if ( optionalCenter !== undefined ) { - } else { + center.copy( optionalCenter ); - box.setFromPoints(points).center(center); + } else { - } + box.setFromPoints( points ).center( center ); - var maxRadiusSq = 0; + } - for (var i = 0, il = points.length; i < il; i++) { + var maxRadiusSq = 0; - maxRadiusSq = Math.max(maxRadiusSq, center.distanceToSquared(points[i])); + 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; + this.radius = Math.sqrt( maxRadiusSq ); - }; + return this; - }(), + }; - copy: function (sphere) { + }(), - this.center.copy(sphere.center); - this.radius = sphere.radius; + clone: function () { - return this; + return new this.constructor().copy( this ); - }, + }, - empty: function () { + copy: function ( sphere ) { - return ( this.radius <= 0 ); + this.center.copy( sphere.center ); + this.radius = sphere.radius; - }, + return this; - containsPoint: function (point) { + }, - return ( point.distanceToSquared(this.center) <= ( this.radius * this.radius ) ); + empty: function () { - }, + return ( this.radius <= 0 ); - distanceToPoint: function (point) { + }, - return ( point.distanceTo(this.center) - this.radius ); + containsPoint: function ( point ) { - }, + return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) ); - intersectsSphere: function (sphere) { + }, - var radiusSum = this.radius + sphere.radius; + distanceToPoint: function ( point ) { - return sphere.center.distanceToSquared(this.center) <= ( radiusSum * radiusSum ); + return ( point.distanceTo( this.center ) - this.radius ); - }, + }, - clampPoint: function (point, optionalTarget) { + intersectsSphere: function ( sphere ) { - var deltaLengthSq = this.center.distanceToSquared(point); + var radiusSum = this.radius + sphere.radius; - var result = optionalTarget || new THREE.Vector3(); - result.copy(point); + return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum ); - if (deltaLengthSq > ( this.radius * this.radius )) { + }, - result.sub(this.center).normalize(); - result.multiplyScalar(this.radius).add(this.center); + clampPoint: function ( point, optionalTarget ) { - } + var deltaLengthSq = this.center.distanceToSquared( point ); - return result; + var result = optionalTarget || new THREE.Vector3(); + result.copy( point ); - }, + if ( deltaLengthSq > ( this.radius * this.radius ) ) { - getBoundingBox: function (optionalTarget) { + result.sub( this.center ).normalize(); + result.multiplyScalar( this.radius ).add( this.center ); - var box = optionalTarget || new THREE.Box3(); + } - box.set(this.center, this.center); - box.expandByScalar(this.radius); + return result; - return box; + }, - }, + getBoundingBox: function ( optionalTarget ) { - applyMatrix4: function (matrix) { + var box = optionalTarget || new THREE.Box3(); - this.center.applyMatrix4(matrix); - this.radius = this.radius * matrix.getMaxScaleOnAxis(); + box.set( this.center, this.center ); + box.expandByScalar( this.radius ); - return this; + return box; - }, + }, - translate: function (offset) { + applyMatrix4: function ( matrix ) { - this.center.add(offset); + this.center.applyMatrix4( matrix ); + this.radius = this.radius * matrix.getMaxScaleOnAxis(); - return this; + return this; - }, + }, - equals: function (sphere) { + translate: function ( offset ) { - return sphere.center.equals(this.center) && ( sphere.radius === this.radius ); + this.center.add( offset ); - }, + return this; - clone: function () { + }, - return new THREE.Sphere().copy(this); + equals: function ( sphere ) { - } + return sphere.center.equals( this.center ) && ( sphere.radius === this.radius ); + + } }; @@ -6512,405 +6488,406 @@ THREE.Sphere.prototype = { /** * @author mrdoob / http://mrdoob.com/ * @author alteredq / http://alteredqualia.com/ - * @author bhouston / http://exocortex.com + * @author bhouston / http://clara.io */ -THREE.Frustum = function (p0, p1, p2, p3, p4, p5) { +THREE.Frustum = function ( p0, p1, p2, p3, p4, p5 ) { - this.planes = [ + 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() + ( 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, + constructor: THREE.Frustum, - set: function (p0, p1, p2, p3, p4, p5) { + set: function ( p0, p1, p2, p3, p4, p5 ) { - var planes = this.planes; + 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); + 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; + return this; - }, + }, - copy: function (frustum) { + clone: function () { - var planes = this.planes; + return new this.constructor().copy( this ); - for (var i = 0; i < 6; i++) { + }, - planes[i].copy(frustum.planes[i]); + copy: function ( frustum ) { - } + var planes = this.planes; - return this; + for ( var i = 0; i < 6; i ++ ) { - }, + planes[ i ].copy( frustum.planes[ i ] ); - 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]; + return this; - 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; + 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 ]; - intersectsObject: function () { + 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(); - var sphere = new THREE.Sphere(); + return this; - return function (object) { + }, - var geometry = object.geometry; + intersectsObject: function () { - if (geometry.boundingSphere === null) geometry.computeBoundingSphere(); + var sphere = new THREE.Sphere(); - sphere.copy(geometry.boundingSphere); - sphere.applyMatrix4(object.matrixWorld); + return function ( object ) { - return this.intersectsSphere(sphere); + var geometry = object.geometry; - }; + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - }(), + sphere.copy( geometry.boundingSphere ); + sphere.applyMatrix4( object.matrixWorld ); - intersectsSphere: function (sphere) { + return this.intersectsSphere( 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); + intersectsSphere: function ( sphere ) { - if (distance < negRadius) { + var planes = this.planes; + var center = sphere.center; + var negRadius = - sphere.radius; - return false; + for ( var i = 0; i < 6; i ++ ) { - } + var distance = planes[ i ].distanceToPoint( center ); - } + if ( distance < negRadius ) { - return true; + return false; - }, + } - intersectsBox: function () { + } - var p1 = new THREE.Vector3(), - p2 = new THREE.Vector3(); + return true; - return function (box) { + }, - var planes = this.planes; + intersectsBox: function () { - for (var i = 0; i < 6; i++) { + var p1 = new THREE.Vector3(), + p2 = new THREE.Vector3(); - var plane = planes[i]; + return function ( box ) { - 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 planes = this.planes; - var d1 = plane.distanceToPoint(p1); - var d2 = plane.distanceToPoint(p2); + for ( var i = 0; i < 6 ; i ++ ) { - // if both outside plane, no intersection + var plane = planes[ i ]; - if (d1 < 0 && d2 < 0) { + 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; - return false; + var d1 = plane.distanceToPoint( p1 ); + var d2 = plane.distanceToPoint( p2 ); - } - } + // if both outside plane, no intersection - return true; - }; + if ( d1 < 0 && d2 < 0 ) { - }(), + return false; + } - containsPoint: function (point) { + } - var planes = this.planes; + return true; - for (var i = 0; i < 6; i++) { + }; - if (planes[i].distanceToPoint(point) < 0) { + }(), - return false; - } + containsPoint: function ( point ) { - } + var planes = this.planes; - return true; + for ( var i = 0; i < 6; i ++ ) { - }, + if ( planes[ i ].distanceToPoint( point ) < 0 ) { - clone: function () { + return false; - return new THREE.Frustum().copy(this); + } - } + } + + return true; + + } }; // File:src/math/Plane.js /** - * @author bhouston / http://exocortex.com + * @author bhouston / http://clara.io */ -THREE.Plane = function (normal, constant) { +THREE.Plane = function ( normal, constant ) { - this.normal = ( normal !== undefined ) ? normal : new THREE.Vector3(1, 0, 0); - this.constant = ( constant !== undefined ) ? constant : 0; + 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) { + constructor: THREE.Plane, - this.normal.copy(normal); - this.constant = constant; + set: function ( normal, constant ) { - return this; + this.normal.copy( normal ); + this.constant = constant; - }, + return this; - setComponents: function (x, y, z, w) { + }, - this.normal.set(x, y, z); - this.constant = w; + setComponents: function ( x, y, z, w ) { - return this; + 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 + setFromNormalAndCoplanarPoint: function ( normal, point ) { - return this; + 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(); + setFromCoplanarPoints: function () { - return function (a, b, c) { + var v1 = new THREE.Vector3(); + var v2 = new THREE.Vector3(); - var normal = v1.subVectors(c, b).cross(v2.subVectors(a, b)).normalize(); + return function ( a, b, c ) { - // Q: should an error be thrown if normal is zero (e.g. degenerate plane)? + var normal = v1.subVectors( c, b ).cross( v2.subVectors( a, b ) ).normalize(); - this.setFromNormalAndCoplanarPoint(normal, a); + // Q: should an error be thrown if normal is zero (e.g. degenerate plane)? - return this; + this.setFromNormalAndCoplanarPoint( normal, a ); - }; + return this; - }(), + }; + }(), - copy: function (plane) { + clone: function () { - this.normal.copy(plane.normal); - this.constant = plane.constant; + return new this.constructor().copy( this ); - return this; + }, - }, + copy: function ( plane ) { - normalize: function () { + this.normal.copy( plane.normal ); + this.constant = plane.constant; - // Note: will lead to a divide by zero if the plane is invalid. + return this; - var inverseNormalLength = 1.0 / this.normal.length(); - this.normal.multiplyScalar(inverseNormalLength); - this.constant *= inverseNormalLength; + }, - return this; + normalize: function () { - }, + // Note: will lead to a divide by zero if the plane is invalid. - negate: function () { + var inverseNormalLength = 1.0 / this.normal.length(); + this.normal.multiplyScalar( inverseNormalLength ); + this.constant *= inverseNormalLength; - this.constant *= -1; - this.normal.negate(); + return this; - return this; + }, - }, + negate: function () { - distanceToPoint: function (point) { + this.constant *= - 1; + this.normal.negate(); - return this.normal.dot(point) + this.constant; + return this; - }, + }, - distanceToSphere: function (sphere) { + distanceToPoint: function ( point ) { - return this.distanceToPoint(sphere.center) - sphere.radius; + return this.normal.dot( point ) + this.constant; - }, + }, - projectPoint: function (point, optionalTarget) { + distanceToSphere: function ( sphere ) { - return this.orthoPoint(point, optionalTarget).sub(point).negate(); + return this.distanceToPoint( sphere.center ) - sphere.radius; - }, + }, - orthoPoint: function (point, optionalTarget) { + projectPoint: function ( point, optionalTarget ) { - var perpendicularMagnitude = this.distanceToPoint(point); + return this.orthoPoint( point, optionalTarget ).sub( point ).negate(); - var result = optionalTarget || new THREE.Vector3(); - return result.copy(this.normal).multiplyScalar(perpendicularMagnitude); + }, - }, + orthoPoint: function ( point, optionalTarget ) { - isIntersectionLine: function (line) { + var perpendicularMagnitude = this.distanceToPoint( point ); - // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it. + var result = optionalTarget || new THREE.Vector3(); + return result.copy( this.normal ).multiplyScalar( perpendicularMagnitude ); - var startSign = this.distanceToPoint(line.start); - var endSign = this.distanceToPoint(line.end); + }, - return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 ); + isIntersectionLine: function ( line ) { - }, + // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it. - intersectLine: function () { + var startSign = this.distanceToPoint( line.start ); + var endSign = this.distanceToPoint( line.end ); - var v1 = new THREE.Vector3(); + return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 ); - return function (line, optionalTarget) { + }, - var result = optionalTarget || new THREE.Vector3(); + intersectLine: function () { - var direction = line.delta(v1); + var v1 = new THREE.Vector3(); - var denominator = this.normal.dot(direction); + return function ( line, optionalTarget ) { - if (denominator == 0) { + var result = optionalTarget || new THREE.Vector3(); - // line is coplanar, return origin - if (this.distanceToPoint(line.start) == 0) { + var direction = line.delta( v1 ); - return result.copy(line.start); + var denominator = this.normal.dot( direction ); - } + if ( denominator === 0 ) { - // Unsure if this is the correct method to handle this case. - return undefined; + // line is coplanar, return origin + if ( this.distanceToPoint( line.start ) === 0 ) { - } + return result.copy( line.start ); - var t = -( line.start.dot(this.normal) + this.constant ) / denominator; + } - if (t < 0 || t > 1) { + // Unsure if this is the correct method to handle this case. + return undefined; - return undefined; + } - } + var t = - ( line.start.dot( this.normal ) + this.constant ) / denominator; - return result.copy(direction).multiplyScalar(t).add(line.start); + 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 () { + coplanarPoint: function ( optionalTarget ) { - var v1 = new THREE.Vector3(); - var v2 = new THREE.Vector3(); - var m1 = new THREE.Matrix3(); + var result = optionalTarget || new THREE.Vector3(); + return result.copy( this.normal ).multiplyScalar( - this.constant ); - 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); + applyMatrix4: function () { - var newCoplanarPoint = this.coplanarPoint(v2); - newCoplanarPoint.applyMatrix4(matrix); + var v1 = new THREE.Vector3(); + var v2 = new THREE.Vector3(); + var m1 = new THREE.Matrix3(); - this.setFromNormalAndCoplanarPoint(newNormal, newCoplanarPoint); + return function ( matrix, optionalNormalMatrix ) { - return this; + // 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 ); - translate: function (offset) { + return this; - this.constant = this.constant - offset.dot(this.normal); + }; - return this; + }(), - }, + translate: function ( offset ) { - equals: function (plane) { + this.constant = this.constant - offset.dot( this.normal ); - return plane.normal.equals(this.normal) && ( plane.constant == this.constant ); + return this; - }, + }, - clone: function () { + equals: function ( plane ) { - return new THREE.Plane().copy(this); + return plane.normal.equals( this.normal ) && ( plane.constant === this.constant ); - } + } }; @@ -6923,166 +6900,176 @@ THREE.Plane.prototype = { THREE.Math = { - generateUUID: function () { + generateUUID: function () { - // http://www.broofa.com/Tools/Math.uuid.htm + // http://www.broofa.com/Tools/Math.uuid.htm - var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); - var uuid = new Array(36); - var rnd = 0, r; + var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split( '' ); + var uuid = new Array( 36 ); + var rnd = 0, r; - return function () { + return function () { - for (var i = 0; i < 36; i++) { + for ( var i = 0; i < 36; i ++ ) { - if (i == 8 || i == 13 || i == 18 || i == 23) { + if ( i === 8 || i === 13 || i === 18 || i === 23 ) { - uuid[i] = '-'; + uuid[ i ] = '-'; - } else if (i == 14) { + } else if ( i === 14 ) { - uuid[i] = '4'; + uuid[ i ] = '4'; - } else { + } 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]; + 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(''); + } - }; + return uuid.join( '' ); - }(), + }; - // Clamp value to range + }(), - clamp: function (x, a, b) { + // Clamp value to range - return ( x < a ) ? a : ( ( x > b ) ? b : x ); + 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) { + // compute euclidian modulo of m % n + // https://en.wikipedia.org/wiki/Modulo_operation - return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 ); + euclideanModulo: function ( n, m ) { - }, + return ( ( n % m ) + m ) % m; - // http://en.wikipedia.org/wiki/Smoothstep + }, - smoothstep: function (x, min, max) { + // Linear mapping from range to range - if (x <= min) return 0; - if (x >= max) return 1; + mapLinear: function ( x, a1, a2, b1, b2 ) { - x = ( x - min ) / ( max - min ); + return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 ); - return x * x * ( 3 - 2 * x ); + }, - }, + // http://en.wikipedia.org/wiki/Smoothstep - smootherstep: function (x, min, max) { + smoothstep: function ( x, min, max ) { - if (x <= min) return 0; - if (x >= max) return 1; + if ( x <= min ) return 0; + if ( x >= max ) return 1; - x = ( x - min ) / ( max - min ); + x = ( x - min ) / ( max - min ); - return x * x * x * ( x * ( x * 6 - 15 ) + 10 ); + return x * x * ( 3 - 2 * x ); - }, + }, - // Random float from <0, 1> with 16 bits of randomness - // (standard Math.random() creates repetitive patterns when applied over larger space) + smootherstep: function ( x, min, max ) { - random16: function () { + if ( x <= min ) return 0; + if ( x >= max ) return 1; - return ( 65280 * Math.random() + 255 * Math.random() ) / 65535; + x = ( x - min ) / ( max - min ); - }, + return x * x * x * ( x * ( x * 6 - 15 ) + 10 ); - // Random integer from interval + }, - randInt: function (low, high) { + // Random float from <0, 1> with 16 bits of randomness + // (standard Math.random() creates repetitive patterns when applied over larger space) - return Math.floor(this.randFloat(low, high)); + random16: function () { - }, + return ( 65280 * Math.random() + 255 * Math.random() ) / 65535; - // Random float from interval + }, - randFloat: function (low, high) { + // Random integer from interval - return low + Math.random() * ( high - low ); + randInt: function ( low, high ) { - }, + return low + Math.floor( Math.random() * ( high - low + 1 ) ); - // Random float from <-range/2, range/2> interval + }, - randFloatSpread: function (range) { + // Random float from interval - return range * ( 0.5 - Math.random() ); + randFloat: function ( low, high ) { - }, + return low + Math.random() * ( high - low ); - degToRad: function () { + }, - var degreeToRadiansFactor = Math.PI / 180; + // Random float from <-range/2, range/2> interval - return function (degrees) { + randFloatSpread: function ( range ) { - return degrees * degreeToRadiansFactor; + return range * ( 0.5 - Math.random() ); - }; + }, - }(), + degToRad: function () { - radToDeg: function () { + var degreeToRadiansFactor = Math.PI / 180; - var radianToDegreesFactor = 180 / Math.PI; + return function ( degrees ) { - return function (radians) { + return degrees * degreeToRadiansFactor; - return radians * radianToDegreesFactor; + }; - }; + }(), - }(), + radToDeg: function () { - isPowerOfTwo: function (value) { + var radianToDegreesFactor = 180 / Math.PI; - return ( value & ( value - 1 ) ) === 0 && value !== 0; + return function ( radians ) { - }, + return radians * radianToDegreesFactor; - nextPowerOfTwo: function (value) { + }; - value--; - value |= value >> 1; - value |= value >> 2; - value |= value >> 4; - value |= value >> 8; - value |= value >> 16; - value++; + }(), - return value; + isPowerOfTwo: function ( value ) { - } + return ( value & ( value - 1 ) ) === 0 && value !== 0; + + }, + + nextPowerOfTwo: function ( value ) { + + value --; + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value ++; + + return value; + + } }; @@ -7096,365 +7083,367 @@ THREE.Math = { * @author alteredq / http://alteredqualia.com/ */ -THREE.Spline = function (points) { +THREE.Spline = function ( points ) { - this.points = points; + this.points = points; - var c = [], v3 = {x: 0, y: 0, z: 0}, - point, intPoint, weight, w2, w3, - pa, pb, pc, pd; + var c = [], v3 = { x: 0, y: 0, z: 0 }, + point, intPoint, weight, w2, w3, + pa, pb, pc, pd; - this.initFromArray = function (a) { + this.initFromArray = function ( a ) { - this.points = []; + this.points = []; - for (var i = 0; i < a.length; i++) { + for ( var i = 0; i < a.length; i ++ ) { - this.points[i] = {x: a[i][0], y: a[i][1], z: a[i][2]}; + this.points[ i ] = { x: a[ i ][ 0 ], y: a[ i ][ 1 ], z: a[ i ][ 2 ] }; - } + } - }; + }; - this.getPoint = function (k) { + this.getPoint = function ( k ) { - point = ( this.points.length - 1 ) * k; - intPoint = Math.floor(point); - weight = point - intPoint; + 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; + 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]]; + 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; + 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); + 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; + return v3; - }; + }; - this.getControlPointsArray = function () { + this.getControlPointsArray = function () { - var i, p, l = this.points.length, - coords = []; + var i, p, l = this.points.length, + coords = []; - for (i = 0; i < l; i++) { + for ( i = 0; i < l; i ++ ) { - p = this.points[i]; - coords[i] = [p.x, p.y, p.z]; + p = this.points[ i ]; + coords[ i ] = [ p.x, p.y, p.z ]; - } + } - return coords; + return coords; - }; + }; - // approximate length by summing linear segments + // approximate length by summing linear segments - this.getLength = function (nSubDivisions) { + 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; + 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 + // first point has 0 length - chunkLengths[0] = 0; + chunkLengths[ 0 ] = 0; - if (!nSubDivisions) nSubDivisions = 100; + if ( ! nSubDivisions ) nSubDivisions = 100; - nSamples = this.points.length * nSubDivisions; + nSamples = this.points.length * nSubDivisions; - oldPosition.copy(this.points[0]); + oldPosition.copy( this.points[ 0 ] ); - for (i = 1; i < nSamples; i++) { + for ( i = 1; i < nSamples; i ++ ) { - index = i / nSamples; + index = i / nSamples; - position = this.getPoint(index); - tmpVec.copy(position); + position = this.getPoint( index ); + tmpVec.copy( position ); - totalLength += tmpVec.distanceTo(oldPosition); + totalLength += tmpVec.distanceTo( oldPosition ); - oldPosition.copy(position); + oldPosition.copy( position ); - point = ( this.points.length - 1 ) * index; - intPoint = Math.floor(point); + point = ( this.points.length - 1 ) * index; + intPoint = Math.floor( point ); - if (intPoint != oldIntPoint) { + if ( intPoint !== oldIntPoint ) { - chunkLengths[intPoint] = totalLength; - oldIntPoint = intPoint; + chunkLengths[ intPoint ] = totalLength; + oldIntPoint = intPoint; - } + } - } + } - // last point ends with total length + // last point ends with total length - chunkLengths[chunkLengths.length] = totalLength; + chunkLengths[ chunkLengths.length ] = totalLength; - return {chunks: chunkLengths, total: totalLength}; + return { chunks: chunkLengths, total: totalLength }; - }; + }; - this.reparametrizeByArcLength = function (samplingCoef) { + this.reparametrizeByArcLength = function ( samplingCoef ) { - var i, j, - index, indexCurrent, indexNext, - realDistance, - sampling, position, - newpoints = [], - tmpVec = new THREE.Vector3(), - sl = this.getLength(); + var i, j, + index, indexCurrent, indexNext, + realDistance, + sampling, position, + newpoints = [], + tmpVec = new THREE.Vector3(), + sl = this.getLength(); - newpoints.push(tmpVec.copy(this.points[0]).clone()); + newpoints.push( tmpVec.copy( this.points[ 0 ] ).clone() ); - for (i = 1; i < this.points.length; i++) { + for ( i = 1; i < this.points.length; i ++ ) { - //tmpVec.copy( this.points[ i - 1 ] ); - //linearDistance = tmpVec.distanceTo( this.points[ i ] ); + //tmpVec.copy( this.points[ i - 1 ] ); + //linearDistance = tmpVec.distanceTo( this.points[ i ] ); - realDistance = sl.chunks[i] - sl.chunks[i - 1]; + realDistance = sl.chunks[ i ] - sl.chunks[ i - 1 ]; - sampling = Math.ceil(samplingCoef * realDistance / sl.total); + sampling = Math.ceil( samplingCoef * realDistance / sl.total ); - indexCurrent = ( i - 1 ) / ( this.points.length - 1 ); - indexNext = i / ( this.points.length - 1 ); + indexCurrent = ( i - 1 ) / ( this.points.length - 1 ); + indexNext = i / ( this.points.length - 1 ); - for (j = 1; j < sampling - 1; j++) { + for ( j = 1; j < sampling - 1; j ++ ) { - index = indexCurrent + j * ( 1 / sampling ) * ( indexNext - indexCurrent ); + index = indexCurrent + j * ( 1 / sampling ) * ( indexNext - indexCurrent ); - position = this.getPoint(index); - newpoints.push(tmpVec.copy(position).clone()); + position = this.getPoint( index ); + newpoints.push( tmpVec.copy( position ).clone() ); - } + } - newpoints.push(tmpVec.copy(this.points[i]).clone()); + newpoints.push( tmpVec.copy( this.points[ i ] ).clone() ); - } + } - this.points = newpoints; + this.points = newpoints; - }; + }; - // Catmull-Rom + // Catmull-Rom - function interpolate(p0, p1, p2, p3, t, t2, t3) { + function interpolate( p0, p1, p2, p3, t, t2, t3 ) { - var v0 = ( p2 - p0 ) * 0.5, - v1 = ( p3 - p1 ) * 0.5; + 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 ( 2 * ( p1 - p2 ) + v0 + v1 ) * t3 + ( - 3 * ( p1 - p2 ) - 2 * v0 - v1 ) * t2 + v0 * t + p1; - } + } }; // File:src/math/Triangle.js /** - * @author bhouston / http://exocortex.com + * @author bhouston / http://clara.io * @author mrdoob / http://mrdoob.com/ */ -THREE.Triangle = function (a, b, c) { +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(); + 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(); + var v0 = new THREE.Vector3(); - return function (a, b, c, optionalTarget) { + return function ( a, b, c, optionalTarget ) { - var result = optionalTarget || new THREE.Vector3(); + var result = optionalTarget || new THREE.Vector3(); - result.subVectors(c, b); - v0.subVectors(a, b); - result.cross(v0); + result.subVectors( c, b ); + v0.subVectors( a, b ); + result.cross( v0 ); - var resultLengthSq = result.lengthSq(); - if (resultLengthSq > 0) { + var resultLengthSq = result.lengthSq(); + if ( resultLengthSq > 0 ) { - return result.multiplyScalar(1 / Math.sqrt(resultLengthSq)); + return result.multiplyScalar( 1 / Math.sqrt( resultLengthSq ) ); - } + } - return result.set(0, 0, 0); + return result.set( 0, 0, 0 ); - }; + }; }(); -// static/instance method to calculate barycoordinates +// static/instance method to calculate barycentric coordinates // 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(); + var v0 = new THREE.Vector3(); + var v1 = new THREE.Vector3(); + var v2 = new THREE.Vector3(); - return function (point, a, b, c, optionalTarget) { + return function ( point, a, b, c, optionalTarget ) { - v0.subVectors(c, a); - v1.subVectors(b, a); - v2.subVectors(point, a); + 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 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 denom = ( dot00 * dot11 - dot01 * dot01 ); - var result = optionalTarget || new THREE.Vector3(); + 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); - } + // collinear or singular triangle + if ( denom === 0 ) { - var invDenom = 1 / denom; - var u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom; - var v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom; + // arbitrary location outside of triangle? + // not sure if this is the best idea, maybe should be returning undefined + return result.set( - 2, - 1, - 1 ); - // barycoordinates must always sum to 1 - return result.set(1 - u - v, v, u); + } - }; + var invDenom = 1 / denom; + var u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom; + var v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom; + + // barycentric coordinates must always sum to 1 + return result.set( 1 - u - v, v, u ); + + }; }(); THREE.Triangle.containsPoint = function () { - var v1 = new THREE.Vector3(); + var v1 = new THREE.Vector3(); - return function (point, a, b, c) { + return function ( point, a, b, c ) { - var result = THREE.Triangle.barycoordFromPoint(point, a, b, c, v1); + var result = THREE.Triangle.barycoordFromPoint( point, a, b, c, v1 ); - return ( result.x >= 0 ) && ( result.y >= 0 ) && ( ( result.x + result.y ) <= 1 ); + return ( result.x >= 0 ) && ( result.y >= 0 ) && ( ( result.x + result.y ) <= 1 ); - }; + }; }(); THREE.Triangle.prototype = { - constructor: THREE.Triangle, + constructor: THREE.Triangle, - set: function (a, b, c) { + set: function ( a, b, c ) { - this.a.copy(a); - this.b.copy(b); - this.c.copy(c); + this.a.copy( a ); + this.b.copy( b ); + this.c.copy( c ); - return this; + return this; - }, + }, - setFromPointsAndIndices: function (points, i0, i1, i2) { + setFromPointsAndIndices: function ( points, i0, i1, i2 ) { - this.a.copy(points[i0]); - this.b.copy(points[i1]); - this.c.copy(points[i2]); + this.a.copy( points[ i0 ] ); + this.b.copy( points[ i1 ] ); + this.c.copy( points[ i2 ] ); - return this; + return this; - }, + }, - copy: function (triangle) { + clone: function () { - this.a.copy(triangle.a); - this.b.copy(triangle.b); - this.c.copy(triangle.c); + return new this.constructor().copy( this ); - return this; + }, - }, + copy: function ( triangle ) { - area: function () { + this.a.copy( triangle.a ); + this.b.copy( triangle.b ); + this.c.copy( triangle.c ); - var v0 = new THREE.Vector3(); - var v1 = new THREE.Vector3(); + return this; - return function () { + }, - v0.subVectors(this.c, this.b); - v1.subVectors(this.a, this.b); + area: function () { - return v0.cross(v1).length() * 0.5; + var v0 = new THREE.Vector3(); + var v1 = new THREE.Vector3(); - }; + return function () { - }(), + v0.subVectors( this.c, this.b ); + v1.subVectors( this.a, this.b ); - midpoint: function (optionalTarget) { + return v0.cross( v1 ).length() * 0.5; - var result = optionalTarget || new THREE.Vector3(); - return result.addVectors(this.a, this.b).add(this.c).multiplyScalar(1 / 3); + }; - }, + }(), - normal: function (optionalTarget) { + midpoint: function ( optionalTarget ) { - return THREE.Triangle.normal(this.a, this.b, this.c, optionalTarget); + var result = optionalTarget || new THREE.Vector3(); + return result.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 ); - }, + }, - plane: function (optionalTarget) { + normal: function ( optionalTarget ) { - var result = optionalTarget || new THREE.Plane(); + return THREE.Triangle.normal( this.a, this.b, this.c, optionalTarget ); - return result.setFromCoplanarPoints(this.a, this.b, this.c); + }, - }, + plane: function ( optionalTarget ) { - barycoordFromPoint: function (point, optionalTarget) { + var result = optionalTarget || new THREE.Plane(); - return THREE.Triangle.barycoordFromPoint(point, this.a, this.b, this.c, optionalTarget); + return result.setFromCoplanarPoints( this.a, this.b, this.c ); - }, + }, - containsPoint: function (point) { + barycoordFromPoint: function ( point, optionalTarget ) { - return THREE.Triangle.containsPoint(point, this.a, this.b, this.c); + return THREE.Triangle.barycoordFromPoint( point, this.a, this.b, this.c, optionalTarget ); - }, + }, - equals: function (triangle) { + containsPoint: function ( point ) { - return triangle.a.equals(this.a) && triangle.b.equals(this.b) && triangle.c.equals(this.c); + return THREE.Triangle.containsPoint( point, this.a, this.b, this.c ); - }, + }, - clone: function () { + equals: function ( triangle ) { - return new THREE.Triangle().copy(this); + return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c ); - } + } }; @@ -7464,72 +7453,77 @@ THREE.Triangle.prototype = { * @author alteredq / http://alteredqualia.com/ */ -THREE.Clock = function (autoStart) { +THREE.Clock = function ( autoStart ) { - this.autoStart = ( autoStart !== undefined ) ? autoStart : true; + this.autoStart = ( autoStart !== undefined ) ? autoStart : true; - this.startTime = 0; - this.oldTime = 0; - this.elapsedTime = 0; + this.startTime = 0; + this.oldTime = 0; + this.elapsedTime = 0; - this.running = false; + this.running = false; }; THREE.Clock.prototype = { - constructor: THREE.Clock, + constructor: THREE.Clock, - start: function () { + _now: function () { - this.startTime = self.performance !== undefined && self.performance.now !== undefined - ? self.performance.now() - : Date.now(); + return self.performance !== undefined && self.performance.now !== undefined + ? self.performance.now() + : Date.now(); - this.oldTime = this.startTime; - this.running = true; - }, + }, - stop: function () { + start: function () { - this.getElapsedTime(); - this.running = false; + this.startTime = this._now(); - }, + this.oldTime = this.startTime; + this.running = true; - getElapsedTime: function () { + }, - this.getDelta(); - return this.elapsedTime; + stop: function () { - }, + this.getElapsedTime(); + this.running = false; - getDelta: function () { + }, - var diff = 0; + getElapsedTime: function () { - if (this.autoStart && !this.running) { + this.getDelta(); + return this.elapsedTime; - this.start(); + }, - } + getDelta: function () { - if (this.running) { + var diff = 0; - var newTime = self.performance !== undefined && self.performance.now !== undefined - ? self.performance.now() - : Date.now(); + if ( this.autoStart && ! this.running ) { - diff = 0.001 * ( newTime - this.oldTime ); - this.oldTime = newTime; + this.start(); - this.elapsedTime += diff; + } - } + if ( this.running ) { - return diff; + var newTime = this._now(); - } + diff = 0.001 * ( newTime - this.oldTime ); + this.oldTime = newTime; + + this.elapsedTime += diff; + + } + + return diff; + + } }; @@ -7539,108 +7533,107 @@ THREE.Clock.prototype = { * https://github.com/mrdoob/eventdispatcher.js/ */ -THREE.EventDispatcher = function () { -}; +THREE.EventDispatcher = function () {}; THREE.EventDispatcher.prototype = { - constructor: THREE.EventDispatcher, + constructor: THREE.EventDispatcher, - apply: function (object) { + 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; + 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) { + addEventListener: function ( type, listener ) { - if (this._listeners === undefined) this._listeners = {}; + if ( this._listeners === undefined ) this._listeners = {}; - var listeners = this._listeners; + var listeners = this._listeners; - if (listeners[type] === undefined) { + if ( listeners[ type ] === undefined ) { - listeners[type] = []; + listeners[ type ] = []; - } + } - if (listeners[type].indexOf(listener) === -1) { + if ( listeners[ type ].indexOf( listener ) === - 1 ) { - listeners[type].push(listener); + listeners[ type ].push( listener ); - } + } - }, + }, - hasEventListener: function (type, listener) { + hasEventListener: function ( type, listener ) { - if (this._listeners === undefined) return false; + if ( this._listeners === undefined ) return false; - var listeners = this._listeners; + var listeners = this._listeners; - if (listeners[type] !== undefined && listeners[type].indexOf(listener) !== -1) { + if ( listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1 ) { - return true; + return true; - } + } - return false; + return false; - }, + }, - removeEventListener: function (type, listener) { + removeEventListener: function ( type, listener ) { - if (this._listeners === undefined) return; + if ( this._listeners === undefined ) return; - var listeners = this._listeners; - var listenerArray = listeners[type]; + var listeners = this._listeners; + var listenerArray = listeners[ type ]; - if (listenerArray !== undefined) { + if ( listenerArray !== undefined ) { - var index = listenerArray.indexOf(listener); + var index = listenerArray.indexOf( listener ); - if (index !== -1) { + if ( index !== - 1 ) { - listenerArray.splice(index, 1); + listenerArray.splice( index, 1 ); - } + } - } + } - }, + }, - dispatchEvent: function (event) { + dispatchEvent: function ( event ) { - if (this._listeners === undefined) return; + if ( this._listeners === undefined ) return; - var listeners = this._listeners; - var listenerArray = listeners[event.type]; + var listeners = this._listeners; + var listenerArray = listeners[ event.type ]; - if (listenerArray !== undefined) { + if ( listenerArray !== undefined ) { - event.target = this; + event.target = this; - var array = []; - var length = listenerArray.length; + var array = []; + var length = listenerArray.length; - for (var i = 0; i < length; i++) { + for ( var i = 0; i < length; i ++ ) { - array[i] = listenerArray[i]; + array[ i ] = listenerArray[ i ]; - } + } - for (var i = 0; i < length; i++) { + for ( var i = 0; i < length; i ++ ) { - array[i].call(this, event); + array[ i ].call( this, event ); - } + } - } + } - } + } }; @@ -7648,129 +7641,139 @@ THREE.EventDispatcher.prototype = { /** * @author mrdoob / http://mrdoob.com/ - * @author bhouston / http://exocortex.com/ + * @author bhouston / http://clara.io/ * @author stephomi / http://stephaneginier.com/ */ -( function (THREE) { +( 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) - THREE.Raycaster = function (origin, direction, near, far) { + this.near = near || 0; + this.far = far || Infinity; - this.ray = new THREE.Ray(origin, direction); - // direction is assumed to be normalized (for accurate distance calculations) + this.params = { + Mesh: {}, + Line: {}, + LOD: {}, + Points: { threshold: 1 }, + Sprite: {} + }; - this.near = near || 0; - this.far = far || Infinity; + Object.defineProperties( this.params, { + PointCloud: { + get: function () { + console.warn( 'THREE.Raycaster: params.PointCloud has been renamed to params.Points.' ); + return this.Points; + } + } + } ); - this.params = { - Sprite: {}, - Mesh: {}, - PointCloud: {threshold: 1}, - LOD: {}, - Line: {} - }; + }; - }; + function descSort( a, b ) { - var descSort = function (a, b) { + return a.distance - b.distance; - return a.distance - b.distance; + } - }; + var intersectObject = function ( object, raycaster, intersects, recursive ) { - var intersectObject = function (object, raycaster, intersects, recursive) { + if ( object.visible === false ) return; - object.raycast(raycaster, intersects); + object.raycast( raycaster, intersects ); - if (recursive === true) { + if ( recursive === true ) { - var children = object.children; + var children = object.children; - for (var i = 0, l = children.length; i < l; i++) { + for ( var i = 0, l = children.length; i < l; i ++ ) { - intersectObject(children[i], raycaster, intersects, true); + intersectObject( children[ i ], raycaster, intersects, true ); - } + } - } + } - }; + }; - // + // - THREE.Raycaster.prototype = { + THREE.Raycaster.prototype = { - constructor: THREE.Raycaster, + constructor: THREE.Raycaster, - precision: 0.0001, - linePrecision: 1, + linePrecision: 1, - set: function (origin, direction) { + set: function ( origin, direction ) { - // direction is assumed to be normalized (for accurate distance calculations) + // direction is assumed to be normalized (for accurate distance calculations) - this.ray.set(origin, direction); + this.ray.set( origin, direction ); - }, + }, - setFromCamera: function (coords, camera) { + setFromCamera: function ( coords, camera ) { - if (camera instanceof THREE.PerspectiveCamera) { + if ( camera instanceof THREE.PerspectiveCamera ) { - this.ray.origin.setFromMatrixPosition(camera.matrixWorld); - this.ray.direction.set(coords.x, coords.y, 0.5).unproject(camera).sub(this.ray.origin).normalize(); + this.ray.origin.setFromMatrixPosition( camera.matrixWorld ); + this.ray.direction.set( coords.x, coords.y, 0.5 ).unproject( camera ).sub( this.ray.origin ).normalize(); - } else if (camera instanceof THREE.OrthographicCamera) { + } else if ( camera instanceof THREE.OrthographicCamera ) { - this.ray.origin.set(coords.x, coords.y, -1).unproject(camera); - this.ray.direction.set(0, 0, -1).transformDirection(camera.matrixWorld); + this.ray.origin.set( coords.x, coords.y, - 1 ).unproject( camera ); + this.ray.direction.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld ); - } else { + } else { - THREE.error('THREE.Raycaster: Unsupported camera type.'); + console.error( 'THREE.Raycaster: Unsupported camera type.' ); - } + } - }, + }, - intersectObject: function (object, recursive) { + intersectObject: function ( object, recursive ) { - var intersects = []; + var intersects = []; - intersectObject(object, this, intersects, recursive); + intersectObject( object, this, intersects, recursive ); - intersects.sort(descSort); + intersects.sort( descSort ); - return intersects; + return intersects; - }, + }, - intersectObjects: function (objects, recursive) { + intersectObjects: function ( objects, recursive ) { - var intersects = []; + var intersects = []; - if (objects instanceof Array === false) { + if ( Array.isArray( objects ) === false ) { - THREE.warn('THREE.Raycaster.intersectObjects: objects is not an Array.'); - return intersects; + console.warn( 'THREE.Raycaster.intersectObjects: objects is not an Array.' ); + return intersects; - } + } - for (var i = 0, l = objects.length; i < l; i++) { + for ( var i = 0, l = objects.length; i < l; i ++ ) { - intersectObject(objects[i], this, intersects, recursive); + intersectObject( objects[ i ], this, intersects, recursive ); - } + } - intersects.sort(descSort); + intersects.sort( descSort ); - return intersects; + return intersects; - } + } - }; + }; -}(THREE) ); +}( THREE ) ); // File:src/core/Object3D.js @@ -7779,705 +7782,734 @@ THREE.EventDispatcher.prototype = { * @author mikael emtinger / http://gomo.se/ * @author alteredq / http://alteredqualia.com/ * @author WestLangley / http://github.com/WestLangley + * @author elephantatwork / www.elephantatwork.ch */ THREE.Object3D = function () { - Object.defineProperty(this, 'id', {value: THREE.Object3DIdCount++}); + Object.defineProperty( this, 'id', { value: THREE.Object3DIdCount ++ } ); + + this.uuid = THREE.Math.generateUUID(); + + this.name = ''; + this.type = 'Object3D'; + + this.parent = null; + this.children = []; + + this.up = THREE.Object3D.DefaultUp.clone(); - this.uuid = THREE.Math.generateUUID(); + var position = new THREE.Vector3(); + var rotation = new THREE.Euler(); + var quaternion = new THREE.Quaternion(); + var scale = new THREE.Vector3( 1, 1, 1 ); - this.name = ''; - this.type = 'Object3D'; + var onRotationChange = function () { - this.parent = undefined; - this.children = []; + quaternion.setFromEuler( rotation, false ); - this.up = THREE.Object3D.DefaultUp.clone(); + }; - var position = new THREE.Vector3(); - var rotation = new THREE.Euler(); - var quaternion = new THREE.Quaternion(); - var scale = new THREE.Vector3(1, 1, 1); + var onQuaternionChange = function () { - var onRotationChange = function () { - quaternion.setFromEuler(rotation, false); - }; + rotation.setFromQuaternion( quaternion, undefined, false ); - var onQuaternionChange = function () { - rotation.setFromQuaternion(quaternion, undefined, false); - }; + }; - rotation.onChange(onRotationChange); - quaternion.onChange(onQuaternionChange); + rotation.onChange( onRotationChange ); + quaternion.onChange( onQuaternionChange ); - Object.defineProperties(this, { - position: { - enumerable: true, - value: position - }, - rotation: { - enumerable: true, - value: rotation - }, - quaternion: { - enumerable: true, - value: quaternion - }, - scale: { - enumerable: true, - value: scale - } - }); + Object.defineProperties( this, { + position: { + enumerable: true, + value: position + }, + rotation: { + enumerable: true, + value: rotation + }, + quaternion: { + enumerable: true, + value: quaternion + }, + scale: { + enumerable: true, + value: scale + }, + modelViewMatrix: { + value: new THREE.Matrix4() + }, + normalMatrix: { + value: new THREE.Matrix3() + } + } ); - this.rotationAutoUpdate = true; + this.rotationAutoUpdate = true; - this.matrix = new THREE.Matrix4(); - this.matrixWorld = new THREE.Matrix4(); + this.matrix = new THREE.Matrix4(); + this.matrixWorld = new THREE.Matrix4(); - this.matrixAutoUpdate = true; - this.matrixWorldNeedsUpdate = false; + this.matrixAutoUpdate = THREE.Object3D.DefaultMatrixAutoUpdate; + this.matrixWorldNeedsUpdate = false; - this.visible = true; + this.visible = true; - this.castShadow = false; - this.receiveShadow = false; + this.castShadow = false; + this.receiveShadow = false; - this.frustumCulled = true; - this.renderOrder = 0; + this.frustumCulled = true; + this.renderOrder = 0; - this.userData = {}; + this.userData = {}; }; -THREE.Object3D.DefaultUp = new THREE.Vector3(0, 1, 0); +THREE.Object3D.DefaultUp = new THREE.Vector3( 0, 1, 0 ); +THREE.Object3D.DefaultMatrixAutoUpdate = true; THREE.Object3D.prototype = { - constructor: THREE.Object3D, + constructor: THREE.Object3D, - get eulerOrder() { + get eulerOrder () { - THREE.warn('THREE.Object3D: .eulerOrder has been moved to .rotation.order.'); + console.warn( 'THREE.Object3D: .eulerOrder has been moved to .rotation.order.' ); - return this.rotation.order; + return this.rotation.order; - }, + }, - set eulerOrder(value) { + set eulerOrder ( value ) { - THREE.warn('THREE.Object3D: .eulerOrder has been moved to .rotation.order.'); + console.warn( 'THREE.Object3D: .eulerOrder has been moved to .rotation.order.' ); - this.rotation.order = value; + this.rotation.order = value; - }, + }, - get useQuaternion() { + get useQuaternion () { - THREE.warn('THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.'); + console.warn( 'THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.' ); - }, + }, - set useQuaternion(value) { + set useQuaternion ( value ) { - THREE.warn('THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.'); + console.warn( 'THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.' ); - }, + }, - applyMatrix: function (matrix) { + set renderDepth ( value ) { - this.matrix.multiplyMatrices(matrix, this.matrix); + console.warn( 'THREE.Object3D: .renderDepth has been removed. Use .renderOrder, instead.' ); - this.matrix.decompose(this.position, this.quaternion, this.scale); + }, - }, + applyMatrix: function ( matrix ) { - setRotationFromAxisAngle: function (axis, angle) { + this.matrix.multiplyMatrices( matrix, this.matrix ); - // assumes axis is normalized + this.matrix.decompose( this.position, this.quaternion, this.scale ); - this.quaternion.setFromAxisAngle(axis, angle); + }, - }, + setRotationFromAxisAngle: function ( axis, angle ) { - setRotationFromEuler: function (euler) { + // assumes axis is normalized - this.quaternion.setFromEuler(euler, true); + this.quaternion.setFromAxisAngle( axis, angle ); - }, + }, - setRotationFromMatrix: function (m) { + setRotationFromEuler: function ( euler ) { - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + this.quaternion.setFromEuler( euler, true ); - this.quaternion.setFromRotationMatrix(m); + }, - }, + setRotationFromMatrix: function ( m ) { - setRotationFromQuaternion: function (q) { + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - // assumes q is normalized + this.quaternion.setFromRotationMatrix( m ); - this.quaternion.copy(q); + }, - }, + setRotationFromQuaternion: function ( q ) { - rotateOnAxis: function () { + // assumes q is normalized - // rotate object on axis in object space - // axis is assumed to be normalized + this.quaternion.copy( q ); - var q1 = new THREE.Quaternion(); + }, - return function (axis, angle) { + rotateOnAxis: function () { - q1.setFromAxisAngle(axis, angle); + // rotate object on axis in object space + // axis is assumed to be normalized - this.quaternion.multiply(q1); + var q1 = new THREE.Quaternion(); - return this; + return function ( axis, angle ) { - } + q1.setFromAxisAngle( axis, angle ); - }(), + this.quaternion.multiply( q1 ); - rotateX: function () { + return this; - var v1 = new THREE.Vector3(1, 0, 0); + }; - return function (angle) { + }(), - return this.rotateOnAxis(v1, angle); + rotateX: function () { - }; + var v1 = new THREE.Vector3( 1, 0, 0 ); - }(), + return function ( angle ) { - rotateY: function () { + return this.rotateOnAxis( v1, angle ); - var v1 = new THREE.Vector3(0, 1, 0); + }; - return function (angle) { + }(), - return this.rotateOnAxis(v1, angle); + rotateY: function () { - }; + var v1 = new THREE.Vector3( 0, 1, 0 ); - }(), + return function ( angle ) { - rotateZ: function () { + return this.rotateOnAxis( v1, angle ); - var v1 = new THREE.Vector3(0, 0, 1); + }; - return function (angle) { + }(), - return this.rotateOnAxis(v1, angle); + rotateZ: function () { - }; + var v1 = new THREE.Vector3( 0, 0, 1 ); - }(), + return function ( angle ) { - translateOnAxis: function () { + return this.rotateOnAxis( v1, angle ); - // translate object by distance along axis in object space - // axis is assumed to be normalized + }; - var v1 = new THREE.Vector3(); + }(), - return function (axis, distance) { + translateOnAxis: function () { - v1.copy(axis).applyQuaternion(this.quaternion); + // translate object by distance along axis in object space + // axis is assumed to be normalized - this.position.add(v1.multiplyScalar(distance)); + var v1 = new THREE.Vector3(); - return this; + return function ( axis, distance ) { - } + v1.copy( axis ).applyQuaternion( this.quaternion ); - }(), + this.position.add( v1.multiplyScalar( distance ) ); - translate: function (distance, axis) { + return this; - THREE.warn('THREE.Object3D: .translate() has been removed. Use .translateOnAxis( axis, distance ) instead.'); - return this.translateOnAxis(axis, distance); + }; - }, + }(), - translateX: function () { + translate: function ( distance, axis ) { - var v1 = new THREE.Vector3(1, 0, 0); + console.warn( 'THREE.Object3D: .translate() has been removed. Use .translateOnAxis( axis, distance ) instead.' ); + return this.translateOnAxis( axis, distance ); - return function (distance) { + }, - return this.translateOnAxis(v1, distance); + translateX: function () { - }; + var v1 = new THREE.Vector3( 1, 0, 0 ); - }(), + return function ( distance ) { - translateY: function () { + return this.translateOnAxis( v1, distance ); - var v1 = new THREE.Vector3(0, 1, 0); + }; - return function (distance) { + }(), - return this.translateOnAxis(v1, distance); + translateY: function () { - }; + var v1 = new THREE.Vector3( 0, 1, 0 ); - }(), + return function ( distance ) { - translateZ: function () { + return this.translateOnAxis( v1, distance ); - var v1 = new THREE.Vector3(0, 0, 1); + }; - return function (distance) { + }(), - return this.translateOnAxis(v1, distance); + translateZ: function () { - }; + var v1 = new THREE.Vector3( 0, 0, 1 ); - }(), + return function ( distance ) { - localToWorld: function (vector) { + return this.translateOnAxis( v1, distance ); - return vector.applyMatrix4(this.matrixWorld); + }; - }, + }(), - worldToLocal: function () { + localToWorld: function ( vector ) { - var m1 = new THREE.Matrix4(); + return vector.applyMatrix4( this.matrixWorld ); - return function (vector) { + }, - return vector.applyMatrix4(m1.getInverse(this.matrixWorld)); + worldToLocal: function () { - }; + var m1 = new THREE.Matrix4(); - }(), + return function ( vector ) { - lookAt: function () { + return vector.applyMatrix4( m1.getInverse( this.matrixWorld ) ); - // This routine does not support objects with rotated and/or translated parent(s) + }; - var m1 = new THREE.Matrix4(); + }(), - return function (vector) { + lookAt: function () { - m1.lookAt(vector, this.position, this.up); + // This routine does not support objects with rotated and/or translated parent(s) - this.quaternion.setFromRotationMatrix(m1); + var m1 = new THREE.Matrix4(); - }; + return function ( vector ) { - }(), + m1.lookAt( vector, this.position, this.up ); - add: function (object) { + this.quaternion.setFromRotationMatrix( m1 ); - if (arguments.length > 1) { + }; - for (var i = 0; i < arguments.length; i++) { + }(), - this.add(arguments[i]); + add: function ( object ) { - } + if ( arguments.length > 1 ) { - return this; + for ( var i = 0; i < arguments.length; i ++ ) { - } + this.add( arguments[ i ] ); - if (object === this) { + } - THREE.error("THREE.Object3D.add: object can't be added as a child of itself.", object); - return this; + return this; - } + } - if (object instanceof THREE.Object3D) { + if ( object === this ) { - if (object.parent !== undefined) { + console.error( "THREE.Object3D.add: object can't be added as a child of itself.", object ); + return this; - object.parent.remove(object); + } - } + if ( object instanceof THREE.Object3D ) { - object.parent = this; - object.dispatchEvent({type: 'added'}); + if ( object.parent !== null ) { - this.children.push(object); + object.parent.remove( object ); - } else { + } - THREE.error("THREE.Object3D.add: object not an instance of THREE.Object3D.", object); + object.parent = this; + object.dispatchEvent( { type: 'added' } ); - } + this.children.push( object ); - return this; + } else { - }, + console.error( "THREE.Object3D.add: object not an instance of THREE.Object3D.", object ); - remove: function (object) { + } - if (arguments.length > 1) { + return this; - for (var i = 0; i < arguments.length; i++) { + }, - this.remove(arguments[i]); + remove: function ( object ) { - } + if ( arguments.length > 1 ) { - } + for ( var i = 0; i < arguments.length; i ++ ) { - var index = this.children.indexOf(object); + this.remove( arguments[ i ] ); - if (index !== -1) { + } - object.parent = undefined; + } - object.dispatchEvent({type: 'removed'}); + var index = this.children.indexOf( object ); - this.children.splice(index, 1); + if ( index !== - 1 ) { - } + object.parent = null; - }, + object.dispatchEvent( { type: 'removed' } ); - getChildByName: function (name) { + this.children.splice( index, 1 ); - THREE.warn('THREE.Object3D: .getChildByName() has been renamed to .getObjectByName().'); - return this.getObjectByName(name); + } - }, + }, - getObjectById: function (id) { + getChildByName: function ( name ) { - return this.getObjectByProperty('id', id); + console.warn( 'THREE.Object3D: .getChildByName() has been renamed to .getObjectByName().' ); + return this.getObjectByName( name ); - }, + }, - getObjectByName: function (name) { + getObjectById: function ( id ) { - return this.getObjectByProperty('name', name); + return this.getObjectByProperty( 'id', id ); - }, + }, - getObjectByProperty: function (name, value) { + getObjectByName: function ( name ) { - if (this[name] === value) return this; + return this.getObjectByProperty( 'name', name ); - for (var i = 0, l = this.children.length; i < l; i++) { + }, - var child = this.children[i]; - var object = child.getObjectByProperty(name, value); + getObjectByProperty: function ( name, value ) { - if (object !== undefined) { + if ( this[ name ] === value ) return this; - return object; + for ( var i = 0, l = this.children.length; i < l; i ++ ) { - } + var child = this.children[ i ]; + var object = child.getObjectByProperty( name, value ); - } + if ( object !== undefined ) { - return undefined; + return object; - }, + } - getWorldPosition: function (optionalTarget) { + } - var result = optionalTarget || new THREE.Vector3(); + return undefined; - this.updateMatrixWorld(true); + }, - return result.setFromMatrixPosition(this.matrixWorld); + getWorldPosition: function ( optionalTarget ) { - }, + var result = optionalTarget || new THREE.Vector3(); - getWorldQuaternion: function () { + this.updateMatrixWorld( true ); - var position = new THREE.Vector3(); - var scale = new THREE.Vector3(); + return result.setFromMatrixPosition( this.matrixWorld ); - return function (optionalTarget) { + }, - var result = optionalTarget || new THREE.Quaternion(); + getWorldQuaternion: function () { - this.updateMatrixWorld(true); + var position = new THREE.Vector3(); + var scale = new THREE.Vector3(); - this.matrixWorld.decompose(position, result, scale); + return function ( optionalTarget ) { - return result; + var result = optionalTarget || new THREE.Quaternion(); - } + this.updateMatrixWorld( true ); - }(), + this.matrixWorld.decompose( position, result, scale ); - getWorldRotation: function () { + return result; - var quaternion = new THREE.Quaternion(); + }; - return function (optionalTarget) { + }(), - var result = optionalTarget || new THREE.Euler(); + getWorldRotation: function () { - this.getWorldQuaternion(quaternion); + var quaternion = new THREE.Quaternion(); - return result.setFromQuaternion(quaternion, this.rotation.order, false); + return function ( optionalTarget ) { - } + var result = optionalTarget || new THREE.Euler(); - }(), + this.getWorldQuaternion( quaternion ); - getWorldScale: function () { + return result.setFromQuaternion( quaternion, this.rotation.order, false ); - var position = new THREE.Vector3(); - var quaternion = new THREE.Quaternion(); + }; - return function (optionalTarget) { + }(), - var result = optionalTarget || new THREE.Vector3(); + getWorldScale: function () { - this.updateMatrixWorld(true); + var position = new THREE.Vector3(); + var quaternion = new THREE.Quaternion(); - this.matrixWorld.decompose(position, quaternion, result); + return function ( optionalTarget ) { - return result; + var result = optionalTarget || new THREE.Vector3(); - } + this.updateMatrixWorld( true ); - }(), + this.matrixWorld.decompose( position, quaternion, result ); - getWorldDirection: function () { + return result; - var quaternion = new THREE.Quaternion(); + }; - return function (optionalTarget) { + }(), - var result = optionalTarget || new THREE.Vector3(); + getWorldDirection: function () { - this.getWorldQuaternion(quaternion); + var quaternion = new THREE.Quaternion(); - return result.set(0, 0, 1).applyQuaternion(quaternion); + return function ( optionalTarget ) { - } + var result = optionalTarget || new THREE.Vector3(); - }(), + this.getWorldQuaternion( quaternion ); - raycast: function () { - }, + return result.set( 0, 0, 1 ).applyQuaternion( quaternion ); - traverse: function (callback) { + }; - callback(this); + }(), - for (var i = 0, l = this.children.length; i < l; i++) { + raycast: function () {}, - this.children[i].traverse(callback); + traverse: function ( callback ) { - } + callback( this ); - }, + var children = this.children; - traverseVisible: function (callback) { + for ( var i = 0, l = children.length; i < l; i ++ ) { - if (this.visible === false) return; + children[ i ].traverse( callback ); - callback(this); + } - for (var i = 0, l = this.children.length; i < l; i++) { + }, - this.children[i].traverseVisible(callback); + traverseVisible: function ( callback ) { - } + if ( this.visible === false ) return; - }, + callback( this ); - traverseAncestors: function (callback) { + var children = this.children; - if (this.parent) { + for ( var i = 0, l = children.length; i < l; i ++ ) { - callback(this.parent); + children[ i ].traverseVisible( callback ); - this.parent.traverseAncestors(callback); + } - } + }, - }, + traverseAncestors: function ( callback ) { - updateMatrix: function () { + var parent = this.parent; - this.matrix.compose(this.position, this.quaternion, this.scale); + if ( parent !== null ) { - this.matrixWorldNeedsUpdate = true; + callback( parent ); - }, + parent.traverseAncestors( callback ); - updateMatrixWorld: function (force) { + } - if (this.matrixAutoUpdate === true) this.updateMatrix(); + }, - if (this.matrixWorldNeedsUpdate === true || force === true) { + updateMatrix: function () { - if (this.parent === undefined) { + this.matrix.compose( this.position, this.quaternion, this.scale ); - this.matrixWorld.copy(this.matrix); + this.matrixWorldNeedsUpdate = true; - } else { + }, - this.matrixWorld.multiplyMatrices(this.parent.matrixWorld, this.matrix); + updateMatrixWorld: function ( force ) { - } + if ( this.matrixAutoUpdate === true ) this.updateMatrix(); - this.matrixWorldNeedsUpdate = false; + if ( this.matrixWorldNeedsUpdate === true || force === true ) { - force = true; + if ( this.parent === null ) { - } + this.matrixWorld.copy( this.matrix ); - // update children + } else { - for (var i = 0, l = this.children.length; i < l; i++) { + this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); - this.children[i].updateMatrixWorld(force); + } - } + this.matrixWorldNeedsUpdate = false; - }, + force = true; - toJSON: function (meta) { + } - var isRootObject = ( meta === undefined ); + // update children - // we will store all serialization data on 'data' - var data = {}; - var metadata; + for ( var i = 0, l = this.children.length; i < l; i ++ ) { - // meta is a hash used to collect geometries, materials. - // not providing it implies that this is the root object - // being serialized. - if (isRootObject) { + this.children[ i ].updateMatrixWorld( force ); - // initialize meta obj - meta = { - geometries: {}, - materials: {} - }; + } - // add metadata - metadata = { - version: 4.4, - type: 'Object', - generator: 'Object3D.toJSON' - } + }, - } + toJSON: function ( meta ) { - // standard Object3D serialization + var isRootObject = ( meta === undefined ); - data.type = this.type; - data.uuid = this.uuid; - if (this.name !== '') data.name = this.name; - if (JSON.stringify(this.userData) !== '{}') data.userData = this.userData; - if (this.visible !== true) data.visible = this.visible; + var data = {}; + var output = { object: data }; - data.matrix = this.matrix.toArray(); + // meta is a hash used to collect geometries, materials. + // not providing it implies that this is the root object + // being serialized. + if ( isRootObject ) { - if (this.children.length > 0) { + // initialize meta obj + meta = { + geometries: {}, + materials: {}, + textures: {}, + images: {} + }; - data.children = []; + output.metadata = { + version: 4.4, + type: 'Object', + generator: 'Object3D.toJSON' + }; - for (var i = 0; i < this.children.length; i++) { + } - data.children.push(this.children[i].toJSON(meta).object); + // standard Object3D serialization - } + data.uuid = this.uuid; + data.type = this.type; - } + if ( this.name !== '' ) data.name = this.name; + if ( JSON.stringify( this.userData ) !== '{}' ) data.userData = this.userData; + if ( this.visible !== true ) data.visible = this.visible; - // wrap serialized object with additional data + data.matrix = this.matrix.toArray(); - var output; + if ( this.children.length > 0 ) { - if (isRootObject) { + data.children = []; - output = { - metadata: metadata, - geometries: extractFromCache(meta.geometries), - materials: extractFromCache(meta.materials), - object: data - }; + for ( var i = 0; i < this.children.length; i ++ ) { - } else { + data.children.push( this.children[ i ].toJSON( meta ).object ); - output = {object: data}; + } - } + } - return output; + if ( isRootObject ) { - // extract data from the cache hash - // remove metadata on each item - // and return as array - function extractFromCache(cache) { - var values = []; - for (var key in cache) { - var data = cache[key]; - delete data.metadata; - values.push(data); - } - return values; - } + var geometries = extractFromCache( meta.geometries ); + var materials = extractFromCache( meta.materials ); + var textures = extractFromCache( meta.textures ); + var images = extractFromCache( meta.images ); - }, + if ( geometries.length > 0 ) output.geometries = geometries; + if ( materials.length > 0 ) output.materials = materials; + if ( textures.length > 0 ) output.textures = textures; + if ( images.length > 0 ) output.images = images; - clone: function (object, recursive) { + } - if (object === undefined) object = new THREE.Object3D(); - if (recursive === undefined) recursive = true; + return output; - object.name = this.name; + // extract data from the cache hash + // remove metadata on each item + // and return as array + function extractFromCache ( cache ) { - object.up.copy(this.up); + var values = []; + for ( var key in cache ) { - object.position.copy(this.position); - object.quaternion.copy(this.quaternion); - object.scale.copy(this.scale); + var data = cache[ key ]; + delete data.metadata; + values.push( data ); - object.rotationAutoUpdate = this.rotationAutoUpdate; + } + return values; - object.matrix.copy(this.matrix); - object.matrixWorld.copy(this.matrixWorld); + } - object.matrixAutoUpdate = this.matrixAutoUpdate; - object.matrixWorldNeedsUpdate = this.matrixWorldNeedsUpdate; + }, - object.visible = this.visible; + clone: function ( recursive ) { - object.castShadow = this.castShadow; - object.receiveShadow = this.receiveShadow; + return new this.constructor().copy( this, recursive ); - object.frustumCulled = this.frustumCulled; + }, - object.userData = JSON.parse(JSON.stringify(this.userData)); + copy: function ( source, recursive ) { - if (recursive === true) { + if ( recursive === undefined ) recursive = true; - for (var i = 0; i < this.children.length; i++) { + this.name = source.name; - var child = this.children[i]; - object.add(child.clone()); + this.up.copy( source.up ); - } + this.position.copy( source.position ); + this.quaternion.copy( source.quaternion ); + this.scale.copy( source.scale ); - } + this.rotationAutoUpdate = source.rotationAutoUpdate; - return object; + this.matrix.copy( source.matrix ); + this.matrixWorld.copy( source.matrixWorld ); - } + this.matrixAutoUpdate = source.matrixAutoUpdate; + this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate; + + this.visible = source.visible; + + this.castShadow = source.castShadow; + this.receiveShadow = source.receiveShadow; + + this.frustumCulled = source.frustumCulled; + this.renderOrder = source.renderOrder; + + this.userData = JSON.parse( JSON.stringify( source.userData ) ); + + if ( recursive === true ) { + + for ( var i = 0; i < source.children.length; i ++ ) { + + var child = source.children[ i ]; + this.add( child.clone() ); + + } + + } + + return this; + + } }; -THREE.EventDispatcher.prototype.apply(THREE.Object3D.prototype); +THREE.EventDispatcher.prototype.apply( THREE.Object3D.prototype ); THREE.Object3DIdCount = 0; @@ -8488,54 +8520,58 @@ THREE.Object3DIdCount = 0; * @author alteredq / http://alteredqualia.com/ */ -THREE.Face3 = function (a, b, c, normal, color) { +THREE.Face3 = function ( a, b, c, normal, color, materialIndex ) { - this.a = a; - this.b = b; - this.c = c; + 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.normal = normal instanceof THREE.Vector3 ? normal : new THREE.Vector3(); + this.vertexNormals = Array.isArray( normal ) ? normal : []; - this.color = color instanceof THREE.Color ? color : new THREE.Color(); - this.vertexColors = color instanceof Array ? color : []; + this.color = color instanceof THREE.Color ? color : new THREE.Color(); + this.vertexColors = Array.isArray( color ) ? color : []; - this.vertexTangents = []; + this.materialIndex = materialIndex !== undefined ? materialIndex : 0; }; THREE.Face3.prototype = { - constructor: THREE.Face3, + constructor: THREE.Face3, - clone: function () { + clone: function () { - var face = new THREE.Face3(this.a, this.b, this.c); + return new this.constructor().copy( this ); - face.normal.copy(this.normal); - face.color.copy(this.color); + }, - for (var i = 0, il = this.vertexNormals.length; i < il; i++) { + copy: function ( source ) { - face.vertexNormals[i] = this.vertexNormals[i].clone(); + this.a = source.a; + this.b = source.b; + this.c = source.c; - } + this.normal.copy( source.normal ); + this.color.copy( source.color ); - for (var i = 0, il = this.vertexColors.length; i < il; i++) { + this.materialIndex = source.materialIndex; - face.vertexColors[i] = this.vertexColors[i].clone(); + for ( var i = 0, il = source.vertexNormals.length; i < il; i ++ ) { - } + this.vertexNormals[ i ] = source.vertexNormals[ i ].clone(); - for (var i = 0, il = this.vertexTangents.length; i < il; i++) { + } - face.vertexTangents[i] = this.vertexTangents[i].clone(); + for ( var i = 0, il = source.vertexColors.length; i < il; i ++ ) { - } + this.vertexColors[ i ] = source.vertexColors[ i ].clone(); - return face; + } - } + return this; + + } }; @@ -8545,10 +8581,10 @@ THREE.Face3.prototype = { * @author mrdoob / http://mrdoob.com/ */ -THREE.Face4 = function (a, b, c, d, normal, color, materialIndex) { +THREE.Face4 = function ( a, b, c, d, normal, color, materialIndex ) { - THREE.warn('THREE.Face4 has been removed. A THREE.Face3 will be created instead.'); - return new THREE.Face3(a, b, c, 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 ); }; @@ -8558,337 +8594,376 @@ THREE.Face4 = function (a, b, c, d, normal, color, materialIndex) { * @author mrdoob / http://mrdoob.com/ */ -THREE.BufferAttribute = function (array, itemSize) { +THREE.BufferAttribute = function ( array, itemSize ) { + + this.uuid = THREE.Math.generateUUID(); + + this.array = array; + this.itemSize = itemSize; - this.array = array; - this.itemSize = itemSize; + this.dynamic = false; + this.updateRange = { offset: 0, count: - 1 }; - this.needsUpdate = false; + this.version = 0; }; THREE.BufferAttribute.prototype = { - constructor: THREE.BufferAttribute, + constructor: THREE.BufferAttribute, - get length() { + get length() { - return this.array.length; + console.warn( 'THREE.BufferAttribute: .length has been deprecated. Please use .count.' ); + return this.array.length; - }, + }, - copyAt: function (index1, attribute, index2) { + get count() { - index1 *= this.itemSize; - index2 *= attribute.itemSize; + return this.array.length / this.itemSize; - for (var i = 0, l = this.itemSize; i < l; i++) { + }, - this.array[index1 + i] = attribute.array[index2 + i]; + set needsUpdate( value ) { - } + if ( value === true ) this.version ++; - return this; + }, - }, + setDynamic: function ( value ) { - copyArray: function (array) { + this.dynamic = value; - this.array.set(array); + return this; - return this; + }, - }, + copy: function ( source ) { - copyColorsArray: function (colors) { + this.array = new source.array.constructor( source.array ); + this.itemSize = source.itemSize; - var array = this.array, offset = 0; + this.dynamic = source.dynamic; - for (var i = 0, l = colors.length; i < l; i++) { + return this; - var color = colors[i]; + }, - if (color === undefined) { + copyAt: function ( index1, attribute, index2 ) { - console.warn('THREE.BufferAttribute.copyColorsArray(): color is undefined', i); - color = new THREE.Color(); + index1 *= this.itemSize; + index2 *= attribute.itemSize; - } + for ( var i = 0, l = this.itemSize; i < l; i ++ ) { - array[offset++] = color.r; - array[offset++] = color.g; - array[offset++] = color.b; + this.array[ index1 + i ] = attribute.array[ index2 + i ]; - } + } - return this; + return this; - }, + }, - copyFacesArray: function (faces) { + copyArray: function ( array ) { - var array = this.array, offset = 0; + this.array.set( array ); - for (var i = 0, l = faces.length; i < l; i++) { + return this; - var face = faces[i]; + }, - array[offset++] = face.a; - array[offset++] = face.b; - array[offset++] = face.c; + copyColorsArray: function ( colors ) { - } + var array = this.array, offset = 0; - return this; + for ( var i = 0, l = colors.length; i < l; i ++ ) { - }, + var color = colors[ i ]; - copyVector2sArray: function (vectors) { + if ( color === undefined ) { - var array = this.array, offset = 0; + console.warn( 'THREE.BufferAttribute.copyColorsArray(): color is undefined', i ); + color = new THREE.Color(); - for (var i = 0, l = vectors.length; i < l; i++) { + } - var vector = vectors[i]; + array[ offset ++ ] = color.r; + array[ offset ++ ] = color.g; + array[ offset ++ ] = color.b; - if (vector === undefined) { + } - console.warn('THREE.BufferAttribute.copyVector2sArray(): vector is undefined', i); - vector = new THREE.Vector2(); + return this; - } + }, - array[offset++] = vector.x; - array[offset++] = vector.y; + copyIndicesArray: function ( indices ) { - } + var array = this.array, offset = 0; - return this; + for ( var i = 0, l = indices.length; i < l; i ++ ) { - }, + var index = indices[ i ]; - copyVector3sArray: function (vectors) { + array[ offset ++ ] = index.a; + array[ offset ++ ] = index.b; + array[ offset ++ ] = index.c; - var array = this.array, offset = 0; + } - for (var i = 0, l = vectors.length; i < l; i++) { + return this; - var vector = vectors[i]; + }, - if (vector === undefined) { + copyVector2sArray: function ( vectors ) { - console.warn('THREE.BufferAttribute.copyVector3sArray(): vector is undefined', i); - vector = new THREE.Vector3(); + var array = this.array, offset = 0; - } + for ( var i = 0, l = vectors.length; i < l; i ++ ) { - array[offset++] = vector.x; - array[offset++] = vector.y; - array[offset++] = vector.z; + var vector = vectors[ i ]; - } + if ( vector === undefined ) { - return this; + console.warn( 'THREE.BufferAttribute.copyVector2sArray(): vector is undefined', i ); + vector = new THREE.Vector2(); - }, + } - set: function (value, offset) { + array[ offset ++ ] = vector.x; + array[ offset ++ ] = vector.y; - if (offset === undefined) offset = 0; + } - this.array.set(value, offset); + return this; - return this; + }, - }, + copyVector3sArray: function ( vectors ) { - setX: function (index, x) { + var array = this.array, offset = 0; - this.array[index * this.itemSize] = x; + for ( var i = 0, l = vectors.length; i < l; i ++ ) { - return this; + var vector = vectors[ i ]; - }, + if ( vector === undefined ) { - setY: function (index, y) { + console.warn( 'THREE.BufferAttribute.copyVector3sArray(): vector is undefined', i ); + vector = new THREE.Vector3(); - this.array[index * this.itemSize + 1] = y; + } - return this; + array[ offset ++ ] = vector.x; + array[ offset ++ ] = vector.y; + array[ offset ++ ] = vector.z; - }, + } - setZ: function (index, z) { + return this; - this.array[index * this.itemSize + 2] = z; + }, - return this; + copyVector4sArray: function ( vectors ) { - }, + var array = this.array, offset = 0; - setW: function (index, w) { + for ( var i = 0, l = vectors.length; i < l; i ++ ) { - this.array[index * this.itemSize + 3] = w; + var vector = vectors[ i ]; - return this; + if ( vector === undefined ) { - }, + console.warn( 'THREE.BufferAttribute.copyVector4sArray(): vector is undefined', i ); + vector = new THREE.Vector4(); - getX: function (index) { + } - return this.array[index * this.itemSize]; + array[ offset ++ ] = vector.x; + array[ offset ++ ] = vector.y; + array[ offset ++ ] = vector.z; + array[ offset ++ ] = vector.w; - }, + } - getY: function (index) { + return this; - return this.array[index * this.itemSize + 1]; + }, - }, + set: function ( value, offset ) { - getZ: function (index) { + if ( offset === undefined ) offset = 0; - return this.array[index * this.itemSize + 2]; + this.array.set( value, offset ); - }, + return this; - getW: function (index) { + }, - return this.array[index * this.itemSize + 3]; + getX: function ( index ) { - }, + return this.array[ index * this.itemSize ]; - setXY: function (index, x, y) { + }, - index *= this.itemSize; + setX: function ( index, x ) { - this.array[index + 0] = x; - this.array[index + 1] = y; + this.array[ index * this.itemSize ] = x; - return this; + return this; - }, + }, - setXYZ: function (index, x, y, z) { + getY: function ( index ) { - index *= this.itemSize; + return this.array[ index * this.itemSize + 1 ]; - this.array[index + 0] = x; - this.array[index + 1] = y; - this.array[index + 2] = z; + }, - return this; + setY: function ( index, y ) { - }, + this.array[ index * this.itemSize + 1 ] = y; - setXYZW: function (index, x, y, z, w) { + return this; - index *= this.itemSize; + }, - this.array[index + 0] = x; - this.array[index + 1] = y; - this.array[index + 2] = z; - this.array[index + 3] = w; + getZ: function ( index ) { - return this; + return this.array[ index * this.itemSize + 2 ]; - }, + }, - clone: function () { + setZ: function ( index, z ) { - return new THREE.BufferAttribute(new this.array.constructor(this.array), this.itemSize); + this.array[ index * this.itemSize + 2 ] = z; - } + return this; -}; + }, -// + getW: function ( index ) { -THREE.Int8Attribute = function (data, itemSize) { + return this.array[ index * this.itemSize + 3 ]; - THREE.warn('THREE.Int8Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.'); - return new THREE.BufferAttribute(data, itemSize); + }, -}; + setW: function ( index, w ) { + + this.array[ index * this.itemSize + 3 ] = w; + + return this; + + }, + + setXY: function ( index, x, y ) { + + index *= this.itemSize; + + this.array[ index + 0 ] = x; + this.array[ index + 1 ] = y; + + return this; + + }, + + setXYZ: function ( index, x, y, z ) { + + index *= this.itemSize; + + this.array[ index + 0 ] = x; + this.array[ index + 1 ] = y; + this.array[ index + 2 ] = z; + + return this; -THREE.Uint8Attribute = function (data, itemSize) { + }, - THREE.warn('THREE.Uint8Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.'); - return new THREE.BufferAttribute(data, itemSize); + setXYZW: function ( index, x, y, z, w ) { + + index *= this.itemSize; + + this.array[ index + 0 ] = x; + this.array[ index + 1 ] = y; + this.array[ index + 2 ] = z; + this.array[ index + 3 ] = w; + + return this; + + }, + + clone: function () { + + return new this.constructor().copy( this ); + + } }; -THREE.Uint8ClampedAttribute = function (data, itemSize) { +// - THREE.warn('THREE.Uint8ClampedAttribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.'); - return new THREE.BufferAttribute(data, itemSize); +THREE.Int8Attribute = function ( array, itemSize ) { + return new THREE.BufferAttribute( new Int8Array( array ), itemSize ); }; -THREE.Int16Attribute = function (data, itemSize) { +THREE.Uint8Attribute = function ( array, itemSize ) { - THREE.warn('THREE.Int16Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.'); - return new THREE.BufferAttribute(data, itemSize); + return new THREE.BufferAttribute( new Uint8Array( array ), itemSize ); }; -THREE.Uint16Attribute = function (data, itemSize) { +THREE.Uint8ClampedAttribute = function ( array, itemSize ) { - THREE.warn('THREE.Uint16Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.'); - return new THREE.BufferAttribute(data, itemSize); + return new THREE.BufferAttribute( new Uint8ClampedArray( array ), itemSize ); }; -THREE.Int32Attribute = function (data, itemSize) { +THREE.Int16Attribute = function ( array, itemSize ) { - THREE.warn('THREE.Int32Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.'); - return new THREE.BufferAttribute(data, itemSize); + return new THREE.BufferAttribute( new Int16Array( array ), itemSize ); }; -THREE.Uint32Attribute = function (data, itemSize) { +THREE.Uint16Attribute = function ( array, itemSize ) { - THREE.warn('THREE.Uint32Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.'); - return new THREE.BufferAttribute(data, itemSize); + return new THREE.BufferAttribute( new Uint16Array( array ), itemSize ); }; -THREE.Float32Attribute = function (data, itemSize) { +THREE.Int32Attribute = function ( array, itemSize ) { - THREE.warn('THREE.Float32Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.'); - return new THREE.BufferAttribute(data, itemSize); + return new THREE.BufferAttribute( new Int32Array( array ), itemSize ); }; -THREE.Float64Attribute = function (data, itemSize) { +THREE.Uint32Attribute = function ( array, itemSize ) { - THREE.warn('THREE.Float64Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.'); - return new THREE.BufferAttribute(data, itemSize); + return new THREE.BufferAttribute( new Uint32Array( array ), itemSize ); }; -// File:src/core/DynamicBufferAttribute.js +THREE.Float32Attribute = function ( array, itemSize ) { -/** - * @author benaadams / https://twitter.com/ben_a_adams - * @author mrdoob / http://mrdoob.com/ - */ + return new THREE.BufferAttribute( new Float32Array( array ), itemSize ); -THREE.DynamicBufferAttribute = function (array, itemSize) { +}; - THREE.BufferAttribute.call(this, array, itemSize); +THREE.Float64Attribute = function ( array, itemSize ) { - this.updateRange = {offset: 0, count: -1}; + return new THREE.BufferAttribute( new Float64Array( array ), itemSize ); }; -THREE.DynamicBufferAttribute.prototype = Object.create(THREE.BufferAttribute.prototype); -THREE.DynamicBufferAttribute.prototype.constructor = THREE.DynamicBufferAttribute; -THREE.DynamicBufferAttribute.prototype.clone = function () { +// Deprecated + +THREE.DynamicBufferAttribute = function ( array, itemSize ) { - return new THREE.DynamicBufferAttribute(new this.array.constructor(this.array), this.itemSize); + console.warn( 'THREE.DynamicBufferAttribute has been removed. Use new THREE.BufferAttribute().setDynamic( true ) instead.' ); + return new THREE.BufferAttribute( array, itemSize ).setDynamic( true ); }; @@ -8898,21 +8973,24 @@ THREE.DynamicBufferAttribute.prototype.clone = function () { * @author benaadams / https://twitter.com/ben_a_adams */ -THREE.InstancedBufferAttribute = function (array, itemSize, meshPerAttribute, dynamic) { +THREE.InstancedBufferAttribute = function ( array, itemSize, meshPerAttribute ) { - THREE.DynamicBufferAttribute.call(this, array, itemSize); + THREE.BufferAttribute.call( this, array, itemSize ); - this.dynamic = dynamic || false; - this.meshPerAttribute = meshPerAttribute || 1; + this.meshPerAttribute = meshPerAttribute || 1; }; -THREE.InstancedBufferAttribute.prototype = Object.create(THREE.DynamicBufferAttribute.prototype); +THREE.InstancedBufferAttribute.prototype = Object.create( THREE.BufferAttribute.prototype ); THREE.InstancedBufferAttribute.prototype.constructor = THREE.InstancedBufferAttribute; -THREE.InstancedBufferAttribute.prototype.clone = function () { +THREE.InstancedBufferAttribute.prototype.copy = function ( source ) { + + THREE.BufferAttribute.prototype.copy.call( this, source ); + + this.meshPerAttribute = source.meshPerAttribute; - return new THREE.InstancedBufferAttribute(new this.array.constructor(this.array), this.itemSize, this.meshPerAttribute, this.dynamic); + return this; }; @@ -8922,58 +9000,88 @@ THREE.InstancedBufferAttribute.prototype.clone = function () { * @author benaadams / https://twitter.com/ben_a_adams */ -THREE.InterleavedBuffer = function (array, stride, dynamic) { +THREE.InterleavedBuffer = function ( array, stride ) { - this.array = array; - this.stride = stride; + this.uuid = THREE.Math.generateUUID(); - this.needsUpdate = false; + this.array = array; + this.stride = stride; - this.dynamic = dynamic || false; - this.updateRange = {offset: 0, count: -1}; + this.dynamic = false; + this.updateRange = { offset: 0, count: - 1 }; + + this.version = 0; }; THREE.InterleavedBuffer.prototype = { - constructor: THREE.InterleavedBuffer, + constructor: THREE.InterleavedBuffer, - get length() { + get length () { - return this.array.length; + return this.array.length; - }, + }, - copyAt: function (index1, attribute, index2) { + get count () { - index1 *= this.stride; - index2 *= attribute.stride; + return this.array.length / this.stride; - for (var i = 0, l = this.stride; i < l; i++) { + }, - this.array[index1 + i] = attribute.array[index2 + i]; + set needsUpdate( value ) { - } + if ( value === true ) this.version ++; - return this; + }, - }, + setDynamic: function ( value ) { - set: function (value, offset) { + this.dynamic = value; - if (offset === undefined) offset = 0; + return this; - this.array.set(value, offset); + }, - return this; + copy: function ( source ) { - }, + this.array = new source.array.constructor( source.array ); + this.stride = source.stride; + this.dynamic = source.dynamic; - clone: function () { + }, - return new THREE.InterleavedBuffer(new this.array.constructor(this.array), this.stride, this.dynamic); + copyAt: function ( index1, attribute, index2 ) { - } + index1 *= this.stride; + index2 *= attribute.stride; + + for ( var i = 0, l = this.stride; i < l; i ++ ) { + + this.array[ index1 + i ] = attribute.array[ index2 + i ]; + + } + + return this; + + }, + + set: function ( value, offset ) { + + if ( offset === undefined ) offset = 0; + + this.array.set( value, offset ); + + return this; + + }, + + clone: function () { + + return new this.constructor().copy( this ); + + } }; @@ -8983,20 +9091,24 @@ THREE.InterleavedBuffer.prototype = { * @author benaadams / https://twitter.com/ben_a_adams */ -THREE.InstancedInterleavedBuffer = function (array, stride, dynamic, meshPerAttribute) { +THREE.InstancedInterleavedBuffer = function ( array, stride, meshPerAttribute ) { - THREE.InterleavedBuffer.call(this, array, stride, dynamic); + THREE.InterleavedBuffer.call( this, array, stride ); - this.meshPerAttribute = meshPerAttribute || 1; + this.meshPerAttribute = meshPerAttribute || 1; }; -THREE.InstancedInterleavedBuffer.prototype = Object.create(THREE.InterleavedBuffer.prototype); +THREE.InstancedInterleavedBuffer.prototype = Object.create( THREE.InterleavedBuffer.prototype ); THREE.InstancedInterleavedBuffer.prototype.constructor = THREE.InstancedInterleavedBuffer; -THREE.InstancedInterleavedBuffer.prototype.clone = function () { +THREE.InstancedInterleavedBuffer.prototype.copy = function ( source ) { + + THREE.InterleavedBuffer.prototype.copy.call( this, source ); - return new THREE.InstancedInterleavedBuffer(new this.array.constructor(this.array), this.stride, this.dynamic, this.meshPerAttribute); + this.meshPerAttribute = source.meshPerAttribute; + + return this; }; @@ -9006,116 +9118,125 @@ THREE.InstancedInterleavedBuffer.prototype.clone = function () { * @author benaadams / https://twitter.com/ben_a_adams */ -THREE.InterleavedBufferAttribute = function (interleavedBuffer, itemSize, offset) { +THREE.InterleavedBufferAttribute = function ( interleavedBuffer, itemSize, offset ) { + + this.uuid = THREE.Math.generateUUID(); - this.data = interleavedBuffer; - this.itemSize = itemSize; - this.offset = offset; + this.data = interleavedBuffer; + this.itemSize = itemSize; + this.offset = offset; }; THREE.InterleavedBufferAttribute.prototype = { - constructor: THREE.InterleavedBufferAttribute, + constructor: THREE.InterleavedBufferAttribute, - get length() { + get length() { - return this.itemSize * this.data.array.length / this.data.stride; + console.warn( 'THREE.BufferAttribute: .length has been deprecated. Please use .count.' ); + return this.array.length; - }, + }, - setX: function (index, x) { + get count() { - this.data.array[index * this.data.stride + this.offset] = x; + return this.data.array.length / this.data.stride; - return this; + }, - }, + setX: function ( index, x ) { - setY: function (index, y) { + this.data.array[ index * this.data.stride + this.offset ] = x; - this.data.array[index * this.data.stride + this.offset + 1] = y; + return this; - return this; + }, - }, + setY: function ( index, y ) { - setZ: function (index, z) { + this.data.array[ index * this.data.stride + this.offset + 1 ] = y; - this.data.array[index * this.data.stride + this.offset + 2] = z; + return this; - return this; + }, - }, + setZ: function ( index, z ) { - setW: function (index, w) { + this.data.array[ index * this.data.stride + this.offset + 2 ] = z; - this.data.array[index * this.data.stride + this.offset + 3] = w; + return this; - return this; + }, - }, + setW: function ( index, w ) { - getX: function (index) { + this.data.array[ index * this.data.stride + this.offset + 3 ] = w; - return this.data.array[index * this.data.stride + this.offset]; + return this; - }, + }, - getY: function (index) { + getX: function ( index ) { - return this.data.array[index * this.data.stride + this.offset + 1]; + return this.data.array[ index * this.data.stride + this.offset ]; - }, + }, - getZ: function (index) { + getY: function ( index ) { - return this.data.array[index * this.data.stride + this.offset + 2]; + return this.data.array[ index * this.data.stride + this.offset + 1 ]; - }, + }, - getW: function (index) { + getZ: function ( index ) { - return this.data.array[index * this.data.stride + this.offset + 3]; + return this.data.array[ index * this.data.stride + this.offset + 2 ]; - }, + }, - setXY: function (index, x, y) { + getW: function ( index ) { - index = index * this.data.stride + this.offset; + return this.data.array[ index * this.data.stride + this.offset + 3 ]; - this.data.array[index + 0] = x; - this.data.array[index + 1] = y; + }, - return this; + setXY: function ( index, x, y ) { - }, + index = index * this.data.stride + this.offset; - setXYZ: function (index, x, y, z) { + this.data.array[ index + 0 ] = x; + this.data.array[ index + 1 ] = y; - index = index * this.data.stride + this.offset; + return this; - this.data.array[index + 0] = x; - this.data.array[index + 1] = y; - this.data.array[index + 2] = z; + }, - return this; + setXYZ: function ( index, x, y, z ) { - }, + index = index * this.data.stride + this.offset; - setXYZW: function (index, x, y, z, w) { + this.data.array[ index + 0 ] = x; + this.data.array[ index + 1 ] = y; + this.data.array[ index + 2 ] = z; - index = index * this.data.stride + this.offset; + return this; - this.data.array[index + 0] = x; - this.data.array[index + 1] = y; - this.data.array[index + 2] = z; - this.data.array[index + 3] = w; + }, - return this; + setXYZW: function ( index, x, y, z, w ) { - } + index = index * this.data.stride + this.offset; + + this.data.array[ index + 0 ] = x; + this.data.array[ index + 1 ] = y; + this.data.array[ index + 2 ] = z; + this.data.array[ index + 3 ] = w; + + return this; + + } }; @@ -9127,5650 +9248,7820 @@ THREE.InterleavedBufferAttribute.prototype = { * @author alteredq / http://alteredqualia.com/ * @author mikael emtinger / http://gomo.se/ * @author zz85 / http://www.lab4games.net/zz85/blog - * @author bhouston / http://exocortex.com + * @author bhouston / http://clara.io */ THREE.Geometry = function () { - Object.defineProperty(this, 'id', {value: THREE.GeometryIdCount++}); - - this.uuid = THREE.Math.generateUUID(); - - this.name = ''; - this.type = 'Geometry'; + Object.defineProperty( this, 'id', { value: THREE.GeometryIdCount ++ } ); - this.vertices = []; - this.colors = []; - this.faces = []; - this.faceVertexUvs = [[]]; + this.uuid = THREE.Math.generateUUID(); - this.morphTargets = []; - this.morphColors = []; - this.morphNormals = []; + this.name = ''; + this.type = 'Geometry'; - this.skinWeights = []; - this.skinIndices = []; + this.vertices = []; + this.colors = []; + this.faces = []; + this.faceVertexUvs = [ [] ]; - this.lineDistances = []; + this.morphTargets = []; + this.morphColors = []; + this.morphNormals = []; - this.boundingBox = null; - this.boundingSphere = null; + this.skinWeights = []; + this.skinIndices = []; - this.hasTangents = false; + this.lineDistances = []; - // update flags + this.boundingBox = null; + this.boundingSphere = null; - this.verticesNeedUpdate = false; - this.elementsNeedUpdate = false; - this.uvsNeedUpdate = false; - this.normalsNeedUpdate = false; - this.tangentsNeedUpdate = false; - this.colorsNeedUpdate = false; - this.lineDistancesNeedUpdate = false; + // update flags - this.groupsNeedUpdate = false; + this.verticesNeedUpdate = false; + this.elementsNeedUpdate = false; + this.uvsNeedUpdate = false; + this.normalsNeedUpdate = false; + this.colorsNeedUpdate = false; + this.lineDistancesNeedUpdate = false; + this.groupsNeedUpdate = false; }; THREE.Geometry.prototype = { - constructor: THREE.Geometry, + constructor: THREE.Geometry, - applyMatrix: function (matrix) { + applyMatrix: function ( matrix ) { - var normalMatrix = new THREE.Matrix3().getNormalMatrix(matrix); + var normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix ); - for (var i = 0, il = this.vertices.length; i < il; i++) { + for ( var i = 0, il = this.vertices.length; i < il; i ++ ) { - var vertex = this.vertices[i]; - vertex.applyMatrix4(matrix); + var vertex = this.vertices[ i ]; + vertex.applyMatrix4( matrix ); - } + } - for (var i = 0, il = this.faces.length; i < il; i++) { + for ( var i = 0, il = this.faces.length; i < il; i ++ ) { - var face = this.faces[i]; - face.normal.applyMatrix3(normalMatrix).normalize(); + var face = this.faces[ i ]; + face.normal.applyMatrix3( normalMatrix ).normalize(); - for (var j = 0, jl = face.vertexNormals.length; j < jl; j++) { + for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) { - face.vertexNormals[j].applyMatrix3(normalMatrix).normalize(); + face.vertexNormals[ j ].applyMatrix3( normalMatrix ).normalize(); - } + } - } + } - if (this.boundingBox !== null) { + if ( this.boundingBox !== null ) { - this.computeBoundingBox(); + this.computeBoundingBox(); - } + } - if (this.boundingSphere !== null) { + if ( this.boundingSphere !== null ) { - this.computeBoundingSphere(); + this.computeBoundingSphere(); - } + } - this.verticesNeedUpdate = true; - this.normalsNeedUpdate = true; + this.verticesNeedUpdate = true; + this.normalsNeedUpdate = true; - }, + }, - fromBufferGeometry: function (geometry) { + rotateX: function () { - var scope = this; + // rotate geometry around world x-axis - var attributes = geometry.attributes; + var m1; - var vertices = attributes.position.array; - var indices = attributes.index !== undefined ? attributes.index.array : undefined; - var normals = attributes.normal !== undefined ? attributes.normal.array : undefined; - var colors = attributes.color !== undefined ? attributes.color.array : undefined; - var uvs = attributes.uv !== undefined ? attributes.uv.array : undefined; + return function rotateX( angle ) { - var tempNormals = []; - var tempUVs = []; + if ( m1 === undefined ) m1 = new THREE.Matrix4(); - for (var i = 0, j = 0; i < vertices.length; i += 3, j += 2) { + m1.makeRotationX( angle ); - scope.vertices.push(new THREE.Vector3(vertices[i], vertices[i + 1], vertices[i + 2])); + this.applyMatrix( m1 ); - if (normals !== undefined) { + return this; - tempNormals.push(new THREE.Vector3(normals[i], normals[i + 1], normals[i + 2])); + }; - } + }(), - if (colors !== undefined) { + rotateY: function () { - scope.colors.push(new THREE.Color(colors[i], colors[i + 1], colors[i + 2])); + // rotate geometry around world y-axis - } + var m1; - if (uvs !== undefined) { + return function rotateY( angle ) { - tempUVs.push(new THREE.Vector2(uvs[j], uvs[j + 1])); + if ( m1 === undefined ) m1 = new THREE.Matrix4(); - } + m1.makeRotationY( angle ); - } + this.applyMatrix( m1 ); - var addFace = function (a, b, c) { + return this; - var vertexNormals = normals !== undefined ? [tempNormals[a].clone(), tempNormals[b].clone(), tempNormals[c].clone()] : []; - var vertexColors = colors !== undefined ? [scope.colors[a].clone(), scope.colors[b].clone(), scope.colors[c].clone()] : []; + }; - scope.faces.push(new THREE.Face3(a, b, c, vertexNormals, vertexColors)); + }(), - if (uvs !== undefined) { + rotateZ: function () { - scope.faceVertexUvs[0].push([tempUVs[a].clone(), tempUVs[b].clone(), tempUVs[c].clone()]); + // rotate geometry around world z-axis - } + var m1; - }; + return function rotateZ( angle ) { - if (indices !== undefined) { + if ( m1 === undefined ) m1 = new THREE.Matrix4(); - var drawcalls = geometry.drawcalls; + m1.makeRotationZ( angle ); - if (drawcalls.length > 0) { + this.applyMatrix( m1 ); - for (var i = 0; i < drawcalls.length; i++) { + return this; - var drawcall = drawcalls[i]; + }; - var start = drawcall.start; - var count = drawcall.count; - var index = drawcall.index; + }(), - for (var j = start, jl = start + count; j < jl; j += 3) { + translate: function () { - addFace(index + indices[j], index + indices[j + 1], index + indices[j + 2]); + // translate geometry - } + var m1; - } + return function translate( x, y, z ) { - } else { + if ( m1 === undefined ) m1 = new THREE.Matrix4(); - for (var i = 0; i < indices.length; i += 3) { + m1.makeTranslation( x, y, z ); - addFace(indices[i], indices[i + 1], indices[i + 2]); + this.applyMatrix( m1 ); - } + return this; - } + }; - } else { + }(), - for (var i = 0; i < vertices.length / 3; i += 3) { + scale: function () { - addFace(i, i + 1, i + 2); + // scale geometry - } + var m1; - } + return function scale( x, y, z ) { - this.computeFaceNormals(); + if ( m1 === undefined ) m1 = new THREE.Matrix4(); - if (geometry.boundingBox !== null) { + m1.makeScale( x, y, z ); - this.boundingBox = geometry.boundingBox.clone(); + this.applyMatrix( m1 ); - } + return this; - if (geometry.boundingSphere !== null) { + }; - this.boundingSphere = geometry.boundingSphere.clone(); + }(), - } + lookAt: function () { - return this; + var obj; - }, + return function lookAt( vector ) { - center: function () { + if ( obj === undefined ) obj = new THREE.Object3D(); - this.computeBoundingBox(); + obj.lookAt( vector ); - var offset = this.boundingBox.center().negate(); + obj.updateMatrix(); - this.applyMatrix(new THREE.Matrix4().setPosition(offset)); + this.applyMatrix( obj.matrix ); - return offset; + }; - }, + }(), - computeFaceNormals: function () { + fromBufferGeometry: function ( geometry ) { - var cb = new THREE.Vector3(), ab = new THREE.Vector3(); + var scope = this; - for (var f = 0, fl = this.faces.length; f < fl; f++) { + var indices = geometry.index !== null ? geometry.index.array : undefined; + var attributes = geometry.attributes; - var face = this.faces[f]; + var vertices = attributes.position.array; + var normals = attributes.normal !== undefined ? attributes.normal.array : undefined; + var colors = attributes.color !== undefined ? attributes.color.array : undefined; + var uvs = attributes.uv !== undefined ? attributes.uv.array : undefined; + var uvs2 = attributes.uv2 !== undefined ? attributes.uv2.array : undefined; - var vA = this.vertices[face.a]; - var vB = this.vertices[face.b]; - var vC = this.vertices[face.c]; + if ( uvs2 !== undefined ) this.faceVertexUvs[ 1 ] = []; - cb.subVectors(vC, vB); - ab.subVectors(vA, vB); - cb.cross(ab); + var tempNormals = []; + var tempUVs = []; + var tempUVs2 = []; - cb.normalize(); + for ( var i = 0, j = 0, k = 0; i < vertices.length; i += 3, j += 2, k += 4 ) { - face.normal.copy(cb); + scope.vertices.push( new THREE.Vector3( vertices[ i ], vertices[ i + 1 ], vertices[ i + 2 ] ) ); - } + if ( normals !== undefined ) { - }, + tempNormals.push( new THREE.Vector3( normals[ i ], normals[ i + 1 ], normals[ i + 2 ] ) ); - computeVertexNormals: function (areaWeighted) { + } - var v, vl, f, fl, face, vertices; + if ( colors !== undefined ) { - vertices = new Array(this.vertices.length); + scope.colors.push( new THREE.Color( colors[ i ], colors[ i + 1 ], colors[ i + 2 ] ) ); - for (v = 0, vl = this.vertices.length; v < vl; v++) { + } - vertices[v] = new THREE.Vector3(); + if ( uvs !== undefined ) { - } + tempUVs.push( new THREE.Vector2( uvs[ j ], uvs[ j + 1 ] ) ); - if (areaWeighted) { + } - // vertex normals weighted by triangle areas - // http://www.iquilezles.org/www/articles/normals/normals.htm + if ( uvs2 !== undefined ) { - var vA, vB, vC; - var cb = new THREE.Vector3(), ab = new THREE.Vector3(); + tempUVs2.push( new THREE.Vector2( uvs2[ j ], uvs2[ j + 1 ] ) ); - 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]; + function addFace( a, b, c ) { - cb.subVectors(vC, vB); - ab.subVectors(vA, vB); - cb.cross(ab); + var vertexNormals = normals !== undefined ? [ tempNormals[ a ].clone(), tempNormals[ b ].clone(), tempNormals[ c ].clone() ] : []; + var vertexColors = colors !== undefined ? [ scope.colors[ a ].clone(), scope.colors[ b ].clone(), scope.colors[ c ].clone() ] : []; - vertices[face.a].add(cb); - vertices[face.b].add(cb); - vertices[face.c].add(cb); + var face = new THREE.Face3( a, b, c, vertexNormals, vertexColors ); - } + scope.faces.push( face ); - } else { + if ( uvs !== undefined ) { - for (f = 0, fl = this.faces.length; f < fl; f++) { + scope.faceVertexUvs[ 0 ].push( [ tempUVs[ a ].clone(), tempUVs[ b ].clone(), tempUVs[ c ].clone() ] ); - face = this.faces[f]; + } - vertices[face.a].add(face.normal); - vertices[face.b].add(face.normal); - vertices[face.c].add(face.normal); + if ( uvs2 !== undefined ) { - } + scope.faceVertexUvs[ 1 ].push( [ tempUVs2[ a ].clone(), tempUVs2[ b ].clone(), tempUVs2[ c ].clone() ] ); - } + } - for (v = 0, vl = this.vertices.length; v < vl; v++) { + }; - vertices[v].normalize(); + if ( indices !== undefined ) { - } + var groups = geometry.groups; - for (f = 0, fl = this.faces.length; f < fl; f++) { + if ( groups.length > 0 ) { - face = this.faces[f]; + for ( var i = 0; i < groups.length; i ++ ) { - face.vertexNormals[0] = vertices[face.a].clone(); - face.vertexNormals[1] = vertices[face.b].clone(); - face.vertexNormals[2] = vertices[face.c].clone(); + var group = groups[ i ]; - } + var start = group.start; + var count = group.count; - }, + for ( var j = start, jl = start + count; j < jl; j += 3 ) { - computeMorphNormals: function () { + addFace( indices[ j ], indices[ j + 1 ], indices[ j + 2 ] ); - 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++) { + } else { - face = this.faces[f]; + for ( var i = 0; i < indices.length; i += 3 ) { - if (!face.__originalFaceNormal) { + addFace( indices[ i ], indices[ i + 1 ], indices[ i + 2 ] ); - face.__originalFaceNormal = face.normal.clone(); + } - } else { + } - face.__originalFaceNormal.copy(face.normal); + } else { - } + for ( var i = 0; i < vertices.length / 3; i += 3 ) { - if (!face.__originalVertexNormals) face.__originalVertexNormals = []; + addFace( i, i + 1, i + 2 ); - for (i = 0, il = face.vertexNormals.length; i < il; i++) { + } - if (!face.__originalVertexNormals[i]) { + } - face.__originalVertexNormals[i] = face.vertexNormals[i].clone(); + this.computeFaceNormals(); - } else { + if ( geometry.boundingBox !== null ) { - face.__originalVertexNormals[i].copy(face.vertexNormals[i]); + this.boundingBox = geometry.boundingBox.clone(); - } + } - } + if ( geometry.boundingSphere !== null ) { - } + this.boundingSphere = geometry.boundingSphere.clone(); - // use temp geometry to compute face and vertex normals for each morph + } - var tmpGeo = new THREE.Geometry(); - tmpGeo.faces = this.faces; + return this; - for (i = 0, il = this.morphTargets.length; i < il; i++) { + }, - // create on first access + center: function () { - if (!this.morphNormals[i]) { + this.computeBoundingBox(); - this.morphNormals[i] = {}; - this.morphNormals[i].faceNormals = []; - this.morphNormals[i].vertexNormals = []; + var offset = this.boundingBox.center().negate(); - var dstNormalsFace = this.morphNormals[i].faceNormals; - var dstNormalsVertex = this.morphNormals[i].vertexNormals; + this.translate( offset.x, offset.y, offset.z ); - var faceNormal, vertexNormals; + return offset; - for (f = 0, fl = this.faces.length; f < fl; f++) { + }, - faceNormal = new THREE.Vector3(); - vertexNormals = {a: new THREE.Vector3(), b: new THREE.Vector3(), c: new THREE.Vector3()}; + normalize: function () { - dstNormalsFace.push(faceNormal); - dstNormalsVertex.push(vertexNormals); + this.computeBoundingSphere(); - } + var center = this.boundingSphere.center; + var radius = this.boundingSphere.radius; - } + var s = radius === 0 ? 1 : 1.0 / radius; - var morphNormals = this.morphNormals[i]; + var matrix = new THREE.Matrix4(); + matrix.set( + s, 0, 0, - s * center.x, + 0, s, 0, - s * center.y, + 0, 0, s, - s * center.z, + 0, 0, 0, 1 + ); - // set vertices to morph target + this.applyMatrix( matrix ); - tmpGeo.vertices = this.morphTargets[i].vertices; + return this; - // compute morph normals + }, - tmpGeo.computeFaceNormals(); - tmpGeo.computeVertexNormals(); + computeFaceNormals: function () { - // store morph normals + var cb = new THREE.Vector3(), ab = new THREE.Vector3(); - var faceNormal, vertexNormals; + for ( var f = 0, fl = this.faces.length; f < fl; f ++ ) { - for (f = 0, fl = this.faces.length; f < fl; f++) { + var face = this.faces[ f ]; - face = this.faces[f]; + var vA = this.vertices[ face.a ]; + var vB = this.vertices[ face.b ]; + var vC = this.vertices[ face.c ]; - faceNormal = morphNormals.faceNormals[f]; - vertexNormals = morphNormals.vertexNormals[f]; + cb.subVectors( vC, vB ); + ab.subVectors( vA, vB ); + cb.cross( ab ); - faceNormal.copy(face.normal); + cb.normalize(); - vertexNormals.a.copy(face.vertexNormals[0]); - vertexNormals.b.copy(face.vertexNormals[1]); - vertexNormals.c.copy(face.vertexNormals[2]); + face.normal.copy( cb ); - } + } - } + }, - // restore original normals + computeVertexNormals: function ( areaWeighted ) { - for (f = 0, fl = this.faces.length; f < fl; f++) { + var v, vl, f, fl, face, vertices; - face = this.faces[f]; + vertices = new Array( this.vertices.length ); - face.normal = face.__originalFaceNormal; - face.vertexNormals = face.__originalVertexNormals; + for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) { - } + vertices[ v ] = new THREE.Vector3(); - }, + } - computeTangents: function () { + if ( areaWeighted ) { - // based on http://www.terathon.com/code/tangent.html - // tangents go to vertices + // vertex normals weighted by triangle areas + // http://www.iquilezles.org/www/articles/normals/normals.htm - var f, fl, v, vl, i, 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; + var vA, vB, vC; + var cb = new THREE.Vector3(), ab = new THREE.Vector3(); - for (v = 0, vl = this.vertices.length; v < vl; v++) { + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { - tan1[v] = new THREE.Vector3(); - tan2[v] = new THREE.Vector3(); + face = this.faces[ f ]; - } + vA = this.vertices[ face.a ]; + vB = this.vertices[ face.b ]; + vC = this.vertices[ face.c ]; - function handleTriangle(context, a, b, c, ua, ub, uc) { + cb.subVectors( vC, vB ); + ab.subVectors( vA, vB ); + cb.cross( ab ); - vA = context.vertices[a]; - vB = context.vertices[b]; - vC = context.vertices[c]; + vertices[ face.a ].add( cb ); + vertices[ face.b ].add( cb ); + vertices[ face.c ].add( cb ); - 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; + } else { - s1 = uvB.x - uvA.x; - s2 = uvC.x - uvA.x; - t1 = uvB.y - uvA.y; - t2 = uvC.y - uvA.y; + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { - 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); + face = this.faces[ f ]; - tan1[a].add(sdir); - tan1[b].add(sdir); - tan1[c].add(sdir); + vertices[ face.a ].add( face.normal ); + vertices[ face.b ].add( face.normal ); + vertices[ face.c ].add( face.normal ); - tan2[a].add(tdir); - tan2[b].add(tdir); - tan2[c].add(tdir); + } - } + } - for (f = 0, fl = this.faces.length; f < fl; f++) { + for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) { - face = this.faces[f]; - uv = this.faceVertexUvs[0][f]; // use UV layer 0 for tangents + vertices[ v ].normalize(); - handleTriangle(this, face.a, face.b, face.c, 0, 1, 2); + } - } + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { - var faceIndex = ['a', 'b', 'c', 'd']; + face = this.faces[ f ]; - for (f = 0, fl = this.faces.length; f < fl; f++) { + var vertexNormals = face.vertexNormals; - face = this.faces[f]; + if ( vertexNormals.length === 3 ) { - for (i = 0; i < Math.min(face.vertexNormals.length, 3); i++) { + vertexNormals[ 0 ].copy( vertices[ face.a ] ); + vertexNormals[ 1 ].copy( vertices[ face.b ] ); + vertexNormals[ 2 ].copy( vertices[ face.c ] ); - n.copy(face.vertexNormals[i]); + } else { - vertexIndex = face[faceIndex[i]]; + vertexNormals[ 0 ] = vertices[ face.a ].clone(); + vertexNormals[ 1 ] = vertices[ face.b ].clone(); + vertexNormals[ 2 ] = vertices[ face.c ].clone(); - t = tan1[vertexIndex]; + } - // Gram-Schmidt orthogonalize + } - tmp.copy(t); - tmp.sub(n.multiplyScalar(n.dot(t))).normalize(); + }, - // Calculate handedness + computeMorphNormals: function () { - tmp2.crossVectors(face.vertexNormals[i], t); - test = tmp2.dot(tan2[vertexIndex]); - w = ( test < 0.0 ) ? -1.0 : 1.0; + var i, il, f, fl, face; - face.vertexTangents[i] = new THREE.Vector4(tmp.x, tmp.y, tmp.z, w); + // 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 ]; - this.hasTangents = true; + if ( ! face.__originalFaceNormal ) { - }, + face.__originalFaceNormal = face.normal.clone(); - computeLineDistances: function () { + } else { - var d = 0; - var vertices = this.vertices; + face.__originalFaceNormal.copy( face.normal ); - for (var i = 0, il = vertices.length; i < il; i++) { + } - if (i > 0) { + if ( ! face.__originalVertexNormals ) face.__originalVertexNormals = []; - d += vertices[i].distanceTo(vertices[i - 1]); + for ( i = 0, il = face.vertexNormals.length; i < il; i ++ ) { - } + if ( ! face.__originalVertexNormals[ i ] ) { - this.lineDistances[i] = d; + face.__originalVertexNormals[ i ] = face.vertexNormals[ i ].clone(); - } + } else { - }, + face.__originalVertexNormals[ i ].copy( face.vertexNormals[ i ] ); - computeBoundingBox: function () { + } - if (this.boundingBox === null) { + } - this.boundingBox = new THREE.Box3(); + } - } + // use temp geometry to compute face and vertex normals for each morph - this.boundingBox.setFromPoints(this.vertices); + var tmpGeo = new THREE.Geometry(); + tmpGeo.faces = this.faces; - }, + for ( i = 0, il = this.morphTargets.length; i < il; i ++ ) { - computeBoundingSphere: function () { + // create on first access - if (this.boundingSphere === null) { + if ( ! this.morphNormals[ i ] ) { - this.boundingSphere = new THREE.Sphere(); + this.morphNormals[ i ] = {}; + this.morphNormals[ i ].faceNormals = []; + this.morphNormals[ i ].vertexNormals = []; - } + var dstNormalsFace = this.morphNormals[ i ].faceNormals; + var dstNormalsVertex = this.morphNormals[ i ].vertexNormals; - this.boundingSphere.setFromPoints(this.vertices); + var faceNormal, vertexNormals; - }, + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { - merge: function (geometry, matrix) { + faceNormal = new THREE.Vector3(); + vertexNormals = { a: new THREE.Vector3(), b: new THREE.Vector3(), c: new THREE.Vector3() }; - if (geometry instanceof THREE.Geometry === false) { + dstNormalsFace.push( faceNormal ); + dstNormalsVertex.push( vertexNormals ); - THREE.error('THREE.Geometry.merge(): geometry not an instance of THREE.Geometry.', geometry); - return; + } - } + } - var normalMatrix, - vertexOffset = this.vertices.length, - vertices1 = this.vertices, - vertices2 = geometry.vertices, - faces1 = this.faces, - faces2 = geometry.faces, - uvs1 = this.faceVertexUvs[0], - uvs2 = geometry.faceVertexUvs[0]; + var morphNormals = this.morphNormals[ i ]; - if (matrix !== undefined) { + // set vertices to morph target - normalMatrix = new THREE.Matrix3().getNormalMatrix(matrix); + tmpGeo.vertices = this.morphTargets[ i ].vertices; - } + // compute morph normals - // vertices + tmpGeo.computeFaceNormals(); + tmpGeo.computeVertexNormals(); - for (var i = 0, il = vertices2.length; i < il; i++) { + // store morph normals - var vertex = vertices2[i]; + var faceNormal, vertexNormals; - var vertexCopy = vertex.clone(); + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { - if (matrix !== undefined) vertexCopy.applyMatrix4(matrix); + face = this.faces[ f ]; - vertices1.push(vertexCopy); + faceNormal = morphNormals.faceNormals[ f ]; + vertexNormals = morphNormals.vertexNormals[ f ]; - } + faceNormal.copy( face.normal ); - // faces + vertexNormals.a.copy( face.vertexNormals[ 0 ] ); + vertexNormals.b.copy( face.vertexNormals[ 1 ] ); + vertexNormals.c.copy( face.vertexNormals[ 2 ] ); - 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); + // restore original normals - if (normalMatrix !== undefined) { + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { - faceCopy.normal.applyMatrix3(normalMatrix).normalize(); + face = this.faces[ f ]; - } + face.normal = face.__originalFaceNormal; + face.vertexNormals = face.__originalVertexNormals; - for (var j = 0, jl = faceVertexNormals.length; j < jl; j++) { + } - normal = faceVertexNormals[j].clone(); + }, - if (normalMatrix !== undefined) { + computeTangents: function () { - normal.applyMatrix3(normalMatrix).normalize(); + console.warn( 'THREE.Geometry: .computeTangents() has been removed.' ); - } + }, - faceCopy.vertexNormals.push(normal); + computeLineDistances: function () { - } + var d = 0; + var vertices = this.vertices; - faceCopy.color.copy(face.color); + for ( var i = 0, il = vertices.length; i < il; i ++ ) { - for (var j = 0, jl = faceVertexColors.length; j < jl; j++) { + if ( i > 0 ) { - color = faceVertexColors[j]; - faceCopy.vertexColors.push(color.clone()); + d += vertices[ i ].distanceTo( vertices[ i - 1 ] ); - } + } - faces1.push(faceCopy); + this.lineDistances[ i ] = d; - } + } - // uvs + }, - for (i = 0, il = uvs2.length; i < il; i++) { + computeBoundingBox: function () { - var uv = uvs2[i], uvCopy = []; + if ( this.boundingBox === null ) { - if (uv === undefined) { + this.boundingBox = new THREE.Box3(); - continue; + } - } + this.boundingBox.setFromPoints( this.vertices ); - for (var j = 0, jl = uv.length; j < jl; j++) { + }, - uvCopy.push(uv[j].clone()); + computeBoundingSphere: function () { - } + if ( this.boundingSphere === null ) { - uvs1.push(uvCopy); + this.boundingSphere = new THREE.Sphere(); - } + } - }, + this.boundingSphere.setFromPoints( this.vertices ); - mergeMesh: function (mesh) { + }, - if (mesh instanceof THREE.Mesh === false) { + merge: function ( geometry, matrix, materialIndexOffset ) { - THREE.error('THREE.Geometry.mergeMesh(): mesh not an instance of THREE.Mesh.', mesh); - return; + if ( geometry instanceof THREE.Geometry === false ) { - } + console.error( 'THREE.Geometry.merge(): geometry not an instance of THREE.Geometry.', geometry ); + return; - mesh.matrixAutoUpdate && mesh.updateMatrix(); + } - this.merge(mesh.geometry, mesh.matrix); + var normalMatrix, + vertexOffset = this.vertices.length, + vertices1 = this.vertices, + vertices2 = geometry.vertices, + faces1 = this.faces, + faces2 = geometry.faces, + uvs1 = this.faceVertexUvs[ 0 ], + uvs2 = geometry.faceVertexUvs[ 0 ]; - }, + if ( materialIndexOffset === undefined ) materialIndexOffset = 0; - /* - * Checks for duplicate vertices with hashmap. - * Duplicated vertices are removed - * and faces' vertices are updated. - */ + if ( matrix !== undefined ) { - mergeVertices: function () { + normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix ); - 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, j, jl; + // vertices - for (i = 0, il = this.vertices.length; i < il; i++) { + for ( var i = 0, il = vertices2.length; i < il; i ++ ) { - v = this.vertices[i]; - key = Math.round(v.x * precision) + '_' + Math.round(v.y * precision) + '_' + Math.round(v.z * precision); + var vertex = vertices2[ i ]; - if (verticesMap[key] === undefined) { + var vertexCopy = vertex.clone(); - verticesMap[key] = i; - unique.push(this.vertices[i]); - changes[i] = unique.length - 1; + if ( matrix !== undefined ) vertexCopy.applyMatrix4( matrix ); - } else { + vertices1.push( vertexCopy ); - //THREE.log('Duplicate vertex found. ', i, ' could be using ', verticesMap[key]); - changes[i] = changes[verticesMap[key]]; + } - } + // faces - } + for ( i = 0, il = faces2.length; i < il; i ++ ) { + var face = faces2[ i ], faceCopy, normal, color, + faceVertexNormals = face.vertexNormals, + faceVertexColors = face.vertexColors; - // if faces are completely degenerate after merging vertices, we - // have to remove them from the geometry. - var faceIndicesToRemove = []; + faceCopy = new THREE.Face3( face.a + vertexOffset, face.b + vertexOffset, face.c + vertexOffset ); + faceCopy.normal.copy( face.normal ); - for (i = 0, il = this.faces.length; i < il; i++) { + if ( normalMatrix !== undefined ) { - face = this.faces[i]; + faceCopy.normal.applyMatrix3( normalMatrix ).normalize(); - face.a = changes[face.a]; - face.b = changes[face.b]; - face.c = changes[face.c]; + } - indices = [face.a, face.b, face.c]; + for ( var j = 0, jl = faceVertexNormals.length; j < jl; j ++ ) { - var dupIndex = -1; + normal = faceVertexNormals[ j ].clone(); - // 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]) { + if ( normalMatrix !== undefined ) { - dupIndex = n; - faceIndicesToRemove.push(i); - break; + normal.applyMatrix3( normalMatrix ).normalize(); - } - } + } - } + faceCopy.vertexNormals.push( normal ); - for (i = faceIndicesToRemove.length - 1; i >= 0; i--) { - var idx = faceIndicesToRemove[i]; + } - this.faces.splice(idx, 1); + faceCopy.color.copy( face.color ); - for (j = 0, jl = this.faceVertexUvs.length; j < jl; j++) { + for ( var j = 0, jl = faceVertexColors.length; j < jl; j ++ ) { - this.faceVertexUvs[j].splice(idx, 1); + color = faceVertexColors[ j ]; + faceCopy.vertexColors.push( color.clone() ); - } + } - } + faceCopy.materialIndex = face.materialIndex + materialIndexOffset; - // Use unique set of vertices + faces1.push( faceCopy ); - var diff = this.vertices.length - unique.length; - this.vertices = unique; - return diff; + } - }, + // uvs - toJSON: function () { + for ( i = 0, il = uvs2.length; i < il; i ++ ) { - // we will store all serialization data on 'data' - var data = {}; + var uv = uvs2[ i ], uvCopy = []; - // add metadata - data.metadata = { - version: 4.4, - type: 'Geometry', - generator: 'Geometry.toJSON' - }; + if ( uv === undefined ) { - // standard Geometry serialization + continue; - data.type = this.type; - data.uuid = this.uuid; - if (this.name !== '') data.name = this.name; + } - if (this.parameters !== undefined) { + for ( var j = 0, jl = uv.length; j < jl; j ++ ) { - var parameters = this.parameters; + uvCopy.push( uv[ j ].clone() ); - for (var key in parameters) { + } - if (parameters[key] !== undefined) data[key] = parameters[key]; + uvs1.push( uvCopy ); - } + } - return data; + }, - } + mergeMesh: function ( mesh ) { - var vertices = []; + if ( mesh instanceof THREE.Mesh === false ) { - for (var i = 0; i < this.vertices.length; i++) { + console.error( 'THREE.Geometry.mergeMesh(): mesh not an instance of THREE.Mesh.', mesh ); + return; - var vertex = this.vertices[i]; - vertices.push(vertex.x, vertex.y, vertex.z); + } - } + mesh.matrixAutoUpdate && mesh.updateMatrix(); - var faces = []; - var normals = []; - var normalsHash = {}; - var colors = []; - var colorsHash = {}; - var uvs = []; - var uvsHash = {}; + this.merge( mesh.geometry, mesh.matrix ); - for (var i = 0; i < this.faces.length; i++) { + }, - var face = this.faces[i]; + /* + * Checks for duplicate vertices with hashmap. + * Duplicated vertices are removed + * and faces' vertices are updated. + */ - var hasMaterial = false; // face.materialIndex !== undefined; - var hasFaceUv = false; // deprecated - var hasFaceVertexUv = this.faceVertexUvs[0][i] !== undefined; - var hasFaceNormal = face.normal.length() > 0; - var hasFaceVertexNormal = face.vertexNormals.length > 0; - var hasFaceColor = face.color.r !== 1 || face.color.g !== 1 || face.color.b !== 1; - var hasFaceVertexColor = face.vertexColors.length > 0; + mergeVertices: function () { - var faceType = 0; + var verticesMap = {}; // Hashmap for looking up vertices by position coordinates (and making sure they are unique) + var unique = [], changes = []; - faceType = setBit(faceType, 0, 0); - faceType = setBit(faceType, 1, hasMaterial); - faceType = setBit(faceType, 2, hasFaceUv); - faceType = setBit(faceType, 3, hasFaceVertexUv); - faceType = setBit(faceType, 4, hasFaceNormal); - faceType = setBit(faceType, 5, hasFaceVertexNormal); - faceType = setBit(faceType, 6, hasFaceColor); - faceType = setBit(faceType, 7, hasFaceVertexColor); + var v, key; + var precisionPoints = 4; // number of decimal points, e.g. 4 for epsilon of 0.0001 + var precision = Math.pow( 10, precisionPoints ); + var i, il, face; + var indices, j, jl; - faces.push(faceType); - faces.push(face.a, face.b, face.c); + for ( i = 0, il = this.vertices.length; i < il; i ++ ) { - if (hasFaceVertexUv) { + v = this.vertices[ i ]; + key = Math.round( v.x * precision ) + '_' + Math.round( v.y * precision ) + '_' + Math.round( v.z * precision ); - var faceVertexUvs = this.faceVertexUvs[0][i]; + if ( verticesMap[ key ] === undefined ) { - faces.push( - getUvIndex(faceVertexUvs[0]), - getUvIndex(faceVertexUvs[1]), - getUvIndex(faceVertexUvs[2]) - ); + verticesMap[ key ] = i; + unique.push( this.vertices[ i ] ); + changes[ i ] = unique.length - 1; - } + } else { - if (hasFaceNormal) { + //console.log('Duplicate vertex found. ', i, ' could be using ', verticesMap[key]); + changes[ i ] = changes[ verticesMap[ key ] ]; - faces.push(getNormalIndex(face.normal)); + } - } + } - if (hasFaceVertexNormal) { - var vertexNormals = face.vertexNormals; + // if faces are completely degenerate after merging vertices, we + // have to remove them from the geometry. + var faceIndicesToRemove = []; - faces.push( - getNormalIndex(vertexNormals[0]), - getNormalIndex(vertexNormals[1]), - getNormalIndex(vertexNormals[2]) - ); + for ( i = 0, il = this.faces.length; i < il; i ++ ) { - } + face = this.faces[ i ]; - if (hasFaceColor) { + face.a = changes[ face.a ]; + face.b = changes[ face.b ]; + face.c = changes[ face.c ]; - faces.push(getColorIndex(face.color)); + indices = [ face.a, face.b, face.c ]; - } + var dupIndex = - 1; - if (hasFaceVertexColor) { + // 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 ++ ) { - var vertexColors = face.vertexColors; + if ( indices[ n ] === indices[ ( n + 1 ) % 3 ] ) { - faces.push( - getColorIndex(vertexColors[0]), - getColorIndex(vertexColors[1]), - getColorIndex(vertexColors[2]) - ); + dupIndex = n; + faceIndicesToRemove.push( i ); + break; - } + } - } + } - function setBit(value, position, enabled) { + } - return enabled ? value | ( 1 << position ) : value & ( ~( 1 << position) ); + for ( i = faceIndicesToRemove.length - 1; i >= 0; i -- ) { - } + var idx = faceIndicesToRemove[ i ]; - function getNormalIndex(normal) { + this.faces.splice( idx, 1 ); - var hash = normal.x.toString() + normal.y.toString() + normal.z.toString(); + for ( j = 0, jl = this.faceVertexUvs.length; j < jl; j ++ ) { - if (normalsHash[hash] !== undefined) { + this.faceVertexUvs[ j ].splice( idx, 1 ); - return normalsHash[hash]; + } - } + } - normalsHash[hash] = normals.length / 3; - normals.push(normal.x, normal.y, normal.z); + // Use unique set of vertices - return normalsHash[hash]; + var diff = this.vertices.length - unique.length; + this.vertices = unique; + return diff; - } + }, - function getColorIndex(color) { + sortFacesByMaterialIndex: function () { - var hash = color.r.toString() + color.g.toString() + color.b.toString(); + function materialIndexSort( a, b ) { - if (colorsHash[hash] !== undefined) { + return a.materialIndex - b.materialIndex; - return colorsHash[hash]; + } - } + this.faces.sort( materialIndexSort ); - colorsHash[hash] = colors.length; - colors.push(color.getHex()); + }, - return colorsHash[hash]; + toJSON: function () { - } + var data = { + metadata: { + version: 4.4, + type: 'Geometry', + generator: 'Geometry.toJSON' + } + }; - function getUvIndex(uv) { + // standard Geometry serialization - var hash = uv.x.toString() + uv.y.toString(); + data.uuid = this.uuid; + data.type = this.type; + if ( this.name !== '' ) data.name = this.name; - if (uvsHash[hash] !== undefined) { + if ( this.parameters !== undefined ) { - return uvsHash[hash]; + var parameters = this.parameters; - } + for ( var key in parameters ) { - uvsHash[hash] = uvs.length / 2; - uvs.push(uv.x, uv.y); + if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ]; - return uvsHash[hash]; + } - } + return data; - data.data = {}; + } - data.data.vertices = vertices; - data.data.normals = normals; - if (colors.length > 0) data.data.colors = colors; - if (uvs.length > 0) data.data.uvs = [uvs]; // temporal backward compatibility - data.data.faces = faces; + var vertices = []; - return data; + for ( var i = 0; i < this.vertices.length; i ++ ) { - }, + var vertex = this.vertices[ i ]; + vertices.push( vertex.x, vertex.y, vertex.z ); - clone: function () { + } - var geometry = new THREE.Geometry(); + var faces = []; + var normals = []; + var normalsHash = {}; + var colors = []; + var colorsHash = {}; + var uvs = []; + var uvsHash = {}; - var vertices = this.vertices; + for ( var i = 0; i < this.faces.length; i ++ ) { - for (var i = 0, il = vertices.length; i < il; i++) { + var face = this.faces[ i ]; - geometry.vertices.push(vertices[i].clone()); + var hasMaterial = false; // face.materialIndex !== undefined; + var hasFaceUv = false; // deprecated + var hasFaceVertexUv = this.faceVertexUvs[ 0 ][ i ] !== undefined; + var hasFaceNormal = face.normal.length() > 0; + var hasFaceVertexNormal = face.vertexNormals.length > 0; + var hasFaceColor = face.color.r !== 1 || face.color.g !== 1 || face.color.b !== 1; + var hasFaceVertexColor = face.vertexColors.length > 0; - } + var faceType = 0; - var faces = this.faces; + faceType = setBit( faceType, 0, 0 ); + faceType = setBit( faceType, 1, hasMaterial ); + faceType = setBit( faceType, 2, hasFaceUv ); + faceType = setBit( faceType, 3, hasFaceVertexUv ); + faceType = setBit( faceType, 4, hasFaceNormal ); + faceType = setBit( faceType, 5, hasFaceVertexNormal ); + faceType = setBit( faceType, 6, hasFaceColor ); + faceType = setBit( faceType, 7, hasFaceVertexColor ); - for (var i = 0, il = faces.length; i < il; i++) { + faces.push( faceType ); + faces.push( face.a, face.b, face.c ); - geometry.faces.push(faces[i].clone()); + if ( hasFaceVertexUv ) { - } + var faceVertexUvs = this.faceVertexUvs[ 0 ][ i ]; - for (var i = 0, il = this.faceVertexUvs.length; i < il; i++) { + faces.push( + getUvIndex( faceVertexUvs[ 0 ] ), + getUvIndex( faceVertexUvs[ 1 ] ), + getUvIndex( faceVertexUvs[ 2 ] ) + ); - var faceVertexUvs = this.faceVertexUvs[i]; + } - if (geometry.faceVertexUvs[i] === undefined) { + if ( hasFaceNormal ) { - geometry.faceVertexUvs[i] = []; + faces.push( getNormalIndex( face.normal ) ); - } + } - for (var j = 0, jl = faceVertexUvs.length; j < jl; j++) { + if ( hasFaceVertexNormal ) { - var uvs = faceVertexUvs[j], uvsCopy = []; + var vertexNormals = face.vertexNormals; - for (var k = 0, kl = uvs.length; k < kl; k++) { + faces.push( + getNormalIndex( vertexNormals[ 0 ] ), + getNormalIndex( vertexNormals[ 1 ] ), + getNormalIndex( vertexNormals[ 2 ] ) + ); - var uv = uvs[k]; + } - uvsCopy.push(uv.clone()); + if ( hasFaceColor ) { - } + faces.push( getColorIndex( face.color ) ); - geometry.faceVertexUvs[i].push(uvsCopy); + } - } + if ( hasFaceVertexColor ) { - } + var vertexColors = face.vertexColors; - return geometry; + faces.push( + getColorIndex( vertexColors[ 0 ] ), + getColorIndex( vertexColors[ 1 ] ), + getColorIndex( vertexColors[ 2 ] ) + ); - }, + } - dispose: function () { + } - this.dispatchEvent({type: 'dispose'}); + function setBit( value, position, enabled ) { - } + return enabled ? value | ( 1 << position ) : value & ( ~ ( 1 << position ) ); -}; + } -THREE.EventDispatcher.prototype.apply(THREE.Geometry.prototype); + function getNormalIndex( normal ) { -THREE.GeometryIdCount = 0; + var hash = normal.x.toString() + normal.y.toString() + normal.z.toString(); -// File:src/core/DynamicGeometry.js + if ( normalsHash[ hash ] !== undefined ) { -/** - * @author mrdoob / http://mrdoob.com/ - */ + return normalsHash[ hash ]; -THREE.DynamicGeometry = function () { + } - Object.defineProperty(this, 'id', {value: THREE.GeometryIdCount++}); + normalsHash[ hash ] = normals.length / 3; + normals.push( normal.x, normal.y, normal.z ); - this.uuid = THREE.Math.generateUUID(); + return normalsHash[ hash ]; - this.name = ''; - this.type = 'DynamicGeometry'; + } - this.vertices = []; - this.colors = []; - this.normals = []; - this.colors = []; - this.uvs = []; - this.faces = []; + function getColorIndex( color ) { - /* + var hash = color.r.toString() + color.g.toString() + color.b.toString(); - this.morphTargets = []; - this.morphColors = []; - this.morphNormals = []; + if ( colorsHash[ hash ] !== undefined ) { - this.skinWeights = []; - this.skinIndices = []; + return colorsHash[ hash ]; - this.lineDistances = []; + } - */ + colorsHash[ hash ] = colors.length; + colors.push( color.getHex() ); - this.boundingBox = null; - this.boundingSphere = null; + return colorsHash[ hash ]; - // update flags + } - this.verticesNeedUpdate = false; - this.normalsNeedUpdate = false; - this.colorsNeedUpdate = false; - this.uvsNeedUpdate = false; + function getUvIndex( uv ) { -}; + var hash = uv.x.toString() + uv.y.toString(); -THREE.DynamicGeometry.prototype = { + if ( uvsHash[ hash ] !== undefined ) { - constructor: THREE.DynamicGeometry, + return uvsHash[ hash ]; - computeBoundingBox: THREE.Geometry.prototype.computeBoundingBox, - computeBoundingSphere: THREE.Geometry.prototype.computeBoundingSphere, + } - computeFaceNormals: function () { + uvsHash[ hash ] = uvs.length / 2; + uvs.push( uv.x, uv.y ); - THREE.warn('THREE.DynamicGeometry: computeFaceNormals() is not a method of this type of geometry.'); - return this; + return uvsHash[ hash ]; - }, + } - computeVertexNormals: function () { + data.data = {}; - THREE.warn('THREE.DynamicGeometry: computeVertexNormals () is not a method of this type of geometry.'); - return this; + data.data.vertices = vertices; + data.data.normals = normals; + if ( colors.length > 0 ) data.data.colors = colors; + if ( uvs.length > 0 ) data.data.uvs = [ uvs ]; // temporal backward compatibility + data.data.faces = faces; - }, + return data; - fromGeometry: function (geometry) { + }, - this.vertices = geometry.vertices; - this.faces = geometry.faces; + clone: function () { - var faces = geometry.faces; - var faceVertexUvs = geometry.faceVertexUvs[0]; + return new this.constructor().copy( this ); - for (var i = 0, il = faces.length; i < il; i++) { + }, - var face = faces[i]; - var indices = [face.a, face.b, face.c]; + copy: function ( source ) { - var vertexNormals = face.vertexNormals; - var vertexColors = face.vertexColors; - var vertexUvs = faceVertexUvs[i]; + this.vertices = []; + this.faces = []; + this.faceVertexUvs = [ [] ]; - for (var j = 0, jl = vertexNormals.length; j < jl; j++) { + var vertices = source.vertices; - this.normals[indices[j]] = vertexNormals[j]; + for ( var i = 0, il = vertices.length; i < il; i ++ ) { - } + this.vertices.push( vertices[ i ].clone() ); - for (var j = 0, jl = vertexColors.length; j < jl; j++) { + } - this.colors[indices[j]] = vertexColors[j]; + var faces = source.faces; - } + for ( var i = 0, il = faces.length; i < il; i ++ ) { - for (var j = 0, jl = vertexUvs.length; j < jl; j++) { + this.faces.push( faces[ i ].clone() ); - this.uvs[indices[j]] = vertexUvs[j]; + } - } + for ( var i = 0, il = source.faceVertexUvs.length; i < il; i ++ ) { - } + var faceVertexUvs = source.faceVertexUvs[ i ]; - return this; + if ( this.faceVertexUvs[ i ] === undefined ) { - }, + this.faceVertexUvs[ i ] = []; - dispose: function () { + } - this.dispatchEvent({type: 'dispose'}); + for ( var j = 0, jl = faceVertexUvs.length; j < jl; j ++ ) { - } + var uvs = faceVertexUvs[ j ], uvsCopy = []; -}; - -THREE.EventDispatcher.prototype.apply(THREE.DynamicGeometry.prototype); + for ( var k = 0, kl = uvs.length; k < kl; k ++ ) { -// File:src/core/BufferGeometry.js + var uv = uvs[ k ]; -/** - * @author alteredq / http://alteredqualia.com/ - * @author mrdoob / http://mrdoob.com/ - */ + uvsCopy.push( uv.clone() ); -THREE.BufferGeometry = function () { + } - Object.defineProperty(this, 'id', {value: THREE.GeometryIdCount++}); + this.faceVertexUvs[ i ].push( uvsCopy ); - this.uuid = THREE.Math.generateUUID(); + } - this.name = ''; - this.type = 'BufferGeometry'; + } - this.attributes = {}; - this.attributesKeys = []; + return this; - this.drawcalls = []; - this.offsets = this.drawcalls; // backwards compatibility + }, - this.boundingBox = null; - this.boundingSphere = null; + dispose: function () { -}; + this.dispatchEvent( { type: 'dispose' } ); -THREE.BufferGeometry.prototype = { + } - constructor: THREE.BufferGeometry, +}; - addAttribute: function (name, attribute) { +THREE.EventDispatcher.prototype.apply( THREE.Geometry.prototype ); - if (attribute instanceof THREE.BufferAttribute === false && attribute instanceof THREE.InterleavedBufferAttribute === false) { +THREE.GeometryIdCount = 0; - THREE.warn('THREE.BufferGeometry: .addAttribute() now expects ( name, attribute ).'); +// File:src/core/DirectGeometry.js - this.attributes[name] = {array: arguments[1], itemSize: arguments[2]}; +/** + * @author mrdoob / http://mrdoob.com/ + */ - return; +THREE.DirectGeometry = function () { - } + Object.defineProperty( this, 'id', { value: THREE.GeometryIdCount ++ } ); - this.attributes[name] = attribute; - this.attributesKeys = Object.keys(this.attributes); + this.uuid = THREE.Math.generateUUID(); - }, + this.name = ''; + this.type = 'DirectGeometry'; - getAttribute: function (name) { + this.indices = []; + this.vertices = []; + this.normals = []; + this.colors = []; + this.uvs = []; + this.uvs2 = []; - return this.attributes[name]; + this.groups = []; - }, + this.morphTargets = {}; - addDrawCall: function (start, count, indexOffset) { + this.skinWeights = []; + this.skinIndices = []; - this.drawcalls.push({ + // this.lineDistances = []; - start: start, - count: count, - index: indexOffset !== undefined ? indexOffset : 0 + this.boundingBox = null; + this.boundingSphere = null; - }); + // update flags - }, + this.verticesNeedUpdate = false; + this.normalsNeedUpdate = false; + this.colorsNeedUpdate = false; + this.uvsNeedUpdate = false; + this.groupsNeedUpdate = false; - applyMatrix: function (matrix) { +}; - var position = this.attributes.position; +THREE.DirectGeometry.prototype = { - if (position !== undefined) { + constructor: THREE.DirectGeometry, - matrix.applyToBuffer(position); - position.needsUpdate = true; + computeBoundingBox: THREE.Geometry.prototype.computeBoundingBox, + computeBoundingSphere: THREE.Geometry.prototype.computeBoundingSphere, - } + computeFaceNormals: function () { - var normal = this.attributes.normal; + console.warn( 'THREE.DirectGeometry: computeFaceNormals() is not a method of this type of geometry.' ); - if (normal !== undefined) { + }, - var normalMatrix = new THREE.Matrix3().getNormalMatrix(matrix); + computeVertexNormals: function () { - normalMatrix.applyToBuffer(normal); - normal.needsUpdate = true; + console.warn( 'THREE.DirectGeometry: computeVertexNormals() is not a method of this type of geometry.' ); - } + }, - if (this.boundingBox !== null) { + computeGroups: function ( geometry ) { - this.computeBoundingBox(); + var group; + var groups = []; + var materialIndex; - } + var faces = geometry.faces; - if (this.boundingSphere !== null) { + for ( var i = 0; i < faces.length; i ++ ) { - this.computeBoundingSphere(); + var face = faces[ i ]; - } + // materials - }, + if ( face.materialIndex !== materialIndex ) { - center: function () { + materialIndex = face.materialIndex; - this.computeBoundingBox(); + if ( group !== undefined ) { - var offset = this.boundingBox.center().negate(); + group.count = ( i * 3 ) - group.start; + groups.push( group ); - this.applyMatrix(new THREE.Matrix4().setPosition(offset)); + } - return offset; + group = { + start: i * 3, + materialIndex: materialIndex + }; - }, + } - setFromObject: function (object) { + } - console.log('THREE.BufferGeometry.setFromObject(). Converting ', object, this); + if ( group !== undefined ) { - var geometry = object.geometry; - var material = object.material; + group.count = ( i * 3 ) - group.start; + groups.push( group ); - if (object instanceof THREE.PointCloud || object instanceof THREE.Line) { + } - var positions = new Float32Array(geometry.vertices.length * 3); - var colors = new Float32Array(geometry.colors.length * 3); + this.groups = groups; - this.addAttribute('position', new THREE.BufferAttribute(positions, 3).copyVector3sArray(geometry.vertices)); - this.addAttribute('color', new THREE.BufferAttribute(colors, 3).copyColorsArray(geometry.colors)); - this.computeBoundingSphere(); + }, - } else if (object instanceof THREE.Mesh) { + fromGeometry: function ( geometry ) { - if (geometry instanceof THREE.DynamicGeometry) { + var faces = geometry.faces; + var vertices = geometry.vertices; + var faceVertexUvs = geometry.faceVertexUvs; - this.fromDynamicGeometry(geometry); + var hasFaceVertexUv = faceVertexUvs[ 0 ] && faceVertexUvs[ 0 ].length > 0; + var hasFaceVertexUv2 = faceVertexUvs[ 1 ] && faceVertexUvs[ 1 ].length > 0; - } else if (geometry instanceof THREE.Geometry) { + // morphs - this.fromGeometry(geometry, material); + var morphTargets = geometry.morphTargets; + var morphTargetsLength = morphTargets.length; - } + if ( morphTargetsLength > 0 ) { - } + var morphTargetsPosition = []; - if (material.attributes !== undefined) { + for ( var i = 0; i < morphTargetsLength; i ++ ) { - var attributes = material.attributes; + morphTargetsPosition[ i ] = []; - for (var name in attributes) { + } - var attribute = attributes[name]; + this.morphTargets.position = morphTargetsPosition; - var type = attribute.type; - var array = attribute.value; + } - switch (type) { + var morphNormals = geometry.morphNormals; + var morphNormalsLength = morphNormals.length; - case "f": - var floats = new Float32Array(array.length); - this.addAttribute(name, new THREE.BufferAttribute(floats, 1).copyArray(array)); - break; + if ( morphNormalsLength > 0 ) { - case "c": - var colors = new Float32Array(array.length * 3); - this.addAttribute(name, new THREE.BufferAttribute(colors, 3).copyColorsArray(array)); - break; + var morphTargetsNormal = []; - case "v3": - var colors = new Float32Array(array.length * 3); - this.addAttribute(name, new THREE.BufferAttribute(colors, 3).copyVector3sArray(array)); - break; + for ( var i = 0; i < morphNormalsLength; i ++ ) { - default: - console.warn('THREE.BufferGeometry.setFromObject(). TODO: attribute unsupported', type); - break; + morphTargetsNormal[ i ] = []; - } + } - } + this.morphTargets.normal = morphTargetsNormal; - } + } - return this; + // skins - }, + var skinIndices = geometry.skinIndices; + var skinWeights = geometry.skinWeights; - updateFromObject: function (object) { + var hasSkinIndices = skinIndices.length === vertices.length; + var hasSkinWeights = skinWeights.length === vertices.length; - var geometry = object.geometry; + // - if (geometry.verticesNeedUpdate === true) { + for ( var i = 0; i < faces.length; i ++ ) { - var attribute = this.attributes.position; + var face = faces[ i ]; - if (attribute !== undefined) { + this.vertices.push( vertices[ face.a ], vertices[ face.b ], vertices[ face.c ] ); - attribute.copyVector3sArray(geometry.vertices); - attribute.needsUpdate = true; + var vertexNormals = face.vertexNormals; - } + if ( vertexNormals.length === 3 ) { - geometry.verticesNeedUpdate = false; + this.normals.push( vertexNormals[ 0 ], vertexNormals[ 1 ], vertexNormals[ 2 ] ); - } + } else { - if (geometry.colorsNeedUpdate === true) { + var normal = face.normal; - var attribute = this.attributes.color; + this.normals.push( normal, normal, normal ); - if (attribute !== undefined) { + } - attribute.copyColorsArray(geometry.colors); - attribute.needsUpdate = true; + var vertexColors = face.vertexColors; - } + if ( vertexColors.length === 3 ) { - geometry.colorsNeedUpdate = false; + this.colors.push( vertexColors[ 0 ], vertexColors[ 1 ], vertexColors[ 2 ] ); - } + } else { - }, + var color = face.color; - updateFromMaterial: function (material) { + this.colors.push( color, color, color ); - if (material.attributes !== undefined) { + } - var attributes = material.attributes; + if ( hasFaceVertexUv === true ) { - for (var name in attributes) { + var vertexUvs = faceVertexUvs[ 0 ][ i ]; - var attribute = attributes[name]; + if ( vertexUvs !== undefined ) { - var type = attribute.type; - var array = attribute.value; + this.uvs.push( vertexUvs[ 0 ], vertexUvs[ 1 ], vertexUvs[ 2 ] ); - switch (type) { + } else { - case "f": - this.attributes[name].copyArray(array); - this.attributes[name].needsUpdate = true; - break; + console.warn( 'THREE.DirectGeometry.fromGeometry(): Undefined vertexUv ', i ); - case "c": - this.attributes[name].copyColorsArray(array); - this.attributes[name].needsUpdate = true; - break; + this.uvs.push( new THREE.Vector2(), new THREE.Vector2(), new THREE.Vector2() ); - case "v3": - this.attributes[name].copyVector3sArray(array); - this.attributes[name].needsUpdate = true; - break; + } - } + } - } + if ( hasFaceVertexUv2 === true ) { - } + var vertexUvs = faceVertexUvs[ 1 ][ i ]; - }, + if ( vertexUvs !== undefined ) { - fromGeometry: function (geometry, material) { + this.uvs2.push( vertexUvs[ 0 ], vertexUvs[ 1 ], vertexUvs[ 2 ] ); - material = material || {'vertexColors': THREE.NoColors}; + } else { - var vertices = geometry.vertices; - var faces = geometry.faces; - var faceVertexUvs = geometry.faceVertexUvs; - var vertexColors = material.vertexColors; + console.warn( 'THREE.DirectGeometry.fromGeometry(): Undefined vertexUv2 ', i ); - var hasFaceVertexUv = faceVertexUvs[0].length > 0; - var hasFaceVertexUv2 = faceVertexUvs[1] && faceVertexUvs[1].length > 0; + this.uvs2.push( new THREE.Vector2(), new THREE.Vector2(), new THREE.Vector2() ); - var hasFaceVertexNormals = faces[0] && faces[0].vertexNormals.length == 3; + } - var positions = new Float32Array(faces.length * 3 * 3); - this.addAttribute('position', new THREE.BufferAttribute(positions, 3)); + } - var normals = new Float32Array(faces.length * 3 * 3); - this.addAttribute('normal', new THREE.BufferAttribute(normals, 3)); + // morphs - if (vertexColors !== THREE.NoColors) { + for ( var j = 0; j < morphTargetsLength; j ++ ) { - var colors = new Float32Array(faces.length * 3 * 3); - this.addAttribute('color', new THREE.BufferAttribute(colors, 3)); + var morphTarget = morphTargets[ j ].vertices; - } + morphTargetsPosition[ j ].push( morphTarget[ face.a ], morphTarget[ face.b ], morphTarget[ face.c ] ); - if (hasFaceVertexUv === true) { + } - var uvs = new Float32Array(faces.length * 3 * 2); - this.addAttribute('uv', new THREE.BufferAttribute(uvs, 2)); + for ( var j = 0; j < morphNormalsLength; j ++ ) { - } + var morphNormal = morphNormals[ j ].vertexNormals[ i ]; - if (hasFaceVertexUv2 === true) { + morphTargetsNormal[ j ].push( morphNormal.a, morphNormal.b, morphNormal.c ); - var uvs2 = new Float32Array(faces.length * 3 * 2); - this.addAttribute('uv2', new THREE.BufferAttribute(uvs2, 2)); + } - } + // skins - for (var i = 0, i2 = 0, i3 = 0; i < faces.length; i++, i2 += 6, i3 += 9) { + if ( hasSkinIndices ) { - var face = faces[i]; + this.skinIndices.push( skinIndices[ face.a ], skinIndices[ face.b ], skinIndices[ face.c ] ); - var a = vertices[face.a]; - var b = vertices[face.b]; - var c = vertices[face.c]; + } - positions[i3] = a.x; - positions[i3 + 1] = a.y; - positions[i3 + 2] = a.z; + if ( hasSkinWeights ) { - positions[i3 + 3] = b.x; - positions[i3 + 4] = b.y; - positions[i3 + 5] = b.z; + this.skinWeights.push( skinWeights[ face.a ], skinWeights[ face.b ], skinWeights[ face.c ] ); - positions[i3 + 6] = c.x; - positions[i3 + 7] = c.y; - positions[i3 + 8] = c.z; + } - if (hasFaceVertexNormals === true) { + } - var na = face.vertexNormals[0]; - var nb = face.vertexNormals[1]; - var nc = face.vertexNormals[2]; + this.computeGroups( geometry ); - normals[i3] = na.x; - normals[i3 + 1] = na.y; - normals[i3 + 2] = na.z; + this.verticesNeedUpdate = geometry.verticesNeedUpdate; + this.normalsNeedUpdate = geometry.normalsNeedUpdate; + this.colorsNeedUpdate = geometry.colorsNeedUpdate; + this.uvsNeedUpdate = geometry.uvsNeedUpdate; + this.groupsNeedUpdate = geometry.groupsNeedUpdate; - normals[i3 + 3] = nb.x; - normals[i3 + 4] = nb.y; - normals[i3 + 5] = nb.z; + return this; - normals[i3 + 6] = nc.x; - normals[i3 + 7] = nc.y; - normals[i3 + 8] = nc.z; + }, - } else { + dispose: function () { - var n = face.normal; + this.dispatchEvent( { type: 'dispose' } ); - normals[i3] = n.x; - normals[i3 + 1] = n.y; - normals[i3 + 2] = n.z; + } - normals[i3 + 3] = n.x; - normals[i3 + 4] = n.y; - normals[i3 + 5] = n.z; +}; - normals[i3 + 6] = n.x; - normals[i3 + 7] = n.y; - normals[i3 + 8] = n.z; +THREE.EventDispatcher.prototype.apply( THREE.DirectGeometry.prototype ); - } +// File:src/core/BufferGeometry.js - if (vertexColors === THREE.FaceColors) { +/** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + */ - var fc = face.color; +THREE.BufferGeometry = function () { - colors[i3] = fc.r; - colors[i3 + 1] = fc.g; - colors[i3 + 2] = fc.b; + Object.defineProperty( this, 'id', { value: THREE.GeometryIdCount ++ } ); - colors[i3 + 3] = fc.r; - colors[i3 + 4] = fc.g; - colors[i3 + 5] = fc.b; + this.uuid = THREE.Math.generateUUID(); - colors[i3 + 6] = fc.r; - colors[i3 + 7] = fc.g; - colors[i3 + 8] = fc.b; + this.name = ''; + this.type = 'BufferGeometry'; - } else if (vertexColors === THREE.VertexColors) { + this.index = null; + this.attributes = {}; - var vca = face.vertexColors[0]; - var vcb = face.vertexColors[1]; - var vcc = face.vertexColors[2]; + this.morphAttributes = {}; - colors[i3] = vca.r; - colors[i3 + 1] = vca.g; - colors[i3 + 2] = vca.b; + this.groups = []; - colors[i3 + 3] = vcb.r; - colors[i3 + 4] = vcb.g; - colors[i3 + 5] = vcb.b; + this.boundingBox = null; + this.boundingSphere = null; - colors[i3 + 6] = vcc.r; - colors[i3 + 7] = vcc.g; - colors[i3 + 8] = vcc.b; + this.drawRange = { start: 0, count: Infinity }; - } +}; - if (hasFaceVertexUv === true) { +THREE.BufferGeometry.prototype = { - var uva = faceVertexUvs[0][i][0]; - var uvb = faceVertexUvs[0][i][1]; - var uvc = faceVertexUvs[0][i][2]; + constructor: THREE.BufferGeometry, - uvs[i2] = uva.x; - uvs[i2 + 1] = uva.y; + addIndex: function ( index ) { - uvs[i2 + 2] = uvb.x; - uvs[i2 + 3] = uvb.y; + console.warn( 'THREE.BufferGeometry: .addIndex() has been renamed to .setIndex().' ); + this.setIndex( index ); - uvs[i2 + 4] = uvc.x; - uvs[i2 + 5] = uvc.y; + }, - } + getIndex: function () { - if (hasFaceVertexUv2 === true) { + return this.index; - var uva = faceVertexUvs[1][i][0]; - var uvb = faceVertexUvs[1][i][1]; - var uvc = faceVertexUvs[1][i][2]; + }, - uvs2[i2] = uva.x; - uvs2[i2 + 1] = uva.y; + setIndex: function ( index ) { - uvs2[i2 + 2] = uvb.x; - uvs2[i2 + 3] = uvb.y; + this.index = index; - uvs2[i2 + 4] = uvc.x; - uvs2[i2 + 5] = uvc.y; + }, - } + addAttribute: function ( name, attribute ) { - } + if ( attribute instanceof THREE.BufferAttribute === false && attribute instanceof THREE.InterleavedBufferAttribute === false ) { - this.computeBoundingSphere(); + console.warn( 'THREE.BufferGeometry: .addAttribute() now expects ( name, attribute ).' ); - return this; + this.addAttribute( name, new THREE.BufferAttribute( arguments[ 1 ], arguments[ 2 ] ) ); - }, + return; - fromDynamicGeometry: function (geometry) { + } - var indices = new Uint16Array(geometry.faces.length * 3); - this.addAttribute('index', new THREE.BufferAttribute(indices, 1).copyFacesArray(geometry.faces)); + if ( name === 'index' ) { - var positions = new Float32Array(geometry.vertices.length * 3); - this.addAttribute('position', new THREE.BufferAttribute(positions, 3).copyVector3sArray(geometry.vertices)); + console.warn( 'THREE.BufferGeometry.addAttribute: Use .setIndex() for index attribute.' ); + this.setIndex( attribute ); - var normals = new Float32Array(geometry.normals.length * 3); - this.addAttribute('normal', new THREE.BufferAttribute(normals, 3).copyVector3sArray(geometry.normals)); + return; - var colors = new Float32Array(geometry.colors.length * 3); - this.addAttribute('color', new THREE.BufferAttribute(colors, 3).copyVector3sArray(geometry.colors)); + } - var uvs = new Float32Array(geometry.uvs.length * 2); - this.addAttribute('uv', new THREE.BufferAttribute(uvs, 2).copyVector2sArray(geometry.uvs)); + this.attributes[ name ] = attribute; - this.computeBoundingSphere(); + }, - return this; + getAttribute: function ( name ) { - }, + return this.attributes[ name ]; - computeBoundingBox: function () { + }, - var vector = new THREE.Vector3(); + removeAttribute: function ( name ) { - return function () { + delete this.attributes[ name ]; - if (this.boundingBox === null) { + }, - this.boundingBox = new THREE.Box3(); + get drawcalls() { - } + console.error( 'THREE.BufferGeometry: .drawcalls has been renamed to .groups.' ); + return this.groups; - var positions = this.attributes.position; + }, - if (positions) { + get offsets() { - var bb = this.boundingBox; - bb.makeEmpty(); + console.warn( 'THREE.BufferGeometry: .offsets has been renamed to .groups.' ); + return this.groups; - for (var i = 0, il = positions.length / positions.itemSize; i < il; i++) { + }, - vector.set(positions.getX(i), positions.getY(i), positions.getZ(i)); - bb.expandByPoint(vector); + addDrawCall: function ( start, count, indexOffset ) { - } + if ( indexOffset !== undefined ) { - } + console.warn( 'THREE.BufferGeometry: .addDrawCall() no longer supports indexOffset.' ); - if (positions === undefined || positions.length === 0) { + } - this.boundingBox.min.set(0, 0, 0); - this.boundingBox.max.set(0, 0, 0); + console.warn( 'THREE.BufferGeometry: .addDrawCall() is now .addGroup().' ); + this.addGroup( start, count ); - } + }, - if (isNaN(this.boundingBox.min.x) || isNaN(this.boundingBox.min.y) || isNaN(this.boundingBox.min.z)) { + clearDrawCalls: function () { - THREE.error('THREE.BufferGeometry.computeBoundingBox: Computed min/max have NaN values. The "position" attribute is likely to have NaN values.'); + console.warn( 'THREE.BufferGeometry: .clearDrawCalls() is now .clearGroups().' ); + this.clearGroups(); - } + }, - } + addGroup: function ( start, count, materialIndex ) { - }(), + this.groups.push( { - computeBoundingSphere: function () { + start: start, + count: count, + materialIndex: materialIndex !== undefined ? materialIndex : 0 - var box = new THREE.Box3(); - var vector = new THREE.Vector3(); + } ); - return function () { + }, - if (this.boundingSphere === null) { + clearGroups: function () { - this.boundingSphere = new THREE.Sphere(); + this.groups = []; - } + }, - var positions = this.attributes.position; + setDrawRange: function ( start, count ) { - if (positions) { + this.drawRange.start = start; + this.drawRange.count = count; - box.makeEmpty(); + }, - var center = this.boundingSphere.center; + applyMatrix: function ( matrix ) { - for (var i = 0, il = positions.length / positions.itemSize; i < il; i++) { + var position = this.attributes.position; - vector.set(positions.getX(i), positions.getY(i), positions.getZ(i)); - box.expandByPoint(vector); + if ( position !== undefined ) { - } + matrix.applyToVector3Array( position.array ); + position.needsUpdate = true; - box.center(center); + } - // hoping to find a boundingSphere with a radius smaller than the - // boundingSphere of the boundingBox: sqrt(3) smaller in the best case + var normal = this.attributes.normal; - var maxRadiusSq = 0; + if ( normal !== undefined ) { - for (var i = 0, il = positions.length / positions.itemSize; i < il; i++) { + var normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix ); - vector.set(positions.getX(i), positions.getY(i), positions.getZ(i)); - maxRadiusSq = Math.max(maxRadiusSq, center.distanceToSquared(vector)); + normalMatrix.applyToVector3Array( normal.array ); + normal.needsUpdate = true; - } + } - this.boundingSphere.radius = Math.sqrt(maxRadiusSq); + if ( this.boundingBox !== null ) { - if (isNaN(this.boundingSphere.radius)) { + this.computeBoundingBox(); - THREE.error('THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.'); + } - } + if ( this.boundingSphere !== null ) { - } + this.computeBoundingSphere(); - } + } - }(), + }, - computeFaceNormals: function () { + rotateX: function () { - // backwards compatibility + // rotate geometry around world x-axis - }, + var m1; - computeVertexNormals: function () { + return function rotateX( angle ) { - var attributes = this.attributes; + if ( m1 === undefined ) m1 = new THREE.Matrix4(); - if (attributes.position) { + m1.makeRotationX( angle ); - var positions = attributes.position.array; + this.applyMatrix( m1 ); - if (attributes.normal === undefined) { + return this; - this.addAttribute('normal', new THREE.BufferAttribute(new Float32Array(positions.length), 3)); + }; - } else { + }(), - // reset existing normals to zero + rotateY: function () { - var normals = attributes.normal.array; + // rotate geometry around world y-axis - for (var i = 0, il = normals.length; i < il; i++) { + var m1; - normals[i] = 0; + return function rotateY( angle ) { - } + if ( m1 === undefined ) m1 = new THREE.Matrix4(); - } + m1.makeRotationY( angle ); - var normals = attributes.normal.array; + this.applyMatrix( m1 ); - var vA, vB, vC, + return this; - pA = new THREE.Vector3(), - pB = new THREE.Vector3(), - pC = new THREE.Vector3(), + }; - cb = new THREE.Vector3(), - ab = new THREE.Vector3(); + }(), - // indexed elements + rotateZ: function () { - if (attributes.index) { + // rotate geometry around world z-axis - var indices = attributes.index.array; + var m1; - var offsets = ( this.offsets.length > 0 ? this.offsets : [{ - start: 0, - count: indices.length, - index: 0 - }] ); + return function rotateZ( angle ) { - for (var j = 0, jl = offsets.length; j < jl; ++j) { + if ( m1 === undefined ) m1 = new THREE.Matrix4(); - var start = offsets[j].start; - var count = offsets[j].count; - var index = offsets[j].index; + m1.makeRotationZ( angle ); - for (var i = start, il = start + count; i < il; i += 3) { + this.applyMatrix( m1 ); - vA = ( index + indices[i] ) * 3; - vB = ( index + indices[i + 1] ) * 3; - vC = ( index + indices[i + 2] ) * 3; + return this; - pA.fromArray(positions, vA); - pB.fromArray(positions, vB); - pC.fromArray(positions, vC); + }; - cb.subVectors(pC, pB); - ab.subVectors(pA, pB); - cb.cross(ab); + }(), - normals[vA] += cb.x; - normals[vA + 1] += cb.y; - normals[vA + 2] += cb.z; + translate: function () { - normals[vB] += cb.x; - normals[vB + 1] += cb.y; - normals[vB + 2] += cb.z; + // translate geometry - normals[vC] += cb.x; - normals[vC + 1] += cb.y; - normals[vC + 2] += cb.z; + var m1; - } + return function translate( x, y, z ) { - } + if ( m1 === undefined ) m1 = new THREE.Matrix4(); - } else { + m1.makeTranslation( x, y, z ); - // non-indexed elements (unconnected triangle soup) + this.applyMatrix( m1 ); - for (var i = 0, il = positions.length; i < il; i += 9) { + return this; - pA.fromArray(positions, i); - pB.fromArray(positions, i + 3); - pC.fromArray(positions, i + 6); + }; - 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; + scale: function () { - normals[i + 3] = cb.x; - normals[i + 4] = cb.y; - normals[i + 5] = cb.z; + // scale geometry - normals[i + 6] = cb.x; - normals[i + 7] = cb.y; - normals[i + 8] = cb.z; + var m1; - } + return function scale( x, y, z ) { - } + if ( m1 === undefined ) m1 = new THREE.Matrix4(); - this.normalizeNormals(); + m1.makeScale( x, y, z ); - attributes.normal.needsUpdate = true; + this.applyMatrix( m1 ); - } + return this; - }, + }; - computeTangents: function () { + }(), - // based on http://www.terathon.com/code/tangent.html - // (per vertex tangents) + lookAt: function () { - if (this.attributes.index === undefined || - this.attributes.position === undefined || - this.attributes.normal === undefined || - this.attributes.uv === undefined) { + var obj; - THREE.warn('THREE.BufferGeometry: Missing required attributes (index, position, normal or uv) in BufferGeometry.computeTangents()'); - return; + return function lookAt( vector ) { - } + if ( obj === undefined ) obj = new THREE.Object3D(); - var indices = this.attributes.index.array; - var positions = this.attributes.position.array; - var normals = this.attributes.normal.array; - var uvs = this.attributes.uv.array; + obj.lookAt( vector ); - var nVertices = positions.length / 3; + obj.updateMatrix(); - if (this.attributes.tangent === undefined) { + this.applyMatrix( obj.matrix ); - this.addAttribute('tangent', new THREE.BufferAttribute(new Float32Array(4 * nVertices), 4)); + }; - } + }(), - var tangents = this.attributes.tangent.array; + center: function () { - var tan1 = [], tan2 = []; + this.computeBoundingBox(); - for (var k = 0; k < nVertices; k++) { + var offset = this.boundingBox.center().negate(); - tan1[k] = new THREE.Vector3(); - tan2[k] = new THREE.Vector3(); + this.translate( offset.x, offset.y, offset.z ); - } + return offset; - var vA = new THREE.Vector3(), - vB = new THREE.Vector3(), - vC = new THREE.Vector3(), + }, - uvA = new THREE.Vector2(), - uvB = new THREE.Vector2(), - uvC = new THREE.Vector2(), + setFromObject: function ( object ) { - x1, x2, y1, y2, z1, z2, - s1, s2, t1, t2, r; + // console.log( 'THREE.BufferGeometry.setFromObject(). Converting', object, this ); - var sdir = new THREE.Vector3(), tdir = new THREE.Vector3(); + var geometry = object.geometry; - function handleTriangle(a, b, c) { + if ( object instanceof THREE.Points || object instanceof THREE.Line ) { - vA.fromArray(positions, a * 3); - vB.fromArray(positions, b * 3); - vC.fromArray(positions, c * 3); + var positions = new THREE.Float32Attribute( geometry.vertices.length * 3, 3 ); + var colors = new THREE.Float32Attribute( geometry.colors.length * 3, 3 ); - uvA.fromArray(uvs, a * 2); - uvB.fromArray(uvs, b * 2); - uvC.fromArray(uvs, c * 2); + this.addAttribute( 'position', positions.copyVector3sArray( geometry.vertices ) ); + this.addAttribute( 'color', colors.copyColorsArray( geometry.colors ) ); - x1 = vB.x - vA.x; - x2 = vC.x - vA.x; + if ( geometry.lineDistances && geometry.lineDistances.length === geometry.vertices.length ) { - y1 = vB.y - vA.y; - y2 = vC.y - vA.y; + var lineDistances = new THREE.Float32Attribute( geometry.lineDistances.length, 1 ); - z1 = vB.z - vA.z; - z2 = vC.z - vA.z; + this.addAttribute( 'lineDistance', lineDistances.copyArray( geometry.lineDistances ) ); - s1 = uvB.x - uvA.x; - s2 = uvC.x - uvA.x; + } - t1 = uvB.y - uvA.y; - t2 = uvC.y - uvA.y; + if ( geometry.boundingSphere !== null ) { - r = 1.0 / ( s1 * t2 - s2 * t1 ); + this.boundingSphere = geometry.boundingSphere.clone(); - 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 - ); + if ( geometry.boundingBox !== null ) { - tan1[a].add(sdir); - tan1[b].add(sdir); - tan1[c].add(sdir); + this.boundingBox = geometry.boundingBox.clone(); - tan2[a].add(tdir); - tan2[b].add(tdir); - tan2[c].add(tdir); + } - } + } else if ( object instanceof THREE.Mesh ) { - var i, il; - var j, jl; - var iA, iB, iC; + if ( geometry instanceof THREE.Geometry ) { - if (this.drawcalls.length === 0) { + this.fromGeometry( geometry ); - this.addDrawCall(0, indices.length, 0); + } - } + } - var drawcalls = this.drawcalls; + return this; - for (j = 0, jl = drawcalls.length; j < jl; ++j) { + }, - var start = drawcalls[j].start; - var count = drawcalls[j].count; - var index = drawcalls[j].index; + updateFromObject: function ( object ) { - for (i = start, il = start + count; i < il; i += 3) { + var geometry = object.geometry; - iA = index + indices[i]; - iB = index + indices[i + 1]; - iC = index + indices[i + 2]; + if ( object instanceof THREE.Mesh ) { - handleTriangle(iA, iB, iC); + var direct = geometry.__directGeometry; - } + if ( direct === undefined ) { - } + return this.fromGeometry( geometry ); - 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) { + direct.verticesNeedUpdate = geometry.verticesNeedUpdate; + direct.normalsNeedUpdate = geometry.normalsNeedUpdate; + direct.colorsNeedUpdate = geometry.colorsNeedUpdate; + direct.uvsNeedUpdate = geometry.uvsNeedUpdate; + direct.groupsNeedUpdate = geometry.groupsNeedUpdate; - n.fromArray(normals, v * 3); - n2.copy(n); + geometry.verticesNeedUpdate = false; + geometry.normalsNeedUpdate = false; + geometry.colorsNeedUpdate = false; + geometry.uvsNeedUpdate = false; + geometry.groupsNeedUpdate = false; - t = tan1[v]; + geometry = direct; - // Gram-Schmidt orthogonalize + } - tmp.copy(t); - tmp.sub(n.multiplyScalar(n.dot(t))).normalize(); + if ( geometry.verticesNeedUpdate === true ) { - // Calculate handedness + var attribute = this.attributes.position; - tmp2.crossVectors(n2, t); - test = tmp2.dot(tan2[v]); - w = ( test < 0.0 ) ? -1.0 : 1.0; + if ( attribute !== undefined ) { - tangents[v * 4] = tmp.x; - tangents[v * 4 + 1] = tmp.y; - tangents[v * 4 + 2] = tmp.z; - tangents[v * 4 + 3] = w; + attribute.copyVector3sArray( geometry.vertices ); + attribute.needsUpdate = true; - } + } - for (j = 0, jl = drawcalls.length; j < jl; ++j) { + geometry.verticesNeedUpdate = false; - var start = drawcalls[j].start; - var count = drawcalls[j].count; - var index = drawcalls[j].index; + } - for (i = start, il = start + count; i < il; i += 3) { + if ( geometry.normalsNeedUpdate === true ) { - iA = index + indices[i]; - iB = index + indices[i + 1]; - iC = index + indices[i + 2]; + var attribute = this.attributes.normal; - handleVertex(iA); - handleVertex(iB); - handleVertex(iC); + if ( attribute !== undefined ) { - } + attribute.copyVector3sArray( geometry.normals ); + attribute.needsUpdate = true; - } + } - }, + geometry.normalsNeedUpdate = false; - /* - 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. - size - Defaults to 65535 or 4294967296 if extension OES_element_index_uint supported, but allows for larger or smaller chunks. - */ - computeOffsets: function (size) { + } - if (size === undefined) size = THREE.BufferGeometry.MaxIndex; + if ( geometry.colorsNeedUpdate === true ) { - var indices = this.attributes.index.array; - var vertices = this.attributes.position.array; + var attribute = this.attributes.color; - var facesCount = ( indices.length / 3 ); + if ( attribute !== undefined ) { - var UintArray = ( ( vertices.length / 3 ) > 65535 && THREE.BufferGeometry.MaxIndex > 65535 ) ? Uint32Array : Uint16Array; + attribute.copyColorsArray( geometry.colors ); + attribute.needsUpdate = true; - /* - THREE.log("Computing buffers in offsets of "+size+" -> indices:"+indices.length+" vertices:"+vertices.length); - THREE.log("Faces to process: "+(indices.length/3)); - THREE.log("Reordering "+verticesCount+" vertices."); - */ + } - var sortedIndices = new UintArray(indices.length); + geometry.colorsNeedUpdate = false; - var indexPtr = 0; - var vertexPtr = 0; + } - var offsets = [{start: 0, count: 0, index: 0}]; - var offset = offsets[0]; + if ( geometry.lineDistancesNeedUpdate ) { - 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; - } + var attribute = this.attributes.lineDistance; - /* - Traverse every face and reorder vertices in the proper offsets of 65k. - We can have more than 'size' entries in the index buffer per offset, but only reference 'size' values. - */ - for (var findex = 0; findex < facesCount; findex++) { - newVerticeMaps = 0; + if ( attribute !== undefined ) { - 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]; - } - } + attribute.copyArray( geometry.lineDistances ); + attribute.needsUpdate = true; - 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; - } - } + geometry.lineDistancesNeedUpdate = false; - //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++; + if ( geometry.groupsNeedUpdate ) { - vertexMap[vid] = new_vid; - revVertexMap[new_vid] = vid; - sortedIndices[indexPtr++] = new_vid - offset.index; //XXX overflows at 16bit - offset.count++; - } - } + geometry.computeGroups( object.geometry ); + this.groups = geometry.groups; - /* 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; // TODO: Deprecate - this.drawcalls = offsets; - - /* - var orderTime = Date.now(); - THREE.log("Reorder time: "+(orderTime-s)+"ms"); - THREE.log("Duplicated "+duplicatedVertices+" vertices."); - THREE.log("Compute Buffers time: "+(Date.now()-s)+"ms"); - THREE.log("Draw offsets: "+offsets.length); - */ - - return offsets; - - }, - - merge: function (geometry, offset) { - - if (geometry instanceof THREE.BufferGeometry === false) { - - THREE.error('THREE.BufferGeometry.merge(): geometry not an instance of THREE.BufferGeometry.', geometry); - return; - - } - - if (offset === undefined) offset = 0; - - var attributes = this.attributes; - - for (var key in attributes) { - - if (geometry.attributes[key] === undefined) continue; - - var attribute1 = attributes[key]; - var attributeArray1 = attribute1.array; - - var attribute2 = geometry.attributes[key]; - var attributeArray2 = attribute2.array; - - var attributeSize = attribute2.itemSize; - - for (var i = 0, j = attributeSize * offset; i < attributeArray2.length; i++, j++) { - - attributeArray1[j] = attributeArray2[i]; - - } + geometry.groupsNeedUpdate = false; - } + } - return this; + return this; - }, + }, - normalizeNormals: function () { + fromGeometry: function ( geometry ) { - var normals = this.attributes.normal.array; + geometry.__directGeometry = new THREE.DirectGeometry().fromGeometry( geometry ); - var x, y, z, n; + return this.fromDirectGeometry( geometry.__directGeometry ); - for (var i = 0, il = normals.length; i < il; i += 3) { + }, - x = normals[i]; - y = normals[i + 1]; - z = normals[i + 2]; + fromDirectGeometry: function ( geometry ) { - n = 1.0 / Math.sqrt(x * x + y * y + z * z); + var positions = new Float32Array( geometry.vertices.length * 3 ); + this.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ).copyVector3sArray( geometry.vertices ) ); - normals[i] *= n; - normals[i + 1] *= n; - normals[i + 2] *= n; + if ( geometry.normals.length > 0 ) { - } + var normals = new Float32Array( geometry.normals.length * 3 ); + this.addAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ).copyVector3sArray( geometry.normals ) ); - }, + } - /* - 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) { + if ( geometry.colors.length > 0 ) { - /* Create a copy of all attributes for reordering. */ - var sortedAttributes = {}; - for (var attr in this.attributes) { - if (attr == 'index') - continue; - var sourceArray = this.attributes[attr].array; - sortedAttributes[attr] = new sourceArray.constructor(this.attributes[attr].itemSize * vertexCount); - } + var colors = new Float32Array( geometry.colors.length * 3 ); + this.addAttribute( 'color', new THREE.BufferAttribute( colors, 3 ).copyColorsArray( geometry.colors ) ); - /* 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; - } - }, + if ( geometry.uvs.length > 0 ) { - toJSON: function () { + var uvs = new Float32Array( geometry.uvs.length * 2 ); + this.addAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ).copyVector2sArray( geometry.uvs ) ); - // we will store all serialization data on 'data' - var data = {}; + } - // add metadata - data.metadata = { - version: 4.4, - type: 'BufferGeometry', - generator: 'BufferGeometry.toJSON' - }; + if ( geometry.uvs2.length > 0 ) { - // standard BufferGeometry serialization + var uvs2 = new Float32Array( geometry.uvs2.length * 2 ); + this.addAttribute( 'uv2', new THREE.BufferAttribute( uvs2, 2 ).copyVector2sArray( geometry.uvs2 ) ); - data.type = this.type; - data.uuid = this.uuid; - if (this.name !== '') data.name = this.name; - data.data = {}; - data.data.attributes = {}; + } - var attributes = this.attributes; - var offsets = this.offsets; - var boundingSphere = this.boundingSphere; + if ( geometry.indices.length > 0 ) { - for (var key in attributes) { + var TypeArray = geometry.vertices.length > 65535 ? Uint32Array : Uint16Array; + var indices = new TypeArray( geometry.indices.length * 3 ); + this.setIndex( new THREE.BufferAttribute( indices, 1 ).copyIndicesArray( geometry.indices ) ); - var attribute = attributes[key]; + } - var array = Array.prototype.slice.call(attribute.array); + // groups - data.data.attributes[key] = { - itemSize: attribute.itemSize, - type: attribute.array.constructor.name, - array: array - } + this.groups = geometry.groups; - } + // morphs - if (offsets.length > 0) { + for ( var name in geometry.morphTargets ) { - data.data.offsets = JSON.parse(JSON.stringify(offsets)); + var array = []; + var morphTargets = geometry.morphTargets[ name ]; - } + for ( var i = 0, l = morphTargets.length; i < l; i ++ ) { - if (boundingSphere !== null) { + var morphTarget = morphTargets[ i ]; - data.data.boundingSphere = { - center: boundingSphere.center.toArray(), - radius: boundingSphere.radius - } + var attribute = new THREE.Float32Attribute( morphTarget.length * 3, 3 ); - } + array.push( attribute.copyVector3sArray( morphTarget ) ); - return data; + } - }, + this.morphAttributes[ name ] = array; - clone: function () { + } - var geometry = new THREE.BufferGeometry(); + // skinning - for (var attr in this.attributes) { + if ( geometry.skinIndices.length > 0 ) { - var sourceAttr = this.attributes[attr]; - geometry.addAttribute(attr, sourceAttr.clone()); + var skinIndices = new THREE.Float32Attribute( geometry.skinIndices.length * 4, 4 ); + this.addAttribute( 'skinIndex', skinIndices.copyVector4sArray( geometry.skinIndices ) ); - } + } - for (var i = 0, il = this.offsets.length; i < il; i++) { + if ( geometry.skinWeights.length > 0 ) { - var offset = this.offsets[i]; + var skinWeights = new THREE.Float32Attribute( geometry.skinWeights.length * 4, 4 ); + this.addAttribute( 'skinWeight', skinWeights.copyVector4sArray( geometry.skinWeights ) ); - geometry.offsets.push({ + } - start: offset.start, - index: offset.index, - count: offset.count + // - }); + if ( geometry.boundingSphere !== null ) { - } + this.boundingSphere = geometry.boundingSphere.clone(); - return geometry; + } - }, + if ( geometry.boundingBox !== null ) { - dispose: function () { + this.boundingBox = geometry.boundingBox.clone(); - this.dispatchEvent({type: 'dispose'}); + } - } + return this; -}; + }, -THREE.EventDispatcher.prototype.apply(THREE.BufferGeometry.prototype); + computeBoundingBox: function () { -THREE.BufferGeometry.MaxIndex = 65535; + var vector = new THREE.Vector3(); -// File:src/core/InstancedBufferGeometry.js + return function () { -/** - * @author benaadams / https://twitter.com/ben_a_adams - */ + if ( this.boundingBox === null ) { -THREE.InstancedBufferGeometry = function () { + this.boundingBox = new THREE.Box3(); - THREE.BufferGeometry.call(this); + } - this.type = 'InstancedBufferGeometry'; - this.maxInstancedCount = undefined; + var positions = this.attributes.position.array; -}; + if ( positions ) { -THREE.InstancedBufferGeometry.prototype = Object.create(THREE.BufferGeometry.prototype); -THREE.InstancedBufferGeometry.prototype.constructor = THREE.InstancedBufferGeometry; + var bb = this.boundingBox; + bb.makeEmpty(); -THREE.InstancedBufferGeometry.prototype.addDrawCall = function (start, count, indexOffset, instances) { + for ( var i = 0, il = positions.length; i < il; i += 3 ) { - this.drawcalls.push({ + vector.fromArray( positions, i ); + bb.expandByPoint( vector ); - start: start, - count: count, - index: indexOffset !== undefined ? indexOffset : 0, - instances: instances + } - }); + } -}, + if ( positions === undefined || positions.length === 0 ) { - THREE.InstancedBufferGeometry.prototype.clone = function () { + this.boundingBox.min.set( 0, 0, 0 ); + this.boundingBox.max.set( 0, 0, 0 ); - var geometry = new THREE.InstancedBufferGeometry(); + } - for (var attr in this.attributes) { + if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) { - var sourceAttr = this.attributes[attr]; - geometry.addAttribute(attr, sourceAttr.clone()); + console.error( 'THREE.BufferGeometry.computeBoundingBox: Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this ); - } + } - for (var i = 0, il = this.offsets.length; i < il; i++) { + }; - var offset = this.offsets[i]; + }(), - geometry.offsets.push({ + computeBoundingSphere: function () { - start: offset.start, - index: offset.index, - count: offset.count, - instances: offset.instances + var box = new THREE.Box3(); + var vector = new THREE.Vector3(); - }); + return function () { - } + if ( this.boundingSphere === null ) { - return geometry; + this.boundingSphere = new THREE.Sphere(); - }; + } -THREE.EventDispatcher.prototype.apply(THREE.InstancedBufferGeometry.prototype); + var positions = this.attributes.position.array; -// File:src/cameras/Camera.js + if ( positions ) { -/** - * @author mrdoob / http://mrdoob.com/ - * @author mikael emtinger / http://gomo.se/ - * @author WestLangley / http://github.com/WestLangley - */ + box.makeEmpty(); -THREE.Camera = function () { + var center = this.boundingSphere.center; - THREE.Object3D.call(this); + for ( var i = 0, il = positions.length; i < il; i += 3 ) { - this.type = 'Camera'; + vector.fromArray( positions, i ); + box.expandByPoint( vector ); - this.matrixWorldInverse = new THREE.Matrix4(); - this.projectionMatrix = new THREE.Matrix4(); + } -}; + box.center( center ); -THREE.Camera.prototype = Object.create(THREE.Object3D.prototype); -THREE.Camera.prototype.constructor = THREE.Camera; + // hoping to find a boundingSphere with a radius smaller than the + // boundingSphere of the boundingBox: sqrt(3) smaller in the best case -THREE.Camera.prototype.getWorldDirection = function () { + var maxRadiusSq = 0; - var quaternion = new THREE.Quaternion(); + for ( var i = 0, il = positions.length; i < il; i += 3 ) { - return function (optionalTarget) { + vector.fromArray( positions, i ); + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) ); - var result = optionalTarget || new THREE.Vector3(); + } - this.getWorldQuaternion(quaternion); + this.boundingSphere.radius = Math.sqrt( maxRadiusSq ); - return result.set(0, 0, -1).applyQuaternion(quaternion); + if ( isNaN( this.boundingSphere.radius ) ) { - } + console.error( 'THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.', this ); -}(); + } -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) { + computeFaceNormals: function () { - m1.lookAt(this.position, vector, this.up); + // backwards compatibility - this.quaternion.setFromRotationMatrix(m1); + }, - }; + computeVertexNormals: function () { -}(); + var index = this.index; + var attributes = this.attributes; + var groups = this.groups; -THREE.Camera.prototype.clone = function (camera) { + if ( attributes.position ) { - if (camera === undefined) camera = new THREE.Camera(); + var positions = attributes.position.array; - THREE.Object3D.prototype.clone.call(this, camera); + if ( attributes.normal === undefined ) { - camera.matrixWorldInverse.copy(this.matrixWorldInverse); - camera.projectionMatrix.copy(this.projectionMatrix); + this.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( positions.length ), 3 ) ); - return camera; -}; + } else { -// File:src/cameras/CubeCamera.js + // reset existing normals to zero -/** - * Camera for rendering cube maps - * - renders scene into axis-aligned cube - * - * @author alteredq / http://alteredqualia.com/ - */ + var normals = attributes.normal.array; -THREE.CubeCamera = function (near, far, cubeResolution) { + for ( var i = 0, il = normals.length; i < il; i ++ ) { - THREE.Object3D.call(this); + normals[ i ] = 0; - this.type = 'CubeCamera'; + } - 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 normals = attributes.normal.array; - 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 vA, vB, vC, - 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); + pA = new THREE.Vector3(), + pB = new THREE.Vector3(), + pC = new THREE.Vector3(), - 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); + cb = new THREE.Vector3(), + ab = new THREE.Vector3(); - 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); + // indexed elements - 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); + if ( index ) { - this.renderTarget = new THREE.WebGLRenderTargetCube(cubeResolution, cubeResolution, { - format: THREE.RGBFormat, - magFilter: THREE.LinearFilter, - minFilter: THREE.LinearFilter - }); + var indices = index.array; - this.updateCubeMap = function (renderer, scene) { + if ( groups.length === 0 ) { - var renderTarget = this.renderTarget; - var generateMipmaps = renderTarget.generateMipmaps; + this.addGroup( 0, indices.length ); - renderTarget.generateMipmaps = false; + } - renderTarget.activeCubeFace = 0; - renderer.render(scene, cameraPX, renderTarget); + for ( var j = 0, jl = groups.length; j < jl; ++ j ) { - renderTarget.activeCubeFace = 1; - renderer.render(scene, cameraNX, renderTarget); + var group = groups[ j ]; - renderTarget.activeCubeFace = 2; - renderer.render(scene, cameraPY, renderTarget); + var start = group.start; + var count = group.count; - renderTarget.activeCubeFace = 3; - renderer.render(scene, cameraNY, renderTarget); + for ( var i = start, il = start + count; i < il; i += 3 ) { - renderTarget.activeCubeFace = 4; - renderer.render(scene, cameraPZ, renderTarget); + vA = indices[ i + 0 ] * 3; + vB = indices[ i + 1 ] * 3; + vC = indices[ i + 2 ] * 3; - renderTarget.generateMipmaps = generateMipmaps; + pA.fromArray( positions, vA ); + pB.fromArray( positions, vB ); + pC.fromArray( positions, vC ); - renderTarget.activeCubeFace = 5; - renderer.render(scene, cameraNZ, renderTarget); + cb.subVectors( pC, pB ); + ab.subVectors( pA, pB ); + cb.cross( ab ); - renderer.setRenderTarget(null); + normals[ vA ] += cb.x; + normals[ vA + 1 ] += cb.y; + normals[ vA + 2 ] += cb.z; - }; + normals[ vB ] += cb.x; + normals[ vB + 1 ] += cb.y; + normals[ vB + 2 ] += cb.z; -}; + normals[ vC ] += cb.x; + normals[ vC + 1 ] += cb.y; + normals[ vC + 2 ] += cb.z; -THREE.CubeCamera.prototype = Object.create(THREE.Object3D.prototype); -THREE.CubeCamera.prototype.constructor = THREE.CubeCamera; + } -// File:src/cameras/OrthographicCamera.js + } -/** - * @author alteredq / http://alteredqualia.com/ - */ + } else { -THREE.OrthographicCamera = function (left, right, top, bottom, near, far) { + // non-indexed elements (unconnected triangle soup) - THREE.Camera.call(this); + for ( var i = 0, il = positions.length; i < il; i += 9 ) { - this.type = 'OrthographicCamera'; + pA.fromArray( positions, i ); + pB.fromArray( positions, i + 3 ); + pC.fromArray( positions, i + 6 ); - this.zoom = 1; + cb.subVectors( pC, pB ); + ab.subVectors( pA, pB ); + cb.cross( ab ); - this.left = left; - this.right = right; - this.top = top; - this.bottom = bottom; + normals[ i ] = cb.x; + normals[ i + 1 ] = cb.y; + normals[ i + 2 ] = cb.z; - this.near = ( near !== undefined ) ? near : 0.1; - this.far = ( far !== undefined ) ? far : 2000; + normals[ i + 3 ] = cb.x; + normals[ i + 4 ] = cb.y; + normals[ i + 5 ] = cb.z; - this.updateProjectionMatrix(); + normals[ i + 6 ] = cb.x; + normals[ i + 7 ] = cb.y; + normals[ i + 8 ] = cb.z; -}; + } -THREE.OrthographicCamera.prototype = Object.create(THREE.Camera.prototype); -THREE.OrthographicCamera.prototype.constructor = THREE.OrthographicCamera; + } -THREE.OrthographicCamera.prototype.updateProjectionMatrix = function () { + this.normalizeNormals(); - var dx = ( this.right - this.left ) / ( 2 * this.zoom ); - var dy = ( this.top - this.bottom ) / ( 2 * this.zoom ); - var cx = ( this.right + this.left ) / 2; - var cy = ( this.top + this.bottom ) / 2; + attributes.normal.needsUpdate = true; - this.projectionMatrix.makeOrthographic(cx - dx, cx + dx, cy + dy, cy - dy, this.near, this.far); + } -}; + }, -THREE.OrthographicCamera.prototype.clone = function () { + computeTangents: function () { - var camera = new THREE.OrthographicCamera(); + console.warn( 'THREE.BufferGeometry: .computeTangents() has been removed.' ); - THREE.Camera.prototype.clone.call(this, camera); + }, - camera.zoom = this.zoom; + computeOffsets: function ( size ) { - camera.left = this.left; - camera.right = this.right; - camera.top = this.top; - camera.bottom = this.bottom; + console.warn( 'THREE.BufferGeometry: .computeOffsets() has been removed.') - camera.near = this.near; - camera.far = this.far; + }, - camera.projectionMatrix.copy(this.projectionMatrix); + merge: function ( geometry, offset ) { - return camera; -}; + if ( geometry instanceof THREE.BufferGeometry === false ) { -THREE.OrthographicCamera.prototype.toJSON = function (meta) { + console.error( 'THREE.BufferGeometry.merge(): geometry not an instance of THREE.BufferGeometry.', geometry ); + return; - var data = THREE.Object3D.prototype.toJSON.call(this, meta); + } - data.object.left = this.left; - data.object.right = this.right; - data.object.top = this.top; - data.object.bottom = this.bottom; - data.object.near = this.near; - data.object.far = this.far; + if ( offset === undefined ) offset = 0; - return data; + var attributes = this.attributes; -}; + for ( var key in attributes ) { -// File:src/cameras/PerspectiveCamera.js + if ( geometry.attributes[ key ] === undefined ) continue; -/** - * @author mrdoob / http://mrdoob.com/ - * @author greggman / http://games.greggman.com/ - * @author zz85 / http://www.lab4games.net/zz85/blog - */ + var attribute1 = attributes[ key ]; + var attributeArray1 = attribute1.array; -THREE.PerspectiveCamera = function (fov, aspect, near, far) { + var attribute2 = geometry.attributes[ key ]; + var attributeArray2 = attribute2.array; - THREE.Camera.call(this); + var attributeSize = attribute2.itemSize; - this.type = 'PerspectiveCamera'; + for ( var i = 0, j = attributeSize * offset; i < attributeArray2.length; i ++, j ++ ) { - this.zoom = 1; + attributeArray1[ j ] = attributeArray2[ i ]; - 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(); + } -}; + return this; -THREE.PerspectiveCamera.prototype = Object.create(THREE.Camera.prototype); -THREE.PerspectiveCamera.prototype.constructor = THREE.PerspectiveCamera; + }, + normalizeNormals: function () { -/** - * 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 - */ + var normals = this.attributes.normal.array; -THREE.PerspectiveCamera.prototype.setLens = function (focalLength, frameHeight) { + var x, y, z, n; - if (frameHeight === undefined) frameHeight = 24; + for ( var i = 0, il = normals.length; i < il; i += 3 ) { - this.fov = 2 * THREE.Math.radToDeg(Math.atan(frameHeight / ( focalLength * 2 ))); - this.updateProjectionMatrix(); + 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; -/** - * 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; + toJSON: function () { - this.updateProjectionMatrix(); + var data = { + metadata: { + version: 4.4, + type: 'BufferGeometry', + generator: 'BufferGeometry.toJSON' + } + }; -}; + // standard BufferGeometry serialization + data.uuid = this.uuid; + data.type = this.type; + if ( this.name !== '' ) data.name = this.name; -THREE.PerspectiveCamera.prototype.updateProjectionMatrix = function () { + if ( this.parameters !== undefined ) { - var fov = THREE.Math.radToDeg(2 * Math.atan(Math.tan(THREE.Math.degToRad(this.fov) * 0.5) / this.zoom)); + var parameters = this.parameters; - if (this.fullWidth) { + for ( var key in parameters ) { - var aspect = this.fullWidth / this.fullHeight; - var top = Math.tan(THREE.Math.degToRad(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); + if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ]; - 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 { + return data; - this.projectionMatrix.makePerspective(fov, this.aspect, this.near, this.far); + } - } + data.data = { attributes: {} }; -}; + var index = this.index; -THREE.PerspectiveCamera.prototype.clone = function () { + if ( index !== null ) { - var camera = new THREE.PerspectiveCamera(); + var array = Array.prototype.slice.call( index.array ); - THREE.Camera.prototype.clone.call(this, camera); + data.data.index = { + type: index.array.constructor.name, + array: array + }; - camera.zoom = this.zoom; + } - camera.fov = this.fov; - camera.aspect = this.aspect; - camera.near = this.near; - camera.far = this.far; + var attributes = this.attributes; - camera.projectionMatrix.copy(this.projectionMatrix); + for ( var key in attributes ) { - return camera; + var attribute = attributes[ key ]; -}; + var array = Array.prototype.slice.call( attribute.array ); -THREE.PerspectiveCamera.prototype.toJSON = function (meta) { + data.data.attributes[ key ] = { + itemSize: attribute.itemSize, + type: attribute.array.constructor.name, + array: array + }; - var data = THREE.Object3D.prototype.toJSON.call(this, meta); + } - data.object.fov = this.fov; - data.object.aspect = this.aspect; - data.object.near = this.near; - data.object.far = this.far; + var groups = this.groups; - return data; + if ( groups.length > 0 ) { -}; + data.data.groups = JSON.parse( JSON.stringify( groups ) ); -// File:src/lights/Light.js + } -/** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - */ + var boundingSphere = this.boundingSphere; -THREE.Light = function (color) { + if ( boundingSphere !== null ) { - THREE.Object3D.call(this); + data.data.boundingSphere = { + center: boundingSphere.center.toArray(), + radius: boundingSphere.radius + }; - this.type = 'Light'; + } - this.color = new THREE.Color(color); + return data; -}; + }, -THREE.Light.prototype = Object.create(THREE.Object3D.prototype); -THREE.Light.prototype.constructor = THREE.Light; + clone: function () { -THREE.Light.prototype.clone = function (light) { + return new this.constructor().copy( this ); - if (light === undefined) light = new THREE.Light(); + }, - THREE.Object3D.prototype.clone.call(this, light); + copy: function ( source ) { - light.color.copy(this.color); + var index = source.index; - return light; + if ( index !== null ) { -}; + this.setIndex( index.clone() ); -// File:src/lights/AmbientLight.js + } -/** - * @author mrdoob / http://mrdoob.com/ - */ + var attributes = source.attributes; -THREE.AmbientLight = function (color) { + for ( var name in attributes ) { - THREE.Light.call(this, color); + var attribute = attributes[ name ]; + this.addAttribute( name, attribute.clone() ); - this.type = 'AmbientLight'; + } -}; + var groups = source.groups; -THREE.AmbientLight.prototype = Object.create(THREE.Light.prototype); -THREE.AmbientLight.prototype.constructor = THREE.AmbientLight; + for ( var i = 0, l = groups.length; i < l; i ++ ) { -THREE.AmbientLight.prototype.clone = function () { + var group = groups[ i ]; + this.addGroup( group.start, group.count ); - var light = new THREE.AmbientLight(); + } - THREE.Light.prototype.clone.call(this, light); + return this; - return light; + }, -}; + dispose: function () { -THREE.AmbientLight.prototype.toJSON = function (meta) { + this.dispatchEvent( { type: 'dispose' } ); - var data = THREE.Object3D.prototype.toJSON.call(this, meta); + } - data.object.color = this.color.getHex(); +}; - return data; +THREE.EventDispatcher.prototype.apply( THREE.BufferGeometry.prototype ); -}; +THREE.BufferGeometry.MaxIndex = 65535; -// File:src/lights/AreaLight.js +// File:src/core/InstancedBufferGeometry.js /** - * @author MPanknin / http://www.redplant.de/ - * @author alteredq / http://alteredqualia.com/ + * @author benaadams / https://twitter.com/ben_a_adams */ -THREE.AreaLight = function (color, intensity) { - - THREE.Light.call(this, color); +THREE.InstancedBufferGeometry = function () { - this.type = 'AreaLight'; + THREE.BufferGeometry.call( this ); - this.normal = new THREE.Vector3(0, -1, 0); - this.right = new THREE.Vector3(1, 0, 0); + this.type = 'InstancedBufferGeometry'; + this.maxInstancedCount = undefined; - this.intensity = ( intensity !== undefined ) ? intensity : 1; +}; - this.width = 1.0; - this.height = 1.0; +THREE.InstancedBufferGeometry.prototype = Object.create( THREE.BufferGeometry.prototype ); +THREE.InstancedBufferGeometry.prototype.constructor = THREE.InstancedBufferGeometry; - this.constantAttenuation = 1.5; - this.linearAttenuation = 0.5; - this.quadraticAttenuation = 0.1; +THREE.InstancedBufferGeometry.prototype.addGroup = function ( start, count, instances ) { -}; + this.groups.push( { -THREE.AreaLight.prototype = Object.create(THREE.Light.prototype); -THREE.AreaLight.prototype.constructor = THREE.AreaLight; + start: start, + count: count, + instances: instances + } ); -// File:src/lights/DirectionalLight.js +}; -/** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - */ +THREE.InstancedBufferGeometry.prototype.copy = function ( source ) { -THREE.DirectionalLight = function (color, intensity) { + var index = source.index; - THREE.Light.call(this, color); + if ( index !== null ) { - this.type = 'DirectionalLight'; + this.setIndex( index.clone() ); - this.position.set(0, 1, 0); - this.target = new THREE.Object3D(); + } - this.intensity = ( intensity !== undefined ) ? intensity : 1; + var attributes = source.attributes; - this.castShadow = false; - this.onlyShadow = false; + for ( var name in attributes ) { - // + var attribute = attributes[ name ]; + this.addAttribute( name, attribute.clone() ); - this.shadowCameraNear = 50; - this.shadowCameraFar = 5000; + } - this.shadowCameraLeft = -500; - this.shadowCameraRight = 500; - this.shadowCameraTop = 500; - this.shadowCameraBottom = -500; + var groups = source.groups; - this.shadowCameraVisible = false; + for ( var i = 0, l = groups.length; i < l; i ++ ) { - this.shadowBias = 0; - this.shadowDarkness = 0.5; + var group = groups[ i ]; + this.addGroup( group.start, group.count, group.instances ); - this.shadowMapWidth = 512; - this.shadowMapHeight = 512; + } - // + return this; - this.shadowCascade = false; +}; - this.shadowCascadeOffset = new THREE.Vector3(0, 0, -1000); - this.shadowCascadeCount = 2; +THREE.EventDispatcher.prototype.apply( THREE.InstancedBufferGeometry.prototype ); - this.shadowCascadeBias = [0, 0, 0]; - this.shadowCascadeWidth = [512, 512, 512]; - this.shadowCascadeHeight = [512, 512, 512]; +// File:src/animation/AnimationAction.js - this.shadowCascadeNearZ = [-1.000, 0.990, 0.998]; - this.shadowCascadeFarZ = [0.990, 0.998, 1.000]; +/** + * + * A clip that has been explicitly scheduled. + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + */ - this.shadowCascadeArray = []; +THREE.AnimationAction = function ( clip, startTime, timeScale, weight, loop ) { - // + if( clip === undefined ) throw new Error( 'clip is null' ); + this.clip = clip; + this.localRoot = null; + this.startTime = startTime || 0; + this.timeScale = timeScale || 1; + this.weight = weight || 1; + this.loop = loop || THREE.LoopRepeat; + this.loopCount = 0; + this.enabled = true; // allow for easy disabling of the action. - this.shadowMap = null; - this.shadowMapSize = null; - this.shadowCamera = null; - this.shadowMatrix = null; + this.actionTime = - this.startTime; + this.clipTime = 0; + this.propertyBindings = []; }; -THREE.DirectionalLight.prototype = Object.create(THREE.Light.prototype); -THREE.DirectionalLight.prototype.constructor = THREE.DirectionalLight; +/* +THREE.LoopOnce = 2200; +THREE.LoopRepeat = 2201; +THREE.LoopPingPing = 2202; +*/ -THREE.DirectionalLight.prototype.clone = function () { +THREE.AnimationAction.prototype = { - var light = new THREE.DirectionalLight(); + constructor: THREE.AnimationAction, - THREE.Light.prototype.clone.call(this, light); + setLocalRoot: function( localRoot ) { - light.target = this.target.clone(); + this.localRoot = localRoot; - light.intensity = this.intensity; + return this; + + }, - light.castShadow = this.castShadow; - light.onlyShadow = this.onlyShadow; + updateTime: function( clipDeltaTime ) { - // + var previousClipTime = this.clipTime; + var previousLoopCount = this.loopCount; + var previousActionTime = this.actionTime; - light.shadowCameraNear = this.shadowCameraNear; - light.shadowCameraFar = this.shadowCameraFar; + var duration = this.clip.duration; + + this.actionTime = this.actionTime + clipDeltaTime; + + if( this.loop === THREE.LoopOnce ) { - light.shadowCameraLeft = this.shadowCameraLeft; - light.shadowCameraRight = this.shadowCameraRight; - light.shadowCameraTop = this.shadowCameraTop; - light.shadowCameraBottom = this.shadowCameraBottom; + this.loopCount = 0; + this.clipTime = Math.min( Math.max( this.actionTime, 0 ), duration ); + + // if time is changed since last time, see if we have hit a start/end limit + if( this.clipTime !== previousClipTime ) { - light.shadowCameraVisible = this.shadowCameraVisible; + if( this.clipTime === duration ) { - light.shadowBias = this.shadowBias; - light.shadowDarkness = this.shadowDarkness; + this.mixer.dispatchEvent( { type: 'finished', action: this, direction: 1 } ); - light.shadowMapWidth = this.shadowMapWidth; - light.shadowMapHeight = this.shadowMapHeight; + } + else if( this.clipTime === 0 ) { - // + this.mixer.dispatchEvent( { type: 'finished', action: this, direction: -1 } ); - light.shadowCascade = this.shadowCascade; + } - light.shadowCascadeOffset.copy(this.shadowCascadeOffset); - light.shadowCascadeCount = this.shadowCascadeCount; + } - light.shadowCascadeBias = this.shadowCascadeBias.slice(0); - light.shadowCascadeWidth = this.shadowCascadeWidth.slice(0); - light.shadowCascadeHeight = this.shadowCascadeHeight.slice(0); + + return this.clipTime; - light.shadowCascadeNearZ = this.shadowCascadeNearZ.slice(0); - light.shadowCascadeFarZ = this.shadowCascadeFarZ.slice(0); + } + + this.loopCount = Math.floor( this.actionTime / duration ); + + var newClipTime = this.actionTime - this.loopCount * duration; + newClipTime = newClipTime % duration; + + // if we are ping pong looping, ensure that we go backwards when appropriate + if( this.loop == THREE.LoopPingPong ) { - return light; + if( Math.abs( this.loopCount % 2 ) === 1 ) { -}; + newClipTime = duration - newClipTime; -THREE.DirectionalLight.prototype.toJSON = function (meta) { + } - var data = THREE.Object3D.prototype.toJSON.call(this, meta); + } - data.object.color = this.color.getHex(); - data.object.intensity = this.intensity; + this.clipTime = newClipTime; - return data; + if( this.loopCount !== previousLoopCount ) { -}; + this.mixer.dispatchEvent( { type: 'loop', action: this, loopDelta: ( this.loopCount - this.loopCount ) } ); -// File:src/lights/HemisphereLight.js + } + + return this.clipTime; -/** - * @author alteredq / http://alteredqualia.com/ - */ + }, -THREE.HemisphereLight = function (skyColor, groundColor, intensity) { + syncWith: function( action ) { - THREE.Light.call(this, skyColor); + this.actionTime = action.actionTime; + this.timeScale = action.timeScale; - this.type = 'HemisphereLight'; + return this; + }, - this.position.set(0, 100, 0); + warpToDuration: function( duration ) { - this.groundColor = new THREE.Color(groundColor); - this.intensity = ( intensity !== undefined ) ? intensity : 1; + this.timeScale = this.clip.duration / duration; -}; + return this; + }, -THREE.HemisphereLight.prototype = Object.create(THREE.Light.prototype); -THREE.HemisphereLight.prototype.constructor = THREE.HemisphereLight; + init: function( time ) { -THREE.HemisphereLight.prototype.clone = function () { + this.clipTime = time - this.startTime; - var light = new THREE.HemisphereLight(); + return this; - THREE.Light.prototype.clone.call(this, light); + }, - light.groundColor.copy(this.groundColor); - light.intensity = this.intensity; + update: function( clipDeltaTime ) { - return light; + this.updateTime( clipDeltaTime ); -}; + var clipResults = this.clip.getAt( this.clipTime ); -THREE.HemisphereLight.prototype.toJSON = function (meta) { + return clipResults; + + }, - var data = THREE.Object3D.prototype.toJSON.call(this, meta); + getTimeScaleAt: function( time ) { - data.object.color = this.color.getHex(); - data.object.groundColor = this.groundColor.getHex(); + if( this.timeScale.getAt ) { + // pass in time, not clip time, allows for fadein/fadeout across multiple loops of the clip + return this.timeScale.getAt( time ); - return data; + } -}; + return this.timeScale; -// File:src/lights/PointLight.js + }, -/** - * @author mrdoob / http://mrdoob.com/ - */ + getWeightAt: function( time ) { -THREE.PointLight = function (color, intensity, distance, decay) { + if( this.weight.getAt ) { + // pass in time, not clip time, allows for fadein/fadeout across multiple loops of the clip + return this.weight.getAt( time ); - THREE.Light.call(this, color); + } - this.type = 'PointLight'; + return this.weight; - this.intensity = ( intensity !== undefined ) ? intensity : 1; - this.distance = ( distance !== undefined ) ? distance : 0; - this.decay = ( decay !== undefined ) ? decay : 1; // for physically correct lights, should be 2. + } }; -THREE.PointLight.prototype = Object.create(THREE.Light.prototype); -THREE.PointLight.prototype.constructor = THREE.PointLight; +// File:src/animation/AnimationClip.js -THREE.PointLight.prototype.clone = function () { +/** + * + * Reusable set of Tracks that represent an animation. + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + */ - var light = new THREE.PointLight(); +THREE.AnimationClip = function ( name, duration, tracks ) { - THREE.Light.prototype.clone.call(this, light); + this.name = name; + this.tracks = tracks; + this.duration = ( duration !== undefined ) ? duration : -1; - light.intensity = this.intensity; - light.distance = this.distance; - light.decay = this.decay; + // this means it should figure out its duration by scanning the tracks + if( this.duration < 0 ) { + for( var i = 0; i < this.tracks.length; i ++ ) { + var track = this.tracks[i]; + this.duration = Math.max( track.keys[ track.keys.length - 1 ].time ); + } + } - return light; + // maybe only do these on demand, as doing them here could potentially slow down loading + // but leaving these here during development as this ensures a lot of testing of these functions + this.trim(); + this.optimize(); + this.results = []; + }; -THREE.PointLight.prototype.toJSON = function (meta) { +THREE.AnimationClip.prototype = { - var data = THREE.Object3D.prototype.toJSON.call(this, meta); + constructor: THREE.AnimationClip, - data.object.color = this.color.getHex(); - data.object.intensity = this.intensity; - data.object.distance = this.distance; - data.object.decay = this.decay; + getAt: function( clipTime ) { - return data; - -}; + clipTime = Math.max( 0, Math.min( clipTime, this.duration ) ); -// File:src/lights/SpotLight.js + for( var i = 0; i < this.tracks.length; i ++ ) { -/** - * @author alteredq / http://alteredqualia.com/ - */ + var track = this.tracks[ i ]; -THREE.SpotLight = function (color, intensity, distance, angle, exponent, decay) { + this.results[ i ] = track.getAt( clipTime ); - THREE.Light.call(this, color); + } - this.type = 'SpotLight'; + return this.results; + }, - this.position.set(0, 1, 0); - this.target = new THREE.Object3D(); + trim: function() { - 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.decay = ( decay !== undefined ) ? decay : 1; // for physically correct lights, should be 2. + for( var i = 0; i < this.tracks.length; i ++ ) { - this.castShadow = false; - this.onlyShadow = false; + this.tracks[ i ].trim( 0, this.duration ); - // + } - this.shadowCameraNear = 50; - this.shadowCameraFar = 5000; - this.shadowCameraFov = 50; + return this; - this.shadowCameraVisible = false; + }, - this.shadowBias = 0; - this.shadowDarkness = 0.5; + optimize: function() { - this.shadowMapWidth = 512; - this.shadowMapHeight = 512; + for( var i = 0; i < this.tracks.length; i ++ ) { - // + this.tracks[ i ].optimize(); - this.shadowMap = null; - this.shadowMapSize = null; - this.shadowCamera = null; - this.shadowMatrix = null; + } -}; + return this; -THREE.SpotLight.prototype = Object.create(THREE.Light.prototype); -THREE.SpotLight.prototype.constructor = THREE.SpotLight; + } -THREE.SpotLight.prototype.clone = function () { +}; - var light = new THREE.SpotLight(); - THREE.Light.prototype.clone.call(this, light); +THREE.AnimationClip.CreateFromMorphTargetSequence = function( name, morphTargetSequence, fps ) { - light.target = this.target.clone(); - light.intensity = this.intensity; - light.distance = this.distance; - light.angle = this.angle; - light.exponent = this.exponent; - light.decay = this.decay; + var numMorphTargets = morphTargetSequence.length; + var tracks = []; - light.castShadow = this.castShadow; - light.onlyShadow = this.onlyShadow; + for( var i = 0; i < numMorphTargets; i ++ ) { - // + var keys = []; - light.shadowCameraNear = this.shadowCameraNear; - light.shadowCameraFar = this.shadowCameraFar; - light.shadowCameraFov = this.shadowCameraFov; + keys.push( { time: ( i + numMorphTargets - 1 ) % numMorphTargets, value: 0 } ); + keys.push( { time: i, value: 1 } ); + keys.push( { time: ( i + 1 ) % numMorphTargets, value: 0 } ); - light.shadowCameraVisible = this.shadowCameraVisible; + keys.sort( THREE.KeyframeTrack.keyComparer ); - light.shadowBias = this.shadowBias; - light.shadowDarkness = this.shadowDarkness; + // if there is a key at the first frame, duplicate it as the last frame as well for perfect loop. + if( keys[0].time === 0 ) { + keys.push( { + time: numMorphTargets, + value: keys[0].value + }); + } - light.shadowMapWidth = this.shadowMapWidth; - light.shadowMapHeight = this.shadowMapHeight; + tracks.push( new THREE.NumberKeyframeTrack( '.morphTargetInfluences[' + morphTargetSequence[i].name + ']', keys ).scale( 1.0 / fps ) ); + } - return light; + return new THREE.AnimationClip( name, -1, tracks ); }; -THREE.SpotLight.prototype.toJSON = function (meta) { - - var data = THREE.Object3D.prototype.toJSON.call(this, meta); +THREE.AnimationClip.findByName = function( clipArray, name ) { - data.object.color = this.color.getHex(); - data.object.intensity = this.intensity; - data.object.distance = this.distance; - data.object.angle = this.angle; - data.object.exponent = this.exponent; - data.object.decay = this.decay; + for( var i = 0; i < clipArray.length; i ++ ) { - return data; - -}; - -// File:src/loaders/Cache.js + if( clipArray[i].name === name ) { -/** - * @author mrdoob / http://mrdoob.com/ - */ + return clipArray[i]; -THREE.Cache = { + } + } - files: {}, + return null; - add: function (key, file) { +}; - // THREE.log( 'THREE.Cache', 'Adding key:', key ); +THREE.AnimationClip.CreateClipsFromMorphTargetSequences = function( morphTargets, fps ) { + + var animationToMorphTargets = {}; - this.files[key] = file; + // tested with https://regex101.com/ on trick sequences such flamingo_flyA_003, flamingo_run1_003, crdeath0059 + var pattern = /^([\w-]*?)([\d]+)$/; - }, + // sort morph target names into animation groups based patterns like Walk_001, Walk_002, Run_001, Run_002 + for ( var i = 0, il = morphTargets.length; i < il; i ++ ) { - get: function (key) { + var morphTarget = morphTargets[ i ]; + var parts = morphTarget.name.match( pattern ); + + if ( parts && parts.length > 1 ) { - // THREE.log( 'THREE.Cache', 'Checking key:', key ); + var name = parts[ 1 ]; - return this.files[key]; + var animationMorphTargets = animationToMorphTargets[ name ]; + if( ! animationMorphTargets ) { + animationToMorphTargets[ name ] = animationMorphTargets = []; + } - }, + animationMorphTargets.push( morphTarget ); - remove: function (key) { + } - delete this.files[key]; + } - }, + var clips = []; - clear: function () { + for( var name in animationToMorphTargets ) { - this.files = {} + clips.push( THREE.AnimationClip.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps ) ); + } - } + return clips; }; -// File:src/loaders/Loader.js +// parse the standard JSON format for clips +THREE.AnimationClip.parse = function( json ) { -/** - * @author alteredq / http://alteredqualia.com/ - */ + var tracks = []; -THREE.Loader = function (showStatus) { + for( var i = 0; i < json.tracks.length; i ++ ) { - this.showStatus = showStatus; - this.statusDomElement = showStatus ? THREE.Loader.prototype.addStatusElement() : null; + tracks.push( THREE.KeyframeTrack.parse( json.tracks[i] ).scale( 1.0 / json.fps ) ); - this.imageLoader = new THREE.ImageLoader(); + } - this.onLoadStart = function () { - }; - this.onLoadProgress = function () { - }; - this.onLoadComplete = function () { - }; + return new THREE.AnimationClip( json.name, json.duration, tracks ); }; -THREE.Loader.prototype = { - constructor: THREE.Loader, +// parse the animation.hierarchy format +THREE.AnimationClip.parseAnimation = function( animation, bones, nodeName ) { - crossOrigin: undefined, + if( ! animation ) { + console.error( " no animation in JSONLoader data" ); + return null; + } - addStatusElement: function () { + var convertTrack = function( trackName, animationKeys, propertyName, trackType, animationKeyToValueFunc ) { - var e = document.createElement('div'); + var keys = []; - 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; + for( var k = 0; k < animationKeys.length; k ++ ) { - e.innerHTML = 'Loading ...'; + var animationKey = animationKeys[k]; - return e; + if( animationKey[propertyName] !== undefined ) { - }, + keys.push( { time: animationKey.time, value: animationKeyToValueFunc( animationKey ) } ); + } + + } - updateProgress: function (progress) { + // only return track if there are actually keys. + if( keys.length > 0 ) { + + return new trackType( trackName, keys ); - var message = 'Loaded '; + } - if (progress.total) { + return null; - message += ( 100 * progress.loaded / progress.total ).toFixed(0) + '%'; + }; + var tracks = []; - } else { + var clipName = animation.name || 'default'; + var duration = animation.length || -1; // automatic length determination in AnimationClip. + var fps = animation.fps || 30; - message += ( progress.loaded / 1024 ).toFixed(2) + ' KB'; + var hierarchyTracks = animation.hierarchy || []; - } + for ( var h = 0; h < hierarchyTracks.length; h ++ ) { - this.statusDomElement.innerHTML = message; + var animationKeys = hierarchyTracks[ h ].keys; - }, + // skip empty tracks + if( ! animationKeys || animationKeys.length == 0 ) { + continue; + } - extractUrlBase: function (url) { + // process morph targets in a way exactly compatible with AnimationHandler.init( animation ) + if( animationKeys[0].morphTargets ) { - var parts = url.split('/'); + // figure out all morph targets used in this track + var morphTargetNames = {}; + for( var k = 0; k < animationKeys.length; k ++ ) { - if (parts.length === 1) return './'; + if( animationKeys[k].morphTargets ) { + for( var m = 0; m < animationKeys[k].morphTargets.length; m ++ ) { - parts.pop(); + morphTargetNames[ animationKeys[k].morphTargets[m] ] = -1; + } + } - return parts.join('/') + '/'; + } - }, + // create a track for each morph target with all zero morphTargetInfluences except for the keys in which the morphTarget is named. + for( var morphTargetName in morphTargetNames ) { - initMaterials: function (materials, texturePath) { + var keys = []; - var array = []; + for( var m = 0; m < animationKeys[k].morphTargets.length; m ++ ) { - for (var i = 0; i < materials.length; ++i) { + var animationKey = animationKeys[k]; - array[i] = this.createMaterial(materials[i], texturePath); + keys.push( { + time: animationKey.time, + value: (( animationKey.morphTarget === morphTargetName ) ? 1 : 0 ) + }); + + } - } + tracks.push( new THREE.NumberKeyframeTrack( nodeName + '.morphTargetInfluence[' + morphTargetName + ']', keys ) ); - return array; + } - }, + duration = morphTargetNames.length * ( fps || 1.0 ); - needsTangents: function (materials) { + } + else { - for (var i = 0, il = materials.length; i < il; i++) { + var boneName = nodeName + '.bones[' + bones[ h ].name + ']'; + + // track contains positions... + var positionTrack = convertTrack( boneName + '.position', animationKeys, 'pos', THREE.VectorKeyframeTrack, function( animationKey ) { + return new THREE.Vector3().fromArray( animationKey.pos ) + } ); - var m = materials[i]; + if( positionTrack ) tracks.push( positionTrack ); + + // track contains quaternions... + var quaternionTrack = convertTrack( boneName + '.quaternion', animationKeys, 'rot', THREE.QuaternionKeyframeTrack, function( animationKey ) { + if( animationKey.rot.slerp ) { + return animationKey.rot.clone(); + } + else { + return new THREE.Quaternion().fromArray( animationKey.rot ); + } + } ); - if (m instanceof THREE.ShaderMaterial) return true; + if( quaternionTrack ) tracks.push( quaternionTrack ); - } + // track contains quaternions... + var scaleTrack = convertTrack( boneName + '.scale', animationKeys, 'scl', THREE.VectorKeyframeTrack, function( animationKey ) { + return new THREE.Vector3().fromArray( animationKey.scl ) + } ); - return false; + if( scaleTrack ) tracks.push( scaleTrack ); - }, + } + } - createMaterial: function (m, texturePath) { + if( tracks.length === 0 ) { - var scope = this; + return null; - function nearest_pow2(n) { + } - var l = Math.log(n) / Math.LN2; - return Math.pow(2, Math.round(l)); + var clip = new THREE.AnimationClip( clipName, duration, tracks ); - } + return clip; - function create_texture(where, name, sourceFile, repeat, offset, wrap, anisotropy) { +}; - var fullPath = texturePath + sourceFile; +// File:src/animation/AnimationMixer.js - var texture; +/** + * + * Mixes together the AnimationClips scheduled by AnimationActions and applies them to the root and subtree + * + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + */ - var loader = THREE.Loader.Handlers.get(fullPath); +THREE.AnimationMixer = function( root ) { - if (loader !== null) { + this.root = root; + this.time = 0; + this.timeScale = 1.0; + this.actions = []; + this.propertyBindingMap = {}; - texture = loader.load(fullPath); +}; - } else { +THREE.AnimationMixer.prototype = { - texture = new THREE.Texture(); + constructor: THREE.AnimationMixer, - loader = scope.imageLoader; - loader.crossOrigin = scope.crossOrigin; - loader.load(fullPath, function (image) { + addAction: function( action ) { - if (THREE.Math.isPowerOfTwo(image.width) === false || - THREE.Math.isPowerOfTwo(image.height) === false) { + // TODO: check for duplicate action names? Or provide each action with a UUID? - var width = nearest_pow2(image.width); - var height = nearest_pow2(image.height); + this.actions.push( action ); + action.init( this.time ); + action.mixer = this; - var canvas = document.createElement('canvas'); - canvas.width = width; - canvas.height = height; + var tracks = action.clip.tracks; - var context = canvas.getContext('2d'); - context.drawImage(image, 0, 0, width, height); + var root = action.localRoot || this.root; - texture.image = canvas; + for( var i = 0; i < tracks.length; i ++ ) { - } else { + var track = tracks[ i ]; - texture.image = image; + var propertyBindingKey = root.uuid + '-' + track.name; + var propertyBinding = this.propertyBindingMap[ propertyBindingKey ]; - } + if( propertyBinding === undefined ) { + + propertyBinding = new THREE.PropertyBinding( root, track.name ); + this.propertyBindingMap[ propertyBindingKey ] = propertyBinding; + + } - texture.needsUpdate = true; + // push in the same order as the tracks. + action.propertyBindings.push( propertyBinding ); + + // track usages of shared property bindings, because if we leave too many around, the mixer can get slow + propertyBinding.referenceCount += 1; - }); + } - } + }, - texture.sourceFile = sourceFile; + removeAllActions: function() { - if (repeat) { + for( var i = 0; i < this.actions.length; i ++ ) { - texture.repeat.set(repeat[0], repeat[1]); + this.actions[i].mixer = null; + + } - if (repeat[0] !== 1) texture.wrapS = THREE.RepeatWrapping; - if (repeat[1] !== 1) texture.wrapT = THREE.RepeatWrapping; + // unbind all property bindings + for( var properyBindingKey in this.propertyBindingMap ) { - } + this.propertyBindingMap[ properyBindingKey ].unbind(); - if (offset) { + } - texture.offset.set(offset[0], offset[1]); + this.actions = []; + this.propertyBindingMap = {}; - } + return this; - if (wrap) { + }, - var wrapMap = { - 'repeat': THREE.RepeatWrapping, - 'mirror': THREE.MirroredRepeatWrapping - }; + removeAction: function( action ) { - if (wrapMap[wrap[0]] !== undefined) texture.wrapS = wrapMap[wrap[0]]; - if (wrapMap[wrap[1]] !== undefined) texture.wrapT = wrapMap[wrap[1]]; + var index = this.actions.indexOf( action ); - } + if ( index !== - 1 ) { - if (anisotropy) { + this.actions.splice( index, 1 ); + action.mixer = null; - texture.anisotropy = anisotropy; + } - } - where[name] = texture; + // remove unused property bindings because if we leave them around the mixer can get slow + var root = action.localRoot || this.root; + var tracks = action.clip.tracks; - } + for( var i = 0; i < tracks.length; i ++ ) { + + var track = tracks[ i ]; - function rgb2hex(rgb) { + var propertyBindingKey = root.uuid + '-' + track.name; + var propertyBinding = this.propertyBindingMap[ propertyBindingKey ]; + + propertyBinding.referenceCount -= 1; - return ( rgb[0] * 255 << 16 ) + ( rgb[1] * 255 << 8 ) + rgb[2] * 255; + if( propertyBinding.referenceCount <= 0 ) { - } + propertyBinding.unbind(); - // defaults + delete this.propertyBindingMap[ propertyBindingKey ]; - var mtype = 'MeshLambertMaterial'; - var mpars = { - color: 0xeeeeee, - opacity: 1.0, - map: null, - lightMap: null, - normalMap: null, - bumpMap: null, - wireframe: false - }; + } + } - // parameters from model file + return this; - if (m.shading) { + }, - var shading = m.shading.toLowerCase(); + // can be optimized if needed + findActionByName: function( name ) { - if (shading === 'phong') mtype = 'MeshPhongMaterial'; - else if (shading === 'basic') mtype = 'MeshBasicMaterial'; + for( var i = 0; i < this.actions.length; i ++ ) { - } + if( this.actions[i].name === name ) return this.actions[i]; - if (m.blending !== undefined && THREE[m.blending] !== undefined) { + } - mpars.blending = THREE[m.blending]; + return null; - } + }, - if (m.transparent !== undefined) { + play: function( action, optionalFadeInDuration ) { - mpars.transparent = m.transparent; + action.startTime = this.time; + this.addAction( action ); - } + return this; - if (m.opacity !== undefined && m.opacity < 1.0) { + }, - mpars.transparent = true; + fadeOut: function( action, duration ) { - } + var keys = []; - if (m.depthTest !== undefined) { + keys.push( { time: this.time, value: 1 } ); + keys.push( { time: this.time + duration, value: 0 } ); + + action.weight = new THREE.NumberKeyframeTrack( "weight", keys ); - mpars.depthTest = m.depthTest; + return this; - } + }, - if (m.depthWrite !== undefined) { + fadeIn: function( action, duration ) { + + var keys = []; + + keys.push( { time: this.time, value: 0 } ); + keys.push( { time: this.time + duration, value: 1 } ); + + action.weight = new THREE.NumberKeyframeTrack( "weight", keys ); - mpars.depthWrite = m.depthWrite; + return this; - } + }, - if (m.visible !== undefined) { + warp: function( action, startTimeScale, endTimeScale, duration ) { - mpars.visible = m.visible; + var keys = []; + + keys.push( { time: this.time, value: startTimeScale } ); + keys.push( { time: this.time + duration, value: endTimeScale } ); + + action.timeScale = new THREE.NumberKeyframeTrack( "timeScale", keys ); - } + return this; - if (m.flipSided !== undefined) { + }, - mpars.side = THREE.BackSide; + crossFade: function( fadeOutAction, fadeInAction, duration, warp ) { - } + this.fadeOut( fadeOutAction, duration ); + this.fadeIn( fadeInAction, duration ); - if (m.doubleSided !== undefined) { + if( warp ) { + + var startEndRatio = fadeOutAction.clip.duration / fadeInAction.clip.duration; + var endStartRatio = 1.0 / startEndRatio; - mpars.side = THREE.DoubleSide; + this.warp( fadeOutAction, 1.0, startEndRatio, duration ); + this.warp( fadeInAction, endStartRatio, 1.0, duration ); - } + } - if (m.wireframe !== undefined) { + return this; + + }, - mpars.wireframe = m.wireframe; + update: function( deltaTime ) { - } + var mixerDeltaTime = deltaTime * this.timeScale; + this.time += mixerDeltaTime; - if (m.vertexColors !== undefined) { + for( var i = 0; i < this.actions.length; i ++ ) { - if (m.vertexColors === 'face') { + var action = this.actions[i]; - mpars.vertexColors = THREE.FaceColors; + var weight = action.getWeightAt( this.time ); - } else if (m.vertexColors) { + var actionTimeScale = action.getTimeScaleAt( this.time ); + var actionDeltaTime = mixerDeltaTime * actionTimeScale; + + var actionResults = action.update( actionDeltaTime ); - mpars.vertexColors = THREE.VertexColors; + if( action.weight <= 0 || ! action.enabled ) continue; - } + for( var j = 0; j < actionResults.length; j ++ ) { - } + var name = action.clip.tracks[j].name; - // colors + action.propertyBindings[ j ].accumulate( actionResults[j], weight ); - if (m.colorDiffuse) { + } - mpars.color = rgb2hex(m.colorDiffuse); + } + + // apply to nodes + for( var propertyBindingKey in this.propertyBindingMap ) { - } else if (m.DbgColor) { + this.propertyBindingMap[ propertyBindingKey ].apply(); - mpars.color = m.DbgColor; + } - } + return this; + + } - if (m.colorSpecular) { +}; - mpars.specular = rgb2hex(m.colorSpecular); +THREE.EventDispatcher.prototype.apply( THREE.AnimationMixer.prototype ); - } +// File:src/animation/AnimationUtils.js - if (m.colorEmissive) { +/** + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + */ - mpars.emissive = rgb2hex(m.colorEmissive); + THREE.AnimationUtils = { - } + getEqualsFunc: function( exemplarValue ) { + + if( exemplarValue.equals ) { + return function equals_object( a, b ) { + return a.equals( b ); + } + } + + return function equals_primitive( a, b ) { + return ( a === b ); + }; - // modifiers + }, - if (m.transparency !== undefined) { + clone: function( exemplarValue ) { - THREE.warn('THREE.Loader: transparency has been renamed to opacity'); - m.opacity = m.transparency; + var typeName = typeof exemplarValue; + if( typeName === "object" ) { + if( exemplarValue.clone ) { + return exemplarValue.clone(); + } + console.error( "can not figure out how to copy exemplarValue", exemplarValue ); + } - } + return exemplarValue; - if (m.opacity !== undefined) { + }, - mpars.opacity = m.opacity; + lerp: function( a, b, alpha, interTrack ) { - } + var lerpFunc = THREE.AnimationUtils.getLerpFunc( a, interTrack ); - if (m.specularCoef) { + return lerpFunc( a, b, alpha ); - mpars.shininess = m.specularCoef; + }, - } + lerp_object: function( a, b, alpha ) { + return a.lerp( b, alpha ); + }, + + slerp_object: function( a, b, alpha ) { + return a.slerp( b, alpha ); + }, - // textures + lerp_number: function( a, b, alpha ) { + return a * ( 1 - alpha ) + b * alpha; + }, - if (m.mapDiffuse && texturePath) { + lerp_boolean: function( a, b, alpha ) { + return ( alpha < 0.5 ) ? a : b; + }, - create_texture(mpars, 'map', m.mapDiffuse, m.mapDiffuseRepeat, m.mapDiffuseOffset, m.mapDiffuseWrap, m.mapDiffuseAnisotropy); + lerp_boolean_immediate: function( a, b, alpha ) { + return a; + }, + + lerp_string: function( a, b, alpha ) { + return ( alpha < 0.5 ) ? a : b; + }, + + lerp_string_immediate: function( a, b, alpha ) { + return a; + }, - } + // NOTE: this is an accumulator function that modifies the first argument (e.g. a). This is to minimize memory alocations. + getLerpFunc: function( exemplarValue, interTrack ) { - if (m.mapLight && texturePath) { + if( exemplarValue === undefined || exemplarValue === null ) throw new Error( "examplarValue is null" ); - create_texture(mpars, 'lightMap', m.mapLight, m.mapLightRepeat, m.mapLightOffset, m.mapLightWrap, m.mapLightAnisotropy); + var typeName = typeof exemplarValue; + switch( typeName ) { + case "object": { - } + if( exemplarValue.lerp ) { - if (m.mapAO && texturePath) { + return THREE.AnimationUtils.lerp_object; - create_texture(mpars, 'aoMap', m.mapAO, m.mapAORepeat, m.mapAOOffset, m.mapAOWrap, m.mapAOAnisotropy); + } + if( exemplarValue.slerp ) { - } + return THREE.AnimationUtils.slerp_object; - if (m.mapBump && texturePath) { + } + break; + } + case "number": { + return THREE.AnimationUtils.lerp_number; + } + case "boolean": { + if( interTrack ) { + return THREE.AnimationUtils.lerp_boolean; + } + else { + return THREE.AnimationUtils.lerp_boolean_immediate; + } + } + case "string": { + if( interTrack ) { + return THREE.AnimationUtils.lerp_string; + } + else { + return THREE.AnimationUtils.lerp_string_immediate; + } + } + }; + + } + +}; +// File:src/animation/KeyframeTrack.js - create_texture(mpars, 'bumpMap', m.mapBump, m.mapBumpRepeat, m.mapBumpOffset, m.mapBumpWrap, m.mapBumpAnisotropy); +/** + * + * A Track that returns a keyframe interpolated value, currently linearly interpolated + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + */ - } +THREE.KeyframeTrack = function ( name, keys ) { - if (m.mapNormal && texturePath) { + if( name === undefined ) throw new Error( "track name is undefined" ); + if( keys === undefined || keys.length === 0 ) throw new Error( "no keys in track named " + name ); - create_texture(mpars, 'normalMap', m.mapNormal, m.mapNormalRepeat, m.mapNormalOffset, m.mapNormalWrap, m.mapNormalAnisotropy); + this.name = name; + this.keys = keys; // time in seconds, value as value - } + // the index of the last result, used as a starting point for local search. + this.lastIndex = 0; - if (m.mapSpecular && texturePath) { + this.validate(); + this.optimize(); +}; - create_texture(mpars, 'specularMap', m.mapSpecular, m.mapSpecularRepeat, m.mapSpecularOffset, m.mapSpecularWrap, m.mapSpecularAnisotropy); +THREE.KeyframeTrack.prototype = { - } + constructor: THREE.KeyframeTrack, - if (m.mapAlpha && texturePath) { + getAt: function( time ) { - create_texture(mpars, 'alphaMap', m.mapAlpha, m.mapAlphaRepeat, m.mapAlphaOffset, m.mapAlphaWrap, m.mapAlphaAnisotropy); - } + // this can not go higher than this.keys.length. + while( ( this.lastIndex < this.keys.length ) && ( time >= this.keys[this.lastIndex].time ) ) { + this.lastIndex ++; + }; - // + // this can not go lower than 0. + while( ( this.lastIndex > 0 ) && ( time < this.keys[this.lastIndex - 1].time ) ) { + this.lastIndex --; + } - if (m.mapBumpScale) { + if( this.lastIndex >= this.keys.length ) { - mpars.bumpScale = m.mapBumpScale; + this.setResult( this.keys[ this.keys.length - 1 ].value ); - } + return this.result; - if (m.mapNormalFactor) { + } - mpars.normalScale = new THREE.Vector2(m.mapNormalFactor, m.mapNormalFactor); + if( this.lastIndex === 0 ) { - } + this.setResult( this.keys[ 0 ].value ); - var material = new THREE[mtype](mpars); + return this.result; - if (m.DbgName !== undefined) material.name = m.DbgName; + } - return material; + var prevKey = this.keys[ this.lastIndex - 1 ]; + this.setResult( prevKey.value ); - } + // if true, means that prev/current keys are identical, thus no interpolation required. + if( prevKey.constantToNext ) { -}; + return this.result; -THREE.Loader.Handlers = { + } - handlers: [], + // linear interpolation to start with + var currentKey = this.keys[ this.lastIndex ]; + var alpha = ( time - prevKey.time ) / ( currentKey.time - prevKey.time ); + this.result = this.lerpValues( this.result, currentKey.value, alpha ); - add: function (regex, loader) { + return this.result; - this.handlers.push(regex, loader); + }, - }, + // move all keyframes either forwards or backwards in time + shift: function( timeOffset ) { - get: function (file) { + if( timeOffset !== 0.0 ) { - for (var i = 0, l = this.handlers.length; i < l; i += 2) { + for( var i = 0; i < this.keys.length; i ++ ) { + this.keys[i].time += timeOffset; + } - var regex = this.handlers[i]; - var loader = this.handlers[i + 1]; + } - if (regex.test(file)) { + return this; - return loader; + }, - } + // scale all keyframe times by a factor (useful for frame <-> seconds conversions) + scale: function( timeScale ) { - } + if( timeScale !== 1.0 ) { - return null; + for( var i = 0; i < this.keys.length; i ++ ) { + this.keys[i].time *= timeScale; + } - } + } -}; + return this; -// File:src/loaders/XHRLoader.js + }, -/** - * @author mrdoob / http://mrdoob.com/ - */ + // removes keyframes before and after animation without changing any values within the range [startTime, endTime]. + // IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values + trim: function( startTime, endTime ) { -THREE.XHRLoader = function (manager) { + var firstKeysToRemove = 0; + for( var i = 1; i < this.keys.length; i ++ ) { + if( this.keys[i] <= startTime ) { + firstKeysToRemove ++; + } + } - this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + var lastKeysToRemove = 0; + for( var i = this.keys.length - 2; i > 0; i ++ ) { + if( this.keys[i] >= endTime ) { + lastKeysToRemove ++; + } + else { + break; + } + } -}; + // remove last keys first because it doesn't affect the position of the first keys (the otherway around doesn't work as easily) + if( ( firstKeysToRemove + lastKeysToRemove ) > 0 ) { + this.keys = this.keys.splice( firstKeysToRemove, this.keys.length - lastKeysToRemove - firstKeysToRemove );; + } -THREE.XHRLoader.prototype = { + return this; - constructor: THREE.XHRLoader, + }, - load: function (url, onLoad, onProgress, onError) { + /* NOTE: This is commented out because we really shouldn't have to handle unsorted key lists + Tracks with out of order keys should be considered to be invalid. - bhouston + sort: function() { - var scope = this; + this.keys.sort( THREE.KeyframeTrack.keyComparer ); - var cached = THREE.Cache.get(url); + return this; - if (cached !== undefined) { + },*/ - if (onLoad) onLoad(cached); - return; + // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable + // One could eventually ensure that all key.values in a track are all of the same type (otherwise interpolation makes no sense.) + validate: function() { - } + var prevKey = null; - var request = new XMLHttpRequest(); - request.open('GET', url, true); + if( this.keys.length === 0 ) { + console.error( " track is empty, no keys", this ); + return; + } - request.addEventListener('load', function (event) { + for( var i = 0; i < this.keys.length; i ++ ) { - THREE.Cache.add(url, this.response); + var currKey = this.keys[i]; - if (onLoad) onLoad(this.response); + if( ! currKey ) { + console.error( " key is null in track", this, i ); + return; + } - scope.manager.itemEnd(url); + if( ( typeof currKey.time ) !== 'number' || Number.isNaN( currKey.time ) ) { + console.error( " key.time is not a valid number", this, i, currKey ); + return; + } - }, false); + if( currKey.value === undefined || currKey.value === null) { + console.error( " key.value is null in track", this, i, currKey ); + return; + } - if (onProgress !== undefined) { + if( prevKey && prevKey.time > currKey.time ) { + console.error( " key.time is less than previous key time, out of order keys", this, i, currKey, prevKey ); + return; + } - request.addEventListener('progress', function (event) { + prevKey = currKey; - onProgress(event); + } - }, false); + return this; - } + }, - if (onError !== undefined) { + // currently only removes equivalent sequential keys (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0), which are common in morph target animations + optimize: function() { - request.addEventListener('error', function (event) { + var newKeys = []; + var prevKey = this.keys[0]; + newKeys.push( prevKey ); - onError(event); + var equalsFunc = THREE.AnimationUtils.getEqualsFunc( prevKey.value ); - }, false); + for( var i = 1; i < this.keys.length - 1; i ++ ) { + var currKey = this.keys[i]; + var nextKey = this.keys[i+1]; - } + // if prevKey & currKey are the same time, remove currKey. If you want immediate adjacent keys, use an epsilon offset + // it is not possible to have two keys at the same time as we sort them. The sort is not stable on keys with the same time. + if( ( prevKey.time === currKey.time ) ) { - if (this.crossOrigin !== undefined) request.crossOrigin = this.crossOrigin; - if (this.responseType !== undefined) request.responseType = this.responseType; + continue; - request.send(null); + } - scope.manager.itemStart(url); + // remove completely unnecessary keyframes that are the same as their prev and next keys + if( this.compareValues( prevKey.value, currKey.value ) && this.compareValues( currKey.value, nextKey.value ) ) { - }, + continue; - setResponseType: function (value) { + } - this.responseType = value; + // determine if interpolation is required + prevKey.constantToNext = this.compareValues( prevKey.value, currKey.value ); - }, + newKeys.push( currKey ); + prevKey = currKey; + } + newKeys.push( this.keys[ this.keys.length - 1 ] ); - setCrossOrigin: function (value) { + this.keys = newKeys; - this.crossOrigin = value; + return this; - } + } }; -// File:src/loaders/ImageLoader.js - -/** - * @author mrdoob / http://mrdoob.com/ - */ +THREE.KeyframeTrack.keyComparer = function keyComparator(key0, key1) { + return key0.time - key1.time; +}; -THREE.ImageLoader = function (manager) { +THREE.KeyframeTrack.parse = function( json ) { - this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + if( json.type === undefined ) throw new Error( "track type undefined, can not parse" ); -}; + var trackType = THREE.KeyframeTrack.GetTrackTypeForTypeName( json.type ); -THREE.ImageLoader.prototype = { + return trackType.parse( json ); - constructor: THREE.ImageLoader, +}; - load: function (url, onLoad, onProgress, onError) { +THREE.KeyframeTrack.GetTrackTypeForTypeName = function( typeName ) { + switch( typeName.toLowerCase() ) { + case "vector": + case "vector2": + case "vector3": + case "vector4": + return THREE.VectorKeyframeTrack; - var scope = this; + case "quaternion": + return THREE.QuaternionKeyframeTrack; - var cached = THREE.Cache.get(url); + case "integer": + case "scalar": + case "double": + case "float": + case "number": + return THREE.NumberKeyframeTrack; - if (cached !== undefined) { + case "bool": + case "boolean": + return THREE.BooleanKeyframeTrack; - onLoad(cached); - return; + case "string": + return THREE.StringKeyframeTrack; + }; - } + throw new Error( "Unsupported typeName: " + typeName ); +}; +// File:src/animation/PropertyBinding.js - var image = document.createElement('img'); +/** + * + * A track bound to a real value in the scene graph. + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + */ - image.addEventListener('load', function (event) { +THREE.PropertyBinding = function ( rootNode, trackName ) { - THREE.Cache.add(url, this); + this.rootNode = rootNode; + this.trackName = trackName; + this.referenceCount = 0; + this.originalValue = null; // the value of the property before it was controlled by this binding - if (onLoad) onLoad(this); + var parseResults = THREE.PropertyBinding.parseTrackName( trackName ); - scope.manager.itemEnd(url); + this.directoryName = parseResults.directoryName; + this.nodeName = parseResults.nodeName; + this.objectName = parseResults.objectName; + this.objectIndex = parseResults.objectIndex; + this.propertyName = parseResults.propertyName; + this.propertyIndex = parseResults.propertyIndex; - }, false); + this.node = THREE.PropertyBinding.findNode( rootNode, this.nodeName ) || rootNode; + + this.cumulativeValue = null; + this.cumulativeWeight = 0; +}; - if (onProgress !== undefined) { +THREE.PropertyBinding.prototype = { - image.addEventListener('progress', function (event) { + constructor: THREE.PropertyBinding, - onProgress(event); + reset: function() { - }, false); + this.cumulativeValue = null; + this.cumulativeWeight = 0; + + }, - } + accumulate: function( value, weight ) { + + if( ! this.isBound ) this.bind(); - if (onError !== undefined) { + if( this.cumulativeWeight === 0 ) { - image.addEventListener('error', function (event) { + if( weight > 0 ) { - onError(event); + if( this.cumulativeValue === null ) { + this.cumulativeValue = THREE.AnimationUtils.clone( value ); + } + this.cumulativeWeight = weight; - }, false); + } - } + } + else { + + var lerpAlpha = weight / ( this.cumulativeWeight + weight ); + this.cumulativeValue = this.lerpValue( this.cumulativeValue, value, lerpAlpha ); + this.cumulativeWeight += weight; + + } + + }, + + unbind: function() { + + if( ! this.isBound ) return; + + this.setValue( this.originalValue ); + + this.setValue = null; + this.getValue = null; + this.lerpValue = null; + this.equalsValue = null; + this.triggerDirty = null; + this.isBound = false; + + }, + + // bind to the real property in the scene graph, remember original value, memorize various accessors for speed/inefficiency + bind: function() { + + if( this.isBound ) return; + + var targetObject = this.node; + + // ensure there is a value node + if( ! targetObject ) { + console.error( " trying to update node for track: " + this.trackName + " but it wasn't found." ); + return; + } + + if( this.objectName ) { + // special case were we need to reach deeper into the hierarchy to get the face materials.... + if( this.objectName === "materials" ) { + if( ! targetObject.material ) { + console.error( ' can not bind to material as node does not have a material', this ); + return; + } + if( ! targetObject.material.materials ) { + console.error( ' can not bind to material.materials as node.material does not have a materials array', this ); + return; + } + targetObject = targetObject.material.materials; + } + else if( this.objectName === "bones" ) { + if( ! targetObject.skeleton ) { + console.error( ' can not bind to bones as node does not have a skeleton', this ); + return; + } + // potential future optimization: skip this if propertyIndex is already an integer, and convert the integer string to a true integer. + + targetObject = targetObject.skeleton.bones; + + // support resolving morphTarget names into indices. + for( var i = 0; i < targetObject.length; i ++ ) { + if( targetObject[i].name === this.objectIndex ) { + this.objectIndex = i; + break; + } + } + } + else { + + if( targetObject[ this.objectName ] === undefined ) { + console.error( ' can not bind to objectName of node, undefined', this ); + return; + } + targetObject = targetObject[ this.objectName ]; + } + + if( this.objectIndex !== undefined ) { + if( targetObject[ this.objectIndex ] === undefined ) { + console.error( " trying to bind to objectIndex of objectName, but is undefined:", this, targetObject ); + return; + } + + targetObject = targetObject[ this.objectIndex ]; + } + + } + + // special case mappings + var nodeProperty = targetObject[ this.propertyName ]; + if( ! nodeProperty ) { + console.error( " trying to update property for track: " + this.nodeName + '.' + this.propertyName + " but it wasn't found.", targetObject ); + return; + } + + // access a sub element of the property array (only primitives are supported right now) + if( this.propertyIndex !== undefined ) { + + if( this.propertyName === "morphTargetInfluences" ) { + // potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer. + + // support resolving morphTarget names into indices. + if( ! targetObject.geometry ) { + console.error( ' can not bind to morphTargetInfluences becasuse node does not have a geometry', this ); + } + if( ! targetObject.geometry.morphTargets ) { + console.error( ' can not bind to morphTargetInfluences becasuse node does not have a geometry.morphTargets', this ); + } + + for( var i = 0; i < this.node.geometry.morphTargets.length; i ++ ) { + if( targetObject.geometry.morphTargets[i].name === this.propertyIndex ) { + this.propertyIndex = i; + break; + } + } + } + + this.setValue = function setValue_propertyIndexed( value ) { + if( ! this.equalsValue( nodeProperty[ this.propertyIndex ], value ) ) { + nodeProperty[ this.propertyIndex ] = value; + return true; + } + return false; + }; + + this.getValue = function getValue_propertyIndexed() { + return nodeProperty[ this.propertyIndex ]; + }; + + } + // must use copy for Object3D.Euler/Quaternion + else if( nodeProperty.copy ) { + + this.setValue = function setValue_propertyObject( value ) { + if( ! this.equalsValue( nodeProperty, value ) ) { + nodeProperty.copy( value ); + return true; + } + return false; + } + + this.getValue = function getValue_propertyObject() { + return nodeProperty; + }; + + } + // otherwise just set the property directly on the node (do not use nodeProperty as it may not be a reference object) + else { + + this.setValue = function setValue_property( value ) { + if( ! this.equalsValue( targetObject[ this.propertyName ], value ) ) { + targetObject[ this.propertyName ] = value; + return true; + } + return false; + } + + this.getValue = function getValue_property() { + return targetObject[ this.propertyName ]; + }; + + } + + // trigger node dirty + if( targetObject.needsUpdate !== undefined ) { // material + + this.triggerDirty = function triggerDirty_needsUpdate() { + this.node.needsUpdate = true; + } + + } + else if( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform + + this.triggerDirty = function triggerDirty_matrixWorldNeedsUpdate() { + targetObject.matrixWorldNeedsUpdate = true; + } - if (this.crossOrigin !== undefined) image.crossOrigin = this.crossOrigin; + } - image.src = url; + this.originalValue = this.getValue(); - scope.manager.itemStart(url); + this.equalsValue = THREE.AnimationUtils.getEqualsFunc( this.originalValue ); + this.lerpValue = THREE.AnimationUtils.getLerpFunc( this.originalValue, true ); - return image; + this.isBound = true; - }, + }, - setCrossOrigin: function (value) { + apply: function() { - this.crossOrigin = value; + // for speed capture the setter pattern as a closure (sort of a memoization pattern: https://en.wikipedia.org/wiki/Memoization) + if( ! this.isBound ) this.bind(); - } + // early exit if there is nothing to apply. + if( this.cumulativeWeight > 0 ) { + + // blend with original value + if( this.cumulativeWeight < 1 ) { -}; + var remainingWeight = 1 - this.cumulativeWeight; + var lerpAlpha = remainingWeight / ( this.cumulativeWeight + remainingWeight ); + this.cumulativeValue = this.lerpValue( this.cumulativeValue, this.originalValue, lerpAlpha ); -// File:src/loaders/JSONLoader.js + } -/** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - */ + var valueChanged = this.setValue( this.cumulativeValue ); -THREE.JSONLoader = function (showStatus) { + if( valueChanged && this.triggerDirty ) { + this.triggerDirty(); + } - THREE.Loader.call(this, showStatus); + // reset accumulator + this.cumulativeValue = null; + this.cumulativeWeight = 0; - this.withCredentials = false; + } + } }; -THREE.JSONLoader.prototype = Object.create(THREE.Loader.prototype); -THREE.JSONLoader.prototype.constructor = THREE.JSONLoader; -THREE.JSONLoader.prototype.load = function (url, callback, texturePath) { +THREE.PropertyBinding.parseTrackName = function( trackName ) { - // todo: unify load API to for easier SceneLoader use + // matches strings in the form of: + // nodeName.property + // nodeName.property[accessor] + // nodeName.material.property[accessor] + // uuid.property[accessor] + // uuid.objectName[objectIndex].propertyName[propertyIndex] + // parentName/nodeName.property + // parentName/parentName/nodeName.property[index] + // .bone[Armature.DEF_cog].position + // created and tested via https://regex101.com/#javascript - texturePath = texturePath && ( typeof texturePath === 'string' ) ? texturePath : this.extractUrlBase(url); + var re = /^(([\w]+\/)*)([\w-\d]+)?(\.([\w]+)(\[([\w\d\[\]\_. ]+)\])?)?(\.([\w.]+)(\[([\w\d\[\]\_. ]+)\])?)$/; + var matches = re.exec(trackName); - this.onLoadStart(); - this.loadAjaxJSON(this, url, callback, texturePath); + if( ! matches ) { + throw new Error( "cannot parse trackName at all: " + trackName ); + } -}; + if (matches.index === re.lastIndex) { + re.lastIndex++; + } -THREE.JSONLoader.prototype.loadAjaxJSON = function (context, url, callback, texturePath, callbackProgress) { + var results = { + directoryName: matches[1], + nodeName: matches[3], // allowed to be null, specified root node. + objectName: matches[5], + objectIndex: matches[7], + propertyName: matches[9], + propertyIndex: matches[11] // allowed to be null, specifies that the whole property is set. + }; - var xhr = new XMLHttpRequest(); + if( results.propertyName === null || results.propertyName.length === 0 ) { + throw new Error( "can not parse propertyName from trackName: " + trackName ); + } - var length = 0; + return results; - xhr.onreadystatechange = function () { +}; - if (xhr.readyState === xhr.DONE) { +THREE.PropertyBinding.findNode = function( root, nodeName ) { - if (xhr.status === 200 || xhr.status === 0) { + if( ! nodeName || nodeName === "" || nodeName === "root" || nodeName === "." || nodeName === -1 || nodeName === root.name || nodeName === root.uuid ) { - if (xhr.responseText) { + return root; - var json = JSON.parse(xhr.responseText); - var metadata = json.metadata; + } - if (metadata !== undefined) { + // search into skeleton bones. + if( root.skeleton ) { - if (metadata.type === 'object') { + var searchSkeleton = function( skeleton ) { - THREE.error('THREE.JSONLoader: ' + url + ' should be loaded with THREE.ObjectLoader instead.'); - return; + for( var i = 0; i < skeleton.bones.length; i ++ ) { - } + var bone = skeleton.bones[i]; - if (metadata.type === 'scene') { + if( bone.name === nodeName ) { - THREE.error('THREE.JSONLoader: ' + url + ' seems to be a Scene. Use THREE.SceneLoader instead.'); - return; + return bone; - } + } + } - } + return null; - var result = context.parse(json, texturePath); - callback(result.geometry, result.materials); + }; - } else { + var bone = searchSkeleton( root.skeleton ); - THREE.error('THREE.JSONLoader: ' + url + ' seems to be unreachable or the file is empty.'); + if( bone ) { - } + return bone; - // in context of more complex asset initialization - // do not block on single failed file - // maybe should go even one more level up + } + } - context.onLoadComplete(); + // search into node subtree. + if( root.children ) { - } else { + var searchNodeSubtree = function( children ) { - THREE.error('THREE.JSONLoader: Couldn\'t load ' + url + ' (' + xhr.status + ')'); + for( var i = 0; i < children.length; i ++ ) { - } + var childNode = children[i]; - } else if (xhr.readyState === xhr.LOADING) { + if( childNode.name === nodeName || childNode.uuid === nodeName ) { - if (callbackProgress) { + return childNode; - if (length === 0) { + } - length = xhr.getResponseHeader('Content-Length'); + var result = searchNodeSubtree( childNode.children ); - } + if( result ) return result; - callbackProgress({total: length, loaded: xhr.responseText.length}); + } - } + return null; - } else if (xhr.readyState === xhr.HEADERS_RECEIVED) { + }; - if (callbackProgress !== undefined) { + var subTreeNode = searchNodeSubtree( root.children ); - length = xhr.getResponseHeader('Content-Length'); + if( subTreeNode ) { - } + return subTreeNode; - } + } - }; + } - xhr.open('GET', url, true); - xhr.withCredentials = this.withCredentials; - xhr.send(null); + return null; +} -}; +// File:src/animation/tracks/VectorKeyframeTrack.js -THREE.JSONLoader.prototype.parse = function (json, texturePath) { +/** + * + * A Track that interpolates Vectors + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + */ + +THREE.VectorKeyframeTrack = function ( name, keys ) { + + THREE.KeyframeTrack.call( this, name, keys ); + + // local cache of value type to avoid allocations during runtime. + this.result = this.keys[0].value.clone(); + +}; - var geometry = new THREE.Geometry(), - scale = ( json.scale !== undefined ) ? 1.0 / json.scale : 1.0; +THREE.VectorKeyframeTrack.prototype = Object.create( THREE.KeyframeTrack.prototype ); - parseModel(scale); +THREE.VectorKeyframeTrack.prototype.constructor = THREE.VectorKeyframeTrack; - parseSkin(); - parseMorphing(scale); +THREE.VectorKeyframeTrack.prototype.setResult = function( value ) { - geometry.computeFaceNormals(); - geometry.computeBoundingSphere(); + this.result.copy( value ); - function parseModel(scale) { +}; - function isBitSet(value, position) { +// memoization of the lerp function for speed. +// NOTE: Do not optimize as a prototype initialization closure, as value0 will be different on a per class basis. +THREE.VectorKeyframeTrack.prototype.lerpValues = function( value0, value1, alpha ) { - return value & ( 1 << position ); + return value0.lerp( value1, alpha ); - } +}; - var i, j, fi, +THREE.VectorKeyframeTrack.prototype.compareValues = function( value0, value1 ) { - offset, zLength, + return value0.equals( value1 ); - colorIndex, normalIndex, uvIndex, +}; - type, - isQuad, - hasMaterial, - hasFaceVertexUv, - hasFaceNormal, hasFaceVertexNormal, - hasFaceColor, hasFaceVertexColor, +THREE.VectorKeyframeTrack.prototype.clone = function() { - vertex, face, faceA, faceB, hex, normal, + var clonedKeys = []; - uvLayer, uv, u, v, + for( var i = 0; i < this.keys.length; i ++ ) { + + var key = this.keys[i]; + clonedKeys.push( { + time: key.time, + value: key.value.clone() + } ); + } - faces = json.faces, - vertices = json.vertices, - normals = json.normals, - colors = json.colors, + return new THREE.VectorKeyframeTrack( this.name, clonedKeys ); - nUvLayers = 0; +}; - if (json.uvs !== undefined) { +THREE.VectorKeyframeTrack.parse = function( json ) { - // disregard empty arrays + var elementCount = json.keys[0].value.length; + var valueType = THREE[ 'Vector' + elementCount ]; - for (i = 0; i < json.uvs.length; i++) { + var keys = []; - if (json.uvs[i].length) nUvLayers++; + for( var i = 0; i < json.keys.length; i ++ ) { + var jsonKey = json.keys[i]; + keys.push( { + value: new valueType().fromArray( jsonKey.value ), + time: jsonKey.time + } ); + } - } + return new THREE.VectorKeyframeTrack( json.name, keys ); - for (i = 0; i < nUvLayers; i++) { +}; + +// File:src/animation/tracks/QuaternionKeyframeTrack.js - geometry.faceVertexUvs[i] = []; +/** + * + * A Track that interpolates Quaternion + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + */ - } +THREE.QuaternionKeyframeTrack = function ( name, keys ) { - } + THREE.KeyframeTrack.call( this, name, keys ); - offset = 0; - zLength = vertices.length; + // local cache of value type to avoid allocations during runtime. + this.result = this.keys[0].value.clone(); - while (offset < zLength) { +}; + +THREE.QuaternionKeyframeTrack.prototype = Object.create( THREE.KeyframeTrack.prototype ); - vertex = new THREE.Vector3(); +THREE.QuaternionKeyframeTrack.prototype.constructor = THREE.QuaternionKeyframeTrack; - vertex.x = vertices[offset++] * scale; - vertex.y = vertices[offset++] * scale; - vertex.z = vertices[offset++] * scale; +THREE.QuaternionKeyframeTrack.prototype.setResult = function( value ) { - geometry.vertices.push(vertex); + this.result.copy( value ); - } +}; - offset = 0; - zLength = faces.length; +// memoization of the lerp function for speed. +// NOTE: Do not optimize as a prototype initialization closure, as value0 will be different on a per class basis. +THREE.QuaternionKeyframeTrack.prototype.lerpValues = function( value0, value1, alpha ) { - while (offset < zLength) { + return value0.slerp( value1, alpha ); - type = faces[offset++]; +}; +THREE.QuaternionKeyframeTrack.prototype.compareValues = function( value0, value1 ) { - 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); + return value0.equals( value1 ); - // THREE.log("type", type, "bits", isQuad, hasMaterial, hasFaceVertexUv, hasFaceNormal, hasFaceVertexNormal, hasFaceColor, hasFaceVertexColor); +}; - if (isQuad) { +THREE.QuaternionKeyframeTrack.prototype.multiply = function( quat ) { - faceA = new THREE.Face3(); - faceA.a = faces[offset]; - faceA.b = faces[offset + 1]; - faceA.c = faces[offset + 3]; + for( var i = 0; i < this.keys.length; i ++ ) { - faceB = new THREE.Face3(); - faceB.a = faces[offset + 1]; - faceB.b = faces[offset + 2]; - faceB.c = faces[offset + 3]; + this.keys[i].value.multiply( quat ); + + } - offset += 4; + return this; - if (hasMaterial) { +}; - offset++; +THREE.QuaternionKeyframeTrack.prototype.clone = function() { - } + var clonedKeys = []; - // to get face <=> uv index correspondence + for( var i = 0; i < this.keys.length; i ++ ) { + + var key = this.keys[i]; + clonedKeys.push( { + time: key.time, + value: key.value.clone() + } ); + } - fi = geometry.faces.length; + return new THREE.QuaternionKeyframeTrack( this.name, clonedKeys ); - if (hasFaceVertexUv) { +}; - for (i = 0; i < nUvLayers; i++) { +THREE.QuaternionKeyframeTrack.parse = function( json ) { - uvLayer = json.uvs[i]; + var keys = []; - geometry.faceVertexUvs[i][fi] = []; - geometry.faceVertexUvs[i][fi + 1] = []; + for( var i = 0; i < json.keys.length; i ++ ) { + var jsonKey = json.keys[i]; + keys.push( { + value: new THREE.Quaternion().fromArray( jsonKey.value ), + time: jsonKey.time + } ); + } - for (j = 0; j < 4; j++) { + return new THREE.QuaternionKeyframeTrack( json.name, keys ); - uvIndex = faces[offset++]; +}; + +// File:src/animation/tracks/StringKeyframeTrack.js - u = uvLayer[uvIndex * 2]; - v = uvLayer[uvIndex * 2 + 1]; +/** + * + * A Track that interpolates Strings + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + */ - uv = new THREE.Vector2(u, v); +THREE.StringKeyframeTrack = function ( name, keys ) { - if (j !== 2) geometry.faceVertexUvs[i][fi].push(uv); - if (j !== 0) geometry.faceVertexUvs[i][fi + 1].push(uv); + THREE.KeyframeTrack.call( this, name, keys ); - } + // local cache of value type to avoid allocations during runtime. + this.result = this.keys[0].value; - } +}; - } +THREE.StringKeyframeTrack.prototype = Object.create( THREE.KeyframeTrack.prototype ); - if (hasFaceNormal) { +THREE.StringKeyframeTrack.prototype.constructor = THREE.StringKeyframeTrack; - normalIndex = faces[offset++] * 3; +THREE.StringKeyframeTrack.prototype.setResult = function( value ) { - faceA.normal.set( - normals[normalIndex++], - normals[normalIndex++], - normals[normalIndex] - ); + this.result = value; - faceB.normal.copy(faceA.normal); +}; - } +// memoization of the lerp function for speed. +// NOTE: Do not optimize as a prototype initialization closure, as value0 will be different on a per class basis. +THREE.StringKeyframeTrack.prototype.lerpValues = function( value0, value1, alpha ) { - if (hasFaceVertexNormal) { + return ( alpha < 1.0 ) ? value0 : value1; - for (i = 0; i < 4; i++) { +}; - normalIndex = faces[offset++] * 3; +THREE.StringKeyframeTrack.prototype.compareValues = function( value0, value1 ) { - normal = new THREE.Vector3( - normals[normalIndex++], - normals[normalIndex++], - normals[normalIndex] - ); + return ( value0 === value1 ); +}; - if (i !== 2) faceA.vertexNormals.push(normal); - if (i !== 0) faceB.vertexNormals.push(normal); +THREE.StringKeyframeTrack.prototype.clone = function() { - } + var clonedKeys = []; - } + for( var i = 0; i < this.keys.length; i ++ ) { + + var key = this.keys[i]; + clonedKeys.push( { + time: key.time, + value: key.value + } ); + } + return new THREE.StringKeyframeTrack( this.name, clonedKeys ); - if (hasFaceColor) { +}; - colorIndex = faces[offset++]; - hex = colors[colorIndex]; +THREE.StringKeyframeTrack.parse = function( json ) { - faceA.color.setHex(hex); - faceB.color.setHex(hex); + return new THREE.StringKeyframeTrack( json.name, json.keys ); - } +}; + +// File:src/animation/tracks/BooleanKeyframeTrack.js +/** + * + * A Track that interpolates Boolean + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + */ - if (hasFaceVertexColor) { +THREE.BooleanKeyframeTrack = function ( name, keys ) { - for (i = 0; i < 4; i++) { + THREE.KeyframeTrack.call( this, name, keys ); - colorIndex = faces[offset++]; - hex = colors[colorIndex]; + // local cache of value type to avoid allocations during runtime. + this.result = this.keys[0].value; - if (i !== 2) faceA.vertexColors.push(new THREE.Color(hex)); - if (i !== 0) faceB.vertexColors.push(new THREE.Color(hex)); +}; - } +THREE.BooleanKeyframeTrack.prototype = Object.create( THREE.KeyframeTrack.prototype ); - } +THREE.BooleanKeyframeTrack.prototype.constructor = THREE.BooleanKeyframeTrack; - geometry.faces.push(faceA); - geometry.faces.push(faceB); +THREE.BooleanKeyframeTrack.prototype.setResult = function( value ) { - } else { + this.result = value; - face = new THREE.Face3(); - face.a = faces[offset++]; - face.b = faces[offset++]; - face.c = faces[offset++]; +}; - if (hasMaterial) { +// memoization of the lerp function for speed. +// NOTE: Do not optimize as a prototype initialization closure, as value0 will be different on a per class basis. +THREE.BooleanKeyframeTrack.prototype.lerpValues = function( value0, value1, alpha ) { - offset++; + return ( alpha < 1.0 ) ? value0 : value1; - } +}; - // to get face <=> uv index correspondence +THREE.BooleanKeyframeTrack.prototype.compareValues = function( value0, value1 ) { - fi = geometry.faces.length; + return ( value0 === value1 ); - if (hasFaceVertexUv) { +}; - for (i = 0; i < nUvLayers; i++) { +THREE.BooleanKeyframeTrack.prototype.clone = function() { - uvLayer = json.uvs[i]; + var clonedKeys = []; - geometry.faceVertexUvs[i][fi] = []; + for( var i = 0; i < this.keys.length; i ++ ) { + + var key = this.keys[i]; + clonedKeys.push( { + time: key.time, + value: key.value + } ); + } - for (j = 0; j < 3; j++) { + return new THREE.BooleanKeyframeTrack( this.name, clonedKeys ); - uvIndex = faces[offset++]; +}; - u = uvLayer[uvIndex * 2]; - v = uvLayer[uvIndex * 2 + 1]; +THREE.BooleanKeyframeTrack.parse = function( json ) { - uv = new THREE.Vector2(u, v); + return new THREE.BooleanKeyframeTrack( json.name, json.keys ); - geometry.faceVertexUvs[i][fi].push(uv); +}; + +// File:src/animation/tracks/NumberKeyframeTrack.js - } +/** + * + * A Track that interpolates Numbers + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + */ - } +THREE.NumberKeyframeTrack = function ( name, keys ) { - } + THREE.KeyframeTrack.call( this, name, keys ); - if (hasFaceNormal) { + // local cache of value type to avoid allocations during runtime. + this.result = this.keys[0].value; - normalIndex = faces[offset++] * 3; +}; - face.normal.set( - normals[normalIndex++], - normals[normalIndex++], - normals[normalIndex] - ); +THREE.NumberKeyframeTrack.prototype = Object.create( THREE.KeyframeTrack.prototype ); - } +THREE.NumberKeyframeTrack.prototype.constructor = THREE.NumberKeyframeTrack; - if (hasFaceVertexNormal) { +THREE.NumberKeyframeTrack.prototype.setResult = function( value ) { - for (i = 0; i < 3; i++) { + this.result = value; - normalIndex = faces[offset++] * 3; +}; - normal = new THREE.Vector3( - normals[normalIndex++], - normals[normalIndex++], - normals[normalIndex] - ); +// memoization of the lerp function for speed. +// NOTE: Do not optimize as a prototype initialization closure, as value0 will be different on a per class basis. +THREE.NumberKeyframeTrack.prototype.lerpValues = function( value0, value1, alpha ) { - face.vertexNormals.push(normal); + return value0 * ( 1 - alpha ) + value1 * alpha; - } +}; - } +THREE.NumberKeyframeTrack.prototype.compareValues = function( value0, value1 ) { + return ( value0 === value1 ); - if (hasFaceColor) { +}; - colorIndex = faces[offset++]; - face.color.setHex(colors[colorIndex]); +THREE.NumberKeyframeTrack.prototype.clone = function() { - } + var clonedKeys = []; + for( var i = 0; i < this.keys.length; i ++ ) { + + var key = this.keys[i]; + clonedKeys.push( { + time: key.time, + value: key.value + } ); + } - if (hasFaceVertexColor) { + return new THREE.NumberKeyframeTrack( this.name, clonedKeys ); - for (i = 0; i < 3; i++) { +}; - colorIndex = faces[offset++]; - face.vertexColors.push(new THREE.Color(colors[colorIndex])); +THREE.NumberKeyframeTrack.parse = function( json ) { - } + return new THREE.NumberKeyframeTrack( json.name, json.keys ); - } +}; + +// File:src/cameras/Camera.js - geometry.faces.push(face); +/** + * @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.type = 'Camera'; - function parseSkin() { - var influencesPerVertex = ( json.influencesPerVertex !== undefined ) ? json.influencesPerVertex : 2; + this.matrixWorldInverse = new THREE.Matrix4(); + this.projectionMatrix = new THREE.Matrix4(); - if (json.skinWeights) { +}; - for (var i = 0, l = json.skinWeights.length; i < l; i += influencesPerVertex) { +THREE.Camera.prototype = Object.create( THREE.Object3D.prototype ); +THREE.Camera.prototype.constructor = THREE.Camera; - var x = json.skinWeights[i]; - var y = ( influencesPerVertex > 1 ) ? json.skinWeights[i + 1] : 0; - var z = ( influencesPerVertex > 2 ) ? json.skinWeights[i + 2] : 0; - var w = ( influencesPerVertex > 3 ) ? json.skinWeights[i + 3] : 0; +THREE.Camera.prototype.getWorldDirection = function () { - geometry.skinWeights.push(new THREE.Vector4(x, y, z, w)); + var quaternion = new THREE.Quaternion(); - } + return function ( optionalTarget ) { - } + var result = optionalTarget || new THREE.Vector3(); - if (json.skinIndices) { + this.getWorldQuaternion( quaternion ); - for (var i = 0, l = json.skinIndices.length; i < l; i += influencesPerVertex) { + return result.set( 0, 0, - 1 ).applyQuaternion( quaternion ); - var a = json.skinIndices[i]; - var b = ( influencesPerVertex > 1 ) ? json.skinIndices[i + 1] : 0; - var c = ( influencesPerVertex > 2 ) ? json.skinIndices[i + 2] : 0; - var d = ( influencesPerVertex > 3 ) ? json.skinIndices[i + 3] : 0; + }; - geometry.skinIndices.push(new THREE.Vector4(a, b, c, d)); +}(); - } +THREE.Camera.prototype.lookAt = function () { - } + // This routine does not support cameras with rotated and/or translated parent(s) - geometry.bones = json.bones; + var m1 = new THREE.Matrix4(); - if (geometry.bones && geometry.bones.length > 0 && ( geometry.skinWeights.length !== geometry.skinIndices.length || geometry.skinIndices.length !== geometry.vertices.length )) { + return function ( vector ) { - THREE.warn('THREE.JSONLoader: When skinning, number of vertices (' + geometry.vertices.length + '), skinIndices (' + - geometry.skinIndices.length + '), and skinWeights (' + geometry.skinWeights.length + ') should match.'); + m1.lookAt( this.position, vector, this.up ); - } + this.quaternion.setFromRotationMatrix( m1 ); + }; - // could change this to json.animations[0] or remove completely +}(); - geometry.animation = json.animation; - geometry.animations = json.animations; +THREE.Camera.prototype.clone = function () { - } + return new this.constructor().copy( this ); - function parseMorphing(scale) { +}; - if (json.morphTargets !== undefined) { +THREE.Camera.prototype.copy = function ( source ) { - var i, l, v, vl, dstVertices, srcVertices; + THREE.Object3D.prototype.copy.call( this, source ); - for (i = 0, l = json.morphTargets.length; i < l; i++) { + this.matrixWorldInverse.copy( source.matrixWorldInverse ); + this.projectionMatrix.copy( source.projectionMatrix ); - geometry.morphTargets[i] = {}; - geometry.morphTargets[i].name = json.morphTargets[i].name; - geometry.morphTargets[i].vertices = []; + return this; - dstVertices = geometry.morphTargets[i].vertices; - srcVertices = json.morphTargets [i].vertices; +}; - for (v = 0, vl = srcVertices.length; v < vl; v += 3) { +// File:src/cameras/CubeCamera.js - var vertex = new THREE.Vector3(); - vertex.x = srcVertices[v] * scale; - vertex.y = srcVertices[v + 1] * scale; - vertex.z = srcVertices[v + 2] * scale; +/** + * Camera for rendering cube maps + * - renders scene into axis-aligned cube + * + * @author alteredq / http://alteredqualia.com/ + */ - dstVertices.push(vertex); +THREE.CubeCamera = function ( near, far, cubeResolution ) { - } + THREE.Object3D.call( this ); - } + this.type = 'CubeCamera'; - } + var fov = 90, aspect = 1; - if (json.morphColors !== undefined) { + 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 i, l, c, cl, dstColors, srcColors, color; + 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 ); - for (i = 0, l = json.morphColors.length; i < l; i++) { + 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 ); - geometry.morphColors[i] = {}; - geometry.morphColors[i].name = json.morphColors[i].name; - geometry.morphColors[i].colors = []; + 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 ); - dstColors = geometry.morphColors[i].colors; - srcColors = json.morphColors [i].colors; + 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 ); - for (c = 0, cl = srcColors.length; c < cl; c += 3) { + 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 ); - color = new THREE.Color(0xffaa00); - color.setRGB(srcColors[c], srcColors[c + 1], srcColors[c + 2]); - dstColors.push(color); + this.renderTarget = new THREE.WebGLRenderTargetCube( cubeResolution, cubeResolution, { format: THREE.RGBFormat, magFilter: THREE.LinearFilter, minFilter: THREE.LinearFilter } ); - } + this.updateCubeMap = function ( renderer, scene ) { - } + if ( this.parent === null ) this.updateMatrixWorld(); - } + var renderTarget = this.renderTarget; + var generateMipmaps = renderTarget.generateMipmaps; - } + renderTarget.generateMipmaps = false; - if (json.materials === undefined || json.materials.length === 0) { + renderTarget.activeCubeFace = 0; + renderer.render( scene, cameraPX, renderTarget ); - return {geometry: geometry}; + renderTarget.activeCubeFace = 1; + renderer.render( scene, cameraNX, renderTarget ); - } else { + renderTarget.activeCubeFace = 2; + renderer.render( scene, cameraPY, renderTarget ); - var materials = this.initMaterials(json.materials, texturePath); + renderTarget.activeCubeFace = 3; + renderer.render( scene, cameraNY, renderTarget ); - if (this.needsTangents(materials)) { + renderTarget.activeCubeFace = 4; + renderer.render( scene, cameraPZ, renderTarget ); - geometry.computeTangents(); + renderTarget.generateMipmaps = generateMipmaps; - } + renderTarget.activeCubeFace = 5; + renderer.render( scene, cameraNZ, renderTarget ); - return {geometry: geometry, materials: materials}; + renderer.setRenderTarget( null ); - } + }; }; -// File:src/loaders/LoadingManager.js +THREE.CubeCamera.prototype = Object.create( THREE.Object3D.prototype ); +THREE.CubeCamera.prototype.constructor = THREE.CubeCamera; + +// File:src/cameras/OrthographicCamera.js /** - * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ */ -THREE.LoadingManager = function (onLoad, onProgress, onError) { +THREE.OrthographicCamera = function ( left, right, top, bottom, near, far ) { - var scope = this; + THREE.Camera.call( this ); - var loaded = 0, total = 0; + this.type = 'OrthographicCamera'; - this.onLoad = onLoad; - this.onProgress = onProgress; - this.onError = onError; + this.zoom = 1; - this.itemStart = function (url) { + this.left = left; + this.right = right; + this.top = top; + this.bottom = bottom; - total++; + this.near = ( near !== undefined ) ? near : 0.1; + this.far = ( far !== undefined ) ? far : 2000; - }; + this.updateProjectionMatrix(); - this.itemEnd = function (url) { +}; - loaded++; +THREE.OrthographicCamera.prototype = Object.create( THREE.Camera.prototype ); +THREE.OrthographicCamera.prototype.constructor = THREE.OrthographicCamera; - if (scope.onProgress !== undefined) { +THREE.OrthographicCamera.prototype.updateProjectionMatrix = function () { - scope.onProgress(url, loaded, total); + var dx = ( this.right - this.left ) / ( 2 * this.zoom ); + var dy = ( this.top - this.bottom ) / ( 2 * this.zoom ); + var cx = ( this.right + this.left ) / 2; + var cy = ( this.top + this.bottom ) / 2; - } + this.projectionMatrix.makeOrthographic( cx - dx, cx + dx, cy + dy, cy - dy, this.near, this.far ); - if (loaded === total && scope.onLoad !== undefined) { +}; - scope.onLoad(); +THREE.OrthographicCamera.prototype.copy = function ( source ) { + + THREE.Camera.prototype.copy.call( this, source ); + + this.left = source.left; + this.right = source.right; + this.top = source.top; + this.bottom = source.bottom; + this.near = source.near; + this.far = source.far; + + this.zoom = source.zoom; + + return this; + +}; - } +THREE.OrthographicCamera.prototype.toJSON = function ( meta ) { - }; + var data = THREE.Object3D.prototype.toJSON.call( this, meta ); -}; + data.object.zoom = this.zoom; + data.object.left = this.left; + data.object.right = this.right; + data.object.top = this.top; + data.object.bottom = this.bottom; + data.object.near = this.near; + data.object.far = this.far; -THREE.DefaultLoadingManager = new THREE.LoadingManager(); + return data; -// File:src/loaders/BufferGeometryLoader.js +}; + +// File:src/cameras/PerspectiveCamera.js /** * @author mrdoob / http://mrdoob.com/ + * @author greggman / http://games.greggman.com/ + * @author zz85 / http://www.lab4games.net/zz85/blog */ -THREE.BufferGeometryLoader = function (manager) { +THREE.PerspectiveCamera = function ( fov, aspect, near, far ) { - this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + THREE.Camera.call( this ); -}; + this.type = 'PerspectiveCamera'; -THREE.BufferGeometryLoader.prototype = { + this.zoom = 1; - constructor: THREE.BufferGeometryLoader, + 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; - load: function (url, onLoad, onProgress, onError) { + this.updateProjectionMatrix(); - var scope = this; +}; - var loader = new THREE.XHRLoader(scope.manager); - loader.setCrossOrigin(this.crossOrigin); - loader.load(url, function (text) { +THREE.PerspectiveCamera.prototype = Object.create( THREE.Camera.prototype ); +THREE.PerspectiveCamera.prototype.constructor = THREE.PerspectiveCamera; - onLoad(scope.parse(JSON.parse(text))); - }, onProgress, onError); +/** + * Uses Focal Length (in mm) to estimate and set FOV + * 35mm (full-frame) 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 ) { - setCrossOrigin: function (value) { + if ( frameHeight === undefined ) frameHeight = 24; - this.crossOrigin = value; + this.fov = 2 * THREE.Math.radToDeg( Math.atan( frameHeight / ( focalLength * 2 ) ) ); + this.updateProjectionMatrix(); - }, +}; - parse: function (json) { - var geometry = new THREE.BufferGeometry(); +/** + * 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. + */ - var attributes = json.data.attributes; +THREE.PerspectiveCamera.prototype.setViewOffset = function ( fullWidth, fullHeight, x, y, width, height ) { - for (var key in attributes) { + this.fullWidth = fullWidth; + this.fullHeight = fullHeight; + this.x = x; + this.y = y; + this.width = width; + this.height = height; - var attribute = attributes[key]; - var typedArray = new self[attribute.type](attribute.array); + this.updateProjectionMatrix(); - geometry.addAttribute(key, new THREE.BufferAttribute(typedArray, attribute.itemSize)); +}; - } - var offsets = json.data.offsets; +THREE.PerspectiveCamera.prototype.updateProjectionMatrix = function () { - if (offsets !== undefined) { + var fov = THREE.Math.radToDeg( 2 * Math.atan( Math.tan( THREE.Math.degToRad( this.fov ) * 0.5 ) / this.zoom ) ); - geometry.offsets = JSON.parse(JSON.stringify(offsets)); + if ( this.fullWidth ) { - } + var aspect = this.fullWidth / this.fullHeight; + var top = Math.tan( THREE.Math.degToRad( 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 ); - var boundingSphere = json.data.boundingSphere; + 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 + ); - if (boundingSphere !== undefined) { + } else { - var center = new THREE.Vector3(); + this.projectionMatrix.makePerspective( fov, this.aspect, this.near, this.far ); - if (boundingSphere.center !== undefined) { + } - center.fromArray(boundingSphere.center); +}; - } +THREE.PerspectiveCamera.prototype.copy = function ( source ) { + + THREE.Camera.prototype.copy.call( this, source ); + + this.fov = source.fov; + this.aspect = source.aspect; + this.near = source.near; + this.far = source.far; + + this.zoom = source.zoom; + + return this; + +}; - geometry.boundingSphere = new THREE.Sphere(center, boundingSphere.radius); +THREE.PerspectiveCamera.prototype.toJSON = function ( meta ) { - } + var data = THREE.Object3D.prototype.toJSON.call( this, meta ); - return geometry; + data.object.zoom = this.zoom; + data.object.fov = this.fov; + data.object.aspect = this.aspect; + data.object.near = this.near; + data.object.far = this.far; - } + return data; }; -// File:src/loaders/MaterialLoader.js +// File:src/lights/Light.js /** * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ */ -THREE.MaterialLoader = function (manager) { - - this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; - -}; - -THREE.MaterialLoader.prototype = { - - constructor: THREE.MaterialLoader, - - load: function (url, onLoad, onProgress, onError) { +THREE.Light = function ( color ) { - var scope = this; + THREE.Object3D.call( this ); - var loader = new THREE.XHRLoader(scope.manager); - loader.setCrossOrigin(this.crossOrigin); - loader.load(url, function (text) { + this.type = 'Light'; - onLoad(scope.parse(JSON.parse(text))); + this.color = new THREE.Color( color ); - }, onProgress, onError); - - }, - - setCrossOrigin: function (value) { +}; - this.crossOrigin = value; +THREE.Light.prototype = Object.create( THREE.Object3D.prototype ); +THREE.Light.prototype.constructor = THREE.Light; - }, +THREE.Light.prototype.copy = function ( source ) { + + THREE.Object3D.prototype.copy.call( this, source ); + + this.color.copy( source.color ); + + return this; - parse: function (json) { +}; +// File:src/lights/AmbientLight.js - var material = new THREE[json.type]; +/** + * @author mrdoob / http://mrdoob.com/ + */ - if (json.color !== undefined) material.color.setHex(json.color); - 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.uniforms !== undefined) material.uniforms = json.uniforms; - if (json.vertexShader !== undefined) material.vertexShader = json.vertexShader; - if (json.fragmentShader !== undefined) material.fragmentShader = json.fragmentShader; - if (json.vertexColors !== undefined) material.vertexColors = json.vertexColors; - if (json.shading !== undefined) material.shading = json.shading; - 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.alphaTest !== undefined) material.alphaTest = json.alphaTest; +THREE.AmbientLight = function ( color ) { - // for PointCloudMaterial - if (json.size !== undefined) material.size = json.size; - if (json.sizeAttenuation !== undefined) material.sizeAttenuation = json.sizeAttenuation; + THREE.Light.call( this, color ); - if (json.materials !== undefined) { + this.type = 'AmbientLight'; - for (var i = 0, l = json.materials.length; i < l; i++) { +}; - material.materials.push(this.parse(json.materials[i])); +THREE.AmbientLight.prototype = Object.create( THREE.Light.prototype ); +THREE.AmbientLight.prototype.constructor = THREE.AmbientLight; - } +THREE.AmbientLight.prototype.toJSON = function ( meta ) { - } + var data = THREE.Object3D.prototype.toJSON.call( this, meta ); - return material; + data.object.color = this.color.getHex(); - } + return data; }; -// File:src/loaders/ObjectLoader.js +// File:src/lights/DirectionalLight.js /** * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ */ -THREE.ObjectLoader = function (manager) { +THREE.DirectionalLight = function ( color, intensity ) { - this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; - this.texturePath = ''; + THREE.Light.call( this, color ); -}; + this.type = 'DirectionalLight'; -THREE.ObjectLoader.prototype = { + this.position.set( 0, 1, 0 ); + this.updateMatrix(); + + this.target = new THREE.Object3D(); - constructor: THREE.ObjectLoader, + this.intensity = ( intensity !== undefined ) ? intensity : 1; - load: function (url, onLoad, onProgress, onError) { + this.castShadow = false; + this.onlyShadow = false; - if (this.texturePath === '') { + this.shadowCameraNear = 50; + this.shadowCameraFar = 5000; - this.texturePath = url.substring(0, url.lastIndexOf('/') + 1); + this.shadowCameraLeft = - 500; + this.shadowCameraRight = 500; + this.shadowCameraTop = 500; + this.shadowCameraBottom = - 500; - } + this.shadowCameraVisible = false; - var scope = this; + this.shadowBias = 0; + this.shadowDarkness = 0.5; - var loader = new THREE.XHRLoader(scope.manager); - loader.setCrossOrigin(this.crossOrigin); - loader.load(url, function (text) { + this.shadowMapWidth = 512; + this.shadowMapHeight = 512; - scope.parse(JSON.parse(text), onLoad); + this.shadowMap = null; + this.shadowMapSize = null; + this.shadowCamera = null; + this.shadowMatrix = null; - }, onProgress, onError); +}; - }, +THREE.DirectionalLight.prototype = Object.create( THREE.Light.prototype ); +THREE.DirectionalLight.prototype.constructor = THREE.DirectionalLight; - setTexturePath: function (value) { +THREE.DirectionalLight.prototype.copy = function ( source ) { - this.texturePath = value; + THREE.Light.prototype.copy.call( this, source ); - }, + this.intensity = source.intensity; + this.target = source.target.clone(); - setCrossOrigin: function (value) { + this.castShadow = source.castShadow; + this.onlyShadow = source.onlyShadow; - this.crossOrigin = value; + this.shadowCameraNear = source.shadowCameraNear; + this.shadowCameraFar = source.shadowCameraFar; - }, + this.shadowCameraLeft = source.shadowCameraLeft; + this.shadowCameraRight = source.shadowCameraRight; + this.shadowCameraTop = source.shadowCameraTop; + this.shadowCameraBottom = source.shadowCameraBottom; - parse: function (json, onLoad) { + this.shadowCameraVisible = source.shadowCameraVisible; - var geometries = this.parseGeometries(json.geometries); + this.shadowBias = source.shadowBias; + this.shadowDarkness = source.shadowDarkness; - var images = this.parseImages(json.images, function () { + this.shadowMapWidth = source.shadowMapWidth; + this.shadowMapHeight = source.shadowMapHeight; - if (onLoad !== undefined) onLoad(object); + return this; - }); - var textures = this.parseTextures(json.textures, images); - var materials = this.parseMaterials(json.materials, textures); - var object = this.parseObject(json.object, geometries, materials); +}; - if (json.images === undefined || json.images.length === 0) { +THREE.DirectionalLight.prototype.toJSON = function ( meta ) { - if (onLoad !== undefined) onLoad(object); + var data = THREE.Object3D.prototype.toJSON.call( this, meta ); - } + data.object.color = this.color.getHex(); + data.object.intensity = this.intensity; - return object; + return data; - }, +}; - parseGeometries: function (json) { +// File:src/lights/HemisphereLight.js - var geometries = {}; +/** + * @author alteredq / http://alteredqualia.com/ + */ - if (json !== undefined) { +THREE.HemisphereLight = function ( skyColor, groundColor, intensity ) { - var geometryLoader = new THREE.JSONLoader(); - var bufferGeometryLoader = new THREE.BufferGeometryLoader(); + THREE.Light.call( this, skyColor ); - for (var i = 0, l = json.length; i < l; i++) { + this.type = 'HemisphereLight'; - var geometry; - var data = json[i]; + this.position.set( 0, 1, 0 ); + this.updateMatrix(); - switch (data.type) { + this.groundColor = new THREE.Color( groundColor ); + this.intensity = ( intensity !== undefined ) ? intensity : 1; - case 'PlaneGeometry': - case 'PlaneBufferGeometry': +}; - geometry = new THREE[data.type]( - data.width, - data.height, - data.widthSegments, - data.heightSegments - ); +THREE.HemisphereLight.prototype = Object.create( THREE.Light.prototype ); +THREE.HemisphereLight.prototype.constructor = THREE.HemisphereLight; - break; +THREE.HemisphereLight.prototype.copy = function ( source ) { - case 'BoxGeometry': - case 'CubeGeometry': // backwards compatible + THREE.Light.prototype.copy.call( this, source ); - geometry = new THREE.BoxGeometry( - data.width, - data.height, - data.depth, - data.widthSegments, - data.heightSegments, - data.depthSegments - ); + this.groundColor.copy( source.groundColor ); + this.intensity = source.intensity; - break; + return this; - case 'CircleGeometry': +}; - geometry = new THREE.CircleGeometry( - data.radius, - data.segments - ); +THREE.HemisphereLight.prototype.toJSON = function ( meta ) { - break; + var data = THREE.Object3D.prototype.toJSON.call( this, meta ); - case 'CylinderGeometry': + data.object.color = this.color.getHex(); + data.object.groundColor = this.groundColor.getHex(); + data.object.intensity = this.intensity; - geometry = new THREE.CylinderGeometry( - data.radiusTop, - data.radiusBottom, - data.height, - data.radialSegments, - data.heightSegments, - data.openEnded - ); + return data; - break; +}; - case 'SphereGeometry': +// File:src/lights/PointLight.js - geometry = new THREE.SphereGeometry( - data.radius, - data.widthSegments, - data.heightSegments, - data.phiStart, - data.phiLength, - data.thetaStart, - data.thetaLength - ); +/** + * @author mrdoob / http://mrdoob.com/ + */ - break; - case 'IcosahedronGeometry': +THREE.PointLight = function ( color, intensity, distance, decay ) { - geometry = new THREE.IcosahedronGeometry( - data.radius, - data.detail - ); + THREE.Light.call( this, color ); - break; + this.type = 'PointLight'; - case 'TorusGeometry': + this.intensity = ( intensity !== undefined ) ? intensity : 1; + this.distance = ( distance !== undefined ) ? distance : 0; + this.decay = ( decay !== undefined ) ? decay : 1; // for physically correct lights, should be 2. - geometry = new THREE.TorusGeometry( - data.radius, - data.tube, - data.radialSegments, - data.tubularSegments, - data.arc - ); + this.castShadow = false; + this.onlyShadow = false; - break; + // - case 'TorusKnotGeometry': + this.shadowCameraNear = 1; + this.shadowCameraFar = 500; + this.shadowCameraFov = 90; - geometry = new THREE.TorusKnotGeometry( - data.radius, - data.tube, - data.radialSegments, - data.tubularSegments, - data.p, - data.q, - data.heightScale - ); + this.shadowCameraVisible = false; - break; + this.shadowBias = 0; + this.shadowDarkness = 0.5; - case 'BufferGeometry': + this.shadowMapWidth = 512; + this.shadowMapHeight = 512; - geometry = bufferGeometryLoader.parse(data); + // - break; + this.shadowMap = null; + this.shadowMapSize = null; + this.shadowCamera = null; + this.shadowMatrix = null; - case 'Geometry': +}; - geometry = geometryLoader.parse(data.data).geometry; +THREE.PointLight.prototype = Object.create( THREE.Light.prototype ); +THREE.PointLight.prototype.constructor = THREE.PointLight; - break; +THREE.PointLight.prototype.copy = function ( source ) { - case 'TextGeometry': + THREE.Light.prototype.copy.call( this, source ); - geometry = new THREE.TextGeometry( - data.text, - data.data - ); + this.intensity = source.intensity; + this.distance = source.distance; + this.decay = source.decay; - break; + this.castShadow = source.castShadow; + this.onlyShadow = source.onlyShadow; - } + this.shadowCameraNear = source.shadowCameraNear; + this.shadowCameraFar = source.shadowCameraFar; + this.shadowCameraFov = source.shadowCameraFov; - geometry.uuid = data.uuid; + this.shadowCameraVisible = source.shadowCameraVisible; - if (data.name !== undefined) geometry.name = data.name; + this.shadowBias = source.shadowBias; + this.shadowDarkness = source.shadowDarkness; - geometries[data.uuid] = geometry; + this.shadowMapWidth = source.shadowMapWidth; + this.shadowMapHeight = source.shadowMapHeight; - } + return this; - } +}; - return geometries; +THREE.PointLight.prototype.toJSON = function ( meta ) { - }, + var data = THREE.Object3D.prototype.toJSON.call( this, meta ); - parseMaterials: function (json, textures) { + data.object.color = this.color.getHex(); + data.object.intensity = this.intensity; + data.object.distance = this.distance; + data.object.decay = this.decay; - var materials = {}; + return data; - if (json !== undefined) { +}; - var getTexture = function (name) { +// File:src/lights/SpotLight.js - if (textures[name] === undefined) { +/** + * @author alteredq / http://alteredqualia.com/ + */ - THREE.warn('THREE.ObjectLoader: Undefined texture', name); +THREE.SpotLight = function ( color, intensity, distance, angle, exponent, decay ) { - } + THREE.Light.call( this, color ); - return textures[name]; + this.type = 'SpotLight'; - }; + this.position.set( 0, 1, 0 ); + this.updateMatrix(); - var loader = new THREE.MaterialLoader(); + this.target = new THREE.Object3D(); - for (var i = 0, l = json.length; i < l; i++) { + 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.decay = ( decay !== undefined ) ? decay : 1; // for physically correct lights, should be 2. - var data = json[i]; - var material = loader.parse(data); + this.castShadow = false; + this.onlyShadow = false; - material.uuid = data.uuid; + this.shadowCameraNear = 50; + this.shadowCameraFar = 5000; + this.shadowCameraFov = 50; - if (data.name !== undefined) material.name = data.name; + this.shadowCameraVisible = false; - if (data.map !== undefined) { + this.shadowBias = 0; + this.shadowDarkness = 0.5; - material.map = getTexture(data.map); + this.shadowMapWidth = 512; + this.shadowMapHeight = 512; - } + this.shadowMap = null; + this.shadowMapSize = null; + this.shadowCamera = null; + this.shadowMatrix = null; - if (data.bumpMap !== undefined) { +}; - material.bumpMap = getTexture(data.bumpMap); - if (data.bumpScale !== undefined) { - material.bumpScale = data.bumpScale; - } +THREE.SpotLight.prototype = Object.create( THREE.Light.prototype ); +THREE.SpotLight.prototype.constructor = THREE.SpotLight; - } +THREE.SpotLight.prototype.copy = function ( source ) { - if (data.alphaMap !== undefined) { + THREE.Light.prototype.copy.call( this, source ); - material.alphaMap = getTexture(data.alphaMap); + this.intensity = source.intensity; + this.distance = source.distance; + this.angle = source.angle; + this.exponent = source.exponent; + this.decay = source.decay; - } + this.target = source.target.clone(); - if (data.envMap !== undefined) { + this.castShadow = source.castShadow; + this.onlyShadow = source.onlyShadow; - material.envMap = getTexture(data.envMap); + this.shadowCameraNear = source.shadowCameraNear; + this.shadowCameraFar = source.shadowCameraFar; + this.shadowCameraFov = source.shadowCameraFov; - } + this.shadowCameraVisible = source.shadowCameraVisible; - if (data.normalMap !== undefined) { + this.shadowBias = source.shadowBias; + this.shadowDarkness = source.shadowDarkness; - material.normalMap = getTexture(data.normalMap); - if (data.normalScale !== undefined) { - material.normalScale = new THREE.Vector2(data.normalScale, data.normalScale); - } + this.shadowMapWidth = source.shadowMapWidth; + this.shadowMapHeight = source.shadowMapHeight; - } + return this; +} - if (data.lightMap !== undefined) { +THREE.SpotLight.prototype.toJSON = function ( meta ) { - material.lightMap = getTexture(data.lightMap); + var data = THREE.Object3D.prototype.toJSON.call( this, meta ); - if (data.lightMapIntensity !== undefined) { - material.lightMapIntensity = data.lightMapIntensity; - } + data.object.color = this.color.getHex(); + data.object.intensity = this.intensity; + data.object.distance = this.distance; + data.object.angle = this.angle; + data.object.exponent = this.exponent; + data.object.decay = this.decay; - } + return data; - if (data.aoMap !== undefined) { +}; - material.aoMap = getTexture(data.aoMap); - if (data.aoMapIntensity !== undefined) { - material.aoMapIntensity = data.aoMapIntensity; - } - } +// File:src/loaders/Cache.js - if (data.specularMap !== undefined) { +/** + * @author mrdoob / http://mrdoob.com/ + */ - material.specularMap = getTexture(data.specularMap); +THREE.Cache = { - } + enabled: false, - materials[data.uuid] = material; + files: {}, - } + add: function ( key, file ) { - } + if ( this.enabled === false ) return; - return materials; + // console.log( 'THREE.Cache', 'Adding key:', key ); - }, + this.files[ key ] = file; - parseImages: function (json, onLoad) { + }, - var scope = this; - var images = {}; + get: function ( key ) { - if (json !== undefined && json.length > 0) { + if ( this.enabled === false ) return; - var manager = new THREE.LoadingManager(onLoad); + // console.log( 'THREE.Cache', 'Checking key:', key ); - var loader = new THREE.ImageLoader(manager); - loader.setCrossOrigin(this.crossOrigin); + return this.files[ key ]; - var loadImage = function (url) { + }, - scope.manager.itemStart(url); + remove: function ( key ) { - return loader.load(url, function () { + delete this.files[ key ]; - scope.manager.itemEnd(url); + }, - }); + clear: function () { - }; + this.files = {}; - for (var i = 0, l = json.length; i < l; i++) { + } - var image = json[i]; - var path = /^(\/\/)|([a-z]+:(\/\/)?)/i.test(image.url) ? image.url : scope.texturePath + image.url; +}; - images[image.uuid] = loadImage(path); +// File:src/loaders/Loader.js - } +/** + * @author alteredq / http://alteredqualia.com/ + */ - } +THREE.Loader = function () { - return images; + this.onLoadStart = function () {}; + this.onLoadProgress = function () {}; + this.onLoadComplete = function () {}; - }, +}; - parseTextures: function (json, images) { +THREE.Loader.prototype = { - var textures = {}; + constructor: THREE.Loader, - if (json !== undefined) { + crossOrigin: undefined, - for (var i = 0, l = json.length; i < l; i++) { + extractUrlBase: function ( url ) { - var data = json[i]; + var parts = url.split( '/' ); - if (data.image === undefined) { + if ( parts.length === 1 ) return './'; - THREE.warn('THREE.ObjectLoader: No "image" speficied for', data.uuid); + parts.pop(); - } + return parts.join( '/' ) + '/'; - if (images[data.image] === undefined) { + }, - THREE.warn('THREE.ObjectLoader: Undefined image', data.image); + initMaterials: function ( materials, texturePath, crossOrigin ) { - } + var array = []; - var texture = new THREE.Texture(images[data.image]); - texture.needsUpdate = true; + for ( var i = 0; i < materials.length; ++ i ) { - texture.uuid = data.uuid; + array[ i ] = this.createMaterial( materials[ i ], texturePath, crossOrigin ); - if (data.name !== undefined) texture.name = data.name; - if (data.repeat !== undefined) texture.repeat = new THREE.Vector2(data.repeat[0], data.repeat[1]); - if (data.minFilter !== undefined) texture.minFilter = THREE[data.minFilter]; - if (data.magFilter !== undefined) texture.magFilter = THREE[data.magFilter]; - if (data.anisotropy !== undefined) texture.anisotropy = data.anisotropy; - if (data.wrap instanceof Array) { + } - texture.wrapS = THREE[data.wrap[0]]; - texture.wrapT = THREE[data.wrap[1]]; + return array; - } + }, - textures[data.uuid] = texture; + createMaterial: ( function () { - } + var imageLoader; - } + return function createMaterial( m, texturePath, crossOrigin ) { - return textures; + var scope = this; - }, + if ( crossOrigin === undefined && scope.crossOrigin !== undefined ) crossOrigin = scope.crossOrigin; - parseObject: function () { + if ( imageLoader === undefined ) imageLoader = new THREE.ImageLoader(); - var matrix = new THREE.Matrix4(); + function nearest_pow2( n ) { - return function (data, geometries, materials) { + var l = Math.log( n ) / Math.LN2; + return Math.pow( 2, Math.round( l ) ); - var object; + } - var getGeometry = function (name) { + function create_texture( where, name, sourceFile, repeat, offset, wrap, anisotropy ) { - if (geometries[name] === undefined) { + var fullPath = texturePath + sourceFile; - THREE.warn('THREE.ObjectLoader: Undefined geometry', name); + var texture; - } + var loader = THREE.Loader.Handlers.get( fullPath ); - return geometries[name]; + if ( loader !== null ) { - }; + texture = loader.load( fullPath ); - var getMaterial = function (name) { + } else { - if (materials[name] === undefined) { + texture = new THREE.Texture(); - THREE.warn('THREE.ObjectLoader: Undefined material', name); + loader = imageLoader; + loader.setCrossOrigin( crossOrigin ); + loader.load( fullPath, function ( image ) { - } + if ( THREE.Math.isPowerOfTwo( image.width ) === false || + THREE.Math.isPowerOfTwo( image.height ) === false ) { - return materials[name]; + var width = nearest_pow2( image.width ); + var height = nearest_pow2( image.height ); - }; + var canvas = document.createElement( 'canvas' ); + canvas.width = width; + canvas.height = height; - switch (data.type) { + var context = canvas.getContext( '2d' ); + context.drawImage( image, 0, 0, width, height ); - case 'Scene': + texture.image = canvas; - object = new THREE.Scene(); + } else { - break; + texture.image = image; - case 'PerspectiveCamera': + } - object = new THREE.PerspectiveCamera(data.fov, data.aspect, data.near, data.far); + texture.needsUpdate = true; - break; + } ); - case 'OrthographicCamera': + } - object = new THREE.OrthographicCamera(data.left, data.right, data.top, data.bottom, data.near, data.far); + texture.sourceFile = sourceFile; - break; + if ( repeat ) { - case 'AmbientLight': + texture.repeat.set( repeat[ 0 ], repeat[ 1 ] ); - object = new THREE.AmbientLight(data.color); + if ( repeat[ 0 ] !== 1 ) texture.wrapS = THREE.RepeatWrapping; + if ( repeat[ 1 ] !== 1 ) texture.wrapT = THREE.RepeatWrapping; - break; + } - case 'DirectionalLight': + if ( offset ) { - object = new THREE.DirectionalLight(data.color, data.intensity); + texture.offset.set( offset[ 0 ], offset[ 1 ] ); - break; + } - case 'PointLight': + if ( wrap ) { - object = new THREE.PointLight(data.color, data.intensity, data.distance, data.decay); + var wrapMap = { + 'repeat': THREE.RepeatWrapping, + 'mirror': THREE.MirroredRepeatWrapping + }; - break; + if ( wrapMap[ wrap[ 0 ] ] !== undefined ) texture.wrapS = wrapMap[ wrap[ 0 ] ]; + if ( wrapMap[ wrap[ 1 ] ] !== undefined ) texture.wrapT = wrapMap[ wrap[ 1 ] ]; - case 'SpotLight': + } - object = new THREE.SpotLight(data.color, data.intensity, data.distance, data.angle, data.exponent, data.decay); + if ( anisotropy ) { - break; + texture.anisotropy = anisotropy; - case 'HemisphereLight': + } - object = new THREE.HemisphereLight(data.color, data.groundColor, data.intensity); + where[ name ] = texture; - break; + } - case 'Mesh': + function rgb2hex( rgb ) { - object = new THREE.Mesh(getGeometry(data.geometry), getMaterial(data.material)); + return ( rgb[ 0 ] * 255 << 16 ) + ( rgb[ 1 ] * 255 << 8 ) + rgb[ 2 ] * 255; - break; + } - case 'Line': + // defaults - object = new THREE.Line(getGeometry(data.geometry), getMaterial(data.material), data.mode); + var mtype = 'MeshLambertMaterial'; + var mpars = {}; - break; + // parameters from model file - case 'PointCloud': + if ( m.shading ) { - object = new THREE.PointCloud(getGeometry(data.geometry), getMaterial(data.material)); + var shading = m.shading.toLowerCase(); - break; + if ( shading === 'phong' ) mtype = 'MeshPhongMaterial'; + else if ( shading === 'basic' ) mtype = 'MeshBasicMaterial'; - case 'Sprite': + } - object = new THREE.Sprite(getMaterial(data.material)); + if ( m.blending !== undefined && THREE[ m.blending ] !== undefined ) { - break; + mpars.blending = THREE[ m.blending ]; - case 'Group': + } - object = new THREE.Group(); + if ( m.transparent !== undefined ) { - break; + mpars.transparent = m.transparent; - default: + } - object = new THREE.Object3D(); + if ( m.opacity !== undefined && m.opacity < 1.0 ) { - } + mpars.transparent = true; - object.uuid = data.uuid; + } - if (data.name !== undefined) object.name = data.name; - if (data.matrix !== undefined) { + if ( m.depthTest !== undefined ) { - matrix.fromArray(data.matrix); - matrix.decompose(object.position, object.quaternion, object.scale); + mpars.depthTest = m.depthTest; - } 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 ( m.depthWrite !== undefined ) { - } + mpars.depthWrite = m.depthWrite; - if (data.castShadow !== undefined) object.castShadow = data.castShadow; - if (data.receiveShadow !== undefined) object.receiveShadow = data.receiveShadow; + } - if (data.visible !== undefined) object.visible = data.visible; - if (data.userData !== undefined) object.userData = data.userData; + if ( m.visible !== undefined ) { - if (data.children !== undefined) { + mpars.visible = m.visible; - for (var child in data.children) { + } - object.add(this.parseObject(data.children[child], geometries, materials)); + if ( m.flipSided !== undefined ) { - } + mpars.side = THREE.BackSide; - } + } - return object; + if ( m.doubleSided !== undefined ) { - } + mpars.side = THREE.DoubleSide; - }() + } -}; + if ( m.wireframe !== undefined ) { -// File:src/loaders/TextureLoader.js + mpars.wireframe = m.wireframe; -/** - * @author mrdoob / http://mrdoob.com/ - */ + } -THREE.TextureLoader = function (manager) { + if ( m.vertexColors !== undefined ) { - this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + if ( m.vertexColors === 'face' ) { -}; + mpars.vertexColors = THREE.FaceColors; -THREE.TextureLoader.prototype = { + } else if ( m.vertexColors ) { - constructor: THREE.TextureLoader, + mpars.vertexColors = THREE.VertexColors; - 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) { + // colors - var texture = new THREE.Texture(image); - texture.needsUpdate = true; + if ( m.colorDiffuse ) { - if (onLoad !== undefined) { + mpars.color = rgb2hex( m.colorDiffuse ); - onLoad(texture); + } else if ( m.DbgColor ) { - } + mpars.color = m.DbgColor; - }, onProgress, onError); + } - }, + if ( m.colorEmissive ) { - setCrossOrigin: function (value) { + mpars.emissive = rgb2hex( m.colorEmissive ); - this.crossOrigin = value; + } - } + if ( mtype === 'MeshPhongMaterial' ) { -}; + if ( m.colorSpecular ) { -// File:src/loaders/BinaryTextureLoader.js + mpars.specular = rgb2hex( m.colorSpecular ); -/** - * @author Nikos M. / https://github.com/foo123/ - * - * Abstract Base class to load generic binary textures formats (rgbe, hdr, ...) - */ + } -THREE.DataTextureLoader = THREE.BinaryTextureLoader = function () { + if ( m.specularCoef ) { - // override in sub classes - this._parser = null; + mpars.shininess = m.specularCoef; -}; + } -THREE.BinaryTextureLoader.prototype = { + } - constructor: THREE.BinaryTextureLoader, + // modifiers - load: function (url, onLoad, onProgress, onError) { + if ( m.transparency !== undefined ) { - var scope = this; + console.warn( 'THREE.Loader: transparency has been renamed to opacity' ); + m.opacity = m.transparency; - var texture = new THREE.DataTexture(); + } - var loader = new THREE.XHRLoader(); - loader.setResponseType('arraybuffer'); + if ( m.opacity !== undefined ) { - loader.load(url, function (buffer) { + mpars.opacity = m.opacity; - var texData = scope._parser(buffer); + } - if (!texData) return; + // textures - if (undefined !== texData.image) { + if ( texturePath ) { - texture.image = texData.image; + if ( m.mapDiffuse ) { - } else if (undefined !== texData.data) { + create_texture( mpars, 'map', m.mapDiffuse, m.mapDiffuseRepeat, m.mapDiffuseOffset, m.mapDiffuseWrap, m.mapDiffuseAnisotropy ); - texture.image.width = texData.width; - texture.image.height = texData.height; - texture.image.data = texData.data; + } - } + if ( m.mapLight ) { - texture.wrapS = undefined !== texData.wrapS ? texData.wrapS : THREE.ClampToEdgeWrapping; - texture.wrapT = undefined !== texData.wrapT ? texData.wrapT : THREE.ClampToEdgeWrapping; + create_texture( mpars, 'lightMap', m.mapLight, m.mapLightRepeat, m.mapLightOffset, m.mapLightWrap, m.mapLightAnisotropy ); - texture.magFilter = undefined !== texData.magFilter ? texData.magFilter : THREE.LinearFilter; - texture.minFilter = undefined !== texData.minFilter ? texData.minFilter : THREE.LinearMipMapLinearFilter; + } - texture.anisotropy = undefined !== texData.anisotropy ? texData.anisotropy : 1; + if ( m.mapAO ) { - if (undefined !== texData.format) { + create_texture( mpars, 'aoMap', m.mapAO, m.mapAORepeat, m.mapAOOffset, m.mapAOWrap, m.mapAOAnisotropy ); - texture.format = texData.format; + } - } - if (undefined !== texData.type) { + if ( m.mapBump ) { - texture.type = texData.type; + create_texture( mpars, 'bumpMap', m.mapBump, m.mapBumpRepeat, m.mapBumpOffset, m.mapBumpWrap, m.mapBumpAnisotropy ); - } + } - if (undefined !== texData.mipmaps) { + if ( m.mapNormal ) { - texture.mipmaps = texData.mipmaps; + create_texture( mpars, 'normalMap', m.mapNormal, m.mapNormalRepeat, m.mapNormalOffset, m.mapNormalWrap, m.mapNormalAnisotropy ); - } + } - if (1 === texData.mipmapCount) { + if ( m.mapSpecular ) { - texture.minFilter = THREE.LinearFilter; + create_texture( mpars, 'specularMap', m.mapSpecular, m.mapSpecularRepeat, m.mapSpecularOffset, m.mapSpecularWrap, m.mapSpecularAnisotropy ); - } + } - texture.needsUpdate = true; + if ( m.mapAlpha ) { - if (onLoad) onLoad(texture, texData); + create_texture( mpars, 'alphaMap', m.mapAlpha, m.mapAlphaRepeat, m.mapAlphaOffset, m.mapAlphaWrap, m.mapAlphaAnisotropy ); - }, onProgress, onError); + } + } - return texture; + // - } + if ( m.mapBumpScale ) { -}; + mpars.bumpScale = m.mapBumpScale; -// File:src/loaders/CompressedTextureLoader.js + } -/** - * @author mrdoob / http://mrdoob.com/ - * - * Abstract Base class to block based textures loader (dds, pvr, ...) - */ + if ( m.mapNormalFactor ) { -THREE.CompressedTextureLoader = function () { + mpars.normalScale = new THREE.Vector2( m.mapNormalFactor, m.mapNormalFactor ); - // override in sub classes - this._parser = null; + } -}; + var material = new THREE[ mtype ]( mpars ); + if ( m.DbgName !== undefined ) material.name = m.DbgName; -THREE.CompressedTextureLoader.prototype = { + return material; - constructor: THREE.CompressedTextureLoader, + }; - load: function (url, onLoad, onError) { + } )() - var scope = this; +}; - var images = []; +THREE.Loader.Handlers = { - var texture = new THREE.CompressedTexture(); - texture.image = images; + handlers: [], - var loader = new THREE.XHRLoader(); - loader.setResponseType('arraybuffer'); + add: function ( regex, loader ) { - if (url instanceof Array) { + this.handlers.push( regex, loader ); - var loaded = 0; + }, - var loadTexture = function (i) { + get: function ( file ) { - loader.load(url[i], function (buffer) { + for ( var i = 0, l = this.handlers.length; i < l; i += 2 ) { - var texDatas = scope._parser(buffer, true); + var regex = this.handlers[ i ]; + var loader = this.handlers[ i + 1 ]; - images[i] = { - width: texDatas.width, - height: texDatas.height, - format: texDatas.format, - mipmaps: texDatas.mipmaps - }; + if ( regex.test( file ) ) { - loaded += 1; + return loader; - if (loaded === 6) { + } - if (texDatas.mipmapCount == 1) - texture.minFilter = THREE.LinearFilter; + } - texture.format = texDatas.format; - texture.needsUpdate = true; + return null; - if (onLoad) onLoad(texture); + } - } +}; - }); +// File:src/loaders/XHRLoader.js - }; +/** + * @author mrdoob / http://mrdoob.com/ + */ - for (var i = 0, il = url.length; i < il; ++i) { +THREE.XHRLoader = function ( manager ) { - loadTexture(i); + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; - } +}; - } else { +THREE.XHRLoader.prototype = { - // compressed cubemap texture stored in a single DDS file + constructor: THREE.XHRLoader, - loader.load(url, function (buffer) { + load: function ( url, onLoad, onProgress, onError ) { - var texDatas = scope._parser(buffer, true); + var scope = this; - if (texDatas.isCubemap) { + var cached = THREE.Cache.get( url ); - var faces = texDatas.mipmaps.length / texDatas.mipmapCount; + if ( cached !== undefined ) { - for (var f = 0; f < faces; f++) { + if ( onLoad ) { - images[f] = {mipmaps: []}; + setTimeout( function () { - for (var i = 0; i < texDatas.mipmapCount; i++) { + onLoad( cached ); - images[f].mipmaps.push(texDatas.mipmaps[f * texDatas.mipmapCount + i]); - images[f].format = texDatas.format; - images[f].width = texDatas.width; - images[f].height = texDatas.height; + }, 0 ); - } + } - } + return cached; - } else { + } - texture.image.width = texDatas.width; - texture.image.height = texDatas.height; - texture.mipmaps = texDatas.mipmaps; + var request = new XMLHttpRequest(); + request.open( 'GET', url, true ); - } + request.addEventListener( 'load', function ( event ) { - if (texDatas.mipmapCount === 1) { + THREE.Cache.add( url, this.response ); - texture.minFilter = THREE.LinearFilter; + if ( onLoad ) onLoad( this.response ); - } + scope.manager.itemEnd( url ); - texture.format = texDatas.format; - texture.needsUpdate = true; + }, false ); - if (onLoad) onLoad(texture); + if ( onProgress !== undefined ) { - }); + request.addEventListener( 'progress', function ( event ) { - } + onProgress( event ); - return texture; + }, false ); - } + } -}; + request.addEventListener( 'error', function ( event ) { -// File:src/materials/Material.js + if ( onError ) onError( event ); -/** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - */ + scope.manager.itemError( url ); -THREE.Material = function () { + }, false ); - Object.defineProperty(this, 'id', {value: THREE.MaterialIdCount++}); + if ( this.crossOrigin !== undefined ) request.crossOrigin = this.crossOrigin; + if ( this.responseType !== undefined ) request.responseType = this.responseType; + if ( this.withCredentials !== undefined ) request.withCredentials = this.withCredentials; - this.uuid = THREE.Math.generateUUID(); + request.send( null ); - this.name = ''; - this.type = 'Material'; + scope.manager.itemStart( url ); - this.side = THREE.FrontSide; + return request; - this.opacity = 1; - this.transparent = false; + }, - this.blending = THREE.NormalBlending; + setResponseType: function ( value ) { - this.blendSrc = THREE.SrcAlphaFactor; - this.blendDst = THREE.OneMinusSrcAlphaFactor; - this.blendEquation = THREE.AddEquation; - this.blendSrcAlpha = null; - this.blendDstAlpha = null; - this.blendEquationAlpha = null; + this.responseType = value; - this.depthFunc = THREE.LessEqualDepth; - this.depthTest = true; - this.depthWrite = true; + }, - this.colorWrite = true; + setCrossOrigin: function ( value ) { - this.polygonOffset = false; - this.polygonOffsetFactor = 0; - this.polygonOffsetUnits = 0; + this.crossOrigin = value; - this.alphaTest = 0; + }, - this.overdraw = 0; // Overdrawn pixels (typically between 0 and 1) for fixing antialiasing gaps in CanvasRenderer + setWithCredentials: function ( value ) { - this.visible = true; + this.withCredentials = value; - this._needsUpdate = true; + } }; -THREE.Material.prototype = { +// File:src/loaders/ImageLoader.js - constructor: THREE.Material, +/** + * @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 cached = THREE.Cache.get( url ); + + if ( cached !== undefined ) { + + if ( onLoad ) { + + setTimeout( function () { + + onLoad( cached ); + + }, 0 ); + + } + + return cached; + + } + + var image = document.createElement( 'img' ); + + image.addEventListener( 'load', function ( event ) { + + THREE.Cache.add( url, this ); + + if ( onLoad ) onLoad( this ); + + scope.manager.itemEnd( url ); + + }, false ); + + if ( onProgress !== undefined ) { + + image.addEventListener( 'progress', function ( event ) { + + onProgress( event ); + + }, false ); + + } + + image.addEventListener( 'error', function ( event ) { + + if ( onError ) onError( event ); + + scope.manager.itemError( url ); + + }, false ); + + if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin; + + scope.manager.itemStart( url ); + + image.src = url; + + return image; + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + } + +}; + +// File:src/loaders/JSONLoader.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.JSONLoader = function ( manager ) { + + if ( typeof manager === 'boolean' ) { + + console.warn( 'THREE.JSONLoader: showStatus parameter has been removed from constructor.' ); + manager = undefined; + + } + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + + this.withCredentials = false; + +}; + +THREE.JSONLoader.prototype = { + + constructor: THREE.JSONLoader, + + // Deprecated + + get statusDomElement () { + + if ( this._statusDomElement === undefined ) { + + this._statusDomElement = document.createElement( 'div' ); + + } + + console.warn( 'THREE.JSONLoader: .statusDomElement has been removed.' ); + return this._statusDomElement; + + }, + + load: function( url, onLoad, onProgress, onError ) { + + var scope = this; + + var texturePath = this.texturePath && ( typeof this.texturePath === "string" ) ? this.texturePath : THREE.Loader.prototype.extractUrlBase( url ); + + var loader = new THREE.XHRLoader( this.manager ); + loader.setCrossOrigin( this.crossOrigin ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, function ( text ) { + + var json = JSON.parse( text ); + var metadata = json.metadata; + + if ( metadata !== undefined ) { + + if ( metadata.type === 'object' ) { + + console.error( 'THREE.JSONLoader: ' + url + ' should be loaded with THREE.ObjectLoader instead.' ); + return; + + } + + if ( metadata.type === 'scene' ) { + + console.error( 'THREE.JSONLoader: ' + url + ' should be loaded with THREE.SceneLoader instead.' ); + return; + + } + + } + + var object = scope.parse( json, texturePath ); + onLoad( object.geometry, object.materials ); + + } ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + }, + + setTexturePath: function ( value ) { + + this.texturePath = value; + + }, + + parse: function ( json, texturePath ) { + + var geometry = new THREE.Geometry(), + scale = ( json.scale !== undefined ) ? 1.0 / json.scale : 1.0; + + parseModel( scale ); + + parseSkin(); + parseMorphing( scale ); + parseAnimations(); + + 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, 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() { + + var influencesPerVertex = ( json.influencesPerVertex !== undefined ) ? json.influencesPerVertex : 2; + + if ( json.skinWeights ) { + + for ( var i = 0, l = json.skinWeights.length; i < l; i += influencesPerVertex ) { + + var x = json.skinWeights[ i ]; + var y = ( influencesPerVertex > 1 ) ? json.skinWeights[ i + 1 ] : 0; + var z = ( influencesPerVertex > 2 ) ? json.skinWeights[ i + 2 ] : 0; + var w = ( influencesPerVertex > 3 ) ? json.skinWeights[ i + 3 ] : 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 += influencesPerVertex ) { + + var a = json.skinIndices[ i ]; + var b = ( influencesPerVertex > 1 ) ? json.skinIndices[ i + 1 ] : 0; + var c = ( influencesPerVertex > 2 ) ? json.skinIndices[ i + 2 ] : 0; + var d = ( influencesPerVertex > 3 ) ? json.skinIndices[ i + 3 ] : 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.' ); + + } + + }; + + 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 ); + + } + + } + + } + } + + function parseAnimations() { + + var outputAnimations = []; + + // parse old style Bone/Hierarchy animations + var animations = []; + if( json.animation !== undefined ) { + animations.push( json.animation ); + } + if( json.animations !== undefined ) { + if( json.animations.length ) { + animations = animations.concat( json.animations ); + } + else { + animations.push( json.animations ); + } + } + + for( var i = 0; i < animations.length; i ++ ) { + + var clip = THREE.AnimationClip.parseAnimation( animations[i], geometry.bones ); + if( clip ) outputAnimations.push( clip ); + + } + + // parse implicit morph animations + if( geometry.morphTargets ) { + + // TODO: Figure out what an appropraite FPS is for morph target animations -- defaulting to 10, but really it is completely arbitrary. + var morphAnimationClips = THREE.AnimationClip.CreateClipsFromMorphTargetSequences( geometry.morphTargets, 10 ); + outputAnimations = outputAnimations.concat( morphAnimationClips ); + + } + + if( outputAnimations.length > 0 ) geometry.animations = outputAnimations; + + }; + + if ( json.materials === undefined || json.materials.length === 0 ) { + + return { geometry: geometry }; + + } else { + + var materials = THREE.Loader.prototype.initMaterials( json.materials, texturePath, this.crossOrigin ); + + return { geometry: geometry, materials: materials }; + + } + + } + +}; + +// File:src/loaders/LoadingManager.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.LoadingManager = function ( onLoad, onProgress, onError ) { + + var scope = this; + + var isLoading = false, itemsLoaded = 0, itemsTotal = 0; + + this.onStart = undefined; + this.onLoad = onLoad; + this.onProgress = onProgress; + this.onError = onError; + + this.itemStart = function ( url ) { + + itemsTotal ++; + + if ( isLoading === false ) { + + if ( scope.onStart !== undefined ) { + + scope.onStart( url, itemsLoaded, itemsTotal ); + + } + + } + + isLoading = true; + + }; + + this.itemEnd = function ( url ) { + + itemsLoaded ++; + + if ( scope.onProgress !== undefined ) { + + scope.onProgress( url, itemsLoaded, itemsTotal ); + + } + + if ( itemsLoaded === itemsTotal ) { + + isLoading = false; + + if ( scope.onLoad !== undefined ) { + + scope.onLoad(); + + } + + } + + }; + + this.itemError = function ( url ) { + + if ( scope.onError !== undefined ) { + + scope.onError( url ); + + } + + }; + +}; + +THREE.DefaultLoadingManager = new THREE.LoadingManager(); + +// File:src/loaders/BufferGeometryLoader.js + +/** + * @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( scope.manager ); + loader.setCrossOrigin( this.crossOrigin ); + loader.load( url, function ( text ) { + + onLoad( scope.parse( JSON.parse( text ) ) ); + + }, onProgress, onError ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + }, + + parse: function ( json ) { + + var geometry = new THREE.BufferGeometry(); + + var index = json.data.index; + + if ( index !== undefined ) { + + var typedArray = new self[ index.type ]( index.array ); + geometry.setIndex( new THREE.BufferAttribute( typedArray, 1 ) ); + + } + + var attributes = json.data.attributes; + + for ( var key in attributes ) { + + var attribute = attributes[ key ]; + var typedArray = new self[ attribute.type ]( attribute.array ); + + geometry.addAttribute( key, new THREE.BufferAttribute( typedArray, attribute.itemSize ) ); + + } + + var groups = json.data.groups || json.data.drawcalls || json.data.offsets; + + if ( groups !== undefined ) { + + for ( var i = 0, n = groups.length; i !== n; ++ i ) { + + var group = groups[ i ]; + + geometry.addGroup( group.start, group.count ); + + } + + } + + var boundingSphere = json.data.boundingSphere; + + if ( boundingSphere !== undefined ) { + + var center = new THREE.Vector3(); + + if ( boundingSphere.center !== undefined ) { + + center.fromArray( boundingSphere.center ); + + } + + geometry.boundingSphere = new THREE.Sphere( center, boundingSphere.radius ); + + } + + return geometry; + + } + +}; + +// File:src/loaders/MaterialLoader.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.MaterialLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + this.textures = {}; + +}; + +THREE.MaterialLoader.prototype = { + + constructor: THREE.MaterialLoader, + + 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 ) ) ); + + }, onProgress, onError ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + }, + + setTextures: function ( value ) { + + this.textures = value; + + }, + + getTexture: function ( name ) { + + var textures = this.textures; + + if ( textures[ name ] === undefined ) { + + console.warn( 'THREE.MaterialLoader: Undefined texture', name ); + + } + + return textures[ name ]; + + }, + + parse: function ( json ) { + + var material = new THREE[ json.type ]; + material.uuid = json.uuid; + + if ( json.name !== undefined ) material.name = json.name; + if ( json.color !== undefined ) material.color.setHex( json.color ); + 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.uniforms !== undefined ) material.uniforms = json.uniforms; + if ( json.vertexShader !== undefined ) material.vertexShader = json.vertexShader; + if ( json.fragmentShader !== undefined ) material.fragmentShader = json.fragmentShader; + if ( json.vertexColors !== undefined ) material.vertexColors = json.vertexColors; + if ( json.shading !== undefined ) material.shading = json.shading; + 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.alphaTest !== undefined ) material.alphaTest = json.alphaTest; + if ( json.depthTest !== undefined ) material.depthTest = json.depthTest; + if ( json.depthWrite !== undefined ) material.depthWrite = json.depthWrite; + if ( json.wireframe !== undefined ) material.wireframe = json.wireframe; + if ( json.wireframeLinewidth !== undefined ) material.wireframeLinewidth = json.wireframeLinewidth; + + // for PointsMaterial + if ( json.size !== undefined ) material.size = json.size; + if ( json.sizeAttenuation !== undefined ) material.sizeAttenuation = json.sizeAttenuation; + + // maps + + if ( json.map !== undefined ) material.map = this.getTexture( json.map ); + + if ( json.alphaMap !== undefined ) { + + material.alphaMap = this.getTexture( json.alphaMap ); + material.transparent = true; + + } + + if ( json.bumpMap !== undefined ) material.bumpMap = this.getTexture( json.bumpMap ); + if ( json.bumpScale !== undefined ) material.bumpScale = json.bumpScale; + + if ( json.normalMap !== undefined ) material.normalMap = this.getTexture( json.normalMap ); + if ( json.normalScale ) material.normalScale = new THREE.Vector2( json.normalScale, json.normalScale ); + + if ( json.displacementMap !== undefined ) material.displacementMap = this.getTexture( json.displacementMap ); + if ( json.displacementScale !== undefined ) material.displacementScale = json.displacementScale; + if ( json.displacementBias !== undefined ) material.displacementBias = json.displacementBias; + + if ( json.specularMap !== undefined ) material.specularMap = this.getTexture( json.specularMap ); + + if ( json.envMap !== undefined ) { + + material.envMap = this.getTexture( json.envMap ); + material.combine = THREE.MultiplyOperation; + + } + + if ( json.reflectivity ) material.reflectivity = json.reflectivity; + + if ( json.lightMap !== undefined ) material.lightMap = this.getTexture( json.lightMap ); + if ( json.lightMapIntensity !== undefined ) material.lightMapIntensity = json.lightMapIntensity; + + if ( json.aoMap !== undefined ) material.aoMap = this.getTexture( json.aoMap ); + if ( json.aoMapIntensity !== undefined ) material.aoMapIntensity = json.aoMapIntensity; + + // MeshFaceMaterial + + 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; + + } + +}; + +// File:src/loaders/ObjectLoader.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.ObjectLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + this.texturePath = ''; + +}; + +THREE.ObjectLoader.prototype = { + + constructor: THREE.ObjectLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + if ( this.texturePath === '' ) { + + this.texturePath = url.substring( 0, url.lastIndexOf( '/' ) + 1 ); + + } + + 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 ); + + }, onProgress, onError ); + + }, + + setTexturePath: function ( value ) { + + this.texturePath = value; + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + }, + + parse: function ( json, onLoad ) { + + var geometries = this.parseGeometries( json.geometries ); + + var images = this.parseImages( json.images, function () { + + if ( onLoad !== undefined ) onLoad( object ); + + } ); + + var textures = this.parseTextures( json.textures, images ); + var materials = this.parseMaterials( json.materials, textures ); + + var object = this.parseObject( json.object, geometries, materials ); + + if( json.animations ) { + + object.animations = this.parseAnimations( json.animations ); + + } + + if ( json.images === undefined || json.images.length === 0 ) { + + if ( onLoad !== undefined ) onLoad( object ); + + } + + return object; + + }, + + parseGeometries: function ( json ) { + + var geometries = {}; + + if ( json !== undefined ) { + + var geometryLoader = new THREE.JSONLoader(); + 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': + case 'PlaneBufferGeometry': + + geometry = new THREE[ data.type ]( + data.width, + data.height, + data.widthSegments, + data.heightSegments + ); + + break; + + case 'BoxGeometry': + case 'CubeGeometry': // backwards compatible + + geometry = new THREE.BoxGeometry( + data.width, + data.height, + data.depth, + data.widthSegments, + data.heightSegments, + data.depthSegments + ); + + break; + + case 'CircleBufferGeometry': + + geometry = new THREE.CircleBufferGeometry( + data.radius, + data.segments, + data.thetaStart, + data.thetaLength + ); + + break; + + case 'CircleGeometry': + + geometry = new THREE.CircleGeometry( + data.radius, + data.segments, + data.thetaStart, + data.thetaLength + ); + + break; + + case 'CylinderGeometry': + + geometry = new THREE.CylinderGeometry( + data.radiusTop, + data.radiusBottom, + data.height, + data.radialSegments, + data.heightSegments, + data.openEnded, + data.thetaStart, + data.thetaLength + ); + + break; + + case 'SphereGeometry': + + geometry = new THREE.SphereGeometry( + data.radius, + data.widthSegments, + data.heightSegments, + data.phiStart, + data.phiLength, + data.thetaStart, + data.thetaLength + ); + + break; + + case 'SphereBufferGeometry': + + geometry = new THREE.SphereBufferGeometry( + data.radius, + data.widthSegments, + data.heightSegments, + data.phiStart, + data.phiLength, + data.thetaStart, + data.thetaLength + ); + + break; + + case 'DodecahedronGeometry': + + geometry = new THREE.DodecahedronGeometry( + data.radius, + data.detail + ); + + break; + + case 'IcosahedronGeometry': + + geometry = new THREE.IcosahedronGeometry( + data.radius, + data.detail + ); + + break; + + case 'OctahedronGeometry': + + geometry = new THREE.OctahedronGeometry( + data.radius, + data.detail + ); + + break; + + case 'TetrahedronGeometry': + + geometry = new THREE.TetrahedronGeometry( + data.radius, + data.detail + ); + + break; + + case 'RingGeometry': + + geometry = new THREE.RingGeometry( + data.innerRadius, + data.outerRadius, + data.thetaSegments, + data.phiSegments, + data.thetaStart, + data.thetaLength + ); + + 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 'TextGeometry': + + geometry = new THREE.TextGeometry( + data.text, + data.data + ); + + break; + + case 'BufferGeometry': + + geometry = bufferGeometryLoader.parse( data ); + + break; + + case 'Geometry': + + geometry = geometryLoader.parse( data.data, this.texturePath ).geometry; + + break; + + default: + + console.warn( 'THREE.ObjectLoader: Unsupported geometry type "' + data.type + '"' ); + + continue; + + } + + geometry.uuid = data.uuid; + + if ( data.name !== undefined ) geometry.name = data.name; + + geometries[ data.uuid ] = geometry; + + } + + } + + return geometries; + + }, + + parseMaterials: function ( json, textures ) { + + var materials = {}; + + if ( json !== undefined ) { + + var loader = new THREE.MaterialLoader(); + loader.setTextures( textures ); + + for ( var i = 0, l = json.length; i < l; i ++ ) { + + var material = loader.parse( json[ i ] ); + materials[ material.uuid ] = material; + + } + + } + + return materials; + + }, + + parseAnimations: function ( json ) { + + var animations = []; + + for( var i = 0; i < json.length; i ++ ) { + + var clip = THREE.AnimationClip.parse( json[i] ); + + animations.push( clip ); + + } + + return animations; + + }, + + parseImages: function ( json, onLoad ) { + + var scope = this; + var images = {}; + + function loadImage( url ) { + + scope.manager.itemStart( url ); + + return loader.load( url, function () { + + scope.manager.itemEnd( url ); + + } ); + + } + + if ( json !== undefined && json.length > 0 ) { + + var manager = new THREE.LoadingManager( onLoad ); + + var loader = new THREE.ImageLoader( manager ); + loader.setCrossOrigin( this.crossOrigin ); + + for ( var i = 0, l = json.length; i < l; i ++ ) { + + var image = json[ i ]; + var path = /^(\/\/)|([a-z]+:(\/\/)?)/i.test( image.url ) ? image.url : scope.texturePath + image.url; + + images[ image.uuid ] = loadImage( path ); + + } + + } + + return images; + + }, + + parseTextures: function ( json, images ) { + + function parseConstant( value ) { + + if ( typeof( value ) === 'number' ) return value; + + console.warn( 'THREE.ObjectLoader.parseTexture: Constant should be in numeric form.', value ); + + return THREE[ value ]; + + } + + var textures = {}; + + if ( json !== undefined ) { + + for ( var i = 0, l = json.length; i < l; i ++ ) { + + var data = json[ i ]; + + if ( data.image === undefined ) { + + console.warn( 'THREE.ObjectLoader: No "image" specified for', data.uuid ); + + } + + if ( images[ data.image ] === undefined ) { + + console.warn( 'THREE.ObjectLoader: Undefined image', data.image ); + + } + + var texture = new THREE.Texture( images[ data.image ] ); + texture.needsUpdate = true; + + texture.uuid = data.uuid; + + if ( data.name !== undefined ) texture.name = data.name; + if ( data.mapping !== undefined ) texture.mapping = parseConstant( data.mapping ); + if ( data.offset !== undefined ) texture.offset = new THREE.Vector2( data.offset[ 0 ], data.offset[ 1 ] ); + if ( data.repeat !== undefined ) texture.repeat = new THREE.Vector2( data.repeat[ 0 ], data.repeat[ 1 ] ); + if ( data.minFilter !== undefined ) texture.minFilter = parseConstant( data.minFilter ); + if ( data.magFilter !== undefined ) texture.magFilter = parseConstant( data.magFilter ); + if ( data.anisotropy !== undefined ) texture.anisotropy = data.anisotropy; + if ( Array.isArray( data.wrap ) ) { + + texture.wrapS = parseConstant( data.wrap[ 0 ] ); + texture.wrapT = parseConstant( data.wrap[ 1 ] ); + + } + + textures[ data.uuid ] = texture; + + } + + } + + return textures; + + }, + + parseObject: function () { + + var matrix = new THREE.Matrix4(); + + return function ( data, geometries, materials ) { + + var object; + + var getGeometry = function ( name ) { + + if ( geometries[ name ] === undefined ) { + + console.warn( 'THREE.ObjectLoader: Undefined geometry', name ); + + } + + return geometries[ name ]; + + }; + + var getMaterial = function ( name ) { + + if ( materials[ name ] === undefined ) { + + console.warn( 'THREE.ObjectLoader: Undefined material', name ); + + } + + return materials[ name ]; + + }; + + 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, data.decay ); + + break; + + case 'SpotLight': + + object = new THREE.SpotLight( data.color, data.intensity, data.distance, data.angle, data.exponent, data.decay ); + + break; + + case 'HemisphereLight': + + object = new THREE.HemisphereLight( data.color, data.groundColor, data.intensity ); + + break; + + case 'Mesh': + + object = new THREE.Mesh( getGeometry( data.geometry ), getMaterial( data.material ) ); + + break; + + case 'LOD': + + object = new THREE.LOD(); + + break; + + case 'Line': + + object = new THREE.Line( getGeometry( data.geometry ), getMaterial( data.material ), data.mode ); + + break; + + case 'PointCloud': + case 'Points': + + object = new THREE.Points( getGeometry( data.geometry ), getMaterial( data.material ) ); + + break; + + case 'Sprite': + + object = new THREE.Sprite( getMaterial( data.material ) ); + + break; + + case 'Group': + + object = new THREE.Group(); + + 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.castShadow !== undefined ) object.castShadow = data.castShadow; + if ( data.receiveShadow !== undefined ) object.receiveShadow = data.receiveShadow; + + 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 ) ); + + } + + } + + if ( data.type === 'LOD' ) { + + var levels = data.levels; + + for ( var l = 0; l < levels.length; l ++ ) { + + var level = levels[ l ]; + var child = object.getObjectByProperty( 'uuid', level.object ); + + if ( child !== undefined ) { + + object.addLevel( child, level.distance ); + + } + + } + + } + + return object; + + } + + }() + +}; + +// File:src/loaders/TextureLoader.js + +/** + * @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 ); + + } + + }, onProgress, onError ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + } + +}; + +// File:src/loaders/BinaryTextureLoader.js + +/** + * @author Nikos M. / https://github.com/foo123/ + * + * Abstract Base class to load generic binary textures formats (rgbe, hdr, ...) + */ + +THREE.DataTextureLoader = THREE.BinaryTextureLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + + // override in sub classes + this._parser = null; + +}; + +THREE.BinaryTextureLoader.prototype = { + + constructor: THREE.BinaryTextureLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var texture = new THREE.DataTexture(); + + var loader = new THREE.XHRLoader( this.manager ); + loader.setCrossOrigin( this.crossOrigin ); + loader.setResponseType( 'arraybuffer' ); + + loader.load( url, function ( buffer ) { + + var texData = scope._parser( buffer ); + + if ( ! texData ) return; + + if ( undefined !== texData.image ) { + + texture.image = texData.image; + + } else if ( undefined !== texData.data ) { + + texture.image.width = texData.width; + texture.image.height = texData.height; + texture.image.data = texData.data; + + } + + texture.wrapS = undefined !== texData.wrapS ? texData.wrapS : THREE.ClampToEdgeWrapping; + texture.wrapT = undefined !== texData.wrapT ? texData.wrapT : THREE.ClampToEdgeWrapping; + + texture.magFilter = undefined !== texData.magFilter ? texData.magFilter : THREE.LinearFilter; + texture.minFilter = undefined !== texData.minFilter ? texData.minFilter : THREE.LinearMipMapLinearFilter; + + texture.anisotropy = undefined !== texData.anisotropy ? texData.anisotropy : 1; + + if ( undefined !== texData.format ) { + + texture.format = texData.format; + + } + if ( undefined !== texData.type ) { + + texture.type = texData.type; + + } + + if ( undefined !== texData.mipmaps ) { + + texture.mipmaps = texData.mipmaps; + + } + + if ( 1 === texData.mipmapCount ) { + + texture.minFilter = THREE.LinearFilter; + + } + + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture, texData ); + + }, onProgress, onError ); + + + return texture; + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + } + +}; + +// File:src/loaders/CompressedTextureLoader.js + +/** + * @author mrdoob / http://mrdoob.com/ + * + * Abstract Base class to block based textures loader (dds, pvr, ...) + */ + +THREE.CompressedTextureLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + + // override in sub classes + this._parser = null; + +}; + + +THREE.CompressedTextureLoader.prototype = { + + constructor: THREE.CompressedTextureLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var images = []; + + var texture = new THREE.CompressedTexture(); + texture.image = images; + + var loader = new THREE.XHRLoader( this.manager ); + loader.setCrossOrigin( this.crossOrigin ); + loader.setResponseType( 'arraybuffer' ); + + if ( Array.isArray( url ) ) { + + var loaded = 0; + + var loadTexture = function ( i ) { + + loader.load( url[ i ], function ( buffer ) { + + var texDatas = scope._parser( buffer, true ); + + images[ i ] = { + width: texDatas.width, + height: texDatas.height, + format: texDatas.format, + mipmaps: texDatas.mipmaps + }; + + loaded += 1; + + if ( loaded === 6 ) { + + if ( texDatas.mipmapCount === 1 ) + texture.minFilter = THREE.LinearFilter; + + texture.format = texDatas.format; + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture ); + + } - get needsUpdate() { + }, onProgress, onError ); - return this._needsUpdate; + }; - }, + for ( var i = 0, il = url.length; i < il; ++ i ) { - set needsUpdate(value) { + loadTexture( i ); - if (value === true) this.update(); + } - this._needsUpdate = value; + } else { - }, + // compressed cubemap texture stored in a single DDS file - setValues: function (values) { + loader.load( url, function ( buffer ) { - if (values === undefined) return; + var texDatas = scope._parser( buffer, true ); - for (var key in values) { + if ( texDatas.isCubemap ) { - var newValue = values[key]; + var faces = texDatas.mipmaps.length / texDatas.mipmapCount; - if (newValue === undefined) { + for ( var f = 0; f < faces; f ++ ) { - THREE.warn("THREE.Material: '" + key + "' parameter is undefined."); - continue; + images[ f ] = { mipmaps : [] }; - } + for ( var i = 0; i < texDatas.mipmapCount; i ++ ) { - if (key in this) { + images[ f ].mipmaps.push( texDatas.mipmaps[ f * texDatas.mipmapCount + i ] ); + images[ f ].format = texDatas.format; + images[ f ].width = texDatas.width; + images[ f ].height = texDatas.height; - var currentValue = this[key]; + } - if (currentValue instanceof THREE.Color) { + } - currentValue.set(newValue); + } else { - } else if (currentValue instanceof THREE.Vector3 && newValue instanceof THREE.Vector3) { + texture.image.width = texDatas.width; + texture.image.height = texDatas.height; + texture.mipmaps = texDatas.mipmaps; - currentValue.copy(newValue); + } - } else if (key == 'overdraw') { + if ( texDatas.mipmapCount === 1 ) { - // ensure overdraw is backwards-compatable with legacy boolean type - this[key] = Number(newValue); + texture.minFilter = THREE.LinearFilter; - } else { + } - this[key] = newValue; + texture.format = texDatas.format; + texture.needsUpdate = true; - } + if ( onLoad ) onLoad( texture ); - } + }, onProgress, onError ); - } + } - }, + return texture; - toJSON: function () { + }, - // we will store all serialization data on 'data' - var data = {}; + setCrossOrigin: function ( value ) { - // add metadata - data.metadata = { - version: 4.4, - type: 'Material', - generator: 'Material.toJSON' - }; + this.crossOrigin = value; - // standard Material serialization - data.type = this.type; - data.uuid = this.uuid; - if (this.name !== '') data.name = this.name; + } - if (this.opacity < 1) data.opacity = this.opacity; - if (this.transparent !== false) data.transparent = this.transparent; - if (this.wireframe !== false) data.wireframe = this.wireframe; +}; + +// File:src/materials/Material.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ - return data; +THREE.Material = function () { - }, + Object.defineProperty( this, 'id', { value: THREE.MaterialIdCount ++ } ); - clone: function (material) { + this.uuid = THREE.Math.generateUUID(); - if (material === undefined) material = new THREE.Material(); + this.name = ''; + this.type = 'Material'; - material.name = this.name; + this.side = THREE.FrontSide; - material.side = this.side; + this.opacity = 1; + this.transparent = false; - material.opacity = this.opacity; - material.transparent = this.transparent; + this.blending = THREE.NormalBlending; - material.blending = this.blending; + this.blendSrc = THREE.SrcAlphaFactor; + this.blendDst = THREE.OneMinusSrcAlphaFactor; + this.blendEquation = THREE.AddEquation; + this.blendSrcAlpha = null; + this.blendDstAlpha = null; + this.blendEquationAlpha = null; - material.blendSrc = this.blendSrc; - material.blendDst = this.blendDst; - material.blendEquation = this.blendEquation; - material.blendSrcAlpha = this.blendSrcAlpha; - material.blendDstAlpha = this.blendDstAlpha; - material.blendEquationAlpha = this.blendEquationAlpha; + this.depthFunc = THREE.LessEqualDepth; + this.depthTest = true; + this.depthWrite = true; - material.depthFunc = this.depthFunc; - material.depthTest = this.depthTest; - material.depthWrite = this.depthWrite; + this.colorWrite = true; - material.polygonOffset = this.polygonOffset; - material.polygonOffsetFactor = this.polygonOffsetFactor; - material.polygonOffsetUnits = this.polygonOffsetUnits; + this.precision = null; // override the renderer's default precision for this material - material.alphaTest = this.alphaTest; + this.polygonOffset = false; + this.polygonOffsetFactor = 0; + this.polygonOffsetUnits = 0; - material.overdraw = this.overdraw; + this.alphaTest = 0; - material.visible = this.visible; + this.overdraw = 0; // Overdrawn pixels (typically between 0 and 1) for fixing antialiasing gaps in CanvasRenderer - return material; + this.visible = true; - }, + this._needsUpdate = true; - update: function () { +}; - this.dispatchEvent({type: 'update'}); +THREE.Material.prototype = { - }, + constructor: THREE.Material, - dispose: function () { + get needsUpdate () { - this.dispatchEvent({type: 'dispose'}); + return this._needsUpdate; - } + }, + + set needsUpdate ( value ) { + + if ( value === true ) this.update(); + + this._needsUpdate = value; + + }, + + 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; + + } + + var currentValue = this[ key ]; + + if ( currentValue === undefined ) { + + console.warn( "THREE." + this.type + ": '" + key + "' is not a property of this material." ); + continue; + + } + + 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-compatible with legacy boolean type + this[ key ] = Number( newValue ); + + } else { + + this[ key ] = newValue; + + } + + } + + }, + + toJSON: function ( meta ) { + + var data = { + metadata: { + version: 4.4, + type: 'Material', + generator: 'Material.toJSON' + } + }; + + // standard Material serialization + data.uuid = this.uuid; + data.type = this.type; + if ( this.name !== '' ) data.name = this.name; + + if ( this.color instanceof THREE.Color ) data.color = this.color.getHex(); + if ( this.emissive instanceof THREE.Color ) data.emissive = this.emissive.getHex(); + if ( this.specular instanceof THREE.Color ) data.specular = this.specular.getHex(); + if ( this.shininess !== undefined ) data.shininess = this.shininess; + + if ( this.map instanceof THREE.Texture ) data.map = this.map.toJSON( meta ).uuid; + if ( this.alphaMap instanceof THREE.Texture ) data.alphaMap = this.alphaMap.toJSON( meta ).uuid; + if ( this.lightMap instanceof THREE.Texture ) data.lightMap = this.lightMap.toJSON( meta ).uuid; + if ( this.bumpMap instanceof THREE.Texture ) { + + data.bumpMap = this.bumpMap.toJSON( meta ).uuid; + data.bumpScale = this.bumpScale; + + } + if ( this.normalMap instanceof THREE.Texture ) { + + data.normalMap = this.normalMap.toJSON( meta ).uuid; + data.normalScale = this.normalScale; // Removed for now, causes issue in editor ui.js + + } + if ( this.displacementMap instanceof THREE.Texture ) { + + data.displacementMap = this.displacementMap.toJSON( meta ).uuid; + data.displacementScale = this.displacementScale; + data.displacementBias = this.displacementBias; + + } + if ( this.specularMap instanceof THREE.Texture ) data.specularMap = this.specularMap.toJSON( meta ).uuid; + if ( this.envMap instanceof THREE.Texture ) { + + data.envMap = this.envMap.toJSON( meta ).uuid; + data.reflectivity = this.reflectivity; // Scale behind envMap + + } + + if ( this.size !== undefined ) data.size = this.size; + if ( this.sizeAttenuation !== undefined ) data.sizeAttenuation = this.sizeAttenuation; + + if ( this.vertexColors !== undefined && this.vertexColors !== THREE.NoColors ) data.vertexColors = this.vertexColors; + if ( this.shading !== undefined && this.shading !== THREE.SmoothShading ) data.shading = this.shading; + if ( this.blending !== undefined && this.blending !== THREE.NormalBlending ) data.blending = this.blending; + if ( this.side !== undefined && this.side !== THREE.FrontSide ) data.side = this.side; + + if ( this.opacity < 1 ) data.opacity = this.opacity; + if ( this.transparent === true ) data.transparent = this.transparent; + if ( this.alphaTest > 0 ) data.alphaTest = this.alphaTest; + if ( this.wireframe === true ) data.wireframe = this.wireframe; + if ( this.wireframeLinewidth > 1 ) data.wireframeLinewidth = this.wireframeLinewidth; + + return data; + + }, + + clone: function () { + + return new this.constructor().copy( this ); + + }, + + copy: function ( source ) { + + this.name = source.name; + + this.side = source.side; + + this.opacity = source.opacity; + this.transparent = source.transparent; + + this.blending = source.blending; + + this.blendSrc = source.blendSrc; + this.blendDst = source.blendDst; + this.blendEquation = source.blendEquation; + this.blendSrcAlpha = source.blendSrcAlpha; + this.blendDstAlpha = source.blendDstAlpha; + this.blendEquationAlpha = source.blendEquationAlpha; + + this.depthFunc = source.depthFunc; + this.depthTest = source.depthTest; + this.depthWrite = source.depthWrite; + + this.precision = source.precision; + + this.polygonOffset = source.polygonOffset; + this.polygonOffsetFactor = source.polygonOffsetFactor; + this.polygonOffsetUnits = source.polygonOffsetUnits; + + this.alphaTest = source.alphaTest; + + this.overdraw = source.overdraw; + + this.visible = source.visible; + + return this; + + }, + + update: function () { + + this.dispatchEvent( { type: 'update' } ); + + }, + + dispose: function () { + + this.dispatchEvent( { type: 'dispose' } ); + + }, + + // Deprecated + + get wrapAround () { + + console.warn( 'THREE.' + this.type + ': .wrapAround has been removed.' ); + + }, + + set wrapAround ( boolean ) { + + console.warn( 'THREE.' + this.type + ': .wrapAround has been removed.' ); + + }, + + get wrapRGB () { + + console.warn( 'THREE.' + this.type + ': .wrapRGB has been removed.' ); + return new THREE.Color(); + + } }; -THREE.EventDispatcher.prototype.apply(THREE.Material.prototype); +THREE.EventDispatcher.prototype.apply( THREE.Material.prototype ); THREE.MaterialIdCount = 0; @@ -14798,46 +17089,44 @@ THREE.MaterialIdCount = 0; * } */ -THREE.LineBasicMaterial = function (parameters) { +THREE.LineBasicMaterial = function ( parameters ) { - THREE.Material.call(this); + THREE.Material.call( this ); - this.type = 'LineBasicMaterial'; + this.type = 'LineBasicMaterial'; - this.color = new THREE.Color(0xffffff); + this.color = new THREE.Color( 0xffffff ); - this.linewidth = 1; - this.linecap = 'round'; - this.linejoin = 'round'; + this.linewidth = 1; + this.linecap = 'round'; + this.linejoin = 'round'; - this.vertexColors = THREE.NoColors; + this.vertexColors = THREE.NoColors; - this.fog = true; + this.fog = true; - this.setValues(parameters); + this.setValues( parameters ); }; -THREE.LineBasicMaterial.prototype = Object.create(THREE.Material.prototype); +THREE.LineBasicMaterial.prototype = Object.create( THREE.Material.prototype ); THREE.LineBasicMaterial.prototype.constructor = THREE.LineBasicMaterial; -THREE.LineBasicMaterial.prototype.clone = function () { - - var material = new THREE.LineBasicMaterial(); +THREE.LineBasicMaterial.prototype.copy = function ( source ) { - THREE.Material.prototype.clone.call(this, material); + THREE.Material.prototype.copy.call( this, source ); - material.color.copy(this.color); + this.color.copy( source.color ); - material.linewidth = this.linewidth; - material.linecap = this.linecap; - material.linejoin = this.linejoin; + this.linewidth = source.linewidth; + this.linecap = source.linecap; + this.linejoin = source.linejoin; - material.vertexColors = this.vertexColors; + this.vertexColors = source.vertexColors; - material.fog = this.fog; + this.fog = source.fog; - return material; + return this; }; @@ -14866,50 +17155,48 @@ THREE.LineBasicMaterial.prototype.clone = function () { * } */ -THREE.LineDashedMaterial = function (parameters) { +THREE.LineDashedMaterial = function ( parameters ) { - THREE.Material.call(this); + THREE.Material.call( this ); - this.type = 'LineDashedMaterial'; + this.type = 'LineDashedMaterial'; - this.color = new THREE.Color(0xffffff); + this.color = new THREE.Color( 0xffffff ); - this.linewidth = 1; + this.linewidth = 1; - this.scale = 1; - this.dashSize = 3; - this.gapSize = 1; + this.scale = 1; + this.dashSize = 3; + this.gapSize = 1; - this.vertexColors = false; + this.vertexColors = false; - this.fog = true; + this.fog = true; - this.setValues(parameters); + this.setValues( parameters ); }; -THREE.LineDashedMaterial.prototype = Object.create(THREE.Material.prototype); +THREE.LineDashedMaterial.prototype = Object.create( THREE.Material.prototype ); THREE.LineDashedMaterial.prototype.constructor = THREE.LineDashedMaterial; -THREE.LineDashedMaterial.prototype.clone = function () { - - var material = new THREE.LineDashedMaterial(); - - THREE.Material.prototype.clone.call(this, material); +THREE.LineDashedMaterial.prototype.copy = function ( source ) { - material.color.copy(this.color); + THREE.Material.prototype.copy.call( this, source ); - material.linewidth = this.linewidth; + this.color.copy( source.color ); + + this.linewidth = source.linewidth; - material.scale = this.scale; - material.dashSize = this.dashSize; - material.gapSize = this.gapSize; + this.scale = source.scale; + this.dashSize = source.dashSize; + this.gapSize = source.gapSize; - material.vertexColors = this.vertexColors; + this.vertexColors = source.vertexColors; - material.fog = this.fog; + this.fog = source.fog; - return material; + return this; }; @@ -14924,6 +17211,9 @@ THREE.LineDashedMaterial.prototype.clone = function () { * opacity: , * map: new THREE.Texture( ), * + * aoMap: new THREE.Texture( ), + * aoMapIntensity: + * * specularMap: new THREE.Texture( ), * * alphaMap: new THREE.Texture( ), @@ -14950,93 +17240,84 @@ THREE.LineDashedMaterial.prototype.clone = function () { * } */ -THREE.MeshBasicMaterial = function (parameters) { +THREE.MeshBasicMaterial = function ( parameters ) { + + THREE.Material.call( this ); - THREE.Material.call(this); + this.type = 'MeshBasicMaterial'; - this.type = 'MeshBasicMaterial'; + this.color = new THREE.Color( 0xffffff ); // emissive - this.color = new THREE.Color(0xffffff); // emissive + this.map = null; - this.map = null; + this.aoMap = null; + this.aoMapIntensity = 1.0; - this.specularMap = null; + this.specularMap = null; - this.alphaMap = null; + this.alphaMap = null; - this.envMap = null; - this.combine = THREE.MultiplyOperation; - this.reflectivity = 1; - this.refractionRatio = 0.98; + this.envMap = null; + this.combine = THREE.MultiplyOperation; + this.reflectivity = 1; + this.refractionRatio = 0.98; - this.fog = true; + this.fog = true; - this.shading = THREE.SmoothShading; + this.shading = THREE.SmoothShading; - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; + this.wireframe = false; + this.wireframeLinewidth = 1; + this.wireframeLinecap = 'round'; + this.wireframeLinejoin = 'round'; - this.vertexColors = THREE.NoColors; + this.vertexColors = THREE.NoColors; - this.skinning = false; - this.morphTargets = false; + this.skinning = false; + this.morphTargets = false; - this.setValues(parameters); + this.setValues( parameters ); }; -THREE.MeshBasicMaterial.prototype = Object.create(THREE.Material.prototype); +THREE.MeshBasicMaterial.prototype = Object.create( THREE.Material.prototype ); THREE.MeshBasicMaterial.prototype.constructor = THREE.MeshBasicMaterial; -THREE.MeshBasicMaterial.prototype.clone = function () { +THREE.MeshBasicMaterial.prototype.copy = function ( source ) { + + THREE.Material.prototype.copy.call( this, source ); - var material = new THREE.MeshBasicMaterial(); + this.color.copy( source.color ); - THREE.Material.prototype.clone.call(this, material); + this.map = source.map; - material.color.copy(this.color); + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; - material.map = this.map; + this.specularMap = source.specularMap; - material.specularMap = this.specularMap; + this.alphaMap = source.alphaMap; - material.alphaMap = this.alphaMap; + this.envMap = source.envMap; + this.combine = source.combine; + this.reflectivity = source.reflectivity; + this.refractionRatio = source.refractionRatio; - material.envMap = this.envMap; - material.combine = this.combine; - material.reflectivity = this.reflectivity; - material.refractionRatio = this.refractionRatio; + this.fog = source.fog; - material.fog = this.fog; + this.shading = source.shading; - material.shading = this.shading; + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; - material.wireframe = this.wireframe; - material.wireframeLinewidth = this.wireframeLinewidth; - material.wireframeLinecap = this.wireframeLinecap; - material.wireframeLinejoin = this.wireframeLinejoin; + this.vertexColors = source.vertexColors; - material.vertexColors = this.vertexColors; - - material.skinning = this.skinning; - material.morphTargets = this.morphTargets; - - return material; - -}; - -THREE.MeshBasicMaterial.prototype.toJSON = function () { - - var data = THREE.Material.prototype.toJSON.call(this); - - data.color = this.color.getHex(); - if (this.vertexColors !== THREE.NoColors) data.vertexColors = this.vertexColors; - if (this.blending !== THREE.NormalBlending) data.blending = this.blending; - if (this.side !== THREE.FrontSide) data.side = this.side; - - return data; + this.skinning = source.skinning; + this.morphTargets = source.morphTargets; + + return this; }; @@ -15062,7 +17343,6 @@ THREE.MeshBasicMaterial.prototype.toJSON = function () { * reflectivity: , * refractionRatio: , * - * shading: THREE.SmoothShading, * blending: THREE.NormalBlending, * depthTest: , * depthWrite: , @@ -15080,99 +17360,78 @@ THREE.MeshBasicMaterial.prototype.toJSON = function () { * } */ -THREE.MeshLambertMaterial = function (parameters) { - - THREE.Material.call(this); +THREE.MeshLambertMaterial = function ( parameters ) { - this.type = 'MeshLambertMaterial'; + THREE.Material.call( this ); - this.color = new THREE.Color(0xffffff); // diffuse - this.emissive = new THREE.Color(0x000000); + this.type = 'MeshLambertMaterial'; - this.map = null; + this.color = new THREE.Color( 0xffffff ); // diffuse + this.emissive = new THREE.Color( 0x000000 ); - this.specularMap = null; + this.map = null; - this.alphaMap = null; + this.specularMap = null; - this.envMap = null; - this.combine = THREE.MultiplyOperation; - this.reflectivity = 1; - this.refractionRatio = 0.98; + this.alphaMap = null; - this.fog = true; + this.envMap = null; + this.combine = THREE.MultiplyOperation; + this.reflectivity = 1; + this.refractionRatio = 0.98; - this.shading = THREE.SmoothShading; + this.fog = true; - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; + this.wireframe = false; + this.wireframeLinewidth = 1; + this.wireframeLinecap = 'round'; + this.wireframeLinejoin = 'round'; - this.vertexColors = THREE.NoColors; + this.vertexColors = THREE.NoColors; - this.skinning = false; - this.morphTargets = false; - this.morphNormals = false; + this.skinning = false; + this.morphTargets = false; + this.morphNormals = false; - this.setValues(parameters); + this.setValues( parameters ); }; -THREE.MeshLambertMaterial.prototype = Object.create(THREE.Material.prototype); +THREE.MeshLambertMaterial.prototype = Object.create( THREE.Material.prototype ); THREE.MeshLambertMaterial.prototype.constructor = THREE.MeshLambertMaterial; -THREE.MeshLambertMaterial.prototype.clone = function () { - - var material = new THREE.MeshLambertMaterial(); - - THREE.Material.prototype.clone.call(this, material); - - material.color.copy(this.color); - material.emissive.copy(this.emissive); - - material.map = this.map; - - material.specularMap = this.specularMap; - - material.alphaMap = this.alphaMap; +THREE.MeshLambertMaterial.prototype.copy = function ( source ) { - material.envMap = this.envMap; - material.combine = this.combine; - material.reflectivity = this.reflectivity; - material.refractionRatio = this.refractionRatio; + THREE.Material.prototype.copy.call( this, source ); - material.fog = this.fog; + this.color.copy( source.color ); + this.emissive.copy( source.emissive ); - material.shading = this.shading; + this.map = source.map; - material.wireframe = this.wireframe; - material.wireframeLinewidth = this.wireframeLinewidth; - material.wireframeLinecap = this.wireframeLinecap; - material.wireframeLinejoin = this.wireframeLinejoin; + this.specularMap = source.specularMap; - material.vertexColors = this.vertexColors; + this.alphaMap = source.alphaMap; - material.skinning = this.skinning; - material.morphTargets = this.morphTargets; - material.morphNormals = this.morphNormals; + this.envMap = source.envMap; + this.combine = source.combine; + this.reflectivity = source.reflectivity; + this.refractionRatio = source.refractionRatio; - return material; + this.fog = source.fog; -}; - -THREE.MeshLambertMaterial.prototype.toJSON = function () { + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; - var data = THREE.Material.prototype.toJSON.call(this); + this.vertexColors = source.vertexColors; - data.color = this.color.getHex(); - data.emissive = this.emissive.getHex(); - if (this.vertexColors !== THREE.NoColors) data.vertexColors = this.vertexColors; - if (this.shading !== THREE.SmoothShading) data.shading = this.shading; - if (this.blending !== THREE.NormalBlending) data.blending = this.blending; - if (this.side !== THREE.FrontSide) data.side = this.side; + this.skinning = source.skinning; + this.morphTargets = source.morphTargets; + this.morphNormals = source.morphNormals; - return data; + return this; }; @@ -15197,12 +17456,18 @@ THREE.MeshLambertMaterial.prototype.toJSON = function () { * aoMap: new THREE.Texture( ), * aoMapIntensity: * + * emissiveMap: new THREE.Texture( ), + * * bumpMap: new THREE.Texture( ), * bumpScale: , * * normalMap: new THREE.Texture( ), * normalScale: , * + * displacementMap: new THREE.Texture( ), + * displacementScale: , + * displacementBias: , + * * specularMap: new THREE.Texture( ), * * alphaMap: new THREE.Texture( ), @@ -15230,134 +17495,126 @@ THREE.MeshLambertMaterial.prototype.toJSON = function () { * } */ -THREE.MeshPhongMaterial = function (parameters) { +THREE.MeshPhongMaterial = function ( parameters ) { - THREE.Material.call(this); + THREE.Material.call( this ); - this.type = 'MeshPhongMaterial'; + this.type = 'MeshPhongMaterial'; - this.color = new THREE.Color(0xffffff); // diffuse - this.emissive = new THREE.Color(0x000000); - this.specular = new THREE.Color(0x111111); - this.shininess = 30; + this.color = new THREE.Color( 0xffffff ); // diffuse + this.emissive = new THREE.Color( 0x000000 ); + this.specular = new THREE.Color( 0x111111 ); + this.shininess = 30; - this.metal = false; + this.metal = false; - this.map = null; + this.map = null; - this.lightMap = null; - this.lightMapIntensity = 1.0; + this.lightMap = null; + this.lightMapIntensity = 1.0; - this.aoMap = null; - this.aoMapIntensity = 1.0; + this.aoMap = null; + this.aoMapIntensity = 1.0; - this.bumpMap = null; - this.bumpScale = 1; + this.emissiveMap = null; - this.normalMap = null; - this.normalScale = new THREE.Vector2(1, 1); + this.bumpMap = null; + this.bumpScale = 1; - this.specularMap = null; + this.normalMap = null; + this.normalScale = new THREE.Vector2( 1, 1 ); - this.alphaMap = null; + this.displacementMap = null; + this.displacementScale = 1; + this.displacementBias = 0; - this.envMap = null; - this.combine = THREE.MultiplyOperation; - this.reflectivity = 1; - this.refractionRatio = 0.98; + this.specularMap = null; - this.fog = true; + this.alphaMap = null; - this.shading = THREE.SmoothShading; + this.envMap = null; + this.combine = THREE.MultiplyOperation; + this.reflectivity = 1; + this.refractionRatio = 0.98; - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; + this.fog = true; - this.vertexColors = THREE.NoColors; - - this.skinning = false; - this.morphTargets = false; - this.morphNormals = false; - - this.setValues(parameters); - -}; + this.shading = THREE.SmoothShading; -THREE.MeshPhongMaterial.prototype = Object.create(THREE.Material.prototype); -THREE.MeshPhongMaterial.prototype.constructor = THREE.MeshPhongMaterial; + this.wireframe = false; + this.wireframeLinewidth = 1; + this.wireframeLinecap = 'round'; + this.wireframeLinejoin = 'round'; -THREE.MeshPhongMaterial.prototype.clone = function () { + this.vertexColors = THREE.NoColors; - var material = new THREE.MeshPhongMaterial(); + this.skinning = false; + this.morphTargets = false; + this.morphNormals = false; - THREE.Material.prototype.clone.call(this, material); + this.setValues( parameters ); - material.color.copy(this.color); - material.emissive.copy(this.emissive); - material.specular.copy(this.specular); - material.shininess = this.shininess; +}; - material.metal = this.metal; +THREE.MeshPhongMaterial.prototype = Object.create( THREE.Material.prototype ); +THREE.MeshPhongMaterial.prototype.constructor = THREE.MeshPhongMaterial; - material.map = this.map; +THREE.MeshPhongMaterial.prototype.copy = function ( source ) { - material.lightMap = this.lightMap; - material.lightMapIntensity = this.lightMapIntensity; + THREE.Material.prototype.copy.call( this, source ); - material.aoMap = this.aoMap; - material.aoMapIntensity = this.aoMapIntensity; + this.color.copy( source.color ); + this.emissive.copy( source.emissive ); + this.specular.copy( source.specular ); + this.shininess = source.shininess; - material.bumpMap = this.bumpMap; - material.bumpScale = this.bumpScale; + this.metal = source.metal; - material.normalMap = this.normalMap; - material.normalScale.copy(this.normalScale); + this.map = source.map; - material.specularMap = this.specularMap; + this.lightMap = source.lightMap; + this.lightMapIntensity = source.lightMapIntensity; - material.alphaMap = this.alphaMap; + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; - material.envMap = this.envMap; - material.combine = this.combine; - material.reflectivity = this.reflectivity; - material.refractionRatio = this.refractionRatio; + this.emissiveMap = source.emissiveMap; - material.fog = this.fog; + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; - material.shading = this.shading; + this.normalMap = source.normalMap; + this.normalScale.copy( source.normalScale ); - material.wireframe = this.wireframe; - material.wireframeLinewidth = this.wireframeLinewidth; - material.wireframeLinecap = this.wireframeLinecap; - material.wireframeLinejoin = this.wireframeLinejoin; + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; - material.vertexColors = this.vertexColors; + this.specularMap = source.specularMap; - material.skinning = this.skinning; - material.morphTargets = this.morphTargets; - material.morphNormals = this.morphNormals; + this.alphaMap = source.alphaMap; - return material; + this.envMap = source.envMap; + this.combine = source.combine; + this.reflectivity = source.reflectivity; + this.refractionRatio = source.refractionRatio; -}; + this.fog = source.fog; -THREE.MeshPhongMaterial.prototype.toJSON = function () { + this.shading = source.shading; - var data = THREE.Material.prototype.toJSON.call(this); + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; - data.color = this.color.getHex(); - data.emissive = this.emissive.getHex(); - data.specular = this.specular.getHex(); - data.shininess = this.shininess; + this.vertexColors = source.vertexColors; - if (this.vertexColors !== THREE.NoColors) data.vertexColors = this.vertexColors; - if (this.shading !== THREE.SmoothShading) data.shading = this.shading; - if (this.blending !== THREE.NormalBlending) data.blending = this.blending; - if (this.side !== THREE.FrontSide) data.side = this.side; + this.skinning = source.skinning; + this.morphTargets = source.morphTargets; + this.morphNormals = source.morphNormals; - return data; + return this; }; @@ -15379,44 +17636,31 @@ THREE.MeshPhongMaterial.prototype.toJSON = function () { * } */ -THREE.MeshDepthMaterial = function (parameters) { +THREE.MeshDepthMaterial = function ( parameters ) { - THREE.Material.call(this); + THREE.Material.call( this ); - this.type = 'MeshDepthMaterial'; + this.type = 'MeshDepthMaterial'; - this.morphTargets = false; - this.wireframe = false; - this.wireframeLinewidth = 1; + this.morphTargets = false; + this.wireframe = false; + this.wireframeLinewidth = 1; - this.setValues(parameters); + this.setValues( parameters ); }; -THREE.MeshDepthMaterial.prototype = Object.create(THREE.Material.prototype); +THREE.MeshDepthMaterial.prototype = Object.create( THREE.Material.prototype ); THREE.MeshDepthMaterial.prototype.constructor = THREE.MeshDepthMaterial; -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; +THREE.MeshDepthMaterial.prototype.copy = function ( source ) { -}; - -THREE.MeshDepthMaterial.prototype.toJSON = function () { + THREE.Material.prototype.copy.call( this, source ); - var data = THREE.Material.prototype.toJSON.call(this); + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; - if (this.blending !== THREE.NormalBlending) data.blending = this.blending; - if (this.side !== THREE.FrontSide) data.side = this.side; - - return data; + return this; }; @@ -15438,66 +17682,105 @@ THREE.MeshDepthMaterial.prototype.toJSON = function () { * } */ -THREE.MeshNormalMaterial = function (parameters) { +THREE.MeshNormalMaterial = function ( parameters ) { - THREE.Material.call(this, parameters); + THREE.Material.call( this, parameters ); - this.type = 'MeshNormalMaterial'; + this.type = 'MeshNormalMaterial'; - this.wireframe = false; - this.wireframeLinewidth = 1; + this.wireframe = false; + this.wireframeLinewidth = 1; - this.morphTargets = false; + this.morphTargets = false; - this.setValues(parameters); + this.setValues( parameters ); }; -THREE.MeshNormalMaterial.prototype = Object.create(THREE.Material.prototype); +THREE.MeshNormalMaterial.prototype = Object.create( THREE.Material.prototype ); THREE.MeshNormalMaterial.prototype.constructor = THREE.MeshNormalMaterial; -THREE.MeshNormalMaterial.prototype.clone = function () { - - var material = new THREE.MeshNormalMaterial(); +THREE.MeshNormalMaterial.prototype.copy = function ( source ) { - THREE.Material.prototype.clone.call(this, material); + THREE.Material.prototype.copy.call( this, source ); - material.wireframe = this.wireframe; - material.wireframeLinewidth = this.wireframeLinewidth; + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; - return material; + return this; }; -THREE.MeshNormalMaterial.prototype.toJSON = function () { +// File:src/materials/MultiMaterial.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.MultiMaterial = function ( materials ) { + + this.uuid = THREE.Math.generateUUID(); - var data = THREE.Material.prototype.toJSON.call(this); + this.type = 'MultiMaterial'; - if (this.blending !== THREE.NormalBlending) data.blending = this.blending; - if (this.side !== THREE.FrontSide) data.side = this.side; + this.materials = materials instanceof Array ? materials : []; - return data; + this.visible = true; }; -// File:src/materials/MeshFaceMaterial.js +THREE.MultiMaterial.prototype = { -/** - * @author mrdoob / http://mrdoob.com/ - */ + constructor: THREE.MultiMaterial, + + toJSON: function () { + + var output = { + metadata: { + version: 4.2, + type: 'material', + generator: 'MaterialExporter' + }, + uuid: this.uuid, + type: this.type, + materials: [] + }; + + for ( var i = 0, l = this.materials.length; i < l; i ++ ) { -THREE.MeshFaceMaterial = function (materials) { + output.materials.push( this.materials[ i ].toJSON() ); - THREE.error('THREE.MeshFaceMaterial has been removed.'); + } - var material = materials !== undefined ? materials[0] : new THREE.MeshBasicMaterial(); - material.materials = []; // temporal workaround + output.visible = this.visible; - return material; + return output; + + }, + + clone: function () { + + var material = new this.constructor(); + + for ( var i = 0; i < this.materials.length; i ++ ) { + + material.materials.push( this.materials[ i ].clone() ); + + } + + material.visible = this.visible; + + return material; + + } }; -// File:src/materials/PointCloudMaterial.js +// backwards compatibility + +THREE.MeshFaceMaterial = THREE.MultiMaterial; + +// File:src/materials/PointsMaterial.js /** * @author mrdoob / http://mrdoob.com/ @@ -15521,79 +17804,69 @@ THREE.MeshFaceMaterial = function (materials) { * } */ -THREE.PointCloudMaterial = function (parameters) { +THREE.PointsMaterial = function ( parameters ) { - THREE.Material.call(this); + THREE.Material.call( this ); - this.type = 'PointCloudMaterial'; + this.type = 'PointsMaterial'; - this.color = new THREE.Color(0xffffff); + this.color = new THREE.Color( 0xffffff ); - this.map = null; + this.map = null; - this.size = 1; - this.sizeAttenuation = true; + this.size = 1; + this.sizeAttenuation = true; - this.vertexColors = THREE.NoColors; + this.vertexColors = THREE.NoColors; - this.fog = true; + this.fog = true; - this.setValues(parameters); + this.setValues( parameters ); }; -THREE.PointCloudMaterial.prototype = Object.create(THREE.Material.prototype); -THREE.PointCloudMaterial.prototype.constructor = THREE.PointCloudMaterial; +THREE.PointsMaterial.prototype = Object.create( THREE.Material.prototype ); +THREE.PointsMaterial.prototype.constructor = THREE.PointsMaterial; -THREE.PointCloudMaterial.prototype.clone = function () { +THREE.PointsMaterial.prototype.copy = function ( source ) { - var material = new THREE.PointCloudMaterial(); + THREE.Material.prototype.copy.call( this, source ); - THREE.Material.prototype.clone.call(this, material); + this.color.copy( source.color ); - material.color.copy(this.color); + this.map = source.map; - material.map = this.map; + this.size = source.size; + this.sizeAttenuation = source.sizeAttenuation; - material.size = this.size; - material.sizeAttenuation = this.sizeAttenuation; + this.vertexColors = source.vertexColors; - material.vertexColors = this.vertexColors; + this.fog = source.fog; - material.fog = this.fog; - - return material; + return this; }; -THREE.PointCloudMaterial.prototype.toJSON = function () { - - var data = THREE.Material.prototype.toJSON.call(this); - - data.size = this.size; - data.sizeAttenuation = this.sizeAttenuation; - data.color = this.color.getHex(); +// backwards compatibility - if (this.vertexColors !== THREE.NoColors) data.vertexColors = this.vertexColors; - if (this.blending !== THREE.NormalBlending) data.blending = this.blending; +THREE.PointCloudMaterial = function ( parameters ) { - return data; + console.warn( 'THREE.PointCloudMaterial has been renamed to THREE.PointsMaterial.' ); + return new THREE.PointsMaterial( parameters ); }; -// backwards compatibility - -THREE.ParticleBasicMaterial = function (parameters) { +THREE.ParticleBasicMaterial = function ( parameters ) { - THREE.warn('THREE.ParticleBasicMaterial has been renamed to THREE.PointCloudMaterial.'); - return new THREE.PointCloudMaterial(parameters); + console.warn( 'THREE.ParticleBasicMaterial has been renamed to THREE.PointsMaterial.' ); + return new THREE.PointsMaterial( parameters ); }; -THREE.ParticleSystemMaterial = function (parameters) { +THREE.ParticleSystemMaterial = function ( parameters ) { - THREE.warn('THREE.ParticleSystemMaterial has been renamed to THREE.PointCloudMaterial.'); - return new THREE.PointCloudMaterial(parameters); + console.warn( 'THREE.ParticleSystemMaterial has been renamed to THREE.PointsMaterial.' ); + return new THREE.PointsMaterial( parameters ); }; @@ -15629,97 +17902,109 @@ THREE.ParticleSystemMaterial = function (parameters) { * } */ -THREE.ShaderMaterial = function (parameters) { +THREE.ShaderMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + this.type = 'ShaderMaterial'; + + this.defines = {}; + this.uniforms = {}; + + this.vertexShader = 'void main() {\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}'; + this.fragmentShader = 'void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n}'; + + this.shading = THREE.SmoothShading; + + this.linewidth = 1; - THREE.Material.call(this); + this.wireframe = false; + this.wireframeLinewidth = 1; - this.type = 'ShaderMaterial'; + this.fog = false; // set to use scene fog - this.defines = {}; - this.uniforms = {}; - this.attributes = null; + this.lights = false; // set to use scene lights - this.vertexShader = 'void main() {\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}'; - this.fragmentShader = 'void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n}'; + this.vertexColors = THREE.NoColors; // set to use "color" attribute stream - this.shading = THREE.SmoothShading; + this.skinning = false; // set to use skinning attribute streams - this.linewidth = 1; + this.morphTargets = false; // set to use morph targets + this.morphNormals = false; // set to use morph normals - this.wireframe = false; - this.wireframeLinewidth = 1; + this.derivatives = false; // set to use derivatives - this.fog = false; // set to use scene fog + // 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 ] + }; - this.lights = false; // set to use scene lights + this.index0AttributeName = undefined; - this.vertexColors = THREE.NoColors; // set to use "color" attribute stream + if ( parameters !== undefined ) { - this.skinning = false; // set to use skinning attribute streams + if ( parameters.attributes !== undefined ) { - this.morphTargets = false; // set to use morph targets - this.morphNormals = false; // set to use morph normals + console.error( 'THREE.ShaderMaterial: attributes should now be defined in THREE.BufferGeometry instead.' ); - // 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] - }; + } - this.index0AttributeName = undefined; + this.setValues( parameters ); - this.setValues(parameters); + } }; -THREE.ShaderMaterial.prototype = Object.create(THREE.Material.prototype); +THREE.ShaderMaterial.prototype = Object.create( THREE.Material.prototype ); THREE.ShaderMaterial.prototype.constructor = THREE.ShaderMaterial; -THREE.ShaderMaterial.prototype.clone = function (material) { +THREE.ShaderMaterial.prototype.copy = function ( source ) { - if (material === undefined) material = new THREE.ShaderMaterial(); + THREE.Material.prototype.copy.call( this, source ); - THREE.Material.prototype.clone.call(this, material); + this.fragmentShader = source.fragmentShader; + this.vertexShader = source.vertexShader; - material.fragmentShader = this.fragmentShader; - material.vertexShader = this.vertexShader; + this.uniforms = THREE.UniformsUtils.clone( source.uniforms ); - material.uniforms = THREE.UniformsUtils.clone(this.uniforms); + this.attributes = source.attributes; + this.defines = source.defines; - material.attributes = this.attributes; - material.defines = this.defines; + this.shading = source.shading; - material.shading = this.shading; + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; - material.wireframe = this.wireframe; - material.wireframeLinewidth = this.wireframeLinewidth; + this.fog = source.fog; - material.fog = this.fog; + this.lights = source.lights; - material.lights = this.lights; + this.vertexColors = source.vertexColors; - material.vertexColors = this.vertexColors; + this.skinning = source.skinning; - material.skinning = this.skinning; + this.morphTargets = source.morphTargets; + this.morphNormals = source.morphNormals; - material.morphTargets = this.morphTargets; - material.morphNormals = this.morphNormals; + this.derivatives = source.derivatives; - return material; + return this; }; -THREE.ShaderMaterial.prototype.toJSON = function () { +THREE.ShaderMaterial.prototype.toJSON = function ( meta ) { - var data = THREE.Material.prototype.toJSON.call(this); + var data = THREE.Material.prototype.toJSON.call( this, meta ); - data.uniforms = this.uniforms; - data.vertexShader = this.vertexShader; - data.fragmentShader = this.fragmentShader; + data.uniforms = this.uniforms; + data.attributes = this.attributes; + data.vertexShader = this.vertexShader; + data.fragmentShader = this.fragmentShader; - return data; + return data; }; @@ -15729,341 +18014,476 @@ THREE.ShaderMaterial.prototype.toJSON = function () { * @author mrdoob / http://mrdoob.com/ */ -THREE.RawShaderMaterial = function (parameters) { +THREE.RawShaderMaterial = function ( parameters ) { + + THREE.ShaderMaterial.call( this, parameters ); + + this.type = 'RawShaderMaterial'; + +}; + +THREE.RawShaderMaterial.prototype = Object.create( THREE.ShaderMaterial.prototype ); +THREE.RawShaderMaterial.prototype.constructor = THREE.RawShaderMaterial; +// File:src/materials/SpriteMaterial.js + +/** + * @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 ); + + this.type = 'SpriteMaterial'; + + 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.constructor = THREE.SpriteMaterial; + +THREE.SpriteMaterial.prototype.copy = function ( source ) { + + THREE.Material.prototype.copy.call( this, source ); + + this.color.copy( source.color ); + this.map = source.map; + + this.rotation = source.rotation; + + this.fog = source.fog; + + return this; + +}; + +// File:src/textures/Texture.js + +/** + * @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 ) { + + Object.defineProperty( this, 'id', { value: THREE.TextureIdCount ++ } ); + + this.uuid = THREE.Math.generateUUID(); + + this.name = ''; + this.sourceFile = ''; + + this.image = image !== undefined ? image : THREE.Texture.DEFAULT_IMAGE; + this.mipmaps = []; + + this.mapping = mapping !== undefined ? mapping : THREE.Texture.DEFAULT_MAPPING; + + 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.version = 0; + this.onUpdate = null; + +}; + +THREE.Texture.DEFAULT_IMAGE = undefined; +THREE.Texture.DEFAULT_MAPPING = THREE.UVMapping; + +THREE.Texture.prototype = { + + constructor: THREE.Texture, + + set needsUpdate ( value ) { + + if ( value === true ) this.version ++; + + }, + + clone: function () { + + return new this.constructor().copy( this ); + + }, + + copy: function ( source ) { + + this.image = source.image; + this.mipmaps = source.mipmaps.slice( 0 ); + + this.mapping = source.mapping; + + this.wrapS = source.wrapS; + this.wrapT = source.wrapT; + + this.magFilter = source.magFilter; + this.minFilter = source.minFilter; + + this.anisotropy = source.anisotropy; + + this.format = source.format; + this.type = source.type; - THREE.ShaderMaterial.call(this, parameters); + this.offset.copy( source.offset ); + this.repeat.copy( source.repeat ); - this.type = 'RawShaderMaterial'; + this.generateMipmaps = source.generateMipmaps; + this.premultiplyAlpha = source.premultiplyAlpha; + this.flipY = source.flipY; + this.unpackAlignment = source.unpackAlignment; -}; + return this; -THREE.RawShaderMaterial.prototype = Object.create(THREE.ShaderMaterial.prototype); -THREE.RawShaderMaterial.prototype.constructor = THREE.RawShaderMaterial; + }, -THREE.RawShaderMaterial.prototype.clone = function () { + toJSON: function ( meta ) { - var material = new THREE.RawShaderMaterial(); + if ( meta.textures[ this.uuid ] !== undefined ) { - THREE.ShaderMaterial.prototype.clone.call(this, material); + return meta.textures[ this.uuid ]; - return material; + } -}; + function getDataURL( image ) { -// File:src/materials/SpriteMaterial.js + var canvas; -/** - * @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: - * } - */ + if ( image.toDataURL !== undefined ) { -THREE.SpriteMaterial = function (parameters) { + canvas = image; - THREE.Material.call(this); + } else { - this.type = 'SpriteMaterial'; + canvas = document.createElement( 'canvas' ); + canvas.width = image.width; + canvas.height = image.height; - this.color = new THREE.Color(0xffffff); - this.map = null; + canvas.getContext( '2d' ).drawImage( image, 0, 0, image.width, image.height ); - this.rotation = 0; + } - this.fog = false; + if ( canvas.width > 2048 || canvas.height > 2048 ) { - // set parameters + return canvas.toDataURL( 'image/jpeg', 0.6 ); - this.setValues(parameters); + } else { -}; + return canvas.toDataURL( 'image/png' ); -THREE.SpriteMaterial.prototype = Object.create(THREE.Material.prototype); -THREE.SpriteMaterial.prototype.constructor = THREE.SpriteMaterial; + } -THREE.SpriteMaterial.prototype.clone = function () { + } - var material = new THREE.SpriteMaterial(); + var output = { + metadata: { + version: 4.4, + type: 'Texture', + generator: 'Texture.toJSON' + }, - THREE.Material.prototype.clone.call(this, material); + uuid: this.uuid, + name: this.name, - material.color.copy(this.color); - material.map = this.map; + mapping: this.mapping, - material.rotation = this.rotation; + repeat: [ this.repeat.x, this.repeat.y ], + offset: [ this.offset.x, this.offset.y ], + wrap: [ this.wrapS, this.wrapT ], - material.fog = this.fog; + minFilter: this.minFilter, + magFilter: this.magFilter, + anisotropy: this.anisotropy + }; - return material; + if ( this.image !== undefined ) { -}; + // TODO: Move to THREE.Image -THREE.SpriteMaterial.prototype.toJSON = function () { + var image = this.image; - var data = THREE.Material.prototype.toJSON.call(this); + if ( image.uuid === undefined ) { - data.color = this.color.getHex(); + image.uuid = THREE.Math.generateUUID(); // UGH - return data; + } -}; + if ( meta.images[ image.uuid ] === undefined ) { -// File:src/textures/Texture.js + meta.images[ image.uuid ] = { + uuid: image.uuid, + url: getDataURL( image ) + }; -/** - * @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) { + output.image = image.uuid; - Object.defineProperty(this, 'id', {value: THREE.TextureIdCount++}); + } - this.uuid = THREE.Math.generateUUID(); + meta.textures[ this.uuid ] = output; - this.name = ''; - this.sourceFile = ''; + return output; - this.image = image !== undefined ? image : THREE.Texture.DEFAULT_IMAGE; - this.mipmaps = []; + }, - this.mapping = mapping !== undefined ? mapping : THREE.Texture.DEFAULT_MAPPING; + dispose: function () { - this.wrapS = wrapS !== undefined ? wrapS : THREE.ClampToEdgeWrapping; - this.wrapT = wrapT !== undefined ? wrapT : THREE.ClampToEdgeWrapping; + this.dispatchEvent( { type: 'dispose' } ); - this.magFilter = magFilter !== undefined ? magFilter : THREE.LinearFilter; - this.minFilter = minFilter !== undefined ? minFilter : THREE.LinearMipMapLinearFilter; + }, - this.anisotropy = anisotropy !== undefined ? anisotropy : 1; + transformUv: function ( uv ) { - this.format = format !== undefined ? format : THREE.RGBAFormat; - this.type = type !== undefined ? type : THREE.UnsignedByteType; + if ( this.mapping !== THREE.UVMapping ) return; - this.offset = new THREE.Vector2(0, 0); - this.repeat = new THREE.Vector2(1, 1); + uv.multiply( this.repeat ); + uv.add( this.offset ); - 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) + if ( uv.x < 0 || uv.x > 1 ) { - this._needsUpdate = false; - this.onUpdate = null; + switch ( this.wrapS ) { -}; + case THREE.RepeatWrapping: -THREE.Texture.DEFAULT_IMAGE = undefined; -THREE.Texture.DEFAULT_MAPPING = THREE.UVMapping; + uv.x = uv.x - Math.floor( uv.x ); + break; -THREE.Texture.prototype = { + case THREE.ClampToEdgeWrapping: - constructor: THREE.Texture, + uv.x = uv.x < 0 ? 0 : 1; + break; - get needsUpdate() { + case THREE.MirroredRepeatWrapping: - return this._needsUpdate; + if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) { - }, + uv.x = Math.ceil( uv.x ) - uv.x; - set needsUpdate(value) { + } else { - if (value === true) this.update(); + uv.x = uv.x - Math.floor( uv.x ); - this._needsUpdate = value; + } + break; - }, + } - clone: function (texture) { + } - if (texture === undefined) texture = new THREE.Texture(); + if ( uv.y < 0 || uv.y > 1 ) { - texture.image = this.image; - texture.mipmaps = this.mipmaps.slice(0); + switch ( this.wrapT ) { - texture.mapping = this.mapping; + case THREE.RepeatWrapping: - texture.wrapS = this.wrapS; - texture.wrapT = this.wrapT; + uv.y = uv.y - Math.floor( uv.y ); + break; - texture.magFilter = this.magFilter; - texture.minFilter = this.minFilter; + case THREE.ClampToEdgeWrapping: - texture.anisotropy = this.anisotropy; + uv.y = uv.y < 0 ? 0 : 1; + break; - texture.format = this.format; - texture.type = this.type; + case THREE.MirroredRepeatWrapping: - texture.offset.copy(this.offset); - texture.repeat.copy(this.repeat); + if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) { - texture.generateMipmaps = this.generateMipmaps; - texture.premultiplyAlpha = this.premultiplyAlpha; - texture.flipY = this.flipY; - texture.unpackAlignment = this.unpackAlignment; + uv.y = Math.ceil( uv.y ) - uv.y; - return texture; + } else { - }, + uv.y = uv.y - Math.floor( uv.y ); - update: function () { + } + break; - this.dispatchEvent({type: 'update'}); + } - }, + } - dispose: function () { + if ( this.flipY ) { - this.dispatchEvent({type: 'dispose'}); + uv.y = 1 - uv.y; - } + } + + } }; -THREE.EventDispatcher.prototype.apply(THREE.Texture.prototype); +THREE.EventDispatcher.prototype.apply( THREE.Texture.prototype ); THREE.TextureIdCount = 0; -// File:src/textures/CubeTexture.js +// File:src/textures/CanvasTexture.js /** * @author mrdoob / http://mrdoob.com/ */ -THREE.CubeTexture = function (images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy) { - - mapping = mapping !== undefined ? mapping : THREE.CubeReflectionMapping; +THREE.CanvasTexture = function ( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { - THREE.Texture.call(this, images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy); + THREE.Texture.call( this, canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); - this.images = images; + this.needsUpdate = true; }; -THREE.CubeTexture.prototype = Object.create(THREE.Texture.prototype); -THREE.CubeTexture.prototype.constructor = THREE.CubeTexture; +THREE.CanvasTexture.prototype = Object.create( THREE.Texture.prototype ); +THREE.CanvasTexture.prototype.constructor = THREE.CanvasTexture; + +// File:src/textures/CubeTexture.js -THREE.CubeTexture.clone = function (texture) { +/** + * @author mrdoob / http://mrdoob.com/ + */ - if (texture === undefined) texture = new THREE.CubeTexture(); +THREE.CubeTexture = function ( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { - THREE.Texture.prototype.clone.call(this, texture); + mapping = mapping !== undefined ? mapping : THREE.CubeReflectionMapping; - texture.images = this.images; + THREE.Texture.call( this, images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); - return texture; + this.images = images; + this.flipY = false; }; +THREE.CubeTexture.prototype = Object.create( THREE.Texture.prototype ); +THREE.CubeTexture.prototype.constructor = THREE.CubeTexture; + +THREE.CubeTexture.prototype.copy = function ( source ) { + + THREE.Texture.prototype.copy.call( this, source ); + + this.images = source.images; + + return this; + +}; // File:src/textures/CompressedTexture.js /** * @author alteredq / http://alteredqualia.com/ */ -THREE.CompressedTexture = function (mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy) { +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); + THREE.Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); - this.image = {width: width, height: height}; - this.mipmaps = mipmaps; + this.image = { width: width, height: height }; + this.mipmaps = mipmaps; - // no flipping for cube textures - // (also flipping doesn't work for compressed textures ) + // no flipping for cube textures + // (also flipping doesn't work for compressed textures ) - this.flipY = false; + this.flipY = false; - // can't generate mipmaps for compressed textures - // mips must be embedded in DDS files + // can't generate mipmaps for compressed textures + // mips must be embedded in DDS files - this.generateMipmaps = false; + this.generateMipmaps = false; }; -THREE.CompressedTexture.prototype = Object.create(THREE.Texture.prototype); +THREE.CompressedTexture.prototype = Object.create( THREE.Texture.prototype ); THREE.CompressedTexture.prototype.constructor = THREE.CompressedTexture; -THREE.CompressedTexture.prototype.clone = function () { - - var texture = new THREE.CompressedTexture(); - - THREE.Texture.prototype.clone.call(this, texture); - - return texture; - -}; - // File:src/textures/DataTexture.js /** * @author alteredq / http://alteredqualia.com/ */ -THREE.DataTexture = function (data, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy) { +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 ); - THREE.Texture.call(this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy); + this.image = { data: data, width: width, height: height }; - this.image = {data: data, width: width, height: height}; + this.magFilter = magFilter !== undefined ? magFilter : THREE.NearestFilter; + this.minFilter = minFilter !== undefined ? minFilter : THREE.NearestFilter; + + this.flipY = false; + this.generateMipmaps = false; }; -THREE.DataTexture.prototype = Object.create(THREE.Texture.prototype); +THREE.DataTexture.prototype = Object.create( THREE.Texture.prototype ); THREE.DataTexture.prototype.constructor = THREE.DataTexture; -THREE.DataTexture.prototype.clone = function () { - - var texture = new THREE.DataTexture(); - - THREE.Texture.prototype.clone.call(this, texture); - - return texture; - -}; - // File:src/textures/VideoTexture.js /** * @author mrdoob / http://mrdoob.com/ */ -THREE.VideoTexture = function (video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy) { +THREE.VideoTexture = function ( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { - THREE.Texture.call(this, video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy); + THREE.Texture.call( this, video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); - this.generateMipmaps = false; + this.generateMipmaps = false; - var scope = this; + var scope = this; - var update = function () { + var update = function () { - requestAnimationFrame(update); + requestAnimationFrame( update ); - if (video.readyState === video.HAVE_ENOUGH_DATA) { + if ( video.readyState === video.HAVE_ENOUGH_DATA ) { - scope.needsUpdate = true; + scope.needsUpdate = true; - } + } - }; + }; - update(); + update(); }; -THREE.VideoTexture.prototype = Object.create(THREE.Texture.prototype); +THREE.VideoTexture.prototype = Object.create( THREE.Texture.prototype ); THREE.VideoTexture.prototype.constructor = THREE.VideoTexture; // File:src/textures/DepthTexture.js @@ -16072,30 +18492,30 @@ THREE.VideoTexture.prototype.constructor = THREE.VideoTexture; * @author Marius Kintel / https://github.com/kintel */ -THREE.DepthTexture = function (width, height, hasStencil) { +THREE.DepthTexture = function ( width, height, hasStencil ) { - var format = hasStencil ? THREE.DepthStencilFormat : THREE.DepthFormat; - var type = hasStencil ? THREE.UnsignedInt24_8Type : THREE.UnsignedIntType; + var format = hasStencil ? THREE.DepthStencilFormat : THREE.DepthFormat; + var type = hasStencil ? THREE.UnsignedInt24_8Type : THREE.UnsignedIntType; - THREE.Texture.call(this, null, THREE.Texture.DEFAULT_MAPPING, THREE.ClampToEdgeWrapping, THREE.ClampToEdgeWrapping, THREE.NearestFilter, THREE.NearestFilter, format, type); + THREE.Texture.call( this, null, THREE.Texture.DEFAULT_MAPPING, THREE.ClampToEdgeWrapping, THREE.ClampToEdgeWrapping, THREE.NearestFilter, THREE.NearestFilter, format, type ); - this.width = width; - this.height = height; + this.width = width; + this.height = height; }; -THREE.DepthTexture.prototype = Object.create(THREE.Texture.prototype); +THREE.DepthTexture.prototype = Object.create( THREE.Texture.prototype ); THREE.DepthTexture.prototype.clone = function () { - var texture = new THREE.DepthTexture(); + var texture = new THREE.DepthTexture(); - THREE.Texture.prototype.clone.call(this, texture); + THREE.Texture.prototype.clone.call( this, texture ); - texture.width = this.width; - texture.height = this.height; + texture.width = this.width; + texture.height = this.height; - return texture; + return texture; }; @@ -16107,199 +18527,182 @@ THREE.DepthTexture.prototype.clone = function () { THREE.Group = function () { - THREE.Object3D.call(this); + THREE.Object3D.call( this ); - this.type = 'Group'; + this.type = 'Group'; }; -THREE.Group.prototype = Object.create(THREE.Object3D.prototype); +THREE.Group.prototype = Object.create( THREE.Object3D.prototype ); THREE.Group.prototype.constructor = THREE.Group; - -// File:src/objects/PointCloud.js +// File:src/objects/Points.js /** * @author alteredq / http://alteredqualia.com/ */ -THREE.PointCloud = function (geometry, material) { +THREE.Points = function ( geometry, material ) { - THREE.Object3D.call(this); + THREE.Object3D.call( this ); - this.type = 'PointCloud'; + this.type = 'Points'; - this.geometry = geometry !== undefined ? geometry : new THREE.Geometry(); - this.material = material !== undefined ? material : new THREE.PointCloudMaterial({color: Math.random() * 0xffffff}); + this.geometry = geometry !== undefined ? geometry : new THREE.Geometry(); + this.material = material !== undefined ? material : new THREE.PointsMaterial( { color: Math.random() * 0xffffff } ); }; -THREE.PointCloud.prototype = Object.create(THREE.Object3D.prototype); -THREE.PointCloud.prototype.constructor = THREE.PointCloud; - -THREE.PointCloud.prototype.raycast = ( function () { +THREE.Points.prototype = Object.create( THREE.Object3D.prototype ); +THREE.Points.prototype.constructor = THREE.Points; - var inverseMatrix = new THREE.Matrix4(); - var ray = new THREE.Ray(); +THREE.Points.prototype.raycast = ( function () { - return function (raycaster, intersects) { + var inverseMatrix = new THREE.Matrix4(); + var ray = new THREE.Ray(); - var object = this; - var geometry = object.geometry; - var threshold = raycaster.params.PointCloud.threshold; + return function raycast( raycaster, intersects ) { - inverseMatrix.getInverse(this.matrixWorld); - ray.copy(raycaster.ray).applyMatrix4(inverseMatrix); + var object = this; + var geometry = object.geometry; + var threshold = raycaster.params.Points.threshold; - if (geometry.boundingBox !== null) { + inverseMatrix.getInverse( this.matrixWorld ); + ray.copy( raycaster.ray ).applyMatrix4( inverseMatrix ); - if (ray.isIntersectionBox(geometry.boundingBox) === false) { + if ( geometry.boundingBox !== null ) { - return; + if ( ray.isIntersectionBox( geometry.boundingBox ) === false ) { - } + return; - } + } - var localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); - var position = new THREE.Vector3(); + } - var testPoint = function (point, index) { + var localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); + var localThresholdSq = localThreshold * localThreshold; + var position = new THREE.Vector3(); - var rayPointDistance = ray.distanceToPoint(point); + function testPoint( point, index ) { - if (rayPointDistance < localThreshold) { + var rayPointDistanceSq = ray.distanceSqToPoint( point ); - var intersectPoint = ray.closestPointToPoint(point); - intersectPoint.applyMatrix4(object.matrixWorld); + if ( rayPointDistanceSq < localThresholdSq ) { - var distance = raycaster.ray.origin.distanceTo(intersectPoint); + var intersectPoint = ray.closestPointToPoint( point ); + intersectPoint.applyMatrix4( object.matrixWorld ); - intersects.push({ + var distance = raycaster.ray.origin.distanceTo( intersectPoint ); - distance: distance, - distanceToRay: rayPointDistance, - point: intersectPoint.clone(), - index: index, - face: null, - object: object + if ( distance < raycaster.near || distance > raycaster.far ) return; - }); + intersects.push( { - } + distance: distance, + distanceToRay: Math.sqrt( rayPointDistanceSq ), + point: intersectPoint.clone(), + index: index, + face: null, + object: object - }; + } ); - if (geometry instanceof THREE.BufferGeometry) { + } - var attributes = geometry.attributes; - var positions = attributes.position.array; + } - if (attributes.index !== undefined) { + if ( geometry instanceof THREE.BufferGeometry ) { - var indices = attributes.index.array; - var offsets = geometry.offsets; + var index = geometry.index; + var attributes = geometry.attributes; + var positions = attributes.position.array; - if (offsets.length === 0) { + if ( index !== null ) { - var offset = { - start: 0, - count: indices.length, - index: 0 - }; + var indices = index.array; - offsets = [offset]; + for ( var i = 0, il = indices.length; i < il; i ++ ) { - } + var a = indices[ i ]; - for (var oi = 0, ol = offsets.length; oi < ol; ++oi) { + position.fromArray( positions, a * 3 ); - var start = offsets[oi].start; - var count = offsets[oi].count; - var index = offsets[oi].index; + testPoint( position, a ); - for (var i = start, il = start + count; i < il; i++) { + } - var a = index + indices[i]; + } else { - position.fromArray(positions, a * 3); + for ( var i = 0, l = positions.length / 3; i < l; i ++ ) { - testPoint(position, a); + position.fromArray( positions, i * 3 ); - } + testPoint( position, i ); - } + } - } else { + } - var pointCount = positions.length / 3; + } else { - for (var i = 0; i < pointCount; i++) { + var vertices = geometry.vertices; - position.set( - positions[3 * i], - positions[3 * i + 1], - positions[3 * i + 2] - ); + for ( var i = 0, l = vertices.length; i < l; i ++ ) { - testPoint(position, i); + testPoint( vertices[ i ], i ); - } + } - } + } - } else { + }; - var vertices = this.geometry.vertices; +}() ); - for (var i = 0; i < vertices.length; i++) { +THREE.Points.prototype.clone = function () { - testPoint(vertices[i], i); + return new this.constructor( this.geometry, this.material ).copy( this ); - } +}; - } +THREE.Points.prototype.toJSON = function ( meta ) { - }; + var data = THREE.Object3D.prototype.toJSON.call( this, meta ); -}() ); + // only serialize if not in meta geometries cache + if ( meta.geometries[ this.geometry.uuid ] === undefined ) { -THREE.PointCloud.prototype.clone = function (object) { + meta.geometries[ this.geometry.uuid ] = this.geometry.toJSON(); - if (object === undefined) object = new THREE.PointCloud(this.geometry, this.material); + } - THREE.Object3D.prototype.clone.call(this, object); + // only serialize if not in meta materials cache + if ( meta.materials[ this.material.uuid ] === undefined ) { - return object; + meta.materials[ this.material.uuid ] = this.material.toJSON(); -}; + } -THREE.PointCloud.prototype.toJSON = function (meta) { + data.object.geometry = this.geometry.uuid; + data.object.material = this.material.uuid; - var data = THREE.Object3D.prototype.toJSON.call(this, meta); + return data; - // only serialize if not in meta geometries cache - if (meta.geometries[this.geometry.uuid] === undefined) { - meta.geometries[this.geometry.uuid] = this.geometry.toJSON(); - } +}; - // only serialize if not in meta materials cache - if (meta.materials[this.material.uuid] === undefined) { - meta.materials[this.material.uuid] = this.material.toJSON(); - } +// Backwards compatibility - data.object.geometry = this.geometry.uuid; - data.object.material = this.material.uuid; +THREE.PointCloud = function ( geometry, material ) { - return data; + console.warn( 'THREE.PointCloud has been renamed to THREE.Points.' ); + return new THREE.Points( geometry, material ); }; -// Backwards compatibility - -THREE.ParticleSystem = function (geometry, material) { +THREE.ParticleSystem = function ( geometry, material ) { - THREE.warn('THREE.ParticleSystem has been renamed to THREE.PointCloud.'); - return new THREE.PointCloud(geometry, material); + console.warn( 'THREE.ParticleSystem has been renamed to THREE.Points.' ); + return new THREE.Points( geometry, material ); }; @@ -16309,219 +18712,232 @@ THREE.ParticleSystem = function (geometry, material) { * @author mrdoob / http://mrdoob.com/ */ -THREE.Line = function (geometry, material, mode) { +THREE.Line = function ( geometry, material, mode ) { - THREE.Object3D.call(this); + if ( mode === 1 ) { - this.type = 'Line'; + console.warn( 'THREE.Line: parameter THREE.LinePieces no longer supported. Created THREE.LineSegments instead.' ); + return new THREE.LineSegments( geometry, material ); - this.geometry = geometry !== undefined ? geometry : new THREE.Geometry(); - this.material = material !== undefined ? material : new THREE.LineBasicMaterial({color: Math.random() * 0xffffff}); + } - this.mode = mode !== undefined ? mode : THREE.LineStrip; + THREE.Object3D.call( this ); -}; + this.type = 'Line'; -THREE.LineStrip = 0; -THREE.LinePieces = 1; + this.geometry = geometry !== undefined ? geometry : new THREE.Geometry(); + this.material = material !== undefined ? material : new THREE.LineBasicMaterial( { color: Math.random() * 0xffffff } ); + +}; -THREE.Line.prototype = Object.create(THREE.Object3D.prototype); +THREE.Line.prototype = Object.create( THREE.Object3D.prototype ); THREE.Line.prototype.constructor = THREE.Line; THREE.Line.prototype.raycast = ( function () { - var inverseMatrix = new THREE.Matrix4(); - var ray = new THREE.Ray(); - var sphere = new THREE.Sphere(); + var inverseMatrix = new THREE.Matrix4(); + var ray = new THREE.Ray(); + var sphere = new THREE.Sphere(); - return function (raycaster, intersects) { + return function raycast( raycaster, intersects ) { - var precision = raycaster.linePrecision; - var precisionSq = precision * precision; + var precision = raycaster.linePrecision; + var precisionSq = precision * precision; - var geometry = this.geometry; + var geometry = this.geometry; - if (geometry.boundingSphere === null) geometry.computeBoundingSphere(); + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - // Checking boundingSphere distance to ray + // Checking boundingSphere distance to ray - sphere.copy(geometry.boundingSphere); - sphere.applyMatrix4(this.matrixWorld); + sphere.copy( geometry.boundingSphere ); + sphere.applyMatrix4( this.matrixWorld ); - if (raycaster.ray.isIntersectionSphere(sphere) === false) { + if ( raycaster.ray.isIntersectionSphere( sphere ) === false ) { - return; + return; - } + } - inverseMatrix.getInverse(this.matrixWorld); - ray.copy(raycaster.ray).applyMatrix4(inverseMatrix); + inverseMatrix.getInverse( this.matrixWorld ); + ray.copy( raycaster.ray ).applyMatrix4( inverseMatrix ); - var vStart = new THREE.Vector3(); - var vEnd = new THREE.Vector3(); - var interSegment = new THREE.Vector3(); - var interRay = new THREE.Vector3(); - var step = this.mode === THREE.LineStrip ? 1 : 2; + var vStart = new THREE.Vector3(); + var vEnd = new THREE.Vector3(); + var interSegment = new THREE.Vector3(); + var interRay = new THREE.Vector3(); + var step = this instanceof THREE.LineSegments ? 2 : 1; - if (geometry instanceof THREE.BufferGeometry) { + if ( geometry instanceof THREE.BufferGeometry ) { - var attributes = geometry.attributes; + var index = geometry.index; + var attributes = geometry.attributes; - if (attributes.index !== undefined) { + if ( index !== null ) { - var indices = attributes.index.array; - var positions = attributes.position.array; - var offsets = geometry.offsets; + var indices = index.array; + var positions = attributes.position.array; - if (offsets.length === 0) { + for ( var i = 0, l = indices.length - 1; i < l; i += step ) { - offsets = [{start: 0, count: indices.length, index: 0}]; + var a = indices[ i ]; + var b = indices[ i + 1 ]; - } + vStart.fromArray( positions, a * 3 ); + vEnd.fromArray( positions, b * 3 ); - for (var oi = 0; oi < offsets.length; oi++) { + var distSq = ray.distanceSqToSegment( vStart, vEnd, interRay, interSegment ); - var start = offsets[oi].start; - var count = offsets[oi].count; - var index = offsets[oi].index; + if ( distSq > precisionSq ) continue; - for (var i = start; i < start + count - 1; i += step) { + interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation - var a = index + indices[i]; - var b = index + indices[i + 1]; + var distance = raycaster.ray.origin.distanceTo( interRay ); - vStart.fromArray(positions, a * 3); - vEnd.fromArray(positions, b * 3); + if ( distance < raycaster.near || distance > raycaster.far ) continue; - var distSq = ray.distanceSqToSegment(vStart, vEnd, interRay, interSegment); + intersects.push( { - if (distSq > precisionSq) continue; + distance: distance, + // What do we want? intersection point on the ray or on the segment?? + // point: raycaster.ray.at( distance ), + point: interSegment.clone().applyMatrix4( this.matrixWorld ), + index: i, + face: null, + faceIndex: null, + object: this - var distance = ray.origin.distanceTo(interRay); + } ); - if (distance < raycaster.near || distance > raycaster.far) continue; + } - intersects.push({ + } else { - distance: distance, - // What do we want? intersection point on the ray or on the segment?? - // point: raycaster.ray.at( distance ), - point: interSegment.clone().applyMatrix4(this.matrixWorld), - index: i, - offsetIndex: oi, - face: null, - faceIndex: null, - object: this + var positions = attributes.position.array; - }); + for ( var i = 0, l = positions.length / 3 - 1; i < l; i += step ) { - } + vStart.fromArray( positions, 3 * i ); + vEnd.fromArray( positions, 3 * i + 3 ); - } + var distSq = ray.distanceSqToSegment( vStart, vEnd, interRay, interSegment ); - } else { + if ( distSq > precisionSq ) continue; - var positions = attributes.position.array; + interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation - for (var i = 0; i < positions.length / 3 - 1; i += step) { + var distance = raycaster.ray.origin.distanceTo( interRay ); - vStart.fromArray(positions, 3 * i); - vEnd.fromArray(positions, 3 * i + 3); + if ( distance < raycaster.near || distance > raycaster.far ) continue; - var distSq = ray.distanceSqToSegment(vStart, vEnd, interRay, interSegment); + intersects.push( { - if (distSq > precisionSq) continue; + distance: distance, + // What do we want? intersection point on the ray or on the segment?? + // point: raycaster.ray.at( distance ), + point: interSegment.clone().applyMatrix4( this.matrixWorld ), + index: i, + face: null, + faceIndex: null, + object: this - var distance = ray.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(this.matrixWorld), - index: i, - face: null, - faceIndex: null, - object: this + } else if ( geometry instanceof THREE.Geometry ) { - }); + var vertices = geometry.vertices; + var nbVertices = vertices.length; - } + for ( var i = 0; i < nbVertices - 1; i += step ) { - } + var distSq = ray.distanceSqToSegment( vertices[ i ], vertices[ i + 1 ], interRay, interSegment ); - } else if (geometry instanceof THREE.Geometry) { + if ( distSq > precisionSq ) continue; - var vertices = geometry.vertices; - var nbVertices = vertices.length; + interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation - for (var i = 0; i < nbVertices - 1; i += step) { + var distance = raycaster.ray.origin.distanceTo( interRay ); - var distSq = ray.distanceSqToSegment(vertices[i], vertices[i + 1], interRay, interSegment); + if ( distance < raycaster.near || distance > raycaster.far ) continue; - if (distSq > precisionSq) continue; + intersects.push( { - var distance = ray.origin.distanceTo(interRay); + distance: distance, + // What do we want? intersection point on the ray or on the segment?? + // point: raycaster.ray.at( distance ), + point: interSegment.clone().applyMatrix4( this.matrixWorld ), + index: i, + face: null, + faceIndex: null, + object: this - 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(this.matrixWorld), - index: i, - face: null, - faceIndex: null, - object: this + } - }); + }; - } +}() ); - } +THREE.Line.prototype.clone = function () { - }; + return new this.constructor( this.geometry, this.material ).copy( this ); -}() ); +}; + +THREE.Line.prototype.toJSON = function ( meta ) { + + var data = THREE.Object3D.prototype.toJSON.call( this, meta ); + + // only serialize if not in meta geometries cache + if ( meta.geometries[ this.geometry.uuid ] === undefined ) { + + meta.geometries[ this.geometry.uuid ] = this.geometry.toJSON(); -THREE.Line.prototype.clone = function (object) { + } - if (object === undefined) object = new THREE.Line(this.geometry, this.material, this.mode); + // only serialize if not in meta materials cache + if ( meta.materials[ this.material.uuid ] === undefined ) { - THREE.Object3D.prototype.clone.call(this, object); + meta.materials[ this.material.uuid ] = this.material.toJSON(); - return object; + } + + data.object.geometry = this.geometry.uuid; + data.object.material = this.material.uuid; + + return data; }; -THREE.Line.prototype.toJSON = function (meta) { +// DEPRECATED - var data = THREE.Object3D.prototype.toJSON.call(this, meta); +THREE.LineStrip = 0; +THREE.LinePieces = 1; - data.object.mode = this.mode; +// File:src/objects/LineSegments.js - // only serialize if not in meta geometries cache - if (meta.geometries[this.geometry.uuid] === undefined) { - meta.geometries[this.geometry.uuid] = this.geometry.toJSON(); - } +/** + * @author mrdoob / http://mrdoob.com/ + */ - // only serialize if not in meta materials cache - if (meta.materials[this.material.uuid] === undefined) { - meta.materials[this.material.uuid] = this.material.toJSON(); - } +THREE.LineSegments = function ( geometry, material ) { - data.object.geometry = this.geometry.uuid; - data.object.material = this.material.uuid; + THREE.Line.call( this, geometry, material ); - return data; + this.type = 'LineSegments'; }; +THREE.LineSegments.prototype = Object.create( THREE.Line.prototype ); +THREE.LineSegments.prototype.constructor = THREE.LineSegments; + // File:src/objects/Mesh.js /** @@ -16531,343 +18947,378 @@ THREE.Line.prototype.toJSON = function (meta) { * @author jonobr1 / http://jonobr1.com/ */ -THREE.Mesh = function (geometry, material) { +THREE.Mesh = function ( geometry, material ) { - THREE.Object3D.call(this); + THREE.Object3D.call( this ); - this.type = 'Mesh'; + this.type = 'Mesh'; - this.geometry = geometry !== undefined ? geometry : new THREE.Geometry(); - this.material = material !== undefined ? material : new THREE.MeshBasicMaterial({color: Math.random() * 0xffffff}); + this.geometry = geometry !== undefined ? geometry : new THREE.Geometry(); + this.material = material !== undefined ? material : new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff } ); - this.updateMorphTargets(); + this.updateMorphTargets(); }; -THREE.Mesh.prototype = Object.create(THREE.Object3D.prototype); +THREE.Mesh.prototype = Object.create( THREE.Object3D.prototype ); THREE.Mesh.prototype.constructor = THREE.Mesh; THREE.Mesh.prototype.updateMorphTargets = function () { - if (this.geometry.morphTargets !== undefined && this.geometry.morphTargets.length > 0) { + if ( this.geometry.morphTargets !== undefined && this.geometry.morphTargets.length > 0 ) { - this.morphTargetBase = -1; - this.morphTargetForcedOrder = []; - this.morphTargetInfluences = []; - this.morphTargetDictionary = {}; + this.morphTargetBase = - 1; + this.morphTargetInfluences = []; + this.morphTargetDictionary = {}; - for (var m = 0, ml = this.geometry.morphTargets.length; m < ml; m++) { + for ( var m = 0, ml = this.geometry.morphTargets.length; m < ml; m ++ ) { - this.morphTargetInfluences.push(0); - this.morphTargetDictionary[this.geometry.morphTargets[m].name] = m; + this.morphTargetInfluences.push( 0 ); + this.morphTargetDictionary[ this.geometry.morphTargets[ m ].name ] = m; - } + } - } + } }; -THREE.Mesh.prototype.getMorphTargetIndexByName = function (name) { +THREE.Mesh.prototype.getMorphTargetIndexByName = function ( name ) { - if (this.morphTargetDictionary[name] !== undefined) { + if ( this.morphTargetDictionary[ name ] !== undefined ) { - return this.morphTargetDictionary[name]; + return this.morphTargetDictionary[ name ]; - } + } - THREE.warn('THREE.Mesh.getMorphTargetIndexByName: morph target ' + name + ' does not exist. Returning 0.'); + console.warn( 'THREE.Mesh.getMorphTargetIndexByName: morph target ' + name + ' does not exist. Returning 0.' ); - return 0; + return 0; }; THREE.Mesh.prototype.raycast = ( function () { - var inverseMatrix = new THREE.Matrix4(); - var ray = new THREE.Ray(); - var sphere = new THREE.Sphere(); + var inverseMatrix = new THREE.Matrix4(); + var ray = new THREE.Ray(); + var sphere = new THREE.Sphere(); - var vA = new THREE.Vector3(); - var vB = new THREE.Vector3(); - var vC = new THREE.Vector3(); + var vA = new THREE.Vector3(); + var vB = new THREE.Vector3(); + var vC = new THREE.Vector3(); - return function (raycaster, intersects) { + var tempA = new THREE.Vector3(); + var tempB = new THREE.Vector3(); + var tempC = new THREE.Vector3(); - var geometry = this.geometry; + var uvA = new THREE.Vector2(); + var uvB = new THREE.Vector2(); + var uvC = new THREE.Vector2(); - // Checking boundingSphere distance to ray + var barycoord = new THREE.Vector3(); - if (geometry.boundingSphere === null) geometry.computeBoundingSphere(); + var intersectionPoint = new THREE.Vector3(); + var intersectionPointWorld = new THREE.Vector3(); - sphere.copy(geometry.boundingSphere); - sphere.applyMatrix4(this.matrixWorld); + function uvIntersection( point, p1, p2, p3, uv1, uv2, uv3 ) { - if (raycaster.ray.isIntersectionSphere(sphere) === false) { + THREE.Triangle.barycoordFromPoint( point, p1, p2, p3, barycoord ); - return; + uv1.multiplyScalar( barycoord.x ); + uv2.multiplyScalar( barycoord.y ); + uv3.multiplyScalar( barycoord.z ); - } + uv1.add( uv2 ).add( uv3 ); - // Check boundingBox before continuing + return uv1.clone(); - inverseMatrix.getInverse(this.matrixWorld); - ray.copy(raycaster.ray).applyMatrix4(inverseMatrix); + } - if (geometry.boundingBox !== null) { + return function raycast( raycaster, intersects ) { - if (ray.isIntersectionBox(geometry.boundingBox) === false) { + var geometry = this.geometry; + var material = this.material; - return; + if ( material === undefined ) return; - } + // Checking boundingSphere distance to ray - } + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - if (geometry instanceof THREE.BufferGeometry) { + sphere.copy( geometry.boundingSphere ); + sphere.applyMatrix4( this.matrixWorld ); - var material = this.material; + if ( raycaster.ray.isIntersectionSphere( sphere ) === false ) { - if (material === undefined) return; + return; - var attributes = geometry.attributes; + } - var a, b, c; - var precision = raycaster.precision; + // Check boundingBox before continuing - if (attributes.index !== undefined) { + inverseMatrix.getInverse( this.matrixWorld ); + ray.copy( raycaster.ray ).applyMatrix4( inverseMatrix ); - var indices = attributes.index.array; - var positions = attributes.position.array; - var offsets = geometry.offsets; + if ( geometry.boundingBox !== null ) { - if (offsets.length === 0) { + if ( ray.isIntersectionBox( geometry.boundingBox ) === false ) { - offsets = [{start: 0, count: indices.length, index: 0}]; + return; - } + } - 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; + var a, b, c; - for (var i = start, il = start + count; i < il; i += 3) { + if ( geometry instanceof THREE.BufferGeometry ) { - a = index + indices[i]; - b = index + indices[i + 1]; - c = index + indices[i + 2]; + var index = geometry.index; + var attributes = geometry.attributes; - vA.fromArray(positions, a * 3); - vB.fromArray(positions, b * 3); - vC.fromArray(positions, c * 3); + if ( index !== null ) { - if (material.side === THREE.BackSide) { + var indices = index.array; + var positions = attributes.position.array; - var intersectionPoint = ray.intersectTriangle(vC, vB, vA, true); + for ( var i = 0, l = indices.length; i < l; i += 3 ) { - } else { + a = indices[ i ]; + b = indices[ i + 1 ]; + c = indices[ i + 2 ]; - var intersectionPoint = ray.intersectTriangle(vA, vB, vC, material.side !== THREE.DoubleSide); + vA.fromArray( positions, a * 3 ); + vB.fromArray( positions, b * 3 ); + vC.fromArray( positions, c * 3 ); - } + if ( material.side === THREE.BackSide ) { - if (intersectionPoint === null) continue; + if ( ray.intersectTriangle( vC, vB, vA, true, intersectionPoint ) === null ) continue; - intersectionPoint.applyMatrix4(this.matrixWorld); + } else { - var distance = raycaster.ray.origin.distanceTo(intersectionPoint); + if ( ray.intersectTriangle( vA, vB, vC, material.side !== THREE.DoubleSide, intersectionPoint ) === null ) continue; - if (distance < precision || distance < raycaster.near || distance > raycaster.far) continue; + } - intersects.push({ + intersectionPointWorld.copy( intersectionPoint ); + intersectionPointWorld.applyMatrix4( this.matrixWorld ); - distance: distance, - point: intersectionPoint, - face: new THREE.Face3(a, b, c, THREE.Triangle.normal(vA, vB, vC)), - faceIndex: null, - object: this + var distance = raycaster.ray.origin.distanceTo( intersectionPointWorld ); - }); + if ( distance < raycaster.near || distance > raycaster.far ) continue; - } + var uv; - } + if ( attributes.uv !== undefined ) { - } else { + var uvs = attributes.uv.array; + uvA.fromArray( uvs, a * 2 ); + uvB.fromArray( uvs, b * 2 ); + uvC.fromArray( uvs, c * 2 ); + uv = uvIntersection( intersectionPoint, vA, vB, vC, uvA, uvB, uvC ); - var positions = attributes.position.array; + } - for (var i = 0, j = 0, il = positions.length; i < il; i += 3, j += 9) { + intersects.push( { - a = i; - b = i + 1; - c = i + 2; + distance: distance, + point: intersectionPointWorld.clone(), + uv: uv, + face: new THREE.Face3( a, b, c, THREE.Triangle.normal( vA, vB, vC ) ), + faceIndex: Math.floor( i / 3 ), // triangle number in indices buffer semantics + object: this - vA.fromArray(positions, j); - vB.fromArray(positions, j + 3); - vC.fromArray(positions, j + 6); + } ); - if (material.side === THREE.BackSide) { + } - var intersectionPoint = ray.intersectTriangle(vC, vB, vA, true); + } else { - } else { + var positions = attributes.position.array; - var intersectionPoint = ray.intersectTriangle(vA, vB, vC, material.side !== THREE.DoubleSide); + for ( var i = 0, l = positions.length; i < l; i += 9 ) { - } + vA.fromArray( positions, i ); + vB.fromArray( positions, i + 3 ); + vC.fromArray( positions, i + 6 ); - if (intersectionPoint === null) continue; + if ( material.side === THREE.BackSide ) { - intersectionPoint.applyMatrix4(this.matrixWorld); + if ( ray.intersectTriangle( vC, vB, vA, true, intersectionPoint ) === null ) continue; - var distance = raycaster.ray.origin.distanceTo(intersectionPoint); + } else { - if (distance < precision || distance < raycaster.near || distance > raycaster.far) continue; + if ( ray.intersectTriangle( vA, vB, vC, material.side !== THREE.DoubleSide, intersectionPoint ) === null ) continue; - intersects.push({ + } - distance: distance, - point: intersectionPoint, - face: new THREE.Face3(a, b, c, THREE.Triangle.normal(vA, vB, vC)), - faceIndex: null, - object: this + intersectionPointWorld.copy( intersectionPoint ); + intersectionPointWorld.applyMatrix4( this.matrixWorld ); - }); + var distance = raycaster.ray.origin.distanceTo( intersectionPointWorld ); - } + if ( distance < raycaster.near || distance > raycaster.far ) continue; - } + var uv; - } else if (geometry instanceof THREE.Geometry) { + if ( attributes.uv !== undefined ) { - var isFaceMaterial = this.material instanceof THREE.MeshFaceMaterial; - var objectMaterials = isFaceMaterial === true ? this.material.materials : null; + var uvs = attributes.uv.array; + uvA.fromArray( uvs, i ); + uvB.fromArray( uvs, i + 2 ); + uvC.fromArray( uvs, i + 4 ); + uv = uvIntersection( intersectionPoint, vA, vB, vC, uvA, uvB, uvC ); - var a, b, c; - var precision = raycaster.precision; + } - var vertices = geometry.vertices; + a = i / 3; + b = a + 1; + c = a + 2; - for (var f = 0, fl = geometry.faces.length; f < fl; f++) { + intersects.push( { - var face = geometry.faces[f]; + distance: distance, + point: intersectionPointWorld.clone(), + uv: uv, + face: new THREE.Face3( a, b, c, THREE.Triangle.normal( vA, vB, vC ) ), + index: a, // triangle number in positions buffer semantics + object: this - var material = isFaceMaterial === true ? objectMaterials[face.materialIndex] : this.material; + } ); - if (material === undefined) continue; + } - a = vertices[face.a]; - b = vertices[face.b]; - c = vertices[face.c]; + } - if (material.morphTargets === true) { + } else if ( geometry instanceof THREE.Geometry ) { - var morphTargets = geometry.morphTargets; - var morphInfluences = this.morphTargetInfluences; + var isFaceMaterial = material instanceof THREE.MeshFaceMaterial; + var materials = isFaceMaterial === true ? material.materials : null; - vA.set(0, 0, 0); - vB.set(0, 0, 0); - vC.set(0, 0, 0); + var vertices = geometry.vertices; + var faces = geometry.faces; - for (var t = 0, tl = morphTargets.length; t < tl; t++) { + for ( var f = 0, fl = faces.length; f < fl; f ++ ) { - var influence = morphInfluences[t]; + var face = faces[ f ]; + var faceMaterial = isFaceMaterial === true ? materials[ face.materialIndex ] : material; - if (influence === 0) continue; + if ( faceMaterial === undefined ) continue; - var targets = morphTargets[t].vertices; + a = vertices[ face.a ]; + b = vertices[ face.b ]; + c = vertices[ face.c ]; - 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; + if ( faceMaterial.morphTargets === true ) { - 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; + var morphTargets = geometry.morphTargets; + var morphInfluences = this.morphTargetInfluences; - 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.set( 0, 0, 0 ); + vB.set( 0, 0, 0 ); + vC.set( 0, 0, 0 ); - } + for ( var t = 0, tl = morphTargets.length; t < tl; t ++ ) { - vA.add(a); - vB.add(b); - vC.add(c); + var influence = morphInfluences[ t ]; - a = vA; - b = vB; - c = vC; + if ( influence === 0 ) continue; - } + var targets = morphTargets[ t ].vertices; - if (material.side === THREE.BackSide) { + vA.addScaledVector( tempA.subVectors( targets[ face.a ], a ), influence ); + vB.addScaledVector( tempB.subVectors( targets[ face.b ], b ), influence ); + vC.addScaledVector( tempC.subVectors( targets[ face.c ], c ), influence ); - var intersectionPoint = ray.intersectTriangle(c, b, a, true); + } - } else { + vA.add( a ); + vB.add( b ); + vC.add( c ); - var intersectionPoint = ray.intersectTriangle(a, b, c, material.side !== THREE.DoubleSide); + a = vA; + b = vB; + c = vC; - } + } - if (intersectionPoint === null) continue; + if ( faceMaterial.side === THREE.BackSide ) { - intersectionPoint.applyMatrix4(this.matrixWorld); + if ( ray.intersectTriangle( c, b, a, true, intersectionPoint ) === null ) continue; - var distance = raycaster.ray.origin.distanceTo(intersectionPoint); + } else { - if (distance < precision || distance < raycaster.near || distance > raycaster.far) continue; + if ( ray.intersectTriangle( a, b, c, faceMaterial.side !== THREE.DoubleSide, intersectionPoint ) === null ) continue; - intersects.push({ + } - distance: distance, - point: intersectionPoint, - face: face, - faceIndex: f, - object: this + intersectionPointWorld.copy( intersectionPoint ); + intersectionPointWorld.applyMatrix4( this.matrixWorld ); - }); + var distance = raycaster.ray.origin.distanceTo( intersectionPointWorld ); - } + if ( distance < raycaster.near || distance > raycaster.far ) continue; - } + var uv; - }; + if ( geometry.faceVertexUvs[ 0 ].length > 0 ) { -}() ); + var uvs = geometry.faceVertexUvs[ 0 ][ f ]; + uvA.copy( uvs[ 0 ] ); + uvB.copy( uvs[ 1 ] ); + uvC.copy( uvs[ 2 ] ); + uv = uvIntersection( intersectionPoint, a, b, c, uvA, uvB, uvC ); + + } + + intersects.push( { -THREE.Mesh.prototype.clone = function (object, recursive) { + distance: distance, + point: intersectionPointWorld.clone(), + uv: uv, + face: face, + faceIndex: f, + object: this - if (object === undefined) object = new THREE.Mesh(this.geometry, this.material); + } ); - THREE.Object3D.prototype.clone.call(this, object, recursive); + } - return object; + } + + }; + +}() ); + +THREE.Mesh.prototype.clone = function () { + + return new this.constructor( this.geometry, this.material ).copy( this ); }; -THREE.Mesh.prototype.toJSON = function (meta) { +THREE.Mesh.prototype.toJSON = function ( meta ) { - var data = THREE.Object3D.prototype.toJSON.call(this, meta); + var data = THREE.Object3D.prototype.toJSON.call( this, meta ); - // only serialize if not in meta geometries cache - if (meta.geometries[this.geometry.uuid] === undefined) { - meta.geometries[this.geometry.uuid] = this.geometry.toJSON(); - } + // only serialize if not in meta geometries cache + if ( meta.geometries[ this.geometry.uuid ] === undefined ) { - // only serialize if not in meta materials cache - if (meta.materials[this.material.uuid] === undefined) { - meta.materials[this.material.uuid] = this.material.toJSON(); - } + meta.geometries[ this.geometry.uuid ] = this.geometry.toJSON( meta ); + + } - data.object.geometry = this.geometry.uuid; - data.object.material = this.material.uuid; + // only serialize if not in meta materials cache + if ( meta.materials[ this.material.uuid ] === undefined ) { - return data; + meta.materials[ this.material.uuid ] = this.material.toJSON( meta ); + + } + + data.object.geometry = this.geometry.uuid; + data.object.material = this.material.uuid; + + return data; }; @@ -16879,19 +19330,29 @@ THREE.Mesh.prototype.toJSON = function (meta) { * @author ikerr / http://verold.com */ -THREE.Bone = function (skin) { +THREE.Bone = function ( skin ) { - THREE.Object3D.call(this); + THREE.Object3D.call( this ); - this.type = 'Bone'; + this.type = 'Bone'; - this.skin = skin; + this.skin = skin; }; -THREE.Bone.prototype = Object.create(THREE.Object3D.prototype); +THREE.Bone.prototype = Object.create( THREE.Object3D.prototype ); THREE.Bone.prototype.constructor = THREE.Bone; +THREE.Bone.prototype.copy = function ( source ) { + + THREE.Object3D.prototype.copy.call( this, source ); + + this.skin = source.skin; + + return this; + +}; + // File:src/objects/Skeleton.js /** @@ -16901,180 +19362,175 @@ THREE.Bone.prototype.constructor = THREE.Bone; * @author ikerr / http://verold.com */ -THREE.Skeleton = function (bones, boneInverses, useVertexTexture) { +THREE.Skeleton = function ( bones, boneInverses, useVertexTexture ) { - this.useVertexTexture = useVertexTexture !== undefined ? useVertexTexture : true; + this.useVertexTexture = useVertexTexture !== undefined ? useVertexTexture : true; - this.identityMatrix = new THREE.Matrix4(); + this.identityMatrix = new THREE.Matrix4(); - // copy the bone array + // copy the bone array - bones = bones || []; + bones = bones || []; - this.bones = bones.slice(0); + this.bones = bones.slice( 0 ); - // create a bone texture or an array of floats + // create a bone texture or an array of floats - if (this.useVertexTexture) { + 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) + // layout (1 matrix = 4 pixels) + // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4) + // with 8x8 pixel texture max 16 bones * 4 pixels = (8 * 8) + // 16x16 pixel texture max 64 bones * 4 pixels = (16 * 16) + // 32x32 pixel texture max 256 bones * 4 pixels = (32 * 32) + // 64x64 pixel texture max 1024 bones * 4 pixels = (64 * 64) - var size; + + var size = Math.sqrt( this.bones.length * 4 ); // 4 pixels needed for 1 matrix + size = THREE.Math.nextPowerOfTwo( Math.ceil( size ) ); + size = Math.max( size, 4 ); - if (this.bones.length > 256) - size = 64; - else if (this.bones.length > 64) - size = 32; - else if (this.bones.length > 16) - size = 16; - else - size = 8; + this.boneTextureWidth = size; + this.boneTextureHeight = size; - 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.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 { - } else { + this.boneMatrices = new Float32Array( 16 * this.bones.length ); - this.boneMatrices = new Float32Array(16 * this.bones.length); - - } + } - // use the supplied bone inverses or calculate the inverses + // use the supplied bone inverses or calculate the inverses - if (boneInverses === undefined) { + if ( boneInverses === undefined ) { - this.calculateInverses(); + this.calculateInverses(); - } else { + } else { - if (this.bones.length === boneInverses.length) { + if ( this.bones.length === boneInverses.length ) { - this.boneInverses = boneInverses.slice(0); + this.boneInverses = boneInverses.slice( 0 ); - } else { + } else { - THREE.warn('THREE.Skeleton bonInverses is the wrong length.'); + console.warn( 'THREE.Skeleton bonInverses is the wrong length.' ); - this.boneInverses = []; + this.boneInverses = []; - for (var b = 0, bl = this.bones.length; b < bl; b++) { + for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) { - this.boneInverses.push(new THREE.Matrix4()); + this.boneInverses.push( new THREE.Matrix4() ); - } + } - } + } - } + } }; THREE.Skeleton.prototype.calculateInverses = function () { - this.boneInverses = []; + this.boneInverses = []; - for (var b = 0, bl = this.bones.length; b < bl; b++) { + for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) { - var inverse = new THREE.Matrix4(); + var inverse = new THREE.Matrix4(); - if (this.bones[b]) { + if ( this.bones[ b ] ) { - inverse.getInverse(this.bones[b].matrixWorld); + inverse.getInverse( this.bones[ b ].matrixWorld ); - } + } - this.boneInverses.push(inverse); + this.boneInverses.push( inverse ); - } + } }; THREE.Skeleton.prototype.pose = function () { - var bone; + var bone; - // recover the bind-time world matrices + // recover the bind-time world matrices - for (var b = 0, bl = this.bones.length; b < bl; b++) { + for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) { - bone = this.bones[b]; + bone = this.bones[ b ]; - if (bone) { + if ( bone ) { - bone.matrixWorld.getInverse(this.boneInverses[b]); + bone.matrixWorld.getInverse( this.boneInverses[ b ] ); - } + } - } + } - // compute the local matrices, positions, rotations and scales + // compute the local matrices, positions, rotations and scales - for (var b = 0, bl = this.bones.length; b < bl; b++) { + for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) { - bone = this.bones[b]; + bone = this.bones[ b ]; - if (bone) { + if ( bone ) { - if (bone.parent) { + if ( bone.parent ) { - bone.matrix.getInverse(bone.parent.matrixWorld); - bone.matrix.multiply(bone.matrixWorld); + bone.matrix.getInverse( bone.parent.matrixWorld ); + bone.matrix.multiply( bone.matrixWorld ); - } else { + } else { - bone.matrix.copy(bone.matrixWorld); + bone.matrix.copy( bone.matrixWorld ); - } + } - bone.matrix.decompose(bone.position, bone.quaternion, bone.scale); + bone.matrix.decompose( bone.position, bone.quaternion, bone.scale ); - } + } - } + } }; -THREE.Skeleton.prototype.update = (function () { +THREE.Skeleton.prototype.update = ( function () { - var offsetMatrix = new THREE.Matrix4(); + var offsetMatrix = new THREE.Matrix4(); - return function () { + return function update() { - // flatten bone matrices to array + // flatten bone matrices to array - for (var b = 0, bl = this.bones.length; b < bl; b++) { + for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) { - // compute the offset between the current and the original transform + // compute the offset between the current and the original transform - var matrix = this.bones[b] ? this.bones[b].matrixWorld : this.identityMatrix; + var matrix = this.bones[ b ] ? this.bones[ b ].matrixWorld : this.identityMatrix; - offsetMatrix.multiplyMatrices(matrix, this.boneInverses[b]); - offsetMatrix.flattenToArrayOffset(this.boneMatrices, b * 16); + offsetMatrix.multiplyMatrices( matrix, this.boneInverses[ b ] ); + offsetMatrix.flattenToArrayOffset( this.boneMatrices, b * 16 ); - } + } - if (this.useVertexTexture) { + if ( this.useVertexTexture ) { - this.boneTexture.needsUpdate = true; + this.boneTexture.needsUpdate = true; - } + } - }; + }; -})(); +} )(); +THREE.Skeleton.prototype.clone = function () { + + return new THREE.Skeleton( this.bones, this.boneInverses, this.useVertexTexture ); + +}; // File:src/objects/SkinnedMesh.js @@ -17084,384 +19540,148 @@ THREE.Skeleton.prototype.update = (function () { * @author ikerr / http://verold.com */ -THREE.SkinnedMesh = function (geometry, material, useVertexTexture) { - - THREE.Mesh.call(this, geometry, material); +THREE.SkinnedMesh = function ( geometry, material, useVertexTexture ) { - this.type = 'SkinnedMesh'; + THREE.Mesh.call( this, geometry, material ); - this.bindMode = "attached"; - this.bindMatrix = new THREE.Matrix4(); - this.bindMatrixInverse = new THREE.Matrix4(); + this.type = 'SkinnedMesh'; - // init bones + this.bindMode = "attached"; + this.bindMatrix = new THREE.Matrix4(); + this.bindMatrixInverse = new THREE.Matrix4(); - // TODO: remove bone creation as there is no reason (other than - // convenience) for THREE.SkinnedMesh to do this. + // init bones - var bones = []; + // TODO: remove bone creation as there is no reason (other than + // convenience) for THREE.SkinnedMesh to do this. - if (this.geometry && this.geometry.bones !== undefined) { + var bones = []; - var bone, gbone, p, q, s; + if ( this.geometry && this.geometry.bones !== undefined ) { - for (var b = 0, bl = this.geometry.bones.length; b < bl; ++b) { + var bone, gbone; - gbone = this.geometry.bones[b]; + for ( var b = 0, bl = this.geometry.bones.length; b < bl; ++ b ) { - p = gbone.pos; - q = gbone.rotq; - s = gbone.scl; + gbone = this.geometry.bones[ b ]; - bone = new THREE.Bone(this); - bones.push(bone); + bone = new THREE.Bone( this ); + bones.push( bone ); - bone.name = gbone.name; - bone.position.set(p[0], p[1], p[2]); - bone.quaternion.set(q[0], q[1], q[2], q[3]); + bone.name = gbone.name; + bone.position.fromArray( gbone.pos ); + bone.quaternion.fromArray( gbone.rotq ); + if ( gbone.scl !== undefined ) bone.scale.fromArray( gbone.scl ); - if (s !== undefined) { + } - bone.scale.set(s[0], s[1], s[2]); + for ( var b = 0, bl = this.geometry.bones.length; b < bl; ++ b ) { - } else { + gbone = this.geometry.bones[ b ]; - bone.scale.set(1, 1, 1); + if ( gbone.parent !== - 1 ) { - } + bones[ gbone.parent ].add( bones[ b ] ); - } + } else { - for (var b = 0, bl = this.geometry.bones.length; b < bl; ++b) { + this.add( bones[ b ] ); - gbone = this.geometry.bones[b]; + } - if (gbone.parent !== -1) { + } - bones[gbone.parent].add(bones[b]); + } - } else { + this.normalizeSkinWeights(); - this.add(bones[b]); - - } - - } - - } - - this.normalizeSkinWeights(); - - this.updateMatrixWorld(true); - this.bind(new THREE.Skeleton(bones, undefined, useVertexTexture)); + this.updateMatrixWorld( true ); + this.bind( new THREE.Skeleton( bones, undefined, useVertexTexture ), this.matrixWorld ); }; -THREE.SkinnedMesh.prototype = Object.create(THREE.Mesh.prototype); +THREE.SkinnedMesh.prototype = Object.create( THREE.Mesh.prototype ); THREE.SkinnedMesh.prototype.constructor = THREE.SkinnedMesh; -THREE.SkinnedMesh.prototype.bind = function (skeleton, bindMatrix) { +THREE.SkinnedMesh.prototype.bind = function( skeleton, bindMatrix ) { - this.skeleton = skeleton; + this.skeleton = skeleton; - if (bindMatrix === undefined) { + if ( bindMatrix === undefined ) { - this.updateMatrixWorld(true); + this.updateMatrixWorld( true ); + + this.skeleton.calculateInverses(); - bindMatrix = this.matrixWorld; + bindMatrix = this.matrixWorld; - } + } - this.bindMatrix.copy(bindMatrix); - this.bindMatrixInverse.getInverse(bindMatrix); + this.bindMatrix.copy( bindMatrix ); + this.bindMatrixInverse.getInverse( bindMatrix ); }; THREE.SkinnedMesh.prototype.pose = function () { - this.skeleton.pose(); + this.skeleton.pose(); }; 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.updateMatrixWorld = function (force) { - - THREE.Mesh.prototype.updateMatrixWorld.call(this, true); - - if (this.bindMode === "attached") { - - this.bindMatrixInverse.getInverse(this.matrixWorld); - - } else if (this.bindMode === "detached") { - - this.bindMatrixInverse.getInverse(this.bindMatrix); - - } else { - - THREE.warn('THREE.SkinnedMesh unreckognized bindMode: ' + this.bindMode); - - } - -}; - -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; - -}; - - -// File:src/objects/MorphAnimMesh.js - -/** - * @author alteredq / http://alteredqualia.com/ - */ - -THREE.MorphAnimMesh = function (geometry, material) { - - THREE.Mesh.call(this, geometry, material); - - this.type = 'MorphAnimMesh'; - - // 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.constructor = THREE.MorphAnimMesh; - -THREE.MorphAnimMesh.prototype.setFrameRange = function (start, end) { - - this.startKeyframe = start; - this.endKeyframe = end; - - this.length = this.endKeyframe - this.startKeyframe + 1; + if ( this.geometry instanceof THREE.Geometry ) { -}; - -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]; - - 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 { - - THREE.warn('THREE.MorphAnimMesh: animation[' + label + '] undefined in .playAnimation()'); - - } - -}; - -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) { + for ( var i = 0; i < this.geometry.skinIndices.length; i ++ ) { - this.direction *= -1; + var sw = this.geometry.skinWeights[ i ]; - if (this.time > this.duration) { + var scale = 1.0 / sw.lengthManhattan(); - this.time = this.duration; - this.directionBackwards = true; + if ( scale !== Infinity ) { - } - - 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; - - } + sw.multiplyScalar( scale ); - var mix = ( this.time % frameTime ) / frameTime; + } else { - if (this.directionBackwards) { + sw.set( 1 ); // this will be normalized by the shader anyway - mix = 1 - mix; + } - } + } - this.morphTargetInfluences[this.currentKeyframe] = mix; - this.morphTargetInfluences[this.lastKeyframe] = 1 - mix; + } else { -}; + // skinning weights assumed to be normalized for THREE.BufferGeometry -THREE.MorphAnimMesh.prototype.interpolateTargets = function (a, b, t) { + } - var influences = this.morphTargetInfluences; +}; - for (var i = 0, l = influences.length; i < l; i++) { +THREE.SkinnedMesh.prototype.updateMatrixWorld = function( force ) { - influences[i] = 0; + THREE.Mesh.prototype.updateMatrixWorld.call( this, true ); - } + if ( this.bindMode === "attached" ) { - if (a > -1) influences[a] = 1 - t; - if (b > -1) influences[b] = t; + this.bindMatrixInverse.getInverse( this.matrixWorld ); -}; + } else if ( this.bindMode === "detached" ) { -THREE.MorphAnimMesh.prototype.clone = function (object) { + this.bindMatrixInverse.getInverse( this.bindMatrix ); - if (object === undefined) object = new THREE.MorphAnimMesh(this.geometry, this.material); + } else { - object.duration = this.duration; - object.mirroredLoop = this.mirroredLoop; - object.time = this.time; + console.warn( 'THREE.SkinnedMesh unrecognized bindMode: ' + this.bindMode ); - object.lastKeyframe = this.lastKeyframe; - object.currentKeyframe = this.currentKeyframe; + } - object.direction = this.direction; - object.directionBackwards = this.directionBackwards; +}; - THREE.Mesh.prototype.clone.call(this, object); +THREE.SkinnedMesh.prototype.clone = function() { - return object; + return new this.constructor( this.geometry, this.material, this.useVertexTexture ).copy( this ); }; @@ -17475,125 +19695,172 @@ THREE.MorphAnimMesh.prototype.clone = function (object) { THREE.LOD = function () { - THREE.Object3D.call(this); + THREE.Object3D.call( this ); + + this.type = 'LOD'; + + Object.defineProperties( this, { + levels: { + enumerable: true, + value: [] + }, + objects: { + get: function () { - this.objects = []; + console.warn( 'THREE.LOD: .objects has been renamed to .levels.' ); + return this.levels; + + } + } + } ); }; -THREE.LOD.prototype = Object.create(THREE.Object3D.prototype); +THREE.LOD.prototype = Object.create( THREE.Object3D.prototype ); THREE.LOD.prototype.constructor = THREE.LOD; -THREE.LOD.prototype.addLevel = function (object, distance) { +THREE.LOD.prototype.addLevel = function ( object, distance ) { - if (distance === undefined) distance = 0; + if ( distance === undefined ) distance = 0; - distance = Math.abs(distance); + distance = Math.abs( distance ); - for (var l = 0; l < this.objects.length; l++) { + var levels = this.levels; - if (distance < this.objects[l].distance) { + for ( var l = 0; l < levels.length; l ++ ) { - break; + if ( distance < levels[ l ].distance ) { - } + break; - } + } + + } + + levels.splice( l, 0, { distance: distance, object: object } ); - this.objects.splice(l, 0, {distance: distance, object: object}); - this.add(object); + this.add( object ); }; -THREE.LOD.prototype.getObjectForDistance = function (distance) { +THREE.LOD.prototype.getObjectForDistance = function ( distance ) { - for (var i = 1, l = this.objects.length; i < l; i++) { + var levels = this.levels; - if (distance < this.objects[i].distance) { + for ( var i = 1, l = levels.length; i < l; i ++ ) { - break; + if ( distance < levels[ i ].distance ) { - } + break; - } + } + + } - return this.objects[i - 1].object; + return levels[ i - 1 ].object; }; THREE.LOD.prototype.raycast = ( function () { - var matrixPosition = new THREE.Vector3(); + var matrixPosition = new THREE.Vector3(); - return function (raycaster, intersects) { + return function raycast( raycaster, intersects ) { - matrixPosition.setFromMatrixPosition(this.matrixWorld); + matrixPosition.setFromMatrixPosition( this.matrixWorld ); - var distance = raycaster.ray.origin.distanceTo(matrixPosition); + var distance = raycaster.ray.origin.distanceTo( matrixPosition ); - this.getObjectForDistance(distance).raycast(raycaster, intersects); + this.getObjectForDistance( distance ).raycast( raycaster, intersects ); - }; + }; }() ); THREE.LOD.prototype.update = function () { - var v1 = new THREE.Vector3(); - var v2 = new THREE.Vector3(); + var v1 = new THREE.Vector3(); + var v2 = new THREE.Vector3(); - return function (camera) { + return function update( camera ) { - if (this.objects.length > 1) { + var levels = this.levels; - v1.setFromMatrixPosition(camera.matrixWorld); - v2.setFromMatrixPosition(this.matrixWorld); + if ( levels.length > 1 ) { - var distance = v1.distanceTo(v2); + v1.setFromMatrixPosition( camera.matrixWorld ); + v2.setFromMatrixPosition( this.matrixWorld ); - this.objects[0].object.visible = true; + var distance = v1.distanceTo( v2 ); - for (var i = 1, l = this.objects.length; i < l; i++) { + levels[ 0 ].object.visible = true; - if (distance >= this.objects[i].distance) { + for ( var i = 1, l = levels.length; i < l; i ++ ) { - this.objects[i - 1].object.visible = false; - this.objects[i].object.visible = true; + if ( distance >= levels[ i ].distance ) { - } else { + levels[ i - 1 ].object.visible = false; + levels[ i ].object.visible = true; - break; + } else { - } + break; - } + } - for (; i < l; i++) { + } - this.objects[i].object.visible = false; + for ( ; i < l; i ++ ) { - } + levels[ i ].object.visible = false; - } + } - }; + } + + }; }(); -THREE.LOD.prototype.clone = function (object) { +THREE.LOD.prototype.copy = function ( source ) { - if (object === undefined) object = new THREE.LOD(); + THREE.Object3D.prototype.copy.call( this, source, false ); - THREE.Object3D.prototype.clone.call(this, object); + var levels = source.levels; - 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); - } + for ( var i = 0, l = levels.length; i < l; i ++ ) { - return object; + var level = levels[ i ]; + + this.addLevel( level.object.clone(), level.distance ); + + } + + return this; + +}; + +THREE.LOD.prototype.toJSON = function ( meta ) { + + var data = THREE.Object3D.prototype.toJSON.call( this, meta ); + + data.object.levels = []; + + var levels = this.levels; + + for ( var i = 0, l = levels.length; i < l; i ++ ) { + + var level = levels[ i ]; + + data.object.levels.push( { + object: level.object.uuid, + distance: level.distance + } ); + + } + + return data; }; @@ -17604,84 +19871,83 @@ THREE.LOD.prototype.clone = function (object) { * @author alteredq / http://alteredqualia.com/ */ -THREE.Sprite = (function () { +THREE.Sprite = ( function () { - var indices = new Uint16Array([0, 1, 2, 0, 2, 3]); - var vertices = new Float32Array([-0.5, -0.5, 0, 0.5, -0.5, 0, 0.5, 0.5, 0, -0.5, 0.5, 0]); - var uvs = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]); + var indices = new Uint16Array( [ 0, 1, 2, 0, 2, 3 ] ); + var vertices = new Float32Array( [ - 0.5, - 0.5, 0, 0.5, - 0.5, 0, 0.5, 0.5, 0, - 0.5, 0.5, 0 ] ); + var uvs = new Float32Array( [ 0, 0, 1, 0, 1, 1, 0, 1 ] ); - var geometry = new THREE.BufferGeometry(); - geometry.addAttribute('index', new THREE.BufferAttribute(indices, 1)); - geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3)); - geometry.addAttribute('uv', new THREE.BufferAttribute(uvs, 2)); + var geometry = new THREE.BufferGeometry(); + geometry.setIndex( new THREE.BufferAttribute( indices, 1 ) ); + geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); + geometry.addAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) ); - return function (material) { + return function Sprite( material ) { - THREE.Object3D.call(this); + THREE.Object3D.call( this ); - this.type = 'Sprite'; + this.type = 'Sprite'; - this.geometry = geometry; - this.material = ( material !== undefined ) ? material : new THREE.SpriteMaterial(); + this.geometry = geometry; + this.material = ( material !== undefined ) ? material : new THREE.SpriteMaterial(); - }; + }; -})(); +} )(); -THREE.Sprite.prototype = Object.create(THREE.Object3D.prototype); +THREE.Sprite.prototype = Object.create( THREE.Object3D.prototype ); THREE.Sprite.prototype.constructor = THREE.Sprite; THREE.Sprite.prototype.raycast = ( function () { - var matrixPosition = new THREE.Vector3(); + var matrixPosition = new THREE.Vector3(); - return function (raycaster, intersects) { + return function raycast( raycaster, intersects ) { - matrixPosition.setFromMatrixPosition(this.matrixWorld); + matrixPosition.setFromMatrixPosition( this.matrixWorld ); - var distance = raycaster.ray.distanceToPoint(matrixPosition); + var distanceSq = raycaster.ray.distanceSqToPoint( matrixPosition ); + var guessSizeSq = this.scale.x * this.scale.y; - if (distance > this.scale.x) { + if ( distanceSq > guessSizeSq ) { - return; + return; - } + } - intersects.push({ + intersects.push( { - distance: distance, - point: this.position, - face: null, - object: this + distance: Math.sqrt( distanceSq ), + point: this.position, + face: null, + object: this - }); + } ); - }; + }; }() ); -THREE.Sprite.prototype.clone = function (object) { +THREE.Sprite.prototype.clone = function () { - if (object === undefined) object = new THREE.Sprite(this.material); + return new this.constructor( this.material ).copy( this ); - THREE.Object3D.prototype.clone.call(this, object); +}; - return object; +THREE.Sprite.prototype.toJSON = function ( meta ) { -}; + var data = THREE.Object3D.prototype.toJSON.call( this, meta ); -THREE.Sprite.prototype.toJSON = function (meta) { + // only serialize if not in meta materials cache + if ( meta.materials[ this.material.uuid ] === undefined ) { - var data = THREE.Object3D.prototype.toJSON.call(this, meta); + meta.materials[ this.material.uuid ] = this.material.toJSON(); - // only serialize if not in meta materials cache - if (meta.materials[this.material.uuid] === undefined) { - meta.materials[this.material.uuid] = this.material.toJSON(); - } + } - data.object.material = this.material.uuid; + data.object.material = this.material.uuid; - return data; + return data; }; @@ -17696,24 +19962,24 @@ THREE.Particle = THREE.Sprite; * @author alteredq / http://alteredqualia.com/ */ -THREE.LensFlare = function (texture, size, distance, blending, color) { +THREE.LensFlare = function ( texture, size, distance, blending, color ) { - THREE.Object3D.call(this); + THREE.Object3D.call( this ); - this.lensFlares = []; + this.lensFlares = []; - this.positionScreen = new THREE.Vector3(); - this.customUpdateCallback = undefined; + this.positionScreen = new THREE.Vector3(); + this.customUpdateCallback = undefined; - if (texture !== undefined) { + if ( texture !== undefined ) { - this.add(texture, size, distance, blending, color); + this.add( texture, size, distance, blending, color ); - } + } }; -THREE.LensFlare.prototype = Object.create(THREE.Object3D.prototype); +THREE.LensFlare.prototype = Object.create( THREE.Object3D.prototype ); THREE.LensFlare.prototype.constructor = THREE.LensFlare; @@ -17721,27 +19987,27 @@ THREE.LensFlare.prototype.constructor = THREE.LensFlare; * Add: adds another flare */ -THREE.LensFlare.prototype.add = function (texture, size, distance, blending, color, opacity) { +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; + 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)); + 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 - }); + 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 in front z = 1 is back + scale: 1, // scale + rotation: 0, // rotation + opacity: opacity, // opacity + color: color, // color + blending: blending // blending + } ); }; @@ -17752,25 +20018,41 @@ THREE.LensFlare.prototype.add = function (texture, size, distance, blending, col 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; + 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++) { + for ( f = 0; f < fl; f ++ ) { - flare = this.lensFlares[f]; + flare = this.lensFlares[ f ]; - flare.x = this.positionScreen.x + vecX * flare.distance; - flare.y = this.positionScreen.y + vecY * flare.distance; + 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; + flare.wantedRotation = flare.x * Math.PI * 0.25; + flare.rotation += ( flare.wantedRotation - flare.rotation ) * 0.25; - } + } }; +THREE.LensFlare.prototype.copy = function ( source ) { + + THREE.Object3D.prototype.copy.call( this, source ); + + this.positionScreen.copy( source.positionScreen ); + this.customUpdateCallback = source.customUpdateCallback; + + for ( var i = 0, l = source.lensFlares.length; i < l; i ++ ) { + + this.lensFlares.push( source.lensFlares[ i ] ); + + } + + return this; + +}; // File:src/scenes/Scene.js @@ -17780,33 +20062,31 @@ THREE.LensFlare.prototype.updateLensFlares = function () { THREE.Scene = function () { - THREE.Object3D.call(this); + THREE.Object3D.call( this ); - this.type = 'Scene'; + this.type = 'Scene'; - this.fog = null; - this.overrideMaterial = null; + this.fog = null; + this.overrideMaterial = null; - this.autoUpdate = true; // checked by the renderer + this.autoUpdate = true; // checked by the renderer }; -THREE.Scene.prototype = Object.create(THREE.Object3D.prototype); +THREE.Scene.prototype = Object.create( THREE.Object3D.prototype ); THREE.Scene.prototype.constructor = THREE.Scene; -THREE.Scene.prototype.clone = function (object) { - - if (object === undefined) object = new THREE.Scene(); +THREE.Scene.prototype.copy = function ( source ) { - THREE.Object3D.prototype.clone.call(this, object); + THREE.Object3D.prototype.copy.call( this, source ); - if (this.fog !== null) object.fog = this.fog.clone(); - if (this.overrideMaterial !== null) object.overrideMaterial = this.overrideMaterial.clone(); + if ( source.fog !== null ) this.fog = source.fog.clone(); + if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone(); - object.autoUpdate = this.autoUpdate; - object.matrixAutoUpdate = this.matrixAutoUpdate; + this.autoUpdate = source.autoUpdate; + this.matrixAutoUpdate = source.matrixAutoUpdate; - return object; + return this; }; @@ -17817,20 +20097,20 @@ THREE.Scene.prototype.clone = function (object) { * @author alteredq / http://alteredqualia.com/ */ -THREE.Fog = function (color, near, far) { +THREE.Fog = function ( color, near, far ) { - this.name = ''; + this.name = ''; - this.color = new THREE.Color(color); + this.color = new THREE.Color( color ); - this.near = ( near !== undefined ) ? near : 1; - this.far = ( far !== undefined ) ? far : 1000; + 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); + return new THREE.Fog( this.color.getHex(), this.near, this.far ); }; @@ -17841,18 +20121,18 @@ THREE.Fog.prototype.clone = function () { * @author alteredq / http://alteredqualia.com/ */ -THREE.FogExp2 = function (color, density) { +THREE.FogExp2 = function ( color, density ) { - this.name = ''; + this.name = ''; - this.color = new THREE.Color(color); - this.density = ( density !== undefined ) ? density : 0.00025; + 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); + return new THREE.FogExp2( this.color.getHex(), this.density ); }; @@ -17862,231 +20142,263 @@ THREE.ShaderChunk = {}; // File:src/renderers/shaders/ShaderChunk/alphamap_fragment.glsl -THREE.ShaderChunk['alphamap_fragment'] = "#ifdef USE_ALPHAMAP\n\n\tdiffuseColor.a *= texture2D( alphaMap, vUv ).g;\n\n#endif\n"; +THREE.ShaderChunk[ 'alphamap_fragment'] = "#ifdef USE_ALPHAMAP\n\n diffuseColor.a *= texture2D( alphaMap, vUv ).g;\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/alphamap_pars_fragment.glsl -THREE.ShaderChunk['alphamap_pars_fragment'] = "#ifdef USE_ALPHAMAP\n\n\tuniform sampler2D alphaMap;\n\n#endif\n"; +THREE.ShaderChunk[ 'alphamap_pars_fragment'] = "#ifdef USE_ALPHAMAP\n\n uniform sampler2D alphaMap;\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/alphatest_fragment.glsl -THREE.ShaderChunk['alphatest_fragment'] = "#ifdef ALPHATEST\n\n\tif ( diffuseColor.a < ALPHATEST ) discard;\n\n#endif\n"; +THREE.ShaderChunk[ 'alphatest_fragment'] = "#ifdef ALPHATEST\n\n if ( diffuseColor.a < ALPHATEST ) discard;\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/aomap_fragment.glsl -THREE.ShaderChunk['aomap_fragment'] = "#ifdef USE_AOMAP\n\n\ttotalAmbientLight *= ( texture2D( aoMap, vUv2 ).r - 1.0 ) * aoMapIntensity + 1.0;\n\n#endif\n"; +THREE.ShaderChunk[ 'aomap_fragment'] = "#ifdef USE_AOMAP\n\n totalAmbientLight *= ( texture2D( aoMap, vUv2 ).r - 1.0 ) * aoMapIntensity + 1.0;\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/aomap_pars_fragment.glsl -THREE.ShaderChunk['aomap_pars_fragment'] = "#ifdef USE_AOMAP\n\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n\n#endif"; +THREE.ShaderChunk[ 'aomap_pars_fragment'] = "#ifdef USE_AOMAP\n\n uniform sampler2D aoMap;\n uniform float aoMapIntensity;\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/begin_vertex.glsl + +THREE.ShaderChunk[ 'begin_vertex'] = "\nvec3 transformed = vec3( position );\n"; + +// File:src/renderers/shaders/ShaderChunk/beginnormal_vertex.glsl + +THREE.ShaderChunk[ 'beginnormal_vertex'] = "\nvec3 objectNormal = vec3( normal );\n"; // File:src/renderers/shaders/ShaderChunk/bumpmap_pars_fragment.glsl -THREE.ShaderChunk['bumpmap_pars_fragment'] = "#ifdef USE_BUMPMAP\n\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\n\t// Derivative maps - bump mapping unparametrized surfaces by Morten Mikkelsen\n\t// http://mmikkelsen3d.blogspot.sk/2011/07/derivative-maps.html\n\n\t// Evaluate the derivative of the height w.r.t. screen-space using forward differencing (listing 2)\n\n\tvec2 dHdxy_fwd() {\n\n\t\tvec2 dSTdx = dFdx( vUv );\n\t\tvec2 dSTdy = dFdy( vUv );\n\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;\n\n\t\treturn vec2( dBx, dBy );\n\n\t}\n\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {\n\n\t\tvec3 vSigmaX = dFdx( surf_pos );\n\t\tvec3 vSigmaY = dFdy( surf_pos );\n\t\tvec3 vN = surf_norm;\t\t// normalized\n\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\n\t\tfloat fDet = dot( vSigmaX, R1 );\n\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\n\t}\n\n#endif\n"; +THREE.ShaderChunk[ 'bumpmap_pars_fragment'] = "#ifdef USE_BUMPMAP\n\n uniform sampler2D bumpMap;\n uniform float bumpScale;\n\n // Derivative maps - bump mapping unparametrized surfaces by Morten Mikkelsen\n // http://mmikkelsen3d.blogspot.sk/2011/07/derivative-maps.html\n\n // Evaluate the derivative of the height w.r.t. screen-space using forward differencing (listing 2)\n\n vec2 dHdxy_fwd() {\n\n vec2 dSTdx = dFdx( vUv );\n vec2 dSTdy = dFdy( vUv );\n\n float Hll = bumpScale * texture2D( bumpMap, vUv ).x;\n float dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;\n float dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;\n\n return vec2( dBx, dBy );\n\n }\n\n vec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {\n\n vec3 vSigmaX = dFdx( surf_pos );\n vec3 vSigmaY = dFdy( surf_pos );\n vec3 vN = surf_norm; // normalized\n\n vec3 R1 = cross( vSigmaY, vN );\n vec3 R2 = cross( vN, vSigmaX );\n\n float fDet = dot( vSigmaX, R1 );\n\n vec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n return normalize( abs( fDet ) * surf_norm - vGrad );\n\n }\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/color_fragment.glsl -THREE.ShaderChunk['color_fragment'] = "#ifdef USE_COLOR\n\n\tdiffuseColor.rgb *= vColor;\n\n#endif"; +THREE.ShaderChunk[ 'color_fragment'] = "#ifdef USE_COLOR\n\n diffuseColor.rgb *= vColor;\n\n#endif"; // File:src/renderers/shaders/ShaderChunk/color_pars_fragment.glsl -THREE.ShaderChunk['color_pars_fragment'] = "#ifdef USE_COLOR\n\n\tvarying vec3 vColor;\n\n#endif\n"; +THREE.ShaderChunk[ 'color_pars_fragment'] = "#ifdef USE_COLOR\n\n varying vec3 vColor;\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/color_pars_vertex.glsl -THREE.ShaderChunk['color_pars_vertex'] = "#ifdef USE_COLOR\n\n\tvarying vec3 vColor;\n\n#endif"; +THREE.ShaderChunk[ 'color_pars_vertex'] = "#ifdef USE_COLOR\n\n varying vec3 vColor;\n\n#endif"; // File:src/renderers/shaders/ShaderChunk/color_vertex.glsl -THREE.ShaderChunk['color_vertex'] = "#ifdef USE_COLOR\n\n\tvColor.xyz = inputToLinear( color.xyz );\n\n#endif"; +THREE.ShaderChunk[ 'color_vertex'] = "#ifdef USE_COLOR\n\n vColor.xyz = color.xyz;\n\n#endif"; // File:src/renderers/shaders/ShaderChunk/common.glsl -THREE.ShaderChunk['common'] = "#define PI 3.14159\n#define PI2 6.28318\n#define RECIPROCAL_PI2 0.15915494\n#define LOG2 1.442695\n#define EPSILON 1e-6\n\n#define saturate(a) clamp( a, 0.0, 1.0 )\n#define whiteCompliment(a) ( 1.0 - saturate( a ) )\n\nvec3 transformDirection( in vec3 normal, in mat4 matrix ) {\n\n\treturn normalize( ( matrix * vec4( normal, 0.0 ) ).xyz );\n\n}\n\n// http://en.wikibooks.org/wiki/GLSL_Programming/Applying_Matrix_Transformations\nvec3 inverseTransformDirection( in vec3 normal, in mat4 matrix ) {\n\n\treturn normalize( ( vec4( normal, 0.0 ) * matrix ).xyz );\n\n}\n\nvec3 projectOnPlane(in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\n\tfloat distance = dot( planeNormal, point - pointOnPlane );\n\n\treturn - distance * planeNormal + point;\n\n}\n\nfloat sideOfPlane( in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\n\treturn sign( dot( point - pointOnPlane, planeNormal ) );\n\n}\n\nvec3 linePlaneIntersect( in vec3 pointOnLine, in vec3 lineDirection, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\n\treturn lineDirection * ( dot( planeNormal, pointOnPlane - pointOnLine ) / dot( planeNormal, lineDirection ) ) + pointOnLine;\n\n}\n\nfloat calcLightAttenuation( float lightDistance, float cutoffDistance, float decayExponent ) {\n\n\tif ( decayExponent > 0.0 ) {\n\n\t return pow( saturate( -lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\n\t}\n\n\treturn 1.0;\n\n}\n\nvec3 F_Schlick( in vec3 specularColor, in float dotLH ) {\n\n\treturn ( 1.0 - specularColor ) * pow( 1.0 - dotLH, 5.0 ) + specularColor;\n\n}\n\nfloat G_BlinnPhong_Implicit( /* in float dotNL, in float dotNV */ ) {\n\n\t// geometry term is (nâ‹…l)(nâ‹…v) / 4(nâ‹…l)(nâ‹…v)\n\n\treturn 0.25;\n\n}\n\nfloat D_BlinnPhong( in float shininess, in float dotNH ) {\n\n\t// factor of 1/PI in distribution term omitted\n\n\treturn ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n\n}\n\nvec3 BRDF_BlinnPhong( in vec3 specularColor, in float shininess, in vec3 normal, in vec3 lightDir, in vec3 viewDir ) {\n\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\n\t//float dotNL = saturate( dot( normal, lightDir ) );\n\t//float dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotLH = saturate( dot( lightDir, halfDir ) );\n\n\tvec3 F = F_Schlick( specularColor, dotLH );\n\n\tfloat G = G_BlinnPhong_Implicit( /* dotNL, dotNV */ );\n\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\n\treturn F * G * D;\n\n}\n\nvec3 inputToLinear( in vec3 a ) {\n\n\t#ifdef GAMMA_INPUT\n\n\t\treturn pow( a, vec3( float( GAMMA_FACTOR ) ) );\n\n\t#else\n\n\t\treturn a;\n\n\t#endif\n\n}\n\nvec3 linearToOutput( in vec3 a ) {\n\n\t#ifdef GAMMA_OUTPUT\n\n\t\treturn pow( a, vec3( 1.0 / float( GAMMA_FACTOR ) ) );\n\n\t#else\n\n\t\treturn a;\n\n\t#endif\n\n}\n"; +THREE.ShaderChunk[ 'common'] = "#define PI 3.14159\n#define PI2 6.28318\n#define RECIPROCAL_PI2 0.15915494\n#define LOG2 1.442695\n#define EPSILON 1e-6\n\n#define saturate(a) clamp( a, 0.0, 1.0 )\n#define whiteCompliment(a) ( 1.0 - saturate( a ) )\n\nvec3 transformDirection( in vec3 normal, in mat4 matrix ) {\n\n return normalize( ( matrix * vec4( normal, 0.0 ) ).xyz );\n\n}\n\n// http://en.wikibooks.org/wiki/GLSL_Programming/Applying_Matrix_Transformations\nvec3 inverseTransformDirection( in vec3 normal, in mat4 matrix ) {\n\n return normalize( ( vec4( normal, 0.0 ) * matrix ).xyz );\n\n}\n\nvec3 projectOnPlane(in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\n float distance = dot( planeNormal, point - pointOnPlane );\n\n return - distance * planeNormal + point;\n\n}\n\nfloat sideOfPlane( in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\n return sign( dot( point - pointOnPlane, planeNormal ) );\n\n}\n\nvec3 linePlaneIntersect( in vec3 pointOnLine, in vec3 lineDirection, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\n return lineDirection * ( dot( planeNormal, pointOnPlane - pointOnLine ) / dot( planeNormal, lineDirection ) ) + pointOnLine;\n\n}\n\nfloat calcLightAttenuation( float lightDistance, float cutoffDistance, float decayExponent ) {\n\n if ( decayExponent > 0.0 ) {\n\n return pow( saturate( -lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\n }\n\n return 1.0;\n\n}\n\nvec3 F_Schlick( in vec3 specularColor, in float dotLH ) {\n\n // Original approximation by Christophe Schlick '94\n //;float fresnel = pow( 1.0 - dotLH, 5.0 );\n\n // Optimized variant (presented by Epic at SIGGRAPH '13)\n float fresnel = exp2( ( -5.55437 * dotLH - 6.98316 ) * dotLH );\n\n return ( 1.0 - specularColor ) * fresnel + specularColor;\n\n}\n\nfloat G_BlinnPhong_Implicit( /* in float dotNL, in float dotNV */ ) {\n\n // geometry term is (nâ‹…l)(nâ‹…v) / 4(nâ‹…l)(nâ‹…v)\n\n return 0.25;\n\n}\n\nfloat D_BlinnPhong( in float shininess, in float dotNH ) {\n\n // factor of 1/PI in distribution term omitted\n\n return ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n\n}\n\nvec3 BRDF_BlinnPhong( in vec3 specularColor, in float shininess, in vec3 normal, in vec3 lightDir, in vec3 viewDir ) {\n\n vec3 halfDir = normalize( lightDir + viewDir );\n\n //float dotNL = saturate( dot( normal, lightDir ) );\n //float dotNV = saturate( dot( normal, viewDir ) );\n float dotNH = saturate( dot( normal, halfDir ) );\n float dotLH = saturate( dot( lightDir, halfDir ) );\n\n vec3 F = F_Schlick( specularColor, dotLH );\n\n float G = G_BlinnPhong_Implicit( /* dotNL, dotNV */ );\n\n float D = D_BlinnPhong( shininess, dotNH );\n\n return F * G * D;\n\n}\n\nvec3 inputToLinear( in vec3 a ) {\n\n #ifdef GAMMA_INPUT\n\n return pow( a, vec3( float( GAMMA_FACTOR ) ) );\n\n #else\n\n return a;\n\n #endif\n\n}\n\nvec3 linearToOutput( in vec3 a ) {\n\n #ifdef GAMMA_OUTPUT\n\n return pow( a, vec3( 1.0 / float( GAMMA_FACTOR ) ) );\n\n #else\n\n return a;\n\n #endif\n\n}\n"; -// File:src/renderers/shaders/ShaderChunk/default_vertex.glsl +// File:src/renderers/shaders/ShaderChunk/defaultnormal_vertex.glsl -THREE.ShaderChunk['default_vertex'] = "#ifdef USE_SKINNING\n\n\tvec4 mvPosition = modelViewMatrix * skinned;\n\n#elif defined( USE_MORPHTARGETS )\n\n\tvec4 mvPosition = modelViewMatrix * vec4( morphed, 1.0 );\n\n#else\n\n\tvec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\n\n#endif\n\ngl_Position = projectionMatrix * mvPosition;\n"; +THREE.ShaderChunk[ 'defaultnormal_vertex'] = "#ifdef FLIP_SIDED\n\n objectNormal = -objectNormal;\n\n#endif\n\nvec3 transformedNormal = normalMatrix * objectNormal;\n"; -// File:src/renderers/shaders/ShaderChunk/defaultnormal_vertex.glsl +// File:src/renderers/shaders/ShaderChunk/displacementmap_vertex.glsl + +THREE.ShaderChunk[ 'displacementmap_vertex'] = "#ifdef USE_DISPLACEMENTMAP\n\n transformed += normal * ( texture2D( displacementMap, uv ).x * displacementScale + displacementBias );\n\n#endif\n"; + +// File:src/renderers/shaders/ShaderChunk/displacementmap_pars_vertex.glsl + +THREE.ShaderChunk[ 'displacementmap_pars_vertex'] = "#ifdef USE_DISPLACEMENTMAP\n\n uniform sampler2D displacementMap;\n uniform float displacementScale;\n uniform float displacementBias;\n\n#endif\n"; + +// File:src/renderers/shaders/ShaderChunk/emissivemap_fragment.glsl + +THREE.ShaderChunk[ 'emissivemap_fragment'] = "#ifdef USE_EMISSIVEMAP\n\n vec4 emissiveColor = texture2D( emissiveMap, vUv );\n\n emissiveColor.rgb = inputToLinear( emissiveColor.rgb );\n\n totalEmissiveLight *= emissiveColor.rgb;\n\n#endif\n"; + +// File:src/renderers/shaders/ShaderChunk/emissivemap_pars_fragment.glsl -THREE.ShaderChunk['defaultnormal_vertex'] = "#ifdef USE_SKINNING\n\n\tvec3 objectNormal = skinnedNormal.xyz;\n\n#elif defined( USE_MORPHNORMALS )\n\n\tvec3 objectNormal = morphedNormal;\n\n#else\n\n\tvec3 objectNormal = normal;\n\n#endif\n\n#ifdef FLIP_SIDED\n\n\tobjectNormal = -objectNormal;\n\n#endif\n\nvec3 transformedNormal = normalMatrix * objectNormal;\n"; +THREE.ShaderChunk[ 'emissivemap_pars_fragment'] = "#ifdef USE_EMISSIVEMAP\n\n uniform sampler2D emissiveMap;\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/envmap_fragment.glsl -THREE.ShaderChunk['envmap_fragment'] = "#ifdef USE_ENVMAP\n\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\n\t\tvec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );\n\n\t\t// Transforming Normal Vectors with the Inverse Transformation\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\n\t\t\tvec3 reflectVec = reflect( cameraToVertex, worldNormal );\n\n\t\t#else\n\n\t\t\tvec3 reflectVec = refract( cameraToVertex, worldNormal, refractionRatio );\n\n\t\t#endif\n\n\t#else\n\n\t\tvec3 reflectVec = vReflect;\n\n\t#endif\n\n\t#ifdef DOUBLE_SIDED\n\t\tfloat flipNormal = ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t#else\n\t\tfloat flipNormal = 1.0;\n\t#endif\n\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, flipNormal * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\n\t#elif defined( ENVMAP_TYPE_EQUIREC )\n\t\tvec2 sampleUV;\n\t\tsampleUV.y = saturate( flipNormal * reflectVec.y * 0.5 + 0.5 );\n\t\tsampleUV.x = atan( flipNormal * reflectVec.z, flipNormal * reflectVec.x ) * RECIPROCAL_PI2 + 0.5;\n\t\tvec4 envColor = texture2D( envMap, sampleUV );\n\n\t#elif defined( ENVMAP_TYPE_SPHERE )\n\t\tvec3 reflectView = flipNormal * normalize((viewMatrix * vec4( reflectVec, 0.0 )).xyz + vec3(0.0,0.0,1.0));\n\t\tvec4 envColor = texture2D( envMap, reflectView.xy * 0.5 + 0.5 );\n\t#endif\n\n\tenvColor.xyz = inputToLinear( envColor.xyz );\n\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\n\t#endif\n\n#endif\n"; +THREE.ShaderChunk[ 'envmap_fragment'] = "#ifdef USE_ENVMAP\n\n #if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\n vec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );\n\n // Transforming Normal Vectors with the Inverse Transformation\n vec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\n #ifdef ENVMAP_MODE_REFLECTION\n\n vec3 reflectVec = reflect( cameraToVertex, worldNormal );\n\n #else\n\n vec3 reflectVec = refract( cameraToVertex, worldNormal, refractionRatio );\n\n #endif\n\n #else\n\n vec3 reflectVec = vReflect;\n\n #endif\n\n #ifdef DOUBLE_SIDED\n float flipNormal = ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n #else\n float flipNormal = 1.0;\n #endif\n\n #ifdef ENVMAP_TYPE_CUBE\n vec4 envColor = textureCube( envMap, flipNormal * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\n #elif defined( ENVMAP_TYPE_EQUIREC )\n vec2 sampleUV;\n sampleUV.y = saturate( flipNormal * reflectVec.y * 0.5 + 0.5 );\n sampleUV.x = atan( flipNormal * reflectVec.z, flipNormal * reflectVec.x ) * RECIPROCAL_PI2 + 0.5;\n vec4 envColor = texture2D( envMap, sampleUV );\n\n #elif defined( ENVMAP_TYPE_SPHERE )\n vec3 reflectView = flipNormal * normalize((viewMatrix * vec4( reflectVec, 0.0 )).xyz + vec3(0.0,0.0,1.0));\n vec4 envColor = texture2D( envMap, reflectView.xy * 0.5 + 0.5 );\n #endif\n\n envColor.xyz = inputToLinear( envColor.xyz );\n\n #ifdef ENVMAP_BLENDING_MULTIPLY\n\n outgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\n #elif defined( ENVMAP_BLENDING_MIX )\n\n outgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\n #elif defined( ENVMAP_BLENDING_ADD )\n\n outgoingLight += envColor.xyz * specularStrength * reflectivity;\n\n #endif\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/envmap_pars_fragment.glsl -THREE.ShaderChunk['envmap_pars_fragment'] = "#ifdef USE_ENVMAP\n\n\tuniform float reflectivity;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\tuniform float flipEnvMap;\n\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\n\t\tuniform float refractionRatio;\n\n\t#else\n\n\t\tvarying vec3 vReflect;\n\n\t#endif\n\n#endif\n"; +THREE.ShaderChunk[ 'envmap_pars_fragment'] = "#ifdef USE_ENVMAP\n\n uniform float reflectivity;\n #ifdef ENVMAP_TYPE_CUBE\n uniform samplerCube envMap;\n #else\n uniform sampler2D envMap;\n #endif\n uniform float flipEnvMap;\n\n #if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\n uniform float refractionRatio;\n\n #else\n\n varying vec3 vReflect;\n\n #endif\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/envmap_pars_vertex.glsl -THREE.ShaderChunk['envmap_pars_vertex'] = "#if defined( USE_ENVMAP ) && ! defined( USE_BUMPMAP ) && ! defined( USE_NORMALMAP ) && ! defined( PHONG )\n\n\tvarying vec3 vReflect;\n\n\tuniform float refractionRatio;\n\n#endif\n"; +THREE.ShaderChunk[ 'envmap_pars_vertex'] = "#if defined( USE_ENVMAP ) && ! defined( USE_BUMPMAP ) && ! defined( USE_NORMALMAP ) && ! defined( PHONG )\n\n varying vec3 vReflect;\n\n uniform float refractionRatio;\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/envmap_vertex.glsl -THREE.ShaderChunk['envmap_vertex'] = "#if defined( USE_ENVMAP ) && ! defined( USE_BUMPMAP ) && ! defined( USE_NORMALMAP ) && ! defined( PHONG )\n\n\tvec3 worldNormal = transformDirection( objectNormal, modelMatrix );\n\n\tvec3 cameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\n\t#ifdef ENVMAP_MODE_REFLECTION\n\n\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\n\t#else\n\n\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\n\t#endif\n\n#endif\n"; +THREE.ShaderChunk[ 'envmap_vertex'] = "#if defined( USE_ENVMAP ) && ! defined( USE_BUMPMAP ) && ! defined( USE_NORMALMAP ) && ! defined( PHONG )\n\n vec3 worldNormal = transformDirection( objectNormal, modelMatrix );\n\n vec3 cameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\n #ifdef ENVMAP_MODE_REFLECTION\n\n vReflect = reflect( cameraToVertex, worldNormal );\n\n #else\n\n vReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\n #endif\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/fog_fragment.glsl -THREE.ShaderChunk['fog_fragment'] = "#ifdef USE_FOG\n\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\n\t\tfloat depth = gl_FragDepthEXT / gl_FragCoord.w;\n\n\t#else\n\n\t\tfloat depth = gl_FragCoord.z / gl_FragCoord.w;\n\n\t#endif\n\n\t#ifdef FOG_EXP2\n\n\t\tfloat fogFactor = whiteCompliment( exp2( - fogDensity * fogDensity * depth * depth * LOG2 ) );\n\n\t#else\n\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, depth );\n\n\t#endif\n\t\n\toutgoingLight = mix( outgoingLight, fogColor, fogFactor );\n\n#endif"; +THREE.ShaderChunk[ 'fog_fragment'] = "#ifdef USE_FOG\n\n #ifdef USE_LOGDEPTHBUF_EXT\n\n float depth = gl_FragDepthEXT / gl_FragCoord.w;\n\n #else\n\n float depth = gl_FragCoord.z / gl_FragCoord.w;\n\n #endif\n\n #ifdef FOG_EXP2\n\n float fogFactor = whiteCompliment( exp2( - fogDensity * fogDensity * depth * depth * LOG2 ) );\n\n #else\n\n float fogFactor = smoothstep( fogNear, fogFar, depth );\n\n #endif\n \n outgoingLight = mix( outgoingLight, fogColor, fogFactor );\n\n#endif"; // File:src/renderers/shaders/ShaderChunk/fog_pars_fragment.glsl -THREE.ShaderChunk['fog_pars_fragment'] = "#ifdef USE_FOG\n\n\tuniform vec3 fogColor;\n\n\t#ifdef FOG_EXP2\n\n\t\tuniform float fogDensity;\n\n\t#else\n\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n\n#endif"; +THREE.ShaderChunk[ 'fog_pars_fragment'] = "#ifdef USE_FOG\n\n uniform vec3 fogColor;\n\n #ifdef FOG_EXP2\n\n uniform float fogDensity;\n\n #else\n\n uniform float fogNear;\n uniform float fogFar;\n #endif\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/hemilight_fragment.glsl + +THREE.ShaderChunk[ 'hemilight_fragment'] = "#if MAX_HEMI_LIGHTS > 0\n\n for( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {\n\n vec3 lightDir = hemisphereLightDirection[ i ];\n\n float dotProduct = dot( normal, lightDir );\n\n float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;\n\n vec3 lightColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );\n\n totalAmbientLight += lightColor;\n\n }\n\n#endif\n\n"; // File:src/renderers/shaders/ShaderChunk/lightmap_fragment.glsl -THREE.ShaderChunk['lightmap_fragment'] = "#ifdef USE_LIGHTMAP\n\n\ttotalAmbientLight += texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\n\n#endif\n"; +THREE.ShaderChunk[ 'lightmap_fragment'] = "#ifdef USE_LIGHTMAP\n\n totalAmbientLight += texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/lightmap_pars_fragment.glsl -THREE.ShaderChunk['lightmap_pars_fragment'] = "#ifdef USE_LIGHTMAP\n\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n\n#endif"; +THREE.ShaderChunk[ 'lightmap_pars_fragment'] = "#ifdef USE_LIGHTMAP\n\n uniform sampler2D lightMap;\n uniform float lightMapIntensity;\n\n#endif"; // File:src/renderers/shaders/ShaderChunk/lights_lambert_pars_vertex.glsl -THREE.ShaderChunk['lights_lambert_pars_vertex'] = "uniform vec3 ambientLightColor;\n\n#if MAX_DIR_LIGHTS > 0\n\n\tuniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\n\tuniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\n\n#endif\n\n#if MAX_HEMI_LIGHTS > 0\n\n\tuniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];\n\tuniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];\n\tuniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];\n\n#endif\n\n#if MAX_POINT_LIGHTS > 0\n\n\tuniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];\n\tuniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\n\tuniform float pointLightDistance[ MAX_POINT_LIGHTS ];\n\tuniform float pointLightDecay[ MAX_POINT_LIGHTS ];\n\n#endif\n\n#if MAX_SPOT_LIGHTS > 0\n\n\tuniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];\n\tuniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\n\tuniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];\n\tuniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\n\tuniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];\n\tuniform float spotLightExponent[ MAX_SPOT_LIGHTS ];\n\tuniform float spotLightDecay[ MAX_SPOT_LIGHTS ];\n\n#endif\n"; +THREE.ShaderChunk[ 'lights_lambert_pars_vertex'] = "uniform vec3 ambientLightColor;\n\n#if MAX_DIR_LIGHTS > 0\n\n uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\n uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\n\n#endif\n\n#if MAX_HEMI_LIGHTS > 0\n\n uniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];\n uniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];\n uniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];\n\n#endif\n\n#if MAX_POINT_LIGHTS > 0\n\n uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];\n uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\n uniform float pointLightDistance[ MAX_POINT_LIGHTS ];\n uniform float pointLightDecay[ MAX_POINT_LIGHTS ];\n\n#endif\n\n#if MAX_SPOT_LIGHTS > 0\n\n uniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];\n uniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\n uniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];\n uniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\n uniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];\n uniform float spotLightExponent[ MAX_SPOT_LIGHTS ];\n uniform float spotLightDecay[ MAX_SPOT_LIGHTS ];\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/lights_lambert_vertex.glsl -THREE.ShaderChunk['lights_lambert_vertex'] = "vLightFront = vec3( 0.0 );\n\n#ifdef DOUBLE_SIDED\n\n\tvLightBack = vec3( 0.0 );\n\n#endif\n\nvec3 normal = normalize( transformedNormal );\n\n#if MAX_POINT_LIGHTS > 0\n\n\tfor ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\n\n\t\tvec3 lightColor = pointLightColor[ i ];\n\n\t\tvec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );\n\t\tvec3 lVector = lPosition.xyz - mvPosition.xyz;\n\t\tvec3 lightDir = normalize( lVector );\n\n\t\t// attenuation\n\n\t\tfloat attenuation = calcLightAttenuation( length( lVector ), pointLightDistance[ i ], pointLightDecay[ i ] );\n\n\t\t// diffuse\n\n\t\tfloat dotProduct = dot( normal, lightDir );\n\n\t\tvLightFront += lightColor * attenuation * saturate( dotProduct );\n\n\t\t#ifdef DOUBLE_SIDED\n\n\t\t\tvLightBack += lightColor * attenuation * saturate( - dotProduct );\n\n\t\t#endif\n\n\t}\n\n#endif\n\n#if MAX_SPOT_LIGHTS > 0\n\n\tfor ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\n\n\t\tvec3 lightColor = spotLightColor[ i ];\n\n\t\tvec3 lightPosition = spotLightPosition[ i ];\n\t\tvec4 lPosition = viewMatrix * vec4( lightPosition, 1.0 );\n\t\tvec3 lVector = lPosition.xyz - mvPosition.xyz;\n\t\tvec3 lightDir = normalize( lVector );\n\n\t\tfloat spotEffect = dot( spotLightDirection[ i ], normalize( lightPosition - worldPosition.xyz ) );\n\n\t\tif ( spotEffect > spotLightAngleCos[ i ] ) {\n\n\t\t\tspotEffect = saturate( pow( saturate( spotEffect ), spotLightExponent[ i ] ) );\n\n\t\t\t// attenuation\n\n\t\t\tfloat attenuation = calcLightAttenuation( length( lVector ), spotLightDistance[ i ], spotLightDecay[ i ] );\n\n\t\t\tattenuation *= spotEffect;\n\n\t\t\t// diffuse\n\n\t\t\tfloat dotProduct = dot( normal, lightDir );\n\n\t\t\tvLightFront += lightColor * attenuation * saturate( dotProduct );\n\n\t\t\t#ifdef DOUBLE_SIDED\n\n\t\t\t\tvLightBack += lightColor * attenuation * saturate( - dotProduct );\n\n\t\t\t#endif\n\n\t\t}\n\n\t}\n\n#endif\n\n#if MAX_DIR_LIGHTS > 0\n\n\tfor ( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {\n\n\t\tvec3 lightColor = directionalLightColor[ i ];\n\n\t\tvec3 lightDir = transformDirection( directionalLightDirection[ i ], viewMatrix );\n\n\t\t// diffuse\n\n\t\tfloat dotProduct = dot( normal, lightDir );\n\n\t\tvLightFront += lightColor * saturate( dotProduct );\n\n\t\t#ifdef DOUBLE_SIDED\n\n\t\t\tvLightBack += lightColor * saturate( - dotProduct );\n\n\t\t#endif\n\n\t}\n\n#endif\n\n#if MAX_HEMI_LIGHTS > 0\n\n\tfor ( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {\n\n\t\tvec3 lightDir = transformDirection( hemisphereLightDirection[ i ], viewMatrix );\n\n\t\t// diffuse\n\n\t\tfloat dotProduct = dot( normal, lightDir );\n\n\t\tfloat hemiDiffuseWeight = 0.5 * dotProduct + 0.5;\n\n\t\tvLightFront += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );\n\n\t\t#ifdef DOUBLE_SIDED\n\n\t\t\tfloat hemiDiffuseWeightBack = - 0.5 * dotProduct + 0.5;\n\n\t\t\tvLightBack += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeightBack );\n\n\t\t#endif\n\n\t}\n\n#endif\n\nvLightFront += ambientLightColor;\n\n#ifdef DOUBLE_SIDED\n\n\tvLightBack += ambientLightColor;\n\n#endif\n"; +THREE.ShaderChunk[ 'lights_lambert_vertex'] = "vLightFront = vec3( 0.0 );\n\n#ifdef DOUBLE_SIDED\n\n vLightBack = vec3( 0.0 );\n\n#endif\n\nvec3 normal = normalize( transformedNormal );\n\n#if MAX_POINT_LIGHTS > 0\n\n for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\n\n vec3 lightColor = pointLightColor[ i ];\n\n vec3 lVector = pointLightPosition[ i ] - mvPosition.xyz;\n vec3 lightDir = normalize( lVector );\n\n // attenuation\n\n float attenuation = calcLightAttenuation( length( lVector ), pointLightDistance[ i ], pointLightDecay[ i ] );\n\n // diffuse\n\n float dotProduct = dot( normal, lightDir );\n\n vLightFront += lightColor * attenuation * saturate( dotProduct );\n\n #ifdef DOUBLE_SIDED\n\n vLightBack += lightColor * attenuation * saturate( - dotProduct );\n\n #endif\n\n }\n\n#endif\n\n#if MAX_SPOT_LIGHTS > 0\n\n for ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\n\n vec3 lightColor = spotLightColor[ i ];\n\n vec3 lightPosition = spotLightPosition[ i ];\n vec3 lVector = lightPosition - mvPosition.xyz;\n vec3 lightDir = normalize( lVector );\n\n float spotEffect = dot( spotLightDirection[ i ], lightDir );\n\n if ( spotEffect > spotLightAngleCos[ i ] ) {\n\n spotEffect = saturate( pow( saturate( spotEffect ), spotLightExponent[ i ] ) );\n\n // attenuation\n\n float attenuation = calcLightAttenuation( length( lVector ), spotLightDistance[ i ], spotLightDecay[ i ] );\n\n attenuation *= spotEffect;\n\n // diffuse\n\n float dotProduct = dot( normal, lightDir );\n\n vLightFront += lightColor * attenuation * saturate( dotProduct );\n\n #ifdef DOUBLE_SIDED\n\n vLightBack += lightColor * attenuation * saturate( - dotProduct );\n\n #endif\n\n }\n\n }\n\n#endif\n\n#if MAX_DIR_LIGHTS > 0\n\n for ( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {\n\n vec3 lightColor = directionalLightColor[ i ];\n\n vec3 lightDir = directionalLightDirection[ i ];\n\n // diffuse\n\n float dotProduct = dot( normal, lightDir );\n\n vLightFront += lightColor * saturate( dotProduct );\n\n #ifdef DOUBLE_SIDED\n\n vLightBack += lightColor * saturate( - dotProduct );\n\n #endif\n\n }\n\n#endif\n\n#if MAX_HEMI_LIGHTS > 0\n\n for ( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {\n\n vec3 lightDir = hemisphereLightDirection[ i ];\n\n // diffuse\n\n float dotProduct = dot( normal, lightDir );\n\n float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;\n\n vLightFront += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );\n\n #ifdef DOUBLE_SIDED\n\n float hemiDiffuseWeightBack = - 0.5 * dotProduct + 0.5;\n\n vLightBack += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeightBack );\n\n #endif\n\n }\n\n#endif\n\nvLightFront += ambientLightColor;\n\n#ifdef DOUBLE_SIDED\n\n vLightBack += ambientLightColor;\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/lights_phong_fragment.glsl -THREE.ShaderChunk['lights_phong_fragment'] = "#ifndef FLAT_SHADED\n\n\tvec3 normal = normalize( vNormal );\n\n\t#ifdef DOUBLE_SIDED\n\n\t\tnormal = normal * ( -1.0 + 2.0 * float( gl_FrontFacing ) );\n\n\t#endif\n\n#else\n\n\tvec3 fdx = dFdx( vViewPosition );\n\tvec3 fdy = dFdy( vViewPosition );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n\n#endif\n\n#ifdef USE_NORMALMAP\n\n\tnormal = perturbNormal2Arb( -vViewPosition, normal );\n\n#elif defined( USE_BUMPMAP )\n\n\tnormal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );\n\n#endif\n\nvec3 viewDir = normalize( vViewPosition );\n\nvec3 totalDiffuseLight = vec3( 0.0 );\nvec3 totalSpecularLight = vec3( 0.0 );\n\n#if MAX_POINT_LIGHTS > 0\n\n\tfor ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\n\n\t\tvec3 lightColor = pointLightColor[ i ];\n\n\t\tvec3 lightPosition = pointLightPosition[ i ];\n\t\tvec4 lPosition = viewMatrix * vec4( lightPosition, 1.0 );\n\t\tvec3 lVector = lPosition.xyz + vViewPosition.xyz;\n\t\tvec3 lightDir = normalize( lVector );\n\n\t\t// attenuation\n\n\t\tfloat attenuation = calcLightAttenuation( length( lVector ), pointLightDistance[ i ], pointLightDecay[ i ] );\n\n\t\t// diffuse\n\n\t\tfloat cosineTerm = saturate( dot( normal, lightDir ) );\n\n\t\ttotalDiffuseLight += lightColor * attenuation * cosineTerm;\n\n\t\t// specular\n\n\t\tvec3 brdf = BRDF_BlinnPhong( specular, shininess, normal, lightDir, viewDir );\n\n\t\ttotalSpecularLight += brdf * specularStrength * lightColor * attenuation * cosineTerm;\n\n\n\t}\n\n#endif\n\n#if MAX_SPOT_LIGHTS > 0\n\n\tfor ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\n\n\t\tvec3 lightColor = spotLightColor[ i ];\n\n\t\tvec3 lightPosition = spotLightPosition[ i ];\n\t\tvec4 lPosition = viewMatrix * vec4( lightPosition, 1.0 );\n\t\tvec3 lVector = lPosition.xyz + vViewPosition.xyz;\n\t\tvec3 lightDir = normalize( lVector );\n\n\t\tfloat spotEffect = dot( spotLightDirection[ i ], normalize( lightPosition - vWorldPosition ) );\n\n\t\tif ( spotEffect > spotLightAngleCos[ i ] ) {\n\n\t\t\tspotEffect = saturate( pow( saturate( spotEffect ), spotLightExponent[ i ] ) );\n\n\t\t\t// attenuation\n\n\t\t\tfloat attenuation = calcLightAttenuation( length( lVector ), spotLightDistance[ i ], spotLightDecay[ i ] );\n\n\t\t\tattenuation *= spotEffect;\n\n\t\t\t// diffuse\n\n\t\t\tfloat cosineTerm = saturate( dot( normal, lightDir ) );\n\n\t\t\ttotalDiffuseLight += lightColor * attenuation * cosineTerm;\n\n\t\t\t// specular\n\n\t\t\tvec3 brdf = BRDF_BlinnPhong( specular, shininess, normal, lightDir, viewDir );\n\n\t\t\ttotalSpecularLight += brdf * specularStrength * lightColor * attenuation * cosineTerm;\n\n\t\t}\n\n\t}\n\n#endif\n\n#if MAX_DIR_LIGHTS > 0\n\n\tfor( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {\n\n\t\tvec3 lightColor = directionalLightColor[ i ];\n\n\t\tvec3 lightDir = transformDirection( directionalLightDirection[ i ], viewMatrix );\n\n\t\t// diffuse\n\n\t\tfloat cosineTerm = saturate( dot( normal, lightDir ) );\n\n\t\ttotalDiffuseLight += lightColor * cosineTerm;\n\n\t\t// specular\n\n\t\tvec3 brdf = BRDF_BlinnPhong( specular, shininess, normal, lightDir, viewDir );\n\n\t\ttotalSpecularLight += brdf * specularStrength * lightColor * cosineTerm;\n\n\t}\n\n#endif\n\n#if MAX_HEMI_LIGHTS > 0\n\n\tfor( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {\n\n\t\tvec3 lightDir = transformDirection( hemisphereLightDirection[ i ], viewMatrix );\n\n\t\t// diffuse\n\n\t\tfloat dotProduct = dot( normal, lightDir );\n\n\t\tfloat hemiDiffuseWeight = 0.5 * dotProduct + 0.5;\n\n\t\tvec3 lightColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );\n\n\t\ttotalDiffuseLight += lightColor;\n\n\t\t// specular (sky term only)\n\n\t\tvec3 brdf = BRDF_BlinnPhong( specular, shininess, normal, lightDir, viewDir );\n\n\t\ttotalSpecularLight += brdf * specularStrength * lightColor * max( dotProduct, 0.0 );\n\n\t}\n\n#endif\n\n#ifdef METAL\n\n\toutgoingLight += diffuseColor.rgb * ( totalDiffuseLight + totalAmbientLight ) * specular + totalSpecularLight + emissive;\n\n#else\n\n\toutgoingLight += diffuseColor.rgb * ( totalDiffuseLight + totalAmbientLight ) + totalSpecularLight + emissive;\n\n#endif\n"; +THREE.ShaderChunk[ 'lights_phong_fragment'] = "vec3 viewDir = normalize( vViewPosition );\n\nvec3 totalDiffuseLight = vec3( 0.0 );\nvec3 totalSpecularLight = vec3( 0.0 );\n\n#if MAX_POINT_LIGHTS > 0\n\n for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\n\n vec3 lightColor = pointLightColor[ i ];\n\n vec3 lightPosition = pointLightPosition[ i ];\n vec3 lVector = lightPosition + vViewPosition.xyz;\n vec3 lightDir = normalize( lVector );\n\n // attenuation\n\n float attenuation = calcLightAttenuation( length( lVector ), pointLightDistance[ i ], pointLightDecay[ i ] );\n\n // diffuse\n\n float cosineTerm = saturate( dot( normal, lightDir ) );\n\n totalDiffuseLight += lightColor * attenuation * cosineTerm;\n\n // specular\n\n vec3 brdf = BRDF_BlinnPhong( specular, shininess, normal, lightDir, viewDir );\n\n totalSpecularLight += brdf * specularStrength * lightColor * attenuation * cosineTerm;\n\n\n }\n\n#endif\n\n#if MAX_SPOT_LIGHTS > 0\n\n for ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\n\n vec3 lightColor = spotLightColor[ i ];\n\n vec3 lightPosition = spotLightPosition[ i ];\n vec3 lVector = lightPosition + vViewPosition.xyz;\n vec3 lightDir = normalize( lVector );\n\n float spotEffect = dot( spotLightDirection[ i ], lightDir );\n\n if ( spotEffect > spotLightAngleCos[ i ] ) {\n\n spotEffect = saturate( pow( saturate( spotEffect ), spotLightExponent[ i ] ) );\n\n // attenuation\n\n float attenuation = calcLightAttenuation( length( lVector ), spotLightDistance[ i ], spotLightDecay[ i ] );\n\n attenuation *= spotEffect;\n\n // diffuse\n\n float cosineTerm = saturate( dot( normal, lightDir ) );\n\n totalDiffuseLight += lightColor * attenuation * cosineTerm;\n\n // specular\n\n vec3 brdf = BRDF_BlinnPhong( specular, shininess, normal, lightDir, viewDir );\n\n totalSpecularLight += brdf * specularStrength * lightColor * attenuation * cosineTerm;\n\n }\n\n }\n\n#endif\n\n#if MAX_DIR_LIGHTS > 0\n\n for( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {\n\n vec3 lightColor = directionalLightColor[ i ];\n\n vec3 lightDir = directionalLightDirection[ i ];\n\n // diffuse\n\n float cosineTerm = saturate( dot( normal, lightDir ) );\n\n totalDiffuseLight += lightColor * cosineTerm;\n\n // specular\n\n vec3 brdf = BRDF_BlinnPhong( specular, shininess, normal, lightDir, viewDir );\n\n totalSpecularLight += brdf * specularStrength * lightColor * cosineTerm;\n\n }\n\n#endif\n\n#ifdef METAL\n\n outgoingLight += diffuseColor.rgb * ( totalDiffuseLight + totalAmbientLight ) * specular + totalSpecularLight + totalEmissiveLight;\n\n#else\n\n outgoingLight += diffuseColor.rgb * ( totalDiffuseLight + totalAmbientLight ) + totalSpecularLight + totalEmissiveLight;\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/lights_phong_pars_fragment.glsl -THREE.ShaderChunk['lights_phong_pars_fragment'] = "uniform vec3 ambientLightColor;\n\n#if MAX_DIR_LIGHTS > 0\n\n\tuniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\n\tuniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\n\n#endif\n\n#if MAX_HEMI_LIGHTS > 0\n\n\tuniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];\n\tuniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];\n\tuniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];\n\n#endif\n\n#if MAX_POINT_LIGHTS > 0\n\n\tuniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];\n\n\tuniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\n\tuniform float pointLightDistance[ MAX_POINT_LIGHTS ];\n\tuniform float pointLightDecay[ MAX_POINT_LIGHTS ];\n\n#endif\n\n#if MAX_SPOT_LIGHTS > 0\n\n\tuniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];\n\tuniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\n\tuniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];\n\tuniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];\n\tuniform float spotLightExponent[ MAX_SPOT_LIGHTS ];\n\tuniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\n\tuniform float spotLightDecay[ MAX_SPOT_LIGHTS ];\n\n#endif\n\n#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP ) || defined( USE_ENVMAP )\n\n\tvarying vec3 vWorldPosition;\n\n#endif\n\nvarying vec3 vViewPosition;\n\n#ifndef FLAT_SHADED\n\n\tvarying vec3 vNormal;\n\n#endif\n"; +THREE.ShaderChunk[ 'lights_phong_pars_fragment'] = "uniform vec3 ambientLightColor;\n\n#if MAX_DIR_LIGHTS > 0\n\n uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\n uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\n\n#endif\n\n#if MAX_HEMI_LIGHTS > 0\n\n uniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];\n uniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];\n uniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];\n\n#endif\n\n#if MAX_POINT_LIGHTS > 0\n\n uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];\n\n uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\n uniform float pointLightDistance[ MAX_POINT_LIGHTS ];\n uniform float pointLightDecay[ MAX_POINT_LIGHTS ];\n\n#endif\n\n#if MAX_SPOT_LIGHTS > 0\n\n uniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];\n uniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\n uniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];\n uniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];\n uniform float spotLightExponent[ MAX_SPOT_LIGHTS ];\n uniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\n uniform float spotLightDecay[ MAX_SPOT_LIGHTS ];\n\n#endif\n\n#if MAX_SPOT_LIGHTS > 0 || defined( USE_ENVMAP )\n\n varying vec3 vWorldPosition;\n\n#endif\n\nvarying vec3 vViewPosition;\n\n#ifndef FLAT_SHADED\n\n varying vec3 vNormal;\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/lights_phong_pars_vertex.glsl -THREE.ShaderChunk['lights_phong_pars_vertex'] = "#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP ) || defined( USE_ENVMAP )\n\n\tvarying vec3 vWorldPosition;\n\n#endif\n"; +THREE.ShaderChunk[ 'lights_phong_pars_vertex'] = "#if MAX_SPOT_LIGHTS > 0 || defined( USE_ENVMAP )\n\n varying vec3 vWorldPosition;\n\n#endif\n\n#if MAX_POINT_LIGHTS > 0\n\n uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/lights_phong_vertex.glsl -THREE.ShaderChunk['lights_phong_vertex'] = "#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP ) || defined( USE_ENVMAP )\n\n\tvWorldPosition = worldPosition.xyz;\n\n#endif"; +THREE.ShaderChunk[ 'lights_phong_vertex'] = "#if MAX_SPOT_LIGHTS > 0 || defined( USE_ENVMAP )\n\n vWorldPosition = worldPosition.xyz;\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/linear_to_gamma_fragment.glsl -THREE.ShaderChunk['linear_to_gamma_fragment'] = "\n\toutgoingLight = linearToOutput( outgoingLight );\n"; +THREE.ShaderChunk[ 'linear_to_gamma_fragment'] = "\n outgoingLight = linearToOutput( outgoingLight );\n"; // File:src/renderers/shaders/ShaderChunk/logdepthbuf_fragment.glsl -THREE.ShaderChunk['logdepthbuf_fragment'] = "#if defined(USE_LOGDEPTHBUF) && defined(USE_LOGDEPTHBUF_EXT)\n\n\tgl_FragDepthEXT = log2(vFragDepth) * logDepthBufFC * 0.5;\n\n#endif"; +THREE.ShaderChunk[ 'logdepthbuf_fragment'] = "#if defined(USE_LOGDEPTHBUF) && defined(USE_LOGDEPTHBUF_EXT)\n\n gl_FragDepthEXT = log2(vFragDepth) * logDepthBufFC * 0.5;\n\n#endif"; // File:src/renderers/shaders/ShaderChunk/logdepthbuf_pars_fragment.glsl -THREE.ShaderChunk['logdepthbuf_pars_fragment'] = "#ifdef USE_LOGDEPTHBUF\n\n\tuniform float logDepthBufFC;\n\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\n\t\t#extension GL_EXT_frag_depth : enable\n\t\tvarying float vFragDepth;\n\n\t#endif\n\n#endif"; +THREE.ShaderChunk[ 'logdepthbuf_pars_fragment'] = "#ifdef USE_LOGDEPTHBUF\n\n uniform float logDepthBufFC;\n\n #ifdef USE_LOGDEPTHBUF_EXT\n\n varying float vFragDepth;\n\n #endif\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/logdepthbuf_pars_vertex.glsl -THREE.ShaderChunk['logdepthbuf_pars_vertex'] = "#ifdef USE_LOGDEPTHBUF\n\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\n\t\tvarying float vFragDepth;\n\n\t#endif\n\n\tuniform float logDepthBufFC;\n\n#endif"; +THREE.ShaderChunk[ 'logdepthbuf_pars_vertex'] = "#ifdef USE_LOGDEPTHBUF\n\n #ifdef USE_LOGDEPTHBUF_EXT\n\n varying float vFragDepth;\n\n #endif\n\n uniform float logDepthBufFC;\n\n#endif"; // File:src/renderers/shaders/ShaderChunk/logdepthbuf_vertex.glsl -THREE.ShaderChunk['logdepthbuf_vertex'] = "#ifdef USE_LOGDEPTHBUF\n\n\tgl_Position.z = log2(max( EPSILON, gl_Position.w + 1.0 )) * logDepthBufFC;\n\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\n#else\n\n\t\tgl_Position.z = (gl_Position.z - 1.0) * gl_Position.w;\n\n\t#endif\n\n#endif"; +THREE.ShaderChunk[ 'logdepthbuf_vertex'] = "#ifdef USE_LOGDEPTHBUF\n\n gl_Position.z = log2(max( EPSILON, gl_Position.w + 1.0 )) * logDepthBufFC;\n\n #ifdef USE_LOGDEPTHBUF_EXT\n\n vFragDepth = 1.0 + gl_Position.w;\n\n#else\n\n gl_Position.z = (gl_Position.z - 1.0) * gl_Position.w;\n\n #endif\n\n#endif"; // File:src/renderers/shaders/ShaderChunk/map_fragment.glsl -THREE.ShaderChunk['map_fragment'] = "#ifdef USE_MAP\n\n\tvec4 texelColor = texture2D( map, vUv );\n\n\ttexelColor.xyz = inputToLinear( texelColor.xyz );\n\n\tdiffuseColor *= texelColor;\n\n#endif"; +THREE.ShaderChunk[ 'map_fragment'] = "#ifdef USE_MAP\n\n vec4 texelColor = texture2D( map, vUv );\n\n texelColor.xyz = inputToLinear( texelColor.xyz );\n\n diffuseColor *= texelColor;\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/map_pars_fragment.glsl -THREE.ShaderChunk['map_pars_fragment'] = "#ifdef USE_MAP\n\n\tuniform sampler2D map;\n\n#endif"; +THREE.ShaderChunk[ 'map_pars_fragment'] = "#ifdef USE_MAP\n\n uniform sampler2D map;\n\n#endif"; // File:src/renderers/shaders/ShaderChunk/map_particle_fragment.glsl -THREE.ShaderChunk['map_particle_fragment'] = "#ifdef USE_MAP\n\n\tdiffuseColor *= texture2D( map, vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y ) * offsetRepeat.zw + offsetRepeat.xy );\n\n#endif\n"; +THREE.ShaderChunk[ 'map_particle_fragment'] = "#ifdef USE_MAP\n\n diffuseColor *= texture2D( map, vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y ) * offsetRepeat.zw + offsetRepeat.xy );\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/map_particle_pars_fragment.glsl -THREE.ShaderChunk['map_particle_pars_fragment'] = "#ifdef USE_MAP\n\n\tuniform vec4 offsetRepeat;\n\tuniform sampler2D map;\n\n#endif\n"; +THREE.ShaderChunk[ 'map_particle_pars_fragment'] = "#ifdef USE_MAP\n\n uniform vec4 offsetRepeat;\n uniform sampler2D map;\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/morphnormal_vertex.glsl -THREE.ShaderChunk['morphnormal_vertex'] = "#ifdef USE_MORPHNORMALS\n\n\tvec3 morphedNormal = vec3( 0.0 );\n\n\tmorphedNormal += ( morphNormal0 - normal ) * morphTargetInfluences[ 0 ];\n\tmorphedNormal += ( morphNormal1 - normal ) * morphTargetInfluences[ 1 ];\n\tmorphedNormal += ( morphNormal2 - normal ) * morphTargetInfluences[ 2 ];\n\tmorphedNormal += ( morphNormal3 - normal ) * morphTargetInfluences[ 3 ];\n\n\tmorphedNormal += normal;\n\n#endif"; +THREE.ShaderChunk[ 'morphnormal_vertex'] = "#ifdef USE_MORPHNORMALS\n\n objectNormal += ( morphNormal0 - normal ) * morphTargetInfluences[ 0 ];\n objectNormal += ( morphNormal1 - normal ) * morphTargetInfluences[ 1 ];\n objectNormal += ( morphNormal2 - normal ) * morphTargetInfluences[ 2 ];\n objectNormal += ( morphNormal3 - normal ) * morphTargetInfluences[ 3 ];\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/morphtarget_pars_vertex.glsl -THREE.ShaderChunk['morphtarget_pars_vertex'] = "#ifdef USE_MORPHTARGETS\n\n\t#ifndef USE_MORPHNORMALS\n\n\tuniform float morphTargetInfluences[ 8 ];\n\n\t#else\n\n\tuniform float morphTargetInfluences[ 4 ];\n\n\t#endif\n\n#endif"; +THREE.ShaderChunk[ 'morphtarget_pars_vertex'] = "#ifdef USE_MORPHTARGETS\n\n #ifndef USE_MORPHNORMALS\n\n uniform float morphTargetInfluences[ 8 ];\n\n #else\n\n uniform float morphTargetInfluences[ 4 ];\n\n #endif\n\n#endif"; // File:src/renderers/shaders/ShaderChunk/morphtarget_vertex.glsl -THREE.ShaderChunk['morphtarget_vertex'] = "#ifdef USE_MORPHTARGETS\n\n\tvec3 morphed = vec3( 0.0 );\n\tmorphed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];\n\tmorphed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];\n\tmorphed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];\n\tmorphed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];\n\n\t#ifndef USE_MORPHNORMALS\n\n\tmorphed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];\n\tmorphed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];\n\tmorphed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];\n\tmorphed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];\n\n\t#endif\n\n\tmorphed += position;\n\n#endif"; +THREE.ShaderChunk[ 'morphtarget_vertex'] = "#ifdef USE_MORPHTARGETS\n\n transformed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];\n transformed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];\n transformed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];\n transformed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];\n\n #ifndef USE_MORPHNORMALS\n\n transformed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];\n transformed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];\n transformed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];\n transformed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];\n\n #endif\n\n#endif\n"; + +// File:src/renderers/shaders/ShaderChunk/normal_phong_fragment.glsl + +THREE.ShaderChunk[ 'normal_phong_fragment'] = "#ifndef FLAT_SHADED\n\n vec3 normal = normalize( vNormal );\n\n #ifdef DOUBLE_SIDED\n\n normal = normal * ( -1.0 + 2.0 * float( gl_FrontFacing ) );\n\n #endif\n\n#else\n\n vec3 fdx = dFdx( vViewPosition );\n vec3 fdy = dFdy( vViewPosition );\n vec3 normal = normalize( cross( fdx, fdy ) );\n\n#endif\n\n#ifdef USE_NORMALMAP\n\n normal = perturbNormal2Arb( -vViewPosition, normal );\n\n#elif defined( USE_BUMPMAP )\n\n normal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );\n\n#endif\n\n"; // File:src/renderers/shaders/ShaderChunk/normalmap_pars_fragment.glsl -THREE.ShaderChunk['normalmap_pars_fragment'] = "#ifdef USE_NORMALMAP\n\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n\n\t// Per-Pixel Tangent Space Normal Mapping\n\t// http://hacksoflife.blogspot.ch/2009/11/per-pixel-tangent-space-normal-mapping.html\n\n\tvec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {\n\n\t\tvec3 q0 = dFdx( eye_pos.xyz );\n\t\tvec3 q1 = dFdy( eye_pos.xyz );\n\t\tvec2 st0 = dFdx( vUv.st );\n\t\tvec2 st1 = dFdy( vUv.st );\n\n\t\tvec3 S = normalize( q0 * st1.t - q1 * st0.t );\n\t\tvec3 T = normalize( -q0 * st1.s + q1 * st0.s );\n\t\tvec3 N = normalize( surf_norm );\n\n\t\tvec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\t\tmapN.xy = normalScale * mapN.xy;\n\t\tmat3 tsn = mat3( S, T, N );\n\t\treturn normalize( tsn * mapN );\n\n\t}\n\n#endif\n"; +THREE.ShaderChunk[ 'normalmap_pars_fragment'] = "#ifdef USE_NORMALMAP\n\n uniform sampler2D normalMap;\n uniform vec2 normalScale;\n\n // Per-Pixel Tangent Space Normal Mapping\n // http://hacksoflife.blogspot.ch/2009/11/per-pixel-tangent-space-normal-mapping.html\n\n vec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {\n\n vec3 q0 = dFdx( eye_pos.xyz );\n vec3 q1 = dFdy( eye_pos.xyz );\n vec2 st0 = dFdx( vUv.st );\n vec2 st1 = dFdy( vUv.st );\n\n vec3 S = normalize( q0 * st1.t - q1 * st0.t );\n vec3 T = normalize( -q0 * st1.s + q1 * st0.s );\n vec3 N = normalize( surf_norm );\n\n vec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n mapN.xy = normalScale * mapN.xy;\n mat3 tsn = mat3( S, T, N );\n return normalize( tsn * mapN );\n\n }\n\n#endif\n"; + +// File:src/renderers/shaders/ShaderChunk/project_vertex.glsl + +THREE.ShaderChunk[ 'project_vertex'] = "#ifdef USE_SKINNING\n\n vec4 mvPosition = modelViewMatrix * skinned;\n\n#else\n\n vec4 mvPosition = modelViewMatrix * vec4( transformed, 1.0 );\n\n#endif\n\ngl_Position = projectionMatrix * mvPosition;\n"; // File:src/renderers/shaders/ShaderChunk/shadowmap_fragment.glsl -THREE.ShaderChunk['shadowmap_fragment'] = "#ifdef USE_SHADOWMAP\n\n\t#ifdef SHADOWMAP_DEBUG\n\n\t\tvec3 frustumColors[3];\n\t\tfrustumColors[0] = vec3( 1.0, 0.5, 0.0 );\n\t\tfrustumColors[1] = vec3( 0.0, 1.0, 0.8 );\n\t\tfrustumColors[2] = vec3( 0.0, 0.5, 1.0 );\n\n\t#endif\n\n\t#ifdef SHADOWMAP_CASCADE\n\n\t\tint inFrustumCount = 0;\n\n\t#endif\n\n\tfloat fDepth;\n\tvec3 shadowColor = vec3( 1.0 );\n\n\tfor( int i = 0; i < MAX_SHADOWS; i ++ ) {\n\n\t\tvec3 shadowCoord = vShadowCoord[ i ].xyz / vShadowCoord[ i ].w;\n\n\t\t\t\t// if ( something && something ) breaks ATI OpenGL shader compiler\n\t\t\t\t// if ( all( something, something ) ) using this instead\n\n\t\tbvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );\n\t\tbool inFrustum = all( inFrustumVec );\n\n\t\t\t\t// don't shadow pixels outside of light frustum\n\t\t\t\t// use just first frustum (for cascades)\n\t\t\t\t// don't shadow pixels behind far plane of light frustum\n\n\t\t#ifdef SHADOWMAP_CASCADE\n\n\t\t\tinFrustumCount += int( inFrustum );\n\t\t\tbvec3 frustumTestVec = bvec3( inFrustum, inFrustumCount == 1, shadowCoord.z <= 1.0 );\n\n\t\t#else\n\n\t\t\tbvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );\n\n\t\t#endif\n\n\t\tbool frustumTest = all( frustumTestVec );\n\n\t\tif ( frustumTest ) {\n\n\t\t\tshadowCoord.z += shadowBias[ i ];\n\n\t\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\n\t\t\t\t\t\t// Percentage-close filtering\n\t\t\t\t\t\t// (9 pixel kernel)\n\t\t\t\t\t\t// http://fabiensanglard.net/shadowmappingPCF/\n\n\t\t\t\tfloat shadow = 0.0;\n\n\t\t/*\n\t\t\t\t\t\t// nested loops breaks shader compiler / validator on some ATI cards when using OpenGL\n\t\t\t\t\t\t// must enroll loop manually\n\n\t\t\t\tfor ( float y = -1.25; y <= 1.25; y += 1.25 )\n\t\t\t\t\tfor ( float x = -1.25; x <= 1.25; x += 1.25 ) {\n\n\t\t\t\t\t\tvec4 rgbaDepth = texture2D( shadowMap[ i ], vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy );\n\n\t\t\t\t\t\t\t\t// doesn't seem to produce any noticeable visual difference compared to simple texture2D lookup\n\t\t\t\t\t\t\t\t//vec4 rgbaDepth = texture2DProj( shadowMap[ i ], vec4( vShadowCoord[ i ].w * ( vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy ), 0.05, vShadowCoord[ i ].w ) );\n\n\t\t\t\t\t\tfloat fDepth = unpackDepth( rgbaDepth );\n\n\t\t\t\t\t\tif ( fDepth < shadowCoord.z )\n\t\t\t\t\t\t\tshadow += 1.0;\n\n\t\t\t\t}\n\n\t\t\t\tshadow /= 9.0;\n\n\t\t*/\n\n\t\t\t\tconst float shadowDelta = 1.0 / 9.0;\n\n\t\t\t\tfloat xPixelOffset = 1.0 / shadowMapSize[ i ].x;\n\t\t\t\tfloat yPixelOffset = 1.0 / shadowMapSize[ i ].y;\n\n\t\t\t\tfloat dx0 = -1.25 * xPixelOffset;\n\t\t\t\tfloat dy0 = -1.25 * yPixelOffset;\n\t\t\t\tfloat dx1 = 1.25 * xPixelOffset;\n\t\t\t\tfloat dy1 = 1.25 * yPixelOffset;\n\n\t\t\t\tfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );\n\t\t\t\tif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n\t\t\t\tfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );\n\t\t\t\tif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n\t\t\t\tfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );\n\t\t\t\tif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n\t\t\t\tfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );\n\t\t\t\tif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n\t\t\t\tfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );\n\t\t\t\tif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n\t\t\t\tfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );\n\t\t\t\tif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n\t\t\t\tfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );\n\t\t\t\tif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n\t\t\t\tfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );\n\t\t\t\tif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n\t\t\t\tfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );\n\t\t\t\tif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n\t\t\t\tshadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );\n\n\t\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\n\t\t\t\t\t\t// Percentage-close filtering\n\t\t\t\t\t\t// (9 pixel kernel)\n\t\t\t\t\t\t// http://fabiensanglard.net/shadowmappingPCF/\n\n\t\t\t\tfloat shadow = 0.0;\n\n\t\t\t\tfloat xPixelOffset = 1.0 / shadowMapSize[ i ].x;\n\t\t\t\tfloat yPixelOffset = 1.0 / shadowMapSize[ i ].y;\n\n\t\t\t\tfloat dx0 = -1.0 * xPixelOffset;\n\t\t\t\tfloat dy0 = -1.0 * yPixelOffset;\n\t\t\t\tfloat dx1 = 1.0 * xPixelOffset;\n\t\t\t\tfloat dy1 = 1.0 * yPixelOffset;\n\n\t\t\t\tmat3 shadowKernel;\n\t\t\t\tmat3 depthKernel;\n\n\t\t\t\tdepthKernel[0][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );\n\t\t\t\tdepthKernel[0][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );\n\t\t\t\tdepthKernel[0][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );\n\t\t\t\tdepthKernel[1][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );\n\t\t\t\tdepthKernel[1][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );\n\t\t\t\tdepthKernel[1][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );\n\t\t\t\tdepthKernel[2][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );\n\t\t\t\tdepthKernel[2][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );\n\t\t\t\tdepthKernel[2][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );\n\n\t\t\t\tvec3 shadowZ = vec3( shadowCoord.z );\n\t\t\t\tshadowKernel[0] = vec3(lessThan(depthKernel[0], shadowZ ));\n\t\t\t\tshadowKernel[0] *= vec3(0.25);\n\n\t\t\t\tshadowKernel[1] = vec3(lessThan(depthKernel[1], shadowZ ));\n\t\t\t\tshadowKernel[1] *= vec3(0.25);\n\n\t\t\t\tshadowKernel[2] = vec3(lessThan(depthKernel[2], shadowZ ));\n\t\t\t\tshadowKernel[2] *= vec3(0.25);\n\n\t\t\t\tvec2 fractionalCoord = 1.0 - fract( shadowCoord.xy * shadowMapSize[i].xy );\n\n\t\t\t\tshadowKernel[0] = mix( shadowKernel[1], shadowKernel[0], fractionalCoord.x );\n\t\t\t\tshadowKernel[1] = mix( shadowKernel[2], shadowKernel[1], fractionalCoord.x );\n\n\t\t\t\tvec4 shadowValues;\n\t\t\t\tshadowValues.x = mix( shadowKernel[0][1], shadowKernel[0][0], fractionalCoord.y );\n\t\t\t\tshadowValues.y = mix( shadowKernel[0][2], shadowKernel[0][1], fractionalCoord.y );\n\t\t\t\tshadowValues.z = mix( shadowKernel[1][1], shadowKernel[1][0], fractionalCoord.y );\n\t\t\t\tshadowValues.w = mix( shadowKernel[1][2], shadowKernel[1][1], fractionalCoord.y );\n\n\t\t\t\tshadow = dot( shadowValues, vec4( 1.0 ) );\n\n\t\t\t\tshadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );\n\n\t\t\t#else\n\n\t\t\t\tvec4 rgbaDepth = texture2D( shadowMap[ i ], shadowCoord.xy );\n\t\t\t\tfloat fDepth = unpackDepth( rgbaDepth );\n\n\t\t\t\tif ( fDepth < shadowCoord.z )\n\n\t\t// spot with multiple shadows is darker\n\n\t\t\t\t\tshadowColor = shadowColor * vec3( 1.0 - shadowDarkness[ i ] );\n\n\t\t// spot with multiple shadows has the same color as single shadow spot\n\n\t\t// \t\t\t\t\tshadowColor = min( shadowColor, vec3( shadowDarkness[ i ] ) );\n\n\t\t\t#endif\n\n\t\t}\n\n\n\t\t#ifdef SHADOWMAP_DEBUG\n\n\t\t\t#ifdef SHADOWMAP_CASCADE\n\n\t\t\t\tif ( inFrustum && inFrustumCount == 1 ) outgoingLight *= frustumColors[ i ];\n\n\t\t\t#else\n\n\t\t\t\tif ( inFrustum ) outgoingLight *= frustumColors[ i ];\n\n\t\t\t#endif\n\n\t\t#endif\n\n\t}\n\n\t// NOTE: I am unsure if this is correct in linear space. -bhouston, Dec 29, 2014\n\tshadowColor = inputToLinear( shadowColor );\n\n\toutgoingLight = outgoingLight * shadowColor;\n\n#endif\n"; +THREE.ShaderChunk[ 'shadowmap_fragment'] = "#ifdef USE_SHADOWMAP\n\n #ifdef SHADOWMAP_DEBUG\n\n vec3 frustumColors[3];\n frustumColors[0] = vec3( 1.0, 0.5, 0.0 );\n frustumColors[1] = vec3( 0.0, 1.0, 0.8 );\n frustumColors[2] = vec3( 0.0, 0.5, 1.0 );\n\n #endif\n\n float fDepth;\n vec3 shadowColor = vec3( 1.0 );\n\n for( int i = 0; i < MAX_SHADOWS; i ++ ) {\n\n // to save on uniform space, we use the sign of @shadowDarkness[ i ] to determine\n // whether or not this light is a point light ( shadowDarkness[ i ] < 0 == point light)\n bool isPointLight = shadowDarkness[ i ] < 0.0;\n\n // get the real shadow darkness\n float realShadowDarkness = abs( shadowDarkness[ i ] );\n\n // for point lights, the uniform @vShadowCoord is re-purposed to hold\n // the distance from the light to the world-space position of the fragment.\n vec3 lightToPosition = vShadowCoord[ i ].xyz;\n\n float texelSizeX = 1.0 / shadowMapSize[ i ].x;\n float texelSizeY = 1.0 / shadowMapSize[ i ].y;\n\n vec3 shadowCoord = vShadowCoord[ i ].xyz / vShadowCoord[ i ].w;\n float shadow = 0.0;\n\n // if ( something && something ) breaks ATI OpenGL shader compiler\n // if ( all( something, something ) ) using this instead\n\n bvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );\n bool inFrustum = all( inFrustumVec );\n\n bvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );\n\n bool frustumTest = all( frustumTestVec );\n\n if ( frustumTest || isPointLight ) { \n\n #if defined( SHADOWMAP_TYPE_PCF )\n\n #if defined(POINT_LIGHT_SHADOWS)\n\n if( isPointLight ) {\n\n float cubeTexelSize = 1.0 / ( shadowMapSize[ i ].x * 0.25 );\n vec3 baseDirection3D = normalize( lightToPosition );\n vec2 baseDirection2D = cubeToUV( baseDirection3D, texelSizeY );\n\n initGridSamplingDisk();\n\n float diskRadius = 1.25;\n float numSamples = 1.0;\n shadow = 0.0;\n\n vec3 baseDirection = normalize( lightToPosition );\n float curDistance = length( lightToPosition );\n\n float dist = unpack1K( texture2D( shadowMap[ i ], baseDirection2D ) ) + 0.1;\n if ( curDistance >= dist )\n shadow += 1.0;\n \n // evaluate each sampling direction\n for( int s = 0; s < 20; s++ ) {\n \n vec3 offset = gridSamplingDisk[ s ] * diskRadius * cubeTexelSize;\n vec3 adjustedBaseDirection3D = baseDirection3D + offset;\n vec2 adjustedBaseDirection2D = cubeToUV( adjustedBaseDirection3D, texelSizeY );\n dist = unpack1K( texture2D( shadowMap[ i ], adjustedBaseDirection2D ) ) + shadowBias[ i ];\n if ( curDistance >= dist )\n shadow += 1.0;\n numSamples += 1.0;\n\n }\n\n shadow /= numSamples;\n\n } else {\n\n #endif\n\n // Percentage-close filtering\n // (9 pixel kernel)\n // http://fabiensanglard.net/shadowmappingPCF/\n \n /*\n // nested loops breaks shader compiler / validator on some ATI cards when using OpenGL\n // must enroll loop manually\n for ( float y = -1.25; y <= 1.25; y += 1.25 )\n for ( float x = -1.25; x <= 1.25; x += 1.25 ) {\n vec4 rgbaDepth = texture2D( shadowMap[ i ], vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy );\n // doesn't seem to produce any noticeable visual difference compared to simple texture2D lookup\n //vec4 rgbaDepth = texture2DProj( shadowMap[ i ], vec4( vShadowCoord[ i ].w * ( vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy ), 0.05, vShadowCoord[ i ].w ) );\n float fDepth = unpackDepth( rgbaDepth );\n if ( fDepth < shadowCoord.z )\n shadow += 1.0;\n }\n shadow /= 9.0;\n */\n\n shadowCoord.z += shadowBias[ i ];\n\n const float shadowDelta = 1.0 / 9.0;\n\n float xPixelOffset = texelSizeX;\n float yPixelOffset = texelSizeY;\n\n float dx0 = -1.25 * xPixelOffset;\n float dy0 = -1.25 * yPixelOffset;\n float dx1 = 1.25 * xPixelOffset;\n float dy1 = 1.25 * yPixelOffset;\n\n fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );\n if ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );\n if ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );\n if ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );\n if ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );\n if ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );\n if ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );\n if ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );\n if ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );\n if ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n #if defined(POINT_LIGHT_SHADOWS)\n\n }\n\n #endif\n\n shadowColor = shadowColor * vec3( ( 1.0 - realShadowDarkness * shadow ) );\n\n #elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\n #if defined(POINT_LIGHT_SHADOWS)\n\n if( isPointLight ) {\n\n float cubeTexelSize = 1.0 / ( shadowMapSize[ i ].x * 0.25 );\n vec3 baseDirection3D = normalize( lightToPosition );\n vec2 baseDirection2D = cubeToUV( baseDirection3D, texelSizeY );\n\n initGridSamplingDisk();\n\n float diskRadius = 2.25;\n float numSamples = 1.0;\n shadow = 0.0;\n\n vec3 baseDirection = normalize( lightToPosition );\n float curDistance = length( lightToPosition );\n\n float dist = unpack1K( texture2D( shadowMap[ i ], baseDirection2D ) ) + 0.1;\n if ( curDistance >= dist )\n shadow += 1.0;\n\n // evaluate each sampling direction\n for( int s = 0; s < 20; s++ ) {\n\n vec3 offset = gridSamplingDisk[ s ] * diskRadius * cubeTexelSize;\n vec3 adjustedBaseDirection3D = baseDirection3D + offset;\n vec2 adjustedBaseDirection2D = cubeToUV( adjustedBaseDirection3D, texelSizeY );\n dist = unpack1K( texture2D( shadowMap[ i ], adjustedBaseDirection2D ) ) + shadowBias[ i ];\n if ( curDistance >= dist )\n shadow += 1.0;\n numSamples += 1.0;\n\n }\n\n shadow /= numSamples;\n\n } else {\n\n #endif\n\n // Percentage-close filtering\n // (9 pixel kernel)\n // http://fabiensanglard.net/shadowmappingPCF/\n\n shadowCoord.z += shadowBias[ i ];\n\n float xPixelOffset = texelSizeX;\n float yPixelOffset = texelSizeY;\n\n float dx0 = -1.0 * xPixelOffset;\n float dy0 = -1.0 * yPixelOffset;\n float dx1 = 1.0 * xPixelOffset;\n float dy1 = 1.0 * yPixelOffset;\n\n mat3 shadowKernel;\n mat3 depthKernel;\n\n depthKernel[0][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );\n depthKernel[0][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );\n depthKernel[0][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );\n depthKernel[1][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );\n depthKernel[1][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );\n depthKernel[1][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );\n depthKernel[2][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );\n depthKernel[2][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );\n depthKernel[2][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );\n\n vec3 shadowZ = vec3( shadowCoord.z );\n shadowKernel[0] = vec3(lessThan(depthKernel[0], shadowZ ));\n shadowKernel[0] *= vec3(0.25);\n\n shadowKernel[1] = vec3(lessThan(depthKernel[1], shadowZ ));\n shadowKernel[1] *= vec3(0.25);\n\n shadowKernel[2] = vec3(lessThan(depthKernel[2], shadowZ ));\n shadowKernel[2] *= vec3(0.25);\n\n vec2 fractionalCoord = 1.0 - fract( shadowCoord.xy * shadowMapSize[i].xy );\n\n shadowKernel[0] = mix( shadowKernel[1], shadowKernel[0], fractionalCoord.x );\n shadowKernel[1] = mix( shadowKernel[2], shadowKernel[1], fractionalCoord.x );\n\n vec4 shadowValues;\n shadowValues.x = mix( shadowKernel[0][1], shadowKernel[0][0], fractionalCoord.y );\n shadowValues.y = mix( shadowKernel[0][2], shadowKernel[0][1], fractionalCoord.y );\n shadowValues.z = mix( shadowKernel[1][1], shadowKernel[1][0], fractionalCoord.y );\n shadowValues.w = mix( shadowKernel[1][2], shadowKernel[1][1], fractionalCoord.y );\n\n shadow = dot( shadowValues, vec4( 1.0 ) );\n\n #if defined(POINT_LIGHT_SHADOWS)\n \n }\n\n #endif\n\n shadowColor = shadowColor * vec3( ( 1.0 - realShadowDarkness * shadow ) );\n\n #else\n\n #if defined(POINT_LIGHT_SHADOWS)\n\n if( isPointLight ) {\n\n vec3 baseDirection3D = normalize( lightToPosition );\n vec2 baseDirection2D = cubeToUV( baseDirection3D, texelSizeY );\n vec4 data = texture2D( shadowMap[ i ], baseDirection2D );\n float dist = unpack1K( data ) + shadowBias[ i ];\n if ( length( lightToPosition ) >= dist)\n shadowColor = shadowColor * vec3( 1.0 - realShadowDarkness );\n\n } else {\n\n #endif\n shadowCoord.z += shadowBias[ i ];\n\n vec4 rgbaDepth = texture2D( shadowMap[ i ], shadowCoord.xy );\n float fDepth = unpackDepth( rgbaDepth );\n\n if ( fDepth < shadowCoord.z )\n\n // spot with multiple shadows is darker\n\n shadowColor = shadowColor * vec3( 1.0 - realShadowDarkness );\n\n // spot with multiple shadows has the same color as single shadow spot\n\n // shadowColor = min( shadowColor, vec3( realShadowDarkness ) );\n\n #if defined(POINT_LIGHT_SHADOWS)\n\n }\n\n #endif\n\n #endif\n\n }\n\n\n #ifdef SHADOWMAP_DEBUG\n\n if ( inFrustum ) outgoingLight *= frustumColors[ i ];\n\n #endif\n\n }\n\n outgoingLight = outgoingLight * shadowColor;\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl -THREE.ShaderChunk['shadowmap_pars_fragment'] = "#ifdef USE_SHADOWMAP\n\n\tuniform sampler2D shadowMap[ MAX_SHADOWS ];\n\tuniform vec2 shadowMapSize[ MAX_SHADOWS ];\n\n\tuniform float shadowDarkness[ MAX_SHADOWS ];\n\tuniform float shadowBias[ MAX_SHADOWS ];\n\n\tvarying vec4 vShadowCoord[ MAX_SHADOWS ];\n\n\tfloat unpackDepth( const in vec4 rgba_depth ) {\n\n\t\tconst vec4 bit_shift = vec4( 1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0 );\n\t\tfloat depth = dot( rgba_depth, bit_shift );\n\t\treturn depth;\n\n\t}\n\n#endif"; +THREE.ShaderChunk[ 'shadowmap_pars_fragment'] = "#ifdef USE_SHADOWMAP\n\n uniform sampler2D shadowMap[ MAX_SHADOWS ];\n uniform vec2 shadowMapSize[ MAX_SHADOWS ];\n\n uniform float shadowDarkness[ MAX_SHADOWS ];\n uniform float shadowBias[ MAX_SHADOWS ];\n\n varying vec4 vShadowCoord[ MAX_SHADOWS ];\n\n float unpackDepth( const in vec4 rgba_depth ) {\n\n 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 );\n float depth = dot( rgba_depth, bit_shift );\n return depth;\n\n }\n\n #if defined(POINT_LIGHT_SHADOWS)\n\n float unpack1K ( vec4 color ) {\n\n const vec4 bitSh = vec4( 1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0 );\n return dot( color, bitSh ) * 1000.0;\n\n }\n\n // cubeToUV() maps a 3D direction vector suitable for cube texture mapping to a 2D\n // vector suitable for 2D texture mapping. This code uses the following layout for the\n // 2D texture:\n //\n // xzXZ\n // y Y\n //\n // Y - Positive y direction\n // y - Negative y direction\n // X - Positive x direction\n // x - Negative x direction\n // Z - Positive z direction\n // z - Negative z direction\n //\n // Source and test bed:\n // https://gist.github.com/tschw/da10c43c467ce8afd0c4\n\n vec2 cubeToUV( vec3 v, float texelSizeY ) {\n\n // Number of texels to avoid at the edge of each square\n\n vec3 absV = abs( v );\n\n // Intersect unit cube\n\n float scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n absV *= scaleToCube;\n\n // Apply scale to avoid seams\n\n // two texels less per square (one texel will do for NEAREST)\n v *= scaleToCube * ( 1.0 - 4.0 * texelSizeY );\n\n // Unwrap\n\n // space: -1 ... 1 range for each square\n //\n // #X## dim := ( 4 , 2 )\n // # # center := ( 1 , 1 )\n\n vec2 planar = v.xy;\n\n float almostATexel = 1.5 * texelSizeY;\n float almostOne = 1.0 - almostATexel;\n\n if ( absV.z >= almostOne ) {\n\n if ( v.z > 0.0 )\n planar.x = 4.0 - v.x;\n\n } else if ( absV.x >= almostOne ) {\n\n float signX = sign( v.x );\n planar.x = v.z * signX + 2.0 * signX;\n\n } else if ( absV.y >= almostOne ) {\n\n float signY = sign( v.y );\n planar.x = v.x + 2.0 * signY + 2.0;\n planar.y = v.z * signY - 2.0;\n\n }\n\n // Transform to UV space\n\n // scale := 0.5 / dim\n // translate := ( center + 0.5 ) / dim\n return vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\n }\n\n vec3 gridSamplingDisk[ 20 ];\n bool gridSamplingInitialized = false;\n\n void initGridSamplingDisk() {\n\n if( gridSamplingInitialized ) {\n\n return;\n\n }\n\n gridSamplingDisk[0] = vec3(1, 1, 1);\n gridSamplingDisk[1] = vec3(1, -1, 1);\n gridSamplingDisk[2] = vec3(-1, -1, 1);\n gridSamplingDisk[3] = vec3(-1, 1, 1);\n gridSamplingDisk[4] = vec3(1, 1, -1);\n gridSamplingDisk[5] = vec3(1, -1, -1);\n gridSamplingDisk[6] = vec3(-1, -1, -1);\n gridSamplingDisk[7] = vec3(-1, 1, -1);\n gridSamplingDisk[8] = vec3(1, 1, 0);\n gridSamplingDisk[9] = vec3(1, -1, 0);\n gridSamplingDisk[10] = vec3(-1, -1, 0);\n gridSamplingDisk[11] = vec3(-1, 1, 0);\n gridSamplingDisk[12] = vec3(1, 0, 1);\n gridSamplingDisk[13] = vec3(-1, 0, 1);\n gridSamplingDisk[14] = vec3(1, 0, -1);\n gridSamplingDisk[15] = vec3(-1, 0, -1);\n gridSamplingDisk[16] = vec3(0, 1, 1);\n gridSamplingDisk[17] = vec3(0, -1, 1);\n gridSamplingDisk[18] = vec3(0, -1, -1);\n gridSamplingDisk[19] = vec3(0, 1, -1);\n\n gridSamplingInitialized = true;\n\n }\n\n #endif\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/shadowmap_pars_vertex.glsl -THREE.ShaderChunk['shadowmap_pars_vertex'] = "#ifdef USE_SHADOWMAP\n\n\tvarying vec4 vShadowCoord[ MAX_SHADOWS ];\n\tuniform mat4 shadowMatrix[ MAX_SHADOWS ];\n\n#endif"; +THREE.ShaderChunk[ 'shadowmap_pars_vertex'] = "#ifdef USE_SHADOWMAP\n\n uniform float shadowDarkness[ MAX_SHADOWS ];\n uniform mat4 shadowMatrix[ MAX_SHADOWS ];\n varying vec4 vShadowCoord[ MAX_SHADOWS ];\n\n#endif"; // File:src/renderers/shaders/ShaderChunk/shadowmap_vertex.glsl -THREE.ShaderChunk['shadowmap_vertex'] = "#ifdef USE_SHADOWMAP\n\n\tfor( int i = 0; i < MAX_SHADOWS; i ++ ) {\n\n\t\tvShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;\n\n\t}\n\n#endif"; +THREE.ShaderChunk[ 'shadowmap_vertex'] = "#ifdef USE_SHADOWMAP\n\n for( int i = 0; i < MAX_SHADOWS; i ++ ) {\n\n #if defined(POINT_LIGHT_SHADOWS)\n\n // if shadowDarkness[ i ] < 0.0, that means we have a point light with a cube\n // shadow map\n if( shadowDarkness[ i ] < 0.0 ) {\n\n // calculate vector from light to vertex in view space\n\n vec3 fromLight = mvPosition.xyz - pointLightPosition[ i ];\n\n // Transform 'fromLight' into world space by multiplying it on the left\n // side of 'viewMatrix'. This is equivalent to multiplying it on the right\n // side of the transpose of 'viewMatrix'. Since 'viewMatrix' is orthogonal, \n // its transpose is the same as its inverse.\n\n fromLight = fromLight * mat3( viewMatrix );\n\n // We repurpose vShadowCoord to hold the distance in world space from the\n // light to the vertex. This value will be interpolated correctly in the fragment shader.\n\n vShadowCoord[ i ] = vec4( fromLight, 1.0 );\n\n } else {\n\n vShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;\n\n }\n\n #else\n\n vShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;\n\n #endif\n\n }\n\n#endif"; // File:src/renderers/shaders/ShaderChunk/skinbase_vertex.glsl -THREE.ShaderChunk['skinbase_vertex'] = "#ifdef USE_SKINNING\n\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n\n#endif"; +THREE.ShaderChunk[ 'skinbase_vertex'] = "#ifdef USE_SKINNING\n\n mat4 boneMatX = getBoneMatrix( skinIndex.x );\n mat4 boneMatY = getBoneMatrix( skinIndex.y );\n mat4 boneMatZ = getBoneMatrix( skinIndex.z );\n mat4 boneMatW = getBoneMatrix( skinIndex.w );\n\n#endif"; // File:src/renderers/shaders/ShaderChunk/skinning_pars_vertex.glsl -THREE.ShaderChunk['skinning_pars_vertex'] = "#ifdef USE_SKINNING\n\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\n\t#ifdef BONE_TEXTURE\n\n\t\tuniform sampler2D boneTexture;\n\t\tuniform int boneTextureWidth;\n\t\tuniform int boneTextureHeight;\n\n\t\tmat4 getBoneMatrix( const in float i ) {\n\n\t\t\tfloat j = i * 4.0;\n\t\t\tfloat x = mod( j, float( boneTextureWidth ) );\n\t\t\tfloat y = floor( j / float( boneTextureWidth ) );\n\n\t\t\tfloat dx = 1.0 / float( boneTextureWidth );\n\t\t\tfloat dy = 1.0 / float( boneTextureHeight );\n\n\t\t\ty = dy * ( y + 0.5 );\n\n\t\t\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n\t\t\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n\t\t\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n\t\t\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n\n\t\t\tmat4 bone = mat4( v1, v2, v3, v4 );\n\n\t\t\treturn bone;\n\n\t\t}\n\n\t#else\n\n\t\tuniform mat4 boneGlobalMatrices[ MAX_BONES ];\n\n\t\tmat4 getBoneMatrix( const in float i ) {\n\n\t\t\tmat4 bone = boneGlobalMatrices[ int(i) ];\n\t\t\treturn bone;\n\n\t\t}\n\n\t#endif\n\n#endif\n"; +THREE.ShaderChunk[ 'skinning_pars_vertex'] = "#ifdef USE_SKINNING\n\n uniform mat4 bindMatrix;\n uniform mat4 bindMatrixInverse;\n\n #ifdef BONE_TEXTURE\n\n uniform sampler2D boneTexture;\n uniform int boneTextureWidth;\n uniform int boneTextureHeight;\n\n mat4 getBoneMatrix( const in float i ) {\n\n float j = i * 4.0;\n float x = mod( j, float( boneTextureWidth ) );\n float y = floor( j / float( boneTextureWidth ) );\n\n float dx = 1.0 / float( boneTextureWidth );\n float dy = 1.0 / float( boneTextureHeight );\n\n y = dy * ( y + 0.5 );\n\n vec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n vec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n vec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n vec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n\n mat4 bone = mat4( v1, v2, v3, v4 );\n\n return bone;\n\n }\n\n #else\n\n uniform mat4 boneGlobalMatrices[ MAX_BONES ];\n\n mat4 getBoneMatrix( const in float i ) {\n\n mat4 bone = boneGlobalMatrices[ int(i) ];\n return bone;\n\n }\n\n #endif\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/skinning_vertex.glsl -THREE.ShaderChunk['skinning_vertex'] = "#ifdef USE_SKINNING\n\n\t#ifdef USE_MORPHTARGETS\n\n\tvec4 skinVertex = bindMatrix * vec4( morphed, 1.0 );\n\n\t#else\n\n\tvec4 skinVertex = bindMatrix * vec4( position, 1.0 );\n\n\t#endif\n\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\tskinned = bindMatrixInverse * skinned;\n\n#endif\n"; +THREE.ShaderChunk[ 'skinning_vertex'] = "#ifdef USE_SKINNING\n\n vec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\n vec4 skinned = vec4( 0.0 );\n skinned += boneMatX * skinVertex * skinWeight.x;\n skinned += boneMatY * skinVertex * skinWeight.y;\n skinned += boneMatZ * skinVertex * skinWeight.z;\n skinned += boneMatW * skinVertex * skinWeight.w;\n skinned = bindMatrixInverse * skinned;\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/skinnormal_vertex.glsl -THREE.ShaderChunk['skinnormal_vertex'] = "#ifdef USE_SKINNING\n\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\n\t#ifdef USE_MORPHNORMALS\n\n\tvec4 skinnedNormal = skinMatrix * vec4( morphedNormal, 0.0 );\n\n\t#else\n\n\tvec4 skinnedNormal = skinMatrix * vec4( normal, 0.0 );\n\n\t#endif\n\n#endif\n"; +THREE.ShaderChunk[ 'skinnormal_vertex'] = "#ifdef USE_SKINNING\n\n mat4 skinMatrix = mat4( 0.0 );\n skinMatrix += skinWeight.x * boneMatX;\n skinMatrix += skinWeight.y * boneMatY;\n skinMatrix += skinWeight.z * boneMatZ;\n skinMatrix += skinWeight.w * boneMatW;\n skinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\n objectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/specularmap_fragment.glsl -THREE.ShaderChunk['specularmap_fragment'] = "float specularStrength;\n\n#ifdef USE_SPECULARMAP\n\n\tvec4 texelSpecular = texture2D( specularMap, vUv );\n\tspecularStrength = texelSpecular.r;\n\n#else\n\n\tspecularStrength = 1.0;\n\n#endif"; +THREE.ShaderChunk[ 'specularmap_fragment'] = "float specularStrength;\n\n#ifdef USE_SPECULARMAP\n\n vec4 texelSpecular = texture2D( specularMap, vUv );\n specularStrength = texelSpecular.r;\n\n#else\n\n specularStrength = 1.0;\n\n#endif"; // File:src/renderers/shaders/ShaderChunk/specularmap_pars_fragment.glsl -THREE.ShaderChunk['specularmap_pars_fragment'] = "#ifdef USE_SPECULARMAP\n\n\tuniform sampler2D specularMap;\n\n#endif"; +THREE.ShaderChunk[ 'specularmap_pars_fragment'] = "#ifdef USE_SPECULARMAP\n\n uniform sampler2D specularMap;\n\n#endif"; // File:src/renderers/shaders/ShaderChunk/uv2_pars_fragment.glsl -THREE.ShaderChunk['uv2_pars_fragment'] = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\n\tvarying vec2 vUv2;\n\n#endif"; +THREE.ShaderChunk[ 'uv2_pars_fragment'] = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\n varying vec2 vUv2;\n\n#endif"; // File:src/renderers/shaders/ShaderChunk/uv2_pars_vertex.glsl -THREE.ShaderChunk['uv2_pars_vertex'] = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\n\tattribute vec2 uv2;\n\tvarying vec2 vUv2;\n\n#endif"; +THREE.ShaderChunk[ 'uv2_pars_vertex'] = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\n attribute vec2 uv2;\n varying vec2 vUv2;\n\n#endif"; // File:src/renderers/shaders/ShaderChunk/uv2_vertex.glsl -THREE.ShaderChunk['uv2_vertex'] = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\n\tvUv2 = uv2;\n\n#endif"; +THREE.ShaderChunk[ 'uv2_vertex'] = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\n vUv2 = uv2;\n\n#endif"; // File:src/renderers/shaders/ShaderChunk/uv_pars_fragment.glsl -THREE.ShaderChunk['uv_pars_fragment'] = "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP )\n\n\tvarying vec2 vUv;\n\n#endif"; +THREE.ShaderChunk[ 'uv_pars_fragment'] = "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP )\n\n varying vec2 vUv;\n\n#endif"; // File:src/renderers/shaders/ShaderChunk/uv_pars_vertex.glsl -THREE.ShaderChunk['uv_pars_vertex'] = "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP )\n\n\tvarying vec2 vUv;\n\tuniform vec4 offsetRepeat;\n\n#endif\n"; +THREE.ShaderChunk[ 'uv_pars_vertex'] = "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP )\n\n varying vec2 vUv;\n uniform vec4 offsetRepeat;\n\n#endif\n"; // File:src/renderers/shaders/ShaderChunk/uv_vertex.glsl -THREE.ShaderChunk['uv_vertex'] = "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP )\n\n\tvUv = uv * offsetRepeat.zw + offsetRepeat.xy;\n\n#endif"; +THREE.ShaderChunk[ 'uv_vertex'] = "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP )\n\n vUv = uv * offsetRepeat.zw + offsetRepeat.xy;\n\n#endif"; // File:src/renderers/shaders/ShaderChunk/worldpos_vertex.glsl -THREE.ShaderChunk['worldpos_vertex'] = "#if defined( USE_ENVMAP ) || defined( PHONG ) || defined( LAMBERT ) || defined ( USE_SHADOWMAP )\n\n\t#ifdef USE_SKINNING\n\n\t\tvec4 worldPosition = modelMatrix * skinned;\n\n\t#elif defined( USE_MORPHTARGETS )\n\n\t\tvec4 worldPosition = modelMatrix * vec4( morphed, 1.0 );\n\n\t#else\n\n\t\tvec4 worldPosition = modelMatrix * vec4( position, 1.0 );\n\n\t#endif\n\n#endif\n"; +THREE.ShaderChunk[ 'worldpos_vertex'] = "#if defined( USE_ENVMAP ) || defined( PHONG ) || defined( LAMBERT ) || defined ( USE_SHADOWMAP )\n\n #ifdef USE_SKINNING\n\n vec4 worldPosition = modelMatrix * skinned;\n\n #else\n\n vec4 worldPosition = modelMatrix * vec4( transformed, 1.0 );\n\n #endif\n\n#endif\n"; // File:src/renderers/shaders/UniformsUtils.js @@ -18096,65 +20408,65 @@ THREE.ShaderChunk['worldpos_vertex'] = "#if defined( USE_ENVMAP ) || defined( PH THREE.UniformsUtils = { - merge: function (uniforms) { + merge: function ( uniforms ) { - var merged = {}; + var merged = {}; - for (var u = 0; u < uniforms.length; u++) { + for ( var u = 0; u < uniforms.length; u ++ ) { - var tmp = this.clone(uniforms[u]); + var tmp = this.clone( uniforms[ u ] ); - for (var p in tmp) { + for ( var p in tmp ) { - merged[p] = tmp[p]; + merged[ p ] = tmp[ p ]; - } + } - } + } - return merged; + return merged; - }, + }, - clone: function (uniforms_src) { + clone: function ( uniforms_src ) { - var uniforms_dst = {}; + var uniforms_dst = {}; - for (var u in uniforms_src) { + for ( var u in uniforms_src ) { - uniforms_dst[u] = {}; + uniforms_dst[ u ] = {}; - for (var p in uniforms_src[u]) { + for ( var p in uniforms_src[ u ] ) { - var parameter_src = uniforms_src[u][p]; + var 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.Matrix3 || - parameter_src instanceof THREE.Matrix4 || - parameter_src instanceof THREE.Texture) { + 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.Matrix3 || + parameter_src instanceof THREE.Matrix4 || + parameter_src instanceof THREE.Texture ) { - uniforms_dst[u][p] = parameter_src.clone(); + uniforms_dst[ u ][ p ] = parameter_src.clone(); - } else if (parameter_src instanceof Array) { + } else if ( Array.isArray( parameter_src ) ) { - uniforms_dst[u][p] = parameter_src.slice(); + uniforms_dst[ u ][ p ] = parameter_src.slice(); - } else { + } else { - uniforms_dst[u][p] = parameter_src; + uniforms_dst[ u ][ p ] = parameter_src; - } + } - } + } - } + } - return uniforms_dst; + return uniforms_dst; - } + } }; @@ -18166,115 +20478,128 @@ THREE.UniformsUtils = { THREE.UniformsLib = { - common: { + common: { - "diffuse": {type: "c", value: new THREE.Color(0xeeeeee)}, - "opacity": {type: "f", value: 1.0}, + "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)}, + "map" : { type: "t", value: null }, + "offsetRepeat" : { type: "v4", value: new THREE.Vector4( 0, 0, 1, 1 ) }, - "specularMap": {type: "t", value: null}, - "alphaMap": {type: "t", value: null}, + "specularMap" : { type: "t", value: null }, + "alphaMap" : { type: "t", value: null }, - "envMap": {type: "t", value: null}, - "flipEnvMap": {type: "f", value: -1}, - "reflectivity": {type: "f", value: 1.0}, - "refractionRatio": {type: "f", value: 0.98}, + "envMap" : { type: "t", value: null }, + "flipEnvMap" : { type: "f", value: - 1 }, + "reflectivity" : { type: "f", value: 1.0 }, + "refractionRatio" : { type: "f", value: 0.98 } - "morphTargetInfluences": {type: "f", value: 0} + }, - }, + aomap: { - aomap: { + "aoMap" : { type: "t", value: null }, + "aoMapIntensity" : { type: "f", value: 1 }, - "aoMap": {type: "t", value: null}, - "aoMapIntensity": {type: "f", value: 1}, + }, - }, + lightmap: { - lightmap: { + "lightMap" : { type: "t", value: null }, + "lightMapIntensity" : { type: "f", value: 1 }, - "lightMap": {type: "t", value: null}, - "lightMapIntensity": {type: "f", value: 1}, + }, - }, + emissivemap: { - bump: { + "emissiveMap" : { type: "t", value: null }, - "bumpMap": {type: "t", value: null}, - "bumpScale": {type: "f", value: 1} + }, - }, + bumpmap: { - normalmap: { + "bumpMap" : { type: "t", value: null }, + "bumpScale" : { type: "f", value: 1 } - "normalMap": {type: "t", value: null}, - "normalScale": {type: "v2", value: new THREE.Vector2(1, 1)} - }, + }, - fog: { + normalmap: { - "fogDensity": {type: "f", value: 0.00025}, - "fogNear": {type: "f", value: 1}, - "fogFar": {type: "f", value: 2000}, - "fogColor": {type: "c", value: new THREE.Color(0xffffff)} + "normalMap" : { type: "t", value: null }, + "normalScale" : { type: "v2", value: new THREE.Vector2( 1, 1 ) } - }, + }, - lights: { + displacementmap: { - "ambientLightColor": {type: "fv", value: []}, + "displacementMap" : { type: "t", value: null }, + "displacementScale" : { type: "f", value: 1 }, + "displacementBias" : { type: "f", value: 0 } - "directionalLightDirection": {type: "fv", value: []}, - "directionalLightColor": {type: "fv", value: []}, + }, - "hemisphereLightDirection": {type: "fv", value: []}, - "hemisphereLightSkyColor": {type: "fv", value: []}, - "hemisphereLightGroundColor": {type: "fv", value: []}, + fog : { - "pointLightColor": {type: "fv", value: []}, - "pointLightPosition": {type: "fv", value: []}, - "pointLightDistance": {type: "fv1", value: []}, - "pointLightDecay": {type: "fv1", value: []}, + "fogDensity" : { type: "f", value: 0.00025 }, + "fogNear" : { type: "f", value: 1 }, + "fogFar" : { type: "f", value: 2000 }, + "fogColor" : { type: "c", value: new THREE.Color( 0xffffff ) } - "spotLightColor": {type: "fv", value: []}, - "spotLightPosition": {type: "fv", value: []}, - "spotLightDirection": {type: "fv", value: []}, - "spotLightDistance": {type: "fv1", value: []}, - "spotLightAngleCos": {type: "fv1", value: []}, - "spotLightExponent": {type: "fv1", value: []}, - "spotLightDecay": {type: "fv1", value: []} + }, - }, + lights: { - particle: { + "ambientLightColor" : { type: "fv", value: [] }, - "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}, - "offsetRepeat": {type: "v4", value: new THREE.Vector4(0, 0, 1, 1)}, + "directionalLightDirection" : { type: "fv", value: [] }, + "directionalLightColor" : { type: "fv", value: [] }, - "fogDensity": {type: "f", value: 0.00025}, - "fogNear": {type: "f", value: 1}, - "fogFar": {type: "f", value: 2000}, - "fogColor": {type: "c", value: new THREE.Color(0xffffff)} + "hemisphereLightDirection" : { type: "fv", value: [] }, + "hemisphereLightSkyColor" : { type: "fv", value: [] }, + "hemisphereLightGroundColor" : { type: "fv", value: [] }, - }, + "pointLightColor" : { type: "fv", value: [] }, + "pointLightPosition" : { type: "fv", value: [] }, + "pointLightDistance" : { type: "fv1", value: [] }, + "pointLightDecay" : { type: "fv1", value: [] }, - shadowmap: { + "spotLightColor" : { type: "fv", value: [] }, + "spotLightPosition" : { type: "fv", value: [] }, + "spotLightDirection" : { type: "fv", value: [] }, + "spotLightDistance" : { type: "fv1", value: [] }, + "spotLightAngleCos" : { type: "fv1", value: [] }, + "spotLightExponent" : { type: "fv1", value: [] }, + "spotLightDecay" : { type: "fv1", value: [] } - "shadowMap": {type: "tv", value: []}, - "shadowMapSize": {type: "v2v", value: []}, + }, - "shadowBias": {type: "fv1", value: []}, - "shadowDarkness": {type: "fv1", value: []}, + points: { - "shadowMatrix": {type: "m4v", value: []} + "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 }, + "offsetRepeat" : { type: "v4", value: new THREE.Vector4( 0, 0, 1, 1 ) }, - } + "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: [] } + + } }; @@ -18291,820 +20616,904 @@ THREE.UniformsLib = { THREE.ShaderLib = { - 'basic': { + 'basic': { - uniforms: THREE.UniformsUtils.merge([ + uniforms: THREE.UniformsUtils.merge( [ - THREE.UniformsLib["common"], - THREE.UniformsLib["fog"], - THREE.UniformsLib["shadowmap"] + THREE.UniformsLib[ "common" ], + THREE.UniformsLib[ "aomap" ], + THREE.UniformsLib[ "fog" ], + THREE.UniformsLib[ "shadowmap" ] - ]), + ] ), - vertexShader: [ + vertexShader: [ - THREE.ShaderChunk["common"], - THREE.ShaderChunk["uv_pars_vertex"], - THREE.ShaderChunk["uv2_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"], - THREE.ShaderChunk["logdepthbuf_pars_vertex"], + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "uv_pars_vertex" ], + THREE.ShaderChunk[ "uv2_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" ], + THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ], - "void main() {", + "void main() {", - THREE.ShaderChunk["uv_vertex"], - THREE.ShaderChunk["uv2_vertex"], - THREE.ShaderChunk["color_vertex"], - THREE.ShaderChunk["skinbase_vertex"], + THREE.ShaderChunk[ "uv_vertex" ], + THREE.ShaderChunk[ "uv2_vertex" ], + THREE.ShaderChunk[ "color_vertex" ], + THREE.ShaderChunk[ "skinbase_vertex" ], - " #ifdef USE_ENVMAP", + " #ifdef USE_ENVMAP", - THREE.ShaderChunk["morphnormal_vertex"], - THREE.ShaderChunk["skinnormal_vertex"], - THREE.ShaderChunk["defaultnormal_vertex"], + THREE.ShaderChunk[ "beginnormal_vertex" ], + THREE.ShaderChunk[ "morphnormal_vertex" ], + THREE.ShaderChunk[ "skinnormal_vertex" ], + THREE.ShaderChunk[ "defaultnormal_vertex" ], - " #endif", + " #endif", - THREE.ShaderChunk["morphtarget_vertex"], - THREE.ShaderChunk["skinning_vertex"], - THREE.ShaderChunk["default_vertex"], - THREE.ShaderChunk["logdepthbuf_vertex"], + THREE.ShaderChunk[ "begin_vertex" ], + THREE.ShaderChunk[ "morphtarget_vertex" ], + THREE.ShaderChunk[ "skinning_vertex" ], + THREE.ShaderChunk[ "project_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_vertex" ], - THREE.ShaderChunk["worldpos_vertex"], - THREE.ShaderChunk["envmap_vertex"], - THREE.ShaderChunk["shadowmap_vertex"], + THREE.ShaderChunk[ "worldpos_vertex" ], + THREE.ShaderChunk[ "envmap_vertex" ], + THREE.ShaderChunk[ "shadowmap_vertex" ], - "}" + "}" - ].join("\n"), + ].join( "\n" ), - fragmentShader: [ + fragmentShader: [ - "uniform vec3 diffuse;", - "uniform float opacity;", + "uniform vec3 diffuse;", + "uniform float opacity;", - THREE.ShaderChunk["common"], - THREE.ShaderChunk["color_pars_fragment"], - THREE.ShaderChunk["uv_pars_fragment"], - THREE.ShaderChunk["uv2_pars_fragment"], - THREE.ShaderChunk["map_pars_fragment"], - THREE.ShaderChunk["alphamap_pars_fragment"], - THREE.ShaderChunk["envmap_pars_fragment"], - THREE.ShaderChunk["fog_pars_fragment"], - THREE.ShaderChunk["shadowmap_pars_fragment"], - THREE.ShaderChunk["specularmap_pars_fragment"], - THREE.ShaderChunk["logdepthbuf_pars_fragment"], + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "color_pars_fragment" ], + THREE.ShaderChunk[ "uv_pars_fragment" ], + THREE.ShaderChunk[ "uv2_pars_fragment" ], + THREE.ShaderChunk[ "map_pars_fragment" ], + THREE.ShaderChunk[ "alphamap_pars_fragment" ], + THREE.ShaderChunk[ "aomap_pars_fragment" ], + THREE.ShaderChunk[ "envmap_pars_fragment" ], + THREE.ShaderChunk[ "fog_pars_fragment" ], + THREE.ShaderChunk[ "shadowmap_pars_fragment" ], + THREE.ShaderChunk[ "specularmap_pars_fragment" ], + THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ], - "void main() {", + "void main() {", - " vec3 outgoingLight = vec3( 0.0 );", // outgoing light does not have an alpha, the surface does - " vec4 diffuseColor = vec4( diffuse, opacity );", + " vec3 outgoingLight = vec3( 0.0 );", + " vec4 diffuseColor = vec4( diffuse, opacity );", + " vec3 totalAmbientLight = vec3( 1.0 );", // hardwired - THREE.ShaderChunk["logdepthbuf_fragment"], - THREE.ShaderChunk["map_fragment"], - THREE.ShaderChunk["color_fragment"], - THREE.ShaderChunk["alphamap_fragment"], - THREE.ShaderChunk["alphatest_fragment"], - THREE.ShaderChunk["specularmap_fragment"], + THREE.ShaderChunk[ "logdepthbuf_fragment" ], + THREE.ShaderChunk[ "map_fragment" ], + THREE.ShaderChunk[ "color_fragment" ], + THREE.ShaderChunk[ "alphamap_fragment" ], + THREE.ShaderChunk[ "alphatest_fragment" ], + THREE.ShaderChunk[ "specularmap_fragment" ], + THREE.ShaderChunk[ "aomap_fragment" ], - " outgoingLight = diffuseColor.rgb;", // simple shader + " outgoingLight = diffuseColor.rgb * totalAmbientLight;", // simple shader - THREE.ShaderChunk["envmap_fragment"], - THREE.ShaderChunk["shadowmap_fragment"], // TODO: Shadows on an otherwise unlit surface doesn't make sense. + THREE.ShaderChunk[ "envmap_fragment" ], + THREE.ShaderChunk[ "shadowmap_fragment" ], // TODO: Shadows on an otherwise unlit surface doesn't make sense. - THREE.ShaderChunk["linear_to_gamma_fragment"], + THREE.ShaderChunk[ "linear_to_gamma_fragment" ], - THREE.ShaderChunk["fog_fragment"], + THREE.ShaderChunk[ "fog_fragment" ], - " gl_FragColor = vec4( outgoingLight, diffuseColor.a );", // TODO, this should be pre-multiplied to allow for bright highlights on very transparent objects + " gl_FragColor = vec4( outgoingLight, diffuseColor.a );", - "}" + "}" - ].join("\n") + ].join( "\n" ) - }, + }, - 'lambert': { + 'lambert': { - uniforms: THREE.UniformsUtils.merge([ + uniforms: THREE.UniformsUtils.merge( [ - THREE.UniformsLib["common"], - THREE.UniformsLib["fog"], - THREE.UniformsLib["lights"], - THREE.UniformsLib["shadowmap"], + THREE.UniformsLib[ "common" ], + THREE.UniformsLib[ "fog" ], + THREE.UniformsLib[ "lights" ], + THREE.UniformsLib[ "shadowmap" ], - { - "emissive": {type: "c", value: new THREE.Color(0x000000)} - } + { + "emissive" : { type: "c", value: new THREE.Color( 0x000000 ) } + } - ]), + ] ), - vertexShader: [ + vertexShader: [ - "#define LAMBERT", + "#define LAMBERT", - "varying vec3 vLightFront;", + "varying vec3 vLightFront;", - "#ifdef DOUBLE_SIDED", + "#ifdef DOUBLE_SIDED", - " varying vec3 vLightBack;", + " varying vec3 vLightBack;", - "#endif", + "#endif", - THREE.ShaderChunk["common"], - THREE.ShaderChunk["uv_pars_vertex"], - THREE.ShaderChunk["uv2_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"], - THREE.ShaderChunk["logdepthbuf_pars_vertex"], + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "uv_pars_vertex" ], + THREE.ShaderChunk[ "uv2_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" ], + THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ], - "void main() {", + "void main() {", - THREE.ShaderChunk["uv_vertex"], - THREE.ShaderChunk["uv2_vertex"], - THREE.ShaderChunk["color_vertex"], + THREE.ShaderChunk[ "uv_vertex" ], + THREE.ShaderChunk[ "uv2_vertex" ], + THREE.ShaderChunk[ "color_vertex" ], - THREE.ShaderChunk["morphnormal_vertex"], - THREE.ShaderChunk["skinbase_vertex"], - THREE.ShaderChunk["skinnormal_vertex"], - THREE.ShaderChunk["defaultnormal_vertex"], + THREE.ShaderChunk[ "beginnormal_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["logdepthbuf_vertex"], + THREE.ShaderChunk[ "begin_vertex" ], + THREE.ShaderChunk[ "morphtarget_vertex" ], + THREE.ShaderChunk[ "skinning_vertex" ], + THREE.ShaderChunk[ "project_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_vertex" ], - THREE.ShaderChunk["worldpos_vertex"], - THREE.ShaderChunk["envmap_vertex"], - THREE.ShaderChunk["lights_lambert_vertex"], - THREE.ShaderChunk["shadowmap_vertex"], + THREE.ShaderChunk[ "worldpos_vertex" ], + THREE.ShaderChunk[ "envmap_vertex" ], + THREE.ShaderChunk[ "lights_lambert_vertex" ], + THREE.ShaderChunk[ "shadowmap_vertex" ], - "}" + "}" - ].join("\n"), + ].join( "\n" ), - fragmentShader: [ + fragmentShader: [ - "uniform vec3 diffuse;", - "uniform vec3 emissive;", - "uniform float opacity;", + "uniform vec3 diffuse;", + "uniform vec3 emissive;", + "uniform float opacity;", - "varying vec3 vLightFront;", + "varying vec3 vLightFront;", - "#ifdef DOUBLE_SIDED", + "#ifdef DOUBLE_SIDED", - " varying vec3 vLightBack;", + " varying vec3 vLightBack;", - "#endif", + "#endif", - THREE.ShaderChunk["common"], - THREE.ShaderChunk["color_pars_fragment"], - THREE.ShaderChunk["uv_pars_fragment"], - THREE.ShaderChunk["uv2_pars_fragment"], - THREE.ShaderChunk["map_pars_fragment"], - THREE.ShaderChunk["alphamap_pars_fragment"], - THREE.ShaderChunk["envmap_pars_fragment"], - THREE.ShaderChunk["fog_pars_fragment"], - THREE.ShaderChunk["shadowmap_pars_fragment"], - THREE.ShaderChunk["specularmap_pars_fragment"], - THREE.ShaderChunk["logdepthbuf_pars_fragment"], + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "color_pars_fragment" ], + THREE.ShaderChunk[ "uv_pars_fragment" ], + THREE.ShaderChunk[ "uv2_pars_fragment" ], + THREE.ShaderChunk[ "map_pars_fragment" ], + THREE.ShaderChunk[ "alphamap_pars_fragment" ], + THREE.ShaderChunk[ "envmap_pars_fragment" ], + THREE.ShaderChunk[ "fog_pars_fragment" ], + THREE.ShaderChunk[ "shadowmap_pars_fragment" ], + THREE.ShaderChunk[ "specularmap_pars_fragment" ], + THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ], - "void main() {", + "void main() {", - " vec3 outgoingLight = vec3( 0.0 );", // outgoing light does not have an alpha, the surface does - " vec4 diffuseColor = vec4( diffuse, opacity );", + " vec3 outgoingLight = vec3( 0.0 );", // outgoing light does not have an alpha, the surface does + " vec4 diffuseColor = vec4( diffuse, opacity );", - THREE.ShaderChunk["logdepthbuf_fragment"], - THREE.ShaderChunk["map_fragment"], - THREE.ShaderChunk["color_fragment"], - THREE.ShaderChunk["alphamap_fragment"], - THREE.ShaderChunk["alphatest_fragment"], - THREE.ShaderChunk["specularmap_fragment"], + THREE.ShaderChunk[ "logdepthbuf_fragment" ], + THREE.ShaderChunk[ "map_fragment" ], + THREE.ShaderChunk[ "color_fragment" ], + THREE.ShaderChunk[ "alphamap_fragment" ], + THREE.ShaderChunk[ "alphatest_fragment" ], + THREE.ShaderChunk[ "specularmap_fragment" ], - " #ifdef DOUBLE_SIDED", + " #ifdef DOUBLE_SIDED", - //"float isFront = float( gl_FrontFacing );", - //"gl_FragColor.xyz *= isFront * vLightFront + ( 1.0 - isFront ) * vLightBack;", + " if ( gl_FrontFacing )", + " outgoingLight += diffuseColor.rgb * vLightFront + emissive;", + " else", + " outgoingLight += diffuseColor.rgb * vLightBack + emissive;", - " if ( gl_FrontFacing )", - " outgoingLight += diffuseColor.rgb * vLightFront + emissive;", - " else", - " outgoingLight += diffuseColor.rgb * vLightBack + emissive;", + " #else", - " #else", + " outgoingLight += diffuseColor.rgb * vLightFront + emissive;", - " outgoingLight += diffuseColor.rgb * vLightFront + emissive;", + " #endif", - " #endif", + THREE.ShaderChunk[ "envmap_fragment" ], + THREE.ShaderChunk[ "shadowmap_fragment" ], - THREE.ShaderChunk["envmap_fragment"], - THREE.ShaderChunk["shadowmap_fragment"], + THREE.ShaderChunk[ "linear_to_gamma_fragment" ], - THREE.ShaderChunk["linear_to_gamma_fragment"], + THREE.ShaderChunk[ "fog_fragment" ], - THREE.ShaderChunk["fog_fragment"], + " gl_FragColor = vec4( outgoingLight, diffuseColor.a );", - " gl_FragColor = vec4( outgoingLight, diffuseColor.a );", // TODO, this should be pre-multiplied to allow for bright highlights on very transparent objects + "}" - "}" + ].join( "\n" ) - ].join("\n") + }, - }, + 'phong': { - 'phong': { + uniforms: THREE.UniformsUtils.merge( [ - uniforms: THREE.UniformsUtils.merge([ + THREE.UniformsLib[ "common" ], + THREE.UniformsLib[ "aomap" ], + THREE.UniformsLib[ "lightmap" ], + THREE.UniformsLib[ "emissivemap" ], + THREE.UniformsLib[ "bumpmap" ], + THREE.UniformsLib[ "normalmap" ], + THREE.UniformsLib[ "displacementmap" ], + THREE.UniformsLib[ "fog" ], + THREE.UniformsLib[ "lights" ], + THREE.UniformsLib[ "shadowmap" ], - THREE.UniformsLib["common"], - THREE.UniformsLib["aomap"], - THREE.UniformsLib["lightmap"], - THREE.UniformsLib["bump"], - THREE.UniformsLib["normalmap"], - THREE.UniformsLib["fog"], - THREE.UniformsLib["lights"], - THREE.UniformsLib["shadowmap"], + { + "emissive" : { type: "c", value: new THREE.Color( 0x000000 ) }, + "specular" : { type: "c", value: new THREE.Color( 0x111111 ) }, + "shininess": { type: "f", value: 30 } + } - { - "emissive": {type: "c", value: new THREE.Color(0x000000)}, - "specular": {type: "c", value: new THREE.Color(0x111111)}, - "shininess": {type: "f", value: 30} - } + ] ), - ]), + vertexShader: [ - vertexShader: [ + "#define PHONG", - "#define PHONG", + "varying vec3 vViewPosition;", - "varying vec3 vViewPosition;", + "#ifndef FLAT_SHADED", - "#ifndef FLAT_SHADED", + " varying vec3 vNormal;", - " varying vec3 vNormal;", + "#endif", - "#endif", + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "uv_pars_vertex" ], + THREE.ShaderChunk[ "uv2_pars_vertex" ], + THREE.ShaderChunk[ "displacementmap_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" ], + THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ], - THREE.ShaderChunk["common"], - THREE.ShaderChunk["uv_pars_vertex"], - THREE.ShaderChunk["uv2_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"], - THREE.ShaderChunk["logdepthbuf_pars_vertex"], + "void main() {", - "void main() {", + THREE.ShaderChunk[ "uv_vertex" ], + THREE.ShaderChunk[ "uv2_vertex" ], + THREE.ShaderChunk[ "color_vertex" ], - THREE.ShaderChunk["uv_vertex"], - THREE.ShaderChunk["uv2_vertex"], - THREE.ShaderChunk["color_vertex"], + THREE.ShaderChunk[ "beginnormal_vertex" ], + THREE.ShaderChunk[ "morphnormal_vertex" ], + THREE.ShaderChunk[ "skinbase_vertex" ], + THREE.ShaderChunk[ "skinnormal_vertex" ], + THREE.ShaderChunk[ "defaultnormal_vertex" ], - THREE.ShaderChunk["morphnormal_vertex"], - THREE.ShaderChunk["skinbase_vertex"], - THREE.ShaderChunk["skinnormal_vertex"], - THREE.ShaderChunk["defaultnormal_vertex"], + "#ifndef FLAT_SHADED", // Normal computed with derivatives when FLAT_SHADED - "#ifndef FLAT_SHADED", // Normal computed with derivatives when FLAT_SHADED + " vNormal = normalize( transformedNormal );", - " vNormal = normalize( transformedNormal );", + "#endif", - "#endif", + THREE.ShaderChunk[ "begin_vertex" ], + THREE.ShaderChunk[ "displacementmap_vertex" ], + THREE.ShaderChunk[ "morphtarget_vertex" ], + THREE.ShaderChunk[ "skinning_vertex" ], + THREE.ShaderChunk[ "project_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_vertex" ], - THREE.ShaderChunk["morphtarget_vertex"], - THREE.ShaderChunk["skinning_vertex"], - THREE.ShaderChunk["default_vertex"], - THREE.ShaderChunk["logdepthbuf_vertex"], + " vViewPosition = - mvPosition.xyz;", - " vViewPosition = -mvPosition.xyz;", + THREE.ShaderChunk[ "worldpos_vertex" ], + THREE.ShaderChunk[ "envmap_vertex" ], + THREE.ShaderChunk[ "lights_phong_vertex" ], + THREE.ShaderChunk[ "shadowmap_vertex" ], - THREE.ShaderChunk["worldpos_vertex"], - THREE.ShaderChunk["envmap_vertex"], - THREE.ShaderChunk["lights_phong_vertex"], - THREE.ShaderChunk["shadowmap_vertex"], + "}" - "}" + ].join( "\n" ), - ].join("\n"), + fragmentShader: [ - fragmentShader: [ + "#define PHONG", - "#define PHONG", + "uniform vec3 diffuse;", + "uniform vec3 emissive;", + "uniform vec3 specular;", + "uniform float shininess;", + "uniform float opacity;", - "uniform vec3 diffuse;", - "uniform vec3 emissive;", - "uniform vec3 specular;", - "uniform float shininess;", - "uniform float opacity;", + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "color_pars_fragment" ], + THREE.ShaderChunk[ "uv_pars_fragment" ], + THREE.ShaderChunk[ "uv2_pars_fragment" ], + THREE.ShaderChunk[ "map_pars_fragment" ], + THREE.ShaderChunk[ "alphamap_pars_fragment" ], + THREE.ShaderChunk[ "aomap_pars_fragment" ], + THREE.ShaderChunk[ "lightmap_pars_fragment" ], + THREE.ShaderChunk[ "emissivemap_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" ], + THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ], - THREE.ShaderChunk["common"], - THREE.ShaderChunk["color_pars_fragment"], - THREE.ShaderChunk["uv_pars_fragment"], - THREE.ShaderChunk["uv2_pars_fragment"], - THREE.ShaderChunk["map_pars_fragment"], - THREE.ShaderChunk["alphamap_pars_fragment"], - THREE.ShaderChunk["aomap_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"], - THREE.ShaderChunk["logdepthbuf_pars_fragment"], + "void main() {", - "void main() {", + " vec3 outgoingLight = vec3( 0.0 );", + " vec4 diffuseColor = vec4( diffuse, opacity );", + " vec3 totalAmbientLight = ambientLightColor;", + " vec3 totalEmissiveLight = emissive;", - " vec3 outgoingLight = vec3( 0.0 );", // outgoing light does not have an alpha, the surface does - " vec4 diffuseColor = vec4( diffuse, opacity );", - " vec3 totalAmbientLight = ambientLightColor;", + THREE.ShaderChunk[ "logdepthbuf_fragment" ], + THREE.ShaderChunk[ "map_fragment" ], + THREE.ShaderChunk[ "color_fragment" ], + THREE.ShaderChunk[ "alphamap_fragment" ], + THREE.ShaderChunk[ "alphatest_fragment" ], + THREE.ShaderChunk[ "specularmap_fragment" ], + THREE.ShaderChunk[ "normal_phong_fragment" ], + THREE.ShaderChunk[ "lightmap_fragment" ], + THREE.ShaderChunk[ "hemilight_fragment" ], + THREE.ShaderChunk[ "aomap_fragment" ], + THREE.ShaderChunk[ "emissivemap_fragment" ], - THREE.ShaderChunk["logdepthbuf_fragment"], - THREE.ShaderChunk["map_fragment"], - THREE.ShaderChunk["color_fragment"], - THREE.ShaderChunk["alphamap_fragment"], - THREE.ShaderChunk["alphatest_fragment"], - THREE.ShaderChunk["specularmap_fragment"], - THREE.ShaderChunk["lightmap_fragment"], - THREE.ShaderChunk["aomap_fragment"], + THREE.ShaderChunk[ "lights_phong_fragment" ], - THREE.ShaderChunk["lights_phong_fragment"], + THREE.ShaderChunk[ "envmap_fragment" ], + THREE.ShaderChunk[ "shadowmap_fragment" ], - THREE.ShaderChunk["envmap_fragment"], - THREE.ShaderChunk["shadowmap_fragment"], + THREE.ShaderChunk[ "linear_to_gamma_fragment" ], - THREE.ShaderChunk["linear_to_gamma_fragment"], + THREE.ShaderChunk[ "fog_fragment" ], - THREE.ShaderChunk["fog_fragment"], + " gl_FragColor = vec4( outgoingLight, diffuseColor.a );", - " gl_FragColor = vec4( outgoingLight, diffuseColor.a );", // TODO, this should be pre-multiplied to allow for bright highlights on very transparent objects + "}" - "}" + ].join( "\n" ) - ].join("\n") + }, - }, + 'points': { - 'particle_basic': { + uniforms: THREE.UniformsUtils.merge( [ - uniforms: THREE.UniformsUtils.merge([ + THREE.UniformsLib[ "points" ], + THREE.UniformsLib[ "shadowmap" ] - THREE.UniformsLib["particle"], - THREE.UniformsLib["shadowmap"] + ] ), - ]), + vertexShader: [ - vertexShader: [ + "uniform float size;", + "uniform float scale;", - "uniform float size;", - "uniform float scale;", + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "color_pars_vertex" ], + THREE.ShaderChunk[ "shadowmap_pars_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ], - THREE.ShaderChunk["common"], - THREE.ShaderChunk["color_pars_vertex"], - THREE.ShaderChunk["shadowmap_pars_vertex"], - THREE.ShaderChunk["logdepthbuf_pars_vertex"], + "void main() {", - "void main() {", + THREE.ShaderChunk[ "color_vertex" ], - THREE.ShaderChunk["color_vertex"], + " vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );", - " vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );", + " #ifdef USE_SIZEATTENUATION", + " gl_PointSize = size * ( scale / length( mvPosition.xyz ) );", + " #else", + " gl_PointSize = size;", + " #endif", - " #ifdef USE_SIZEATTENUATION", - " gl_PointSize = size * ( scale / length( mvPosition.xyz ) );", - " #else", - " gl_PointSize = size;", - " #endif", + " gl_Position = projectionMatrix * mvPosition;", - " gl_Position = projectionMatrix * mvPosition;", + THREE.ShaderChunk[ "logdepthbuf_vertex" ], + THREE.ShaderChunk[ "worldpos_vertex" ], + THREE.ShaderChunk[ "shadowmap_vertex" ], - THREE.ShaderChunk["logdepthbuf_vertex"], - THREE.ShaderChunk["worldpos_vertex"], - THREE.ShaderChunk["shadowmap_vertex"], + "}" - "}" + ].join( "\n" ), - ].join("\n"), + fragmentShader: [ - fragmentShader: [ + "uniform vec3 psColor;", + "uniform float opacity;", - "uniform vec3 psColor;", - "uniform float opacity;", + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "color_pars_fragment" ], + THREE.ShaderChunk[ "map_particle_pars_fragment" ], + THREE.ShaderChunk[ "fog_pars_fragment" ], + THREE.ShaderChunk[ "shadowmap_pars_fragment" ], + THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ], - THREE.ShaderChunk["common"], - THREE.ShaderChunk["color_pars_fragment"], - THREE.ShaderChunk["map_particle_pars_fragment"], - THREE.ShaderChunk["fog_pars_fragment"], - THREE.ShaderChunk["shadowmap_pars_fragment"], - THREE.ShaderChunk["logdepthbuf_pars_fragment"], + "void main() {", - "void main() {", + " vec3 outgoingLight = vec3( 0.0 );", + " vec4 diffuseColor = vec4( psColor, opacity );", - " vec3 outgoingLight = vec3( 0.0 );", // outgoing light does not have an alpha, the surface does - " vec4 diffuseColor = vec4( psColor, opacity );", + THREE.ShaderChunk[ "logdepthbuf_fragment" ], + THREE.ShaderChunk[ "map_particle_fragment" ], + THREE.ShaderChunk[ "color_fragment" ], + THREE.ShaderChunk[ "alphatest_fragment" ], - THREE.ShaderChunk["logdepthbuf_fragment"], - THREE.ShaderChunk["map_particle_fragment"], - THREE.ShaderChunk["color_fragment"], - THREE.ShaderChunk["alphatest_fragment"], + " outgoingLight = diffuseColor.rgb;", // simple shader - " outgoingLight = diffuseColor.rgb;", // simple shader + THREE.ShaderChunk[ "shadowmap_fragment" ], + THREE.ShaderChunk[ "fog_fragment" ], - THREE.ShaderChunk["shadowmap_fragment"], - THREE.ShaderChunk["fog_fragment"], + " gl_FragColor = vec4( outgoingLight, diffuseColor.a );", - " gl_FragColor = vec4( outgoingLight, diffuseColor.a );", // TODO, this should be pre-multiplied to allow for bright highlights on very transparent objects + "}" - "}" + ].join( "\n" ) - ].join("\n") + }, - }, + 'dashed': { - 'dashed': { + uniforms: THREE.UniformsUtils.merge( [ - uniforms: THREE.UniformsUtils.merge([ + THREE.UniformsLib[ "common" ], + THREE.UniformsLib[ "fog" ], - THREE.UniformsLib["common"], - THREE.UniformsLib["fog"], + { + "scale" : { type: "f", value: 1 }, + "dashSize" : { type: "f", value: 1 }, + "totalSize": { type: "f", value: 2 } + } - { - "scale": {type: "f", value: 1}, - "dashSize": {type: "f", value: 1}, - "totalSize": {type: "f", value: 2} - } + ] ), - ]), + vertexShader: [ - vertexShader: [ + "uniform float scale;", + "attribute float lineDistance;", - "uniform float scale;", - "attribute float lineDistance;", + "varying float vLineDistance;", - "varying float vLineDistance;", + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "color_pars_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ], - THREE.ShaderChunk["common"], - THREE.ShaderChunk["color_pars_vertex"], - THREE.ShaderChunk["logdepthbuf_pars_vertex"], + "void main() {", - "void main() {", + THREE.ShaderChunk[ "color_vertex" ], - THREE.ShaderChunk["color_vertex"], + " vLineDistance = scale * lineDistance;", - " vLineDistance = scale * lineDistance;", + " vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );", + " gl_Position = projectionMatrix * mvPosition;", - " vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );", - " gl_Position = projectionMatrix * mvPosition;", + THREE.ShaderChunk[ "logdepthbuf_vertex" ], - THREE.ShaderChunk["logdepthbuf_vertex"], + "}" - "}" + ].join( "\n" ), - ].join("\n"), + fragmentShader: [ - fragmentShader: [ + "uniform vec3 diffuse;", + "uniform float opacity;", - "uniform vec3 diffuse;", - "uniform float opacity;", + "uniform float dashSize;", + "uniform float totalSize;", - "uniform float dashSize;", - "uniform float totalSize;", + "varying float vLineDistance;", - "varying float vLineDistance;", + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "color_pars_fragment" ], + THREE.ShaderChunk[ "fog_pars_fragment" ], + THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ], - THREE.ShaderChunk["common"], - THREE.ShaderChunk["color_pars_fragment"], - THREE.ShaderChunk["fog_pars_fragment"], - THREE.ShaderChunk["logdepthbuf_pars_fragment"], + "void main() {", - "void main() {", + " if ( mod( vLineDistance, totalSize ) > dashSize ) {", - " if ( mod( vLineDistance, totalSize ) > dashSize ) {", + " discard;", - " discard;", + " }", - " }", + " vec3 outgoingLight = vec3( 0.0 );", + " vec4 diffuseColor = vec4( diffuse, opacity );", - " vec3 outgoingLight = vec3( 0.0 );", // outgoing light does not have an alpha, the surface does - " vec4 diffuseColor = vec4( diffuse, opacity );", + THREE.ShaderChunk[ "logdepthbuf_fragment" ], + THREE.ShaderChunk[ "color_fragment" ], - THREE.ShaderChunk["logdepthbuf_fragment"], - THREE.ShaderChunk["color_fragment"], + " outgoingLight = diffuseColor.rgb;", // simple shader - " outgoingLight = diffuseColor.rgb;", // simple shader + THREE.ShaderChunk[ "fog_fragment" ], - THREE.ShaderChunk["fog_fragment"], + " gl_FragColor = vec4( outgoingLight, diffuseColor.a );", - " gl_FragColor = vec4( outgoingLight, diffuseColor.a );", // TODO, this should be pre-multiplied to allow for bright highlights on very transparent objects + "}" - "}" + ].join( "\n" ) - ].join("\n") + }, - }, + 'depth': { - 'depth': { + uniforms: { - uniforms: { + "mNear": { type: "f", value: 1.0 }, + "mFar" : { type: "f", value: 2000.0 }, + "opacity" : { type: "f", value: 1.0 } - "mNear": {type: "f", value: 1.0}, - "mFar": {type: "f", value: 2000.0}, - "opacity": {type: "f", value: 1.0} + }, - }, + vertexShader: [ - vertexShader: [ + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "morphtarget_pars_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ], - THREE.ShaderChunk["common"], - THREE.ShaderChunk["morphtarget_pars_vertex"], - THREE.ShaderChunk["logdepthbuf_pars_vertex"], + "void main() {", - "void main() {", + THREE.ShaderChunk[ "begin_vertex" ], + THREE.ShaderChunk[ "morphtarget_vertex" ], + THREE.ShaderChunk[ "project_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_vertex" ], - THREE.ShaderChunk["morphtarget_vertex"], - THREE.ShaderChunk["default_vertex"], - THREE.ShaderChunk["logdepthbuf_vertex"], + "}" - "}" + ].join( "\n" ), - ].join("\n"), + fragmentShader: [ - fragmentShader: [ + "uniform float mNear;", + "uniform float mFar;", + "uniform float opacity;", - "uniform float mNear;", - "uniform float mFar;", - "uniform float opacity;", + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ], - THREE.ShaderChunk["common"], - THREE.ShaderChunk["logdepthbuf_pars_fragment"], + "void main() {", - "void main() {", + THREE.ShaderChunk[ "logdepthbuf_fragment" ], - THREE.ShaderChunk["logdepthbuf_fragment"], + " #ifdef USE_LOGDEPTHBUF_EXT", - " #ifdef USE_LOGDEPTHBUF_EXT", + " float depth = gl_FragDepthEXT / gl_FragCoord.w;", - " float depth = gl_FragDepthEXT / gl_FragCoord.w;", + " #else", - " #else", + " float depth = gl_FragCoord.z / gl_FragCoord.w;", - " float depth = gl_FragCoord.z / gl_FragCoord.w;", + " #endif", - " #endif", + " float color = 1.0 - smoothstep( mNear, mFar, depth );", + " gl_FragColor = vec4( vec3( color ), opacity );", - " float color = 1.0 - smoothstep( mNear, mFar, depth );", - " gl_FragColor = vec4( vec3( color ), opacity );", // TODO, this should be pre-multiplied to allow for bright highlights on very transparent objects + "}" - "}" + ].join( "\n" ) - ].join("\n") + }, - }, + 'normal': { - 'normal': { + uniforms: { - uniforms: { + "opacity" : { type: "f", value: 1.0 } - "opacity": {type: "f", value: 1.0} + }, - }, + vertexShader: [ - vertexShader: [ + "varying vec3 vNormal;", - "varying vec3 vNormal;", + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "morphtarget_pars_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ], - THREE.ShaderChunk["common"], - THREE.ShaderChunk["morphtarget_pars_vertex"], - THREE.ShaderChunk["logdepthbuf_pars_vertex"], + "void main() {", - "void main() {", + " vNormal = normalize( normalMatrix * normal );", - " vNormal = normalize( normalMatrix * normal );", + THREE.ShaderChunk[ "begin_vertex" ], + THREE.ShaderChunk[ "morphtarget_vertex" ], + THREE.ShaderChunk[ "project_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_vertex" ], - THREE.ShaderChunk["morphtarget_vertex"], - THREE.ShaderChunk["default_vertex"], - THREE.ShaderChunk["logdepthbuf_vertex"], + "}" - "}" + ].join( "\n" ), - ].join("\n"), + fragmentShader: [ - fragmentShader: [ + "uniform float opacity;", + "varying vec3 vNormal;", - "uniform float opacity;", - "varying vec3 vNormal;", + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ], - THREE.ShaderChunk["common"], - THREE.ShaderChunk["logdepthbuf_pars_fragment"], + "void main() {", - "void main() {", + " gl_FragColor = vec4( 0.5 * normalize( vNormal ) + 0.5, opacity );", - " gl_FragColor = vec4( 0.5 * normalize( vNormal ) + 0.5, opacity );", + THREE.ShaderChunk[ "logdepthbuf_fragment" ], - THREE.ShaderChunk["logdepthbuf_fragment"], + "}" - "}" + ].join( "\n" ) - ].join("\n") + }, - }, + /* ------------------------------------------------------------------------- + // Cube map shader + ------------------------------------------------------------------------- */ - /* ------------------------------------------------------------------------- - // Cube map shader - ------------------------------------------------------------------------- */ + 'cube': { - 'cube': { + uniforms: { "tCube": { type: "t", value: null }, + "tFlip": { type: "f", value: - 1 } }, - uniforms: { - "tCube": {type: "t", value: null}, - "tFlip": {type: "f", value: -1} - }, + vertexShader: [ - vertexShader: [ + "varying vec3 vWorldPosition;", - "varying vec3 vWorldPosition;", + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ], - THREE.ShaderChunk["common"], - THREE.ShaderChunk["logdepthbuf_pars_vertex"], + "void main() {", - "void main() {", + " vWorldPosition = transformDirection( position, modelMatrix );", - " vWorldPosition = transformDirection( position, modelMatrix );", + " gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", - " gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", + THREE.ShaderChunk[ "logdepthbuf_vertex" ], - THREE.ShaderChunk["logdepthbuf_vertex"], + "}" - "}" + ].join( "\n" ), - ].join("\n"), + fragmentShader: [ - fragmentShader: [ + "uniform samplerCube tCube;", + "uniform float tFlip;", - "uniform samplerCube tCube;", - "uniform float tFlip;", + "varying vec3 vWorldPosition;", - "varying vec3 vWorldPosition;", + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ], - THREE.ShaderChunk["common"], - THREE.ShaderChunk["logdepthbuf_pars_fragment"], + "void main() {", - "void main() {", + " gl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );", - " gl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );", + THREE.ShaderChunk[ "logdepthbuf_fragment" ], - THREE.ShaderChunk["logdepthbuf_fragment"], + "}" - "}" + ].join( "\n" ) - ].join("\n") + }, - }, + /* ------------------------------------------------------------------------- + // Cube map shader + ------------------------------------------------------------------------- */ - /* ------------------------------------------------------------------------- - // Cube map shader - ------------------------------------------------------------------------- */ + 'equirect': { - 'equirect': { + uniforms: { "tEquirect": { type: "t", value: null }, + "tFlip": { type: "f", value: - 1 } }, - uniforms: { - "tEquirect": {type: "t", value: null}, - "tFlip": {type: "f", value: -1} - }, + vertexShader: [ - vertexShader: [ + "varying vec3 vWorldPosition;", - "varying vec3 vWorldPosition;", + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ], - THREE.ShaderChunk["common"], - THREE.ShaderChunk["logdepthbuf_pars_vertex"], + "void main() {", - "void main() {", + " vWorldPosition = transformDirection( position, modelMatrix );", - " vWorldPosition = transformDirection( position, modelMatrix );", + " gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", - " gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", + THREE.ShaderChunk[ "logdepthbuf_vertex" ], - THREE.ShaderChunk["logdepthbuf_vertex"], + "}" - "}" + ].join( "\n" ), - ].join("\n"), + fragmentShader: [ - fragmentShader: [ + "uniform sampler2D tEquirect;", + "uniform float tFlip;", - "uniform sampler2D tEquirect;", - "uniform float tFlip;", + "varying vec3 vWorldPosition;", - "varying vec3 vWorldPosition;", + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ], - THREE.ShaderChunk["common"], - THREE.ShaderChunk["logdepthbuf_pars_fragment"], + "void main() {", - "void main() {", + // " gl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );", + "vec3 direction = normalize( vWorldPosition );", + "vec2 sampleUV;", + "sampleUV.y = saturate( tFlip * direction.y * -0.5 + 0.5 );", + "sampleUV.x = atan( direction.z, direction.x ) * RECIPROCAL_PI2 + 0.5;", + "gl_FragColor = texture2D( tEquirect, sampleUV );", - // " gl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );", - "vec3 direction = normalize( vWorldPosition );", - "vec2 sampleUV;", - "sampleUV.y = saturate( tFlip * direction.y * -0.5 + 0.5 );", - "sampleUV.x = atan( direction.z, direction.x ) * RECIPROCAL_PI2 + 0.5;", - "gl_FragColor = texture2D( tEquirect, sampleUV );", + THREE.ShaderChunk[ "logdepthbuf_fragment" ], - THREE.ShaderChunk["logdepthbuf_fragment"], + "}" - "}" + ].join( "\n" ) - ].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 + * http://aras-p.info/blog/2009/07/30/encoding-floats-to-rgba-the-final/ + */ - /* 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 - * http://aras-p.info/blog/2009/07/30/encoding-floats-to-rgba-the-final/ - */ + 'depthRGBA': { - 'depthRGBA': { + uniforms: {}, - uniforms: {}, + vertexShader: [ - vertexShader: [ + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "morphtarget_pars_vertex" ], + THREE.ShaderChunk[ "skinning_pars_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ], - THREE.ShaderChunk["common"], - THREE.ShaderChunk["morphtarget_pars_vertex"], - THREE.ShaderChunk["skinning_pars_vertex"], - THREE.ShaderChunk["logdepthbuf_pars_vertex"], + "void main() {", - "void main() {", + THREE.ShaderChunk[ "skinbase_vertex" ], - THREE.ShaderChunk["skinbase_vertex"], - THREE.ShaderChunk["morphtarget_vertex"], - THREE.ShaderChunk["skinning_vertex"], - THREE.ShaderChunk["default_vertex"], - THREE.ShaderChunk["logdepthbuf_vertex"], + THREE.ShaderChunk[ "begin_vertex" ], + THREE.ShaderChunk[ "morphtarget_vertex" ], + THREE.ShaderChunk[ "skinning_vertex" ], + THREE.ShaderChunk[ "project_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_vertex" ], - "}" + "}" - ].join("\n"), + ].join( "\n" ), - fragmentShader: [ + fragmentShader: [ - THREE.ShaderChunk["common"], - THREE.ShaderChunk["logdepthbuf_pars_fragment"], + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ], - "vec4 pack_depth( const in float depth ) {", + "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 = mod( depth * bit_shift * vec4( 255 ), vec4( 256 ) ) / vec4( 255 );", // " vec4 res = fract( depth * bit_shift );", - " res -= res.xxyz * bit_mask;", - " return res;", + " 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 = mod( depth * bit_shift * vec4( 255 ), vec4( 256 ) ) / vec4( 255 );", // " vec4 res = fract( depth * bit_shift );", + " res -= res.xxyz * bit_mask;", + " return res;", - "}", + "}", - "void main() {", + "void main() {", - THREE.ShaderChunk["logdepthbuf_fragment"], + THREE.ShaderChunk[ "logdepthbuf_fragment" ], - " #ifdef USE_LOGDEPTHBUF_EXT", + " #ifdef USE_LOGDEPTHBUF_EXT", - " gl_FragData[ 0 ] = pack_depth( gl_FragDepthEXT );", + " gl_FragData[ 0 ] = pack_depth( gl_FragDepthEXT );", - " #else", + " #else", - " gl_FragData[ 0 ] = pack_depth( gl_FragCoord.z );", + " gl_FragData[ 0 ] = pack_depth( gl_FragCoord.z );", - " #endif", + " #endif", - //"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 );", + //"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") + ].join( "\n" ) - } + }, + + + 'distanceRGBA': { + + uniforms: { + + "lightPos": { type: "v3", value: new THREE.Vector3( 0, 0, 0 ) } + + }, + + vertexShader: [ + + "varying vec4 vWorldPosition;", + + THREE.ShaderChunk[ "common" ], + THREE.ShaderChunk[ "morphtarget_pars_vertex" ], + THREE.ShaderChunk[ "skinning_pars_vertex" ], + + "void main() {", + + THREE.ShaderChunk[ "skinbase_vertex" ], + THREE.ShaderChunk[ "begin_vertex" ], + THREE.ShaderChunk[ "morphtarget_vertex" ], + THREE.ShaderChunk[ "skinning_vertex" ], + THREE.ShaderChunk[ "project_vertex" ], + THREE.ShaderChunk[ "worldpos_vertex" ], + + "vWorldPosition = worldPosition;", + + "}" + + ].join( "\n" ), + + fragmentShader: [ + + "uniform vec3 lightPos;", + "varying vec4 vWorldPosition;", + + THREE.ShaderChunk[ "common" ], + + "vec4 pack1K ( float depth ) {", + + " depth /= 1000.0;", + " const vec4 bitSh = vec4( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0 );", + " const vec4 bitMsk = vec4( 0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0 );", + " vec4 res = fract( depth * bitSh );", + " res -= res.xxyz * bitMsk;", + " return res; ", + + "}", + + "float unpack1K ( vec4 color ) {", + + " const vec4 bitSh = vec4( 1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0 );", + " return dot( color, bitSh ) * 1000.0;", + + "}", + + "void main () {", + + " gl_FragColor = pack1K( length( vWorldPosition.xyz - lightPos.xyz ) );", + + "}" + + ].join( "\n" ) + + } }; + // File:src/renderers/WebGLRenderer.js /** @@ -19114,6120 +21523,6099 @@ THREE.ShaderLib = { * @author szimek / https://github.com/szimek/ */ -THREE.WebGLRenderer = function (parameters) { +THREE.WebGLRenderer = function ( parameters ) { - console.log('THREE.WebGLRenderer', THREE.REVISION); + console.log( 'THREE.WebGLRenderer', THREE.REVISION ); - parameters = parameters || {}; + parameters = parameters || {}; - var _canvas = parameters.canvas !== undefined ? parameters.canvas : document.createElement('canvas'), - _context = parameters.context !== undefined ? parameters.context : null, + var _canvas = parameters.canvas !== undefined ? parameters.canvas : document.createElement( 'canvas' ), + _context = parameters.context !== undefined ? parameters.context : null, - _width = _canvas.width, - _height = _canvas.height, + _width = _canvas.width, + _height = _canvas.height, - pixelRatio = 1, + pixelRatio = 1, - _precision = parameters.precision !== undefined ? parameters.precision : 'highp', + _alpha = parameters.alpha !== undefined ? parameters.alpha : false, + _depth = parameters.depth !== undefined ? parameters.depth : true, + _stencil = parameters.stencil !== undefined ? parameters.stencil : true, + _antialias = parameters.antialias !== undefined ? parameters.antialias : false, + _premultipliedAlpha = parameters.premultipliedAlpha !== undefined ? parameters.premultipliedAlpha : true, + _preserveDrawingBuffer = parameters.preserveDrawingBuffer !== undefined ? parameters.preserveDrawingBuffer : false, - _alpha = parameters.alpha !== undefined ? parameters.alpha : false, - _depth = parameters.depth !== undefined ? parameters.depth : true, - _stencil = parameters.stencil !== undefined ? parameters.stencil : true, - _antialias = parameters.antialias !== undefined ? parameters.antialias : false, - _premultipliedAlpha = parameters.premultipliedAlpha !== undefined ? parameters.premultipliedAlpha : true, - _preserveDrawingBuffer = parameters.preserveDrawingBuffer !== undefined ? parameters.preserveDrawingBuffer : false, - _logarithmicDepthBuffer = parameters.logarithmicDepthBuffer !== undefined ? parameters.logarithmicDepthBuffer : false, + _clearColor = new THREE.Color( 0x000000 ), + _clearAlpha = 0; - _clearColor = new THREE.Color(0x000000), - _clearAlpha = 0; + var lights = []; - var lights = []; + var opaqueObjects = []; + var opaqueObjectsLastIndex = -1; + var transparentObjects = []; + var transparentObjectsLastIndex = -1; - var opaqueObjects = []; - var transparentObjects = []; + var morphInfluences = new Float32Array( 8 ); - var sprites = []; - var lensFlares = []; - // public properties + var sprites = []; + var lensFlares = []; - this.domElement = _canvas; - this.context = null; + // public properties - // clearing + this.domElement = _canvas; + this.context = null; - this.autoClear = true; - this.autoClearColor = true; - this.autoClearDepth = true; - this.autoClearStencil = true; + // clearing - // scene graph + this.autoClear = true; + this.autoClearColor = true; + this.autoClearDepth = true; + this.autoClearStencil = true; - this.sortObjects = true; + // scene graph - // physically based shading + this.sortObjects = true; - this.gammaFactor = 2.0; // for backwards compatibility - this.gammaInput = false; - this.gammaOutput = false; + // physically based shading - // morphs + this.gammaFactor = 2.0; // for backwards compatibility + this.gammaInput = false; + this.gammaOutput = false; - this.maxMorphTargets = 8; - this.maxMorphNormals = 4; + // morphs - // flags + this.maxMorphTargets = 8; + this.maxMorphNormals = 4; - this.autoScaleCubemaps = true; + // flags - // info + this.autoScaleCubemaps = true; - this.info = { + // internal properties - memory: { + var _this = this, - programs: 0, - geometries: 0, - textures: 0 + // internal state cache - }, + _currentProgram = null, + _currentFramebuffer = null, + _currentMaterialId = - 1, + _currentGeometryProgram = '', + _currentCamera = null, - render: { + _usedTextureUnits = 0, - calls: 0, - vertices: 0, - faces: 0, - points: 0 + _viewportX = 0, + _viewportY = 0, + _viewportWidth = _canvas.width, + _viewportHeight = _canvas.height, + _currentWidth = 0, + _currentHeight = 0, - } + // frustum - }; + _frustum = new THREE.Frustum(), - // internal properties + // camera matrices cache - var _this = this, + _projScreenMatrix = new THREE.Matrix4(), - _programs = [], + _vector3 = new THREE.Vector3(), - // internal state cache + // light arrays cache - _currentProgram = null, - _currentFramebuffer = null, - _currentMaterialId = -1, - _currentGeometryProgram = '', - _currentCamera = null, + _direction = new THREE.Vector3(), - _usedTextureUnits = 0, + _lightsNeedUpdate = true, - _viewportX = 0, - _viewportY = 0, - _viewportWidth = _canvas.width, - _viewportHeight = _canvas.height, - _currentWidth = 0, - _currentHeight = 0, + _lights = { - // frustum + ambient: [ 0, 0, 0 ], + directional: { length: 0, colors: [], positions: [] }, + point: { length: 0, colors: [], positions: [], distances: [], decays: [] }, + spot: { length: 0, colors: [], positions: [], distances: [], directions: [], anglesCos: [], exponents: [], decays: [] }, + hemi: { length: 0, skyColors: [], groundColors: [], positions: [] } - _frustum = new THREE.Frustum(), + }, - // camera matrices cache + // info - _projScreenMatrix = new THREE.Matrix4(), + _infoMemory = { - _vector3 = new THREE.Vector3(), + geometries: 0, + textures: 0 - // light arrays cache + }, - _direction = new THREE.Vector3(), + _infoRender = { - _lightsNeedUpdate = true, + calls: 0, + vertices: 0, + faces: 0, + points: 0 - _lights = { + }; - ambient: [0, 0, 0], - directional: {length: 0, colors: [], positions: []}, - point: {length: 0, colors: [], positions: [], distances: [], decays: []}, - spot: { - length: 0, - colors: [], - positions: [], - distances: [], - directions: [], - anglesCos: [], - exponents: [], - decays: [] - }, - hemi: {length: 0, skyColors: [], groundColors: [], positions: []} + this.info = { - }; + render: _infoRender, + memory: _infoMemory, + programs: null - // initialize + }; - var _gl; - try { + // initialize - var attributes = { - alpha: _alpha, - depth: _depth, - stencil: _stencil, - antialias: _antialias, - premultipliedAlpha: _premultipliedAlpha, - preserveDrawingBuffer: _preserveDrawingBuffer - }; + var _gl; - _gl = _context || _canvas.getContext('webgl', attributes) || _canvas.getContext('experimental-webgl', attributes); + try { - if (_gl === null) { + var attributes = { + alpha: _alpha, + depth: _depth, + stencil: _stencil, + antialias: _antialias, + premultipliedAlpha: _premultipliedAlpha, + preserveDrawingBuffer: _preserveDrawingBuffer + }; - if (_canvas.getContext('webgl') !== null) { + _gl = _context || _canvas.getContext( 'webgl', attributes ) || _canvas.getContext( 'experimental-webgl', attributes ); - throw 'Error creating WebGL context with your selected attributes.'; + if ( _gl === null ) { - } else { + if ( _canvas.getContext( 'webgl' ) !== null ) { - throw 'Error creating WebGL context.'; + throw 'Error creating WebGL context with your selected attributes.'; - } + } else { - } + throw 'Error creating WebGL context.'; - _canvas.addEventListener('webglcontextlost', function (event) { + } - event.preventDefault(); + } - resetGLState(); - setDefaultGLState(); + _canvas.addEventListener( 'webglcontextlost', onContextLost, false ); - objects.objects = {}; + } catch ( error ) { - }, false); + console.error( 'THREE.WebGLRenderer: ' + error ); - } catch (error) { + } - THREE.error('THREE.WebGLRenderer: ' + error); + var extensions = new THREE.WebGLExtensions( _gl ); - } + extensions.get( 'OES_texture_float' ); + extensions.get( 'OES_texture_float_linear' ); + extensions.get( 'WEBGL_depth_texture' ); + extensions.get( 'OES_texture_half_float' ); + extensions.get( 'OES_texture_half_float_linear' ); + extensions.get( 'OES_standard_derivatives' ); + extensions.get( 'ANGLE_instanced_arrays' ); - var state = new THREE.WebGLState(_gl, paramThreeToGL); + if ( extensions.get( 'OES_element_index_uint' ) ) { - if (_gl.getShaderPrecisionFormat === undefined) { + THREE.BufferGeometry.MaxIndex = 4294967296; - _gl.getShaderPrecisionFormat = function () { + } - return { - 'rangeMin': 1, - 'rangeMax': 1, - 'precision': 1 - }; + var capabilities = new THREE.WebGLCapabilities( _gl, extensions, parameters ); - } + var state = new THREE.WebGLState( _gl, extensions, paramThreeToGL ); + var properties = new THREE.WebGLProperties(); + var objects = new THREE.WebGLObjects( _gl, properties, this.info ); + var programCache = new THREE.WebGLPrograms( this, capabilities ); - } + this.info.programs = programCache.programs; - var extensions = new THREE.WebGLExtensions(_gl); - var objects = new THREE.WebGLObjects(_gl, this.info); + var bufferRenderer = new THREE.WebGLBufferRenderer( _gl, extensions, _infoRender ); + var indexedBufferRenderer = new THREE.WebGLIndexedBufferRenderer( _gl, extensions, _infoRender ); - extensions.get('OES_texture_float'); - extensions.get('OES_texture_float_linear'); - extensions.get('OES_texture_half_float'); - extensions.get('OES_texture_half_float_linear'); - extensions.get('OES_standard_derivatives'); - extensions.get('WEBGL_depth_texture'); - extensions.get('ANGLE_instanced_arrays'); + // - if (extensions.get('OES_element_index_uint')) { + function glClearColor( r, g, b, a ) { - THREE.BufferGeometry.MaxIndex = 4294967296; + if ( _premultipliedAlpha === true ) { - } + r *= a; g *= a; b *= a; - if (_logarithmicDepthBuffer) { + } - extensions.get('EXT_frag_depth'); + _gl.clearColor( r, g, b, a ); - } + } - // + function setDefaultGLState() { - var glClearColor = function (r, g, b, a) { + state.init(); - if (_premultipliedAlpha === true) { + _gl.viewport( _viewportX, _viewportY, _viewportWidth, _viewportHeight ); - r *= a; - g *= a; - b *= a; + glClearColor( _clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha ); - } + } - _gl.clearColor(r, g, b, a); + function resetGLState() { - }; + _currentProgram = null; + _currentCamera = null; - var setDefaultGLState = function () { + _currentGeometryProgram = ''; + _currentMaterialId = - 1; - _gl.clearColor(0, 0, 0, 1); - _gl.clearDepth(1); - _gl.clearStencil(0); + _lightsNeedUpdate = true; - _gl.enable(_gl.DEPTH_TEST); - _gl.depthFunc(_gl.LEQUAL); + state.reset(); - _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); + setDefaultGLState(); - _gl.viewport(_viewportX, _viewportY, _viewportWidth, _viewportHeight); + this.context = _gl; + this.capabilities = capabilities; + this.extensions = extensions; + this.state = state; - glClearColor(_clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha); + // shadow map - }; + var shadowMap = new THREE.WebGLShadowMap( this, lights, objects ); - var resetGLState = function () { + this.shadowMap = shadowMap; - _currentProgram = null; - _currentCamera = null; - _currentGeometryProgram = ''; - _currentMaterialId = -1; + // Plugins - _lightsNeedUpdate = true; + var spritePlugin = new THREE.SpritePlugin( this, sprites ); + var lensFlarePlugin = new THREE.LensFlarePlugin( this, lensFlares ); - state.reset(); + // API - }; + this.getContext = function () { - setDefaultGLState(); + return _gl; - this.context = _gl; - this.extensions = extensions; - this.state = state; + }; - // shadow map + this.getContextAttributes = function () { - var shadowMap = new THREE.WebGLShadowMap(this, lights, objects); + return _gl.getContextAttributes(); - this.shadowMap = shadowMap; + }; - // GPU capabilities + this.forceContextLoss = function () { - 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); + extensions.get( 'WEBGL_lose_context' ).loseContext(); - var _supportsVertexTextures = _maxVertexTextures > 0; - var _supportsBoneTextures = _supportsVertexTextures && extensions.get('OES_texture_float'); - var _supportsInstancedArrays = extensions.get('ANGLE_instanced_arrays'); + }; - // + this.getMaxAnisotropy = ( function () { - var _vertexShaderPrecisionHighpFloat = _gl.getShaderPrecisionFormat(_gl.VERTEX_SHADER, _gl.HIGH_FLOAT); - var _vertexShaderPrecisionMediumpFloat = _gl.getShaderPrecisionFormat(_gl.VERTEX_SHADER, _gl.MEDIUM_FLOAT); + var value; - var _fragmentShaderPrecisionHighpFloat = _gl.getShaderPrecisionFormat(_gl.FRAGMENT_SHADER, _gl.HIGH_FLOAT); - var _fragmentShaderPrecisionMediumpFloat = _gl.getShaderPrecisionFormat(_gl.FRAGMENT_SHADER, _gl.MEDIUM_FLOAT); + return function getMaxAnisotropy() { - var getCompressedTextureFormats = (function () { + if ( value !== undefined ) return value; - var array; + var extension = extensions.get( 'EXT_texture_filter_anisotropic' ); - return function () { + if ( extension !== null ) { - if (array !== undefined) { + value = _gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT ); - return array; + } else { - } + value = 0; - array = []; + } - if (extensions.get('WEBGL_compressed_texture_pvrtc') || extensions.get('WEBGL_compressed_texture_s3tc')) { + return value; - var formats = _gl.getParameter(_gl.COMPRESSED_TEXTURE_FORMATS); + } - for (var i = 0; i < formats.length; i++) { + } )(); - array.push(formats[i]); + this.getPrecision = function () { - } + return capabilities.precision; - } + }; - return array; + this.getPixelRatio = function () { - }; + return pixelRatio; - })(); + }; - // clamp precision to maximum available + this.setPixelRatio = function ( value ) { - var highpAvailable = _vertexShaderPrecisionHighpFloat.precision > 0 && _fragmentShaderPrecisionHighpFloat.precision > 0; - var mediumpAvailable = _vertexShaderPrecisionMediumpFloat.precision > 0 && _fragmentShaderPrecisionMediumpFloat.precision > 0; + if ( value !== undefined ) pixelRatio = value; - if (_precision === 'highp' && !highpAvailable) { + }; - if (mediumpAvailable) { + this.getSize = function () { - _precision = 'mediump'; - THREE.warn('THREE.WebGLRenderer: highp not supported, using mediump.'); + return { + width: _width, + height: _height + }; - } else { + }; - _precision = 'lowp'; - THREE.warn('THREE.WebGLRenderer: highp and mediump not supported, using lowp.'); + this.setSize = function ( width, height, updateStyle ) { - } + _width = width; + _height = height; - } + _canvas.width = width * pixelRatio; + _canvas.height = height * pixelRatio; - if (_precision === 'mediump' && !mediumpAvailable) { + if ( updateStyle !== false ) { - _precision = 'lowp'; - THREE.warn('THREE.WebGLRenderer: mediump not supported, using lowp.'); + _canvas.style.width = width + 'px'; + _canvas.style.height = height + 'px'; - } + } - // Plugins + this.setViewport( 0, 0, width, height ); - var spritePlugin = new THREE.SpritePlugin(this, sprites); - var lensFlarePlugin = new THREE.LensFlarePlugin(this, lensFlares); + }; - // API + this.setViewport = function ( x, y, width, height ) { - this.getContext = function () { + _viewportX = x * pixelRatio; + _viewportY = y * pixelRatio; - return _gl; + _viewportWidth = width * pixelRatio; + _viewportHeight = height * pixelRatio; - }; + _gl.viewport( _viewportX, _viewportY, _viewportWidth, _viewportHeight ); - this.forceContextLoss = function () { + }; - extensions.get('WEBGL_lose_context').loseContext(); + this.getViewport = function ( dimensions ) { - }; + dimensions.x = _viewportX; + dimensions.y = _viewportY; - this.supportsVertexTextures = function () { + dimensions.z = _viewportWidth; + dimensions.w = _viewportHeight; - return _supportsVertexTextures; + }; - }; + this.setScissor = function ( x, y, width, height ) { - this.supportsInstancedArrays = function () { + _gl.scissor( + x * pixelRatio, + y * pixelRatio, + width * pixelRatio, + height * pixelRatio + ); - return _supportsInstancedArrays; + }; - }; + this.enableScissorTest = function ( boolean ) { - this.supportsFloatTextures = function () { + state.setScissorTest( boolean ); - return extensions.get('OES_texture_float'); + }; - }; + // Clearing - this.supportsHalfFloatTextures = function () { + this.getClearColor = function () { - return extensions.get('OES_texture_half_float'); + return _clearColor; - }; + }; - this.supportsDepthTextures = function () { + this.setClearColor = function ( color, alpha ) { - return extensions.get('WEBGL_depth_texture'); + _clearColor.set( color ); - }; + _clearAlpha = alpha !== undefined ? alpha : 1; - this.supportsStandardDerivatives = function () { + glClearColor( _clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha ); - return extensions.get('OES_standard_derivatives'); + }; - }; + this.getClearAlpha = function () { - this.supportsCompressedTextureS3TC = function () { + return _clearAlpha; - return extensions.get('WEBGL_compressed_texture_s3tc'); + }; - }; + this.setClearAlpha = function ( alpha ) { - this.supportsCompressedTexturePVRTC = function () { + _clearAlpha = alpha; - return extensions.get('WEBGL_compressed_texture_pvrtc'); + glClearColor( _clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha ); - }; + }; - this.supportsBlendMinMax = function () { + this.clear = function ( color, depth, stencil ) { - return extensions.get('EXT_blend_minmax'); + 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; - this.getMaxAnisotropy = (function () { + _gl.clear( bits ); - var value; + }; - return function () { + this.clearColor = function () { - if (value !== undefined) { + _gl.clear( _gl.COLOR_BUFFER_BIT ); - return value; + }; - } + this.clearDepth = function () { - var extension = extensions.get('EXT_texture_filter_anisotropic'); + _gl.clear( _gl.DEPTH_BUFFER_BIT ); - value = extension !== null ? _gl.getParameter(extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT) : 0; + }; - return value; + this.clearStencil = function () { - } + _gl.clear( _gl.STENCIL_BUFFER_BIT ); - })(); + }; - this.getPrecision = function () { + this.clearTarget = function ( renderTarget, color, depth, stencil ) { - return _precision; + this.setRenderTarget( renderTarget ); + this.clear( color, depth, stencil ); - }; + }; - this.getPixelRatio = function () { + // Reset - return pixelRatio; + this.resetGLState = resetGLState; - }; + this.dispose = function() { - this.setPixelRatio = function (value) { + _canvas.removeEventListener( 'webglcontextlost', onContextLost, false ); - pixelRatio = value; + }; - }; + // Events - this.getSize = function () { + function onContextLost( event ) { - return { - width: _width, - height: _height - }; + event.preventDefault(); - }; + resetGLState(); + setDefaultGLState(); - this.setSize = function (width, height, updateStyle) { + properties.clear(); - _width = width; - _height = height; + }; - _canvas.width = width * pixelRatio; - _canvas.height = height * pixelRatio; + function onTextureDispose( event ) { - if (updateStyle !== false) { + var texture = event.target; - _canvas.style.width = width + 'px'; - _canvas.style.height = height + 'px'; + texture.removeEventListener( 'dispose', onTextureDispose ); - } + deallocateTexture( texture ); - this.setViewport(0, 0, width, height); + _infoMemory.textures --; - }; - this.setViewport = function (x, y, width, height) { + } - _viewportX = x * pixelRatio; - _viewportY = y * pixelRatio; + function onRenderTargetDispose( event ) { - _viewportWidth = width * pixelRatio; - _viewportHeight = height * pixelRatio; + var renderTarget = event.target; - _gl.viewport(_viewportX, _viewportY, _viewportWidth, _viewportHeight); + renderTarget.removeEventListener( 'dispose', onRenderTargetDispose ); - }; + deallocateRenderTarget( renderTarget ); - this.setScissor = function (x, y, width, height) { + _infoMemory.textures --; - _gl.scissor( - x * pixelRatio, - y * pixelRatio, - width * pixelRatio, - height * pixelRatio - ); + } - }; + function onMaterialDispose( event ) { - this.enableScissorTest = function (enable) { + var material = event.target; - enable ? _gl.enable(_gl.SCISSOR_TEST) : _gl.disable(_gl.SCISSOR_TEST); + material.removeEventListener( 'dispose', onMaterialDispose ); - }; + deallocateMaterial( material ); - // Clearing + } - this.getClearColor = function () { + // Buffer deallocation - return _clearColor; + function deallocateTexture( texture ) { - }; + var textureProperties = properties.get( texture ); - this.setClearColor = function (color, alpha) { + if ( texture.image && textureProperties.__image__webglTextureCube ) { - _clearColor.set(color); + // cube texture - _clearAlpha = alpha !== undefined ? alpha : 1; + _gl.deleteTexture( textureProperties.__image__webglTextureCube ); - glClearColor(_clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha); + } else { - }; + // 2D texture - this.getClearAlpha = function () { + if ( textureProperties.__webglInit === undefined ) return; - return _clearAlpha; + _gl.deleteTexture( textureProperties.__webglTexture ); - }; + } - this.setClearAlpha = function (alpha) { + // remove all webgl properties + properties.delete( texture ); - _clearAlpha = alpha; + } - glClearColor(_clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha); + function deallocateRenderTarget( renderTarget ) { - }; + var renderTargetProperties = properties.get( renderTarget ); + var textureProperties = properties.get( renderTarget.texture ); - this.clear = function (color, depth, stencil) { + if ( ! renderTarget || textureProperties.__webglTexture === undefined ) return; - var bits = 0; + _gl.deleteTexture( textureProperties.__webglTexture ); - 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; + if ( renderTarget instanceof THREE.WebGLRenderTargetCube ) { - _gl.clear(bits); + for ( var i = 0; i < 6; i ++ ) { - }; + _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] ); + if ( renderTargetProperties.__webglRenderbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglRenderbuffer[ i ] ); - this.clearColor = function () { + } - _gl.clear(_gl.COLOR_BUFFER_BIT); + } else { - }; + _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer ); + if ( renderTargetProperties.__webglRenderbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglRenderbuffer ); - this.clearDepth = function () { + } - _gl.clear(_gl.DEPTH_BUFFER_BIT); + properties.delete( renderTarget.texture ); + properties.delete( renderTarget ); - }; + } - this.clearStencil = function () { + function deallocateMaterial( material ) { - _gl.clear(_gl.STENCIL_BUFFER_BIT); + releaseMaterialProgramReference( material ); - }; + properties.delete( material ); - this.clearTarget = function (renderTarget, color, depth, stencil) { + } - this.setRenderTarget(renderTarget); - this.clear(color, depth, stencil); - }; + function releaseMaterialProgramReference( material ) { - // Reset + var programInfo = properties.get( material ).program; - this.resetGLState = resetGLState; + material.program = undefined; - // Events + if ( programInfo !== undefined ) { - var onTextureDispose = function (event) { + programCache.releaseProgram( programInfo ); + } - var texture = event.target; + } - texture.removeEventListener('dispose', onTextureDispose); + // Buffer rendering - deallocateTexture(texture); + this.renderBufferImmediate = function ( object, program, material ) { - _this.info.memory.textures--; + state.initAttributes(); + var buffers = properties.get( object ); - }; + if ( object.hasPositions && ! buffers.position ) buffers.position = _gl.createBuffer(); + if ( object.hasNormals && ! buffers.normal ) buffers.normal = _gl.createBuffer(); + if ( object.hasUvs && ! buffers.uv ) buffers.uv = _gl.createBuffer(); + if ( object.hasColors && ! buffers.color ) buffers.color = _gl.createBuffer(); - var onRenderTargetDispose = function (event) { + var attributes = program.getAttributes(); - var renderTarget = event.target; + if ( object.hasPositions ) { - renderTarget.removeEventListener('dispose', onRenderTargetDispose); + _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.position ); + _gl.bufferData( _gl.ARRAY_BUFFER, object.positionArray, _gl.DYNAMIC_DRAW ); - deallocateRenderTarget(renderTarget); + state.enableAttribute( attributes.position ); + _gl.vertexAttribPointer( attributes.position, 3, _gl.FLOAT, false, 0, 0 ); - _this.info.memory.textures--; + } - }; + if ( object.hasNormals ) { - var onMaterialDispose = function (event) { + _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.normal ); - var material = event.target; + if ( material.type !== 'MeshPhongMaterial' && material.shading === THREE.FlatShading ) { - material.removeEventListener('dispose', onMaterialDispose); + for ( var i = 0, l = object.count * 3; i < l; i += 9 ) { - deallocateMaterial(material); + var array = object.normalArray; - }; + var nx = ( array[ i + 0 ] + array[ i + 3 ] + array[ i + 6 ] ) / 3; + var ny = ( array[ i + 1 ] + array[ i + 4 ] + array[ i + 7 ] ) / 3; + var nz = ( array[ i + 2 ] + array[ i + 5 ] + array[ i + 8 ] ) / 3; - // Buffer deallocation + array[ i + 0 ] = nx; + array[ i + 1 ] = ny; + array[ i + 2 ] = nz; - var deallocateTexture = function (texture) { + array[ i + 3 ] = nx; + array[ i + 4 ] = ny; + array[ i + 5 ] = nz; - if (texture.image && texture.image.__webglTextureCube) { + array[ i + 6 ] = nx; + array[ i + 7 ] = ny; + array[ i + 8 ] = nz; - // cube texture + } - _gl.deleteTexture(texture.image.__webglTextureCube); + } - delete texture.image.__webglTextureCube; + _gl.bufferData( _gl.ARRAY_BUFFER, object.normalArray, _gl.DYNAMIC_DRAW ); - } else { + state.enableAttribute( attributes.normal ); - // 2D texture + _gl.vertexAttribPointer( attributes.normal, 3, _gl.FLOAT, false, 0, 0 ); - if (texture.__webglInit === undefined) return; + } - _gl.deleteTexture(texture.__webglTexture); + if ( object.hasUvs && material.map ) { - delete texture.__webglTexture; - delete texture.__webglInit; + _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.uv ); + _gl.bufferData( _gl.ARRAY_BUFFER, object.uvArray, _gl.DYNAMIC_DRAW ); - } + state.enableAttribute( attributes.uv ); - }; + _gl.vertexAttribPointer( attributes.uv, 2, _gl.FLOAT, false, 0, 0 ); - var deallocateRenderTarget = function (renderTarget) { + } - if (!renderTarget || renderTarget.texture.__webglTexture === undefined) return; + if ( object.hasColors && material.vertexColors !== THREE.NoColors ) { - _gl.deleteTexture(renderTarget.texture.__webglTexture); + _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.color ); + _gl.bufferData( _gl.ARRAY_BUFFER, object.colorArray, _gl.DYNAMIC_DRAW ); - delete renderTarget.__webglTexture; + state.enableAttribute( attributes.color ); - if (renderTarget instanceof THREE.WebGLRenderTargetCube) { + _gl.vertexAttribPointer( attributes.color, 3, _gl.FLOAT, false, 0, 0 ); - for (var i = 0; i < 6; i++) { + } - _gl.deleteFramebuffer(renderTarget.texture.__webglFramebuffer[i]); - if (renderTarget.__webglRenderbuffer) _gl.deleteRenderbuffer(renderTarget.__webglRenderbuffer[i]); + state.disableUnusedAttributes(); - } + _gl.drawArrays( _gl.TRIANGLES, 0, object.count ); - } else { + object.count = 0; - _gl.deleteFramebuffer(renderTarget.texture.__webglFramebuffer); - if (renderTarget.__webglRenderbuffer) _gl.deleteRenderbuffer(renderTarget.__webglRenderbuffer); + }; - } + this.renderBufferDirect = function ( camera, lights, fog, geometry, material, object, group ) { - delete renderTarget.__webglFramebuffer; - delete renderTarget.__webglRenderbuffer; + setMaterial( material ); - }; + var program = setProgram( camera, lights, fog, material, object ); - var deallocateMaterial = function (material) { + var updateBuffers = false; + var geometryProgram = geometry.id + '_' + program.id + '_' + material.wireframe; - var program = material.program.program; + if ( geometryProgram !== _currentGeometryProgram ) { - if (program === undefined) return; + _currentGeometryProgram = geometryProgram; + updateBuffers = true; - 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) + // morph targets - var i, il, programInfo; - var deleteProgram = false; + var morphTargetInfluences = object.morphTargetInfluences; - for (i = 0, il = _programs.length; i < il; i++) { + if ( morphTargetInfluences !== undefined ) { - programInfo = _programs[i]; + var activeInfluences = []; - if (programInfo.program === program) { + for ( var i = 0, l = morphTargetInfluences.length; i < l; i ++ ) { - programInfo.usedTimes--; + var influence = morphTargetInfluences[ i ]; + activeInfluences.push( [ influence, i ] ); - if (programInfo.usedTimes === 0) { + } - deleteProgram = true; + activeInfluences.sort( numericalSort ); - } + if ( activeInfluences.length > 8 ) { - break; + activeInfluences.length = 8; - } + } - } + var morphAttributes = geometry.morphAttributes; - if (deleteProgram === true) { + for ( var i = 0, l = activeInfluences.length; i < l; i ++ ) { - // avoid using array.splice, this is costlier than creating new array from scratch + var influence = activeInfluences[ i ]; + morphInfluences[ i ] = influence[ 0 ]; - var newPrograms = []; + if ( influence[ 0 ] !== 0 ) { - for (i = 0, il = _programs.length; i < il; i++) { + var index = influence[ 1 ]; - programInfo = _programs[i]; + if ( material.morphTargets === true && morphAttributes.position ) geometry.addAttribute( 'morphTarget' + i, morphAttributes.position[ index ] ); + if ( material.morphNormals === true && morphAttributes.normal ) geometry.addAttribute( 'morphNormal' + i, morphAttributes.normal[ index ] ); - if (programInfo.program !== program) { + } else { - newPrograms.push(programInfo); + if ( material.morphTargets === true ) geometry.removeAttribute( 'morphTarget' + i ); + if ( material.morphNormals === true ) geometry.removeAttribute( 'morphNormal' + i ); - } + } - } + } - _programs = newPrograms; + var uniforms = program.getUniforms(); - _gl.deleteProgram(program); + if ( uniforms.morphTargetInfluences !== null ) { - _this.info.memory.programs--; + _gl.uniform1fv( uniforms.morphTargetInfluences, morphInfluences ); - } + } - }; + updateBuffers = true; - // Buffer rendering + } - this.renderBufferImmediate = function (object, program, material) { + // - state.initAttributes(); + var index = geometry.index; + var position = geometry.attributes.position; - 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 ( material.wireframe === true ) { - if (object.hasPositions) { + index = objects.getWireframeAttribute( geometry ); - _gl.bindBuffer(_gl.ARRAY_BUFFER, object.__webglVertexBuffer); - _gl.bufferData(_gl.ARRAY_BUFFER, object.positionArray, _gl.DYNAMIC_DRAW); + } - state.enableAttribute(program.attributes.position); + var renderer; - _gl.vertexAttribPointer(program.attributes.position, 3, _gl.FLOAT, false, 0, 0); + if ( index !== null ) { - } + renderer = indexedBufferRenderer; + renderer.setIndex( index ); - if (object.hasNormals) { + } else { - _gl.bindBuffer(_gl.ARRAY_BUFFER, object.__webglNormalBuffer); + renderer = bufferRenderer; - if (material instanceof THREE.MeshPhongMaterial === false && material.shading === THREE.FlatShading) { + } - var nx, ny, nz, - nax, nbx, ncx, nay, nby, ncy, naz, nbz, ncz, - normalArray, - i, il = object.count * 3; + if ( updateBuffers ) { - for (i = 0; i < il; i += 9) { + setupVertexAttributes( material, program, geometry ); - normalArray = object.normalArray; + if ( index !== null ) { - nax = normalArray[i]; - nay = normalArray[i + 1]; - naz = normalArray[i + 2]; + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, objects.getAttributeBuffer( index ) ); - 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; + if ( group === null ) { - normalArray[i] = nx; - normalArray[i + 1] = ny; - normalArray[i + 2] = nz; + var count; - normalArray[i + 3] = nx; - normalArray[i + 4] = ny; - normalArray[i + 5] = nz; + if ( index !== null ) { - normalArray[i + 6] = nx; - normalArray[i + 7] = ny; - normalArray[i + 8] = nz; + count = index.array.length; - } + } else { - } + count = position.count; - _gl.bufferData(_gl.ARRAY_BUFFER, object.normalArray, _gl.DYNAMIC_DRAW); + } - state.enableAttribute(program.attributes.normal); + var drawRange = geometry.drawRange; - _gl.vertexAttribPointer(program.attributes.normal, 3, _gl.FLOAT, false, 0, 0); + group = { + start: drawRange.start, + count: Math.min( drawRange.count, count ) + }; - } + } - if (object.hasUvs && material.map) { + if ( object instanceof THREE.Mesh ) { - _gl.bindBuffer(_gl.ARRAY_BUFFER, object.__webglUvBuffer); - _gl.bufferData(_gl.ARRAY_BUFFER, object.uvArray, _gl.DYNAMIC_DRAW); + if ( material.wireframe === true ) { - state.enableAttribute(program.attributes.uv); + state.setLineWidth( material.wireframeLinewidth * pixelRatio ); + renderer.setMode( _gl.LINES ); - _gl.vertexAttribPointer(program.attributes.uv, 2, _gl.FLOAT, false, 0, 0); + } else { - } + renderer.setMode( _gl.TRIANGLES ); - if (object.hasColors && material.vertexColors !== THREE.NoColors) { + } - _gl.bindBuffer(_gl.ARRAY_BUFFER, object.__webglColorBuffer); - _gl.bufferData(_gl.ARRAY_BUFFER, object.colorArray, _gl.DYNAMIC_DRAW); + if ( geometry instanceof THREE.InstancedBufferGeometry && geometry.maxInstancedCount > 0 ) { - state.enableAttribute(program.attributes.color); + renderer.renderInstances( geometry ); - _gl.vertexAttribPointer(program.attributes.color, 3, _gl.FLOAT, false, 0, 0); + } else { - } + renderer.render( group.start, group.count ); - state.disableUnusedAttributes(); + } - _gl.drawArrays(_gl.TRIANGLES, 0, object.count); + } else if ( object instanceof THREE.Line ) { - object.count = 0; + var lineWidth = material.linewidth; - }; + if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material - function setupVertexAttributes(material, program, geometry, startIndex) { + state.setLineWidth( lineWidth * pixelRatio ); - var extension; + if ( object instanceof THREE.LineSegments ) { - if (geometry instanceof THREE.InstancedBufferGeometry) { + renderer.setMode( _gl.LINES ); - extension = extensions.get('ANGLE_instanced_arrays'); + } else { - if (extension === null) { + renderer.setMode( _gl.LINE_STRIP ); - THREE.error('THREE.WebGLRenderer.setupVertexAttributes: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.'); - return; + } - } + renderer.render( group.start, group.count ); - } + } else if ( object instanceof THREE.Points ) { - var geometryAttributes = geometry.attributes; + renderer.setMode( _gl.POINTS ); + renderer.render( group.start, group.count ); - var programAttributes = program.attributes; - var programAttributesKeys = program.attributesKeys; + } - for (var i = 0, l = programAttributesKeys.length; i < l; i++) { + }; - var key = programAttributesKeys[i]; - var programAttribute = programAttributes[key]; + function setupVertexAttributes( material, program, geometry, startIndex ) { - if (programAttribute >= 0) { + var extension; - var geometryAttribute = geometryAttributes[key]; + if ( geometry instanceof THREE.InstancedBufferGeometry ) { - if (geometryAttribute !== undefined) { + extension = extensions.get( 'ANGLE_instanced_arrays' ); - var size = geometryAttribute.itemSize; - state.enableAttribute(programAttribute); + if ( extension === null ) { - if (geometryAttribute instanceof THREE.InterleavedBufferAttribute) { + console.error( 'THREE.WebGLRenderer.setupVertexAttributes: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); + return; - var data = geometryAttribute.data; - var stride = data.stride; - var offset = geometryAttribute.offset; + } - _gl.bindBuffer(_gl.ARRAY_BUFFER, geometryAttribute.data.buffer); - _gl.vertexAttribPointer(programAttribute, size, _gl.FLOAT, false, stride * data.array.BYTES_PER_ELEMENT, ( startIndex * stride + offset ) * data.array.BYTES_PER_ELEMENT); + } - if (data instanceof THREE.InstancedInterleavedBuffer) { + if ( startIndex === undefined ) startIndex = 0; - if (extension === null) { + state.initAttributes(); - THREE.error('THREE.WebGLRenderer.setupVertexAttributes: using THREE.InstancedBufferAttribute but hardware does not support extension ANGLE_instanced_arrays.'); - return; + var geometryAttributes = geometry.attributes; - } + var programAttributes = program.getAttributes(); - extension.vertexAttribDivisorANGLE(programAttribute, data.meshPerAttribute); + var materialDefaultAttributeValues = material.defaultAttributeValues; - if (geometry.maxInstancedCount === undefined) { + for ( var name in programAttributes ) { - geometry.maxInstancedCount = data.meshPerAttribute * ( data.array.length / data.stride ); + var programAttribute = programAttributes[ name ]; - } + if ( programAttribute >= 0 ) { - } + var geometryAttribute = geometryAttributes[ name ]; - } else { + if ( geometryAttribute !== undefined ) { - _gl.bindBuffer(_gl.ARRAY_BUFFER, geometryAttribute.buffer); - _gl.vertexAttribPointer(programAttribute, size, _gl.FLOAT, false, 0, startIndex * size * 4); // 4 bytes per Float32 + var size = geometryAttribute.itemSize; + var buffer = objects.getAttributeBuffer( geometryAttribute ); - if (geometryAttribute instanceof THREE.InstancedBufferAttribute) { + if ( geometryAttribute instanceof THREE.InterleavedBufferAttribute ) { - if (extension === null) { + var data = geometryAttribute.data; + var stride = data.stride; + var offset = geometryAttribute.offset; - THREE.error('THREE.WebGLRenderer.setupVertexAttributes: using THREE.InstancedBufferAttribute but hardware does not support extension ANGLE_instanced_arrays.'); - return; + if ( data instanceof THREE.InstancedInterleavedBuffer ) { - } + state.enableAttributeAndDivisor( programAttribute, data.meshPerAttribute, extension ); - extension.vertexAttribDivisorANGLE(programAttribute, geometryAttribute.meshPerAttribute); + if ( geometry.maxInstancedCount === undefined ) { - if (geometry.maxInstancedCount === undefined) { + geometry.maxInstancedCount = data.meshPerAttribute * data.count; - geometry.maxInstancedCount = geometryAttribute.meshPerAttribute * ( geometryAttribute.array.length / geometryAttribute.itemSize ); + } - } + } else { - } + state.enableAttribute( programAttribute ); - } + } - } else if (material.defaultAttributeValues !== undefined) { + _gl.bindBuffer( _gl.ARRAY_BUFFER, buffer ); + _gl.vertexAttribPointer( programAttribute, size, _gl.FLOAT, false, stride * data.array.BYTES_PER_ELEMENT, ( startIndex * stride + offset ) * data.array.BYTES_PER_ELEMENT ); - if (material.defaultAttributeValues[key].length === 2) { + } else { - _gl.vertexAttrib2fv(programAttribute, material.defaultAttributeValues[key]); + if ( geometryAttribute instanceof THREE.InstancedBufferAttribute ) { - } else if (material.defaultAttributeValues[key].length === 3) { + state.enableAttributeAndDivisor( programAttribute, geometryAttribute.meshPerAttribute, extension ); - _gl.vertexAttrib3fv(programAttribute, material.defaultAttributeValues[key]); + if ( geometry.maxInstancedCount === undefined ) { - } + geometry.maxInstancedCount = geometryAttribute.meshPerAttribute * geometryAttribute.count; - } + } - } + } else { - } + state.enableAttribute( programAttribute ); - state.disableUnusedAttributes(); + } - } + _gl.bindBuffer( _gl.ARRAY_BUFFER, buffer ); + _gl.vertexAttribPointer( programAttribute, size, _gl.FLOAT, false, 0, startIndex * size * 4 ); // 4 bytes per Float32 - this.renderBufferDirect = function (camera, lights, fog, material, geometry, object) { + } - if (material.visible === false) return; + } else if ( materialDefaultAttributeValues !== undefined ) { - objects.update(object); + var value = materialDefaultAttributeValues[ name ]; - var program = setProgram(camera, lights, fog, material, object); + if ( value !== undefined ) { - var updateBuffers = false, - wireframeBit = material.wireframe ? 1 : 0, - geometryProgram = 'direct_' + geometry.id + '_' + program.id + '_' + wireframeBit; + switch ( value.length ) { - if (geometryProgram !== _currentGeometryProgram) { + case 2: + _gl.vertexAttrib2fv( programAttribute, value ); + break; - _currentGeometryProgram = geometryProgram; - updateBuffers = true; + case 3: + _gl.vertexAttrib3fv( programAttribute, value ); + break; - } + case 4: + _gl.vertexAttrib4fv( programAttribute, value ); + break; - if (updateBuffers) { + default: + _gl.vertexAttrib1fv( programAttribute, value ); - state.initAttributes(); + } - } + } - // render mesh + } - if (object instanceof THREE.Mesh) { + } - var mode = material.wireframe === true ? _gl.LINES : _gl.TRIANGLES; + } - var index = geometry.attributes.index; + state.disableUnusedAttributes(); - if (index) { + } - // indexed triangles + // Sorting - var type, size; + function numericalSort ( a, b ) { - if (index.array instanceof Uint32Array && extensions.get('OES_element_index_uint')) { + return b[ 0 ] - a[ 0 ]; - type = _gl.UNSIGNED_INT; - size = 4; + } - } else { + function painterSortStable ( a, b ) { - type = _gl.UNSIGNED_SHORT; - size = 2; + if ( a.object.renderOrder !== b.object.renderOrder ) { - } + return a.object.renderOrder - b.object.renderOrder; - var offsets = geometry.offsets; + } else if ( a.material.id !== b.material.id ) { - if (offsets.length === 0) { + return a.material.id - b.material.id; - if (updateBuffers) { + } else if ( a.z !== b.z ) { - setupVertexAttributes(material, program, geometry, 0); - _gl.bindBuffer(_gl.ELEMENT_ARRAY_BUFFER, index.buffer); + return a.z - b.z; - } + } else { - if (geometry instanceof THREE.InstancedBufferGeometry && geometry.maxInstancedCount > 0) { + return a.id - b.id; - var extension = extensions.get('ANGLE_instanced_arrays'); + } - if (extension === null) { + } - THREE.error('THREE.WebGLRenderer.setupVertexAttributes: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.'); - return; + function reversePainterSortStable ( a, b ) { - } + if ( a.object.renderOrder !== b.object.renderOrder ) { - extension.drawElementsInstancedANGLE(mode, index.array.length, type, 0, geometry.maxInstancedCount); // Draw the instanced meshes + return a.object.renderOrder - b.object.renderOrder; - } else { + } if ( a.z !== b.z ) { - _gl.drawElements(mode, index.array.length, type, 0); + return b.z - a.z; - } - _this.info.render.calls++; - _this.info.render.vertices += index.array.length; // not really true, here vertices can be shared - _this.info.render.faces += index.array.length / 3; + } else { - } else { + return a.id - b.id; - // 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 + } - updateBuffers = true; + } - for (var i = 0, il = offsets.length; i < il; i++) { + // Rendering - var startIndex = offsets[i].index; + this.render = function ( scene, camera, renderTarget, forceClear ) { - if (updateBuffers) { + if ( camera instanceof THREE.Camera === false ) { - setupVertexAttributes(material, program, geometry, startIndex); - _gl.bindBuffer(_gl.ELEMENT_ARRAY_BUFFER, index.buffer); + console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' ); + return; - } + } - // render indexed triangles + var fog = scene.fog; - if (geometry instanceof THREE.InstancedBufferGeometry && offsets[i].instances > 0) { + // reset caching for this frame - var extension = extensions.get('ANGLE_instanced_arrays'); + _currentGeometryProgram = ''; + _currentMaterialId = - 1; + _currentCamera = null; + _lightsNeedUpdate = true; - if (extension === null) { + // update scene graph - THREE.error('THREE.WebGLRenderer.setupVertexAttributes: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.'); - return; + if ( scene.autoUpdate === true ) scene.updateMatrixWorld(); - } + // update camera matrices and frustum - extension.drawElementsInstancedANGLE(mode, offsets[i].count, type, offsets[i].start * size, offsets[i].count, type, offsets[i].instances); // Draw the instanced meshes + if ( camera.parent === null ) camera.updateMatrixWorld(); - } else { + camera.matrixWorldInverse.getInverse( camera.matrixWorld ); - _gl.drawElements(mode, offsets[i].count, type, offsets[i].start * size); + _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); + _frustum.setFromMatrix( _projScreenMatrix ); - } + lights.length = 0; - _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; + opaqueObjectsLastIndex = -1; + transparentObjectsLastIndex = -1; - } + sprites.length = 0; + lensFlares.length = 0; - } + projectObject( scene ); - } else { + opaqueObjects.length = opaqueObjectsLastIndex + 1; + transparentObjects.length = transparentObjectsLastIndex + 1; - // non-indexed triangles + if ( _this.sortObjects === true ) { - if (updateBuffers) { + opaqueObjects.sort( painterSortStable ); + transparentObjects.sort( reversePainterSortStable ); - setupVertexAttributes(material, program, geometry, 0); + } - } + // - var position = geometry.attributes['position']; + shadowMap.render( scene ); - // render non-indexed triangles + // - if (geometry instanceof THREE.InstancedBufferGeometry && geometry.maxInstancedCount > 0) { + _infoRender.calls = 0; + _infoRender.vertices = 0; + _infoRender.faces = 0; + _infoRender.points = 0; - var extension = extensions.get('ANGLE_instanced_arrays'); + this.setRenderTarget( renderTarget ); - if (extension === null) { + if ( this.autoClear || forceClear ) { - THREE.error('THREE.WebGLRenderer.setupVertexAttributes: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.'); - return; + this.clear( this.autoClearColor, this.autoClearDepth, this.autoClearStencil ); - } + } - if (position instanceof THREE.InterleavedBufferAttribute) { + // - extension.drawArraysInstancedANGLE(mode, 0, position.data.array.length / position.data.stride, geometry.maxInstancedCount); // Draw the instanced meshes + if ( scene.overrideMaterial ) { - } else { + var overrideMaterial = scene.overrideMaterial; - extension.drawArraysInstancedANGLE(mode, 0, position.array.length / position.itemSize, geometry.maxInstancedCount); // Draw the instanced meshes + renderObjects( opaqueObjects, camera, lights, fog, overrideMaterial ); + renderObjects( transparentObjects, camera, lights, fog, overrideMaterial ); - } + } else { - } else { + // opaque pass (front-to-back order) - if (position instanceof THREE.InterleavedBufferAttribute) { + state.setBlending( THREE.NoBlending ); + renderObjects( opaqueObjects, camera, lights, fog ); - _gl.drawArrays(mode, 0, position.data.array.length / position.data.stride); + // transparent pass (back-to-front order) - } else { + renderObjects( transparentObjects, camera, lights, fog ); - _gl.drawArrays(mode, 0, position.array.length / position.itemSize); + } - } + // custom render plugins (post pass) - } + spritePlugin.render( scene, camera ); + lensFlarePlugin.render( scene, camera, _currentWidth, _currentHeight ); - _this.info.render.calls++; - _this.info.render.vertices += position.array.length / position.itemSize; - _this.info.render.faces += position.array.length / ( 3 * position.itemSize ); + // Generate mipmap if we're using any kind of mipmap filtering - } + if ( renderTarget && renderTarget.generateMipmaps && renderTarget.texture.minFilter !== THREE.NearestFilter && renderTarget.texture.minFilter !== THREE.LinearFilter ) { - } else if (object instanceof THREE.PointCloud) { + updateRenderTargetMipmap( renderTarget ); - // render particles + } - var mode = _gl.POINTS; + // Ensure depth buffer writing is enabled so it can be cleared on next render - var index = geometry.attributes.index; + state.setDepthTest( true ); + state.setDepthWrite( true ); + state.setColorWrite( true ); - if (index) { + // _gl.finish(); - // indexed points + }; - var type, size; + function pushRenderItem( object, geometry, material, z, group ) { - if (index.array instanceof Uint32Array && extensions.get('OES_element_index_uint')) { + var array, index; - type = _gl.UNSIGNED_INT; - size = 4; + // allocate the next position in the appropriate array - } else { + if ( material.transparent ) { - type = _gl.UNSIGNED_SHORT; - size = 2; + array = transparentObjects; + index = ++ transparentObjectsLastIndex; - } + } else { - var offsets = geometry.offsets; + array = opaqueObjects; + index = ++ opaqueObjectsLastIndex; - if (offsets.length === 0) { + } - if (updateBuffers) { + // recycle existing render item or grow the array - setupVertexAttributes(material, program, geometry, 0); - _gl.bindBuffer(_gl.ELEMENT_ARRAY_BUFFER, index.buffer); + var renderItem = array[ index ]; - } + if ( renderItem !== undefined ) { - _gl.drawElements(mode, index.array.length, type, 0); + renderItem.id = object.id; + renderItem.object = object; + renderItem.geometry = geometry; + renderItem.material = material; + renderItem.z = _vector3.z; + renderItem.group = group; - _this.info.render.calls++; - _this.info.render.points += index.array.length; + } else { - } else { + renderItem = { + id: object.id, + object: object, + geometry: geometry, + material: material, + z: _vector3.z, + group: group + }; - // 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 + // assert( index === array.length ); + array.push( renderItem ); - if (offsets.length > 1) updateBuffers = true; + } - for (var i = 0, il = offsets.length; i < il; i++) { + } - var startIndex = offsets[i].index; + function projectObject( object ) { - if (updateBuffers) { + if ( object.visible === false ) return; - setupVertexAttributes(material, program, geometry, startIndex); - _gl.bindBuffer(_gl.ELEMENT_ARRAY_BUFFER, index.buffer); + if ( object instanceof THREE.Light ) { - } + lights.push( object ); - // render indexed points + } else if ( object instanceof THREE.Sprite ) { - _gl.drawElements(mode, offsets[i].count, type, offsets[i].start * size); + sprites.push( object ); - _this.info.render.calls++; - _this.info.render.points += offsets[i].count; + } else if ( object instanceof THREE.LensFlare ) { - } + lensFlares.push( object ); - } + } else if ( object instanceof THREE.ImmediateRenderObject ) { - } else { + if ( _this.sortObjects === true ) { - // non-indexed points + _vector3.setFromMatrixPosition( object.matrixWorld ); + _vector3.applyProjection( _projScreenMatrix ); - if (updateBuffers) { + } - setupVertexAttributes(material, program, geometry, 0); + pushRenderItem( object, null, object.material, _vector3.z, null ); - } + } else if ( object instanceof THREE.Mesh || object instanceof THREE.Line || object instanceof THREE.Points ) { - var position = geometry.attributes.position; - var offsets = geometry.offsets; + if ( object instanceof THREE.SkinnedMesh ) { - if (offsets.length === 0) { + object.skeleton.update(); - _gl.drawArrays(mode, 0, position.array.length / 3); + } - _this.info.render.calls++; - _this.info.render.points += position.array.length / 3; + if ( object.frustumCulled === false || _frustum.intersectsObject( object ) === true ) { - } else { + var material = object.material; - for (var i = 0, il = offsets.length; i < il; i++) { + if ( material.visible === true ) { - _gl.drawArrays(mode, offsets[i].index, offsets[i].count); + if ( _this.sortObjects === true ) { - _this.info.render.calls++; - _this.info.render.points += offsets[i].count; + _vector3.setFromMatrixPosition( object.matrixWorld ); + _vector3.applyProjection( _projScreenMatrix ); - } + } - } + var geometry = objects.update( object ); - } + if ( material instanceof THREE.MeshFaceMaterial ) { - } else if (object instanceof THREE.Line) { + var groups = geometry.groups; + var materials = material.materials; - var mode = ( object.mode === THREE.LineStrip ) ? _gl.LINE_STRIP : _gl.LINES; + for ( var i = 0, l = groups.length; i < l; i ++ ) { - // In case user is not using Line*Material by mistake - var lineWidth = material.linewidth !== undefined ? material.linewidth : 1; + var group = groups[ i ]; + var groupMaterial = materials[ group.materialIndex ]; - state.setLineWidth(lineWidth * pixelRatio); + if ( groupMaterial.visible === true ) { - var index = geometry.attributes.index; + pushRenderItem( object, geometry, groupMaterial, _vector3.z, group ); - if (index) { + } - // indexed lines + } - var type, size; + } else { - if (index.array instanceof Uint32Array) { + pushRenderItem( object, geometry, material, _vector3.z, null ); - type = _gl.UNSIGNED_INT; - size = 4; + } - } else { + } - type = _gl.UNSIGNED_SHORT; - size = 2; + } - } + } - var offsets = geometry.offsets; + var children = object.children; - if (offsets.length === 0) { + for ( var i = 0, l = children.length; i < l; i ++ ) { - if (updateBuffers) { + projectObject( children[ i ] ); - setupVertexAttributes(material, program, geometry, 0); - _gl.bindBuffer(_gl.ELEMENT_ARRAY_BUFFER, index.buffer); + } - } + } - _gl.drawElements(mode, index.array.length, type, 0); // 2 bytes per Uint16Array + function renderObjects( renderList, camera, lights, fog, overrideMaterial ) { - _this.info.render.calls++; - _this.info.render.vertices += index.array.length; // not really true, here vertices can be shared + for ( var i = 0, l = renderList.length; i < l; i ++ ) { - } else { + var renderItem = renderList[ i ]; - // 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 + var object = renderItem.object; + var geometry = renderItem.geometry; + var material = overrideMaterial === undefined ? renderItem.material : overrideMaterial; + var group = renderItem.group; - if (offsets.length > 1) updateBuffers = true; + object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + object.normalMatrix.getNormalMatrix( object.modelViewMatrix ); - for (var i = 0, il = offsets.length; i < il; i++) { + if ( object instanceof THREE.ImmediateRenderObject ) { - var startIndex = offsets[i].index; + setMaterial( material ); - if (updateBuffers) { + var program = setProgram( camera, lights, fog, material, object ); - setupVertexAttributes(material, program, geometry, startIndex); - _gl.bindBuffer(_gl.ELEMENT_ARRAY_BUFFER, index.buffer); + _currentGeometryProgram = ''; - } + object.render( function ( object ) { - // render indexed lines + _this.renderBufferImmediate( object, program, material ); - _gl.drawElements(mode, offsets[i].count, type, offsets[i].start * size); // 2 bytes per Uint16Array + } ); - _this.info.render.calls++; - _this.info.render.vertices += offsets[i].count; // not really true, here vertices can be shared + } else { - } + _this.renderBufferDirect( camera, lights, fog, geometry, material, object, group ); - } + } - } else { + } - // non-indexed lines + } - if (updateBuffers) { + function initMaterial( material, lights, fog, object ) { - setupVertexAttributes(material, program, geometry, 0); + var materialProperties = properties.get( material ); - } + var parameters = programCache.getParameters( material, lights, fog, object ); + var code = programCache.getProgramCode( material, parameters ); - var position = geometry.attributes.position; - var offsets = geometry.offsets; + var program = materialProperties.program; + var programChange = true; - if (offsets.length === 0) { + if ( program === undefined ) { - _gl.drawArrays(mode, 0, position.array.length / 3); + // new material + material.addEventListener( 'dispose', onMaterialDispose ); - _this.info.render.calls++; - _this.info.render.vertices += position.array.length / 3; + } else if ( program.code !== code ) { - } else { + // changed glsl or parameters + releaseMaterialProgramReference( material ); - for (var i = 0, il = offsets.length; i < il; i++) { + } else if ( parameters.shaderID !== undefined ) { - _gl.drawArrays(mode, offsets[i].index, offsets[i].count); + // same glsl and uniform list + return; - _this.info.render.calls++; - _this.info.render.vertices += offsets[i].count; + } else { - } + // only rebuild uniform list + programChange = false; - } + } - } + if ( programChange ) { - } + if ( parameters.shaderID ) { - }; + var shader = THREE.ShaderLib[ parameters.shaderID ]; - function setupMorphTargets(material, geometryGroup, object) { + materialProperties.__webglShader = { + name: material.type, + uniforms: THREE.UniformsUtils.clone( shader.uniforms ), + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader + }; - // set base + } else { - var attributes = material.program.attributes; + materialProperties.__webglShader = { + name: material.type, + uniforms: material.uniforms, + vertexShader: material.vertexShader, + fragmentShader: material.fragmentShader + }; - if (object.morphTargetBase !== -1 && attributes.position >= 0) { + } - _gl.bindBuffer(_gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[object.morphTargetBase]); + material.__webglShader = materialProperties.__webglShader; - state.enableAttribute(attributes.position); + program = programCache.acquireProgram( material, parameters, code ); - _gl.vertexAttribPointer(attributes.position, 3, _gl.FLOAT, false, 0, 0); + materialProperties.program = program; + material.program = program; - } else if (attributes.position >= 0) { + } - _gl.bindBuffer(_gl.ARRAY_BUFFER, geometryGroup.__webglVertexBuffer); + var attributes = program.getAttributes(); - state.enableAttribute(attributes.position); + if ( material.morphTargets ) { - _gl.vertexAttribPointer(attributes.position, 3, _gl.FLOAT, false, 0, 0); + material.numSupportedMorphTargets = 0; - } + for ( var i = 0; i < _this.maxMorphTargets; i ++ ) { - if (object.morphTargetForcedOrder.length) { + if ( attributes[ 'morphTarget' + i ] >= 0 ) { - // set forced order + material.numSupportedMorphTargets ++; - var m = 0; - var order = object.morphTargetForcedOrder; - var influences = object.morphTargetInfluences; + } - var attribute; + } - while (m < material.numSupportedMorphTargets && m < order.length) { + } - attribute = attributes['morphTarget' + m]; + if ( material.morphNormals ) { - if (attribute >= 0) { + material.numSupportedMorphNormals = 0; - _gl.bindBuffer(_gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[order[m]]); + for ( i = 0; i < _this.maxMorphNormals; i ++ ) { - state.enableAttribute(attribute); + if ( attributes[ 'morphNormal' + i ] >= 0 ) { - _gl.vertexAttribPointer(attribute, 3, _gl.FLOAT, false, 0, 0); + material.numSupportedMorphNormals ++; - } + } - attribute = attributes['morphNormal' + m]; + } - if (attribute >= 0 && material.morphNormals) { + } - _gl.bindBuffer(_gl.ARRAY_BUFFER, geometryGroup.__webglMorphNormalsBuffers[order[m]]); + materialProperties.uniformsList = []; - state.enableAttribute(attribute); + var uniformLocations = materialProperties.program.getUniforms(); - _gl.vertexAttribPointer(attribute, 3, _gl.FLOAT, false, 0, 0); + for ( var u in materialProperties.__webglShader.uniforms ) { - } + var location = uniformLocations[ u ]; - object.__webglMorphTargetInfluences[m] = influences[order[m]]; + if ( location ) { - m++; + materialProperties.uniformsList.push( [ materialProperties.__webglShader.uniforms[ u ], location ] ); - } + } - } else { + } - // find the most influencing + } - var activeInfluenceIndices = []; - var influences = object.morphTargetInfluences; - var morphTargets = object.geometry.morphTargets; + function setMaterial( material ) { - if (influences.length > morphTargets.length) { + setMaterialFaces( material ); - THREE.warn('THREE.WebGLRenderer: Influences array is bigger than morphTargets array.'); - influences.length = morphTargets.length; + if ( material.transparent === true ) { - } + state.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha ); - for (var i = 0, il = influences.length; i < il; i++) { + } else { - var influence = influences[i]; + state.setBlending( THREE.NoBlending ); - activeInfluenceIndices.push([influence, i]); + } - } + state.setDepthFunc( material.depthFunc ); + state.setDepthTest( material.depthTest ); + state.setDepthWrite( material.depthWrite ); + state.setColorWrite( material.colorWrite ); + state.setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); - if (activeInfluenceIndices.length > material.numSupportedMorphTargets) { + } - activeInfluenceIndices.sort(numericalSort); - activeInfluenceIndices.length = material.numSupportedMorphTargets; + function setMaterialFaces( material ) { - } else if (activeInfluenceIndices.length > material.numSupportedMorphNormals) { + material.side !== THREE.DoubleSide ? state.enable( _gl.CULL_FACE ) : state.disable( _gl.CULL_FACE ); + state.setFlipSided( material.side === THREE.BackSide ); - activeInfluenceIndices.sort(numericalSort); + } - } else if (activeInfluenceIndices.length === 0) { + function setProgram( camera, lights, fog, material, object ) { - activeInfluenceIndices.push([0, 0]); + _usedTextureUnits = 0; - } + var materialProperties = properties.get( material ); - var attribute; + if ( material.needsUpdate || ! materialProperties.program ) { - for (var m = 0, ml = material.numSupportedMorphTargets; m < ml; m++) { + initMaterial( material, lights, fog, object ); + material.needsUpdate = false; - if (activeInfluenceIndices[m]) { + } - var influenceIndex = activeInfluenceIndices[m][1]; + var refreshProgram = false; + var refreshMaterial = false; + var refreshLights = false; - attribute = attributes['morphTarget' + m]; + var program = materialProperties.program, + p_uniforms = program.getUniforms(), + m_uniforms = materialProperties.__webglShader.uniforms; - if (attribute >= 0) { + if ( program.id !== _currentProgram ) { - _gl.bindBuffer(_gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[influenceIndex]); + _gl.useProgram( program.program ); + _currentProgram = program.id; - state.enableAttribute(attribute); + refreshProgram = true; + refreshMaterial = true; + refreshLights = true; - _gl.vertexAttribPointer(attribute, 3, _gl.FLOAT, false, 0, 0); + } - } + if ( material.id !== _currentMaterialId ) { - attribute = attributes['morphNormal' + m]; + if ( _currentMaterialId === - 1 ) refreshLights = true; + _currentMaterialId = material.id; - if (attribute >= 0 && material.morphNormals) { + refreshMaterial = true; - _gl.bindBuffer(_gl.ARRAY_BUFFER, geometryGroup.__webglMorphNormalsBuffers[influenceIndex]); + } - state.enableAttribute(attribute); + if ( refreshProgram || camera !== _currentCamera ) { - _gl.vertexAttribPointer(attribute, 3, _gl.FLOAT, false, 0, 0); + _gl.uniformMatrix4fv( p_uniforms.projectionMatrix, false, camera.projectionMatrix.elements ); - } + if ( capabilities.logarithmicDepthBuffer ) { - object.__webglMorphTargetInfluences[m] = influences[influenceIndex]; + _gl.uniform1f( p_uniforms.logDepthBufFC, 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) ); - } else { + } - /* - _gl.vertexAttribPointer( attributes[ "morphTarget" + m ], 3, _gl.FLOAT, false, 0, 0 ); - if ( material.morphNormals ) { + if ( camera !== _currentCamera ) _currentCamera = camera; - _gl.vertexAttribPointer( attributes[ "morphNormal" + m ], 3, _gl.FLOAT, false, 0, 0 ); + // 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 ) { - object.__webglMorphTargetInfluences[m] = 0; + if ( p_uniforms.cameraPosition !== undefined ) { - } + _vector3.setFromMatrixPosition( camera.matrixWorld ); + _gl.uniform3f( p_uniforms.cameraPosition, _vector3.x, _vector3.y, _vector3.z ); - } + } - } + } - // load updated influences uniform + if ( material instanceof THREE.MeshPhongMaterial || + material instanceof THREE.MeshLambertMaterial || + material instanceof THREE.MeshBasicMaterial || + material instanceof THREE.ShaderMaterial || + material.skinning ) { - if (material.program.uniforms.morphTargetInfluences !== null) { + if ( p_uniforms.viewMatrix !== undefined ) { - _gl.uniform1fv(material.program.uniforms.morphTargetInfluences, object.__webglMorphTargetInfluences); + _gl.uniformMatrix4fv( p_uniforms.viewMatrix, false, camera.matrixWorldInverse.elements ); - } + } - } + } - // Sorting + } - function painterSortStable(a, b) { + // 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 (a.object.renderOrder !== b.object.renderOrder) { + if ( material.skinning ) { - return a.object.renderOrder - b.object.renderOrder; + if ( object.bindMatrix && p_uniforms.bindMatrix !== undefined ) { - } else if (a.material.id !== b.material.id) { + _gl.uniformMatrix4fv( p_uniforms.bindMatrix, false, object.bindMatrix.elements ); - return a.material.id - b.material.id; + } - } else if (a.z !== b.z) { + if ( object.bindMatrixInverse && p_uniforms.bindMatrixInverse !== undefined ) { - return a.z - b.z; + _gl.uniformMatrix4fv( p_uniforms.bindMatrixInverse, false, object.bindMatrixInverse.elements ); - } else { + } - return a.id - b.id; + if ( capabilities.floatVertexTextures && object.skeleton && object.skeleton.useVertexTexture ) { - } + if ( p_uniforms.boneTexture !== undefined ) { - } + var textureUnit = getTextureUnit(); - function reversePainterSortStable(a, b) { + _gl.uniform1i( p_uniforms.boneTexture, textureUnit ); + _this.setTexture( object.skeleton.boneTexture, textureUnit ); - if (a.object.renderOrder !== b.object.renderOrder) { + } - return a.object.renderOrder - b.object.renderOrder; + if ( p_uniforms.boneTextureWidth !== undefined ) { - } - if (a.z !== b.z) { + _gl.uniform1i( p_uniforms.boneTextureWidth, object.skeleton.boneTextureWidth ); - return b.z - a.z; + } - } else { + if ( p_uniforms.boneTextureHeight !== undefined ) { - return a.id - b.id; + _gl.uniform1i( p_uniforms.boneTextureHeight, object.skeleton.boneTextureHeight ); - } + } - } + } else if ( object.skeleton && object.skeleton.boneMatrices ) { - function numericalSort(a, b) { + if ( p_uniforms.boneGlobalMatrices !== undefined ) { - return b[0] - a[0]; + _gl.uniformMatrix4fv( p_uniforms.boneGlobalMatrices, false, object.skeleton.boneMatrices ); - } + } - // Rendering + } - this.render = function (scene, camera, renderTarget, forceClear) { + } - if (camera instanceof THREE.Camera === false) { + if ( refreshMaterial ) { - THREE.error('THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.'); - return; + // refresh uniforms common to several materials - } + if ( fog && material.fog ) { - var fog = scene.fog; + refreshUniformsFog( m_uniforms, fog ); - // reset caching for this frame + } - _currentGeometryProgram = ''; - _currentMaterialId = -1; - _currentCamera = null; - _lightsNeedUpdate = true; + if ( material instanceof THREE.MeshPhongMaterial || + material instanceof THREE.MeshLambertMaterial || + material.lights ) { - // update scene graph + if ( _lightsNeedUpdate ) { - if (scene.autoUpdate === true) scene.updateMatrixWorld(); + refreshLights = true; + setupLights( lights, camera ); + _lightsNeedUpdate = false; - // update camera matrices and frustum + } - if (camera.parent === undefined) camera.updateMatrixWorld(); + if ( refreshLights ) { - // update Skeleton objects + refreshUniformsLights( m_uniforms, _lights ); + markUniformsLightsNeedsUpdate( m_uniforms, true ); - scene.traverse(function (object) { + } else { - if (object instanceof THREE.SkinnedMesh) { + markUniformsLightsNeedsUpdate( m_uniforms, false ); - object.skeleton.update(); + } - } + } - }); + if ( material instanceof THREE.MeshBasicMaterial || + material instanceof THREE.MeshLambertMaterial || + material instanceof THREE.MeshPhongMaterial ) { - camera.matrixWorldInverse.getInverse(camera.matrixWorld); + refreshUniformsCommon( m_uniforms, material ); - _projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); - _frustum.setFromMatrix(_projScreenMatrix); + } - lights.length = 0; - opaqueObjects.length = 0; - transparentObjects.length = 0; + // refresh single material specific uniforms - sprites.length = 0; - lensFlares.length = 0; + if ( material instanceof THREE.LineBasicMaterial ) { - projectObject(scene); + refreshUniformsLine( m_uniforms, material ); - if (_this.sortObjects === true) { + } else if ( material instanceof THREE.LineDashedMaterial ) { - opaqueObjects.sort(painterSortStable); - transparentObjects.sort(reversePainterSortStable); + refreshUniformsLine( m_uniforms, material ); + refreshUniformsDash( m_uniforms, material ); - } + } else if ( material instanceof THREE.PointsMaterial ) { - // + refreshUniformsParticle( m_uniforms, material ); - shadowMap.render(scene, camera); + } else if ( material instanceof THREE.MeshPhongMaterial ) { - // + refreshUniformsPhong( m_uniforms, material ); - _this.info.render.calls = 0; - _this.info.render.vertices = 0; - _this.info.render.faces = 0; - _this.info.render.points = 0; + } else if ( material instanceof THREE.MeshDepthMaterial ) { - this.setRenderTarget(renderTarget); + m_uniforms.mNear.value = camera.near; + m_uniforms.mFar.value = camera.far; + m_uniforms.opacity.value = material.opacity; - if (this.autoClear || forceClear) { + } else if ( material instanceof THREE.MeshNormalMaterial ) { - this.clear(this.autoClearColor, this.autoClearDepth, this.autoClearStencil); + m_uniforms.opacity.value = material.opacity; - } + } - // set matrices for immediate objects + if ( object.receiveShadow && ! material._shadowPass ) { - for (var i = 0, il = objects.objectsImmediate.length; i < il; i++) { + refreshUniformsShadow( m_uniforms, lights, camera ); - var webglObject = objects.objectsImmediate[i]; - var object = webglObject.object; + } - if (object.visible) { + // load common uniforms - setupMatrices(object, camera); + loadUniformsGeneric( materialProperties.uniformsList ); - unrollImmediateBufferMaterial(webglObject); + } - } + loadUniformsMatrices( p_uniforms, object ); - } + if ( p_uniforms.modelMatrix !== undefined ) { - if (scene.overrideMaterial) { + _gl.uniformMatrix4fv( p_uniforms.modelMatrix, false, object.matrixWorld.elements ); - var overrideMaterial = scene.overrideMaterial; + } - setMaterial(overrideMaterial); + return program; - renderObjects(opaqueObjects, camera, lights, fog, overrideMaterial); - renderObjects(transparentObjects, camera, lights, fog, overrideMaterial); - renderObjectsImmediate(objects.objectsImmediate, '', camera, lights, fog, overrideMaterial); + } - } else { + // Uniforms (refresh uniforms objects) - // opaque pass (front-to-back order) + function refreshUniformsCommon ( uniforms, material ) { - state.setBlending(THREE.NoBlending); + uniforms.opacity.value = material.opacity; - renderObjects(opaqueObjects, camera, lights, fog, null); - renderObjectsImmediate(objects.objectsImmediate, 'opaque', camera, lights, fog, null); + uniforms.diffuse.value = material.color; - // transparent pass (back-to-front order) + if ( material.emissive ) { - renderObjects(transparentObjects, camera, lights, fog, null); - renderObjectsImmediate(objects.objectsImmediate, 'transparent', camera, lights, fog, null); + uniforms.emissive.value = material.emissive; - } + } - // custom render plugins (post pass) + uniforms.map.value = material.map; + uniforms.specularMap.value = material.specularMap; + uniforms.alphaMap.value = material.alphaMap; - spritePlugin.render(scene, camera); - lensFlarePlugin.render(scene, camera, _currentWidth, _currentHeight); + if ( material.aoMap ) { - // Generate mipmap if we're using any kind of mipmap filtering + uniforms.aoMap.value = material.aoMap; + uniforms.aoMapIntensity.value = material.aoMapIntensity; - if (renderTarget && renderTarget.generateMipmaps && renderTarget.texture.minFilter !== THREE.NearestFilter && renderTarget.texture.minFilter !== THREE.LinearFilter) { + } - updateRenderTargetMipmap(renderTarget); + // uv repeat and offset setting priorities + // 1. color map + // 2. specular map + // 3. normal map + // 4. bump map + // 5. alpha map + // 6. emissive map - } + var uvScaleMap; - // Ensure depth buffer writing is enabled so it can be cleared on next render + if ( material.map ) { - state.setDepthTest(true); - state.setDepthWrite(true); - state.setColorWrite(true); + uvScaleMap = material.map; - // _gl.finish(); + } else if ( material.specularMap ) { - }; + uvScaleMap = material.specularMap; - function projectObject(object) { + } else if ( material.displacementMap ) { - if (object.visible === false) return; + uvScaleMap = material.displacementMap; - if (object instanceof THREE.Scene || object instanceof THREE.Group) { + } else if ( material.normalMap ) { - // skip + uvScaleMap = material.normalMap; - } else { + } else if ( material.bumpMap ) { - objects.init(object); + uvScaleMap = material.bumpMap; - if (object instanceof THREE.Light) { + } else if ( material.alphaMap ) { - lights.push(object); + uvScaleMap = material.alphaMap; - } else if (object instanceof THREE.Sprite) { + } else if ( material.emissiveMap ) { - sprites.push(object); + uvScaleMap = material.emissiveMap; - } else if (object instanceof THREE.LensFlare) { + } - lensFlares.push(object); + if ( uvScaleMap !== undefined ) { - } else { + if ( uvScaleMap instanceof THREE.WebGLRenderTarget ) uvScaleMap = uvScaleMap.texture; - var webglObject = objects.objects[object.id]; + var offset = uvScaleMap.offset; + var repeat = uvScaleMap.repeat; - if (webglObject && ( object.frustumCulled === false || _frustum.intersectsObject(object) === true )) { + uniforms.offsetRepeat.value.set( offset.x, offset.y, repeat.x, repeat.y ); - unrollBufferMaterial(webglObject); + } - webglObject.render = true; + uniforms.envMap.value = material.envMap; + uniforms.flipEnvMap.value = ( material.envMap instanceof THREE.WebGLRenderTargetCube ) ? 1 : - 1; - if (_this.sortObjects === true) { + uniforms.reflectivity.value = material.reflectivity; + uniforms.refractionRatio.value = material.refractionRatio; - _vector3.setFromMatrixPosition(object.matrixWorld); - _vector3.applyProjection(_projScreenMatrix); + } - webglObject.z = _vector3.z; + 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; - for (var i = 0, l = object.children.length; i < l; i++) { + } - projectObject(object.children[i]); + 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 renderObjects(renderList, camera, lights, fog, overrideMaterial) { + if ( material.map !== null ) { - var material; + var offset = material.map.offset; + var repeat = material.map.repeat; - for (var i = 0, l = renderList.length; i < l; i++) { + uniforms.offsetRepeat.value.set( offset.x, offset.y, repeat.x, repeat.y ); - var webglObject = renderList[i]; + } - var object = webglObject.object; - var buffer = objects.geometries.get(object); + } - setupMatrices(object, camera); + function refreshUniformsFog ( uniforms, fog ) { - if (overrideMaterial) { + uniforms.fogColor.value = fog.color; - material = overrideMaterial; + if ( fog instanceof THREE.Fog ) { - } else { + uniforms.fogNear.value = fog.near; + uniforms.fogFar.value = fog.far; - material = webglObject.material; + } else if ( fog instanceof THREE.FogExp2 ) { - if (!material) continue; + uniforms.fogDensity.value = fog.density; - setMaterial(material); + } - } + } - _this.setMaterialFaces(material); - _this.renderBufferDirect(camera, lights, fog, material, buffer, object); + function refreshUniformsPhong ( uniforms, material ) { - } + uniforms.specular.value = material.specular; + uniforms.shininess.value = material.shininess; - } + if ( material.lightMap ) { - function renderObjectsImmediate(renderList, materialType, camera, lights, fog, overrideMaterial) { + uniforms.lightMap.value = material.lightMap; + uniforms.lightMapIntensity.value = material.lightMapIntensity; - var material; + } - for (var i = 0, l = renderList.length; i < l; i++) { + if ( material.emissiveMap ) { - var webglObject = renderList[i]; - var object = webglObject.object; + uniforms.emissiveMap.value = material.emissiveMap; - if (object.visible) { + } - if (overrideMaterial) { + if ( material.bumpMap ) { - material = overrideMaterial; + uniforms.bumpMap.value = material.bumpMap; + uniforms.bumpScale.value = material.bumpScale; - } else { + } - material = webglObject[materialType]; + if ( material.normalMap ) { - if (!material) continue; + uniforms.normalMap.value = material.normalMap; + uniforms.normalScale.value.copy( material.normalScale ); - setMaterial(material); + } - } + if ( material.displacementMap ) { - _this.renderImmediateObject(camera, lights, fog, material, object); + uniforms.displacementMap.value = material.displacementMap; + uniforms.displacementScale.value = material.displacementScale; + uniforms.displacementBias.value = material.displacementBias; - } + } - } + } - } + function refreshUniformsLights ( uniforms, lights ) { - this.renderImmediateObject = function (camera, lights, fog, material, object) { + uniforms.ambientLightColor.value = lights.ambient; - var program = setProgram(camera, lights, fog, material, object); + uniforms.directionalLightColor.value = lights.directional.colors; + uniforms.directionalLightDirection.value = lights.directional.positions; - _currentGeometryProgram = ''; + uniforms.pointLightColor.value = lights.point.colors; + uniforms.pointLightPosition.value = lights.point.positions; + uniforms.pointLightDistance.value = lights.point.distances; + uniforms.pointLightDecay.value = lights.point.decays; - _this.setMaterialFaces(material); + 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.spotLightDecay.value = lights.spot.decays; - if (object.immediateRenderCallback) { + uniforms.hemisphereLightSkyColor.value = lights.hemi.skyColors; + uniforms.hemisphereLightGroundColor.value = lights.hemi.groundColors; + uniforms.hemisphereLightDirection.value = lights.hemi.positions; - object.immediateRenderCallback(program, _gl, _frustum); + } - } else { + // If uniforms are marked as clean, they don't need to be loaded to the GPU. - object.render(function (object) { - _this.renderBufferImmediate(object, program, material); - }); + function markUniformsLightsNeedsUpdate ( uniforms, value ) { - } + uniforms.ambientLightColor.needsUpdate = value; - }; + uniforms.directionalLightColor.needsUpdate = value; + uniforms.directionalLightDirection.needsUpdate = value; - function unrollImmediateBufferMaterial(globject) { + uniforms.pointLightColor.needsUpdate = value; + uniforms.pointLightPosition.needsUpdate = value; + uniforms.pointLightDistance.needsUpdate = value; + uniforms.pointLightDecay.needsUpdate = value; - var object = globject.object, - material = object.material; + uniforms.spotLightColor.needsUpdate = value; + uniforms.spotLightPosition.needsUpdate = value; + uniforms.spotLightDistance.needsUpdate = value; + uniforms.spotLightDirection.needsUpdate = value; + uniforms.spotLightAngleCos.needsUpdate = value; + uniforms.spotLightExponent.needsUpdate = value; + uniforms.spotLightDecay.needsUpdate = value; - if (material.transparent) { + uniforms.hemisphereLightSkyColor.needsUpdate = value; + uniforms.hemisphereLightGroundColor.needsUpdate = value; + uniforms.hemisphereLightDirection.needsUpdate = value; - globject.transparent = material; - globject.opaque = null; + } - } else { + function refreshUniformsShadow ( uniforms, lights, camera ) { - globject.opaque = material; - globject.transparent = null; + if ( uniforms.shadowMatrix ) { - } + var j = 0; - } + for ( var i = 0, il = lights.length; i < il; i ++ ) { - function unrollBufferMaterial(globject) { + var light = lights[ i ]; - var object = globject.object; - var material = object.material; + if ( ! light.castShadow ) continue; - if (material) { + if ( light instanceof THREE.PointLight || light instanceof THREE.SpotLight || light instanceof THREE.DirectionalLight ) { - globject.material = material; + if ( light instanceof THREE.PointLight ) { - if (material.transparent) { + // for point lights we set the sign of the shadowDarkness uniform to be negative + uniforms.shadowDarkness.value[ j ] = - light.shadowDarkness; - transparentObjects.push(globject); + } else { + + uniforms.shadowDarkness.value[ j ] = light.shadowDarkness; - } else { + } - opaqueObjects.push(globject); + uniforms.shadowMatrix.value[ j ] = light.shadowMatrix; + uniforms.shadowMap.value[ j ] = light.shadowMap; + uniforms.shadowMapSize.value[ j ] = light.shadowMapSize; + uniforms.shadowBias.value[ j ] = light.shadowBias; - } + j ++; - } + } - } + } - // Materials + } - var shaderIDs = { - MeshDepthMaterial: 'depth', - MeshNormalMaterial: 'normal', - MeshBasicMaterial: 'basic', - MeshLambertMaterial: 'lambert', - MeshPhongMaterial: 'phong', - LineBasicMaterial: 'basic', - LineDashedMaterial: 'dashed', - PointCloudMaterial: 'particle_basic' - }; + } - function initMaterial(material, lights, fog, object) { + // Uniforms (load to GPU) - var shaderID = shaderIDs[material.type]; + function loadUniformsMatrices ( uniforms, object ) { - // heuristics to create shader parameters according to lights in the scene - // (not to blow over maxLights budget) + _gl.uniformMatrix4fv( uniforms.modelViewMatrix, false, object.modelViewMatrix.elements ); - var maxLightCount = allocateLights(lights); - var maxShadows = allocateShadows(lights); - var maxBones = allocateBones(object); + if ( uniforms.normalMatrix ) { - var parameters = { + _gl.uniformMatrix3fv( uniforms.normalMatrix, false, object.normalMatrix.elements ); - precision: _precision, - supportsVertexTextures: _supportsVertexTextures, + } - map: !!material.map, - envMap: !!material.envMap, - envMapMode: material.envMap && material.envMap.mapping, - lightMap: !!material.lightMap, - aoMap: !!material.aoMap, - bumpMap: !!material.bumpMap, - normalMap: !!material.normalMap, - specularMap: !!material.specularMap, - alphaMap: !!material.alphaMap, + } - combine: material.combine, + function getTextureUnit() { - vertexColors: material.vertexColors, + var textureUnit = _usedTextureUnits; - fog: fog, - useFog: material.fog, - fogExp: fog instanceof THREE.FogExp2, + if ( textureUnit >= capabilities.maxTextures ) { - flatShading: material.shading === THREE.FlatShading, + console.warn( 'WebGLRenderer: trying to use ' + textureUnit + ' texture units while this GPU supports only ' + capabilities.maxTextures ); - sizeAttenuation: material.sizeAttenuation, - logarithmicDepthBuffer: _logarithmicDepthBuffer, + } - skinning: material.skinning, - maxBones: maxBones, - useVertexTexture: _supportsBoneTextures && object && object.skeleton && object.skeleton.useVertexTexture, + _usedTextureUnits += 1; - morphTargets: material.morphTargets, - morphNormals: material.morphNormals, - maxMorphTargets: _this.maxMorphTargets, - maxMorphNormals: _this.maxMorphNormals, + return textureUnit; - maxDirLights: maxLightCount.directional, - maxPointLights: maxLightCount.point, - maxSpotLights: maxLightCount.spot, - maxHemiLights: maxLightCount.hemi, + } - maxShadows: maxShadows, - shadowMapEnabled: shadowMap.enabled && object.receiveShadow && maxShadows > 0, - shadowMapType: shadowMap.type, - shadowMapDebug: shadowMap.debug, - shadowMapCascade: shadowMap.cascade, + function loadUniformsGeneric ( uniforms ) { - alphaTest: material.alphaTest, - metal: material.metal, - doubleSided: material.side === THREE.DoubleSide, - flipSided: material.side === THREE.BackSide + var texture, textureUnit; - }; + for ( var j = 0, jl = uniforms.length; j < jl; j ++ ) { - // Generate code + var uniform = uniforms[ j ][ 0 ]; - var chunks = []; + // needsUpdate property is not added to all uniforms. + if ( uniform.needsUpdate === false ) continue; - if (shaderID) { + var type = uniform.type; + var value = uniform.value; + var location = uniforms[ j ][ 1 ]; - chunks.push(shaderID); + switch ( type ) { - } else { + case '1i': + _gl.uniform1i( location, value ); + break; - chunks.push(material.fragmentShader); - chunks.push(material.vertexShader); + case '1f': + _gl.uniform1f( location, value ); + break; - } + case '2f': + _gl.uniform2f( location, value[ 0 ], value[ 1 ] ); + break; - if (material.defines !== undefined) { + case '3f': + _gl.uniform3f( location, value[ 0 ], value[ 1 ], value[ 2 ] ); + break; - for (var name in material.defines) { + case '4f': + _gl.uniform4f( location, value[ 0 ], value[ 1 ], value[ 2 ], value[ 3 ] ); + break; - chunks.push(name); - chunks.push(material.defines[name]); + case '1iv': + _gl.uniform1iv( location, value ); + break; - } + case '3iv': + _gl.uniform3iv( location, value ); + break; - } + case '1fv': + _gl.uniform1fv( location, value ); + break; - for (var name in parameters) { + case '2fv': + _gl.uniform2fv( location, value ); + break; - chunks.push(name); - chunks.push(parameters[name]); + case '3fv': + _gl.uniform3fv( location, value ); + break; - } + case '4fv': + _gl.uniform4fv( location, value ); + break; - var code = chunks.join(); + case 'Matrix3fv': + _gl.uniformMatrix3fv( location, false, value ); + break; - if (!material.program) { + case 'Matrix4fv': + _gl.uniformMatrix4fv( location, false, value ); + break; - // new material - material.addEventListener('dispose', onMaterialDispose); + // - } else if (material.program.code !== code) { + case 'i': - // changed glsl or parameters - deallocateMaterial(material); + // single integer + _gl.uniform1i( location, value ); - } else { + break; - // same glsl and parameters - return; + case 'f': - } + // single float + _gl.uniform1f( location, value ); - if (shaderID) { + break; - var shader = THREE.ShaderLib[shaderID]; + case 'v2': - material.__webglShader = { - uniforms: THREE.UniformsUtils.clone(shader.uniforms), - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader - } + // single THREE.Vector2 + _gl.uniform2f( location, value.x, value.y ); - } else { + break; - material.__webglShader = { - uniforms: material.uniforms, - vertexShader: material.vertexShader, - fragmentShader: material.fragmentShader - } + case 'v3': - } + // single THREE.Vector3 + _gl.uniform3f( location, value.x, value.y, value.z ); - var program; + break; - // Check if code has been already compiled + case 'v4': - for (var p = 0, pl = _programs.length; p < pl; p++) { + // single THREE.Vector4 + _gl.uniform4f( location, value.x, value.y, value.z, value.w ); - var programInfo = _programs[p]; + break; - if (programInfo.code === code) { + case 'c': - program = programInfo; - program.usedTimes++; + // single THREE.Color + _gl.uniform3f( location, value.r, value.g, value.b ); - break; + break; - } + case 'iv1': - } + // flat array of integers (JS or typed array) + _gl.uniform1iv( location, value ); - if (program === undefined) { + break; - program = new THREE.WebGLProgram(_this, code, material, parameters); - _programs.push(program); + case 'iv': - _this.info.memory.programs = _programs.length; + // flat array of integers with 3 x N size (JS or typed array) + _gl.uniform3iv( location, value ); - } + break; - material.program = program; + case 'fv1': - var attributes = program.attributes; + // flat array of floats (JS or typed array) + _gl.uniform1fv( location, value ); - if (material.morphTargets) { + break; - material.numSupportedMorphTargets = 0; + case 'fv': - var id, base = 'morphTarget'; + // flat array of floats with 3 x N size (JS or typed array) + _gl.uniform3fv( location, value ); - for (var i = 0; i < _this.maxMorphTargets; i++) { + break; - id = base + i; + case 'v2v': - if (attributes[id] >= 0) { + // array of THREE.Vector2 - material.numSupportedMorphTargets++; + if ( uniform._array === undefined ) { - } + uniform._array = new Float32Array( 2 * value.length ); - } + } - } + for ( var i = 0, i2 = 0, il = value.length; i < il; i ++, i2 += 2 ) { - if (material.morphNormals) { + uniform._array[ i2 + 0 ] = value[ i ].x; + uniform._array[ i2 + 1 ] = value[ i ].y; - material.numSupportedMorphNormals = 0; + } - var id, base = 'morphNormal'; + _gl.uniform2fv( location, uniform._array ); - for (i = 0; i < _this.maxMorphNormals; i++) { + break; - id = base + i; + case 'v3v': - if (attributes[id] >= 0) { + // array of THREE.Vector3 - material.numSupportedMorphNormals++; + if ( uniform._array === undefined ) { - } + uniform._array = new Float32Array( 3 * value.length ); - } + } - } + for ( var i = 0, i3 = 0, il = value.length; i < il; i ++, i3 += 3 ) { - material.uniformsList = []; + uniform._array[ i3 + 0 ] = value[ i ].x; + uniform._array[ i3 + 1 ] = value[ i ].y; + uniform._array[ i3 + 2 ] = value[ i ].z; - for (var u in material.__webglShader.uniforms) { + } - var location = material.program.uniforms[u]; + _gl.uniform3fv( location, uniform._array ); - if (location) { - material.uniformsList.push([material.__webglShader.uniforms[u], location]); - } + break; - } + case 'v4v': - } + // array of THREE.Vector4 - function setMaterial(material) { + if ( uniform._array === undefined ) { - if (material.transparent === true) { + uniform._array = new Float32Array( 4 * value.length ); - state.setBlending(material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha); + } - } else { + for ( var i = 0, i4 = 0, il = value.length; i < il; i ++, i4 += 4 ) { - state.setBlending(THREE.NoBlending); + uniform._array[ i4 + 0 ] = value[ i ].x; + uniform._array[ i4 + 1 ] = value[ i ].y; + uniform._array[ i4 + 2 ] = value[ i ].z; + uniform._array[ i4 + 3 ] = value[ i ].w; - } + } - state.setDepthFunc(material.depthFunc); - state.setDepthTest(material.depthTest); - state.setDepthWrite(material.depthWrite); - state.setColorWrite(material.colorWrite); - state.setPolygonOffset(material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits); + _gl.uniform4fv( location, uniform._array ); - } + break; - function setProgram(camera, lights, fog, material, object) { + case 'm3': - _usedTextureUnits = 0; + // single THREE.Matrix3 + _gl.uniformMatrix3fv( location, false, value.elements ); - if (material.needsUpdate) { + break; - initMaterial(material, lights, fog, object); - material.needsUpdate = false; + case 'm3v': - } + // array of THREE.Matrix3 - if (material.morphTargets) { + if ( uniform._array === undefined ) { - if (!object.__webglMorphTargetInfluences) { + uniform._array = new Float32Array( 9 * value.length ); - object.__webglMorphTargetInfluences = new Float32Array(_this.maxMorphTargets); + } - } + for ( var i = 0, il = value.length; i < il; i ++ ) { - } + value[ i ].flattenToArrayOffset( uniform._array, i * 9 ); - var refreshProgram = false; - var refreshMaterial = false; - var refreshLights = false; + } - var program = material.program, - p_uniforms = program.uniforms, - m_uniforms = material.__webglShader.uniforms; + _gl.uniformMatrix3fv( location, false, uniform._array ); - if (program.id !== _currentProgram) { + break; - _gl.useProgram(program.program); - _currentProgram = program.id; + case 'm4': - refreshProgram = true; - refreshMaterial = true; - refreshLights = true; + // single THREE.Matrix4 + _gl.uniformMatrix4fv( location, false, value.elements ); - } + break; - if (material.id !== _currentMaterialId) { + case 'm4v': - if (_currentMaterialId === -1) refreshLights = true; - _currentMaterialId = material.id; + // array of THREE.Matrix4 - refreshMaterial = true; + if ( uniform._array === undefined ) { - } + uniform._array = new Float32Array( 16 * value.length ); - if (refreshProgram || camera !== _currentCamera) { + } - _gl.uniformMatrix4fv(p_uniforms.projectionMatrix, false, camera.projectionMatrix.elements); + for ( var i = 0, il = value.length; i < il; i ++ ) { - if (_logarithmicDepthBuffer) { + value[ i ].flattenToArrayOffset( uniform._array, i * 16 ); - _gl.uniform1f(p_uniforms.logDepthBufFC, 2.0 / ( Math.log(camera.far + 1.0) / Math.LN2 )); + } - } + _gl.uniformMatrix4fv( location, false, uniform._array ); + break; - if (camera !== _currentCamera) _currentCamera = camera; + case 't': - // load material specific uniforms - // (shader material also gets them for the sake of genericity) + // single THREE.Texture (2d or cube) - if (material instanceof THREE.ShaderMaterial || - material instanceof THREE.MeshPhongMaterial || - material.envMap) { + texture = value; + textureUnit = getTextureUnit(); - if (p_uniforms.cameraPosition !== null) { + _gl.uniform1i( location, textureUnit ); - _vector3.setFromMatrixPosition(camera.matrixWorld); - _gl.uniform3f(p_uniforms.cameraPosition, _vector3.x, _vector3.y, _vector3.z); + if ( ! texture ) continue; - } + if ( texture instanceof THREE.CubeTexture || + ( Array.isArray( texture.image ) && texture.image.length === 6 ) ) { - } + // CompressedTexture can have Array in image :/ - if (material instanceof THREE.MeshPhongMaterial || - material instanceof THREE.MeshLambertMaterial || - material instanceof THREE.MeshBasicMaterial || - material instanceof THREE.ShaderMaterial || - material.skinning) { + setCubeTexture( texture, textureUnit ); - if (p_uniforms.viewMatrix !== null) { + } else if ( texture instanceof THREE.WebGLRenderTargetCube ) { - _gl.uniformMatrix4fv(p_uniforms.viewMatrix, false, camera.matrixWorldInverse.elements); + setCubeTextureDynamic( texture.texture, textureUnit ); - } + } else if ( texture instanceof THREE.WebGLRenderTarget ) { + + _this.setTexture( texture.texture, textureUnit ); - } + } else { - } + _this.setTexture( texture, textureUnit ); - // 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) { + break; - if (object.bindMatrix && p_uniforms.bindMatrix !== null) { + case 'tv': - _gl.uniformMatrix4fv(p_uniforms.bindMatrix, false, object.bindMatrix.elements); + // array of THREE.Texture (2d or cube) - } + if ( uniform._array === undefined ) { - if (object.bindMatrixInverse && p_uniforms.bindMatrixInverse !== null) { + uniform._array = []; - _gl.uniformMatrix4fv(p_uniforms.bindMatrixInverse, false, object.bindMatrixInverse.elements); + } - } + for ( var i = 0, il = uniform.value.length; i < il; i ++ ) { - if (_supportsBoneTextures && object.skeleton && object.skeleton.useVertexTexture) { + uniform._array[ i ] = getTextureUnit(); - if (p_uniforms.boneTexture !== null) { + } - var textureUnit = getTextureUnit(); + _gl.uniform1iv( location, uniform._array ); - _gl.uniform1i(p_uniforms.boneTexture, textureUnit); - _this.setTexture(object.skeleton.boneTexture, textureUnit); + for ( var i = 0, il = uniform.value.length; i < il; i ++ ) { - } + texture = uniform.value[ i ]; + textureUnit = uniform._array[ i ]; - if (p_uniforms.boneTextureWidth !== null) { + if ( ! texture ) continue; - _gl.uniform1i(p_uniforms.boneTextureWidth, object.skeleton.boneTextureWidth); + if ( texture instanceof THREE.CubeTexture || + ( texture.image instanceof Array && texture.image.length === 6 ) ) { - } + // CompressedTexture can have Array in image :/ - if (p_uniforms.boneTextureHeight !== null) { + setCubeTexture( texture, textureUnit ); - _gl.uniform1i(p_uniforms.boneTextureHeight, object.skeleton.boneTextureHeight); + } else if ( texture instanceof THREE.WebGLRenderTargetCube ) { - } + setCubeTextureDynamic( texture, textureUnit ); - } else if (object.skeleton && object.skeleton.boneMatrices) { + } else { - if (p_uniforms.boneGlobalMatrices !== null) { + _this.setTexture( texture, textureUnit ); - _gl.uniformMatrix4fv(p_uniforms.boneGlobalMatrices, false, object.skeleton.boneMatrices); + } - } + } - } + break; - } + default: - if (refreshMaterial) { + console.warn( 'THREE.WebGLRenderer: Unknown uniform type: ' + type ); - // refresh uniforms common to several materials + } - if (fog && material.fog) { + } - refreshUniformsFog(m_uniforms, fog); + } - } + function setColorLinear( array, offset, color, intensity ) { - if (material instanceof THREE.MeshPhongMaterial || - material instanceof THREE.MeshLambertMaterial || - material.lights) { + array[ offset + 0 ] = color.r * intensity; + array[ offset + 1 ] = color.g * intensity; + array[ offset + 2 ] = color.b * intensity; - if (_lightsNeedUpdate) { + } - refreshLights = true; - setupLights(lights); - _lightsNeedUpdate = false; - } + function setupLights ( lights, camera ) { - if (refreshLights) { - refreshUniformsLights(m_uniforms, _lights); - markUniformsLightsNeedsUpdate(m_uniforms, true); - } else { - markUniformsLightsNeedsUpdate(m_uniforms, false); - } + var l, ll, light, + r = 0, g = 0, b = 0, + color, skyColor, groundColor, + intensity, + distance, - } + zlights = _lights, - if (material instanceof THREE.MeshBasicMaterial || - material instanceof THREE.MeshLambertMaterial || - material instanceof THREE.MeshPhongMaterial) { + viewMatrix = camera.matrixWorldInverse, - refreshUniformsCommon(m_uniforms, material); + dirColors = zlights.directional.colors, + dirPositions = zlights.directional.positions, - } + pointColors = zlights.point.colors, + pointPositions = zlights.point.positions, + pointDistances = zlights.point.distances, + pointDecays = zlights.point.decays, - // refresh single material specific uniforms + spotColors = zlights.spot.colors, + spotPositions = zlights.spot.positions, + spotDistances = zlights.spot.distances, + spotDirections = zlights.spot.directions, + spotAnglesCos = zlights.spot.anglesCos, + spotExponents = zlights.spot.exponents, + spotDecays = zlights.spot.decays, - if (material instanceof THREE.LineBasicMaterial) { + hemiSkyColors = zlights.hemi.skyColors, + hemiGroundColors = zlights.hemi.groundColors, + hemiPositions = zlights.hemi.positions, - refreshUniformsLine(m_uniforms, material); + dirLength = 0, + pointLength = 0, + spotLength = 0, + hemiLength = 0, - } else if (material instanceof THREE.LineDashedMaterial) { + dirCount = 0, + pointCount = 0, + spotCount = 0, + hemiCount = 0, - refreshUniformsLine(m_uniforms, material); - refreshUniformsDash(m_uniforms, material); + dirOffset = 0, + pointOffset = 0, + spotOffset = 0, + hemiOffset = 0; - } else if (material instanceof THREE.PointCloudMaterial) { + for ( l = 0, ll = lights.length; l < ll; l ++ ) { - refreshUniformsParticle(m_uniforms, material); + light = lights[ l ]; - } else if (material instanceof THREE.MeshPhongMaterial) { + if ( light.onlyShadow ) continue; - refreshUniformsPhong(m_uniforms, material); + color = light.color; + intensity = light.intensity; + distance = light.distance; - } else if (material instanceof THREE.MeshLambertMaterial) { + if ( light instanceof THREE.AmbientLight ) { - refreshUniformsLambert(m_uniforms, material); + if ( ! light.visible ) continue; - } else if (material instanceof THREE.MeshDepthMaterial) { + r += color.r; + g += color.g; + b += color.b; - m_uniforms.mNear.value = camera.near; - m_uniforms.mFar.value = camera.far; - m_uniforms.opacity.value = material.opacity; + } else if ( light instanceof THREE.DirectionalLight ) { - } else if (material instanceof THREE.MeshNormalMaterial) { + dirCount += 1; - m_uniforms.opacity.value = material.opacity; + if ( ! light.visible ) continue; - } + _direction.setFromMatrixPosition( light.matrixWorld ); + _vector3.setFromMatrixPosition( light.target.matrixWorld ); + _direction.sub( _vector3 ); + _direction.transformDirection( viewMatrix ); - if (object.receiveShadow && !material._shadowPass) { + dirOffset = dirLength * 3; - refreshUniformsShadow(m_uniforms, lights); + dirPositions[ dirOffset + 0 ] = _direction.x; + dirPositions[ dirOffset + 1 ] = _direction.y; + dirPositions[ dirOffset + 2 ] = _direction.z; - } + setColorLinear( dirColors, dirOffset, color, intensity ); - // load common uniforms + dirLength += 1; - loadUniformsGeneric(material.uniformsList); + } else if ( light instanceof THREE.PointLight ) { - } + pointCount += 1; - loadUniformsMatrices(p_uniforms, object); + if ( ! light.visible ) continue; - if (p_uniforms.modelMatrix !== null) { + pointOffset = pointLength * 3; - _gl.uniformMatrix4fv(p_uniforms.modelMatrix, false, object.matrixWorld.elements); + setColorLinear( pointColors, pointOffset, color, intensity ); - } + _vector3.setFromMatrixPosition( light.matrixWorld ); + _vector3.applyMatrix4( viewMatrix ); - return program; + pointPositions[ pointOffset + 0 ] = _vector3.x; + pointPositions[ pointOffset + 1 ] = _vector3.y; + pointPositions[ pointOffset + 2 ] = _vector3.z; - } + // distance is 0 if decay is 0, because there is no attenuation at all. + pointDistances[ pointLength ] = distance; + pointDecays[ pointLength ] = ( light.distance === 0 ) ? 0.0 : light.decay; - // Uniforms (refresh uniforms objects) + pointLength += 1; - function refreshUniformsCommon(uniforms, material) { + } else if ( light instanceof THREE.SpotLight ) { - uniforms.opacity.value = material.opacity; + spotCount += 1; - uniforms.diffuse.value = material.color; + if ( ! light.visible ) continue; - uniforms.map.value = material.map; - uniforms.specularMap.value = material.specularMap; - uniforms.alphaMap.value = material.alphaMap; + spotOffset = spotLength * 3; - if (material.bumpMap) { + setColorLinear( spotColors, spotOffset, color, intensity ); - uniforms.bumpMap.value = material.bumpMap; - uniforms.bumpScale.value = material.bumpScale; + _direction.setFromMatrixPosition( light.matrixWorld ); + _vector3.copy( _direction ).applyMatrix4( viewMatrix ); - } + spotPositions[ spotOffset + 0 ] = _vector3.x; + spotPositions[ spotOffset + 1 ] = _vector3.y; + spotPositions[ spotOffset + 2 ] = _vector3.z; - if (material.normalMap) { + spotDistances[ spotLength ] = distance; - uniforms.normalMap.value = material.normalMap; - uniforms.normalScale.value.copy(material.normalScale); + _vector3.setFromMatrixPosition( light.target.matrixWorld ); + _direction.sub( _vector3 ); + _direction.transformDirection( viewMatrix ); - } + spotDirections[ spotOffset + 0 ] = _direction.x; + spotDirections[ spotOffset + 1 ] = _direction.y; + spotDirections[ spotOffset + 2 ] = _direction.z; - // uv repeat and offset setting priorities - // 1. color map - // 2. specular map - // 3. normal map - // 4. bump map - // 5. alpha map + spotAnglesCos[ spotLength ] = Math.cos( light.angle ); + spotExponents[ spotLength ] = light.exponent; + spotDecays[ spotLength ] = ( light.distance === 0 ) ? 0.0 : light.decay; - var uvScaleMap; + spotLength += 1; - if (material.map) { + } else if ( light instanceof THREE.HemisphereLight ) { - uvScaleMap = material.map; + hemiCount += 1; - } else if (material.specularMap) { + if ( ! light.visible ) continue; - uvScaleMap = material.specularMap; + _direction.setFromMatrixPosition( light.matrixWorld ); + _direction.transformDirection( viewMatrix ); - } else if (material.normalMap) { + hemiOffset = hemiLength * 3; - uvScaleMap = material.normalMap; + hemiPositions[ hemiOffset + 0 ] = _direction.x; + hemiPositions[ hemiOffset + 1 ] = _direction.y; + hemiPositions[ hemiOffset + 2 ] = _direction.z; - } else if (material.bumpMap) { + skyColor = light.color; + groundColor = light.groundColor; - uvScaleMap = material.bumpMap; + setColorLinear( hemiSkyColors, hemiOffset, skyColor, intensity ); + setColorLinear( hemiGroundColors, hemiOffset, groundColor, intensity ); - } else if (material.alphaMap) { + hemiLength += 1; - uvScaleMap = material.alphaMap; + } - } + } - if (uvScaleMap !== undefined) { + // null eventual remains from removed lights + // (this is to avoid if in shader) - if (uvScaleMap instanceof THREE.WebGLRenderTarget) uvScaleMap = uvScaleMap.texture; + 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; - var offset = uvScaleMap.offset; - var repeat = uvScaleMap.repeat; + zlights.directional.length = dirLength; + zlights.point.length = pointLength; + zlights.spot.length = spotLength; + zlights.hemi.length = hemiLength; - uniforms.offsetRepeat.value.set(offset.x, offset.y, repeat.x, repeat.y); + zlights.ambient[ 0 ] = r; + zlights.ambient[ 1 ] = g; + zlights.ambient[ 2 ] = b; - } + } - uniforms.envMap.value = material.envMap; - uniforms.flipEnvMap.value = ( material.envMap instanceof THREE.WebGLRenderTargetCube ) ? 1 : -1; + // GL state setting - uniforms.reflectivity.value = material.reflectivity; - uniforms.refractionRatio.value = material.refractionRatio; + this.setFaceCulling = function ( cullFace, frontFaceDirection ) { - } + if ( cullFace === THREE.CullFaceNone ) { - function refreshUniformsLine(uniforms, material) { + state.disable( _gl.CULL_FACE ); - uniforms.diffuse.value = material.color; - uniforms.opacity.value = material.opacity; + } else { - } + if ( frontFaceDirection === THREE.FrontFaceDirectionCW ) { - function refreshUniformsDash(uniforms, material) { + _gl.frontFace( _gl.CW ); - uniforms.dashSize.value = material.dashSize; - uniforms.totalSize.value = material.dashSize + material.gapSize; - uniforms.scale.value = material.scale; + } else { - } + _gl.frontFace( _gl.CCW ); - 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. + if ( cullFace === THREE.CullFaceBack ) { - uniforms.map.value = material.map; + _gl.cullFace( _gl.BACK ); - if (material.map !== null) { + } else if ( cullFace === THREE.CullFaceFront ) { - var offset = material.map.offset; - var repeat = material.map.repeat; + _gl.cullFace( _gl.FRONT ); - uniforms.offsetRepeat.value.set(offset.x, offset.y, repeat.x, repeat.y); + } else { - } + _gl.cullFace( _gl.FRONT_AND_BACK ); - } + } - function refreshUniformsFog(uniforms, fog) { + state.enable( _gl.CULL_FACE ); - uniforms.fogColor.value = fog.color; + } - if (fog instanceof THREE.Fog) { + }; - uniforms.fogNear.value = fog.near; - uniforms.fogFar.value = fog.far; + // Textures - } else if (fog instanceof THREE.FogExp2) { + function setTextureParameters ( textureType, texture, isImagePowerOfTwo ) { - uniforms.fogDensity.value = fog.density; + var extension; - } + if ( isImagePowerOfTwo ) { - } + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, paramThreeToGL( texture.wrapS ) ); + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, paramThreeToGL( texture.wrapT ) ); - function refreshUniformsPhong(uniforms, material) { + _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, paramThreeToGL( texture.magFilter ) ); + _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, paramThreeToGL( texture.minFilter ) ); - uniforms.shininess.value = material.shininess; + } else { - uniforms.emissive.value = material.emissive; - uniforms.specular.value = material.specular; + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE ); + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE ); - uniforms.lightMap.value = material.lightMap; - uniforms.lightMapIntensity.value = material.lightMapIntensity; + if ( texture.wrapS !== THREE.ClampToEdgeWrapping || texture.wrapT !== THREE.ClampToEdgeWrapping ) { - uniforms.aoMap.value = material.aoMap; - uniforms.aoMapIntensity.value = material.aoMapIntensity; + console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping. ( ' + texture.sourceFile + ' )' ); - } + } - function refreshUniformsLambert(uniforms, material) { + _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterFallback( texture.magFilter ) ); + _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterFallback( texture.minFilter ) ); - uniforms.emissive.value = material.emissive; + if ( texture.minFilter !== THREE.NearestFilter && texture.minFilter !== THREE.LinearFilter ) { - } + console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter. ( ' + texture.sourceFile + ' )' ); - function refreshUniformsLights(uniforms, lights) { + } - uniforms.ambientLightColor.value = lights.ambient; + } - uniforms.directionalLightColor.value = lights.directional.colors; - uniforms.directionalLightDirection.value = lights.directional.positions; + extension = extensions.get( 'EXT_texture_filter_anisotropic' ); - uniforms.pointLightColor.value = lights.point.colors; - uniforms.pointLightPosition.value = lights.point.positions; - uniforms.pointLightDistance.value = lights.point.distances; - uniforms.pointLightDecay.value = lights.point.decays; + if ( extension ) { - 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.spotLightDecay.value = lights.spot.decays; + if ( texture.type === THREE.FloatType && extensions.get( 'OES_texture_float_linear' ) === null ) return; + if ( texture.type === THREE.HalfFloatType && extensions.get( 'OES_texture_half_float_linear' ) === null ) return; - uniforms.hemisphereLightSkyColor.value = lights.hemi.skyColors; - uniforms.hemisphereLightGroundColor.value = lights.hemi.groundColors; - uniforms.hemisphereLightDirection.value = lights.hemi.positions; + if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) { - } + _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, _this.getMaxAnisotropy() ) ); + properties.get( texture ).__currentAnisotropy = texture.anisotropy; - // If uniforms are marked as clean, they don't need to be loaded to the GPU. + } - function markUniformsLightsNeedsUpdate(uniforms, value) { + } - uniforms.ambientLightColor.needsUpdate = value; + } - uniforms.directionalLightColor.needsUpdate = value; - uniforms.directionalLightDirection.needsUpdate = value; + function uploadTexture( textureProperties, texture, slot ) { - uniforms.pointLightColor.needsUpdate = value; - uniforms.pointLightPosition.needsUpdate = value; - uniforms.pointLightDistance.needsUpdate = value; - uniforms.pointLightDecay.needsUpdate = value; + if ( textureProperties.__webglInit === undefined ) { - uniforms.spotLightColor.needsUpdate = value; - uniforms.spotLightPosition.needsUpdate = value; - uniforms.spotLightDistance.needsUpdate = value; - uniforms.spotLightDirection.needsUpdate = value; - uniforms.spotLightAngleCos.needsUpdate = value; - uniforms.spotLightExponent.needsUpdate = value; - uniforms.spotLightDecay.needsUpdate = value; + textureProperties.__webglInit = true; - uniforms.hemisphereLightSkyColor.needsUpdate = value; - uniforms.hemisphereLightGroundColor.needsUpdate = value; - uniforms.hemisphereLightDirection.needsUpdate = value; + texture.__webglInit = true; - } + texture.addEventListener( 'dispose', onTextureDispose ); - function refreshUniformsShadow(uniforms, lights) { + textureProperties.__webglTexture = _gl.createTexture(); - if (uniforms.shadowMatrix) { + _infoMemory.textures ++; - var j = 0; + } - for (var i = 0, il = lights.length; i < il; i++) { + state.activeTexture( _gl.TEXTURE0 + slot ); + state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture ); - var light = lights[i]; + _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 ); - if (!light.castShadow) continue; + texture.image = clampToMaxSize( texture.image, capabilities.maxTextureSize ); - if (light instanceof THREE.SpotLight || ( light instanceof THREE.DirectionalLight && !light.shadowCascade )) { + var image = texture.image, + isImagePowerOfTwo = THREE.Math.isPowerOfTwo( image.width ) && THREE.Math.isPowerOfTwo( image.height ), + glFormat = paramThreeToGL( texture.format ), + glType = paramThreeToGL( texture.type ); - uniforms.shadowMap.value[j] = light.shadowMap; - uniforms.shadowMapSize.value[j] = light.shadowMapSize; + setTextureParameters( _gl.TEXTURE_2D, texture, isImagePowerOfTwo ); - uniforms.shadowMatrix.value[j] = light.shadowMatrix; + var mipmap, mipmaps = texture.mipmaps; - uniforms.shadowDarkness.value[j] = light.shadowDarkness; - uniforms.shadowBias.value[j] = light.shadowBias; + if ( texture instanceof THREE.DataTexture ) { - j++; + // 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 ]; + state.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - } + } - // Uniforms (load to GPU) + texture.generateMipmaps = false; - function loadUniformsMatrices(uniforms, object) { + } else { - _gl.uniformMatrix4fv(uniforms.modelViewMatrix, false, object._modelViewMatrix.elements); + state.texImage2D( _gl.TEXTURE_2D, 0, glFormat, image.width, image.height, 0, glFormat, glType, image.data ); - if (uniforms.normalMatrix) { + } - _gl.uniformMatrix3fv(uniforms.normalMatrix, false, object._normalMatrix.elements); + } else if ( texture instanceof THREE.CompressedTexture ) { - } + for ( var i = 0, il = mipmaps.length; i < il; i ++ ) { - } + mipmap = mipmaps[ i ]; - function getTextureUnit() { + if ( texture.format !== THREE.RGBAFormat && texture.format !== THREE.RGBFormat ) { - var textureUnit = _usedTextureUnits; + if ( state.getCompressedTextureFormats().indexOf( glFormat ) > - 1 ) { - if (textureUnit >= _maxTextures) { + state.compressedTexImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, mipmap.data ); - THREE.warn('WebGLRenderer: trying to use ' + textureUnit + ' texture units while this GPU supports only ' + _maxTextures); + } else { - } + console.warn( "THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()" ); - _usedTextureUnits += 1; + } - return textureUnit; + } else { - } + state.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - function loadUniformsGeneric(uniforms) { + } - var texture, textureUnit, offset; + } - for (var j = 0, jl = uniforms.length; j < jl; j++) { + } else { - var uniform = uniforms[j][0]; + // regular Texture (image, video, canvas) - // needsUpdate property is not added to all uniforms. - if (uniform.needsUpdate === false) continue; + // 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 - var type = uniform.type; - var value = uniform.value; - var location = uniforms[j][1]; + if ( mipmaps.length > 0 && isImagePowerOfTwo ) { - switch (type) { + for ( var i = 0, il = mipmaps.length; i < il; i ++ ) { - case '1i': - _gl.uniform1i(location, value); - break; + mipmap = mipmaps[ i ]; + state.texImage2D( _gl.TEXTURE_2D, i, glFormat, glFormat, glType, mipmap ); - case '1f': - _gl.uniform1f(location, value); - break; + } - case '2f': - _gl.uniform2f(location, value[0], value[1]); - break; + texture.generateMipmaps = false; - case '3f': - _gl.uniform3f(location, value[0], value[1], value[2]); - break; + } else { - case '4f': - _gl.uniform4f(location, value[0], value[1], value[2], value[3]); - break; + state.texImage2D( _gl.TEXTURE_2D, 0, glFormat, glFormat, glType, texture.image ); - case '1iv': - _gl.uniform1iv(location, value); - break; + } - case '3iv': - _gl.uniform3iv(location, value); - break; + } - case '1fv': - _gl.uniform1fv(location, value); - break; + if ( texture.generateMipmaps && isImagePowerOfTwo ) _gl.generateMipmap( _gl.TEXTURE_2D ); - case '2fv': - _gl.uniform2fv(location, value); - break; + textureProperties.__version = texture.version; - case '3fv': - _gl.uniform3fv(location, value); - break; + if ( texture.onUpdate ) texture.onUpdate( texture ); - case '4fv': - _gl.uniform4fv(location, value); - break; + } - case 'Matrix3fv': - _gl.uniformMatrix3fv(location, false, value); - break; + this.setTexture = function ( texture, slot ) { - case 'Matrix4fv': - _gl.uniformMatrix4fv(location, false, value); - break; + var textureProperties = properties.get( texture ); - // + if ( texture.version > 0 && textureProperties.__version !== texture.version ) { - case 'i': + var image = texture.image; - // single integer - _gl.uniform1i(location, value); + if ( image === undefined ) { - break; + console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is undefined', texture ); + return; - case 'f': + } - // single float - _gl.uniform1f(location, value); + if ( image.complete === false ) { - break; + console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete', texture ); + return; - case 'v2': + } - // single THREE.Vector2 - _gl.uniform2f(location, value.x, value.y); + uploadTexture( textureProperties, texture, slot ); + return; - break; + } - case 'v3': + state.activeTexture( _gl.TEXTURE0 + slot ); + state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture ); - // single THREE.Vector3 - _gl.uniform3f(location, value.x, value.y, value.z); + }; - break; + function clampToMaxSize ( image, maxSize ) { - case 'v4': + if ( image.width > maxSize || image.height > maxSize ) { - // single THREE.Vector4 - _gl.uniform4f(location, value.x, value.y, value.z, value.w); + // Warning: Scaling through the canvas will only work with images that use + // premultiplied alpha. - break; + var scale = maxSize / Math.max( image.width, image.height ); - case 'c': + var canvas = document.createElement( 'canvas' ); + canvas.width = Math.floor( image.width * scale ); + canvas.height = Math.floor( image.height * scale ); - // single THREE.Color - _gl.uniform3f(location, value.r, value.g, value.b); + var context = canvas.getContext( '2d' ); + context.drawImage( image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height ); - break; + console.warn( 'THREE.WebGLRenderer: image is too big (' + image.width + 'x' + image.height + '). Resized to ' + canvas.width + 'x' + canvas.height, image ); - case 'iv1': + return canvas; - // flat array of integers (JS or typed array) - _gl.uniform1iv(location, value); + } - break; + return image; - case 'iv': + } - // flat array of integers with 3 x N size (JS or typed array) - _gl.uniform3iv(location, value); + function setCubeTexture ( texture, slot ) { - break; + var textureProperties = properties.get( texture ); - case 'fv1': + if ( texture.image.length === 6 ) { - // flat array of floats (JS or typed array) - _gl.uniform1fv(location, value); + if ( texture.version > 0 && textureProperties.__version !== texture.version ) { - break; + if ( ! textureProperties.__image__webglTextureCube ) { - case 'fv': + texture.addEventListener( 'dispose', onTextureDispose ); - // flat array of floats with 3 x N size (JS or typed array) - _gl.uniform3fv(location, value); + textureProperties.__image__webglTextureCube = _gl.createTexture(); - break; + _infoMemory.textures ++; - case 'v2v': + } - // array of THREE.Vector2 + state.activeTexture( _gl.TEXTURE0 + slot ); + state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__image__webglTextureCube ); - if (uniform._array === undefined) { + _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); - uniform._array = new Float32Array(2 * value.length); + var isCompressed = texture instanceof THREE.CompressedTexture; + var isDataTexture = texture.image[ 0 ] instanceof THREE.DataTexture; - } + var cubeImage = []; - for (var i = 0, il = value.length; i < il; i++) { + for ( var i = 0; i < 6; i ++ ) { - offset = i * 2; + if ( _this.autoScaleCubemaps && ! isCompressed && ! isDataTexture ) { - uniform._array[offset + 0] = value[i].x; - uniform._array[offset + 1] = value[i].y; + cubeImage[ i ] = clampToMaxSize( texture.image[ i ], capabilities.maxCubemapSize ); - } + } else { - _gl.uniform2fv(location, uniform._array); + cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ]; - break; + } - case 'v3v': + } - // array of THREE.Vector3 + var image = cubeImage[ 0 ], + isImagePowerOfTwo = THREE.Math.isPowerOfTwo( image.width ) && THREE.Math.isPowerOfTwo( image.height ), + glFormat = paramThreeToGL( texture.format ), + glType = paramThreeToGL( texture.type ); - if (uniform._array === undefined) { + setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, isImagePowerOfTwo ); - uniform._array = new Float32Array(3 * value.length); + for ( var i = 0; i < 6; i ++ ) { - } + if ( ! isCompressed ) { - for (var i = 0, il = value.length; i < il; i++) { + if ( isDataTexture ) { - offset = i * 3; + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data ); - uniform._array[offset + 0] = value[i].x; - uniform._array[offset + 1] = value[i].y; - uniform._array[offset + 2] = value[i].z; + } else { - } + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, glFormat, glType, cubeImage[ i ] ); - _gl.uniform3fv(location, uniform._array); + } - break; + } else { - case 'v4v': + var mipmap, mipmaps = cubeImage[ i ].mipmaps; - // array of THREE.Vector4 + for ( var j = 0, jl = mipmaps.length; j < jl; j ++ ) { - if (uniform._array === undefined) { + mipmap = mipmaps[ j ]; - uniform._array = new Float32Array(4 * value.length); + if ( texture.format !== THREE.RGBAFormat && texture.format !== THREE.RGBFormat ) { - } + if ( state.getCompressedTextureFormats().indexOf( glFormat ) > - 1 ) { - for (var i = 0, il = value.length; i < il; i++) { + state.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, mipmap.data ); - offset = i * 4; + } else { - uniform._array[offset + 0] = value[i].x; - uniform._array[offset + 1] = value[i].y; - uniform._array[offset + 2] = value[i].z; - uniform._array[offset + 3] = value[i].w; + console.warn( "THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setCubeTexture()" ); - } + } - _gl.uniform4fv(location, uniform._array); + } else { - break; + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - case 'm3': + } - // single THREE.Matrix3 - _gl.uniformMatrix3fv(location, false, value.elements); + } - break; + } - case 'm3v': + } - // array of THREE.Matrix3 + if ( texture.generateMipmaps && isImagePowerOfTwo ) { - if (uniform._array === undefined) { + _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP ); - uniform._array = new Float32Array(9 * value.length); + } - } + textureProperties.__version = texture.version; - for (var i = 0, il = value.length; i < il; i++) { + if ( texture.onUpdate ) texture.onUpdate( texture ); - value[i].flattenToArrayOffset(uniform._array, i * 9); + } else { - } + state.activeTexture( _gl.TEXTURE0 + slot ); + state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__image__webglTextureCube ); - _gl.uniformMatrix3fv(location, false, uniform._array); + } - break; + } - case 'm4': + } - // single THREE.Matrix4 - _gl.uniformMatrix4fv(location, false, value.elements); + function setCubeTextureDynamic ( texture, slot ) { - break; + state.activeTexture( _gl.TEXTURE0 + slot ); + state.bindTexture( _gl.TEXTURE_CUBE_MAP, properties.get( texture ).__webglTexture ); - case 'm4v': + } - // array of THREE.Matrix4 + // Render targets - if (uniform._array === undefined) { + function setupFrameBufferTexture ( framebuffer, renderTarget, attachment, textureTarget ) { - uniform._array = new Float32Array(16 * value.length); + _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( renderTarget.texture ).__webglTexture, 0 ); - } + } - for (var i = 0, il = value.length; i < il; i++) { + function setupRenderBufferStorage ( renderbuffer, renderTarget ) { - value[i].flattenToArrayOffset(uniform._array, i * 16); + _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); - } + if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) { - _gl.uniformMatrix4fv(location, false, uniform._array); + _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_COMPONENT16, renderTarget.width, renderTarget.height ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); - break; + /* For some reason this is not working. Defaulting to RGBA4. + } else if ( ! renderTarget.depthBuffer && renderTarget.stencilBuffer ) { - case 't': + _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.STENCIL_INDEX8, renderTarget.width, renderTarget.height ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); + */ - // single THREE.Texture (2d or cube) + } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) { - texture = value; - textureUnit = getTextureUnit(); + _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); - _gl.uniform1i(location, textureUnit); + } else { - if (!texture) continue; + _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.RGBA4, renderTarget.width, renderTarget.height ); - if (texture instanceof THREE.CubeTexture || - ( texture.image instanceof Array && texture.image.length === 6 )) { // CompressedTexture can have Array in image :/ + } - setCubeTexture(texture, textureUnit); + } - } else if (texture instanceof THREE.WebGLRenderTargetCube) { + function setupDepthRenderbuffer(renderTarget) { + + var renderTargetProperties = properties.get( renderTarget ); + var isCube = ( renderTarget instanceof THREE.WebGLRenderTargetCube ); + if ( isCube ) { - setCubeTextureDynamic(texture.texture, textureUnit); + renderTargetProperties.__webglRenderbuffer = []; + for ( var i = 0; i < 6; i ++ ) { - } else if (texture instanceof THREE.WebGLRenderTarget) { + renderTargetProperties.__webglRenderbuffer[ i ] = _gl.createRenderbuffer(); + setupRenderBufferStorage( renderTargetProperties.__webglRenderbuffer[ i ], renderTarget ); - _this.setTexture(texture.texture, textureUnit); + } - } else { + } + else { - _this.setTexture(texture, textureUnit); + if ( renderTarget.shareDepthFrom ) { + var sharedProperties = properties.get( renderTarget.shareDepthFrom ); + renderTargetProperties.__webglRenderbuffer = sharedProperties.__webglRenderbuffer; + if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) { + + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderTargetProperties.__webglRenderbuffer ); + + } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) { + + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderTargetProperties.__webglRenderbuffer ); + + } + + } else { + + renderTargetProperties.__webglRenderbuffer = _gl.createRenderbuffer(); + setupRenderBufferStorage( renderTargetProperties.__webglRenderbuffer, renderTarget ); + + } + + } + + _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); - } + }; - break; + function setupDepthTexture(renderTarget) { - case 'tv': + var depthTexture = renderTarget.depthTexture; + var depthTextureProperties = properties.get( renderTarget.depthTexture ); + var isPowerOfTwo = THREE.Math.isPowerOfTwo( depthTexture.width ) && THREE.Math.isPowerOfTwo( depthTexture.height ); - // array of THREE.Texture (2d) + depthTextureProperties.__webglTexture = _gl.createTexture(); + _gl.bindTexture(_gl.TEXTURE_2D, depthTextureProperties.__webglTexture); + setTextureParameters(_gl.TEXTURE_2D, depthTexture, isPowerOfTwo); - if (uniform._array === undefined) { + if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) { - uniform._array = []; + _gl.texImage2D(_gl.TEXTURE_2D, 0, _gl.DEPTH_COMPONENT, depthTexture.width, depthTexture.height, 0, _gl.DEPTH_COMPONENT, _gl.UNSIGNED_INT, null); + _gl.framebufferTexture2D(_gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, depthTextureProperties.__webglTexture, 0); - } + } else if ( renderTarget.stencilBuffer ) { + + _gl.texImage2D(_gl.TEXTURE_2D, 0, _gl.DEPTH_STENCIL, depthTexture.width, depthTexture.height, 0, _gl.DEPTH_STENCIL, extensions.get( 'WEBGL_depth_texture' ).UNSIGNED_INT_24_8_WEBGL, null); + _gl.framebufferTexture2D(_gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, depthTextureProperties.__webglTexture, 0); - for (var i = 0, il = uniform.value.length; i < il; i++) { + } - uniform._array[i] = getTextureUnit(); + _gl.bindTexture(_gl.TEXTURE_2D, null); - } + }; + + this.setRenderTarget = function ( renderTarget ) { - _gl.uniform1iv(location, uniform._array); + var isCube = ( renderTarget instanceof THREE.WebGLRenderTargetCube ); - for (var i = 0, il = uniform.value.length; i < il; i++) { + if ( renderTarget && properties.get( renderTarget ).__webglFramebuffer === undefined ) { - texture = uniform.value[i]; - textureUnit = uniform._array[i]; + var renderTargetProperties = properties.get( renderTarget ); + var textureProperties = properties.get( renderTarget.texture ); - if (!texture) continue; + renderTarget.addEventListener( 'dispose', onRenderTargetDispose ); - _this.setTexture(texture, textureUnit); + textureProperties.__webglTexture = _gl.createTexture(); - } + _infoMemory.textures ++; - break; + // + // Setup color buffer + // - default: + var isTargetPowerOfTwo = THREE.Math.isPowerOfTwo( renderTarget.width ) && THREE.Math.isPowerOfTwo( renderTarget.height ), + glFormat = paramThreeToGL( renderTarget.texture.format ), + glType = paramThreeToGL( renderTarget.texture.type ); - THREE.warn('THREE.WebGLRenderer: Unknown uniform type: ' + type); + if ( isCube ) { - } + renderTargetProperties.__webglFramebuffer = []; - } + state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture ); - } + setTextureParameters( _gl.TEXTURE_CUBE_MAP, renderTarget.texture, isTargetPowerOfTwo ); - function setupMatrices(object, camera) { + for ( var i = 0; i < 6; i ++ ) { - object._modelViewMatrix.multiplyMatrices(camera.matrixWorldInverse, object.matrixWorld); - object._normalMatrix.getNormalMatrix(object._modelViewMatrix); + renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer(); + renderTargetProperties.__webglRenderbuffer[ i ] = _gl.createRenderbuffer(); + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null ); - } + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i ); - function setColorLinear(array, offset, color, intensity) { + } - array[offset + 0] = color.r * intensity; - array[offset + 1] = color.g * intensity; - array[offset + 2] = color.b * intensity; + if ( renderTarget.generateMipmaps && isTargetPowerOfTwo ) _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP ); - } + } else { - function setupLights(lights) { + renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer(); - var l, ll, light, - r = 0, g = 0, b = 0, - color, skyColor, groundColor, - intensity, - distance, + state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture ); + setTextureParameters( _gl.TEXTURE_2D, renderTarget.texture, isTargetPowerOfTwo ); - zlights = _lights, + state.texImage2D( _gl.TEXTURE_2D, 0, glFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null ); - dirColors = zlights.directional.colors, - dirPositions = zlights.directional.positions, + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D ); - pointColors = zlights.point.colors, - pointPositions = zlights.point.positions, - pointDistances = zlights.point.distances, - pointDecays = zlights.point.decays, + if ( renderTarget.generateMipmaps && isTargetPowerOfTwo ) _gl.generateMipmap( _gl.TEXTURE_2D ); - spotColors = zlights.spot.colors, - spotPositions = zlights.spot.positions, - spotDistances = zlights.spot.distances, - spotDirections = zlights.spot.directions, - spotAnglesCos = zlights.spot.anglesCos, - spotExponents = zlights.spot.exponents, - spotDecays = zlights.spot.decays, + } - hemiSkyColors = zlights.hemi.skyColors, - hemiGroundColors = zlights.hemi.groundColors, - hemiPositions = zlights.hemi.positions, + // Release textures - dirLength = 0, - pointLength = 0, - spotLength = 0, - hemiLength = 0, + if ( isCube ) { - dirCount = 0, - pointCount = 0, - spotCount = 0, - hemiCount = 0, + state.bindTexture( _gl.TEXTURE_CUBE_MAP, null ); - dirOffset = 0, - pointOffset = 0, - spotOffset = 0, - hemiOffset = 0; + } else { - for (l = 0, ll = lights.length; l < ll; l++) { + state.bindTexture( _gl.TEXTURE_2D, null ); - light = lights[l]; + } - if (light.onlyShadow) continue; + // + // Setup depth and stencil buffers + // + if ( renderTarget.depthBuffer === undefined ) renderTarget.depthBuffer = true; + if ( renderTarget.stencilBuffer === undefined ) renderTarget.stencilBuffer = true; - color = light.color; - intensity = light.intensity; - distance = light.distance; + if (renderTarget.depthBuffer || renderTarget.stencilBuffer) { - if (light instanceof THREE.AmbientLight) { + if ( renderTarget.depthTexture && extensions.get( 'WEBGL_depth_texture' ) ) setupDepthTexture(renderTarget); + else setupDepthRenderbuffer(renderTarget); - if (!light.visible) continue; + } - r += color.r; - g += color.g; - b += color.b; + _gl.bindFramebuffer( _gl.FRAMEBUFFER, null ); - } else if (light instanceof THREE.DirectionalLight) { + } - dirCount += 1; + var framebuffer, width, height, vx, vy; - if (!light.visible) continue; + if ( renderTarget ) { - _direction.setFromMatrixPosition(light.matrixWorld); - _vector3.setFromMatrixPosition(light.target.matrixWorld); - _direction.sub(_vector3); - _direction.normalize(); + var renderTargetProperties = properties.get( renderTarget ); - dirOffset = dirLength * 3; + if ( isCube ) { - dirPositions[dirOffset + 0] = _direction.x; - dirPositions[dirOffset + 1] = _direction.y; - dirPositions[dirOffset + 2] = _direction.z; + framebuffer = renderTargetProperties.__webglFramebuffer[ renderTarget.activeCubeFace ]; - setColorLinear(dirColors, dirOffset, color, intensity); + } else { - dirLength += 1; + framebuffer = renderTargetProperties.__webglFramebuffer; - } else if (light instanceof THREE.PointLight) { + } - pointCount += 1; + width = renderTarget.width; + height = renderTarget.height; - if (!light.visible) continue; + vx = 0; + vy = 0; - pointOffset = pointLength * 3; + } else { - setColorLinear(pointColors, pointOffset, color, intensity); + framebuffer = null; - _vector3.setFromMatrixPosition(light.matrixWorld); + width = _viewportWidth; + height = _viewportHeight; - pointPositions[pointOffset + 0] = _vector3.x; - pointPositions[pointOffset + 1] = _vector3.y; - pointPositions[pointOffset + 2] = _vector3.z; + vx = _viewportX; + vy = _viewportY; - // distance is 0 if decay is 0, because there is no attenuation at all. - pointDistances[pointLength] = distance; - pointDecays[pointLength] = ( light.distance === 0 ) ? 0.0 : light.decay; + } - pointLength += 1; + if ( framebuffer !== _currentFramebuffer ) { - } else if (light instanceof THREE.SpotLight) { + _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + _gl.viewport( vx, vy, width, height ); - spotCount += 1; + _currentFramebuffer = framebuffer; - if (!light.visible) continue; + } - spotOffset = spotLength * 3; + if ( isCube ) { - setColorLinear(spotColors, spotOffset, color, intensity); + var renderTargetProperties = properties.get( renderTarget ); + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + renderTarget.activeCubeFace, renderTargetProperties.__webglTexture, 0 ); - _direction.setFromMatrixPosition(light.matrixWorld); + } - spotPositions[spotOffset + 0] = _direction.x; - spotPositions[spotOffset + 1] = _direction.y; - spotPositions[spotOffset + 2] = _direction.z; + _currentWidth = width; + _currentHeight = height; - spotDistances[spotLength] = distance; + }; - _vector3.setFromMatrixPosition(light.target.matrixWorld); - _direction.sub(_vector3); - _direction.normalize(); + this.readRenderTargetPixels = function( renderTarget, x, y, width, height, buffer ) { - spotDirections[spotOffset + 0] = _direction.x; - spotDirections[spotOffset + 1] = _direction.y; - spotDirections[spotOffset + 2] = _direction.z; + if ( ! ( renderTarget instanceof THREE.WebGLRenderTarget ) ) { - spotAnglesCos[spotLength] = Math.cos(light.angle); - spotExponents[spotLength] = light.exponent; - spotDecays[spotLength] = ( light.distance === 0 ) ? 0.0 : light.decay; + console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' ); + return; - spotLength += 1; + } - } else if (light instanceof THREE.HemisphereLight) { + if ( properties.get( renderTarget ).__webglFramebuffer ) { - hemiCount += 1; + var restore = false; - if (!light.visible) continue; + if ( properties.get( renderTarget ).__webglFramebuffer !== _currentFramebuffer ) { - _direction.setFromMatrixPosition(light.matrixWorld); - _direction.normalize(); + _gl.bindFramebuffer( _gl.FRAMEBUFFER, properties.get( renderTarget ).__webglFramebuffer ); - hemiOffset = hemiLength * 3; + restore = true; - hemiPositions[hemiOffset + 0] = _direction.x; - hemiPositions[hemiOffset + 1] = _direction.y; - hemiPositions[hemiOffset + 2] = _direction.z; + } - skyColor = light.color; - groundColor = light.groundColor; + if ( renderTarget.texture.format !== THREE.RGBAFormat && paramThreeToGL( renderTarget.texture.format ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_FORMAT ) ) { - setColorLinear(hemiSkyColors, hemiOffset, skyColor, intensity); - setColorLinear(hemiGroundColors, hemiOffset, groundColor, intensity); + console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' ); + return; - hemiLength += 1; + } - } + if ( renderTarget.texture.type !== THREE.UnsignedByteType && paramThreeToGL( renderTarget.texture.type ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_TYPE ) ) { - } + console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' ); + return; - // 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; + if ( _gl.checkFramebufferStatus( _gl.FRAMEBUFFER ) === _gl.FRAMEBUFFER_COMPLETE ) { - zlights.directional.length = dirLength; - zlights.point.length = pointLength; - zlights.spot.length = spotLength; - zlights.hemi.length = hemiLength; + _gl.readPixels( x, y, width, height, paramThreeToGL( renderTarget.texture.format ), paramThreeToGL( renderTarget.texture.type ), buffer ); - zlights.ambient[0] = r; - zlights.ambient[1] = g; - zlights.ambient[2] = b; + } else { - } + console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete.' ); - // GL state setting + } - this.setFaceCulling = function (cullFace, frontFaceDirection) { + if ( restore ) { - if (cullFace === THREE.CullFaceNone) { + _gl.bindFramebuffer( _gl.FRAMEBUFFER, _currentFramebuffer ); - _gl.disable(_gl.CULL_FACE); + } - } else { + } - if (frontFaceDirection === THREE.FrontFaceDirectionCW) { + }; - _gl.frontFace(_gl.CW); + function updateRenderTargetMipmap( renderTarget ) { - } else { + var target = renderTarget instanceof THREE.WebGLRenderTargetCube ? _gl.TEXTURE_CUBE_MAP : _gl.TEXTURE_2D; + var texture = properties.get( renderTarget.texture ).__webglTexture; - _gl.frontFace(_gl.CCW); + state.bindTexture( target, texture ); + _gl.generateMipmap( target ); + state.bindTexture( target, null ); - } + } - if (cullFace === THREE.CullFaceBack) { + // Fallback filters for non-power-of-2 textures - _gl.cullFace(_gl.BACK); + function filterFallback ( f ) { - } else if (cullFace === THREE.CullFaceFront) { + if ( f === THREE.NearestFilter || f === THREE.NearestMipMapNearestFilter || f === THREE.NearestMipMapLinearFilter ) { - _gl.cullFace(_gl.FRONT); + return _gl.NEAREST; - } else { + } - _gl.cullFace(_gl.FRONT_AND_BACK); + return _gl.LINEAR; - } + } - _gl.enable(_gl.CULL_FACE); + // Map three.js constants to WebGL constants - } + function paramThreeToGL ( p ) { - }; + var extension; - this.setMaterialFaces = function (material) { + if ( p === THREE.RepeatWrapping ) return _gl.REPEAT; + if ( p === THREE.ClampToEdgeWrapping ) return _gl.CLAMP_TO_EDGE; + if ( p === THREE.MirroredRepeatWrapping ) return _gl.MIRRORED_REPEAT; - state.setDoubleSided(material.side === THREE.DoubleSide); - state.setFlipSided(material.side === THREE.BackSide); + 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; - // Textures + 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; - function setTextureParameters(textureType, texture, isImagePowerOfTwo) { + 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; - var extension; + extension = extensions.get( 'OES_texture_half_float' ); - if (isImagePowerOfTwo) { + if ( extension !== null ) { - _gl.texParameteri(textureType, _gl.TEXTURE_WRAP_S, paramThreeToGL(texture.wrapS)); - _gl.texParameteri(textureType, _gl.TEXTURE_WRAP_T, paramThreeToGL(texture.wrapT)); + if ( p === THREE.HalfFloatType ) return extension.HALF_FLOAT_OES; - _gl.texParameteri(textureType, _gl.TEXTURE_MAG_FILTER, paramThreeToGL(texture.magFilter)); - _gl.texParameteri(textureType, _gl.TEXTURE_MIN_FILTER, paramThreeToGL(texture.minFilter)); + } - } else { + 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; - _gl.texParameteri(textureType, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE); - _gl.texParameteri(textureType, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE); + 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 (texture.wrapS !== THREE.ClampToEdgeWrapping || texture.wrapT !== THREE.ClampToEdgeWrapping) { + 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; - THREE.warn('THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping. ( ' + texture.sourceFile + ' )'); + 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; - } + extension = extensions.get( 'WEBGL_compressed_texture_s3tc' ); - _gl.texParameteri(textureType, _gl.TEXTURE_MAG_FILTER, filterFallback(texture.magFilter)); - _gl.texParameteri(textureType, _gl.TEXTURE_MIN_FILTER, filterFallback(texture.minFilter)); + if ( extension !== null ) { - if (texture.minFilter !== THREE.NearestFilter && texture.minFilter !== THREE.LinearFilter) { + if ( p === THREE.RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT; + if ( p === THREE.RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT; + if ( p === THREE.RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT; + if ( p === THREE.RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT; - THREE.warn('THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter. ( ' + texture.sourceFile + ' )'); + } - } + extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' ); - } + if ( extension !== null ) { - extension = extensions.get('EXT_texture_filter_anisotropic'); + if ( p === THREE.RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG; + if ( p === THREE.RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG; + if ( p === THREE.RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; + if ( p === THREE.RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; - if (extension && texture.type !== THREE.FloatType && texture.type !== THREE.HalfFloatType) { + } - if (texture.anisotropy > 1 || texture.__currentAnisotropy) { + extension = extensions.get( 'EXT_blend_minmax' ); - _gl.texParameterf(textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min(texture.anisotropy, _this.getMaxAnisotropy())); - texture.__currentAnisotropy = texture.anisotropy; + if ( extension !== null ) { - } + if ( p === THREE.MinEquation ) return extension.MIN_EXT; + if ( p === THREE.MaxEquation ) return extension.MAX_EXT; - } + } - } + return 0; - this.uploadTexture = function (texture, slot) { + } - if (texture.__webglInit === undefined) { + // DEPRECATED - texture.__webglInit = true; + this.supportsFloatTextures = function () { - texture.addEventListener('dispose', onTextureDispose); + console.warn( 'THREE.WebGLRenderer: .supportsFloatTextures() is now .extensions.get( \'OES_texture_float\' ).' ); + return extensions.get( 'OES_texture_float' ); - texture.__webglTexture = _gl.createTexture(); + }; - _this.info.memory.textures++; + this.supportsHalfFloatTextures = function () { - } + console.warn( 'THREE.WebGLRenderer: .supportsHalfFloatTextures() is now .extensions.get( \'OES_texture_half_float\' ).' ); + return extensions.get( 'OES_texture_half_float' ); - state.activeTexture(_gl.TEXTURE0 + slot); - state.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); + this.supportsStandardDerivatives = function () { - texture.image = clampToMaxSize(texture.image, _maxTextureSize); + console.warn( 'THREE.WebGLRenderer: .supportsStandardDerivatives() is now .extensions.get( \'OES_standard_derivatives\' ).' ); + return extensions.get( 'OES_standard_derivatives' ); - 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); + this.supportsCompressedTextureS3TC = function () { - var mipmap, mipmaps = texture.mipmaps; + console.warn( 'THREE.WebGLRenderer: .supportsCompressedTextureS3TC() is now .extensions.get( \'WEBGL_compressed_texture_s3tc\' ).' ); + return extensions.get( 'WEBGL_compressed_texture_s3tc' ); - 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 + this.supportsCompressedTexturePVRTC = function () { - if (mipmaps.length > 0 && isImagePowerOfTwo) { + console.warn( 'THREE.WebGLRenderer: .supportsCompressedTexturePVRTC() is now .extensions.get( \'WEBGL_compressed_texture_pvrtc\' ).' ); + return extensions.get( 'WEBGL_compressed_texture_pvrtc' ); - 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); + this.supportsBlendMinMax = function () { - } + console.warn( 'THREE.WebGLRenderer: .supportsBlendMinMax() is now .extensions.get( \'EXT_blend_minmax\' ).' ); + return extensions.get( 'EXT_blend_minmax' ); - texture.generateMipmaps = false; + }; - } else { + this.supportsVertexTextures = function () { - _gl.texImage2D(_gl.TEXTURE_2D, 0, glFormat, image.width, image.height, 0, glFormat, glType, image.data); + return capabilities.vertexTextures; - } + }; - } else if (texture instanceof THREE.CompressedTexture) { + this.supportsInstancedArrays = function () { - for (var i = 0, il = mipmaps.length; i < il; i++) { + console.warn( 'THREE.WebGLRenderer: .supportsInstancedArrays() is now .extensions.get( \'ANGLE_instanced_arrays\' ).' ); + return extensions.get( 'ANGLE_instanced_arrays' ); - mipmap = mipmaps[i]; + }; - if (texture.format !== THREE.RGBAFormat && texture.format !== THREE.RGBFormat) { + // - if (getCompressedTextureFormats().indexOf(glFormat) > -1) { + this.initMaterial = function () { - _gl.compressedTexImage2D(_gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, mipmap.data); + console.warn( 'THREE.WebGLRenderer: .initMaterial() has been removed.' ); - } else { + }; - THREE.warn("THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()"); + this.addPrePlugin = function () { - } + console.warn( 'THREE.WebGLRenderer: .addPrePlugin() has been removed.' ); - } else { + }; - _gl.texImage2D(_gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data); + this.addPostPlugin = function () { - } + console.warn( 'THREE.WebGLRenderer: .addPostPlugin() has been removed.' ); - } + }; - } else { // regular Texture (image, video, canvas) + this.updateShadowMap = function () { - // 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 + console.warn( 'THREE.WebGLRenderer: .updateShadowMap() has been removed.' ); - if (mipmaps.length > 0 && isImagePowerOfTwo) { + }; - for (var i = 0, il = mipmaps.length; i < il; i++) { + Object.defineProperties( this, { + shadowMapEnabled: { + get: function () { - mipmap = mipmaps[i]; - _gl.texImage2D(_gl.TEXTURE_2D, i, glFormat, glFormat, glType, mipmap); + return shadowMap.enabled; - } + }, + set: function ( value ) { - texture.generateMipmaps = false; + console.warn( 'THREE.WebGLRenderer: .shadowMapEnabled is now .shadowMap.enabled.' ); + shadowMap.enabled = value; - } else { + } + }, + shadowMapType: { + get: function () { - _gl.texImage2D(_gl.TEXTURE_2D, 0, glFormat, glFormat, glType, texture.image); + return shadowMap.type; - } + }, + set: function ( value ) { - } + console.warn( 'THREE.WebGLRenderer: .shadowMapType is now .shadowMap.type.' ); + shadowMap.type = value; - if (texture.generateMipmaps && isImagePowerOfTwo) _gl.generateMipmap(_gl.TEXTURE_2D); + } + }, + shadowMapCullFace: { + get: function () { - texture.needsUpdate = false; + return shadowMap.cullFace; - if (texture.onUpdate) texture.onUpdate(texture); + }, + set: function ( value ) { - }; + console.warn( 'THREE.WebGLRenderer: .shadowMapCullFace is now .shadowMap.cullFace.' ); + shadowMap.cullFace = value; - this.setTexture = function (texture, slot) { + } + }, + shadowMapDebug: { + get: function () { - if (texture.needsUpdate === true) { + return shadowMap.debug; - var image = texture.image; + }, + set: function ( value ) { - if (image.complete === false) { + console.warn( 'THREE.WebGLRenderer: .shadowMapDebug is now .shadowMap.debug.' ); + shadowMap.debug = value; - THREE.warn('THREE.WebGLRenderer: Texture marked for update but image is incomplete', texture); - return; + } + } + } ); - } +}; - _this.uploadTexture(texture, slot); - return; +// File:src/renderers/WebGLRenderTarget.js - } +/** + * @author szimek / https://github.com/szimek/ + * @author alteredq / http://alteredqualia.com/ + * @author Marius Kintel / https://github.com/kintel + */ - state.activeTexture(_gl.TEXTURE0 + slot); - state.bindTexture(_gl.TEXTURE_2D, texture.__webglTexture); +/* + In options, we can specify: + * Texture parameters for an auto-generated target texture + * An actual texture of type THREE.Texture + * An optional depthtexture of type THREE.DepthTexture +*/ +THREE.WebGLRenderTarget = function ( width, height, options ) { - }; + this.uuid = THREE.Math.generateUUID(); - function clampToMaxSize(image, maxSize) { + this.width = width; + this.height = height; - if (image.width > maxSize || image.height > maxSize) { + options = options || {}; - // Warning: Scaling through the canvas will only work with images that use - // premultiplied alpha. + this.texture = options.texture; + if (!this.texture) { + this.texture = new THREE.Texture(undefined, undefined, + options.wrapS, options.wrapT, + options.magFilter, options.minFilter, + options.format, options.type, + options.anisotropy); + } + this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true; + this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : true; + this.depthTexture = options.depthTexture; - var scale = maxSize / Math.max(image.width, image.height); + this.generateMipmaps = true; - var canvas = document.createElement('canvas'); - canvas.width = Math.floor(image.width * scale); - canvas.height = Math.floor(image.height * scale); + this.shareDepthFrom = options.shareDepthFrom !== undefined ? options.shareDepthFrom : null; - var context = canvas.getContext('2d'); - context.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height); +}; - THREE.warn('THREE.WebGLRenderer: image is too big (' + image.width + 'x' + image.height + '). Resized to ' + canvas.width + 'x' + canvas.height, image); +THREE.WebGLRenderTarget.prototype = { - return canvas; + constructor: THREE.WebGLRenderTarget, - } + setSize: function ( width, height ) { - return image; + if ( this.width !== width || this.height !== height ) { - } + this.width = width; + this.height = height; - function setCubeTexture(texture, slot) { + this.dispose(); - if (texture.image.length === 6) { + } - if (texture.needsUpdate) { + }, - if (!texture.image.__webglTextureCube) { + clone: function () { - texture.addEventListener('dispose', onTextureDispose); + return new this.constructor().copy( this ); - texture.image.__webglTextureCube = _gl.createTexture(); + }, - _this.info.memory.textures++; + copy: function ( source ) { - } + this.width = source.width; + this.height = source.height; - state.activeTexture(_gl.TEXTURE0 + slot); - state.bindTexture(_gl.TEXTURE_CUBE_MAP, texture.image.__webglTextureCube); + this.depthBuffer = source.depthBuffer; + this.stencilBuffer = source.stencilBuffer; + this.texture = this.texture.clone(); + if (this.depthTexture) this.depthTexture = this.depthTexture.clone(); - _gl.pixelStorei(_gl.UNPACK_FLIP_Y_WEBGL, texture.flipY); + this.generateMipmaps = source.generateMipmaps; - var isCompressed = texture instanceof THREE.CompressedTexture; - var isDataTexture = texture.image[0] instanceof THREE.DataTexture; + this.shareDepthFrom = source.shareDepthFrom; - var cubeImage = []; + return this; - for (var i = 0; i < 6; i++) { + }, - if (_this.autoScaleCubemaps && !isCompressed && !isDataTexture) { + dispose: function () { - cubeImage[i] = clampToMaxSize(texture.image[i], _maxCubemapSize); + this.dispatchEvent( { type: 'dispose' } ); - } else { + } - cubeImage[i] = isDataTexture ? texture.image[i].image : texture.image[i]; +}; - } +THREE.EventDispatcher.prototype.apply( THREE.WebGLRenderTarget.prototype ); - } +// File:src/renderers/WebGLRenderTargetCube.js - var image = cubeImage[0], - isImagePowerOfTwo = THREE.Math.isPowerOfTwo(image.width) && THREE.Math.isPowerOfTwo(image.height), - glFormat = paramThreeToGL(texture.format), - glType = paramThreeToGL(texture.type); +/** + * @author alteredq / http://alteredqualia.com + */ - setTextureParameters(_gl.TEXTURE_CUBE_MAP, texture, isImagePowerOfTwo); +THREE.WebGLRenderTargetCube = function ( width, height, options ) { - for (var i = 0; i < 6; i++) { + THREE.WebGLRenderTarget.call( this, width, height, options ); - if (!isCompressed) { + this.activeCubeFace = 0; // PX 0, NX 1, PY 2, NY 3, PZ 4, NZ 5 - if (isDataTexture) { +}; - _gl.texImage2D(_gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, cubeImage[i].width, cubeImage[i].height, 0, glFormat, glType, cubeImage[i].data); +THREE.WebGLRenderTargetCube.prototype = Object.create( THREE.WebGLRenderTarget.prototype ); +THREE.WebGLRenderTargetCube.prototype.constructor = THREE.WebGLRenderTargetCube; - } else { +// File:src/renderers/webgl/WebGLBufferRenderer.js - _gl.texImage2D(_gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, glFormat, glType, cubeImage[i]); +/** +* @author mrdoob / http://mrdoob.com/ +*/ - } +THREE.WebGLBufferRenderer = function ( _gl, extensions, _infoRender ) { - } else { + var mode; - var mipmap, mipmaps = cubeImage[i].mipmaps; + function setMode( value ) { - for (var j = 0, jl = mipmaps.length; j < jl; j++) { + mode = value; - mipmap = mipmaps[j]; + } - if (texture.format !== THREE.RGBAFormat && texture.format !== THREE.RGBFormat) { + function render( start, count ) { - if (getCompressedTextureFormats().indexOf(glFormat) > -1) { + _gl.drawArrays( mode, start, count ); - _gl.compressedTexImage2D(_gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, mipmap.data); + _infoRender.calls ++; + _infoRender.vertices += count; + if ( mode === _gl.TRIANGLES ) _infoRender.faces += count / 3; - } else { + } - THREE.warn("THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setCubeTexture()"); + function renderInstances( geometry ) { - } + var extension = extensions.get( 'ANGLE_instanced_arrays' ); - } else { + if ( extension === null ) { - _gl.texImage2D(_gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data); + console.error( 'THREE.WebGLBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); + return; - } + } - } + var position = geometry.attributes.position; - } + if ( position instanceof THREE.InterleavedBufferAttribute ) { - } + extension.drawArraysInstancedANGLE( mode, 0, position.data.count, geometry.maxInstancedCount ); - if (texture.generateMipmaps && isImagePowerOfTwo) { + } else { - _gl.generateMipmap(_gl.TEXTURE_CUBE_MAP); + extension.drawArraysInstancedANGLE( mode, 0, position.count, geometry.maxInstancedCount ); - } + } - texture.needsUpdate = false; + } - if (texture.onUpdate) texture.onUpdate(texture); + this.setMode = setMode; + this.render = render; + this.renderInstances = renderInstances; - } else { +}; - state.activeTexture(_gl.TEXTURE0 + slot); - state.bindTexture(_gl.TEXTURE_CUBE_MAP, texture.image.__webglTextureCube); +// File:src/renderers/webgl/WebGLIndexedBufferRenderer.js - } +/** +* @author mrdoob / http://mrdoob.com/ +*/ - } +THREE.WebGLIndexedBufferRenderer = function ( _gl, extensions, _infoRender ) { - } + var mode; - function setCubeTextureDynamic(texture, slot) { + function setMode( value ) { - state.activeTexture(_gl.TEXTURE0 + slot); - state.bindTexture(_gl.TEXTURE_CUBE_MAP, texture.__webglTexture); + mode = value; - } + } - // Render targets + var type, size; - function setupFrameBufferTexture(framebuffer, texture, attachment, textureTarget) { + function setIndex( index ) { - _gl.bindFramebuffer(_gl.FRAMEBUFFER, framebuffer); - _gl.framebufferTexture2D(_gl.FRAMEBUFFER, attachment, textureTarget, texture, 0); + if ( index.array instanceof Uint32Array && extensions.get( 'OES_element_index_uint' ) ) { - } + type = _gl.UNSIGNED_INT; + size = 4; - function setupRenderBufferStorage(renderbuffer, renderTarget) { + } else { - _gl.bindRenderbuffer(_gl.RENDERBUFFER, renderbuffer); + type = _gl.UNSIGNED_SHORT; + size = 2; - 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 ) { + function render( start, count ) { - _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.drawElements( mode, count, type, start * size ); - _gl.renderbufferStorage(_gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height); - _gl.framebufferRenderbuffer(_gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer); + _infoRender.calls ++; + _infoRender.vertices += count; + if ( mode === _gl.TRIANGLES ) _infoRender.faces += count / 3; - } else { + } - _gl.renderbufferStorage(_gl.RENDERBUFFER, _gl.RGBA4, renderTarget.width, renderTarget.height); + function renderInstances( geometry ) { - } + var extension = extensions.get( 'ANGLE_instanced_arrays' ); - } + if ( extension === null ) { - function setupDepthRenderbuffer(renderTarget) { + console.error( 'THREE.WebGLBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); + return; - var isCube = ( renderTarget instanceof THREE.WebGLRenderTargetCube ); - if (isCube) { + } - renderTarget.__webglRenderbuffer = []; - for (var i = 0; i < 6; i++) { + var index = geometry.index; - renderTarget.__webglRenderbuffer[i] = _gl.createRenderbuffer(); - setupRenderBufferStorage(renderTarget.__webglRenderbuffer[i], renderTarget); + extension.drawElementsInstancedANGLE( mode, index.array.length, type, 0, geometry.maxInstancedCount ); - } + } - } - else { + this.setMode = setMode; + this.setIndex = setIndex; + this.render = render; + this.renderInstances = renderInstances; - if (renderTarget.shareDepthFrom) { +}; - renderTarget.__webglRenderbuffer = renderTarget.shareDepthFrom.__webglRenderbuffer; - if (renderTarget.depthBuffer && !renderTarget.stencilBuffer) { +// File:src/renderers/webgl/WebGLExtensions.js - _gl.framebufferRenderbuffer(_gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderTarget.__webglRenderbuffer); +/** +* @author mrdoob / http://mrdoob.com/ +*/ - } else if (renderTarget.depthBuffer && renderTarget.stencilBuffer) { +THREE.WebGLExtensions = function ( gl ) { - _gl.framebufferRenderbuffer(_gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderTarget.__webglRenderbuffer); + var extensions = {}; - } + this.get = function ( name ) { - } else { + if ( extensions[ name ] !== undefined ) { - renderTarget.__webglRenderbuffer = _gl.createRenderbuffer(); - setupRenderBufferStorage(renderTarget.__webglRenderbuffer, renderTarget); + return extensions[ name ]; - } + } - } + var extension; - _gl.bindRenderbuffer(_gl.RENDERBUFFER, null); + switch ( name ) { - }; + case 'EXT_texture_filter_anisotropic': + extension = gl.getExtension( 'EXT_texture_filter_anisotropic' ) || gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) || gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' ); + break; - function setupDepthTexture(renderTarget) { + case 'WEBGL_compressed_texture_s3tc': + extension = gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' ); + break; - var depthTexture = renderTarget.depthTexture; - var isPowerOfTwo = THREE.Math.isPowerOfTwo(depthTexture.width) && THREE.Math.isPowerOfTwo(depthTexture.height); + case 'WEBGL_compressed_texture_pvrtc': + extension = gl.getExtension( 'WEBGL_compressed_texture_pvrtc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ); + break; - depthTexture.__webglTexture = _gl.createTexture(); - _gl.bindTexture(_gl.TEXTURE_2D, depthTexture.__webglTexture); - setTextureParameters(_gl.TEXTURE_2D, depthTexture, isPowerOfTwo); + default: + extension = gl.getExtension( name ); - if (renderTarget.depthBuffer && !renderTarget.stencilBuffer) { + } - _gl.texImage2D(_gl.TEXTURE_2D, 0, _gl.DEPTH_COMPONENT, depthTexture.width, depthTexture.height, 0, _gl.DEPTH_COMPONENT, _gl.UNSIGNED_INT, null); - _gl.framebufferTexture2D(_gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, depthTexture.__webglTexture, 0); + if ( extension === null ) { - } else if (renderTarget.stencilBuffer) { + console.warn( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' ); - _gl.texImage2D(_gl.TEXTURE_2D, 0, _gl.DEPTH_STENCIL, depthTexture.width, depthTexture.height, 0, _gl.DEPTH_STENCIL, extensions.get('WEBGL_depth_texture').UNSIGNED_INT_24_8_WEBGL, null); - _gl.framebufferTexture2D(_gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, depthTexture.__webglTexture, 0); + } - } + extensions[ name ] = extension; - _gl.bindTexture(_gl.TEXTURE_2D, null); + return extension; - }; + }; - this.setRenderTarget = function (renderTarget) { +}; - var isCube = ( renderTarget instanceof THREE.WebGLRenderTargetCube ); +// File:src/renderers/webgl/WebGLCapabilities.js - if (renderTarget && renderTarget.__webglFramebuffer === undefined) { +THREE.WebGLCapabilities = function ( gl, extensions, parameters ) { - renderTarget.addEventListener('dispose', onRenderTargetDispose); + function getMaxPrecision( precision ) { - renderTarget.texture.__webglTexture = _gl.createTexture(); + if ( precision === 'highp' ) { - _this.info.memory.textures++; + if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.HIGH_FLOAT ).precision > 0 && + gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.HIGH_FLOAT ).precision > 0 ) { - // - // Setup color buffer - // - var isTargetPowerOfTwo = THREE.Math.isPowerOfTwo(renderTarget.width) && THREE.Math.isPowerOfTwo(renderTarget.height), - glFormat = paramThreeToGL(renderTarget.texture.format), - glType = paramThreeToGL(renderTarget.texture.type); + return 'highp'; - if (isCube) { + } - renderTarget.__webglFramebuffer = []; + precision = 'mediump'; - state.bindTexture(_gl.TEXTURE_CUBE_MAP, renderTarget.texture.__webglTexture); - setTextureParameters(_gl.TEXTURE_CUBE_MAP, renderTarget.texture, isTargetPowerOfTwo); + } - for (var i = 0; i < 6; i++) { + if ( precision === 'mediump' ) { - renderTarget.__webglFramebuffer[i] = _gl.createFramebuffer(); + if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.MEDIUM_FLOAT ).precision > 0 && + gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT ).precision > 0 ) { - _gl.texImage2D(_gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null); + return 'mediump'; - setupFrameBufferTexture(renderTarget.__webglFramebuffer[i], renderTarget.texture.__webglTexture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i); + } - } + } - if (renderTarget.generateMipmaps && isTargetPowerOfTwo) _gl.generateMipmap(_gl.TEXTURE_CUBE_MAP); + return 'lowp'; - } else { + } - renderTarget.__webglFramebuffer = _gl.createFramebuffer(); + this.getMaxPrecision = getMaxPrecision; - state.bindTexture(_gl.TEXTURE_2D, renderTarget.texture.__webglTexture); - setTextureParameters(_gl.TEXTURE_2D, renderTarget.texture, isTargetPowerOfTwo); + this.precision = parameters.precision !== undefined ? parameters.precision : 'highp', + this.logarithmicDepthBuffer = parameters.logarithmicDepthBuffer !== undefined ? parameters.logarithmicDepthBuffer : false; - _gl.texImage2D(_gl.TEXTURE_2D, 0, glFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null); + this.maxTextures = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS ); + this.maxVertexTextures = gl.getParameter( gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ); + this.maxTextureSize = gl.getParameter( gl.MAX_TEXTURE_SIZE ); + this.maxCubemapSize = gl.getParameter( gl.MAX_CUBE_MAP_TEXTURE_SIZE ); - setupFrameBufferTexture(renderTarget.__webglFramebuffer, renderTarget.texture.__webglTexture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D); + this.maxAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); + this.maxVertexUniforms = gl.getParameter( gl.MAX_VERTEX_UNIFORM_VECTORS ); + this.maxVaryings = gl.getParameter( gl.MAX_VARYING_VECTORS ); + this.maxFragmentUniforms = gl.getParameter( gl.MAX_FRAGMENT_UNIFORM_VECTORS ); - if (renderTarget.generateMipmaps && isTargetPowerOfTwo) _gl.generateMipmap(_gl.TEXTURE_2D); + this.vertexTextures = this.maxVertexTextures > 0; + this.floatFragmentTextures = !! extensions.get( 'OES_texture_float' ); + this.floatVertexTextures = this.vertexTextures && this.floatFragmentTextures; - } + var _maxPrecision = getMaxPrecision( this.precision ); - // Release textures + if ( _maxPrecision !== this.precision ) { - if (isCube) { + console.warn( 'THREE.WebGLRenderer:', this.precision, 'not supported, using', _maxPrecision, 'instead.' ); + this.precision = _maxPrecision; - state.bindTexture(_gl.TEXTURE_CUBE_MAP, null); + } - } else { + if ( this.logarithmicDepthBuffer ) { - state.bindTexture(_gl.TEXTURE_2D, null); + this.logarithmicDepthBuffer = !! extensions.get( 'EXT_frag_depth' ); - } + } - // - // Setup depth and stencil buffers - // - if (renderTarget.depthBuffer === undefined) renderTarget.depthBuffer = true; - if (renderTarget.stencilBuffer === undefined) renderTarget.stencilBuffer = true; +}; - if (renderTarget.depthBuffer || renderTarget.stencilBuffer) { +// File:src/renderers/webgl/WebGLGeometries.js - if (renderTarget.depthTexture && this.supportsDepthTextures()) setupDepthTexture(renderTarget); - else setupDepthRenderbuffer(renderTarget); +/** +* @author mrdoob / http://mrdoob.com/ +*/ - } +THREE.WebGLGeometries = function ( gl, properties, info ) { - _gl.bindFramebuffer(_gl.FRAMEBUFFER, null); + var geometries = {}; - } + function get( object ) { - var framebuffer, width, height, vx, vy; + var geometry = object.geometry; - if (renderTarget) { + if ( geometries[ geometry.id ] !== undefined ) { - if (isCube) { + return geometries[ geometry.id ]; - framebuffer = renderTarget.__webglFramebuffer[renderTarget.activeCubeFace]; + } - } else { + geometry.addEventListener( 'dispose', onGeometryDispose ); - framebuffer = renderTarget.__webglFramebuffer; + var buffergeometry; - } + if ( geometry instanceof THREE.BufferGeometry ) { - width = renderTarget.width; - height = renderTarget.height; + buffergeometry = geometry; - vx = 0; - vy = 0; + } else if ( geometry instanceof THREE.Geometry ) { - } else { + if ( geometry._bufferGeometry === undefined ) { - framebuffer = null; + geometry._bufferGeometry = new THREE.BufferGeometry().setFromObject( object ); - width = _viewportWidth; - height = _viewportHeight; + } - vx = _viewportX; - vy = _viewportY; + buffergeometry = geometry._bufferGeometry; - } + } - if (framebuffer !== _currentFramebuffer) { + geometries[ geometry.id ] = buffergeometry; - _gl.bindFramebuffer(_gl.FRAMEBUFFER, framebuffer); - _gl.viewport(vx, vy, width, height); + info.memory.geometries ++; - _currentFramebuffer = framebuffer; + return buffergeometry; - } + } - _currentWidth = width; - _currentHeight = height; + function onGeometryDispose( event ) { - }; + var geometry = event.target; + var buffergeometry = geometries[ geometry.id ]; - this.readRenderTargetPixels = function (renderTarget, x, y, width, height, buffer) { + deleteAttributes( buffergeometry.attributes ); - if (!( renderTarget instanceof THREE.WebGLRenderTarget )) { + geometry.removeEventListener( 'dispose', onGeometryDispose ); - THREE.error('THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.'); - return; + delete geometries[ geometry.id ]; - } + var property = properties.get( geometry ); + if ( property.wireframe ) deleteAttribute( property.wireframe ); - if (renderTarget.__webglFramebuffer) { + info.memory.geometries --; - var restore = false; + } - if (renderTarget.__webglFramebuffer !== _currentFramebuffer) { + function getAttributeBuffer( attribute ) { - _gl.bindFramebuffer(_gl.FRAMEBUFFER, renderTarget.__webglFramebuffer); + if ( attribute instanceof THREE.InterleavedBufferAttribute ) { - restore = true; + return properties.get( attribute.data ).__webglBuffer; - } + } - if (renderTarget.texture.format !== THREE.RGBAFormat && paramThreeToGL(renderTarget.texture.format) !== _gl.getParameter(_gl.IMPLEMENTATION_COLOR_READ_FORMAT)) { + return properties.get( attribute ).__webglBuffer; - THREE.error('THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.'); - return; + } - } + function deleteAttribute( attribute ) { - if (renderTarget.texture.type !== THREE.UnsignedByteType - && paramThreeToGL(renderTarget.texture.type) !== _gl.getParameter(_gl.IMPLEMENTATION_COLOR_READ_TYPE) - && !(renderTarget.texture.type == THREE.FloatType && _gl.getExtension('WEBGL_color_buffer_float'))) { + var buffer = getAttributeBuffer( attribute ); - THREE.error('THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.'); - return; + if ( buffer !== undefined ) { - } + gl.deleteBuffer( buffer ); + removeAttributeBuffer( attribute ); - if (_gl.checkFramebufferStatus(_gl.FRAMEBUFFER) === _gl.FRAMEBUFFER_COMPLETE) { + } - _gl.readPixels(x, y, width, height, paramThreeToGL(renderTarget.texture.format), paramThreeToGL(renderTarget.texture.type), buffer); + } - } else { + function deleteAttributes( attributes ) { - console.error('THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete.'); + for ( var name in attributes ) { - } + deleteAttribute( attributes[ name ] ); - if (restore) { + } - _gl.bindFramebuffer(_gl.FRAMEBUFFER, _currentFramebuffer); + } - } + function removeAttributeBuffer( attribute ) { - } + if ( attribute instanceof THREE.InterleavedBufferAttribute ) { - }; + properties.delete( attribute.data ); - function updateRenderTargetMipmap(renderTarget) { + } else { - if (renderTarget instanceof THREE.WebGLRenderTargetCube) { + properties.delete( attribute ); - state.bindTexture(_gl.TEXTURE_CUBE_MAP, renderTarget.texture.__webglTexture); - _gl.generateMipmap(_gl.TEXTURE_CUBE_MAP); - state.bindTexture(_gl.TEXTURE_CUBE_MAP, null); + } - } else { + } - state.bindTexture(_gl.TEXTURE_2D, renderTarget.texture.__webglTexture); - _gl.generateMipmap(_gl.TEXTURE_2D); - state.bindTexture(_gl.TEXTURE_2D, null); + this.get = get; - } +}; - } +// File:src/renderers/webgl/WebGLObjects.js - // Fallback filters for non-power-of-2 textures +/** +* @author mrdoob / http://mrdoob.com/ +*/ - function filterFallback(f) { +THREE.WebGLObjects = function ( gl, properties, info ) { - if (f === THREE.NearestFilter || f === THREE.NearestMipMapNearestFilter || f === THREE.NearestMipMapLinearFilter) { + var geometries = new THREE.WebGLGeometries( gl, properties, info ); - return _gl.NEAREST; + // - } + function update( object ) { - return _gl.LINEAR; + // TODO: Avoid updating twice (when using shadowMap). Maybe add frame counter. - } + var geometry = geometries.get( object ); - // Map three.js constants to WebGL constants + if ( object.geometry instanceof THREE.Geometry ) { - function paramThreeToGL(p) { + geometry.updateFromObject( object ); - var extension; + } - if (p === THREE.RepeatWrapping) return _gl.REPEAT; - if (p === THREE.ClampToEdgeWrapping) return _gl.CLAMP_TO_EDGE; - if (p === THREE.MirroredRepeatWrapping) return _gl.MIRRORED_REPEAT; + var index = geometry.index; + var attributes = geometry.attributes; - 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 ( index !== null ) { - 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; + updateAttribute( index, gl.ELEMENT_ARRAY_BUFFER ); - 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; + for ( var name in attributes ) { - extension = extensions.get('OES_texture_half_float'); + updateAttribute( attributes[ name ], gl.ARRAY_BUFFER ); - if (extension !== null) { + } - if (p === THREE.HalfFloatType) return extension.HALF_FLOAT_OES; + // morph targets - } + var morphAttributes = geometry.morphAttributes; - 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; + for ( var name in morphAttributes ) { - 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; + var array = morphAttributes[ name ]; - 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; + for ( var i = 0, l = array.length; i < l; i ++ ) { - 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; + updateAttribute( array[ i ], gl.ARRAY_BUFFER ); - extension = extensions.get('WEBGL_compressed_texture_s3tc'); + } - if (extension !== null) { + } - if (p === THREE.RGB_S3TC_DXT1_Format) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT; - if (p === THREE.RGBA_S3TC_DXT1_Format) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT; - if (p === THREE.RGBA_S3TC_DXT3_Format) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT; - if (p === THREE.RGBA_S3TC_DXT5_Format) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT; + return geometry; - } + } - extension = extensions.get('WEBGL_compressed_texture_pvrtc'); + function updateAttribute( attribute, bufferType ) { - if (extension !== null) { + var data = ( attribute instanceof THREE.InterleavedBufferAttribute ) ? attribute.data : attribute; - if (p === THREE.RGB_PVRTC_4BPPV1_Format) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG; - if (p === THREE.RGB_PVRTC_2BPPV1_Format) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG; - if (p === THREE.RGBA_PVRTC_4BPPV1_Format) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; - if (p === THREE.RGBA_PVRTC_2BPPV1_Format) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; + var attributeProperties = properties.get( data ); - } + if ( attributeProperties.__webglBuffer === undefined ) { - extension = extensions.get('EXT_blend_minmax'); + createBuffer( attributeProperties, data, bufferType ); - if (extension !== null) { + } else if ( attributeProperties.version !== data.version ) { - if (p === THREE.MinEquation) return extension.MIN_EXT; - if (p === THREE.MaxEquation) return extension.MAX_EXT; + updateBuffer( attributeProperties, data, bufferType ); - } + } - return 0; + } - } + function createBuffer( attributeProperties, data, bufferType ) { - // Allocations + attributeProperties.__webglBuffer = gl.createBuffer(); + gl.bindBuffer( bufferType, attributeProperties.__webglBuffer ); - function allocateBones(object) { + var usage = data.dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW; - if (_supportsBoneTextures && object && object.skeleton && object.skeleton.useVertexTexture) { + gl.bufferData( bufferType, data.array, usage ); - return 1024; + attributeProperties.version = data.version; - } 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) + function updateBuffer( attributeProperties, data, bufferType ) { - var nVertexUniforms = _gl.getParameter(_gl.MAX_VERTEX_UNIFORM_VECTORS); - var nVertexMatrices = Math.floor(( nVertexUniforms - 20 ) / 4); + gl.bindBuffer( bufferType, attributeProperties.__webglBuffer ); - var maxBones = nVertexMatrices; + if ( data.dynamic === false || data.updateRange.count === - 1 ) { - if (object !== undefined && object instanceof THREE.SkinnedMesh) { + // Not using update ranges - maxBones = Math.min(object.skeleton.bones.length, maxBones); + gl.bufferSubData( bufferType, 0, data.array ); - if (maxBones < object.skeleton.bones.length) { + } else if ( data.updateRange.count === 0 ) { - THREE.warn('WebGLRenderer: too many bones - ' + object.skeleton.bones.length + ', this GPU supports just ' + maxBones + ' (try OpenGL instead of ANGLE)'); + console.error( 'THREE.WebGLObjects.updateBuffer: dynamic THREE.BufferAttribute marked as needsUpdate but updateRange.count is 0, ensure you are using set methods or updating manually.' ); - } + } else { - } + gl.bufferSubData( bufferType, data.updateRange.offset * data.array.BYTES_PER_ELEMENT, + data.array.subarray( data.updateRange.offset, data.updateRange.offset + data.updateRange.count ) ); - return maxBones; + data.updateRange.count = 0; // reset range - } + } - } + attributeProperties.version = data.version; - function allocateLights(lights) { + } - var dirLights = 0; - var pointLights = 0; - var spotLights = 0; - var hemiLights = 0; + function getAttributeBuffer( attribute ) { - for (var l = 0, ll = lights.length; l < ll; l++) { + if ( attribute instanceof THREE.InterleavedBufferAttribute ) { - var light = lights[l]; + return properties.get( attribute.data ).__webglBuffer; - 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 properties.get( attribute ).__webglBuffer; - } + } - return {'directional': dirLights, 'point': pointLights, 'spot': spotLights, 'hemi': hemiLights}; + function getWireframeAttribute( geometry ) { - } + var property = properties.get( geometry ); - function allocateShadows(lights) { + if ( property.wireframe !== undefined ) { - var maxShadows = 0; + return property.wireframe; - for (var l = 0, ll = lights.length; l < ll; l++) { + } - var light = lights[l]; + var indices = []; - if (!light.castShadow) continue; + var index = geometry.index; + var attributes = geometry.attributes; + var position = attributes.position; - if (light instanceof THREE.SpotLight) maxShadows++; - if (light instanceof THREE.DirectionalLight && !light.shadowCascade) maxShadows++; + // console.time( 'wireframe' ); - } + if ( index !== null ) { - return maxShadows; + var edges = {}; + var array = index.array; - } + for ( var i = 0, l = array.length; i < l; i += 3 ) { - // DEPRECATED - - this.initMaterial = function () { - - THREE.warn('THREE.WebGLRenderer: .initMaterial() has been removed.'); - - }; - - this.addPrePlugin = function () { - - THREE.warn('THREE.WebGLRenderer: .addPrePlugin() has been removed.'); - - }; - - this.addPostPlugin = function () { - - THREE.warn('THREE.WebGLRenderer: .addPostPlugin() has been removed.'); - - }; - - this.updateShadowMap = function () { - - THREE.warn('THREE.WebGLRenderer: .updateShadowMap() has been removed.'); - - }; - - Object.defineProperties(this, { - shadowMapEnabled: { - get: function () { - return shadowMap.enabled; - }, - set: function (value) { - THREE.warn('THREE.WebGLRenderer: .shadowMapEnabled is now .shadowMap.enabled.'); - shadowMap.enabled = value; - } - }, - shadowMapType: { - get: function () { - return shadowMap.type; - }, - set: function (value) { - THREE.warn('THREE.WebGLRenderer: .shadowMapType is now .shadowMap.type.'); - shadowMap.type = value; - } - }, - shadowMapCullFace: { - get: function () { - return shadowMap.cullFace; - }, - set: function (value) { - THREE.warn('THREE.WebGLRenderer: .shadowMapCullFace is now .shadowMap.cullFace.'); - shadowMap.cullFace = value; - } - }, - shadowMapDebug: { - get: function () { - return shadowMap.debug; - }, - set: function (value) { - THREE.warn('THREE.WebGLRenderer: .shadowMapDebug is now .shadowMap.debug.'); - shadowMap.debug = value; - } - }, - shadowMapCascade: { - get: function () { - return shadowMap.cascade; - }, - set: function (value) { - THREE.warn('THREE.WebGLRenderer: .shadowMapCascade is now .shadowMap.cascade.'); - shadowMap.cascade = value; - } - } - }); + var a = array[ i + 0 ]; + var b = array[ i + 1 ]; + var c = array[ i + 2 ]; -}; + if ( checkEdge( edges, a, b ) ) indices.push( a, b ); + if ( checkEdge( edges, b, c ) ) indices.push( b, c ); + if ( checkEdge( edges, c, a ) ) indices.push( c, a ); -// File:src/renderers/WebGLRenderTarget.js + } -/** - * @author szimek / https://github.com/szimek/ - * @author alteredq / http://alteredqualia.com/ - */ + } else { -THREE.WebGLRenderTarget = function (width, height, options) { + var array = attributes.position.array; - this.width = width; - this.height = height; + for ( var i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) { - options = options || {}; + var a = i + 0; + var b = i + 1; + var c = i + 2; - this.texture = options.texture; - if (!this.texture) { + indices.push( a, b, b, c, c, a ); - this.texture = { + } - wrapS: options.wrapS !== undefined ? options.wrapS : THREE.ClampToEdgeWrapping, - wrapT: options.wrapT !== undefined ? options.wrapT : THREE.ClampToEdgeWrapping, - magFilter: options.magFilter !== undefined ? options.magFilter : THREE.LinearFilter, - minFilter: options.minFilter !== undefined ? options.minFilter : THREE.LinearMipMapLinearFilter, - anisotropy: options.anisotropy !== undefined ? options.anisotropy : 1, - offset: new THREE.Vector2(0, 0), - repeat: new THREE.Vector2(1, 1), - format: options.format !== undefined ? options.format : THREE.RGBAFormat, - type: options.type !== undefined ? options.type : THREE.UnsignedByteType, + } - clone: function () { + // console.timeEnd( 'wireframe' ); - var tmp = {}; - tmp.wrapS = this.wrapS; - tmp.wrapT = this.wrapT; + var TypeArray = position.count > 65535 ? Uint32Array : Uint16Array; + var attribute = new THREE.BufferAttribute( new TypeArray( indices ), 1 ); - tmp.magFilter = this.magFilter; - tmp.minFilter = this.minFilter; + updateAttribute( attribute, gl.ELEMENT_ARRAY_BUFFER ); - tmp.anisotropy = this.anisotropy; + property.wireframe = attribute; - tmp.format = this.format; - tmp.type = this.type; + return attribute; - tmp.offset = this.offset.clone(); - tmp.repeat = this.repeat.clone(); + } - return tmp; + function checkEdge( edges, a, b ) { - } + if ( a > b ) { - }; + var tmp = a; + a = b; + b = tmp; - } + } + + var list = edges[ a ]; - this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true; - this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : true; - this.depthTexture = options.depthTexture; + if ( list === undefined ) { - this.generateMipmaps = true; + edges[ a ] = [ b ]; + return true; - this.shareDepthFrom = options.shareDepthFrom !== undefined ? options.shareDepthFrom : null; + } else if ( list.indexOf( b ) === -1 ) { + + list.push( b ); + return true; + + } + + return false; + + } + + this.getAttributeBuffer = getAttributeBuffer; + this.getWireframeAttribute = getWireframeAttribute; + + this.update = update; }; -THREE.WebGLRenderTarget.prototype = { +// File:src/renderers/webgl/WebGLProgram.js - constructor: THREE.WebGLRenderTarget, +THREE.WebGLProgram = ( function () { - setSize: function (width, height) { + var programIdCount = 0; - if (this.width !== width || this.height !== height) { + function generateDefines( defines ) { - this.width = width; - this.height = height; + var chunks = []; - this.dispose(); + for ( var name in defines ) { - } - }, + var value = defines[ name ]; - clone: function () { + if ( value === false ) continue; - var tmp = new THREE.WebGLRenderTarget(this.width, this.height); + chunks.push( '#define ' + name + ' ' + value ); - tmp.texture = this.texture.clone(); - if (this.depthTexture) tmp.depthTexture = this.depthTexture.clone(); + } - tmp.depthBuffer = this.depthBuffer; - tmp.stencilBuffer = this.stencilBuffer; + return chunks.join( '\n' ); - tmp.generateMipmaps = this.generateMipmaps; + } - tmp.shareDepthFrom = this.shareDepthFrom; + function fetchUniformLocations( gl, program, identifiers ) { - return tmp; + var uniforms = {}; - }, + var n = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS ); - dispose: function () { + for ( var i = 0; i < n; i ++ ) { - this.dispatchEvent({type: 'dispose'}); + var info = gl.getActiveUniform( program, i ); + var name = info.name; + var location = gl.getUniformLocation( program, name ); - } + // console.log("THREE.WebGLProgram: ACTIVE UNIFORM:", name); -}; + var suffixPos = name.lastIndexOf( '[0]' ); + if ( suffixPos !== - 1 && suffixPos === name.length - 3 ) { -THREE.EventDispatcher.prototype.apply(THREE.WebGLRenderTarget.prototype); + uniforms[ name.substr( 0, suffixPos ) ] = location; -// File:src/renderers/WebGLRenderTargetCube.js + } -/** - * @author alteredq / http://alteredqualia.com - */ + uniforms[ name ] = location; -THREE.WebGLRenderTargetCube = function (width, height, options) { + } - THREE.WebGLRenderTarget.call(this, width, height, options); + return uniforms; - this.activeCubeFace = 0; // PX 0, NX 1, PY 2, NY 3, PZ 4, NZ 5 + } -}; + function fetchAttributeLocations( gl, program, identifiers ) { -THREE.WebGLRenderTargetCube.prototype = Object.create(THREE.WebGLRenderTarget.prototype); -THREE.WebGLRenderTargetCube.prototype.constructor = THREE.WebGLRenderTargetCube; + var attributes = {}; -// File:src/renderers/webgl/WebGLExtensions.js + var n = gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES ); -/** - * @author mrdoob / http://mrdoob.com/ - */ + for ( var i = 0; i < n; i ++ ) { -THREE.WebGLExtensions = function (gl) { + var info = gl.getActiveAttrib( program, i ); + var name = info.name; - var extensions = {}; + // console.log("THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:", name, i ); - this.get = function (name) { + attributes[ name ] = gl.getAttribLocation( program, name ); - if (extensions[name] !== undefined) { + } - return extensions[name]; + return attributes; - } + } - var extension; + function filterEmptyLine( string ) { - switch (name) { + return string !== ''; - case 'EXT_texture_filter_anisotropic': - extension = gl.getExtension('EXT_texture_filter_anisotropic') || gl.getExtension('MOZ_EXT_texture_filter_anisotropic') || gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic'); - break; + } - case 'WEBGL_compressed_texture_s3tc': - extension = gl.getExtension('WEBGL_compressed_texture_s3tc') || gl.getExtension('MOZ_WEBGL_compressed_texture_s3tc') || gl.getExtension('WEBKIT_WEBGL_compressed_texture_s3tc'); - break; + return function WebGLProgram( renderer, code, material, parameters ) { - case 'WEBGL_compressed_texture_pvrtc': - extension = gl.getExtension('WEBGL_compressed_texture_pvrtc') || gl.getExtension('WEBKIT_WEBGL_compressed_texture_pvrtc'); - break; + var gl = renderer.context; - default: - extension = gl.getExtension(name); + var defines = material.defines; - } + var vertexShader = material.__webglShader.vertexShader; + var fragmentShader = material.__webglShader.fragmentShader; - if (extension === null) { + var shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC'; - THREE.warn('THREE.WebGLRenderer: ' + name + ' extension not supported.'); + if ( parameters.shadowMapType === THREE.PCFShadowMap ) { - } + shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF'; - extensions[name] = extension; + } else if ( parameters.shadowMapType === THREE.PCFSoftShadowMap ) { - return extension; + shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT'; - }; + } -}; + var envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; + var envMapModeDefine = 'ENVMAP_MODE_REFLECTION'; + var envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY'; -// File:src/renderers/webgl/WebGLGeometries.js + if ( parameters.envMap ) { -/** - * @author mrdoob / http://mrdoob.com/ - */ + switch ( material.envMap.mapping ) { -THREE.WebGLGeometries = function (gl, info) { + case THREE.CubeReflectionMapping: + case THREE.CubeRefractionMapping: + envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; + break; - var geometries = {}; + case THREE.EquirectangularReflectionMapping: + case THREE.EquirectangularRefractionMapping: + envMapTypeDefine = 'ENVMAP_TYPE_EQUIREC'; + break; - this.get = function (object) { + case THREE.SphericalReflectionMapping: + envMapTypeDefine = 'ENVMAP_TYPE_SPHERE'; + break; - var geometry = object.geometry; + } - if (geometries[geometry.id] !== undefined) { + switch ( material.envMap.mapping ) { - return geometries[geometry.id]; + case THREE.CubeRefractionMapping: + case THREE.EquirectangularRefractionMapping: + envMapModeDefine = 'ENVMAP_MODE_REFRACTION'; + break; - } + } - geometry.addEventListener('dispose', onGeometryDispose); + switch ( material.combine ) { - if (geometry instanceof THREE.BufferGeometry) { + case THREE.MultiplyOperation: + envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY'; + break; - geometries[geometry.id] = geometry; + case THREE.MixOperation: + envMapBlendingDefine = 'ENVMAP_BLENDING_MIX'; + break; - } else { + case THREE.AddOperation: + envMapBlendingDefine = 'ENVMAP_BLENDING_ADD'; + break; - geometries[geometry.id] = new THREE.BufferGeometry().setFromObject(object); + } - } + } - info.memory.geometries++; + var gammaFactorDefine = ( renderer.gammaFactor > 0 ) ? renderer.gammaFactor : 1.0; - return geometries[geometry.id]; + // console.log( 'building new program ' ); - }; + // - function onGeometryDispose(event) { + var customDefines = generateDefines( defines ); - var geometry = event.target; + // - geometry.removeEventListener('dispose', onGeometryDispose); + var program = gl.createProgram(); - geometry = geometries[geometry.id]; + var prefixVertex, prefixFragment; - for (var name in geometry.attributes) { + if ( material instanceof THREE.RawShaderMaterial ) { - var attribute = geometry.attributes[name]; + prefixVertex = ''; + prefixFragment = ''; - if (attribute.buffer !== undefined) { + } else { - gl.deleteBuffer(attribute.buffer); + prefixVertex = [ - delete attribute.buffer; + 'precision ' + parameters.precision + ' float;', + 'precision ' + parameters.precision + ' int;', - } + '#define SHADER_NAME ' + material.__webglShader.name, - } + customDefines, - info.memory.geometries--; + parameters.supportsVertexTextures ? '#define VERTEX_TEXTURES' : '', - } + renderer.gammaInput ? '#define GAMMA_INPUT' : '', + renderer.gammaOutput ? '#define GAMMA_OUTPUT' : '', + '#define GAMMA_FACTOR ' + gammaFactorDefine, -}; + '#define MAX_DIR_LIGHTS ' + parameters.maxDirLights, + '#define MAX_POINT_LIGHTS ' + parameters.maxPointLights, + '#define MAX_SPOT_LIGHTS ' + parameters.maxSpotLights, + '#define MAX_HEMI_LIGHTS ' + parameters.maxHemiLights, -// File:src/renderers/webgl/WebGLObjects.js + '#define MAX_SHADOWS ' + parameters.maxShadows, -/** - * @author mrdoob / http://mrdoob.com/ - */ + '#define MAX_BONES ' + parameters.maxBones, -THREE.WebGLObjects = function (gl, info) { + parameters.map ? '#define USE_MAP' : '', + parameters.envMap ? '#define USE_ENVMAP' : '', + parameters.envMap ? '#define ' + envMapModeDefine : '', + parameters.lightMap ? '#define USE_LIGHTMAP' : '', + parameters.aoMap ? '#define USE_AOMAP' : '', + parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', + parameters.bumpMap ? '#define USE_BUMPMAP' : '', + parameters.normalMap ? '#define USE_NORMALMAP' : '', + parameters.displacementMap && parameters.supportsVertexTextures ? '#define USE_DISPLACEMENTMAP' : '', + parameters.specularMap ? '#define USE_SPECULARMAP' : '', + parameters.alphaMap ? '#define USE_ALPHAMAP' : '', + parameters.vertexColors ? '#define USE_COLOR' : '', - var objects = {}; - var objectsImmediate = []; + parameters.flatShading ? '#define FLAT_SHADED' : '', - var geometries = new THREE.WebGLGeometries(gl, info); + parameters.skinning ? '#define USE_SKINNING' : '', + parameters.useVertexTexture ? '#define BONE_TEXTURE' : '', - var geometryGroups = {}; - var geometryGroupCounter = 0; + parameters.morphTargets ? '#define USE_MORPHTARGETS' : '', + parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '', + 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.pointLightShadows > 0 ? '#define POINT_LIGHT_SHADOWS' : '', - function onObjectRemoved(event) { + parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '', - var object = event.target; + parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', + parameters.logarithmicDepthBuffer && renderer.extensions.get( 'EXT_frag_depth' ) ? '#define USE_LOGDEPTHBUF_EXT' : '', - object.traverse(function (child) { - child.removeEventListener('remove', onObjectRemoved); - removeObject(child); + '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;', - } + '#ifdef USE_COLOR', - function removeObject(object) { + ' attribute vec3 color;', - if (object instanceof THREE.Mesh || - object instanceof THREE.PointCloud || - object instanceof THREE.Line) { + '#endif', - delete objects[object.id]; + '#ifdef USE_MORPHTARGETS', - } else if (object instanceof THREE.ImmediateRenderObject || object.immediateRenderCallback) { + ' attribute vec3 morphTarget0;', + ' attribute vec3 morphTarget1;', + ' attribute vec3 morphTarget2;', + ' attribute vec3 morphTarget3;', - removeInstances(objectsImmediate, object); + ' #ifdef USE_MORPHNORMALS', - } + ' attribute vec3 morphNormal0;', + ' attribute vec3 morphNormal1;', + ' attribute vec3 morphNormal2;', + ' attribute vec3 morphNormal3;', - delete object.__webglInit; - delete object._modelViewMatrix; - delete object._normalMatrix; + ' #else', - delete object.__webglActive; + ' attribute vec3 morphTarget4;', + ' attribute vec3 morphTarget5;', + ' attribute vec3 morphTarget6;', + ' attribute vec3 morphTarget7;', - } + ' #endif', - function removeInstances(objlist, object) { + '#endif', - for (var o = objlist.length - 1; o >= 0; o--) { + '#ifdef USE_SKINNING', - if (objlist[o].object === object) { + ' attribute vec4 skinIndex;', + ' attribute vec4 skinWeight;', - objlist.splice(o, 1); + '#endif', - } + '\n' - } + ].filter( filterEmptyLine ).join( '\n' ); - } + prefixFragment = [ - // + parameters.bumpMap || parameters.normalMap || parameters.flatShading || material.derivatives ? '#extension GL_OES_standard_derivatives : enable' : '', + parameters.logarithmicDepthBuffer && renderer.extensions.get( 'EXT_frag_depth' ) ? '#extension GL_EXT_frag_depth : enable' : '', - this.objects = objects; - this.objectsImmediate = objectsImmediate; + 'precision ' + parameters.precision + ' float;', + 'precision ' + parameters.precision + ' int;', - this.geometries = geometries; + '#define SHADER_NAME ' + material.__webglShader.name, - this.init = function (object) { + customDefines, - if (object.__webglInit === undefined) { + '#define MAX_DIR_LIGHTS ' + parameters.maxDirLights, + '#define MAX_POINT_LIGHTS ' + parameters.maxPointLights, + '#define MAX_SPOT_LIGHTS ' + parameters.maxSpotLights, + '#define MAX_HEMI_LIGHTS ' + parameters.maxHemiLights, - object.__webglInit = true; - object._modelViewMatrix = new THREE.Matrix4(); - object._normalMatrix = new THREE.Matrix3(); + '#define MAX_SHADOWS ' + parameters.maxShadows, - object.addEventListener('removed', onObjectRemoved); + parameters.alphaTest ? '#define ALPHATEST ' + parameters.alphaTest : '', - } + renderer.gammaInput ? '#define GAMMA_INPUT' : '', + renderer.gammaOutput ? '#define GAMMA_OUTPUT' : '', + '#define GAMMA_FACTOR ' + gammaFactorDefine, - if (object.__webglActive === undefined) { + ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '', + ( parameters.useFog && parameters.fogExp ) ? '#define FOG_EXP2' : '', - object.__webglActive = true; + parameters.map ? '#define USE_MAP' : '', + parameters.envMap ? '#define USE_ENVMAP' : '', + parameters.envMap ? '#define ' + envMapTypeDefine : '', + parameters.envMap ? '#define ' + envMapModeDefine : '', + parameters.envMap ? '#define ' + envMapBlendingDefine : '', + parameters.lightMap ? '#define USE_LIGHTMAP' : '', + parameters.aoMap ? '#define USE_AOMAP' : '', + parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', + parameters.bumpMap ? '#define USE_BUMPMAP' : '', + parameters.normalMap ? '#define USE_NORMALMAP' : '', + parameters.specularMap ? '#define USE_SPECULARMAP' : '', + parameters.alphaMap ? '#define USE_ALPHAMAP' : '', + parameters.vertexColors ? '#define USE_COLOR' : '', - if (object instanceof THREE.Mesh || object instanceof THREE.Line || object instanceof THREE.PointCloud) { + parameters.flatShading ? '#define FLAT_SHADED' : '', - objects[object.id] = { - id: object.id, - object: object, - material: null, - z: 0 - }; + parameters.metal ? '#define METAL' : '', + parameters.doubleSided ? '#define DOUBLE_SIDED' : '', + parameters.flipSided ? '#define FLIP_SIDED' : '', - } else if (object instanceof THREE.ImmediateRenderObject || object.immediateRenderCallback) { + parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', + parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', + parameters.shadowMapDebug ? '#define SHADOWMAP_DEBUG' : '', + parameters.pointLightShadows > 0 ? '#define POINT_LIGHT_SHADOWS' : '', - objectsImmediate.push({ - id: null, - object: object, - opaque: null, - transparent: null, - z: 0 - }); + parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', + parameters.logarithmicDepthBuffer && renderer.extensions.get( 'EXT_frag_depth' ) ? '#define USE_LOGDEPTHBUF_EXT' : '', - } + 'uniform mat4 viewMatrix;', + 'uniform vec3 cameraPosition;', - } + '\n' - }; + ].filter( filterEmptyLine ).join( '\n' ); - this.update = function (object) { + } - var geometry = geometries.get(object); + var vertexGlsl = prefixVertex + vertexShader; + var fragmentGlsl = prefixFragment + fragmentShader; - if (object.geometry instanceof THREE.DynamicGeometry) { + var glVertexShader = THREE.WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl ); + var glFragmentShader = THREE.WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl ); - geometry.updateFromObject(object); + gl.attachShader( program, glVertexShader ); + gl.attachShader( program, glFragmentShader ); - } + // Force a particular attribute to index 0. - geometry.updateFromMaterial(object.material); + if ( material.index0AttributeName !== undefined ) { - // + gl.bindAttribLocation( program, 0, material.index0AttributeName ); - if (geometry instanceof THREE.BufferGeometry) { + } else if ( parameters.morphTargets === true ) { - var attributes = geometry.attributes; - var attributesKeys = geometry.attributesKeys; + // programs with morphTargets displace position out of attribute 0 + gl.bindAttribLocation( program, 0, 'position' ); - for (var i = 0, l = attributesKeys.length; i < l; i++) { + } - var key = attributesKeys[i]; - var attribute = attributes[key]; - var bufferType = ( key === 'index' ) ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + gl.linkProgram( program ); - var data = ( attribute instanceof THREE.InterleavedBufferAttribute ) ? attribute.data : attribute; + var programLog = gl.getProgramInfoLog( program ); + var vertexLog = gl.getShaderInfoLog( glVertexShader ); + var fragmentLog = gl.getShaderInfoLog( glFragmentShader ); - if (data.buffer === undefined) { + var runnable = true; + var haveDiagnostics = true; - data.buffer = gl.createBuffer(); - gl.bindBuffer(bufferType, data.buffer); + if ( gl.getProgramParameter( program, gl.LINK_STATUS ) === false ) { - var usage = gl.STATIC_DRAW; + runnable = false; - if (data instanceof THREE.DynamicBufferAttribute - || ( data instanceof THREE.InstancedBufferAttribute && data.dynamic === true ) - || ( data instanceof THREE.InterleavedBuffer && data.dynamic === true )) { + console.error( 'THREE.WebGLProgram: shader error: ', gl.getError(), 'gl.VALIDATE_STATUS', gl.getProgramParameter( program, gl.VALIDATE_STATUS ), 'gl.getProgramInfoLog', programLog, vertexLog, fragmentLog ); - usage = gl.DYNAMIC_DRAW; + } else if ( programLog !== '' ) { - } + console.warn( 'THREE.WebGLProgram: gl.getProgramInfoLog()', programLog ); - gl.bufferData(bufferType, data.array, usage); + } else if ( vertexLog === '' || fragmentLog === '' ) { - data.needsUpdate = false; + haveDiagnostics = false; - } else if (data.needsUpdate === true) { + } - gl.bindBuffer(bufferType, data.buffer); + if ( haveDiagnostics ) { - if (data.updateRange === undefined || data.updateRange.count === -1) { // Not using update ranges + this.diagnostics = { - gl.bufferSubData(bufferType, 0, data.array); + runnable: runnable, + material: material, - } else if (data.updateRange.count === 0) { + programLog: programLog, - THREE.error('THREE.WebGLRenderer.updateObject: using updateRange for THREE.DynamicBufferAttribute and marked as needsUpdate but count is 0, ensure you are using set methods or updating manually.'); + vertexShader: { - } else { + log: vertexLog, + prefix: prefixVertex - gl.bufferSubData(bufferType, data.updateRange.offset * data.array.BYTES_PER_ELEMENT, - data.array.subarray(data.updateRange.offset, data.updateRange.offset + data.updateRange.count)); + }, - data.updateRange.count = 0; // reset range + fragmentShader: { - } + log: fragmentLog, + prefix: prefixFragment - data.needsUpdate = false; + } - } + }; - } + } - } + // clean up - }; + gl.deleteShader( glVertexShader ); + gl.deleteShader( glFragmentShader ); -}; + // set up caching for uniform locations -// File:src/renderers/webgl/WebGLProgram.js + var cachedUniforms; -THREE.WebGLProgram = (function () { + this.getUniforms = function() { - var programIdCount = 0; + if ( cachedUniforms === undefined ) { - function generateDefines(defines) { + cachedUniforms = fetchUniformLocations( gl, program ); - var value, chunk, chunks = []; + } - for (var d in defines) { + return cachedUniforms; - value = defines[d]; - if (value === false) continue; + }; - chunk = '#define ' + d + ' ' + value; - chunks.push(chunk); + // set up caching for attribute locations - } + var cachedAttributes; - return chunks.join('\n'); + this.getAttributes = function() { - } + if ( cachedAttributes === undefined ) { - function cacheUniformLocations(gl, program, identifiers) { + cachedAttributes = fetchAttributeLocations( gl, program ); - var uniforms = {}; + } - for (var i = 0, l = identifiers.length; i < l; i++) { + return cachedAttributes; - var id = identifiers[i]; - uniforms[id] = gl.getUniformLocation(program, id); + }; - } + // free resource - return uniforms; + this.destroy = function() { - } + gl.deleteProgram( program ); + this.program = undefined; - function cacheAttributeLocations(gl, program, identifiers) { + }; - var attributes = {}; + // DEPRECATED - for (var i = 0, l = identifiers.length; i < l; i++) { + Object.defineProperties( this, { - var id = identifiers[i]; - attributes[id] = gl.getAttribLocation(program, id); + uniforms: { + get: function() { - } + console.warn( 'THREE.WebGLProgram: .uniforms is now .getUniforms().' ); + return this.getUniforms(); - return attributes; + } + }, - } + attributes: { + get: function() { - function programArrayToString(previousValue, currentValue, index, array) { + console.warn( 'THREE.WebGLProgram: .attributes is now .getAttributes().' ); + return this.getAttributes(); - if (currentValue !== '' && currentValue !== undefined && currentValue !== null) { + } + } - return previousValue + currentValue + '\n'; + } ); - } - return previousValue; - } + // - return function (renderer, code, material, parameters) { + this.id = programIdCount ++; + this.code = code; + this.usedTimes = 1; + this.program = program; + this.vertexShader = glVertexShader; + this.fragmentShader = glFragmentShader; - var gl = renderer.context; + return this; - var defines = material.defines; - var uniforms = material.__webglShader.uniforms; - var attributes = material.attributes; + }; - var vertexShader = material.__webglShader.vertexShader; - var fragmentShader = material.__webglShader.fragmentShader; +} )(); - var index0AttributeName = material.index0AttributeName; +// File:src/renderers/webgl/WebGLPrograms.js - if (index0AttributeName === undefined && parameters.morphTargets === true) { +THREE.WebGLPrograms = function ( renderer, capabilities ) { - // programs with morphTargets displace position out of attribute 0 + var programs = []; - index0AttributeName = 'position'; + var shaderIDs = { + MeshDepthMaterial: 'depth', + MeshNormalMaterial: 'normal', + MeshBasicMaterial: 'basic', + MeshLambertMaterial: 'lambert', + MeshPhongMaterial: 'phong', + LineBasicMaterial: 'basic', + LineDashedMaterial: 'dashed', + PointsMaterial: 'points' + }; - } + var parameterNames = [ + "precision", "supportsVertexTextures", "map", "envMap", "envMapMode", + "lightMap", "aoMap", "emissiveMap", "bumpMap", "normalMap", "specularMap", + "alphaMap", "combine", "vertexColors", "fog", "useFog", "fogExp", + "flatShading", "sizeAttenuation", "logarithmicDepthBuffer", "skinning", + "maxBones", "useVertexTexture", "morphTargets", "morphNormals", + "maxMorphTargets", "maxMorphNormals", "maxDirLights", "maxPointLights", + "maxSpotLights", "maxHemiLights", "maxShadows", "shadowMapEnabled", "pointLightShadows", + "shadowMapType", "shadowMapDebug", "alphaTest", "metal", "doubleSided", + "flipSided" + ]; - var shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC'; - if (parameters.shadowMapType === THREE.PCFShadowMap) { + function allocateBones ( object ) { - shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF'; + if ( capabilities.floatVertexTextures && object && object.skeleton && object.skeleton.useVertexTexture ) { - } else if (parameters.shadowMapType === THREE.PCFSoftShadowMap) { + return 1024; - shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT'; + } 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 envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; - var envMapModeDefine = 'ENVMAP_MODE_REFLECTION'; - var envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY'; + var nVertexUniforms = capabilities.maxVertexUniforms; + var nVertexMatrices = Math.floor( ( nVertexUniforms - 20 ) / 4 ); - if (parameters.envMap) { + var maxBones = nVertexMatrices; - switch (material.envMap.mapping) { + if ( object !== undefined && object instanceof THREE.SkinnedMesh ) { - case THREE.CubeReflectionMapping: - case THREE.CubeRefractionMapping: - envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; - break; + maxBones = Math.min( object.skeleton.bones.length, maxBones ); - case THREE.EquirectangularReflectionMapping: - case THREE.EquirectangularRefractionMapping: - envMapTypeDefine = 'ENVMAP_TYPE_EQUIREC'; - break; + if ( maxBones < object.skeleton.bones.length ) { - case THREE.SphericalReflectionMapping: - envMapTypeDefine = 'ENVMAP_TYPE_SPHERE'; - break; + console.warn( 'WebGLRenderer: too many bones - ' + object.skeleton.bones.length + ', this GPU supports just ' + maxBones + ' (try OpenGL instead of ANGLE)' ); - } + } - switch (material.envMap.mapping) { + } - case THREE.CubeRefractionMapping: - case THREE.EquirectangularRefractionMapping: - envMapModeDefine = 'ENVMAP_MODE_REFRACTION'; - break; + return maxBones; - } + } - switch (material.combine) { + } - case THREE.MultiplyOperation: - envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY'; - break; + function allocateLights( lights ) { - case THREE.MixOperation: - envMapBlendingDefine = 'ENVMAP_BLENDING_MIX'; - break; + var dirLights = 0; + var pointLights = 0; + var spotLights = 0; + var hemiLights = 0; - case THREE.AddOperation: - envMapBlendingDefine = 'ENVMAP_BLENDING_ADD'; - break; + for ( var l = 0, ll = lights.length; l < ll; l ++ ) { - } + var light = lights[ l ]; - } + if ( light.onlyShadow || light.visible === false ) continue; - var gammaFactorDefine = ( renderer.gammaFactor > 0 ) ? renderer.gammaFactor : 1.0; + if ( light instanceof THREE.DirectionalLight ) dirLights ++; + if ( light instanceof THREE.PointLight ) pointLights ++; + if ( light instanceof THREE.SpotLight ) spotLights ++; + if ( light instanceof THREE.HemisphereLight ) hemiLights ++; - // THREE.log( 'building new program ' ); + } - // + return { 'directional': dirLights, 'point': pointLights, 'spot': spotLights, 'hemi': hemiLights }; - var customDefines = generateDefines(defines); + } - // + function allocateShadows( lights ) { - var program = gl.createProgram(); + var maxShadows = 0; + var pointLightShadows = 0; - var prefix_vertex, prefix_fragment; + for ( var l = 0, ll = lights.length; l < ll; l ++ ) { - if (material instanceof THREE.RawShaderMaterial) { + var light = lights[ l ]; - prefix_vertex = ''; - prefix_fragment = ''; + if ( ! light.castShadow ) continue; - } else { + if ( light instanceof THREE.SpotLight || light instanceof THREE.DirectionalLight ) maxShadows ++; + if ( light instanceof THREE.PointLight ) { - prefix_vertex = [ + maxShadows ++; + pointLightShadows ++; - 'precision ' + parameters.precision + ' float;', - 'precision ' + parameters.precision + ' int;', + } - customDefines, + } - parameters.supportsVertexTextures ? '#define VERTEX_TEXTURES' : '', + return { 'maxShadows': maxShadows, 'pointLightShadows': pointLightShadows }; - renderer.gammaInput ? '#define GAMMA_INPUT' : '', - renderer.gammaOutput ? '#define GAMMA_OUTPUT' : '', - '#define GAMMA_FACTOR ' + gammaFactorDefine, + } - '#define MAX_DIR_LIGHTS ' + parameters.maxDirLights, - '#define MAX_POINT_LIGHTS ' + parameters.maxPointLights, - '#define MAX_SPOT_LIGHTS ' + parameters.maxSpotLights, - '#define MAX_HEMI_LIGHTS ' + parameters.maxHemiLights, + this.getParameters = function ( material, lights, fog, object ) { - '#define MAX_SHADOWS ' + parameters.maxShadows, + var shaderID = shaderIDs[ material.type ]; + // heuristics to create shader parameters according to lights in the scene + // (not to blow over maxLights budget) - '#define MAX_BONES ' + parameters.maxBones, + var maxLightCount = allocateLights( lights ); + var allocatedShadows = allocateShadows( lights ); + var maxBones = allocateBones( object ); + var precision = renderer.getPrecision(); - parameters.map ? '#define USE_MAP' : '', - parameters.envMap ? '#define USE_ENVMAP' : '', - parameters.envMap ? '#define ' + envMapModeDefine : '', - parameters.lightMap ? '#define USE_LIGHTMAP' : '', - parameters.aoMap ? '#define USE_AOMAP' : '', - parameters.bumpMap ? '#define USE_BUMPMAP' : '', - parameters.normalMap ? '#define USE_NORMALMAP' : '', - parameters.specularMap ? '#define USE_SPECULARMAP' : '', - parameters.alphaMap ? '#define USE_ALPHAMAP' : '', - parameters.vertexColors ? '#define USE_COLOR' : '', + if ( material.precision !== null ) { - parameters.flatShading ? '#define FLAT_SHADED' : '', + precision = capabilities.getMaxPrecision( material.precision ); - parameters.skinning ? '#define USE_SKINNING' : '', - parameters.useVertexTexture ? '#define BONE_TEXTURE' : '', + if ( precision !== material.precision ) { - parameters.morphTargets ? '#define USE_MORPHTARGETS' : '', - parameters.morphNormals ? '#define USE_MORPHNORMALS' : '', - parameters.doubleSided ? '#define DOUBLE_SIDED' : '', - parameters.flipSided ? '#define FLIP_SIDED' : '', + console.warn( 'THREE.WebGLRenderer.initMaterial:', material.precision, 'not supported, using', precision, 'instead.' ); - parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', - parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', - parameters.shadowMapDebug ? '#define SHADOWMAP_DEBUG' : '', - parameters.shadowMapCascade ? '#define SHADOWMAP_CASCADE' : '', + } - parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '', + } - parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', - //renderer.glExtensionFragDepth ? '#define USE_LOGDEPTHBUF_EXT' : '', + var parameters = { + shaderID: shaderID, - 'uniform mat4 modelMatrix;', - 'uniform mat4 modelViewMatrix;', - 'uniform mat4 projectionMatrix;', - 'uniform mat4 viewMatrix;', - 'uniform mat3 normalMatrix;', - 'uniform vec3 cameraPosition;', + precision: precision, + supportsVertexTextures: capabilities.vertexTextures, - 'attribute vec3 position;', - 'attribute vec3 normal;', - 'attribute vec2 uv;', + map: !! material.map, + envMap: !! material.envMap, + envMapMode: material.envMap && material.envMap.mapping, + lightMap: !! material.lightMap, + aoMap: !! material.aoMap, + emissiveMap: !! material.emissiveMap, + bumpMap: !! material.bumpMap, + normalMap: !! material.normalMap, + displacementMap: !! material.displacementMap, + specularMap: !! material.specularMap, + alphaMap: !! material.alphaMap, - '#ifdef USE_COLOR', + combine: material.combine, - ' attribute vec3 color;', + vertexColors: material.vertexColors, - '#endif', + fog: fog, + useFog: material.fog, + fogExp: fog instanceof THREE.FogExp2, - '#ifdef USE_MORPHTARGETS', + flatShading: material.shading === THREE.FlatShading, - ' attribute vec3 morphTarget0;', - ' attribute vec3 morphTarget1;', - ' attribute vec3 morphTarget2;', - ' attribute vec3 morphTarget3;', + sizeAttenuation: material.sizeAttenuation, + logarithmicDepthBuffer: capabilities.logarithmicDepthBuffer, - ' #ifdef USE_MORPHNORMALS', + skinning: material.skinning, + maxBones: maxBones, + useVertexTexture: capabilities.floatVertexTextures && object && object.skeleton && object.skeleton.useVertexTexture, - ' attribute vec3 morphNormal0;', - ' attribute vec3 morphNormal1;', - ' attribute vec3 morphNormal2;', - ' attribute vec3 morphNormal3;', + morphTargets: material.morphTargets, + morphNormals: material.morphNormals, + maxMorphTargets: renderer.maxMorphTargets, + maxMorphNormals: renderer.maxMorphNormals, - ' #else', + maxDirLights: maxLightCount.directional, + maxPointLights: maxLightCount.point, + maxSpotLights: maxLightCount.spot, + maxHemiLights: maxLightCount.hemi, - ' attribute vec3 morphTarget4;', - ' attribute vec3 morphTarget5;', - ' attribute vec3 morphTarget6;', - ' attribute vec3 morphTarget7;', + maxShadows: allocatedShadows.maxShadows, + pointLightShadows: allocatedShadows.pointLightShadows, + shadowMapEnabled: renderer.shadowMap.enabled && object.receiveShadow && allocatedShadows.maxShadows > 0, + shadowMapType: renderer.shadowMap.type, + shadowMapDebug: renderer.shadowMap.debug, - ' #endif', + alphaTest: material.alphaTest, + metal: material.metal, + doubleSided: material.side === THREE.DoubleSide, + flipSided: material.side === THREE.BackSide - '#endif', + }; - '#ifdef USE_SKINNING', + return parameters; - ' attribute vec4 skinIndex;', - ' attribute vec4 skinWeight;', + }; - '#endif', + this.getProgramCode = function ( material, parameters ) { - '' + var chunks = []; - ].reduce(programArrayToString, ''); + if ( parameters.shaderID ) { - prefix_fragment = [ + chunks.push( parameters.shaderID ); - 'precision ' + parameters.precision + ' float;', - 'precision ' + parameters.precision + ' int;', + } else { - ( parameters.bumpMap || parameters.normalMap || parameters.flatShading ) ? '#extension GL_OES_standard_derivatives : enable' : '', + chunks.push( material.fragmentShader ); + chunks.push( material.vertexShader ); - 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, + if ( material.defines !== undefined ) { - '#define MAX_SHADOWS ' + parameters.maxShadows, + for ( var name in material.defines ) { - parameters.alphaTest ? '#define ALPHATEST ' + parameters.alphaTest : '', + chunks.push( name ); + chunks.push( material.defines[ name ] ); - renderer.gammaInput ? '#define GAMMA_INPUT' : '', - renderer.gammaOutput ? '#define GAMMA_OUTPUT' : '', - '#define GAMMA_FACTOR ' + gammaFactorDefine, + } - ( 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.envMap ? '#define ' + envMapTypeDefine : '', - parameters.envMap ? '#define ' + envMapModeDefine : '', - parameters.envMap ? '#define ' + envMapBlendingDefine : '', - parameters.lightMap ? '#define USE_LIGHTMAP' : '', - parameters.aoMap ? '#define USE_AOMAP' : '', - parameters.bumpMap ? '#define USE_BUMPMAP' : '', - parameters.normalMap ? '#define USE_NORMALMAP' : '', - parameters.specularMap ? '#define USE_SPECULARMAP' : '', - parameters.alphaMap ? '#define USE_ALPHAMAP' : '', - parameters.vertexColors ? '#define USE_COLOR' : '', + for ( var i = 0; i < parameterNames.length; i ++ ) { - parameters.flatShading ? '#define FLAT_SHADED' : '', + var parameterName = parameterNames[ i ]; + chunks.push( parameterName ); + chunks.push( parameters[ parameterName ] ); - parameters.metal ? '#define METAL' : '', - 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' : '', + return chunks.join(); - parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', - //renderer.glExtensionFragDepth ? '#define USE_LOGDEPTHBUF_EXT' : '', + }; - 'uniform mat4 viewMatrix;', - 'uniform vec3 cameraPosition;', - '' + this.acquireProgram = function ( material, parameters, code ) { - ].reduce(programArrayToString, ''); + var program; - } + // Check if code has been already compiled + for ( var p = 0, pl = programs.length; p < pl; p ++ ) { - var glVertexShader = new THREE.WebGLShader(gl, gl.VERTEX_SHADER, prefix_vertex + vertexShader); - var glFragmentShader = new THREE.WebGLShader(gl, gl.FRAGMENT_SHADER, prefix_fragment + fragmentShader); + var programInfo = programs[ p ]; - gl.attachShader(program, glVertexShader); - gl.attachShader(program, glFragmentShader); + if ( programInfo.code === code ) { - if (index0AttributeName !== undefined) { + program = programInfo; + ++ program.usedTimes; - // 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 + break; - gl.bindAttribLocation(program, 0, index0AttributeName); + } - } + } - gl.linkProgram(program); + if ( program === undefined ) { - var programLogInfo = gl.getProgramInfoLog(program); - var vertexErrorLogInfo = gl.getShaderInfoLog(glVertexShader); - var fragmentErrorLogInfo = gl.getShaderInfoLog(glFragmentShader); + program = new THREE.WebGLProgram( renderer, code, material, parameters ); + programs.push( program ); - if (gl.getProgramParameter(program, gl.LINK_STATUS) === false) { + } - THREE.error('THREE.WebGLProgram: shader error: ', gl.getError(), 'gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS), 'gl.getProgramInfoLog', programLogInfo, vertexErrorLogInfo, fragmentErrorLogInfo); + return program; - } + }; - if (programLogInfo !== '') { + this.releaseProgram = function( program ) { - THREE.warn('THREE.WebGLProgram: gl.getProgramInfoLog()', programLogInfo); + if ( -- program.usedTimes === 0 ) { - } + // Remove from unordered set + var i = programs.indexOf( program ); + programs[ i ] = programs[ programs.length - 1 ]; + programs.pop(); - // clean up + // Free WebGL resources + program.destroy(); - gl.deleteShader(glVertexShader); - gl.deleteShader(glFragmentShader); + } - // cache uniform locations + }; - var identifiers = [ + // Exposed for resource monitoring & error feedback via renderer.info: + this.programs = programs; - 'viewMatrix', - 'modelViewMatrix', - 'projectionMatrix', - 'normalMatrix', - 'modelMatrix', - 'cameraPosition', - 'morphTargetInfluences', - 'bindMatrix', - 'bindMatrixInverse' +}; - ]; +// File:src/renderers/webgl/WebGLProperties.js - if (parameters.useVertexTexture) { +/** +* @author fordacious / fordacious.github.io +*/ - identifiers.push('boneTexture', 'boneTextureWidth', 'boneTextureHeight'); +THREE.WebGLProperties = function () { - } else { + var properties = {}; - identifiers.push('boneGlobalMatrices'); + this.get = function ( object ) { - } + var uuid = object.uuid; + var map = properties[ uuid ]; - if (parameters.logarithmicDepthBuffer) { + if ( map === undefined ) { - identifiers.push('logDepthBufFC'); + map = {}; + properties[ uuid ] = map; - } + } - for (var u in uniforms) { + return map; - identifiers.push(u); + }; - } + this.delete = function ( object ) { - this.uniforms = cacheUniformLocations(gl, program, identifiers); + delete properties[ object.uuid ]; - // cache attributes locations + }; - identifiers = [ + this.clear = function () { - 'position', - 'normal', - 'uv', - 'uv2', - 'tangent', - 'color', - 'skinIndex', - 'skinWeight', - 'lineDistance' + properties = {}; - ]; + }; - for (var i = 0; i < parameters.maxMorphTargets; i++) { +}; - identifiers.push('morphTarget' + i); +// File:src/renderers/webgl/WebGLShader.js - } +THREE.WebGLShader = ( function () { - for (var i = 0; i < parameters.maxMorphNormals; i++) { + var addLineNumbers = function ( string ) { - identifiers.push('morphNormal' + i); + var lines = string.split( '\n' ); - } + for ( var i = 0; i < lines.length; i ++ ) { - for (var a in attributes) { + lines[ i ] = ( i + 1 ) + ': ' + lines[ i ]; - identifiers.push(a); + } - } + return lines.join( '\n' ); - this.attributes = cacheAttributeLocations(gl, program, identifiers); - this.attributesKeys = Object.keys(this.attributes); + }; - // + return function WebGLShader( gl, type, string ) { - this.id = programIdCount++; - this.code = code; - this.usedTimes = 1; - this.program = program; - this.vertexShader = glVertexShader; - this.fragmentShader = glFragmentShader; + var shader = gl.createShader( type ); - return this; + gl.shaderSource( shader, string ); + gl.compileShader( shader ); - }; + if ( gl.getShaderParameter( shader, gl.COMPILE_STATUS ) === false ) { -})(); + console.error( 'THREE.WebGLShader: Shader couldn\'t compile.' ); -// File:src/renderers/webgl/WebGLShader.js + } -THREE.WebGLShader = (function () { + if ( gl.getShaderInfoLog( shader ) !== '' ) { - var addLineNumbers = function (string) { + console.warn( 'THREE.WebGLShader: gl.getShaderInfoLog()', type === gl.VERTEX_SHADER ? 'vertex' : 'fragment', gl.getShaderInfoLog( shader ), addLineNumbers( string ) ); - var lines = string.split('\n'); + } - for (var i = 0; i < lines.length; i++) { + // --enable-privileged-webgl-extension + // console.log( type, gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) ); - lines[i] = ( i + 1 ) + ': ' + lines[i]; + return shader; - } + }; - return lines.join('\n'); +} )(); - }; +// File:src/renderers/webgl/WebGLShadowMap.js - return function (gl, type, string) { +/** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + */ - var shader = gl.createShader(type); +THREE.WebGLShadowMap = function ( _renderer, _lights, _objects ) { - gl.shaderSource(shader, string); - gl.compileShader(shader); + var _gl = _renderer.context, + _state = _renderer.state, + _frustum = new THREE.Frustum(), + _projScreenMatrix = new THREE.Matrix4(), - if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) === false) { + _min = new THREE.Vector3(), + _max = new THREE.Vector3(), - THREE.error('THREE.WebGLShader: Shader couldn\'t compile.'); + _lookTarget = new THREE.Vector3(), + _lightPositionWorld = new THREE.Vector3(), - } + _renderList = [], - if (gl.getShaderInfoLog(shader) !== '') { + _MorphingFlag = 1, + _SkinningFlag = 2, - THREE.warn('THREE.WebGLShader: gl.getShaderInfoLog()', type === gl.VERTEX_SHADER ? 'vertex' : 'fragment', gl.getShaderInfoLog(shader), addLineNumbers(string)); + _NumberOfMaterialVariants = ( _MorphingFlag | _SkinningFlag ) + 1, - } + _depthMaterials = new Array( _NumberOfMaterialVariants ), + _distanceMaterials = new Array( _NumberOfMaterialVariants ); - // --enable-privileged-webgl-extension - // THREE.log( type, gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) ); + var cubeDirections = [ + new THREE.Vector3( 1, 0, 0 ), new THREE.Vector3( - 1, 0, 0 ), new THREE.Vector3( 0, 0, 1 ), + new THREE.Vector3( 0, 0, - 1 ), new THREE.Vector3( 0, 1, 0 ), new THREE.Vector3( 0, - 1, 0 ) + ]; - return shader; + var cubeUps = [ + new THREE.Vector3( 0, 1, 0 ), new THREE.Vector3( 0, 1, 0 ), new THREE.Vector3( 0, 1, 0 ), + new THREE.Vector3( 0, 1, 0 ), new THREE.Vector3( 0, 0, 1 ), new THREE.Vector3( 0, 0, - 1 ) + ]; - }; + var cube2DViewPorts = [ + new THREE.Vector4(), new THREE.Vector4(), new THREE.Vector4(), + new THREE.Vector4(), new THREE.Vector4(), new THREE.Vector4() + ]; -})(); + var _vector4 = new THREE.Vector4(); -// File:src/renderers/webgl/WebGLShadowMap.js + // init -/** - * @author alteredq / http://alteredqualia.com/ - * @author mrdoob / http://mrdoob.com/ - */ + var depthShader = THREE.ShaderLib[ "depthRGBA" ]; + var depthUniforms = THREE.UniformsUtils.clone( depthShader.uniforms ); -THREE.WebGLShadowMap = function (_renderer, _lights, _objects) { + var distanceShader = THREE.ShaderLib[ "distanceRGBA" ]; + var distanceUniforms = THREE.UniformsUtils.clone( distanceShader.uniforms ); - var _gl = _renderer.context, - _frustum = new THREE.Frustum(), - _projScreenMatrix = new THREE.Matrix4(), + for ( var i = 0; i !== _NumberOfMaterialVariants; ++ i ) { - _min = new THREE.Vector3(), - _max = new THREE.Vector3(), + var useMorphing = ( i & _MorphingFlag ) !== 0; + var useSkinning = ( i & _SkinningFlag ) !== 0; - _webglObjects = _objects.objects, - _webglObjectsImmediate = _objects.objectsImmediate, - _matrixPosition = new THREE.Vector3(), + var depthMaterial = new THREE.ShaderMaterial( { + uniforms: depthUniforms, + vertexShader: depthShader.vertexShader, + fragmentShader: depthShader.fragmentShader, + morphTargets: useMorphing, + skinning: useSkinning + } ); - _renderList = []; + depthMaterial._shadowPass = true; - // init + _depthMaterials[ i ] = depthMaterial; - var depthShader = THREE.ShaderLib["depthRGBA"]; - var depthUniforms = THREE.UniformsUtils.clone(depthShader.uniforms); + var distanceMaterial = new THREE.ShaderMaterial( { + uniforms: distanceUniforms, + vertexShader: distanceShader.vertexShader, + fragmentShader: distanceShader.fragmentShader, + morphTargets: useMorphing, + skinning: useSkinning + } ); - var _depthMaterial = new THREE.ShaderMaterial({ - uniforms: depthUniforms, - vertexShader: depthShader.vertexShader, - fragmentShader: depthShader.fragmentShader - }); + distanceMaterial._shadowPass = true; - var _depthMaterialMorph = new THREE.ShaderMaterial({ - uniforms: depthUniforms, - vertexShader: depthShader.vertexShader, - fragmentShader: depthShader.fragmentShader, - morphTargets: true - }); + _distanceMaterials[ i ] = distanceMaterial; - var _depthMaterialSkin = new THREE.ShaderMaterial({ - uniforms: depthUniforms, - vertexShader: depthShader.vertexShader, - fragmentShader: depthShader.fragmentShader, - skinning: true - }); + } - var _depthMaterialMorphSkin = new THREE.ShaderMaterial({ - uniforms: depthUniforms, - vertexShader: depthShader.vertexShader, - fragmentShader: depthShader.fragmentShader, - morphTargets: true, - skinning: true - }); + // - _depthMaterial._shadowPass = true; - _depthMaterialMorph._shadowPass = true; - _depthMaterialSkin._shadowPass = true; - _depthMaterialMorphSkin._shadowPass = true; + var scope = this; - // + this.enabled = false; - var scope = this; + this.autoUpdate = true; + this.needsUpdate = false; - this.enabled = false; - this.type = THREE.PCFShadowMap; - this.cullFace = THREE.CullFaceFront; - this.debug = false; - this.cascade = false; + this.type = THREE.PCFShadowMap; + this.cullFace = THREE.CullFaceFront; - this.render = function (scene, camera) { + this.render = function ( scene ) { - if (scope.enabled === false) return; + var faceCount, isPointLight; - var i, il, j, jl, n, + if ( scope.enabled === false ) return; + if ( scope.autoUpdate === false && scope.needsUpdate === false ) return; - shadowMap, shadowMatrix, shadowCamera, - buffer, material, - webglObject, object, light, + // set GL state for depth map - lights = [], - k = 0, + _gl.clearColor( 1, 1, 1, 1 ); + _state.disable( _gl.BLEND ); - fog = null; + _state.enable( _gl.CULL_FACE ); + _gl.frontFace( _gl.CCW ); - // set GL state for depth map + if ( scope.cullFace === THREE.CullFaceFront ) { - _gl.clearColor(1, 1, 1, 1); - _gl.disable(_gl.BLEND); + _gl.cullFace( _gl.FRONT ); - _gl.enable(_gl.CULL_FACE); - _gl.frontFace(_gl.CCW); + } else { - if (scope.cullFace === THREE.CullFaceFront) { + _gl.cullFace( _gl.BACK ); - _gl.cullFace(_gl.FRONT); + } - } else { + _state.setDepthTest( true ); - _gl.cullFace(_gl.BACK); + // render depth map - } + for ( var i = 0, il = _lights.length; i < il; i ++ ) { - _renderer.state.setDepthTest(true); + var light = _lights[ i ]; - // preprocess lights - // - skip lights that are not casting shadows - // - create virtual lights for cascaded shadow maps + if ( light instanceof THREE.PointLight ) { - for (i = 0, il = _lights.length; i < il; i++) { + faceCount = 6; + isPointLight = true; - light = _lights[i]; + var vpWidth = light.shadowMapWidth / 4.0; + var vpHeight = light.shadowMapHeight / 2.0; - if (!light.castShadow) continue; + // These viewports map a cube-map onto a 2D texture with the + // following orientation: + // + // xzXZ + // y Y + // + // X - Positive x direction + // x - Negative x direction + // Y - Positive y direction + // y - Negative y direction + // Z - Positive z direction + // z - Negative z direction - if (( light instanceof THREE.DirectionalLight ) && light.shadowCascade) { + // positive X + cube2DViewPorts[ 0 ].set( vpWidth * 2, vpHeight, vpWidth, vpHeight ); + // negative X + cube2DViewPorts[ 1 ].set( 0, vpHeight, vpWidth, vpHeight ); + // positive Z + cube2DViewPorts[ 2 ].set( vpWidth * 3, vpHeight, vpWidth, vpHeight ); + // negative Z + cube2DViewPorts[ 3 ].set( vpWidth, vpHeight, vpWidth, vpHeight ); + // positive Y + cube2DViewPorts[ 4 ].set( vpWidth * 3, 0, vpWidth, vpHeight ); + // negative Y + cube2DViewPorts[ 5 ].set( vpWidth, 0, vpWidth, vpHeight ); - for (n = 0; n < light.shadowCascadeCount; n++) { + } else { - var virtualLight; + faceCount = 1; + isPointLight = false; - if (!light.shadowCascadeArray[n]) { + } - virtualLight = createVirtualLight(light, n); - virtualLight.originalCamera = camera; + if ( ! light.castShadow ) continue; - var gyro = new THREE.Gyroscope(); - gyro.position.copy(light.shadowCascadeOffset); + if ( ! light.shadowMap ) { - gyro.add(virtualLight); - gyro.add(virtualLight.target); + var shadowFilter = THREE.LinearFilter; - camera.add(gyro); + if ( scope.type === THREE.PCFSoftShadowMap ) { - light.shadowCascadeArray[n] = virtualLight; + shadowFilter = THREE.NearestFilter; - //THREE.log( "Created virtualLight", virtualLight ); + } - } else { + var pars = { minFilter: shadowFilter, magFilter: shadowFilter, format: THREE.RGBAFormat }; - virtualLight = light.shadowCascadeArray[n]; + light.shadowMap = new THREE.WebGLRenderTarget( light.shadowMapWidth, light.shadowMapHeight, pars ); + light.shadowMapSize = new THREE.Vector2( light.shadowMapWidth, light.shadowMapHeight ); - } + light.shadowMatrix = new THREE.Matrix4(); - updateVirtualLight(light, n); + } - lights[k] = virtualLight; - k++; + if ( ! light.shadowCamera ) { - } + if ( light instanceof THREE.SpotLight ) { - } else { + light.shadowCamera = new THREE.PerspectiveCamera( light.shadowCameraFov, light.shadowMapWidth / light.shadowMapHeight, light.shadowCameraNear, light.shadowCameraFar ); - lights[k] = light; - k++; + } else if ( light instanceof THREE.DirectionalLight ) { - } + light.shadowCamera = new THREE.OrthographicCamera( light.shadowCameraLeft, light.shadowCameraRight, light.shadowCameraTop, light.shadowCameraBottom, light.shadowCameraNear, light.shadowCameraFar ); - } + } else { - // render depth map + light.shadowCamera = new THREE.PerspectiveCamera( light.shadowCameraFov, 1.0, light.shadowCameraNear, light.shadowCameraFar ); - for (i = 0, il = lights.length; i < il; i++) { + } - light = lights[i]; + scene.add( light.shadowCamera ); - if (!light.shadowMap) { + if ( scene.autoUpdate === true ) scene.updateMatrixWorld(); - var shadowFilter = THREE.LinearFilter; + } - if (scope.type === THREE.PCFSoftShadowMap) { + if ( light.shadowCameraVisible && ! light.cameraHelper ) { - shadowFilter = THREE.NearestFilter; + light.cameraHelper = new THREE.CameraHelper( light.shadowCamera ); + scene.add( light.cameraHelper ); - } + } - var pars = {minFilter: shadowFilter, magFilter: shadowFilter, format: THREE.RGBAFormat}; + var shadowMap = light.shadowMap; + var shadowMatrix = light.shadowMatrix; + var shadowCamera = light.shadowCamera; - light.shadowMap = new THREE.WebGLRenderTarget(light.shadowMapWidth, light.shadowMapHeight, pars); - light.shadowMapSize = new THREE.Vector2(light.shadowMapWidth, light.shadowMapHeight); + _lightPositionWorld.setFromMatrixPosition( light.matrixWorld ); + shadowCamera.position.copy( _lightPositionWorld ); - light.shadowMatrix = new THREE.Matrix4(); + // save the existing viewport so it can be restored later + _renderer.getViewport( _vector4 ); - } + _renderer.setRenderTarget( shadowMap ); + _renderer.clear(); - if (!light.shadowCamera) { + // render shadow map for each cube face (if omni-directional) or + // run a single pass if not - if (light instanceof THREE.SpotLight) { + for ( var face = 0; face < faceCount; face ++ ) { - light.shadowCamera = new THREE.PerspectiveCamera(light.shadowCameraFov, light.shadowMapWidth / light.shadowMapHeight, light.shadowCameraNear, light.shadowCameraFar); + if ( isPointLight ) { - } else if (light instanceof THREE.DirectionalLight) { + _lookTarget.copy( shadowCamera.position ); + _lookTarget.add( cubeDirections[ face ] ); + shadowCamera.up.copy( cubeUps[ face ] ); + shadowCamera.lookAt( _lookTarget ); + var vpDimensions = cube2DViewPorts[ face ]; + _renderer.setViewport( vpDimensions.x, vpDimensions.y, vpDimensions.z, vpDimensions.w ); - light.shadowCamera = new THREE.OrthographicCamera(light.shadowCameraLeft, light.shadowCameraRight, light.shadowCameraTop, light.shadowCameraBottom, light.shadowCameraNear, light.shadowCameraFar); + } else { - } else { + _lookTarget.setFromMatrixPosition( light.target.matrixWorld ); + shadowCamera.lookAt( _lookTarget ); - THREE.error("THREE.ShadowMapPlugin: Unsupported light type for shadow", light); - continue; + } - } + shadowCamera.updateMatrixWorld(); + shadowCamera.matrixWorldInverse.getInverse( shadowCamera.matrixWorld ); - scene.add(light.shadowCamera); + if ( light.cameraHelper ) light.cameraHelper.visible = light.shadowCameraVisible; + if ( light.shadowCameraVisible ) light.cameraHelper.update(); - if (scene.autoUpdate === true) scene.updateMatrixWorld(); + // 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 + ); - if (light.shadowCameraVisible && !light.cameraHelper) { + shadowMatrix.multiply( shadowCamera.projectionMatrix ); + shadowMatrix.multiply( shadowCamera.matrixWorldInverse ); - light.cameraHelper = new THREE.CameraHelper(light.shadowCamera); - scene.add(light.cameraHelper); + // update camera matrices and frustum - } + _projScreenMatrix.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse ); + _frustum.setFromMatrix( _projScreenMatrix ); - if (light.isVirtual && virtualLight.originalCamera == camera) { + // set object matrices & frustum culling - updateShadowCamera(camera, light); + _renderList.length = 0; - } + projectObject( scene, shadowCamera ); - shadowMap = light.shadowMap; - shadowMatrix = light.shadowMatrix; - shadowCamera = light.shadowCamera; + // render shadow map + // render regular objects - // + for ( var j = 0, jl = _renderList.length; j < jl; j ++ ) { - shadowCamera.position.setFromMatrixPosition(light.matrixWorld); - _matrixPosition.setFromMatrixPosition(light.target.matrixWorld); - shadowCamera.lookAt(_matrixPosition); - shadowCamera.updateMatrixWorld(); + var object = _renderList[ j ]; + var geometry = _objects.update( object ); + var material = object.material; - shadowCamera.matrixWorldInverse.getInverse(shadowCamera.matrixWorld); + if ( material instanceof THREE.MeshFaceMaterial ) { - // + var groups = geometry.groups; + var materials = material.materials; - if (light.cameraHelper) light.cameraHelper.visible = light.shadowCameraVisible; - if (light.shadowCameraVisible) light.cameraHelper.update(); + for ( var k = 0, kl = groups.length; k < kl; k ++ ) { - // compute shadow matrix + var group = groups[ k ]; + var groupMaterial = materials[ group.materialIndex ]; - 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 - ); + if ( groupMaterial.visible === true ) { - shadowMatrix.multiply(shadowCamera.projectionMatrix); - shadowMatrix.multiply(shadowCamera.matrixWorldInverse); + var depthMaterial = getDepthMaterial( object, groupMaterial, isPointLight, _lightPositionWorld ); + _renderer.renderBufferDirect( shadowCamera, _lights, null, geometry, depthMaterial, object, group ); - // update camera matrices and frustum + } - _projScreenMatrix.multiplyMatrices(shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse); - _frustum.setFromMatrix(_projScreenMatrix); + } - // render shadow map + } else { - _renderer.setRenderTarget(shadowMap); - _renderer.clear(); + var depthMaterial = getDepthMaterial( object, material, isPointLight, _lightPositionWorld ); + _renderer.renderBufferDirect( shadowCamera, _lights, null, geometry, depthMaterial, object, null ); - // set object matrices & frustum culling + } - _renderList.length = 0; + } - projectObject(scene, scene, shadowCamera); + } + } - // render regular objects + // restore GL state - var objectMaterial, useMorphing, useSkinning; + var clearColor = _renderer.getClearColor(), + clearAlpha = _renderer.getClearAlpha(); - for (j = 0, jl = _renderList.length; j < jl; j++) { + _renderer.setClearColor( clearColor, clearAlpha ); + _state.enable( _gl.BLEND ); - webglObject = _renderList[j]; + if ( scope.cullFace === THREE.CullFaceFront ) { - object = webglObject.object; - buffer = webglObject.buffer; + _gl.cullFace( _gl.BACK ); - // 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) + _renderer.setViewport( _vector4.x, _vector4.y, _vector4.z, _vector4.w ); - objectMaterial = getObjectMaterial(object); + _renderer.resetGLState(); - useMorphing = object.geometry.morphTargets !== undefined && object.geometry.morphTargets.length > 0 && objectMaterial.morphTargets; - useSkinning = object instanceof THREE.SkinnedMesh && objectMaterial.skinning; + scope.needsUpdate = false; - if (object.customDepthMaterial) { + }; - material = object.customDepthMaterial; + function getDepthMaterial( object, material, isPointLight, lightPositionWorld ) { - } else if (useSkinning) { + var geometry = object.geometry; - material = useMorphing ? _depthMaterialMorphSkin : _depthMaterialSkin; + var newMaterial = null; - } else if (useMorphing) { + var materialVariants = _depthMaterials; + var customMaterial = object.customDepthMaterial; - material = _depthMaterialMorph; + if ( isPointLight ) { - } else { + materialVariants = _distanceMaterials; + customMaterial = object.customDistanceMaterial; - material = _depthMaterial; + } - } + if ( ! customMaterial ) { - _renderer.setMaterialFaces(objectMaterial); + var useMorphing = geometry.morphTargets !== undefined && + geometry.morphTargets.length > 0 && material.morphTargets; - if (buffer instanceof THREE.BufferGeometry) { + var useSkinning = object instanceof THREE.SkinnedMesh && material.skinning; - _renderer.renderBufferDirect(shadowCamera, _lights, fog, material, buffer, object); + var variantIndex = 0; - } else { + if ( useMorphing ) variantIndex |= _MorphingFlag; + if ( useSkinning ) variantIndex |= _SkinningFlag; - _renderer.renderBuffer(shadowCamera, _lights, fog, material, buffer, object); + newMaterial = materialVariants[ variantIndex ]; - } + } else { - } + newMaterial = customMaterial; - // set matrices and render immediate objects + } - for (j = 0, jl = _webglObjectsImmediate.length; j < jl; j++) { + newMaterial.visible = material.visible; + newMaterial.wireframe = material.wireframe; + newMaterial.wireframeLinewidth = material.wireframeLinewidth; - webglObject = _webglObjectsImmediate[j]; - object = webglObject.object; + if ( isPointLight && newMaterial.uniforms.lightPos !== undefined ) { - if (object.visible && object.castShadow) { + newMaterial.uniforms.lightPos.value.copy( lightPositionWorld ); - object._modelViewMatrix.multiplyMatrices(shadowCamera.matrixWorldInverse, object.matrixWorld); + } - _renderer.renderImmediateObject(shadowCamera, _lights, fog, _depthMaterial, object); + return newMaterial; - } + } - } + function projectObject( object, camera ) { - } + if ( object.visible === false ) return; - // restore GL state + if ( object instanceof THREE.Mesh || object instanceof THREE.Line || object instanceof THREE.Points ) { - var clearColor = _renderer.getClearColor(), - clearAlpha = _renderer.getClearAlpha(); + if ( object.castShadow && ( object.frustumCulled === false || _frustum.intersectsObject( object ) === true ) ) { - _gl.clearColor(clearColor.r, clearColor.g, clearColor.b, clearAlpha); - _gl.enable(_gl.BLEND); + var material = object.material; - if (scope.cullFace === THREE.CullFaceFront) { + if ( material.visible === true ) { - _gl.cullFace(_gl.BACK); + object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + _renderList.push( object ); - } + } - _renderer.resetGLState(); + } - }; + } - function projectObject(scene, object, shadowCamera) { + var children = object.children; - if (object.visible) { + for ( var i = 0, l = children.length; i < l; i ++ ) { - var webglObjects = _webglObjects[object.id]; + projectObject( children[ i ], camera ); - if (webglObjects && object.castShadow && (object.frustumCulled === false || _frustum.intersectsObject(object) === true)) { + } - for (var i = 0, l = webglObjects.length; i < l; i++) { + } - var webglObject = webglObjects[i]; +}; - object._modelViewMatrix.multiplyMatrices(shadowCamera.matrixWorldInverse, object.matrixWorld); - _renderList.push(webglObject); +// File:src/renderers/webgl/WebGLState.js - } +/** +* @author mrdoob / http://mrdoob.com/ +*/ - } +THREE.WebGLState = function ( gl, extensions, paramThreeToGL ) { - for (var i = 0, l = object.children.length; i < l; i++) { + var _this = this; - projectObject(scene, object.children[i], shadowCamera); + var newAttributes = new Uint8Array( 16 ); + var enabledAttributes = new Uint8Array( 16 ); + var attributeDivisors = new Uint8Array( 16 ); - } + var capabilities = {}; - } + var compressedTextureFormats = null; - } + var currentBlending = null; + var currentBlendEquation = null; + var currentBlendSrc = null; + var currentBlendDst = null; + var currentBlendEquationAlpha = null; + var currentBlendSrcAlpha = null; + var currentBlendDstAlpha = null; - function createVirtualLight(light, cascade) { + var currentDepthFunc = null; + var currentDepthWrite = null; - var virtualLight = new THREE.DirectionalLight(); + var currentColorWrite = null; - virtualLight.isVirtual = true; + var currentFlipSided = null; - virtualLight.onlyShadow = true; - virtualLight.castShadow = true; + var currentLineWidth = null; - virtualLight.shadowCameraNear = light.shadowCameraNear; - virtualLight.shadowCameraFar = light.shadowCameraFar; + var currentPolygonOffsetFactor = null; + var currentPolygonOffsetUnits = null; - virtualLight.shadowCameraLeft = light.shadowCameraLeft; - virtualLight.shadowCameraRight = light.shadowCameraRight; - virtualLight.shadowCameraBottom = light.shadowCameraBottom; - virtualLight.shadowCameraTop = light.shadowCameraTop; + var maxTextures = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS ); - virtualLight.shadowCameraVisible = light.shadowCameraVisible; + var currentTextureSlot = undefined; + var currentBoundTextures = {}; - virtualLight.shadowDarkness = light.shadowDarkness; + this.init = function () { - virtualLight.shadowBias = light.shadowCascadeBias[cascade]; - virtualLight.shadowMapWidth = light.shadowCascadeWidth[cascade]; - virtualLight.shadowMapHeight = light.shadowCascadeHeight[cascade]; + gl.clearColor( 0, 0, 0, 1 ); + gl.clearDepth( 1 ); + gl.clearStencil( 0 ); - virtualLight.pointsWorld = []; - virtualLight.pointsFrustum = []; + this.enable( gl.DEPTH_TEST ); + gl.depthFunc( gl.LEQUAL ); - var pointsWorld = virtualLight.pointsWorld, - pointsFrustum = virtualLight.pointsFrustum; + gl.frontFace( gl.CCW ); + gl.cullFace( gl.BACK ); + this.enable( gl.CULL_FACE ); - for (var i = 0; i < 8; i++) { + this.enable( gl.BLEND ); + gl.blendEquation( gl.FUNC_ADD ); + gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA ); - pointsWorld[i] = new THREE.Vector3(); - pointsFrustum[i] = new THREE.Vector3(); + }; - } + this.initAttributes = function () { - var nearZ = light.shadowCascadeNearZ[cascade]; - var farZ = light.shadowCascadeFarZ[cascade]; + for ( var i = 0, l = newAttributes.length; i < l; i ++ ) { - pointsFrustum[0].set(-1, -1, nearZ); - pointsFrustum[1].set(1, -1, nearZ); - pointsFrustum[2].set(-1, 1, nearZ); - pointsFrustum[3].set(1, 1, nearZ); + newAttributes[ i ] = 0; - 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; + }; - } + this.enableAttribute = function ( attribute ) { - // Synchronize virtual light with the original light + newAttributes[ attribute ] = 1; - function updateVirtualLight(light, cascade) { + if ( enabledAttributes[ attribute ] === 0 ) { - var virtualLight = light.shadowCascadeArray[cascade]; + gl.enableVertexAttribArray( attribute ); + enabledAttributes[ attribute ] = 1; - virtualLight.position.copy(light.position); - virtualLight.target.position.copy(light.target.position); - virtualLight.lookAt(virtualLight.target); + } - virtualLight.shadowCameraVisible = light.shadowCameraVisible; - virtualLight.shadowDarkness = light.shadowDarkness; + if ( attributeDivisors[ attribute ] !== 0 ) { - virtualLight.shadowBias = light.shadowCascadeBias[cascade]; + var extension = extensions.get( 'ANGLE_instanced_arrays' ); - var nearZ = light.shadowCascadeNearZ[cascade]; - var farZ = light.shadowCascadeFarZ[cascade]; + extension.vertexAttribDivisorANGLE( attribute, 0 ); + attributeDivisors[ attribute ] = 0; - 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; + this.enableAttributeAndDivisor = function ( attribute, meshPerAttribute, extension ) { - } + newAttributes[ attribute ] = 1; - // Fit shadow camera's ortho frustum to camera frustum + if ( enabledAttributes[ attribute ] === 0 ) { - function updateShadowCamera(camera, light) { + gl.enableVertexAttribArray( attribute ); + enabledAttributes[ attribute ] = 1; - var shadowCamera = light.shadowCamera, - pointsFrustum = light.pointsFrustum, - pointsWorld = light.pointsWorld; + } - _min.set(Infinity, Infinity, Infinity); - _max.set(-Infinity, -Infinity, -Infinity); + if ( attributeDivisors[ attribute ] !== meshPerAttribute ) { - for (var i = 0; i < 8; i++) { + extension.vertexAttribDivisorANGLE( attribute, meshPerAttribute ); + attributeDivisors[ attribute ] = meshPerAttribute; - var p = pointsWorld[i]; + } - p.copy(pointsFrustum[i]); - p.unproject(camera); + }; - p.applyMatrix4(shadowCamera.matrixWorldInverse); + this.disableUnusedAttributes = function () { - if (p.x < _min.x) _min.x = p.x; - if (p.x > _max.x) _max.x = p.x; + for ( var i = 0, l = enabledAttributes.length; i < l; i ++ ) { - if (p.y < _min.y) _min.y = p.y; - if (p.y > _max.y) _max.y = p.y; + if ( enabledAttributes[ i ] !== newAttributes[ i ] ) { - if (p.z < _min.z) _min.z = p.z; - if (p.z > _max.z) _max.z = p.z; + gl.disableVertexAttribArray( i ); + enabledAttributes[ i ] = 0; - } + } - 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(); + this.enable = function ( id ) { - } + if ( capabilities[ id ] !== true ) { - // 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 + gl.enable( id ); + capabilities[ id ] = true; - function getObjectMaterial(object) { + } - return object.material instanceof THREE.MeshFaceMaterial - ? object.material.materials[0] - : object.material; + }; - } + this.disable = function ( id ) { -}; + if ( capabilities[ id ] !== false ) { -// File:src/renderers/webgl/WebGLState.js + gl.disable( id ); + capabilities[ id ] = false; -/** - * @author mrdoob / http://mrdoob.com/ - */ + } -THREE.WebGLState = function (gl, paramThreeToGL) { + }; - var _this = this; + this.getCompressedTextureFormats = function () { - var newAttributes = new Uint8Array(16); - var enabledAttributes = new Uint8Array(16); + if ( compressedTextureFormats === null ) { - var currentBlending = null; - var currentBlendEquation = null; - var currentBlendSrc = null; - var currentBlendDst = null; - var currentBlendEquationAlpha = null; - var currentBlendSrcAlpha = null; - var currentBlendDstAlpha = null; + compressedTextureFormats = []; - var currentDepthFunc = null; - var currentDepthTest = null; - var currentDepthWrite = null; + if ( extensions.get( 'WEBGL_compressed_texture_pvrtc' ) || + extensions.get( 'WEBGL_compressed_texture_s3tc' ) ) { - var currentColorWrite = null; + var formats = gl.getParameter( gl.COMPRESSED_TEXTURE_FORMATS ); - var currentDoubleSided = null; - var currentFlipSided = null; + for ( var i = 0; i < formats.length; i ++ ) { - var currentLineWidth = null; + compressedTextureFormats.push( formats[ i ] ); - var currentPolygonOffset = null; - var currentPolygonOffsetFactor = null; - var currentPolygonOffsetUnits = null; + } - var maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + } - var currentTextureSlot = undefined; - var currentBoundTextures = {}; + } - this.initAttributes = function () { + return compressedTextureFormats; - for (var i = 0, l = newAttributes.length; i < l; i++) { + }; - newAttributes[i] = 0; + this.setBlending = function ( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha ) { - } + if ( blending !== currentBlending ) { - }; + if ( blending === THREE.NoBlending ) { - this.enableAttribute = function (attribute) { + this.disable( gl.BLEND ); - newAttributes[attribute] = 1; + } else if ( blending === THREE.AdditiveBlending ) { - if (enabledAttributes[attribute] === 0) { + this.enable( gl.BLEND ); + gl.blendEquation( gl.FUNC_ADD ); + gl.blendFunc( gl.SRC_ALPHA, gl.ONE ); - gl.enableVertexAttribArray(attribute); - enabledAttributes[attribute] = 1; + } else if ( blending === THREE.SubtractiveBlending ) { - } + // TODO: Find blendFuncSeparate() combination - }; + this.enable( gl.BLEND ); + gl.blendEquation( gl.FUNC_ADD ); + gl.blendFunc( gl.ZERO, gl.ONE_MINUS_SRC_COLOR ); - this.disableUnusedAttributes = function () { + } else if ( blending === THREE.MultiplyBlending ) { - for (var i = 0, l = enabledAttributes.length; i < l; i++) { + // TODO: Find blendFuncSeparate() combination - if (enabledAttributes[i] !== newAttributes[i]) { + this.enable( gl.BLEND ); + gl.blendEquation( gl.FUNC_ADD ); + gl.blendFunc( gl.ZERO, gl.SRC_COLOR ); - gl.disableVertexAttribArray(i); - enabledAttributes[i] = 0; + } else if ( blending === THREE.CustomBlending ) { - } + this.enable( gl.BLEND ); - } + } else { - }; + this.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 ); - this.setBlending = function (blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha) { + } - if (blending !== currentBlending) { + currentBlending = blending; - if (blending === THREE.NoBlending) { + } - gl.disable(gl.BLEND); + if ( blending === THREE.CustomBlending ) { - } else if (blending === THREE.AdditiveBlending) { + blendEquationAlpha = blendEquationAlpha || blendEquation; + blendSrcAlpha = blendSrcAlpha || blendSrc; + blendDstAlpha = blendDstAlpha || blendDst; - gl.enable(gl.BLEND); - gl.blendEquation(gl.FUNC_ADD); - gl.blendFunc(gl.SRC_ALPHA, gl.ONE); + if ( blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha ) { - } else if (blending === THREE.SubtractiveBlending) { + gl.blendEquationSeparate( paramThreeToGL( blendEquation ), paramThreeToGL( blendEquationAlpha ) ); - // TODO: Find blendFuncSeparate() combination - gl.enable(gl.BLEND); - gl.blendEquation(gl.FUNC_ADD); - gl.blendFunc(gl.ZERO, gl.ONE_MINUS_SRC_COLOR); + currentBlendEquation = blendEquation; + currentBlendEquationAlpha = blendEquationAlpha; - } 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); + if ( blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha ) { - } else if (blending === THREE.CustomBlending) { + gl.blendFuncSeparate( paramThreeToGL( blendSrc ), paramThreeToGL( blendDst ), paramThreeToGL( blendSrcAlpha ), paramThreeToGL( blendDstAlpha ) ); - gl.enable(gl.BLEND); + currentBlendSrc = blendSrc; + currentBlendDst = blendDst; + currentBlendSrcAlpha = blendSrcAlpha; + currentBlendDstAlpha = blendDstAlpha; - } 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); + } else { - } + currentBlendEquation = null; + currentBlendSrc = null; + currentBlendDst = null; + currentBlendEquationAlpha = null; + currentBlendSrcAlpha = null; + currentBlendDstAlpha = null; - currentBlending = blending; + } - } + }; - if (blending === THREE.CustomBlending) { + this.setDepthFunc = function ( depthFunc ) { - blendEquationAlpha = blendEquationAlpha || blendEquation; - blendSrcAlpha = blendSrcAlpha || blendSrc; - blendDstAlpha = blendDstAlpha || blendDst; + if ( currentDepthFunc !== depthFunc ) { - if (blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha) { + if ( depthFunc ) { - gl.blendEquationSeparate(paramThreeToGL(blendEquation), paramThreeToGL(blendEquationAlpha)); + switch ( depthFunc ) { - currentBlendEquation = blendEquation; - currentBlendEquationAlpha = blendEquationAlpha; + case THREE.NeverDepth: - } + gl.depthFunc( gl.NEVER ); + break; - if (blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha) { + case THREE.AlwaysDepth: - gl.blendFuncSeparate(paramThreeToGL(blendSrc), paramThreeToGL(blendDst), paramThreeToGL(blendSrcAlpha), paramThreeToGL(blendDstAlpha)); + gl.depthFunc( gl.ALWAYS ); + break; - currentBlendSrc = blendSrc; - currentBlendDst = blendDst; - currentBlendSrcAlpha = blendSrcAlpha; - currentBlendDstAlpha = blendDstAlpha; + case THREE.LessDepth: - } + gl.depthFunc( gl.LESS ); + break; - } else { + case THREE.LessEqualDepth: - currentBlendEquation = null; - currentBlendSrc = null; - currentBlendDst = null; - currentBlendEquationAlpha = null; - currentBlendSrcAlpha = null; - currentBlendDstAlpha = null; + gl.depthFunc( gl.LEQUAL ); + break; - } + case THREE.EqualDepth: - }; + gl.depthFunc( gl.EQUAL ); + break; - this.setDepthFunc = function (depthFunc) { + case THREE.GreaterEqualDepth: - if (currentDepthFunc !== depthFunc) { + gl.depthFunc( gl.GEQUAL ); + break; - if (depthFunc) { + case THREE.GreaterDepth: - switch (depthFunc) { + gl.depthFunc( gl.GREATER ); + break; - case THREE.NeverDepth: + case THREE.NotEqualDepth: - gl.depthFunc(gl.NEVER); - break; + gl.depthFunc( gl.NOTEQUAL ); + break; - case THREE.AlwaysDepth: + default: - gl.depthFunc(gl.ALWAYS); - break; + gl.depthFunc( gl.LEQUAL ); - case THREE.LessDepth: + } - gl.depthFunc(gl.LESS); - break; + } else { - case THREE.LessEqualDepth: + gl.depthFunc( gl.LEQUAL ); - gl.depthFunc(gl.LEQUAL); - break; + } - case THREE.EqualDepth: + currentDepthFunc = depthFunc; - gl.depthFunc(gl.EQUAL); - break; + } - case THREE.GreaterEqualDepth: + }; - gl.depthFunc(gl.GEQUAL); - break; + this.setDepthTest = function ( depthTest ) { - case THREE.GreaterDepth: + if ( depthTest ) { - gl.depthFunc(gl.GREATER); - break; + this.enable( gl.DEPTH_TEST ); - case THREE.NotEqualDepth: + } else { - gl.depthFunc(gl.NOTEQUAL); - break; + this.disable( gl.DEPTH_TEST ); - default: + } - gl.depthFunc(gl.LEQUAL); - } + }; - } else { + this.setDepthWrite = function ( depthWrite ) { - gl.depthFunc(gl.LEQUAL); + if ( currentDepthWrite !== depthWrite ) { - } + gl.depthMask( depthWrite ); + currentDepthWrite = depthWrite; - currentDepthFunc = depthFunc; + } - } + }; - }; + this.setColorWrite = function ( colorWrite ) { - this.setDepthTest = function (depthTest) { + if ( currentColorWrite !== colorWrite ) { - if (currentDepthTest !== depthTest) { + gl.colorMask( colorWrite, colorWrite, colorWrite, colorWrite ); + currentColorWrite = colorWrite; - if (depthTest) { + } - gl.enable(gl.DEPTH_TEST); + }; - } else { + this.setFlipSided = function ( flipSided ) { - gl.disable(gl.DEPTH_TEST); + if ( currentFlipSided !== flipSided ) { - } + if ( flipSided ) { - currentDepthTest = depthTest; + gl.frontFace( gl.CW ); - } + } else { - }; + gl.frontFace( gl.CCW ); - this.setDepthWrite = function (depthWrite) { + } - if (currentDepthWrite !== depthWrite) { + currentFlipSided = flipSided; - gl.depthMask(depthWrite); - currentDepthWrite = depthWrite; + } - } + }; - }; + this.setLineWidth = function ( width ) { - this.setColorWrite = function (colorWrite) { + if ( width !== currentLineWidth ) { - if (currentColorWrite !== colorWrite) { + gl.lineWidth( width ); - gl.colorMask(colorWrite, colorWrite, colorWrite, colorWrite); - currentColorWrite = colorWrite; + currentLineWidth = width; - } + } - }; + }; - this.setDoubleSided = function (doubleSided) { + this.setPolygonOffset = function ( polygonOffset, factor, units ) { - if (currentDoubleSided !== doubleSided) { + if ( polygonOffset ) { - if (doubleSided) { + this.enable( gl.POLYGON_OFFSET_FILL ); - gl.disable(gl.CULL_FACE); + } else { - } else { + this.disable( gl.POLYGON_OFFSET_FILL ); - gl.enable(gl.CULL_FACE); + } - } + if ( polygonOffset && ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) ) { - currentDoubleSided = doubleSided; + gl.polygonOffset( factor, units ); - } + currentPolygonOffsetFactor = factor; + currentPolygonOffsetUnits = units; - }; + } - this.setFlipSided = function (flipSided) { + }; - if (currentFlipSided !== flipSided) { + this.setScissorTest = function ( scissorTest ) { - if (flipSided) { + if ( scissorTest ) { - gl.frontFace(gl.CW); + this.enable( gl.SCISSOR_TEST ); - } else { + } else { - gl.frontFace(gl.CCW); + this.disable( gl.SCISSOR_TEST ); - } + } - currentFlipSided = flipSided; + }; - } + // texture - }; + this.activeTexture = function ( webglSlot ) { - this.setLineWidth = function (width) { + if ( webglSlot === undefined ) webglSlot = gl.TEXTURE0 + maxTextures - 1; - if (width !== currentLineWidth) { + if ( currentTextureSlot !== webglSlot ) { - gl.lineWidth(width); + gl.activeTexture( webglSlot ); + currentTextureSlot = webglSlot; - currentLineWidth = width; + } - } + } - }; + this.bindTexture = function ( webglType, webglTexture ) { - this.setPolygonOffset = function (polygonoffset, factor, units) { + if ( currentTextureSlot === undefined ) { - if (currentPolygonOffset !== polygonoffset) { + _this.activeTexture(); - if (polygonoffset) { + } - gl.enable(gl.POLYGON_OFFSET_FILL); + var boundTexture = currentBoundTextures[ currentTextureSlot ]; - } else { + if ( boundTexture === undefined ) { - gl.disable(gl.POLYGON_OFFSET_FILL); + boundTexture = { type: undefined, texture: undefined }; + currentBoundTextures[ currentTextureSlot ] = boundTexture; - } + } - currentPolygonOffset = polygonoffset; + if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) { - } + gl.bindTexture( webglType, webglTexture ); - if (polygonoffset && ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units )) { + boundTexture.type = webglType; + boundTexture.texture = webglTexture; - gl.polygonOffset(factor, units); + } - currentPolygonOffsetFactor = factor; - currentPolygonOffsetUnits = units; + }; - } + this.compressedTexImage2D = function () { - }; + try { - this.activeTexture = function (webglSlot) { + gl.compressedTexImage2D.apply( gl, arguments ); - if (webglSlot === undefined) webglSlot = gl.TEXTURE0 + maxTextures - 1; + } catch ( error ) { - if (currentTextureSlot !== webglSlot) { + console.error( error ); - gl.activeTexture(webglSlot); - currentTextureSlot = webglSlot; + } - } + }; - } + this.texImage2D = function () { - this.bindTexture = function (webglType, webglTexture) { + try { - if (currentTextureSlot === undefined) { + gl.texImage2D.apply( gl, arguments ); - _this.activeTexture(); + } catch ( error ) { - } + console.error( error ); - var boundTexture = currentBoundTextures[currentTextureSlot]; + } - if (boundTexture === undefined) { + }; - boundTexture = {type: undefined, texture: undefined}; - currentBoundTextures[currentTextureSlot] = boundTexture; + // - } + this.reset = function () { - if (boundTexture.type !== webglType || boundTexture.texture !== webglTexture) { + for ( var i = 0; i < enabledAttributes.length; i ++ ) { - gl.bindTexture(webglType, webglTexture); + if ( enabledAttributes[ i ] === 1 ) { - boundTexture.type = webglType; - boundTexture.texture = webglTexture; + gl.disableVertexAttribArray( i ); + enabledAttributes[ i ] = 0; - } + } - } + } - this.reset = function () { + capabilities = {}; - for (var i = 0; i < enabledAttributes.length; i++) { + compressedTextureFormats = null; - enabledAttributes[i] = 0; + currentBlending = null; - } + currentDepthWrite = null; + currentColorWrite = null; - currentBlending = null; - currentDepthTest = null; - currentDepthWrite = null; - currentColorWrite = null; - currentDoubleSided = null; - currentFlipSided = null; + currentFlipSided = null; - }; + }; }; @@ -25238,470 +27626,473 @@ THREE.WebGLState = function (gl, paramThreeToGL) { * @author alteredq / http://alteredqualia.com/ */ -THREE.LensFlarePlugin = function (renderer, flares) { +THREE.LensFlarePlugin = function ( renderer, flares ) { - var gl = renderer.context; + var gl = renderer.context; + var state = renderer.state; - var vertexBuffer, elementBuffer; - var program, attributes, uniforms; - var hasVertexTexture; + var vertexBuffer, elementBuffer; + var program, attributes, uniforms; + var hasVertexTexture; - var tempTexture, occlusionTexture; + var tempTexture, occlusionTexture; - var init = function () { + var init = function () { - var vertices = new Float32Array([ - -1, -1, 0, 0, - 1, -1, 1, 0, - 1, 1, 1, 1, - -1, 1, 0, 1 - ]); + var vertices = new Float32Array( [ + - 1, - 1, 0, 0, + 1, - 1, 1, 0, + 1, 1, 1, 1, + - 1, 1, 0, 1 + ] ); - var faces = new Uint16Array([ - 0, 1, 2, - 0, 2, 3 - ]); + var faces = new Uint16Array( [ + 0, 1, 2, + 0, 2, 3 + ] ); - // buffers + // buffers - vertexBuffer = gl.createBuffer(); - elementBuffer = gl.createBuffer(); + vertexBuffer = gl.createBuffer(); + elementBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); + 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); + gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, elementBuffer ); + gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, faces, gl.STATIC_DRAW ); - // textures + // textures - tempTexture = gl.createTexture(); - occlusionTexture = gl.createTexture(); + tempTexture = gl.createTexture(); + occlusionTexture = gl.createTexture(); - renderer.state.bindTexture(gl.TEXTURE_2D, 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); + state.bindTexture( gl.TEXTURE_2D, 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 ); - renderer.state.bindTexture(gl.TEXTURE_2D, 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); + state.bindTexture( gl.TEXTURE_2D, 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 ); - hasVertexTexture = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) > 0; + hasVertexTexture = gl.getParameter( gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ) > 0; - var shader; + var shader; - if (hasVertexTexture) { + if ( hasVertexTexture ) { - shader = { + shader = { - vertexShader: [ + vertexShader: [ - "uniform lowp int renderType;", + "uniform lowp int renderType;", - "uniform vec3 screenPosition;", - "uniform vec2 scale;", - "uniform float rotation;", + "uniform vec3 screenPosition;", + "uniform vec2 scale;", + "uniform float rotation;", - "uniform sampler2D occlusionMap;", + "uniform sampler2D occlusionMap;", - "attribute vec2 position;", - "attribute vec2 uv;", + "attribute vec2 position;", + "attribute vec2 uv;", - "varying vec2 vUV;", - "varying float vVisibility;", + "varying vec2 vUV;", + "varying float vVisibility;", - "void main() {", + "void main() {", - "vUV = uv;", + "vUV = uv;", - "vec2 pos = position;", + "vec2 pos = position;", - "if( renderType == 2 ) {", + "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 ) );", + "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;", + "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;", + "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 );", + "gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 );", - "}" + "}" - ].join("\n"), + ].join( "\n" ), - fragmentShader: [ + fragmentShader: [ - "uniform lowp int renderType;", + "uniform lowp int renderType;", - "uniform sampler2D map;", - "uniform float opacity;", - "uniform vec3 color;", + "uniform sampler2D map;", + "uniform float opacity;", + "uniform vec3 color;", - "varying vec2 vUV;", - "varying float vVisibility;", + "varying vec2 vUV;", + "varying float vVisibility;", - "void main() {", + "void main() {", - // pink square + // pink square - "if( renderType == 0 ) {", + "if( renderType == 0 ) {", - "gl_FragColor = vec4( 1.0, 0.0, 1.0, 0.0 );", + "gl_FragColor = vec4( 1.0, 0.0, 1.0, 0.0 );", - // restore + // restore - "} else if( renderType == 1 ) {", + "} else if( renderType == 1 ) {", - "gl_FragColor = texture2D( map, vUV );", + "gl_FragColor = texture2D( map, vUV );", - // flare + // flare - "} else {", + "} else {", - "vec4 texture = texture2D( map, vUV );", - "texture.a *= opacity * vVisibility;", - "gl_FragColor = texture;", - "gl_FragColor.rgb *= color;", + "vec4 texture = texture2D( map, vUV );", + "texture.a *= opacity * vVisibility;", + "gl_FragColor = texture;", + "gl_FragColor.rgb *= color;", - "}", + "}", - "}" + "}" - ].join("\n") + ].join( "\n" ) - }; + }; - } else { + } else { - shader = { + shader = { - vertexShader: [ + vertexShader: [ - "uniform lowp int renderType;", + "uniform lowp int renderType;", - "uniform vec3 screenPosition;", - "uniform vec2 scale;", - "uniform float rotation;", + "uniform vec3 screenPosition;", + "uniform vec2 scale;", + "uniform float rotation;", - "attribute vec2 position;", - "attribute vec2 uv;", + "attribute vec2 position;", + "attribute vec2 uv;", - "varying vec2 vUV;", + "varying vec2 vUV;", - "void main() {", + "void main() {", - "vUV = uv;", + "vUV = uv;", - "vec2 pos = position;", + "vec2 pos = position;", - "if( renderType == 2 ) {", + "if( renderType == 2 ) {", - "pos.x = cos( rotation ) * position.x - sin( rotation ) * position.y;", - "pos.y = sin( rotation ) * position.x + cos( rotation ) * position.y;", + "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 );", + "gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 );", - "}" + "}" - ].join("\n"), + ].join( "\n" ), - fragmentShader: [ + fragmentShader: [ - "precision mediump float;", + "precision mediump float;", - "uniform lowp int renderType;", + "uniform lowp int renderType;", - "uniform sampler2D map;", - "uniform sampler2D occlusionMap;", - "uniform float opacity;", - "uniform vec3 color;", + "uniform sampler2D map;", + "uniform sampler2D occlusionMap;", + "uniform float opacity;", + "uniform vec3 color;", - "varying vec2 vUV;", + "varying vec2 vUV;", - "void main() {", + "void main() {", - // pink square + // pink square - "if( renderType == 0 ) {", + "if( renderType == 0 ) {", - "gl_FragColor = vec4( texture2D( map, vUV ).rgb, 0.0 );", + "gl_FragColor = vec4( texture2D( map, vUV ).rgb, 0.0 );", - // restore + // restore - "} else if( renderType == 1 ) {", + "} else if( renderType == 1 ) {", - "gl_FragColor = texture2D( map, vUV );", + "gl_FragColor = texture2D( map, vUV );", - // flare + // flare - "} else {", + "} 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 );", + "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;", + "vec4 texture = texture2D( map, vUV );", + "texture.a *= opacity * visibility;", + "gl_FragColor = texture;", + "gl_FragColor.rgb *= color;", - "}", + "}", - "}" + "}" - ].join("\n") + ].join( "\n" ) - }; + }; - } + } - program = createProgram(shader); + program = createProgram( shader ); - attributes = { - vertex: gl.getAttribLocation(program, "position"), - uv: gl.getAttribLocation(program, "uv") - }; + attributes = { + vertex: gl.getAttribLocation ( program, "position" ), + uv: gl.getAttribLocation ( program, "uv" ) + }; - uniforms = { - renderType: gl.getUniformLocation(program, "renderType"), - map: gl.getUniformLocation(program, "map"), - occlusionMap: gl.getUniformLocation(program, "occlusionMap"), - opacity: gl.getUniformLocation(program, "opacity"), - color: gl.getUniformLocation(program, "color"), - scale: gl.getUniformLocation(program, "scale"), - rotation: gl.getUniformLocation(program, "rotation"), - screenPosition: gl.getUniformLocation(program, "screenPosition") - }; + uniforms = { + renderType: gl.getUniformLocation( program, "renderType" ), + map: gl.getUniformLocation( program, "map" ), + occlusionMap: gl.getUniformLocation( program, "occlusionMap" ), + opacity: gl.getUniformLocation( program, "opacity" ), + color: gl.getUniformLocation( program, "color" ), + scale: gl.getUniformLocation( program, "scale" ), + rotation: gl.getUniformLocation( program, "rotation" ), + screenPosition: gl.getUniformLocation( program, "screenPosition" ) + }; - }; + }; - /* - * Render lens flares - * Method: renders 16x16 0xff00ff-colored points scattered over the light source area, - * reads these back and calculates occlusion. - */ + /* + * Render lens flares + * Method: renders 16x16 0xff00ff-colored points scattered over the light source area, + * reads these back and calculates occlusion. + */ - this.render = function (scene, camera, viewportWidth, viewportHeight) { + this.render = function ( scene, camera, viewportWidth, viewportHeight ) { - if (flares.length === 0) return; + if ( flares.length === 0 ) return; - var tempPosition = new THREE.Vector3(); + var tempPosition = new THREE.Vector3(); - var invAspect = viewportHeight / viewportWidth, - halfViewportWidth = viewportWidth * 0.5, - halfViewportHeight = viewportHeight * 0.5; + var invAspect = viewportHeight / viewportWidth, + halfViewportWidth = viewportWidth * 0.5, + halfViewportHeight = viewportHeight * 0.5; - var size = 16 / viewportHeight, - scale = new THREE.Vector2(size * invAspect, size); + 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 screenPosition = new THREE.Vector3( 1, 1, 0 ), + screenPositionPixels = new THREE.Vector2( 1, 1 ); - if (program === undefined) { + if ( program === undefined ) { - init(); + init(); - } + } - gl.useProgram(program); + gl.useProgram( program ); - renderer.state.initAttributes(); - renderer.state.enableAttribute(attributes.vertex); - renderer.state.enableAttribute(attributes.uv); - renderer.state.disableUnusedAttributes(); + state.initAttributes(); + state.enableAttribute( attributes.vertex ); + state.enableAttribute( attributes.uv ); + state.disableUnusedAttributes(); - // loop through all lens flares to update their occlusion and positions - // setup gl and common used attribs/unforms + // loop through all lens flares to update their occlusion and positions + // setup gl and common used attribs/uniforms - gl.uniform1i(uniforms.occlusionMap, 0); - gl.uniform1i(uniforms.map, 1); + gl.uniform1i( uniforms.occlusionMap, 0 ); + gl.uniform1i( uniforms.map, 1 ); - gl.bindBuffer(gl.ARRAY_BUFFER, 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.ARRAY_BUFFER, 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, elementBuffer); + gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, elementBuffer ); - gl.disable(gl.CULL_FACE); - gl.depthMask(false); + state.disable( gl.CULL_FACE ); + gl.depthMask( false ); - for (var i = 0, l = flares.length; i < l; i++) { + for ( var i = 0, l = flares.length; i < l; i ++ ) { - size = 16 / viewportHeight; - scale.set(size * invAspect, size); + size = 16 / viewportHeight; + scale.set( size * invAspect, size ); - // calc object screen position + // calc object screen position - var flare = flares[i]; + var flare = flares[ i ]; - tempPosition.set(flare.matrixWorld.elements[12], flare.matrixWorld.elements[13], flare.matrixWorld.elements[14]); + tempPosition.set( flare.matrixWorld.elements[ 12 ], flare.matrixWorld.elements[ 13 ], flare.matrixWorld.elements[ 14 ] ); - tempPosition.applyMatrix4(camera.matrixWorldInverse); - tempPosition.applyProjection(camera.projectionMatrix); + tempPosition.applyMatrix4( camera.matrixWorldInverse ); + tempPosition.applyProjection( camera.projectionMatrix ); - // setup arrays for gl programs + // setup arrays for gl programs - screenPosition.copy(tempPosition); + screenPosition.copy( tempPosition ); - screenPositionPixels.x = screenPosition.x * halfViewportWidth + halfViewportWidth; - screenPositionPixels.y = screenPosition.y * halfViewportHeight + halfViewportHeight; + screenPositionPixels.x = screenPosition.x * halfViewportWidth + halfViewportWidth; + screenPositionPixels.y = screenPosition.y * halfViewportHeight + halfViewportHeight; - // screen cull + // screen cull - if (hasVertexTexture || ( - screenPositionPixels.x > 0 && - screenPositionPixels.x < viewportWidth && - screenPositionPixels.y > 0 && - screenPositionPixels.y < viewportHeight )) { + if ( hasVertexTexture || ( + screenPositionPixels.x > 0 && + screenPositionPixels.x < viewportWidth && + screenPositionPixels.y > 0 && + screenPositionPixels.y < viewportHeight ) ) { - // save current RGB to temp texture + // save current RGB to temp texture - renderer.state.activeTexture(gl.TEXTURE1); - renderer.state.bindTexture(gl.TEXTURE_2D, tempTexture); - gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGB, screenPositionPixels.x - 8, screenPositionPixels.y - 8, 16, 16, 0); + state.activeTexture( gl.TEXTURE0 ); + state.bindTexture( gl.TEXTURE_2D, null ); + state.activeTexture( gl.TEXTURE1 ); + state.bindTexture( gl.TEXTURE_2D, tempTexture ); + gl.copyTexImage2D( gl.TEXTURE_2D, 0, gl.RGB, screenPositionPixels.x - 8, screenPositionPixels.y - 8, 16, 16, 0 ); - // render pink quad + // 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.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); + state.disable( gl.BLEND ); + state.enable( gl.DEPTH_TEST ); - gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); + gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); - // copy result to occlusionMap + // copy result to occlusionMap - renderer.state.activeTexture(gl.TEXTURE0); - renderer.state.bindTexture(gl.TEXTURE_2D, occlusionTexture); - gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, screenPositionPixels.x - 8, screenPositionPixels.y - 8, 16, 16, 0); + state.activeTexture( gl.TEXTURE0 ); + state.bindTexture( gl.TEXTURE_2D, occlusionTexture ); + gl.copyTexImage2D( gl.TEXTURE_2D, 0, gl.RGBA, screenPositionPixels.x - 8, screenPositionPixels.y - 8, 16, 16, 0 ); - // restore graphics + // restore graphics - gl.uniform1i(uniforms.renderType, 1); - gl.disable(gl.DEPTH_TEST); + gl.uniform1i( uniforms.renderType, 1 ); + state.disable( gl.DEPTH_TEST ); - renderer.state.activeTexture(gl.TEXTURE1); - renderer.state.bindTexture(gl.TEXTURE_2D, tempTexture); - gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); + state.activeTexture( gl.TEXTURE1 ); + state.bindTexture( gl.TEXTURE_2D, tempTexture ); + gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); - // update object positions + // update object positions - flare.positionScreen.copy(screenPosition); + flare.positionScreen.copy( screenPosition ); - if (flare.customUpdateCallback) { + if ( flare.customUpdateCallback ) { - flare.customUpdateCallback(flare); + flare.customUpdateCallback( flare ); - } else { + } else { - flare.updateLensFlares(); + flare.updateLensFlares(); - } + } - // render flares + // render flares - gl.uniform1i(uniforms.renderType, 2); - gl.enable(gl.BLEND); + gl.uniform1i( uniforms.renderType, 2 ); + state.enable( gl.BLEND ); - for (var j = 0, jl = flare.lensFlares.length; j < jl; j++) { + for ( var j = 0, jl = flare.lensFlares.length; j < jl; j ++ ) { - var sprite = flare.lensFlares[j]; + var sprite = flare.lensFlares[ j ]; - if (sprite.opacity > 0.001 && sprite.scale > 0.001) { + if ( sprite.opacity > 0.001 && sprite.scale > 0.001 ) { - screenPosition.x = sprite.x; - screenPosition.y = sprite.y; - screenPosition.z = sprite.z; + screenPosition.x = sprite.x; + screenPosition.y = sprite.y; + screenPosition.z = sprite.z; - size = sprite.size * sprite.scale / viewportHeight; + size = sprite.size * sprite.scale / viewportHeight; - scale.x = size * invAspect; - scale.y = size; + 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.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); + gl.uniform1f( uniforms.opacity, sprite.opacity ); + gl.uniform3f( uniforms.color, sprite.color.r, sprite.color.g, sprite.color.b ); - renderer.state.setBlending(sprite.blending, sprite.blendEquation, sprite.blendSrc, sprite.blendDst); - renderer.setTexture(sprite.texture, 1); + state.setBlending( sprite.blending, sprite.blendEquation, sprite.blendSrc, sprite.blendDst ); + renderer.setTexture( sprite.texture, 1 ); - gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); + gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); - } + } - } + } - } + } - } + } - // restore gl + // restore gl - gl.enable(gl.CULL_FACE); - gl.enable(gl.DEPTH_TEST); - gl.depthMask(true); + state.enable( gl.CULL_FACE ); + state.enable( gl.DEPTH_TEST ); + gl.depthMask( true ); - renderer.resetGLState(); + renderer.resetGLState(); - }; + }; - function createProgram(shader) { + function createProgram ( shader ) { - var program = gl.createProgram(); + var program = gl.createProgram(); - var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); - var vertexShader = gl.createShader(gl.VERTEX_SHADER); + var fragmentShader = gl.createShader( gl.FRAGMENT_SHADER ); + var vertexShader = gl.createShader( gl.VERTEX_SHADER ); - var prefix = "precision " + renderer.getPrecision() + " float;\n"; + var prefix = "precision " + renderer.getPrecision() + " float;\n"; - gl.shaderSource(fragmentShader, prefix + shader.fragmentShader); - gl.shaderSource(vertexShader, prefix + shader.vertexShader); + gl.shaderSource( fragmentShader, prefix + shader.fragmentShader ); + gl.shaderSource( vertexShader, prefix + shader.vertexShader ); - gl.compileShader(fragmentShader); - gl.compileShader(vertexShader); + gl.compileShader( fragmentShader ); + gl.compileShader( vertexShader ); - gl.attachShader(program, fragmentShader); - gl.attachShader(program, vertexShader); + gl.attachShader( program, fragmentShader ); + gl.attachShader( program, vertexShader ); - gl.linkProgram(program); + gl.linkProgram( program ); - return program; + return program; - } + } }; @@ -25712,365 +28103,366 @@ THREE.LensFlarePlugin = function (renderer, flares) { * @author alteredq / http://alteredqualia.com/ */ -THREE.SpritePlugin = function (renderer, sprites) { +THREE.SpritePlugin = function ( renderer, sprites ) { - var gl = renderer.context; + var gl = renderer.context; + var state = renderer.state; - var vertexBuffer, elementBuffer; - var program, attributes, uniforms; + var vertexBuffer, elementBuffer; + var program, attributes, uniforms; - var texture; + var texture; - // decompose matrixWorld + // decompose matrixWorld - var spritePosition = new THREE.Vector3(); - var spriteRotation = new THREE.Quaternion(); - var spriteScale = new THREE.Vector3(); + var spritePosition = new THREE.Vector3(); + var spriteRotation = new THREE.Quaternion(); + var spriteScale = new THREE.Vector3(); - var init = function () { + var init = function () { - var 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 - ]); + var 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 + ] ); - var faces = new Uint16Array([ - 0, 1, 2, - 0, 2, 3 - ]); + var faces = new Uint16Array( [ + 0, 1, 2, + 0, 2, 3 + ] ); - vertexBuffer = gl.createBuffer(); - elementBuffer = gl.createBuffer(); + vertexBuffer = gl.createBuffer(); + elementBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); + 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); + gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, elementBuffer ); + gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, faces, gl.STATIC_DRAW ); - program = createProgram(); + program = createProgram(); - attributes = { - position: gl.getAttribLocation(program, 'position'), - uv: gl.getAttribLocation(program, 'uv') - }; + attributes = { + position: gl.getAttribLocation ( program, 'position' ), + uv: gl.getAttribLocation ( program, 'uv' ) + }; - uniforms = { - uvOffset: gl.getUniformLocation(program, 'uvOffset'), - uvScale: gl.getUniformLocation(program, 'uvScale'), + uniforms = { + uvOffset: gl.getUniformLocation( program, 'uvOffset' ), + uvScale: gl.getUniformLocation( program, 'uvScale' ), - rotation: gl.getUniformLocation(program, 'rotation'), - scale: gl.getUniformLocation(program, 'scale'), + 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'), + 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'), + 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'), + 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') - }; + alphaTest: gl.getUniformLocation( program, 'alphaTest' ) + }; - var canvas = document.createElement('canvas'); - canvas.width = 8; - canvas.height = 8; + var canvas = document.createElement( 'canvas' ); + canvas.width = 8; + canvas.height = 8; - var context = canvas.getContext('2d'); - context.fillStyle = 'white'; - context.fillRect(0, 0, 8, 8); + var context = canvas.getContext( '2d' ); + context.fillStyle = 'white'; + context.fillRect( 0, 0, 8, 8 ); - texture = new THREE.Texture(canvas); - texture.needsUpdate = true; + texture = new THREE.Texture( canvas ); + texture.needsUpdate = true; - }; + }; - this.render = function (scene, camera) { + this.render = function ( scene, camera ) { - if (sprites.length === 0) return; + if ( sprites.length === 0 ) return; - // setup gl + // setup gl - if (program === undefined) { + if ( program === undefined ) { - init(); + init(); - } + } - gl.useProgram(program); + gl.useProgram( program ); - renderer.state.initAttributes(); - renderer.state.enableAttribute(attributes.position); - renderer.state.enableAttribute(attributes.uv); - renderer.state.disableUnusedAttributes(); + state.initAttributes(); + state.enableAttribute( attributes.position ); + state.enableAttribute( attributes.uv ); + state.disableUnusedAttributes(); - gl.disable(gl.CULL_FACE); - gl.enable(gl.BLEND); + state.disable( gl.CULL_FACE ); + state.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.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.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, elementBuffer ); - gl.uniformMatrix4fv(uniforms.projectionMatrix, false, camera.projectionMatrix.elements); + gl.uniformMatrix4fv( uniforms.projectionMatrix, false, camera.projectionMatrix.elements ); - renderer.state.activeTexture(gl.TEXTURE0); - gl.uniform1i(uniforms.map, 0); + state.activeTexture( gl.TEXTURE0 ); + gl.uniform1i( uniforms.map, 0 ); - var oldFogType = 0; - var sceneFogType = 0; - var fog = scene.fog; + var oldFogType = 0; + var sceneFogType = 0; + var fog = scene.fog; - if (fog) { + if ( fog ) { - gl.uniform3f(uniforms.fogColor, fog.color.r, fog.color.g, fog.color.b); + gl.uniform3f( uniforms.fogColor, fog.color.r, fog.color.g, fog.color.b ); - if (fog instanceof THREE.Fog) { + if ( fog instanceof THREE.Fog ) { - gl.uniform1f(uniforms.fogNear, fog.near); - gl.uniform1f(uniforms.fogFar, fog.far); + gl.uniform1f( uniforms.fogNear, fog.near ); + gl.uniform1f( uniforms.fogFar, fog.far ); - gl.uniform1i(uniforms.fogType, 1); - oldFogType = 1; - sceneFogType = 1; + gl.uniform1i( uniforms.fogType, 1 ); + oldFogType = 1; + sceneFogType = 1; - } else if (fog instanceof THREE.FogExp2) { + } else if ( fog instanceof THREE.FogExp2 ) { - gl.uniform1f(uniforms.fogDensity, fog.density); + gl.uniform1f( uniforms.fogDensity, fog.density ); - gl.uniform1i(uniforms.fogType, 2); - oldFogType = 2; - sceneFogType = 2; + gl.uniform1i( uniforms.fogType, 2 ); + oldFogType = 2; + sceneFogType = 2; - } + } - } else { + } else { - gl.uniform1i(uniforms.fogType, 0); - oldFogType = 0; - sceneFogType = 0; + gl.uniform1i( uniforms.fogType, 0 ); + oldFogType = 0; + sceneFogType = 0; - } + } - // update positions and sort + // update positions and sort - for (var i = 0, l = sprites.length; i < l; i++) { + for ( var i = 0, l = sprites.length; i < l; i ++ ) { - var sprite = sprites[i]; + var sprite = sprites[ i ]; - sprite._modelViewMatrix.multiplyMatrices(camera.matrixWorldInverse, sprite.matrixWorld); - sprite.z = -sprite._modelViewMatrix.elements[14]; + sprite.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, sprite.matrixWorld ); + sprite.z = - sprite.modelViewMatrix.elements[ 14 ]; - } + } - sprites.sort(painterSortStable); + sprites.sort( painterSortStable ); - // render all sprites + // render all sprites - var scale = []; + var scale = []; - for (var i = 0, l = sprites.length; i < l; i++) { + for ( var i = 0, l = sprites.length; i < l; i ++ ) { - var sprite = sprites[i]; - var material = sprite.material; + var sprite = sprites[ i ]; + var material = sprite.material; - gl.uniform1f(uniforms.alphaTest, material.alphaTest); - gl.uniformMatrix4fv(uniforms.modelViewMatrix, false, sprite._modelViewMatrix.elements); + gl.uniform1f( uniforms.alphaTest, material.alphaTest ); + gl.uniformMatrix4fv( uniforms.modelViewMatrix, false, sprite.modelViewMatrix.elements ); - sprite.matrixWorld.decompose(spritePosition, spriteRotation, spriteScale); + sprite.matrixWorld.decompose( spritePosition, spriteRotation, spriteScale ); - scale[0] = spriteScale.x; - scale[1] = spriteScale.y; + scale[ 0 ] = spriteScale.x; + scale[ 1 ] = spriteScale.y; - var fogType = 0; + var fogType = 0; - if (scene.fog && material.fog) { + if ( scene.fog && material.fog ) { - fogType = sceneFogType; + fogType = sceneFogType; - } + } - if (oldFogType !== fogType) { + if ( oldFogType !== fogType ) { - gl.uniform1i(uniforms.fogType, fogType); - oldFogType = fogType; + gl.uniform1i( uniforms.fogType, fogType ); + oldFogType = fogType; - } + } - if (material.map !== null) { + 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); + 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 { + } else { - gl.uniform2f(uniforms.uvOffset, 0, 0); - gl.uniform2f(uniforms.uvScale, 1, 1); + 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.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); + gl.uniform1f( uniforms.rotation, material.rotation ); + gl.uniform2fv( uniforms.scale, scale ); - renderer.state.setBlending(material.blending, material.blendEquation, material.blendSrc, material.blendDst); - renderer.state.setDepthTest(material.depthTest); - renderer.state.setDepthWrite(material.depthWrite); + state.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst ); + state.setDepthTest( material.depthTest ); + state.setDepthWrite( material.depthWrite ); - if (material.map && material.map.image && material.map.image.width) { + if ( material.map && material.map.image && material.map.image.width ) { - renderer.setTexture(material.map, 0); + renderer.setTexture( material.map, 0 ); - } else { + } else { - renderer.setTexture(texture, 0); + renderer.setTexture( texture, 0 ); - } + } - gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); + gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); - } + } - // restore gl + // restore gl - gl.enable(gl.CULL_FACE); + state.enable( gl.CULL_FACE ); - renderer.resetGLState(); + renderer.resetGLState(); - }; + }; - function createProgram() { + function createProgram () { - var program = gl.createProgram(); + var program = gl.createProgram(); - var vertexShader = gl.createShader(gl.VERTEX_SHADER); - var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); + var vertexShader = gl.createShader( gl.VERTEX_SHADER ); + var fragmentShader = gl.createShader( gl.FRAGMENT_SHADER ); - gl.shaderSource(vertexShader, [ + gl.shaderSource( vertexShader, [ - 'precision ' + renderer.getPrecision() + ' float;', + 'precision ' + renderer.getPrecision() + ' float;', - 'uniform mat4 modelViewMatrix;', - 'uniform mat4 projectionMatrix;', - 'uniform float rotation;', - 'uniform vec2 scale;', - 'uniform vec2 uvOffset;', - 'uniform vec2 uvScale;', + 'uniform mat4 modelViewMatrix;', + 'uniform mat4 projectionMatrix;', + 'uniform float rotation;', + 'uniform vec2 scale;', + 'uniform vec2 uvOffset;', + 'uniform vec2 uvScale;', - 'attribute vec2 position;', - 'attribute vec2 uv;', + 'attribute vec2 position;', + 'attribute vec2 uv;', - 'varying vec2 vUV;', + 'varying vec2 vUV;', - 'void main() {', + 'void main() {', - 'vUV = uvOffset + uv * uvScale;', + 'vUV = uvOffset + uv * uvScale;', - 'vec2 alignedPosition = position * scale;', + '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;', + 'vec2 rotatedPosition;', + 'rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;', + 'rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;', - 'vec4 finalPosition;', + 'vec4 finalPosition;', - 'finalPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );', - 'finalPosition.xy += rotatedPosition;', - 'finalPosition = projectionMatrix * finalPosition;', + 'finalPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );', + 'finalPosition.xy += rotatedPosition;', + 'finalPosition = projectionMatrix * finalPosition;', - 'gl_Position = finalPosition;', + 'gl_Position = finalPosition;', - '}' + '}' - ].join('\n')); + ].join( '\n' ) ); - gl.shaderSource(fragmentShader, [ + gl.shaderSource( fragmentShader, [ - 'precision ' + renderer.getPrecision() + ' float;', + 'precision ' + renderer.getPrecision() + ' float;', - 'uniform vec3 color;', - 'uniform sampler2D map;', - 'uniform float opacity;', + '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;', + 'uniform int fogType;', + 'uniform vec3 fogColor;', + 'uniform float fogDensity;', + 'uniform float fogNear;', + 'uniform float fogFar;', + 'uniform float alphaTest;', - 'varying vec2 vUV;', + 'varying vec2 vUV;', - 'void main() {', + 'void main() {', - 'vec4 texture = texture2D( map, vUV );', + 'vec4 texture = texture2D( map, vUV );', - 'if ( texture.a < alphaTest ) discard;', + 'if ( texture.a < alphaTest ) discard;', - 'gl_FragColor = vec4( color * texture.xyz, texture.a * opacity );', + 'gl_FragColor = vec4( color * texture.xyz, texture.a * opacity );', - 'if ( fogType > 0 ) {', + 'if ( fogType > 0 ) {', - 'float depth = gl_FragCoord.z / gl_FragCoord.w;', - 'float fogFactor = 0.0;', + 'float depth = gl_FragCoord.z / gl_FragCoord.w;', + 'float fogFactor = 0.0;', - 'if ( fogType == 1 ) {', + 'if ( fogType == 1 ) {', - 'fogFactor = smoothstep( fogNear, fogFar, depth );', + 'fogFactor = smoothstep( fogNear, fogFar, depth );', - '} else {', + '} else {', - 'const float LOG2 = 1.442695;', - 'float fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );', - 'fogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );', + 'const float LOG2 = 1.442695;', + '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 );', + 'gl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );', - '}', + '}', - '}' + '}' - ].join('\n')); + ].join( '\n' ) ); - gl.compileShader(vertexShader); - gl.compileShader(fragmentShader); + gl.compileShader( vertexShader ); + gl.compileShader( fragmentShader ); - gl.attachShader(program, vertexShader); - gl.attachShader(program, fragmentShader); + gl.attachShader( program, vertexShader ); + gl.attachShader( program, fragmentShader ); - gl.linkProgram(program); + gl.linkProgram( program ); - return program; + return program; - } + } - function painterSortStable(a, b) { + function painterSortStable ( a, b ) { - if (a.z !== b.z) { + if ( a.z !== b.z ) { - return b.z - a.z; + return b.z - a.z; - } else { + } else { - return b.id - a.id; + return b.id - a.id; - } + } - } + } }; @@ -26082,31 +28474,31 @@ THREE.SpritePlugin = function (renderer, sprites) { THREE.GeometryUtils = { - merge: function (geometry1, geometry2, materialIndexOffset) { + merge: function ( geometry1, geometry2, materialIndexOffset ) { - THREE.warn('THREE.GeometryUtils: .merge() has been moved to Geometry. Use geometry.merge( geometry2, matrix, materialIndexOffset ) instead.'); + console.warn( 'THREE.GeometryUtils: .merge() has been moved to Geometry. Use geometry.merge( geometry2, matrix, materialIndexOffset ) instead.' ); - var matrix; + var matrix; - if (geometry2 instanceof THREE.Mesh) { + if ( geometry2 instanceof THREE.Mesh ) { - geometry2.matrixAutoUpdate && geometry2.updateMatrix(); + geometry2.matrixAutoUpdate && geometry2.updateMatrix(); - matrix = geometry2.matrix; - geometry2 = geometry2.geometry; + matrix = geometry2.matrix; + geometry2 = geometry2.geometry; - } + } - geometry1.merge(geometry2, matrix, materialIndexOffset); + geometry1.merge( geometry2, matrix, materialIndexOffset ); - }, + }, - center: function (geometry) { + center: function ( geometry ) { - THREE.warn('THREE.GeometryUtils: .center() has been moved to Geometry. Use geometry.center() instead.'); - return geometry.center(); + console.warn( 'THREE.GeometryUtils: .center() has been moved to Geometry. Use geometry.center() instead.' ); + return geometry.center(); - } + } }; @@ -26120,217 +28512,213 @@ THREE.GeometryUtils = { THREE.ImageUtils = { - crossOrigin: undefined, - - loadTexture: function (url, mapping, onLoad, onError) { - - var loader = new THREE.ImageLoader(); - loader.crossOrigin = this.crossOrigin; + crossOrigin: undefined, - var texture = new THREE.Texture(undefined, mapping); + loadTexture: function ( url, mapping, onLoad, onError ) { - loader.load(url, function (image) { + var loader = new THREE.ImageLoader(); + loader.crossOrigin = this.crossOrigin; - texture.image = image; - texture.needsUpdate = true; + var texture = new THREE.Texture( undefined, mapping ); - if (onLoad) onLoad(texture); + loader.load( url, function ( image ) { - }, undefined, function (event) { + texture.image = image; + texture.needsUpdate = true; - if (onError) onError(event); + if ( onLoad ) onLoad( texture ); - }); + }, undefined, function ( event ) { - texture.sourceFile = url; + if ( onError ) onError( event ); - return texture; + } ); - }, + texture.sourceFile = url; - loadTextureCube: function (array, mapping, onLoad, onError) { + return texture; - var images = []; + }, - var loader = new THREE.ImageLoader(); - loader.crossOrigin = this.crossOrigin; + loadTextureCube: function ( array, mapping, onLoad, onError ) { - var texture = new THREE.CubeTexture(images, mapping); + var images = []; - // no flipping needed for cube textures + var loader = new THREE.ImageLoader(); + loader.crossOrigin = this.crossOrigin; - texture.flipY = false; + var texture = new THREE.CubeTexture( images, mapping ); - var loaded = 0; + var loaded = 0; - var loadTexture = function (i) { + var loadTexture = function ( i ) { - loader.load(array[i], function (image) { + loader.load( array[ i ], function ( image ) { - texture.images[i] = image; + texture.images[ i ] = image; - loaded += 1; + loaded += 1; - if (loaded === 6) { + if ( loaded === 6 ) { - texture.needsUpdate = true; + texture.needsUpdate = true; - if (onLoad) onLoad(texture); + if ( onLoad ) onLoad( texture ); - } + } - }, undefined, onError); + }, undefined, onError ); - }; + }; - for (var i = 0, il = array.length; i < il; ++i) { + for ( var i = 0, il = array.length; i < il; ++ i ) { - loadTexture(i); + loadTexture( i ); - } + } - return texture; + return texture; - }, + }, - loadCompressedTexture: function () { + loadCompressedTexture: function () { - THREE.error('THREE.ImageUtils.loadCompressedTexture has been removed. Use THREE.DDSLoader instead.') + console.error( 'THREE.ImageUtils.loadCompressedTexture has been removed. Use THREE.DDSLoader instead.' ) - }, + }, - loadCompressedTextureCube: function () { + loadCompressedTextureCube: function () { - THREE.error('THREE.ImageUtils.loadCompressedTextureCube has been removed. Use THREE.DDSLoader instead.') + console.error( 'THREE.ImageUtils.loadCompressedTextureCube has been removed. Use THREE.DDSLoader instead.' ) - }, + }, - getNormalMap: function (image, depth) { + getNormalMap: function ( image, depth ) { - // Adapted from http://www.paulbrunt.co.uk/lab/heightnormal/ + // Adapted from http://www.paulbrunt.co.uk/lab/heightnormal/ - var cross = function (a, b) { + 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]]; + 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) { + var subtract = function ( a, b ) { - return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; + return [ a[ 0 ] - b[ 0 ], a[ 1 ] - b[ 1 ], a[ 2 ] - b[ 2 ] ]; - }; + }; - var normalize = function (a) { + 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]; + 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; + depth = depth | 1; - var width = image.width; - var height = image.height; + var width = image.width; + var height = image.height; - var canvas = document.createElement('canvas'); - canvas.width = width; - canvas.height = height; + var canvas = document.createElement( 'canvas' ); + canvas.width = width; + canvas.height = height; - var context = canvas.getContext('2d'); - context.drawImage(image, 0, 0); + 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; + 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 x = 0; x < width; x ++ ) { - for (var y = 0; y < height; y++) { + 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 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 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; + var normals = []; + var num_points = points.length; - for (var i = 0; i < num_points; i++) { + 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 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]; + var normal = [ 0, 0, 0 ]; - for (var i = 0; i < normals.length; i++) { + 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[ i ][ 0 ]; + normal[ 1 ] += normals[ i ][ 1 ]; + normal[ 2 ] += normals[ i ][ 2 ]; - } + } - normal[0] /= normals.length; - normal[1] /= normals.length; - normal[2] /= normals.length; + normal[ 0 ] /= normals.length; + normal[ 1 ] /= normals.length; + normal[ 2 ] /= normals.length; - var idx = ( y * width + x ) * 4; + 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; + 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); + context.putImageData( imageData, 0, 0 ); - return canvas; + return canvas; - }, + }, - generateDataTexture: function (width, height, color) { + generateDataTexture: function ( width, height, color ) { - var size = width * height; - var data = new Uint8Array(3 * size); + 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); + 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++) { + for ( var i = 0; i < size; i ++ ) { - data[i * 3] = r; - data[i * 3 + 1] = g; - data[i * 3 + 2] = b; + 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; + var texture = new THREE.DataTexture( data, width, height, THREE.RGBFormat ); + texture.needsUpdate = true; - return texture; + return texture; - } + } }; @@ -26342,38 +28730,38 @@ THREE.ImageUtils = { THREE.SceneUtils = { - createMultiMaterialObject: function (geometry, materials) { + createMultiMaterialObject: function ( geometry, materials ) { - var group = new THREE.Object3D(); + var group = new THREE.Group(); - for (var i = 0, l = materials.length; i < l; i++) { + for ( var i = 0, l = materials.length; i < l; i ++ ) { - group.add(new THREE.Mesh(geometry, materials[i])); + group.add( new THREE.Mesh( geometry, materials[ i ] ) ); - } + } - return group; + return group; - }, + }, - detach: function (child, parent, scene) { + detach: function ( child, parent, scene ) { - child.applyMatrix(parent.matrixWorld); - parent.remove(child); - scene.add(child); + child.applyMatrix( parent.matrixWorld ); + parent.remove( child ); + scene.add( child ); - }, + }, - attach: function (child, scene, parent) { + attach: function ( child, scene, parent ) { - var matrixWorldInverse = new THREE.Matrix4(); - matrixWorldInverse.getInverse(parent.matrixWorld); - child.applyMatrix(matrixWorldInverse); + var matrixWorldInverse = new THREE.Matrix4(); + matrixWorldInverse.getInverse( parent.matrixWorld ); + child.applyMatrix( matrixWorldInverse ); - scene.remove(child); - parent.add(child); + scene.remove( child ); + parent.add( child ); - } + } }; @@ -26387,262 +28775,264 @@ THREE.SceneUtils = { * * 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 * - * 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/ + * 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: {}, + faces: {}, - // Just for now. face[weight][style] + // Just for now. face[weight][style] - face: 'helvetiker', - weight: 'normal', - style: 'normal', - size: 150, - divisions: 10, + face: 'helvetiker', + weight: 'normal', + style: 'normal', + size: 150, + divisions: 10, - getFace: function () { + getFace: function () { - try { + try { - return this.faces[this.face][this.weight][this.style]; + return this.faces[ this.face.toLowerCase() ][ this.weight ][ this.style ]; - } catch (e) { + } catch ( e ) { - throw "The font " + this.face + " with " + this.weight + " weight and " + this.style + " style is missing." + throw "The font " + this.face + " with " + this.weight + " weight and " + this.style + " style is missing." - } + } - }, + }, - loadFace: function (data) { + loadFace: function ( data ) { - var family = data.familyName.toLowerCase(); + var family = data.familyName.toLowerCase(); - var ThreeFont = this; + var ThreeFont = this; - ThreeFont.faces[family] = ThreeFont.faces[family] || {}; + ThreeFont.faces[ family ] = ThreeFont.faces[ family ] || {}; - ThreeFont.faces[family][data.cssFontWeight] = ThreeFont.faces[family][data.cssFontWeight] || {}; - ThreeFont.faces[family][data.cssFontWeight][data.cssFontStyle] = data; + ThreeFont.faces[ family ][ data.cssFontWeight ] = ThreeFont.faces[ family ][ data.cssFontWeight ] || {}; + ThreeFont.faces[ family ][ data.cssFontWeight ][ data.cssFontStyle ] = data; - ThreeFont.faces[family][data.cssFontWeight][data.cssFontStyle] = data; + ThreeFont.faces[ family ][ data.cssFontWeight ][ data.cssFontStyle ] = data; - return data; + return data; - }, + }, - drawText: function (text) { + drawText: function ( text ) { - // RenderText + // RenderText - var i, - face = this.getFace(), - scale = this.size / face.resolution, - offset = 0, - chars = String(text).split(''), - length = chars.length; + var i, + face = this.getFace(), + scale = this.size / face.resolution, + offset = 0, + chars = String( text ).split( '' ), + length = chars.length; - var fontPaths = []; + var fontPaths = []; - for (i = 0; i < length; i++) { + for ( i = 0; i < length; i ++ ) { - var path = new THREE.Path(); + var path = new THREE.Path(); - var ret = this.extractGlyphPoints(chars[i], face, scale, offset, path); - offset += ret.offset; + var ret = this.extractGlyphPoints( chars[ i ], face, scale, offset, path ); + offset += ret.offset; - fontPaths.push(ret.path); + fontPaths.push( ret.path ); - } + } - // get the width + // get the width - var width = offset / 2; - // - // for ( p = 0; p < allPts.length; p++ ) { - // - // allPts[ p ].x -= 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; + //var extract = this.extractPoints( allPts, characterPts ); + //extract.contour = allPts; - //extract.paths = fontPaths; - //extract.offset = width; + //extract.paths = fontPaths; + //extract.offset = width; - return {paths: fontPaths, 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['?']; + extractGlyphPoints: function ( c, face, scale, offset, path ) { - if (!glyph) return; + var pts = []; - if (glyph.o) { + 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[ '?' ]; - outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split(' ') ); - length = outline.length; + if ( ! glyph ) return; - scaleX = scale; - scaleY = scale; + if ( glyph.o ) { - for (i = 0; i < length;) { + outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) ); + length = outline.length; - action = outline[i++]; + scaleX = scale; + scaleY = scale; - //THREE.log( action ); + for ( i = 0; i < length; ) { - switch (action) { + action = outline[ i ++ ]; - case 'm': + //console.log( action ); - // Move To + switch ( action ) { - x = outline[i++] * scaleX + offset; - y = outline[i++] * scaleY; + case 'm': - path.moveTo(x, y); - break; + // Move To - case 'l': + x = outline[ i ++ ] * scaleX + offset; + y = outline[ i ++ ] * scaleY; - // Line To + path.moveTo( x, y ); + break; - x = outline[i++] * scaleX + offset; - y = outline[i++] * scaleY; - path.lineTo(x, y); - break; + case 'l': - case 'q': + // Line To - // QuadraticCurveTo + x = outline[ i ++ ] * scaleX + offset; + y = outline[ i ++ ] * scaleY; + path.lineTo( x, y ); + break; - cpx = outline[i++] * scaleX + offset; - cpy = outline[i++] * scaleY; - cpx1 = outline[i++] * scaleX + offset; - cpy1 = outline[i++] * scaleY; + case 'q': - path.quadraticCurveTo(cpx1, cpy1, cpx, cpy); + // QuadraticCurveTo - laste = pts[pts.length - 1]; + cpx = outline[ i ++ ] * scaleX + offset; + cpy = outline[ i ++ ] * scaleY; + cpx1 = outline[ i ++ ] * scaleX + offset; + cpy1 = outline[ i ++ ] * scaleY; - if (laste) { + path.quadraticCurveTo( cpx1, cpy1, cpx, cpy ); - cpx0 = laste.x; - cpy0 = laste.y; + laste = pts[ pts.length - 1 ]; - for (i2 = 1, divisions = this.divisions; i2 <= divisions; i2++) { + if ( laste ) { - var t = i2 / divisions; - THREE.Shape.Utils.b2(t, cpx0, cpx1, cpx); - THREE.Shape.Utils.b2(t, cpy0, cpy1, cpy); - } + cpx0 = laste.x; + cpy0 = laste.y; - } + for ( i2 = 1, divisions = this.divisions; i2 <= divisions; i2 ++ ) { - break; + var t = i2 / divisions; + THREE.Shape.Utils.b2( t, cpx0, cpx1, cpx ); + THREE.Shape.Utils.b2( t, cpy0, cpy1, cpy ); - 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; + break; - path.bezierCurveTo(cpx1, cpy1, cpx2, cpy2, cpx, cpy); + case 'b': - laste = pts[pts.length - 1]; + // Cubic Bezier Curve - if (laste) { + 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; - cpx0 = laste.x; - cpy0 = laste.y; + path.bezierCurveTo( cpx1, cpy1, cpx2, cpy2, cpx, cpy ); - for (i2 = 1, divisions = this.divisions; i2 <= divisions; i2++) { + laste = pts[ pts.length - 1 ]; - var t = i2 / divisions; - THREE.Shape.Utils.b3(t, cpx0, cpx1, cpx2, cpx); - THREE.Shape.Utils.b3(t, cpy0, cpy1, cpy2, cpy); + if ( laste ) { - } + cpx0 = laste.x; + cpy0 = laste.y; - } + for ( i2 = 1, divisions = this.divisions; i2 <= divisions; i2 ++ ) { - break; + var t = i2 / divisions; + THREE.Shape.Utils.b3( t, cpx0, cpx1, cpx2, cpx ); + THREE.Shape.Utils.b3( t, cpy0, cpy1, cpy2, cpy ); - } + } - } - } + } + break; - return {offset: glyph.ha * scale, path: path}; - } + } + + } + + } + + + + return { offset: glyph.ha * scale, path: path }; + + } }; -THREE.FontUtils.generateShapes = function (text, parameters) { +THREE.FontUtils.generateShapes = function ( text, parameters ) { - // Parameters + // Parameters - parameters = parameters || {}; + parameters = parameters || {}; - var size = parameters.size !== undefined ? parameters.size : 100; - var curveSegments = parameters.curveSegments !== undefined ? parameters.curveSegments : 4; + 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'; + 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.size = size; + THREE.FontUtils.divisions = curveSegments; - THREE.FontUtils.face = font; - THREE.FontUtils.weight = weight; - THREE.FontUtils.style = style; + THREE.FontUtils.face = font; + THREE.FontUtils.weight = weight; + THREE.FontUtils.style = style; - // Get a Font data json object + // Get a Font data json object - var data = THREE.FontUtils.drawText(text); + var data = THREE.FontUtils.drawText( text ); - var paths = data.paths; - var shapes = []; + var paths = data.paths; + var shapes = []; - for (var p = 0, pl = paths.length; p < pl; p++) { + for ( var p = 0, pl = paths.length; p < pl; p ++ ) { - Array.prototype.push.apply(shapes, paths[p].toShapes()); + Array.prototype.push.apply( shapes, paths[ p ].toShapes() ); - } + } - return shapes; + return shapes; }; @@ -26662,201 +29052,189 @@ THREE.FontUtils.generateShapes = function (text, parameters) { */ -(function (namespace) { +( function ( namespace ) { - var EPSILON = 0.0000000001; + var EPSILON = 0.0000000001; - // takes in an contour array and returns + // takes in an contour array and returns - var process = function (contour, indices) { + var process = function ( contour, indices ) { - var n = contour.length; + var n = contour.length; - if (n < 3) return null; + if ( n < 3 ) return null; - var result = [], - verts = [], - vertIndices = []; + var result = [], + verts = [], + vertIndices = []; - /* we want a counter-clockwise polygon in verts */ + /* we want a counter-clockwise polygon in verts */ - var u, v, w; + var u, v, w; - if (area(contour) > 0.0) { + if ( area( contour ) > 0.0 ) { - for (v = 0; v < n; v++) verts[v] = v; + for ( v = 0; v < n; v ++ ) verts[ v ] = v; - } else { + } else { - for (v = 0; v < n; v++) verts[v] = ( n - 1 ) - v; + for ( v = 0; v < n; v ++ ) verts[ v ] = ( n - 1 ) - v; - } + } - var nv = n; + var nv = n; - /* remove nv - 2 vertices, creating 1 triangle every time */ + /* remove nv - 2 vertices, creating 1 triangle every time */ - var count = 2 * nv; - /* error detection */ + var count = 2 * nv; /* error detection */ - for (v = nv - 1; nv > 2;) { + for ( v = nv - 1; nv > 2; ) { - /* if we loop, it is probably a non-simple polygon */ + /* if we loop, it is probably a non-simple polygon */ - if (( count-- ) <= 0) { + if ( ( count -- ) <= 0 ) { - //** Triangulate: ERROR - probable bad polygon! + //** Triangulate: ERROR - probable bad polygon! - //throw ( "Warning, unable to triangulate polygon!" ); - //return null; - // Sometimes warning is fine, especially polygons are triangulated in reverse. - THREE.warn('THREE.FontUtils: Warning, unable to triangulate polygon! in Triangulate.process()'); + //throw ( "Warning, unable to triangulate polygon!" ); + //return null; + // Sometimes warning is fine, especially polygons are triangulated in reverse. + console.warn( 'THREE.FontUtils: Warning, unable to triangulate polygon! in Triangulate.process()' ); - if (indices) return vertIndices; - return result; + if ( indices ) return vertIndices; + return result; - } + } - /* three consecutive vertices in current polygon, */ + /* 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 */ + 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)) { + if ( snip( contour, u, v, w, nv, verts ) ) { - var a, b, c, s, t; + var a, b, c, s, t; - /* true names of the vertices */ + /* true names of the vertices */ - a = verts[u]; - b = verts[v]; - c = verts[w]; + a = verts[ u ]; + b = verts[ v ]; + c = verts[ w ]; - /* output Triangle */ + /* output Triangle */ - result.push([contour[a], - contour[b], - contour[c]]); + result.push( [ contour[ a ], + contour[ b ], + contour[ c ] ] ); - vertIndices.push([verts[u], verts[v], verts[w]]); + vertIndices.push( [ verts[ u ], verts[ v ], verts[ w ] ] ); - /* remove v from the remaining polygon */ + /* remove v from the remaining polygon */ - for (s = v, t = v + 1; t < nv; s++, t++) { + for ( s = v, t = v + 1; t < nv; s ++, t ++ ) { - verts[s] = verts[t]; + verts[ s ] = verts[ t ]; - } + } - nv--; + nv --; - /* reset error detection counter */ + /* reset error detection counter */ - count = 2 * nv; + count = 2 * nv; - } + } - } + } - if (indices) return vertIndices; - return result; + if ( indices ) return vertIndices; + return result; - }; + }; - // calculate area of the contour polygon + // calculate area of the contour polygon - var area = function (contour) { + var area = function ( contour ) { - var n = contour.length; - var a = 0.0; + var n = contour.length; + var a = 0.0; - for (var p = n - 1, q = 0; q < n; p = q++) { + for ( var p = n - 1, q = 0; q < n; p = q ++ ) { - a += contour[p].x * contour[q].y - contour[q].x * contour[p].y; + a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y; - } + } - return a * 0.5; + return a * 0.5; - }; + }; - var snip = function (contour, u, v, w, n, verts) { + var snip = function ( contour, u, v, w, n, verts ) { - var p; - var ax, ay, bx, by; - var cx, cy, px, py; + var p; + var ax, ay, bx, by; + var cx, cy, px, py; - ax = contour[verts[u]].x; - ay = contour[verts[u]].y; + ax = contour[ verts[ u ] ].x; + ay = contour[ verts[ u ] ].y; - bx = contour[verts[v]].x; - by = contour[verts[v]].y; + bx = contour[ verts[ v ] ].x; + by = contour[ verts[ v ] ].y; - cx = contour[verts[w]].x; - cy = contour[verts[w]].y; + cx = contour[ verts[ w ] ].x; + cy = contour[ verts[ w ] ].y; - if (EPSILON > ( ( ( bx - ax ) * ( cy - ay ) ) - ( ( by - ay ) * ( cx - ax ) ) )) return false; + 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; + 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; + aX = cx - bx; aY = cy - by; + bX = ax - cx; bY = ay - cy; + cX = bx - ax; cY = by - ay; - for (p = 0; p < n; p++) { + for ( p = 0; p < n; p ++ ) { - px = contour[verts[p]].x; - py = contour[verts[p]].y; + px = contour[ verts[ p ] ].x; + py = contour[ verts[ p ] ].y; - if (( ( px === ax ) && ( py === ay ) ) || - ( ( px === bx ) && ( py === by ) ) || - ( ( px === cx ) && ( py === cy ) )) continue; + 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; + 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 + // see if p is inside triangle abc - aCROSSbp = aX * bpy - aY * bpx; - cCROSSap = cX * apy - cY * apx; - bCROSScp = bX * cpy - bY * cpx; + aCROSSbp = aX * bpy - aY * bpx; + cCROSSap = cX * apy - cY * apx; + bCROSScp = bX * cpy - bY * cpx; - if (( aCROSSbp >= -EPSILON ) && ( bCROSScp >= -EPSILON ) && ( cCROSSap >= -EPSILON )) return false; + if ( ( aCROSSbp >= - EPSILON ) && ( bCROSScp >= - EPSILON ) && ( cCROSSap >= - EPSILON ) ) return false; - } + } - return true; + return true; - }; + }; - namespace.Triangulate = process; - namespace.Triangulate.area = area; + namespace.Triangulate = process; + namespace.Triangulate.area = area; - return namespace; + return namespace; -})(THREE.FontUtils); +} )( 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; + +THREE.typeface_js = { faces: THREE.FontUtils.faces, loadFace: THREE.FontUtils.loadFace }; +if ( typeof self !== 'undefined' ) self._typeface_js = THREE.typeface_js; // File:src/extras/audio/Audio.js @@ -26864,138 +29242,235 @@ THREE.typeface_js = self._typeface_js; * @author mrdoob / http://mrdoob.com/ */ -THREE.Audio = function (listener) { +THREE.Audio = function ( listener ) { - THREE.Object3D.call(this); + THREE.Object3D.call( this ); - this.type = 'Audio'; + this.type = 'Audio'; - this.context = listener.context; - this.source = this.context.createBufferSource(); - this.source.onended = this.onEnded.bind(this); + this.context = listener.context; + this.source = this.context.createBufferSource(); + this.source.onended = this.onEnded.bind( this ); - this.gain = this.context.createGain(); - this.gain.connect(this.context.destination); + this.gain = this.context.createGain(); + this.gain.connect( this.context.destination ); - this.panner = this.context.createPanner(); - this.panner.connect(this.gain); + this.panner = this.context.createPanner(); + this.panner.connect( this.gain ); - this.autoplay = false; + this.autoplay = false; - this.startTime = 0; - this.isPlaying = false; + this.startTime = 0; + this.playbackRate = 1; + this.isPlaying = false; }; -THREE.Audio.prototype = Object.create(THREE.Object3D.prototype); +THREE.Audio.prototype = Object.create( THREE.Object3D.prototype ); THREE.Audio.prototype.constructor = THREE.Audio; -THREE.Audio.prototype.load = function (file) { +THREE.Audio.prototype.load = function ( file ) { - var scope = this; + var scope = this; - var request = new XMLHttpRequest(); - request.open('GET', file, true); - request.responseType = 'arraybuffer'; - request.onload = function (e) { + var request = new XMLHttpRequest(); + request.open( 'GET', file, true ); + request.responseType = 'arraybuffer'; + request.onload = function ( e ) { - scope.context.decodeAudioData(this.response, function (buffer) { + scope.context.decodeAudioData( this.response, function ( buffer ) { - scope.source.buffer = buffer; + scope.source.buffer = buffer; - if (scope.autoplay) scope.play(); + if ( scope.autoplay ) scope.play(); - }); + } ); - }; - request.send(); + }; + request.send(); - return this; + return this; }; THREE.Audio.prototype.play = function () { - if (this.isPlaying === true) { + if ( this.isPlaying === true ) { - THREE.warn('THREE.Audio: Audio is already playing.'); - return; + console.warn( 'THREE.Audio: Audio is already playing.' ); + return; - } + } + + var source = this.context.createBufferSource(); - var source = this.context.createBufferSource(); + source.buffer = this.source.buffer; + source.loop = this.source.loop; + source.onended = this.source.onended; + source.start( 0, this.startTime ); + source.playbackRate.value = this.playbackRate; - source.buffer = this.source.buffer; - source.loop = this.source.loop; - source.onended = this.source.onended; - source.connect(this.panner); - source.start(0, this.startTime); + this.isPlaying = true; - this.isPlaying = true; + this.source = source; - this.source = source; + this.connect(); }; THREE.Audio.prototype.pause = function () { - this.source.stop(); - this.startTime = this.context.currentTime; + this.source.stop(); + this.startTime = this.context.currentTime; }; THREE.Audio.prototype.stop = function () { - this.source.stop(); - this.startTime = 0; + this.source.stop(); + this.startTime = 0; + +}; + +THREE.Audio.prototype.connect = function () { + + if ( this.filter !== undefined ) { + + this.source.connect( this.filter ); + this.filter.connect( this.panner ); + + } else { + + this.source.connect( this.panner ); + + } + +}; + +THREE.Audio.prototype.disconnect = function () { + + if ( this.filter !== undefined ) { + + this.source.disconnect( this.filter ); + this.filter.disconnect( this.panner ); + + } else { + + this.source.disconnect( this.panner ); + + } + +}; + +THREE.Audio.prototype.setFilter = function ( value ) { + + if ( this.isPlaying === true ) { + + this.disconnect(); + this.filter = value; + this.connect(); + + } else { + + this.filter = value; + + } + +}; + +THREE.Audio.prototype.getFilter = function () { + + return this.filter; + +}; + +THREE.Audio.prototype.setPlaybackRate = function ( value ) { + + this.playbackRate = value; + + if ( this.isPlaying === true ) { + + this.source.playbackRate.value = this.playbackRate; + + } + +}; + +THREE.Audio.prototype.getPlaybackRate = function () { + + return this.playbackRate; + +}; + +THREE.Audio.prototype.onEnded = function() { + + this.isPlaying = false; + +}; + +THREE.Audio.prototype.setLoop = function ( value ) { + + this.source.loop = value; + +}; + +THREE.Audio.prototype.getLoop = function () { + + return this.source.loop; + +}; + +THREE.Audio.prototype.setRefDistance = function ( value ) { + + this.panner.refDistance = value; }; -THREE.Audio.prototype.onEnded = function () { +THREE.Audio.prototype.getRefDistance = function () { - this.isPlaying = false; + return this.panner.refDistance; }; -THREE.Audio.prototype.setLoop = function (value) { +THREE.Audio.prototype.setRolloffFactor = function ( value ) { - this.source.loop = value; + this.panner.rolloffFactor = value; }; -THREE.Audio.prototype.setRefDistance = function (value) { +THREE.Audio.prototype.getRolloffFactor = function () { - this.panner.refDistance = value; + return this.panner.rolloffFactor; }; -THREE.Audio.prototype.setRolloffFactor = function (value) { +THREE.Audio.prototype.setVolume = function ( value ) { - this.panner.rolloffFactor = value; + this.gain.gain.value = value; }; -THREE.Audio.prototype.setVolume = function (value) { +THREE.Audio.prototype.getVolume = function () { - this.gain.gain.value = value; + return this.gain.gain.value; }; -THREE.Audio.prototype.updateMatrixWorld = (function () { +THREE.Audio.prototype.updateMatrixWorld = ( function () { - var position = new THREE.Vector3(); + var position = new THREE.Vector3(); - return function (force) { + return function updateMatrixWorld( force ) { - THREE.Object3D.prototype.updateMatrixWorld.call(this, force); + THREE.Object3D.prototype.updateMatrixWorld.call( this, force ); - position.setFromMatrixPosition(this.matrixWorld); + position.setFromMatrixPosition( this.matrixWorld ); - this.panner.setPosition(position.x, position.y, position.z); + this.panner.setPosition( position.x, position.y, position.z ); - }; + }; -})(); +} )(); // File:src/extras/audio/AudioListener.js @@ -27005,42 +29480,42 @@ THREE.Audio.prototype.updateMatrixWorld = (function () { THREE.AudioListener = function () { - THREE.Object3D.call(this); + THREE.Object3D.call( this ); - this.type = 'AudioListener'; + this.type = 'AudioListener'; - this.context = new ( window.AudioContext || window.webkitAudioContext )(); + this.context = new ( window.AudioContext || window.webkitAudioContext )(); }; -THREE.AudioListener.prototype = Object.create(THREE.Object3D.prototype); +THREE.AudioListener.prototype = Object.create( THREE.Object3D.prototype ); THREE.AudioListener.prototype.constructor = THREE.AudioListener; -THREE.AudioListener.prototype.updateMatrixWorld = (function () { +THREE.AudioListener.prototype.updateMatrixWorld = ( function () { - var position = new THREE.Vector3(); - var quaternion = new THREE.Quaternion(); - var scale = new THREE.Vector3(); + var position = new THREE.Vector3(); + var quaternion = new THREE.Quaternion(); + var scale = new THREE.Vector3(); - var orientation = new THREE.Vector3(); + var orientation = new THREE.Vector3(); - return function (force) { + return function updateMatrixWorld( force ) { - THREE.Object3D.prototype.updateMatrixWorld.call(this, force); + THREE.Object3D.prototype.updateMatrixWorld.call( this, force ); - var listener = this.context.listener; - var up = this.up; + var listener = this.context.listener; + var up = this.up; - this.matrixWorld.decompose(position, quaternion, scale); + this.matrixWorld.decompose( position, quaternion, scale ); - orientation.set(0, 0, -1).applyQuaternion(quaternion); + orientation.set( 0, 0, - 1 ).applyQuaternion( quaternion ); - listener.setPosition(position.x, position.y, position.z); - listener.setOrientation(orientation.x, orientation.y, orientation.z, up.x, up.y, up.z); + listener.setPosition( position.x, position.y, position.z ); + listener.setOrientation( orientation.x, orientation.y, orientation.z, up.x, up.y, up.z ); - }; + }; -})(); +} )(); // File:src/extras/core/Curve.js @@ -27077,7 +29552,7 @@ THREE.AudioListener.prototype.updateMatrixWorld = (function () { **/ /************************************************************** - * Abstract Curve base class + * Abstract Curve base class **************************************************************/ THREE.Curve = function () { @@ -27087,56 +29562,56 @@ THREE.Curve = function () { // Virtual base class method to overwrite and implement in subclasses // - t [0 .. 1] -THREE.Curve.prototype.getPoint = function (t) { +THREE.Curve.prototype.getPoint = function ( t ) { - THREE.warn("THREE.Curve: Warning, getPoint() not implemented!"); - return null; + console.warn( "THREE.Curve: 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) { +THREE.Curve.prototype.getPointAt = function ( u ) { - var t = this.getUtoTmapping(u); - return this.getPoint(t); + var t = this.getUtoTmapping( u ); + return this.getPoint( t ); }; // Get sequence of points using getPoint( t ) -THREE.Curve.prototype.getPoints = function (divisions) { +THREE.Curve.prototype.getPoints = function ( divisions ) { - if (!divisions) divisions = 5; + if ( ! divisions ) divisions = 5; - var d, pts = []; + var d, pts = []; - for (d = 0; d <= divisions; d++) { + for ( d = 0; d <= divisions; d ++ ) { - pts.push(this.getPoint(d / divisions)); + pts.push( this.getPoint( d / divisions ) ); - } + } - return pts; + return pts; }; // Get sequence of points using getPointAt( u ) -THREE.Curve.prototype.getSpacedPoints = function (divisions) { +THREE.Curve.prototype.getSpacedPoints = function ( divisions ) { - if (!divisions) divisions = 5; + if ( ! divisions ) divisions = 5; - var d, pts = []; + var d, pts = []; - for (d = 0; d <= divisions; d++) { + for ( d = 0; d <= divisions; d ++ ) { - pts.push(this.getPointAt(d / divisions)); + pts.push( this.getPointAt( d / divisions ) ); - } + } - return pts; + return pts; }; @@ -27144,133 +29619,135 @@ THREE.Curve.prototype.getSpacedPoints = function (divisions) { THREE.Curve.prototype.getLength = function () { - var lengths = this.getLengths(); - return lengths[lengths.length - 1]; + var lengths = this.getLengths(); + return lengths[ lengths.length - 1 ]; }; // Get list of cumulative segment lengths -THREE.Curve.prototype.getLengths = function (divisions) { +THREE.Curve.prototype.getLengths = function ( divisions ) { - if (!divisions) divisions = (this.__arcLengthDivisions) ? (this.__arcLengthDivisions) : 200; + if ( ! divisions ) divisions = ( this.__arcLengthDivisions ) ? ( this.__arcLengthDivisions ) : 200; - if (this.cacheArcLengths - && ( this.cacheArcLengths.length == divisions + 1 ) - && !this.needsUpdate) { + if ( this.cacheArcLengths + && ( this.cacheArcLengths.length === divisions + 1 ) + && ! this.needsUpdate ) { - //THREE.log( "cached", this.cacheArcLengths ); - return this.cacheArcLengths; + //console.log( "cached", this.cacheArcLengths ); + return this.cacheArcLengths; - } + } - this.needsUpdate = false; + this.needsUpdate = false; - var cache = []; - var current, last = this.getPoint(0); - var p, sum = 0; + var cache = []; + var current, last = this.getPoint( 0 ); + var p, sum = 0; - cache.push(0); + cache.push( 0 ); - for (p = 1; p <= divisions; p++) { + for ( p = 1; p <= divisions; p ++ ) { - current = this.getPoint(p / divisions); - sum += current.distanceTo(last); - cache.push(sum); - last = current; + current = this.getPoint ( p / divisions ); + sum += current.distanceTo( last ); + cache.push( sum ); + last = current; - } + } - this.cacheArcLengths = cache; + this.cacheArcLengths = cache; - return cache; // { sums: cache, sum:sum }; Sum is in the last element. + return cache; // { sums: cache, sum:sum }; Sum is in the last element. }; -THREE.Curve.prototype.updateArcLengths = function () { - this.needsUpdate = true; - this.getLengths(); +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 +// Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant -THREE.Curve.prototype.getUtoTmapping = function (u, distance) { +THREE.Curve.prototype.getUtoTmapping = function ( u, distance ) { - var arcLengths = this.getLengths(); + var arcLengths = this.getLengths(); - var i = 0, il = arcLengths.length; + var i = 0, il = arcLengths.length; - var targetArcLength; // The targeted u distance value to get + var targetArcLength; // The targeted u distance value to get - if (distance) { + if ( distance ) { - targetArcLength = distance; + targetArcLength = distance; - } else { + } else { - targetArcLength = u * arcLengths[il - 1]; + targetArcLength = u * arcLengths[ il - 1 ]; - } + } - //var time = Date.now(); + //var time = Date.now(); - // binary search for the index with largest value smaller than target u distance + // binary search for the index with largest value smaller than target u distance - var low = 0, high = il - 1, comparison; + var low = 0, high = il - 1, comparison; - while (low <= high) { + 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 + 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; + comparison = arcLengths[ i ] - targetArcLength; - if (comparison < 0) { + if ( comparison < 0 ) { - low = i + 1; + low = i + 1; - } else if (comparison > 0) { + } else if ( comparison > 0 ) { - high = i - 1; + high = i - 1; - } else { + } else { - high = i; - break; + high = i; + break; - // DONE + // DONE - } + } - } + } - i = high; + i = high; - //THREE.log('b' , i, low, high, Date.now()- time); + //console.log('b' , i, low, high, Date.now()- time); - if (arcLengths[i] == targetArcLength) { + if ( arcLengths[ i ] === targetArcLength ) { - var t = i / ( il - 1 ); - return t; + var t = i / ( il - 1 ); + return t; - } + } - // we could get finer grain at lengths, or use simple interpolatation between two points + // we could get finer grain at lengths, or use simple interpolation between two points - var lengthBefore = arcLengths[i]; - var lengthAfter = arcLengths[i + 1]; + var lengthBefore = arcLengths[ i ]; + var lengthAfter = arcLengths[ i + 1 ]; - var segmentLength = lengthAfter - lengthBefore; + var segmentLength = lengthAfter - lengthBefore; - // determine where we are between the 'before' and 'after' points + // determine where we are between the 'before' and 'after' points - var segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength; + var segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength; - // add that fractional amount to t + // add that fractional amount to t - var t = ( i + segmentFraction ) / ( il - 1 ); + var t = ( i + segmentFraction ) / ( il - 1 ); - return t; + return t; }; @@ -27279,81 +29756,84 @@ THREE.Curve.prototype.getUtoTmapping = function (u, distance) { // 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) { +THREE.Curve.prototype.getTangent = function( t ) { - var delta = 0.0001; - var t1 = t - delta; - var t2 = t + delta; + var delta = 0.0001; + var t1 = t - delta; + var t2 = t + delta; - // Capping in case of danger + // Capping in case of danger - if (t1 < 0) t1 = 0; - if (t2 > 1) t2 = 1; + if ( t1 < 0 ) t1 = 0; + if ( t2 > 1 ) t2 = 1; - var pt1 = this.getPoint(t1); - var pt2 = this.getPoint(t2); + var pt1 = this.getPoint( t1 ); + var pt2 = this.getPoint( t2 ); - var vec = pt2.clone().sub(pt1); - return vec.normalize(); + var vec = pt2.clone().sub( pt1 ); + return vec.normalize(); }; -THREE.Curve.prototype.getTangentAt = function (u) { +THREE.Curve.prototype.getTangentAt = function ( u ) { - var t = this.getUtoTmapping(u); - return this.getTangent(t); + var t = this.getUtoTmapping( u ); + return this.getTangent( t ); }; + + + /************************************************************** - * Utils + * Utils **************************************************************/ THREE.Curve.Utils = { - tangentQuadraticBezier: function (t, p0, p1, p2) { + tangentQuadraticBezier: function ( t, p0, p1, p2 ) { - return 2 * ( 1 - t ) * ( p1 - p0 ) + 2 * t * ( p2 - p1 ); + return 2 * ( 1 - t ) * ( p1 - p0 ) + 2 * t * ( p2 - p1 ); - }, + }, - // Puay Bing, thanks for helping with this derivative! + // Puay Bing, thanks for helping with this derivative! - tangentCubicBezier: function (t, p0, p1, p2, p3) { + 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; + 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) { + tangentSpline: function ( t, p0, p1, p2, p3 ) { - // To check if my formulas are correct + // 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 + 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; + return h00 + h10 + h01 + h11; - }, + }, - // Catmull-Rom + // Catmull-Rom - interpolate: function (p0, p1, p2, p3, t) { + 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; + 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; - } + } }; @@ -27361,18 +29841,18 @@ THREE.Curve.Utils = { // TODO: Transformation for Curves? /************************************************************** - * 3D Curves + * 3D Curves **************************************************************/ // A Factory method for creating new curve subclasses -THREE.Curve.create = function (constructor, getPointFunc) { +THREE.Curve.create = function ( constructor, getPointFunc ) { - constructor.prototype = Object.create(THREE.Curve.prototype); - constructor.prototype.constructor = constructor; - constructor.prototype.getPoint = getPointFunc; + constructor.prototype = Object.create( THREE.Curve.prototype ); + constructor.prototype.constructor = constructor; + constructor.prototype.getPoint = getPointFunc; - return constructor; + return constructor; }; @@ -27383,3203 +29863,2673 @@ THREE.Curve.create = function (constructor, getPointFunc) { * **/ -/************************************************************** - * 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.constructor = THREE.CurvePath; - -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); - - } - - 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 - - }; - - if (v3) { - - ret.maxZ = maxZ; - ret.minZ = minZ; - - } - - return ret; - -}; - -/************************************************************** - * Create Geometries Helpers - **************************************************************/ - -/// Generate geometry from path points (for Line or Points 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 +/************************************************************** + * Curved Path - a curve path is simply a array of connected + * curves, but retains the api of a curve + **************************************************************/ -THREE.CurvePath.prototype.getWrapPoints = function (oldPts, path) { +THREE.CurvePath = function () { - var bounds = this.getBoundingBox(); + this.curves = []; + this.bends = []; - var i, il, p, oldX, oldY, xNorm; + this.autoClose = false; // Automatically closes the path - for (i = 0, il = oldPts.length; i < il; i++) { +}; - p = oldPts[i]; +THREE.CurvePath.prototype = Object.create( THREE.Curve.prototype ); +THREE.CurvePath.prototype.constructor = THREE.CurvePath; - oldX = p.x; - oldY = p.y; +THREE.CurvePath.prototype.add = function ( curve ) { - xNorm = oldX / bounds.maxX; + this.curves.push( curve ); - // 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); +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 +}; - // check for out of bounds? +THREE.CurvePath.prototype.closePath = function() { - var pathPt = path.getPoint(xNorm); - var normal = path.getTangent(xNorm); - normal.set(-normal.y, normal.x).multiplyScalar(oldY); + // 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 ); - p.x = pathPt.x + normal.x; - p.y = pathPt.y + normal.y; + if ( ! startPoint.equals( endPoint ) ) { - } + this.curves.push( new THREE.LineCurve( endPoint, startPoint ) ); - return oldPts; + } }; +// To get accurate point with reference to +// entire path distance at time t, +// following has to be done: -// File:src/extras/core/Gyroscope.js +// 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') -/** - * @author alteredq / http://alteredqualia.com/ - */ +THREE.CurvePath.prototype.getPoint = function( t ) { -THREE.Gyroscope = function () { + var d = t * this.getLength(); + var curveLengths = this.getCurveLengths(); + var i = 0, diff, curve; - THREE.Object3D.call(this); + // To think about boundaries points. -}; + while ( i < curveLengths.length ) { -THREE.Gyroscope.prototype = Object.create(THREE.Object3D.prototype); -THREE.Gyroscope.prototype.constructor = THREE.Gyroscope; + if ( curveLengths[ i ] >= d ) { -THREE.Gyroscope.prototype.updateMatrixWorld = ( function () { + diff = curveLengths[ i ] - d; + curve = this.curves[ i ]; - var translationObject = new THREE.Vector3(); - var quaternionObject = new THREE.Quaternion(); - var scaleObject = new THREE.Vector3(); + var u = 1 - diff / curve.getLength(); - var translationWorld = new THREE.Vector3(); - var quaternionWorld = new THREE.Quaternion(); - var scaleWorld = new THREE.Vector3(); + return curve.getPointAt( u ); - return function (force) { + } - this.matrixAutoUpdate && this.updateMatrix(); + i ++; - // update matrixWorld + } - if (this.matrixWorldNeedsUpdate || force) { + return null; - if (this.parent) { + // loop where sum != 0, sum > d , sum+1 maxX ) maxX = p.x; + else if ( p.x < minX ) minX = p.x; - this.lineTo(vectors[v].x, vectors[v].y); + 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; -// startPath() endPath()? + } -THREE.Path.prototype.moveTo = function (x, y) { + sum.add( p ); - var args = Array.prototype.slice.call(arguments); - this.actions.push({action: THREE.PathActions.MOVE_TO, args: args}); + } -}; + var ret = { -THREE.Path.prototype.lineTo = function (x, y) { + minX: minX, + minY: minY, + maxX: maxX, + maxY: maxY - var args = Array.prototype.slice.call(arguments); + }; - var lastargs = this.actions[this.actions.length - 1].args; + if ( v3 ) { - var x0 = lastargs[lastargs.length - 2]; - var y0 = lastargs[lastargs.length - 1]; + ret.maxZ = maxZ; + ret.minZ = minZ; - 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}); + return ret; }; -THREE.Path.prototype.quadraticCurveTo = function (aCPx, aCPy, aX, aY) { +/************************************************************** + * Create Geometries Helpers + **************************************************************/ + +/// Generate geometry from path points (for Line or Points objects) + +THREE.CurvePath.prototype.createPointsGeometry = function( divisions ) { - var args = Array.prototype.slice.call(arguments); + var pts = this.getPoints( divisions, true ); + return this.createGeometry( pts ); - var lastargs = this.actions[this.actions.length - 1].args; +}; - var x0 = lastargs[lastargs.length - 2]; - var y0 = lastargs[lastargs.length - 1]; +// Generate geometry from equidistant sampling along the path - var curve = new THREE.QuadraticBezierCurve(new THREE.Vector2(x0, y0), - new THREE.Vector2(aCPx, aCPy), - new THREE.Vector2(aX, aY)); - this.curves.push(curve); +THREE.CurvePath.prototype.createSpacedPointsGeometry = function( divisions ) { - this.actions.push({action: THREE.PathActions.QUADRATIC_CURVE_TO, args: args}); + var pts = this.getSpacedPoints( divisions, true ); + return this.createGeometry( pts ); }; -THREE.Path.prototype.bezierCurveTo = function (aCP1x, aCP1y, - aCP2x, aCP2y, - aX, aY) { +THREE.CurvePath.prototype.createGeometry = function( points ) { - var args = Array.prototype.slice.call(arguments); + var geometry = new THREE.Geometry(); - var lastargs = this.actions[this.actions.length - 1].args; + for ( var i = 0; i < points.length; i ++ ) { - var x0 = lastargs[lastargs.length - 2]; - var y0 = lastargs[lastargs.length - 1]; + geometry.vertices.push( new THREE.Vector3( points[ i ].x, points[ i ].y, points[ i ].z || 0 ) ); - 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}); + return geometry; }; -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; +/************************************************************** + * Bend / Wrap Helper Methods + **************************************************************/ - 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); +// Wrap path / Bend modifiers? - var curve = new THREE.SplineCurve(npts); - this.curves.push(curve); +THREE.CurvePath.prototype.addWrapPath = function ( bendpath ) { - this.actions.push({action: THREE.PathActions.CSPLINE_THRU, args: args}); + this.bends.push( bendpath ); }; -// FUTURE: Change the API or follow canvas API? +THREE.CurvePath.prototype.getTransformedPoints = function( segments, bends ) { -THREE.Path.prototype.arc = function (aX, aY, aRadius, - aStartAngle, aEndAngle, aClockwise) { + var oldPts = this.getPoints( segments ); // getPoints getSpacedPoints + var i, il; - var lastargs = this.actions[this.actions.length - 1].args; - var x0 = lastargs[lastargs.length - 2]; - var y0 = lastargs[lastargs.length - 1]; + if ( ! bends ) { - this.absarc(aX + x0, aY + y0, aRadius, - aStartAngle, aEndAngle, aClockwise); + bends = this.bends; -}; + } -THREE.Path.prototype.absarc = function (aX, aY, aRadius, - aStartAngle, aEndAngle, aClockwise) { - this.absellipse(aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise); -}; + for ( i = 0, il = bends.length; i < il; i ++ ) { -THREE.Path.prototype.ellipse = function (aX, aY, xRadius, yRadius, - aStartAngle, aEndAngle, aClockwise) { + oldPts = this.getWrapPoints( oldPts, bends[ i ] ); - 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); + return oldPts; }; +THREE.CurvePath.prototype.getTransformedSpacedPoints = function( segments, bends ) { -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); + var oldPts = this.getSpacedPoints( segments ); - this.actions.push({action: THREE.PathActions.ELLIPSE, args: args}); + var i, il; -}; + if ( ! bends ) { -THREE.Path.prototype.getSpacedPoints = function (divisions, closedPath) { + bends = this.bends; - if (!divisions) divisions = 40; + } - var points = []; + for ( i = 0, il = bends.length; i < il; i ++ ) { - for (var i = 0; i < divisions; i++) { + oldPts = this.getWrapPoints( oldPts, bends[ i ] ); - points.push(this.getPoint(i / divisions)); + } - //if( !this.getPoint( i / divisions ) ) throw "DIE"; + return oldPts; - } +}; - // if ( closedPath ) { - // - // points.push( points[ 0 ] ); - // - // } +// This returns getPoints() bend/wrapped around the contour of a path. +// Read http://www.planetclegg.com/projects/WarpingTextToSplines.html - return points; +THREE.CurvePath.prototype.getWrapPoints = function ( oldPts, path ) { -}; + var bounds = this.getBoundingBox(); -/* Return an array of vectors based on contour of the path */ + var i, il, p, oldX, oldY, xNorm; -THREE.Path.prototype.getPoints = function (divisions, closedPath) { + for ( i = 0, il = oldPts.length; i < il; i ++ ) { - if (this.useSpacedPoints) { - THREE.log('tata'); - return this.getSpacedPoints(divisions, closedPath); - } + p = oldPts[ i ]; - divisions = divisions || 12; + oldX = p.x; + oldY = p.y; - var points = []; + xNorm = oldX / bounds.maxX; - var i, il, item, action, args; - var cpx, cpy, cpx2, cpy2, cpx1, cpy1, cpx0, cpy0, - laste, j, - t, tx, ty; + // 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 - for (i = 0, il = this.actions.length; i < il; i++) { + xNorm = path.getUtoTmapping( xNorm, oldX ); - item = this.actions[i]; + // check for out of bounds? - action = item.action; - args = item.args; + var pathPt = path.getPoint( xNorm ); + var normal = path.getTangent( xNorm ); + normal.set( - normal.y, normal.x ).multiplyScalar( oldY ); - switch (action) { + p.x = pathPt.x + normal.x; + p.y = pathPt.y + normal.y; - case THREE.PathActions.MOVE_TO: + } - points.push(new THREE.Vector2(args[0], args[1])); + return oldPts; - break; +}; - case THREE.PathActions.LINE_TO: +// File:src/extras/core/Path.js - points.push(new THREE.Vector2(args[0], args[1])); +/** + * @author zz85 / http://www.lab4games.net/zz85/blog + * Creates free form 2d path using series of points, lines or curves. + * + **/ - break; +THREE.Path = function ( points ) { - case THREE.PathActions.QUADRATIC_CURVE_TO: + THREE.CurvePath.call( this ); - cpx = args[2]; - cpy = args[3]; + this.actions = []; - cpx1 = args[0]; - cpy1 = args[1]; + if ( points ) { - if (points.length > 0) { + this.fromPoints( points ); - laste = points[points.length - 1]; + } - cpx0 = laste.x; - cpy0 = laste.y; +}; - } else { +THREE.Path.prototype = Object.create( THREE.CurvePath.prototype ); +THREE.Path.prototype.constructor = THREE.Path; - laste = this.actions[i - 1].args; +THREE.PathActions = { - cpx0 = laste[laste.length - 2]; - cpy0 = laste[laste.length - 1]; + 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 - for (j = 1; j <= divisions; j++) { +// Create path using straight lines to connect all points +// - vectors: array of Vector2 - t = j / divisions; +THREE.Path.prototype.fromPoints = function ( vectors ) { - tx = THREE.Shape.Utils.b2(t, cpx0, cpx1, cpx); - ty = THREE.Shape.Utils.b2(t, cpy0, cpy1, cpy); + this.moveTo( vectors[ 0 ].x, vectors[ 0 ].y ); - points.push(new THREE.Vector2(tx, ty)); + for ( var v = 1, vlen = vectors.length; v < vlen; v ++ ) { - } + this.lineTo( vectors[ v ].x, vectors[ v ].y ); - break; + } - case THREE.PathActions.BEZIER_CURVE_TO: +}; - cpx = args[4]; - cpy = args[5]; +// startPath() endPath()? - cpx1 = args[0]; - cpy1 = args[1]; +THREE.Path.prototype.moveTo = function ( x, y ) { - cpx2 = args[2]; - cpy2 = args[3]; + var args = Array.prototype.slice.call( arguments ); + this.actions.push( { action: THREE.PathActions.MOVE_TO, args: args } ); - if (points.length > 0) { +}; - laste = points[points.length - 1]; +THREE.Path.prototype.lineTo = function ( x, y ) { - cpx0 = laste.x; - cpy0 = laste.y; + var args = Array.prototype.slice.call( arguments ); - } else { + var lastargs = this.actions[ this.actions.length - 1 ].args; - laste = this.actions[i - 1].args; + var x0 = lastargs[ lastargs.length - 2 ]; + var y0 = lastargs[ lastargs.length - 1 ]; - cpx0 = laste[laste.length - 2]; - cpy0 = laste[laste.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 } ); +}; - for (j = 1; j <= divisions; j++) { +THREE.Path.prototype.quadraticCurveTo = function( aCPx, aCPy, aX, aY ) { - t = j / divisions; + var args = Array.prototype.slice.call( arguments ); - tx = THREE.Shape.Utils.b3(t, cpx0, cpx1, cpx2, cpx); - ty = THREE.Shape.Utils.b3(t, cpy0, cpy1, cpy2, cpy); + var lastargs = this.actions[ this.actions.length - 1 ].args; - points.push(new THREE.Vector2(tx, ty)); + 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 ); - break; + this.actions.push( { action: THREE.PathActions.QUADRATIC_CURVE_TO, args: args } ); - case THREE.PathActions.CSPLINE_THRU: +}; - laste = this.actions[i - 1].args; +THREE.Path.prototype.bezierCurveTo = function( aCP1x, aCP1y, + aCP2x, aCP2y, + aX, aY ) { - var last = new THREE.Vector2(laste[laste.length - 2], laste[laste.length - 1]); - var spts = [last]; + var args = Array.prototype.slice.call( arguments ); - var n = divisions * args[0].length; + var lastargs = this.actions[ this.actions.length - 1 ].args; - spts = spts.concat(args[0]); + var x0 = lastargs[ lastargs.length - 2 ]; + var y0 = lastargs[ lastargs.length - 1 ]; - var spline = new THREE.SplineCurve(spts); + 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 ); - for (j = 1; j <= n; j++) { + this.actions.push( { action: THREE.PathActions.BEZIER_CURVE_TO, args: args } ); - points.push(spline.getPointAt(j / n)); +}; - } +THREE.Path.prototype.splineThru = function( pts /*Array of Vector*/ ) { - break; + var args = Array.prototype.slice.call( arguments ); + var lastargs = this.actions[ this.actions.length - 1 ].args; - case THREE.PathActions.ARC: + 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 aX = args[0], aY = args[1], - aRadius = args[2], - aStartAngle = args[3], aEndAngle = args[4], - aClockwise = !!args[5]; + var curve = new THREE.SplineCurve( npts ); + this.curves.push( curve ); - var deltaAngle = aEndAngle - aStartAngle; - var angle; - var tdivisions = divisions * 2; + this.actions.push( { action: THREE.PathActions.CSPLINE_THRU, args: args } ); - for (j = 1; j <= tdivisions; j++) { +}; - t = j / tdivisions; +// FUTURE: Change the API or follow canvas API? - if (!aClockwise) { +THREE.Path.prototype.arc = function ( aX, aY, aRadius, + aStartAngle, aEndAngle, aClockwise ) { - t = 1 - t; + 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 ); - angle = aStartAngle + t * deltaAngle; + }; - tx = aX + aRadius * Math.cos(angle); - ty = aY + aRadius * Math.sin(angle); + THREE.Path.prototype.absarc = function ( aX, aY, aRadius, + aStartAngle, aEndAngle, aClockwise ) { - //THREE.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty); + this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); - points.push(new THREE.Vector2(tx, ty)); + }; - } +THREE.Path.prototype.ellipse = function ( aX, aY, xRadius, yRadius, + aStartAngle, aEndAngle, aClockwise, aRotation ) { - //THREE.log(points); + var lastargs = this.actions[ this.actions.length - 1 ].args; + var x0 = lastargs[ lastargs.length - 2 ]; + var y0 = lastargs[ lastargs.length - 1 ]; - break; + this.absellipse( aX + x0, aY + y0, xRadius, yRadius, + aStartAngle, aEndAngle, aClockwise, aRotation ); - 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]; +THREE.Path.prototype.absellipse = function ( aX, aY, xRadius, yRadius, + aStartAngle, aEndAngle, aClockwise, aRotation ) { - var deltaAngle = aEndAngle - aStartAngle; - var angle; - var tdivisions = divisions * 2; + var args = [ + aX, aY, + xRadius, yRadius, + aStartAngle, aEndAngle, + aClockwise, + aRotation || 0 // aRotation is optional. + ]; + var curve = new THREE.EllipseCurve( aX, aY, xRadius, yRadius, + aStartAngle, aEndAngle, aClockwise, aRotation ); + this.curves.push( curve ); - for (j = 1; j <= tdivisions; j++) { + var lastPoint = curve.getPoint( 1 ); + args.push( lastPoint.x ); + args.push( lastPoint.y ); - t = j / tdivisions; + this.actions.push( { action: THREE.PathActions.ELLIPSE, args: args } ); - if (!aClockwise) { + }; - t = 1 - t; +THREE.Path.prototype.getSpacedPoints = function ( divisions, closedPath ) { - } + if ( ! divisions ) divisions = 40; - angle = aStartAngle + t * deltaAngle; + var points = []; - tx = aX + xRadius * Math.cos(angle); - ty = aY + yRadius * Math.sin(angle); + for ( var i = 0; i < divisions; i ++ ) { - //THREE.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty); + points.push( this.getPoint( i / divisions ) ); - points.push(new THREE.Vector2(tx, ty)); + //if( !this.getPoint( i / divisions ) ) throw "DIE"; - } + } - //THREE.log(points); + // if ( closedPath ) { + // + // points.push( points[ 0 ] ); + // + // } - break; + return points; - } // end switch +}; - } +/* Return an array of vectors based on contour of the path */ +THREE.Path.prototype.getPoints = function( divisions, closedPath ) { - // 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) { + if ( this.useSpacedPoints ) { - points.push(points[0]); + return this.getSpacedPoints( divisions, closedPath ); - } + } - return points; + divisions = divisions || 12; -}; + var points = []; -// -// Breaks path into shapes -// -// Assumptions (if parameter isCCW==true the opposite holds): -// - solid shapes are defined clockwise (CW) -// - holes are defined counterclockwise (CCW) -// -// If parameter noHoles==true: -// - all subPaths are regarded as solid shapes -// - definition order CW/CCW has no relevance -// + var i, il, item, action, args; + var cpx, cpy, cpx2, cpy2, cpx1, cpy1, cpx0, cpy0, + laste, j, + t, tx, ty; -THREE.Path.prototype.toShapes = function (isCCW, noHoles) { + for ( i = 0, il = this.actions.length; i < il; i ++ ) { - function extractSubpaths(inActions) { + item = this.actions[ i ]; - var i, il, item, action, args; + action = item.action; + args = item.args; - var subPaths = [], lastPath = new THREE.Path(); + switch ( action ) { - for (i = 0, il = inActions.length; i < il; i++) { + case THREE.PathActions.MOVE_TO: - item = inActions[i]; + points.push( new THREE.Vector2( args[ 0 ], args[ 1 ] ) ); - args = item.args; - action = item.action; + break; - if (action == THREE.PathActions.MOVE_TO) { + case THREE.PathActions.LINE_TO: - if (lastPath.actions.length != 0) { + points.push( new THREE.Vector2( args[ 0 ], args[ 1 ] ) ); - subPaths.push(lastPath); - lastPath = new THREE.Path(); + break; - } + case THREE.PathActions.QUADRATIC_CURVE_TO: - } + cpx = args[ 2 ]; + cpy = args[ 3 ]; - lastPath[action].apply(lastPath, args); + cpx1 = args[ 0 ]; + cpy1 = args[ 1 ]; - } + if ( points.length > 0 ) { - if (lastPath.actions.length != 0) { + laste = points[ points.length - 1 ]; - subPaths.push(lastPath); + cpx0 = laste.x; + cpy0 = laste.y; - } + } else { - // THREE.log(subPaths); + laste = this.actions[ i - 1 ].args; - return subPaths; - } + cpx0 = laste[ laste.length - 2 ]; + cpy0 = laste[ laste.length - 1 ]; - function toShapesNoHoles(inSubpaths) { + } - var shapes = []; + for ( j = 1; j <= divisions; j ++ ) { - for (var i = 0, il = inSubpaths.length; i < il; i++) { + t = j / divisions; - var tmpPath = inSubpaths[i]; + tx = THREE.Shape.Utils.b2( t, cpx0, cpx1, cpx ); + ty = THREE.Shape.Utils.b2( t, cpy0, cpy1, cpy ); - var tmpShape = new THREE.Shape(); - tmpShape.actions = tmpPath.actions; - tmpShape.curves = tmpPath.curves; + points.push( new THREE.Vector2( tx, ty ) ); - shapes.push(tmpShape); - } + } - //THREE.log("shape", shapes); + break; - return shapes; - } + case THREE.PathActions.BEZIER_CURVE_TO: - 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; - } + cpx = args[ 4 ]; + cpy = args[ 5 ]; + cpx1 = args[ 0 ]; + cpy1 = args[ 1 ]; - var subPaths = extractSubpaths(this.actions); - if (subPaths.length == 0) return []; + cpx2 = args[ 2 ]; + cpy2 = args[ 3 ]; - if (noHoles === true) return toShapesNoHoles(subPaths); + if ( points.length > 0 ) { + laste = points[ points.length - 1 ]; - var solid, tmpPath, tmpShape, shapes = []; + cpx0 = laste.x; + cpy0 = laste.y; - if (subPaths.length == 1) { + } else { - tmpPath = subPaths[0]; - tmpShape = new THREE.Shape(); - tmpShape.actions = tmpPath.actions; - tmpShape.curves = tmpPath.curves; - shapes.push(tmpShape); - return shapes; + laste = this.actions[ i - 1 ].args; - } + cpx0 = laste[ laste.length - 2 ]; + cpy0 = laste[ laste.length - 1 ]; - var holesFirst = !THREE.Shape.Utils.isClockWise(subPaths[0].getPoints()); - holesFirst = isCCW ? !holesFirst : holesFirst; + } - // THREE.log("Holes first", holesFirst); - var betterShapeHoles = []; - var newShapes = []; - var newShapeHoles = []; - var mainIdx = 0; - var tmpPoints; + for ( j = 1; j <= divisions; j ++ ) { - newShapes[mainIdx] = undefined; - newShapeHoles[mainIdx] = []; + t = j / divisions; - var i, il; + tx = THREE.Shape.Utils.b3( t, cpx0, cpx1, cpx2, cpx ); + ty = THREE.Shape.Utils.b3( t, cpy0, cpy1, cpy2, cpy ); - for (i = 0, il = subPaths.length; i < il; i++) { + points.push( new THREE.Vector2( tx, ty ) ); - tmpPath = subPaths[i]; - tmpPoints = tmpPath.getPoints(); - solid = THREE.Shape.Utils.isClockWise(tmpPoints); - solid = isCCW ? !solid : solid; + } - if (solid) { + break; - if ((!holesFirst ) && ( newShapes[mainIdx] )) mainIdx++; + case THREE.PathActions.CSPLINE_THRU: - newShapes[mainIdx] = {s: new THREE.Shape(), p: tmpPoints}; - newShapes[mainIdx].s.actions = tmpPath.actions; - newShapes[mainIdx].s.curves = tmpPath.curves; + laste = this.actions[ i - 1 ].args; - if (holesFirst) mainIdx++; - newShapeHoles[mainIdx] = []; + var last = new THREE.Vector2( laste[ laste.length - 2 ], laste[ laste.length - 1 ] ); + var spts = [ last ]; - //THREE.log('cw', i); + var n = divisions * args[ 0 ].length; - } else { + spts = spts.concat( args[ 0 ] ); - newShapeHoles[mainIdx].push({h: tmpPath, p: tmpPoints[0]}); + var spline = new THREE.SplineCurve( spts ); - //THREE.log('ccw', i); + for ( j = 1; j <= n; j ++ ) { - } + points.push( spline.getPointAt( j / n ) ); - } + } - // only Holes? -> probably all Shapes with wrong orientation - if (!newShapes[0]) return toShapesNoHoles(subPaths); - - - 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 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); - } - } - } - // THREE.log("ambigious: ", ambigious); - if (toChange.length > 0) { - // THREE.log("to change: ", toChange); - if (!ambigious) newShapeHoles = betterShapeHoles; - } - } + break; - 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); - } - } + case THREE.PathActions.ARC: - //THREE.log("shape", shapes); + var aX = args[ 0 ], aY = args[ 1 ], + aRadius = args[ 2 ], + aStartAngle = args[ 3 ], aEndAngle = args[ 4 ], + aClockwise = !! args[ 5 ]; - return shapes; + var deltaAngle = aEndAngle - aStartAngle; + var angle; + var tdivisions = divisions * 2; -}; + for ( j = 1; j <= tdivisions; j ++ ) { -// File:src/extras/core/Shape.js + t = j / tdivisions; -/** - * @author zz85 / http://www.lab4games.net/zz85/blog - * Defines a 2d shape plane using paths. - **/ + if ( ! aClockwise ) { -// 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. + t = 1 - t; -THREE.Shape = function () { + } - THREE.Path.apply(this, arguments); - this.holes = []; + angle = aStartAngle + t * deltaAngle; -}; + tx = aX + aRadius * Math.cos( angle ); + ty = aY + aRadius * Math.sin( angle ); -THREE.Shape.prototype = Object.create(THREE.Path.prototype); -THREE.Shape.prototype.constructor = THREE.Shape; + //console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty); -// Convenience method to return ExtrudeGeometry + points.push( new THREE.Vector2( tx, ty ) ); -THREE.Shape.prototype.extrude = function (options) { + } - var extruded = new THREE.ExtrudeGeometry(this, options); - return extruded; + //console.log(points); -}; + break; -// Convenience method to return ShapeGeometry + case THREE.PathActions.ELLIPSE: -THREE.Shape.prototype.makeGeometry = function (options) { + var aX = args[ 0 ], aY = args[ 1 ], + xRadius = args[ 2 ], + yRadius = args[ 3 ], + aStartAngle = args[ 4 ], aEndAngle = args[ 5 ], + aClockwise = !! args[ 6 ], + aRotation = args[ 7 ]; - var geometry = new THREE.ShapeGeometry(this, options); - return geometry; -}; + var deltaAngle = aEndAngle - aStartAngle; + var angle; + var tdivisions = divisions * 2; -// Get points of holes + var cos, sin; + if ( aRotation !== 0 ) { + + cos = Math.cos( aRotation ); + sin = Math.sin( aRotation ); -THREE.Shape.prototype.getPointsHoles = function (divisions) { + } - var i, il = this.holes.length, holesPts = []; + for ( j = 1; j <= tdivisions; j ++ ) { - for (i = 0; i < il; i++) { + t = j / tdivisions; - holesPts[i] = this.holes[i].getTransformedPoints(divisions, this.bends); + if ( ! aClockwise ) { - } + t = 1 - t; - return holesPts; + } -}; + angle = aStartAngle + t * deltaAngle; -// Get points of holes (spaced by regular distance) + tx = aX + xRadius * Math.cos( angle ); + ty = aY + yRadius * Math.sin( angle ); -THREE.Shape.prototype.getSpacedPointsHoles = function (divisions) { + if ( aRotation !== 0 ) { - var i, il = this.holes.length, holesPts = []; + var x = tx, y = ty; - for (i = 0; i < il; i++) { + // Rotate the point about the center of the ellipse. + tx = ( x - aX ) * cos - ( y - aY ) * sin + aX; + ty = ( x - aX ) * sin + ( y - aY ) * cos + aY; - holesPts[i] = this.holes[i].getTransformedSpacedPoints(divisions, this.bends); + } - } + //console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty); - return holesPts; + points.push( new THREE.Vector2( tx, ty ) ); -}; + } + //console.log(points); -// Get points of shape and holes (keypoints based on segments parameter) + break; -THREE.Shape.prototype.extractAllPoints = function (divisions) { + } // end switch - return { + } - shape: this.getTransformedPoints(divisions), - holes: this.getPointsHoles(divisions) - }; -}; + // 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 ) { -THREE.Shape.prototype.extractPoints = function (divisions) { + points.push( points[ 0 ] ); - if (this.useSpacedPoints) { - return this.extractAllSpacedPoints(divisions); - } + } - return this.extractAllPoints(divisions); + return points; }; // -// THREE.Shape.prototype.extractAllPointsWithBend = function ( divisions, bend ) { -// -// return { +// Breaks path into shapes // -// shape: this.transform( bend, divisions ), -// holes: this.getPointsHoles( divisions, bend ) +// Assumptions (if parameter isCCW==true the opposite holds): +// - solid shapes are defined clockwise (CW) +// - holes are defined counterclockwise (CCW) // -// }; +// If parameter noHoles==true: +// - all subPaths are regarded as solid shapes +// - definition order CW/CCW has no relevance // -// }; - -// Get points of shape and holes (spaced by regular distance) -THREE.Shape.prototype.extractAllSpacedPoints = function (divisions) { +THREE.Path.prototype.toShapes = function( isCCW, noHoles ) { - return { + function extractSubpaths( inActions ) { - shape: this.getTransformedSpacedPoints(divisions), - holes: this.getSpacedPointsHoles(divisions) + var i, il, item, action, args; - }; + var subPaths = [], lastPath = new THREE.Path(); -}; - -/************************************************************** - * Utils - **************************************************************/ + for ( i = 0, il = inActions.length; i < il; i ++ ) { -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; - // THREE.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. - // THREE.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) { - // THREE.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) { - // THREE.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; - } + item = inActions[ i ]; - var holeIndex, shapeIndex, - shapePt, holePt, - holeIdx, cutKey, failedCuts = [], - tmpShape1, tmpShape2, - tmpHole1, tmpHole2; + args = item.args; + action = item.action; - for (var h = 0, hl = holes.length; h < hl; h++) { + if ( action === THREE.PathActions.MOVE_TO ) { - indepHoles.push(h); + if ( lastPath.actions.length !== 0 ) { - } + subPaths.push( lastPath ); + lastPath = new THREE.Path(); - var minShapeIndex = 0; - var counter = indepHoles.length * 2; - while (indepHoles.length > 0) { - counter--; - if (counter < 0) { - THREE.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 = minShapeIndex; shapeIndex < shape.length; shapeIndex++) { + } - shapePt = shape[shapeIndex]; - holeIndex = -1; + lastPath[ action ].apply( lastPath, args ); - // 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; + if ( lastPath.actions.length !== 0 ) { - 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; + subPaths.push( lastPath ); - 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); + // console.log(subPaths); - shape = tmpShape1.concat(tmpHole1).concat(tmpHole2).concat(tmpShape2); + return subPaths; - minShapeIndex = shapeIndex; + } - // Debug only, to show the selected cuts - // glob_CutLines.push( [ shapePt, holePt ] ); + function toShapesNoHoles( inSubpaths ) { - break; - } - if (holeIndex >= 0) break; // hole-vertex found + var shapes = []; - failedCuts[cutKey] = true; // remember failure - } - if (holeIndex >= 0) break; // hole-vertex found - } - } + for ( var i = 0, il = inSubpaths.length; i < il; i ++ ) { - return shape; - /* shape with no holes */ - } + var tmpPath = inSubpaths[ i ]; + var tmpShape = new THREE.Shape(); + tmpShape.actions = tmpPath.actions; + tmpShape.curves = tmpPath.curves; - var i, il, f, face, - key, index, - allPointsMap = {}; + shapes.push( tmpShape ); - // 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(); + //console.log("shape", shapes); - for (var h = 0, hl = holes.length; h < hl; h++) { + return shapes; - Array.prototype.push.apply(allpoints, holes[h]); + } - } + function isPointInsidePolygon( inPt, inPolygon ) { - //THREE.log( "allpoints",allpoints, allpoints.length ); + var EPSILON = 0.0000000001; - // prepare all points map + var polyLen = inPolygon.length; - for (i = 0, il = allpoints.length; i < il; i++) { + // 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 ++ ) { - key = allpoints[i].x + ":" + allpoints[i].y; + var edgeLowPt = inPolygon[ p ]; + var edgeHighPt = inPolygon[ q ]; - if (allPointsMap[key] !== undefined) { + var edgeDx = edgeHighPt.x - edgeLowPt.x; + var edgeDy = edgeHighPt.y - edgeLowPt.y; - THREE.warn("THREE.Shape: Duplicate point", key); + if ( Math.abs( edgeDy ) > EPSILON ) { - } + // not parallel + if ( edgeDy < 0 ) { - allPointsMap[key] = i; + edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx; + edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy; - } + } + if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) continue; - // remove holes by cutting paths to holes and adding them to the shape - var shapeWithoutHoles = removeHoles(contour, holes); + if ( inPt.y === edgeLowPt.y ) { - var triangles = THREE.FontUtils.Triangulate(shapeWithoutHoles, false); // True returns indices for points of spooled shape - //THREE.log( "triangles",triangles, triangles.length ); + if ( inPt.x === edgeLowPt.x ) return true; // inPt is on contour ? + // continue; // no intersection or edgeLowPt => doesn't count !!! - // check all face vertices against all points map + } else { - for (i = 0, il = triangles.length; i < il; i++) { + 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 - face = triangles[i]; + } - for (f = 0; f < 3; f++) { + } else { - key = face[f].x + ":" + face[f].y; + // parallel or collinear + if ( inPt.y !== edgeLowPt.y ) continue; // parallel + // edge 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; - index = allPointsMap[key]; + } - if (index !== undefined) { + } - face[f] = index; + return inside; - } + } - } - } + var subPaths = extractSubpaths( this.actions ); + if ( subPaths.length === 0 ) return []; - return triangles.concat(); + if ( noHoles === true ) return toShapesNoHoles( subPaths ); - }, - isClockWise: function (pts) { + var solid, tmpPath, tmpShape, shapes = []; - return THREE.FontUtils.Triangulate.area(pts) < 0; + if ( subPaths.length === 1 ) { - }, + tmpPath = subPaths[ 0 ]; + tmpShape = new THREE.Shape(); + tmpShape.actions = tmpPath.actions; + tmpShape.curves = tmpPath.curves; + shapes.push( tmpShape ); + return shapes; - // Bezier Curves formulas obtained from - // http://en.wikipedia.org/wiki/B%C3%A9zier_curve + } - // Quad Bezier Functions + var holesFirst = ! THREE.Shape.Utils.isClockWise( subPaths[ 0 ].getPoints() ); + holesFirst = isCCW ? ! holesFirst : holesFirst; - b2p0: function (t, p) { + // console.log("Holes first", holesFirst); - var k = 1 - t; - return k * k * p; + var betterShapeHoles = []; + var newShapes = []; + var newShapeHoles = []; + var mainIdx = 0; + var tmpPoints; - }, + newShapes[ mainIdx ] = undefined; + newShapeHoles[ mainIdx ] = []; - b2p1: function (t, p) { + var i, il; - return 2 * ( 1 - t ) * t * p; + 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; - b2p2: function (t, p) { + if ( solid ) { - return t * t * p; + 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; - b2: function (t, p0, p1, p2) { + if ( holesFirst ) mainIdx ++; + newShapeHoles[ mainIdx ] = []; - return this.b2p0(t, p0) + this.b2p1(t, p1) + this.b2p2(t, p2); + //console.log('cw', i); - }, + } else { - // Cubic Bezier Functions + newShapeHoles[ mainIdx ].push( { h: tmpPath, p: tmpPoints[ 0 ] } ); - b3p0: function (t, p) { + //console.log('ccw', i); - var k = 1 - t; - return k * k * k * p; + } - }, + } - b3p1: function (t, p) { + // only Holes? -> probably all Shapes with wrong orientation + if ( ! newShapes[ 0 ] ) return toShapesNoHoles( subPaths ); - var k = 1 - t; - return 3 * k * k * t * p; - }, + if ( newShapes.length > 1 ) { - b3p2: function (t, p) { + var ambiguous = false; + var toChange = []; - var k = 1 - t; - return 3 * k * t * t * p; + for ( var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { - }, + betterShapeHoles[ sIdx ] = []; - b3p3: function (t, p) { + } + for ( var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { - return t * t * t * p; + 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 ++ ) { - b3: function (t, p0, p1, p2, p3) { + if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) { - return this.b3p0(t, p0) + this.b3p1(t, p1) + this.b3p2(t, p2) + this.b3p3(t, p3); + if ( sIdx !== s2Idx ) toChange.push( { froms: sIdx, tos: s2Idx, hole: hIdx } ); + if ( hole_unassigned ) { - } + hole_unassigned = false; + betterShapeHoles[ s2Idx ].push( ho ); -}; + } else { + ambiguous = true; -// File:src/extras/curves/LineCurve.js + } -/************************************************************** - * Line - **************************************************************/ + } -THREE.LineCurve = function (v1, v2) { + } + if ( hole_unassigned ) { - this.v1 = v1; - this.v2 = v2; + betterShapeHoles[ sIdx ].push( ho ); -}; + } -THREE.LineCurve.prototype = Object.create(THREE.Curve.prototype); -THREE.LineCurve.prototype.constructor = THREE.LineCurve; + } -THREE.LineCurve.prototype.getPoint = function (t) { + } + // console.log("ambiguous: ", ambiguous); + if ( toChange.length > 0 ) { - var point = this.v2.clone().sub(this.v1); - point.multiplyScalar(t).add(this.v1); + // console.log("to change: ", toChange); + if ( ! ambiguous ) newShapeHoles = betterShapeHoles; - return point; + } -}; + } -// Line curve is linear, so we can overwrite default getPointAt + var tmpHoles, j, jl; + for ( i = 0, il = newShapes.length; i < il; i ++ ) { -THREE.LineCurve.prototype.getPointAt = function (u) { + tmpShape = newShapes[ i ].s; + shapes.push( tmpShape ); + tmpHoles = newShapeHoles[ i ]; + for ( j = 0, jl = tmpHoles.length; j < jl; j ++ ) { - return this.getPoint(u); + tmpShape.holes.push( tmpHoles[ j ].h ); -}; + } -THREE.LineCurve.prototype.getTangent = function (t) { + } - var tangent = this.v2.clone().sub(this.v1); + //console.log("shape", shapes); - return tangent.normalize(); + return shapes; }; -// File:src/extras/curves/QuadraticBezierCurve.js +// File:src/extras/core/Shape.js -/************************************************************** - * Quadratic Bezier curve - **************************************************************/ +/** + * @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.QuadraticBezierCurve = function (v0, v1, v2) { +THREE.Shape = function () { - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; + THREE.Path.apply( this, arguments ); + this.holes = []; }; -THREE.QuadraticBezierCurve.prototype = Object.create(THREE.Curve.prototype); -THREE.QuadraticBezierCurve.prototype.constructor = THREE.QuadraticBezierCurve; - - -THREE.QuadraticBezierCurve.prototype.getPoint = function (t) { +THREE.Shape.prototype = Object.create( THREE.Path.prototype ); +THREE.Shape.prototype.constructor = THREE.Shape; - var vector = new THREE.Vector2(); +// Convenience method to return ExtrudeGeometry - vector.x = THREE.Shape.Utils.b2(t, this.v0.x, this.v1.x, this.v2.x); - vector.y = THREE.Shape.Utils.b2(t, this.v0.y, this.v1.y, this.v2.y); +THREE.Shape.prototype.extrude = function ( options ) { - return vector; + var extruded = new THREE.ExtrudeGeometry( this, options ); + return extruded; }; +// Convenience method to return ShapeGeometry -THREE.QuadraticBezierCurve.prototype.getTangent = function (t) { - - var vector = new THREE.Vector2(); - - vector.x = THREE.Curve.Utils.tangentQuadraticBezier(t, this.v0.x, this.v1.x, this.v2.x); - vector.y = THREE.Curve.Utils.tangentQuadraticBezier(t, this.v0.y, this.v1.y, this.v2.y); - - // returns unit vector +THREE.Shape.prototype.makeGeometry = function ( options ) { - return vector.normalize(); + var geometry = new THREE.ShapeGeometry( this, options ); + return geometry; }; -// File:src/extras/curves/CubicBezierCurve.js - -/************************************************************** - * Cubic Bezier curve - **************************************************************/ +// Get points of holes -THREE.CubicBezierCurve = function (v0, v1, v2, v3) { +THREE.Shape.prototype.getPointsHoles = function ( divisions ) { - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; - this.v3 = v3; + var i, il = this.holes.length, holesPts = []; -}; + for ( i = 0; i < il; i ++ ) { -THREE.CubicBezierCurve.prototype = Object.create(THREE.Curve.prototype); -THREE.CubicBezierCurve.prototype.constructor = THREE.CubicBezierCurve; + holesPts[ i ] = this.holes[ i ].getTransformedPoints( divisions, this.bends ); -THREE.CubicBezierCurve.prototype.getPoint = function (t) { + } - var tx, ty; + return holesPts; - 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); +// Get points of holes (spaced by regular distance) -}; +THREE.Shape.prototype.getSpacedPointsHoles = function ( divisions ) { -THREE.CubicBezierCurve.prototype.getTangent = function (t) { + var i, il = this.holes.length, holesPts = []; - var tx, ty; + for ( i = 0; i < il; i ++ ) { - 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); + holesPts[ i ] = this.holes[ i ].getTransformedSpacedPoints( divisions, this.bends ); - var tangent = new THREE.Vector2(tx, ty); - tangent.normalize(); + } - return tangent; + return holesPts; }; -// File:src/extras/curves/SplineCurve.js - -/************************************************************** - * Spline curve - **************************************************************/ -THREE.SplineCurve = function (points /* array of Vector2 */) { +// Get points of shape and holes (keypoints based on segments parameter) - this.points = ( points == undefined ) ? [] : points; +THREE.Shape.prototype.extractAllPoints = function ( divisions ) { -}; + return { -THREE.SplineCurve.prototype = Object.create(THREE.Curve.prototype); -THREE.SplineCurve.prototype.constructor = THREE.SplineCurve; + shape: this.getTransformedPoints( divisions ), + holes: this.getPointsHoles( divisions ) -THREE.SplineCurve.prototype.getPoint = function (t) { + }; - var points = this.points; - var point = ( points.length - 1 ) * t; +}; - var intPoint = Math.floor(point); - var weight = point - intPoint; +THREE.Shape.prototype.extractPoints = function ( divisions ) { - var point0 = points[intPoint == 0 ? intPoint : intPoint - 1]; - var point1 = points[intPoint]; - var point2 = points[intPoint > points.length - 2 ? points.length - 1 : intPoint + 1]; - var point3 = points[intPoint > points.length - 3 ? points.length - 1 : intPoint + 2]; + if ( this.useSpacedPoints ) { - var vector = new THREE.Vector2(); + return this.extractAllSpacedPoints( divisions ); - vector.x = THREE.Curve.Utils.interpolate(point0.x, point1.x, point2.x, point3.x, weight); - vector.y = THREE.Curve.Utils.interpolate(point0.y, point1.y, point2.y, point3.y, weight); + } - return vector; + return this.extractAllPoints( divisions ); }; -// File:src/extras/curves/EllipseCurve.js - -/************************************************************** - * Ellipse curve - **************************************************************/ +// +// THREE.Shape.prototype.extractAllPointsWithBend = function ( divisions, bend ) { +// +// return { +// +// shape: this.transform( bend, divisions ), +// holes: this.getPointsHoles( divisions, bend ) +// +// }; +// +// }; -THREE.EllipseCurve = function (aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise) { +// Get points of shape and holes (spaced by regular distance) - this.aX = aX; - this.aY = aY; +THREE.Shape.prototype.extractAllSpacedPoints = function ( divisions ) { - this.xRadius = xRadius; - this.yRadius = yRadius; + return { - this.aStartAngle = aStartAngle; - this.aEndAngle = aEndAngle; + shape: this.getTransformedSpacedPoints( divisions ), + holes: this.getSpacedPointsHoles( divisions ) - this.aClockwise = aClockwise; + }; }; -THREE.EllipseCurve.prototype = Object.create(THREE.Curve.prototype); -THREE.EllipseCurve.prototype.constructor = THREE.EllipseCurve; +/************************************************************** + * Utils + **************************************************************/ -THREE.EllipseCurve.prototype.getPoint = function (t) { +THREE.Shape.Utils = { - var deltaAngle = this.aEndAngle - this.aStartAngle; + triangulateShape: function ( contour, holes ) { - if (deltaAngle < 0) deltaAngle += Math.PI * 2; - if (deltaAngle > Math.PI * 2) deltaAngle -= Math.PI * 2; + function point_in_segment_2D_colin( inSegPt1, inSegPt2, inOtherPt ) { - var angle; + // inOtherPt needs to be collinear to the inSegment + if ( inSegPt1.x !== inSegPt2.x ) { - if (this.aClockwise === true) { + if ( inSegPt1.x < inSegPt2.x ) { - angle = this.aEndAngle + ( 1 - t ) * ( Math.PI * 2 - deltaAngle ); + return ( ( inSegPt1.x <= inOtherPt.x ) && ( inOtherPt.x <= inSegPt2.x ) ); - } else { + } else { - angle = this.aStartAngle + t * deltaAngle; + return ( ( inSegPt2.x <= inOtherPt.x ) && ( inOtherPt.x <= inSegPt1.x ) ); - } + } - var vector = new THREE.Vector2(); + } else { - vector.x = this.aX + this.xRadius * Math.cos(angle); - vector.y = this.aY + this.yRadius * Math.sin(angle); + if ( inSegPt1.y < inSegPt2.y ) { - return vector; + return ( ( inSegPt1.y <= inOtherPt.y ) && ( inOtherPt.y <= inSegPt2.y ) ); -}; + } else { -// File:src/extras/curves/ArcCurve.js + return ( ( inSegPt2.y <= inOtherPt.y ) && ( inOtherPt.y <= inSegPt1.y ) ); -/************************************************************** - * 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); -THREE.ArcCurve.prototype.constructor = THREE.ArcCurve; + function intersect_segments_2D( inSeg1Pt1, inSeg1Pt2, inSeg2Pt1, inSeg2Pt2, inExcludeAdjacentSegs ) { -// File:src/extras/curves/LineCurve3.js + var EPSILON = 0.0000000001; -/************************************************************** - * Line3D - **************************************************************/ + var seg1dx = inSeg1Pt2.x - inSeg1Pt1.x, seg1dy = inSeg1Pt2.y - inSeg1Pt1.y; + var seg2dx = inSeg2Pt2.x - inSeg2Pt1.x, seg2dy = inSeg2Pt2.y - inSeg2Pt1.y; -THREE.LineCurve3 = THREE.Curve.create( - function (v1, v2) { + var seg1seg2dx = inSeg1Pt1.x - inSeg2Pt1.x; + var seg1seg2dy = inSeg1Pt1.y - inSeg2Pt1.y; - this.v1 = v1; - this.v2 = v2; + var limit = seg1dy * seg2dx - seg1dx * seg2dy; + var perpSeg1 = seg1dy * seg1seg2dx - seg1dx * seg1seg2dy; - }, + if ( Math.abs( limit ) > EPSILON ) { - function (t) { + // not parallel - var vector = new THREE.Vector3(); + var perpSeg2; + if ( limit > 0 ) { - vector.subVectors(this.v2, this.v1); // diff - vector.multiplyScalar(t); - vector.add(this.v1); + if ( ( perpSeg1 < 0 ) || ( perpSeg1 > limit ) ) return []; + perpSeg2 = seg2dy * seg1seg2dx - seg2dx * seg1seg2dy; + if ( ( perpSeg2 < 0 ) || ( perpSeg2 > limit ) ) return []; - return vector; + } else { - } -); + if ( ( perpSeg1 > 0 ) || ( perpSeg1 < limit ) ) return []; + perpSeg2 = seg2dy * seg1seg2dx - seg2dx * seg1seg2dy; + if ( ( perpSeg2 > 0 ) || ( perpSeg2 < limit ) ) return []; -// File:src/extras/curves/QuadraticBezierCurve3.js + } -/************************************************************** - * Quadratic Bezier 3D curve - **************************************************************/ + // i.e. to reduce rounding errors + // intersection at endpoint of segment#1? + if ( perpSeg2 === 0 ) { -THREE.QuadraticBezierCurve3 = THREE.Curve.create( - function (v0, v1, v2) { + if ( ( inExcludeAdjacentSegs ) && + ( ( perpSeg1 === 0 ) || ( perpSeg1 === limit ) ) ) return []; + return [ inSeg1Pt1 ]; - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; + } + if ( perpSeg2 === limit ) { - }, + if ( ( inExcludeAdjacentSegs ) && + ( ( perpSeg1 === 0 ) || ( perpSeg1 === limit ) ) ) return []; + return [ inSeg1Pt2 ]; - function (t) { + } + // intersection at endpoint of segment#2? + if ( perpSeg1 === 0 ) return [ inSeg2Pt1 ]; + if ( perpSeg1 === limit ) return [ inSeg2Pt2 ]; - var vector = new THREE.Vector3(); + // return real intersection point + var factorSeg1 = perpSeg2 / limit; + return [ { x: inSeg1Pt1.x + factorSeg1 * seg1dx, + y: inSeg1Pt1.y + factorSeg1 * seg1dy } ]; - vector.x = THREE.Shape.Utils.b2(t, this.v0.x, this.v1.x, this.v2.x); - vector.y = THREE.Shape.Utils.b2(t, this.v0.y, this.v1.y, this.v2.y); - vector.z = THREE.Shape.Utils.b2(t, this.v0.z, this.v1.z, this.v2.z); + } else { - return vector; + // parallel or collinear + if ( ( perpSeg1 !== 0 ) || + ( seg2dy * seg1seg2dx !== seg2dx * seg1seg2dy ) ) return []; - } -); + // they are collinear or degenerate + var seg1Pt = ( ( seg1dx === 0 ) && ( seg1dy === 0 ) ); // segment1 is just a point? + var seg2Pt = ( ( seg2dx === 0 ) && ( seg2dy === 0 ) ); // segment2 is just a point? + // both segments are points + if ( seg1Pt && seg2Pt ) { -// File:src/extras/curves/CubicBezierCurve3.js + if ( ( inSeg1Pt1.x !== inSeg2Pt1.x ) || + ( inSeg1Pt1.y !== inSeg2Pt1.y ) ) return []; // they are distinct points + return [ inSeg1Pt1 ]; // they are the same point -/************************************************************** - * Cubic Bezier 3D curve - **************************************************************/ + } + // segment#1 is a single point + if ( seg1Pt ) { -THREE.CubicBezierCurve3 = THREE.Curve.create( - function (v0, v1, v2, v3) { + if ( ! point_in_segment_2D_colin( inSeg2Pt1, inSeg2Pt2, inSeg1Pt1 ) ) return []; // but not in segment#2 + return [ inSeg1Pt1 ]; - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; - this.v3 = v3; + } + // 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 ]; - function (t) { + } - var vector = new THREE.Vector3(); + // they are collinear segments, which might overlap + var seg1min, seg1max, seg1minVal, seg1maxVal; + var seg2min, seg2max, seg2minVal, seg2maxVal; + if ( seg1dx !== 0 ) { - vector.x = THREE.Shape.Utils.b3(t, this.v0.x, this.v1.x, this.v2.x, this.v3.x); - vector.y = THREE.Shape.Utils.b3(t, this.v0.y, this.v1.y, this.v2.y, this.v3.y); - vector.z = THREE.Shape.Utils.b3(t, this.v0.z, this.v1.z, this.v2.z, this.v3.z); + // the segments are NOT on a vertical line + if ( inSeg1Pt1.x < inSeg1Pt2.x ) { - return vector; + seg1min = inSeg1Pt1; seg1minVal = inSeg1Pt1.x; + seg1max = inSeg1Pt2; seg1maxVal = inSeg1Pt2.x; - } -); + } else { -// File:src/extras/curves/SplineCurve3.js + seg1min = inSeg1Pt2; seg1minVal = inSeg1Pt2.x; + seg1max = inSeg1Pt1; seg1maxVal = inSeg1Pt1.x; -/************************************************************** - * Spline 3D curve - **************************************************************/ + } + if ( inSeg2Pt1.x < inSeg2Pt2.x ) { + seg2min = inSeg2Pt1; seg2minVal = inSeg2Pt1.x; + seg2max = inSeg2Pt2; seg2maxVal = inSeg2Pt2.x; -THREE.SplineCurve3 = THREE.Curve.create( - function (points /* array of Vector3 */) { + } else { - this.points = ( points == undefined ) ? [] : points; + seg2min = inSeg2Pt2; seg2minVal = inSeg2Pt2.x; + seg2max = inSeg2Pt1; seg2maxVal = inSeg2Pt1.x; - }, + } - function (t) { + } else { - var points = this.points; - var point = ( points.length - 1 ) * t; + // the segments are on a vertical line + if ( inSeg1Pt1.y < inSeg1Pt2.y ) { - var intPoint = Math.floor(point); - var weight = point - intPoint; + seg1min = inSeg1Pt1; seg1minVal = inSeg1Pt1.y; + seg1max = inSeg1Pt2; seg1maxVal = inSeg1Pt2.y; - var point0 = points[intPoint == 0 ? intPoint : intPoint - 1]; - var point1 = points[intPoint]; - var point2 = points[intPoint > points.length - 2 ? points.length - 1 : intPoint + 1]; - var point3 = points[intPoint > points.length - 3 ? points.length - 1 : intPoint + 2]; + } else { - var vector = new THREE.Vector3(); + seg1min = inSeg1Pt2; seg1minVal = inSeg1Pt2.y; + seg1max = inSeg1Pt1; seg1maxVal = inSeg1Pt1.y; - vector.x = THREE.Curve.Utils.interpolate(point0.x, point1.x, point2.x, point3.x, weight); - vector.y = THREE.Curve.Utils.interpolate(point0.y, point1.y, point2.y, point3.y, weight); - vector.z = THREE.Curve.Utils.interpolate(point0.z, point1.z, point2.z, point3.z, weight); + } + if ( inSeg2Pt1.y < inSeg2Pt2.y ) { - return vector; + seg2min = inSeg2Pt1; seg2minVal = inSeg2Pt1.y; + seg2max = inSeg2Pt2; seg2maxVal = inSeg2Pt2.y; - } -); + } else { -// File:src/extras/curves/ClosedSplineCurve3.js + seg2min = inSeg2Pt2; seg2minVal = inSeg2Pt2.y; + seg2max = inSeg2Pt1; seg2maxVal = inSeg2Pt1.y; -/************************************************************** - * Closed Spline 3D curve - **************************************************************/ + } + } + if ( seg1minVal <= seg2minVal ) { -THREE.ClosedSplineCurve3 = THREE.Curve.create( - function (points /* array of Vector3 */) { + if ( seg1maxVal < seg2minVal ) return []; + if ( seg1maxVal === seg2minVal ) { - this.points = ( points == undefined ) ? [] : points; + if ( inExcludeAdjacentSegs ) return []; + return [ seg2min ]; - }, + } + if ( seg1maxVal <= seg2maxVal ) return [ seg2min, seg1max ]; + return [ seg2min, seg2max ]; - function (t) { + } else { - var points = this.points; - var point = ( points.length - 0 ) * t; // This needs to be from 0-length +1 + if ( seg1minVal > seg2maxVal ) return []; + if ( seg1minVal === seg2maxVal ) { - var intPoint = Math.floor(point); - var weight = point - intPoint; + if ( inExcludeAdjacentSegs ) return []; + return [ seg1min ]; - intPoint += intPoint > 0 ? 0 : ( Math.floor(Math.abs(intPoint) / points.length) + 1 ) * points.length; + } + if ( seg1maxVal <= seg2maxVal ) return [ seg1min, seg1max ]; + return [ seg1min, seg2max ]; - var point0 = points[( intPoint - 1 ) % points.length]; - var point1 = points[( intPoint ) % points.length]; - var point2 = points[( intPoint + 1 ) % points.length]; - var point3 = points[( intPoint + 2 ) % points.length]; + } - var vector = new THREE.Vector3(); + } - vector.x = THREE.Curve.Utils.interpolate(point0.x, point1.x, point2.x, point3.x, weight); - vector.y = THREE.Curve.Utils.interpolate(point0.y, point1.y, point2.y, point3.y, weight); - vector.z = THREE.Curve.Utils.interpolate(point0.z, point1.z, point2.z, point3.z, weight); + } - return vector; + function isPointInsideAngle( inVertex, inLegFromPt, inLegToPt, inOtherPt ) { - } -); + // The order of legs is important -// File:src/extras/animation/AnimationHandler.js + var EPSILON = 0.0000000001; -/** - * @author mikael emtinger / http://gomo.se/ - */ + // 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; -THREE.AnimationHandler = { + // main angle >0: < 180 deg.; 0: 180 deg.; <0: > 180 deg. + var from2toAngle = legFromPtX * legToPtY - legFromPtY * legToPtX; + var from2otherAngle = legFromPtX * otherPtY - legFromPtY * otherPtX; - LINEAR: 0, - CATMULLROM: 1, - CATMULLROM_FORWARD: 2, + if ( Math.abs( from2toAngle ) > EPSILON ) { - // + // angle != 180 deg. - add: function () { - THREE.warn('THREE.AnimationHandler.add() has been deprecated.'); - }, - get: function () { - THREE.warn('THREE.AnimationHandler.get() has been deprecated.'); - }, - remove: function () { - THREE.warn('THREE.AnimationHandler.remove() has been deprecated.'); - }, + var other2toAngle = otherPtX * legToPtY - otherPtY * legToPtX; + // console.log( "from2to: " + from2toAngle + ", from2other: " + from2otherAngle + ", other2to: " + other2toAngle ); - // + if ( from2toAngle > 0 ) { - animations: [], + // main angle < 180 deg. + return ( ( from2otherAngle >= 0 ) && ( other2toAngle >= 0 ) ); - init: function (data) { + } else { - if (data.initialized === true) return data; + // main angle > 180 deg. + return ( ( from2otherAngle >= 0 ) || ( other2toAngle >= 0 ) ); - // loop through all keys + } - for (var h = 0; h < data.hierarchy.length; h++) { + } else { - for (var k = 0; k < data.hierarchy[h].keys.length; k++) { + // angle == 180 deg. + // console.log( "from2to: 180 deg., from2other: " + from2otherAngle ); + return ( from2otherAngle > 0 ); - // remove minus times + } - if (data.hierarchy[h].keys[k].time < 0) { + } - data.hierarchy[h].keys[k].time = 0; - } + function removeHoles( contour, holes ) { - // create quaternions + var shape = contour.concat(); // work on this shape + var hole; - if (data.hierarchy[h].keys[k].rot !== undefined && !( data.hierarchy[h].keys[k].rot instanceof THREE.Quaternion )) { + function isCutLineInsideAngles( inShapeIdx, inHoleIdx ) { - var quat = data.hierarchy[h].keys[k].rot; - data.hierarchy[h].keys[k].rot = new THREE.Quaternion().fromArray(quat); + // 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; - // prepare morph target keys + var insideAngle = isPointInsideAngle( shape[ inShapeIdx ], shape[ prevShapeIdx ], shape[ nextShapeIdx ], hole[ inHoleIdx ] ); + if ( ! insideAngle ) { - if (data.hierarchy[h].keys.length && data.hierarchy[h].keys[0].morphTargets !== undefined) { + // console.log( "Vertex (Shape): " + inShapeIdx + ", Point: " + hole[inHoleIdx].x + "/" + hole[inHoleIdx].y ); + return false; - // get all used + } - var usedMorphTargets = {}; + // Check if shape point lies within angle around hole point + var lastHoleIdx = hole.length - 1; - for (var k = 0; k < data.hierarchy[h].keys.length; k++) { + var prevHoleIdx = inHoleIdx - 1; + if ( prevHoleIdx < 0 ) prevHoleIdx = lastHoleIdx; - for (var m = 0; m < data.hierarchy[h].keys[k].morphTargets.length; m++) { + var nextHoleIdx = inHoleIdx + 1; + if ( nextHoleIdx > lastHoleIdx ) nextHoleIdx = 0; - var morphTargetName = data.hierarchy[h].keys[k].morphTargets[m]; - usedMorphTargets[morphTargetName] = -1; + 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; - } + } - data.hierarchy[h].usedMorphTargets = usedMorphTargets; + return true; + } - // set all used on all frames + function intersectsShapeEdge( inShapePt, inHolePt ) { - for (var k = 0; k < data.hierarchy[h].keys.length; k++) { + // checks for intersections with shape edges + var sIdx, nextIdx, intersection; + for ( sIdx = 0; sIdx < shape.length; sIdx ++ ) { - var influences = {}; + nextIdx = sIdx + 1; nextIdx %= shape.length; + intersection = intersect_segments_2D( inShapePt, inHolePt, shape[ sIdx ], shape[ nextIdx ], true ); + if ( intersection.length > 0 ) return true; - for (var morphTargetName in usedMorphTargets) { + } - for (var m = 0; m < data.hierarchy[h].keys[k].morphTargets.length; m++) { + return false; - if (data.hierarchy[h].keys[k].morphTargets[m] === morphTargetName) { + } - influences[morphTargetName] = data.hierarchy[h].keys[k].morphTargetsInfluences[m]; - break; + 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 ++ ) { - if (m === data.hierarchy[h].keys[k].morphTargets.length) { + chkHole = holes[ indepHoles[ ihIdx ]]; + for ( hIdx = 0; hIdx < chkHole.length; hIdx ++ ) { - influences[morphTargetName] = 0; + 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; - data.hierarchy[h].keys[k].morphTargetsInfluences = influences; + } - } + 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 ); - // remove all keys that are on the same time + } - for (var k = 1; k < data.hierarchy[h].keys.length; k++) { + var minShapeIndex = 0; + var counter = indepHoles.length * 2; + while ( indepHoles.length > 0 ) { - if (data.hierarchy[h].keys[k].time === data.hierarchy[h].keys[k - 1].time) { + counter --; + if ( counter < 0 ) { - data.hierarchy[h].keys.splice(k, 1); - k--; + 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 = minShapeIndex; shapeIndex < shape.length; shapeIndex ++ ) { + shapePt = shape[ shapeIndex ]; + holeIndex = - 1; - // set index + // search for hole which can be reached without intersections + for ( var h = 0; h < indepHoles.length; h ++ ) { - for (var k = 0; k < data.hierarchy[h].keys.length; k++) { + holeIdx = indepHoles[ h ]; - data.hierarchy[h].keys[k].index = k; + // 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; - data.initialized = true; + holeIndex = h2; + indepHoles.splice( h, 1 ); - return data; + 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 ); - parse: function (root) { + minShapeIndex = shapeIndex; - var parseRecurseHierarchy = function (root, hierarchy) { + // Debug only, to show the selected cuts + // glob_CutLines.push( [ shapePt, holePt ] ); - hierarchy.push(root); + break; - for (var c = 0; c < root.children.length; c++) - parseRecurseHierarchy(root.children[c], hierarchy); + } + if ( holeIndex >= 0 ) break; // hole-vertex found - }; + failedCuts[ cutKey ] = true; // remember failure - // setup hierarchy + } + if ( holeIndex >= 0 ) break; // hole-vertex found - var hierarchy = []; + } - if (root instanceof THREE.SkinnedMesh) { + } - for (var b = 0; b < root.skeleton.bones.length; b++) { + return shape; /* shape with no holes */ - hierarchy.push(root.skeleton.bones[b]); + } - } - } else { + var i, il, f, face, + key, index, + allPointsMap = {}; - parseRecurseHierarchy(root, hierarchy); + // 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(); - return hierarchy; + for ( var h = 0, hl = holes.length; h < hl; h ++ ) { - }, + Array.prototype.push.apply( allpoints, holes[ h ] ); - play: function (animation) { + } - if (this.animations.indexOf(animation) === -1) { + //console.log( "allpoints",allpoints, allpoints.length ); - this.animations.push(animation); + // prepare all points map - } + for ( i = 0, il = allpoints.length; i < il; i ++ ) { - }, + key = allpoints[ i ].x + ":" + allpoints[ i ].y; - stop: function (animation) { + if ( allPointsMap[ key ] !== undefined ) { - var index = this.animations.indexOf(animation); + console.warn( "THREE.Shape: Duplicate point", key ); - if (index !== -1) { + } - this.animations.splice(index, 1); + allPointsMap[ key ] = i; - } + } - }, + // remove holes by cutting paths to holes and adding them to the shape + var shapeWithoutHoles = removeHoles( contour, holes ); - update: function (deltaTimeMS) { + var triangles = THREE.FontUtils.Triangulate( shapeWithoutHoles, false ); // True returns indices for points of spooled shape + //console.log( "triangles",triangles, triangles.length ); - for (var i = 0; i < this.animations.length; i++) { + // check all face vertices against all points map - this.animations[i].resetBlendWeights(); + for ( i = 0, il = triangles.length; i < il; i ++ ) { - } + face = triangles[ i ]; - for (var i = 0; i < this.animations.length; i++) { + for ( f = 0; f < 3; f ++ ) { - this.animations[i].update(deltaTimeMS); + key = face[ f ].x + ":" + face[ f ].y; - } + index = allPointsMap[ key ]; - } + if ( index !== undefined ) { -}; + face[ f ] = index; -// File:src/extras/animation/Animation.js + } -/** - * @author mikael emtinger / http://gomo.se/ - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - */ + } -THREE.Animation = function (root, data) { + } - this.root = root; - this.data = THREE.AnimationHandler.init(data); - this.hierarchy = THREE.AnimationHandler.parse(root); + return triangles.concat(); - this.currentTime = 0; - this.timeScale = 1; + }, - this.isPlaying = false; - this.loop = true; - this.weight = 0; + isClockWise: function ( pts ) { - this.interpolationType = THREE.AnimationHandler.LINEAR; + return THREE.FontUtils.Triangulate.area( pts ) < 0; -}; + }, -THREE.Animation.prototype = { + // Bezier Curves formulas obtained from + // http://en.wikipedia.org/wiki/B%C3%A9zier_curve - constructor: THREE.Animation, + // Quad Bezier Functions - keyTypes: ["pos", "rot", "scl"], + b2p0: function ( t, p ) { - play: function (startTime, weight) { + var k = 1 - t; + return k * k * p; - this.currentTime = startTime !== undefined ? startTime : 0; - this.weight = weight !== undefined ? weight : 1; + }, - this.isPlaying = true; + b2p1: function ( t, p ) { - this.reset(); + return 2 * ( 1 - t ) * t * p; - THREE.AnimationHandler.play(this); + }, - }, + b2p2: function ( t, p ) { - stop: function () { + return t * t * p; - this.isPlaying = false; + }, - THREE.AnimationHandler.stop(this); + b2: function ( t, p0, p1, p2 ) { - }, + return this.b2p0( t, p0 ) + this.b2p1( t, p1 ) + this.b2p2( t, p2 ); - reset: function () { + }, - for (var h = 0, hl = this.hierarchy.length; h < hl; h++) { + // Cubic Bezier Functions - var object = this.hierarchy[h]; + b3p0: function ( t, p ) { - if (object.animationCache === undefined) { + var k = 1 - t; + return k * k * k * p; - object.animationCache = { - animations: {}, - blending: { - positionWeight: 0.0, - quaternionWeight: 0.0, - scaleWeight: 0.0 - } - }; - } + }, - var name = this.data.name; - var animations = object.animationCache.animations; - var animationCache = animations[name]; + b3p1: function ( t, p ) { - if (animationCache === undefined) { + var k = 1 - t; + return 3 * k * k * t * p; - animationCache = { - prevKey: {pos: 0, rot: 0, scl: 0}, - nextKey: {pos: 0, rot: 0, scl: 0}, - originalMatrix: object.matrix - }; + }, - animations[name] = animationCache; + b3p2: function ( t, p ) { - } + var k = 1 - t; + return 3 * k * t * t * p; - // Get keys to match our current time + }, - for (var t = 0; t < 3; t++) { + b3p3: function ( t, p ) { - var type = this.keyTypes[t]; + return t * t * t * p; - var prevKey = this.data.hierarchy[h].keys[0]; - var nextKey = this.getNextKeyWith(type, h, 1); + }, - while (nextKey.time < this.currentTime && nextKey.index > prevKey.index) { + b3: function ( t, p0, p1, p2, p3 ) { - prevKey = nextKey; - nextKey = this.getNextKeyWith(type, h, nextKey.index + 1); + return this.b3p0( t, p0 ) + this.b3p1( t, p1 ) + this.b3p2( t, p2 ) + this.b3p3( t, p3 ); - } + } - animationCache.prevKey[type] = prevKey; - animationCache.nextKey[type] = nextKey; +}; - } +// File:src/extras/curves/LineCurve.js - } +/************************************************************** + * Line + **************************************************************/ - }, +THREE.LineCurve = function ( v1, v2 ) { - resetBlendWeights: function () { + this.v1 = v1; + this.v2 = v2; - for (var h = 0, hl = this.hierarchy.length; h < hl; h++) { +}; - var object = this.hierarchy[h]; - var animationCache = object.animationCache; +THREE.LineCurve.prototype = Object.create( THREE.Curve.prototype ); +THREE.LineCurve.prototype.constructor = THREE.LineCurve; - if (animationCache !== undefined) { +THREE.LineCurve.prototype.getPoint = function ( t ) { - var blending = animationCache.blending; + var point = this.v2.clone().sub( this.v1 ); + point.multiplyScalar( t ).add( this.v1 ); - blending.positionWeight = 0.0; - blending.quaternionWeight = 0.0; - blending.scaleWeight = 0.0; + return point; - } +}; - } +// Line curve is linear, so we can overwrite default getPointAt - }, +THREE.LineCurve.prototype.getPointAt = function ( u ) { - update: (function () { + return this.getPoint( u ); - var points = []; - var target = new THREE.Vector3(); - var newVector = new THREE.Vector3(); - var newQuat = new THREE.Quaternion(); +}; - // Catmull-Rom spline +THREE.LineCurve.prototype.getTangent = function( t ) { - var interpolateCatmullRom = function (points, scale) { + var tangent = this.v2.clone().sub( this.v1 ); - var c = [], v3 = [], - point, intPoint, weight, w2, w3, - pa, pb, pc, pd; + return tangent.normalize(); - 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; +// File:src/extras/curves/QuadraticBezierCurve.js - pa = points[c[0]]; - pb = points[c[1]]; - pc = points[c[2]]; - pd = points[c[3]]; +/************************************************************** + * Quadratic Bezier curve + **************************************************************/ - 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); +THREE.QuadraticBezierCurve = function ( v0, v1, v2 ) { - return v3; + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; - }; +}; - var interpolate = function (p0, p1, p2, p3, t, t2, t3) { +THREE.QuadraticBezierCurve.prototype = Object.create( THREE.Curve.prototype ); +THREE.QuadraticBezierCurve.prototype.constructor = THREE.QuadraticBezierCurve; - 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; +THREE.QuadraticBezierCurve.prototype.getPoint = function ( t ) { - }; + var vector = new THREE.Vector2(); - return function (delta) { + vector.x = THREE.Shape.Utils.b2( t, this.v0.x, this.v1.x, this.v2.x ); + vector.y = THREE.Shape.Utils.b2( t, this.v0.y, this.v1.y, this.v2.y ); - if (this.isPlaying === false) return; + return vector; - this.currentTime += delta * this.timeScale; +}; - if (this.weight === 0) - return; - // +THREE.QuadraticBezierCurve.prototype.getTangent = function( t ) { - var duration = this.data.length; + var vector = new THREE.Vector2(); - if (this.currentTime > duration || this.currentTime < 0) { + vector.x = THREE.Curve.Utils.tangentQuadraticBezier( t, this.v0.x, this.v1.x, this.v2.x ); + vector.y = THREE.Curve.Utils.tangentQuadraticBezier( t, this.v0.y, this.v1.y, this.v2.y ); - if (this.loop) { + // returns unit vector - this.currentTime %= duration; + return vector.normalize(); - if (this.currentTime < 0) - this.currentTime += duration; +}; - this.reset(); +// File:src/extras/curves/CubicBezierCurve.js - } else { +/************************************************************** + * Cubic Bezier curve + **************************************************************/ - this.stop(); +THREE.CubicBezierCurve = function ( v0, v1, v2, v3 ) { - } + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; - } +}; - for (var h = 0, hl = this.hierarchy.length; h < hl; h++) { +THREE.CubicBezierCurve.prototype = Object.create( THREE.Curve.prototype ); +THREE.CubicBezierCurve.prototype.constructor = THREE.CubicBezierCurve; - var object = this.hierarchy[h]; - var animationCache = object.animationCache.animations[this.data.name]; - var blending = object.animationCache.blending; +THREE.CubicBezierCurve.prototype.getPoint = function ( t ) { - // loop through pos/rot/scl + var tx, ty; - for (var t = 0; t < 3; t++) { + 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 ); - // get keys + return new THREE.Vector2( tx, ty ); - var type = this.keyTypes[t]; - var prevKey = animationCache.prevKey[type]; - var nextKey = animationCache.nextKey[type]; +}; - if (( this.timeScale > 0 && nextKey.time <= this.currentTime ) || - ( this.timeScale < 0 && prevKey.time >= this.currentTime )) { +THREE.CubicBezierCurve.prototype.getTangent = function( t ) { - prevKey = this.data.hierarchy[h].keys[0]; - nextKey = this.getNextKeyWith(type, h, 1); + var tx, ty; - while (nextKey.time < this.currentTime && nextKey.index > prevKey.index) { + 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 ); - prevKey = nextKey; - nextKey = this.getNextKeyWith(type, h, nextKey.index + 1); + var tangent = new THREE.Vector2( tx, ty ); + tangent.normalize(); - } + return tangent; - animationCache.prevKey[type] = prevKey; - animationCache.nextKey[type] = nextKey; +}; - } +// File:src/extras/curves/SplineCurve.js - var scale = ( this.currentTime - prevKey.time ) / ( nextKey.time - prevKey.time ); +/************************************************************** + * Spline curve + **************************************************************/ - var prevXYZ = prevKey[type]; - var nextXYZ = nextKey[type]; +THREE.SplineCurve = function ( points /* array of Vector2 */ ) { - if (scale < 0) scale = 0; - if (scale > 1) scale = 1; + this.points = ( points == undefined ) ? [] : points; - // interpolate +}; - if (type === "pos") { +THREE.SplineCurve.prototype = Object.create( THREE.Curve.prototype ); +THREE.SplineCurve.prototype.constructor = THREE.SplineCurve; - if (this.interpolationType === THREE.AnimationHandler.LINEAR) { +THREE.SplineCurve.prototype.getPoint = function ( t ) { - newVector.x = prevXYZ[0] + ( nextXYZ[0] - prevXYZ[0] ) * scale; - newVector.y = prevXYZ[1] + ( nextXYZ[1] - prevXYZ[1] ) * scale; - newVector.z = prevXYZ[2] + ( nextXYZ[2] - prevXYZ[2] ) * scale; + var points = this.points; + var point = ( points.length - 1 ) * t; - // blend - var proportionalWeight = this.weight / ( this.weight + blending.positionWeight ); - object.position.lerp(newVector, proportionalWeight); - blending.positionWeight += this.weight; + var intPoint = Math.floor( point ); + var weight = point - intPoint; - } else if (this.interpolationType === THREE.AnimationHandler.CATMULLROM || - this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD) { + var point0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ]; + var point1 = points[ intPoint ]; + var point2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ]; + var point3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ]; - 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"]; + var vector = new THREE.Vector2(); - scale = scale * 0.33 + 0.33; + vector.x = THREE.Curve.Utils.interpolate( point0.x, point1.x, point2.x, point3.x, weight ); + vector.y = THREE.Curve.Utils.interpolate( point0.y, point1.y, point2.y, point3.y, weight ); - var currentPoint = interpolateCatmullRom(points, scale); - var proportionalWeight = this.weight / ( this.weight + blending.positionWeight ); - blending.positionWeight += this.weight; + return vector; - // blend +}; - var vector = object.position; +// File:src/extras/curves/EllipseCurve.js - vector.x = vector.x + ( currentPoint[0] - vector.x ) * proportionalWeight; - vector.y = vector.y + ( currentPoint[1] - vector.y ) * proportionalWeight; - vector.z = vector.z + ( currentPoint[2] - vector.z ) * proportionalWeight; +/************************************************************** + * Ellipse curve + **************************************************************/ - if (this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD) { +THREE.EllipseCurve = function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { - var forwardPoint = interpolateCatmullRom(points, scale * 1.01); + this.aX = aX; + this.aY = aY; - target.set(forwardPoint[0], forwardPoint[1], forwardPoint[2]); - target.sub(vector); - target.y = 0; - target.normalize(); + this.xRadius = xRadius; + this.yRadius = yRadius; - var angle = Math.atan2(target.x, target.z); - object.rotation.set(0, angle, 0); + this.aStartAngle = aStartAngle; + this.aEndAngle = aEndAngle; - } + this.aClockwise = aClockwise; + + this.aRotation = aRotation || 0; - } +}; - } else if (type === "rot") { +THREE.EllipseCurve.prototype = Object.create( THREE.Curve.prototype ); +THREE.EllipseCurve.prototype.constructor = THREE.EllipseCurve; - THREE.Quaternion.slerp(prevXYZ, nextXYZ, newQuat, scale); +THREE.EllipseCurve.prototype.getPoint = function ( t ) { - // Avoid paying the cost of an additional slerp if we don't have to - if (blending.quaternionWeight === 0) { + var deltaAngle = this.aEndAngle - this.aStartAngle; - object.quaternion.copy(newQuat); - blending.quaternionWeight = this.weight; + if ( deltaAngle < 0 ) deltaAngle += Math.PI * 2; + if ( deltaAngle > Math.PI * 2 ) deltaAngle -= Math.PI * 2; - } else { + var angle; - var proportionalWeight = this.weight / ( this.weight + blending.quaternionWeight ); - THREE.Quaternion.slerp(object.quaternion, newQuat, object.quaternion, proportionalWeight); - blending.quaternionWeight += this.weight; + if ( this.aClockwise === true ) { - } + angle = this.aEndAngle + ( 1 - t ) * ( Math.PI * 2 - deltaAngle ); - } else if (type === "scl") { + } else { - newVector.x = prevXYZ[0] + ( nextXYZ[0] - prevXYZ[0] ) * scale; - newVector.y = prevXYZ[1] + ( nextXYZ[1] - prevXYZ[1] ) * scale; - newVector.z = prevXYZ[2] + ( nextXYZ[2] - prevXYZ[2] ) * scale; + angle = this.aStartAngle + t * deltaAngle; - var proportionalWeight = this.weight / ( this.weight + blending.scaleWeight ); - object.scale.lerp(newVector, proportionalWeight); - blending.scaleWeight += this.weight; + } + + var x = this.aX + this.xRadius * Math.cos( angle ); + var y = this.aY + this.yRadius * Math.sin( angle ); - } + if ( this.aRotation !== 0 ) { - } + var cos = Math.cos( this.aRotation ); + var sin = Math.sin( this.aRotation ); - } + var tx = x, ty = y; - return true; + // Rotate the point about the center of the ellipse. + x = ( tx - this.aX ) * cos - ( ty - this.aY ) * sin + this.aX; + y = ( tx - this.aX ) * sin + ( ty - this.aY ) * cos + this.aY; - }; + } - })(), + return new THREE.Vector2( x, y ); - getNextKeyWith: function (type, h, key) { +}; - var keys = this.data.hierarchy[h].keys; +// File:src/extras/curves/ArcCurve.js - if (this.interpolationType === THREE.AnimationHandler.CATMULLROM || - this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD) { +/************************************************************** + * Arc curve + **************************************************************/ - key = key < keys.length - 1 ? key : keys.length - 1; +THREE.ArcCurve = function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - } else { + THREE.EllipseCurve.call( this, aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); - key = key % keys.length; +}; - } +THREE.ArcCurve.prototype = Object.create( THREE.EllipseCurve.prototype ); +THREE.ArcCurve.prototype.constructor = THREE.ArcCurve; - for (; key < keys.length; key++) { +// File:src/extras/curves/LineCurve3.js - if (keys[key][type] !== undefined) { +/************************************************************** + * Line3D + **************************************************************/ - return keys[key]; +THREE.LineCurve3 = THREE.Curve.create( - } + function ( v1, v2 ) { - } + this.v1 = v1; + this.v2 = v2; - return this.data.hierarchy[h].keys[0]; + }, - }, + function ( t ) { - getPrevKeyWith: function (type, h, key) { + var vector = new THREE.Vector3(); - var keys = this.data.hierarchy[h].keys; + vector.subVectors( this.v2, this.v1 ); // diff + vector.multiplyScalar( t ); + vector.add( this.v1 ); - if (this.interpolationType === THREE.AnimationHandler.CATMULLROM || - this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD) { + return vector; - key = key > 0 ? key : 0; + } - } else { +); - key = key >= 0 ? key : key + keys.length; +// File:src/extras/curves/QuadraticBezierCurve3.js - } +/************************************************************** + * Quadratic Bezier 3D curve + **************************************************************/ +THREE.QuadraticBezierCurve3 = THREE.Curve.create( - for (; key >= 0; key--) { + function ( v0, v1, v2 ) { - if (keys[key][type] !== undefined) { + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; - return keys[key]; + }, - } + function ( t ) { - } + var vector = new THREE.Vector3(); - return this.data.hierarchy[h].keys[keys.length - 1]; + vector.x = THREE.Shape.Utils.b2( t, this.v0.x, this.v1.x, this.v2.x ); + vector.y = THREE.Shape.Utils.b2( t, this.v0.y, this.v1.y, this.v2.y ); + vector.z = THREE.Shape.Utils.b2( t, this.v0.z, this.v1.z, this.v2.z ); - } + return vector; -}; + } -// File:src/extras/animation/KeyFrameAnimation.js +); -/** - * @author mikael emtinger / http://gomo.se/ - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - * @author khang duong - * @author erik kitson - */ +// File:src/extras/curves/CubicBezierCurve3.js -THREE.KeyFrameAnimation = function (data) { +/************************************************************** + * Cubic Bezier 3D curve + **************************************************************/ - this.root = data.node; - this.data = THREE.AnimationHandler.init(data); - this.hierarchy = THREE.AnimationHandler.parse(this.root); - this.currentTime = 0; - this.timeScale = 0.001; - this.isPlaying = false; - this.isPaused = true; - this.loop = true; +THREE.CubicBezierCurve3 = THREE.Curve.create( - // initialize to first keyframes + function ( v0, v1, v2, v3 ) { - for (var h = 0, hl = this.hierarchy.length; h < hl; h++) { + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; - var keys = this.data.hierarchy[h].keys, - sids = this.data.hierarchy[h].sids, - obj = this.hierarchy[h]; + }, - if (keys.length && sids) { + function ( t ) { - for (var s = 0; s < sids.length; s++) { + var vector = new THREE.Vector3(); - var sid = sids[s], - next = this.getNextKeyWith(sid, h, 0); + vector.x = THREE.Shape.Utils.b3( t, this.v0.x, this.v1.x, this.v2.x, this.v3.x ); + vector.y = THREE.Shape.Utils.b3( t, this.v0.y, this.v1.y, this.v2.y, this.v3.y ); + vector.z = THREE.Shape.Utils.b3( t, this.v0.z, this.v1.z, this.v2.z, this.v3.z ); - if (next) { + return vector; - next.apply(sid); + } - } +); - } +// File:src/extras/curves/SplineCurve3.js - obj.matrixAutoUpdate = false; - this.data.hierarchy[h].node.updateMatrix(); - obj.matrixWorldNeedsUpdate = true; +/************************************************************** + * Spline 3D curve + **************************************************************/ - } - } +THREE.SplineCurve3 = THREE.Curve.create( -}; + function ( points /* array of Vector3 */ ) { -THREE.KeyFrameAnimation.prototype = { + console.warn( 'THREE.SplineCurve3 will be deprecated. Please use THREE.CatmullRomCurve3' ); + this.points = ( points == undefined ) ? [] : points; - constructor: THREE.KeyFrameAnimation, + }, - play: function (startTime) { + function ( t ) { - this.currentTime = startTime !== undefined ? startTime : 0; + var points = this.points; + var point = ( points.length - 1 ) * t; - if (this.isPlaying === false) { + var intPoint = Math.floor( point ); + var weight = point - intPoint; - this.isPlaying = true; + var point0 = points[ intPoint == 0 ? intPoint : intPoint - 1 ]; + var point1 = points[ intPoint ]; + var point2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ]; + var point3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ]; - // reset key cache + var vector = new THREE.Vector3(); - var h, hl = this.hierarchy.length, - object, - node; + vector.x = THREE.Curve.Utils.interpolate( point0.x, point1.x, point2.x, point3.x, weight ); + vector.y = THREE.Curve.Utils.interpolate( point0.y, point1.y, point2.y, point3.y, weight ); + vector.z = THREE.Curve.Utils.interpolate( point0.z, point1.z, point2.z, point3.z, weight ); - for (h = 0; h < hl; h++) { + return vector; - 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.matrix; +// File:src/extras/curves/CatmullRomCurve3.js - } +/** + * @author zz85 https://github.com/zz85 + * + * Centripetal CatmullRom Curve - which is useful for avoiding + * cusps and self-intersections in non-uniform catmull rom curves. + * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf + * + * curve.type accepts centripetal(default), chordal and catmullrom + * curve.tension is used for catmullrom which defaults to 0.5 + */ - var keys = this.data.hierarchy[h].keys; +THREE.CatmullRomCurve3 = ( function() { - if (keys.length) { + var + tmp = new THREE.Vector3(), + px = new CubicPoly(), + py = new CubicPoly(), + pz = new CubicPoly(); - node.animationCache.prevKey = keys[0]; - node.animationCache.nextKey = keys[1]; + /* + Based on an optimized c++ solution in + - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/ + - http://ideone.com/NoEbVM - this.startTime = Math.min(keys[0].time, this.startTime); - this.endTime = Math.max(keys[keys.length - 1].time, this.endTime); + This CubicPoly class could be used for reusing some variables and calculations, + but for three.js curve use, it could be possible inlined and flatten into a single function call + which can be placed in CurveUtils. + */ - } + function CubicPoly() { - } + } - this.update(0); + /* + * Compute coefficients for a cubic polynomial + * p(s) = c0 + c1*s + c2*s^2 + c3*s^3 + * such that + * p(0) = x0, p(1) = x1 + * and + * p'(0) = t0, p'(1) = t1. + */ + CubicPoly.prototype.init = function( x0, x1, t0, t1 ) { - } + this.c0 = x0; + this.c1 = t0; + this.c2 = - 3 * x0 + 3 * x1 - 2 * t0 - t1; + this.c3 = 2 * x0 - 2 * x1 + t0 + t1; - this.isPaused = false; + }; - THREE.AnimationHandler.play(this); + CubicPoly.prototype.initNonuniformCatmullRom = function( x0, x1, x2, x3, dt0, dt1, dt2 ) { - }, + // compute tangents when parameterized in [t1,t2] + var t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1; + var t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2; - stop: function () { + // rescale tangents for parametrization in [0,1] + t1 *= dt1; + t2 *= dt1; - this.isPlaying = false; - this.isPaused = false; + // initCubicPoly + this.init( x1, x2, t1, t2 ); - THREE.AnimationHandler.stop(this); + }; - // reset JIT matrix and remove cache + // standard Catmull-Rom spline: interpolate between x1 and x2 with previous/following points x1/x4 + CubicPoly.prototype.initCatmullRom = function( x0, x1, x2, x3, tension ) { - for (var h = 0; h < this.data.hierarchy.length; h++) { + this.init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) ); - var obj = this.hierarchy[h]; - var node = this.data.hierarchy[h]; + }; - if (node.animationCache !== undefined) { + CubicPoly.prototype.calc = function( t ) { - var original = node.animationCache.originalMatrix; + var t2 = t * t; + var t3 = t2 * t; + return this.c0 + this.c1 * t + this.c2 * t2 + this.c3 * t3; - original.copy(obj.matrix); - obj.matrix = original; + }; - delete node.animationCache; + // Subclass Three.js curve + return THREE.Curve.create( - } + function ( p /* array of Vector3 */ ) { - } + this.points = p || []; - }, + }, - update: function (delta) { + function ( t ) { - if (this.isPlaying === false) return; + var points = this.points, + point, intPoint, weight, l; - this.currentTime += delta * this.timeScale; + l = points.length; - // + if ( l < 2 ) console.log( 'duh, you need at least 2 points' ); - var duration = this.data.length; + point = ( l - 1 ) * t; + intPoint = Math.floor( point ); + weight = point - intPoint; - if (this.loop === true && this.currentTime > duration) { + if ( weight === 0 && intPoint === l - 1 ) { - this.currentTime %= duration; + intPoint = l - 2; + weight = 1; - } + } - this.currentTime = Math.min(this.currentTime, duration); + var p0, p1, p2, p3; - for (var h = 0, hl = this.hierarchy.length; h < hl; h++) { + if ( intPoint === 0 ) { - var object = this.hierarchy[h]; - var node = this.data.hierarchy[h]; + // extrapolate first point + tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] ); + p0 = tmp; - var keys = node.keys, - animationCache = node.animationCache; + } else { + p0 = points[ intPoint - 1 ]; - if (keys.length) { + } - var prevKey = animationCache.prevKey; - var nextKey = animationCache.nextKey; + p1 = points[ intPoint ]; + p2 = points[ intPoint + 1 ]; - if (nextKey.time <= this.currentTime) { + if ( intPoint + 2 < l ) { - while (nextKey.time < this.currentTime && nextKey.index > prevKey.index) { + p3 = points[ intPoint + 2 ] - prevKey = nextKey; - nextKey = keys[prevKey.index + 1]; + } else { - } + // extrapolate last point + tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 2 ] ); + p3 = tmp; - animationCache.prevKey = prevKey; - animationCache.nextKey = nextKey; + } - } + if ( this.type === undefined || this.type === 'centripetal' || this.type === 'chordal' ) { - if (nextKey.time >= this.currentTime) { + // init Centripetal / Chordal Catmull-Rom + var pow = this.type === 'chordal' ? 0.5 : 0.25; + var dt0 = Math.pow( p0.distanceToSquared( p1 ), pow ); + var dt1 = Math.pow( p1.distanceToSquared( p2 ), pow ); + var dt2 = Math.pow( p2.distanceToSquared( p3 ), pow ); - prevKey.interpolate(nextKey, this.currentTime); + // safety check for repeated points + if ( dt1 < 1e-4 ) dt1 = 1.0; + if ( dt0 < 1e-4 ) dt0 = dt1; + if ( dt2 < 1e-4 ) dt2 = dt1; - } else { + px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 ); + py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 ); + pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 ); - prevKey.interpolate(nextKey, nextKey.time); + } else if ( this.type === 'catmullrom' ) { - } + var tension = this.tension !== undefined ? this.tension : 0.5; + px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, tension ); + py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, tension ); + pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, tension ); - this.data.hierarchy[h].node.updateMatrix(); - object.matrixWorldNeedsUpdate = true; + } - } + var v = new THREE.Vector3( + px.calc( weight ), + py.calc( weight ), + pz.calc( weight ) + ); - } + return v; - }, + } - getNextKeyWith: function (sid, h, key) { + ); - var keys = this.data.hierarchy[h].keys; - key = key % keys.length; +} )(); - for (; key < keys.length; key++) { +// File:src/extras/curves/ClosedSplineCurve3.js - if (keys[key].hasTarget(sid)) { +/************************************************************** + * Closed Spline 3D curve + **************************************************************/ - return keys[key]; - } +THREE.ClosedSplineCurve3 = THREE.Curve.create( - } + function ( points /* array of Vector3 */ ) { - return keys[0]; + this.points = ( points == undefined ) ? [] : points; - }, + }, - getPrevKeyWith: function (sid, h, key) { + function ( t ) { - var keys = this.data.hierarchy[h].keys; - key = key >= 0 ? key : key + keys.length; + var points = this.points; + var point = ( points.length - 0 ) * t; // This needs to be from 0-length +1 - for (; key >= 0; key--) { + var intPoint = Math.floor( point ); + var weight = point - intPoint; - if (keys[key].hasTarget(sid)) { + intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / points.length ) + 1 ) * points.length; - return keys[key]; + var point0 = points[ ( intPoint - 1 ) % points.length ]; + var point1 = points[ ( intPoint ) % points.length ]; + var point2 = points[ ( intPoint + 1 ) % points.length ]; + var point3 = points[ ( intPoint + 2 ) % points.length ]; - } + var vector = new THREE.Vector3(); - } + vector.x = THREE.Curve.Utils.interpolate( point0.x, point1.x, point2.x, point3.x, weight ); + vector.y = THREE.Curve.Utils.interpolate( point0.y, point1.y, point2.y, point3.y, weight ); + vector.z = THREE.Curve.Utils.interpolate( point0.z, point1.z, point2.z, point3.z, weight ); - return keys[keys.length - 1]; + return vector; - } + } -}; +); -// File:src/extras/animation/MorphAnimation.js +// File:src/extras/geometries/BoxGeometry.js /** - * @author mrdoob / http://mrdoob.com - * @author willy-vvu / http://willy-vvu.github.io + * @author mrdoob / http://mrdoob.com/ + * based on http://papervision3d.googlecode.com/svn/trunk/as3/trunk/src/org/papervision3d/objects/primitives/Cube.as */ -THREE.MorphAnimation = function (mesh) { +THREE.BoxGeometry = function ( width, height, depth, widthSegments, heightSegments, depthSegments ) { - this.mesh = mesh; - this.frames = mesh.morphTargetInfluences.length; - this.currentTime = 0; - this.duration = 1000; - this.loop = true; - this.lastFrame = 0; - this.currentFrame = 0; + THREE.Geometry.call( this ); - this.isPlaying = false; + this.type = 'BoxGeometry'; -}; + this.parameters = { + width: width, + height: height, + depth: depth, + widthSegments: widthSegments, + heightSegments: heightSegments, + depthSegments: depthSegments + }; -THREE.MorphAnimation.prototype = { + this.widthSegments = widthSegments || 1; + this.heightSegments = heightSegments || 1; + this.depthSegments = depthSegments || 1; - constructor: THREE.MorphAnimation, + var scope = this; - play: function () { + var width_half = width / 2; + var height_half = height / 2; + var depth_half = depth / 2; - this.isPlaying = true; + buildPlane( 'z', 'y', - 1, - 1, depth, height, width_half, 0 ); // px + buildPlane( 'z', 'y', 1, - 1, depth, height, - width_half, 1 ); // nx + buildPlane( 'x', 'z', 1, 1, width, depth, height_half, 2 ); // py + buildPlane( 'x', 'z', 1, - 1, width, depth, - height_half, 3 ); // ny + buildPlane( 'x', 'y', 1, - 1, width, height, depth_half, 4 ); // pz + buildPlane( 'x', 'y', - 1, - 1, width, height, - depth_half, 5 ); // nz - }, + function buildPlane( u, v, udir, vdir, width, height, depth, materialIndex ) { - pause: function () { + var w, ix, iy, + gridX = scope.widthSegments, + gridY = scope.heightSegments, + width_half = width / 2, + height_half = height / 2, + offset = scope.vertices.length; - this.isPlaying = false; + if ( ( u === 'x' && v === 'y' ) || ( u === 'y' && v === 'x' ) ) { - }, + w = 'z'; - update: function (delta) { + } else if ( ( u === 'x' && v === 'z' ) || ( u === 'z' && v === 'x' ) ) { - if (this.isPlaying === false) return; + w = 'y'; + gridY = scope.depthSegments; - this.currentTime += delta; + } else if ( ( u === 'z' && v === 'y' ) || ( u === 'y' && v === 'z' ) ) { - if (this.loop === true && this.currentTime > this.duration) { + w = 'x'; + gridX = scope.depthSegments; - this.currentTime %= this.duration; + } - } + var gridX1 = gridX + 1, + gridY1 = gridY + 1, + segment_width = width / gridX, + segment_height = height / gridY, + normal = new THREE.Vector3(); - this.currentTime = Math.min(this.currentTime, this.duration); + normal[ w ] = depth > 0 ? 1 : - 1; - var interpolation = this.duration / this.frames; - var frame = Math.floor(this.currentTime / interpolation); + for ( iy = 0; iy < gridY1; iy ++ ) { - var influences = this.mesh.morphTargetInfluences; + for ( ix = 0; ix < gridX1; ix ++ ) { - if (frame != this.currentFrame) { + var vector = new THREE.Vector3(); + vector[ u ] = ( ix * segment_width - width_half ) * udir; + vector[ v ] = ( iy * segment_height - height_half ) * vdir; + vector[ w ] = depth; - influences[this.lastFrame] = 0; - influences[this.currentFrame] = 1; - influences[frame] = 0; + scope.vertices.push( vector ); - this.lastFrame = this.currentFrame; - this.currentFrame = frame; + } - } + } - influences[frame] = ( this.currentTime % interpolation ) / interpolation; - influences[this.lastFrame] = 1 - influences[frame]; + 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; -// File:src/extras/geometries/BoxGeometry.js + 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 ); -/** - * @author mrdoob / http://mrdoob.com/ - * based on http://papervision3d.googlecode.com/svn/trunk/as3/trunk/src/org/papervision3d/objects/primitives/Cube.as - */ + 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 ] ); -THREE.BoxGeometry = function (width, height, depth, widthSegments, heightSegments, depthSegments) { + 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; - THREE.Geometry.call(this); + scope.faces.push( face ); + scope.faceVertexUvs[ 0 ].push( [ uvb.clone(), uvc, uvd.clone() ] ); - this.type = 'BoxGeometry'; + } - this.parameters = { - width: width, - height: height, - depth: depth, - widthSegments: widthSegments, - heightSegments: heightSegments, - depthSegments: depthSegments - }; + } - this.widthSegments = widthSegments || 1; - this.heightSegments = heightSegments || 1; - this.depthSegments = depthSegments || 1; + } - var scope = this; + this.mergeVertices(); - var width_half = width / 2; - var height_half = height / 2; - var depth_half = depth / 2; +}; - buildPlane('z', 'y', -1, -1, depth, height, width_half); // px - buildPlane('z', 'y', 1, -1, depth, height, -width_half); // nx - buildPlane('x', 'z', 1, 1, width, depth, height_half); // py - buildPlane('x', 'z', 1, -1, width, depth, -height_half); // ny - buildPlane('x', 'y', 1, -1, width, height, depth_half); // pz - buildPlane('x', 'y', -1, -1, width, height, -depth_half); // nz +THREE.BoxGeometry.prototype = Object.create( THREE.Geometry.prototype ); +THREE.BoxGeometry.prototype.constructor = THREE.BoxGeometry; - function buildPlane(u, v, udir, vdir, width, height, depth) { +THREE.BoxGeometry.prototype.clone = function () { - var w, ix, iy, - gridX = scope.widthSegments, - gridY = scope.heightSegments, - width_half = width / 2, - height_half = height / 2, - offset = scope.vertices.length; + var geometry = new THREE.BoxGeometry( + this.parameters.width, + this.parameters.height, + this.parameters.depth, + this.parameters.widthSegments, + this.parameters.heightSegments, + this.parameters.depthSegments + ); - if (( u === 'x' && v === 'y' ) || ( u === 'y' && v === 'x' )) { + return geometry; - w = 'z'; +}; - } else if (( u === 'x' && v === 'z' ) || ( u === 'z' && v === 'x' )) { +THREE.CubeGeometry = THREE.BoxGeometry; // backwards compatibility - w = 'y'; - gridY = scope.depthSegments; +// File:src/extras/geometries/CircleGeometry.js - } else if (( u === 'z' && v === 'y' ) || ( u === 'y' && v === 'z' )) { +/** + * @author hughes + */ - w = 'x'; - gridX = scope.depthSegments; +THREE.CircleGeometry = function ( radius, segments, thetaStart, thetaLength ) { - } + THREE.Geometry.call( this ); - var gridX1 = gridX + 1, - gridY1 = gridY + 1, - segment_width = width / gridX, - segment_height = height / gridY, - normal = new THREE.Vector3(); + this.type = 'CircleGeometry'; - normal[w] = depth > 0 ? 1 : -1; + this.parameters = { + radius: radius, + segments: segments, + thetaStart: thetaStart, + thetaLength: thetaLength + }; - for (iy = 0; iy < gridY1; iy++) { + radius = radius || 50; + segments = segments !== undefined ? Math.max( 3, segments ) : 8; - for (ix = 0; ix < gridX1; ix++) { + thetaStart = thetaStart !== undefined ? thetaStart : 0; + thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2; - var vector = new THREE.Vector3(); - vector[u] = ( ix * segment_width - width_half ) * udir; - vector[v] = ( iy * segment_height - height_half ) * vdir; - vector[w] = depth; + var i, uvs = [], + center = new THREE.Vector3(), centerUV = new THREE.Vector2( 0.5, 0.5 ); - scope.vertices.push(vector); + this.vertices.push( center ); + uvs.push( centerUV ); - } + for ( i = 0; i <= segments; i ++ ) { - } + var vertex = new THREE.Vector3(); + var segment = thetaStart + i / segments * thetaLength; - for (iy = 0; iy < gridY; iy++) { + vertex.x = radius * Math.cos( segment ); + vertex.y = radius * Math.sin( segment ); - for (ix = 0; ix < gridX; ix++) { + this.vertices.push( vertex ); + uvs.push( new THREE.Vector2( ( vertex.x / radius + 1 ) / 2, ( vertex.y / radius + 1 ) / 2 ) ); - 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 n = new THREE.Vector3( 0, 0, 1 ); - var face = new THREE.Face3(a + offset, b + offset, d + offset); - face.normal.copy(normal); - face.vertexNormals.push(normal.clone(), normal.clone(), normal.clone()); + for ( i = 1; i <= segments; i ++ ) { - scope.faces.push(face); - scope.faceVertexUvs[0].push([uva, uvb, uvd]); + this.faces.push( new THREE.Face3( i, i + 1, 0, [ n.clone(), n.clone(), n.clone() ] ) ); + this.faceVertexUvs[ 0 ].push( [ uvs[ i ].clone(), uvs[ i + 1 ].clone(), centerUV.clone() ] ); - face = new THREE.Face3(b + offset, c + offset, d + offset); - face.normal.copy(normal); - face.vertexNormals.push(normal.clone(), normal.clone(), normal.clone()); + } - scope.faces.push(face); - scope.faceVertexUvs[0].push([uvb.clone(), uvc, uvd.clone()]); + this.computeFaceNormals(); - } + this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius ); - } +}; - } +THREE.CircleGeometry.prototype = Object.create( THREE.Geometry.prototype ); +THREE.CircleGeometry.prototype.constructor = THREE.CircleGeometry; - this.mergeVertices(); +THREE.CircleGeometry.prototype.clone = function () { -}; + var geometry = new THREE.CircleGeometry( + this.parameters.radius, + this.parameters.segments, + this.parameters.thetaStart, + this.parameters.thetaLength + ); -THREE.BoxGeometry.prototype = Object.create(THREE.Geometry.prototype); -THREE.BoxGeometry.prototype.constructor = THREE.BoxGeometry; + return geometry; -THREE.CubeGeometry = THREE.BoxGeometry; // backwards compatibility +}; -// File:src/extras/geometries/CircleGeometry.js +// File:src/extras/geometries/CircleBufferGeometry.js /** - * @author hughes + * @author benaadams / https://twitter.com/ben_a_adams */ -THREE.CircleGeometry = function (radius, segments, thetaStart, thetaLength) { +THREE.CircleBufferGeometry = function ( radius, segments, thetaStart, thetaLength ) { - THREE.Geometry.call(this); + THREE.BufferGeometry.call( this ); - this.type = 'CircleGeometry'; + this.type = 'CircleBufferGeometry'; - this.parameters = { - radius: radius, - segments: segments, - thetaStart: thetaStart, - thetaLength: thetaLength - }; + this.parameters = { + radius: radius, + segments: segments, + thetaStart: thetaStart, + thetaLength: thetaLength + }; - radius = radius || 50; - segments = segments !== undefined ? Math.max(3, segments) : 8; + radius = radius || 50; + segments = segments !== undefined ? Math.max( 3, segments ) : 8; - thetaStart = thetaStart !== undefined ? thetaStart : 0; - thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2; + thetaStart = thetaStart !== undefined ? thetaStart : 0; + thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2; - var i, uvs = [], - center = new THREE.Vector3(), centerUV = new THREE.Vector2(0.5, 0.5); + var vertices = segments + 2; - this.vertices.push(center); - uvs.push(centerUV); + var positions = new Float32Array( vertices * 3 ); + var normals = new Float32Array( vertices * 3 ); + var uvs = new Float32Array( vertices * 2 ); - for (i = 0; i <= segments; i++) { + // center data is already zero, but need to set a few extras + normals[ 3 ] = 1.0; + uvs[ 0 ] = 0.5; + uvs[ 1 ] = 0.5; - var vertex = new THREE.Vector3(); - var segment = thetaStart + i / segments * thetaLength; + for ( var s = 0, i = 3, ii = 2 ; s <= segments; s ++, i += 3, ii += 2 ) { - vertex.x = radius * Math.cos(segment); - vertex.y = radius * Math.sin(segment); + var segment = thetaStart + s / segments * thetaLength; - this.vertices.push(vertex); - uvs.push(new THREE.Vector2(( vertex.x / radius + 1 ) / 2, ( vertex.y / radius + 1 ) / 2)); + positions[ i ] = radius * Math.cos( segment ); + positions[ i + 1 ] = radius * Math.sin( segment ); - } + normals[ i + 2 ] = 1; // normal z - var n = new THREE.Vector3(0, 0, 1); + uvs[ ii ] = ( positions[ i ] / radius + 1 ) / 2; + uvs[ ii + 1 ] = ( positions[ i + 1 ] / radius + 1 ) / 2; - for (i = 1; i <= segments; i++) { + } - this.faces.push(new THREE.Face3(i, i + 1, 0, [n.clone(), n.clone(), n.clone()])); - this.faceVertexUvs[0].push([uvs[i].clone(), uvs[i + 1].clone(), centerUV.clone()]); + var indices = []; - } + for ( var i = 1; i <= segments; i ++ ) { + + indices.push( i ); + indices.push( i + 1 ); + indices.push( 0 ); + + } - this.computeFaceNormals(); + this.setIndex( new THREE.BufferAttribute( new Uint16Array( indices ), 1 ) ); + this.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) ); + this.addAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) ); + this.addAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) ); - this.boundingSphere = new THREE.Sphere(new THREE.Vector3(), radius); + this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius ); }; -THREE.CircleGeometry.prototype = Object.create(THREE.Geometry.prototype); -THREE.CircleGeometry.prototype.constructor = THREE.CircleGeometry; +THREE.CircleBufferGeometry.prototype = Object.create( THREE.BufferGeometry.prototype ); +THREE.CircleBufferGeometry.prototype.constructor = THREE.CircleBufferGeometry; + +THREE.CircleBufferGeometry.prototype.clone = function () { + + var geometry = new THREE.CircleBufferGeometry( + this.parameters.radius, + this.parameters.segments, + this.parameters.thetaStart, + this.parameters.thetaLength + ); + + geometry.copy( this ); + + return geometry; + +}; // File:src/extras/geometries/CylinderGeometry.js @@ -30587,272 +32537,287 @@ THREE.CircleGeometry.prototype.constructor = THREE.CircleGeometry; * @author mrdoob / http://mrdoob.com/ */ -THREE.CylinderGeometry = function (radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength) { +THREE.CylinderGeometry = function ( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) { - THREE.Geometry.call(this); + THREE.Geometry.call( this ); - this.type = 'CylinderGeometry'; + this.type = 'CylinderGeometry'; - this.parameters = { - radiusTop: radiusTop, - radiusBottom: radiusBottom, - height: height, - radialSegments: radialSegments, - heightSegments: heightSegments, - openEnded: openEnded, - thetaStart: thetaStart, - thetaLength: thetaLength - }; + this.parameters = { + radiusTop: radiusTop, + radiusBottom: radiusBottom, + height: height, + radialSegments: radialSegments, + heightSegments: heightSegments, + openEnded: openEnded, + thetaStart: thetaStart, + thetaLength: thetaLength + }; - radiusTop = radiusTop !== undefined ? radiusTop : 20; - radiusBottom = radiusBottom !== undefined ? radiusBottom : 20; - height = height !== undefined ? height : 100; + radiusTop = radiusTop !== undefined ? radiusTop : 20; + radiusBottom = radiusBottom !== undefined ? radiusBottom : 20; + height = height !== undefined ? height : 100; - radialSegments = radialSegments || 8; - heightSegments = heightSegments || 1; + radialSegments = radialSegments || 8; + heightSegments = heightSegments || 1; - openEnded = openEnded !== undefined ? openEnded : false; - thetaStart = thetaStart !== undefined ? thetaStart : 0; - thetaLength = thetaLength !== undefined ? thetaLength : 2 * Math.PI; + openEnded = openEnded !== undefined ? openEnded : false; + thetaStart = thetaStart !== undefined ? thetaStart : 0; + thetaLength = thetaLength !== undefined ? thetaLength : 2 * Math.PI; - var heightHalf = height / 2; + var heightHalf = height / 2; - var x, y, vertices = [], uvs = []; + var x, y, vertices = [], uvs = []; - for (y = 0; y <= heightSegments; y++) { + for ( y = 0; y <= heightSegments; y ++ ) { - var verticesRow = []; - var uvsRow = []; + var verticesRow = []; + var uvsRow = []; - var v = y / heightSegments; - var radius = v * ( radiusBottom - radiusTop ) + radiusTop; + var v = y / heightSegments; + var radius = v * ( radiusBottom - radiusTop ) + radiusTop; - for (x = 0; x <= radialSegments; x++) { + for ( x = 0; x <= radialSegments; x ++ ) { - var u = x / radialSegments; + var u = x / radialSegments; - var vertex = new THREE.Vector3(); - vertex.x = radius * Math.sin(u * thetaLength + thetaStart); - vertex.y = -v * height + heightHalf; - vertex.z = radius * Math.cos(u * thetaLength + thetaStart); + var vertex = new THREE.Vector3(); + vertex.x = radius * Math.sin( u * thetaLength + thetaStart ); + vertex.y = - v * height + heightHalf; + vertex.z = radius * Math.cos( u * thetaLength + thetaStart ); - this.vertices.push(vertex); + this.vertices.push( vertex ); - verticesRow.push(this.vertices.length - 1); - uvsRow.push(new THREE.Vector2(u, 1 - v)); + verticesRow.push( this.vertices.length - 1 ); + uvsRow.push( new THREE.Vector2( u, 1 - v ) ); - } + } - vertices.push(verticesRow); - uvs.push(uvsRow); + vertices.push( verticesRow ); + uvs.push( uvsRow ); - } + } - var tanTheta = ( radiusBottom - radiusTop ) / height; - var na, nb; + var tanTheta = ( radiusBottom - radiusTop ) / height; + var na, nb; - for (x = 0; x < radialSegments; x++) { + for ( x = 0; x < radialSegments; x ++ ) { - if (radiusTop !== 0) { + if ( radiusTop !== 0 ) { - na = this.vertices[vertices[0][x]].clone(); - nb = this.vertices[vertices[0][x + 1]].clone(); + na = this.vertices[ vertices[ 0 ][ x ] ].clone(); + nb = this.vertices[ vertices[ 0 ][ x + 1 ] ].clone(); - } else { + } else { - na = this.vertices[vertices[1][x]].clone(); - nb = this.vertices[vertices[1][x + 1]].clone(); + 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(); + 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++) { + 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 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 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(); + 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( 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.faces.push( new THREE.Face3( v2, v3, v4, [ n2.clone(), n3, n4.clone() ] ) ); + this.faceVertexUvs[ 0 ].push( [ uv2.clone(), uv3, uv4.clone() ] ); - } + } - } + } - // top cap + // top cap - if (openEnded === false && radiusTop > 0) { + if ( openEnded === false && radiusTop > 0 ) { - this.vertices.push(new THREE.Vector3(0, heightHalf, 0)); + this.vertices.push( new THREE.Vector3( 0, heightHalf, 0 ) ); - for (x = 0; x < radialSegments; x++) { + for ( x = 0; x < radialSegments; x ++ ) { - var v1 = vertices[0][x]; - var v2 = vertices[0][x + 1]; - var v3 = this.vertices.length - 1; + 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 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); + 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]); + this.faces.push( new THREE.Face3( v1, v2, v3, [ n1, n2, n3 ], undefined, 1 ) ); + this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv3 ] ); - } + } - } + } - // bottom cap + // bottom cap - if (openEnded === false && radiusBottom > 0) { + if ( openEnded === false && radiusBottom > 0 ) { - this.vertices.push(new THREE.Vector3(0, -heightHalf, 0)); + this.vertices.push( new THREE.Vector3( 0, - heightHalf, 0 ) ); - for (x = 0; x < radialSegments; x++) { + for ( x = 0; x < radialSegments; x ++ ) { - var v1 = vertices[heightSegments][x + 1]; - var v2 = vertices[heightSegments][x]; - var v3 = this.vertices.length - 1; + var v1 = vertices[ heightSegments ][ x + 1 ]; + var v2 = vertices[ heightSegments ][ 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 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[heightSegments][x + 1].clone(); - var uv2 = uvs[heightSegments][x].clone(); - var uv3 = new THREE.Vector2(uv2.x, 1); + var uv1 = uvs[ heightSegments ][ x + 1 ].clone(); + var uv2 = uvs[ heightSegments ][ 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.faces.push( new THREE.Face3( v1, v2, v3, [ n1, n2, n3 ], undefined, 2 ) ); + this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv3 ] ); - } + } - } + } - this.computeFaceNormals(); + this.computeFaceNormals(); }; -THREE.CylinderGeometry.prototype = Object.create(THREE.Geometry.prototype); +THREE.CylinderGeometry.prototype = Object.create( THREE.Geometry.prototype ); THREE.CylinderGeometry.prototype.constructor = THREE.CylinderGeometry; +THREE.CylinderGeometry.prototype.clone = function () { + + var geometry = new THREE.CylinderGeometry( + this.parameters.radiusTop, + this.parameters.radiusBottom, + this.parameters.height, + this.parameters.radialSegments, + this.parameters.heightSegments, + this.parameters.openEnded, + this.parameters.thetaStart, + this.parameters.thetaLength + ); + + return geometry; + +}; + // File:src/extras/geometries/EdgesGeometry.js /** * @author WestLangley / http://github.com/WestLangley */ -THREE.EdgesGeometry = function (geometry, thresholdAngle) { +THREE.EdgesGeometry = function ( geometry, thresholdAngle ) { - THREE.BufferGeometry.call(this); + THREE.BufferGeometry.call( this ); - thresholdAngle = ( thresholdAngle !== undefined ) ? thresholdAngle : 1; + thresholdAngle = ( thresholdAngle !== undefined ) ? thresholdAngle : 1; - var thresholdDot = Math.cos(THREE.Math.degToRad(thresholdAngle)); + var thresholdDot = Math.cos( THREE.Math.degToRad( thresholdAngle ) ); - var edge = [0, 0], hash = {}; - var sortFunction = function (a, b) { - return a - b - }; + var edge = [ 0, 0 ], hash = {}; + var sortFunction = function ( a, b ) { - var keys = ['a', 'b', 'c']; + return a - b; - var geometry2; + }; - if (geometry instanceof THREE.BufferGeometry) { + var keys = [ 'a', 'b', 'c' ]; - geometry2 = new THREE.Geometry(); - geometry2.fromBufferGeometry(geometry); + var geometry2; - } else { + if ( geometry instanceof THREE.BufferGeometry ) { - geometry2 = geometry.clone(); + geometry2 = new THREE.Geometry(); + geometry2.fromBufferGeometry( geometry ); - } + } else { - geometry2.mergeVertices(); - geometry2.computeFaceNormals(); + geometry2 = geometry.clone(); - var vertices = geometry2.vertices; - var faces = geometry2.faces; - var numEdges = 0; + } - for (var i = 0, l = faces.length; i < l; i++) { + geometry2.mergeVertices(); + geometry2.computeFaceNormals(); - var face = faces[i]; + var vertices = geometry2.vertices; + var faces = geometry2.faces; - for (var j = 0; j < 3; j++) { + for ( var i = 0, l = faces.length; i < l; i ++ ) { - edge[0] = face[keys[j]]; - edge[1] = face[keys[( j + 1 ) % 3]]; - edge.sort(sortFunction); + var face = faces[ i ]; - var key = edge.toString(); + for ( var j = 0; j < 3; j ++ ) { - if (hash[key] === undefined) { + edge[ 0 ] = face[ keys[ j ] ]; + edge[ 1 ] = face[ keys[ ( j + 1 ) % 3 ] ]; + edge.sort( sortFunction ); - hash[key] = {vert1: edge[0], vert2: edge[1], face1: i, face2: undefined}; - numEdges++; + var key = edge.toString(); - } else { + if ( hash[ key ] === undefined ) { - hash[key].face2 = i; + hash[ key ] = { vert1: edge[ 0 ], vert2: edge[ 1 ], face1: i, face2: undefined }; - } + } else { - } + hash[ key ].face2 = i; - } + } - var coords = new Float32Array(numEdges * 2 * 3); + } - var index = 0; + } - for (var key in hash) { + var coords = []; - var h = hash[key]; + for ( var key in hash ) { - if (h.face2 === undefined || faces[h.face1].normal.dot(faces[h.face2].normal) <= thresholdDot) { + var h = hash[ key ]; - var vertex = vertices[h.vert1]; - coords[index++] = vertex.x; - coords[index++] = vertex.y; - coords[index++] = vertex.z; + if ( h.face2 === undefined || faces[ h.face1 ].normal.dot( faces[ h.face2 ].normal ) <= thresholdDot ) { - vertex = vertices[h.vert2]; - coords[index++] = vertex.x; - coords[index++] = vertex.y; - coords[index++] = vertex.z; + var vertex = vertices[ h.vert1 ]; + coords.push( vertex.x ); + coords.push( vertex.y ); + coords.push( vertex.z ); - } + vertex = vertices[ h.vert2 ]; + coords.push( vertex.x ); + coords.push( vertex.y ); + coords.push( vertex.z ); - } + } + + } - this.addAttribute('position', new THREE.BufferAttribute(coords, 3)); + this.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( coords ), 3 ) ); }; -THREE.EdgesGeometry.prototype = Object.create(THREE.BufferGeometry.prototype); +THREE.EdgesGeometry.prototype = Object.create( THREE.BufferGeometry.prototype ); THREE.EdgesGeometry.prototype.constructor = THREE.EdgesGeometry; // File:src/extras/geometries/ExtrudeGeometry.js @@ -30865,7 +32830,7 @@ THREE.EdgesGeometry.prototype.constructor = THREE.EdgesGeometry; * parameters = { * * curveSegments: , // number of points on the curves - * steps: , // number of points for z-side extrusions / used for subdividing segements of extrude spline too + * steps: , // number of points for z-side extrusions / used for subdividing segments of extrude spline too * amount: , // Depth to extrude the shape * * bevelEnabled: , // turn on bevel @@ -30881,647 +32846,687 @@ THREE.EdgesGeometry.prototype.constructor = THREE.EdgesGeometry; * } **/ -THREE.ExtrudeGeometry = function (shapes, options) { +THREE.ExtrudeGeometry = function ( shapes, options ) { - if (typeof( shapes ) === "undefined") { - shapes = []; - return; - } + if ( typeof( shapes ) === "undefined" ) { + + shapes = []; + return; - THREE.Geometry.call(this); + } - this.type = 'ExtrudeGeometry'; + THREE.Geometry.call( this ); - shapes = shapes instanceof Array ? shapes : [shapes]; + this.type = 'ExtrudeGeometry'; - this.addShapeList(shapes, options); + shapes = Array.isArray( shapes ) ? shapes : [ shapes ]; - this.computeFaceNormals(); + this.addShapeList( shapes, options ); - // can't really use automatic vertex normals - // as then front and back sides get smoothed too - // should do separate smoothing just for sides + this.computeFaceNormals(); - //this.computeVertexNormals(); + // can't really use automatic vertex normals + // as then front and back sides get smoothed too + // should do separate smoothing just for sides - //THREE.log( "took", ( Date.now() - startTime ) ); + //this.computeVertexNormals(); + + //console.log( "took", ( Date.now() - startTime ) ); }; -THREE.ExtrudeGeometry.prototype = Object.create(THREE.Geometry.prototype); +THREE.ExtrudeGeometry.prototype = Object.create( THREE.Geometry.prototype ); THREE.ExtrudeGeometry.prototype.constructor = THREE.ExtrudeGeometry; -THREE.ExtrudeGeometry.prototype.addShapeList = function (shapes, options) { - var sl = shapes.length; +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 ); + + } - for (var s = 0; s < sl; s++) { - var shape = shapes[s]; - this.addShape(shape, options); - } }; -THREE.ExtrudeGeometry.prototype.addShape = function (shape, options) { +THREE.ExtrudeGeometry.prototype.addShape = function ( shape, options ) { - var amount = options.amount !== undefined ? options.amount : 100; + 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 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 bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true; // false - var curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12; + var curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12; - var steps = options.steps !== undefined ? options.steps : 1; + var steps = options.steps !== undefined ? options.steps : 1; - var extrudePath = options.extrudePath; - var extrudePts, extrudeByPath = false; + var extrudePath = options.extrudePath; + var extrudePts, extrudeByPath = false; - // Use default WorldUVGenerator if no UV generators are specified. - var uvgen = options.UVGenerator !== undefined ? options.UVGenerator : THREE.ExtrudeGeometry.WorldUVGenerator; + // Use default WorldUVGenerator if no UV generators are specified. + var uvgen = options.UVGenerator !== undefined ? options.UVGenerator : THREE.ExtrudeGeometry.WorldUVGenerator; - var splineTube, binormal, normal, position2; - if (extrudePath) { + var splineTube, binormal, normal, position2; + if ( extrudePath ) { - extrudePts = extrudePath.getSpacedPoints(steps); + extrudePts = extrudePath.getSpacedPoints( steps ); - extrudeByPath = true; - bevelEnabled = false; // bevels not supported for path extrusion + extrudeByPath = true; + bevelEnabled = false; // bevels not supported for path extrusion - // SETUP TNB variables + // SETUP TNB variables - // Reuse TNB from TubeGeomtry for now. - // TODO1 - have a .isClosed in spline? + // 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); + splineTube = options.frames !== undefined ? options.frames : new THREE.TubeGeometry.FrenetFrames( extrudePath, steps, false ); - // THREE.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length); + // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length); - binormal = new THREE.Vector3(); - normal = new THREE.Vector3(); - position2 = new THREE.Vector3(); + binormal = new THREE.Vector3(); + normal = new THREE.Vector3(); + position2 = new THREE.Vector3(); - } + } - // Safeguards if bevels are not enabled + // Safeguards if bevels are not enabled - if (!bevelEnabled) { + if ( ! bevelEnabled ) { - bevelSegments = 0; - bevelThickness = 0; - bevelSize = 0; + bevelSegments = 0; + bevelThickness = 0; + bevelSize = 0; - } + } - // Variables initalization + // Variables initialization - var ahole, h, hl; // looping of holes - var scope = this; + var ahole, h, hl; // looping of holes + var scope = this; - var shapesOffset = this.vertices.length; + var shapesOffset = this.vertices.length; - var shapePoints = shape.extractPoints(curveSegments); + var shapePoints = shape.extractPoints( curveSegments ); - var vertices = shapePoints.shape; - var holes = shapePoints.holes; + var vertices = shapePoints.shape; + var holes = shapePoints.holes; - var reverse = !THREE.Shape.Utils.isClockWise(vertices); + var reverse = ! THREE.Shape.Utils.isClockWise( vertices ); - if (reverse) { + if ( reverse ) { - vertices = vertices.reverse(); + vertices = vertices.reverse(); - // Maybe we should also check if holes are in the opposite direction, just to be safe ... + // 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++) { + for ( h = 0, hl = holes.length; h < hl; h ++ ) { - ahole = holes[h]; + ahole = holes[ h ]; - if (THREE.Shape.Utils.isClockWise(ahole)) { + if ( THREE.Shape.Utils.isClockWise( ahole ) ) { - holes[h] = ahole.reverse(); + holes[ h ] = ahole.reverse(); - } + } - } + } - reverse = false; // If vertices are in order now, we shouldn't need to worry about them again (hopefully)! + 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); + var faces = THREE.Shape.Utils.triangulateShape ( vertices, holes ); - /* Vertices */ + /* Vertices */ - var contour = vertices; // vertices has all points but contour has only points of circumference + var contour = vertices; // vertices has all points but contour has only points of circumference - for (h = 0, hl = holes.length; h < hl; h++) { + for ( h = 0, hl = holes.length; h < hl; h ++ ) { - ahole = holes[h]; + ahole = holes[ h ]; - vertices = vertices.concat(ahole); + vertices = vertices.concat( ahole ); - } + } - function scalePt2(pt, vec, size) { + function scalePt2 ( pt, vec, size ) { - if (!vec) THREE.error("THREE.ExtrudeGeometry: vec does not exist"); + if ( ! vec ) console.error( "THREE.ExtrudeGeometry: vec does not exist" ); - return vec.clone().multiplyScalar(size).add(pt); + return vec.clone().multiplyScalar( size ).add( pt ); - } + } - var b, bs, t, z, - vert, vlen = vertices.length, - face, flen = faces.length; + var b, bs, t, z, + vert, vlen = vertices.length, + face, flen = faces.length; - // Find directions for point movement + // Find directions for point movement - function getBevelVec(inPt, inPrev, inNext) { + function getBevelVec( inPt, inPrev, inNext ) { - var EPSILON = 0.0000000001; + var EPSILON = 0.0000000001; - // 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. + // computes for inPt the corresponding point inPt' on a new contour + // shifted 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 + 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 + // 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_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 ); + 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 ); + // check for collinear edges + var collinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x ); - if (Math.abs(colinear0) > EPSILON) { // not colinear + if ( Math.abs( collinear0 ) > EPSILON ) { - // length of vectors for normalizing + // not collinear - 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); + // length of vectors for normalizing - // shift adjacent points by unit vectors to the left + 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 ); - var ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len ); - var ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len ); + // shift adjacent points by unit vectors to the left - var ptNextShift_x = ( inNext.x - v_next_y / v_next_len ); - var ptNextShift_y = ( inNext.y + v_next_x / v_next_len ); + var ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len ); + var ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len ); - // scaling factor for v_prev to intersection point + var ptNextShift_x = ( inNext.x - v_next_y / v_next_len ); + var ptNextShift_y = ( inNext.y + v_next_x / v_next_len ); - 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 ); + // scaling factor for v_prev to intersection point - // vector from inPt 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 ); - v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x ); - v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y ); + // vector from inPt to intersection point - // 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); - } + v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x ); + v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y ); - } else { // handle special case of colinear edges + // 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 ) { - 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 (Math.sign(v_prev_y) == Math.sign(v_next_y)) { - direction_eq = true; - } - } - } + return new THREE.Vector2( v_trans_x, v_trans_y ); - if (direction_eq) { - // THREE.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 { - // THREE.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); - } + } else { - } + shrink_by = Math.sqrt( v_trans_lensq / 2 ); - return new THREE.Vector2(v_trans_x / shrink_by, v_trans_y / shrink_by); + } - } + } else { + // handle special case of collinear edges - var contourMovements = []; + var direction_eq = false; // assumes: opposite + if ( v_prev_x > EPSILON ) { - for (var i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i++, j++, k++) { + if ( v_next_x > EPSILON ) { - if (j === il) j = 0; - if (k === il) k = 0; + direction_eq = true; - // (j)---(i)---(k) - // THREE.log('i,j,k', i, j , k) + } - contourMovements[i] = getBevelVec(contour[i], contour[j], contour[k]); + } else { - } + if ( v_prev_x < - EPSILON ) { - var holesMovements = [], oneHoleMovements, verticesMovements = contourMovements.concat(); + if ( v_next_x < - EPSILON ) { - for (h = 0, hl = holes.length; h < hl; h++) { + direction_eq = true; - ahole = holes[h]; + } - oneHoleMovements = []; + } else { - for (i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i++, j++, k++) { + if ( Math.sign( v_prev_y ) === Math.sign( v_next_y ) ) { - if (j === il) j = 0; - if (k === il) k = 0; + direction_eq = true; - // (j)---(i)---(k) - oneHoleMovements[i] = getBevelVec(ahole[i], ahole[j], ahole[k]); + } - } + } - holesMovements.push(oneHoleMovements); - verticesMovements = verticesMovements.concat(oneHoleMovements); + } - } + 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 ); - // Loop bevelSegments, 1 for the front, 1 for the back + } else { - for (b = 0; b < bevelSegments; b++) { - //for ( b = bevelSegments; b > 0; b -- ) { + // 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 ); - 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 + return new THREE.Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by ); - for (i = 0, il = contour.length; i < il; i++) { + } - vert = scalePt2(contour[i], contourMovements[i], bs); - v(vert.x, vert.y, -z); + var contourMovements = []; - } + for ( var i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { - // expand holes + if ( j === il ) j = 0; + if ( k === il ) k = 0; - for (h = 0, hl = holes.length; h < hl; h++) { + // (j)---(i)---(k) + // console.log('i,j,k', i, j , k) - ahole = holes[h]; - oneHoleMovements = holesMovements[h]; + contourMovements[ i ] = getBevelVec( contour[ i ], contour[ j ], contour[ k ] ); - for (i = 0, il = ahole.length; i < il; i++) { + } - vert = scalePt2(ahole[i], oneHoleMovements[i], bs); + var holesMovements = [], oneHoleMovements, verticesMovements = contourMovements.concat(); - v(vert.x, vert.y, -z); + 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 ++ ) { - bs = bevelSize; + if ( j === il ) j = 0; + if ( k === il ) k = 0; - // Back facing vertices + // (j)---(i)---(k) + oneHoleMovements[ i ] = getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] ); - for (i = 0; i < vlen; i++) { + } - vert = bevelEnabled ? scalePt2(vertices[i], verticesMovements[i], bs) : vertices[i]; + holesMovements.push( oneHoleMovements ); + verticesMovements = verticesMovements.concat( oneHoleMovements ); - if (!extrudeByPath) { + } - v(vert.x, vert.y, 0); - } else { + // Loop bevelSegments, 1 for the front, 1 for the back - // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x ); + for ( b = 0; b < bevelSegments; b ++ ) { - normal.copy(splineTube.normals[0]).multiplyScalar(vert.x); - binormal.copy(splineTube.binormals[0]).multiplyScalar(vert.y); + //for ( b = bevelSegments; b > 0; b -- ) { - position2.copy(extrudePts[0]).add(normal).add(binormal); + t = b / bevelSegments; + z = bevelThickness * ( 1 - t ); - v(position2.x, position2.y, position2.z); + //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 ++ ) { - // Add stepped vertices... - // Including front facing vertices + vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); - var s; + v( vert.x, vert.y, - z ); - for (s = 1; s <= steps; s++) { + } - for (i = 0; i < vlen; i++) { + // expand holes - vert = bevelEnabled ? scalePt2(vertices[i], verticesMovements[i], bs) : vertices[i]; + for ( h = 0, hl = holes.length; h < hl; h ++ ) { - if (!extrudeByPath) { + ahole = holes[ h ]; + oneHoleMovements = holesMovements[ h ]; - v(vert.x, vert.y, amount / steps * s); + for ( i = 0, il = ahole.length; i < il; i ++ ) { - } else { + vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); - // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x ); + v( vert.x, vert.y, - z ); - 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); + } - } + bs = bevelSize; - } + // Back facing vertices - } + for ( i = 0; i < vlen; i ++ ) { + vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; - // Add bevel segments planes + if ( ! extrudeByPath ) { - //for ( b = 1; b <= bevelSegments; b ++ ) { - for (b = bevelSegments - 1; b >= 0; b--) { + v( vert.x, vert.y, 0 ); - 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); + } else { - // contract shape + // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x ); - for (i = 0, il = contour.length; i < il; i++) { + normal.copy( splineTube.normals[ 0 ] ).multiplyScalar( vert.x ); + binormal.copy( splineTube.binormals[ 0 ] ).multiplyScalar( vert.y ); - vert = scalePt2(contour[i], contourMovements[i], bs); - v(vert.x, vert.y, amount + z); + position2.copy( extrudePts[ 0 ] ).add( normal ).add( binormal ); - } + v( position2.x, position2.y, position2.z ); - // expand holes + } - for (h = 0, hl = holes.length; h < hl; h++) { + } - ahole = holes[h]; - oneHoleMovements = holesMovements[h]; + // Add stepped vertices... + // Including front facing vertices - for (i = 0, il = ahole.length; i < il; i++) { + var s; - vert = scalePt2(ahole[i], oneHoleMovements[i], bs); + for ( s = 1; s <= steps; s ++ ) { - if (!extrudeByPath) { + for ( i = 0; i < vlen; i ++ ) { - v(vert.x, vert.y, amount + z); + vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; - } else { + if ( ! extrudeByPath ) { - v(vert.x, vert.y + extrudePts[steps - 1].y, extrudePts[steps - 1].x + z); + 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 ); - /* Faces */ + v( position2.x, position2.y, position2.z ); - // Top and bottom faces + } - buildLidFaces(); + } - // Sides faces + } - buildSideFaces(); + // Add bevel segments planes - ///// Internal functions + //for ( b = 1; b <= bevelSegments; b ++ ) { + for ( b = bevelSegments - 1; b >= 0; b -- ) { - function buildLidFaces() { + 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 ); - if (bevelEnabled) { + // contract shape - var layer = 0; // steps + 1 - var offset = vlen * layer; + for ( i = 0, il = contour.length; i < il; i ++ ) { - // Bottom faces + vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); + v( vert.x, vert.y, amount + z ); - for (i = 0; i < flen; i++) { + } - face = faces[i]; - f3(face[2] + offset, face[1] + offset, face[0] + offset); + // expand holes - } + for ( h = 0, hl = holes.length; h < hl; h ++ ) { - layer = steps + bevelSegments * 2; - offset = vlen * layer; + ahole = holes[ h ]; + oneHoleMovements = holesMovements[ h ]; - // Top faces + for ( i = 0, il = ahole.length; i < il; i ++ ) { - for (i = 0; i < flen; i++) { + vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); - face = faces[i]; - f3(face[0] + offset, face[1] + offset, face[2] + offset); + if ( ! extrudeByPath ) { - } + v( vert.x, vert.y, amount + z ); - } else { + } else { - // Bottom faces + v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z ); - for (i = 0; i < flen; i++) { + } - face = faces[i]; - f3(face[2], face[1], face[0]); + } - } + } - // Top faces + } - for (i = 0; i < flen; i++) { + /* Faces */ - face = faces[i]; - f3(face[0] + vlen * steps, face[1] + vlen * steps, face[2] + vlen * steps); + // Top and bottom faces - } - } + buildLidFaces(); - } + // Sides faces - // Create faces for the z-sides of the shape + buildSideFaces(); - function buildSideFaces() { - var layeroffset = 0; - sidewalls(contour, layeroffset); - layeroffset += contour.length; + ///// Internal functions - for (h = 0, hl = holes.length; h < hl; h++) { + function buildLidFaces() { - ahole = holes[h]; - sidewalls(ahole, layeroffset); + if ( bevelEnabled ) { - //, true - layeroffset += ahole.length; + var layer = 0; // steps + 1 + var offset = vlen * layer; - } + // Bottom faces - } + for ( i = 0; i < flen; i ++ ) { - function sidewalls(contour, layeroffset) { + face = faces[ i ]; + f3( face[ 2 ] + offset, face[ 1 ] + offset, face[ 0 ] + offset ); - var j, k; - i = contour.length; + } - while (--i >= 0) { + layer = steps + bevelSegments * 2; + offset = vlen * layer; - j = i; - k = i - 1; - if (k < 0) k = contour.length - 1; + // Top faces - //THREE.log('b', i,j, i-1, k,vertices.length); + for ( i = 0; i < flen; i ++ ) { - var s = 0, sl = steps + bevelSegments * 2; + face = faces[ i ]; + f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset ); - for (s = 0; s < sl; s++) { + } - var slen1 = vlen * s; - var slen2 = vlen * ( s + 1 ); + } else { - var a = layeroffset + j + slen1, - b = layeroffset + k + slen1, - c = layeroffset + k + slen2, - d = layeroffset + j + slen2; + // Bottom faces - f4(a, b, c, d, contour, s, sl, j, k); + for ( i = 0; i < flen; i ++ ) { - } - } + face = faces[ i ]; + f3( face[ 2 ], face[ 1 ], face[ 0 ] ); - } + } + // Top faces - function v(x, y, z) { + for ( i = 0; i < flen; i ++ ) { - scope.vertices.push(new THREE.Vector3(x, y, z)); + face = faces[ i ]; + f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps ); - } + } - function f3(a, b, c) { + } - a += shapesOffset; - b += shapesOffset; - c += shapesOffset; + } - scope.faces.push(new THREE.Face3(a, b, c)); + // Create faces for the z-sides of the shape - var uvs = uvgen.generateTopUV(scope, a, b, c); + function buildSideFaces() { - scope.faceVertexUvs[0].push(uvs); + var layeroffset = 0; + sidewalls( contour, layeroffset ); + layeroffset += contour.length; - } + for ( h = 0, hl = holes.length; h < hl; h ++ ) { - function f4(a, b, c, d, wallContour, stepIndex, stepsLength, contourIndex1, contourIndex2) { + ahole = holes[ h ]; + sidewalls( ahole, layeroffset ); - a += shapesOffset; - b += shapesOffset; - c += shapesOffset; - d += shapesOffset; + //, true + layeroffset += ahole.length; - scope.faces.push(new THREE.Face3(a, b, d)); - scope.faces.push(new THREE.Face3(b, c, d)); + } - var uvs = uvgen.generateSideWallUV(scope, a, b, c, d); + } - scope.faceVertexUvs[0].push([uvs[0], uvs[1], uvs[3]]); - scope.faceVertexUvs[0].push([uvs[1], uvs[2], uvs[3]]); + 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 ) { + + a += shapesOffset; + b += shapesOffset; + c += shapesOffset; + + scope.faces.push( new THREE.Face3( a, b, c ) ); + + var uvs = uvgen.generateTopUV( scope, 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 ) ); + scope.faces.push( new THREE.Face3( b, c, d ) ); + + var uvs = uvgen.generateSideWallUV( scope, a, b, c, d ); + + 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, indexA, indexB, indexC) { - - var vertices = geometry.vertices; - - var a = vertices[indexA]; - var b = vertices[indexB]; - var c = vertices[indexC]; - - return [ - new THREE.Vector2(a.x, a.y), - new THREE.Vector2(b.x, b.y), - new THREE.Vector2(c.x, c.y) - ]; - - }, - - generateSideWallUV: function (geometry, indexA, indexB, indexC, indexD) { - - var vertices = geometry.vertices; - - var a = vertices[indexA]; - var b = vertices[indexB]; - var c = vertices[indexC]; - var d = vertices[indexD]; - - if (Math.abs(a.y - b.y) < 0.01) { - return [ - new THREE.Vector2(a.x, 1 - a.z), - new THREE.Vector2(b.x, 1 - b.z), - new THREE.Vector2(c.x, 1 - c.z), - new THREE.Vector2(d.x, 1 - d.z) - ]; - } else { - return [ - new THREE.Vector2(a.y, 1 - a.z), - new THREE.Vector2(b.y, 1 - b.z), - new THREE.Vector2(c.y, 1 - c.z), - new THREE.Vector2(d.y, 1 - d.z) - ]; - } - } + generateTopUV: function ( geometry, indexA, indexB, indexC ) { + + var vertices = geometry.vertices; + + var a = vertices[ indexA ]; + var b = vertices[ indexB ]; + var c = vertices[ indexC ]; + + return [ + new THREE.Vector2( a.x, a.y ), + new THREE.Vector2( b.x, b.y ), + new THREE.Vector2( c.x, c.y ) + ]; + + }, + + generateSideWallUV: function ( geometry, indexA, indexB, indexC, indexD ) { + + var vertices = geometry.vertices; + + var a = vertices[ indexA ]; + var b = vertices[ indexB ]; + var c = vertices[ indexC ]; + var d = vertices[ indexD ]; + + if ( Math.abs( a.y - b.y ) < 0.01 ) { + + return [ + new THREE.Vector2( a.x, 1 - a.z ), + new THREE.Vector2( b.x, 1 - b.z ), + new THREE.Vector2( c.x, 1 - c.z ), + new THREE.Vector2( d.x, 1 - d.z ) + ]; + + } else { + + return [ + new THREE.Vector2( a.y, 1 - a.z ), + new THREE.Vector2( b.y, 1 - b.z ), + new THREE.Vector2( c.y, 1 - c.z ), + new THREE.Vector2( d.y, 1 - d.z ) + ]; + + } + + } }; // File:src/extras/geometries/ShapeGeometry.js @@ -31542,121 +33547,119 @@ THREE.ExtrudeGeometry.WorldUVGenerator = { * } **/ -THREE.ShapeGeometry = function (shapes, options) { +THREE.ShapeGeometry = function ( shapes, options ) { - THREE.Geometry.call(this); + THREE.Geometry.call( this ); - this.type = 'ShapeGeometry'; + this.type = 'ShapeGeometry'; - if (shapes instanceof Array === false) shapes = [shapes]; + if ( Array.isArray( shapes ) === false ) shapes = [ shapes ]; - this.addShapeList(shapes, options); + this.addShapeList( shapes, options ); - this.computeFaceNormals(); + this.computeFaceNormals(); }; -THREE.ShapeGeometry.prototype = Object.create(THREE.Geometry.prototype); +THREE.ShapeGeometry.prototype = Object.create( THREE.Geometry.prototype ); THREE.ShapeGeometry.prototype.constructor = THREE.ShapeGeometry; /** * Add an array of shapes to THREE.ShapeGeometry. */ -THREE.ShapeGeometry.prototype.addShapeList = function (shapes, options) { +THREE.ShapeGeometry.prototype.addShapeList = function ( shapes, options ) { - for (var i = 0, l = shapes.length; i < l; i++) { + for ( var i = 0, l = shapes.length; i < l; i ++ ) { - this.addShape(shapes[i], options); + this.addShape( shapes[ i ], options ); - } + } - return this; + return this; }; /** * Adds a shape to THREE.ShapeGeometry, based on THREE.ExtrudeGeometry. */ -THREE.ShapeGeometry.prototype.addShape = function (shape, options) { +THREE.ShapeGeometry.prototype.addShape = function ( shape, options ) { - if (options === undefined) options = {}; - var curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12; + 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 material = options.material; + var uvgen = options.UVGenerator === undefined ? THREE.ExtrudeGeometry.WorldUVGenerator : options.UVGenerator; - // + // - var i, l, hole; + var i, l, hole; - var shapesOffset = this.vertices.length; - var shapePoints = shape.extractPoints(curveSegments); + var shapesOffset = this.vertices.length; + var shapePoints = shape.extractPoints( curveSegments ); - var vertices = shapePoints.shape; - var holes = shapePoints.holes; + var vertices = shapePoints.shape; + var holes = shapePoints.holes; - var reverse = !THREE.Shape.Utils.isClockWise(vertices); + var reverse = ! THREE.Shape.Utils.isClockWise( vertices ); - if (reverse) { + if ( reverse ) { - vertices = vertices.reverse(); + vertices = vertices.reverse(); - // Maybe we should also check if holes are in the opposite direction, just to be safe... + // 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++) { + for ( i = 0, l = holes.length; i < l; i ++ ) { - hole = holes[i]; + hole = holes[ i ]; - if (THREE.Shape.Utils.isClockWise(hole)) { + if ( THREE.Shape.Utils.isClockWise( hole ) ) { - holes[i] = hole.reverse(); + holes[ i ] = hole.reverse(); - } + } - } + } - reverse = false; + reverse = false; - } - - var faces = THREE.Shape.Utils.triangulateShape(vertices, holes); + } - // Vertices + var faces = THREE.Shape.Utils.triangulateShape( vertices, holes ); - var contour = vertices; + // Vertices - for (i = 0, l = holes.length; i < l; i++) { + for ( i = 0, l = holes.length; i < l; i ++ ) { - hole = holes[i]; - vertices = vertices.concat(hole); + hole = holes[ i ]; + vertices = vertices.concat( hole ); - } + } - // + // - var vert, vlen = vertices.length; - var face, flen = faces.length; + var vert, vlen = vertices.length; + var face, flen = faces.length; - for (i = 0; i < vlen; i++) { + for ( i = 0; i < vlen; i ++ ) { - vert = vertices[i]; + vert = vertices[ i ]; - this.vertices.push(new THREE.Vector3(vert.x, vert.y, 0)); + this.vertices.push( new THREE.Vector3( vert.x, vert.y, 0 ) ); - } + } - for (i = 0; i < flen; i++) { + for ( i = 0; i < flen; i ++ ) { - face = faces[i]; + face = faces[ i ]; - var a = face[0] + shapesOffset; - var b = face[1] + shapesOffset; - var c = face[2] + shapesOffset; + 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.generateTopUV(this, a, b, c)); + this.faces.push( new THREE.Face3( a, b, c, null, null, material ) ); + this.faceVertexUvs[ 0 ].push( uvgen.generateTopUV( this, a, b, c ) ); - } + } }; @@ -31665,7 +33668,7 @@ THREE.ShapeGeometry.prototype.addShape = function (shape, options) { /** * @author astrodud / http://astrodud.isgreat.org/ * @author zz85 / https://github.com/zz85 - * @author bhouston / http://exocortex.com + * @author bhouston / http://clara.io */ // points - to create a closed torus, one must use a set of points @@ -31675,432 +33678,409 @@ THREE.ShapeGeometry.prototype.addShape = function (shape, options) { // 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.LatheGeometry = function ( points, segments, phiStart, phiLength ) { - THREE.Geometry.call(this); + THREE.Geometry.call( this ); - this.type = 'LatheGeometry'; + this.type = 'LatheGeometry'; - this.parameters = { - points: points, - segments: segments, - phiStart: phiStart, - phiLength: phiLength - }; + this.parameters = { + points: points, + segments: segments, + phiStart: phiStart, + phiLength: phiLength + }; - segments = segments || 12; - phiStart = phiStart || 0; - phiLength = phiLength || 2 * Math.PI; + segments = segments || 12; + phiStart = phiStart || 0; + phiLength = phiLength || 2 * Math.PI; - var inversePointLength = 1.0 / ( points.length - 1 ); - var inverseSegments = 1.0 / segments; + var inversePointLength = 1.0 / ( points.length - 1 ); + var inverseSegments = 1.0 / segments; - for (var i = 0, il = segments; i <= il; i++) { + for ( var i = 0, il = segments; i <= il; i ++ ) { - var phi = phiStart + i * inverseSegments * phiLength; + var phi = phiStart + i * inverseSegments * phiLength; - var c = Math.cos(phi), - s = Math.sin(phi); + var c = Math.cos( phi ), + s = Math.sin( phi ); - for (var j = 0, jl = points.length; j < jl; j++) { + for ( var j = 0, jl = points.length; j < jl; j ++ ) { - var pt = points[j]; + var pt = points[ j ]; - var vertex = new THREE.Vector3(); + 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; + vertex.x = c * pt.x - s * pt.y; + vertex.y = s * pt.x + c * pt.y; + vertex.z = pt.z; - this.vertices.push(vertex); + this.vertices.push( vertex ); - } + } - } + } - var np = points.length; + var np = points.length; - for (var i = 0, il = segments; i < il; i++) { + for ( var i = 0, il = segments; i < il; i ++ ) { - for (var j = 0, jl = points.length - 1; j < jl; j++) { + 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 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; + 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.faces.push( new THREE.Face3( a, b, d ) ); - this.faceVertexUvs[0].push([ + this.faceVertexUvs[ 0 ].push( [ - new THREE.Vector2(u0, v0), - new THREE.Vector2(u1, v0), - new THREE.Vector2(u0, v1) + 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.faces.push( new THREE.Face3( b, c, d ) ); - this.faceVertexUvs[0].push([ + this.faceVertexUvs[ 0 ].push( [ - new THREE.Vector2(u1, v0), - new THREE.Vector2(u1, v1), - new THREE.Vector2(u0, v1) + new THREE.Vector2( u1, v0 ), + new THREE.Vector2( u1, v1 ), + new THREE.Vector2( u0, v1 ) - ]); + ] ); - } + } - } + } - this.mergeVertices(); - this.computeFaceNormals(); - this.computeVertexNormals(); + this.mergeVertices(); + this.computeFaceNormals(); + this.computeVertexNormals(); }; -THREE.LatheGeometry.prototype = Object.create(THREE.Geometry.prototype); +THREE.LatheGeometry.prototype = Object.create( THREE.Geometry.prototype ); THREE.LatheGeometry.prototype.constructor = THREE.LatheGeometry; -// File:src/extras/geometries/PlaneGeometry.js - -/** - * @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.log('THREE.PlaneGeometry: Consider using THREE.PlaneBufferGeometry for lower memory footprint.'); - - THREE.Geometry.call(this); - - this.type = 'PlaneGeometry'; - - this.parameters = { - width: width, - height: height, - widthSegments: widthSegments, - heightSegments: heightSegments - }; - - this.fromBufferGeometry(new THREE.PlaneBufferGeometry(width, height, widthSegments, heightSegments)); - -}; - -THREE.PlaneGeometry.prototype = Object.create(THREE.Geometry.prototype); -THREE.PlaneGeometry.prototype.constructor = THREE.PlaneGeometry; - -// File:src/extras/geometries/PlaneBufferGeometry.js +// File:src/extras/geometries/PlaneGeometry.js /** * @author mrdoob / http://mrdoob.com/ * based on http://papervision3d.googlecode.com/svn/trunk/as3/trunk/src/org/papervision3d/objects/primitives/Plane.as */ -THREE.PlaneBufferGeometry = function (width, height, widthSegments, heightSegments) { +THREE.PlaneGeometry = function ( width, height, widthSegments, heightSegments ) { - THREE.BufferGeometry.call(this); + THREE.Geometry.call( this ); - this.type = 'PlaneBufferGeometry'; + this.type = 'PlaneGeometry'; - this.parameters = { - width: width, - height: height, - widthSegments: widthSegments, - heightSegments: heightSegments - }; + this.parameters = { + width: width, + height: height, + widthSegments: widthSegments, + heightSegments: heightSegments + }; - var width_half = width / 2; - var height_half = height / 2; + this.fromBufferGeometry( new THREE.PlaneBufferGeometry( width, height, widthSegments, heightSegments ) ); - var gridX = widthSegments || 1; - var gridY = heightSegments || 1; +}; - var gridX1 = gridX + 1; - var gridY1 = gridY + 1; +THREE.PlaneGeometry.prototype = Object.create( THREE.Geometry.prototype ); +THREE.PlaneGeometry.prototype.constructor = THREE.PlaneGeometry; - var segment_width = width / gridX; - var segment_height = height / gridY; +THREE.PlaneGeometry.prototype.clone = function () { - var vertices = new Float32Array(gridX1 * gridY1 * 3); - var normals = new Float32Array(gridX1 * gridY1 * 3); - var uvs = new Float32Array(gridX1 * gridY1 * 2); + var geometry = new THREE.PlaneGeometry( + this.parameters.width, + this.parameters.height, + this.parameters.widthSegments, + this.parameters.heightSegments + ); - var offset = 0; - var offset2 = 0; + return geometry; - for (var iy = 0; iy < gridY1; iy++) { +}; - var y = iy * segment_height - height_half; +// File:src/extras/geometries/PlaneBufferGeometry.js - for (var ix = 0; ix < gridX1; ix++) { +/** + * @author mrdoob / http://mrdoob.com/ + * based on http://papervision3d.googlecode.com/svn/trunk/as3/trunk/src/org/papervision3d/objects/primitives/Plane.as + */ - var x = ix * segment_width - width_half; +THREE.PlaneBufferGeometry = function ( width, height, widthSegments, heightSegments ) { - vertices[offset] = x; - vertices[offset + 1] = -y; + THREE.BufferGeometry.call( this ); - normals[offset + 2] = 1; + this.type = 'PlaneBufferGeometry'; - uvs[offset2] = ix / gridX; - uvs[offset2 + 1] = 1 - ( iy / gridY ); + this.parameters = { + width: width, + height: height, + widthSegments: widthSegments, + heightSegments: heightSegments + }; - offset += 3; - offset2 += 2; + var width_half = width / 2; + var height_half = height / 2; - } + var gridX = Math.floor( widthSegments ) || 1; + var gridY = Math.floor( heightSegments ) || 1; - } + var gridX1 = gridX + 1; + var gridY1 = gridY + 1; - offset = 0; + var segment_width = width / gridX; + var segment_height = height / gridY; - var indices = new ( ( vertices.length / 3 ) > 65535 ? Uint32Array : Uint16Array )(gridX * gridY * 6); + var vertices = new Float32Array( gridX1 * gridY1 * 3 ); + var normals = new Float32Array( gridX1 * gridY1 * 3 ); + var uvs = new Float32Array( gridX1 * gridY1 * 2 ); - for (var iy = 0; iy < gridY; iy++) { + var offset = 0; + var offset2 = 0; - for (var ix = 0; ix < gridX; ix++) { + for ( var iy = 0; iy < gridY1; iy ++ ) { - 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 y = iy * segment_height - height_half; - indices[offset] = a; - indices[offset + 1] = b; - indices[offset + 2] = d; + for ( var ix = 0; ix < gridX1; ix ++ ) { - indices[offset + 3] = b; - indices[offset + 4] = c; - indices[offset + 5] = d; + var x = ix * segment_width - width_half; - offset += 6; + vertices[ offset ] = x; + vertices[ offset + 1 ] = - y; - } + normals[ offset + 2 ] = 1; - } + uvs[ offset2 ] = ix / gridX; + uvs[ offset2 + 1 ] = 1 - ( iy / gridY ); - this.addAttribute('index', new THREE.BufferAttribute(indices, 1)); - this.addAttribute('position', new THREE.BufferAttribute(vertices, 3)); - this.addAttribute('normal', new THREE.BufferAttribute(normals, 3)); - this.addAttribute('uv', new THREE.BufferAttribute(uvs, 2)); + offset += 3; + offset2 += 2; -}; + } -THREE.PlaneBufferGeometry.prototype = Object.create(THREE.BufferGeometry.prototype); -THREE.PlaneBufferGeometry.prototype.constructor = THREE.PlaneBufferGeometry; + } -// File:src/extras/geometries/RingGeometry.js + offset = 0; -/** - * @author Kaleb Murphy - */ + var indices = new ( ( vertices.length / 3 ) > 65535 ? Uint32Array : Uint16Array )( gridX * gridY * 6 ); -THREE.RingGeometry = function (innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength) { + for ( var iy = 0; iy < gridY; iy ++ ) { - THREE.Geometry.call(this); + for ( var ix = 0; ix < gridX; ix ++ ) { - this.type = 'RingGeometry'; + var a = ix + gridX1 * iy; + var b = ix + gridX1 * ( iy + 1 ); + var c = ( ix + 1 ) + gridX1 * ( iy + 1 ); + var d = ( ix + 1 ) + gridX1 * iy; - this.parameters = { - innerRadius: innerRadius, - outerRadius: outerRadius, - thetaSegments: thetaSegments, - phiSegments: phiSegments, - thetaStart: thetaStart, - thetaLength: thetaLength - }; + indices[ offset ] = a; + indices[ offset + 1 ] = b; + indices[ offset + 2 ] = d; - innerRadius = innerRadius || 0; - outerRadius = outerRadius || 50; + indices[ offset + 3 ] = b; + indices[ offset + 4 ] = c; + indices[ offset + 5 ] = d; - thetaStart = thetaStart !== undefined ? thetaStart : 0; - thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2; + offset += 6; - thetaSegments = thetaSegments !== undefined ? Math.max(3, thetaSegments) : 8; - phiSegments = phiSegments !== undefined ? Math.max(1, phiSegments) : 8; + } - var i, o, uvs = [], radius = innerRadius, radiusStep = ( ( outerRadius - innerRadius ) / phiSegments ); + } - for (i = 0; i < phiSegments + 1; i++) { // concentric circles inside ring + this.setIndex( new THREE.BufferAttribute( indices, 1 ) ); + this.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); + this.addAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) ); + this.addAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) ); - for (o = 0; o < thetaSegments + 1; 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); +THREE.PlaneBufferGeometry.prototype = Object.create( THREE.BufferGeometry.prototype ); +THREE.PlaneBufferGeometry.prototype.constructor = THREE.PlaneBufferGeometry; - this.vertices.push(vertex); - uvs.push(new THREE.Vector2(( vertex.x / outerRadius + 1 ) / 2, ( vertex.y / outerRadius + 1 ) / 2)); - } +THREE.PlaneBufferGeometry.prototype.clone = function () { - radius += radiusStep; + var geometry = new THREE.PlaneBufferGeometry( + this.parameters.width, + this.parameters.height, + this.parameters.widthSegments, + this.parameters.heightSegments + ); - } + geometry.copy( this ); - var n = new THREE.Vector3(0, 0, 1); + return geometry; - for (i = 0; i < phiSegments; i++) { // concentric circles inside ring +}; - var thetaSegment = i * (thetaSegments + 1); +// File:src/extras/geometries/RingGeometry.js - for (o = 0; o < thetaSegments; o++) { // number of segments per circle +/** + * @author Kaleb Murphy + */ - var segment = o + thetaSegment; +THREE.RingGeometry = function ( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) { - var v1 = segment; - var v2 = segment + thetaSegments + 1; - var v3 = segment + thetaSegments + 2; + THREE.Geometry.call( this ); - 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.type = 'RingGeometry'; - v1 = segment; - v2 = segment + thetaSegments + 2; - v3 = segment + 1; + this.parameters = { + innerRadius: innerRadius, + outerRadius: outerRadius, + thetaSegments: thetaSegments, + phiSegments: phiSegments, + thetaStart: thetaStart, + thetaLength: thetaLength + }; - 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()]); + innerRadius = innerRadius || 0; + outerRadius = outerRadius || 50; - } - } + thetaStart = thetaStart !== undefined ? thetaStart : 0; + thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2; - this.computeFaceNormals(); + thetaSegments = thetaSegments !== undefined ? Math.max( 3, thetaSegments ) : 8; + phiSegments = phiSegments !== undefined ? Math.max( 1, phiSegments ) : 8; - this.boundingSphere = new THREE.Sphere(new THREE.Vector3(), radius); + var i, o, uvs = [], radius = innerRadius, radiusStep = ( ( outerRadius - innerRadius ) / phiSegments ); -}; + for ( i = 0; i < phiSegments + 1; i ++ ) { -THREE.RingGeometry.prototype = Object.create(THREE.Geometry.prototype); -THREE.RingGeometry.prototype.constructor = THREE.RingGeometry; + // concentric circles inside ring + for ( o = 0; o < thetaSegments + 1; o ++ ) { -// File:src/extras/geometries/SphereGeometry.js + // number of segments per circle -/** - * @author mrdoob / http://mrdoob.com/ - */ + var vertex = new THREE.Vector3(); + var segment = thetaStart + o / thetaSegments * thetaLength; + vertex.x = radius * Math.cos( segment ); + vertex.y = radius * Math.sin( segment ); -THREE.SphereGeometry = function (radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength) { + this.vertices.push( vertex ); + uvs.push( new THREE.Vector2( ( vertex.x / outerRadius + 1 ) / 2, ( vertex.y / outerRadius + 1 ) / 2 ) ); - THREE.log('THREE.SphereGeometry: Consider using THREE.SphereBufferGeometry for lower memory footprint.'); + } - THREE.Geometry.call(this); + radius += radiusStep; - this.type = 'SphereGeometry'; + } - this.parameters = { - radius: radius, - widthSegments: widthSegments, - heightSegments: heightSegments, - phiStart: phiStart, - phiLength: phiLength, - thetaStart: thetaStart, - thetaLength: thetaLength - }; + var n = new THREE.Vector3( 0, 0, 1 ); - radius = radius || 50; + for ( i = 0; i < phiSegments; i ++ ) { - widthSegments = Math.max(3, Math.floor(widthSegments) || 8); - heightSegments = Math.max(2, Math.floor(heightSegments) || 6); + // concentric circles inside ring - phiStart = phiStart !== undefined ? phiStart : 0; - phiLength = phiLength !== undefined ? phiLength : Math.PI * 2; + var thetaSegment = i * ( thetaSegments + 1 ); - thetaStart = thetaStart !== undefined ? thetaStart : 0; - thetaLength = thetaLength !== undefined ? thetaLength : Math.PI; + for ( o = 0; o < thetaSegments ; o ++ ) { - var x, y, vertices = [], uvs = []; + // number of segments per circle - for (y = 0; y <= heightSegments; y++) { + var segment = o + thetaSegment; - var verticesRow = []; - var uvsRow = []; + var v1 = segment; + var v2 = segment + thetaSegments + 1; + var v3 = segment + thetaSegments + 2; - for (x = 0; x <= widthSegments; x++) { + 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() ] ); - var u = x / widthSegments; - var v = y / heightSegments; + v1 = segment; + v2 = segment + thetaSegments + 2; + v3 = segment + 1; - 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.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.vertices.push(vertex); + } - verticesRow.push(this.vertices.length - 1); - uvsRow.push(new THREE.Vector2(u, 1 - v)); + } - } + this.computeFaceNormals(); - vertices.push(verticesRow); - uvs.push(uvsRow); + this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius ); - } +}; - for (y = 0; y < heightSegments; y++) { +THREE.RingGeometry.prototype = Object.create( THREE.Geometry.prototype ); +THREE.RingGeometry.prototype.constructor = THREE.RingGeometry; - for (x = 0; x < widthSegments; x++) { +THREE.RingGeometry.prototype.clone = function () { - 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 geometry = new THREE.RingGeometry( + this.parameters.innerRadius, + this.parameters.outerRadius, + this.parameters.thetaSegments, + this.parameters.phiSegments, + this.parameters.thetaStart, + this.parameters.thetaLength + ); - 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(); + return geometry; - 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) === radius) { +// File:src/extras/geometries/SphereGeometry.js - 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]); +/** + * @author mrdoob / http://mrdoob.com/ + */ - } else if (Math.abs(this.vertices[v3].y) === radius) { +THREE.SphereGeometry = function ( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) { - 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]); + THREE.Geometry.call( this ); - } else { + this.type = 'SphereGeometry'; - this.faces.push(new THREE.Face3(v1, v2, v4, [n1, n2, n4])); - this.faceVertexUvs[0].push([uv1, uv2, uv4]); + this.parameters = { + radius: radius, + widthSegments: widthSegments, + heightSegments: heightSegments, + phiStart: phiStart, + phiLength: phiLength, + thetaStart: thetaStart, + thetaLength: thetaLength + }; - this.faces.push(new THREE.Face3(v2, v3, v4, [n2.clone(), n3, n4.clone()])); - this.faceVertexUvs[0].push([uv2.clone(), uv3, uv4.clone()]); + this.fromBufferGeometry( new THREE.SphereBufferGeometry( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) ); - } +}; - } +THREE.SphereGeometry.prototype = Object.create( THREE.Geometry.prototype ); +THREE.SphereGeometry.prototype.constructor = THREE.SphereGeometry; - } +THREE.SphereGeometry.prototype.clone = function () { - this.computeFaceNormals(); + var geometry = new THREE.SphereGeometry( + this.parameters.radius, + this.parameters.widthSegments, + this.parameters.heightSegments, + this.parameters.phiStart, + this.parameters.phiLength, + this.parameters.thetaStart, + this.parameters.thetaLength + ); - this.boundingSphere = new THREE.Sphere(new THREE.Vector3(), radius); + return geometry; }; -THREE.SphereGeometry.prototype = Object.create(THREE.Geometry.prototype); -THREE.SphereGeometry.prototype.constructor = THREE.SphereGeometry; - // File:src/extras/geometries/SphereBufferGeometry.js /** @@ -32108,112 +34088,121 @@ THREE.SphereGeometry.prototype.constructor = THREE.SphereGeometry; * based on THREE.SphereGeometry */ -THREE.SphereBufferGeometry = function (radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength) { +THREE.SphereBufferGeometry = function ( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) { - THREE.BufferGeometry.call(this); + THREE.BufferGeometry.call( this ); - this.type = 'SphereBufferGeometry'; + this.type = 'SphereBufferGeometry'; - this.parameters = { - radius: radius, - widthSegments: widthSegments, - heightSegments: heightSegments, - phiStart: phiStart, - phiLength: phiLength, - thetaStart: thetaStart, - thetaLength: thetaLength - }; + this.parameters = { + radius: radius, + widthSegments: widthSegments, + heightSegments: heightSegments, + phiStart: phiStart, + phiLength: phiLength, + thetaStart: thetaStart, + thetaLength: thetaLength + }; - radius = radius || 50; + radius = radius || 50; - widthSegments = Math.max(3, Math.floor(widthSegments) || 8); - heightSegments = Math.max(2, Math.floor(heightSegments) || 6); + widthSegments = Math.max( 3, Math.floor( widthSegments ) || 8 ); + heightSegments = Math.max( 2, Math.floor( heightSegments ) || 6 ); - phiStart = phiStart !== undefined ? phiStart : 0; - phiLength = phiLength !== undefined ? phiLength : Math.PI * 2; + phiStart = phiStart !== undefined ? phiStart : 0; + phiLength = phiLength !== undefined ? phiLength : Math.PI * 2; - thetaStart = thetaStart !== undefined ? thetaStart : 0; - thetaLength = thetaLength !== undefined ? thetaLength : Math.PI; + thetaStart = thetaStart !== undefined ? thetaStart : 0; + thetaLength = thetaLength !== undefined ? thetaLength : Math.PI; - var stride = ( 3 + 3 + 2 ); - var vertexBuffer = new THREE.InterleavedBuffer(new Float32Array(( ( widthSegments + 1 ) * ( heightSegments + 1 ) ) * stride), stride); + var thetaEnd = thetaStart + thetaLength; - var positions = new THREE.InterleavedBufferAttribute(vertexBuffer, 3, 0); - this.addAttribute('position', positions); - var normals = new THREE.InterleavedBufferAttribute(vertexBuffer, 3, 3); - this.addAttribute('normal', normals); - var uvs = new THREE.InterleavedBufferAttribute(vertexBuffer, 2, 6); - this.addAttribute('uv', uvs); + var vertexCount = ( ( widthSegments + 1 ) * ( heightSegments + 1 ) ); - var x, y, u, v, px, py, pz, index = 0, vertices = [], normal = new THREE.Vector3(); + var positions = new THREE.BufferAttribute( new Float32Array( vertexCount * 3 ), 3 ); + var normals = new THREE.BufferAttribute( new Float32Array( vertexCount * 3 ), 3 ); + var uvs = new THREE.BufferAttribute( new Float32Array( vertexCount * 2 ), 2 ); - for (y = 0; y <= heightSegments; y++) { + var index = 0, vertices = [], normal = new THREE.Vector3(); - var verticesRow = []; + for ( var y = 0; y <= heightSegments; y ++ ) { - v = y / heightSegments; + var verticesRow = []; - for (x = 0; x <= widthSegments; x++) { + var v = y / heightSegments; - u = x / widthSegments; + for ( var x = 0; x <= widthSegments; x ++ ) { - px = -radius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); - py = radius * Math.cos(thetaStart + v * thetaLength); - pz = radius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); + var u = x / widthSegments; - normal.set(px, py, pz).normalize(); + var px = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); + var py = radius * Math.cos( thetaStart + v * thetaLength ); + var pz = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); - positions.setXYZ(index, px, py, pz); - normals.setXYZ(index, normal.x, normal.y, normal.z); - uvs.setXY(index, u, 1 - v); + normal.set( px, py, pz ).normalize(); - verticesRow.push(index); + positions.setXYZ( index, px, py, pz ); + normals.setXYZ( index, normal.x, normal.y, normal.z ); + uvs.setXY( index, u, 1 - v ); - index++; + verticesRow.push( index ); - } + index ++; - vertices.push(verticesRow); + } - } + vertices.push( verticesRow ); - var indices = []; - var ul; - for (y = 0, ul = heightSegments - 1; y < ul; y++) { + } - for (x = 0; x < widthSegments; x++) { + var indices = []; - var v1 = vertices[y][x + 1]; - var v2 = vertices[y][x]; - var v3 = vertices[y + 1][x]; - var v4 = vertices[y + 1][x + 1]; + for ( var y = 0; y < heightSegments; y ++ ) { - if (y !== 0) indices.push(v1, v2, v4); - indices.push(v2, v3, v4); + for ( var x = 0; x < 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 ]; - y = heightSegments; + if ( y !== 0 || thetaStart > 0 ) indices.push( v1, v2, v4 ); + if ( y !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( v2, v3, v4 ); - for (x = 0; x < widthSegments; x++) { + } - var v2 = vertices[y][x]; - var v3 = vertices[y - 1][x]; - var v4 = vertices[y - 1][x + 1]; + } - indices.push(v2, v4, v3); + this.setIndex( new THREE.BufferAttribute( new Uint16Array( indices ), 1 ) ); + this.addAttribute( 'position', positions ); + this.addAttribute( 'normal', normals ); + this.addAttribute( 'uv', uvs ); - } + this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius ); + +}; + +THREE.SphereBufferGeometry.prototype = Object.create( THREE.BufferGeometry.prototype ); +THREE.SphereBufferGeometry.prototype.constructor = THREE.SphereBufferGeometry; + +THREE.SphereBufferGeometry.prototype.clone = function () { - this.addAttribute('index', new THREE.BufferAttribute(new Uint16Array(indices), 1)); + var geometry = new THREE.SphereBufferGeometry( + this.parameters.radius, + this.parameters.widthSegments, + this.parameters.heightSegments, + this.parameters.phiStart, + this.parameters.phiLength, + this.parameters.thetaStart, + this.parameters.thetaLength + ); - this.boundingSphere = new THREE.Sphere(new THREE.Vector3(), radius); + geometry.copy( this ); + + return geometry; }; -THREE.SphereBufferGeometry.prototype = Object.create(THREE.BufferGeometry.prototype); -THREE.SphereBufferGeometry.prototype.constructor = THREE.SphereBufferGeometry; // File:src/extras/geometries/TextGeometry.js /** @@ -32242,41 +34231,41 @@ THREE.SphereBufferGeometry.prototype.constructor = THREE.SphereBufferGeometry; /* Usage Examples - // TextGeometry wrapper + // TextGeometry wrapper - var text3d = new TextGeometry( text, options ); + var text3d = new TextGeometry( text, options ); - // Complete manner + // Complete manner - var textShapes = THREE.FontUtils.generateShapes( text, options ); - var text3d = new ExtrudeGeometry( textShapes, options ); + var textShapes = THREE.FontUtils.generateShapes( text, options ); + var text3d = new ExtrudeGeometry( textShapes, options ); - */ +*/ -THREE.TextGeometry = function (text, parameters) { +THREE.TextGeometry = function ( text, parameters ) { - parameters = parameters || {}; + parameters = parameters || {}; - var textShapes = THREE.FontUtils.generateShapes(text, parameters); + var textShapes = THREE.FontUtils.generateShapes( text, parameters ); - // translate parameters to ExtrudeGeometry API + // translate parameters to ExtrudeGeometry API - parameters.amount = parameters.height !== undefined ? parameters.height : 50; + parameters.amount = parameters.height !== undefined ? parameters.height : 50; - // defaults + // defaults - if (parameters.bevelThickness === undefined) parameters.bevelThickness = 10; - if (parameters.bevelSize === undefined) parameters.bevelSize = 8; - if (parameters.bevelEnabled === undefined) parameters.bevelEnabled = false; + 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.ExtrudeGeometry.call( this, textShapes, parameters ); - this.type = 'TextGeometry'; + this.type = 'TextGeometry'; }; -THREE.TextGeometry.prototype = Object.create(THREE.ExtrudeGeometry.prototype); +THREE.TextGeometry.prototype = Object.create( THREE.ExtrudeGeometry.prototype ); THREE.TextGeometry.prototype.constructor = THREE.TextGeometry; // File:src/extras/geometries/TorusGeometry.js @@ -32287,80 +34276,94 @@ THREE.TextGeometry.prototype.constructor = THREE.TextGeometry; * 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.TorusGeometry = function ( radius, tube, radialSegments, tubularSegments, arc ) { - THREE.Geometry.call(this); + THREE.Geometry.call( this ); - this.type = 'TorusGeometry'; + this.type = 'TorusGeometry'; - this.parameters = { - radius: radius, - tube: tube, - radialSegments: radialSegments, - tubularSegments: tubularSegments, - arc: arc - }; + this.parameters = { + radius: radius, + tube: tube, + radialSegments: radialSegments, + tubularSegments: tubularSegments, + arc: arc + }; - radius = radius || 100; - tube = tube || 40; - radialSegments = radialSegments || 8; - tubularSegments = tubularSegments || 6; - arc = arc || Math.PI * 2; + radius = radius || 100; + tube = tube || 40; + radialSegments = radialSegments || 8; + tubularSegments = tubularSegments || 6; + arc = arc || Math.PI * 2; - var center = new THREE.Vector3(), uvs = [], normals = []; + var center = new THREE.Vector3(), uvs = [], normals = []; - for (var j = 0; j <= radialSegments; j++) { + for ( var j = 0; j <= radialSegments; j ++ ) { - for (var i = 0; i <= tubularSegments; i++) { + for ( var i = 0; i <= tubularSegments; i ++ ) { - var u = i / tubularSegments * arc; - var v = j / radialSegments * Math.PI * 2; + var u = i / tubularSegments * arc; + var v = j / radialSegments * Math.PI * 2; - center.x = radius * Math.cos(u); - center.y = radius * Math.sin(u); + center.x = radius * Math.cos( u ); + center.y = radius * Math.sin( u ); - var vertex = new THREE.Vector3(); - vertex.x = ( radius + tube * Math.cos(v) ) * Math.cos(u); - vertex.y = ( radius + tube * Math.cos(v) ) * Math.sin(u); - vertex.z = tube * Math.sin(v); + var vertex = new THREE.Vector3(); + vertex.x = ( radius + tube * Math.cos( v ) ) * Math.cos( u ); + vertex.y = ( radius + tube * Math.cos( v ) ) * Math.sin( u ); + vertex.z = tube * Math.sin( v ); - this.vertices.push(vertex); + this.vertices.push( vertex ); - uvs.push(new THREE.Vector2(i / tubularSegments, j / radialSegments)); - normals.push(vertex.clone().sub(center).normalize()); + uvs.push( new THREE.Vector2( i / tubularSegments, j / radialSegments ) ); + normals.push( vertex.clone().sub( center ).normalize() ); - } + } - } + } - for (var j = 1; j <= radialSegments; j++) { + for ( var j = 1; j <= radialSegments; j ++ ) { - for (var i = 1; i <= tubularSegments; i++) { + for ( var i = 1; i <= tubularSegments; i ++ ) { - var a = ( tubularSegments + 1 ) * j + i - 1; - var b = ( tubularSegments + 1 ) * ( j - 1 ) + i - 1; - var c = ( tubularSegments + 1 ) * ( j - 1 ) + i; - var d = ( tubularSegments + 1 ) * j + i; + var a = ( tubularSegments + 1 ) * j + i - 1; + var b = ( tubularSegments + 1 ) * ( j - 1 ) + i - 1; + var c = ( tubularSegments + 1 ) * ( j - 1 ) + i; + var d = ( 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()]); + 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()]); + 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.computeFaceNormals(); + this.computeFaceNormals(); }; -THREE.TorusGeometry.prototype = Object.create(THREE.Geometry.prototype); +THREE.TorusGeometry.prototype = Object.create( THREE.Geometry.prototype ); THREE.TorusGeometry.prototype.constructor = THREE.TorusGeometry; +THREE.TorusGeometry.prototype.clone = function () { + + var geometry = new THREE.TorusGeometry( + this.parameters.radius, + this.parameters.tube, + this.parameters.radialSegments, + this.parameters.tubularSegments, + this.parameters.arc + ); + + return geometry; + +}; + // File:src/extras/geometries/TorusKnotGeometry.js /** @@ -32368,115 +34371,132 @@ THREE.TorusGeometry.prototype.constructor = THREE.TorusGeometry; * 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.TorusKnotGeometry = function ( radius, tube, radialSegments, tubularSegments, p, q, heightScale ) { - THREE.Geometry.call(this); + THREE.Geometry.call( this ); - this.type = 'TorusKnotGeometry'; + this.type = 'TorusKnotGeometry'; - this.parameters = { - radius: radius, - tube: tube, - radialSegments: radialSegments, - tubularSegments: tubularSegments, - p: p, - q: q, - heightScale: heightScale - }; + this.parameters = { + radius: radius, + tube: tube, + radialSegments: radialSegments, + tubularSegments: tubularSegments, + p: p, + q: q, + heightScale: heightScale + }; - radius = radius || 100; - tube = tube || 40; - radialSegments = radialSegments || 64; - tubularSegments = tubularSegments || 8; - p = p || 2; - q = q || 3; - heightScale = heightScale || 1; + radius = radius || 100; + tube = tube || 40; + radialSegments = radialSegments || 64; + tubularSegments = tubularSegments || 8; + p = p || 2; + q = q || 3; + heightScale = heightScale || 1; - var grid = new Array(radialSegments); - var tang = new THREE.Vector3(); - var n = new THREE.Vector3(); - var bitan = new THREE.Vector3(); + var grid = new Array( radialSegments ); + var tang = new THREE.Vector3(); + var n = new THREE.Vector3(); + var bitan = new THREE.Vector3(); - for (var i = 0; i < radialSegments; ++i) { + for ( var i = 0; i < radialSegments; ++ i ) { - grid[i] = new Array(tubularSegments); - var u = i / radialSegments * 2 * p * Math.PI; - var p1 = getPos(u, q, p, radius, heightScale); - var p2 = getPos(u + 0.01, q, p, radius, heightScale); - tang.subVectors(p2, p1); - n.addVectors(p2, p1); + grid[ i ] = new Array( tubularSegments ); + var u = i / radialSegments * 2 * p * Math.PI; + var p1 = getPos( u, q, p, radius, heightScale ); + var p2 = getPos( u + 0.01, q, p, radius, heightScale ); + tang.subVectors( p2, p1 ); + n.addVectors( p2, p1 ); - bitan.crossVectors(tang, n); - n.crossVectors(bitan, tang); - bitan.normalize(); - n.normalize(); + bitan.crossVectors( tang, n ); + n.crossVectors( bitan, tang ); + bitan.normalize(); + n.normalize(); - for (var j = 0; j < tubularSegments; ++j) { + for ( var j = 0; j < tubularSegments; ++ j ) { - var v = j / tubularSegments * 2 * Math.PI; - var cx = -tube * Math.cos(v); // TODO: Hack: Negating it so it faces outside. - var cy = tube * Math.sin(v); + var v = j / tubularSegments * 2 * Math.PI; + var cx = - tube * Math.cos( v ); // TODO: Hack: Negating it so it faces outside. + var cy = 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; + 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; - grid[i][j] = this.vertices.push(pos) - 1; + grid[ i ][ j ] = this.vertices.push( pos ) - 1; - } + } - } + } - for (var i = 0; i < radialSegments; ++i) { + for ( var i = 0; i < radialSegments; ++ i ) { - for (var j = 0; j < tubularSegments; ++j) { + for ( var j = 0; j < tubularSegments; ++ j ) { - var ip = ( i + 1 ) % radialSegments; - var jp = ( j + 1 ) % tubularSegments; + var ip = ( i + 1 ) % radialSegments; + var jp = ( j + 1 ) % tubularSegments; - var a = grid[i][j]; - var b = grid[ip][j]; - var c = grid[ip][jp]; - var d = grid[i][jp]; + var a = grid[ i ][ j ]; + var b = grid[ ip ][ j ]; + var c = grid[ ip ][ jp ]; + var d = grid[ i ][ jp ]; - var uva = new THREE.Vector2(i / radialSegments, j / tubularSegments); - var uvb = new THREE.Vector2(( i + 1 ) / radialSegments, j / tubularSegments); - var uvc = new THREE.Vector2(( i + 1 ) / radialSegments, ( j + 1 ) / tubularSegments); - var uvd = new THREE.Vector2(i / radialSegments, ( j + 1 ) / tubularSegments); + var uva = new THREE.Vector2( i / radialSegments, j / tubularSegments ); + var uvb = new THREE.Vector2( ( i + 1 ) / radialSegments, j / tubularSegments ); + var uvc = new THREE.Vector2( ( i + 1 ) / radialSegments, ( j + 1 ) / tubularSegments ); + var uvd = new THREE.Vector2( i / radialSegments, ( j + 1 ) / tubularSegments ); - this.faces.push(new THREE.Face3(a, b, d)); - this.faceVertexUvs[0].push([uva, uvb, uvd]); + 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.faces.push( new THREE.Face3( b, c, d ) ); + this.faceVertexUvs[ 0 ].push( [ uvb.clone(), uvc, uvd.clone() ] ); - } - } + } - this.computeFaceNormals(); - this.computeVertexNormals(); + } - function getPos(u, in_q, in_p, radius, heightScale) { + this.computeFaceNormals(); + this.computeVertexNormals(); - var cu = Math.cos(u); - var su = Math.sin(u); - var quOverP = in_q / in_p * u; - var cs = Math.cos(quOverP); + function getPos( u, in_q, in_p, radius, heightScale ) { - 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; + var cu = Math.cos( u ); + var su = Math.sin( u ); + var quOverP = in_q / in_p * u; + var cs = Math.cos( quOverP ); - return new THREE.Vector3(tx, ty, tz); + 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); +THREE.TorusKnotGeometry.prototype = Object.create( THREE.Geometry.prototype ); THREE.TorusKnotGeometry.prototype.constructor = THREE.TorusKnotGeometry; +THREE.TorusKnotGeometry.prototype.clone = function () { + + var geometry = new THREE.TorusKnotGeometry( + this.parameters.radius, + this.parameters.tube, + this.parameters.radialSegments, + this.parameters.tubularSegments, + this.parameters.p, + this.parameters.q, + this.parameters.heightScale + ); + + return geometry; + +}; + // File:src/extras/geometries/TubeGeometry.js /** @@ -32493,285 +34513,296 @@ THREE.TorusKnotGeometry.prototype.constructor = THREE.TorusKnotGeometry; * http://www.cs.indiana.edu/pub/techreports/TR425.pdf */ -THREE.TubeGeometry = function (path, segments, radius, radialSegments, closed, taper) { +THREE.TubeGeometry = function ( path, segments, radius, radialSegments, closed, taper ) { - THREE.Geometry.call(this); + THREE.Geometry.call( this ); - this.type = 'TubeGeometry'; + this.type = 'TubeGeometry'; - this.parameters = { - path: path, - segments: segments, - radius: radius, - radialSegments: radialSegments, - closed: closed - }; + this.parameters = { + path: path, + segments: segments, + radius: radius, + radialSegments: radialSegments, + closed: closed + }; - segments = segments || 64; - radius = radius || 1; - radialSegments = radialSegments || 8; - closed = closed || false; - taper = taper || THREE.TubeGeometry.NoTaper; + segments = segments || 64; + radius = radius || 1; + radialSegments = radialSegments || 8; + closed = closed || false; + taper = taper || THREE.TubeGeometry.NoTaper; - var grid = []; + var grid = []; - var scope = this, + var scope = this, - tangent, - normal, - binormal, + tangent, + normal, + binormal, - numpoints = segments + 1, + numpoints = segments + 1, - u, v, r, + u, v, r, - cx, cy, - pos, pos2 = new THREE.Vector3(), - i, j, - ip, jp, - a, b, c, d, - uva, uvb, uvc, uvd; + 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(path, segments, closed), - tangents = frames.tangents, - normals = frames.normals, - binormals = frames.binormals; + var frames = new THREE.TubeGeometry.FrenetFrames( path, segments, closed ), + tangents = frames.tangents, + normals = frames.normals, + binormals = frames.binormals; - // proxy internals - this.tangents = tangents; - this.normals = normals; - this.binormals = binormals; + // proxy internals + this.tangents = tangents; + this.normals = normals; + this.binormals = binormals; - function vert(x, y, z) { + function vert( x, y, z ) { - return scope.vertices.push(new THREE.Vector3(x, y, z)) - 1; + return scope.vertices.push( new THREE.Vector3( x, y, z ) ) - 1; - } + } - // consruct the grid + // construct the grid - for (i = 0; i < numpoints; i++) { + for ( i = 0; i < numpoints; i ++ ) { - grid[i] = []; + grid[ i ] = []; - u = i / ( numpoints - 1 ); + u = i / ( numpoints - 1 ); - pos = path.getPointAt(u); + pos = path.getPointAt( u ); - tangent = tangents[i]; - normal = normals[i]; - binormal = binormals[i]; + tangent = tangents[ i ]; + normal = normals[ i ]; + binormal = binormals[ i ]; - r = radius * taper(u); + r = radius * taper( u ); - for (j = 0; j < radialSegments; j++) { + for ( j = 0; j < radialSegments; j ++ ) { - v = j / radialSegments * 2 * Math.PI; + v = j / radialSegments * 2 * Math.PI; - cx = -r * Math.cos(v); // TODO: Hack: Negating it so it faces outside. - cy = r * Math.sin(v); + cx = - r * Math.cos( v ); // TODO: Hack: Negating it so it faces outside. + cy = r * 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; + 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; - grid[i][j] = vert(pos2.x, pos2.y, pos2.z); + grid[ i ][ j ] = vert( pos2.x, pos2.y, pos2.z ); - } - } + } + } - // construct the mesh - for (i = 0; i < segments; i++) { + // construct the mesh - for (j = 0; j < radialSegments; j++) { + for ( i = 0; i < segments; i ++ ) { - ip = ( closed ) ? (i + 1) % segments : i + 1; - jp = (j + 1) % radialSegments; + for ( j = 0; j < radialSegments; j ++ ) { - a = grid[i][j]; // *** NOT NECESSARILY PLANAR ! *** - b = grid[ip][j]; - c = grid[ip][jp]; - d = grid[i][jp]; + ip = ( closed ) ? ( i + 1 ) % segments : i + 1; + jp = ( j + 1 ) % radialSegments; - uva = new THREE.Vector2(i / segments, j / radialSegments); - uvb = new THREE.Vector2(( i + 1 ) / segments, j / radialSegments); - uvc = new THREE.Vector2(( i + 1 ) / segments, ( j + 1 ) / radialSegments); - uvd = new THREE.Vector2(i / segments, ( j + 1 ) / radialSegments); + a = grid[ i ][ j ]; // *** NOT NECESSARILY PLANAR ! *** + b = grid[ ip ][ j ]; + c = grid[ ip ][ jp ]; + d = grid[ i ][ jp ]; - this.faces.push(new THREE.Face3(a, b, d)); - this.faceVertexUvs[0].push([uva, uvb, uvd]); + uva = new THREE.Vector2( i / segments, j / radialSegments ); + uvb = new THREE.Vector2( ( i + 1 ) / segments, j / radialSegments ); + uvc = new THREE.Vector2( ( i + 1 ) / segments, ( j + 1 ) / radialSegments ); + uvd = new THREE.Vector2( i / segments, ( j + 1 ) / radialSegments ); - this.faces.push(new THREE.Face3(b, c, d)); - this.faceVertexUvs[0].push([uvb.clone(), uvc, uvd.clone()]); + 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.computeFaceNormals(); - this.computeVertexNormals(); + } + + } + + this.computeFaceNormals(); + this.computeVertexNormals(); }; -THREE.TubeGeometry.prototype = Object.create(THREE.Geometry.prototype); +THREE.TubeGeometry.prototype = Object.create( THREE.Geometry.prototype ); THREE.TubeGeometry.prototype.constructor = THREE.TubeGeometry; -THREE.TubeGeometry.NoTaper = function (u) { +THREE.TubeGeometry.NoTaper = function ( u ) { - return 1; + return 1; }; -THREE.TubeGeometry.SinusoidalTaper = function (u) { +THREE.TubeGeometry.SinusoidalTaper = function ( u ) { - return Math.sin(Math.PI * u); + return Math.sin( Math.PI * u ); }; // For computing of Frenet frames, exposing the tangents, normals and binormals the spline -THREE.TubeGeometry.FrenetFrames = function (path, segments, closed) { +THREE.TubeGeometry.FrenetFrames = function ( path, segments, closed ) { - var normal = new THREE.Vector3(), + var normal = new THREE.Vector3(), - tangents = [], - normals = [], - binormals = [], + tangents = [], + normals = [], + binormals = [], - vec = new THREE.Vector3(), - mat = new THREE.Matrix4(), + vec = new THREE.Vector3(), + mat = new THREE.Matrix4(), - numpoints = segments + 1, - theta, - epsilon = 0.0001, - smallest, + numpoints = segments + 1, + theta, + epsilon = 0.0001, + smallest, - tx, ty, tz, - i, u; + tx, ty, tz, + i, u; - // expose internals - this.tangents = tangents; - this.normals = normals; - this.binormals = binormals; + // expose internals + this.tangents = tangents; + this.normals = normals; + this.binormals = binormals; - // compute the tangent vectors for each segment on the path + // compute the tangent vectors for each segment on the path - for (i = 0; i < numpoints; i++) { + for ( i = 0; i < numpoints; i ++ ) { - u = i / ( numpoints - 1 ); + u = i / ( numpoints - 1 ); - tangents[i] = path.getTangentAt(u); - tangents[i].normalize(); + tangents[ i ] = path.getTangentAt( u ); + tangents[ i ].normalize(); - } + } - initialNormal3(); + 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 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() { + function initialNormal2() { - // This uses the Frenet-Serret formula for deriving binormal - var t2 = path.getTangentAt( epsilon ); + // 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 ] = 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(); + 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 + function initialNormal3() { - 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); + // select an initial normal vector perpendicular to the first tangent vector, + // and in the direction of the smallest tangent xyz component - if (tx <= smallest) { - smallest = tx; - normal.set(1, 0, 0); - } + 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 (ty <= smallest) { - smallest = ty; - normal.set(0, 1, 0); - } + if ( tx <= smallest ) { - if (tz <= smallest) { - normal.set(0, 0, 1); - } + smallest = tx; + normal.set( 1, 0, 0 ); - vec.crossVectors(tangents[0], normal).normalize(); + } - normals[0].crossVectors(tangents[0], vec); - binormals[0].crossVectors(tangents[0], normals[0]); - } + if ( ty <= smallest ) { + smallest = ty; + normal.set( 0, 1, 0 ); - // compute the slowly-varying normal and binormal vectors for each segment on the path + } - for (i = 1; i < numpoints; i++) { + if ( tz <= smallest ) { - normals[i] = normals[i - 1].clone(); + normal.set( 0, 0, 1 ); - binormals[i] = binormals[i - 1].clone(); + } - vec.crossVectors(tangents[i - 1], tangents[i]); + vec.crossVectors( tangents[ 0 ], normal ).normalize(); - if (vec.length() > epsilon) { + normals[ 0 ].crossVectors( tangents[ 0 ], vec ); + binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ); - 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)); + // compute the slowly-varying normal and binormal vectors for each segment on the path - } + for ( i = 1; i < numpoints; i ++ ) { - binormals[i].crossVectors(tangents[i], normals[i]); + normals[ i ] = normals[ i - 1 ].clone(); - } + binormals[ i ] = binormals[ i - 1 ].clone(); + vec.crossVectors( tangents[ i - 1 ], tangents[ i ] ); - // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same + if ( vec.length() > epsilon ) { - if (closed) { + vec.normalize(); - theta = Math.acos(THREE.Math.clamp(normals[0].dot(normals[numpoints - 1]), -1, 1)); - theta /= ( numpoints - 1 ); + theta = Math.acos( THREE.Math.clamp( tangents[ i - 1 ].dot( tangents[ i ] ), - 1, 1 ) ); // clamp for floating pt errors - if (tangents[0].dot(vec.crossVectors(normals[0], normals[numpoints - 1])) > 0) { + normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) ); - theta = -theta; + } - } + binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); - 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]); - } + // 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 ] ); + + } + + } - } }; // File:src/extras/geometries/PolyhedronGeometry.js @@ -32780,397 +34811,474 @@ THREE.TubeGeometry.FrenetFrames = function (path, segments, closed) { * @author clockworkgeek / https://github.com/clockworkgeek * @author timothypratley / https://github.com/timothypratley * @author WestLangley / http://github.com/WestLangley - */ +*/ -THREE.PolyhedronGeometry = function (vertices, indices, radius, detail) { +THREE.PolyhedronGeometry = function ( vertices, indices, radius, detail ) { - THREE.Geometry.call(this); + THREE.Geometry.call( this ); - this.type = 'PolyhedronGeometry'; + this.type = 'PolyhedronGeometry'; - this.parameters = { - vertices: vertices, - indices: indices, - radius: radius, - detail: detail - }; + this.parameters = { + vertices: vertices, + indices: indices, + radius: radius, + detail: detail + }; - radius = radius || 1; - detail = detail || 0; + radius = radius || 1; + detail = detail || 0; - var that = this; + var that = this; - for (var i = 0, l = vertices.length; i < l; i += 3) { + for ( var i = 0, l = vertices.length; i < l; i += 3 ) { - prepare(new THREE.Vector3(vertices[i], vertices[i + 1], vertices[i + 2])); + prepare( new THREE.Vector3( vertices[ i ], vertices[ i + 1 ], vertices[ i + 2 ] ) ); - } + } - var p = this.vertices; + var p = this.vertices; - var faces = []; + var faces = []; - for (var i = 0, j = 0, l = indices.length; i < l; i += 3, j++) { + for ( var i = 0, j = 0, l = indices.length; i < l; i += 3, j ++ ) { - var v1 = p[indices[i]]; - var v2 = p[indices[i + 1]]; - var v3 = p[indices[i + 2]]; + var v1 = p[ indices[ i ] ]; + var v2 = p[ indices[ i + 1 ] ]; + var v3 = p[ indices[ i + 2 ] ]; - faces[j] = new THREE.Face3(v1.index, v2.index, v3.index, [v1.clone(), v2.clone(), v3.clone()]); + faces[ j ] = new THREE.Face3( v1.index, v2.index, v3.index, [ v1.clone(), v2.clone(), v3.clone() ], undefined, j ); - } + } - var centroid = new THREE.Vector3(); + var centroid = new THREE.Vector3(); - for (var i = 0, l = faces.length; i < l; i++) { + for ( var i = 0, l = faces.length; i < l; i ++ ) { - subdivide(faces[i], detail); + subdivide( faces[ i ], detail ); - } + } - // Handle case when face straddles the seam + // Handle case when face straddles the seam - for (var i = 0, l = this.faceVertexUvs[0].length; i < l; i++) { + for ( var i = 0, l = this.faceVertexUvs[ 0 ].length; i < l; i ++ ) { - var uvs = this.faceVertexUvs[0][i]; + var uvs = this.faceVertexUvs[ 0 ][ i ]; - var x0 = uvs[0].x; - var x1 = uvs[1].x; - var x2 = uvs[2].x; + 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)); + 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 ( max > 0.9 && min < 0.1 ) { - if (x0 < 0.2) uvs[0].x += 1; - if (x1 < 0.2) uvs[1].x += 1; - if (x2 < 0.2) uvs[2].x += 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++) { + // Apply radius - this.vertices[i].multiplyScalar(radius); + for ( var i = 0, l = this.vertices.length; i < l; i ++ ) { - } + this.vertices[ i ].multiplyScalar( radius ); + } - // Merge vertices - this.mergeVertices(); + // Merge vertices - this.computeFaceNormals(); + this.mergeVertices(); - this.boundingSphere = new THREE.Sphere(new THREE.Vector3(), radius); + this.computeFaceNormals(); + this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius ); - // Project vector onto sphere's surface - function prepare(vector) { + // Project vector onto sphere's surface - var vertex = vector.normalize().clone(); - vertex.index = that.vertices.push(vertex) - 1; + function prepare( vector ) { - // Texture coords are equivalent to map coords, calculate angle and convert to fraction of a circle. + var vertex = vector.normalize().clone(); + vertex.index = that.vertices.push( vertex ) - 1; - 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); + // Texture coords are equivalent to map coords, calculate angle and convert to fraction of a circle. - return vertex; + 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) { + // Approximate a curved face with recursively sub-divided triangles. - var face = new THREE.Face3(v1.index, v2.index, v3.index, [v1.clone(), v2.clone(), v3.clone()]); - that.faces.push(face); + function make( v1, v2, v3, materialIndex ) { - centroid.copy(v1).add(v2).add(v3).divideScalar(3); + var face = new THREE.Face3( v1.index, v2.index, v3.index, [ v1.clone(), v2.clone(), v3.clone() ], undefined, materialIndex ); + that.faces.push( face ); - var azi = azimuth(centroid); + centroid.copy( v1 ).add( v2 ).add( v3 ).divideScalar( 3 ); - that.faceVertexUvs[0].push([ - correctUV(v1.uv, v1, azi), - correctUV(v2.uv, v2, azi), - correctUV(v3.uv, v3, azi) - ]); + var azi = azimuth( 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) { + // Analytically subdivide a face to the required detail level. - var cols = Math.pow(2, detail); - var a = prepare(that.vertices[face.a]); - var b = prepare(that.vertices[face.b]); - var c = prepare(that.vertices[face.c]); - var v = []; + function subdivide( face, detail ) { - // Construct all of the vertices for this subdivision. + var cols = Math.pow( 2, detail ); + var a = prepare( that.vertices[ face.a ] ); + var b = prepare( that.vertices[ face.b ] ); + var c = prepare( that.vertices[ face.c ] ); + var v = []; - for (var i = 0; i <= cols; i++) { + var materialIndex = face.materialIndex; - v[i] = []; + // Construct all of the vertices for this subdivision. - var aj = prepare(a.clone().lerp(c, i / cols)); - var bj = prepare(b.clone().lerp(c, i / cols)); - var rows = cols - i; + for ( var i = 0 ; i <= cols; i ++ ) { - for (var j = 0; j <= rows; j++) { + v[ i ] = []; - if (j == 0 && i == cols) { + var aj = prepare( a.clone().lerp( c, i / cols ) ); + var bj = prepare( b.clone().lerp( c, i / cols ) ); + var rows = cols - i; - v[i][j] = aj; + for ( var j = 0; j <= rows; j ++ ) { - } else { + if ( j === 0 && i === cols ) { - v[i][j] = prepare(aj.clone().lerp(bj, j / rows)); + 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++) { + // Construct all of the faces. - var k = Math.floor(j / 2); + for ( var i = 0; i < cols ; i ++ ) { - if (j % 2 == 0) { + for ( var j = 0; j < 2 * ( cols - i ) - 1; j ++ ) { - make( - v[i][k + 1], - v[i + 1][k], - v[i][k] - ); + var k = Math.floor( j / 2 ); - } else { + if ( j % 2 === 0 ) { - make( - v[i][k + 1], - v[i + 1][k + 1], - v[i + 1][k] - ); + make( + v[ i ][ k + 1 ], + v[ i + 1 ][ k ], + v[ i ][ k ], + materialIndex + ); - } + } else { - } + make( + v[ i ][ k + 1 ], + v[ i + 1 ][ k + 1 ], + v[ i + 1 ][ k ], + materialIndex + ); - } + } - } + } + } - // Angle around the Y axis, counter-clockwise when looking from above. + } - function azimuth(vector) { - return Math.atan2(vector.z, -vector.x); + // 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 ))); + // 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(); + // 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); +THREE.PolyhedronGeometry.prototype = Object.create( THREE.Geometry.prototype ); THREE.PolyhedronGeometry.prototype.constructor = THREE.PolyhedronGeometry; +THREE.PolyhedronGeometry.prototype.clone = function () { + + var geometry = new THREE.PolyhedronGeometry( + this.parameters.vertices, + this.parameters.indices, + this.parameters.radius, + this.parameters.detail + ); + + return geometry.copy( this ); + +}; + +THREE.PolyhedronGeometry.prototype.copy = function ( source ) { + + THREE.Geometry.prototype.copy.call( this, source ); + return this; + +}; + // File:src/extras/geometries/DodecahedronGeometry.js /** * @author Abe Pazos / https://hamoid.com */ -THREE.DodecahedronGeometry = function (radius, detail) { +THREE.DodecahedronGeometry = function ( radius, detail ) { - this.parameters = { - radius: radius, - detail: detail - }; + var t = ( 1 + Math.sqrt( 5 ) ) / 2; + var r = 1 / t; - var t = ( 1 + Math.sqrt(5) ) / 2; - var r = 1 / t; + var vertices = [ - var vertices = [ + // (±1, ±1, ±1) + - 1, - 1, - 1, - 1, - 1, 1, + - 1, 1, - 1, - 1, 1, 1, + 1, - 1, - 1, 1, - 1, 1, + 1, 1, - 1, 1, 1, 1, - // (±1, ±1, ±1) - -1, -1, -1, -1, -1, 1, - -1, 1, -1, -1, 1, 1, - 1, -1, -1, 1, -1, 1, - 1, 1, -1, 1, 1, 1, + // (0, ±1/φ, ±φ) + 0, - r, - t, 0, - r, t, + 0, r, - t, 0, r, t, - // (0, ±1/φ, ±φ) - 0, -r, -t, 0, -r, t, - 0, r, -t, 0, r, t, + // (±1/φ, ±φ, 0) + - r, - t, 0, - r, t, 0, + r, - t, 0, r, t, 0, - // (±1/φ, ±φ, 0) - -r, -t, 0, -r, t, 0, - r, -t, 0, r, t, 0, + // (±φ, 0, ±1/φ) + - t, 0, - r, t, 0, - r, + - t, 0, r, t, 0, r + ]; - // (±φ, 0, ±1/φ) - -t, 0, -r, t, 0, -r, - -t, 0, r, t, 0, r - ]; + var indices = [ + 3, 11, 7, 3, 7, 15, 3, 15, 13, + 7, 19, 17, 7, 17, 6, 7, 6, 15, + 17, 4, 8, 17, 8, 10, 17, 10, 6, + 8, 0, 16, 8, 16, 2, 8, 2, 10, + 0, 12, 1, 0, 1, 18, 0, 18, 16, + 6, 10, 2, 6, 2, 13, 6, 13, 15, + 2, 16, 18, 2, 18, 3, 2, 3, 13, + 18, 1, 9, 18, 9, 11, 18, 11, 3, + 4, 14, 12, 4, 12, 0, 4, 0, 8, + 11, 9, 5, 11, 5, 19, 11, 19, 7, + 19, 5, 14, 19, 14, 4, 19, 4, 17, + 1, 12, 14, 1, 14, 5, 1, 5, 9 + ]; - var indices = [ - 3, 11, 7, 3, 7, 15, 3, 15, 13, - 7, 19, 17, 7, 17, 6, 7, 6, 15, - 17, 4, 8, 17, 8, 10, 17, 10, 6, - 8, 0, 16, 8, 16, 2, 8, 2, 10, - 0, 12, 1, 0, 1, 18, 0, 18, 16, - 6, 10, 2, 6, 2, 13, 6, 13, 15, - 2, 16, 18, 2, 18, 3, 2, 3, 13, - 18, 1, 9, 18, 9, 11, 18, 11, 3, - 4, 14, 12, 4, 12, 0, 4, 0, 8, - 11, 9, 5, 11, 5, 19, 11, 19, 7, - 19, 5, 14, 19, 14, 4, 19, 4, 17, - 1, 12, 14, 1, 14, 5, 1, 5, 9 - ]; + THREE.PolyhedronGeometry.call( this, vertices, indices, radius, detail ); - THREE.PolyhedronGeometry.call(this, vertices, indices, radius, detail); + this.type = 'DodecahedronGeometry'; + + this.parameters = { + radius: radius, + detail: detail + }; }; -THREE.DodecahedronGeometry.prototype = Object.create(THREE.Geometry.prototype); +THREE.DodecahedronGeometry.prototype = Object.create( THREE.PolyhedronGeometry.prototype ); THREE.DodecahedronGeometry.prototype.constructor = THREE.DodecahedronGeometry; +THREE.DodecahedronGeometry.prototype.clone = function () { + + var geometry = new THREE.DodecahedronGeometry( + this.parameters.radius, + this.parameters.detail + ); + + geometry.copy( this ); + + return geometry; + +}; + // File:src/extras/geometries/IcosahedronGeometry.js /** * @author timothypratley / https://github.com/timothypratley */ -THREE.IcosahedronGeometry = function (radius, detail) { +THREE.IcosahedronGeometry = function ( radius, detail ) { + + var t = ( 1 + Math.sqrt( 5 ) ) / 2; - 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 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 indices = [ + 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 + ]; - var indices = [ - 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, indices, radius, detail ); - THREE.PolyhedronGeometry.call(this, vertices, indices, radius, detail); + this.type = 'IcosahedronGeometry'; - this.type = 'IcosahedronGeometry'; + this.parameters = { + radius: radius, + detail: detail + }; - this.parameters = { - radius: radius, - detail: detail - }; }; -THREE.IcosahedronGeometry.prototype = Object.create(THREE.Geometry.prototype); +THREE.IcosahedronGeometry.prototype = Object.create( THREE.PolyhedronGeometry.prototype ); THREE.IcosahedronGeometry.prototype.constructor = THREE.IcosahedronGeometry; +THREE.IcosahedronGeometry.prototype.clone = function () { + + var geometry = new THREE.IcosahedronGeometry( + this.parameters.radius, + this.parameters.detail + ); + + geometry.copy( this ); + + return geometry; + +}; + // File:src/extras/geometries/OctahedronGeometry.js /** * @author timothypratley / https://github.com/timothypratley */ -THREE.OctahedronGeometry = function (radius, detail) { +THREE.OctahedronGeometry = function ( radius, detail ) { - this.parameters = { - radius: radius, - detail: detail - }; + var vertices = [ + 1, 0, 0, - 1, 0, 0, 0, 1, 0, 0, - 1, 0, 0, 0, 1, 0, 0, - 1 + ]; - var vertices = [ - 1, 0, 0, -1, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 1, 0, 0, -1 - ]; + var indices = [ + 0, 2, 4, 0, 4, 3, 0, 3, 5, 0, 5, 2, 1, 2, 5, 1, 5, 3, 1, 3, 4, 1, 4, 2 + ]; - var indices = [ - 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, indices, radius, detail ); - THREE.PolyhedronGeometry.call(this, vertices, indices, radius, detail); + this.type = 'OctahedronGeometry'; - this.type = 'OctahedronGeometry'; + this.parameters = { + radius: radius, + detail: detail + }; - this.parameters = { - radius: radius, - detail: detail - }; }; -THREE.OctahedronGeometry.prototype = Object.create(THREE.Geometry.prototype); +THREE.OctahedronGeometry.prototype = Object.create( THREE.PolyhedronGeometry.prototype ); THREE.OctahedronGeometry.prototype.constructor = THREE.OctahedronGeometry; +THREE.OctahedronGeometry.prototype.clone = function () { + + var geometry = new THREE.OctahedronGeometry( + this.parameters.radius, + this.parameters.detail + ); + + geometry.copy( this ); + + return geometry; + +}; + // File:src/extras/geometries/TetrahedronGeometry.js /** * @author timothypratley / https://github.com/timothypratley */ -THREE.TetrahedronGeometry = function (radius, detail) { +THREE.TetrahedronGeometry = function ( radius, detail ) { - var vertices = [ - 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1 - ]; + var vertices = [ + 1, 1, 1, - 1, - 1, 1, - 1, 1, - 1, 1, - 1, - 1 + ]; - var indices = [ - 2, 1, 0, 0, 3, 2, 1, 3, 0, 2, 3, 1 - ]; + var indices = [ + 2, 1, 0, 0, 3, 2, 1, 3, 0, 2, 3, 1 + ]; - THREE.PolyhedronGeometry.call(this, vertices, indices, radius, detail); + THREE.PolyhedronGeometry.call( this, vertices, indices, radius, detail ); - this.type = 'TetrahedronGeometry'; + this.type = 'TetrahedronGeometry'; - this.parameters = { - radius: radius, - detail: detail - }; + this.parameters = { + radius: radius, + detail: detail + }; }; -THREE.TetrahedronGeometry.prototype = Object.create(THREE.Geometry.prototype); +THREE.TetrahedronGeometry.prototype = Object.create( THREE.PolyhedronGeometry.prototype ); THREE.TetrahedronGeometry.prototype.constructor = THREE.TetrahedronGeometry; +THREE.TetrahedronGeometry.prototype.clone = function () { + + var geometry = new THREE.TetrahedronGeometry( + this.parameters.radius, + this.parameters.detail + ); + + geometry.copy( this ); + + return geometry; + +}; + // File:src/extras/geometries/ParametricGeometry.js /** @@ -33182,80 +35290,81 @@ THREE.TetrahedronGeometry.prototype.constructor = THREE.TetrahedronGeometry; * */ -THREE.ParametricGeometry = function (func, slices, stacks) { +THREE.ParametricGeometry = function ( func, slices, stacks ) { - THREE.Geometry.call(this); + THREE.Geometry.call( this ); - this.type = 'ParametricGeometry'; + this.type = 'ParametricGeometry'; - this.parameters = { - func: func, - slices: slices, - stacks: stacks - }; + this.parameters = { + func: func, + slices: slices, + stacks: stacks + }; - var verts = this.vertices; - var faces = this.faces; - var uvs = this.faceVertexUvs[0]; + var verts = this.vertices; + var faces = this.faces; + var uvs = this.faceVertexUvs[ 0 ]; - var i, j, p; - var u, v; + var i, j, p; + var u, v; - var sliceCount = slices + 1; + var sliceCount = slices + 1; - for (i = 0; i <= stacks; i++) { + for ( i = 0; i <= stacks; i ++ ) { - v = i / stacks; + v = i / stacks; - for (j = 0; j <= slices; j++) { + for ( j = 0; j <= slices; j ++ ) { - u = j / slices; + u = j / slices; - p = func(u, v); - verts.push(p); + p = func( u, v ); + verts.push( p ); - } - } + } - var a, b, c, d; - var uva, uvb, uvc, uvd; + } - for (i = 0; i < stacks; i++) { + var a, b, c, d; + var uva, uvb, uvc, uvd; - for (j = 0; j < slices; j++) { + for ( i = 0; i < stacks; i ++ ) { - a = i * sliceCount + j; - b = i * sliceCount + j + 1; - c = (i + 1) * sliceCount + j + 1; - d = (i + 1) * sliceCount + j; + for ( j = 0; j < slices; 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); + a = i * sliceCount + j; + b = i * sliceCount + j + 1; + c = ( i + 1 ) * sliceCount + j + 1; + d = ( i + 1 ) * sliceCount + j; - faces.push(new THREE.Face3(a, b, d)); - uvs.push([uva, uvb, uvd]); + 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(b, c, d)); - uvs.push([uvb.clone(), uvc, uvd.clone()]); + 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() ] ); - } + } - // THREE.log(this); + } - // magic bullet - // var diff = this.mergeVertices(); - // THREE.log('removed ', diff, ' vertices by merging'); + // console.log(this); - this.computeFaceNormals(); - this.computeVertexNormals(); + // magic bullet + // var diff = this.mergeVertices(); + // console.log('removed ', diff, ' vertices by merging'); + + this.computeFaceNormals(); + this.computeVertexNormals(); }; -THREE.ParametricGeometry.prototype = Object.create(THREE.Geometry.prototype); +THREE.ParametricGeometry.prototype = Object.create( THREE.Geometry.prototype ); THREE.ParametricGeometry.prototype.constructor = THREE.ParametricGeometry; // File:src/extras/geometries/WireframeGeometry.js @@ -33264,175 +35373,182 @@ THREE.ParametricGeometry.prototype.constructor = THREE.ParametricGeometry; * @author mrdoob / http://mrdoob.com/ */ -THREE.WireframeGeometry = function (geometry) { +THREE.WireframeGeometry = function ( geometry ) { - THREE.BufferGeometry.call(this); + THREE.BufferGeometry.call( this ); - var edge = [0, 0], hash = {}; - var sortFunction = function (a, b) { - return a - b - }; + var edge = [ 0, 0 ], hash = {}; + var sortFunction = function ( a, b ) { - var keys = ['a', 'b', 'c']; + return a - b; - if (geometry instanceof THREE.Geometry) { + }; - var vertices = geometry.vertices; - var faces = geometry.faces; - var numEdges = 0; + var keys = [ 'a', 'b', 'c' ]; - // allocate maximal size - var edges = new Uint32Array(6 * faces.length); + if ( geometry instanceof THREE.Geometry ) { - for (var i = 0, l = faces.length; i < l; i++) { + var vertices = geometry.vertices; + var faces = geometry.faces; + var numEdges = 0; - var face = faces[i]; + // allocate maximal size + var edges = new Uint32Array( 6 * faces.length ); - for (var j = 0; j < 3; j++) { + for ( var i = 0, l = faces.length; i < l; i ++ ) { - edge[0] = face[keys[j]]; - edge[1] = face[keys[( j + 1 ) % 3]]; - edge.sort(sortFunction); + var face = faces[ i ]; - var key = edge.toString(); + for ( var j = 0; j < 3; j ++ ) { - if (hash[key] === undefined) { + edge[ 0 ] = face[ keys[ j ] ]; + edge[ 1 ] = face[ keys[ ( j + 1 ) % 3 ] ]; + edge.sort( sortFunction ); - edges[2 * numEdges] = edge[0]; - edges[2 * numEdges + 1] = edge[1]; - hash[key] = true; - numEdges++; + var key = edge.toString(); - } + if ( hash[ key ] === undefined ) { - } + edges[ 2 * numEdges ] = edge[ 0 ]; + edges[ 2 * numEdges + 1 ] = edge[ 1 ]; + hash[ key ] = true; + numEdges ++; - } + } - var coords = new Float32Array(numEdges * 2 * 3); + } - for (var i = 0, l = numEdges; i < l; i++) { + } - for (var j = 0; j < 2; j++) { + var coords = new Float32Array( numEdges * 2 * 3 ); - var vertex = vertices[edges [2 * i + j]]; + for ( var i = 0, l = numEdges; i < l; i ++ ) { - var index = 6 * i + 3 * j; - coords[index + 0] = vertex.x; - coords[index + 1] = vertex.y; - coords[index + 2] = vertex.z; + 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; - this.addAttribute('position', new THREE.BufferAttribute(coords, 3)); + } - } else if (geometry instanceof THREE.BufferGeometry) { + } - if (geometry.attributes.index !== undefined) { // Indexed BufferGeometry + this.addAttribute( 'position', new THREE.BufferAttribute( coords, 3 ) ); - var vertices = geometry.attributes.position; - var indices = geometry.attributes.index.array; - var drawcalls = geometry.drawcalls; - var numEdges = 0; + } else if ( geometry instanceof THREE.BufferGeometry ) { - if (drawcalls.length === 0) { + if ( geometry.index !== null ) { - drawcalls = [{count: indices.length, index: 0, start: 0}]; + // Indexed BufferGeometry - } + var indices = geometry.index.array; + var vertices = geometry.attributes.position; + var drawcalls = geometry.drawcalls; + var numEdges = 0; - // allocate maximal size - var edges = new Uint32Array(2 * indices.length); + if ( drawcalls.length === 0 ) { - for (var o = 0, ol = drawcalls.length; o < ol; ++o) { + geometry.addGroup( 0, indices.length ); - var start = drawcalls[o].start; - var count = drawcalls[o].count; - var index = drawcalls[o].index; + } - for (var i = start, il = start + count; i < il; i += 3) { + // allocate maximal size + var edges = new Uint32Array( 2 * indices.length ); - for (var j = 0; j < 3; j++) { + for ( var o = 0, ol = drawcalls.length; o < ol; ++ o ) { - edge[0] = index + indices[i + j]; - edge[1] = index + indices[i + ( j + 1 ) % 3]; - edge.sort(sortFunction); + var drawcall = drawcalls[ o ]; - var key = edge.toString(); + var start = drawcall.start; + var count = drawcall.count; - if (hash[key] === undefined) { + for ( var i = start, il = start + count; i < il; i += 3 ) { - edges[2 * numEdges] = edge[0]; - edges[2 * numEdges + 1] = edge[1]; - hash[key] = true; - numEdges++; + for ( var j = 0; j < 3; j ++ ) { - } + edge[ 0 ] = indices[ i + j ]; + edge[ 1 ] = 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 ++; - var coords = new Float32Array(numEdges * 2 * 3); + } - for (var i = 0, l = numEdges; i < l; i++) { + } - for (var j = 0; j < 2; j++) { + } - var index = 6 * i + 3 * j; - var index2 = edges[2 * i + j]; + } - coords[index + 0] = vertices.getX(index2); - coords[index + 1] = vertices.getY(index2); - coords[index + 2] = vertices.getZ(index2); + var coords = new Float32Array( numEdges * 2 * 3 ); - } + for ( var i = 0, l = numEdges; i < l; i ++ ) { - } + for ( var j = 0; j < 2; j ++ ) { - this.addAttribute('position', new THREE.BufferAttribute(coords, 3)); + var index = 6 * i + 3 * j; + var index2 = edges[ 2 * i + j ]; - } else { // non-indexed BufferGeometry + coords[ index + 0 ] = vertices.getX( index2 ); + coords[ index + 1 ] = vertices.getY( index2 ); + coords[ index + 2 ] = vertices.getZ( index2 ); - var vertices = geometry.attributes.position.array; - var numEdges = vertices.length / 3; - var numTris = numEdges / 3; + } - var coords = new Float32Array(numEdges * 2 * 3); + } - for (var i = 0, l = numTris; i < l; i++) { + this.addAttribute( 'position', new THREE.BufferAttribute( coords, 3 ) ); - for (var j = 0; j < 3; j++) { + } else { - var index = 18 * i + 6 * j; + // non-indexed BufferGeometry - var index1 = 9 * i + 3 * j; - coords[index + 0] = vertices[index1]; - coords[index + 1] = vertices[index1 + 1]; - coords[index + 2] = vertices[index1 + 2]; + var vertices = geometry.attributes.position.array; + var numEdges = vertices.length / 3; + var numTris = numEdges / 3; - 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]; + var coords = new Float32Array( numEdges * 2 * 3 ); - } + for ( var i = 0, l = numTris; i < l; i ++ ) { - } + for ( var j = 0; j < 3; j ++ ) { - this.addAttribute('position', new THREE.BufferAttribute(coords, 3)); + 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 ]; + + } + + } + + this.addAttribute( 'position', new THREE.BufferAttribute( coords, 3 ) ); + + } + + } }; -THREE.WireframeGeometry.prototype = Object.create(THREE.BufferGeometry.prototype); +THREE.WireframeGeometry.prototype = Object.create( THREE.BufferGeometry.prototype ); THREE.WireframeGeometry.prototype.constructor = THREE.WireframeGeometry; // File:src/extras/helpers/AxisHelper.js @@ -33442,33 +35558,33 @@ THREE.WireframeGeometry.prototype.constructor = THREE.WireframeGeometry; * @author mrdoob / http://mrdoob.com/ */ -THREE.AxisHelper = function (size) { +THREE.AxisHelper = function ( size ) { - size = size || 1; + size = size || 1; - var vertices = new Float32Array([ - 0, 0, 0, size, 0, 0, - 0, 0, 0, 0, size, 0, - 0, 0, 0, 0, 0, size - ]); + var vertices = new Float32Array( [ + 0, 0, 0, size, 0, 0, + 0, 0, 0, 0, size, 0, + 0, 0, 0, 0, 0, size + ] ); - var colors = new Float32Array([ - 1, 0, 0, 1, 0.6, 0, - 0, 1, 0, 0.6, 1, 0, - 0, 0, 1, 0, 0.6, 1 - ]); + var colors = new Float32Array( [ + 1, 0, 0, 1, 0.6, 0, + 0, 1, 0, 0.6, 1, 0, + 0, 0, 1, 0, 0.6, 1 + ] ); - var geometry = new THREE.BufferGeometry(); - geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3)); - geometry.addAttribute('color', new THREE.BufferAttribute(colors, 3)); + var geometry = new THREE.BufferGeometry(); + geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); + geometry.addAttribute( 'color', new THREE.BufferAttribute( colors, 3 ) ); - var material = new THREE.LineBasicMaterial({vertexColors: THREE.VertexColors}); + var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors } ); - THREE.Line.call(this, geometry, material, THREE.LinePieces); + THREE.LineSegments.call( this, geometry, material ); }; -THREE.AxisHelper.prototype = Object.create(THREE.Line.prototype); +THREE.AxisHelper.prototype = Object.create( THREE.LineSegments.prototype ); THREE.AxisHelper.prototype.constructor = THREE.AxisHelper; // File:src/extras/helpers/ArrowHelper.js @@ -33476,7 +35592,7 @@ THREE.AxisHelper.prototype.constructor = THREE.AxisHelper; /** * @author WestLangley / http://github.com/WestLangley * @author zz85 / http://github.com/zz85 - * @author bhouston / http://exocortex.com + * @author bhouston / http://clara.io * * Creates an arrow for visualizing directions * @@ -33491,92 +35607,96 @@ THREE.AxisHelper.prototype.constructor = THREE.AxisHelper; THREE.ArrowHelper = ( function () { - var lineGeometry = new THREE.Geometry(); - lineGeometry.vertices.push(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 1, 0)); - - var coneGeometry = new THREE.CylinderGeometry(0, 0.5, 1, 5, 1); - coneGeometry.applyMatrix(new THREE.Matrix4().makeTranslation(0, -0.5, 0)); + var lineGeometry = new THREE.Geometry(); + lineGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 1, 0 ) ); - return function (dir, origin, length, color, headLength, headWidth) { + var coneGeometry = new THREE.CylinderGeometry( 0, 0.5, 1, 5, 1 ); + coneGeometry.translate( 0, - 0.5, 0 ); - // dir is assumed to be normalized + return function ArrowHelper( dir, origin, length, color, headLength, headWidth ) { - THREE.Object3D.call(this); + // dir is assumed to be normalized - if (color === undefined) color = 0xffff00; - if (length === undefined) length = 1; - if (headLength === undefined) headLength = 0.2 * length; - if (headWidth === undefined) headWidth = 0.2 * headLength; + THREE.Object3D.call( this ); - this.position.copy(origin); + if ( color === undefined ) color = 0xffff00; + if ( length === undefined ) length = 1; + if ( headLength === undefined ) headLength = 0.2 * length; + if ( headWidth === undefined ) headWidth = 0.2 * headLength; - this.line = new THREE.Line(lineGeometry, new THREE.LineBasicMaterial({color: color})); - this.line.matrixAutoUpdate = false; - this.add(this.line); + this.position.copy( origin ); + + if ( headLength < length ) { + this.line = new THREE.Line( lineGeometry, new THREE.LineBasicMaterial( { color: color } ) ); + this.line.matrixAutoUpdate = false; + this.add( this.line ); + } - this.cone = new THREE.Mesh(coneGeometry, new THREE.MeshBasicMaterial({color: color})); - this.cone.matrixAutoUpdate = false; - this.add(this.cone); + this.cone = new THREE.Mesh( coneGeometry, new THREE.MeshBasicMaterial( { color: color } ) ); + this.cone.matrixAutoUpdate = false; + this.add( this.cone ); - this.setDirection(dir); - this.setLength(length, headLength, headWidth); + this.setDirection( dir ); + this.setLength( length, headLength, headWidth ); - } + } }() ); -THREE.ArrowHelper.prototype = Object.create(THREE.Object3D.prototype); +THREE.ArrowHelper.prototype = Object.create( THREE.Object3D.prototype ); THREE.ArrowHelper.prototype.constructor = THREE.ArrowHelper; THREE.ArrowHelper.prototype.setDirection = ( function () { - var axis = new THREE.Vector3(); - var radians; + var axis = new THREE.Vector3(); + var radians; - return function (dir) { + return function setDirection( dir ) { - // dir is assumed to be normalized + // dir is assumed to be normalized - if (dir.y > 0.99999) { + if ( dir.y > 0.99999 ) { - this.quaternion.set(0, 0, 0, 1); + this.quaternion.set( 0, 0, 0, 1 ); - } else if (dir.y < -0.99999) { + } else if ( dir.y < - 0.99999 ) { - this.quaternion.set(1, 0, 0, 0); + this.quaternion.set( 1, 0, 0, 0 ); - } else { + } else { - axis.set(dir.z, 0, -dir.x).normalize(); + axis.set( dir.z, 0, - dir.x ).normalize(); - radians = Math.acos(dir.y); + radians = Math.acos( dir.y ); - this.quaternion.setFromAxisAngle(axis, radians); + this.quaternion.setFromAxisAngle( axis, radians ); - } + } - }; + }; }() ); -THREE.ArrowHelper.prototype.setLength = function (length, headLength, headWidth) { +THREE.ArrowHelper.prototype.setLength = function ( length, headLength, headWidth ) { - if (headLength === undefined) headLength = 0.2 * length; - if (headWidth === undefined) headWidth = 0.2 * headLength; + if ( headLength === undefined ) headLength = 0.2 * length; + if ( headWidth === undefined ) headWidth = 0.2 * headLength; - this.line.scale.set(1, length - headLength, 1); - this.line.updateMatrix(); + if ( headLength < length ){ + this.line.scale.set( 1, length - headLength, 1 ); + this.line.updateMatrix(); + } - this.cone.scale.set(headWidth, headLength, headWidth); - this.cone.position.y = length; - this.cone.updateMatrix(); + this.cone.scale.set( headWidth, headLength, headWidth ); + this.cone.position.y = length; + this.cone.updateMatrix(); }; -THREE.ArrowHelper.prototype.setColor = function (color) { +THREE.ArrowHelper.prototype.setColor = function ( color ) { - this.line.material.color.set(color); - this.cone.material.color.set(color); + if ( this.line !== undefined ) this.line.material.color.set( color ); + this.cone.material.color.set( color ); }; @@ -33586,151 +35706,76 @@ THREE.ArrowHelper.prototype.setColor = function (color) { * @author mrdoob / http://mrdoob.com/ */ -THREE.BoxHelper = function (object) { +THREE.BoxHelper = function ( object ) { - var geometry = new THREE.BufferGeometry(); - geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(72), 3)); + var indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] ); + var positions = new Float32Array( 8 * 3 ); - THREE.Line.call(this, geometry, new THREE.LineBasicMaterial({color: 0xffff00}), THREE.LinePieces); + var geometry = new THREE.BufferGeometry(); + geometry.setIndex( new THREE.BufferAttribute( indices, 1 ) ); + geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) ); - if (object !== undefined) { + THREE.LineSegments.call( this, geometry, new THREE.LineBasicMaterial( { color: 0xffff00 } ) ); - this.update(object); + if ( object !== undefined ) { - } + this.update( object ); + + } }; -THREE.BoxHelper.prototype = Object.create(THREE.Line.prototype); +THREE.BoxHelper.prototype = Object.create( THREE.LineSegments.prototype ); THREE.BoxHelper.prototype.constructor = THREE.BoxHelper; -THREE.BoxHelper.prototype.update = function (object) { +THREE.BoxHelper.prototype.update = ( function () { - var geometry = object.geometry; + var box = new THREE.Box3(); - if (geometry.boundingBox === null) { + return function ( object ) { - geometry.computeBoundingBox(); + box.setFromObject( object ); - } + if ( box.empty() ) return; - var min = geometry.boundingBox.min; - var max = geometry.boundingBox.max; - - /* - 5____4 - 1/___0/| - | 6__|_7 - 2/___3/ - - 0: max.x, max.y, max.z - 1: min.x, max.y, max.z - 2: min.x, min.y, max.z - 3: max.x, min.y, max.z - 4: max.x, max.y, min.z - 5: min.x, max.y, min.z - 6: min.x, min.y, min.z - 7: max.x, min.y, min.z - */ - - var vertices = this.geometry.attributes.position.array; - - vertices[0] = max.x; - vertices[1] = max.y; - vertices[2] = max.z; - vertices[3] = min.x; - vertices[4] = max.y; - vertices[5] = max.z; - - vertices[6] = min.x; - vertices[7] = max.y; - vertices[8] = max.z; - vertices[9] = min.x; - vertices[10] = min.y; - vertices[11] = max.z; - - vertices[12] = min.x; - vertices[13] = min.y; - vertices[14] = max.z; - vertices[15] = max.x; - vertices[16] = min.y; - vertices[17] = max.z; - - vertices[18] = max.x; - vertices[19] = min.y; - vertices[20] = max.z; - vertices[21] = max.x; - vertices[22] = max.y; - vertices[23] = max.z; - - // - - vertices[24] = max.x; - vertices[25] = max.y; - vertices[26] = min.z; - vertices[27] = min.x; - vertices[28] = max.y; - vertices[29] = min.z; - - vertices[30] = min.x; - vertices[31] = max.y; - vertices[32] = min.z; - vertices[33] = min.x; - vertices[34] = min.y; - vertices[35] = min.z; - - vertices[36] = min.x; - vertices[37] = min.y; - vertices[38] = min.z; - vertices[39] = max.x; - vertices[40] = min.y; - vertices[41] = min.z; - - vertices[42] = max.x; - vertices[43] = min.y; - vertices[44] = min.z; - vertices[45] = max.x; - vertices[46] = max.y; - vertices[47] = min.z; - - // - - vertices[48] = max.x; - vertices[49] = max.y; - vertices[50] = max.z; - vertices[51] = max.x; - vertices[52] = max.y; - vertices[53] = min.z; - - vertices[54] = min.x; - vertices[55] = max.y; - vertices[56] = max.z; - vertices[57] = min.x; - vertices[58] = max.y; - vertices[59] = min.z; - - vertices[60] = min.x; - vertices[61] = min.y; - vertices[62] = max.z; - vertices[63] = min.x; - vertices[64] = min.y; - vertices[65] = min.z; - - vertices[66] = max.x; - vertices[67] = min.y; - vertices[68] = max.z; - vertices[69] = max.x; - vertices[70] = min.y; - vertices[71] = min.z; - - this.geometry.attributes.position.needsUpdate = true; - - this.geometry.computeBoundingSphere(); - - this.matrix = object.matrixWorld; - this.matrixAutoUpdate = false; + var min = box.min; + var max = box.max; -}; + /* + 5____4 + 1/___0/| + | 6__|_7 + 2/___3/ + + 0: max.x, max.y, max.z + 1: min.x, max.y, max.z + 2: min.x, min.y, max.z + 3: max.x, min.y, max.z + 4: max.x, max.y, min.z + 5: min.x, max.y, min.z + 6: min.x, min.y, min.z + 7: max.x, min.y, min.z + */ + + var position = this.geometry.attributes.position; + var array = position.array; + + array[ 0 ] = max.x; array[ 1 ] = max.y; array[ 2 ] = max.z; + array[ 3 ] = min.x; array[ 4 ] = max.y; array[ 5 ] = max.z; + array[ 6 ] = min.x; array[ 7 ] = min.y; array[ 8 ] = max.z; + array[ 9 ] = max.x; array[ 10 ] = min.y; array[ 11 ] = max.z; + array[ 12 ] = max.x; array[ 13 ] = max.y; array[ 14 ] = min.z; + array[ 15 ] = min.x; array[ 16 ] = max.y; array[ 17 ] = min.z; + array[ 18 ] = min.x; array[ 19 ] = min.y; array[ 20 ] = min.z; + array[ 21 ] = max.x; array[ 22 ] = min.y; array[ 23 ] = min.z; + + position.needsUpdate = true; + + this.geometry.computeBoundingSphere(); + + } + +} )(); // File:src/extras/helpers/BoundingBoxHelper.js @@ -33740,28 +35785,28 @@ THREE.BoxHelper.prototype.update = function (object) { // a helper to show the world-axis-aligned bounding box for an object -THREE.BoundingBoxHelper = function (object, hex) { +THREE.BoundingBoxHelper = function ( object, hex ) { - var color = ( hex !== undefined ) ? hex : 0x888888; + var color = ( hex !== undefined ) ? hex : 0x888888; - this.object = object; + this.object = object; - this.box = new THREE.Box3(); + this.box = new THREE.Box3(); - THREE.Mesh.call(this, new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial({color: color, wireframe: true})); + 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 = Object.create( THREE.Mesh.prototype ); THREE.BoundingBoxHelper.prototype.constructor = THREE.BoundingBoxHelper; THREE.BoundingBoxHelper.prototype.update = function () { - this.box.setFromObject(this.object); + this.box.setFromObject( this.object ); - this.box.size(this.scale); + this.box.size( this.scale ); - this.box.center(this.position); + this.box.center( this.position ); }; @@ -33770,188 +35815,188 @@ THREE.BoundingBoxHelper.prototype.update = function () { /** * @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 + * - 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) { +THREE.CameraHelper = function ( camera ) { - var geometry = new THREE.Geometry(); - var material = new THREE.LineBasicMaterial({color: 0xffffff, vertexColors: THREE.FaceColors}); + var geometry = new THREE.Geometry(); + var material = new THREE.LineBasicMaterial( { color: 0xffffff, vertexColors: THREE.FaceColors } ); - var pointMap = {}; + var pointMap = {}; - // colors + // colors - var hexFrustum = 0xffaa00; - var hexCone = 0xff0000; - var hexUp = 0x00aaff; - var hexTarget = 0xffffff; - var hexCross = 0x333333; + var hexFrustum = 0xffaa00; + var hexCone = 0xff0000; + var hexUp = 0x00aaff; + var hexTarget = 0xffffff; + var hexCross = 0x333333; - // near + // near - addLine("n1", "n2", hexFrustum); - addLine("n2", "n4", hexFrustum); - addLine("n4", "n3", hexFrustum); - addLine("n3", "n1", hexFrustum); + addLine( "n1", "n2", hexFrustum ); + addLine( "n2", "n4", hexFrustum ); + addLine( "n4", "n3", hexFrustum ); + addLine( "n3", "n1", hexFrustum ); - // far + // far - addLine("f1", "f2", hexFrustum); - addLine("f2", "f4", hexFrustum); - addLine("f4", "f3", hexFrustum); - addLine("f3", "f1", hexFrustum); + addLine( "f1", "f2", hexFrustum ); + addLine( "f2", "f4", hexFrustum ); + addLine( "f4", "f3", hexFrustum ); + addLine( "f3", "f1", hexFrustum ); - // sides + // sides - addLine("n1", "f1", hexFrustum); - addLine("n2", "f2", hexFrustum); - addLine("n3", "f3", hexFrustum); - addLine("n4", "f4", hexFrustum); + addLine( "n1", "f1", hexFrustum ); + addLine( "n2", "f2", hexFrustum ); + addLine( "n3", "f3", hexFrustum ); + addLine( "n4", "f4", hexFrustum ); - // cone + // cone - addLine("p", "n1", hexCone); - addLine("p", "n2", hexCone); - addLine("p", "n3", hexCone); - addLine("p", "n4", hexCone); + addLine( "p", "n1", hexCone ); + addLine( "p", "n2", hexCone ); + addLine( "p", "n3", hexCone ); + addLine( "p", "n4", hexCone ); - // up + // up - addLine("u1", "u2", hexUp); - addLine("u2", "u3", hexUp); - addLine("u3", "u1", hexUp); + addLine( "u1", "u2", hexUp ); + addLine( "u2", "u3", hexUp ); + addLine( "u3", "u1", hexUp ); - // target + // target - addLine("c", "t", hexTarget); - addLine("p", "c", hexCross); + addLine( "c", "t", hexTarget ); + addLine( "p", "c", hexCross ); - // cross + // cross - addLine("cn1", "cn2", hexCross); - addLine("cn3", "cn4", hexCross); + addLine( "cn1", "cn2", hexCross ); + addLine( "cn3", "cn4", hexCross ); - addLine("cf1", "cf2", hexCross); - addLine("cf3", "cf4", hexCross); + addLine( "cf1", "cf2", hexCross ); + addLine( "cf3", "cf4", hexCross ); - function addLine(a, b, hex) { + function addLine( a, b, hex ) { - addPoint(a, hex); - addPoint(b, hex); + addPoint( a, hex ); + addPoint( b, hex ); - } + } - function addPoint(id, hex) { + function addPoint( id, hex ) { - geometry.vertices.push(new THREE.Vector3()); - geometry.colors.push(new THREE.Color(hex)); + geometry.vertices.push( new THREE.Vector3() ); + geometry.colors.push( new THREE.Color( hex ) ); - if (pointMap[id] === undefined) { + if ( pointMap[ id ] === undefined ) { - pointMap[id] = []; + pointMap[ id ] = []; - } + } - pointMap[id].push(geometry.vertices.length - 1); + pointMap[ id ].push( geometry.vertices.length - 1 ); - } + } - THREE.Line.call(this, geometry, material, THREE.LinePieces); + THREE.LineSegments.call( this, geometry, material ); - this.camera = camera; - this.matrix = camera.matrixWorld; - this.matrixAutoUpdate = false; + this.camera = camera; + this.matrix = camera.matrixWorld; + this.matrixAutoUpdate = false; - this.pointMap = pointMap; + this.pointMap = pointMap; - this.update(); + this.update(); }; -THREE.CameraHelper.prototype = Object.create(THREE.Line.prototype); +THREE.CameraHelper.prototype = Object.create( THREE.LineSegments.prototype ); THREE.CameraHelper.prototype.constructor = THREE.CameraHelper; THREE.CameraHelper.prototype.update = function () { - var geometry, pointMap; + var geometry, pointMap; - var vector = new THREE.Vector3(); - var camera = new THREE.Camera(); + var vector = new THREE.Vector3(); + var camera = new THREE.Camera(); - var setPoint = function (point, x, y, z) { + var setPoint = function ( point, x, y, z ) { - vector.set(x, y, z).unproject(camera); + vector.set( x, y, z ).unproject( camera ); - var points = pointMap[point]; + var points = pointMap[ point ]; - if (points !== undefined) { + if ( points !== undefined ) { - for (var i = 0, il = points.length; i < il; i++) { + for ( var i = 0, il = points.length; i < il; i ++ ) { - geometry.vertices[points[i]].copy(vector); + geometry.vertices[ points[ i ] ].copy( vector ); - } + } - } + } - }; + }; - return function () { + return function () { - geometry = this.geometry; - pointMap = this.pointMap; + geometry = this.geometry; + pointMap = this.pointMap; - var w = 1, h = 1; + var w = 1, h = 1; - // we need just camera projection matrix - // world matrix must be identity + // we need just camera projection matrix + // world matrix must be identity - camera.projectionMatrix.copy(this.camera.projectionMatrix); + camera.projectionMatrix.copy( this.camera.projectionMatrix ); - // center / target + // center / target - setPoint("c", 0, 0, -1); - setPoint("t", 0, 0, 1); + setPoint( "c", 0, 0, - 1 ); + setPoint( "t", 0, 0, 1 ); - // near + // near - setPoint("n1", -w, -h, -1); - setPoint("n2", w, -h, -1); - setPoint("n3", -w, h, -1); - setPoint("n4", w, h, -1); + setPoint( "n1", - w, - h, - 1 ); + setPoint( "n2", w, - h, - 1 ); + setPoint( "n3", - w, h, - 1 ); + setPoint( "n4", w, h, - 1 ); - // far + // far - setPoint("f1", -w, -h, 1); - setPoint("f2", w, -h, 1); - setPoint("f3", -w, h, 1); - setPoint("f4", w, h, 1); + setPoint( "f1", - w, - h, 1 ); + setPoint( "f2", w, - h, 1 ); + setPoint( "f3", - w, h, 1 ); + setPoint( "f4", w, h, 1 ); - // up + // 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); + 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 + // cross - setPoint("cf1", -w, 0, 1); - setPoint("cf2", w, 0, 1); - setPoint("cf3", 0, -h, 1); - setPoint("cf4", 0, h, 1); + 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); + setPoint( "cn1", - w, 0, - 1 ); + setPoint( "cn2", w, 0, - 1 ); + setPoint( "cn3", 0, - h, - 1 ); + setPoint( "cn4", 0, h, - 1 ); - geometry.verticesNeedUpdate = true; + geometry.verticesNeedUpdate = true; - }; + }; }(); @@ -33963,80 +36008,81 @@ THREE.CameraHelper.prototype.update = function () { * @author WestLangley / http://github.com/WestLangley */ -THREE.DirectionalLightHelper = function (light, size) { +THREE.DirectionalLightHelper = function ( light, size ) { - THREE.Object3D.call(this); + THREE.Object3D.call( this ); - this.light = light; - this.light.updateMatrixWorld(); + this.light = light; + this.light.updateMatrixWorld(); - this.matrix = light.matrixWorld; - this.matrixAutoUpdate = false; + this.matrix = light.matrixWorld; + this.matrixAutoUpdate = false; - size = size || 1; + size = size || 1; - var geometry = new THREE.Geometry(); - geometry.vertices.push( - new THREE.Vector3(-size, size, 0), - new THREE.Vector3(size, size, 0), - new THREE.Vector3(size, -size, 0), - new THREE.Vector3(-size, -size, 0), - new THREE.Vector3(-size, size, 0) - ); + var geometry = new THREE.Geometry(); + geometry.vertices.push( + new THREE.Vector3( - size, size, 0 ), + new THREE.Vector3( size, size, 0 ), + new THREE.Vector3( size, - size, 0 ), + new THREE.Vector3( - size, - size, 0 ), + new THREE.Vector3( - size, size, 0 ) + ); - var material = new THREE.LineBasicMaterial({fog: false}); - material.color.copy(this.light.color).multiplyScalar(this.light.intensity); + var material = new THREE.LineBasicMaterial( { fog: false } ); + material.color.copy( this.light.color ).multiplyScalar( this.light.intensity ); - this.lightPlane = new THREE.Line(geometry, material); - this.add(this.lightPlane); + this.lightPlane = new THREE.Line( geometry, material ); + this.add( this.lightPlane ); - geometry = new THREE.Geometry(); - geometry.vertices.push( - new THREE.Vector3(), - new THREE.Vector3() - ); + geometry = new THREE.Geometry(); + geometry.vertices.push( + new THREE.Vector3(), + new THREE.Vector3() + ); - material = new THREE.LineBasicMaterial({fog: false}); - material.color.copy(this.light.color).multiplyScalar(this.light.intensity); + 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.targetLine = new THREE.Line( geometry, material ); + this.add( this.targetLine ); - this.update(); + this.update(); }; -THREE.DirectionalLightHelper.prototype = Object.create(THREE.Object3D.prototype); +THREE.DirectionalLightHelper.prototype = Object.create( THREE.Object3D.prototype ); THREE.DirectionalLightHelper.prototype.constructor = THREE.DirectionalLightHelper; THREE.DirectionalLightHelper.prototype.dispose = function () { - this.lightPlane.geometry.dispose(); - this.lightPlane.material.dispose(); - this.targetLine.geometry.dispose(); - this.targetLine.material.dispose(); + 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(); + var v1 = new THREE.Vector3(); + var v2 = new THREE.Vector3(); + var v3 = new THREE.Vector3(); - return function () { + return function () { - v1.setFromMatrixPosition(this.light.matrixWorld); - v2.setFromMatrixPosition(this.light.target.matrixWorld); - v3.subVectors(v2, v1); + 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.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); + this.targetLine.geometry.vertices[ 1 ].copy( v3 ); + this.targetLine.geometry.verticesNeedUpdate = true; + this.targetLine.material.color.copy( this.lightPlane.material.color ); - }; + }; }(); @@ -34046,24 +36092,24 @@ THREE.DirectionalLightHelper.prototype.update = function () { * @author WestLangley / http://github.com/WestLangley * @param object THREE.Mesh whose geometry will be used * @param hex line color - * @param thresholdAngle the minimim angle (in degrees), + * @param thresholdAngle the minimum angle (in degrees), * between the face normals of adjacent faces, * that is required to render an edge. A value of 10 means * an edge is only rendered if the angle is at least 10 degrees. */ -THREE.EdgesHelper = function (object, hex, thresholdAngle) { +THREE.EdgesHelper = function ( object, hex, thresholdAngle ) { - var color = ( hex !== undefined ) ? hex : 0xffffff; + var color = ( hex !== undefined ) ? hex : 0xffffff; - THREE.Line.call(this, new THREE.EdgesGeometry(object.geometry, thresholdAngle), new THREE.LineBasicMaterial({color: color}), THREE.LinePieces); + THREE.LineSegments.call( this, new THREE.EdgesGeometry( object.geometry, thresholdAngle ), new THREE.LineBasicMaterial( { color: color } ) ); - this.matrix = object.matrixWorld; - this.matrixAutoUpdate = false; + this.matrix = object.matrixWorld; + this.matrixAutoUpdate = false; }; -THREE.EdgesHelper.prototype = Object.create(THREE.Line.prototype); +THREE.EdgesHelper.prototype = Object.create( THREE.LineSegments.prototype ); THREE.EdgesHelper.prototype.constructor = THREE.EdgesHelper; // File:src/extras/helpers/FaceNormalsHelper.js @@ -34071,78 +36117,113 @@ THREE.EdgesHelper.prototype.constructor = THREE.EdgesHelper; /** * @author mrdoob / http://mrdoob.com/ * @author WestLangley / http://github.com/WestLangley - */ +*/ -THREE.FaceNormalsHelper = function (object, size, hex, linewidth) { +THREE.FaceNormalsHelper = function ( object, size, hex, linewidth ) { - this.object = object; + // FaceNormalsHelper only supports THREE.Geometry - this.size = ( size !== undefined ) ? size : 1; + this.object = object; - var color = ( hex !== undefined ) ? hex : 0xffff00; + this.size = ( size !== undefined ) ? size : 1; - var width = ( linewidth !== undefined ) ? linewidth : 1; + var color = ( hex !== undefined ) ? hex : 0xffff00; - var geometry = new THREE.Geometry(); + var width = ( linewidth !== undefined ) ? linewidth : 1; - var faces = this.object.geometry.faces; + // - for (var i = 0, l = faces.length; i < l; i++) { + var nNormals = 0; - geometry.vertices.push(new THREE.Vector3(), new THREE.Vector3()); + var objGeometry = this.object.geometry; - } + if ( objGeometry instanceof THREE.Geometry ) { + + nNormals = objGeometry.faces.length; - THREE.Line.call(this, geometry, new THREE.LineBasicMaterial({color: color, linewidth: width}), THREE.LinePieces); + } else { - this.matrixAutoUpdate = false; + console.warn( 'THREE.FaceNormalsHelper: only THREE.Geometry is supported. Use THREE.VertexNormalsHelper, instead.' ); - this.normalMatrix = new THREE.Matrix3(); + } - this.update(); + // + + var geometry = new THREE.BufferGeometry(); + + var positions = new THREE.Float32Attribute( nNormals * 2 * 3, 3 ); + + geometry.addAttribute( 'position', positions ); + + THREE.LineSegments.call( this, geometry, new THREE.LineBasicMaterial( { color: color, linewidth: width } ) ); + + // + + this.matrixAutoUpdate = false; + this.update(); }; -THREE.FaceNormalsHelper.prototype = Object.create(THREE.Line.prototype); +THREE.FaceNormalsHelper.prototype = Object.create( THREE.LineSegments.prototype ); THREE.FaceNormalsHelper.prototype.constructor = THREE.FaceNormalsHelper; -THREE.FaceNormalsHelper.prototype.update = function () { +THREE.FaceNormalsHelper.prototype.update = ( function () { - var vertices = this.geometry.vertices; + var v1 = new THREE.Vector3(); + var v2 = new THREE.Vector3(); + var normalMatrix = new THREE.Matrix3(); - var object = this.object; - var objectVertices = object.geometry.vertices; - var objectFaces = object.geometry.faces; - var objectWorldMatrix = object.matrixWorld; + return function update() { - object.updateMatrixWorld(true); + this.object.updateMatrixWorld( true ); - this.normalMatrix.getNormalMatrix(objectWorldMatrix); + normalMatrix.getNormalMatrix( this.object.matrixWorld ); - for (var i = 0, i2 = 0, l = objectFaces.length; i < l; i++, i2 += 2) { + var matrixWorld = this.object.matrixWorld; - var face = objectFaces[i]; + var position = this.geometry.attributes.position; - vertices[i2].copy(objectVertices[face.a]) - .add(objectVertices[face.b]) - .add(objectVertices[face.c]) - .divideScalar(3) - .applyMatrix4(objectWorldMatrix); + // - vertices[i2 + 1].copy(face.normal) - .applyMatrix3(this.normalMatrix) - .normalize() - .multiplyScalar(this.size) - .add(vertices[i2]); + var objGeometry = this.object.geometry; - } + var vertices = objGeometry.vertices; - this.geometry.verticesNeedUpdate = true; + var faces = objGeometry.faces; - return this; + var idx = 0; -}; + for ( var i = 0, l = faces.length; i < l; i ++ ) { + var face = faces[ i ]; + + var normal = face.normal; + + v1.copy( vertices[ face.a ] ) + .add( vertices[ face.b ] ) + .add( vertices[ face.c ] ) + .divideScalar( 3 ) + .applyMatrix4( matrixWorld ); + + v2.copy( normal ).applyMatrix3( normalMatrix ).normalize().multiplyScalar( this.size ).add( v1 ); + + position.setXYZ( idx, v1.x, v1.y, v1.z ); + + idx = idx + 1; + + position.setXYZ( idx, v2.x, v2.y, v2.z ); + + idx = idx + 1; + + } + + position.needsUpdate = true; + + return this; + + } + +}() ); // File:src/extras/helpers/GridHelper.js @@ -34150,40 +36231,40 @@ THREE.FaceNormalsHelper.prototype.update = function () { * @author mrdoob / http://mrdoob.com/ */ -THREE.GridHelper = function (size, step) { +THREE.GridHelper = function ( size, step ) { - var geometry = new THREE.Geometry(); - var material = new THREE.LineBasicMaterial({vertexColors: THREE.VertexColors}); + 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); + this.color1 = new THREE.Color( 0x444444 ); + this.color2 = new THREE.Color( 0x888888 ); - for (var i = -size; i <= size; i += step) { + 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) - ); + 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; + var color = i === 0 ? this.color1 : this.color2; - geometry.colors.push(color, color, color, color); + geometry.colors.push( color, color, color, color ); - } + } - THREE.Line.call(this, geometry, material, THREE.LinePieces); + THREE.LineSegments.call( this, geometry, material ); }; -THREE.GridHelper.prototype = Object.create(THREE.Line.prototype); +THREE.GridHelper.prototype = Object.create( THREE.LineSegments.prototype ); THREE.GridHelper.prototype.constructor = THREE.GridHelper; -THREE.GridHelper.prototype.setColors = function (colorCenterLine, colorGrid) { +THREE.GridHelper.prototype.setColors = function( colorCenterLine, colorGrid ) { - this.color1.set(colorCenterLine); - this.color2.set(colorGrid); + this.color1.set( colorCenterLine ); + this.color2.set( colorGrid ); - this.geometry.colorsNeedUpdate = true; + this.geometry.colorsNeedUpdate = true; }; @@ -34194,57 +36275,59 @@ THREE.GridHelper.prototype.setColors = function (colorCenterLine, colorGrid) { * @author mrdoob / http://mrdoob.com/ */ -THREE.HemisphereLightHelper = function (light, sphereSize) { +THREE.HemisphereLightHelper = function ( light, sphereSize ) { - THREE.Object3D.call(this); + THREE.Object3D.call( this ); - this.light = light; - this.light.updateMatrixWorld(); + this.light = light; + this.light.updateMatrixWorld(); - this.matrix = light.matrixWorld; - this.matrixAutoUpdate = false; + this.matrix = light.matrixWorld; + this.matrixAutoUpdate = false; - this.colors = [new THREE.Color(), new THREE.Color()]; + 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)); + var geometry = new THREE.SphereGeometry( sphereSize, 4, 2 ); + geometry.rotateX( - Math.PI / 2 ); - for (var i = 0, il = 8; i < il; i++) { + for ( var i = 0, il = 8; i < il; i ++ ) { - geometry.faces[i].color = this.colors[i < 4 ? 0 : 1]; + geometry.faces[ i ].color = this.colors[ i < 4 ? 0 : 1 ]; - } + } - var material = new THREE.MeshBasicMaterial({vertexColors: THREE.FaceColors, wireframe: true}); + var material = new THREE.MeshBasicMaterial( { vertexColors: THREE.FaceColors, wireframe: true } ); - this.lightSphere = new THREE.Mesh(geometry, material); - this.add(this.lightSphere); + this.lightSphere = new THREE.Mesh( geometry, material ); + this.add( this.lightSphere ); - this.update(); + this.update(); }; -THREE.HemisphereLightHelper.prototype = Object.create(THREE.Object3D.prototype); +THREE.HemisphereLightHelper.prototype = Object.create( THREE.Object3D.prototype ); THREE.HemisphereLightHelper.prototype.constructor = THREE.HemisphereLightHelper; THREE.HemisphereLightHelper.prototype.dispose = function () { - this.lightSphere.geometry.dispose(); - this.lightSphere.material.dispose(); + + this.lightSphere.geometry.dispose(); + this.lightSphere.material.dispose(); + }; THREE.HemisphereLightHelper.prototype.update = function () { - var vector = new THREE.Vector3(); + var vector = new THREE.Vector3(); - return function () { + 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.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; + this.lightSphere.lookAt( vector.setFromMatrixPosition( this.light.matrixWorld ).negate() ); + this.lightSphere.geometry.colorsNeedUpdate = true; - } + } }(); @@ -34255,71 +36338,72 @@ THREE.HemisphereLightHelper.prototype.update = function () { * @author mrdoob / http://mrdoob.com/ */ -THREE.PointLightHelper = function (light, sphereSize) { +THREE.PointLightHelper = function ( light, sphereSize ) { - this.light = light; - this.light.updateMatrixWorld(); + 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); + 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); + THREE.Mesh.call( this, geometry, material ); - this.matrix = this.light.matrixWorld; - this.matrixAutoUpdate = false; + this.matrix = 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 } ); + /* + 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 ); + this.lightSphere = new THREE.Mesh( bulbGeometry, bulbMaterial ); + this.lightDistance = new THREE.Mesh( distanceGeometry, distanceMaterial ); - var d = light.distance; + var d = light.distance; - if ( d === 0.0 ) { + if ( d === 0.0 ) { - this.lightDistance.visible = false; + this.lightDistance.visible = false; - } else { + } else { - this.lightDistance.scale.set( d, d, d ); + this.lightDistance.scale.set( d, d, d ); - } + } - this.add( this.lightDistance ); - */ + this.add( this.lightDistance ); + */ }; -THREE.PointLightHelper.prototype = Object.create(THREE.Mesh.prototype); +THREE.PointLightHelper.prototype = Object.create( THREE.Mesh.prototype ); THREE.PointLightHelper.prototype.constructor = THREE.PointLightHelper; THREE.PointLightHelper.prototype.dispose = function () { - this.geometry.dispose(); - this.material.dispose(); + this.geometry.dispose(); + this.material.dispose(); + }; THREE.PointLightHelper.prototype.update = function () { - this.material.color.copy(this.light.color).multiplyScalar(this.light.intensity); + this.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity ); - /* - var d = this.light.distance; + /* + var d = this.light.distance; - if ( d === 0.0 ) { + if ( d === 0.0 ) { - this.lightDistance.visible = false; + this.lightDistance.visible = false; - } else { + } else { - this.lightDistance.visible = true; - this.lightDistance.scale.set( d, d, d ); + this.lightDistance.visible = true; + this.lightDistance.scale.set( d, d, d ); - } - */ + } + */ }; @@ -34332,100 +36416,97 @@ THREE.PointLightHelper.prototype.update = function () { * @author ikerr / http://verold.com */ -THREE.SkeletonHelper = function (object) { +THREE.SkeletonHelper = function ( object ) { - this.bones = this.getBoneList(object); + this.bones = this.getBoneList( object ); - var geometry = new THREE.Geometry(); + var geometry = new THREE.Geometry(); - for (var i = 0; i < this.bones.length; i++) { + for ( var i = 0; i < this.bones.length; i ++ ) { - var bone = this.bones[i]; + var bone = this.bones[ i ]; - if (bone.parent instanceof THREE.Bone) { + if ( bone.parent instanceof THREE.Bone ) { - geometry.vertices.push(new THREE.Vector3()); - geometry.vertices.push(new THREE.Vector3()); - geometry.colors.push(new THREE.Color(0, 0, 1)); - geometry.colors.push(new THREE.Color(0, 1, 0)); + geometry.vertices.push( new THREE.Vector3() ); + geometry.vertices.push( new THREE.Vector3() ); + geometry.colors.push( new THREE.Color( 0, 0, 1 ) ); + geometry.colors.push( new THREE.Color( 0, 1, 0 ) ); - } + } - } + } - var material = new THREE.LineBasicMaterial({ - vertexColors: THREE.VertexColors, - depthTest: false, - depthWrite: false, - transparent: true - }); + geometry.dynamic = true; - THREE.Line.call(this, geometry, material, THREE.LinePieces); + var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors, depthTest: false, depthWrite: false, transparent: true } ); - this.root = object; + THREE.LineSegments.call( this, geometry, material ); - this.matrix = object.matrixWorld; - this.matrixAutoUpdate = false; + this.root = object; - this.update(); + this.matrix = object.matrixWorld; + this.matrixAutoUpdate = false; + + this.update(); }; -THREE.SkeletonHelper.prototype = Object.create(THREE.Line.prototype); +THREE.SkeletonHelper.prototype = Object.create( THREE.LineSegments.prototype ); THREE.SkeletonHelper.prototype.constructor = THREE.SkeletonHelper; -THREE.SkeletonHelper.prototype.getBoneList = function (object) { +THREE.SkeletonHelper.prototype.getBoneList = function( object ) { - var boneList = []; + var boneList = []; - if (object instanceof THREE.Bone) { + if ( object instanceof THREE.Bone ) { - boneList.push(object); + boneList.push( object ); - } + } - for (var i = 0; i < object.children.length; i++) { + for ( var i = 0; i < object.children.length; i ++ ) { - boneList.push.apply(boneList, this.getBoneList(object.children[i])); + boneList.push.apply( boneList, this.getBoneList( object.children[ i ] ) ); - } + } - return boneList; + return boneList; }; THREE.SkeletonHelper.prototype.update = function () { - var geometry = this.geometry; + var geometry = this.geometry; - var matrixWorldInv = new THREE.Matrix4().getInverse(this.root.matrixWorld); + var matrixWorldInv = new THREE.Matrix4().getInverse( this.root.matrixWorld ); - var boneMatrix = new THREE.Matrix4(); + var boneMatrix = new THREE.Matrix4(); - var j = 0; + var j = 0; - for (var i = 0; i < this.bones.length; i++) { + for ( var i = 0; i < this.bones.length; i ++ ) { - var bone = this.bones[i]; + var bone = this.bones[ i ]; - if (bone.parent instanceof THREE.Bone) { + if ( bone.parent instanceof THREE.Bone ) { - boneMatrix.multiplyMatrices(matrixWorldInv, bone.matrixWorld); - geometry.vertices[j].setFromMatrixPosition(boneMatrix); + boneMatrix.multiplyMatrices( matrixWorldInv, bone.matrixWorld ); + geometry.vertices[ j ].setFromMatrixPosition( boneMatrix ); - boneMatrix.multiplyMatrices(matrixWorldInv, bone.parent.matrixWorld); - geometry.vertices[j + 1].setFromMatrixPosition(boneMatrix); + boneMatrix.multiplyMatrices( matrixWorldInv, bone.parent.matrixWorld ); + geometry.vertices[ j + 1 ].setFromMatrixPosition( boneMatrix ); - j += 2; + j += 2; - } + } - } + } - geometry.verticesNeedUpdate = true; + geometry.verticesNeedUpdate = true; - geometry.computeBoundingSphere(); + geometry.computeBoundingSphere(); }; @@ -34435,60 +36516,62 @@ THREE.SkeletonHelper.prototype.update = function () { * @author alteredq / http://alteredqualia.com/ * @author mrdoob / http://mrdoob.com/ * @author WestLangley / http://github.com/WestLangley - */ +*/ -THREE.SpotLightHelper = function (light) { +THREE.SpotLightHelper = function ( light ) { - THREE.Object3D.call(this); + THREE.Object3D.call( this ); - this.light = light; - this.light.updateMatrixWorld(); + this.light = light; + this.light.updateMatrixWorld(); - this.matrix = light.matrixWorld; - this.matrixAutoUpdate = false; + this.matrix = light.matrixWorld; + this.matrixAutoUpdate = false; - var geometry = new THREE.CylinderGeometry(0, 1, 1, 8, 1, true); + 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)); + geometry.translate( 0, - 0.5, 0 ); + geometry.rotateX( - Math.PI / 2 ); - var material = new THREE.MeshBasicMaterial({wireframe: true, fog: false}); + var material = new THREE.MeshBasicMaterial( { wireframe: true, fog: false } ); - this.cone = new THREE.Mesh(geometry, material); - this.add(this.cone); + this.cone = new THREE.Mesh( geometry, material ); + this.add( this.cone ); - this.update(); + this.update(); }; -THREE.SpotLightHelper.prototype = Object.create(THREE.Object3D.prototype); +THREE.SpotLightHelper.prototype = Object.create( THREE.Object3D.prototype ); THREE.SpotLightHelper.prototype.constructor = THREE.SpotLightHelper; THREE.SpotLightHelper.prototype.dispose = function () { - this.cone.geometry.dispose(); - this.cone.material.dispose(); + + this.cone.geometry.dispose(); + this.cone.material.dispose(); + }; THREE.SpotLightHelper.prototype.update = function () { - var vector = new THREE.Vector3(); - var vector2 = new THREE.Vector3(); + var vector = new THREE.Vector3(); + var vector2 = new THREE.Vector3(); - return function () { + return function () { - var coneLength = this.light.distance ? this.light.distance : 10000; - var coneWidth = coneLength * Math.tan(this.light.angle); + var coneLength = this.light.distance ? this.light.distance : 10000; + var coneWidth = coneLength * Math.tan( this.light.angle ); - this.cone.scale.set(coneWidth, coneWidth, coneLength); + this.cone.scale.set( coneWidth, coneWidth, coneLength ); - vector.setFromMatrixPosition(this.light.matrixWorld); - vector2.setFromMatrixPosition(this.light.target.matrixWorld); + vector.setFromMatrixPosition( this.light.matrixWorld ); + vector2.setFromMatrixPosition( this.light.target.matrixWorld ); - this.cone.lookAt(vector2.sub(vector)); + this.cone.lookAt( vector2.sub( vector ) ); - this.cone.material.color.copy(this.light.color).multiplyScalar(this.light.intensity); + this.cone.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity ); - }; + }; }(); @@ -34497,199 +36580,148 @@ THREE.SpotLightHelper.prototype.update = function () { /** * @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 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(), 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.constructor = THREE.VertexNormalsHelper; - -THREE.VertexNormalsHelper.prototype.update = ( function (object) { - - var v1 = new THREE.Vector3(); - - return function (object) { +THREE.VertexNormalsHelper = function ( object, size, hex, linewidth ) { - var keys = ['a', 'b', 'c', 'd']; + this.object = object; - this.object.updateMatrixWorld(true); + this.size = ( size !== undefined ) ? size : 1; - this.normalMatrix.getNormalMatrix(this.object.matrixWorld); + var color = ( hex !== undefined ) ? hex : 0xff0000; - var vertices = this.geometry.vertices; + var width = ( linewidth !== undefined ) ? linewidth : 1; - var verts = this.object.geometry.vertices; + // - var faces = this.object.geometry.faces; + var nNormals = 0; - var worldMatrix = this.object.matrixWorld; + var objGeometry = this.object.geometry; - var idx = 0; + if ( objGeometry instanceof THREE.Geometry ) { - for (var i = 0, l = faces.length; i < l; i++) { + nNormals = objGeometry.faces.length * 3; - var face = faces[i]; + } else if ( objGeometry instanceof THREE.BufferGeometry ) { - for (var j = 0, jl = face.vertexNormals.length; j < jl; j++) { + nNormals = objGeometry.attributes.normal.count - var vertexId = face[keys[j]]; - var vertex = verts[vertexId]; + } - var normal = face.vertexNormals[j]; + // - vertices[idx].copy(vertex).applyMatrix4(worldMatrix); + var geometry = new THREE.BufferGeometry(); - v1.copy(normal).applyMatrix3(this.normalMatrix).normalize().multiplyScalar(this.size); + var positions = new THREE.Float32Attribute( nNormals * 2 * 3, 3 ); - v1.add(vertices[idx]); - idx = idx + 1; + geometry.addAttribute( 'position', positions ); - vertices[idx].copy(v1); - idx = idx + 1; + THREE.LineSegments.call( this, geometry, new THREE.LineBasicMaterial( { color: color, linewidth: width } ) ); - } + // - } + this.matrixAutoUpdate = false; - this.geometry.verticesNeedUpdate = true; + this.update(); - return this; +}; - } +THREE.VertexNormalsHelper.prototype = Object.create( THREE.LineSegments.prototype ); +THREE.VertexNormalsHelper.prototype.constructor = THREE.VertexNormalsHelper; -}()); +THREE.VertexNormalsHelper.prototype.update = ( function () { -// File:src/extras/helpers/VertexTangentsHelper.js + var v1 = new THREE.Vector3(); + var v2 = new THREE.Vector3(); + var normalMatrix = new THREE.Matrix3(); -/** - * @author mrdoob / http://mrdoob.com/ - * @author WestLangley / http://github.com/WestLangley - */ + return function update() { -THREE.VertexTangentsHelper = function (object, size, hex, linewidth) { + var keys = [ 'a', 'b', 'c' ]; - this.object = object; + this.object.updateMatrixWorld( true ); - this.size = ( size !== undefined ) ? size : 1; + normalMatrix.getNormalMatrix( this.object.matrixWorld ); - var color = ( hex !== undefined ) ? hex : 0x0000ff; + var matrixWorld = this.object.matrixWorld; - var width = ( linewidth !== undefined ) ? linewidth : 1; + var position = this.geometry.attributes.position; - var geometry = new THREE.Geometry(); + // - var faces = object.geometry.faces; + var objGeometry = this.object.geometry; - for (var i = 0, l = faces.length; i < l; i++) { + if ( objGeometry instanceof THREE.Geometry ) { - var face = faces[i]; + var vertices = objGeometry.vertices; - for (var j = 0, jl = face.vertexTangents.length; j < jl; j++) { + var faces = objGeometry.faces; - geometry.vertices.push(new THREE.Vector3()); - geometry.vertices.push(new THREE.Vector3()); + var idx = 0; - } + for ( var i = 0, l = faces.length; i < l; i ++ ) { - } + var face = faces[ i ]; - THREE.Line.call(this, geometry, new THREE.LineBasicMaterial({color: color, linewidth: width}), THREE.LinePieces); + for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) { - this.matrixAutoUpdate = false; + var vertex = vertices[ face[ keys[ j ] ] ]; - this.update(); + var normal = face.vertexNormals[ j ]; -}; + v1.copy( vertex ).applyMatrix4( matrixWorld ); -THREE.VertexTangentsHelper.prototype = Object.create(THREE.Line.prototype); -THREE.VertexTangentsHelper.prototype.constructor = THREE.VertexTangentsHelper; + v2.copy( normal ).applyMatrix3( normalMatrix ).normalize().multiplyScalar( this.size ).add( v1 ); -THREE.VertexTangentsHelper.prototype.update = ( function (object) { + position.setXYZ( idx, v1.x, v1.y, v1.z ); - var v1 = new THREE.Vector3(); + idx = idx + 1; - return function (object) { + position.setXYZ( idx, v2.x, v2.y, v2.z ); - var keys = ['a', 'b', 'c', 'd']; + idx = idx + 1; - this.object.updateMatrixWorld(true); + } - var vertices = this.geometry.vertices; + } - var verts = this.object.geometry.vertices; + } else if ( objGeometry instanceof THREE.BufferGeometry ) { - var faces = this.object.geometry.faces; + var objPos = objGeometry.attributes.position; - var worldMatrix = this.object.matrixWorld; + var objNorm = objGeometry.attributes.normal; - var idx = 0; + var idx = 0; - for (var i = 0, l = faces.length; i < l; i++) { + // for simplicity, ignore index and drawcalls, and render every normal - var face = faces[i]; + for ( var j = 0, jl = objPos.count; j < jl; j ++ ) { - for (var j = 0, jl = face.vertexTangents.length; j < jl; j++) { + v1.set( objPos.getX( j ), objPos.getY( j ), objPos.getZ( j ) ).applyMatrix4( matrixWorld ); - var vertexId = face[keys[j]]; - var vertex = verts[vertexId]; + v2.set( objNorm.getX( j ), objNorm.getY( j ), objNorm.getZ( j ) ); - var tangent = face.vertexTangents[j]; + v2.applyMatrix3( normalMatrix ).normalize().multiplyScalar( this.size ).add( v1 ); - vertices[idx].copy(vertex).applyMatrix4(worldMatrix); + position.setXYZ( idx, v1.x, v1.y, v1.z ); - v1.copy(tangent).transformDirection(worldMatrix).multiplyScalar(this.size); + idx = idx + 1; - v1.add(vertices[idx]); - idx = idx + 1; + position.setXYZ( idx, v2.x, v2.y, v2.z ); - vertices[idx].copy(v1); - idx = idx + 1; + idx = idx + 1; - } + } - } + } - this.geometry.verticesNeedUpdate = true; + position.needsUpdate = true; - return this; + return this; - } + } -}()); +}() ); // File:src/extras/helpers/WireframeHelper.js @@ -34697,18 +36729,18 @@ THREE.VertexTangentsHelper.prototype.update = ( function (object) { * @author mrdoob / http://mrdoob.com/ */ -THREE.WireframeHelper = function (object, hex) { +THREE.WireframeHelper = function ( object, hex ) { - var color = ( hex !== undefined ) ? hex : 0xffffff; + var color = ( hex !== undefined ) ? hex : 0xffffff; - THREE.Line.call(this, new THREE.WireframeGeometry(object.geometry), new THREE.LineBasicMaterial({color: color}), THREE.LinePieces); + THREE.LineSegments.call( this, new THREE.WireframeGeometry( object.geometry ), new THREE.LineBasicMaterial( { color: color } ) ); - this.matrix = object.matrixWorld; - this.matrixAutoUpdate = false; + this.matrix = object.matrixWorld; + this.matrixAutoUpdate = false; }; -THREE.WireframeHelper.prototype = Object.create(THREE.Line.prototype); +THREE.WireframeHelper.prototype = Object.create( THREE.LineSegments.prototype ); THREE.WireframeHelper.prototype.constructor = THREE.WireframeHelper; // File:src/extras/objects/ImmediateRenderObject.js @@ -34717,16 +36749,16 @@ THREE.WireframeHelper.prototype.constructor = THREE.WireframeHelper; * @author alteredq / http://alteredqualia.com/ */ -THREE.ImmediateRenderObject = function () { +THREE.ImmediateRenderObject = function ( material ) { - THREE.Object3D.call(this); + THREE.Object3D.call( this ); - this.render = function (renderCallback) { - }; + this.material = material; + this.render = function ( renderCallback ) {}; }; -THREE.ImmediateRenderObject.prototype = Object.create(THREE.Object3D.prototype); +THREE.ImmediateRenderObject.prototype = Object.create( THREE.Object3D.prototype ); THREE.ImmediateRenderObject.prototype.constructor = THREE.ImmediateRenderObject; // File:src/extras/objects/MorphBlendMesh.js @@ -34735,307 +36767,315 @@ THREE.ImmediateRenderObject.prototype.constructor = THREE.ImmediateRenderObject; * @author alteredq / http://alteredqualia.com/ */ -THREE.MorphBlendMesh = function (geometry, material) { +THREE.MorphBlendMesh = function( geometry, material ) { - THREE.Mesh.call(this, geometry, material); + THREE.Mesh.call( this, geometry, material ); - this.animationsMap = {}; - this.animationsList = []; + this.animationsMap = {}; + this.animationsList = []; - // prepare default animation - // (all frames played together in 1 second) + // prepare default animation + // (all frames played together in 1 second) - var numFrames = this.geometry.morphTargets.length; + var numFrames = this.geometry.morphTargets.length; - var name = "__default"; + var name = "__default"; - var startFrame = 0; - var endFrame = numFrames - 1; + var startFrame = 0; + var endFrame = numFrames - 1; - var fps = numFrames / 1; + var fps = numFrames / 1; - this.createAnimation(name, startFrame, endFrame, fps); - this.setAnimationWeight(name, 1); + this.createAnimation( name, startFrame, endFrame, fps ); + this.setAnimationWeight( name, 1 ); }; -THREE.MorphBlendMesh.prototype = Object.create(THREE.Mesh.prototype); +THREE.MorphBlendMesh.prototype = Object.create( THREE.Mesh.prototype ); THREE.MorphBlendMesh.prototype.constructor = THREE.MorphBlendMesh; -THREE.MorphBlendMesh.prototype.createAnimation = function (name, start, end, fps) { +THREE.MorphBlendMesh.prototype.createAnimation = function ( name, start, end, fps ) { - var animation = { + var animation = { - startFrame: start, - endFrame: end, + start: start, + end: end, - length: end - start + 1, + length: end - start + 1, - fps: fps, - duration: ( end - start ) / fps, + fps: fps, + duration: ( end - start ) / fps, - lastFrame: 0, - currentFrame: 0, + lastFrame: 0, + currentFrame: 0, - active: false, + active: false, - time: 0, - direction: 1, - weight: 1, + time: 0, + direction: 1, + weight: 1, - directionBackwards: false, - mirroredLoop: false + directionBackwards: false, + mirroredLoop: false - }; + }; - this.animationsMap[name] = animation; - this.animationsList.push(animation); + this.animationsMap[ name ] = animation; + this.animationsList.push( animation ); }; -THREE.MorphBlendMesh.prototype.autoCreateAnimations = function (fps) { +THREE.MorphBlendMesh.prototype.autoCreateAnimations = function ( fps ) { - var pattern = /([a-z]+)_?(\d+)/; + var pattern = /([a-z]+)_?(\d+)/; - var firstAnimation, frameRanges = {}; + var firstAnimation, frameRanges = {}; - var geometry = this.geometry; + var geometry = this.geometry; - for (var i = 0, il = geometry.morphTargets.length; i < il; i++) { + for ( var i = 0, il = geometry.morphTargets.length; i < il; i ++ ) { - var morph = geometry.morphTargets[i]; - var chunks = morph.name.match(pattern); + var morph = geometry.morphTargets[ i ]; + var chunks = morph.name.match( pattern ); - if (chunks && chunks.length > 1) { + if ( chunks && chunks.length > 1 ) { - var name = chunks[1]; + var name = chunks[ 1 ]; - if (!frameRanges[name]) frameRanges[name] = {start: Infinity, end: -Infinity}; + if ( ! frameRanges[ name ] ) frameRanges[ name ] = { start: Infinity, end: - Infinity }; - var range = frameRanges[name]; + var range = frameRanges[ name ]; - if (i < range.start) range.start = i; - if (i > range.end) range.end = i; + if ( i < range.start ) range.start = i; + if ( i > range.end ) range.end = i; - if (!firstAnimation) firstAnimation = name; + if ( ! firstAnimation ) firstAnimation = name; - } + } - } + } - for (var name in frameRanges) { + for ( var name in frameRanges ) { - var range = frameRanges[name]; - this.createAnimation(name, range.start, range.end, fps); + var range = frameRanges[ name ]; + this.createAnimation( name, range.start, range.end, fps ); - } + } - this.firstAnimation = firstAnimation; + this.firstAnimation = firstAnimation; }; -THREE.MorphBlendMesh.prototype.setAnimationDirectionForward = function (name) { +THREE.MorphBlendMesh.prototype.setAnimationDirectionForward = function ( name ) { - var animation = this.animationsMap[name]; + var animation = this.animationsMap[ name ]; - if (animation) { + if ( animation ) { - animation.direction = 1; - animation.directionBackwards = false; + animation.direction = 1; + animation.directionBackwards = false; - } + } }; -THREE.MorphBlendMesh.prototype.setAnimationDirectionBackward = function (name) { +THREE.MorphBlendMesh.prototype.setAnimationDirectionBackward = function ( name ) { - var animation = this.animationsMap[name]; + var animation = this.animationsMap[ name ]; - if (animation) { + if ( animation ) { - animation.direction = -1; - animation.directionBackwards = true; + animation.direction = - 1; + animation.directionBackwards = true; - } + } }; -THREE.MorphBlendMesh.prototype.setAnimationFPS = function (name, fps) { +THREE.MorphBlendMesh.prototype.setAnimationFPS = function ( name, fps ) { - var animation = this.animationsMap[name]; + var animation = this.animationsMap[ name ]; - if (animation) { + if ( animation ) { - animation.fps = fps; - animation.duration = ( animation.end - animation.start ) / animation.fps; + animation.fps = fps; + animation.duration = ( animation.end - animation.start ) / animation.fps; - } + } }; -THREE.MorphBlendMesh.prototype.setAnimationDuration = function (name, duration) { +THREE.MorphBlendMesh.prototype.setAnimationDuration = function ( name, duration ) { - var animation = this.animationsMap[name]; + var animation = this.animationsMap[ name ]; - if (animation) { + if ( animation ) { - animation.duration = duration; - animation.fps = ( animation.end - animation.start ) / animation.duration; + animation.duration = duration; + animation.fps = ( animation.end - animation.start ) / animation.duration; - } + } }; -THREE.MorphBlendMesh.prototype.setAnimationWeight = function (name, weight) { +THREE.MorphBlendMesh.prototype.setAnimationWeight = function ( name, weight ) { - var animation = this.animationsMap[name]; + var animation = this.animationsMap[ name ]; - if (animation) { + if ( animation ) { - animation.weight = weight; + animation.weight = weight; - } + } }; -THREE.MorphBlendMesh.prototype.setAnimationTime = function (name, time) { +THREE.MorphBlendMesh.prototype.setAnimationTime = function ( name, time ) { - var animation = this.animationsMap[name]; + var animation = this.animationsMap[ name ]; - if (animation) { + if ( animation ) { - animation.time = time; + animation.time = time; - } + } }; -THREE.MorphBlendMesh.prototype.getAnimationTime = function (name) { +THREE.MorphBlendMesh.prototype.getAnimationTime = function ( name ) { - var time = 0; + var time = 0; - var animation = this.animationsMap[name]; + var animation = this.animationsMap[ name ]; - if (animation) { + if ( animation ) { - time = animation.time; + time = animation.time; - } + } - return time; + return time; }; -THREE.MorphBlendMesh.prototype.getAnimationDuration = function (name) { +THREE.MorphBlendMesh.prototype.getAnimationDuration = function ( name ) { - var duration = -1; + var duration = - 1; - var animation = this.animationsMap[name]; + var animation = this.animationsMap[ name ]; - if (animation) { + if ( animation ) { - duration = animation.duration; + duration = animation.duration; - } + } - return duration; + return duration; }; -THREE.MorphBlendMesh.prototype.playAnimation = function (name) { +THREE.MorphBlendMesh.prototype.playAnimation = function ( name ) { - var animation = this.animationsMap[name]; + var animation = this.animationsMap[ name ]; - if (animation) { + if ( animation ) { - animation.time = 0; - animation.active = true; + animation.time = 0; + animation.active = true; - } else { + } else { - THREE.warn("THREE.MorphBlendMesh: animation[" + name + "] undefined in .playAnimation()"); + console.warn( "THREE.MorphBlendMesh: animation[" + name + "] undefined in .playAnimation()" ); - } + } }; -THREE.MorphBlendMesh.prototype.stopAnimation = function (name) { +THREE.MorphBlendMesh.prototype.stopAnimation = function ( name ) { - var animation = this.animationsMap[name]; + var animation = this.animationsMap[ name ]; - if (animation) { + if ( animation ) { - animation.active = false; + animation.active = false; - } + } }; -THREE.MorphBlendMesh.prototype.update = function (delta) { +THREE.MorphBlendMesh.prototype.update = function ( delta ) { - for (var i = 0, il = this.animationsList.length; i < il; i++) { + for ( var i = 0, il = this.animationsList.length; i < il; i ++ ) { - var animation = this.animationsList[i]; + var animation = this.animationsList[ i ]; - if (!animation.active) continue; + if ( ! animation.active ) continue; - var frameTime = animation.duration / animation.length; + var frameTime = animation.duration / animation.length; - animation.time += animation.direction * delta; + animation.time += animation.direction * delta; - if (animation.mirroredLoop) { + if ( animation.mirroredLoop ) { - if (animation.time > animation.duration || animation.time < 0) { + if ( animation.time > animation.duration || animation.time < 0 ) { - animation.direction *= -1; + animation.direction *= - 1; - if (animation.time > animation.duration) { + if ( animation.time > animation.duration ) { - animation.time = animation.duration; - animation.directionBackwards = true; + animation.time = animation.duration; + animation.directionBackwards = true; - } + } - if (animation.time < 0) { + if ( animation.time < 0 ) { - animation.time = 0; - animation.directionBackwards = false; + animation.time = 0; + animation.directionBackwards = false; - } + } - } + } - } else { + } else { - animation.time = animation.time % animation.duration; + animation.time = animation.time % animation.duration; - if (animation.time < 0) 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; + var keyframe = animation.start + THREE.Math.clamp( Math.floor( animation.time / frameTime ), 0, animation.length - 1 ); + var weight = animation.weight; - if (keyframe !== animation.currentFrame) { + if ( keyframe !== animation.currentFrame ) { - this.morphTargetInfluences[animation.lastFrame] = 0; - this.morphTargetInfluences[animation.currentFrame] = 1 * weight; + this.morphTargetInfluences[ animation.lastFrame ] = 0; + this.morphTargetInfluences[ animation.currentFrame ] = 1 * weight; - this.morphTargetInfluences[keyframe] = 0; + this.morphTargetInfluences[ keyframe ] = 0; - animation.lastFrame = animation.currentFrame; - animation.currentFrame = keyframe; + animation.lastFrame = animation.currentFrame; + animation.currentFrame = keyframe; - } + } - var mix = ( animation.time % frameTime ) / frameTime; + var mix = ( animation.time % frameTime ) / frameTime; - if (animation.directionBackwards) mix = 1 - mix; + if ( animation.directionBackwards ) mix = 1 - mix; - this.morphTargetInfluences[animation.currentFrame] = mix * weight; - this.morphTargetInfluences[animation.lastFrame] = ( 1 - mix ) * weight; + if ( animation.currentFrame !== animation.lastFrame ) { - } + this.morphTargetInfluences[ animation.currentFrame ] = mix * weight; + this.morphTargetInfluences[ animation.lastFrame ] = ( 1 - mix ) * weight; + + } else { + + this.morphTargetInfluences[ animation.currentFrame ] = weight; + + } + + } }; diff --git a/webapp/shaders/conservative_model_proj.frag b/webapp/shaders/conservative_model_proj.frag new file mode 100644 index 00000000..9cb7dc1f --- /dev/null +++ b/webapp/shaders/conservative_model_proj.frag @@ -0,0 +1,29 @@ +#extension GL_EXT_frag_depth : require +varying vec3 AABB_min; +varying vec3 AABB_max; +varying vec3 positionK; +// depth encoding : http://aras-p.info/blog/2009/07/30/encoding-floats-to-rgba-the-final/ +highp float factor = (exp2(24.0) - 1.0) / exp2(24.0); +vec3 EncodeFloatRGB(highp float v) { + vec3 enc = fract(vec3(1.0, 255.0, 255.0 * 255.0) * factor * v); + enc -= enc.yzz * vec3(1.0 / 255.0, 1.0 / 255.0, 0.0); + return enc; +} +highp float DecodeFloatRGB(vec3 rgb) { + return dot(rgb, vec3(1.0, 1.0 / 255.0, 1.0 / 255.0 / 255.0)) / factor; +} +void main() { + vec2 pos = positionK.xy; +//lets destroy the fragments that are really out there between the input corner and the dilated corner + if(any(bvec4(lessThan(pos, AABB_min.xy), greaterThan(pos, AABB_max.xy)))) + discard; +// ok, we were pessimistic, but one thing still holds: +// the true Z value can never ever be higher or lower than any Z value of the input vertices, +// so we clip to get back to some reality + float z = clamp(positionK.z, AABB_min.z, AABB_max.z); +// go back to fragment world + z = (0.5 * z + 0.5); +// update the depth buffer, since what was a nice triangle is now a triangle with 2 bent corners (flattened by the Z clamp). + gl_FragDepthEXT = z; + gl_FragData[0] = vec4(1.0 - z, 0.0, 0.0, 1.0); +} \ No newline at end of file diff --git a/webapp/shaders/conservative_model_proj.vert b/webapp/shaders/conservative_model_proj.vert new file mode 100644 index 00000000..6cd5179e --- /dev/null +++ b/webapp/shaders/conservative_model_proj.vert @@ -0,0 +1,59 @@ +attribute vec3 prevPoint; +attribute vec3 nextPoint; +uniform vec2 hPixel; +uniform vec2 hPixelWorld; +varying vec3 AABB_min; +varying vec3 AABB_max; +varying vec3 positionK; + +float cross2d(vec2 v1, vec2 v2) { + return v1.x * v2.y - v1.y * v2.x; +} + +// http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter42.html +void main() { + vec3 eyeDirection = vec3(0.0, 0.0, -1.0); + vec3 p1 = prevPoint, p2 = position, p3 = nextPoint; + vec2 e1 = normalize(p2.xy - p1.xy); + vec2 e2 = normalize(p2.xy - p3.xy); +// project the side on the bisector +// http://stackoverflow.com/a/32515402/72637 + float halfsine = sqrt((1.0 - dot(e1, e2)) / 2.0); + vec2 resultPoint2D = p2.xy + length(hPixelWorld) / halfsine * normalize(e1 + e2); + +// project the 2D point to the triangle plane in 3D +// grab the triangle normal + vec3 normal = normalize(cross(p2.xyz - p1.xyz, p3.xyz - p2.xyz)); +// grab the Z for (x=0, y=0) + float d = dot(normal, p2.xyz); +// the new Z is the distance from the 2D projected point to its projection on the triangle plane + float t = (dot(normal, vec3(resultPoint2D, 0.0)) - d) / (dot(normal, eyeDirection)); + +//I suspect the normalize() function is a bit off and sometimes give a number slightly bigger than 1, and sqrt() is unhappy + float normalZSquared = clamp(normal.z * normal.z, 0.0, 1.0); +//shift the whole triangle up because Z is sampled at pixel center, but the maximum Z is at a corner. +//A mostly vertical triangle might send the Z very high or very low, well clamp that in the fragment shader + float cornerPessimization = sqrt(1.0 - normalZSquared) * length(hPixelWorld); + vec4 shiftedPosition = vec4(resultPoint2D, t + cornerPessimization, 1.0); + vec4 projectedShiftedPosition = projectionMatrix * modelViewMatrix * shiftedPosition; + +//compute the Axis Aligned bounding box + vec4 prevPos = projectionMatrix * modelViewMatrix * vec4(p1, 1.0); + vec4 currPos = projectionMatrix * modelViewMatrix * vec4(p2, 1.0); + vec4 nextPos = projectionMatrix * modelViewMatrix * vec4(p3, 1.0); + vec3 minBounds = prevPos.xyz; + minBounds = min(currPos.xyz, minBounds); + minBounds = min(nextPos.xyz, minBounds); + vec3 maxBounds = prevPos.xyz; + maxBounds = max(currPos.xyz, maxBounds); + maxBounds = max(nextPos.xyz, maxBounds); +// extend the box by one pixel + minBounds = minBounds - vec3(hPixel, 0.0); + maxBounds = maxBounds + vec3(hPixel, 0.0); + + AABB_min = minBounds; + AABB_max = maxBounds; + gl_PointSize = 10.0; + positionK = projectedShiftedPosition.xyz; + gl_Position = projectedShiftedPosition; +} \ No newline at end of file diff --git a/webapp/shaders/model_proj.frag b/webapp/shaders/model_proj.frag new file mode 100644 index 00000000..8a8901f7 --- /dev/null +++ b/webapp/shaders/model_proj.frag @@ -0,0 +1,3 @@ +void main() { + gl_FragData[0] = vec4(1.0 - gl_FragCoord.z, 0.0, 0.0, 1.0); +} \ No newline at end of file diff --git a/webapp/shaders/model_proj.vert b/webapp/shaders/model_proj.vert new file mode 100644 index 00000000..efdfe723 --- /dev/null +++ b/webapp/shaders/model_proj.vert @@ -0,0 +1,5 @@ +attribute vec3 prevPoint; +attribute vec3 nextPoint; +void main() { + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); +} \ No newline at end of file diff --git a/webgcode.xcodeproj/project.pbxproj b/webgcode.xcodeproj/project.pbxproj index 07622892..4508dced 100644 --- a/webgcode.xcodeproj/project.pbxproj +++ b/webgcode.xcodeproj/project.pbxproj @@ -165,6 +165,7 @@ DDAA52AFFAE054B9E7D1FA72 /* cnc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cnc.h; sourceTree = ""; }; DDAA52B1A4915275E73497A2 /* box2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = box2.js; sourceTree = ""; }; DDAA52CAC3926B8B4F4B829A /* threeDView.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = threeDView.js; path = ui/threeDView.js; sourceTree = ""; }; + DDAA52DF37746F4522541880 /* median.frag */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.glsl; name = median.frag; path = shaders/median.frag; sourceTree = ""; }; DDAA52E5A7394BAD5D0799D3 /* build-sandbox.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "build-sandbox.js"; sourceTree = ""; }; DDAA52F3574050B6296E6814 /* test_pycam.ngc */ = {isa = PBXFileReference; lastKnownFileType = file.ngc; path = test_pycam.ngc; sourceTree = ""; }; DDAA531F684874FDE40B5CDE /* morphology.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = morphology.js; sourceTree = ""; }; @@ -200,6 +201,7 @@ DDAA5583FA74E90180E93A59 /* jobView.hbs */ = {isa = PBXFileReference; lastKnownFileType = file.handlebars; path = jobView.hbs; sourceTree = ""; }; DDAA558DB0228EB233245972 /* cam.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = cam.js; path = cam/cam.js; sourceTree = ""; }; DDAA55AE23998F53B255B243 /* visucamTest.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = visucamTest.html; sourceTree = ""; }; + DDAA55B32FE868406218682F /* model_proj.vert */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.glsl; name = model_proj.vert; path = shaders/model_proj.vert; sourceTree = ""; }; DDAA55B330007C9773B22298 /* palette.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = palette.png; sourceTree = ""; }; DDAA55BC4AF31055E9F8A697 /* worker.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = worker.js; path = webapp/worker.js; sourceTree = ""; }; DDAA55C1BC3E322FEB4DDDDE /* contourExtractor.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = contourExtractor.js; sourceTree = ""; }; @@ -265,6 +267,7 @@ DDAA5A99ECD28BC66706A677 /* manifest.json */ = {isa = PBXFileReference; lastKnownFileType = file.json; name = manifest.json; path = webapp/manifest.json; sourceTree = ""; }; DDAA5AAFC3B9C67160FA662D /* geometry.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = geometry.js; path = gcode/geometry.js; sourceTree = ""; }; DDAA5AC14AAD3A2C784B1419 /* yenc.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = yenc.js; path = libs/yenc.js; sourceTree = ""; }; + DDAA5ADCFC9042BDEE4DA768 /* conservative_model_proj.frag */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.glsl; name = conservative_model_proj.frag; path = shaders/conservative_model_proj.frag; sourceTree = ""; }; DDAA5B08E58D8D0CDBC74EAA /* cam.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = cam.js; sourceTree = ""; }; DDAA5B32ABEAC29E2C09425A /* tux.ngc */ = {isa = PBXFileReference; lastKnownFileType = file.ngc; path = tux.ngc; sourceTree = ""; }; DDAA5B453A7758A947C1B962 /* loading.hbs */ = {isa = PBXFileReference; lastKnownFileType = file.hbs; path = loading.hbs; sourceTree = ""; }; @@ -293,6 +296,7 @@ DDAA5DA7202D3250BD94C922 /* contour.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = contour.js; sourceTree = ""; }; DDAA5DAA497AA174431D8606 /* index.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = index.html; path = webapp/index.html; sourceTree = ""; }; DDAA5DADF1563A99CFD20E6D /* geometry.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = geometry.js; sourceTree = ""; }; + DDAA5DBBB429009377B8802F /* conservative_model_proj.vert */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.glsl; name = conservative_model_proj.vert; path = shaders/conservative_model_proj.vert; sourceTree = ""; }; DDAA5DBED23BB93B2FA59786 /* util.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = util.js; sourceTree = ""; }; DDAA5DFFCA6C2A31934FF748 /* job.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = job.js; sourceTree = ""; }; DDAA5E122DE608E329B07F71 /* compile_ember.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = compile_ember.sh; sourceTree = ""; }; @@ -307,6 +311,7 @@ DDAA5F0312AAB5E2ACBE6A7A /* view.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = view.js; sourceTree = ""; }; DDAA5F0C5AECEECBCBD8D9E4 /* icon_fraise_128.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_fraise_128.png; sourceTree = ""; }; DDAA5F11D1C935F6D795348A /* test_3D.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = test_3D.html; sourceTree = ""; }; + DDAA5F16C318EC66400EED7E /* model_proj.frag */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.glsl; name = model_proj.frag; path = shaders/model_proj.frag; sourceTree = ""; }; DDAA5F1FE345E1887B17F30E /* test_3D_stream.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = test_3D_stream.html; sourceTree = ""; }; DDAA5F329B72AC25F4AAA0F4 /* toolpath.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = toolpath.js; sourceTree = ""; }; DDAA5F376DB68FEFFF7FC133 /* CAM.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = CAM.html; path = webapp/CAM.html; sourceTree = ""; }; @@ -630,6 +635,11 @@ DDAA5BFCED9419518BAD691A /* medial_axis.frag */, DDAA53CBCFE5DFFC2B401675 /* simple.frag */, DDAA55F3A85B6FDBC03F92FA /* max_value.frag */, + DDAA52DF37746F4522541880 /* median.frag */, + DDAA55B32FE868406218682F /* model_proj.vert */, + DDAA5DBBB429009377B8802F /* conservative_model_proj.vert */, + DDAA5ADCFC9042BDEE4DA768 /* conservative_model_proj.frag */, + DDAA5F16C318EC66400EED7E /* model_proj.frag */, ); path = webapp; sourceTree = "";