From 95070f854cb7d0b5d1815d9046fa41e88fcac98d Mon Sep 17 00:00:00 2001 From: reo Date: Sun, 29 Jun 2025 23:17:53 +0300 Subject: [PATCH] Implement a basic engine with separate modules --- Cargo.lock | 130 +++++++++++++++++++++- Cargo.toml | 15 ++- resources/cube.bin | Bin 0 -> 840 bytes resources/cube.gltf | 121 +++++++++++++++++++++ resources/cube.obj | 33 ++++++ resources/monkey-smooth.bin | Bin 0 -> 23568 bytes resources/monkey-smooth.gltf | 104 ++++++++++++++++++ resources/monkey.bin | Bin 0 -> 68720 bytes resources/monkey.gltf | 104 ++++++++++++++++++ resources/uvsphere-smooth.bin | Bin 0 -> 23648 bytes resources/uvsphere-smooth.gltf | 109 +++++++++++++++++++ resources/uvsphere.bin | Bin 0 -> 69248 bytes resources/uvsphere.gltf | 104 ++++++++++++++++++ resources/uvsphere2.bin | Bin 0 -> 70088 bytes resources/uvsphere2.gltf | 190 +++++++++++++++++++++++++++++++++ src/camera.rs | 24 +++++ src/ecs.rs | 32 ++++++ src/main.rs | 185 +++++++++++++------------------- src/model.rs | 124 +++++++++++++++++++++ src/render.rs | 95 +++++++++++++++++ 20 files changed, 1253 insertions(+), 117 deletions(-) create mode 100644 resources/cube.bin create mode 100644 resources/cube.gltf create mode 100644 resources/cube.obj create mode 100644 resources/monkey-smooth.bin create mode 100644 resources/monkey-smooth.gltf create mode 100644 resources/monkey.bin create mode 100644 resources/monkey.gltf create mode 100644 resources/uvsphere-smooth.bin create mode 100644 resources/uvsphere-smooth.gltf create mode 100644 resources/uvsphere.bin create mode 100644 resources/uvsphere.gltf create mode 100644 resources/uvsphere2.bin create mode 100644 resources/uvsphere2.gltf create mode 100644 src/camera.rs create mode 100644 src/ecs.rs create mode 100644 src/model.rs create mode 100644 src/render.rs diff --git a/Cargo.lock b/Cargo.lock index bfff70b..df2430f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,6 +173,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "bit_field" version = "0.10.2" @@ -224,6 +230,12 @@ version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "byteorder-lite" version = "0.1.0" @@ -571,8 +583,12 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" name = "fps" version = "0.1.0" dependencies = [ + "anyhow", + "glam", "glium", + "gltf", "glutin", + "hecs", "image", ] @@ -636,6 +652,12 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glam" +version = "0.30.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50a99dbe56b72736564cfa4b85bf9a33079f16ae8b74983ab06af3b1a3696b11" + [[package]] name = "glium" version = "0.36.0" @@ -653,6 +675,45 @@ dependencies = [ "winit", ] +[[package]] +name = "gltf" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ce1918195723ce6ac74e80542c5a96a40c2b26162c1957a5cd70799b8cacf7" +dependencies = [ + "base64", + "byteorder", + "gltf-json", + "image", + "lazy_static", + "serde_json", + "urlencoding", +] + +[[package]] +name = "gltf-derive" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14070e711538afba5d6c807edb74bcb84e5dbb9211a3bf5dea0dfab5b24f4c51" +dependencies = [ + "inflections", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "gltf-json" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6176f9d60a7eab0a877e8e96548605dedbde9190a7ae1e80bbcc1c9af03ab14" +dependencies = [ + "gltf-derive", + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "glutin" version = "0.32.3" @@ -729,6 +790,15 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.15.4" @@ -741,6 +811,16 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hecs" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cbc675ee8d97b4d206a985137f8ad59666538f56f906474f554467a63c776d" +dependencies = [ + "hashbrown 0.14.5", + "spin", +] + [[package]] name = "hermit-abi" version = "0.5.2" @@ -793,9 +873,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.4", ] +[[package]] +name = "inflections" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" + [[package]] name = "interpolate_name" version = "0.2.4" @@ -816,6 +902,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "jni" version = "0.21.1" @@ -870,6 +962,12 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "lebe" version = "0.5.2" @@ -1718,6 +1816,12 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "same-file" version = "1.0.6" @@ -1766,6 +1870,18 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.9" @@ -1842,6 +1958,12 @@ dependencies = [ "serde", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "strict-num" version = "0.1.1" @@ -2002,6 +2124,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "v_frame" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 8b84c8b..8657bbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,15 @@ version = "0.1.0" edition = "2021" [dependencies] -glium = "0.36.0" -glutin = "0.32.3" -image = "0.25.6" +anyhow = "1.0.98" +glam = "0.30.4" + +# glium already re-exports glutin/winit, but we enable the helper features explicitly +glium = { version = "0.36.0", features = ["glutin_backend", "simple_window_builder"] } +gltf = { version = "1.4.1", features = ["import"] } + +# explicit glutin for raw types (same semver as glium’s internal one) +glutin = { version = "0.32.3", default-features = false } + +hecs = "0.10.5" +image = "0.25.6" diff --git a/resources/cube.bin b/resources/cube.bin new file mode 100644 index 0000000000000000000000000000000000000000..29df9d975689d89810aab5225917fb635df0774c GIT binary patch literal 840 zcmaJ-TMmLS5FC6WDk#2wJ&E-w9HlqYqd6np1U7{x+u3q^hF3jvmN1l%U*ARP_G|L#K>?q^Dx4vv&`I9u$nLCd?v4G&yV^R98m2`f~*E8MZh0wwPfdo(z(u;DE61Gt!b{Qv*} literal 0 HcmV?d00001 diff --git a/resources/cube.gltf b/resources/cube.gltf new file mode 100644 index 0000000..8db6d67 --- /dev/null +++ b/resources/cube.gltf @@ -0,0 +1,121 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v4.4.56", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Cube" + } + ], + "materials":[ + { + "doubleSided":true, + "name":"Material", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0.5 + } + } + ], + "meshes":[ + { + "name":"Cube", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3, + "material":0 + } + ] + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":24, + "max":[ + 1, + 1, + 1 + ], + "min":[ + -1, + -1, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":24, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":24, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":36, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":288, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":288, + "byteOffset":288, + "target":34962 + }, + { + "buffer":0, + "byteLength":192, + "byteOffset":576, + "target":34962 + }, + { + "buffer":0, + "byteLength":72, + "byteOffset":768, + "target":34963 + } + ], + "buffers":[ + { + "byteLength":840, + "uri":"cube.bin" + } + ] +} diff --git a/resources/cube.obj b/resources/cube.obj new file mode 100644 index 0000000..c4d834c --- /dev/null +++ b/resources/cube.obj @@ -0,0 +1,33 @@ +# cube.obj +# + +g cube + +v 0.0 0.0 0.0 +v 0.0 0.0 1.0 +v 0.0 1.0 0.0 +v 0.0 1.0 1.0 +v 1.0 0.0 0.0 +v 1.0 0.0 1.0 +v 1.0 1.0 0.0 +v 1.0 1.0 1.0 + +vn 0.0 0.0 1.0 +vn 0.0 0.0 -1.0 +vn 0.0 1.0 0.0 +vn 0.0 -1.0 0.0 +vn 1.0 0.0 0.0 +vn -1.0 0.0 0.0 + +f 1//2 7//2 5//2 +f 1//2 3//2 7//2 +f 1//6 4//6 3//6 +f 1//6 2//6 4//6 +f 3//3 8//3 7//3 +f 3//3 4//3 8//3 +f 5//5 7//5 8//5 +f 5//5 8//5 6//5 +f 1//4 5//4 6//4 +f 1//4 6//4 2//4 +f 2//1 6//1 8//1 +f 2//1 8//1 4//1 diff --git a/resources/monkey-smooth.bin b/resources/monkey-smooth.bin new file mode 100644 index 0000000000000000000000000000000000000000..f6f4431ca0ef9904a1662b50e34ed08b9b7cfc38 GIT binary patch literal 23568 zcmai*30#d^)c>D-ib5($5>kkSWGr;{3du~SWS+^KIWlApnddpPONLA4&hr?r+08s& zGG5~~&(r_6j&{2D^SOB@zjT&p#S1SHqE9S_?{l=v0)-gcakgoH16!UnF z!_;^b^LTho)p!*1`q-jvwLTdP{Si#9PXU)H0bE9UxM={na}%i8k9rZtaDq6 zQ=oT&ZVx>Ly5bGc2SCpXeFJpGLC|BMJ3}7G}LiMj@#t^6&FU?3-5Dw z#KOie=KXfZGxuRUf5mN}^FF#Gwt=p=63QQ;^LtPUy5g_Uc&`FrU-e(S20EX8-uE@o z6?;MFede+kbj5Ml2W~3_dK|{9*caMGSQvC))K{!QTMFa%T7#~b_virHQumDF1JL7w zHLU{bA3%M@+zz0Ob1u4dwDCT!D0y^)>OX!Mu?46*OgU)NHnA_|Eo%e`; zYNNl9bmnuyYn6&;-4)|m#hUzik67y?9|dFHhWc~U>u*DS#ivm}1Z7^&(_A;5JyGs} zXS@(SjbB^^<#0U1>sJN3VjdH(p)aC@u9)Y*XMmpzfUYXoCuxwECQAY zT`~8=V`>Ephpw2{f%hyDdMd`QScAspaOfK9D~^PIA7$Q)NJPbP&?^`}*PQoPG2g>Y zpz}G2hpsppdQa$up+`ek%;&#vy6y{IG4JC5=sX|(3BkB|P56h$6bjye_!h=%xdG2A z<~E&SypP~uUvty-@$%={;`@0#v_u%QFZ38NpABC`#X-=y4(maP ziunx0q0GcVt_NT)9bmji0ho*85NKX7zRyFTD^7xT6UJvR z3A*A0XjfrepU;b8j;GW0tI!oYqh2y}z5|_&U+jkRT<8IaZqOCGqFfugKcXvi#c!eU zUBLJDTm2UwfF1|kdY+*xj)I;Bo#z_`T`}+VLKy43*NP*daa*iMLRZXZU=NJfAsV`3 z9v45$dTr>6W1wm2dJJ^M+#cW2)_sSr_!)G5pDUo9XV4Y%-5H5oDMSb37xP)1nXWrR zS8NYG2|BNvJ#@tvp&f_uop4eA#r)i9SSa+F&=vD@sW7ge1YL1Bw19Ly9J*p&hp2SD zHgv_j-p`;HKs*0pOC~)Nb31(AJz@W1w3(r2Vjd@-9e3Ej7|bnT(lc>=)aSM0IoF4-nBNm$XuSVn&=q??=O5l559o^1xGvV^C1RTX zi#gv7&&-AXUmgKH5zp{XX0DCrOQpA+nV&%qNpB}J4@X@dKmRlYZ~GtE1^SwFJu`pQ zf1X8tR&guBJA=9%%s=lW#ciN-8+=#s{iK-JDjqt|`(KPY8G0t>vGRIVfX4Gs9E^6j z?V0E|7&*m!Ry5S}h5d_BCqvJ~e9vn5oxn}h{}*!|rDx(K`E#y*(30>B&zqU&os4oS zVl<-SAe1knJRUI!Q8DjvB|LWoaV~VlaVTrhk0ZuGSG)n`p3qMtZh)@X8RhEGQxKh@ zE7nlX4t)5hoc;TvPM5Y{}prn zq1u7U7@8-H zzq_%nnA_odx3>PXoeYNN54)ePE9UmI!}dVub`*1a8_*|zu6n{0e}r}bf97(4_7Tr0 zZjxR<9NPcN>FrqS{4dOF#(V9Kn2M;F*O%uM3A+hX+y(veGttog#b`f6&%|Mo;VM&i2*^v%!}FNbb|vHmYcoeVt_k3-$J zXot`1zZi8g^h`V!^)I6ST+|;6UGYNbY0!^DUkF|CD(G{er$S!^UGWm=iI^AbOQ0(r z58VrMUkZIZbj8h}^E+V=y%}`Hy`k@sKi}!z&=n7Wz7#r-lkWhIa~H0_sOVS6m%BkB#-}&=vE$z~9qYZvtI0_r<@jU_BDL;&9fnKCFkc zp3Z!q@DHzh73hkCpz|6Ap#HxYbu#qKjQU;}Pe^+G3`U**iTQpAmp|9%UyS-0dM4&~ zFb4JG(S8@`84Nud`kHiIv6?UIYQD^^P@mVt%4kP1pTk6qpLITmibJ6vNY_K5E9P|$ zkU#75IxFV$wgLJ@w8Q63@j~d^pnrtU=S}fS=scIN&{slN%#{~4(_#rG#Sxx=>ld7|8+X4|B(PMRSTLv2=#Rq2w))u#Op4bp2sb8VAGSZO zyDNc7@%;^DjjQdt?mrP8L|{_Ox>rTzKnevuqtf7rNOK*rM2LORBj{teJ)v_am@H zaa630dGnkY@}56Hf-Q=>M^&Xw`HNHeS+ykCP#khBKaJ}0P0l;*Nnk^ z`}4 zf=TiFIz0^633XB;QqD^-DSjT}X*_RR+&I(cwgi)68xbsXY}&j(=h|2T>x#`jdCl)S z_cM0&O_pGb;t5WH)Te~6@%-W-2{shtz0r2B9!#RlOuRF=&H5Zo`S5HszgyasJPwwP z*M72nTx%oQVq?{teyL5BXL?#WqFi?)=ZzcIXV(|*NQd^k`9C@Lc~5G1thV*p%sj4W zbGmyfr@{SZ<|gM7Wt|)Yt$Jo2e0`DbTaxclP0P%E?nN6h4L9T6*EGey9qLYh2OZPT zb@rEFLveDhq12{&2U+2@iv$~rZO#s(r>zc|TkoqQ!G_|*z!*Aw$xXlQR$GD%#k&R+ zC%=bg<9$M22{sh(>Etc*4_Gc}0|xU_){K6Fub0!Go#UyHXOYD_&3|pRv+Zi}b{r5=@Fu{9f14 z1M`sfxrzjn;?%p7yZ96Pg4Txic93YD{EYBq7R(ap1``|$m@Hh?9x&{ zUn7dZy5i)3p)%NRnO=H(F9Pd|J58-C7ul6cneco#fpx`(jF<0sYx>IH1`$|S%zL`{ z<2Aj{n(nl7O(7Y3+F#%1Jd9aa+%9&Pxp}j-x>2z|fh~%M^*@`k=jwUgr(6#L2P@__ z%dh-RPoC1>`o7F=U!4LL6|?e8Jr~6cX+nPe#>moM+hsY=?##O44SRxU*T#MNp(Hl~ z8;VoAf0xa(+}5A?gb-L){I|_v853t~R4W=vU|q3q#~<=i)ET|NmQVugitkKtpt#EG z^=zL)32Z2Sw)T^Joam-6Xw-(ly5iigw#cjnvsfMth$gVE_%hz_rP)5E)O|XDz`EiN z*QUq>k5;l_{b&N~igQX|{qd8+@_VIW1eS^yHkfC*T<~eiv`V81Y*sw{*d%k8i8b}$ z<--YVQQWby8wIp`U~XNd0f7z0|Fq9V{w3@z;}V(>*iana$dS_g+v@Qn8xYt~9CTx^ zjBn&=B=s#vU|sQ;XC>v{dm+Y^{e=mvE9O1ze!8x)wQ2$DJ}xcOHrVd-&pWoqUtRPv z6Nk}*CHC_2wqusOu_Ksu#nI1~>u1tR>o=nN5m+joQLmW3XhD5FuH8@qOU2g`gXB}+ zK9(}=VhOA(KJZUY>F6x07M(Pbz`9}{o2li*{eyn|98crgPxStYohfdbD_zT3)d(-K zM}iH-gSYpl2vaM4x@DyV8;Y;^7owvSMoAT4n8AkPeiIk#`5pVyvE%L%EERLVzi)VC zPH8cU8hxp!r#N}gp>H*rrD9sJU-qf@MtYs9Phef~XwyyEvAb(Fy! zosDdJ>JeC1%zX`SY-jj{wo;D$mQBkyCa_e@ZBFcJN9${}qzN~DyG z?fNiF%!tth4p#gksf64V__w*}nc)Q16)&k(RqhOTGw&S}OJH5`i|S?N4exsvw|LaA zURGCJZ@R zzonb&U;^uk>o2G;y-GIIr}_;hu&&spWz*nxmO^@|t0M@UqPWDg{&KqQZA<@(LkX-a zPHj?Leyp^~vTR)}fpx_WAsSUrIHG@?6-Hn~@jh3gQHibe2d%vcY$!e*aZNUy??lhv zRV1*k*k`!A40S3ib4?mGNhy2`m+t-?dY( zzRgB&|tdugfsn6y<74DU=} zU9pjvt*YaqZ@OETb_Cw9_~ef=v?_F=?6#-`fepnrg&R_?!-Inz59KAWp}0(GVQk#f zhR%)-H2;Swlb*qyE5Fq{%`j223^e}3GLxRcD|S9H_f0KMxATzkAGT!DGx*5g%jEc% zJt#12lKvmoGwB(;>HQA5W^EMhtJ_Wg59^uq4Bi}UCSzYd3NElz2OEmF#YEF4w`KaE zuLX3lp?K+r+!V9Ak>TKc%mOwP_y3$G8;$5rnfIM(X?&fS)mp#)@P&G-PCvhg zXJ*@-tMr5W23YH4=IR9+8VxV!wd#tyJRU~buQ${y`mZ#D4aGP2ji%Nes#tcHJXIBJ zD6UY=i^hGsm=Za$mhm4pGU*xIqR1mTbwwTd$K-YfSXVsIJ&+bWD{G8NnQsOgipz~o zkR6T;Hk|W!viyhjOnL_Qzi>wOO)F@WsZrYa59^uq4F3H5s_cAst)4VB(g5p<9nTJ? z+~;oXo9i$`2OEmhu2-h@Mcs@YH+Jh_Lvi7}`KejAjlt^|*E7I|Vwcn~THoG1?nSy@G3$0oE07YIVw- zxG>b%_SM?}TNEEHG>r15^wL}HZCn{_D8Az}hz@M8ufNLMO@a-@Q#*%~_$Q~)wM|(G zHWbg^=SIP|8XC0|7Nmd;#Y=`|p~X#_8u>c4)xn11IuC5A^_AjA*7gnt*igLPbY1Gp z8_>o2mvpeMxZ(55vXXxkje6y12J4EyxIB|FSF2L5;Xfo;S3Gp=ak=tyQ##-Kj0Ee7 zw>_*u2d@{RWi`Ac*igJb`IUT^zZRX1xhTQ9V%vkoXwhJQ8hm=A1RIKbw>zHr*rt!+It?gOA2f2$t*GQ~dXj@;{uCNzdRK$Fft!Y*Q@N z`$Q1fQ0zEvmE1d2w~SxVpTN3erw{Ke+md%%#^)bNV6)76M=Qbz4HXn$aOgl`-GPg zY$#4x*jIl3)PZW2za+uBV*Am4`mJ(Tg@vXO3;7U$fRfR z^5eZ|m`8bivj2AZA2u@S8N8z^_S@~6el%vd{0|$M^bEdJtsH$#$ZkAo^GbpZ#Vz;v zk!k5I{rNKw0vn3!zxSo8ueZxDWxyH(ua`CP*?aCne*)`@o9|vFr#&>x{`fr-SXbOPcAI>b zdznRy?MGl;u^4$Vz=mR{6(ud_pOiDA zj#o0kX2pJeS6ilcjWpIvI|FQ1Jo;O4Bd(zfwFv#HgGq78KFw+D?|;aHf3&6WPpRhH zjq7EUUwX!w!wXVI`M`yN!5!Ue)1hWdBu8ysee?dk;WTJLlH{1$c9Xg4?G`lT?mo#e zb@F{Px7+OdPRadf_p(|0NjbJsazCoPyxnBU{czcP@W71r9#jJJ&R>XtFS$T}tE>Z;Kh<@jAqW%=k48RagaW3BC1cYJM)-2QR2KmAmiHM-~h z*?vOYd22u1K97G(mn+tO)Of>|Jhk?t%H>QZ;(oZy>!R9M>(d+U^E|@a{ahc_KCe&s z*GTFVmMY85>O?UEo?F*zki00XPQ%}UqCM7C{)j_CtFHFz(buBZd}0BMuUiY=enw`2?W3S!VL+Czd8(L%N zhbRgQZ7kp5&z`J54t1dyTj~*)&qkM*-TK(knIcVz8Ol^41bU3ib*MIydiUN<;? zTqKRZG=eyG{Jh+JaC$#V_UcU>E1m5oPgEXB1M7zoNAD0fa(vg6jL{b)M|swnp7rfS z<2&Dy91Hi)LBi-smV%EZ$KQL}Q_$+Zw9M~?iWTCXN(bTT% zJIRs9U&v(umFeL{?z`&J%F>49_-JJ@avy=;F{f;o9E*$#poL|s(!Nj2CC9PBW$5b~ zfBH6YrR13NR!JJw*pD96*dRG>vnfJ5^Lo?Dk9#G@W2HSv-{(OC)|`?Y8yK#nea}bT z$6S^i%a^mKb(adzq9rdR$6-aBXlV~Wa@u=Ga$Hv<2lb9ELpx#~OO7#NPV{g{RmwH+ zj^z0J+PvgqNLrqpDmezk6`+}of+_LGAClvX4c?TqX&`xTNs%05b`+*hxyqB%qJ7dD zFL_a7PypR)u1k(_rSsF*jipGeJs~-|nqA2ww;x@Za7A+DbxT+lMaczis6y^2T5#l( z*jA&3~KZIPj7$gK$9Meh`qoadL6K&WbIfmpdNV^yN(3o>aCC8iv z-0AxpFZ%n~pORzPncTFmw-*iUdR1~9P&PYduZ!<#%brS(hstE5$*Hv|Hu1US_=SyZA;NZ z`%?beA0$U!w-T}atno;D+?gmZY*2wJ&B;p+$NgyTwN%NmMb@08r3F!wHxDGoQzdiI zngOkmJ?1)Nsc~$&uSFu;gc~GQR~KnqW&wX-z5i z?R&|w;gT#=XF_A@a^kJz*zUS5H7wSUYSw!%IeL1SDEFuaboA60$&uUZ5D`ufs+p*# z-jFJe_#!#dLmS#&x)F^l|50+x)6ACsh;B@mTYQils|>Zl8Erxdxjspb+)mx>&B(im zE4^>mhK#*eB*)r~bI_1OO)0wVW63dUZg!dy8A$U8|9(;lOySIhf9(pw{vE5 zZ<^RlP~Fhp^d{n)W0D2U-rSR%7JQW)|G1-(OT%6?IQYBd$j=pv?nSwaJJGVS73kXUcO=LA@0}>I zVju;%q)CorLh{maZ-4qe>YU^_ENdQ$zfhVS-&~R$8<)#Rd$yLL_Djx4j(dMANP4}p zG(G8v22`XPl}|{H6XxcpeV5A9yUWKV$CM)ZiKdsM_&p~i$8sCp$?;MEE!lKda(ob% zhr%D0p}_AKCCBY=+~`2|09xvPUUJO2)`bcmuSgGeUY8v2O?9Dn^(s)6N7p6C*YoYH zpJNA4)TJ@@+34HVW|Cup^DiZTzIFXAk2OE%hnbpO%|_SpZ;&}w+I-QP_iR*%s$|JS zC39Dyi`8?{Z`Yh`_f-e6C}sjD+?v-;o+4iu`>3*{T#`$Io|yT9nBQcb%EqqEvyuU_6?&^1J_HA z%RUsLL+%aenDZXV@mtw5=F|{ZI?%EkaqQ{lYwkI=C@mT_m^ilmno|z48%B@g+7QS5 z`;%p^bulz>l@D>`bG&2JP-^1vNLHFRlCFHpK^#54oRm3#45NW#J&0rF7lxFBW2m#e z7jYa=K9sIE52c`T{Upb*kUF$&Yfbw5dn?KDaUE|OG5)TMsTNM}a1T^2{@%=G&(BhF z`BHLc+>uSFP0PDxjt*@kb@ppOk86#S9Q%Gsm9@)VkRx{1BG1*$$jPsx@9ZL3zmTg9`JC!V+xIw0j<$pH)5VLO=wsd!lH<+; zg~{bzPntJspX7M*lOy>z?MJ;F?n;iMX1LLnvOOuwo%51or%Bo9Y`*34>iHF z-Kg5z3X9P6)>blIjHMeP_XIesY=N^2_mQ|`Eal4AgV-@flqd8#(LtmJ6#S&w4N2GQ+F zO(n;}SDVm*bCTXItspsmdf$qIyH}+GMO`JwxH|1sckoXP=wXh8AJe*wt5Z zwE3qcK4TlxyISs&Q9?i# zGna=St0TGGzQhh|J@+hS>BN9m^yj-}l4I^Sq4bx1d)isPx8#`P(sLQ-SKPdCT@ZEK za9#e$`aGE9yxOy^`m!?k`?VaCC3sAbJ0R$m0Whcvg8{E>@wrO%BNH z_16b;eE(;8YybCG=Ahago|(7qXe>E8Kk=Z`zZZ}{X1hv`w?BGN;^8*(U<+5tv9oV( zTK9ge+_9*<&q+ajD#>E`>PU`P!n0D*?%wkLhi;Oim%~LFRpFEQ?%)RW+s*nE z<6Bp9jLVf|)xUnbF75Y(%hiR}T4VCetMYiQSot(zy)}Lt`ZsBc$GCivAroB zUr&)c-Pc&-up;;5^;xrJ@y|=Gu~n&>bSFn6TG+6c==9w$c6{$O6bPLJx9Ql;Bf;g;L}KQ z+?BZ7s?Qw%Kqk7DmQITn|BTE2ksX%Smb2n!S)!vcsXsvd~4j?@Sz;ua-4j9 zc9AuDjUFaz`YpA_GON>Mvn|mwdiV-!JlyqxOs&yY-rT*|8k?`Sp(Bf{$fuk8 zOOAtl?dbmZ8ZvrOXUVbp4m+9~8X*Jwc9a}zAF-uJxqC=2zdn*GEZ~HBOIwDo;D~lDUq~wZ_paU&!z+!=%@Unb!EW z&nLNXMK8H&>qKkRyT6q$54M(fc22cMpQ_(wh|x$6zdObn=lTm8U$LEh9v3S)PWQGY z`_9dz-OhfJ^9p)}P$oG6!{n{Vd0-=QRB zjhZ9ZI&Uy@47^>GcI8+mkBqox=J?&QAl(hxCTso3AvxBw$xZt0D0CE<5%x1U>|rYS_UX@Hvpf|DS{r;$NmCQx+o>@B+DD zlmo!kG&7zELD>u@#40EgS~P=$P^yYvnJb}G86#t^fKnhv&s+|rikKmD07~W2s#$BV zC2PCjcWE7AJGBn*9l&k1?Z~u+wTHSxYX{#Bnbz7iWLm@8Lj7H91K$Rjmf99%TEbdG z-Kw>MZ-qOY8$mhT4yZ+K0@0BwTreGelapjw64f3 zfh~gCOtSo54$wBhZ$RcZZ6Gqg z!PY~quGN6AflLkXK=^^k4bmo}<{;Px)S9FXh93-lqP7<;PlT;QtNV~y3tO*^$2iu& zuR(q-#xVhY0`g-q?p5%skXwy$kAokFOdRH!2%m`DO3ZT%{1{|LVO^HPFGp?#)@3yO zXks7Csi480^zR_=U(V!afayABIefwqM%~ zzZ?18S_-t?uuYgL&wmhX2<$h^wFYdcwX_F*k7j|@fU*as zv>7;&(_nl~XK2&mr{gS6g_aDTjFY$%N&Re4JA{*X0Jar1w?UZP!X1Kt21KnwBk@!+}IG3_Y)QMC1kc3hhWKMyU<)=nTZ8#Wi}No@}N9Asu`r;wQi zn+^3e=p<~8Hd6~jW+rSF)G*L#*er}}KH5G4tBIBufc}8h#D7P%i|`k<%i1MqHQ{T5 z!?ZKnS@^SP=}&MNd>Gm~1tk?e6+NGWb{hUP`aS{WJp6f#^a8Y#@Fy|WV^A)_UqnBb zpdE)lj(*NSPlZoKd*`5^fj?vI=`!k`ft^Rq%b-iJ3z)%doSa$K)3XWloQ1P93p+m> z>#`Ac4gNaTWfN=;{9MfU2K)_VZeqUk;OC*neC$gSd=fI7u`dhY7a-R{y8@*JED5En zxO;BGHbeajEnI=!z|*&oxemLD8)+l7=CHq@Y{H#%9d--58?V)Zuch6G#bbwS!5-kW zO@=ZRb{C~7IB^eP_i#F=LYW3jLn#3#^d9UEPVh7+Ghnw-nhs*^HvUV~p29!Xo@>vb z-G;vnPD2Zi;U6RO1Z~`bzk}Re^!PXY-^e^dpZDPJA;+V82>%e7hu{bB50HC^ww}U2 zh5iiu5dI{Uuyqouka4)@VfQ_Z>t5$5SXoa0c9w(p;{I(2%0Ut zt;i~}h@p7Dvxvc(o!F22zyxm+FEtx63@zD+6toqCyF(K(Fq04>7Wafe)-W4e7~rQ zY-P~}-&2C&gGCi#7M<~Z#EfiJQCHL#UE#ZmexjSGC+fi05z(SHw0`jYL?4vuh+6Qq zL|+jGtq*)3(I2H+Al3$m_V^wa1|J4JOmxJzuQ1U;8z9=^J6V7D{-PIZb-=f;0ivDO zTeQY^vR?4LM0e2}N*icxv?$RM-^seecNaZG6qHubT4_B|s}IJ~9o7RQ>5E)6jP+jV zu{*2}+V2f*0IaX*jC%dx`(b9$sMiI)3+C1W-|c$9_du>ET8>29ky;mVMH?sv!w(jJ zX+y*yaTWe5?$Q=oXE6}<2O>8JEkwdc;`VKhJ9n^X4jU?lh+DXKhlmI*2Fg$o3?D2y zVTEI$nPHu^CR#@^QZ&&LG;ClMhU!8g(xYb3^rF@j*##8@$2NO&niP#Pmf!;cmXwK!-YA`aF- zYp9JD&7n0%TM^J2z&FquY2|S)CyDZ~$s!&nw*q_xF$JYbVjBE3F;z^5HU)l)NI+?t zm;paS%n~!9CBP?$*(l8r^Wf)+IbuGv+3>T)T+u`;O8Je z7vqV5k3fDj#y<;w7BaIj{y6wJ;MM52nQx^ck51HVTkqqJXG z;4NaS(4i&6CyQ+;S%d*^h|S^vv~BR)#1@ncaS;BX*dz`?+XBBuB%yRr9ELwEeiKKa zCBY|&jZjaC6YwXHIVnyfa}st0t(-x|fE|Q-7A>8I9YU{vB4dFWP*c(KS=a%L@*Fbz zVHT+8G163+j#*tmW-n|%)Qgzec~}aRqv9C+G31Y94fetBL%s#p^&$L2aZmh>RgHv? z6!%3-jQcSBVetT?+bG(=w-JwU>RO;?16U)}ip06Q4+|Gd#3l5w1hy8fUlob)iCFK$ znCTt(J80)F=6VGF2->+V9z(eeyMxj*kp`cJ+zsfr;ctt-P<|$E!rv6P#5M68nQO2c zP+yAc@Yj*K1pONPHSr4Nm+1LA>@~)53CdB}F_f-iG_PUDG3tNBW%$c@7p{mG82J_0 z3Mlu)6Zj{{KgFEF;lq)?hn0B({u|mcwDt!6jmTkoi}sF-w=f6OEA;*ixo^mPNB^(k zUn6%7WBmgE1(~lH>vj0+$lbu)KEZ!N<}>DY6aFT0e__@i;6EVq5%azUe+#+WSfh9F z?~r+qHA;g|L+%dNHk;`V?48JNI*Ckn(_Q2)V2n9T7hpdyy5rbo2h&BNna&~e11IB$ z5GD=Ed1&W_$@C{Q0WVB8CKHrYXsN>1bOxu(2HwV$#bgWREVQ#CtLYR@V;1-QXVHRXfPXUcE#faV78W-5SL=79P`D0CAC9K)*eOTot{7oX(>JXBH|)!IXrJLfV_)83 zg+IWY;GIp`F@x-;_gH^BjME8g;Dq@)W1RNz_Q+?!ICH`0Le2%_%nF|s85@i|FMM9) z+%a}rcw1xy#+?s7A95ZTw+Y^a%ny+tstJl_+JyJ9IDB!FkIC1x5qF8NDM=JFZN?qr z1Mg!hVk!n@3$!hwsA((iq$2P|Ox~uVP_{waCJLLj;|}wN_cj$W6^60{+797m+9kG& zLhyx51x;R1c0=1OJWcDwE>RG^ps9q(6UusM>xCa``Iw5s7l-bPo;~3`O{^EfND9Jy z(61M?qOf96y`dFFpI)ZMVyRds8pBGUt>vOAT5XCp8>3ttzPPC^X6I*W3g1+8z%A4k TT1(h(P&(iq@-?-=o%H_zzt6C? literal 0 HcmV?d00001 diff --git a/resources/monkey-smooth.gltf b/resources/monkey-smooth.gltf new file mode 100644 index 0000000..21b6404 --- /dev/null +++ b/resources/monkey-smooth.gltf @@ -0,0 +1,104 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v4.4.56", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Suzanne" + } + ], + "meshes":[ + { + "name":"Suzanne", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3 + } + ] + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":555, + "max":[ + 1.3671875, + 0.984375, + 0.8515625 + ], + "min":[ + -1.3671875, + -0.984375, + -0.8515625 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":555, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":555, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":2904, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":6660, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":6660, + "byteOffset":6660, + "target":34962 + }, + { + "buffer":0, + "byteLength":4440, + "byteOffset":13320, + "target":34962 + }, + { + "buffer":0, + "byteLength":5808, + "byteOffset":17760, + "target":34963 + } + ], + "buffers":[ + { + "byteLength":23568, + "uri":"monkey-smooth.bin" + } + ] +} diff --git a/resources/monkey.bin b/resources/monkey.bin new file mode 100644 index 0000000000000000000000000000000000000000..e7992f36d75135cc5966e2abe94e5937420da413 GIT binary patch literal 68720 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 literal 0 HcmV?d00001 diff --git a/resources/monkey.gltf b/resources/monkey.gltf new file mode 100644 index 0000000..d3e2438 --- /dev/null +++ b/resources/monkey.gltf @@ -0,0 +1,104 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v4.4.56", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Suzanne" + } + ], + "meshes":[ + { + "name":"Suzanne", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3 + } + ] + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":1966, + "max":[ + 1.3671875, + 0.984375, + 0.8515625 + ], + "min":[ + -1.3671875, + -0.984375, + -0.8515625 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":1966, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":1966, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":2904, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":23592, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":23592, + "byteOffset":23592, + "target":34962 + }, + { + "buffer":0, + "byteLength":15728, + "byteOffset":47184, + "target":34962 + }, + { + "buffer":0, + "byteLength":5808, + "byteOffset":62912, + "target":34963 + } + ], + "buffers":[ + { + "byteLength":68720, + "uri":"monkey.bin" + } + ] +} diff --git a/resources/uvsphere-smooth.bin b/resources/uvsphere-smooth.bin new file mode 100644 index 0000000000000000000000000000000000000000..f8b6e5cd108dc4067d8bf9789f596f67d82c790e GIT binary patch literal 23648 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 PDr7Lsx`S+kIQRbmKkNhC literal 0 HcmV?d00001 diff --git a/resources/uvsphere-smooth.gltf b/resources/uvsphere-smooth.gltf new file mode 100644 index 0000000..0f65991 --- /dev/null +++ b/resources/uvsphere-smooth.gltf @@ -0,0 +1,109 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v4.4.56", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Sphere", + "translation":[ + 0, + 0, + 0.003377079963684082 + ] + } + ], + "meshes":[ + { + "name":"Sphere", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3 + } + ] + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":559, + "max":[ + 0.9999997019767761, + 1, + 0.9999993443489075 + ], + "min":[ + -0.9999990463256836, + -1, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":559, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":559, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":2880, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":6708, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":6708, + "byteOffset":6708, + "target":34962 + }, + { + "buffer":0, + "byteLength":4472, + "byteOffset":13416, + "target":34962 + }, + { + "buffer":0, + "byteLength":5760, + "byteOffset":17888, + "target":34963 + } + ], + "buffers":[ + { + "byteLength":23648, + "uri":"uvsphere-smooth.bin" + } + ] +} diff --git a/resources/uvsphere.bin b/resources/uvsphere.bin new file mode 100644 index 0000000000000000000000000000000000000000..dda76ba8ec32da7b97d7044c7e3dac792e01255f GIT binary patch literal 69248 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 literal 0 HcmV?d00001 diff --git a/resources/uvsphere.gltf b/resources/uvsphere.gltf new file mode 100644 index 0000000..edf10c7 --- /dev/null +++ b/resources/uvsphere.gltf @@ -0,0 +1,104 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v4.4.56", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Sphere" + } + ], + "meshes":[ + { + "name":"Sphere", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3 + } + ] + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":1984, + "max":[ + 0.9999997019767761, + 1, + 0.9999993443489075 + ], + "min":[ + -0.9999990463256836, + -1, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":1984, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":1984, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":2880, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":23808, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":23808, + "byteOffset":23808, + "target":34962 + }, + { + "buffer":0, + "byteLength":15872, + "byteOffset":47616, + "target":34962 + }, + { + "buffer":0, + "byteLength":5760, + "byteOffset":63488, + "target":34963 + } + ], + "buffers":[ + { + "byteLength":69248, + "uri":"uvsphere.bin" + } + ] +} diff --git a/resources/uvsphere2.bin b/resources/uvsphere2.bin new file mode 100644 index 0000000000000000000000000000000000000000..c71a8bedfad77adf2c623fc4d5a0e310ddbd1991 GIT binary patch literal 70088 zcmeHwb(|bU^M21v&rHw+cL*LlxaD>a4Nh=}!yN(ymmCDw;10nZLK1@IW`YKHmzTrk zMS{FIxnDiq)l2Q|^kn|}{`Je}GgZ&dRQ2{&J=5D$-M3Lnspa3_y8hnkGv)*_0qF`|Y=V^D3C)QbSJfUF)d43WhCmihNbU+-4@)FU8#FO!U7Lb4+p(pHj>* z&qe%8G3PWF`7OoFnOyLv6f-9gd&kdGyb|#fe5N>MRq))u&Q=4Tf6)(QRq%5U>`()r zhwKyORqz|wUkL+`u;q%TTzRjxO%@%&v@Oh3Av*%SXwQKnN z#9ssRDwyJ4$N0Q4@xKbDai@-P7UNqLjPDBYc{%!iuY$SFOtfE$xzCyCe<|jetO~~c3qHSo&e>TNyeaV$ zBHGn<}1t#O#<$)@LLBSHVZLEpGUX@uv!=?_I;^Q)&KF1#{ok zWW91;1#_F3XulM5pEJ?_Qp_>QMSMyz#~iVD@h`=k(_G}Y6fAo_2e&&H4vMP8N8b873Bl=vIRlzha7!K1okyXJ@{x#6>c}E%_s$lxw zHGJNJ=08<1jhlwgZ8ZO{f(O!PB`&D9=ozL8<~B3YektZYXQKb5m}8QQ_>^LfIldcR z{7W(CG#B|T#mt#p@TU|rCv(BiQjFNsvng`W3H1N*|7{#zq32V4Kd$h_6ONq_pEi4;q#1C->8BgrC5T`4^U083g$L5(S9lBK4+r; zrI=%qi};jcj(INPUy3=WxyWxRX3iK+R>924T=26LXSUW$IcQ&@3Z9j?06r@wnonNd7l`=yxsOg4@Fmtu}dj`piyjyc&k;$Mn6r)1N}Zz*QZkWGU>rIDJ4S4>H z=e#P|&6y|Db3hgR3(ucv{iRj~Z^!dz%xS7%ZZi|@mtr@67X8o4FvkQnNf)0|%rVbJ z{7W(C)U54P!EXL6_>-4m=43ATS&Coa`qJ$*epbOb*O#6pepbQTbA4$TtpilSsPBQ# zn^2v#3Vw|1OQ>^K!CarIZBF-D6^!u>^9{@stKcQf+)Al;X`WRDbDNoHzZ7$yGtvK2 z%rP-*BULcRJQwjV#hlYzc92>S`|E)>;H&P z6+ES<$E;^TSHZLVTmP?D!CXJD9Zuf^Rq!eQ*8l5O@HccX;(72#(p50GiJHG_zZ7$y zGtvK2%rQaD-^HgCbIfxQ|5D63HJq=4nKP*QJN}en=43ATS&Heo)U40qKB$79vs zV^_h4^7`yfG=5gW;627?j1yJxcGNDO2Op<#x(ep?&Dw@EPpN{}w9398^%sW0I3 zGBi$C!Q7^)%~ZkM=S=j!6mv{+5uZ}bF*j?2RWRo?7x^v4%o(#cqzYzE=7OK4cv;>* z^8(GItKeCA|ICjx|Ez*D;y>yem|IoBn74t?7>BChN9eu+pD|CYf-!%?-1aD{Ls!9T z^ZpskbF1K(+6JGur+IW0%xz|({Zh<*&P4x9F~=kq@hQa|bF+3`1#?bwk>66xoXG`$ zN-=XX7yK;6n)f%(K+o@0@KVHo)awRQo~z)iX#5ACG4@o!c&-MY@%>u`PonP?@Ocp$ zKdWH3rxEKs+@tz-*6kEc3E70hjBqWx0LeKwr0f;lF+h)*fznCBw? zrI>S?i~N>i=1ea5Q;M0Bx!`9hrqGYUddkB#4;667SnzpfGY(b3uZ#ts-!$LDRdDAR z@EP+@)Ig_>yT^jhOVfKORRyaRF+T5T>kt)izj5I6!=?^b1&1XeV8hQ`@Us+S-35G}p2ndn zcy(GAbmMasJeJl4!RIw-e6E6N|4#-EkD&3n3Vwsu1;OWqX?(7NU!-+G@cCyNpR3@x zXU6;rJ+1D{dztb(!c2YlX`=Fe3y&Dji}@%&x|({rie z^ZGP@u7ba(eH-BOku-meFqss3CA&r18w!RMKX|5fl%+IJ2< zA4cPU6-?i|hR?%k{!;~iPy5cn=g(=*R0VUJnP|ThbDuNO|5D5`$whohF~>X?@h`=k z(_G}Y6f4L zRZMHLW;}U;#<(h&+s{P%rI`DiiT;;jj!!P)Q;Ip}xrl!$=A7mtzonQtlMDWoV&-Hn z_*stW`4r!eOHv*5*$Vgvn*W2(v(P#~6-@Km9DE*6{I7y(-fZ}cb-XH==H-UN<7xg` z1rMXYn+2b#@3ktJo_h_4+o(QV1#d-vH;YmE@9&?eRl(e5CfYB>+~-X6zZ7#!auJ_W z%rVbJ{7W(CG#B|T#mt#p@TU|rCv(BiQamlyPr&CXpUu-&1+PcXr{FW%uYzgZF?_y& z#{Vi9^B;`QSYNJ!Y2I)6{1`odRlz?IKf!10Gpd5A?qm4e^Zh!gF-;YZ=HI!vf}VM* zU~V%L?U!Qib0+#i=8WNF70jH>1wTvic>7x*b)`m) zse=1b{RDib{l;Uf;MeSLhmd{$aaFMRyVJ2%@RE(cyB;{M3Z`{a!)L55RKX86es^Q! zxGH#Z&u}w7D>Zps70hi?O{&p;Dds+tO{4#%m}5eHYQ(1$bIkD>T>MKh=ag(3`7OoF z8M0~crxY_M$)>^2QalZh&wXeepbB1>$7k9z+*Jjy%H#6`RL81j5=!-{2kAq@%MLC@CH18 zo)&RKe@;{26ubD)(&RWP@iiS|n| z_c;^&FU1@a?7wpHDa9Of>^XPwFU6eGT;#VDGiS`-Y*xX{$z1TW6hF=NjT>qFtb%EO zk(qDc?@z1XO}M^s7_9?T!Km+H{*2#$sDdYOeFJswDj3g4m_MVAUIj0}^$pbNtKj*$ zzVSBAv#MZj6MN2G`=yxsoQeLIVvdRVO@k_!V{U$PqYCDn<|4nPm^owqX1NMxPUeE2 zrTCVA>;G95{L;Vm|Evlg#P$F6Nms#t{#*ags^A&9{!ja?^D6jIuKz<EPj8W3f`O7XQ}>~Rl!&v!T5|iXcfE}wae?Xc@?|~ug_wf ztAbadaTDvawErZpf}iE}*~Ms_u7bJEOtfE$xzCyCe<|jeU`^J=rxbI{u|DhKUy3=W zsA;?WmSX0N`OS(dm^qmXewN|^ynp6tnnzc`Gx7cz%-^cudx-z2KW|7ps)App`xbmg z9kdD_L-!T`0TGr8bTDP~UQf}f@MpU>?6#+07ltKh|n|ESmD zIkgJzr12koUYW+ZDj44@;4^BURq!M9y#hXC9$E#XJ`X;FKUMHw-`o9-s6$l27xVtc zyQ$7m1#_FGc2Wg%pAF}$V2+8In^(acbL?q!@h`=k(_G}Y6f5IGrcO{9XTp9e*z|$+)HV7u z6};a{bU)@VnR!GNY+{m&_)Gb<8$Nl#`WjoTmoGiw{Ve}>N}eSLi3_~eM8^yT>H zxJRTf$3I7oq%Y?;7x$O(<^1O0fb?bldk#~X(Kr)ZQl9sBejoySg^j%hs(Oy8g5HYN}Gk}db4)&(8;wyTMc%(Gu)P( zls1p|{j#>`Zx`2QS-8Os!)?h)X|vbB$+e|uyxL@JhvS~%w&bC-VcZ3K$vtV$?PutN zw3pnIzTE!|pHccs?nz&cPmUN$U&%e`%kj^VBk3!-Cw)1;IXEDFeY|D<OPt8yyRz}F;~{EJbm~2Z-XCo zeKXvaoRl_u-ttoIA>#8~Fa6~{yWzIvq_nyFoWE+b{Ip^$`u{C5rS5ku)KxhH)&{yB0aeI@s#FXuN0 z2c)m$9`h;(uVg#YS8|y7nG1dz{>yeGhsn#|^`6 z$w_HLyfl2K>yCSd+meUUhH)3{CHJH~x1XU8(q3{;`f~p>d`9UjxhH)&J~?72eI@s# zFULPej-;>T9_Kwr-eo(|S8|Vem4jElb|i-jzLLY7_Z)fmwIjL5yvo5V zUptb+%+Fl#)7%TP9q~NaCUBRSSGNh=CFUn>0(XgVzD?jRalf<)+$HXvHi5fne&2@Y zP`szW6X&3`lBc{x7NGh`jl<21j{eCOr(K+mXkJde}--Q7E8Y>{Jvw4rCvld*6&LavYF0jJsej?|o^{?PutNw3qk3^yU6% z_>9t5-uoOkJoC9Ym@!29$}xoVf@ew>XEXlDcH|hsdC!q|Upw;NXI|ytm9HJZtWm{iDlwUhIMU@wrW& z7h`XejpVSj5#w`B+DNX7akVDxB|oLT7+2lr0-s$!G0wZb;l5}SNLwIjw=mv`{F&DV|?R~@gw=Rv-9#Q5y^2|f?ZI=395qn)+}&g&IV^3&{2A*3ZjSAEi1~9( z+KBmZla1u9m;*asJg2+%4L_y5m_NJz5lu zcEr5bkXJ;2qVnm8nGAz)P-DLP+zLab|kk&UC8Af^(B`_)K{C@ z5%nd%p_MI1Xe%H4;&iT%BeHe9q z_u1<>C(m>J8MS?5hcz!;mmHQhT;Dc%lQxpW(uV8jsO{IKjpVSj;rcvk`;JSfwK^WQ z;kvXD_53Cq$y;vU)J>(m8J#crI;tebanz`*<#Gc>NS>uXP{K zr43{3EAaB1+c$MPX)n1ddCvVe^)~4%xhj1*J~_rz=_|P^eL4QvSAg{u?CUVTKAv-a zv9F*eeI-AoFY^cMs*YEN+p-<$%lyP116PMP-1fDDxrnQiOK$T%2kZf>Ne;{Nymtb7 zW14IvhouegufV>qy0noTmNvY91AAl4T*TcAlEc!5_lKCdq_mM7mNvZq#LPFPjpVSj z!TcC?X3S@$jpVSjVcZ3KX(M^d?Pr*eOMA&xY0v%7Fz1!NlB?2}C61YzAv}lWVkKck-m6Vbn8L9 z_YC_RYm(=Z!_o%NvAjpLCT%2#rH$CTSeG`E!_r3VUBp_8+XLz1YtpUmHd>x%unoZY?Ho{=hD~5 zXFBiJI?>Jq-p5<>@mbnXKiytv*JtdPcAppfrOjH4yB8=9Zof3vRNS6w7f0s5eU+#@LVT z+Hc`Aw{Ol%d&x=ezp>%|JD#Ke=Dg&j^yT;%u1a6YN$Jb+cRWY@jj!aS^koh>o+Ee0 zS8`JNGJhP;!5`!6<1_Qq@f`d#zCPX(|J@pW8}Yx3_n(8$4Q@-`N*lVrGTwg^y*xtF)nhQ^kR_n(8$4Q_K@-1ywEq46i<{pa9wgWHmm(uT&jjQ5{|Zw+os zPD&dZKQlKEkv5Wt(uT2_hxmBQ?Ynsh+Bf!+d(xNt@8%)szw!0)mSgDVA&8Ih_3@VD z@5X1u*7*8(%lUQl5aieR`gqIyaq|%H$N2hq%ltI+5a}y*9pQMSyG(X9B|2gIr z4Q@+LN*kL0WW4_zbEF2hB`2i~&95>yHG?EswF`M(a#GsR^KZuc&+&}h;I`zX zw4rD7jQ5}88N9)5$w_HL&+j?!KgTnEgWHmm(uV2-IqyHmb6JK^ZKgV-l zgWHm~(uOhipS$*3c+2g(Iv(0L_CDTn|6Lsq{Wrco-g11f{~UEZ#K-vhc+2s3bv(r1 z_)6|cU(PS~pQC<-{2E`$J?4+AZ(s~G{FlCx!^}@t-vAF?odobao>zD_us6QNM9dsIX>8bj#wBzBR-OQ(wF0J)<>kT-Dvz1ocV0%N}` z)@97SBQW;6n)5g(F!sBecF=~vZojTs(-dp>6MXwp$?8qz6M7B4d(tkjQ$&6?!Uuszpm>)_UoGcjnsea(KUMy1fIa{*U>(=Sx1|4te{%0 z`A(#<4QmjMu|=MjV+GZM&3uo>PONb>#uj;Ajt^98&N1Jk@fB-Zjj=_Zm*WG~vUA@5 zkF~AF*doUSX+!IeIq&~>_YU^|V@;|tZb%zi-^_Xce@)uRF+ti;eLv^@|21hN#{p@> z*z7Bi_rA2}_RU@bX)o`6>C64c{@a@LmG{2%<@mVuS;WWq%6p&V@78A#XEXjtUwQ9y ze%<=48Bg#XA={DnKJ&+|&zkWB-$Ak+$$!atUptb+bpN{j>e!#q;4sx{Gv0sR<~uLB zO0{6~%tG^1*Edt|m**u{sn(qH{&V-)4Y%ca$xo_f=e++Mdk-4imgglusaBrz{&TE# zHMlJ~DQ&10pY#56#|`X1$NrWEZ>0^@_jBHV-eey#}}CdC5c04(e-1a*yNh z_BSHV=3bEPNbYgo-Tp@8(cBBN9mzfBmD}HFYDyRbWjm6?VqW2RXzm5sj^wbw*k|70 zu)yB)lEVUH|9OMk0%M^(2}DKPe*H@GdZ`7IS~ z_n%|Es=;Tf>F4e|W9&a~vXT7cHVtnDcI``F!+*(Pfn7ga+ToZO4h!t!VD1Ikj^v)e zF3v6Oa83<}1$KEf_kwIka!=q1!WaA4!5OsKB{?jxbb`w~z4|f7j;b6|Q~ovx(bcTy_07e@`LXk@tldpIzL*Nf!t6_i>WX zVtjUa0VnbIL2i6*YDbLEF7JkizIMd;?09AVp2F9T7@r+K@eGK+55hdb{H>hivzR}- zXURHhu40~?Ne=tYZ*}XN*=+PTZc81$S0*{^J1^$XndGqVyqG6vlEc39VxEk3ng)mE zc`<)(vXLB?He&vawIVkUaXiHS^Gw=^`E!$v4X`n|#Im+2sXu25&o@-(2L~{r#q!KR2}_=Fg5-hLgT_ z#QfRu)6_S7?TGq@tC^s_f!{Z9b&O1M*mqvkF*3uYPIS35e}gP-MC}529IxE(8@T#LQ#+!*;rMC(Hrdw>)#*?Jtiw-oTh#gS8yyX9`_79xe=L`;JTIw>~6?r43`O zLpiQukGjK#x1xrR->z}%R<3=+Re4`<{|z^#ujH!axv1gi(pU0R`ilBLe%r^bTeST=Kbfc25z`5 z)?x7*N`}wiweP%GhjpLP{XUr6dw}2ma%->VGrQjqlN^>dV(rz$(fx*)p{v%v3zx%F9xu}`4MM)H>1H{S_jJ=e8w zz8|E$Sf9mji@ALTuKz7O7wfb5T|2k0z{Lk^tWCZgf9wq~<1g;-1@BJkNIse<|o!`96t@WeeH<-GxE1t@cWH!pG*^n<$2!! zfc@u9Hj>-YM(mA2&ApK4(njo!G2eN-$Hv9EjyZ+25qo2rY$S)Jjo2IGa&79F)J9g^o_|BQ<*_I;_RrwAJ>9-9#~;IU$!)QJ z#_`i|Tec&)&1;(O_jTRgMf|?5+uzv4ZLz-*zmMwnF5-7e8~m3XmNsI4qj_d``>Tv25xYlB?2Q>~GAaujH!q75f`qd`w-$$8)j2(Zv?~a$WqfN72RB z@SO9Dy|wuLYPT=f<+p|BVt=FKkMWiKlsuPf?$THCT>6r|`+JI}-;4I~oZ1}3d(qLK zHr_*y-;r+I3%xk(_LH01rQ@EDx0J88qTk~7@sRCJjwruvyzjaxcQWtvzUUA3Lc9K$ ze92r$4)AAm`D*z3^2qVQUT7B|<12F^eL4OuzlZG0qf+g4I%?&56kqr0Zocl*3hzOO zPxs^N#k!|wIdi>OuU4o3m$XeOzoz%4{;h`h_vm&$@6(-p-KR(Kb)TNd*L}L1ulqFO zX8P~Zh#Ri^bbr2HtOGUBT=!|4JG9@DkR(_vt_lHP?&vK+dmU+q~j4 zD2ii|Zs+qp-O1N|dK6#x>4|*Zr@Q&OPb=!X>9v#vdLm!<>2AL6 z)5x#sw@3Hm^FEEd!oOJCybj>}wrZPK_#?kX8hOR}Vr}y}fb-j`Z9eh7Lrpu)xptDh*L_-1-%Yzg~o2Kf|x*<=5}<>sk5rSNwWCF@auv6dTaA~N>5*Yy)$3$rnw>h?ZvN0@#}-k>prTTUk~H!orA3X z3H-Vr^V_ed z`qce8H~v$4inUVR+`dmM>bvQ;MRABd_pl)$l`JecJj>WPhLT=IgE6 z_Dj`$+P06pqJKW!pRc!Sw2S=uv~9PY{adx|*AU~6{Puor{X5vdSlfIm&Tnr$xn`gL z{hHo`;x`#F^yqdz@6(-py;$4(cCyJc-lz37?pvHM*7p8I|GN2o{?$D_KhQbkw^-ZH zg}lPwr~C8uVr}o=$tKTer}t}n-=h6?Zl|{%!smUugRd8Bn_s-|P-EAuyE(spZS#tl zDLR^UPtQlSzkBL*{}pR%-^qTh8h-ZvE!Ob2dG%}i9_Ux=f%a{Fi?#JZUg7K0{rP&U zM*H^uE!MW(cJ^=8wtu*9(Z5!0{X5vdSlfJdbAEg4S2(|ZZS(pH;zUQQMt*VLr|q~5 zzfSgV*7kmVh40%Uof|)!XIyW7ZDWA;k)vka)AKT&!~I*Vtv&JzKcDW;*Ne5iUti(- zwpH8v7VWolJH7P~KJU{Ve7#uP{NjCw8oOpak@M@uBnSqN7#Y`*t+nzpdKdxA5y^|7LCTJDT%b zr19BtzF6D5qWx}e|6e_&=RP`z{1$8bd5~B5`*eT4UaW0?M{|B#waqKqZ|8P;>mhvJ zr#tw1v9@`|`wlgB&3Y8)*RO3}MHpwQBo$kXQKobbr2HtZjZraeiC1%`4h(=XQE)FQ{k(}QmjlAM~v9@_dzq?^)#4f7twXa(;_6@{04V+V-oP`{&chuj#)>_v7=e z+V;CY#SQ&0);7PLoZnV$^9uiVZs%WZ`!$5$-={nHdaFjiY+il3gY)ax_P*_){wX?I zwH=>3Onz~__iGy)_;s>>v$px|;QTi0klI6DecJY?o7-*GfA{Q0*Hqo7tv~MD4zg?2 zcwgkzr{QPw>(@4~Xa{*M(nI*XPj~S3Vr}z__aAEPnsqzp*RO3}+Yu-9u}M#%d)MB# z#oF3;vR|>b`EBR?7HQnKIA5&oxQu>v^ZWd((LdzZr)~d`SNQvMf4*L~rPZS6bRw^hRr`Soe|+xxay z!w>hZPycSNtGZ8HzkZZ=w9~3>|NGN@fzRKn;fMVCwDoIe|5k1NhOmFDw*DRLU##tY ztN8xyt+77ZN&Z+Lg~s|QG}cFbSJ_?QX zQE04>LSuatdMLlf`Y2ux;n!Fn#p{9m8tbE-#N)pF8tbFDj`dM!tdBxteH0q&qn&lT zK8n{^ABD#HC^Xhbp(nOke*3kJ@kDADId0Z}_vFm)Vh#Jh&D_qf!Ef04G`)wpUZnB< zmfywt7js?JecJki*YI!E=vQEPU8V<`INI+i*fqbl-%p5JyRnlR?~8HEr)|7D*soYy ze&c=d{nT4`a(?~V=Czaa+o~~s5m!u}duvVS>0HgHVgI+uZ?T3SeRkttq~UMp9mSf* zX73uI_3uYFPUw1fQi){666 ztS9&M48qu6uIuz22fJ2H+%Wm|Y53v3^=bI=_fw&UAM)$d-R8Qg`?U4L-VU_Ws%^jf zQ{3=5S~dJ|zxuTGYiIvfZT*I@f3Ze>ZU0&|+P8W2YGpazNo@?L&p<1DM*sWTes_1e z?{L-n+T6c%9h$y@?ETz$ZO7+M8iR(L>%CvwaT(t|*PH9TU*oWUYs44_{`J!4zNK?g!wj#Lvg30ntuOcYydFsBaUB}=&9CivjQ4Lh*L%O_ z`Iojm4Tw{F{cCA{eV>qwH z8h#j`ecJjXpYU(h@S8$wi#|Ptu2aa#rdY!ddGu+#ugz<*h9AafpN7B9XR(GK@>r&k zM_ez`wtvVg`d6ffa(?~V-mgPVo@qYVq8Vr~6#pTfUb+q|NGu<5P$Px-@jpGIDBy;vi^xL%~uKjhb^;b+HTzqa|^kMrEB z?fhy#lV`No`?bw4+Ha?$S)*Szzs1_xBd_o));6!`7xLR%<2xJQyMAr++rjy5)>G(j zXD}}NG}^WIZ?VSSeS7~FYwM4^!oO9+-`>B)8vU~OZ?U$2JAm;S*IPB-*XFla!_VHo z#Tx!Luf-buLwDi8+E%Ciutd-iH^INRp zXY*UEtv~V#|5gouo8MwRg|6HD7Hjz1ycTP`ug!0G)Z&2O=Wzs+y4#{1g*7Hjz1{1$8Y+x!-5_}lyzYwM4^!oOIDCa*)NAINWSy#eR9 zSoib{HhC@6_Wm8rd2ZH9Z9wnYN%<+(XxHYqSi{fewOGU7=C@c+q3brk#Tx!Lzr`Bw zZ}VHM;cxR>tnKG+r}x0;Z`JU(`Sok$)%LShqg|UOgTZR1lXf5fL*qfHwhpS~fTj|eiK>aS)~cc+6$4pM8W zo6|roNOD27h`K+WRkf3BSL-RQZb=sylhoZK!TfW3>dyCDhXD;dETO z0m%*2rmBy+BVC5%GHRgeN*_u$A-Rd#LQSuxQOlEDUah3AN!JVJRA1`}=@2!OnnA5h za%Hu;8kJs~&P8%AHLtoS?Wa~Fxf+#W=2!EmZAoscwpVqvh8jk4m^xN1q!v&+klaD- ztk$7Y^D!ikQ75X!)WT{PlDnu5HCU~!P9S-LI$15L7FWBI++FRZHdgDaPLiGKG_|Z+ zO6^T@Z?&J=Ty3OIC3&hkORb=mQ~Q(LUmc{jQk$tWNuH_BSF5NM)xjhWRzua?YH+Z% z`mf$novYSVtE$6D9;S{|2dI734J2<+H>-2h>1ra$iRx)}h&oW+Lh=@MyShM~t)3+L zqHA;|1tG7tLrQT7wdPMy~ z@)z}ox=~%P-X;02`anIQGW9#j-_;a#tGY>jNb*C~ttP3*)!!ulR$*|bx=nqo?xf>0 z^}KqDiY(95kp%auyVU3EUOK)~7pjee7t{~>S*3%q>OS?g8cWA_>QZ&FdWqyq>J=3Q z|ESf1C|E711z)I7)qEu93;G9X5Cnrr4hq%|zENMQ1xYR#EE2q^HVyg&Kk7JW4}MVp zRg02bG*}{dRlTgHB{^*{eekpTUJW2QAXp}NQ@yTcBspWyFZfOUq?RVRbg+Ezo_br& zOmgO6*5EJos~SjhV6aN?AN9VPo#gDnoI#KJQ>{dDrC^QV6ZMgri{xCvyunOC-{1g} z2LuNND+bF3XOcWKI6s&zm?b!bD6 zGPo?5KbR*tisVtjF+n|8J-CGACBap}Lcs#Tu_TWTP6*Zs+JY-cUJ+auEEX&roJjJ- zpfi{z*gV)P_*t(NToWu6EFPRp^5o#OV1{72U>}nE1p5Wc1xp5}k~}pyD>x}QJ{V7O zeDF|keQ;&)2FW*q_kvS`;laZs9}Xr2dk0$tqk{kGYlC-!GlCI87s;;Rv0(pT-{3}) zHwHHc=LDw*6G=`Co(>KU4h(K3d24Wca6xc(@FdA6gXe<7f0ibYmi(c ztcRZkp9Bk#Tp(O9OhOg5k!%as3BL-y2o@%}aJXnVZK%VwNUjwQ4!;e)2^J^0c(_D3 zW7sELkK}sc#^L1P`(P=OONGmXGl$cM84(AQ$4!0w@UARM7 z3s(z|B6(DJeAqvnKirArPT?-$+Toz^IFiSO!^1_wg~HuP?iTJIZXIqGTp0eO&kdgq zj|>kB?<09%I5ylq+%~+J&GcXV??o9NrLqMDnBXlkn+qV)zfqf5IrbJ-j9SG`yXTFT&@-C&M6mj*c|CJG>+O zGQ69PZ^Dt`Cc%s0Wc^GSNB4*Kg#Qiir{jn4lJKJNWs)z4uZ9nVqr>k>ejolEUJ+gv zzE1M>@Xheya9sEk$)CdC!fV2-!naAj9ljT4;X~oCB!3P63cm}#4i}HUqhqP)mGGr- zI+D{xGekdz--b()TryfNdLw)->`St5G;{R7aB{dT$z`Jzqj$o$!dXbp63rg{5&jac zKyrm>)#!up-Ea<)b3}7TQ^McFRYNPABOXgoF|$;3ZuWnHAt=z)uYeCkHZB> zE)XpgC6S8SNVY}mL|=uUhYOQjI9e=vA>1gKCi-6MXsu}VXr<_Il7~k}NApH=Mca|w zF4{4wMXNFj`a~N9>qXz{ z_UOcDiD=PiPm+5^`$p48(?)|y4vsd8mWc*L`;goxIxy-N%@}P=a^q;TX!&UA=m3%j zM2AMRMl(g5liWPoGFmwr7#%|Lkm!hL&SqjFBQi8hVSA$d-8esoxLP;@8BJEQxet)nfX3rJoN zT@)P|4UO(4d2ci}+CJJQx|rm}(Ph!G(XeO?$uZG`(azBh(d8sBkFJVNijIlKlN=vC z8g)dwL|2o%I=U`eH(Eb9CHh965Iqv@9qk@nPxAWcrs$IB!stbkFGjCK4@9G*Z%KX| z{TN*lT^hYg^3~{#=;3HwG@0b&=zq~Q(UsAgB;SnQi8`Zog9*`B`l0BT=!WRp=v|WU zMju9}MkhyIB)g)C(Jj%a=mU};L?1_IMyExOk$fzAGP)zWIqD|a9ep018=VzBMe?cW zndqMA_UJQ`pG99sBct=8XGuOAy%60W-5q^J@~h~(=+o#w(VY5II_A?)M^8lmL{HP9 z^q0{m(Yz$*)eGw9qDfIeGSFK8H~J##PqM#WRKFBGAH^hN-ADfreG@H0auL0Rel2=A znug>wdV2kH^nEmdlek*!Cnt|jDx}W|n`YBqPUzoX^!l6o(_93A`XnRQ>iA;}H(CVEA^tlp30 ze)iF^fr1*^jEYB$yM}fdI3GR-kRjr zdIufqo@jNFtLs5}VLiXzp5*p=7oF%xw~=hq?Rs&&klvZ(&U$w}txok?B-hgG>ZSBz zx`Skg-doS8r_<|^Tu*PHchfuSOGsX#uheVnx;~cVvHAqPr`}axLGlWHtzKWRqfaDx zqVClD>OJ%|B(Kpo=#BJXeKN_D^{M(my^kJ6a+JPRZ>Bfar;$8OpQ#Vk2k4tg-mLG` zTk6gAStQTW=jxU9_Q6p7w?0_kuD8=$>GMdQr!UlN=vDRMBoEia^iFzPJ(A=|eW|YL zHT6*>kJ88JyYwx(n`F2CLZ7eC(NB?lNn%t>nZvU{h7X4U!b2Q`K*3XAE}Sf z_maFtC*NsiYK>Ff1X`VEqA z=y&xgdboa=V$B)o4Yy7@`OAjPDFkU6j^rL!qlC#Hi#vkeT^hzXGir0uA*SVgHKRGS825|c*01TMNiH2PA1@X!9PdJM zm$)OICf+qzHxA>q;uGSf;>F|LN$wu+9nTO?7q3rp{dj|Txp>KVFOqx3`^PiIedCQt zZWM14uNW^I??-aK_>g$Ec$RoGlAFat;#K1n;)6&Y6dxYX9nTSONpj10>v+w0<#;H` zq481i{P8^Twj{TWw~y=b>hUm=!{X!Oh2jO`9Z2pF?;Niax5dYhJSILVJ|aFizMbUl z@xAdj@mBHqB+rjW#z)78#dnjuJ02777;hI}MDn8elKA-e$apl#(ee0r*LbJ+GLo0Y zSH#2PW8-lo$HkAtd&IlNSCYIkz9wET?g%<#9iJFK6z?1F8DC5C+W3Ze!+3Cf3dvLA zQ{w~Ued19hN5!|so5mZ*r;|KAJ~KWvJ|MoCc z{7ZaOe0}^r$@k-r;?v?0aYizWC&stMH^v{3{2=}$J}W*Wew^gv@ssgg@hx#T$?o`z z_`LX>_-T?)$Iry~#dpM?k^C(FCcZGfAbyVIbMXuD*!Z6KE0SNu-^Z867soG=d?|h< zelWg2{+8so@lWwp@#XPrBwvf)h#!p~h$oYr9RC_$7hfH}P4eydz4*oW`8Z8pq+^=o zhxqGwQId-$OC_(yFUQl7oGzIm`8obBUV`Kj$#Th?@#}G4l6{kzli%VW<7G%LldPD$ z6TcPDLUNX5_M|J`Gx#G;0?HSe(ay#gEbPPyA{8VLW&8DIE(W zPsLBfJ@Hd?1j(22$MJk5=Svn&o{cBP5y>ctlmEt_#|x5NFj+jAE150XisV+wcFEuI zpYf_BS4~z==1=BKwk5f3vQwgxDe;;l*G$@yg_3!b9ZBw(?3U;xOzI@-$y&)`N&jRQ zlDi~(CVi45S%>60$$H6>$s)<_BzI5tO{Pz#O$L)3oNSaVn+!r;M3dz#R{v`KL4ozlFW=b|Exp}f>@_W2{uyPW|1CxW3eUgskT9Vf$HzgY+>n9^f zjz~^V4oLP&ZXkI>a$B-VvSD%t$up9(lS7jIl3PgLlH8RHNj6Q+A$d-6ezH=sTX0ws z#RnyKB-|9 zSavU743$*Yp|+sO&Zamj-uA50!eu1l^+-Xi%{@_y2poRmCD^3fzqZcMI8-Xr;5 z@=4f_su6zCHOoxiC2|d7k9+$;-*Gq(6K=JWb#-rChsP1Br~V)(lJLm zA$d6YEtx>apUHoccaquD|Ijf{Ix*=={z@j&F(sLte4i|pPNrkobf$FrbR&`*rJJX} zBtIt0kz6iaA)PJlmu^OKvvjNU_vC-ciX>M|S4rneXHB;xxn;Ut`gih2vMR|{(>2oh z(>c>^Np73&l&bWfWKEK5rnPjTbl!AFk~^lmr8*7LI>~yvcKTJaL$GN2kM5uDl1`Jx z={h9WNe8FjCf_8BlUzJqGMypqldey4{dD7WP`Y}0G|8jWTElRP~=CmonBlO9O&!1R!Gi*%FpY?5cE7o;nt%cqBuJTyH#-6kE9 zo=@`p^x|~2bmjC2l1HRRr8}fsrx%gDD7`#AD?K&6m*lM`O^2ojr?-*3Elt#g>AC5c^g=owOoye1rKuW5$DQe=>Bw|E$?@r<=`rb%>0Ko6 zN~cvNGm;OZQ9nPH!T4QyNm`xija3>erSSgeiXLjYzo?$NNX?VA8D+!;WgUm&$b<;aUQR69@pD(jkFT4aUJJ7aE%W4+O$Wu zy)~|Px^{(z9sGN1*yDV&4ppqCQz0FFR6=Vavyq%l&8%isbI>ZttaQvst0a9@L^7fV WQo65ZAUT5y)a+_nrAcblPyIjlAF==d literal 0 HcmV?d00001 diff --git a/resources/uvsphere2.gltf b/resources/uvsphere2.gltf new file mode 100644 index 0000000..6657f6e --- /dev/null +++ b/resources/uvsphere2.gltf @@ -0,0 +1,190 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v4.4.56", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0, + 1 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Sphere", + "translation":[ + 0, + 0, + -1.0871706008911133 + ] + }, + { + "mesh":1, + "name":"Cube", + "translation":[ + 0, + 0, + 1.0190757513046265 + ] + } + ], + "meshes":[ + { + "name":"Sphere", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3 + } + ] + }, + { + "name":"Cube.001", + "primitives":[ + { + "attributes":{ + "POSITION":4, + "NORMAL":5, + "TEXCOORD_0":6 + }, + "indices":7 + } + ] + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":1984, + "max":[ + 0.9999997019767761, + 1, + 0.9999993443489075 + ], + "min":[ + -0.9999990463256836, + -1, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":1984, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":1984, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":2880, + "type":"SCALAR" + }, + { + "bufferView":4, + "componentType":5126, + "count":24, + "max":[ + 1, + 1, + 1 + ], + "min":[ + -1, + -1, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":5, + "componentType":5126, + "count":24, + "type":"VEC3" + }, + { + "bufferView":6, + "componentType":5126, + "count":24, + "type":"VEC2" + }, + { + "bufferView":7, + "componentType":5123, + "count":36, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":23808, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":23808, + "byteOffset":23808, + "target":34962 + }, + { + "buffer":0, + "byteLength":15872, + "byteOffset":47616, + "target":34962 + }, + { + "buffer":0, + "byteLength":5760, + "byteOffset":63488, + "target":34963 + }, + { + "buffer":0, + "byteLength":288, + "byteOffset":69248, + "target":34962 + }, + { + "buffer":0, + "byteLength":288, + "byteOffset":69536, + "target":34962 + }, + { + "buffer":0, + "byteLength":192, + "byteOffset":69824, + "target":34962 + }, + { + "buffer":0, + "byteLength":72, + "byteOffset":70016, + "target":34963 + } + ], + "buffers":[ + { + "byteLength":70088, + "uri":"uvsphere2.bin" + } + ] +} diff --git a/src/camera.rs b/src/camera.rs new file mode 100644 index 0000000..53b3599 --- /dev/null +++ b/src/camera.rs @@ -0,0 +1,24 @@ +use glam::{Mat4, Vec3}; + +#[derive(Copy, Clone)] +pub struct Camera { + pub eye: Vec3, + pub center: Vec3, + pub up: Vec3, + pub fovy: f32, + pub aspect: f32, + pub znear: f32, + pub zfar: f32, +} + +impl Camera { + pub fn view(&self) -> Mat4 { + Mat4::look_at_rh(self.eye, self.center, self.up) + } + pub fn projection(&self) -> Mat4 { + Mat4::perspective_rh(self.fovy, self.aspect, self.znear, self.zfar) + } + pub fn view_proj(&self) -> Mat4 { + self.projection() * self.view() + } +} diff --git a/src/ecs.rs b/src/ecs.rs new file mode 100644 index 0000000..2fed082 --- /dev/null +++ b/src/ecs.rs @@ -0,0 +1,32 @@ +use glam::{Mat4, Quat, Vec3}; +use hecs::World; + +/// ------------ components ------------ +#[derive(Copy, Clone)] +pub struct Transform { + pub translation: Vec3, + pub rotation: Quat, + pub scale: Vec3, +} +impl Transform { + pub fn matrix(&self) -> Mat4 { + Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation) + } +} + +#[derive(Clone)] +pub struct MeshHandle(pub usize); + +/// ------------ systems ------------ +pub fn rotation_system(world: &mut World, dt: f32) { + for (_, transform) in world.query_mut::<&mut Transform>() { + transform.rotation *= Quat::from_rotation_y(dt); + } +} + +/// Update the aspect ratio for all camera components in the world. +pub fn set_camera_aspect(world: &mut World, aspect: f32) { + for (_, cam) in world.query_mut::<&mut crate::camera::Camera>() { + cam.aspect = aspect; + } +} diff --git a/src/main.rs b/src/main.rs index b31ae36..7d0ac14 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,127 +1,86 @@ -#[macro_use] -extern crate glium; -use glium::Surface; -mod teapot; +mod camera; +mod ecs; +mod model; +mod render; -fn main() { +use anyhow::Result; +use camera::Camera; +use ecs::{rotation_system, MeshHandle, Transform}; +use glam::{Quat, Vec3}; +use glium::backend::glutin::SimpleWindowBuilder; +use render::{Renderer, GliumRenderer}; +use std::time::Instant; + +fn main() -> Result<()> { let event_loop = glium::winit::event_loop::EventLoop::builder() .build() - .expect("event loop building"); - let (window, display) = glium::backend::glutin::SimpleWindowBuilder::new() - .with_title("Glium tutorial #3") + .expect("create event-loop"); + + let (window, display) = SimpleWindowBuilder::new() + .with_title("fps") + .with_inner_size(1280, 720) .build(&event_loop); + let mut world = hecs::World::new(); - let positions = glium::VertexBuffer::new(&display, &teapot::VERTICES).unwrap(); - let normals = glium::VertexBuffer::new(&display, &teapot::NORMALS).unwrap(); - let indices = glium::IndexBuffer::new(&display, glium::index::PrimitiveType::TrianglesList, - &teapot::INDICES).unwrap(); + let mesh = model::load_gltf("resources/monkey-smooth.gltf", &display)?; + // let mesh = model::cube(&display)?; + let mut renderer = GliumRenderer::new(display)?; + let mesh_id = renderer.meshes.len(); + renderer.meshes.push(mesh); - let vertex_shader_src = r#" - #version 140 + world.spawn(( + Transform { + translation: Vec3::ZERO, + rotation: Quat::IDENTITY, + scale: Vec3::ONE, + }, + MeshHandle(mesh_id), + )); - in vec3 position; - in vec3 normal; + { + let (w, h): (u32, u32) = window.inner_size().into(); + world.spawn((Camera { + eye: Vec3::new(3.0, 2.0, 3.0), + center: Vec3::ZERO, + up: Vec3::Y, + fovy: 45_f32.to_radians(), + aspect: w as f32 / h as f32, + znear: 0.1, + zfar: 100.0, + },)); + } - out vec3 v_normal; + event_loop + .run(move |event, el| { + use glium::winit::event::{Event, WindowEvent}; - uniform mat4 matrix; - uniform mat4 perspective; - - void main() { - v_normal = transpose(inverse(mat3(matrix))) * normal; - gl_Position = perspective * matrix * vec4(position, 1.0); - } - "#; - let fragment_shader_src = r#" - #version 140 - - in vec3 v_normal; - out vec4 color; - uniform vec3 u_light; - - void main() { - float brightness = dot(normalize(v_normal), normalize(u_light)); - vec3 dark_color = vec3(0.6, 0.0, 0.0); - vec3 regular_color = vec3(1.0, 0.0, 0.0); - color = vec4(mix(dark_color, regular_color, brightness), 1.0); - } - "#; - let program = glium::Program::from_source(&display, vertex_shader_src, fragment_shader_src, None).unwrap(); - - let light = [-1.0, 0.4, 0.9f32]; - let mut t: f32 = 0.0; - #[allow(deprecated)] - event_loop.run(move |ev, window_target| { - match ev { - glium::winit::event::Event::WindowEvent { event, .. } => match event { - glium::winit::event::WindowEvent::CloseRequested => { - window_target.exit(); + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => el.exit(), + WindowEvent::Resized(sz) => { + ecs::set_camera_aspect(&mut world, sz.width as f32 / sz.height as f32); + } + WindowEvent::RedrawRequested => { + renderer.render(&world); + } + _ => {} }, - // We now need to render everyting in response to a RedrawRequested event due to the animation - glium::winit::event::WindowEvent::RedrawRequested => { - let mut target = display.draw(); - target.clear_color_and_depth((0.0, 0.0, 0.0, 1.0), 1.0); - t += 0.01; - let x = t.sin() * 0.5; - let y = t.cos() * 0.5; - - let perspective = { - let (width, height) = target.get_dimensions(); - let aspect_ratio = height as f32 / width as f32; - - let fov: f32 = 3.141592 / 3.0; - let zfar = 1024.0; - let znear = 0.1; - - let f = 1.0 / (fov / 2.0).tan(); - - [ - [f * aspect_ratio , 0.0, 0.0 , 0.0], - [ 0.0 , f , 0.0 , 0.0], - [ 0.0 , 0.0, (zfar+znear)/(zfar-znear) , 1.0], - [ 0.0 , 0.0, -(2.0*zfar*znear)/(zfar-znear), 0.0], - ] + Event::AboutToWait => { + // -- update logic -- + let now = Instant::now(); + static mut LAST: Option = None; + let dt = unsafe { // FIXME + let last = LAST.replace(now).unwrap_or(now); + (now - last).as_secs_f32() }; + rotation_system(&mut world, dt); - let uniforms = uniform! { - matrix: [ - [0.01, 0.0, 0.0, 0.0], - [0.0, 0.01, 0.0, 0.0], - [0.0, 0.0, 0.01, 0.0], - [x, y, 2.0, 1.0f32 ], - ], - u_light: light, - perspective: perspective - }; - - let params = glium::DrawParameters { - depth: glium::Depth { - test: glium::draw_parameters::DepthTest::IfLess, - write: true, - .. Default::default() - }, - .. Default::default() - }; - - target.draw((&positions, &normals), &indices, &program, &uniforms, - ¶ms).unwrap(); - target.finish().unwrap(); - }, - // Because glium doesn't know about windows we need to resize the display - // when the window's size has changed. - glium::winit::event::WindowEvent::Resized(window_size) => { - display.resize(window_size.into()); - }, - _ => (), - }, - // By requesting a redraw in response to a RedrawEventsCleared event we get continuous rendering. - // For applications that only change due to user input you could remove this handler. - glium::winit::event::Event::AboutToWait => { - window.request_redraw(); - }, - _ => (), - } - }) - .unwrap(); + // ask for next frame + window.request_redraw(); + } + _ => {} + } + }) + .map_err(Into::into) } diff --git a/src/model.rs b/src/model.rs new file mode 100644 index 0000000..815e2a2 --- /dev/null +++ b/src/model.rs @@ -0,0 +1,124 @@ +//! GPU-ready mesh loader for **glTF 2.0** +//! +//! Loads the first mesh/primitive found in a .gltf/.glb file. + +use anyhow::{Context, Result}; +use glium::{backend::Facade, implement_vertex, IndexBuffer, VertexBuffer}; +use glium::index::PrimitiveType; +use gltf::mesh::util::ReadIndices; +use std::{fmt::Debug, path::Path}; + +#[derive(Copy, Clone)] +pub struct Vertex { + pub position: [f32; 3], + pub normal: [f32; 3], +} +implement_vertex!(Vertex, position, normal); + +pub struct Mesh { + pub vbuf: VertexBuffer, + pub ibuf: IndexBuffer, +} + +/// Load a glTF 2.0 file from disk and upload the first primitive to the GPU. +pub fn load_gltf(path: P, facade: &F) -> Result +where + P: AsRef + Debug, // `gltf::import` wants Debug for diagnostics :contentReference[oaicite:3]{index=3} + F: Facade + ?Sized, +{ + // -- parse the asset & bring buffer blobs into memory -- + let (doc, buffers, _images) = + gltf::import(path.as_ref()).context("failed to import glTF file")?; // :contentReference[oaicite:4]{index=4} + + // -- grab the very first mesh / primitive -- + let mesh = doc.meshes().next().context("glTF has no meshes")?; + let primitive = mesh.primitives().next().context("mesh has no primitives")?; + + // -- read vertex and index streams using the util::Reader helper -- + let reader = primitive.reader(|buf| Some(&buffers[buf.index()].0)); // Reader pattern :contentReference[oaicite:5]{index=5} + + let positions : Vec<[f32; 3]> = reader + .read_positions() + .context("primitive is missing POSITION attribute")? // POSITION is mandatory :contentReference[oaicite:6]{index=6} + .collect(); + + let normals : Vec<[f32; 3]> = reader + .read_normals() + .context("primitive is missing NORMAL attribute")? + .collect(); + + let indices : Vec = reader + .read_indices() + .context("primitive has no indices")? + .into_u32() + .collect(); // ReadIndices enum :contentReference[oaicite:7]{index=7} + + // -- interleave into our engine's Vertex struct -- + let vertices: Vec = positions + .into_iter() + .zip(normals.into_iter()) + .map(|(p, n)| Vertex { position: p, normal: n }) + .collect(); + + // -- immutable GPU buffers (fast path in glium) -- + let vbuf = VertexBuffer::immutable(facade, &vertices)?; // Immutable VBO :contentReference[oaicite:8]{index=8} + let ibuf = IndexBuffer ::immutable(facade, PrimitiveType::TrianglesList, &indices)?; + + Ok(Mesh { vbuf, ibuf }) +} + +/// Create a unit cube (edge length = 2) with per-face normals. +pub fn cube(facade: &F) -> Result +where + F: Facade + ?Sized, +{ + // 24 unique vertices (4 per face) so that each face has a flat normal. + let vertices: [Vertex; 24] = [ + // Front (+Z) + Vertex { position: [-1.0, -1.0, 1.0], normal: [ 0.0, 0.0, 1.0] }, + Vertex { position: [ 1.0, -1.0, 1.0], normal: [ 0.0, 0.0, 1.0] }, + Vertex { position: [ 1.0, 1.0, 1.0], normal: [ 0.0, 0.0, 1.0] }, + Vertex { position: [-1.0, 1.0, 1.0], normal: [ 0.0, 0.0, 1.0] }, + + // Back (-Z) + Vertex { position: [ 1.0, -1.0, -1.0], normal: [ 0.0, 0.0, -1.0] }, + Vertex { position: [-1.0, -1.0, -1.0], normal: [ 0.0, 0.0, -1.0] }, + Vertex { position: [-1.0, 1.0, -1.0], normal: [ 0.0, 0.0, -1.0] }, + Vertex { position: [ 1.0, 1.0, -1.0], normal: [ 0.0, 0.0, -1.0] }, + + // Left (-X) + Vertex { position: [-1.0, -1.0, -1.0], normal: [-1.0, 0.0, 0.0] }, + Vertex { position: [-1.0, -1.0, 1.0], normal: [-1.0, 0.0, 0.0] }, + Vertex { position: [-1.0, 1.0, 1.0], normal: [-1.0, 0.0, 0.0] }, + Vertex { position: [-1.0, 1.0, -1.0], normal: [-1.0, 0.0, 0.0] }, + + // Right (+X) + Vertex { position: [ 1.0, -1.0, 1.0], normal: [ 1.0, 0.0, 0.0] }, + Vertex { position: [ 1.0, -1.0, -1.0], normal: [ 1.0, 0.0, 0.0] }, + Vertex { position: [ 1.0, 1.0, -1.0], normal: [ 1.0, 0.0, 0.0] }, + Vertex { position: [ 1.0, 1.0, 1.0], normal: [ 1.0, 0.0, 0.0] }, + + // Top (+Y) + Vertex { position: [-1.0, 1.0, 1.0], normal: [ 0.0, 1.0, 0.0] }, + Vertex { position: [ 1.0, 1.0, 1.0], normal: [ 0.0, 1.0, 0.0] }, + Vertex { position: [ 1.0, 1.0, -1.0], normal: [ 0.0, 1.0, 0.0] }, + Vertex { position: [-1.0, 1.0, -1.0], normal: [ 0.0, 1.0, 0.0] }, + + // Bottom (-Y) + Vertex { position: [-1.0, -1.0, -1.0], normal: [ 0.0, -1.0, 0.0] }, + Vertex { position: [ 1.0, -1.0, -1.0], normal: [ 0.0, -1.0, 0.0] }, + Vertex { position: [ 1.0, -1.0, 1.0], normal: [ 0.0, -1.0, 0.0] }, + Vertex { position: [-1.0, -1.0, 1.0], normal: [ 0.0, -1.0, 0.0] }, + ]; + + let mut indices: Vec = Vec::with_capacity(36); + for face in 0..6 { + let o = (face * 4) as u32; + indices.extend_from_slice(&[o, o + 1, o + 2, o, o + 2, o + 3]); + } + + let vbuf = VertexBuffer::immutable(facade, &vertices)?; + let ibuf = IndexBuffer::immutable(facade, PrimitiveType::TrianglesList, &indices)?; + + Ok(Mesh { vbuf, ibuf }) +} diff --git a/src/render.rs b/src/render.rs new file mode 100644 index 0000000..e6319b4 --- /dev/null +++ b/src/render.rs @@ -0,0 +1,95 @@ +use crate::camera::Camera; +use crate::ecs::{MeshHandle, Transform}; +use crate::model::Mesh; +use glium::{uniform, Program, Surface}; +use glam::Vec3; +use hecs::World; +use glium::glutin::surface::WindowSurface; + +/// Generic rendering backend trait. +pub trait Renderer { + /// Render a single frame for the given `World`. + fn render(&mut self, world: &World); +} + +/// Concrete OpenGL (glium) renderer implementing `Renderer`. +pub struct GliumRenderer { + display: glium::Display, + program: Program, + pub meshes: Vec, + params: glium::DrawParameters<'static>, +} + +impl GliumRenderer { + /// Create a new OpenGL renderer consuming the provided `display`. + pub fn new(display: glium::Display) -> anyhow::Result { + const VERT: &str = r#" + #version 330 core + in vec3 position; + in vec3 normal; + uniform mat4 model; + uniform mat4 view; + uniform mat4 projection; + uniform vec3 light_dir; + out vec3 v_color; + void main() { + vec3 n = normalize(mat3(model) * normal); + float diff = max(dot(n, -light_dir), 0.0); + vec3 base = vec3(0.6, 0.6, 0.8); + v_color = base * diff + 0.1; + gl_Position = projection * view * model * vec4(position, 1.0); + }"#; + + const FRAG: &str = r#" + #version 330 core + in vec3 v_color; + out vec4 color; + void main() { color = vec4(v_color, 1.0); }"#; + + let program = Program::from_source(&display, VERT, FRAG, None)?; + + let params = glium::DrawParameters { + depth: glium::Depth { + test: glium::draw_parameters::DepthTest::IfLess, + write: true, + .. Default::default() + }, + .. Default::default() + }; + + Ok(Self { display, program, meshes: Vec::new(), params }) + } +} + +impl Renderer for GliumRenderer { + fn render(&mut self, world: &World) { + let mut frame = self.display.draw(); + frame.clear_color_and_depth((0.1, 0.1, 0.15, 1.0), 1.0); + + // Expect exactly one active camera in the world. + let cam = match world.query::<&Camera>().iter().next() { + Some((_, cam)) => *cam, + None => { + eprintln!("[renderer] No camera component found – skipping frame"); + return; + } + }; + + let light_dir: Vec3 = Vec3::new(-1.0, -1.0, -1.0).normalize(); + + for (_, (tr, mh)) in world.query::<(&Transform, &MeshHandle)>().iter() { + let mesh = &self.meshes[mh.0]; + let uniforms = uniform! { + model: tr.matrix().to_cols_array_2d(), + view: cam.view().to_cols_array_2d(), + projection: cam.projection().to_cols_array_2d(), + light_dir: [light_dir.x, light_dir.y, light_dir.z], + }; + + frame.draw(&mesh.vbuf, &mesh.ibuf, &self.program, &uniforms, &self.params) + .unwrap(); + } + + frame.finish().unwrap(); + } +}