From 71700254b41c84184db42dcfb3162943cf7aab27 Mon Sep 17 00:00:00 2001 From: Hristo Stamenov Date: Sat, 26 Jun 2021 14:22:00 +0300 Subject: [PATCH] Major revamp gltf (#1849) * Added my own model and license to Raylib exclusively created by me and provided for use in the examples (and other projects if anyone decides to) * Use animation vertices on initial load if possible. * Added girl model to model example * Revamped GLTF model loading as it was wrong by default. Also updated some comments. GLTF models were loaded only by mesh but they should be loaded recursively by hierarchical nodes because tehre are some static node transformations that are to be applied to the vertices. It also resulted in more meshes being included in some models. It is the correct way of loading GLTF and what is suggested in the official examples. Currenlty limiting to only one scene but more can be included later. * Refactored the new names and structure of extracted functions. * Safer and easier read value. * Made reading easier for accessor->bufferView->buffer in GLTF. Now there is no need to check for supported types or anything. * Correct inclusion of limits.h in the cases of MSVC based compilers vs the world * Removed weird example file --- examples/models/models_gltf_model.c | 3 +- examples/models/resources/gltf/LICENSE | 3 + examples/models/resources/gltf/girl.glb | Bin 0 -> 135000 bytes src/models.c | 1166 +++++++++++++++-------- 4 files changed, 790 insertions(+), 382 deletions(-) create mode 100644 examples/models/resources/gltf/girl.glb diff --git a/examples/models/models_gltf_model.c b/examples/models/models_gltf_model.c index 9889ad25a..3f843c9d4 100644 --- a/examples/models/models_gltf_model.c +++ b/examples/models/models_gltf_model.c @@ -21,7 +21,7 @@ #include -#define MAX_MODELS 7 +#define MAX_MODELS 8 int main(void) { @@ -49,6 +49,7 @@ int main(void) model[4] = LoadModel("resources/gltf/AnimatedTriangle.gltf"); model[5] = LoadModel("resources/gltf/AnimatedMorphCube.glb"); model[6] = LoadModel("resources/gltf/vertex_colored_object.glb"); + model[7] = LoadModel("resources/gltf/girl.glb"); int currentModel = 0; diff --git a/examples/models/resources/gltf/LICENSE b/examples/models/resources/gltf/LICENSE index 8617385fa..305079cfb 100644 --- a/examples/models/resources/gltf/LICENSE +++ b/examples/models/resources/gltf/LICENSE @@ -15,6 +15,9 @@ Animated Triangle model is licensed as CC0 Universal Public Domain Gearbox Assy model has been provided by Okino Computer Graphics, using Okino Polytrans Software. no license information was provided +Girl model has been provided by Hristo Stamenov (https://thatonegamedev.com/) +and licensed as CC0 Universal Public Domain + Check for details on CC0: https://creativecommons.org/publicdomain/zero/1.0/ Check for details on CC4: http://creativecommons.org/licenses/by/4.0/ GLTF sample models for testing are taken from: https://github.com/KhronosGroup/glTF-Sample-Models/ diff --git a/examples/models/resources/gltf/girl.glb b/examples/models/resources/gltf/girl.glb new file mode 100644 index 0000000000000000000000000000000000000000..c368590c856e593fe5176f1975132c36bd37325b GIT binary patch literal 135000 zcmeFa2Urx#6F4|SP(cw?%n89P!sYD6LAi<2HV$KN_bH=RCEbNYo z>6xEdpZd%>=j_!C%qyu5=s}R@8b+77yT&K< ziJ=N4^6qKfjijMoKumNWXget(B|3#}ylZojhe#?{$<-pMQmhs!lyaq1?%rJNp;n2c zQkht+Qj5h(nIxvUNbTNS=An?Q6=IQ0DVNBkDpgE#vB+KIp;oJBCWL@b(C|AowQng$vmrL^NLyk%%QmREtXw8sitOZS^Bt^$3$1#FZT`Lq~s(q1C zDOF1pv`8SnN~8jr6e_h$DG{qxa(7YpH20CNz5B<;^+}3>8SE;e<|2Qlf@69QEIb3y zf>af*x|g7~4n34f1GPc$Ac$6$%MVo>-+6E0v%tF-*Hi0uu*7E>%hu zQn^Geai?pFC7>CFQUpqt!0hI02vjVV%H+_kQmtl>5{BdxEO3MrU3Jq=Q+2xbNTizKivBnoO! zuvDZXP&iDz3apN$YvU|{;YdVaIcf!;uEicog+vDS52IEnWpZV~pd})iLZOz+MItbB z5Y1if0VK4d7Rg|>$;FhpaYrmQjyNJQk$1$PP_aU!hN%<73RJ0-YD%3_Fkx6mYK2My zqA0-TMII6vtVUSga#&GdCJK73g581TD5Nm+kokXqZ5x-nsrf5CByyQZEmMO8uoflM z%A)N6tW6A)BoRx&L{&2P=5h~cMoCnc?JDb1+*xomxD?SPC=%Si^1+?U?VVla&Tr6m^(4GKq1R?ND>c~ zNC93|u2#upBC(wEAk?ZTSkYk1Qi+PPW6Iem)k+w*6c(jOE|tg(V`4Ap4uuyqSS?JM z47>pNJdjN)rnD*sC8#&XL zf+i>jF4SSePDZUMuwFU%1SRy$C>4wmb^^6RBBh+H!d(gN!)BwDz=j|OOEV0g<-)0j zRxlfem2EO3GPy(nDgs+lD%GGhB{kn-nF?H%T&aL1r3LP!A!!&2qq~3U#6y%NLf_=s+K84uy07@uzR^T2h)(r z!G)_-QZ*Fhn+A7bQ#vUauwh}F4w$lB8El5&Az+P*C}$>7isW)?xH2iVa3~q1Pz_c( zI1!Lf1tWw!aN!`WSPZ*}3@{aY#0Acr?Z$YrfP=G(Nny63W4Tx<0ZmB7`MV}%fz&dl z{10XDBBhF&1F${`Y<4i)u>Fcu^n7tAfs(3V5)2EOGYOP;qZX4`2Cf@6Y8f~w+Ihnc z0)&Ke^q_BA@hYhVA`m%*DN0xj63Q0jU~pg}u+za3lC!-Vv!$_Tf#03ORsijSJAo|$ z;s9pUyenM<%Tpd)y9F-+7C;Nh2XDH3>HACNem;GsU#3IfH$Jd1=Iw4D!mb!_;N8Tc%xT7&s5MM3rqrR z$V%|a(1D6Fagh>MxI_wz1Nww{7t?kjQNt>PwE%m+TmdVG-qqEx>q2;`1QUXw%G~ld z`b3jqQ(-Xp6o{JDB8Z$o!*VICM%thYEVy+-LJD^q5_u?Kh9D}YtPHk(*oQ%G*z6!0 zR8hl$fD617EjZYx0@gTqdhi=ifm+#0@Zzw1A!Grwhg}lnV*D1PKBLe~lm@;Lwg`!g zp~35dvLNtRQv94XEr%ckYz&4)Ef8?rl*vNR;6&vrDR^60RIrp35UoI1u7o6m7!%wL zuW54ww6-z*xKlzYQ$+btF?0;eg$hj}?4@#HuZ1NJAt+1<73>#egHgfEtD%}$BvwKN z5RcxkV3!7WClRry#o){pYJ~_wK?wY)4V^Z1l^UXV$Wv1R1ZN&392G|2NBoP=s?57ebhyVryVGheW_=8p2%Tvce z3aS|?wFecR251$wHmOt${fk7@G~_#5h(+L#fL=T*m_S*6{7eNWN`*oufdv7!1IBE! z9ys0{)&sQ&Wg-=9#?)p)4}|g>uv^160@DCHOa4I$xNgV?Zw^NV&?9A(aG(nN9JMx} zKiC;r12!xNPzmfo5D*k-9Snpz_JUngp)Obvur|c7@F<~8BRd6b>D2KG{D+03%0DtT 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

E@PUIo9mUy(^Gzp7VNMNV(CINgPveT=wPljznA zv$~GLJGtnItNvj>6V|wQSZU$@+oU6k9J8KHblYaMDl5Jp5l&t> zHRI!7*k+un>LL!Q5KdB$5q|CV5kiXFG;z?|=_F`Xe|~&}GUD5;4DspZX=J2(IG^ep zC|>fhl%hj^CR|Z8U#(V>7!}%FYCU#5nLBAKKW5%dG3v^2snnfd(yqz@zUu9A(n-6; zlA-Z1GA=QTPh3!6x-e^#)V$E2wCwC+ll zpS{CRsn%B_SF)s~@t&kkx%<3jPJe0mTdXf3#01*r!Of&u*~aVzA#H)qan`zFWY)H^II`!M+6Sesj62PA?G;)7#zB2U&z=t9%-0O?p zsh0tsZ@<^%nzoNYyzRcKsV6?1W^MxP8=K3#2zi}y8(`;Jdzon?oprcw%{jL2wt>Hz zt^oXFr##c9DOXbv4jOtH{Pt^I@}5%$Df&0+a$dLLX>(%oz)bAh$UOBEmTzj*4?NJ1&qq7%l*|rnBU+otso{AuoSDS>wAytF5*#v==7`FqL&S2ne&jF6f1M$}ZPX%M zg#4Dghy1n+$-ENxGLC}V19cY^6#E@C&e6d7(;$*3Hfbk z-3KD%w*>RsNXTz_`!W*bw*>R|=aAn9C)!JU{CX42Zx=#-Yc<(P`uR{Vg86?H$ZxlA zY9zt^067o$1Lq;XUF_#3<=^urnBNMJ-=-aAq#K#uWFy=!oGUS31NgUU_!ohEYmg85 zGckeu7qEViX8n@-l?DCBYV^MW`}2YQ!To}=zbUZ)ChGq2exV!Kr!&|G?iUE&FVqJ6 zH3s{^{Q|-Jg%x1mj$mK7Um$qDun6ow2J8>_3#2F5pXR012dGbI@%TOs9piUTVeGLX zFU4tL>L*lYNnXTb)5@oE5Km>7lu7BS4^sI^Px)!DU9+SdoI@Nolu7lWyjtCmb{J!$ zm72nL@6^!!vg)K#|Mx(crLpIFY(uQQ$1)c@7i3$Ec*SINU2OFuJd>+NdkwfboB4%P25SX;St#dge#9o?1t zf{3k)OsgNYcE=jTg)fh~buFx*?5i2+X8JL}+a2!_7KZjVRpE>E?6*a)+wiiSl9SiH zc+~iOOqQ}=`=J|6598M;d+Y@HzqY<`XmNhaU#ykLV}AQ*asJE86B2pMZ!Z?-xBUCY zNqi~DZ$CnQ8-J-Szdj|2kAVES0_4~EwJdnZZ+XnGr$ByNt1y)N_+cB5`S(4@&+T7t z7QE)i^Rpp8_lNxU{{1ZR)2A37^Y@vM-yRHVB28+uipTsm3G&;rh9GI`s3m*|a}0kD^t%iCSJdb~0ruyl*842;&DuS8vjxnl!J36@sx+2%{Znol~t04>ZO%Q*U|P` zI*Laeg|+e#r=6oT+FG%rW9*gss@o%n>VmqyQtF@1s`-}D!i&7jl-jJxhC58o%>boV ztksDFt{v;$SLl-s`*isq2PycM@kuG#I@H?Xqe670zGzD?H87*(4gZq`5urZEjOu2o{75bD{>)h)4=oR_% zw=Z<-``|aF-s|*_S9LokA6Mj0|5!_>U8~@%q~I@vkI+>%c2e~3@peLr)(%gjEERm< zsSl?0$M5l1N)6;4mgez!pWBl+EAH_Ap8fdAGf(lZrjEqJ>pGvcw>xin{Rq$Y?n274 z`TRQvj=$VtAMaPK7m1Uz_?tZ&@I(9(_})(bWP|?!zRr~Lyy?|4-Z6YAIdOL@@6q}e zxB2iGzNU9DIUT!#mj@(qQ6pHs`^!+W<fc>&n`x(G~MPT0nVBd*g-)3On7_k3UuzyX}{`C7se=P~qG4|M~ zPh*ewQ`EN*#(DHfdT)hrN&1pw8pcPTX}*9FXCTvGK4Ug~#9_pm#o$jRGhX0nNFsMPwb-(V?| zUNKpzgSm8~G3AwgU4`aG$Ji9Dd~MCmv2l*dx@Yd+_=a9vi?h+;L=);W0qP!T>tN=6 z8qA>nvwIYp$Wt>#&W%!!n8f@OM%>GsEe2y!tB#5eaUq`|xBpsKbY%CM?zTYF-FTbn z>_3PL{BWL=I#uSIM1>J)n-BN=uhU$Irj__J>0#v6vDTd4@d($luPy(~Et~{dR^+(K zySPEqO7Sz!hZDoxH1=fFN^ahwr`+iUGsyP!*6gP(L0pR`=Q$U*2omUaCBv&vL+@WW>>gxEdND#}ad~tTHo;>q_lFn!TR-y@&0D%;yqEfO=X@i{ zx|mRro9rVD?a-3*>u*!nKMDHH(&*;{`gZ~SKdJi1!~WjE{+g@%V_^T;u>S?>{>OoR z3>y1<0Q<2T`(=QApK9#eLAAfE+Mj;Y)Fw9=Aldef+QhMyQG!&+I>?tkf{az36qI2|J^zXs&?)n!Rgz>F)Q}J8$-{7en zN`ti&OY&m9LiL^eFTCGd_j~w~GBxkRf00RPXkAFuO$)2<-Yaz~v92Q0<-AHJ_SAYE>tNrKE4?;S4)V9+`Uj6=Z8O3+N1sUf zeUv>{*L5*#(PRU+%O+Aju&y%auxta{`_NX-`elSX#I-t?!6dW4oR8-QypE8cf2_$( zZ@QbUJU(CWi-?pXV*R<;->0+V4o#J&&6+AVd%u1X$UdKIc%z+o(audibfA@NZ8*tYDu@;nJ=)5L6J6vXuM)U-xk+LmN95HJ z4dqhjn{$*Z$6qFHabHWxX|mb?k-L(ZJB zG$bvGV$X#idhX#{e3t=6JifVy z?}{Ug`Ri#F#&`2hK||lfck^Jfl}Y zt6*Nd)*^r_0*w0n3e8Hzp9lCZ(4%~;2Zi?0)^5|*VuLrjm6~+;MmM}quDp+p?~`BZ zv{J#l_qWzv=&?e12YbYwGE7u2e}!iB;!RARYm)L7_dSTVEH8{#-pzh_W)Sns=L$+a zyQ2qVnB5V>6nd-u?xxeF)+%pqJAY`|?3I_b^5%9I_`faM`f&p-elp?v-nXiBH`ggE!eMZvz-~g!+1h^@%WnK&J44y zRnjZ$Djr8S*qQMd=41C3k6*wz55_yyaj4DC3}>L{Vppe^2YGJWc4XXvdAs(-;|*Kp zNsXFUO!podB)&IIAv3C{N_F?xrguFMD|WCBCAKpUODmpRr-yS%qRS6sNVzAO(k0G1 zz0vEPVx2pq$)v`orFC_!(wjT&6$>naiN~Y!Qi5OEbjc}A^hh2>jy*J!`#6}T*Jye} z92hu&bTY3izlyq*_ByS+le|&dea@PVV#3gIgYSD%JkrxQI}wlX zETeCrkKy~w=vNn@)dd)NPe5y+;xhqm2r$YQR2X>=0Ng~Sj|8{{!07*7p|w--y#Ve2 zF#2|H6_0Q?6-Igj&7jf;1Iz%7`s{>;`XG!tIIA$~QBI}DeLaSTa&YgsU*EY$l{b(V zJZQi?WA?b>o6mjx7^Z(&HU;aB)k!pCoQHWRZyNumi`ISqtGM=A=Y=W8s3a?;Hd&&d z%K&E0oeN`6GP5g|QtEkK4US`M$5|-6OPc31rgx08*43)HZn_EU z6#ZxWu4Ur(-%<2wb@Y~L$KZjA&ZXiKOzUdZSKe^uCf;+q{C>V0zCpe5+6dEK-#Fzh z=rY;1?(?&sDeqTLN^EX=JoTfZPn5UORG?q1>}BDOyUcHsn~)Jdm~+qD2MD)6vZV1) z9f{*!v&MWP1deJ=nojzO^n7!PZE`3rV?z-~vX@sNm6(-mjj0ywvpYWI_QM^#<%j6B z%NH-RRyGqzz^G}wduON;sk7!DH=RcQxVV$|Sh-9{vM$A4KRKRMd-s<2-7ryLr8eB5 zgu&#na|KdwV1Q6y--V06;6v`0b|AKGe1+pucP_}T8yT>;HmUF2S#Yh{gX`PClT6NP zK<2h2LdZ>Tu5_UXxt-gXG@fk``Ud-OWm@ngLuf>9?zIy>RSV%#D!{wam7GX}po1Bi zeoMLZyiTNpmj$^cn=&pt#dFSE{K}7nkL-J!9!gFA~$TF1a_~&}x1%p1 zybszqfImYY0ByaBHvpUrFs{`MTB?e#18}+uI{++bU=d*axzPsNPJq#$@$8)iFv`Jq zg7F<;#N$5jX941?L*EP<>JSY5Vdzn}Fo4%XkN-C&5a4X+k#{)2$Dv2w0RUf8VGn>Y zZh_cXr1AFmV2EkI&>*ad+rPn6Ih00?+rOdF#O+`3-&^;4_`lN!YcrL^Yhra}Ej4cc zvM$nNeJC|rbh1`xkl{;@;`!oh)}g}9f@{u=l45cd*{ zdv`$G8)9>p|J@^yEQPrD7R0^R*H^+~NrfS@@r29<73UoiG;~wN4h0b~j@(wux@{T6{*@p3!D@4dU+(Py=BGW_A*zl@Xdo$V_?gFcfAjmF8*0Arkt=OzBEsHDcph_?n9 z&+vSJ@2T?9Z~vV<0&7eBT^@mQX&!;Get{|zSkX#s}|6?A3c(eohHNuETddxfO zeU4IU`V=;ZG-_+yXln^Uo#hl-XIWdnMq6iRYtdUH*2h_6PnfhUZzT@*eELe)sm)U* zmJW?6&1mI7+^*m;ZeQK8xOPv|?z40dhb#4cFb>!1qpkI$tD~*{b0E4(>G`+*Dj|$YtEa6jN-o;@sg-y6)7F7sWdC@-aQHa@B}2{QN3Y zzo~5$bYHL3S~@D_r>JqSUUo|3Xxv-Q!AuWvFQ*jkfs332bOyt?v~w3}_DF?M{ru(rgu>nEwJ zV5Wav{VKO->Oj){X|`0R$VxvhbvKtdaWom+Z?ELC$W}itH;(IYavbSDt)~<*%3l9` zSaa?|=ya0$X-CGxgerRT6|42-;NSceUbq)4D*z zvl0DM8z=XGF~$Ih@21kAZ?M=@Ib#5J1sL%rXc#9Wj4^O+l?HvNBETpI@faH;4aUs> zDvv-uq^EgA9>BFTBd4ajrFulYP~zvf6M4GZ9oH%`_Uqs0GM}>MDD*q6=eVCWgy}F=Pha)GbbJO; z;`Z_DJeXf~QHl;W!$+D7x63N@MSoOf)(zdBg1JEQk{_9*Cl*Q`F>L5fU9jo6BIn3U z9TQ&AT+z+ha)&9WZXHDjLm>QJb2md{jL&Nr_Up=AZKvQrmPs&`%fG${b@(`Nt;xQ( zy&`8?Nxm z*lWi^+$++!*E8s#0peZ}dm|Cm?->y*C?9B{X%l8?d3g!2aCF2hl#_^j3X~X{^C2^$BB!M?h?GK z2EZLlWwL0;GO_Aid!dJ;H>p3~iTvu~E1n6OU^sW76L~bQ4yih!f|&R`jQw*{N3sL* zj=?{ei+9=wu$Xr=g1lob_gYBIC+u)F@9=taNw7PVW}tb84YN{ko@yb`yu;#SbepRe zFAJD=42Ha;Q|FQF4?1fR^Ny7KJ^W#L2|G2$Qf$GGC#MI!=aXhnVE+g*h>!TeWXzcg z#C-Q4_RgfPqQ?*)(z~w%>22f7R_otG6!N+f_Kq`o?cJGe_N1G*f43*eTTqX1Z3$cD zaTjs1u|271bRp~3>)8dLI*a}X+9^IzGENrYo7Tq32;p7#Vk|!dU_7_++(Vy7 zKf(Cj4I27Y5cKdj|B5d~0K67poz0f0X18DnHnz{hv zT_D899KMbFA~Avg2maS~z`v9G|NGA9KkI*M|KhkAedM3^uWc~u;v078$sc5!)jjzj zfE)W)ljCnWD)7iI)#a`ij&O*_cTUF(dHRP3-moJ-+RHb5tR=*Ew6TyM%v&s-N`A{0 zE-IAl%VbD5oZqpXOG#3{-~Lc&Jg01uMxMDR{ki*3_C?oaQvRVA(vx1VSmEs)>G_Jc z(!jWv?A`@4r3c-LB+sCi?0xH*(z~J}sT07}`!19g47jJ@--pLZ&QJC$u#NvdiQh9( z>IgI$Jx)oD%RCT~x823_()F1;*bV?MwKyjQj&W{|{-O&2IkU=0DjJ&TpiKMUMspY?yG2oKJ`m0mp>Oq{Q` zXWcjK5qj8vlHwZ&kOsM4Y?~9?1-qPz@?Z-e5_4-fyS(pWVcUMayvNd&bX&EGU3jOu z(Cft**${e|AJ!>@{WW`G#xpit_NmmAe>f=X-yXkQx-{cP*>HJKzK*Ztp2fcWBadnVhh)`kqv@?r9+!=8NY&`h?*@0&9wo|!I}n%R{g7vE&p`r`g9<<>)wr>5t||`UHD$QK5;Pd)^%h{+W)`uZ9LnMhV~TJj{hqe z+Ic)Tv1xHsW=Y(CYOK|j!g#Jz`4mTciZ9vYU6_|D5Ol)8dK$-RDYv z-~Wt@Z3JhXpgBJU&G{*4&QC#eehPSgx)h(Eg68}bH0P(FIX?w@e!i~pO7Q!7ehT#b z6dJq{$k+2z(8L#lCcY3f@r9s?F9eJ)t`^4^0*xOXC2!-8nC|6~rSIaw?KR7C!R2 zuki?hctpl{B=1&rq4$e1gbzI|mG%4*j}VAQBuzZ>-yW;+2!VJ+(!?VK;t@#`j}VAQ lBuzZ>#oqrfd{}!fQkb5fIHow-|DWjaf5WR?@85|p{ud2*&W`{9 literal 0 HcmV?d00001 diff --git a/src/models.c b/src/models.c index b243a45e7..4ad7d32b6 100644 --- a/src/models.c +++ b/src/models.c @@ -50,6 +50,12 @@ #include // Required for: memcmp(), strlen() #include // Required for: sinf(), cosf(), sqrtf(), fabsf() +#if defined(_MSC_VER) + #include // Required for numerical limit constants like CHAR_MAX +#else + #include // Required for numerical limit constants like CHAR_MAX +#endif + #if defined(_WIN32) #include // Required for: _chdir() [Used in LoadOBJ()] #define CHDIR _chdir @@ -117,11 +123,14 @@ static ModelAnimation *LoadIQMModelAnimations(const char *fileName, int *animCou #if defined(SUPPORT_FILEFORMAT_GLTF) static Model LoadGLTF(const char *fileName); // Load GLTF mesh data static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCount); // Load GLTF animation data -static void LoadGLTFModelIndices(Model *model, cgltf_accessor *indexAccessor, int primitiveIndex); -static void BindGLTFPrimitiveToBones(Model *model, const cgltf_data *data, int primitiveIndex); -static void LoadGLTFBoneAttribute(Model *model, cgltf_accessor *jointsAccessor, const cgltf_data *data, int primitiveIndex); static void LoadGLTFMaterial(Model *model, const char *fileName, const cgltf_data *data); +static void LoadGLTFMesh(cgltf_data *data, cgltf_mesh* mesh, Model* outModel, Matrix currentTransform, int* primitiveIndex, const char* fileName); +static void LoadGLTFNode(cgltf_data *data, cgltf_node* node, Model* outModel, Matrix currentTransform, int* primitiveIndex, const char* fileName); static void InitGLTFBones(Model *model, const cgltf_data *data); +static void BindGLTFPrimitiveToBones(Model *model, const cgltf_data *data, int primitiveIndex); +static void GetGLTFPrimitiveCount(cgltf_node* node, int* outCount); +static bool GLTFReadValue(cgltf_accessor* acc, unsigned int index, void *variable); +static void* GLTFReadValuesAs(cgltf_accessor* acc, cgltf_component_type type, bool adjustOnDownCasting); #endif //---------------------------------------------------------------------------------- @@ -844,7 +853,8 @@ void UploadMesh(Mesh *mesh, bool dynamic) // NOTE: Attributes must be uploaded considering default locations points // Enable vertex attributes: position (shader-location = 0) - mesh->vboId[0] = rlLoadVertexBuffer(mesh->vertices, mesh->vertexCount*3*sizeof(float), dynamic); + void* vertices = mesh->animVertices != NULL ? mesh->animVertices : mesh->vertices; + mesh->vboId[0] = rlLoadVertexBuffer(vertices, mesh->vertexCount*3*sizeof(float), dynamic); rlSetVertexAttribute(0, 3, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(0); @@ -856,7 +866,8 @@ void UploadMesh(Mesh *mesh, bool dynamic) if (mesh->normals != NULL) { // Enable vertex attributes: normals (shader-location = 2) - mesh->vboId[2] = rlLoadVertexBuffer(mesh->normals, mesh->vertexCount*3*sizeof(float), dynamic); + void* normals = mesh->animNormals != NULL ? mesh->animNormals : mesh->vertices; + mesh->vboId[2] = rlLoadVertexBuffer(normals, mesh->vertexCount*3*sizeof(float), dynamic); rlSetVertexAttribute(2, 3, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(2); } @@ -4132,26 +4143,542 @@ static Image LoadImageFromCgltfImage(cgltf_image *image, const char *texPath, Co } -static bool GLTFReadValue(cgltf_accessor* acc, unsigned int index, void *variable, unsigned int elements, unsigned int size) +static bool GLTFReadValue(cgltf_accessor* acc, unsigned int index, void *variable) { + unsigned int typeElements = 0; + switch(acc->type) { + case cgltf_type_scalar: typeElements = 1; break; + case cgltf_type_vec2: typeElements = 2; break; + case cgltf_type_vec3: typeElements = 3; break; + case cgltf_type_vec4: + case cgltf_type_mat2: typeElements = 4; break; + case cgltf_type_mat3: typeElements = 9; break; + case cgltf_type_mat4: typeElements = 16; break; + case cgltf_type_invalid: typeElements = 0; break; + } + + unsigned int typeSize = 0; + switch(acc->component_type) { + case cgltf_component_type_r_8u: + case cgltf_component_type_r_8: typeSize = 1; break; + case cgltf_component_type_r_16u: + case cgltf_component_type_r_16: typeSize = 2; break; + case cgltf_component_type_r_32f: + case cgltf_component_type_r_32u: typeSize = 4; break; + case cgltf_component_type_invalid: typeSize = 0; break; + } + + unsigned int singleElementSize = typeSize * typeElements; + if (acc->count == 2) { if (index > 1) return false; - memcpy(variable, index == 0 ? acc->min : acc->max, elements*size); + memcpy(variable, index == 0 ? acc->min : acc->max, singleElementSize); return true; } - - unsigned int stride = size*elements; - memset(variable, 0, stride); + + memset(variable, 0, singleElementSize); if (acc->buffer_view == NULL || acc->buffer_view->buffer == NULL || acc->buffer_view->buffer->data == NULL) return false; - - void* readPosition = ((char *)acc->buffer_view->buffer->data) + (index*stride) + acc->buffer_view->offset + acc->offset; - memcpy(variable, readPosition, stride); + + if(!acc->buffer_view->stride) + { + void* readPosition = ((char *)acc->buffer_view->buffer->data) + (index * singleElementSize) + acc->buffer_view->offset + acc->offset; + memcpy(variable, readPosition, singleElementSize); + } + else + { + void* readPosition = ((char *)acc->buffer_view->buffer->data) + (index * acc->buffer_view->stride) + acc->buffer_view->offset + acc->offset; + memcpy(variable, readPosition, singleElementSize); + } + return true; } +static void* GLTFReadValuesAs(cgltf_accessor* acc, cgltf_component_type type, bool adjustOnDownCasting) +{ + unsigned int count = acc->count; + unsigned int typeSize = 0; + switch(type) { + case cgltf_component_type_r_8u: + case cgltf_component_type_r_8: typeSize = 1; break; + case cgltf_component_type_r_16u: + case cgltf_component_type_r_16: typeSize = 2; break; + case cgltf_component_type_r_32f: + case cgltf_component_type_r_32u: typeSize = 4; break; + case cgltf_component_type_invalid: typeSize = 0; break; + } + + unsigned int typeElements = 0; + switch(acc->type) { + case cgltf_type_scalar: typeElements = 1; break; + case cgltf_type_vec2: typeElements = 2; break; + case cgltf_type_vec3: typeElements = 3; break; + case cgltf_type_vec4: + case cgltf_type_mat2: typeElements = 4; break; + case cgltf_type_mat3: typeElements = 9; break; + case cgltf_type_mat4: typeElements = 16; break; + case cgltf_type_invalid: typeElements = 0; break; + } + + if(acc->component_type == type) + { + void* array = RL_MALLOC(count * typeElements * typeSize); + + for(unsigned int i = 0; i < count; i++) + { + GLTFReadValue(acc, i, (char*)array + i * typeElements * typeSize); + } + + return array; + + } else { + + unsigned int accTypeSize = 0; + switch(acc->component_type) { + case cgltf_component_type_r_8u: + case cgltf_component_type_r_8: accTypeSize = 1; break; + case cgltf_component_type_r_16u: + case cgltf_component_type_r_16: accTypeSize = 2; break; + case cgltf_component_type_r_32f: + case cgltf_component_type_r_32u: accTypeSize = 4; break; + case cgltf_component_type_invalid: accTypeSize = 0; break; + } + + void* array = RL_MALLOC(count * typeElements * typeSize); + void* additionalArray = RL_MALLOC(count * typeElements * accTypeSize); + + for(unsigned int i = 0; i < count; i++) + { + GLTFReadValue(acc, i, (char*)additionalArray + i * typeElements * accTypeSize); + } + + switch(acc->component_type) { + case cgltf_component_type_r_8u: + { + unsigned char* typedAdditionalArray = (unsigned char*)additionalArray; + switch(type) { + case cgltf_component_type_r_8: + { + char* typedArray = (char*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + if(adjustOnDownCasting) + { + typedArray[i] = (char)(typedAdditionalArray[i] / (UCHAR_MAX / CHAR_MAX)); + } + else + { + typedArray[i] = (char)typedAdditionalArray[i]; + } + } + break; + } + case cgltf_component_type_r_16u: + { + unsigned short* typedArray = (unsigned short*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned short)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_16: + { + short* typedArray = (short*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (short)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_32f: + { + float* typedArray = (float*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (float)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_32u: + { + unsigned int* typedArray = (unsigned int*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned int)typedAdditionalArray[i]; + } + break; + } + default: { + RL_FREE(array); + RL_FREE(additionalArray); + return NULL; + } + } + break; + } + case cgltf_component_type_r_8: + { + char* typedAdditionalArray = (char*)additionalArray; + switch(type) { + case cgltf_component_type_r_8u: + { + unsigned char* typedArray = (unsigned char*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned char)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_16u: + { + unsigned short* typedArray = (unsigned short*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned short)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_16: + { + short* typedArray = (short*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (short)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_32f: + { + float* typedArray = (float*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (float)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_32u: + { + unsigned int* typedArray = (unsigned int*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned int)typedAdditionalArray[i]; + } + break; + } + default: { + RL_FREE(array); + RL_FREE(additionalArray); + return NULL; + } + } + break; + } + case cgltf_component_type_r_16u: + { + unsigned short* typedAdditionalArray = (unsigned short*)additionalArray; + switch(type) { + case cgltf_component_type_r_8u: + { + unsigned char* typedArray = (unsigned char*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + if(adjustOnDownCasting) + { + typedArray[i] = (unsigned char)(typedAdditionalArray[i] / (USHRT_MAX / UCHAR_MAX)); + } + else + { + typedArray[i] = (unsigned char)typedAdditionalArray[i]; + } + } + break; + } + case cgltf_component_type_r_8: + { + char* typedArray = (char*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + if(adjustOnDownCasting) + { + typedArray[i] = (char)(typedAdditionalArray[i] / (USHRT_MAX / CHAR_MAX)); + } + else + { + typedArray[i] = (char)typedAdditionalArray[i]; + } + } + break; + } + case cgltf_component_type_r_16: + { + short* typedArray = (short*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + if(adjustOnDownCasting) + { + typedArray[i] = (short)(typedAdditionalArray[i] / (USHRT_MAX / SHRT_MAX)); + } + else + { + typedArray[i] = (short)typedAdditionalArray[i]; + } + } + break; + } + case cgltf_component_type_r_32f: + { + float* typedArray = (float*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (float)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_32u: + { + unsigned int* typedArray = (unsigned int*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned int)typedAdditionalArray[i]; + } + break; + } + default: { + RL_FREE(array); + RL_FREE(additionalArray); + return NULL; + } + } + break; + } + case cgltf_component_type_r_16: + { + short* typedAdditionalArray = (short*)additionalArray; + switch(type) { + case cgltf_component_type_r_8u: + { + unsigned char* typedArray = (unsigned char*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + if(adjustOnDownCasting) + { + typedArray[i] = (unsigned char)(typedAdditionalArray[i] / (SHRT_MAX / UCHAR_MAX)); + } + else + { + typedArray[i] = (unsigned char)typedAdditionalArray[i]; + } + } + break; + } + case cgltf_component_type_r_8: + { + char* typedArray = (char*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + if(adjustOnDownCasting) + { + typedArray[i] = (char)(typedAdditionalArray[i] / (SHRT_MAX / CHAR_MAX)); + } + else + { + typedArray[i] = (char)typedAdditionalArray[i]; + } + } + break; + } + case cgltf_component_type_r_16u: + { + unsigned short* typedArray = (unsigned short*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned short)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_32f: + { + float* typedArray = (float*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (float)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_32u: + { + unsigned int* typedArray = (unsigned int*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned int)typedAdditionalArray[i]; + } + break; + } + default: { + RL_FREE(array); + RL_FREE(additionalArray); + return NULL; + } + } + break; + } + case cgltf_component_type_r_32f: + { + float* typedAdditionalArray = (float*)additionalArray; + switch(type) { + case cgltf_component_type_r_8u: + { + unsigned char* typedArray = (unsigned char*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned char)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_8: + { + char* typedArray = (char*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (char)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_16u: + { + unsigned short* typedArray = (unsigned short*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned short)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_16: + { + short* typedArray = (short*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (short)typedAdditionalArray[i]; + } + break; + } + case cgltf_component_type_r_32u: + { + unsigned int* typedArray = (unsigned int*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (unsigned int)typedAdditionalArray[i]; + } + break; + } + default: { + RL_FREE(array); + RL_FREE(additionalArray); + return NULL; + } + } + break; + } + case cgltf_component_type_r_32u: + { + unsigned int* typedAdditionalArray = (unsigned int*)additionalArray; + switch(type) { + case cgltf_component_type_r_8u: + { + unsigned char* typedArray = (unsigned char*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + if(adjustOnDownCasting) + { + typedArray[i] = (unsigned char)(typedAdditionalArray[i] / (UINT_MAX / UCHAR_MAX)); + } + else + { + typedArray[i] = (unsigned char)typedAdditionalArray[i]; + } + } + break; + } + case cgltf_component_type_r_8: + { + char* typedArray = (char*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + if(adjustOnDownCasting) + { + typedArray[i] = (char)(typedAdditionalArray[i] / (UINT_MAX / CHAR_MAX)); + } + else + { + typedArray[i] = (char)typedAdditionalArray[i]; + } + } + break; + } + case cgltf_component_type_r_16u: + { + unsigned short* typedArray = (unsigned short*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + if(adjustOnDownCasting) + { + typedArray[i] = (unsigned short)(typedAdditionalArray[i] / (UINT_MAX / USHRT_MAX)); + } + else + { + typedArray[i] = (unsigned short)typedAdditionalArray[i]; + } + } + break; + } + case cgltf_component_type_r_16: + { + short* typedArray = (short*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + if(adjustOnDownCasting) + { + typedArray[i] = (short)(typedAdditionalArray[i] / (UINT_MAX / SHRT_MAX)); + } + else + { + typedArray[i] = (short)typedAdditionalArray[i]; + } + } + break; + } + case cgltf_component_type_r_32f: + { + float* typedArray = (float*) array; + for (unsigned int i = 0; i < count * typeElements; i++) + { + typedArray[i] = (float)typedAdditionalArray[i]; + } + break; + } + default: { + RL_FREE(array); + RL_FREE(additionalArray); + return NULL; + } + } + break; + } + default: { + RL_FREE(array); + RL_FREE(additionalArray); + return NULL; + } + } + + RL_FREE(additionalArray); + return array; + } +} + // LoadGLTF loads in model data from given filename, supporting both .gltf and .glb static Model LoadGLTF(const char *fileName) { @@ -4194,12 +4721,15 @@ static Model LoadGLTF(const char *fileName) // Read data buffers result = cgltf_load_buffers(&options, data, fileName); if (result != cgltf_result_success) TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load mesh/material buffers", fileName); + + if(data->scenes_count > 1) TRACELOG(LOG_INFO, "MODEL: [%s] Has multiple scenes but only the first one will be loaded", fileName); int primitivesCount = 0; - - for (unsigned int i = 0; i < data->meshes_count; i++) - primitivesCount += (int)data->meshes[i].primitives_count; - + for (unsigned int i = 0; i < data->scene->nodes_count; i++) + { + GetGLTFPrimitiveCount(data->scene->nodes[i], &primitivesCount); + } + // Process glTF data and map to model model.meshCount = primitivesCount; model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh)); @@ -4209,195 +4739,15 @@ static Model LoadGLTF(const char *fileName) model.boneCount = (int)data->nodes_count; model.bones = RL_CALLOC(model.boneCount, sizeof(BoneInfo)); model.bindPose = RL_CALLOC(model.boneCount, sizeof(Transform)); - + InitGLTFBones(&model, data); LoadGLTFMaterial(&model, fileName, data); - + int primitiveIndex = 0; - - for (unsigned int i = 0; i < data->meshes_count; i++) + for(unsigned int i = 0; i < data->scene->nodes_count; i++) { - for (unsigned int p = 0; p < data->meshes[i].primitives_count; p++) - { - for (unsigned int j = 0; j < data->meshes[i].primitives[p].attributes_count; j++) - { - if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_position) - { - cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; - model.meshes[primitiveIndex].vertexCount = (int)acc->count; - int bufferSize = model.meshes[primitiveIndex].vertexCount*3*sizeof(float); - model.meshes[primitiveIndex].vertices = RL_MALLOC(bufferSize); - model.meshes[primitiveIndex].animVertices = RL_MALLOC(bufferSize); - - if (acc->component_type == cgltf_component_type_r_32f) - { - for (unsigned int a = 0; a < acc->count; a++) - { - GLTFReadValue(acc, a, model.meshes[primitiveIndex].vertices + (a*3), 3, sizeof(float)); - } - } - else if (acc->component_type == cgltf_component_type_r_32u) - { - int readValue[3]; - for (unsigned int a = 0; a < acc->count; a++) - { - GLTFReadValue(acc, a, readValue, 3, sizeof(int)); - model.meshes[primitiveIndex].vertices[(a*3) + 0] = (float)readValue[0]; - model.meshes[primitiveIndex].vertices[(a*3) + 1] = (float)readValue[1]; - model.meshes[primitiveIndex].vertices[(a*3) + 2] = (float)readValue[2]; - } - } - else - { - // TODO: Support normalized unsigned byte/unsigned short vertices - TRACELOG(LOG_WARNING, "MODEL: [%s] glTF vertices must be float or int", fileName); - } - - memcpy(model.meshes[primitiveIndex].animVertices, model.meshes[primitiveIndex].vertices, bufferSize); - } - else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_normal) - { - cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; - - int bufferSize = (int)(acc->count*3*sizeof(float)); - model.meshes[primitiveIndex].normals = RL_MALLOC(bufferSize); - model.meshes[primitiveIndex].animNormals = RL_MALLOC(bufferSize); - - if (acc->component_type == cgltf_component_type_r_32f) - { - for (unsigned int a = 0; a < acc->count; a++) - { - GLTFReadValue(acc, a, model.meshes[primitiveIndex].normals + (a*3), 3, sizeof(float)); - } - } - else if (acc->component_type == cgltf_component_type_r_32u) - { - int readValue[3]; - for (unsigned int a = 0; a < acc->count; a++) - { - GLTFReadValue(acc, a, readValue, 3, sizeof(int)); - model.meshes[primitiveIndex].normals[(a*3) + 0] = (float)readValue[0]; - model.meshes[primitiveIndex].normals[(a*3) + 1] = (float)readValue[1]; - model.meshes[primitiveIndex].normals[(a*3) + 2] = (float)readValue[2]; - } - } - else - { - // TODO: Support normalized unsigned byte/unsigned short normals - TRACELOG(LOG_WARNING, "MODEL: [%s] glTF normals must be float or int", fileName); - } - - memcpy(model.meshes[primitiveIndex].animNormals, model.meshes[primitiveIndex].normals, bufferSize); - } - else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_texcoord) - { - cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; - - if (acc->component_type == cgltf_component_type_r_32f) - { - model.meshes[primitiveIndex].texcoords = RL_MALLOC(acc->count*2*sizeof(float)); - - for (unsigned int a = 0; a < acc->count; a++) - { - GLTFReadValue(acc, a, model.meshes[primitiveIndex].texcoords + (a*2), 2, sizeof(float)); - } - } - else - { - // TODO: Support normalized unsigned byte/unsigned short texture coordinates - TRACELOG(LOG_WARNING, "MODEL: [%s] glTF texture coordinates must be float", fileName); - } - } - else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_joints) - { - cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; - LoadGLTFBoneAttribute(&model, acc, data, primitiveIndex); - } - else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_weights) - { - cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; - - model.meshes[primitiveIndex].boneWeights = RL_MALLOC(acc->count*4*sizeof(float)); - - if (acc->component_type == cgltf_component_type_r_32f) - { - for (unsigned int a = 0; a < acc->count; a++) - { - GLTFReadValue(acc, a, model.meshes[primitiveIndex].boneWeights + (a*4), 4, sizeof(float)); - } - } - else if (acc->component_type == cgltf_component_type_r_32u) - { - unsigned int readValue[4]; - for (unsigned int a = 0; a < acc->count; a++) - { - GLTFReadValue(acc, a, readValue, 4, sizeof(unsigned int)); - model.meshes[primitiveIndex].boneWeights[(a*4) + 0] = (float)readValue[0]; - model.meshes[primitiveIndex].boneWeights[(a*4) + 1] = (float)readValue[1]; - model.meshes[primitiveIndex].boneWeights[(a*4) + 2] = (float)readValue[2]; - model.meshes[primitiveIndex].boneWeights[(a*4) + 3] = (float)readValue[3]; - } - } - else - { - // TODO: Support normalized unsigned byte/unsigned short weights - TRACELOG(LOG_WARNING, "MODEL: [%s] glTF normals must be float or int", fileName); - } - } - else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_color) - { - cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; - model.meshes[primitiveIndex].colors = RL_MALLOC(acc->count*4*sizeof(unsigned char)); - - if (acc->component_type == cgltf_component_type_r_8u) - { - for (int a = 0; a < acc->count; a++) - { - GLTFReadValue(acc, a, model.meshes[primitiveIndex].colors + (a*4), 4, sizeof(unsigned char)); - } - } - if (acc->component_type == cgltf_component_type_r_16u) - { - TRACELOG(LOG_WARNING, "MODEL: [%s] converting glTF colors to unsigned char", fileName); - for (int a = 0; a < acc->count; a++) - { - unsigned short readValue[4]; - for (int a = 0; a < acc->count; a++) - { - GLTFReadValue(acc, a, readValue, 4, sizeof(unsigned short)); - // 257 = 65535/255 - model.meshes[primitiveIndex].colors[(a*4) + 0] = (unsigned char)(readValue[0]/257); - model.meshes[primitiveIndex].colors[(a*4) + 1] = (unsigned char)(readValue[1]/257); - model.meshes[primitiveIndex].colors[(a*4) + 2] = (unsigned char)(readValue[2]/257); - model.meshes[primitiveIndex].colors[(a*4) + 3] = (unsigned char)(readValue[3]/257); - } - } - } - else - { - TRACELOG(LOG_WARNING, "MODEL: [%s] glTF colors must be uchar or ushort", fileName); - } - } - } - - cgltf_accessor *acc = data->meshes[i].primitives[p].indices; - LoadGLTFModelIndices(&model, acc, primitiveIndex); - - if (data->meshes[i].primitives[p].material) - { - // Compute the offset - model.meshMaterial[primitiveIndex] = (int)(data->meshes[i].primitives[p].material - data->materials); - } - else - { - model.meshMaterial[primitiveIndex] = model.materialCount - 1; - } - - BindGLTFPrimitiveToBones(&model, data, primitiveIndex); - - primitiveIndex++; - } - + Matrix staticTransform = MatrixIdentity(); + LoadGLTFNode(data, data->scene->nodes[i], &model, staticTransform, &primitiveIndex, fileName); } cgltf_free(data); @@ -4540,79 +4890,20 @@ static void LoadGLTFMaterial(Model *model, const char *fileName, const cgltf_dat model->materials[model->materialCount - 1] = LoadMaterialDefault(); } -static void LoadGLTFBoneAttribute(Model *model, cgltf_accessor *jointsAccessor, const cgltf_data *data, int primitiveIndex) -{ - if (jointsAccessor->component_type == cgltf_component_type_r_16u) - { - model->meshes[primitiveIndex].boneIds = RL_MALLOC(jointsAccessor->count*4*sizeof(int)); - short* bones = RL_MALLOC(jointsAccessor->count*4*sizeof(short)); - - for (unsigned int a = 0; a < jointsAccessor->count; a++) - { - GLTFReadValue(jointsAccessor, a, bones + (a*4), 4, sizeof(short)); - } - - for (unsigned int a = 0; a < jointsAccessor->count*4; a++) - { - cgltf_node* skinJoint = data->skins->joints[bones[a]]; - - for (unsigned int k = 0; k < data->nodes_count; k++) - { - if (&(data->nodes[k]) == skinJoint) - { - model->meshes[primitiveIndex].boneIds[a] = k; - break; - } - } - } - RL_FREE(bones); - } - else if (jointsAccessor->component_type == cgltf_component_type_r_8u) - { - model->meshes[primitiveIndex].boneIds = RL_MALLOC(jointsAccessor->count*4*sizeof(int)); - unsigned char *bones = RL_MALLOC(jointsAccessor->count*4*sizeof(unsigned char)); - - for (unsigned int a = 0; a < jointsAccessor->count; a++) - { - GLTFReadValue(jointsAccessor, a, bones + (a*4), 4, sizeof(unsigned char)); - } - - for (unsigned int a = 0; a < jointsAccessor->count*4; a++) - { - cgltf_node* skinJoint = data->skins->joints[bones[a]]; - - for (unsigned int k = 0; k < data->nodes_count; k++) - { - if (&(data->nodes[k]) == skinJoint) - { - model->meshes[primitiveIndex].boneIds[a] = k; - break; - } - } - } - RL_FREE(bones); - } - else - { - // TODO: Support other size of bone index? - TRACELOG(LOG_WARNING, "MODEL: glTF bones in unexpected format"); - } -} - static void BindGLTFPrimitiveToBones(Model* model, const cgltf_data* data, int primitiveIndex) { - if (model->meshes[primitiveIndex].boneIds == NULL && data->nodes_count > 0) + for (unsigned int nodeId = 0; nodeId < data->nodes_count; nodeId++) { - for (unsigned int nodeId = 0; nodeId < data->nodes_count; nodeId++) + if (data->nodes[nodeId].mesh == &(data->meshes[primitiveIndex])) { - if (data->nodes[nodeId].mesh == &(data->meshes[primitiveIndex])) + if (model->meshes[primitiveIndex].boneIds == NULL) { - model->meshes[primitiveIndex].boneIds = RL_CALLOC(4*model->meshes[primitiveIndex].vertexCount, sizeof(int)); - model->meshes[primitiveIndex].boneWeights = RL_CALLOC(4*model->meshes[primitiveIndex].vertexCount, sizeof(float)); + model->meshes[primitiveIndex].boneIds = RL_CALLOC(4 * model->meshes[primitiveIndex].vertexCount, sizeof(int)); + model->meshes[primitiveIndex].boneWeights = RL_CALLOC(4 * model->meshes[primitiveIndex].vertexCount, sizeof(float)); - for (int b = 0; b < 4*model->meshes[primitiveIndex].vertexCount; b++) + for (int b = 0; b < 4 * model->meshes[primitiveIndex].vertexCount; b++) { - if (b%4 == 0) + if (b % 4 == 0) { model->meshes[primitiveIndex].boneIds[b] = nodeId; model->meshes[primitiveIndex].boneWeights[b] = 1.0f; @@ -4622,102 +4913,12 @@ static void BindGLTFPrimitiveToBones(Model* model, const cgltf_data* data, int p model->meshes[primitiveIndex].boneIds[b] = 0; model->meshes[primitiveIndex].boneWeights[b] = 0.0f; } - - } - - Vector3 boundVertex = { 0 }; - Vector3 boundNormal = { 0 }; - - Vector3 outTranslation = { 0 }; - Quaternion outRotation = { 0 }; - Vector3 outScale = { 0 }; - - int vCounter = 0; - int boneCounter = 0; - int boneId = 0; - - for (int i = 0; i < model->meshes[primitiveIndex].vertexCount; i++) - { - boneId = model->meshes[primitiveIndex].boneIds[boneCounter]; - outTranslation = model->bindPose[boneId].translation; - outRotation = model->bindPose[boneId].rotation; - outScale = model->bindPose[boneId].scale; - - // Vertices processing - boundVertex = (Vector3){ model->meshes[primitiveIndex].vertices[vCounter], model->meshes[primitiveIndex].vertices[vCounter + 1], model->meshes[primitiveIndex].vertices[vCounter + 2] }; - boundVertex = Vector3Multiply(boundVertex, outScale); - boundVertex = Vector3RotateByQuaternion(boundVertex, outRotation); - boundVertex = Vector3Add(boundVertex, outTranslation); - model->meshes[primitiveIndex].vertices[vCounter] = boundVertex.x; - model->meshes[primitiveIndex].vertices[vCounter + 1] = boundVertex.y; - model->meshes[primitiveIndex].vertices[vCounter + 2] = boundVertex.z; - - // Normals processing - if (model->meshes[primitiveIndex].normals != NULL) - { - boundNormal = (Vector3){ model->meshes[primitiveIndex].normals[vCounter], model->meshes[primitiveIndex].normals[vCounter + 1], model->meshes[primitiveIndex].normals[vCounter + 2] }; - boundNormal = Vector3RotateByQuaternion(boundNormal, outRotation); - model->meshes[primitiveIndex].normals[vCounter] = boundNormal.x; - model->meshes[primitiveIndex].normals[vCounter + 1] = boundNormal.y; - model->meshes[primitiveIndex].normals[vCounter + 2] = boundNormal.z; - } - - vCounter += 3; - boneCounter += 4; } } } } } -static void LoadGLTFModelIndices(Model* model, cgltf_accessor* indexAccessor, int primitiveIndex) -{ - if (indexAccessor) - { - if (indexAccessor->component_type == cgltf_component_type_r_16u || indexAccessor->component_type == cgltf_component_type_r_16) - { - model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count/3; - model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short)); - - unsigned short readValue = 0; - for (unsigned int a = 0; a < indexAccessor->count; a++) - { - GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(short)); - model->meshes[primitiveIndex].indices[a] = readValue; - } - } - else if (indexAccessor->component_type == cgltf_component_type_r_8u || indexAccessor->component_type == cgltf_component_type_r_8) - { - model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count/3; - model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short)); - - unsigned char readValue = 0; - for (unsigned int a = 0; a < indexAccessor->count; a++) - { - GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(char)); - model->meshes[primitiveIndex].indices[a] = (unsigned short)readValue; - } - } - else if (indexAccessor->component_type == cgltf_component_type_r_32u) - { - model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count/3; - model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short)); - - unsigned int readValue; - for (unsigned int a = 0; a < indexAccessor->count; a++) - { - GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(unsigned int)); - model->meshes[primitiveIndex].indices[a] = (unsigned short)readValue; - } - } - } - else - { - // Unindexed mesh - model->meshes[primitiveIndex].triangleCount = model->meshes[primitiveIndex].vertexCount/3; - } -} - // LoadGLTF loads in animation data from given filename static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCount) { @@ -4759,9 +4960,9 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo for (unsigned int a = 0; a < data->animations_count; a++) { // gltf animation consists of the following structures: - // - nodes - bones + // - nodes - bones are part of the node system (the whole node system is animatable) // - channels - single transformation type on a single bone - // - node - bone + // - node - animatable node // - transformation type (path) - translation, rotation, scale // - sampler - animation samples // - input - points in time this transformation happens @@ -4773,7 +4974,7 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo ModelAnimation *output = animations + a; // 30 frames sampled per second - const float timeStep = (1.0f/30.0f); + const float timeStep = (1.0f/60.0f); float animationDuration = 0.0f; // Getting the max animation time to consider for animation duration @@ -4783,7 +4984,7 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo int frameCounts = (int)channel->sampler->input->count; float lastFrameTime = 0.0f; - if (GLTFReadValue(channel->sampler->input, frameCounts - 1, &lastFrameTime, 1, sizeof(float))) + if (GLTFReadValue(channel->sampler->input, frameCounts - 1, &lastFrameTime)) { animationDuration = fmaxf(lastFrameTime, animationDuration); } @@ -4808,12 +5009,18 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo { output->framePoses[frame] = RL_MALLOC(output->boneCount*sizeof(Transform)); - for (int i = 0; i < output->boneCount; i++) + for (unsigned int i = 0; i < output->boneCount; i++) { - output->framePoses[frame][i].translation = Vector3Zero(); - output->framePoses[frame][i].rotation = QuaternionIdentity(); + if (data->nodes[i].has_translation) memcpy(&output->framePoses[frame][i].translation, data->nodes[i].translation, 3 * sizeof(float)); + else output->framePoses[frame][i].translation = Vector3Zero(); + + if (data->nodes[i].has_rotation) memcpy(&output->framePoses[frame][i], data->nodes[i].rotation, 4 * sizeof(float)); + else output->framePoses[frame][i].rotation = QuaternionIdentity(); + output->framePoses[frame][i].rotation = QuaternionNormalize(output->framePoses[frame][i].rotation); - output->framePoses[frame][i].scale = Vector3One(); + + if (data->nodes[i].has_scale) memcpy(&output->framePoses[frame][i].scale, data->nodes[i].scale, 3 * sizeof(float)); + else output->framePoses[frame][i].scale = Vector3One(); } } @@ -4839,7 +5046,7 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo for (unsigned int j = 0; j < sampler->input->count; j++) { float inputFrameTime; - if (GLTFReadValue(sampler->input, j, &inputFrameTime, 1, sizeof(float))) + if (GLTFReadValue(sampler->input, j, &inputFrameTime)) { if (frameTime < inputFrameTime) { @@ -4848,7 +5055,7 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo outputMax = j; float previousInputTime = 0.0f; - if (GLTFReadValue(sampler->input, outputMin, &previousInputTime, 1, sizeof(float))) + if (GLTFReadValue(sampler->input, outputMin, &previousInputTime)) { if ((inputFrameTime - previousInputTime) != 0) { @@ -4864,38 +5071,72 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo // If the current transformation has no information for the current frame time point if (shouldSkipFurtherTransformation) continue; - + if (channel->target_path == cgltf_animation_path_type_translation) { Vector3 translationStart; Vector3 translationEnd; - bool success = GLTFReadValue(sampler->output, outputMin, &translationStart, 3, sizeof(float)); - success = GLTFReadValue(sampler->output, outputMax, &translationEnd, 3, sizeof(float)) || success; + float values[3]; + + bool success = GLTFReadValue(sampler->output, outputMin, values); + + translationStart.x = values[0]; + translationStart.y = values[1]; + translationStart.z = values[2]; + + success = GLTFReadValue(sampler->output, outputMax, values) || success; + + translationEnd.x = values[0]; + translationEnd.y = values[1]; + translationEnd.z = values[2]; if (success) output->framePoses[frame][boneId].translation = Vector3Lerp(translationStart, translationEnd, lerpPercent); } if (channel->target_path == cgltf_animation_path_type_rotation) { - Quaternion rotationStart; - Quaternion rotationEnd; - - bool success = GLTFReadValue(sampler->output, outputMin, &rotationStart, 4, sizeof(float)); - success = GLTFReadValue(sampler->output, outputMax, &rotationEnd, 4, sizeof(float)) || success; + Vector4 rotationStart; + Vector4 rotationEnd; + + float values[4]; + + bool success = GLTFReadValue(sampler->output, outputMin, &values); + + rotationStart.x = values[0]; + rotationStart.y = values[1]; + rotationStart.z = values[2]; + rotationStart.w = values[3]; + + success = GLTFReadValue(sampler->output, outputMax, &values) || success; + + rotationEnd.x = values[0]; + rotationEnd.y = values[1]; + rotationEnd.z = values[2]; + rotationEnd.w = values[3]; if (success) { - output->framePoses[frame][boneId].rotation = QuaternionLerp(rotationStart, rotationEnd, lerpPercent); - output->framePoses[frame][boneId].rotation = QuaternionNormalize(output->framePoses[frame][boneId].rotation); + output->framePoses[frame][boneId].rotation = QuaternionNlerp(rotationStart, rotationEnd, lerpPercent); } } if (channel->target_path == cgltf_animation_path_type_scale) { Vector3 scaleStart; Vector3 scaleEnd; - - bool success = GLTFReadValue(sampler->output, outputMin, &scaleStart, 3, sizeof(float)); - success = GLTFReadValue(sampler->output, outputMax, &scaleEnd, 3, sizeof(float)) || success; + + float values[3]; + + bool success = GLTFReadValue(sampler->output, outputMin, &values); + + scaleStart.x = values[0]; + scaleStart.y = values[1]; + scaleStart.z = values[2]; + + success = GLTFReadValue(sampler->output, outputMax, &values) || success; + + scaleEnd.x = values[0]; + scaleEnd.y = values[1]; + scaleEnd.z = values[2]; if (success) output->framePoses[frame][boneId].scale = Vector3Lerp(scaleStart, scaleEnd, lerpPercent); } @@ -4946,4 +5187,167 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo return animations; } +void LoadGLTFMesh(cgltf_data* data, cgltf_mesh* mesh, Model* outModel, Matrix currentTransform, int* primitiveIndex, const char* fileName) +{ + for (unsigned int p = 0; p < mesh->primitives_count; p++) + { + for (unsigned int j = 0; j < mesh->primitives[p].attributes_count; j++) + { + if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_position) + { + cgltf_accessor *acc = mesh->primitives[p].attributes[j].data; + outModel->meshes[(*primitiveIndex)].vertexCount = (int)acc->count; + int bufferSize = outModel->meshes[(*primitiveIndex)].vertexCount*3*sizeof(float); + outModel->meshes[(*primitiveIndex)].animVertices = RL_MALLOC(bufferSize); + + outModel->meshes[(*primitiveIndex)].vertices = GLTFReadValuesAs(acc, cgltf_component_type_r_32f, false); + + // Transform using the nodes matrix attributes + for (unsigned int v = 0; v < outModel->meshes[(*primitiveIndex)].vertexCount; v++) + { + Vector3 vertex = { + outModel->meshes[(*primitiveIndex)].vertices[(v * 3 + 0)], + outModel->meshes[(*primitiveIndex)].vertices[(v * 3 + 1)], + outModel->meshes[(*primitiveIndex)].vertices[(v * 3 + 2)], + }; + + vertex = Vector3Transform(vertex, currentTransform); + + outModel->meshes[(*primitiveIndex)].vertices[(v * 3 + 0)] = vertex.x; + outModel->meshes[(*primitiveIndex)].vertices[(v * 3 + 1)] = vertex.y; + outModel->meshes[(*primitiveIndex)].vertices[(v * 3 + 2)] = vertex.z; + } + + memcpy(outModel->meshes[(*primitiveIndex)].animVertices, outModel->meshes[(*primitiveIndex)].vertices, bufferSize); + } + else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_normal) + { + cgltf_accessor *acc = mesh->primitives[p].attributes[j].data; + + int bufferSize = (int)(acc->count*3*sizeof(float)); + outModel->meshes[(*primitiveIndex)].animNormals = RL_MALLOC(bufferSize); + + outModel->meshes[(*primitiveIndex)].normals = GLTFReadValuesAs(acc, cgltf_component_type_r_32f, false); + + // Transform using the nodes matrix attributes + for (unsigned int v = 0; v < outModel->meshes[(*primitiveIndex)].vertexCount; v++) + { + Vector3 normal = { + outModel->meshes[(*primitiveIndex)].normals[(v * 3 + 0)], + outModel->meshes[(*primitiveIndex)].normals[(v * 3 + 1)], + outModel->meshes[(*primitiveIndex)].normals[(v * 3 + 2)], + }; + + normal = Vector3Transform(normal, currentTransform); + + outModel->meshes[(*primitiveIndex)].normals[(v * 3 + 0)] = normal.x; + outModel->meshes[(*primitiveIndex)].normals[(v * 3 + 1)] = normal.y; + outModel->meshes[(*primitiveIndex)].normals[(v * 3 + 2)] = normal.z; + } + + memcpy(outModel->meshes[(*primitiveIndex)].animNormals, outModel->meshes[(*primitiveIndex)].normals, bufferSize); + } + else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_texcoord) + { + cgltf_accessor *acc = mesh->primitives[p].attributes[j].data; + outModel->meshes[(*primitiveIndex)].texcoords = GLTFReadValuesAs(acc, cgltf_component_type_r_32f, false); + } + else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_joints) + { + cgltf_accessor *acc = mesh->primitives[p].attributes[j].data; + unsigned int boneCount = acc->count; + unsigned int totalBoneWeights = boneCount * 4; + + outModel->meshes[(*primitiveIndex)].boneIds = RL_MALLOC(totalBoneWeights * sizeof(int)); + short* bones = GLTFReadValuesAs(acc, cgltf_component_type_r_16, false); + for (unsigned int a = 0; a < totalBoneWeights; a++) + { + outModel->meshes[(*primitiveIndex)].boneIds[a] = 0; + if(bones[a] < 0) continue; + + cgltf_node* skinJoint = data->skins->joints[bones[a]]; + for (unsigned int k = 0; k < data->nodes_count; k++) + { + if (data->nodes + k == skinJoint) + { + outModel->meshes[(*primitiveIndex)].boneIds[a] = k; + break; + } + } + } + RL_FREE(bones); + } + else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_weights) + { + cgltf_accessor *acc = mesh->primitives[p].attributes[j].data; + outModel->meshes[(*primitiveIndex)].boneWeights = GLTFReadValuesAs(acc, cgltf_component_type_r_32f, false); + } + else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_color) + { + cgltf_accessor *acc = mesh->primitives[p].attributes[j].data; + outModel->meshes[(*primitiveIndex)].colors = GLTFReadValuesAs(acc, cgltf_component_type_r_8u, true); + } + } + + cgltf_accessor *acc = mesh->primitives[p].indices; + if (acc) + { + outModel->meshes[(*primitiveIndex)].triangleCount = acc->count/3; + outModel->meshes[(*primitiveIndex)].indices = GLTFReadValuesAs(acc, cgltf_component_type_r_16u, false); + } + else + { + // Unindexed mesh + outModel->meshes[(*primitiveIndex)].triangleCount = outModel->meshes[(*primitiveIndex)].vertexCount/3; + } + + if (mesh->primitives[p].material) + { + // Compute the offset + outModel->meshMaterial[(*primitiveIndex)] = (int)(mesh->primitives[p].material - data->materials); + } + else + { + outModel->meshMaterial[(*primitiveIndex)] = outModel->materialCount - 1; + } + + BindGLTFPrimitiveToBones(outModel, data, *primitiveIndex); + + (*primitiveIndex) = (*primitiveIndex) + 1; + } +} + +void LoadGLTFNode(cgltf_data* data, cgltf_node* node, Model* outModel, Matrix currentTransform, int* primitiveIndex, const char* fileName) +{ + Matrix nodeTransform = { node->matrix[0], node->matrix[4], node->matrix[8], node->matrix[12], + node->matrix[1], node->matrix[5], node->matrix[9], node->matrix[13], + node->matrix[2], node->matrix[6], node->matrix[10], node->matrix[14], + node->matrix[3], node->matrix[7], node->matrix[11], node->matrix[15] }; + + currentTransform = MatrixMultiply(nodeTransform, currentTransform); + + if(node->mesh != NULL) + { + LoadGLTFMesh(data, node->mesh, outModel, currentTransform, primitiveIndex, fileName); + } + + for(unsigned int i = 0; i < node->children_count; i++) + { + LoadGLTFNode(data, node->children[i], outModel, currentTransform, primitiveIndex, fileName); + } +} + +static void GetGLTFPrimitiveCount(cgltf_node* node, int* outCount) +{ + if(node->mesh != NULL) + { + *outCount += node->mesh->primitives_count; + } + + for(unsigned int i = 0; i < node->children_count; i++) + { + GetGLTFPrimitiveCount(node->children[i], outCount); + } +} + #endif