From 216ef470c58397724ca0565b751999ba2a740361 Mon Sep 17 00:00:00 2001 From: Paul Mathieu Date: Tue, 8 Nov 2022 00:33:51 -0800 Subject: [PATCH] skycraft: orbit computatino still borken But we got ourselves a spaceship! Improved the camera a bit. Now we can see the ship's orbit. --- skycraft/index.js | 99 ++++++++++++++++++++++------------------- skycraft/linalg.js | 38 ++++++++++++++++ skycraft/spaceship.stl | Bin 0 -> 97684 bytes skycraft/stl.js | 76 +++++++++++++++++++++++++++++++ 4 files changed, 166 insertions(+), 47 deletions(-) create mode 100644 skycraft/linalg.js create mode 100644 skycraft/spaceship.stl create mode 100644 skycraft/stl.js diff --git a/skycraft/index.js b/skycraft/index.js index 52ae1c1..f4731b8 100644 --- a/skycraft/index.js +++ b/skycraft/index.js @@ -3,43 +3,8 @@ import * as se3 from '../se3'; import {loadTexture, makeProgram} from '../gl'; import {makeFace, makeBufferFromFaces} from '../geometry'; - -const linalg = { - cross: (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], - ]; - }, - diff: (a, b) => { - return [ - a[0] - b[0], - a[1] - b[1], - a[2] - b[2], - ]; - }, - add: (a, b) => { - return [ - a[0] + b[0], - a[1] + b[1], - a[2] + b[2], - ]; - }, - norm: a => { - return Math.sqrt(a[0]**2 + a[1]**2 + a[2]**2); - }, - dot: (a, b) => { - return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; - }, - scale: (a, s) => { - return [ - a[0] * s, - a[1] * s, - a[2] * s, - ]; - }, -}; +import { loadStlModel } from './stl'; +import * as linalg from './linalg'; const VSHADER = ` attribute vec3 aPosition; @@ -462,7 +427,7 @@ function closeToPlanet(context) { } function makeSun(seed) { - const radius = 79; + const radius = 7; const radiusChunks = Math.floor(radius / CHUNKSIZE); const chunks = []; @@ -756,6 +721,9 @@ function handleInput(context) { case 'KeyD': move(0.0, 0.5); return; + case 'KeyR': + context.timeOffset += 1; + return; } }); } @@ -998,6 +966,7 @@ function getCartesianState(orbit, mu, time) { const rd = e * Math.sqrt(mu / p) * Math.sin(nu); if (orbit.tf === undefined) { + // FIXME: this is actually borken. :/ orbit.tf = [se3.rotz(Om), se3.rotx(i), se3.rotz(w)].reduce(se3.product); } @@ -1023,11 +992,14 @@ function makeOrbitObject(context, orbit, parentPosition) { const {gl} = context; const position = parentPosition; const glContext = context.orbitGlContext; - const orientation = [ - se3.rotz(orbit.ascendingNodeLongitude), - se3.rotx(orbit.inclination), - se3.rotz(orbit.periapsisArgument), - ].reduce(se3.product); + const orientation = orbit.tf; + + // FIXME: currently borken. + // const orientation = [ + // se3.rotz(orbit.ascendingNodeLongitude), + // se3.rotx(orbit.inclination), + // se3.rotz(orbit.periapsisArgument), + // ].reduce(se3.product); const buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); @@ -1062,7 +1034,7 @@ const kGravitationalConstant = 6.674e-11; function getObjects(context, body, parentPosition) { const objects = []; - const {gl, glContext} = context; + const {gl, glContext, player} = context; const {position, orientation} = body; if (body.glBuffer === undefined) { @@ -1078,6 +1050,19 @@ function getObjects(context, body, parentPosition) { if (parentPosition !== undefined) { const orbitObject = makeOrbitObject(context, body.orbit, parentPosition); objects.push(orbitObject); + } else { + const shipOrientation = [ + se3.rotationOnly(player.tf), + se3.rotationOnly(context.camera.tf), + se3.rotxyz(-Math.PI / 2, 0, Math.PI / 2), + ].reduce(se3.product); + const shipPos = player.position; + objects.push({ + geometry: makeBufferFromFaces(gl, context.spaceship), + orientation: shipOrientation, + position: shipPos, + glContext, + }); } if (body.children !== undefined) { @@ -1108,7 +1093,12 @@ function draw(context) { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - const viewMatrix = se3.inverse(se3.product(context.player.tf, context.camera.tf)); + const viewMatrix = se3.inverse([ + context.player.tf, // player position & orientation + + context.camera.tf, // camera orientation relative to player + se3.translation(0, 1, 4), // step back from the player + ].reduce(se3.product)); let lastGlContext; for (const {position, orientation, geometry, glContext} of objects) { @@ -1135,7 +1125,8 @@ function draw(context) { function tick(time, context) { handleInput(context); - updatePhysics(time * 0.001, context); + const simTime = time * 0.001 + context.timeOffset; + updatePhysics(simTime, context); const campos = context.player.position; @@ -1198,6 +1189,15 @@ async function main() { return; } + // TODO + // [ ] loading bar + // [ ] spaceship + // [ ] landing + // [ ] huge planets + // [ ] lighting + + const modelPromise = loadStlModel('spaceship.stl'); + const context = { gl, projMatrix: se3.perspective(Math.PI / 3, canvas.clientWidth / canvas.clientHeight, 0.1, 10000.0), @@ -1213,7 +1213,7 @@ async function main() { keys: new Set(), lightDirection: [-0.2, -0.5, 0.4], skyColor: [0.10, 0.15, 0.2], - ambiantLight: 0.7, + ambiantLight: 0.4, blockSelectDistance: 8, flying: true, isOnGround: false, @@ -1221,6 +1221,7 @@ async function main() { jumpForce: 6.5, // objects: makeObjects(gl), universe: getSolarSystem(0), + timeOffset: 0, }; context.glContext = await initWorldGl(gl); @@ -1228,6 +1229,10 @@ async function main() { initUiListeners(canvas, context); // setupParamPanel(context); + const starshipGeom = await modelPromise; + console.log(`loaded ${starshipGeom.length} triangles`); + + context.spaceship = starshipGeom; requestAnimationFrame(time => tick(time, context)); } diff --git a/skycraft/linalg.js b/skycraft/linalg.js new file mode 100644 index 0000000..0dfae12 --- /dev/null +++ b/skycraft/linalg.js @@ -0,0 +1,38 @@ +export function cross(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], + ]; +} + +export function diff(a, b) { + return [ + a[0] - b[0], + a[1] - b[1], + a[2] - b[2], + ]; +} + +export function add(a, b) { + return [ + a[0] + b[0], + a[1] + b[1], + a[2] + b[2], + ]; +} + +export function norm(a) { + return Math.sqrt(a[0] ** 2 + a[1] ** 2 + a[2] ** 2); +} + +export function dot(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} +export function scale(a, s) { + return [ + a[0] * s, + a[1] * s, + a[2] * s, + ]; +} \ No newline at end of file diff --git a/skycraft/spaceship.stl b/skycraft/spaceship.stl new file mode 100644 index 0000000000000000000000000000000000000000..a9a8783cc17441a82c15ea378ccb68811f9dcaaf GIT binary patch literal 97684 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 z z86}sn|HyqTL`esqE1OYTdH=YEWgGus4!0?Y0~!dI(l3REtTW*A;pFMxLoH$NMUMF&8rB-Br_KpRyUJD|0*Dq=3m(zgs zuGabvBn$9xY6-h<-KJ72Mahpr9Mggb-KiMa{VsWf{PW@O46BpWa>XRwUhRBEM+ zt;atG5xR@jwq~Bl=`-v&6y`jJEWpoEOW0Y)Z7Q`AJD&vtzAX?grC(B>GkH_AA?Cc9 zEWk zsMb6y=~U+W!gB0~3aOjLHlGGqZ#+pz1t^+?q7&1f|`eQl2i z!PiP}z7|AC>6addamZZ2=c*4kGoKmly4O5jH|CAHnJ>suX=KpM^8bMt^W0_y!lm>} zYJ>R?ZRpJCkH`YlEfwxtx2e=hHR#R&@k9$Ebk{FQt2|!m`(rn<0Ch`+`_^qLwbJce z8-uu`1rfTxO6;oNHch>&-M-z)0_2M#MPhi(Z7Q`|9K<0Xg9zOfC9L!H4&`>Bd_#q9 zr_e1G9&v6{sgWm07~K@(5$Ew*YPCLyEkTfRI13_l z=Y(TtfIS`dT(ILLW&p}s)}0g1mfl{8IF%W|Dj?o%AYAI*2hN@R{wsI&wuTRLR@gXB znV!ro6(y&Zt2nx!>XEdAHIf;?(zNjoZ8$@^hGiR%FauZ}1X+y*!lgJl+Z(c6e7-Na z)L-Q09%o3`P|rhJX%*+q!8v-xCza$TmW5(#CHJPnye>g+BO{G@aG10kH`P>mM zrN4(and!sl%D(=>)lYSN#BrNStuzDJ1;poC5TScHUM&QlZ%r=s54gFw_{{RtZd-vH(@Ni#k@x|*iNn&K@MFZ)q*^QYfooW{@dOBJH1^1~>^j1w^!L!P zs+4VbFSSJ$6GbGi{A=r5&nT8v2|C#fh+f-^bFNlE_giGm{)CQ-QX+twh z)fTym`_G*3oWo14v{J;rU2jqgB6Qa;sjt}UrVZUVwG#Q-1+9HY1BX-@UQmbJg_6PA{3nFwEbB6z`O6>f0t}W7a7IvFT ztww;Dm-{BEDLV@ybk|+RisKE^J19O^eIMOB>7$x+c&U|Y#OtjkPk0tY=w6PA!-$eq z$pZWxx2$@Pyg@P!XF=$9rMrGf&vT`=6j$qz1xRLCijo+e-KJ8jH9@QhLU$v3{J50q&UY@QzpIUGxpsJOqoUj-?y7e7az@tk zkXD*mp24lwRH2;(5mNdkJ`-tNL+9xQr$SkmD-RGT*Fc; z&Mx(K1EJB!ed|*CyRu}-F(F!%*?$3uec?_9`!RKm2Oy)L~T740OauaW@`8*IVCFWEcKS=XL z?e-l&rr|R+I}0Hu^LL`uO7q0qKL!!Hi#gR-c}}fWoSVrs{7=o!YC}xs9A0Xr8>gNF zF|7p=x{Ep0Z>uGri_c#oU;F-=oz;e7*Ezh@N{HJ&1`)c86T|=c&dGDh*CubfU}5L* zQma+q^YuXlpGzz8wo8f66~~+n=N%NE?@_;l&Z*AMrB-W!IQ?S~p}T%*Xe^lFM4SMh#|YO%#P9mpRaH74bpTm_WCJE2M2~tb>6v;`s`tWMdj^QpL0sK}2q}3` z`zsr!RZdkS;lBG0`GaY87t!Fcq-UXX9)RlH4S?Wm>MV$mnn7IMM;^{bX+2R@>%K=8 zpzgi*e)m`^wHgUx*N;Jj?)s%ySS1>t))VzLF@L_HBE?ligGWiJmF~Rfi$yn76bP46 zTs6d!vgSN-1>Pu5pz=m(P3Ey$ zE9WI|uWVxq2&&K~I~?Is`g>@YYs&s;+v^7AuUBz`mz?_E^;yom_i_4lDj)au|13pjN5xVP_^meVEMh0>1s^n6Cs$P=` zUUEh*wNh4?^0jAgL4@w*c%`rHZ;?wKt2n_+Zd0k1YK2jSwnG)##C?tk-OKSxE0Eie zo2VO`-M1cz2eac&b?ENmX*T{Mh)qGf)Gxy!= zZa^YYh@(L)Qm=(OBBT^0x+N;_#OdVAo^!v@>C`LR;Wf9Z)Joqt?0!5=W4mNRDEfg$4I{yp~$&tLQ5rKHq`}-4(mK z=`&|+eTGP!hipcw(56TX?sJ<;t?C`cPV6W;B6L^ms*RjMCq6%y+{DN0{lVZqe~U`3 zRKaW#2=XA)oWT*HySP^E4W_=*%#td!om<%-bnf%FsMJdP=lgvOB6Qa;soyqDK3AQ) z!^vhOt1-<0A`+eTORbdAcq9njd+%)NQu=%NKc6yvnhbAp!xNwDjS_FWO{G?h7~a5LB+41rbv6hjJ6wO8JS}MZSzoL+a2apNDVV=X%z=F)U?^=!}VOy#03z zBBa#Q2QoX>znnAiDw&4=A_KyG>yc4vwIWeMHN@^qEr`%vzogv6htioJa@9H~lBY%$ zv$TsGzIC6MT8##w8*g>bx_ix~^mpw`d_R42o{Z0x(?>1VuKU)}C1N^=?Ld$}I13_v zcYpE|r!&g^lYjZf+jqI~_D^-@*-$i;t2kt&b*@eMiGPMU$I!;uI*%+YcMZ!nh~2@k z!+FW2^!Lz^lVLFDugNsz{@S!A6D;HWs^=lC#I-kr_(KaKq{M3Sp#D{r)?`k`AFJaJ z=OWjz)C!;XCV*I{1rfUImxdxyt2k$qX?Q4kWX?s-uccN(oCHFdXH7)tu6!>=WhcE+ zTE(IMobC-zTpKLoHkDef4dNLP8@C`rcd?q@uDQ}XcqGhu6#3d8Je>2$f@NI8QY*2u zGQ4Lr5H2NF(J)b$Z6I7q9^|A&_giVspzn`&iI5)=DcBdX`C>QthHnzG$f>$3pC zOKwxC)#)H^0kK~L;Zlk#wYNp;E8Tdj+(exoaQ1SWO0BL1L1p7ES&aq4rSwbcw>^{3 zRVjp<<-1gfP3#<_fk$|$mDu@t5L7sx1rfR{XMZSm*MMtZhG+i`-#VwdO{G?<8Ziw7 zRcL2Hgzow!MLX3H@rUMc90Kl=qnNlhMrY^RQY&gxb*OAS3&N#zE@0@by#=oQE*z-4 z+?~mskxRtcAPxng`{EtpQtNZJZz4l2{|{uV??>6i3fGG{s`th`sv z^7X~-K5(DwS*wpZ+i)2WoH*?2%xDu4Qaal?gLS^O(mM5{_?)}wyX!J{_a1e0iMRv= zl?uB{H4rZKQ_cx*!TA2}E80g_<>5`4m%+KiHWS?AMDgH`n;K7WrkmZ1&z zt!r4e@lz18gS#^dgiGn%<&fptiCdnPk2nDuT;!3V=OL|xpx$d|Y<*_b5h0~0kp~w{ z9#nm}QTT&=&UBtLMj5xM)Jp3mociq^(LlJA_)6Z)E5bSf%Iy@Luh!=|gJnEkORY4A zo0GMY4O$SPyI4(8c|(d_&Ed3GGL{t(=OSm$QmfT##AYps&|Ujq+5xyBeb*}gY8WdY zRBw%wdWj*OUrVi&<25gc?oQs?}ORY`=q1BbkTM(hU&RwdnCa1nq^;Yiq>2lXk zT3ZN~aScnYlv%@>(eAb_h|s+pueADc2rD1k)h9qBcHO2@D^>E`1H@?!giDpwbjSJYMy@A!F4_X=Dh**cL=cDN0slUG&t%I4co}TBClBwQBd8$E&J_Xk@rA z?KJ3CT_LE3SRhZ#7^jz0#Cg1yT74hH2hS|p{h$RAx+_Yw zgG_b=v8%6%(X2RsnU!bvt=m*;_2Ki+^fm=CrUenY>ujgmxFWToS>Ltn+Z|We{fLP3 zcrCU1A&6gs*r^2(y37A+Z!q=MWFnEeAKfL{zw?N5n@X*&0dX{lIT{F;(l4psjz~UN zB>q2k`nv4jdBk}nmRc!4@pTZY6w*Y5?yAqBD9II?bY}EV?AvjhS(*U^Z@W#UR$A+O z7Q|m#5TUzf0E*+qQnV|_YX%t-RLv=h^NU_-tJ%B-pY!1E9)nXP#6m z>^Jbaa>B2r4d*`Buxw*Ih<89tZXjGrHAiIm|B~g0z~?Xg<;>0}*uV1;$5~&`Lt5#? z;fm}HK3`V~3AS{pa=cPq&e!nA0ry$gtvjX*rO{H%zOQf)ffbpTZcx$Op@a~FMg@&oq`c#nE}C1P3f zqP7Jw^T85PN-2{fujJ)<;@#v8Zcjv=ZVP5R2Cp{ml+_$b_snvxbHU+UgK6ivm>F@I9fuHZ;bM2CyLhjWj zb$z&C8IRXeE8QQpD~JRCmut{DB6Jt4X(Tc~O@hoNKoQ1334Uh%q2~ zAU;!9dviqmt{7(q|Bk(R5$rex#`!N$p{acs3 zcHQaTIv*a>FW!%|RTwY>9BGy07|qyy`NR>`HaYJ$ZZUH?^<-RXeQl&^_2M zvEJYamvU`0N6kY$%h{LCRZn?Sdm3f*ZZCih@1|PkLn9HpeP|=Cbmw;c-pTSC854W5 zd_hpIA$Q&*sp_rop_L;_Dd~HBvM--2y8rBRdY>d0xqdr)8Z-1P4=#@fOAR&pr~>Bd zi)7QA9=aX`^$e!E8 z>3$G5fq1Wha4ENs*kz`FBh@K;)Q;Aym@%q9cB_SNQXNROLDkEi+r$h2#PuLPXdqn5 zd!(waU`%|J>Xawdm7vvE8v8V=sjsNw&~uwOB?RJe5YHW%YexsdrMyQtyTrWxwR6Vx z|9;(+K_4@GEODEvN_haPXSoh%Kpf#x-Xm3|>M*~j#_xV>olC$Xza^_GMntFb`1l-pEQ z`r7`0N`>ok;>mSq2G}{OzU@R>>3&?QQ_{w85M(p zILF9*3q~-2M$RV~4|O6eW|jLcaT#-lg&5ma+weZx@Ntmus5FX76W5TMfFNYYjSYlL zxqZ2|;bK%O{C9n)MKFNIL3xn+l4<5Tq@mbAwGHRtY^sl(Zik-3zN#V*Al-tYJ1sQ! z1p}y86^Xht%56%!842-|2EwJhM{>oJA3pg)Z+v~HMKFNIK}Fxayislwmcs@|fcRbm z;ZklN)xYy9PP)^=$3Z>oEz+I2Zc|l#sYcAnz4wlADesY7weAbld!4Vo(;^r^l9;POFjZlpSN4S*7 zF(U(!_{vpJ^+wfqpM`JbHQfO_p86}YFKo*9X1oSssRqKOyho~P1tL)u7q_qPK8tu2 zhu<}GrT#9o<2G>)6oe`+ez$>eDYuWgGm*Fe^%K{u?>>uomDhA*y>2jco7n9Gq5IX= zZ6I7q&(R~WGqJ0?&*rS}J`09a?5d3|X~S)*s--~e3gW%`ezicjl*e(7#0RLKcmy>} zJYIFnnEFb$*||-*Mhf*&`WG}1E~V$_5mrCobE=*652S{PbKgtM0Oa!{@VVQR&r}Ps za|7X0{s!k60GWpU{p!2Vg7w9N5xZ_vM(hdVtOmlRyhj+@nE`A<720R0VdAVmzzjg~ z`Y7+9+mz3~vTNB_ofJp7l=nzg?F65z>Zxu^bADyds}t{?+f-HCfl$@cZ5s%e^4UOD zJwSdU)lT}~pjwEtrDm`zGLyyXzPCr+C}sevDZ5Dn;Zm+`o&l`PZE4rnx1|NA`sh>6 z0AA*+j*Q84gQSlcK9;zKRAK``PI!M!v~q+?xqVf&9<#o`Q4LWyHhce&_0mzi>W(D& zUv)0^k5hCYR6p^;2EwI0V#qurN?xQI;yb7)MX>uQsg_Q1F7H_ysRrPJ6MD_Z}Q7jNH7Yl?-d5`2(gM&s*?HyO&ksJIfmQnQS zpW8(42kfl-?hk7qTuRT;BkT#ooLlcavi~f#?wn=xtT#wJJ7db;oa;ReE`oOg2- z+Rsx{_7rN}Im_sVT^4)Nt@3VDW@p`Ze_;dRQhJUa$$Q(EQBzj8*gMPUhF!Js3I4fF zte3#G7l634fp97Bkz5(?GHS{mU*9wy{OV(g+myNXS`a%n5H96C!pa0(tNZS6q1K&` z89tV{O;x4+!COJx-axpN_eid!LpEby@#|jG^S7BJYQCkY9AJ(p&Z?>rM7zc+-RtcL zm-0B~+ki;igD4~qGR>nRURS|unj>mnqDZ_S&dp!+irxJ{?9xEEl*ciwM(qA6*Jvz! zE3c6W+PjDOkK4ou2jWQ(WaAbHmvZ~4vdOIPGNN!EqS3vkXWb32`)u8&{8e-lh_|^F z+7T}0ahz|2R@`1hG`iRHtTq;+4Yw)ZuXa0#pEnRLrRR8qnZxnLawt)FCDG{ds%Q0; zV%crVD_%Q+xT1k@DYvhxblzD}NL9xayTOo(UHN=B+Hsrm{c69>RUHe2OL>n})nf3u zYBZ7uSumt{Fk;tjqJkL+)qzyqWoJv5@*c^z6}$$Q{|!!e*4MM*^)BX$ZWFh}FlW$w zVR{4MQvL?>l?tDW%Sq2ESXf*e@15IJRl9&V7{mq*giE=7RrL>M0J@7_chWmsexH@+ zrir#9~6(=JRa@xiHLOYR68^+?A+}%L#>oA$=?=l_gYw|u2`l4`zz92%%BZiyI@c9;e7wBFK z_W>skJ5;;qz0De*e6F^RWOXX%*6QwaeNi~Vr95J&#XyuS#|)hO!M^wC2M?S(g1xlf z7}gZMy{dZgV-POoZL{J<4&=_%Pn^IPMYBiAc^o9Cq!A_0GwWLwt#m(!M+RrGdSSV1 zSXDDYtki;VDYq}*GdiBtl?7R4@yO7#Jg7Tsr0tM~T2CZnvbR_R;ZklNQ37*vUM+dB z$ui+Jw~3u<5F3M74-Y!RrMySjyMsALv%2yst1QkkdRAO*LG-yz8L=0L=?#QSd5`4X zrEjykqC4rEEJG$}T3?d3VG|=YT)S%v!lm54eB;!zxe9H;MPeD<4X>{|w~6^K?EDQ7 zy2ajk$))rhGY)32%=+G8b!8V;S(+@PHV&f=*%vnD>SEl0(c7tka4GMR%(W-7x}rPj zn=GT>)lKkjQ@->5R1o(x5H98RRh7;gY{KfwovgC>nBilI>`wQ(|SNZYU} zuO|wzK?C7ZZeNbX!&ue6fYt4$*Yvy8&`_eSFd5^Si$??Y1k=J|{&j|@HQcjYx{t7myL=Piu}LGEjTa4EMh-5yWcEE(e8_6qbvW(i$J?pYh&$216Cmuxx(lrf)OL>pv6_zhjQ&#unHd#i$tJQPa zr)SwjMhY{_VJ!%k@*c^zrTrfnNKcbj)MOd`?(+Pu?9;PsVh#t_>h}6SG!QQ3J(5>g zX67oii8+H`H3QV|a-JpGr)Sxe=ZV{a&|SRFOX`J?@*W|-1m=8@nzB7Ik$lYXu|)O> zEt~kiAavjTJq?6Qd5^I31K0M*<5WJU^Q)fqyUI*(n=(7UK^`YLqXojH+`haPK7-q! zFRLR_zboI$Yfo`{W2t(csCsmHp12Cp{vn8n-9RW_L(1bgXVAS*{ls6@ktl?GE3dJ( zpqhy(c5mP$$2w_`Ui;3UCO`Ow214-~QXa?oR$bLkjM!Bh@~yn4Hnhj$HdWP1#O`V! zly~QTb}2pUmvXeL9`cFgAU|72qW+6~E3c`qzFN-{!=}6zemsajHV|&P$1%0^nDzZo zcRLe_?lnEja%x)?HsxHkKY@6$fpE*+z8t%cP(SguIud1v##+U0c+hRi+0l1^xV?dJ zDLqGzPMtN)^SRqU$9n&yda6JtBkt~}?@)v+50m+~ITGl2cchL0J55aJ2( ztL}#XRXtA(n=)dr7KB^wZ!mqu41jFklv(U-`A_DF575qU8|~$lqBUC(u9eRQ@;q?` z>ejwk&j4hHI8|&pCud`H%WgRXxEwBzHk#ZQmb->k^+OQb)LJ>hrQE(e1Na?PXs@XG zTv~}C#eMRizD!FEH3Qg{O1DjfOX)f6t18u**bjf~RP(w1iz3m-5@{PYQ5h73JV@Qz zf>T|}?PK)e+j+)Y0|Vn~K35xJNFPhwrkuS)1?7Rg8wi)uvwkVh8IC4Oe$Oc+<@R}x z&SX82sPAl#P4YQW-+42AZBGYrI|$_uI>MzqV)ClZH|&vIZZXJ{x*onQ2#8#iRf5Q9MVwx&Zkmw zae)TH?eK`n_pwlcU#HxP$WWB<`yH(U%f7HF-(atrng4AdT*`YSuMYM(VR~A!2Er}(h{-qD&qg)GSYZi%70a+Y-O(DE+mv_vW^yKV+j>nV z5H98KO5VF;O{OzhuNi$|xyM*l-2vCCO3171t2vW#FWCsyHsD5;v~L*g zQ9TL7`XH3s=Lpxz+s?UzyHnfnxO%rQ_)biAHS=~Vpfu)iTfntl+R$#F^OE+tLKD}p zsy@M*&t@Q$)#wP9a&14_?d!yDU$B>WNgmWKyQPMj!;!DuS)ktS3xrGQIqc&rl^NL{ zwLhA?6#j6V@+uDb+R1~Ca4EMh&&al-cFBwN-ktN3;%YnID7Pu^vCIL2Iw@)0ClD^B z=dh0*cfM=)pmxbI^=@AEsjkG8?3$Ec6PxSXfLcpbbxixt5qn1L2l?ROU4q)wol}p+}#x;Nhe$ch)9t!zQxsiQT6`Y}-J{ zrjT;`s!FwYPM~6)G7dfZ^!!1tfS7XF!lt|ec@K#9>Kw1&KDXR^Bxf6vfDOX)f6%egqJDa$>)iJgPP#ngHSl_%^rRh4e;eF?JXi=84DgMk(vp*;3CjmbguM?@m?msC8E$TuRT;Bdid>&O0#rC~wmRA4cP2aaz#5-J5Faz)P`~*`6^N*f}kdHfl#h%NO_Fqd7>&CQ$I09NyL)!=K0=C zCwJVY{QdDy5Xv}oAGnm;m)H89qZ;CLvH?Ao^sGwY#94}~u&Lff&h--ugnA*Qyhn2G zplX;ZBiFsA+NcSjkthV?dx3B%w~wr1qGW&S4k{baV`(&JOjzgba`R0( zhY;;i{Q`t48|%D*BU~%jHqR4Jr?T;J_344&J2BZqtRY{R&cS)^AhXv&vKhO|YII&w z-h61{8uCRC;$RTpY#?09wat6Nr%(;?_w`wTU}W)#cNR5e$Jb{8G`@FWY~d`!EIt}hn?%XwFSbZyhrlsfe)!E zdmcH}K4$n>;x^^ozPTQm**UF&a4BbMX0LGVrMaeT!N@+AxJ@}{TZmi8!uC0?tA()agcYaXM$L$fp960 zyvl33&v{sVLRxpqd^Mfnj;fA(H4rIwYblRon3LF5l}%-ty4Uo4ICUB0gml=Hzj1T} z#s&?9TkbuQS0Fz_jmA0aGoul&@)~!ccc~+rVmEBcE8Bmi4y5jpc0ar29>-O+G?B>H zN>_J0yVt%+>~0JjOyzsfZQ{fsi0^~grh#xN?~%NoNN!^HYxSAYU`WL-GjP>APuhk} zob+Zc$|+OTfh;)HEqD9!JdsnT-7nOqOoRIryJ|xjfNm3CcSO4oAJ(T#1L0EMBUPo_ z(#U4)o=J|PbDz#vs;_RPuiU1bM|K~GA2bjycLqJeNJe}nUxQPzCA`_^YhW2_YqDt3vdbc!}?Vm*+4zZx-jpc>Z}@ zbuQHrF6Fa$ZuDj@0X7uq<#z!A-j}>7Ms*O9mfp95rJI?^trZ(vH`_5Nobe4T#6M1BO ze>@H1y#~Uiyhl>*xoV4aFCZJxeV|$&*HQtMh5YSw?j@WceIe?iyCrnIPT-aZ3Z?Qf?oN!>n&5sv+K8S0f2763fVg!|`CL zp;mEb193|O;ZkMf>-2!?S*jX|vy8F_`4EU7~K8d=!R zOD^R-l4pINr3&pTb#0O0SFwyn1D!>7n{o#BXFw&wc0Jb)O(d(Y>bUooMH8 z3x0pHs-)dYJ3FpejpR$1*wuaB^ED7I<#C)N@rTrCJgly(7V#>tJ;ey!*?ZZMZc|>f zJP1U;fp953M~~z)qnC1{?woJlJlOQwa;#FSLO7opp{5aK$3!AFy80LvT2QknU#v2$ypE@_OQ*sWqt_MUPiK>vw-T$6_Phrm9jN*tZ+!IiZWCE?AnpdCtVTz;l=n#9Q6wX*dkNvzxCk+GJ~!k>FuTbt8{1h4%)-Bd4fy1wmC2SPt?ZkER(BK2J8FiQ_d4t zK^tlHI{)iqhL0t#VVb=vC%miksg7_d*Ea7WD@sO_4cP3TH(+P&=F8{0l|(lO!-I8{ z%vIM(^;ilikC?pbtecuQCmYZsBO+!+vYq7rwP0$_+YaQJrQ+)N2EwI0VupN9lnl5J z=!MQT%$62qjwSoTro8X0vHh_I!lk@N=qsW`H_OjX-kta81S*E!NF+W=TizZgctFS> zyEPCl<=W;b`NGuu2gcSFl!Lt#CFBiu7EQIaeLZnGqC;)yq=H8V(btpZ%F}QS*(IwH zb2Jbx<@V+E#4};e%j*K*vL+v|L!?d$=1L0C-N7ogUgNwv6ioU5tpWBq{+#Lqut_H%T^c+1> zRZGI0;@ac5#lFchdOsfLEpnSU69 zm7s%PeZ+B_@_7RxW*nI-K?lO6+`g*Py%|&_?Y^fhY~xoSOWY=A03bLy(7n5Xa4GMR zoS&%MxmEGqy~cND?=aS6bhi)RgT2>?W2#6pigJHck8DPL*A@tu@;J`bzpvqo<=Fb( zjEGlxZ8O%E9wPrkk*Lfe>VVRQPMJ;waYzHQy+`s|xV~{#BuBC7HSK1x)|X;8Y|3YaHEvcAo`r*$Q7k2XM1 zx3>Fm1L0DxZJq&a|EW6$hSfK11nY|}AA|$ffNRTn;u`R|w0e*>e9Z8%#5KepAjnkj zKHWgLl-rl<)>4JG^G1DdhQ2Mu`aYJF8fsT&Nf75W5H6+XurJU0He(I>uVfs0Wayd9 zw$5EdyZpZuJAVhI+XUC)YeF|QKh;3Elt&EJVOT?^5=nQ{xnC$e#okS4IP9#h&&l7c@mu_Tpuep>*OjW(hO&c$=hP)TKecq$na`WDjjMjhRZSwY53nxmnucrJ# zN4S)?ozIwT_S_8vx70VXMP#T)A7F-YU7APxJn@}-?;CuXHnyYy2Klr!oq zqv!CN+m!RjwA&}2JHn;BN2+Rfn6qNN!Gkh#@o7frUYg_l-pI6_=7L4pt-Xq*GLR@X0^BKC{qk5hP zf9U2|@??4kGY>k5e^vEG5IcbQ<81R62$%90%Te+fvJ-UwviE4*M*AVzfQpj!@qNA{ z_aMGMDiCBq6bP5{7|T&|71;?ll0WD@s@nr}FY-+OxlMd|5m#gycJF8)T*`Z-s?H&< zt|!N9Y<)+r-aEyTdRKj=_;s7|F0yWF{(b}DQr;tZ?@ssK-$DMM$C94aKf2%7ZOXI0 z`$6a)UPri;_XunHL3twf=cVgje|I?dAML-FQ2?fp95rn-zL$g&ohWx~JB+ z>INf=$#lc6&J&e$xZ!Xu`Hx-QV(+{}wplMMcMYrRvmlhKro3%OxRl$MS8*;R$LmS5 zu$#Oj59-US)NpUuc}4EwB`d5zxRjp5zMO%r9Ix^C!^aFCOWdaXB`lv)g|O-S0Ag zBd;&n7dGX!z5_uV-ax1qLdtt2XHz{6Ybt-R$uhdpmD6WQ+psCm`g9hciEzu^zPw&? z7OW}0b(YbyxK)`?Zc|n1R^4Mje6E3TDen=gi@}_ISo31o(^*D0zpD*SC?xyBrp(Sa zgP7Dns24)YdxX1$;MxOV&B?H5lVyZZuBF?Q&jRcK;>HHTrMyR~>N-|GbQisDv2U`B ze7+6sxJ{XzZv#QS*8<^E-Xp{=TuUys^7IQX661uQWnb8o=WtYS?M`nXT*`YS@0k22 zXZ9!N41N`hDiWzym*%KpQ?BGG#DC%2CL@QG_eeVD)lr_l-a#Lie7tg-@=n|lAT+)^ z!lj&dxdDtw)R)A3d{H#Lru&?gd!VSi8Jk9@uWj9Fu?&bWHV`i5ahzv;|Br8vF?>1sC2C3X+tOG4iu zO|NO4?@-qJWM9~ncl&m4LAaFnNIsSFch+M@vo7QD+9h`9W1UZH;cipjMdo%2ol7kc zF6BMK`@yVFH?k`m(BqX;hMfUcrnFxq`@*KYio*>t9qyJX5H96CLXHkYaeB2Emb-?04>AK-8pJydgiE=7c@^i)!8toiu#(f{K6y|%QPOru zL!BPjms6%+Y#?09?aMnRIx{MNG`TOl<~HSZpRH=2JHn;BNAe6{7uI7w$4ZWm89tWC zzOadzJ~KjLDrF*K2Y>& z6#KI?7RWgiCpkP=SZJ=m5KL z8?hVLWHPnE>E-mMg-yIsAV#(z+;Z=cyo#fJyGiWEHJMDmtGzsF8#d+HtLm3r)j+tE z+gDY;VJXZd_}d@lRKrhMbnN+6UQ?g*Fi9;vEf@cI7i!hOhYT$9Pd z&$2IUVwViW7!cGiDG)B@J;E*k>pt_c3-=_uan9koTUhbB0q>yOlxF~M=V~Mc!lm54 zTq9m@l=6gqT=MZs_JvKE&s9T6`*)6TDesZIC%iD33Cd7t_NXd9y^6mTF?!b5J-tg@ ztqS6I4TMX1jO9$gpOMR;Y`|uZ>UWprcNHaiR?bG=6W&1C4-JG%d5qm+aHCY+_Cg;yMry zG!QQ3J(BOjeuDfiWdnLF=~=%!m*#K0D|(hqd7e0Zgt- zeyRoGQr>pn6W)M|a<7v=*km%bF*VuayM6b=wb4eCmxPezt|74kVs5lj#-Srz%I(W5 zMQ40z+B#H-P3#=(C0R?k^O?FrvTMY%~0giE=7RCQ$CXHEP;jo*U3#7p{J zc}?2tSvKVxBs;YSoylCv?aS<}`f#U_=hfsT{q7%#KDR0F56%QJp@DEIJx7n^8W8^_ zzv~L}ynM{?vBYhnLKA%c2M`xD5H98Rxg< z<ofb;6O(ti=(X@*3GZ|}?XJTYt{>jlJTK`2MD8LuJb zJ(Aab$TS?htd1+SA-+>2{)RIw+Q)I5a*o#>Ag*d4TuRUSCG`lqmasFqiG#P+?5zJH zz7vzFjUUj4+eAcw7y}~sJPoQ?s)W;yW>!`bu+Zw<+%^9?*htDSsdH zjZ@E&)jzG~bG0G96O+m3tJU+wuqm$;-3;PC4TM|n+@9I_HnRHfui05`Xyg==g$LcH zjCiR9;Zl0mFXb7)D$EntC#ygBT>nKJEg$l373=V4Q>GjT2m<%Ty|Kct-7`No0I zPp{voCR@f>;x=VI-=hWLQhJuQc?NI--_9}m_$cF}kJ^s*s%mcdJVrQ2xRh&4{Swx~ zPv@l5)%Djz#FBcH{gvK^^?D-pq*A?~d_L*`5Xv}oA1IbW%400opc~-4sLqXgEX^RU z?&BQtM)i84&^Zr4)kt*Tz48Ygp;!tjk1^(O%=&g+ePWMW?2A}ZkG{e=cCq=?aN6)&+GInmihAy6$Qej+`fF5>E)a@)E9lz zYon=Y@JhX&7&bA7BNC}dnoc%4!Y%h0tEz{It4BGRNln>;@7`oRQ9Grhc^~yl(OzD~ zA*W9_R1^r8^0qTOvx;-uvh^xXFtV6z1>QlOIxX3G3S3Kt*wjXomqHWQu&T7~^H>YQ zrTksV?A+nLiTUeQoM2?}l02xju~I|jk^N}fSrIO!=ddr$^m`Z8{ty?%sHW%en(Pak z^4Y#gwa*>lQr;u1`>-Z6ALm7XRjIN$QMBTqJeNJJx7n^Rh&<7 zd;PcSRh(dCag^So^%)u5Cf=yL?vu`@I+MAS+n2M#$ffShUa!dnBl}q5Hf26v6~w0+ z2$#}x^hmB1rrYbO5L+;^k0owX{@VT)h*Z#6 zCUY?R^{NiNc$zzC1#%k@+}KWa)(uWU!W z%4@1iwRyc89yXC#L$qH3V)h2YE%!KP50XgSle!{@v9jItnrfs@t#`x2rhG1STM)-K z5bA}H@*c@wEMK9n$mi?*LFYc*)v1gvw<$;a2oTDHY({uk?s5Flj$$u%6rKCjhBD}6 zU)WStpJ8S>54B6Ebep0*5H96ClB@7=2!1^zl)~M;~u5=M-%Kg1WT@!lk_J zJOkL5TPjbecNBy5ee@}30J@iKkot3~T-WR$aua)Nk^do|lXKa7H&uDO3WO@wDXY;D zF69xE&zPwG+`sD#h=>gJ&w*6&&|;rkr7fJF?bN4Qw_2I z5*ZL48G0svqW2KF0Ma&W$~hCSg7{Yh;ZklNr&5R#-FT~fHTQv@RaN!-d~>=@DIczL zA_!GVbA(H|eN{D`DA|rmZ_4fS$k?C!#OYLP{U`6cw^vmsgCK9PPrcUy;Zojqt||L2 zH{SlK&O8hDQk0ON*t>R?{6u1RFxqgI(Q{bt8geTSh%sn2w$40@x5%a3zRa9AQ-AIc zbskx8kyu6^R2EFBp{kMm8U%Obrd6CkxKtVWTe2oI0e`H6KbkBPk?1z%b)S<#tlmJl zl=nzplR1g{bILq(E>g~wqOZdnv{g{il zNcM$Ic@;-B?zlI+K)96mNM=qlyn8dKT-Rh7jRwlXcAKi|NY)dN0`Wlu;ZoisdAIL( z)StVYJTf0Md@PZDVG}E?UQgva{INicVE+OC;(p`a>EBm+}~^s-2jTojGc1 zkJu{uUA;@TS#R(9REq90)ycrTi>%x0&uSnPOCjYkmaE*TPVu9&&7Yzq`dvLrc6|R^ z%o*G!_Ron#sv-83)#yHODUY$LI-DqZpQ^(rlcVT8TIbEDj6=66e}B9S;ztdHOL>fO zHxW@nO!WuJQS?|+E+2D5u|UUds;UJ*TnS=o1L0C`AJrCE#kqy5!~4}|0i2iAS6k3m zZWDV&AgC$ZKed5yDesZ0x)!cg)nR2dHhD=t-xHs^P5D+`@*n#;J>UqJ@*c_T{5(~M zRejibNqO@TyLwl`Cb*V4oNlkD3T=UKDSx~2*}iF19sU#tm!^&#>|Ju6RsH=+edMpu%T5W7mtY|c~F0~pXmL$r(=R#9f zgP2h32Z5vlrZLScVAUwqC^l=M5~5MxsMVq`R%rwS1b7ijxOa|3`4F_46w?5bU@Bi} zS_uk>V(8v8^PKFt_xyl0>%GrD&z_I-%$$AZJfm|T_vYJur*#TzIA#}bN2ryy2qFXK z+OH!|G0t*t=uGw(__ps1oICI$+e!T4trQSlK;XRi1ff>S7T=QL>w)RJX14B(Uk})f z%$bbm4t%fU+rFDH*WL&pBj7`0hQ<)K8Mm$%=HoOQy3** z1OlhnCkVCD5rcOfSi|9Kz=Gd%Y9H|X%H252=_2UV?0;yN1@|h3eXl&70#DotLalVf z;Eg-_99aa5GqCSYTl8UkCp-#gN4<;vgX%BKUIBuq*AHz&sFkt}JGL&y=@iezFF;}}4 z!7_XBc=TY(kZEW4`p zm9|J&ksUqfHTRkLJ*UmDoMkxr7Gd;hox&HOJe}h7HiTMfi{MEZGbfafVkCA0>MX-| zP3k0&a zCbO4Xv1~1ZSb~|8XZ-ZX_nbP*uuLB>W8JQG3TwElf!K>(*osgqZILXSfteG^NAVQ) zRO>9mZy(uKy=W_~QlCuL@{Avz^`i*2 z()xugn^)t+o*D6bPMcpfmS~-V!g2!;SGFP4N?Rn1-DfuJa1&zQ4;!!SwZYIur(^!$ zNc>B@+4zKy663J4B;v9*gj(r14yrTHJ$ncH2esE&hVEJ{gBHrXAB-z&{9A`lV6+qQ z-)#uB(s7(+w_%<5+s!8xzs3H+_Fm%|que>Cb;`0SK=gV2q=GvR6`@uvTZ?4bp>3Pp z#W=<6OV~fCy~gh=>0>s0Xr00n%Oya3v<;zF+9JpmhLQMvoO?Dl=KZko${u7}wa`{% zYjp}M%V&U?+J;aotsipTW1V;_PBHs1_7CcKWtn|G1%0k{%CcS{cozMiv?0_=TO{lq z+=Elhm?=cZD|2M9pP8Xa>l8-&2S7C15Nf64IB0#Zk#Ji^KcgAwwU+BW_^t% zDZ?9p*sLUq`p5pi66?8-?dpTQe#rfq^CWYeNuMCpN=FQKPGG;?6*%?% z{dH^V+M?Wz0F}1j|LPC9sW3`-7JcuFDMGE3ZCEG1fRkuP#Efz_GT5TM==XI;Ohs-~ z-LW+Yd#}dA$8h-2_F*oySo6hXW(<*a2%}^S5MNuLBGf9aAK#MAz5hXXO3V*ua}oRh z7|a`68yB9RG9;oGh|wqis@6W1tR6&I{g49!tGh<AyMY)HGs@|VY&F;V;Vm*| zHFx~SioP-1o6QrP0d_+XzJ2K*_tLld!#O5Edhi;`FE%Y&)Yy6Kp;ngt`<=gP9rN+k`J7L6B?z^;n z`S;Jd(JMt5i49pNG`RM;yuPfsc6m?ZwB-kZxMsk-*6v5U^OLUpdV)}^LGyoE9M<(f zTR_&#Ol@p6t4!an+e~Ec*C<))AwvfOqtb!Ho+(duHS1 zGp9LP;r{RlwbGWxZ1U;xZpM-K=Wm~TozJR*yVLh`T)p}+rtOBaWgmRGb;LXK^Ezi+7P9W7>vz^K<&3;!pqg?Bb=#cjt;wtF*0N zf9pWK|LAFM!KF79T%{roHYWVdiFR*$e<@AS+-#4fPC}F zS?+6>{DVKY&+cvYmm+3Q?W!-|wBLR1u5k%Mt#pi)k)a`x!Ou&v2Mb1nh$uoViP0yQ z=;IM;6(do>NK{4!Cy~KFpT!<57!BemDMBoX(Z@;j@d&kw<1Iej#mL|!GWchX*n#(Z?gyO53fB4247n-&0y!ZG|%W z6cT+rLakyX^5dUx{M4Bi41<;*0$0yRz{zOL?4e( zt8_k(ks+7JP%xHg>#Dyji_s^S=;IM;rRxVMMIkce5*hs6siQ>wmC+}c=;IM;6(f=N z4b}PWTZdD;6#MumejBg8VQAaD0YM583%|rfw+BI zicqT$;Y$7SRmZ9Lx+BZp*mg&;@yzYD^X~uTilzVfBmac*_xRFD{iTS<=wtRU_vib& z>iUcNU>T>HGjA>C!C63j0f+|3_ zR&&-Yu0686yMvW+PZ8{oe;Jp#XD1Hytn6CqgXOi*#Gl8zJUpcWF?oFE=2alfiawa_ z9p~Ny@pG^m{#1I^)%u^=!#pdymNH?Pf5&|YA5Q|Yes@>v ztqO!$(Z>-`b54M&6UNoWBl_0d z#19_tX|A8pi7=~vV8t?@h(lz!@cO|+`kb}BgO&M95gb>07WUL$f)D*gnXt^=(V?xL z0%FeNJ+&7r5N5?I9gSFZyl*SUtA70kuQ()w{duJrxME;+1g5dh0*^xME~@Y!qVEU#C5&{!#>4or75Qw-~$ni!x!EXR{(^ zffd{80>rAzDiCJHE8#rx7IM;epPnrdW}IFiW>z4~ zidVwfZte#@wsTy4b>-sb{$*(tRevdBHdqZnU#vo(=r78IWn@=zJYNr|P6M$Lh$kx$ zX63JhF@>zqamG36{q@Zgk4k48^_L5E0r4#$ zR?Y83u&rptD>tav5lTy{8NHM075gFaOSIK4#Hzik7k98S?kR$!{l}|a{SmBr^%rFl^MSaq zPlkw#5UcKOL#P$6gsVL^VUT@&DD{WSt)mxvDT{dd)^Rtf4ZQjY4uQqS*1R>F6qCwtMxcqSAWs#vdk4Z zck+ZMuZah`TC*w;X2mPvTZj6O{J7$1|L|w})<%9l9VO~7MX=`E5P3#p?CLMdgk|i& z&iTDb7$prLuBt$o6|aQ7^>yao2|c*z(3slRchb?W{!#?nY78O|R!g3hT}zp;%$@Q4 zz9Br(--aizTPhG{MIX55^Cx_~;yt+G+3wnVFQkz{nWPAg_G!5O<6x%0C=-_XZ5;P? zh8|o7#HMupGt>$9fZp1U8{-R7+#{18ldmGC2 zmAkP5VOG2n-gD-={F;HRF8kb;+7gU-ot@QRieOvKz&-vbRx-@6 z7>1QhJHo8^chj6g4=y}?du`q5bS0zc6cN>^T+}G6)r#Jz;y5Z$Q8jWwzhdh`@ybQ< z@(8tx_3&^0!aWH6s@bkzsgKCYU#V21a#5o^6Fy->fpS!!qH5%Veiaq3TokX8JD(V7 zr3}NG0s2+5UB6Nv%CJ;Y{j+ z+|4Sj2geokt7escWj?eOLQI4jRTDMJGvRuUD-J49^ucn_ucG2r6UD3KZdPeMI1fU< zTG6gwsSj<1umYF^jjUabA_58&-r?G zjcSP+<(be53RIm66n#LMin=ISLGfyd;#G1tDEMDyITP1eB>f=vPtk za-w*7gj%V;P;=;4wJQC}d}y6YHOh$^<(bfmc}1u|(Fd~w2mMM`WM1X4>#CJ94C4y= zRjpFLiaxx**b2W}Er}ZCnb3+S>{5ZE4`zxD`W43&6fY-=SIOP1lwn{6{i;@_UzrbW zg;I@jqDFZpwBm`bRG{d?pBn1*tEhN6QM^j-W|h_>>Q}WY{mOi3E0k)K6E(^+p%qVI zqXK0La6&0_#C|(|+Rw=8fUp1@rEAzpcTb;rvff`j8HOe!g z6|*}~fuav)5D5BJRJ`hUL|+4O6$Sc z4*jZGrC*s3ZH2&!Z~H`z@=R#Oo%U3q=)=Cv32g<%D;LG9 { + let bytesReceived = 0; + let gotHeader = false; + const partial = new Uint8Array(50); // each triangle is 50 bytes + let partialOffset = 0; + const reader = stlDataStream.getReader(); + + function pump() { + reader.read().then(({ done, value }) => { + if (done) { + return resolve(triangles); + } + + const skipped = bytesReceived; + bytesReceived += value.length; + let inputOffset = 0; + + if (!gotHeader) { + if (bytesReceived < 84) { // header + triangle count + return pump(); + } else { + gotHeader = true; + inputOffset = 84 - skipped; + } + } + + // parse triangle data + while (true) { + const spaceLeft = 50 - partialOffset; + console.assert(spaceLeft > 0); + + const leftToCopy = value.length - inputOffset; + const toCopy = value.subarray(inputOffset, inputOffset + Math.min(leftToCopy, spaceLeft)); + partial.set(toCopy, partialOffset); + if (leftToCopy < spaceLeft) { + break; + } + // parse a triangle! + triangles.push(parseTriangle(partial)); + partialOffset = 0; + inputOffset += toCopy.length; + } + pump(); + }); + } + pump(); + }); +} \ No newline at end of file