From 0d9dde8cbc401dfb3762c474e1ac4c95496108fc Mon Sep 17 00:00:00 2001 From: raed22 <abdennadher.raed@gmail.com> Date: Fri, 30 Oct 2020 12:46:47 +0100 Subject: [PATCH] Add lab3 files --- lab3/README.md | 6 + lab3/docs/Cours 3D Labo 3.pdf | Bin 0 -> 32952 bytes lab3/lib/cuon-utils.js | 113 ++++++ lab3/lib/webgl-debug.js | 677 ++++++++++++++++++++++++++++++++++ lab3/lib/webgl-utils.js | 197 ++++++++++ lab3/src/lab3.html | 16 + lab3/src/lab3.js | 18 + 7 files changed, 1027 insertions(+) create mode 100644 lab3/README.md create mode 100644 lab3/docs/Cours 3D Labo 3.pdf create mode 100644 lab3/lib/cuon-utils.js create mode 100644 lab3/lib/webgl-debug.js create mode 100644 lab3/lib/webgl-utils.js create mode 100644 lab3/src/lab3.html create mode 100644 lab3/src/lab3.js diff --git a/lab3/README.md b/lab3/README.md new file mode 100644 index 0000000..37ef2aa --- /dev/null +++ b/lab3/README.md @@ -0,0 +1,6 @@ +# Lab3 IHM + +* **docs**: contains the lab's statement +* **libs**: contains WebGL libraries and utilities +* **src**: contains the source code of the lab2 that you should complete (`main()` function in **lab3.js** file) +* **DO NOT CHANGE** the name of the file `lab3.html`, however, you are free to change its content. diff --git a/lab3/docs/Cours 3D Labo 3.pdf b/lab3/docs/Cours 3D Labo 3.pdf new file mode 100644 index 0000000000000000000000000000000000000000..95be1783746c753299fd5ae3d412b90a2c39ad9d GIT binary patch literal 32952 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ðKXPd*Dt1?%l0ho3+mF3;VtLfW<lMj}B5M zfvyZ9fuTBIy=9%ESFCxrXr8yHkMSxqpFI1PUs4R)T}1*IU6WtQDR0?u{mWm0qc+#K zADk;B!s6E5vwM=8_^j&-Si!#9{G~SE@A{fq?|pWP3+SvBELd9bs^IVhE2#?$n7XvB zL#~_LmGYMO%Ds>|K1O%#2Cd^~rtZ>bcx<=4i{T>J07waq7A)tle%)*BxW#*|*OTZ& zpVnv{jokF6U~xm$EQSk<HiySujz6NmVC@B~MbUp(F1T@1Fh%dpV_A>)v&yEgeR=D` zF00xTGosF2`<&=;Q|W7*!#Ud(%ub+;+~riDt?ckoc6q1Lu6c(n|D9!d%P`M{YqF?? zDqm&H_eTw88jDTm+ihPSogKEz=DK43yT_FZ?b$W2_E$WYkoa!GxDXsF>Am^>Rv#}M zJ@N07*nfsBgIvo`+jH;ENnS5o9ECMYGd`=A%CkGUxT5OuscHGz(cA0wMDM)sJ<er4 z`9`?y<N1>Hhg?e^ddL58w{cwd=K9-|&!yVJJh>;O9n~kF_fL*3KWZ(#`?WtGw|C}p z!|bw89!gHPL(+8$FYrg%Y23Jb-~C}=X0FLUzx~-d^Mb@~G?zs@ULd!H;l7~vqmzG} z>$k^l|8T7_lS?9BwafgsPfN=4!Uud)JM%ws$L#u%{;;>en=3x<wP3`ZUB|d~Ny>UM zd{NO@^=ne}tv|un-tF;U5dZ4o<M!WzVpo@UGuEXtzgwO6uBteqY*yWB@q=rPm>1u# z-RyFG+Ot=;Pb54Qb4%V(FL0R8eJ?{*+U~WlHvDnBxP{w#O53ephn~6X3M8one%fx= zzP;+9jdJ;#inMt7?DDED<pG)3H*Y<|bVdDE8t11CIe(fh9)z9Uw${F@PVYy3*yf90 zElf^VOLzRcT$*#SElt+)g%w}b=g5=G<6}>Mls@!!*?srY)iJ)s>DEpzM!btN?q61E z{CR;n*Cuy;=KK%6udd(Ix?Jk<HG9Uw-IpGE9FFRA3t1Vw`{|3~dwXYjifPPqw+IP3 zQ_HYk{KC9b@AP>ytNa@mwnw#jxUIH*HhF9ARrYfSPe>-YIkH^e9CJu7SGBQ^WktaJ z6-OB=qANZ}&32rBd}2kjXIto$HC2jT%F5B5W^eW9{@%5HdDWY(w)HU|)q8XQSU&vg zTC%$0%hfA->;AM(J@<)8f%m|JrtH>Bx=9yn)ND-dvG=_{`r+IKchzIlRJvS^QoK@> zS<B`}y<UH>p0&pB-`zbMcFy+Ne`(Uyr<=BCvKYI$ONXm4vCGFNJMdrG6Zz;r1OE^C z(^uXukW-i)B$Yeosrz(;<WFa}$XT41Tf8)C!&Mvihx6q#>*TMOrd)mVwL&FB-Rv~? zQ7xXShdzY#e}3J)-u2Ob;R+$yqSwBY-<m~T$`YH#q`PT$hy}y??;AW<UyM8Xczx$C zw&jV-?>jGjvEi)B=DWAISTCJe_J(n%O8El5YNwCB=UexQm`u}WDZhT}&$R$=`(Ha| zKCvwO#vt`_kLjaYugk|jROEd>GObQ=>)y?ok9IZ+MoEf%TArxPlG1ZsrN(gO=?}ZE zxnF#~W4g~r=~qHG_NbS9XwN;M`TIcgkDlxC4?S~A?WK+vK77x0`{ga$y{jv)PkOmO zed;nB7AFUb<53K+_7px+`>?#()Hcf`eA+GjyQ@p|j%Yo<tr9-%N8kCQ+rHmp-0_iZ zJ>Np!b2~GYm2Pv)x))TuLGb%@map=M<0gL8cHa3>{_xv)pH05sgIwfP<d2*<xber^ z<Bwz5)<2TIFMjqxceXEcSnrzBZqM~kW?h_`m};SHdt9gZ0)uS^^M()iJ4<c7_@%a< zS#jL!N}lf9f`(j|m=$(=eqCFC_;1qZ_z!9mSG^L_zI*3TTv#X<=N)&)%S$W&&Rbde zu-E^%l*ui*m4%h7;(d#ga^H5lYn|93Z|}e^skXbm_1ktad)^Z3>>Vmi6%Q02Pxaa~ z_v{t9DU7}AUe#$`u~Rc|{Sq4{Yj^&)VcwIuYo~?xyx0EJ+%x&W^S-TY{g2j*#Z)98 zxmD#Tw)j!4)QelkUMv(V@VKog$sk}<_(p5q?nnGT-0duF?@S9X4_mu%m0xa>kxlYh z6|;*cB^l~WCl)TPXnTDi&iH!Hn)SKsO!qpbcU{qpJiBTFzpSJ7JX^ahjN6mfzy2lO z|3#KR@T2RxEv7CL^VVKtao<|%d!To@#d(=uQZ66gZA^R+FX6kwSNe67>XT_3?WZy1 z9^sgs@!+|ApEjH7rOC(axc7Z{-ny0BR_k1*^3EN<nH4it92S_lJv;d7cwg1k`H~sa zh5jUMytK&Y<v)e$>fKAfZ@arA`A+TvUSIbkbxPBZT7|di*|_H}^t$C!UL5C{*Sao` z^PnqlD?{;t=gm>;t3Pu5P`V|U&Q#I(zV58<`4fQ(C;$Gt^LUxXU#;!E>vg9ed7JuZ zKcB4o;i6k@TX!{`TsBE_^JmS}r$tuDva7pumE*SmNMG(+n!0LRo9TVCY#qZLQgsz! z%yX)%yBJ=7;TL%omEW)b$9_dc*ZsK7?cUkncJ!&Zw_e?FqD-db`TW2L^E&m9{~7*h z@9vk6e)VnR?Ni?F%VxVON%YKSX1AI4uK1XiuhF%0pC5(fF0Cl~>~mp#iRG3TYQMQ% z`krnVOYB*3%q@Cyf7Q{eTXT~=erQ%d<UY@zWq9|d{$$<9uHE?>UK|BKpIdS<d|klz z{K!7tD=8nvdLQ2Q&AaUS+qM1ndQ;A;eC!S#60IjK*Xp;+sqejZZ0oXD=G(r1xAMI| zNk8`hXQ4FD;)KF^e1$RRJ4<fo+8>)G-cdSN^sB9quS%4U*yXo!F3r!vG@A;Fdmdj~ z|Kiv0itIzTo_E$i6!F$Ix!ahTot>fkY!c(~nF@bApKr@x-uF>#-7faFuey<wD>iSd zY^twVHHrOE#d^_?{(scW>sT*rU7H@hefO3t>vPqsUAA$4i#@y3Y=cctf#cWp2_NHT zs(0)WUG;JUgW8_WCEbPpl=kb@%Y;wL+VyPd+8@l0AMVJ>T@w%7IPaqV_ge-YM_hQC z@2fB$&$+<9UaevOhZ*}j;#@x49sOo;F`1!NPqN!Q>}fdjHvYAhclH@y)A=X=<9yh* z>WcR!f8#t9QX3s^$+Dj@Y_m|Ge75RqMeVcRRpy8J<)YTjEy>d}-Ko;1$elOQr=wis zxN`G}1-#F$)+8^eSbdmxb+*~{YgJE{Nv>7iD!6vD=p8e&q8sOfU%vOVI^O-q|B<|) z=Yp(l-^8qB?x(liecLiWd#YQ6weVEQ$I92%R~$O@>2d4_?!(z8ow;>VX)Y>mHhWU{ zvR~V{Chu!L(~rqV%e)t4nZH~WYx-`!?V9MtI)|s0ofBr4RKL`q=-)BxbdC0hzkjBO ztz45G-gYL|r)#5=%`w&!FAw!E&G_Mc#C%qLugZrxyY9AaP5D*1d+V;PeHMR~g&lSa zGv0o0$6I^%dA}bVZ!Q(NJUg)4`^v0m&n_n4OKm?}a3lWFukieb?whadi7hN$P!V_A z@1nv+t-L3f>&}~O-uQdv1ilM<D_%RuY^qp%{JK2v{A2$aZhxD&boHD_-ANOzI?X1% zz44pzLe=&B;z!)me{?_ecJT<i^`F6S-Xn*Lnx%@44CVorZ?Emtu9Xj#-(31^R>|*$ zHHUnnC1#(o-+%t@v8v+e_siq9ABks)shw1>#H*C?Z1QRA<YWm3!#B0P48_JjzQ^ys z__ban^2c(YO?~GcIc6?8F>%^F6$|T5>+R+iNAq6&)BI?+@likj%Q)%X+cvd_U$VRP z$yZ?V$&=?EzH)fLH|M(MvRly=+0Iw?*q1+8xZ=lLy(eNur<YY;yraD>@XU;37x*nr zZy#M6@AxtL@MPNw9=Tt{+rqbZtPwo9`(59GDt<49lB&1=CVu3KH4mC6y<*KCBYpKb z4l6!+taFy>b5Ic8zO?Y|NB;P&AMUR2S@zLxrj~cXvRp<X+mp<8cbNG89!R(zxjJKd z)%>Hip{{#YXO}-r-z?6`clNC^@9tgc>=x^cJ=!E{^p{r@-9NBle|PfK+~<?Nd+~Cu z$e$H9SwcB~FGKZHY=Z%!-H+;pBYp&X{JXF(GKE*^D35{OOSOr+wECW0vzz&m&HDB1 zzjn`^uIg;fw$9(mYv<xNsbX`_@wS7{`xeh)c(Y=?$q(;GYu#hFURiKOw=_Iu@7zo8 zUb;_PCBev7<oEQvPx5-x%N47Sh3ylcbnN<@@Tj)Mbx(EIycH3z;o11)0pn{2SzXbm zaYEOZf0Xatle_!3-!0KCjjT&{3PtZb#v@wXa8q{I$0##9<=F@R@qPF<ch#*|R((4w zq%3j+I@OCO-M1F*W0bU9!0)nXr>yevZ?mRO+wom0_iO93PWhSDsaES}tlqFg%kkg| zcA2h)%l?F2+Nv$RHXw7>+<m>d8F}&n^JF(plaLE~F!%iAK8q73jQ1|agmr(E;th`2 zbUD8y*57k-V9)8K?;Q*CM0h5Cn%s8W!t}_G;76k8T2mjj?YeBbHZiw!_3~*MZC|!E z<TB(X-{vXnYo4dUeCx$Jss9Xdm$vc?R_ML5Uh7-GB`7jih>e@4;TZq>M#~3shV!zy z589bt*=pbMPi6D&%U_pnNpC;Z^-w&wL2y#d!QD^q_8HE(z`i;#uBuM=<-*qc-`tk} zZON-U`*-8h^%M777x%ooInO+4n~n414wKlp`)->r>zC$+W%T@KNZZUkrAH<&=?H_Z z2J@;#dzz2W7mE7+@Kjcue0$jTY2FigHY>51_4O@WJmGm$abwT3-QN1rw@<Ffu*y9* zb7kbcY;Mag>ls&l>Vlqbf7oYam2BV2P+Agh@y}?3Z*<&cA91^~&aCa>yj#n5Z4zg+ zeDa|DXwu7i(JgC(j=c_vH4ol?aqU{(GqYNcC?@N0^=lURJwKh?XT-69ud~KDXP-&E z@Xt?wm%eG8*xftZIq&?s;^@*NFYXIIxUzM&uGw#wD_>@sN}QQy6sOcWhjEgHI&WV= z_WHehn*BG<`}WFFc8}neNtXXanN=>Z+*p_H{UJomPGk0w@Tj@p7S~+dVo<wudhVW_ zn_3NwKWBa3FJ9w5J^E5fa_X|zQ8DIKOLuPDJ+(~rO!dCRsWvIa%~!%p*VQ<*dtX~; z(#<O!cuikdQTXoKS?d&>Pn=Xq?RdiIAloZCdqMU#JCzG>OKUA}zRe3S%9BWJZsK_3 z$WkYIFk9f{<NU62JL#Q!cD1`+%9&KTaNCSy=~GV}NU}V@zI-b~Rk22j)?qC-j`eo( zoNs-U{U#rg+B1t`{<(jr%7Z0;ZqL>2Jz9Lu_@8O|p<QyTAN-AuYJ2wg`G;ML&Zm42 z{yE9P;q*%NxqIui+D<>(+I#hE^XWyertR`uY_Wawm1n&!K|57=dY(ESXX$_KAp0SJ zfqB6Tt+tKJB46DqzWIvherj2;QYRNX!}Cn#jqj|l>#q3YvwiEeQd_yh4%bYr-&*wB z<}Qj|==JXI?(&CyOTMkxS}*-0JK{$%-}2(@;_~Fx^EVw%n%Hc!Nhmx)@?m4}x$5*w z4c1qWUED5lTw>{Zix1!Xa!u;_;xn)Rs@o{~t}$h^Z*Iwo^ht~tuQYGnU^vg+>xKTF z(q$1Zqxcp+xpQvTosF)UGG0qRE1XbQVPJTDasl7Gu7{@ao!@5Fep@WMu6tFM*{tB) zEBd;DJ_T}*Cvgkjm(||9Vc+&EUw&7=_GFoMU2x4ti+`%h3Kv+r9&L?ppPzGeEB9Ob zBT?I??3%#zi95|{QejW;fvGCxqPH$Oh?wNcop-$Wwq!NOsi<4WlzW6H_Xkx^zH;@K z24s+~3pdSg_~WR^CG!@q*sXJtqdH7a1l;o$DBH&`Q+#dil8l$BACs6@RLuGuc5U&c z8!g!`E*{*EpQXOvJ;CtaF8<E44|o39T$@&rJT>gjuJ(W%i5Ggl%WXYackbhod9qja zWkuJ`d!X!dSTBT+F>lAW1$@&V{9gY@dDo#f*0$~C6X&^~(XtoVbnxTpL&Y%y8%`z` zi}>8NIp=4$LusQ8V?K|(bWPZX!!p@VP84)8Jaakkb>YWv9^Vh2&u3gX%X>=ViI%xq z@f^APtqgG=mR$S%C;8*<=uI0Ua;~15^XH&nz1&s(-mlSDKlZnM(bd$+{VM&?c-i7o zhAFpKt&32wKawr{;OrIw-?9q_WpdT#W}FLT(D6v}{A$YomaAi*z~y&SLt<RZSc)1y zFHm^SzT$<w7Q@QLhjq8D|Cn{JHt?(EWWN^MD~*n2zinnp#If;(%WmxQ7j(M+;fz#8 zum7}d+xoRuyxNyE`Pb*jI{7WzwqIW-#$B-}a*xV+4HquiE}qGKOMcDz>d$}MQUB0x z?M#K<2e}5?E+@DqZeC`U)S$Nix{1cu^L#f?=S>fve(>HQm)`hOjE=hVR&l)3ZvHU+ z&t&_~`foy4?Z0@eEB;p7Wm)67)>|d(Q}i(*@zj&O-bEtc<PPng&v#hphxl=?jSIr} zUEyX}%x>88tikfH^xyh}{~3OLo&TS~rR(N}^8Nk$)Q<}O{d}CkfnVjPZck9=gd=J{ zvO6YJmVJ8j)BW`Cjh8R2%rbD6e=EJ*W1`!7)qT$s7Kqse^Gv?q!2Zxd=Ge?7od4LP z&#(G%=pC=%MwwkTVud@yKko><*m<`(_FkLV<;U~ZiyoeD<STan%`-o4tqIa!CD&VR z+Pw8YLtAgP{ATBGrn@s;_Z@uNeylwA<($v6Ui@ds*l|hM*ll;OsGiGuiJ5Nlc~*I^ z`?DB!e`F57_;r4()zx#|*-sqhHf;2Hq|lTZ=5klz_&xUi8J`NH<5Z@Z{@D59n_DSI z;y%XC2a`D%g7tNBu0N1$%vBKh&ycocUPMps-Gg`gV?NK1`s^=ySm5vcJ=PYzEEi)p z^!Y9>N}X+CyNmx+gY~j`ntw_^x-Og;qt`DawA@08?Zh8#>)6)wZ+53B28l%7wrO<F zH}$mm^*N}5>(VV*^|()KANHTUpqOc<Abqm;r22JB)4M<NZgJL$MMitF<TGd9j#6_7 z>0RDaa+INH@{2?Eq7`<&@yE<(%{gm1+rL%kF;~HYjXFCN3;VJ+{Fp4WxyJM(mlJ1k zs^Wpy4Mz1o*C+ft|IyvN>uaZKuk71L+T2D@jx62DRn5P&mRoD}p%<?tBR#a-97Px( z-_zQ=VfsV=#%q?!X_Maj2{gEQ2AC(mJeRpEZu-i*f4o0ly8UBXuAIv(N!cKdC!duy z5B~hcU~8LwuEsPy>z?K{f!j`D_k&s-c#;MA?SIto{t@o~+tFRdPUWInr>N>YrAfPb zOuBb3mV12T0{e;@?QNIe?%~YY75nt))7fXWkDDG*GRxU{p5e>6Y`%-i+kQNM<So52 zivMk`mfH5x+%*T?uHCBNV`<-7CDzVjc8hO5kDyAR>MmA~D_ZMMSuw_i|8{od|D&|c zye_DAnRV|<vx|EjUon?5H5lELx*q%STl4a)U(*)nW-r(#_Uh#_ZB2PI{uS)<GnO4+ ztn;6NuSVZ)?PKZVm-AG%dS`3sEY9)$qrq|TL6dg%!`QiN!mX;^bGb}*vE(X-FjpFz zd44@xaG&pO?)l@{r-NDC?%ndd$FYrfqQN<?zOK3{j4%K3$bT&Sr|_TQdUpO_k?b>X z66VEo*4Tc^k2>hLvs7XihxDnk;tdZz&f|~zdY;Fv`*FUc(W17@ggXvT{=NCS&gj*p zoBPvmKh3V_I`ZkHgod5lXa6&{{OYq9u3y??n{{8vlsne{NcgN{D}EWal~#xJim~S; zZ2He2R-L?XUGM?6+T^2kQXk%4i`=6!z3s7bzkx?`|0jt*tINMt_sSo<zCEYwy3yU- zNl8ZU7#@hdDx7uaL+&2IKb{}wOgt=g;*#smsSfJaohKLYsUMyd`sS8;@00mQ%cWwM zNX>nJ`)$xIOLI+`ZPD{*q%gjXTmQhjIy;^@<HAi}lWVK9=J`xMzVRl%!qm41*Yp3_ zn9KAh_431rWWm^<Y7d!sHy8g&{;^-`my=1vBNtb$t_4~v4lrK`U|zAPi$Mbo|Gx=d zZ-Ks02D|{2=w%c}hDNYu6lMn0UqxYTYHkWz?gDe<P*_D_!*FGE6$N;8$dF$}@%8n2 znToPQ@3N!kx&~E5tX@9tI7^-A&fG%Lr)$Cu%w8;YcyZ&HqonBP{|tTM>AhP6|1<b* z{@IXydylCSKle+AUH=)5FNyuna8vYl$6>d*7UyjJ?YX<>Ms04H^u7K}d*H{u<|X$| zrdQW(jB)Qhxv|g4$cpRg#(mqbtXZ^Y>*O;{fjgyHQtC_}{$~*SHedR#(FDgnJIDVF zDdDSsNdI%0tNm}se}-VMZE^;SrMk-`YGPg`se3L>{g>|SS}^PUQC)T4UDeqhD*Gn# zNnUJPR@-m+XXPySqp^Db-s-;BP2H$hwx;#)0=wr7{iinnTALS_7t6bEcl)y^&#t^* zvCP{fz~S?$R}+<{rK>lvt6ZOVzb5AL!yOrKN?yL3BEz08*!GJ@_~)ZVbET%%u@-yn z=nARtcvGb-daNS-k?8i0ucnv(Gi-joeeRSEs)sd>uYB-t>dh-xmTTot%Q~|vG%?KP zLb;=B*#7<7FXu`<d-=_6|4h54oev*}OxEX{Uaq3>_k;c6KUutdtGX61&6@D)o6{?i ziQ<JpG6_#YdA*N@S=FB0<~r%w0~wKy61$dP7uep4o^gNdojNyZ@1~tw?p~Z*WH0yi z(&9A}*K{15*cRG&LaBkhv{&*@!zv@sH`)w)zn+_GwkChcwt%%Nf~?YIe6u$hdH&m4 z9Fbh~A+vDu?8!4d{z<go`}0G-H~hMKw_(KmX?MlX?>wGyebGO4rUtp|<xx|veVgEM z?fA*abtw+qYVI7y7v|M72EQyir?0=}>D+Z<cPFQ;VS7I#J@a_s-z;wTe`hW(zmj+T z`{%Sw@qPYx9ltyXJ*#%{t&aQLvuD<%&iJ*v^v|w35ymP=Z{WPedk63u4$v&W-H+e@ z+<Sxn?b83(uyy<LKYCa7zr4;q@l`v;^HHRz-<t-v!b=-%DnC~|4mobWNB$Sb`s@#z z^FJ(`bpOk1`F~B?H`hm%tZ+R1>%z}JW^!|Dd|mB&|D`wm=ysZYc-QXC&u1o`Sg|hB zxUX*O%lo;Ljz3s*o>{bhGsi*pWiQR<UzqiHpZeT;=5rV?X&nD&9s9lA<jVCr(c3T2 z-FCUKi|zQ`gy-xZ6aqWuz6_r8dv%-GeBG)oFTZ8V1wLGxb=&bO@0VXyahnzjF5bpu z{(YBR;rw&+AH~Mm3uHC@;x&kz=CE!>k<5RF>IKi`Pp!RiCp(`1kHaPJtk467yDY3# z<CA|d&w91=$KFDny4ZO=`>G3eFV!*r!XO-d_VoN)!M%Up6$b}=%bObc>E_$hzvjwr z-TQ3+hjrE|DzT?CH{?Co7~CVj?oOTQlUZ>K>mTZtpU-v`H)QJNJ(Irhulu#Vy92J4 za_h6o`X=($Z9A#hpLpzXPfzK)?4~PrsxN14o36TV@xzZgPq`Y-ajmXua&vzeQxWOA zQdRos=KYdfnFn`%I;m@N=l+N7y(ReqSs!(7RYks9%5_|gk<Yr}Zgq+2#q4>?{VwzF zsc);Sxj4!382eY#Cw2@M)^C*;x*2<1c<tIXiz3yOxsO}0?=*dP$ysEn&&0)3En`pd z@oz1DoOx)jx5MkxtFEl}p0()p_Zf9Mm-S3z6<Jr@RmoGidAZ==-^w+<#z)sLxqUQo zMdl~<NedkVw{EZvxgNMSYyXXJ8}CTSr5Ldm><vly9P@4UqqA!N8GI+PZkv{w5+oSQ zeyX<kpXjmj*{@ec-N`67zs9pP%sh{cBf<E@*LB4Q!d@!hi}Nt|;Rras;+*H|c><Tx zn>}`kXPnp`!*RQLJ4^b<^DiT-a*rNw{;K(4Z<E`WDIb;i_^br~i7;OLvFn<3KYxt% ziu-)B(ifhje_3H8UT~?RptASVVPyrCFEaA4BdeZw3P)|W%A3k^Y0nWh>*{n1(K=Z> zUwfC$7ezM~RvbBV`j7V2{|r9-#~Msbjz_v)+iZDj(|?B5>OT_W=FeB#th>0@?<dRZ zv!}UN9{eXU{clj6ZMJh{#W&|=`X~AB8`aeFXkMwa;fiJ#vkE=7x@33C{O@P>b^qb8 z=c!X`kT3qvAk3R3?RZ9P#=QLUf2{w~Kdt&cPhp4Elxb3-ox7(A{=TNaHT>547vDBT z->jVcpW*n~!;;JXGgN&0!^ZwF=GM7ce5+SGuBg1|SabC8jvkrM^ZxDmH-&Mjey85r zFWs|O&o%N$Z@kK7d1za8b6@79AML?gE~o9;eJM;ecDH>E-<$ba&(iu{#mWcmh|GJk z@lH4A+)mxkeD&wodKRlTSJb`L)q9^l`FrZMnz!+@w;EXOo-cT0p6tJC+Z;7c1hsD5 zGWmqP*RlP(=LcT@oVwgo_2?>Paqr%im5dwQeTqYE+?V<<yJ@nm;cwdu#aRXx?lMib z#{a5A#N|`^<9GcoJ$iT3&%(E6KUwB>Uu&@DHIJ%3>-Fl;v)gYzA6VG*{+y%Ce+H}5 z(~o|7o3?$U(CV0<0+ZFu|IPP5x9Z1Km2F~C`{tdiIZ@M=#OIOv)ARd(hSwoh_vY+M zYhHX%?JKw0%ehufl`7T$8Cn&rvaRoiMR><#CaLo_>?^RTx)?dj_1$#Q#X8~FKk0q) zoMLEs;EPBAyH`@roGC%Ig?<c-4F;^bA`F+M3{SeMU+D^9>S18`BHGBE6>vOLrO8v& z<;ViJ`xhRV`P**vYY)1@qWG-KX<za>7amV(#yA81dne9mFz0zJo@wSSwQJrPBQ1+H zYZG!W+;*0(T9P1tX=CrTl~Sh_A|r1&mCfiA5d19IP<cMDZfe@cbx%M1NZ$RHC%Amd z+BtcWj~RCb?-rf7KJ?z9bME)PO)&n?P`Q}pT8Z%;chx=K&vaBh?>;$k`*!(V>z#pp z&wqs$+AS=V$yO<n@yvVwd(v#_GtyJ48rY+RGo<{B7leictXjZ$fzdm%D)#K3@50<$ zI2K#P_HfPlHGxs$yQ!0R_iXijB}OM3B79yw&_Bm^cX{){2kVzA&XtejbyMh!E;`b< z*Qw_k@7;4NF1U$Gte<f-#c<Q)?X2ZmO%Cd|ZH2c@ld`*y|Ms%7dpgm{MKNh<x&GPR z`|CexFlSAiVsMzjeWB?2)5W!=Iv;I>erml4sm$RxvY_<1vds5KOZr~@(|qX2U74%g z)Gf&VejBGsvc&O12kA_i!oNpC|1*>%t-OEZbfJm3+2xMBggYXe-i5JCvJ}41R^#3q zsjD*KNco;s@ftsUo|lKdFj(ABIP31_%9PEm&yUXv5xd19`Ag1~A-LJ|Ou5^)e7owU zH<eetWh`u1WzCnRaw+eLfoo;e=BNV)r;6#Wc~Wwx`1b9!Ie`wW%32!^Ybamniqc>- zFzn%}3fx-1%U&x=gF!*C$=D;*^JG9h7f6odAX5|LE5@aZ8Uz@u7&MkHf-EFGwezZ^ zesY(IlF9tjvz+^XK8t23{qk&U7IS*eb)|=E9~<*c5|#Y&+Cd=mR>u{=xe3z@m^Mw9 z;NkZP&8`hgO_qK1c6OEil$E^;`xwu8B)*Rdh&q@f;mGACy(08jmf`Y9Ei3K_r+wzU z&2vf?aQUw3J$g+t)=79{xO)2y|MmMXP2GAUSm)!09_Q!m?1~MNinUqKZ|EGUX5Hdg zxSf&R=lRdiUOKv>&D<G{+#70|-#wQ2vx3RG)-i3%tcMRLDT<Y|#Vz1@Qgt=#YVu4M zp(tZdMc<8vECvZ4&zFCge`-tOwl<D4g)SW}PgeCX2lm>Xak<DH;gaL1y&+-Zr3S6@ zs|?znP7@Dp=6XM&;EoLA{q<38xhcns_a}2lwA{Ykv(s;fe9X1Z4V!f$Zo7EKE_ox< z?`3y5^SRCbwC*zn&SnQE&)dT2@!I_~OQi3f=|BBXA8PQFe0^>5@09*6h7E7rTzi;L zzYDl=d~Lk9cElB}=wl1g88jX%vwvB(ZGPH=wyhmc7>b&=`=4qso*pATE4J_AiH(&S z?3M+`=N|}LyXAhyw!Ykcxv%H0ODNyC%-g!K|MYK@XoJPlYz9_!p+-u_x;hqyx-l>} zI`H9GR>h2~ql&yA)U7lxxg;|`540ac!C2oVKQ}Qm547>nFjfJw4I?#L0i?lD0kj3t zDZexiv@O#>&oov+AEvoDTEP&!!$QFjv^g^fbx#6V3f3`(X#v}3gl3<!A;>O61(4m& z2GI%zdX@&3777M>h6bh<VA{mez$jKh-w$;I1K3ugZNO<N$Vx*6i1p|;An%L}fb8&u zdLcL?F(orE-NpucFCHolRqULfUjzz2T_X!qJ#$k96JtX?Gh+oaV<SBy7!MTi6(y;8 zDbe7K5nuu-uoVnJ(Gg^42kPI$cIklktHMG6WEXhP2z&zv+Kw9|v>i9PPWd@0By77e zGy`pF)hA-#4LCWVI25E6!=WzlEj?g&BR1Z^Tt=ncH|CbeyKjsvz_vkm-<V_HePa$* z1KWLLWPxw@4S26GhUXBmP+5>_fCzdgXBSsjSMBi3ymU|kD%K53O)t$!EP`Y^(C#PC zl+?VE%#uo759sEil*GIe&=#m-1w+tIpCJ8^{Ls8i(B51HLjzC}4MGWPkQ%Uaq7@85 zn|y*|6~IGRU@@==m}O%_gri`ax12p)KrR5KF(b%sau8SF+0!K?-`&&2H?cq;=4^eJ za0NpH0|Ss`aAk2xYA$H^xPlRAS91_JSP}8AU<6Lp`XC*kB!c1*unw40{fkmki!$@l zHDK0gD(DBLre_wH6jf?CrsOB3YAWalmlhP{q~?P59vL793D_2xNgzL<IRnfECqJCA zgFh)+SXx5kszkxaz!Z5On7JWhADD%u0U{+D8k<^zErzB<?EAnhAZj2f(Ewz)0!X8R zxjCq~1Vw^dW=^V+f}sIu%N}a@fg&Hp*ZxUa!6mu+ehTI$kZi1AYHkkOq=jwEx0#70 zC>&wqi4}8}M%>Q3?I!TFxZ&Sn8#R5=a?f{5IhZUqHnZ)N+&0@zrFMbpEit7|Emuj; z`W5@H9)Gen{=&C6uG=Es&RWFFsPx_F@J-|N^s;H*UYd_X3{FWH_PKfaaGm0MG;@)T zyZv&V#XcIRB%ey0H?)5`Ww}c5nJX%(Zk3LwdN?no>8MXTC3Cscu*1<lIV$krN5Pp+ z^CFfR`U{`t_H}h!lhDM~xcrdKiPI*FnLY`9F81^(^qg^O$~6^*^{Q_Ijs$<6a%ABI zmNRKl69kUzbg)*Rx7@Om$NwZp?4Ac)ZNlQ3w<b*A5bO|l&`+mD`$nhUIz#zY#%dN1 zO&{M`BjT^>!8D8Kn8cDI<2VMx9_=&Q2I~~m4c0NNNV4?oRcB*8W?B%+`hfY-j9JDv zmNSSRp3gKvCLyzXixRV-2t%*}cg)md!rd1pOqYppj$`8It!!>*OVKO{XwW*U(8jR3 z$+*GtI8!gb(dQ#AHm#x0IJX!j#A`Hj)iAIZH*5a05YbAz!2E2dBfozQheVf1eC4l_ zlAM+xE6KTie$$^Om3%9W?De~yW}DZacD1IY#&f1@z3<D->Gn38lp=o1DzQH<i#>O4 zmHyl0Z!K=??S+2#=84?Z^4vS=u`I6u<61+hNxL^*uX(uVpU{*v!^}6Ge|_qM=Uty- z`MWY8xNP~~qs?#5Cts@Ewfyh@Kfk}fKlkfRW7VYXo^yRBy+5flQ*HGUuURdVN;e&l zUbxrox02@Mjk)@UtG6WEFDlK6PS!8xwbi{hCuZiH=-#|DFBPq~AI*2)9_=mnrR7_8 z<>x)~YIq;cjJBI`Lws@TofQ?LQ~VeIyD{B=`#%l273!8P`g#p_4*fb@@q~YY>Bb%F z%L?Sw@9$Z!_9I?BJ|{nSqiKe8r({&|>WgO#q{7dI32rfceROw0MfdMVvQO&`8~)et zGCjfm%Cqj{kKBM?&o<6|aCc8{<2R8Gy*GO#>*lT1>WI^i=01K+>D0A9d0)CTqPrP$ zzP@<L{;G4)_AMQfZ?45&iIDn}qW_Z1eV3BgF1s6xmh4V_{i2~LRa)^{R=R^&YdkNX zn9dCA?_pmS=FRS!m1P)tdC9zOTh`2~ygKWgr>p(Xe3sAAiq(weuSElQeSK53y=%e( ziF3hXKE=!;s_Y9AvM*meq`s6Zf3}0w=Y^llI?6cTGOKZ?X<YP<dw4>7(G$@(embsG z_9QX9G>c#hzQW*Y=BjIb^8TK3;r_!#>kl;C^Y5SdIc<iC-?}y0cD5gnu@z5wk-EO% z&D6ke-xTFi*FQNd$gj4Nv1^ie!%-OqJ?o11%T`2RIi0Ba)=!?rcy3Cq-GrH+9p~&R z|Lx!wykyVjRh+*SX1rq4@R}E0R&Hx>?7z(Jg`QsVJ}j4K>r`!=#J9TQ!kdFyQc+hv z7>n{RTguoqiT^;>tHPp*TbXw1z4=(bRXWz1{m!*Vdu~lK)%eYErunANw|Tp!SeLHU zSX%WVw0eo7j66%ftbEL*4--%4%bn-WuM_zEkd=df*{Qvvve#d?9lN^b&K&s)gXb&n z%~^iu)|J}TETX*9zvO!Op6y}mnk0Ps=+gdqq3ySmrM<%+d|ukSV%d8Y%R@6C3q1WQ z7*(HpG$ouVpKHe{qkS9a?B7<s;O(2Xnf-UR>av-$ewB_+$k#JyvJCqAj`OX@ipAAV zci$??-W9I;!P(DV{Di^v#6;7iF3agP_dib1T;Us4{9~=nBDMdu|Kq>h^w7C@`G(EQ zTj?jOD?aZvH=7|?`hMPpJpUIJ8{RqA>{oJg?Ur!o%Fis|J=`rJcV<nsw$%n!9<OPi zcExP*Sg>Wq`qe)zt13<f*Pj;kS-tDK#EQFBXJudiT{NLj?TJs0;yhQu^6TOM!j6is z-1qB~<?-V|@2mH(sJ^dzY-xVq?5jI?JBz;8mKVR)clfJS{x$dHeACd<pzCj6ESVRY ze|qW88D{ZTdJiM4v(~Nf?e7hht~Fo$e#5NmSHqLyzIwlix{_;JmHw|!Z1U}6X%EXd z?Zclg$-1+9;>|t#qs}bre)VOQiP)^ZW>%9aZ@0w#D7);Z@3rT~we_W+f9_dl-_Q5@ zV-?@*ob#_%t%^!cv95~ln_ac~?<21N(QglDTCUdLZsnJL*>kJu^;-7Jdb5k{sx0@$ zm`{y=z58p;SHX?~|INPTmg~dhV?SRrykq<4?d@+%)4zMIt3G;VujR{WU+*5;5^Yl2 z|Nbs>)_04mzF+Tdxx`y({rh9P`+vs0`TtwN?G{+=18emf!y7m@Hv0bhKAFWOV8X97 zHwoNE(D%#FO9j<iFdZf&>o5a#Y+!XKQFefOD)94mh|*zb0BZceEO9PMO$2qxToOxC zHC(KW42%p64U7#8OiYYSjCBpn)eQ{PH5K%I^HcDvK=!wx0o)tW3P$FjPyn^UVA$B$ zSOJ7#d<bo73TlZ#NQej~X=Y}w0K(=*MhYMdVk4^p>9#Nibpy<ywt`d{D3}=<VpR{* z3(^P11_laX+SCl}Za5o!CXs=Hk&%%C$b6VMh&BNG#niw6p~C=VE=UAK4<8KiEwX<= zBBrKhC=GoBQ1F9O7q+-Z)(uG+ATto5jx3L!Mj&x%0FFsyb&&W4#S2IjHBF%MK=Kd_ zvK5pnkZG8G5O+c1)55|MoO(cNLD)dS*uoN=MnI}TA|MkW?l({vesK;7EeHwnDMoCA z`hnoswz0v}qBk-|Ss+j`Cw9W!tiuL8t?&Pe?8?#an%uLA*GZv7q-dl3w6}~h(Vo2B zxgyV%^6hWx%K9W6c6=`EK56;8rNw4Ve-zeN_ZUQ_bBOPs<R($D$!`j)#OlYsPvg&P zahxpJuPgZcTyNYJ(-Z!AdK1lVY(2)mL;TZRo=K&`Z}$1yzOLha$evyz$aT>qq2P(* zdaXSxB^}e=T#$Tsdxz|dpT#w~j0;cg+S7LN-UFd2Zb5lYf#NZ|SJKzHEinH7>*`b~ zx7xrdcl`O^tn16Yo$__gmB$;;yg55{*^<=vf?vIky!_-^v_|BF)gF1>9}`x5sbie> zwTKmKRRZZX5e_zU0|Qg&Q5A*?h9<@aCZJ*#Rxwq~xoXW_B7A(S@$N}s{~qn<<=WtM zLqaF|UxM`Q8kT<yWu8233F=EedPPM`dq<_JCR=9~%baMJ3cAzwZSRt^+kZ}MacNs( zI>$slDCpO(xJ&b{@44J&k{eYuX<H$mt$yaONjLUb8}FXPqqbcC`PSO}<C}}qpPifg zdwyi0gM{J~PJLma^)HG-!}nHglTv)seo=d`qvoBPA$vL0_VXske|gu}<-e0p-c3Wf zZNkot`gi329Ax)p`6p7a?k#`M7g_s1Ke9QV?rER!v~s`B&wIO{uXS6%S{w4by>ZT+ zD^&}&iv05ad+^1FS;5Q17yMUpx+BeaRr~7y9eJ`&pXax13~#zrFhkrom?593#!$n3 zDYO2cBdr0pZ#uP{g97TlwJrSbbVAJag;8`wugiIJMfo%R!hbfXwXgs2a8tIR*WFfu z{j!3QXYU+b|L<P$<E@{=>NB=R3RcS56+P$4tp9ecMAFH4?#lf_s}{`m`*@OhZGh?J z&VXBtE$8-6`?dN<&&M~*mG?VdS8$u;9C@NuD&b}IvTRA-L_s<Cv=r05SyiGtG!3Gj zJU&`7W5<=vOT9%V{+?6uF3?-Djw6&a)WS?^zNw;%wb<VL*zQ}wUKba7Px@{DEuy}= z@6X<ELi@gbzx`VN<A-+rY5p4)D=+z{{NrbQg@x^<PXhi@dl*)+s<Nx?(h&2!U-GhI z*Nc^Ns&^>w{5-X`-gfVi&EG%k{8}w1oaLzE9O>x1tl0ZpuH|#<<#VgQ{Hbg?cQf;S z)uq|rqcz?un7lu`V_L+bA6!h0cfPKE6@D!wJam`Vsky1z)28cbYsS5bND>#<kE)oN zd+^VqLrs77+?n%cP326<mzgUy!wx2yeUx!K7v>(lUPAk2?@TY5*=AEG?{jWq)XZF9 zu68KkcFvp`H@q(%xj7^6z?<C+m(Lp9E%ceAy3TF&>A6X(oqPU9cRSBH5}x~(?MI)O zd%wi&MIXetW_x9*Gh3BeuMvpu{-c}3^Sx{B#%*{1ZM=3*Y=Qa2X}8{FObb3eH$%yk z_kc-5Qf}V|mb~b0%>!Gvn)wx+n8g@#^p>pd+Kp%X#11+2=;uwX*?4a4-L<-AyZ0`? zBYF62T)TRkjC9iHQyN@Z%WfahIcwOMr>x~7vn75`!9I(!1S9Uss?(Qh`Q~p~ZGFt! zHl_c~rW<bV-Z_tX)4Na2o&00s+q)a5JoD_?Rxa=U_PM*dS^u^zUnblwnzcKB`HR+u zxQfG(()QP)mUJw!HY?jLvAaII{L`Abd#bA%GInlGj@~Z2qil<uK&Qm*T`O4}M8tPC zcpAAKNntwO7iM&+dai_bVNSu1<Lh3tChJOcFU(c`wwU|Y-Q(x~OKsiWm3(`*?6(_M zvlqK(?aWc)@6DCA+;&qpZ=P-NyE!LLrOldmV$O2rmy#zNWacjxa8(y5$Xy{N+$S^p zpli~ia2CH^vWDv_L@Q6OzA&R)biu*P&5nz?UaT+ISv^tWP|TdC0>|xK9&q%n{GQhD z@vCF0#cO95hevva)0-=q`=(Ak8n#AZo{~v(X5;3qTjHbN-I0A=(6aeR1p5k}(z3&g z=DCTlxRa+IcF+E$o7{^dZrjT?@9VJHw)n(bt=FA}dsCjdCacx6&y+Mxdvv;5_QARH z7ZT3(M|wZ1|GbH-DEe=&Vax}~%SN-?KV*uk`#gKJ@!UJ1%$)semz2M8;GeOK*QqdP z%~{25y<hu0CqH+$<ug2OXm;_Up;+EqQ<qsb{~y&wpQ&ANyx(!<MyX}J5qZVt`H!n4 zj@<k)ujq-Ndv5LlFTZV9_j=BG@~SUyRa=^V?uW^1Dw|B(BR@-I%cY(zkNC`x{Vw@z z+O?ddvu@XN5_)HwW=C$GWy-r|<A!5Lq~@94sAb)f?_|sTm3PH|#;>U>?zjHZxw3qr zT;ng5E5{euaecL0@!e@J`zyv3)eEZ@#(8Jjo%N7UJo|V}Ws52Q=4Xo6?wp<_c5O}i zS*dGll6ntF8E0EuQ`jzjE8d}2_EqKz`<7o^SAqlfvwxLbaeRRt>({y!@$FVjSGTXU z(pS}AT2~|VzARttn7qKT_aevM3mgkgv{C5!&iQzg!9H(^{azC91&{4+U3yJ5?BcEy zRWnN)x1NYfIJ@~-#O5_Kua#u4x|+2$!Z++3S9^fY^!E>}c9}isyU`tY<)L75{ktNc zM_1N{A3f8@YccI|qt&*2jjpv?-G#q1%w`{Xu5R$})mpPjC;F6o&fl+U_sy@a3UIu6 z)c@j|IQQo#iZcw?xijs_?&IvPSD0{7PeOM2;>Rf^bNa*{E}pw$m-DUry9LfVT6}zP ze$L$1o2vv0t#_-h$#C}ntanZB@ueWgd2zG%raL=^Rp^SYI~i9p`E3j5!{Y%`M~bav z1SA7ahWz~X>1zsC-p3TJ$#Z0Xg<SOYExXP6XNKKW*26a!oZ5f0_t9rN`@K(mL`C%9 zJ<EHxj_F183Fh~^7yepsk2PMSeMd46`&EZt#w&gI4ClLD^;rDa^o93_JjZ&*qj%lh z!<6~YR5Sb**^-@jj$3z`3ExBJEq{%6v1a`5v_3ZXiQlE0r4HNYCTut)YdpWL&?#TP zifb?H{dp31JKnYIeEed0ifDixi}|zu0?jFZf0S2ry>1Eg`NnR=+)&H$qgL)O(_i&- zhqFIQ)bSM*?U=2ppR+yifF<kG<x{vcqYD^TM;wl4oRw}lZ#C;Gt?m80=XV#X|CGBN z^*PhKZ)qF{gDB5;E4kF>mX=#jS8$hoTk!gENaRn>XCK1$$o~Fl{_*dD!<sg%Pnp-v ziDr*de=1$GUi0pkb(IJHC``FLP4<v&821IU!?p#CLV<=+oIzU;$wfD&%kSd5>u|a^ zU?0n_JI(K$zb|ILkZ98Aw_y5-c`taXxNBK%&17#q5!x^P!B4|Bz@$caAM2Nm_ABo+ z?(gk?D16|4|Hs#Nk|xZW%HVN+$DPx6&)OdTJL7lwyYM@a@A&V`uQ;;&!}U*cc8BcG z*q_^fY=7FVlwJ3~G`#e^JUcC?FVOdC#3!BW5#O9G6_#)?AJRQ^y`{S4{j#tHP7_XZ z7HwTTSK%s$$0yYt@B0E*`#Myz*>XQM`Nceu`BY`BP>@ssciv&^1z#Sle!wPhM9@ZN z`U}NB9CbYQ%$xS`J$?Rzr%w96;C}zp?sHnTADnvhskA8ej$dBGZ?$sq@44?dKb%Ou zQ+((A&g~AzHs5JiNS2y^`1$AVA7T4=PnG@4s+)2DwERQ=Pw782H$?Bs?9E)8+5S>$ z!e7zLk(YNTc%C~myZh|cXKosG#V2YeSML8A_u+-+=P9alvs8~y%Ff($CvWHZqBEbk zPj8u^FT&xyAcP~-X}W+w8FxQ-dTaR_|7C0Z1B14Ns8`ACYus1v_{EO<-e05g-lMHz z4~!r1UjO`=!CPhH@ul32BCXbLJA`)?Xhyu6{zBP~wRA?DqxH|#fd}3^yxj8rLb65D zE=l`?+ZU=|JbWnckIenk{vY{gckBB4%)fK@&*MWK7qs^<<bKl2;6XvN-2`?{WJ zcEPm|*S<La!}*Kr4a-w&9oA2HFSbXTZ;tad^+L8Iw>0w%@8#D`u+Q2xKi~9z%k*Z} z+-H~ntX(1Y^zipU%_ydQd2R3AKRKS5c;D+sU`L%CA4hxjybs1Xza;<h)%eeKfA;xL z;UDXk7~|l`;EcP!Kdf7|=H#|?@!Ol<F1DE^_w)jf(fofES693=`Pr9cU+?zgfDP-E z%MJgSo;divP%K}0GRfET^hxcggz_bYGjIO%t#MwMs~VpxHeGD`l;~3dU+0zFd2oKt zldS$}Qwt~m2-_}P=-KVl?e!@((xNu=<0&<7S)tse7hRv;jJ#-7oOq?Q;G)X*PZ_au zUs{MYUSG*}{mYjS=W8L%Yj0Y%21K4#ODyQ>6EnN8^HAHWQ%*hHMGM8mrt2IoHNE<i ziEV+x{Cy86_m^>Zzs?GuW-*Op^X#%adHcTY{nP*cVe|ixo{f*A=clAEpPE$H8#`xP z_=nfeZ!Xi_X({>V)t58=&rBcun$~lsRXY5+xJX{i^m&1C!9DhdqIq+E{+N7!Hk;M_ zs>)42FY_*&6!Pxa&zYB3U((t+`wjOp*Ew^iW<GX1b;Q4ZbLRC|tuHhC+9ut(qgs0U z@}+5ld*z)@RB_})aHzW|-|E=5IQE5O`Lk79x9&)Hzq`uq->0dr)(*SxzjnIwr#1Vm z?c|uhhO=L9J6ph9+CBaLwnaa;g+zzHdcn1&_}`(aTYIe?PloFs_Fmn+W9`iF!?Bm< zhR?O0WBDe|YR{Vo*UT0s-`Kl#)_Uu{w=3?LhTdS%D)sffXumA`SNM$wPOn!;Zd&%= z#`S~4f8JfTG3!=&>F8)jZd<dYOf_RpO$2w7s{Q&63nr+mX&e!|bY5RaM?j}fwIS61 zOa8j!7nqYpu7m{tJuKvN%~5`ttasPylh*eA{dX@;H2*Q#zAvWovFUD~8J9m5na^4m zy(8hxH~)1JQ{xL%k8_vb|NCR=LiG<)_Idke|Nkz1_^G!2w$R*&dzCAemvQg=Q{HvE z_Vk%uLOS80p`ZSSPg}ok=dS<rx3}%y<rEg4=y+?{)RmdW@`d^{UoAJkwKi;{c52<# zeHUk!o4uE*Nd4>OeRbW-@YQS8Lbh1!$z8KI?fCNdvUdVPqkg?Qu*`nfgT@ZqqJKSK z^*W{ng|ulY6ooIU+Hg;*q<{CFZ8fc%FFiTs_;Tj9)Kt;XtESqQySTGohnbqJ-4;<0 zRr^?K^N!^kD*XNY7Iq&wt>k^nDO~m7>ecaAFU$yw-dE!qdHI#8nEL!_GY`!_5PraQ z)2+GN-Xx?QbxPl|$7b_4i|x!ycdU51z^}ew+N@qzmJi3%I<9Ww7Ji(tr6#ycu1Lpm zYvj?brC-lf9Fh8}lJ}{ja9U&JC+>pUygc51?u|Py^t3*TX?#BY=_kfJ=ARdvpFi`5 z<9q1#`)Y4(<9<In@yS&4vsP2?{U51^o9|X%E8e-ZJ}x%6#awrOrFeNn`n>~pU5)*( zhiv<Q=j)1-``^Dd{=V1pv;Gdgw)Z!;E7<P;-}hTfPVDT_(_!;h-1+XI_OGe-ndKd$ zVm5L8d6kDBZ92PW|DU%rg>U}YG*dtQvBt>?_X~=rBWHaSTl{*S>C-3TGg`i`{1L~Y zBE84hA(-XdgEQ|$rW{X?RM-{d|A1`^hq<ij^OUvz%sV*D7uM)73mR7z$4#m_aM8Zu z3iH%fhCn41-$h4Oo%$73ZFg?_^;-w02Kt&Z)o*v+e(u8~<!QhCulv0}e!TtTpULVU z|0LR2?)~35dHcO@mlis=@7T7{+5FyDn{zWO|Nq>bXJhiL{?|0|xE)V3cZkX!JYRTu zSxJJ*``;YJUng9t^x4_Cf7PiNkL&I`{2X4*=`(yeF?E91{H9rVR)*~eed}25ljgDe z?K_^+D>z-A^B1xEYIf8FMai4&%&?k!F3;h$hD*|(Rhuks&(lu1etR?P=V|_aN$)j{ z>rZ7zeLm^Q&miwVe^<f!SIJ`YCghZ-NBy*27Wl*C(2ImF$;q)T?7=VeeyL5^u{n{O z=lB_;YQ;STVgYqGPis65x*WxSn`xfV-al;h|MLTPOgtZ2_qSZH-jFMvvAq9(dh4x? z67zfhD@FJA+}~hW;WwAB*u!A|>ccjhX5PP)7%bE$nL7Kwce}2}?MbT+Gf8c|QF5c} zlg|B14?{jr-XPt4IbiG4Oxbe>>^JQ3txGhYnBQFT`ml)DiTz2D>Kb#HBJ5s!TlEC` zdF|vY)-ZmbZFOexu08s%H)wJm@0s>Q_HrWkf%UI9Gz8vrx5{x{`jc(WtZQ?F)*Q|} z5xya!-S|f>YqjPauOBfJ+#Z@7dfxO?#Lr*qywaYR1$qZ8xu*qN=yM)-yKWX(k$CAx z(5`LG2d*_8so1seed5Mln>P8~SyW+ay6Bth(X>$R)w4uGuLQL2uoDp2uKD+0hRYo_ z_mtf2Z&u%UH~r{buNC*%O_jc_RGc9*$u#oINx`M|D!*OMcidR0DOfmJisO`EAlJ_$ zY5SM3GTdA6<uqfdM~G8WsI#-`T;>I<oX&Wh^-x_hnPb(gOY2*fI3&2J{C9XIyx-tO zXw9S@H&?eQ`4#S6w$k~6kW_4-XLm<n=$BOo3mo`PEnFeNn)Rz|g^kS8DM#%zS1{Nn zO!RSYSaWc@*h&V)mu`N6lS9|Wb7`7S;JfKCnR$~4qnyO^tBXWDgQ8;O{k+UxT~BeI zv|-u1u$oO8oeP>4NCjOv$YCH59_G^5l{J5bz|4f{8`!G&TpPufvOGE<r8QM*5~u9} zUgq^$FIxHd-BNvfdR!J2O;|ZgXYx!gjk5h}t$(}3FR&kMI$*E!V)C9Mm374dTUn-V zys$hgDSdOW${M%OvzmM-l;uJ%)ueD}G_=fmBKx5sa*m72{1=y}+%<R;ti)Vo;w7-e zpg_-6X^r%?7xVls{M`^dW$`^O^^5l=@SJ1KO}cHT>aa!ZLt~AD|5v^s#e`%}&JEk$ zWv3-3cyRt)@pdnx>Y8Hr6!}RG{BoTJL5g{Nos-Tl7w|7rt6U@WiH*(i@2_LdURU(1 zLMj?#c%(yIJ5;xDPYe3x(5k54AuRdjm*)Rjht4cn#kj1}evi_Ta+OV!Z^$cXU6{!; zd16O+r%Q;^9Cx)>pI12Y`}|IQ->|}a+L9RyxZFI`yg0r79w?u~7|9fE{O!dyMh<ZU z!3yUK%r=5afxf&e{$BZIHB07`)+}>|dE!i8vxJT^>+_t5+r;}(Les;hhr{{kqHC8! z`d$87%QVO`NH1bu#F-=+pec2rS>S8k;f+?Itv?MHKV(*m5MT1@@{`x>o;}}q+g@xt zqT@MlRzc8(?OxA#yeCOaKae31xVeH)diP7Wj&l>2I)sT9{B}qeIxE|<LHx%{X`TsZ zYlV_oPw<{pc^!DImMhY6##hZ*Yl^+qlrBzW5ZuNaQ@(wPW>HDYNhu!hdm0-jq#FoK zSbRpn=tY{t`LI=Pmv~Cw&UG<vIm%MZU|jX_$p!t@j+U!5<0jth%Xs?BF=5UPXWpJK zmuomQH_U(QHnX`UV&a7ZW;^Q*6f%1sO<U(S>(ly{-i~)!+FGk;ty{5jb?@V8uXlJV zGK+<;Snv5D?Jw6vwjIj*G}%>i8CaTTF3G!phn?%9x#1Z;4_zbQTY+VEI~p3i%s;GZ zR-7>9g{foGZ;yzj=L{e9yB+S;yMEz9fa`@B>lz$NUAH=3sE;TM4Bp+wb1#-tYvREI z3x_RU0a{1oN(F*Wah&WrFy+vk7CA+M1A)^n9AeCDc{y3l@ByEUu}dzuqEN`9z_L4n z3z@pY`2?d31Qn$&Hrh>0^UdH4Ym&8)sA;~ZF+=EFXlsgCXm>tCmsC^02lYZTm7~Se z^$+fqYC60mBvS8`Bfp`i%-kK{(hIyVX3k>LS6Y|qqIkO@(rfuu7t?@M2lNU~2_$Dd z;c4ZO;aV{%f7>?|<@?=*vsX0-dMqh;B526j7thy{o-MLYYN4R)TnU$j?91an3b?Oy zP+X$K%Q~@zX$80ILS9ArJ*E}%Zb~t%+6%8RZei6t!TngP%i%3^jG*b|tBJp+CActZ zu*`p_6vh9A=|W!f#=0l%J2~E-yl|>vuk<~;19$j}jn2Gb*&~qf>&vu+?}1r4PX%IK z4Qi*bEtFbh;_}2%`NO-0%GO7DO{I(zJeS$32|m2)|CH;+V`&Xu$zNIl;xG3pt#aZG z(3*U0<3ug3P&TinS07a6@r9o9v|p+bXe;oKL*|O*KPD-bSgnxDO1F3n779E1t+MRR zs`Pj-U1RV%qh!6tneLKMfi3<jN>iM#s0Gg5ezV#oF=xg(X&d2=f+a8h80t!`R<_zM z{%ZD{_=V9%ih>U(pYT(8zg$N9Mca=a#YVTSy3Y2CUMogEzbax}#Oc5LhW#ARJCmjb z?7yIv=#^@IwaMpY_bzc3O|g&H9S@{P?YOa-V}4-PMY~gL6V=tcXY5nn^-MCJbKZ>7 z8+M%0E2nHbD8E?x+SgUD{!Ka{x1{W{hPraV;wzU<#^t_IJO8PP<!znR%)|pqcZ^>( zSMeDA-7MRoU$N3e{P)gfulu&1jC!$HOgvECMSNrX!mApx>dMR;(pYM|uNHST9<4|) zyYk{5bHmX^M<usU>W^x5xtu3^+da9a;g+h@a)m(Iu8>0=d8Wx`KG&s-tV;LGFB3TU zx#`Hc9q)Mqec2xU-MQrLV#DMgQ4PO(msKy}ciW>7dq~J9sdJrYTc=;!%42g&;#;Fk z1i!}}2yFO$bC>ZOJC~ozMwJ>zOoALYdv-kfxnf<<oHu)1`cwsOW(Iu!cT<QzkgIN% zY=~^qH?L*7FMlP?Is0_C&N7izmikA(yt>wTLFcLerTxo-%@%nswetH`xu4JVQKfs= zX%`zif$vQ>XYm{_R5CGk%Qz!B?emLi<*zQWJv<vWSwi8TW?aJNowjNonWyG2)NK9b zuz%hAZc~NJo3GS2eiORH_T_S`tKex~$5YV?otrhz);=kD?ceqH=9QEzPF91xR~FZP znUi#H^+i!O+iBd)A;N7YZhaG1?2Y7+nC2OwEm++m&o68KseDFNoXWiYH*Ow0r&gwS z`d4RqK=R|}Uy<+kYb2K*T4SUwum1VrWz&f^t6xs^x%Iknlg#sk30cP{T<e&&v-nla z%qc&&OU0Kb`ba(BvBZ6rzkQ|t(&rysH=XTkc@bYZJ*h0TbXV=J`JSHN`I?j0uajTY z?GRHGe<@bK@RC5ZWcl8fYqch?et!vW$-L%n>$lHg`!TLZJC%Rw_C{>;J-=(Wrasr% zV&%<my!QAny3!-@DWHy7#N1LT<!(Nk-j3*rvOTwbl(OIV{O(zJbf<~<Mco+>-ane{ zY`<5U_2mq=y>4?v%iGoq{Ga?Kx;1#a#m?DZ)bH}&)ZI`&Er;{0=P%w|J^34M$~hCB zx$~MT6<vO{Xl>!?{Xx@DeApeeAcXV#X0;O5RgFJyS-zMS?@<0Oxb5hUNmH(?*v|fX z`R0muiu;!A(Y$Q4^5U7*$F85Tz1{v#Jzk@&URrRo^Zu<TJk8%~c>Sv@cqy~OQ>Y?S zjq9bE>7P<Py=~3BhfRc~XC}8C^AvgDw%~2k3&o{P_l~EpUw6SjMEq`g!1ww|`LUjX zZ!hHwoUAH8dy?BS|B%UNseM@iig&&T%)Fu7>LR&&xuLJ-W+Tr>+Af#<*F2Gqno)K5 zgwB7TJ4amp_6hbrXYt&>{`8l;buYeeKXQF;;!1|89?LEs_>rWoR46~w_LBJYgOcy1 zw>-PV_dDssMPtiV?yA4zLLZfjndbdX;hf>l`*L~He<3UBuKU`P-^cE=EcL&(L4R`4 zfp|Y<4UhMl`@93pH+_44Ik&0mk=fCri4(W#*>F!*_2^r9v&K~QC&R>+lZW=D_MEsf z;iK%ScLx-!&UsItVp3RiZr^?BYo2|<Q}4On?445kRPvh1iytp%O8@hbPu-u@QgT{l zg|y(J7g1{eoPT^hV(i7bOGdRWb>ieIp|8)M+>gECd;dU%^fTv@R+sbQ@=E)!Z}|Vb z>B7&@C;vB|{h6>=SM|Tw3+|R*r8h(pH%vYd?&{XG<e$0Q+MH$_^LC&$3gCIWXa$34 z(0p&Kf`Xy3VYGsYsRfv69<5+uX&9@3Iwx+fU;tmF0bU3SyTk~-TsOkkM<06Qjgf_g zv7H?+7i^x-#=fE;F*!T6L?J0PJu}Z%>HY5gN(z}Nwo2iqz6QPp&Z!xh9#uuD!Bu`C z$yM3OmMKd1b~Y7O6}bhusU?XD6}dTi#a0!zN{OKLs#bZ$Rv=-0B?YjOl5ATgV?9G% za|1&qJ1(1|lr*a#7dNP;qLegSrHqo20xNy}^73-Ma$~*xqI7*jOG`_A10#JSBi*8u zG~MFLypqHU-MnIDm<h0@wwd{P3Lp~`lk!VTY?YK0pyn1JnVVOv2X>fVa(=FUK}wpw zp`Nim$RK@?P9&3{u0s;RYIaI8+{p!{MLA#xrzGpALRRP&>l^ABQeu2XZUNkU6sK1t z7U&!58Gy~M$Stq}udGQ0t(vg|t*X&AG|)9R2r)3RGBL6;0WGbuGBB{wM^kA7;``>O zWTsUTqZ3J`4Vl4Jkz3&Fi{D4NxiC-p`dYc<CzpbjI(xd<m6m3vSeZH+SX#PTnCMzK znz`tjSXvtBT9`Vz=vq3NnVGn{I2&6!n}EHEsu$!@UtcTFyyB9?yyR4JOheL(#q$bu z09<siLt)esl0tbjxJHAEq!1uU@o4HA4K9*GfF#AEsf)DWf>irKE^c-<`ryVas9|e| zXfLPcrPwN!E7?Qt0fn`vVau~2w}gUQ^ENj6E}6vzIf<1n`N<)fB{`|!jgX*LI6^hX zzNUzv)HMCz%&JrcLjz0waL{&A69v$YQSd#cpjFmE`ru7ykR{ha`kpS)HWnt9t_GH- zj%F?vj+O={mY~KGa$CucunN%r%*>n;?Du6FnOlII4fAZp9NSAf{hS;{j(ub_*UM|z z(Y&Z>#g2uK4y6_|U-5YAoROcDl=_JK2h+D}+xE5kC*&q2>-1WD|6M&dsm}7`oZ@{+ zJsX$b<-1{>)pJ~UPv-({C*2+5er)?(E%b}b7tLs5{~_{@wWjmZVvYM<UCfacqE}mD zbDdSr8eUn*=xfomf4cg%^<tk_^Km#a>{`fe^-zE>r~2>ReEw!;TY)CV7wvH`nggwu zew4Rk3zTT#=s94(<0zw+$aE}0=GtCf-TK2OK8{S24A?hMXp%f}$YzNmvt>jRYeb7_ zL~Ck9qwb9sn;eIm-nGw{-)oiCIAAvAK&!3BA-gLJm_qZvY~JT<tmmiv=#Bdv@gq;$ zcX)Nb7Ei8Yd@QE@25axGq9`?um&-uG(9nRF%K!?Dj1<gFO^r<zKvGaK(9TN`Qy~wo z&d|(40VHIABxYn}f+1#VZip^sWN2iDDQ0eoA!clXsTZ^+7tLNHGXr$JhK7bl=4j>_ znpjvEV2D{-8lj7s8dzeeGc^S5I{;Y>_phO;F=)R7nwTj@SQ(m{gLYD&sk5*^_nV=q zC1~$4syZ_R3k>&}8G?2Wpr|u2FfcO4j5i|_EMlfu#LTdWnPU;Nz#?XeDQ0YdMa&S3 z7?!kVjKv?uSo~p(#UI94{9%m6AI9izHncD_#Z0>fCRqGog2f*uSo~pv#UCbE{9%H{ zA0}A*VS>dUCYb&(0&P@APaCFK{9%g4AEsFRVT#2crda%8ip3wMSo~p%#UG|v(v>N? z-wX^4%&_>w42wTNd;U?%FhdI?Z1HP`#UEx^{9%U0AE2E%D0YC{hs7UeSo~p*#UJKa z{9%qIKFqPihdCC1m}BvWITnAIV~G!QEdDUZ;tvZf{;<H}4+|{*u)yLE3oQPyz~T=J zEdH>-;t$YX7L+(Lv@ph&KP<8M!xD=>EV1~*5{o}9vG~Ihi$5%}_`?#5KP<7thb3lw z7#d*l2ZorTg)v56Ftji+#i(a2EX^?LK}!=%y_V)?rf7Ajk(q@Vdi`c(W(hjk0;Oy+ zvb3-;M%Qa;X@pdtmlP#t=A;(!a@p8Go1TUS`ZRBELRyrGMJ13nlCgn_p&>7qs;aBM s8!s0`%s|0_mup~Lryo$1TBZPYls>3U9$b>EU<3*_bY~lxo5P$90IFXuW&i*H literal 0 HcmV?d00001 diff --git a/lab3/lib/cuon-utils.js b/lab3/lib/cuon-utils.js new file mode 100644 index 0000000..dc08b2e --- /dev/null +++ b/lab3/lib/cuon-utils.js @@ -0,0 +1,113 @@ +// cuon-utils.js (c) 2012 kanda and matsuda +/** + * Create a program object and make current + * @param gl GL context + * @param vshader a vertex shader program (string) + * @param fshader a fragment shader program (string) + * @return true, if the program object was created and successfully made current + */ +function initShaders(gl, vshader, fshader) { + var program = createProgram(gl, vshader, fshader); + if (!program) { + console.log('Failed to create program'); + return false; + } + + gl.useProgram(program); + gl.program = program; + + return true; +} + +/** + * Create the linked program object + * @param gl GL context + * @param vshader a vertex shader program (string) + * @param fshader a fragment shader program (string) + * @return created program object, or null if the creation has failed + */ +function createProgram(gl, vshader, fshader) { + // Create shader object + var vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader); + var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader); + if (!vertexShader || !fragmentShader) { + return null; + } + + // Create a program object + var program = gl.createProgram(); + if (!program) { + return null; + } + + // Attach the shader objects + gl.attachShader(program, vertexShader); + gl.attachShader(program, fragmentShader); + + // Link the program object + gl.linkProgram(program); + + // Check the result of linking + var linked = gl.getProgramParameter(program, gl.LINK_STATUS); + if (!linked) { + var error = gl.getProgramInfoLog(program); + console.log('Failed to link program: ' + error); + gl.deleteProgram(program); + gl.deleteShader(fragmentShader); + gl.deleteShader(vertexShader); + return null; + } + return program; +} + +/** + * Create a shader object + * @param gl GL context + * @param type the type of the shader object to be created + * @param source shader program (string) + * @return created shader object, or null if the creation has failed. + */ +function loadShader(gl, type, source) { + // Create shader object + var shader = gl.createShader(type); + if (shader == null) { + console.log('unable to create shader'); + return null; + } + + // Set the shader program + gl.shaderSource(shader, source); + + // Compile the shader + gl.compileShader(shader); + + // Check the result of compilation + var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); + if (!compiled) { + var error = gl.getShaderInfoLog(shader); + console.log('Failed to compile shader: ' + error); + gl.deleteShader(shader); + return null; + } + + return shader; +} + +/** + * Initialize and get the rendering for WebglInstance + * @param canvas <cavnas> element + * @param opt_debug flag to initialize the context for debugging + * @return the rendering context for WebglInstance + */ +function getWebGLContext(canvas, opt_debug) { + // Get the rendering context for WebglInstance + var gl = WebGLUtils.setupWebGL(canvas); + if (!gl) return null; + + // if opt_debug is explicitly false, create the context for debugging + if (arguments.length < 2 || opt_debug) { + gl = WebGLDebugUtils.makeDebugContext(gl); + } + + return gl; +} diff --git a/lab3/lib/webgl-debug.js b/lab3/lib/webgl-debug.js new file mode 100644 index 0000000..685868d --- /dev/null +++ b/lab3/lib/webgl-debug.js @@ -0,0 +1,677 @@ +//Copyright (c) 2009 The Chromium Authors. All rights reserved. +//Use of this source code is governed by a BSD-style license that can be +//found in the LICENSE file. + +// Various functions for helping debug WebglInstance apps. + +WebGLDebugUtils = function() { + +/** + * Wrapped logging function. + * @param {string} msg Message to log. + */ +var log = function(msg) { + if (window.console && window.console.log) { + window.console.log(msg); + } +}; + +/** + * Which arguements are enums. + * @type {!Object.<number, string>} + */ +var glValidEnumContexts = { + + // Generic setters and getters + + 'enable': { 0:true }, + 'disable': { 0:true }, + 'getParameter': { 0:true }, + + // Rendering + + 'drawArrays': { 0:true }, + 'drawElements': { 0:true, 2:true }, + + // Shaders + + 'createShader': { 0:true }, + 'getShaderParameter': { 1:true }, + 'getProgramParameter': { 1:true }, + + // Vertex attributes + + 'getVertexAttrib': { 1:true }, + 'vertexAttribPointer': { 2:true }, + + // Textures + + 'bindTexture': { 0:true }, + 'activeTexture': { 0:true }, + 'getTexParameter': { 0:true, 1:true }, + 'texParameterf': { 0:true, 1:true }, + 'texParameteri': { 0:true, 1:true, 2:true }, + 'texImage2D': { 0:true, 2:true, 6:true, 7:true }, + 'texSubImage2D': { 0:true, 6:true, 7:true }, + 'copyTexImage2D': { 0:true, 2:true }, + 'copyTexSubImage2D': { 0:true }, + 'generateMipmap': { 0:true }, + + // Buffer objects + + 'bindBuffer': { 0:true }, + 'bufferData': { 0:true, 2:true }, + 'bufferSubData': { 0:true }, + 'getBufferParameter': { 0:true, 1:true }, + + // Renderbuffers and framebuffers + + 'pixelStorei': { 0:true, 1:true }, + 'readPixels': { 4:true, 5:true }, + 'bindRenderbuffer': { 0:true }, + 'bindFramebuffer': { 0:true }, + 'checkFramebufferStatus': { 0:true }, + 'framebufferRenderbuffer': { 0:true, 1:true, 2:true }, + 'framebufferTexture2D': { 0:true, 1:true, 2:true }, + 'getFramebufferAttachmentParameter': { 0:true, 1:true, 2:true }, + 'getRenderbufferParameter': { 0:true, 1:true }, + 'renderbufferStorage': { 0:true, 1:true }, + + // Frame buffer operations (clear, blend, depth test, stencil) + + 'clear': { 0:true }, + 'depthFunc': { 0:true }, + 'blendFunc': { 0:true, 1:true }, + 'blendFuncSeparate': { 0:true, 1:true, 2:true, 3:true }, + 'blendEquation': { 0:true }, + 'blendEquationSeparate': { 0:true, 1:true }, + 'stencilFunc': { 0:true }, + 'stencilFuncSeparate': { 0:true, 1:true }, + 'stencilMaskSeparate': { 0:true }, + 'stencilOp': { 0:true, 1:true, 2:true }, + 'stencilOpSeparate': { 0:true, 1:true, 2:true, 3:true }, + + // Culling + + 'cullFace': { 0:true }, + 'frontFace': { 0:true }, +}; + +/** + * Map of numbers to names. + * @type {Object} + */ +var glEnums = null; + +/** + * Initializes this module. Safe to call more than once. + * @param {!WebGLRenderingContext} ctx A WebglInstance context. If + * you have more than one context it doesn't matter which one + * you pass in, it is only used to pull out constants. + */ +function init(ctx) { + if (glEnums == null) { + glEnums = { }; + for (var propertyName in ctx) { + if (typeof ctx[propertyName] == 'number') { + glEnums[ctx[propertyName]] = propertyName; + } + } + } +} + +/** + * Checks the utils have been initialized. + */ +function checkInit() { + if (glEnums == null) { + throw 'WebGLDebugUtils.init(ctx) not called'; + } +} + +/** + * Returns true or false if value matches any WebglInstance enum + * @param {*} value Value to check if it might be an enum. + * @return {boolean} True if value matches one of the WebglInstance defined enums + */ +function mightBeEnum(value) { + checkInit(); + return (glEnums[value] !== undefined); +} + +/** + * Gets an string version of an WebglInstance enum. + * + * Example: + * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); + * + * @param {number} value Value to return an enum for + * @return {string} The string version of the enum. + */ +function glEnumToString(value) { + checkInit(); + var name = glEnums[value]; + return (name !== undefined) ? name : + ("*UNKNOWN WebglInstance ENUM (0x" + value.toString(16) + ")"); +} + +/** + * Returns the string version of a WebglInstance argument. + * Attempts to convert enum arguments to strings. + * @param {string} functionName the name of the WebglInstance function. + * @param {number} argumentIndx the index of the argument. + * @param {*} value The value of the argument. + * @return {string} The value as a string. + */ +function glFunctionArgToString(functionName, argumentIndex, value) { + var funcInfo = glValidEnumContexts[functionName]; + if (funcInfo !== undefined) { + if (funcInfo[argumentIndex]) { + return glEnumToString(value); + } + } + return value.toString(); +} + +/** + * Given a WebglInstance context returns a wrapped context that calls + * gl.getError after every command and calls a function if the + * result is not gl.NO_ERROR. + * + * @param {!WebGLRenderingContext} ctx The webgl context to + * wrap. + * @param {!function(err, funcName, args): void} opt_onErrorFunc + * The function to call when gl.getError returns an + * error. If not specified the default function calls + * console.log with a message. + */ +function makeDebugContext(ctx, opt_onErrorFunc) { + init(ctx); + opt_onErrorFunc = opt_onErrorFunc || function(err, functionName, args) { + // apparently we can't do args.join(","); + var argStr = ""; + for (var ii = 0; ii < args.length; ++ii) { + argStr += ((ii == 0) ? '' : ', ') + + glFunctionArgToString(functionName, ii, args[ii]); + } + log("WebglInstance error "+ glEnumToString(err) + " in "+ functionName + + "(" + argStr + ")"); + }; + + // Holds booleans for each GL error so after we get the error ourselves + // we can still return it to the client app. + var glErrorShadow = { }; + + // Makes a function that calls a WebglInstance function and then calls getError. + function makeErrorWrapper(ctx, functionName) { + return function() { + var result = ctx[functionName].apply(ctx, arguments); + var err = ctx.getError(); + if (err != 0) { + glErrorShadow[err] = true; + opt_onErrorFunc(err, functionName, arguments); + } + return result; + }; + } + + // Make a an object that has a copy of every property of the WebglInstance context + // but wraps all functions. + var wrapper = {}; + for (var propertyName in ctx) { + if (typeof ctx[propertyName] == 'function') { + wrapper[propertyName] = makeErrorWrapper(ctx, propertyName); + } else { + wrapper[propertyName] = ctx[propertyName]; + } + } + + // Override the getError function with one that returns our saved results. + wrapper.getError = function() { + for (var err in glErrorShadow) { + if (glErrorShadow[err]) { + glErrorShadow[err] = false; + return err; + } + } + return ctx.NO_ERROR; + }; + + return wrapper; +} + +function resetToInitialState(ctx) { + var numAttribs = ctx.getParameter(ctx.MAX_VERTEX_ATTRIBS); + var tmp = ctx.createBuffer(); + ctx.bindBuffer(ctx.ARRAY_BUFFER, tmp); + for (var ii = 0; ii < numAttribs; ++ii) { + ctx.disableVertexAttribArray(ii); + ctx.vertexAttribPointer(ii, 4, ctx.FLOAT, false, 0, 0); + ctx.vertexAttrib1f(ii, 0); + } + ctx.deleteBuffer(tmp); + + var numTextureUnits = ctx.getParameter(ctx.MAX_TEXTURE_IMAGE_UNITS); + for (var ii = 0; ii < numTextureUnits; ++ii) { + ctx.activeTexture(ctx.TEXTURE0 + ii); + ctx.bindTexture(ctx.TEXTURE_CUBE_MAP, null); + ctx.bindTexture(ctx.TEXTURE_2D, null); + } + + ctx.activeTexture(ctx.TEXTURE0); + ctx.useProgram(null); + ctx.bindBuffer(ctx.ARRAY_BUFFER, null); + ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null); + ctx.bindFramebuffer(ctx.FRAMEBUFFER, null); + ctx.bindRenderbuffer(ctx.RENDERBUFFER, null); + ctx.disable(ctx.BLEND); + ctx.disable(ctx.CULL_FACE); + ctx.disable(ctx.DEPTH_TEST); + ctx.disable(ctx.DITHER); + ctx.disable(ctx.SCISSOR_TEST); + ctx.blendColor(0, 0, 0, 0); + ctx.blendEquation(ctx.FUNC_ADD); + ctx.blendFunc(ctx.ONE, ctx.ZERO); + ctx.clearColor(0, 0, 0, 0); + ctx.clearDepth(1); + ctx.clearStencil(-1); + ctx.colorMask(true, true, true, true); + ctx.cullFace(ctx.BACK); + ctx.depthFunc(ctx.LESS); + ctx.depthMask(true); + ctx.depthRange(0, 1); + ctx.frontFace(ctx.CCW); + ctx.hint(ctx.GENERATE_MIPMAP_HINT, ctx.DONT_CARE); + ctx.lineWidth(1); + ctx.pixelStorei(ctx.PACK_ALIGNMENT, 4); + ctx.pixelStorei(ctx.UNPACK_ALIGNMENT, 4); + ctx.pixelStorei(ctx.UNPACK_FLIP_Y_WEBGL, false); + ctx.pixelStorei(ctx.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); + // TODO: Delete this IF. + if (ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL) { + ctx.pixelStorei(ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL, ctx.BROWSER_DEFAULT_WEBGL); + } + ctx.polygonOffset(0, 0); + ctx.sampleCoverage(1, false); + ctx.scissor(0, 0, ctx.canvas.width, ctx.canvas.height); + ctx.stencilFunc(ctx.ALWAYS, 0, 0xFFFFFFFF); + ctx.stencilMask(0xFFFFFFFF); + ctx.stencilOp(ctx.KEEP, ctx.KEEP, ctx.KEEP); + ctx.viewport(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight); + ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT | ctx.STENCIL_BUFFER_BIT); + + // TODO: This should NOT be needed but Firefox fails with 'hint' + while(ctx.getError()); +} + +function makeLostContextSimulatingContext(ctx) { + var wrapper_ = {}; + var contextId_ = 1; + var contextLost_ = false; + var resourceId_ = 0; + var resourceDb_ = []; + var onLost_ = undefined; + var onRestored_ = undefined; + var nextOnRestored_ = undefined; + + // Holds booleans for each GL error so can simulate errors. + var glErrorShadow_ = { }; + + function isWebGLObject(obj) { + //return false; + return (obj instanceof WebGLBuffer || + obj instanceof WebGLFramebuffer || + obj instanceof WebGLProgram || + obj instanceof WebGLRenderbuffer || + obj instanceof WebGLShader || + obj instanceof WebGLTexture); + } + + function checkResources(args) { + for (var ii = 0; ii < args.length; ++ii) { + var arg = args[ii]; + if (isWebGLObject(arg)) { + return arg.__webglDebugContextLostId__ == contextId_; + } + } + return true; + } + + function clearErrors() { + var k = Object.keys(glErrorShadow_); + for (var ii = 0; ii < k.length; ++ii) { + delete glErrorShdow_[k]; + } + } + + // Makes a function that simulates WebglInstance when out of context. + function makeLostContextWrapper(ctx, functionName) { + var f = ctx[functionName]; + return function() { + // Only call the functions if the context is not lost. + if (!contextLost_) { + if (!checkResources(arguments)) { + glErrorShadow_[ctx.INVALID_OPERATION] = true; + return; + } + var result = f.apply(ctx, arguments); + return result; + } + }; + } + + for (var propertyName in ctx) { + if (typeof ctx[propertyName] == 'function') { + wrapper_[propertyName] = makeLostContextWrapper(ctx, propertyName); + } else { + wrapper_[propertyName] = ctx[propertyName]; + } + } + + function makeWebGLContextEvent(statusMessage) { + return {statusMessage: statusMessage}; + } + + function freeResources() { + for (var ii = 0; ii < resourceDb_.length; ++ii) { + var resource = resourceDb_[ii]; + if (resource instanceof WebGLBuffer) { + ctx.deleteBuffer(resource); + } else if (resource instanceof WebctxFramebuffer) { + ctx.deleteFramebuffer(resource); + } else if (resource instanceof WebctxProgram) { + ctx.deleteProgram(resource); + } else if (resource instanceof WebctxRenderbuffer) { + ctx.deleteRenderbuffer(resource); + } else if (resource instanceof WebctxShader) { + ctx.deleteShader(resource); + } else if (resource instanceof WebctxTexture) { + ctx.deleteTexture(resource); + } + } + } + + wrapper_.loseContext = function() { + if (!contextLost_) { + contextLost_ = true; + ++contextId_; + while (ctx.getError()); + clearErrors(); + glErrorShadow_[ctx.CONTEXT_LOST_WEBGL] = true; + setTimeout(function() { + if (onLost_) { + onLost_(makeWebGLContextEvent("context lost")); + } + }, 0); + } + }; + + wrapper_.restoreContext = function() { + if (contextLost_) { + if (onRestored_) { + setTimeout(function() { + freeResources(); + resetToInitialState(ctx); + contextLost_ = false; + if (onRestored_) { + var callback = onRestored_; + onRestored_ = nextOnRestored_; + nextOnRestored_ = undefined; + callback(makeWebGLContextEvent("context restored")); + } + }, 0); + } else { + throw "You can not restore the context without a listener" + } + } + }; + + // Wrap a few functions specially. + wrapper_.getError = function() { + if (!contextLost_) { + var err; + while (err = ctx.getError()) { + glErrorShadow_[err] = true; + } + } + for (var err in glErrorShadow_) { + if (glErrorShadow_[err]) { + delete glErrorShadow_[err]; + return err; + } + } + return ctx.NO_ERROR; + }; + + var creationFunctions = [ + "createBuffer", + "createFramebuffer", + "createProgram", + "createRenderbuffer", + "createShader", + "createTexture" + ]; + for (var ii = 0; ii < creationFunctions.length; ++ii) { + var functionName = creationFunctions[ii]; + wrapper_[functionName] = function(f) { + return function() { + if (contextLost_) { + return null; + } + var obj = f.apply(ctx, arguments); + obj.__webglDebugContextLostId__ = contextId_; + resourceDb_.push(obj); + return obj; + }; + }(ctx[functionName]); + } + + var functionsThatShouldReturnNull = [ + "getActiveAttrib", + "getActiveUniform", + "getBufferParameter", + "getContextAttributes", + "getAttachedShaders", + "getFramebufferAttachmentParameter", + "getParameter", + "getProgramParameter", + "getProgramInfoLog", + "getRenderbufferParameter", + "getShaderParameter", + "getShaderInfoLog", + "getShaderSource", + "getTexParameter", + "getUniform", + "getUniformLocation", + "getVertexAttrib" + ]; + for (var ii = 0; ii < functionsThatShouldReturnNull.length; ++ii) { + var functionName = functionsThatShouldReturnNull[ii]; + wrapper_[functionName] = function(f) { + return function() { + if (contextLost_) { + return null; + } + return f.apply(ctx, arguments); + } + }(wrapper_[functionName]); + } + + var isFunctions = [ + "isBuffer", + "isEnabled", + "isFramebuffer", + "isProgram", + "isRenderbuffer", + "isShader", + "isTexture" + ]; + for (var ii = 0; ii < isFunctions.length; ++ii) { + var functionName = isFunctions[ii]; + wrapper_[functionName] = function(f) { + return function() { + if (contextLost_) { + return false; + } + return f.apply(ctx, arguments); + } + }(wrapper_[functionName]); + } + + wrapper_.checkFramebufferStatus = function(f) { + return function() { + if (contextLost_) { + return ctx.FRAMEBUFFER_UNSUPPORTED; + } + return f.apply(ctx, arguments); + }; + }(wrapper_.checkFramebufferStatus); + + wrapper_.getAttribLocation = function(f) { + return function() { + if (contextLost_) { + return -1; + } + return f.apply(ctx, arguments); + }; + }(wrapper_.getAttribLocation); + + wrapper_.getVertexAttribOffset = function(f) { + return function() { + if (contextLost_) { + return 0; + } + return f.apply(ctx, arguments); + }; + }(wrapper_.getVertexAttribOffset); + + wrapper_.isContextLost = function() { + return contextLost_; + }; + + function wrapEvent(listener) { + if (typeof(listener) == "function") { + return listener; + } else { + return function(info) { + listener.handleEvent(info); + } + } + } + + wrapper_.registerOnContextLostListener = function(listener) { + onLost_ = wrapEvent(listener); + }; + + wrapper_.registerOnContextRestoredListener = function(listener) { + if (contextLost_) { + nextOnRestored_ = wrapEvent(listener); + } else { + onRestored_ = wrapEvent(listener); + } + } + + return wrapper_; +} + +return { + /** + * Initializes this module. Safe to call more than once. + * @param {!WebGLRenderingContext} ctx A WebglInstance context. If + * you have more than one context it doesn't matter which one + * you pass in, it is only used to pull out constants. + */ + 'init': init, + + /** + * Returns true or false if value matches any WebglInstance enum + * @param {*} value Value to check if it might be an enum. + * @return {boolean} True if value matches one of the WebglInstance defined enums + */ + 'mightBeEnum': mightBeEnum, + + /** + * Gets an string version of an WebglInstance enum. + * + * Example: + * WebGLDebugUtil.init(ctx); + * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); + * + * @param {number} value Value to return an enum for + * @return {string} The string version of the enum. + */ + 'glEnumToString': glEnumToString, + + /** + * Converts the argument of a WebglInstance function to a string. + * Attempts to convert enum arguments to strings. + * + * Example: + * WebGLDebugUtil.init(ctx); + * var str = WebGLDebugUtil.glFunctionArgToString('bindTexture', 0, gl.TEXTURE_2D); + * + * would return 'TEXTURE_2D' + * + * @param {string} functionName the name of the WebglInstance function. + * @param {number} argumentIndx the index of the argument. + * @param {*} value The value of the argument. + * @return {string} The value as a string. + */ + 'glFunctionArgToString': glFunctionArgToString, + + /** + * Given a WebglInstance context returns a wrapped context that calls + * gl.getError after every command and calls a function if the + * result is not NO_ERROR. + * + * You can supply your own function if you want. For example, if you'd like + * an exception thrown on any GL error you could do this + * + * function throwOnGLError(err, funcName, args) { + * throw WebGLDebugUtils.glEnumToString(err) + " was caused by call to" + + * funcName; + * }; + * + * ctx = WebGLDebugUtils.makeDebugContext( + * canvas.getContext("webgl"), throwOnGLError); + * + * @param {!WebGLRenderingContext} ctx The webgl context to wrap. + * @param {!function(err, funcName, args): void} opt_onErrorFunc The function + * to call when gl.getError returns an error. If not specified the default + * function calls console.log with a message. + */ + 'makeDebugContext': makeDebugContext, + + /** + * Given a WebglInstance context returns a wrapped context that adds 4 + * functions. + * + * ctx.loseContext: + * simulates a lost context event. + * + * ctx.restoreContext: + * simulates the context being restored. + * + * ctx.registerOnContextLostListener(listener): + * lets you register a listener for context lost. Use instead + * of addEventListener('webglcontextlostevent', listener); + * + * ctx.registerOnContextRestoredListener(listener): + * lets you register a listener for context restored. Use + * instead of addEventListener('webglcontextrestored', + * listener); + * + * @param {!WebGLRenderingContext} ctx The webgl context to wrap. + */ + 'makeLostContextSimulatingContext': makeLostContextSimulatingContext, + + /** + * Resets a context to the initial state. + * @param {!WebGLRenderingContext} ctx The webgl context to + * reset. + */ + 'resetToInitialState': resetToInitialState +}; + +}(); + diff --git a/lab3/lib/webgl-utils.js b/lab3/lib/webgl-utils.js new file mode 100644 index 0000000..26ed37e --- /dev/null +++ b/lab3/lib/webgl-utils.js @@ -0,0 +1,197 @@ +/* + * Copyright 2010, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +/** + * @fileoverview This file contains functions every webgl program will need + * a version of one way or another. + * + * Instead of setting up a context manually it is recommended to + * use. This will check for success or failure. On failure it + * will attempt to present an approriate message to the user. + * + * gl = WebGLUtils.setupWebGL(canvas); + * + * For animated WebglInstance apps use of setTimeout or setInterval are + * discouraged. It is recommended you structure your rendering + * loop like this. + * + * function render() { + * window.requestAnimationFrame(render, canvas); + * + * // do rendering + * ... + * } + * render(); + * + * This will call your rendering function up to the refresh rate + * of your display but will stop rendering if your app is not + * visible. + */ + +WebGLUtils = function() { + +/** + * Creates the HTLM for a failure message + * @param {string} canvasContainerId id of container of th + * canvas. + * @return {string} The html. + */ +var makeFailHTML = function(msg) { + return '' + + '<div style="margin: auto; width:500px;z-index:10000;margin-top:20em;text-align:center;">' + msg + '</div>'; + return '' + + '<table style="background-color: #8CE; width: 100%; height: 100%;"><tr>' + + '<td align="center">' + + '<div style="display: table-cell; vertical-align: middle;">' + + '<div style="">' + msg + '</div>' + + '</div>' + + '</td></tr></table>'; +}; + +/** + * Mesasge for getting a webgl browser + * @type {string} + */ +var GET_A_WEBGL_BROWSER = '' + + 'This page requires a browser that supports WebglInstance.<br/>' + + '<a href="http://get.webgl.org">Click here to upgrade your browser.</a>'; + +/** + * Mesasge for need better hardware + * @type {string} + */ +var OTHER_PROBLEM = '' + + "It doesn't appear your computer can support WebglInstance.<br/>" + + '<a href="http://get.webgl.org">Click here for more information.</a>'; + +/** + * Creates a webgl context. If creation fails it will + * change the contents of the container of the <canvas> + * tag to an error message with the correct links for WebglInstance. + * @param {Element} canvas. The canvas element to create a + * context from. + * @param {WebGLContextCreationAttirbutes} opt_attribs Any + * creation attributes you want to pass in. + * @param {function:(msg)} opt_onError An function to call + * if there is an error during creation. + * @return {WebGLRenderingContext} The created context. + */ +var setupWebGL = function(canvas, opt_attribs, opt_onError) { + function handleCreationError(msg) { + var container = document.getElementsByTagName("body")[0]; + //var container = canvas.parentNode; + if (container) { + var str = window.WebGLRenderingContext ? + OTHER_PROBLEM : + GET_A_WEBGL_BROWSER; + if (msg) { + str += "<br/><br/>Status: " + msg; + } + container.innerHTML = makeFailHTML(str); + } + }; + + opt_onError = opt_onError || handleCreationError; + + if (canvas.addEventListener) { + canvas.addEventListener("webglcontextcreationerror", function(event) { + opt_onError(event.statusMessage); + }, false); + } + var context = create3DContext(canvas, opt_attribs); + if (!context) { + if (!window.WebGLRenderingContext) { + opt_onError(""); + } else { + opt_onError(""); + } + } + + return context; +}; + +/** + * Creates a webgl context. + * @param {!Canvas} canvas The canvas tag to get context + * from. If one is not passed in one will be created. + * @return {!WebGLContext} The created context. + */ +var create3DContext = function(canvas, opt_attribs) { + var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"]; + var context = null; + for (var ii = 0; ii < names.length; ++ii) { + try { + context = canvas.getContext(names[ii], opt_attribs); + } catch(e) {} + if (context) { + break; + } + } + return context; +} + +return { + create3DContext: create3DContext, + setupWebGL: setupWebGL +}; +}(); + +/** + * Provides requestAnimationFrame in a cross browser + * way. + */ +if (!window.requestAnimationFrame) { + window.requestAnimationFrame = (function() { + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) { + window.setTimeout(callback, 1000/60); + }; + })(); +} + +/** * ERRATA: 'cancelRequestAnimationFrame' renamed to 'cancelAnimationFrame' to reflect an update to the W3C Animation-Timing Spec. + * + * Cancels an animation frame request. + * Checks for cross-browser support, falls back to clearTimeout. + * @param {number} Animation frame request. */ +if (!window.cancelAnimationFrame) { + window.cancelAnimationFrame = (window.cancelRequestAnimationFrame || + window.webkitCancelAnimationFrame || window.webkitCancelRequestAnimationFrame || + window.mozCancelAnimationFrame || window.mozCancelRequestAnimationFrame || + window.msCancelAnimationFrame || window.msCancelRequestAnimationFrame || + window.oCancelAnimationFrame || window.oCancelRequestAnimationFrame || + window.clearTimeout); +} \ No newline at end of file diff --git a/lab3/src/lab3.html b/lab3/src/lab3.html new file mode 100644 index 0000000..bc58e57 --- /dev/null +++ b/lab3/src/lab3.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>Lab 3</title> +</head> +<body onload="main()"> +<canvas width="400" height="600" id="my-canvas"> + Please use a browser that supports "canvas" +</canvas> +<script src="../lib/webgl-utils.js"></script> +<script src="../lib/webgl-debug.js"></script> +<script src="../lib/cuon-utils.js"></script> +<script src="lab3.js"></script> +</body> +</html> \ No newline at end of file diff --git a/lab3/src/lab3.js b/lab3/src/lab3.js new file mode 100644 index 0000000..105a227 --- /dev/null +++ b/lab3/src/lab3.js @@ -0,0 +1,18 @@ +// Vertex shader program +const VSHADER_SOURCE = + '\n' + + // TODO: Implement your vertex shader code here + '\n'; + +// Fragment shader program +const FSHADER_SOURCE = + '\n' + + // TODO: Implement your fragment shader code here + '\n'; + +function main() { + // Retrieve <canvas> element + const canvas = document.getElementById('my-canvas'); + + // TODO: Complete with your code here +} \ No newline at end of file -- GitLab