From fde6384a099143a4a3033e090d61296b1d15f051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Sun, 1 Jun 2025 23:21:35 +0200 Subject: [PATCH] Fix and clean up skeletal animation (#15722) * Fix attachments lagging behind their parents (#14818) * Fix animation blending (#14817) * Bring back cool guy as another .x smoke test * Add .x mesh loader unittest * Do bounding box & matrix calculation at proper point in time * Remove obsolete `SAnimatedMesh` --- doc/lua_api.md | 3 + .../mods/testentities/models/LICENSE.txt | 8 +- .../models/testentities_cool_guy.png | Bin 0 -> 3673 bytes .../models/testentities_cool_guy.x | 2 + games/devtest/mods/testentities/visuals.lua | 13 + irr/include/IAnimatedMesh.h | 22 +- irr/include/IAnimatedMeshSceneNode.h | 47 +- irr/include/IBoneSceneNode.h | 88 +-- irr/include/IMesh.h | 36 +- irr/include/IMeshManipulator.h | 20 - irr/include/IMeshSceneNode.h | 2 +- irr/include/ISceneNode.h | 14 +- irr/include/SAnimatedMesh.h | 167 ----- irr/include/SMesh.h | 13 +- irr/include/SkinnedMesh.h | 146 ++-- irr/include/Transform.h | 42 ++ irr/include/quaternion.h | 2 +- irr/src/CAnimatedMeshSceneNode.cpp | 337 ++++----- irr/src/CAnimatedMeshSceneNode.h | 44 +- irr/src/CB3DMeshFileLoader.cpp | 34 +- irr/src/CBoneSceneNode.cpp | 86 --- irr/src/CBoneSceneNode.h | 71 +- irr/src/CGLTFMeshFileLoader.cpp | 46 +- irr/src/CMakeLists.txt | 1 - irr/src/CMeshCache.cpp | 8 +- irr/src/CMeshManipulator.cpp | 30 - irr/src/CMeshManipulator.h | 9 - irr/src/CNullDriver.cpp | 2 +- irr/src/COBJMeshFileLoader.cpp | 21 +- irr/src/CSceneManager.cpp | 1 - irr/src/CXMeshFileLoader.cpp | 12 +- irr/src/SkinnedMesh.cpp | 656 +++++++----------- lib/tiniergltf/tiniergltf.hpp | 7 +- src/client/content_cao.cpp | 77 +- src/client/content_cao.h | 2 - src/client/mesh.cpp | 10 +- src/nodedef.cpp | 8 - src/unittest/CMakeLists.txt | 2 + src/unittest/test_irr_gltf_mesh_loader.cpp | 44 +- src/unittest/test_irr_x_mesh_loader.cpp | 111 +++ 40 files changed, 856 insertions(+), 1388 deletions(-) create mode 100644 games/devtest/mods/testentities/models/testentities_cool_guy.png create mode 100755 games/devtest/mods/testentities/models/testentities_cool_guy.x delete mode 100644 irr/include/SAnimatedMesh.h create mode 100644 irr/include/Transform.h delete mode 100644 irr/src/CBoneSceneNode.cpp create mode 100644 src/unittest/test_irr_x_mesh_loader.cpp diff --git a/doc/lua_api.md b/doc/lua_api.md index 3a6da1bdb..07f70c36f 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -315,6 +315,9 @@ due to their space savings. Bone weights should be normalized, e.g. using ["normalize all" in Blender](https://docs.blender.org/manual/en/4.2/grease_pencil/modes/weight_paint/weights_menu.html#normalize-all). +Note that nodes using matrix transforms must not be animated. +This also extends to bone overrides, which must not be applied to them. + You can use the [Khronos glTF validator](https://github.com/KhronosGroup/glTF-Validator) to check whether a model is a valid glTF file. diff --git a/games/devtest/mods/testentities/models/LICENSE.txt b/games/devtest/mods/testentities/models/LICENSE.txt index 19ffffc5c..6ec445db6 100644 --- a/games/devtest/mods/testentities/models/LICENSE.txt +++ b/games/devtest/mods/testentities/models/LICENSE.txt @@ -12,4 +12,10 @@ Jordach (CC BY-SA 3.0): Zeg9 (CC BY-SA 3.0): testentities_lava_flan.x - testentities_lava_flan.png \ No newline at end of file + testentities_lava_flan.png + +"Cool Guy": + +hecks (refer to irr/LICENSE): + testentities_cool_guy.x + testentities_cool_guy.png diff --git a/games/devtest/mods/testentities/models/testentities_cool_guy.png b/games/devtest/mods/testentities/models/testentities_cool_guy.png new file mode 100644 index 0000000000000000000000000000000000000000..84cc12e442b73df3583daed7fe9c5433b86c5749 GIT binary patch literal 3673 zcmd6qXHeA5md5`>90o~}Gl&StAUOz!nB14p%GeZ0aIHU>mG&^jGYd(mA{u>)a*y@H(8g*X6!e4Ffa#$1++=q z70I+EM+qUxQ3{wS2?1e&NHH-IKFw&pN$^{FK#oI>L#2q)x{wmwv!^h)<* z33zDJ8Oo7MbNFs^M`)HEx9d9MDz=?%DPf_*F}AYvf_FukRnd05z+!Sg_4umbrw6~E z7#}H`%+_<)7Qd^QXx@{t@0rS6?GLb-Mew#j%mQh;% z5tQprzF5k*^6rDjv$mOPYbKQZLNk~wCfE#&%mH2qG+ z(1xPlteMPUeF5BPrqXQMqcAMlU4p*v{;6kvF;DUf7Nr~o>!cn)MMb3e+G(f>epDj= z3bh!L%lTu)efVfhU#z7}?4$|vU^X)$q1>+LW2M7Dru@rLOtl*o6;%;q^W(P1#3lGj zN=j{QZJXnzm4>Dg?$ZI-!5l8NxRMwCZqJ^j^jLPUmz6xtp?KX)K`8V|@hJZKP8hpb zD7Hv-S$V-!w?vyHS`s691^+bJ6fImO_j7P~)*IJ6VX0##=`q_oFp$MrTocH4mcEs~ zWq{I=4L;E-YHNE#!Tunpu(XL5_E0QwZ5q{0$ zC^!lXb<%bDf8Dq~k)LY{*k8gD&$;-=2V$b;=&H*Ycikik2{7Cs8-M+l)d-q+! z>ir81leRT;^&EZR70+8?mwnxy=leTpn&Gm<6l}@N=IcS%SLgZKX};1K`VWU2Ifs*n zvR>!r278C$yicJRHDuN@rxu)C*6(4qnB=*h*!wXm>|v|X&B z_Zw!m%uPCH86M^0t$|(nK5KEvR`WVvJ|3Pfr}nU_d`b9fgNUVo3e5iC zIg@x8`=b$833eHHuhH@$b_j{PJmul$t|h?w*Y)8SaRy5^DzmF<{auahLH&fd}nF7sg-B3MV#MOWCO&T_m$|;mQoC zV>DXdOlo)?Z;ZiZDfzH!{SJGLtyVsJXFm}-Tm+k8i(x}qA45`8YS4FWdef2?qKGTo zmKh7zRg#=C>Aws)4eiV}y)YFK|Fya^Q(qH`3wk{?KdB_%K}3I5?z>@tpfLV~7OzSqbq{Uw!e74Vx|&R%YOTIXAx|CI#Wh4s)4(}PIS7%<MOA+02|$40m-c>mJPZ{(9EYLNBMh&PG?^@TVWX8UF17t?y*8sWd*p#5Z~ zoc*g|aPhI--S%ARTb%DSEjnjyWmN)M=+~auSdkhO3Req|c_@i4xmf1IWFZJojk}yQ z!2c~#gqx@@1VL~ofd5W%n#BCNJeP z{~@jd@)<81XsB*}!av?_CD?j0fx$iR9=(OOwsCMGc?6eAK0V3>coCnY8ouSo#f7(e zx2O9>C z*oxZh4RTtMsqX23Etm|FxSv(|?N3>+V+2~R8D%!7KY_k?@f+~2<}v1w7m!5T8Q*<+ zT`{~L`!y38VqVuuz12NWd+O(TvG;osRQ^stCpE!;ruE#cyyx?|Z;5*3kY=#HLg?g% zr?lMEfF;n|)8FwrvF7m6M^K{Eg<&tVsCNkYhA1KE=YZ4H1Sfq<<4E@a6=+gK2phBr zrmJP_gj_H_s@@;c<+^x4QfNO^QHmxWHc{6!vHJZ;fme$~&*rJ}ijod%OJ-*IaYXa8 zJ6Wue$i6|^OhWwUwCvE2O@_Zlm08)hlWaKaMY^q2B@752%F+JgO@cfzz4M21B*0Iv zWb-So8@Ny9smQ&2>vF{enpi7&-*lyDW2^kd{7asO$6_uR;SXc=rrkAT9F=dE@G6>; zC{Dx3RK2DO3HljRCQ>3Z`i?m-_Txuf4v93SQQN%MW4HXb%v5^eyT=tNUAjlZ6^&3L z`TXQO=v@>py>|4)%RG5!OgQCb{F1G@@;xW>&%@`f-P94=2|Dzh8Ul_z-#~TW(@@#p zFF??s`&@0?aez8S-MK@8Qv@D;5{qLi=EBl@=e zc>(k;iZs|OmtLAp{mSOucc*RdLHskl-hS){Yj8Nh1EQRR34!+Vv`m-wPEIX`Uu{3nn?k>i#km6bCvj*~I6?c-Kx8hcs1?$BCyO zaxpNxDZ*gLN3@(W;wizGMN5+!t`3`2$;yE#e&a=9iDa~z@bMf`y~`T-qf+;08fr_g z9_&+|FX2YSF1N`XUQMIyY;6uIQkp9iysZuU_gXIU9+WK|e(>;Tp-Txi;XvWrTBA-p z%L1-0*34%SiPk7ads*ifm6#Z!X(_~()^rR$FLSVD$6cst@ydi3I|6S$2%rd6kYJr2 z#eExcV$3sNoE=jyzqD?OBk188iSr02N*;q7Y2Auw-tl4*c>>V1K+~c{Bd!`NY;4wk z0hL$*&#t0rUgg3eUkBrtRdy z?fYX$m(eZkW z?W?8R9S(8#SD=wCH~9aw1v;bqS7!eS{co-K+u6Sj0{^q$|K={SoHBh|gr|C2!JjW9 OprN9p{PnR_*na>&K!GLz literal 0 HcmV?d00001 diff --git a/games/devtest/mods/testentities/models/testentities_cool_guy.x b/games/devtest/mods/testentities/models/testentities_cool_guy.x new file mode 100755 index 000000000..e806d8315 --- /dev/null +++ b/games/devtest/mods/testentities/models/testentities_cool_guy.x @@ -0,0 +1,2 @@ +xof 0303txt 0032 +AnimationSet{Animation{{Armature}AnimationKey{0;2;0;4;1,0,0,0;;,29;4;1,0,0,0;;;}AnimationKey{2;2;0;3;0,0,0;;,29;3;0,0,0;;;}}Animation{{Armature_knee_r}AnimationKey{0;16;0;4;0.864183,0.503177,0,0;;,1;4;0.829812,0.558043,0,0;;,3;4;0.708698,0.705512,0,0;;,5;4;0.589108,0.808054,0,0;;,7;4;0.593659,0.804717,0,0;;,9;4;0.748627,0.662991,0,0;;,11;4;0.910305,0.413938,0,0;;,13;4;0.975925,0.218107,0,0;;,15;4;0.981302,0.192476,0,0;;,17;4;0.975476,0.220108,0,0;;,19;4;0.963662,0.267124,0,0;;,21;4;0.945893,0.324478,0,0;;,23;4;0.923816,0.382838,0,0;;,25;4;0.901205,0.433394,0,0;;,27;4;0.883429,0.468566,0,0;;,29;4;0.876305,0.481757,0,0;;;}AnimationKey{2;2;0;3;0,0,1.10139;;,29;3;0,0,1.10139;;;}}Animation{{Armature_elbow_r}AnimationKey{0;16;0;4;0.756295,0.004619,-0.619265,0.210967;;,1;4;0.771977,0.005599,-0.60257,0.202311;;,3;4;0.825501,0.009164,-0.538259,0.169533;;,5;4;0.891859,0.014253,-0.436142,0.119019;;,7;4;0.949154,0.019821,-0.308768,0.058108;;,9;4;0.983251,0.024703,-0.18057,-0.001258;;,11;4;0.995416,0.028143,-0.07812,-0.047458;;,13;4;0.996672,0.02991,-0.020368,-0.073041;;,15;4;0.996672,0.02991,-0.020368,-0.073041;;,17;4;0.995416,0.028143,-0.07812,-0.047458;;,19;4;0.983251,0.024703,-0.18057,-0.001258;;,21;4;0.949154,0.019821,-0.308768,0.058108;;,23;4;0.891859,0.014253,-0.436142,0.119019;;,25;4;0.825501,0.009164,-0.538259,0.169533;;,27;4;0.771977,0.005599,-0.60257,0.202311;;,29;4;0.750682,0.004275,-0.625038,0.213976;;;}AnimationKey{2;2;0;3;0,0,0.754892;;,29;3;0,0,0.754892;;;}}Animation{{Armature_arm_r}AnimationKey{0;16;0;4;0.28219,0.629905,0.723388,-0.017285;;,1;4;0.277641,0.632543,0.722699,-0.022614;;,3;4;0.261375,0.641615,0.719924,-0.041507;;,5;4;0.238321,0.653533,0.715186,-0.067874;;,7;4;0.212026,0.665838,0.708676,-0.097381;;,9;4;0.186345,0.676585,0.701229,-0.125643;;,11;4;0.165298,0.684491,0.694351,-0.14841;;,13;4;0.152894,0.688778,0.68998,-0.161665;;,15;4;0.152894,0.688779,0.68998,-0.161665;;,17;4;0.165298,0.684491,0.694351,-0.14841;;,19;4;0.186345,0.676585,0.701229,-0.125643;;,21;4;0.212026,0.665838,0.708676,-0.097381;;,23;4;0.238321,0.653533,0.715186,-0.067874;;,25;4;0.261375,0.641615,0.719924,-0.041507;;,27;4;0.277641,0.632543,0.722699,-0.022614;;,29;4;0.283802,0.628959,0.723623,-0.015394;;;}AnimationKey{2;2;0;3;-0.545315,0,1;;,29;3;-0.545315,0,1;;;}}Animation{{Armature_knee_l}AnimationKey{0;16;0;4;0.981896,0.189423,0,0;;,1;4;0.9814,0.191974,0,0;;,3;4;0.979127,0.203251,0,0;;,5;4;0.974526,0.224276,0,0;;,7;4;0.96645,0.256853,0,0;;,9;4;0.953088,0.302692,0,0;;,11;4;0.931731,0.36315,0,0;;,13;4;0.898645,0.438676,0,0;;,15;4;0.848226,0.529634,0,0;;,17;4;0.773692,0.633562,0,0;;,19;4;0.689831,0.72397,0,0;;,21;4;0.629304,0.777159,0,0;;,23;4;0.648685,0.761057,0,0;;,25;4;0.812268,0.583284,0,0;;,27;4;0.948066,0.318074,0,0;;,29;4;0.982049,0.188624,0,0;;;}AnimationKey{2;2;0;3;0,0,1.10139;;,29;3;0,0,1.10139;;;}}Animation{{Armature_Bone_007}AnimationKey{0;16;0;4;0.993671,-0.112331,0,0;;,1;4;0.994784,-0.102002,0,0;;,3;4;0.997507,-0.070564,0,0;;,5;4;0.999237,-0.039056,0,0;;,7;4;0.999694,-0.024737,0,0;;,9;4;0.999079,-0.042907,0,0;;,11;4;0.99677,-0.080308,0,0;;,13;4;0.993798,-0.111199,0,0;;,15;4;0.993599,-0.112965,0,0;;,17;4;0.995813,-0.091409,0,0;;,19;4;0.998181,-0.060285,0,0;;,21;4;0.999479,-0.032286,0,0;;,23;4;0.999797,-0.020142,0,0;;,25;4;0.998983,-0.045097,0,0;;,27;4;0.995813,-0.091409,0,0;;,29;4;0.993221,-0.116243,0,0;;;}AnimationKey{2;2;0;3;0,0,1.221802;;,29;3;0,0,1.221802;;;}}Animation{{Armature_elbow_l}AnimationKey{0;16;0;4;0.995195,-0.034868,-0.015799,-0.090119;;,1;4;0.993465,-0.046368,-0.030155,-0.099838;;,3;4;0.983557,-0.0879,-0.082099,-0.134715;;,5;4;0.959324,-0.146904,-0.156177,-0.183648;;,7;4;0.917546,-0.212233,-0.238611,-0.236921;;,9;4;0.864109,-0.271657,-0.314022,-0.284443;;,11;4;0.813172,-0.315829,-0.370387,-0.319087;;,13;4;0.781004,-0.339668,-0.400938,-0.337501;;,15;4;0.781004,-0.339668,-0.400938,-0.337501;;,17;4;0.813172,-0.315829,-0.370387,-0.319087;;,19;4;0.864109,-0.271657,-0.314022,-0.284443;;,21;4;0.917546,-0.212233,-0.238611,-0.236921;;,23;4;0.959324,-0.146904,-0.156177,-0.183648;;,25;4;0.983557,-0.0879,-0.082099,-0.134715;;,27;4;0.993465,-0.046368,-0.030155,-0.099838;;,29;4;0.995701,-0.030812,-0.010739,-0.086685;;;}AnimationKey{2;2;0;3;0,0,0.754892;;,29;3;0,0,0.754892;;;}}Animation{{Armature_body}AnimationKey{0;16;0;4;-0,0,0.601298,0.799025;;,1;4;-0,0,0.608144,0.793827;;,3;4;-0,0,0.627465,0.778645;;,5;4;-0,0,0.643183,0.765712;;,7;4;-0,0,0.643755,0.765231;;,9;4;-0,0,0.631076,0.775721;;,11;4;-0,0,0.613775,0.789481;;,13;4;-0,0,0.6007,0.799474;;,15;4;-0,0,0.601488,0.798882;;,17;4;-0,0,0.619499,0.784997;;,19;4;-0,0,0.643196,0.765702;;,21;4;-0,0,0.660441,0.750878;;,23;4;-0,0,0.659666,0.751559;;,25;4;-0,0,0.638264,0.769817;;,27;4;-0,0,0.611752,0.791049;;,29;4;-0,0,0.598631,0.801025;;;}AnimationKey{2;2;0;3;0,2.580534,0;;,29;3;0,2.571201,0;;;}}Animation{{Armature_leg_l}AnimationKey{0;16;0;4;0.390287,0.920693,0,0;;,1;4;0.362565,0.931959,0,0;;,3;4;0.266163,0.963928,0,0;;,5;4;0.138294,0.990391,0,0;;,7;4;0.012725,0.999919,0,0;;,9;4;-0.090194,0.995924,0,0;;,11;4;-0.162502,0.986708,0,0;;,13;4;-0.201466,0.979496,0,0;;,15;4;-0.185641,0.982618,0,0;;,17;4;-0.013697,0.999906,0,0;;,19;4;0.24238,0.970181,0,0;;,21;4;0.417271,0.908782,0,0;;,23;4;0.439308,0.898336,0,0;;,25;4;0.424255,0.905543,0,0;;,27;4;0.407664,0.913132,0,0;;,29;4;0.400263,0.9164,0,0;;;}AnimationKey{2;2;0;3;0.246294,0,-0.171352;;,29;3;0.246294,0,-0.171351;;;}}Animation{{Armature_leg_r}AnimationKey{0;16;0;4;0.174933,-0.98458,0,0;;,1;4;0.082829,-0.996564,0,0;;,3;4;-0.21147,-0.977384,0,0;;,5;4;-0.442802,-0.89662,0,0;;,7;4;-0.47604,-0.879424,0,0;;,9;4;-0.47279,-0.881175,0,0;;,11;4;-0.459567,-0.888143,0,0;;,13;4;-0.427425,-0.904051,0,0;;,15;4;-0.361724,-0.932285,0,0;;,17;4;-0.251362,-0.967893,0,0;;,19;4;-0.114531,-0.99342,0,0;;,21;4;0.021053,-0.999778,0,0;;,23;4;0.12473,-0.992191,0,0;;,25;4;0.181473,-0.983396,0,0;;,27;4;0.204037,-0.978963,0,0;;,29;4;0.208187,-0.978089,0,0;;;}AnimationKey{2;2;0;3;-0.246294,0,-0.171352;;,29;3;-0.246294,0,-0.171351;;;}}Animation{{Armature_arm_l}AnimationKey{0;16;0;4;0.200754,-0.659656,-0.716264,-0.107316;;,1;4;0.192268,-0.660735,-0.716526,-0.114246;;,3;4;0.161871,-0.663925,-0.716753,-0.138802;;,5;4;0.118745,-0.666682,-0.715211,-0.17294;;,7;4;0.069733,-0.667364,-0.710872,-0.210767;;,9;4;0.022313,-0.665594,-0.704111,-0.246404;;,11;4;-0.016046,-0.662426,-0.696821,-0.274543;;,13;4;-0.038374,-0.659874,-0.691824,-0.290643;;,15;4;-0.038373,-0.659874,-0.691824,-0.290643;;,17;4;-0.016044,-0.662427,-0.696822,-0.274543;;,19;4;0.022312,-0.665594,-0.70411,-0.246404;;,21;4;0.069733,-0.667365,-0.710872,-0.210767;;,23;4;0.118745,-0.666682,-0.715211,-0.17294;;,25;4;0.161871,-0.663925,-0.716753,-0.138802;;,27;4;0.192268,-0.660735,-0.716526,-0.114246;;,29;4;0.203757,-0.659255,-0.716151,-0.104856;;;}AnimationKey{2;2;0;3;0.545315,0,1;;,29;3;0.545315,0,1;;;}}}Frame Root{FrameTransformMatrix{1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1;;}Frame Armature{FrameTransformMatrix{1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1;;}Frame Armature_body{FrameTransformMatrix{-1,0,0,0,0,0,1,0,0,1,0,0,0,2.571201,0,1;;}Frame Armature_arm_r{FrameTransformMatrix{-0.047733,0.997488,-0.05233,0,0.901521,0.020464,-0.432251,0,-0.430095,-0.067809,-0.900233,0,-0.545315,0,1,1;;}Frame Armature_elbow_r{FrameTransformMatrix{0.987983,0.151721,-0.029519,0,-0.153228,0.986478,-0.058162,0,0.020295,0.061987,0.997871,0,0,0,0.754892,1;;}}}Frame Armature_arm_l{FrameTransformMatrix{-0.047732,0.994072,-0.097683,0,0.901521,0.084983,0.424309,0,0.430095,-0.067809,-0.900233,0,0.545315,0,1,1;;}Frame Armature_elbow_l{FrameTransformMatrix{0.984741,0.173286,-0.016044,0,-0.171963,0.983073,0.063221,0,0.026727,-0.059497,0.99787,0,0,0,0.754892,1;;}}}Frame Armature_leg_l{FrameTransformMatrix{1,0,0,0,0,-0.998426,-0.056453,0,0,0.056453,-0.998405,0,0.246294,0,-0.171351,1;;}Frame Armature_knee_l{FrameTransformMatrix{1,0,0,0,0,0.993861,-0.110639,0,0,0.110639,0.993861,0,0,0,1.10139,1;;}}}Frame Armature_leg_r{FrameTransformMatrix{1,0,0,0,0,-0.998426,-0.056453,0,0,0.056453,-0.998405,0,-0.246294,0,-0.171351,1;;}Frame Armature_knee_r{FrameTransformMatrix{1,0,0,0,0,0.993861,-0.110639,0,0,0.110639,0.993861,0,0,0,1.10139,1;;}}}Frame Armature_Bone_007{FrameTransformMatrix{1,0,0,0,0,1,0,0,0,0,1,0,0,0,1.221802,1;;}}}Frame cool_dude{FrameTransformMatrix{-1,0,0,0,0,1,0,0,0,0,-1,0,0,0,0,1;;}Mesh{272;0;2.440814;0.219926;,0;3.688199;0.219926;,0.466212;3.688199;0.219926;,0.466212;2.440814;0.219926;,0.466212;2.440814;0.219926;,0.466212;3.688199;0.219926;,0.466212;3.688199;-0.219926;,0.466212;2.440814;-0.219926;,0;2.440814;0.219926;,0.466212;2.440814;0.219926;,0.466212;2.440814;-0.219926;,0;2.440814;-0.219926;,0.055633;1.27575;-0.190081;,0.055633;2.35741;-0.190081;,0.055633;2.35741;0.190081;,0.055633;1.27575;0.190081;,0.055633;1.27575;0.190081;,0.055633;2.35741;0.190081;,0.43017;2.35741;0.190081;,0.43017;1.27575;0.190081;,0.43017;1.27575;0.190081;,0.43017;2.35741;0.190081;,0.43017;2.35741;-0.190081;,0.43017;1.27575;-0.190081;,0.43017;1.27575;-0.190081;,0.43017;2.35741;-0.190081;,0.055633;2.35741;-0.190081;,0.055633;1.27575;-0.190081;,0.055633;1.27575;0.190081;,0.43017;1.27575;0.190081;,0.43017;1.27575;-0.190081;,0.055633;1.27575;-0.190081;,0.43017;2.35741;0.190081;,0.055633;2.35741;0.190081;,0.055633;2.35741;-0.190081;,0.43017;2.35741;-0.190081;,0.466212;3.688199;0.219926;,0;3.688199;0.219926;,0;3.688199;-0.219926;,0.466212;3.688199;-0.219926;,0.466212;2.440814;-0.219926;,0.466212;3.688199;-0.219926;,0;3.688199;-0.219926;,0;2.440814;-0.219926;,0.769341;2.834949;-0.041122;,0.440953;3.555781;-0.041122;,0.440953;3.555781;0.207294;,0.769341;2.834949;0.207294;,0.769341;2.834949;0.207294;,0.440953;3.555781;0.207294;,0.616273;3.635651;0.207294;,0.944661;2.914819;0.207294;,0.944661;2.914819;0.207294;,0.616273;3.635651;0.207294;,0.616273;3.635651;-0.041122;,0.944661;2.914819;-0.041122;,0.944661;2.914819;-0.041122;,0.616273;3.635651;-0.041122;,0.440953;3.555781;-0.041122;,0.769341;2.834949;-0.041122;,0.769341;2.834949;0.207294;,0.944661;2.914819;0.207294;,0.944661;2.914819;-0.041122;,0.769341;2.834949;-0.041122;,0.616273;3.635651;0.207294;,0.440953;3.555781;0.207294;,0.440953;3.555781;-0.041122;,0.616273;3.635651;-0.041122;,1.104504;2.080977;-0.086788;,0.776116;2.801809;-0.086788;,0.776116;2.801809;0.161627;,1.104504;2.080977;0.161627;,1.104504;2.080977;0.161627;,0.776116;2.801809;0.161627;,0.951436;2.881679;0.161627;,1.279824;2.160847;0.161627;,1.279824;2.160847;0.161627;,0.951436;2.881679;0.161627;,0.951436;2.881679;-0.086788;,1.279824;2.160847;-0.086788;,1.279824;2.160847;-0.086788;,0.951436;2.881679;-0.086788;,0.776116;2.801809;-0.086788;,1.104504;2.080977;-0.086788;,1.104504;2.080977;0.161627;,1.279824;2.160847;0.161627;,1.279824;2.160847;-0.086788;,1.104504;2.080977;-0.086788;,0.951436;2.881679;0.161627;,0.776116;2.801809;0.161627;,0.776116;2.801809;-0.086788;,0.951436;2.881679;-0.086788;,0.055633;0.093601;-0.190081;,0.055633;1.205294;-0.190081;,0.055633;1.205294;0.190081;,0.055633;0.093601;0.190081;,0.055633;0.093601;0.190081;,0.055633;1.205294;0.190081;,0.43017;1.205294;0.190081;,0.43017;0.093601;0.190081;,0.43017;0.093601;0.190081;,0.43017;1.205294;0.190081;,0.43017;1.205294;-0.190081;,0.43017;0.093601;-0.190081;,0.43017;0.093601;-0.190081;,0.43017;1.205294;-0.190081;,0.055633;1.205294;-0.190081;,0.055633;0.093601;-0.190081;,0.055633;0.093601;0.190081;,0.43017;0.093601;0.190081;,0.43017;0.093601;-0.190081;,0.055633;0.093601;-0.190081;,0.43017;1.205294;0.190081;,0.055633;1.205294;0.190081;,0.055633;1.205294;-0.190081;,0.43017;1.205294;-0.190081;,0;3.790919;0.428464;,0;4.579204;0.428464;,0.43344;4.560537;0.409797;,0.43344;3.809586;0.409797;,0.43344;3.809586;0.409797;,0.43344;4.560537;0.409797;,0.43344;4.560537;-0.284975;,0.43344;3.809586;-0.284975;,0;3.790919;0.428464;,0.43344;3.809586;0.409797;,0.43344;3.809586;-0.284975;,0;3.790919;-0.303642;,0.43344;4.560537;0.409797;,0;4.579204;0.428464;,0;4.579204;-0.303642;,0.43344;4.560537;-0.284975;,0.43344;3.809586;-0.284975;,0.43344;4.560537;-0.284975;,0;4.579204;-0.303642;,0;3.790919;-0.303642;,0;2.440814;0.219926;,-0.466212;2.440814;0.219926;,-0.466212;3.688199;0.219926;,0;3.688199;0.219926;,-0.466212;2.440814;0.219926;,-0.466212;2.440814;-0.219926;,-0.466212;3.688199;-0.219926;,-0.466212;3.688199;0.219926;,0;2.440814;0.219926;,0;2.440814;-0.219926;,-0.466212;2.440814;-0.219926;,-0.466212;2.440814;0.219926;,-0.055633;1.27575;-0.190081;,-0.055633;1.27575;0.190081;,-0.055633;2.35741;0.190081;,-0.055633;2.35741;-0.190081;,-0.055633;1.27575;0.190081;,-0.43017;1.27575;0.190081;,-0.43017;2.35741;0.190081;,-0.055633;2.35741;0.190081;,-0.43017;1.27575;0.190081;,-0.43017;1.27575;-0.190081;,-0.43017;2.35741;-0.190081;,-0.43017;2.35741;0.190081;,-0.43017;1.27575;-0.190081;,-0.055633;1.27575;-0.190081;,-0.055633;2.35741;-0.190081;,-0.43017;2.35741;-0.190081;,-0.055633;1.27575;0.190081;,-0.055633;1.27575;-0.190081;,-0.43017;1.27575;-0.190081;,-0.43017;1.27575;0.190081;,-0.43017;2.35741;0.190081;,-0.43017;2.35741;-0.190081;,-0.055633;2.35741;-0.190081;,-0.055633;2.35741;0.190081;,-0.466212;3.688199;0.219926;,-0.466212;3.688199;-0.219926;,0;3.688199;-0.219926;,0;3.688199;0.219926;,-0.466212;2.440814;-0.219926;,0;2.440814;-0.219926;,0;3.688199;-0.219926;,-0.466212;3.688199;-0.219926;,-0.769341;2.834949;-0.041122;,-0.769341;2.834949;0.207294;,-0.440953;3.555781;0.207294;,-0.440953;3.555781;-0.041122;,-0.769341;2.834949;0.207294;,-0.944661;2.914819;0.207294;,-0.616273;3.635651;0.207294;,-0.440953;3.555781;0.207294;,-0.944661;2.914819;0.207294;,-0.944661;2.914819;-0.041122;,-0.616273;3.635651;-0.041122;,-0.616273;3.635651;0.207294;,-0.944661;2.914819;-0.041122;,-0.769341;2.834949;-0.041122;,-0.440953;3.555781;-0.041122;,-0.616273;3.635651;-0.041122;,-0.769341;2.834949;0.207294;,-0.769341;2.834949;-0.041122;,-0.944661;2.914819;-0.041122;,-0.944661;2.914819;0.207294;,-0.616273;3.635651;0.207294;,-0.616273;3.635651;-0.041122;,-0.440953;3.555781;-0.041122;,-0.440953;3.555781;0.207294;,-1.104504;2.080977;-0.086788;,-1.104504;2.080977;0.161627;,-0.776116;2.801809;0.161627;,-0.776116;2.801809;-0.086788;,-1.104504;2.080977;0.161627;,-1.279824;2.160847;0.161627;,-0.951436;2.881679;0.161627;,-0.776116;2.801809;0.161627;,-1.279824;2.160847;0.161627;,-1.279824;2.160847;-0.086788;,-0.951436;2.881679;-0.086788;,-0.951436;2.881679;0.161627;,-1.279824;2.160847;-0.086788;,-1.104504;2.080977;-0.086788;,-0.776116;2.801809;-0.086788;,-0.951436;2.881679;-0.086788;,-1.104504;2.080977;0.161627;,-1.104504;2.080977;-0.086788;,-1.279824;2.160847;-0.086788;,-1.279824;2.160847;0.161627;,-0.951436;2.881679;0.161627;,-0.951436;2.881679;-0.086788;,-0.776116;2.801809;-0.086788;,-0.776116;2.801809;0.161627;,-0.055633;0.093601;-0.190081;,-0.055633;0.093601;0.190081;,-0.055633;1.205294;0.190081;,-0.055633;1.205294;-0.190081;,-0.055633;0.093601;0.190081;,-0.43017;0.093601;0.190081;,-0.43017;1.205294;0.190081;,-0.055633;1.205294;0.190081;,-0.43017;0.093601;0.190081;,-0.43017;0.093601;-0.190081;,-0.43017;1.205294;-0.190081;,-0.43017;1.205294;0.190081;,-0.43017;0.093601;-0.190081;,-0.055633;0.093601;-0.190081;,-0.055633;1.205294;-0.190081;,-0.43017;1.205294;-0.190081;,-0.055633;0.093601;0.190081;,-0.055633;0.093601;-0.190081;,-0.43017;0.093601;-0.190081;,-0.43017;0.093601;0.190081;,-0.43017;1.205294;0.190081;,-0.43017;1.205294;-0.190081;,-0.055633;1.205294;-0.190081;,-0.055633;1.205294;0.190081;,0;3.790919;0.428464;,-0.43344;3.809586;0.409797;,-0.43344;4.560537;0.409797;,0;4.579204;0.428464;,-0.43344;3.809586;0.409797;,-0.43344;3.809586;-0.284975;,-0.43344;4.560537;-0.284975;,-0.43344;4.560537;0.409797;,0;3.790919;0.428464;,0;3.790919;-0.303642;,-0.43344;3.809586;-0.284975;,-0.43344;3.809586;0.409797;,-0.43344;4.560537;0.409797;,-0.43344;4.560537;-0.284975;,0;4.579204;-0.303642;,0;4.579204;0.428464;,-0.43344;3.809586;-0.284975;,0;3.790919;-0.303642;,0;4.579204;-0.303642;,-0.43344;4.560537;-0.284975;;68;4;3,2,1,0;,4;7,6,5,4;,4;11,10,9,8;,4;15,14,13,12;,4;19,18,17,16;,4;23,22,21,20;,4;27,26,25,24;,4;31,30,29,28;,4;35,34,33,32;,4;39,38,37,36;,4;43,42,41,40;,4;47,46,45,44;,4;51,50,49,48;,4;55,54,53,52;,4;59,58,57,56;,4;63,62,61,60;,4;67,66,65,64;,4;71,70,69,68;,4;75,74,73,72;,4;79,78,77,76;,4;83,82,81,80;,4;87,86,85,84;,4;91,90,89,88;,4;95,94,93,92;,4;99,98,97,96;,4;103,102,101,100;,4;107,106,105,104;,4;111,110,109,108;,4;115,114,113,112;,4;119,118,117,116;,4;123,122,121,120;,4;127,126,125,124;,4;131,130,129,128;,4;135,134,133,132;,4;139,138,137,136;,4;143,142,141,140;,4;147,146,145,144;,4;151,150,149,148;,4;155,154,153,152;,4;159,158,157,156;,4;163,162,161,160;,4;167,166,165,164;,4;171,170,169,168;,4;175,174,173,172;,4;179,178,177,176;,4;183,182,181,180;,4;187,186,185,184;,4;191,190,189,188;,4;195,194,193,192;,4;199,198,197,196;,4;203,202,201,200;,4;207,206,205,204;,4;211,210,209,208;,4;215,214,213,212;,4;219,218,217,216;,4;223,222,221,220;,4;227,226,225,224;,4;231,230,229,228;,4;235,234,233,232;,4;239,238,237,236;,4;243,242,241,240;,4;247,246,245,244;,4;251,250,249,248;,4;255,254,253,252;,4;259,258,257,256;,4;263,262,261,260;,4;267,266,265,264;,4;271,270,269,268;;MeshNormals{272;0;-0.707083;0.707083;,0;0.707083;0.707083;,0.577349;0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0;-0.707083;0.707083;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;-0.577349;,0;-0.707083;-0.707083;,-0.577349;-0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;-0.577349;,0.577349;0.577349;0.577349;,-0.577349;0.577349;0.577349;,-0.577349;0.577349;-0.577349;,0.577349;0.577349;-0.577349;,0.577349;0.577349;0.577349;,0;0.707083;0.707083;,0;0.707083;-0.707083;,0.577349;0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;0.577349;-0.577349;,0;0.707083;-0.707083;,0;-0.707083;-0.707083;,-0.286019;-0.764733;-0.577349;,-0.764733;0.286019;-0.577349;,-0.764733;0.286019;0.577349;,-0.286019;-0.764733;0.577349;,-0.286019;-0.764733;0.577349;,-0.764733;0.286019;0.577349;,0.286019;0.764733;0.577349;,0.764733;-0.286019;0.577349;,0.764733;-0.286019;0.577349;,0.286019;0.764733;0.577349;,0.286019;0.764733;-0.577349;,0.764733;-0.286019;-0.577349;,0.764733;-0.286019;-0.577349;,0.286019;0.764733;-0.577349;,-0.764733;0.286019;-0.577349;,-0.286019;-0.764733;-0.577349;,-0.286019;-0.764733;0.577349;,0.764733;-0.286019;0.577349;,0.764733;-0.286019;-0.577349;,-0.286019;-0.764733;-0.577349;,0.286019;0.764733;0.577349;,-0.764733;0.286019;0.577349;,-0.764733;0.286019;-0.577349;,0.286019;0.764733;-0.577349;,-0.286019;-0.764733;-0.577349;,-0.764733;0.286019;-0.577349;,-0.764733;0.286019;0.577349;,-0.286019;-0.764733;0.577349;,-0.286019;-0.764733;0.577349;,-0.764733;0.286019;0.577349;,0.286019;0.764733;0.577349;,0.764733;-0.286019;0.577349;,0.764733;-0.286019;0.577349;,0.286019;0.764733;0.577349;,0.286019;0.764733;-0.577349;,0.764733;-0.286019;-0.577349;,0.764733;-0.286019;-0.577349;,0.286019;0.764733;-0.577349;,-0.764733;0.286019;-0.577349;,-0.286019;-0.764733;-0.577349;,-0.286019;-0.764733;0.577349;,0.764733;-0.286019;0.577349;,0.764733;-0.286019;-0.577349;,-0.286019;-0.764733;-0.577349;,0.286019;0.764733;0.577349;,-0.764733;0.286019;0.577349;,-0.764733;0.286019;-0.577349;,0.286019;0.764733;-0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;-0.577349;,0.577349;0.577349;0.577349;,-0.577349;0.577349;0.577349;,-0.577349;0.577349;-0.577349;,0.577349;0.577349;-0.577349;,0;-0.707083;0.707083;,0;0.707083;0.707083;,0.599902;0.565722;0.565722;,0.599902;-0.565722;0.565722;,0.599902;-0.565722;0.565722;,0.599902;0.565722;0.565722;,0.599902;0.565722;-0.565722;,0.599902;-0.565722;-0.565722;,0;-0.707083;0.707083;,0.599902;-0.565722;0.565722;,0.599902;-0.565722;-0.565722;,0;-0.707083;-0.707083;,0.599902;0.565722;0.565722;,0;0.707083;0.707083;,0;0.707083;-0.707083;,0.599902;0.565722;-0.565722;,0.599902;-0.565722;-0.565722;,0.599902;0.565722;-0.565722;,0;0.707083;-0.707083;,0;-0.707083;-0.707083;,0;-0.707083;0.707083;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,0;0.707083;0.707083;,-0.577349;-0.577349;0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;0.577349;0.577349;,0;-0.707083;0.707083;,0;-0.707083;-0.707083;,-0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;0.577349;,0.577349;-0.577349;-0.577349;,0.577349;-0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;0.577349;-0.577349;,0.577349;-0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,0.577349;0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;0.577349;0.577349;,-0.577349;-0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,-0.577349;0.577349;-0.577349;,0.577349;0.577349;-0.577349;,0.577349;0.577349;0.577349;,-0.577349;0.577349;0.577349;,-0.577349;0.577349;-0.577349;,0;0.707083;-0.707083;,0;0.707083;0.707083;,-0.577349;-0.577349;-0.577349;,0;-0.707083;-0.707083;,0;0.707083;-0.707083;,-0.577349;0.577349;-0.577349;,0.286019;-0.764733;-0.577349;,0.286019;-0.764733;0.577349;,0.764733;0.286019;0.577349;,0.764733;0.286019;-0.577349;,0.286019;-0.764733;0.577349;,-0.764733;-0.286019;0.577349;,-0.286019;0.764733;0.577349;,0.764733;0.286019;0.577349;,-0.764733;-0.286019;0.577349;,-0.764733;-0.286019;-0.577349;,-0.286019;0.764733;-0.577349;,-0.286019;0.764733;0.577349;,-0.764733;-0.286019;-0.577349;,0.286019;-0.764733;-0.577349;,0.764733;0.286019;-0.577349;,-0.286019;0.764733;-0.577349;,0.286019;-0.764733;0.577349;,0.286019;-0.764733;-0.577349;,-0.764733;-0.286019;-0.577349;,-0.764733;-0.286019;0.577349;,-0.286019;0.764733;0.577349;,-0.286019;0.764733;-0.577349;,0.764733;0.286019;-0.577349;,0.764733;0.286019;0.577349;,0.286019;-0.764733;-0.577349;,0.286019;-0.764733;0.577349;,0.764733;0.286019;0.577349;,0.764733;0.286019;-0.577349;,0.286019;-0.764733;0.577349;,-0.764733;-0.286019;0.577349;,-0.286019;0.764733;0.577349;,0.764733;0.286019;0.577349;,-0.764733;-0.286019;0.577349;,-0.764733;-0.286019;-0.577349;,-0.286019;0.764733;-0.577349;,-0.286019;0.764733;0.577349;,-0.764733;-0.286019;-0.577349;,0.286019;-0.764733;-0.577349;,0.764733;0.286019;-0.577349;,-0.286019;0.764733;-0.577349;,0.286019;-0.764733;0.577349;,0.286019;-0.764733;-0.577349;,-0.764733;-0.286019;-0.577349;,-0.764733;-0.286019;0.577349;,-0.286019;0.764733;0.577349;,-0.286019;0.764733;-0.577349;,0.764733;0.286019;-0.577349;,0.764733;0.286019;0.577349;,0.577349;-0.577349;-0.577349;,0.577349;-0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;0.577349;-0.577349;,0.577349;-0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,0.577349;0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;0.577349;0.577349;,-0.577349;-0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,-0.577349;0.577349;-0.577349;,0.577349;0.577349;-0.577349;,0.577349;0.577349;0.577349;,0;-0.707083;0.707083;,-0.599902;-0.565722;0.565722;,-0.599872;0.565722;0.565722;,0;0.707083;0.707083;,-0.599902;-0.565722;0.565722;,-0.599902;-0.565722;-0.565722;,-0.599872;0.565722;-0.565722;,-0.599872;0.565722;0.565722;,0;-0.707083;0.707083;,0;-0.707083;-0.707083;,-0.599902;-0.565722;-0.565722;,-0.599902;-0.565722;0.565722;,-0.599872;0.565722;0.565722;,-0.599872;0.565722;-0.565722;,0;0.707083;-0.707083;,0;0.707083;0.707083;,-0.599902;-0.565722;-0.565722;,0;-0.707083;-0.707083;,0;0.707083;-0.707083;,-0.599872;0.565722;-0.565722;;68;4;3,2,1,0;,4;7,6,5,4;,4;11,10,9,8;,4;15,14,13,12;,4;19,18,17,16;,4;23,22,21,20;,4;27,26,25,24;,4;31,30,29,28;,4;35,34,33,32;,4;39,38,37,36;,4;43,42,41,40;,4;47,46,45,44;,4;51,50,49,48;,4;55,54,53,52;,4;59,58,57,56;,4;63,62,61,60;,4;67,66,65,64;,4;71,70,69,68;,4;75,74,73,72;,4;79,78,77,76;,4;83,82,81,80;,4;87,86,85,84;,4;91,90,89,88;,4;95,94,93,92;,4;99,98,97,96;,4;103,102,101,100;,4;107,106,105,104;,4;111,110,109,108;,4;115,114,113,112;,4;119,118,117,116;,4;123,122,121,120;,4;127,126,125,124;,4;131,130,129,128;,4;135,134,133,132;,4;139,138,137,136;,4;143,142,141,140;,4;147,146,145,144;,4;151,150,149,148;,4;155,154,153,152;,4;159,158,157,156;,4;163,162,161,160;,4;167,166,165,164;,4;171,170,169,168;,4;175,174,173,172;,4;179,178,177,176;,4;183,182,181,180;,4;187,186,185,184;,4;191,190,189,188;,4;195,194,193,192;,4;199,198,197,196;,4;203,202,201,200;,4;207,206,205,204;,4;211,210,209,208;,4;215,214,213,212;,4;219,218,217,216;,4;223,222,221,220;,4;227,226,225,224;,4;231,230,229,228;,4;235,234,233,232;,4;239,238,237,236;,4;243,242,241,240;,4;247,246,245,244;,4;251,250,249,248;,4;255,254,253,252;,4;259,258,257,256;,4;263,262,261,260;,4;267,266,265,264;,4;271,270,269,268;;}MeshTextureCoords{272;0.849264;0.899246;,0.849264;0.931916;,0.861547;0.931916;,0.861547;0.899246;,0.916988;0.931916;,0.916988;0.899246;,0.9054;0.899246;,0.9054;0.931916;,0.84857;0.844707;,0.84857;0.83254;,0.836981;0.83254;,0.836981;0.844707;,0.927004;0.903587;,0.927004;0.931916;,0.937019;0.931916;,0.937019;0.903587;,0.937019;0.903587;,0.937019;0.931916;,0.946887;0.931916;,0.946887;0.903587;,0.888533;0.856954;,0.888533;0.828625;,0.878517;0.828625;,0.878517;0.856954;,0.939292;0.870917;,0.939292;0.899246;,0.949159;0.899246;,0.949159;0.870917;,0.946887;0.91117;,0.956719;0.91117;,0.956719;0.901213;,0.946887;0.901213;,0.865118;0.813135;,0.855286;0.813135;,0.855286;0.823092;,0.865118;0.823092;,0.866874;0.847426;,0.866874;0.835259;,0.855286;0.835259;,0.855286;0.847426;,0.598002;0.973516;,0.598002;0.206739;,0.309722;0.206739;,0.309722;0.973516;,0.909393;0.822135;,0.909393;0.841014;,0.915938;0.841014;,0.915938;0.822135;,0.951962;0.931916;,0.951962;0.91117;,0.946887;0.91117;,0.946887;0.931916;,0.948762;0.841801;,0.948762;0.822921;,0.942217;0.822921;,0.942217;0.841801;,0.893608;0.838075;,0.893608;0.817329;,0.888533;0.817329;,0.888533;0.838075;,0.900724;0.909292;,0.90515;0.909292;,0.90515;0.902786;,0.900724;0.902786;,0.953585;0.871994;,0.949159;0.871994;,0.949159;0.8785;,0.953585;0.8785;,0.84857;0.837995;,0.84857;0.856874;,0.855114;0.856874;,0.855114;0.837995;,0.902881;0.83746;,0.902881;0.816714;,0.897805;0.816714;,0.897805;0.83746;,0.942217;0.841801;,0.942217;0.822921;,0.935673;0.822921;,0.935673;0.841801;,0.949159;0.8785;,0.949159;0.899246;,0.954235;0.899246;,0.954235;0.8785;,0.919226;0.822135;,0.923651;0.822135;,0.923651;0.815629;,0.919226;0.815629;,0.928077;0.815629;,0.923651;0.815629;,0.923651;0.822135;,0.928077;0.822135;,0.865301;0.847426;,0.865301;0.876542;,0.875317;0.876542;,0.875317;0.847426;,0.909393;0.841014;,0.909393;0.87013;,0.919261;0.87013;,0.919261;0.841014;,0.855286;0.847426;,0.855286;0.876542;,0.865301;0.876542;,0.865301;0.847426;,0.919261;0.841014;,0.919261;0.87013;,0.929128;0.87013;,0.929128;0.841014;,0.878517;0.828625;,0.888349;0.828625;,0.88835;0.818668;,0.878517;0.818668;,0.836981;0.83254;,0.846814;0.83254;,0.846814;0.822583;,0.836981;0.822583;,0.857749;0.887894;,0.836981;0.887894;,0.837473;0.899246;,0.857257;0.899246;,0.855286;0.876542;,0.855286;0.856874;,0.836981;0.856874;,0.836981;0.876542;,0.897805;0.887893;,0.897313;0.876622;,0.879009;0.876622;,0.878517;0.887893;,0.886604;0.909292;,0.886112;0.920645;,0.9054;0.920645;,0.904908;0.909292;,0.977665;0.442421;,0.977665;0.131438;,0.799225;0.123708;,0.799225;0.450151;,0.849264;0.899246;,0.836981;0.899246;,0.836981;0.931916;,0.849264;0.931916;,0.909393;0.866576;,0.897805;0.866576;,0.897805;0.899246;,0.909393;0.899246;,0.84857;0.844707;,0.836981;0.844707;,0.836981;0.856874;,0.84857;0.856874;,0.929276;0.899246;,0.939292;0.899246;,0.939292;0.870917;,0.929276;0.870917;,0.876741;0.819096;,0.866874;0.819096;,0.866874;0.847426;,0.876741;0.847426;,0.939144;0.841801;,0.929128;0.841801;,0.929128;0.87013;,0.939144;0.87013;,0.949011;0.841801;,0.939144;0.841801;,0.939144;0.87013;,0.949011;0.87013;,0.836981;0.812626;,0.836981;0.822583;,0.846814;0.822583;,0.846814;0.812626;,0.909393;0.812178;,0.909393;0.822135;,0.919226;0.822135;,0.919226;0.812178;,0.866874;0.823092;,0.855286;0.823092;,0.855286;0.835259;,0.866874;0.835259;,0.021442;0.973516;,0.309722;0.973516;,0.309722;0.206739;,0.021442;0.206739;,0.916039;0.841014;,0.922583;0.841014;,0.922583;0.822135;,0.916039;0.822135;,0.907956;0.816714;,0.902881;0.816714;,0.902881;0.83746;,0.907956;0.83746;,0.929128;0.822135;,0.922583;0.822135;,0.922583;0.841014;,0.929128;0.841014;,0.853645;0.817249;,0.84857;0.817249;,0.84857;0.837995;,0.853645;0.837995;,0.900724;0.909292;,0.900724;0.902786;,0.895944;0.902786;,0.895944;0.909292;,0.93896;0.816415;,0.93896;0.822921;,0.94374;0.822921;,0.94374;0.816415;,0.935673;0.822921;,0.929128;0.822921;,0.929128;0.841801;,0.935673;0.841801;,0.954087;0.849384;,0.949011;0.849384;,0.949011;0.87013;,0.954087;0.87013;,0.895077;0.838075;,0.888533;0.838075;,0.888533;0.856954;,0.895077;0.856954;,0.948762;0.841801;,0.953838;0.841801;,0.953838;0.821055;,0.948762;0.821055;,0.94374;0.816415;,0.94374;0.822921;,0.94852;0.822921;,0.94852;0.816415;,0.949011;0.842878;,0.949011;0.849384;,0.953791;0.849384;,0.953791;0.842878;,0.919409;0.87013;,0.909393;0.87013;,0.909393;0.899246;,0.919409;0.899246;,0.897805;0.866576;,0.907672;0.866576;,0.907672;0.83746;,0.897805;0.83746;,0.927004;0.9028;,0.916988;0.9028;,0.916988;0.931916;,0.927004;0.931916;,0.929276;0.87013;,0.919409;0.87013;,0.919409;0.899246;,0.929276;0.899246;,0.93896;0.822921;,0.93896;0.812965;,0.929128;0.812965;,0.929128;0.822921;,0.886112;0.899336;,0.886112;0.909292;,0.895944;0.909292;,0.895944;0.899336;,0.857749;0.887894;,0.857257;0.876542;,0.837473;0.876542;,0.836981;0.887894;,0.896821;0.856954;,0.878517;0.856954;,0.878517;0.876622;,0.896821;0.876622;,0.897805;0.887893;,0.878517;0.887893;,0.879009;0.899246;,0.897313;0.899246;,0.886604;0.931916;,0.904908;0.931916;,0.9054;0.920645;,0.886112;0.920645;,0.620785;0.44242;,0.799225;0.450151;,0.799225;0.123708;,0.620785;0.131438;;}XSkinMeshHeader{3;9;10;}SkinWeights{"Armature_arm_l";24;44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,67,66;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;-0.047733,0.901521,0.430095,0,-0.097683,0.424309,-0.900233,0,-0.994073,-0.084983,0.06781,0,0.374873,-2.006904,2.980378,1;;}SkinWeights{"Armature_elbow_r";24;216,219,218,213,212,215,214,209,224,208,227,211,226,210,206,221,207,220,204,223,205,222,225,217;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;0.102316,0.92166,-0.374266,0,-0.090709,-0.366028,-0.926173,0,-0.990608,0.128712,0.046152,0,0.402018,1.853661,2.350172,1;;}SkinWeights{"Armature_arm_r";24;186,187,184,185,182,183,180,194,195,203,202,192,193,201,200,199,190,198,191,197,188,196,189,181;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;-0.047733,0.901521,-0.430095,0,-0.05233,-0.432251,-0.900234,0,-0.997489,-0.020464,0.067809,0,0.160852,2.035269,2.980378,1;;}SkinWeights{"Armature_knee_l";24;105,99,114,106,98,115,107,101,93,108,100,92,109,103,95,110,102,94,111,97,112,104,113,96;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;1,0,0,0,0,0.054357,-0.998522,0,0,0.998501,0.054355,0,-0.246294,-0.008592,1.301673,1;;}SkinWeights{"Armature_Bone_007";40;132,133,134,135,124,125,126,252,253,254,255,121,122,264,265,123,267,268,269,270,116,256,258,259,260,261,262,263,271,266,120,119,117,128,129,127,130,118,131,257;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;1,0,0,0,0,0,1,0,0,-1,0,0,0,0,-3.793003,1;;}SkinWeights{"Armature_elbow_l";24;88,80,72,91,83,75,90,82,74,70,85,77,71,84,76,68,87,79,69,86,78,89,81,73;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;0.102316,0.92166,0.374266,0,-0.008222,0.377011,-0.926173,0,-0.994719,0.091686,0.046152,0,-0.014321,-1.896701,2.350171,1;;}SkinWeights{"Armature_knee_r";24;249,235,250,234,251,229,244,228,245,231,246,230,247,240,241,242,243,237,236,239,238,233,248,232;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;1,0,0,0,0,0.054357,-0.998522,0,0,0.998501,0.054355,0,0.246294,-0.008592,1.301673,1;;}SkinWeights{"Armature_leg_l";38;0,3,4,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,40,43,136,145,177,144;0.055873,0.852304,0.852304,0.82998,0.055873,0.852304,0.82998,0.054606,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0.82998,0.054606,0.055873,0.054606,0.054606,0.055873;1,0,0,0,0,-0.056452,-0.998405,0,0,0.998385,-0.056452,0,-0.246294,0.135476,2.396023,1;;}SkinWeights{"Armature_leg_r";38;0,170,169,11,168,151,150,149,148,147,146,176,145,177,144,159,158,157,156,155,154,153,167,136,166,137,165,164,163,140,162,141,161,43,160,152,8,171;0.055873,1,1,0.054606,1,1,1,1,1,0.852304,0.82998,0.82998,0.054606,0.054606,0.055873,1,1,1,1,1,1,1,1,0.055873,1,0.852304,1,1,1,0.852304,1,0.82998,1,0.054606,1,1,0.055873,1;1,0,0,0,0,-0.056452,-0.998405,0,0,0.998385,-0.056452,0,0.246294,0.135476,2.396023,1;;}SkinWeights{"Armature_body";40;0,1,2,3,4,5,6,7,8,9,10,11,36,37,38,39,40,41,42,43,136,137,138,139,140,147,141,146,142,145,143,144,179,174,178,173,177,172,176,175;0.888255,1,1,0.147696,0.147696,1,1,0.17002,0.888255,0.147696,0.17002,0.890788,1,1,1,1,0.17002,1,1,0.890788,0.888255,0.147696,1,1,0.147696,0.147696,0.17002,0.17002,1,0.890788,1,0.888255,1,1,1,1,0.890788,1,0.17002,1;1,0,0,0,0,0,1,0,0,-1,0,0,0,0,-2.571201,1;;}}}}} \ No newline at end of file diff --git a/games/devtest/mods/testentities/visuals.lua b/games/devtest/mods/testentities/visuals.lua index dfbf655ea..26b538e7f 100644 --- a/games/devtest/mods/testentities/visuals.lua +++ b/games/devtest/mods/testentities/visuals.lua @@ -102,6 +102,19 @@ core.register_entity("testentities:lava_flan", { end, }) +core.register_entity("testentities:cool_guy", { + initial_properties = { + visual = "mesh", + mesh = "testentities_cool_guy.x", + textures = { + "testentities_cool_guy.png" + }, + }, + on_activate = function(self) + self.object:set_animation({x = 0, y = 29}, 30, 0, true) + end, +}) + -- Advanced visual tests -- An entity for testing animated and yaw-modulated sprites diff --git a/irr/include/IAnimatedMesh.h b/irr/include/IAnimatedMesh.h index 62568bf6b..0ef0971bd 100644 --- a/irr/include/IAnimatedMesh.h +++ b/irr/include/IAnimatedMesh.h @@ -13,8 +13,8 @@ namespace scene //! Interface for an animated mesh. /** There are already simple implementations of this interface available so you don't have to implement this interface on your own if you need to: -You might want to use irr::scene::SAnimatedMesh, irr::scene::SMesh, -irr::scene::SMeshBuffer etc. */ +You might want to use irr::scene::SMesh, irr::scene::SMeshBuffer etc. +*/ class IAnimatedMesh : public IMesh { public: @@ -34,22 +34,8 @@ public: scene node the mesh is instantiated in.*/ virtual void setAnimationSpeed(f32 fps) = 0; - //! Returns the IMesh interface for a frame. - /** \param frame: Frame number, >= 0, <= getMaxFrameNumber() - Linear interpolation is used if this is between two frames. - \return Returns the animated mesh for the given frame */ - virtual IMesh *getMesh(f32 frame) = 0; - - //! Returns the type of the animated mesh. - /** In most cases it is not necessary to use this method. - This is useful for making a safe downcast. For example, - if getMeshType() returns EAMT_MD2 it's safe to cast the - IAnimatedMesh to IAnimatedMeshMD2. - \returns Type of the mesh. */ - E_ANIMATED_MESH_TYPE getMeshType() const override - { - return EAMT_UNKNOWN; - } + //! Returns the type of the animated mesh. Useful for safe downcasts. + E_ANIMATED_MESH_TYPE getMeshType() const = 0; }; } // end namespace scene diff --git a/irr/include/IAnimatedMeshSceneNode.h b/irr/include/IAnimatedMeshSceneNode.h index 8f9f6d661..2df8da917 100644 --- a/irr/include/IAnimatedMeshSceneNode.h +++ b/irr/include/IAnimatedMeshSceneNode.h @@ -12,35 +12,8 @@ namespace irr { namespace scene { -enum E_JOINT_UPDATE_ON_RENDER -{ - //! do nothing - EJUOR_NONE = 0, - - //! get joints positions from the mesh (for attached nodes, etc) - EJUOR_READ, - - //! control joint positions in the mesh (eg. ragdolls, or set the animation from animateJoints() ) - EJUOR_CONTROL -}; - class IAnimatedMeshSceneNode; -//! Callback interface for catching events of ended animations. -/** Implement this interface and use -IAnimatedMeshSceneNode::setAnimationEndCallback to be able to -be notified if an animation playback has ended. -**/ -class IAnimationEndCallBack : public virtual IReferenceCounted -{ -public: - //! Will be called when the animation playback has ended. - /** See IAnimatedMeshSceneNode::setAnimationEndCallback for - more information. - \param node: Node of which the animation has ended. */ - virtual void OnAnimationEnd(IAnimatedMeshSceneNode *node) = 0; -}; - //! Scene node capable of displaying an animated mesh. class IAnimatedMeshSceneNode : public ISceneNode { @@ -120,11 +93,10 @@ public: /** When true the animations are played looped */ virtual bool getLoopMode() const = 0; - //! Sets a callback interface which will be called if an animation playback has ended. - /** Set this to 0 to disable the callback again. - Please note that this will only be called when in non looped - mode, see IAnimatedMeshSceneNode::setLoopMode(). */ - virtual void setAnimationEndCallback(IAnimationEndCallBack *callback = 0) = 0; + //! Will be called right after the joints have been animated, + //! but before the transforms have been propagated recursively to children. + virtual void setOnAnimateCallback( + const std::function &cb) = 0; //! Sets if the scene node should not copy the materials of the mesh but use them in a read only style. /** In this way it is possible to change the materials a mesh @@ -139,20 +111,15 @@ public: virtual void setMesh(IAnimatedMesh *mesh) = 0; //! Returns the current mesh - virtual IAnimatedMesh *getMesh(void) = 0; - - //! Set how the joints should be updated on render - virtual void setJointMode(E_JOINT_UPDATE_ON_RENDER mode) = 0; + virtual IAnimatedMesh *getMesh() = 0; //! Sets the transition time in seconds - /** Note: This needs to enable joints, and setJointmode set to - EJUOR_CONTROL. You must call animateJoints(), or the mesh will - not animate. */ + /** Note: You must call animateJoints(), or the mesh will not animate. */ virtual void setTransitionTime(f32 Time) = 0; //! animates the joints in the mesh based on the current frame. /** Also takes in to account transitions. */ - virtual void animateJoints(bool CalculateAbsolutePositions = true) = 0; + virtual void animateJoints() = 0; //! render mesh ignoring its transformation. /** Culling is unaffected. */ diff --git a/irr/include/IBoneSceneNode.h b/irr/include/IBoneSceneNode.h index eef55f6e0..d169b8d30 100644 --- a/irr/include/IBoneSceneNode.h +++ b/irr/include/IBoneSceneNode.h @@ -11,85 +11,41 @@ namespace irr namespace scene { -//! Enumeration for different bone animation modes -enum E_BONE_ANIMATION_MODE -{ - //! The bone is usually animated, unless it's parent is not animated - EBAM_AUTOMATIC = 0, - - //! The bone is animated by the skin, if it's parent is not animated then animation will resume from this bone onward - EBAM_ANIMATED, - - //! The bone is not animated by the skin - EBAM_UNANIMATED, - - //! Not an animation mode, just here to count the available modes - EBAM_COUNT - -}; - -enum E_BONE_SKINNING_SPACE -{ - //! local skinning, standard - EBSS_LOCAL = 0, - - //! global skinning - EBSS_GLOBAL, - - EBSS_COUNT -}; - -//! Names for bone animation modes -const c8 *const BoneAnimationModeNames[] = { - "automatic", - "animated", - "unanimated", - 0, - }; - //! Interface for bones used for skeletal animation. /** Used with SkinnedMesh and IAnimatedMeshSceneNode. */ class IBoneSceneNode : public ISceneNode { public: - IBoneSceneNode(ISceneNode *parent, ISceneManager *mgr, s32 id = -1) : - ISceneNode(parent, mgr, id), positionHint(-1), scaleHint(-1), rotationHint(-1) {} + IBoneSceneNode(ISceneNode *parent, ISceneManager *mgr, + s32 id = -1, u32 boneIndex = 0, + const std::optional &boneName = std::nullopt) + : + ISceneNode(parent, mgr, id), + BoneIndex(boneIndex) + { + setName(boneName); + } - //! Get the index of the bone - virtual u32 getBoneIndex() const = 0; + //! Returns the index of the bone + u32 getBoneIndex() const + { + return BoneIndex; + } - //! Sets the animation mode of the bone. - /** \return True if successful. (Unused) */ - virtual bool setAnimationMode(E_BONE_ANIMATION_MODE mode) = 0; + //! returns the axis aligned bounding box of this node + const core::aabbox3d &getBoundingBox() const override + { + return Box; + } - //! Gets the current animation mode of the bone - virtual E_BONE_ANIMATION_MODE getAnimationMode() const = 0; + const u32 BoneIndex; - //! Get the axis aligned bounding box of this node - const core::aabbox3d &getBoundingBox() const override = 0; - - //! Returns the relative transformation of the scene node. - // virtual core::matrix4 getRelativeTransformation() const = 0; - - //! The animation method. - void OnAnimate(u32 timeMs) override = 0; + // Bogus box; bone scene nodes are not rendered anyways. + static constexpr core::aabbox3d Box = {{0, 0, 0}}; //! The render method. /** Does nothing as bones are not visible. */ void render() override {} - - //! How the relative transformation of the bone is used - virtual void setSkinningSpace(E_BONE_SKINNING_SPACE space) = 0; - - //! How the relative transformation of the bone is used - virtual E_BONE_SKINNING_SPACE getSkinningSpace() const = 0; - - //! Updates the absolute position based on the relative and the parents position - virtual void updateAbsolutePositionOfAllChildren() = 0; - - s32 positionHint; - s32 scaleHint; - s32 rotationHint; }; } // end namespace scene diff --git a/irr/include/IMesh.h b/irr/include/IMesh.h index 8ee180d5d..e8656e868 100644 --- a/irr/include/IMesh.h +++ b/irr/include/IMesh.h @@ -20,38 +20,6 @@ enum E_ANIMATED_MESH_TYPE //! Unknown animated mesh type. EAMT_UNKNOWN = 0, - //! Quake 2 MD2 model file - EAMT_MD2, - - //! Quake 3 MD3 model file - EAMT_MD3, - - //! Maya .obj static model - EAMT_OBJ, - - //! Quake 3 .bsp static Map - EAMT_BSP, - - //! 3D Studio .3ds file - EAMT_3DS, - - //! My3D Mesh, the file format by Zhuck Dimitry - EAMT_MY3D, - - //! Pulsar LMTools .lmts file. This Irrlicht loader was written by Jonas Petersen - EAMT_LMTS, - - //! Cartography Shop .csm file. This loader was created by Saurav Mohapatra. - EAMT_CSM, - - //! .oct file for Paul Nette's FSRad or from Murphy McCauley's Blender .oct exporter. - /** The oct file format contains 3D geometry and lightmaps and - can be loaded directly by Irrlicht */ - EAMT_OCT, - - //! Halflife MDL model file - EAMT_MDL_HALFLIFE, - //! generic skinned mesh EAMT_SKINNED, @@ -119,9 +87,7 @@ public: virtual void setDirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) = 0; //! Returns the type of the meshes. - /** This is useful for making a safe downcast. For example, - if getMeshType() returns EAMT_MD2 it's safe to cast the - IMesh to IAnimatedMeshMD2. + /** This is useful for making a safe downcast. Note: It's no longer just about animated meshes, that name has just historical reasons. \returns Type of the mesh */ virtual E_ANIMATED_MESH_TYPE getMeshType() const diff --git a/irr/include/IMeshManipulator.h b/irr/include/IMeshManipulator.h index c9d989cae..0312a38c8 100644 --- a/irr/include/IMeshManipulator.h +++ b/irr/include/IMeshManipulator.h @@ -66,26 +66,6 @@ public: IReferenceCounted::drop() for more information. */ virtual SMesh *createMeshCopy(IMesh *mesh) const = 0; - //! Get amount of polygons in mesh. - /** \param mesh Input mesh - \return Number of polygons in mesh. */ - virtual s32 getPolyCount(IMesh *mesh) const = 0; - - //! Get amount of polygons in mesh. - /** \param mesh Input mesh - \return Number of polygons in mesh. */ - virtual s32 getPolyCount(IAnimatedMesh *mesh) const = 0; - - //! Create a new AnimatedMesh and adds the mesh to it - /** \param mesh Input mesh - \param type The type of the animated mesh to create. - \return Newly created animated mesh with mesh as its only - content. When you don't need the animated mesh anymore, you - should call IAnimatedMesh::drop(). See - IReferenceCounted::drop() for more information. */ - virtual IAnimatedMesh *createAnimatedMesh(IMesh *mesh, - scene::E_ANIMATED_MESH_TYPE type = scene::EAMT_UNKNOWN) const = 0; - //! Apply a manipulator on the Meshbuffer /** \param func A functor defining the mesh manipulation. \param buffer The Meshbuffer to apply the manipulator to. diff --git a/irr/include/IMeshSceneNode.h b/irr/include/IMeshSceneNode.h index 1fb005405..3065f4c07 100644 --- a/irr/include/IMeshSceneNode.h +++ b/irr/include/IMeshSceneNode.h @@ -32,7 +32,7 @@ public: //! Get the currently defined mesh for display. /** \return Pointer to mesh which is displayed by this node. */ - virtual IMesh *getMesh(void) = 0; + virtual IMesh *getMesh() = 0; //! Sets if the scene node should not copy the materials of the mesh but use them directly. /** In this way it is possible to change the materials of a mesh diff --git a/irr/include/ISceneNode.h b/irr/include/ISceneNode.h index f91fd6499..573715861 100644 --- a/irr/include/ISceneNode.h +++ b/irr/include/ISceneNode.h @@ -94,16 +94,12 @@ public: \param timeMs Current time in milliseconds. */ virtual void OnAnimate(u32 timeMs) { - if (IsVisible) { - // update absolute position - updateAbsolutePosition(); + if (!IsVisible && Children.empty()) + return; - // perform the post render process on all children - - ISceneNodeList::iterator it = Children.begin(); - for (; it != Children.end(); ++it) - (*it)->OnAnimate(timeMs); - } + updateAbsolutePosition(); + for (auto *child : Children) + child->OnAnimate(timeMs); } //! Renders the node. diff --git a/irr/include/SAnimatedMesh.h b/irr/include/SAnimatedMesh.h deleted file mode 100644 index dcc65410f..000000000 --- a/irr/include/SAnimatedMesh.h +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (C) 2002-2012 Nikolaus Gebhardt -// This file is part of the "Irrlicht Engine". -// For conditions of distribution and use, see copyright notice in irrlicht.h - -#pragma once - -#include -#include "IAnimatedMesh.h" -#include "IMesh.h" -#include "aabbox3d.h" - -namespace irr -{ -namespace scene -{ - -//! Simple implementation of the IAnimatedMesh interface. -struct SAnimatedMesh final : public IAnimatedMesh -{ - //! constructor - SAnimatedMesh(scene::IMesh *mesh = 0, scene::E_ANIMATED_MESH_TYPE type = scene::EAMT_UNKNOWN) : - IAnimatedMesh(), FramesPerSecond(25.f), Type(type) - { - addMesh(mesh); - recalculateBoundingBox(); - } - - //! destructor - virtual ~SAnimatedMesh() - { - // drop meshes - for (auto *mesh : Meshes) - mesh->drop(); - } - - f32 getMaxFrameNumber() const override - { - return static_cast(Meshes.size() - 1); - } - - //! Gets the default animation speed of the animated mesh. - /** \return Amount of frames per second. If the amount is 0, it is a static, non animated mesh. */ - f32 getAnimationSpeed() const override - { - return FramesPerSecond; - } - - //! Gets the frame count of the animated mesh. - /** \param fps Frames per second to play the animation with. If the amount is 0, it is not animated. - The actual speed is set in the scene node the mesh is instantiated in.*/ - void setAnimationSpeed(f32 fps) override - { - FramesPerSecond = fps; - } - - //! Returns the IMesh interface for a frame. - /** \param frame: Frame number as zero based index. - \return The animated mesh based for the given frame */ - IMesh *getMesh(f32 frame) override - { - if (Meshes.empty()) - return nullptr; - - return Meshes[static_cast(frame)]; - } - - //! adds a Mesh - void addMesh(IMesh *mesh) - { - if (mesh) { - mesh->grab(); - Meshes.push_back(mesh); - } - } - - //! Returns an axis aligned bounding box of the mesh. - /** \return A bounding box of this mesh is returned. */ - const core::aabbox3d &getBoundingBox() const override - { - return Box; - } - - //! set user axis aligned bounding box - void setBoundingBox(const core::aabbox3df &box) override - { - Box = box; - } - - //! Recalculates the bounding box. - void recalculateBoundingBox() - { - Box.reset(0, 0, 0); - - if (Meshes.empty()) - return; - - Box = Meshes[0]->getBoundingBox(); - - for (u32 i = 1; i < Meshes.size(); ++i) - Box.addInternalBox(Meshes[i]->getBoundingBox()); - } - - //! Returns the type of the animated mesh. - E_ANIMATED_MESH_TYPE getMeshType() const override - { - return Type; - } - - //! returns amount of mesh buffers. - u32 getMeshBufferCount() const override - { - if (Meshes.empty()) - return 0; - - return Meshes[0]->getMeshBufferCount(); - } - - //! returns pointer to a mesh buffer - IMeshBuffer *getMeshBuffer(u32 nr) const override - { - if (Meshes.empty()) - return 0; - - return Meshes[0]->getMeshBuffer(nr); - } - - //! Returns pointer to a mesh buffer which fits a material - /** \param material: material to search for - \return Returns the pointer to the mesh buffer or - NULL if there is no such mesh buffer. */ - IMeshBuffer *getMeshBuffer(const video::SMaterial &material) const override - { - if (Meshes.empty()) - return 0; - - return Meshes[0]->getMeshBuffer(material); - } - - //! set the hardware mapping hint, for driver - void setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint, E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) override - { - for (u32 i = 0; i < Meshes.size(); ++i) - Meshes[i]->setHardwareMappingHint(newMappingHint, buffer); - } - - //! flags the meshbuffer as changed, reloads hardware buffers - void setDirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) override - { - for (u32 i = 0; i < Meshes.size(); ++i) - Meshes[i]->setDirty(buffer); - } - - //! All meshes defining the animated mesh - std::vector Meshes; - - //! The bounding box of this mesh - core::aabbox3d Box{{0.0f, 0.0f, 0.0f}}; - - //! Default animation speed of this mesh. - f32 FramesPerSecond; - - //! The type of the mesh. - E_ANIMATED_MESH_TYPE Type; -}; - -} // end namespace scene -} // end namespace irr diff --git a/irr/include/SMesh.h b/irr/include/SMesh.h index 5e76fafc5..b22bb7749 100644 --- a/irr/include/SMesh.h +++ b/irr/include/SMesh.h @@ -5,7 +5,7 @@ #pragma once #include -#include "IMesh.h" +#include "IAnimatedMesh.h" #include "IMeshBuffer.h" #include "aabbox3d.h" @@ -14,7 +14,7 @@ namespace irr namespace scene { //! Simple implementation of the IMesh interface. -struct SMesh final : public IMesh +struct SMesh final : public IAnimatedMesh { //! constructor SMesh() {} @@ -134,6 +134,15 @@ struct SMesh final : public IMesh //! The bounding box of this mesh core::aabbox3d BoundingBox{{0, 0, 0}}; + + // Implement animated mesh interface as a static mesh. + // Slightly hacky: Eventually should be consolidated with SSkinnedMesh, + // with all the animation-related parts behind an optional. + + virtual f32 getMaxFrameNumber() const override { return 0.0f; } + virtual f32 getAnimationSpeed() const override { return 0.0f; } + virtual void setAnimationSpeed(f32 fps) override {} + E_ANIMATED_MESH_TYPE getMeshType() const override { return EAMT_STATIC; } }; } // end namespace scene diff --git a/irr/include/SkinnedMesh.h b/irr/include/SkinnedMesh.h index a527db76e..aa718c882 100644 --- a/irr/include/SkinnedMesh.h +++ b/irr/include/SkinnedMesh.h @@ -8,11 +8,18 @@ #include "ISceneManager.h" #include "SMeshBuffer.h" #include "SSkinMeshBuffer.h" +#include "aabbox3d.h" +#include "irrMath.h" +#include "irrTypes.h" +#include "matrix4.h" #include "quaternion.h" #include "vector3d.h" +#include "Transform.h" #include #include +#include +#include namespace irr { @@ -37,9 +44,8 @@ public: //! constructor SkinnedMesh(SourceFormat src_format) : EndFrame(0.f), FramesPerSecond(25.f), - LastAnimatedFrame(-1), SkinnedLastFrame(false), HasAnimation(false), PreparedForSkinning(false), - AnimateNormals(true), HardwareSkinning(false), + AnimateNormals(true), SrcFormat(src_format) { SkinningBuffers = &LocalBuffers; @@ -64,14 +70,12 @@ public: The actual speed is set in the scene node the mesh is instantiated in.*/ void setAnimationSpeed(f32 fps) override; - //! returns the animated mesh for the given frame - IMesh *getMesh(f32) override; + //! Turns the given array of local matrices into an array of global matrices + //! by multiplying with respective parent matrices. + void calculateGlobalMatrices(std::vector &matrices) const; - //! Animates joints based on frame input - void animateMesh(f32 frame); - - //! Performs a software skin on this mesh based of joint positions - void skinMesh(); + //! Performs a software skin on this mesh based on the given joint matrices + void skinMesh(const std::vector &animated_transforms); //! returns amount of mesh buffers. u32 getMeshBufferCount() const override; @@ -89,14 +93,15 @@ public: void setTextureSlot(u32 meshbufNr, u32 textureSlot); - //! returns an axis aligned bounding box + //! Returns bounding box of the mesh *in static pose*. const core::aabbox3d &getBoundingBox() const override { - return BoundingBox; + // TODO ideally we shouldn't be forced to implement this + return StaticPoseBox; } - //! set user axis aligned bounding box + //! Set bounding box of the mesh *in static pose*. void setBoundingBox(const core::aabbox3df &box) override { - BoundingBox = box; + StaticPoseBox = box; } //! set the hardware mapping hint, for driver @@ -140,28 +145,15 @@ public: return !HasAnimation; } - //! Allows to enable hardware skinning. - /* This feature is not implemented in Irrlicht yet */ - bool setHardwareSkinning(bool on); - //! Refreshes vertex data cached in joints such as positions and normals void refreshJointCache(); //! Moves the mesh into static position. void resetAnimation(); - void updateBoundingBox(); - - //! Recovers the joints from the mesh - void recoverJointsFromMesh(std::vector &jointChildSceneNodes); - - //! Transfers the joint data to the mesh - void transferJointsToMesh(const std::vector &jointChildSceneNodes); - //! Creates an array of joints from this mesh as children of node - void addJoints(std::vector &jointChildSceneNodes, - IAnimatedMeshSceneNode *node, - ISceneManager *smgr); + std::vector addJoints( + IAnimatedMeshSceneNode *node, ISceneManager *smgr); //! A vertex weight struct SWeight @@ -236,7 +228,7 @@ public: static core::quaternion interpolateValue(core::quaternion from, core::quaternion to, f32 time) { core::quaternion result; - result.slerp(from, to, time, 0.001f); + result.slerp(from, to, time); return result; } @@ -288,15 +280,14 @@ public: }); } - void updateTransform(f32 frame, - core::vector3df &t, core::quaternion &r, core::vector3df &s) const + void updateTransform(f32 frame, core::Transform &transform) const { if (auto pos = position.get(frame)) - t = *pos; + transform.translation = *pos; if (auto rot = rotation.get(frame)) - r = *rot; + transform.rotation = *rot; if (auto scl = scale.get(frame)) - s = *scl; + transform.scale = *scl; } void cleanup() { @@ -309,16 +300,34 @@ public: //! Joints struct SJoint { - SJoint() : GlobalSkinningSpace(false) {} + SJoint() {} //! The name of this joint std::optional Name; - //! Local matrix of this joint - core::matrix4 LocalMatrix; + //! Local transformation to be set by loaders. Mutated by animation. + using VariantTransform = std::variant; + VariantTransform transform{core::Transform{}}; + + VariantTransform animate(f32 frame) const { + if (keys.empty()) + return transform; + + if (std::holds_alternative(transform)) { + // .x lets animations override matrix transforms entirely, + // which is what we implement here. + // .gltf does not allow animation of nodes using matrix transforms. + // Note that a decomposition into a TRS transform need not exist! + core::Transform trs; + keys.updateTransform(frame, trs); + return {trs}; + } + + auto trs = std::get(transform); + keys.updateTransform(frame, trs); + return {trs}; + } - //! List of child joints - std::vector Children; //! List of attached meshes std::vector AttachedMeshes; @@ -329,42 +338,49 @@ public: //! Skin weights std::vector Weights; + //! Bounding box of all affected vertices, in local space + core::aabbox3df LocalBoundingBox{{0, 0, 0}}; + //! Unnecessary for loaders, will be overwritten on finalize core::matrix4 GlobalMatrix; // loaders may still choose to set this (temporarily) to calculate absolute vertex data. - core::matrix4 GlobalAnimatedMatrix; - core::matrix4 LocalAnimatedMatrix; - - //! These should be set by loaders. - core::vector3df Animatedposition; - core::vector3df Animatedscale; - core::quaternion Animatedrotation; // The .x and .gltf formats pre-calculate this std::optional GlobalInversedMatrix; - private: - //! Internal members used by SkinnedMesh - friend class SkinnedMesh; - bool GlobalSkinningSpace; + void setParent(SJoint *parent) { + ParentJointID = parent ? parent->JointID : std::optional{}; + } + + u16 JointID; // TODO refactor away: pointers -> IDs (problem: .x loader abuses SJoint) + std::optional ParentJointID; }; + //! Animates joints based on frame input + std::vector animateMesh(f32 frame); + + //! Calculates a bounding box given an animation in the form of global joint transforms. + core::aabbox3df calculateBoundingBox( + const std::vector &global_transforms); + + void recalculateBaseBoundingBoxes(); + const std::vector &getAllJoints() const { return AllJoints; } protected: - void checkForAnimation(); + bool checkForAnimation() const; + + void topoSortJoints(); + + void prepareForSkinning(); + + void calculateStaticBoundingBox(); + void calculateJointBoundingBoxes(); + void calculateBufferBoundingBoxes(); void normalizeWeights(); - void buildAllLocalAnimatedMatrices(); - - void buildAllGlobalAnimatedMatrices(SJoint *Joint = 0, SJoint *ParentJoint = 0); - - void calculateGlobalMatrices(SJoint *Joint, SJoint *ParentJoint); - - void skinJoint(SJoint *Joint, SJoint *ParentJoint); - void calculateTangents(core::vector3df &normal, core::vector3df &tangent, core::vector3df &binormal, const core::vector3df &vt1, const core::vector3df &vt2, const core::vector3df &vt3, @@ -376,25 +392,25 @@ protected: //! Mapping from meshbuffer number to bindable texture slot std::vector TextureSlots; + //! Joints, topologically sorted (parents come before their children). std::vector AllJoints; - std::vector RootJoints; // bool can't be used here because std::vector // doesn't allow taking a reference to individual elements. std::vector> Vertices_Moved; - core::aabbox3d BoundingBox{{0, 0, 0}}; + //! Bounding box of just the static parts of the mesh + core::aabbox3df StaticPartsBox{{0, 0, 0}}; + + //! Bounding box of the mesh in static pose + core::aabbox3df StaticPoseBox{{0, 0, 0}}; f32 EndFrame; f32 FramesPerSecond; - f32 LastAnimatedFrame; - bool SkinnedLastFrame; - bool HasAnimation; bool PreparedForSkinning; bool AnimateNormals; - bool HardwareSkinning; SourceFormat SrcFormat; }; diff --git a/irr/include/Transform.h b/irr/include/Transform.h new file mode 100644 index 000000000..1e96e183d --- /dev/null +++ b/irr/include/Transform.h @@ -0,0 +1,42 @@ +#pragma once + +#include "irrMath.h" +#include +#include +#include + +namespace irr +{ +namespace core +{ + +struct Transform { + vector3df translation; + quaternion rotation; + vector3df scale{1}; + + Transform interpolate(Transform to, f32 time) const + { + core::quaternion interpolated_rotation; + interpolated_rotation.slerp(rotation, to.rotation, time); + return { + to.translation.getInterpolated(translation, time), + interpolated_rotation, + to.scale.getInterpolated(scale, time), + }; + } + + matrix4 buildMatrix() const + { + matrix4 T; + T.setTranslation(translation); + matrix4 R; + rotation.getMatrix_transposed(R); + matrix4 S; + S.setScale(scale); + return T * R * S; + } +}; + +} // end namespace core +} // end namespace irr diff --git a/irr/include/quaternion.h b/irr/include/quaternion.h index e23b1317d..42e0428a9 100644 --- a/irr/include/quaternion.h +++ b/irr/include/quaternion.h @@ -180,7 +180,7 @@ public: linear interpolation. */ quaternion &slerp(quaternion q1, quaternion q2, - f32 time, f32 threshold = .05f); + f32 time, f32 threshold = .001f); //! Set this quaternion to represent a rotation from angle and axis. /** Axis must be unit length. diff --git a/irr/src/CAnimatedMeshSceneNode.cpp b/irr/src/CAnimatedMeshSceneNode.cpp index 09d83038b..7b1d9053a 100644 --- a/irr/src/CAnimatedMeshSceneNode.cpp +++ b/irr/src/CAnimatedMeshSceneNode.cpp @@ -3,9 +3,13 @@ // For conditions of distribution and use, see copyright notice in irrlicht.h #include "CAnimatedMeshSceneNode.h" +#include "CBoneSceneNode.h" #include "IVideoDriver.h" #include "ISceneManager.h" #include "S3DVertex.h" +#include "Transform.h" +#include "irrTypes.h" +#include "matrix4.h" #include "os.h" #include "SkinnedMesh.h" #include "IDummyTransformationSceneNode.h" @@ -17,6 +21,9 @@ #include "IFileSystem.h" #include "quaternion.h" #include +#include +#include +#include namespace irr { @@ -30,13 +37,13 @@ CAnimatedMeshSceneNode::CAnimatedMeshSceneNode(IAnimatedMesh *mesh, const core::vector3df &rotation, const core::vector3df &scale) : IAnimatedMeshSceneNode(parent, mgr, id, position, rotation, scale), - Mesh(0), + Mesh(nullptr), StartFrame(0), EndFrame(0), FramesPerSecond(0.025f), CurrentFrameNr(0.f), LastTimeMs(0), TransitionTime(0), Transiting(0.f), TransitingBlend(0.f), - JointMode(EJUOR_NONE), JointsUsed(false), + JointsUsed(false), Looping(true), ReadOnlyMaterials(false), RenderFromIdentity(false), - LoopCallBack(0), PassCount(0) + PassCount(0) { setMesh(mesh); } @@ -44,8 +51,6 @@ CAnimatedMeshSceneNode::CAnimatedMeshSceneNode(IAnimatedMesh *mesh, //! destructor CAnimatedMeshSceneNode::~CAnimatedMeshSceneNode() { - if (LoopCallBack) - LoopCallBack->drop(); if (Mesh) Mesh->drop(); } @@ -87,8 +92,7 @@ void CAnimatedMeshSceneNode::buildFrameNr(u32 timeMs) if (FramesPerSecond > 0.f) { // forwards... if (CurrentFrameNr > EndFrame) CurrentFrameNr = StartFrame + fmodf(CurrentFrameNr - StartFrame, EndFrame - StartFrame); - } else // backwards... - { + } else { // backwards... if (CurrentFrameNr < StartFrame) CurrentFrameNr = EndFrame - fmodf(EndFrame - CurrentFrameNr, EndFrame - StartFrame); } @@ -97,18 +101,9 @@ void CAnimatedMeshSceneNode::buildFrameNr(u32 timeMs) CurrentFrameNr += timeMs * FramesPerSecond; if (FramesPerSecond > 0.f) { // forwards... - if (CurrentFrameNr > EndFrame) { - CurrentFrameNr = EndFrame; - if (LoopCallBack) - LoopCallBack->OnAnimationEnd(this); - } - } else // backwards... - { - if (CurrentFrameNr < StartFrame) { - CurrentFrameNr = StartFrame; - if (LoopCallBack) - LoopCallBack->OnAnimationEnd(this); - } + CurrentFrameNr = std::min(CurrentFrameNr, EndFrame); + } else { // backwards... + CurrentFrameNr = std::max(CurrentFrameNr, StartFrame); } } } @@ -156,38 +151,18 @@ void CAnimatedMeshSceneNode::OnRegisterSceneNode() IMesh *CAnimatedMeshSceneNode::getMeshForCurrentFrame() { if (Mesh->getMeshType() != EAMT_SKINNED) { - return Mesh->getMesh(getFrameNr()); - } else { - // As multiple scene nodes may be sharing the same skinned mesh, we have to - // re-animate it every frame to ensure that this node gets the mesh that it needs. - - SkinnedMesh *skinnedMesh = static_cast(Mesh); - - if (JointMode == EJUOR_CONTROL) // write to mesh - skinnedMesh->transferJointsToMesh(JointChildSceneNodes); - else - skinnedMesh->animateMesh(getFrameNr()); - - // Update the skinned mesh for the current joint transforms. - skinnedMesh->skinMesh(); - - if (JointMode == EJUOR_READ) { // read from mesh - skinnedMesh->recoverJointsFromMesh(JointChildSceneNodes); - - //---slow--- - for (u32 n = 0; n < JointChildSceneNodes.size(); ++n) - if (JointChildSceneNodes[n]->getParent() == this) { - JointChildSceneNodes[n]->updateAbsolutePositionOfAllChildren(); // temp, should be an option - } - } - - if (JointMode == EJUOR_CONTROL) { - // For meshes other than EJUOR_CONTROL, this is done by calling animateMesh() - skinnedMesh->updateBoundingBox(); - } - - return skinnedMesh; + return Mesh; } + + // As multiple scene nodes may be sharing the same skinned mesh, we have to + // re-animate it every frame to ensure that this node gets the mesh that it needs. + + auto *skinnedMesh = static_cast(Mesh); + + // Matrices have already been calculated in OnAnimate + skinnedMesh->skinMesh(PerJoint.GlobalMatrices); + + return skinnedMesh; } //! OnAnimate() is called just before rendering the whole scene. @@ -201,7 +176,28 @@ void CAnimatedMeshSceneNode::OnAnimate(u32 timeMs) buildFrameNr(timeMs - LastTimeMs); LastTimeMs = timeMs; + // This needs to be done on animate, which is called recursively *before* + // anything is rendered so that the transformations of children are up to date + animateJoints(); + + // Copy old transforms *before* bone overrides have been applied. + // TODO if there are no bone overrides or no animation blending, this is unnecessary. + copyOldTransforms(); + + if (OnAnimateCallback) + OnAnimateCallback(timeMs / 1000.0f); + IAnimatedMeshSceneNode::OnAnimate(timeMs); + + if (auto *skinnedMesh = dynamic_cast(Mesh)) { + for (u16 i = 0; i < PerJoint.SceneNodes.size(); ++i) + PerJoint.GlobalMatrices[i] = PerJoint.SceneNodes[i]->getRelativeTransformation(); + assert(PerJoint.GlobalMatrices.size() == skinnedMesh->getJointCount()); + skinnedMesh->calculateGlobalMatrices(PerJoint.GlobalMatrices); + Box = skinnedMesh->calculateBoundingBox(PerJoint.GlobalMatrices); + } else { + Box = Mesh->getBoundingBox(); + } } //! renders the node. @@ -218,15 +214,7 @@ void CAnimatedMeshSceneNode::render() ++PassCount; scene::IMesh *m = getMeshForCurrentFrame(); - - if (m) { - Box = m->getBoundingBox(); - } else { -#ifdef _DEBUG - os::Printer::log("Animated Mesh returned no mesh to render.", ELL_WARNING); -#endif - return; - } + assert(m); driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); @@ -294,11 +282,12 @@ void CAnimatedMeshSceneNode::render() if (DebugDataVisible & scene::EDS_SKELETON) { if (Mesh->getMeshType() == EAMT_SKINNED) { // draw skeleton - - for (auto *joint : ((SkinnedMesh *)Mesh)->getAllJoints()) { - for (const auto *childJoint : joint->Children) { - driver->draw3DLine(joint->GlobalAnimatedMatrix.getTranslation(), - childJoint->GlobalAnimatedMatrix.getTranslation(), + const auto &joints = (static_cast(Mesh))->getAllJoints(); + for (u16 i = 0; i < PerJoint.GlobalMatrices.size(); ++i) { + const auto translation = PerJoint.GlobalMatrices[i].getTranslation(); + if (auto pjid = joints[i]->ParentJointID) { + const auto parent_translation = PerJoint.GlobalMatrices[*pjid].getTranslation(); + driver->draw3DLine(parent_translation, translation, video::SColor(255, 51, 66, 255)); } } @@ -407,12 +396,12 @@ IBoneSceneNode *CAnimatedMeshSceneNode::getJointNode(const c8 *jointName) return 0; } - if (JointChildSceneNodes.size() <= *number) { + if (PerJoint.SceneNodes.size() <= *number) { os::Printer::log("Joint was found in mesh, but is not loaded into node", jointName, ELL_WARNING); return 0; } - return JointChildSceneNodes[*number]; + return PerJoint.SceneNodes[*number]; } //! Returns a pointer to a child node, which has the same transformation as @@ -426,12 +415,12 @@ IBoneSceneNode *CAnimatedMeshSceneNode::getJointNode(u32 jointID) checkJoints(); - if (JointChildSceneNodes.size() <= jointID) { + if (PerJoint.SceneNodes.size() <= jointID) { os::Printer::log("Joint not loaded into node", ELL_WARNING); return 0; } - return JointChildSceneNodes[jointID]; + return PerJoint.SceneNodes[jointID]; } //! Gets joint count. @@ -452,9 +441,9 @@ bool CAnimatedMeshSceneNode::removeChild(ISceneNode *child) { if (ISceneNode::removeChild(child)) { if (JointsUsed) { // stop weird bugs caused while changing parents as the joints are being created - for (u32 i = 0; i < JointChildSceneNodes.size(); ++i) { - if (JointChildSceneNodes[i] == child) { - JointChildSceneNodes[i] = 0; // remove link to child + for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i) { + if (PerJoint.SceneNodes[i] == child) { + PerJoint.SceneNodes[i] = 0; // remove link to child break; } } @@ -478,22 +467,6 @@ bool CAnimatedMeshSceneNode::getLoopMode() const return Looping; } -//! Sets a callback interface which will be called if an animation -//! playback has ended. Set this to 0 to disable the callback again. -void CAnimatedMeshSceneNode::setAnimationEndCallback(IAnimationEndCallBack *callback) -{ - if (callback == LoopCallBack) - return; - - if (LoopCallBack) - LoopCallBack->drop(); - - LoopCallBack = callback; - - if (LoopCallBack) - LoopCallBack->grab(); -} - //! Sets if the scene node should not copy the materials of the mesh but use them in a read only style. void CAnimatedMeshSceneNode::setReadOnlyMaterials(bool readonly) { @@ -525,18 +498,15 @@ void CAnimatedMeshSceneNode::setMesh(IAnimatedMesh *mesh) // get materials and bounding box Box = Mesh->getBoundingBox(); - IMesh *m = Mesh->getMesh(0); - if (m) { - Materials.clear(); - Materials.reallocate(m->getMeshBufferCount()); + Materials.clear(); + Materials.reallocate(Mesh->getMeshBufferCount()); - for (u32 i = 0; i < m->getMeshBufferCount(); ++i) { - IMeshBuffer *mb = m->getMeshBuffer(i); - if (mb) - Materials.push_back(mb->getMaterial()); - else - Materials.push_back(video::SMaterial()); - } + for (u32 i = 0; i < Mesh->getMeshBufferCount(); ++i) { + IMeshBuffer *mb = Mesh->getMeshBuffer(i); + if (mb) + Materials.push_back(mb->getMaterial()); + else + Materials.push_back(video::SMaterial()); } // clean up joint nodes @@ -556,14 +526,7 @@ void CAnimatedMeshSceneNode::updateAbsolutePosition() IAnimatedMeshSceneNode::updateAbsolutePosition(); } -//! Set the joint update mode (0-unused, 1-get joints only, 2-set joints only, 3-move and set) -void CAnimatedMeshSceneNode::setJointMode(E_JOINT_UPDATE_ON_RENDER mode) -{ - checkJoints(); - JointMode = mode; -} - -//! Sets the transition time in seconds (note: This needs to enable joints, and setJointmode maybe set to 2) +//! Sets the transition time in seconds (note: This needs to enable joints) //! you must call animateJoints(), or the mesh will not animate void CAnimatedMeshSceneNode::setTransitionTime(f32 time) { @@ -571,10 +534,6 @@ void CAnimatedMeshSceneNode::setTransitionTime(f32 time) if (TransitionTime == ttime) return; TransitionTime = ttime; - if (ttime != 0) - setJointMode(EJUOR_CONTROL); - else - setJointMode(EJUOR_NONE); } //! render mesh ignoring its transformation. Used with ragdolls. (culling is unaffected) @@ -583,120 +542,104 @@ void CAnimatedMeshSceneNode::setRenderFromIdentity(bool enable) RenderFromIdentity = enable; } -//! updates the joint positions of this mesh -void CAnimatedMeshSceneNode::animateJoints(bool CalculateAbsolutePositions) +void CAnimatedMeshSceneNode::addJoints() { - if (Mesh && Mesh->getMeshType() == EAMT_SKINNED) { - checkJoints(); - const f32 frame = getFrameNr(); // old? + const auto &joints = static_cast(Mesh)->getAllJoints(); + PerJoint.setN(joints.size()); + PerJoint.SceneNodes.clear(); + PerJoint.SceneNodes.reserve(joints.size()); + for (size_t i = 0; i < joints.size(); ++i) { + const auto *joint = joints[i]; + ISceneNode *parent = this; + if (joint->ParentJointID) + parent = PerJoint.SceneNodes.at(*joint->ParentJointID); // exists because of topo. order + assert(parent); + const auto *matrix = std::get_if(&joint->transform); + PerJoint.SceneNodes.push_back(new CBoneSceneNode( + parent, SceneManager, 0, i, joint->Name, + matrix ? core::Transform{} : std::get(joint->transform), + matrix ? *matrix : std::optional{})); + } +} - SkinnedMesh *skinnedMesh = static_cast(Mesh); - - skinnedMesh->animateMesh(frame); - skinnedMesh->recoverJointsFromMesh(JointChildSceneNodes); - - //----------------------------------------- - // Transition - //----------------------------------------- - - if (Transiting != 0.f) { - // Init additional matrices - if (PretransitingSave.size() < JointChildSceneNodes.size()) { - for (u32 n = PretransitingSave.size(); n < JointChildSceneNodes.size(); ++n) - PretransitingSave.push_back(core::matrix4()); - } - - for (u32 n = 0; n < JointChildSceneNodes.size(); ++n) { - //------Position------ - - JointChildSceneNodes[n]->setPosition( - core::lerp( - PretransitingSave[n].getTranslation(), - JointChildSceneNodes[n]->getPosition(), - TransitingBlend)); - - //------Rotation------ - - // Code is slow, needs to be fixed up - - const core::quaternion RotationStart(PretransitingSave[n].getRotationRadians()); - const core::quaternion RotationEnd(JointChildSceneNodes[n]->getRotation() * core::DEGTORAD); - - core::quaternion QRotation; - QRotation.slerp(RotationStart, RotationEnd, TransitingBlend); - - core::vector3df tmpVector; - QRotation.toEuler(tmpVector); - tmpVector *= core::RADTODEG; // convert from radians back to degrees - JointChildSceneNodes[n]->setRotation(tmpVector); - - //------Scale------ - - // JointChildSceneNodes[n]->setScale( - // core::lerp( - // PretransitingSave[n].getScale(), - // JointChildSceneNodes[n]->getScale(), - // TransitingBlend)); - } +void CAnimatedMeshSceneNode::updateJointSceneNodes( + const std::vector &transforms) +{ + for (size_t i = 0; i < transforms.size(); ++i) { + const auto &transform = transforms[i]; + auto *node = static_cast(PerJoint.SceneNodes[i]); + if (const auto *trs = std::get_if(&transform)) { + node->setTransform(*trs); + // .x lets animations override matrix transforms entirely. + node->Matrix = std::nullopt; + } else { + node->Matrix = std::get(transform); } + } +} - if (CalculateAbsolutePositions) { - //---slow--- - for (u32 n = 0; n < JointChildSceneNodes.size(); ++n) { - if (JointChildSceneNodes[n]->getParent() == this) { - JointChildSceneNodes[n]->updateAbsolutePositionOfAllChildren(); // temp, should be an option - } +//! updates the joint positions of this mesh +void CAnimatedMeshSceneNode::animateJoints() +{ + if (!Mesh || Mesh->getMeshType() != EAMT_SKINNED) + return; + + checkJoints(); + + SkinnedMesh *skinnedMesh = static_cast(Mesh); + if (!skinnedMesh->isStatic()) + updateJointSceneNodes(skinnedMesh->animateMesh(getFrameNr())); + + //----------------------------------------- + // Transition + //----------------------------------------- + + if (Transiting != 0.f) { + for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i) { + if (PerJoint.PreTransSaves[i]) { + PerJoint.SceneNodes[i]->setTransform(PerJoint.PreTransSaves[i]->interpolate( + PerJoint.SceneNodes[i]->getTransform(), TransitingBlend)); } } } } -/*! - */ void CAnimatedMeshSceneNode::checkJoints() { if (!Mesh || Mesh->getMeshType() != EAMT_SKINNED) return; if (!JointsUsed) { - for (u32 i = 0; i < JointChildSceneNodes.size(); ++i) - removeChild(JointChildSceneNodes[i]); - JointChildSceneNodes.clear(); - - // Create joints for SkinnedMesh - ((SkinnedMesh *)Mesh)->addJoints(JointChildSceneNodes, this, SceneManager); - ((SkinnedMesh *)Mesh)->recoverJointsFromMesh(JointChildSceneNodes); + for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i) + removeChild(PerJoint.SceneNodes[i]); + addJoints(); JointsUsed = true; - JointMode = EJUOR_READ; } } -/*! - */ +void CAnimatedMeshSceneNode::copyOldTransforms() +{ + for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i) { + if (!PerJoint.SceneNodes[i]->Matrix) { + PerJoint.PreTransSaves[i] = PerJoint.SceneNodes[i]->getTransform(); + } else { + PerJoint.PreTransSaves[i] = std::nullopt; + } + } +} + void CAnimatedMeshSceneNode::beginTransition() { if (!JointsUsed) return; if (TransitionTime != 0) { - // Check the array is big enough - if (PretransitingSave.size() < JointChildSceneNodes.size()) { - for (u32 n = PretransitingSave.size(); n < JointChildSceneNodes.size(); ++n) - PretransitingSave.push_back(core::matrix4()); - } - - // Copy the position of joints - for (u32 n = 0; n < JointChildSceneNodes.size(); ++n) - PretransitingSave[n] = JointChildSceneNodes[n]->getRelativeTransformation(); - Transiting = core::reciprocal((f32)TransitionTime); } TransitingBlend = 0.f; } -/*! - */ ISceneNode *CAnimatedMeshSceneNode::clone(ISceneNode *newParent, ISceneManager *newManager) { if (!newParent) @@ -722,19 +665,15 @@ ISceneNode *CAnimatedMeshSceneNode::clone(ISceneNode *newParent, ISceneManager * newNode->EndFrame = EndFrame; newNode->FramesPerSecond = FramesPerSecond; newNode->CurrentFrameNr = CurrentFrameNr; - newNode->JointMode = JointMode; newNode->JointsUsed = JointsUsed; newNode->TransitionTime = TransitionTime; newNode->Transiting = Transiting; newNode->TransitingBlend = TransitingBlend; newNode->Looping = Looping; newNode->ReadOnlyMaterials = ReadOnlyMaterials; - newNode->LoopCallBack = LoopCallBack; - if (newNode->LoopCallBack) - newNode->LoopCallBack->grab(); newNode->PassCount = PassCount; - newNode->JointChildSceneNodes = JointChildSceneNodes; - newNode->PretransitingSave = PretransitingSave; + newNode->PerJoint.SceneNodes = PerJoint.SceneNodes; + newNode->PerJoint.PreTransSaves = PerJoint.PreTransSaves; newNode->RenderFromIdentity = RenderFromIdentity; return newNode; diff --git a/irr/src/CAnimatedMeshSceneNode.h b/irr/src/CAnimatedMeshSceneNode.h index 5149f7618..c67479481 100644 --- a/irr/src/CAnimatedMeshSceneNode.h +++ b/irr/src/CAnimatedMeshSceneNode.h @@ -4,9 +4,12 @@ #pragma once +#include "CBoneSceneNode.h" #include "IAnimatedMeshSceneNode.h" #include "IAnimatedMesh.h" +#include "SkinnedMesh.h" +#include "Transform.h" #include "matrix4.h" namespace irr @@ -54,9 +57,11 @@ public: //! returns the current loop mode bool getLoopMode() const override; - //! Sets a callback interface which will be called if an animation - //! playback has ended. Set this to 0 to disable the callback again. - void setAnimationEndCallback(IAnimationEndCallBack *callback = 0) override; + void setOnAnimateCallback( + const std::function &cb) override + { + OnAnimateCallback = cb; + } //! sets the speed with which the animation is played //! NOTE: setMesh will also change this value and set it to the default speed of the mesh @@ -117,15 +122,16 @@ public: //! updates the absolute position based on the relative and the parents position void updateAbsolutePosition() override; - //! Set the joint update mode (0-unused, 1-get joints only, 2-set joints only, 3-move and set) - void setJointMode(E_JOINT_UPDATE_ON_RENDER mode) override; - - //! Sets the transition time in seconds (note: This needs to enable joints, and setJointmode maybe set to 2) + //! Sets the transition time in seconds (note: This needs to enable joints) //! you must call animateJoints(), or the mesh will not animate void setTransitionTime(f32 Time) override; + void updateJointSceneNodes(const std::vector &transforms); + //! updates the joint positions of this mesh - void animateJoints(bool CalculateAbsolutePositions = true) override; + void animateJoints() override; + + void addJoints(); //! render mesh ignoring its transformation. Used with ragdolls. (culling is unaffected) void setRenderFromIdentity(bool On) override; @@ -142,6 +148,7 @@ private: void buildFrameNr(u32 timeMs); void checkJoints(); + void copyOldTransforms(); void beginTransition(); core::array Materials; @@ -158,19 +165,30 @@ private: f32 Transiting; // is mesh transiting (plus cache of TransitionTime) f32 TransitingBlend; // 0-1, calculated on buildFrameNr - // 0-unused, 1-get joints only, 2-set joints only, 3-move and set - E_JOINT_UPDATE_ON_RENDER JointMode; bool JointsUsed; bool Looping; bool ReadOnlyMaterials; bool RenderFromIdentity; - IAnimationEndCallBack *LoopCallBack; s32 PassCount; + std::function OnAnimateCallback; - std::vector JointChildSceneNodes; - core::array PretransitingSave; + struct PerJointData { + std::vector SceneNodes; + std::vector GlobalMatrices; + std::vector> PreTransSaves; + void setN(u16 n) { + SceneNodes.clear(); + SceneNodes.resize(n); + GlobalMatrices.clear(); + GlobalMatrices.resize(n); + PreTransSaves.clear(); + PreTransSaves.resize(n); + } + }; + + PerJointData PerJoint; }; } // end namespace scene diff --git a/irr/src/CB3DMeshFileLoader.cpp b/irr/src/CB3DMeshFileLoader.cpp index 51342f451..6e3e14791 100644 --- a/irr/src/CB3DMeshFileLoader.cpp +++ b/irr/src/CB3DMeshFileLoader.cpp @@ -143,31 +143,25 @@ bool CB3DMeshFileLoader::readChunkNODE(SkinnedMesh::SJoint *inJoint) os::Printer::log(logStr.c_str(), joint->Name.value_or("").c_str(), ELL_DEBUG); #endif - f32 position[3], scale[3], rotation[4]; + core::Transform transform; + { + f32 t[3], s[3], r[4]; - readFloats(position, 3); - readFloats(scale, 3); - readFloats(rotation, 4); + readFloats(t, 3); + readFloats(s, 3); + readFloats(r, 4); - joint->Animatedposition = core::vector3df(position[0], position[1], position[2]); - joint->Animatedscale = core::vector3df(scale[0], scale[1], scale[2]); - joint->Animatedrotation = core::quaternion(rotation[1], rotation[2], rotation[3], rotation[0]); - - // Build LocalMatrix: - - core::matrix4 positionMatrix; - positionMatrix.setTranslation(joint->Animatedposition); - core::matrix4 scaleMatrix; - scaleMatrix.setScale(joint->Animatedscale); - core::matrix4 rotationMatrix; - joint->Animatedrotation.getMatrix_transposed(rotationMatrix); - - joint->LocalMatrix = positionMatrix * rotationMatrix * scaleMatrix; + joint->transform = transform = { + {t[0], t[1], t[2]}, + {r[1], r[2], r[3], r[0]}, + {s[0], s[1], s[2]}, + }; + } if (inJoint) - joint->GlobalMatrix = inJoint->GlobalMatrix * joint->LocalMatrix; + joint->GlobalMatrix = inJoint->GlobalMatrix * transform.buildMatrix(); else - joint->GlobalMatrix = joint->LocalMatrix; + joint->GlobalMatrix = transform.buildMatrix(); while (B3dStack.getLast().startposition + B3dStack.getLast().length > B3DFile->getPos()) // this chunk repeats { diff --git a/irr/src/CBoneSceneNode.cpp b/irr/src/CBoneSceneNode.cpp deleted file mode 100644 index 7aa637094..000000000 --- a/irr/src/CBoneSceneNode.cpp +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (C) 2002-2012 Nikolaus Gebhardt -// This file is part of the "Irrlicht Engine". -// For conditions of distribution and use, see copyright notice in irrlicht.h - -#include "CBoneSceneNode.h" - -#include - -namespace irr -{ -namespace scene -{ - -//! constructor -CBoneSceneNode::CBoneSceneNode(ISceneNode *parent, ISceneManager *mgr, s32 id, - u32 boneIndex, const std::optional &boneName) : - IBoneSceneNode(parent, mgr, id), - BoneIndex(boneIndex), - AnimationMode(EBAM_AUTOMATIC), SkinningSpace(EBSS_LOCAL) -{ - setName(boneName); -} - -//! Returns the index of the bone -u32 CBoneSceneNode::getBoneIndex() const -{ - return BoneIndex; -} - -//! Sets the animation mode of the bone. Returns true if successful. -bool CBoneSceneNode::setAnimationMode(E_BONE_ANIMATION_MODE mode) -{ - AnimationMode = mode; - return true; -} - -//! Gets the current animation mode of the bone -E_BONE_ANIMATION_MODE CBoneSceneNode::getAnimationMode() const -{ - return AnimationMode; -} - -//! returns the axis aligned bounding box of this node -const core::aabbox3d &CBoneSceneNode::getBoundingBox() const -{ - return Box; -} - -/* -//! Returns the relative transformation of the scene node. -core::matrix4 CBoneSceneNode::getRelativeTransformation() const -{ - return core::matrix4(); // RelativeTransformation; -} -*/ - -void CBoneSceneNode::OnAnimate(u32 timeMs) -{ - if (IsVisible) { - // update absolute position - // updateAbsolutePosition(); - - // perform the post render process on all children - ISceneNodeList::iterator it = Children.begin(); - for (; it != Children.end(); ++it) - (*it)->OnAnimate(timeMs); - } -} - -void CBoneSceneNode::helper_updateAbsolutePositionOfAllChildren(ISceneNode *Node) -{ - Node->updateAbsolutePosition(); - - ISceneNodeList::const_iterator it = Node->getChildren().begin(); - for (; it != Node->getChildren().end(); ++it) { - helper_updateAbsolutePositionOfAllChildren((*it)); - } -} - -void CBoneSceneNode::updateAbsolutePositionOfAllChildren() -{ - helper_updateAbsolutePositionOfAllChildren(this); -} - -} // namespace scene -} // namespace irr diff --git a/irr/src/CBoneSceneNode.h b/irr/src/CBoneSceneNode.h index d900570db..4151f7372 100644 --- a/irr/src/CBoneSceneNode.h +++ b/irr/src/CBoneSceneNode.h @@ -7,6 +7,8 @@ // Used with SkinnedMesh and IAnimatedMeshSceneNode, for boned meshes #include "IBoneSceneNode.h" +#include "Transform.h" +#include "matrix4.h" #include @@ -21,49 +23,48 @@ public: //! constructor CBoneSceneNode(ISceneNode *parent, ISceneManager *mgr, s32 id = -1, u32 boneIndex = 0, - const std::optional &boneName = std::nullopt); - - //! Returns the index of the bone - u32 getBoneIndex() const override; - - //! Sets the animation mode of the bone. Returns true if successful. - bool setAnimationMode(E_BONE_ANIMATION_MODE mode) override; - - //! Gets the current animation mode of the bone - E_BONE_ANIMATION_MODE getAnimationMode() const override; - - //! returns the axis aligned bounding box of this node - const core::aabbox3d &getBoundingBox() const override; - - /* - //! Returns the relative transformation of the scene node. - //core::matrix4 getRelativeTransformation() const override; - */ - - void OnAnimate(u32 timeMs) override; - - void updateAbsolutePositionOfAllChildren() override; - - //! How the relative transformation of the bone is used - void setSkinningSpace(E_BONE_SKINNING_SPACE space) override + const std::optional &boneName = std::nullopt, + const core::Transform &transform = {}, + const std::optional &matrix = std::nullopt) : + IBoneSceneNode(parent, mgr, id, boneIndex, boneName), + Matrix(matrix) { - SkinningSpace = space; + setTransform(transform); } - E_BONE_SKINNING_SPACE getSkinningSpace() const override + void setTransform(const core::Transform &transform) { - return SkinningSpace; + setPosition(transform.translation); + { + core::vector3df euler; + auto rot = transform.rotation; + // Invert to be consistent with setRotationDegrees + rot.makeInverse(); + rot.toEuler(euler); + setRotation(euler * core::RADTODEG); + } + setScale(transform.scale); } -private: - void helper_updateAbsolutePositionOfAllChildren(ISceneNode *Node); + core::Transform getTransform() const + { + return { + getPosition(), + core::quaternion(getRotation() * core::DEGTORAD).makeInverse(), + getScale() + }; + } - u32 BoneIndex; + core::matrix4 getRelativeTransformation() const override + { + if (Matrix) + return *Matrix; + return IBoneSceneNode::getRelativeTransformation(); + } - core::aabbox3d Box{-1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f}; - - E_BONE_ANIMATION_MODE AnimationMode; - E_BONE_SKINNING_SPACE SkinningSpace; + //! Some file formats alternatively let bones specify a transformation matrix. + //! If this is set, it overrides the TRS properties. + std::optional Matrix; }; } // end namespace scene diff --git a/irr/src/CGLTFMeshFileLoader.cpp b/irr/src/CGLTFMeshFileLoader.cpp index 3f2096f40..53ae1b580 100644 --- a/irr/src/CGLTFMeshFileLoader.cpp +++ b/irr/src/CGLTFMeshFileLoader.cpp @@ -539,34 +539,25 @@ static core::matrix4 loadTransform(const tiniergltf::Node::Matrix &m, SkinnedMes mat[i] = static_cast(m[i]); mat = convertHandedness(mat); - // Decompose the matrix into translation, scale, and rotation. - joint->Animatedposition = mat.getTranslation(); - - auto scale = mat.getScale(); - joint->Animatedscale = scale; - joint->Animatedrotation = mat.getRotationRadians(scale); - // Invert the rotation because it is applied using `getMatrix_transposed`, - // which again inverts. - joint->Animatedrotation.makeInverse(); - + // Note: "When a node is targeted for animation [...], + // only TRS properties MAY be present; matrix MUST NOT be present." + // Thus we MUST NOT do any decomposition, which in general need not exist. + joint->transform = mat; return mat; } static core::matrix4 loadTransform(const tiniergltf::Node::TRS &trs, SkinnedMesh::SJoint *joint) { - const auto &trans = trs.translation; - const auto &rot = trs.rotation; - const auto &scale = trs.scale; - core::matrix4 transMat; - joint->Animatedposition = convertHandedness(core::vector3df(trans[0], trans[1], trans[2])); - transMat.setTranslation(joint->Animatedposition); - core::matrix4 rotMat; - joint->Animatedrotation = convertHandedness(core::quaternion(rot[0], rot[1], rot[2], rot[3])); - core::quaternion(joint->Animatedrotation).getMatrix_transposed(rotMat); - joint->Animatedscale = core::vector3df(scale[0], scale[1], scale[2]); - core::matrix4 scaleMat; - scaleMat.setScale(joint->Animatedscale); - return transMat * rotMat * scaleMat; + const auto &t = trs.translation; + const auto &r = trs.rotation; + const auto &s = trs.scale; + core::Transform transform{ + convertHandedness(core::vector3df(t[0], t[1], t[2])), + convertHandedness(core::quaternion(r[0], r[1], r[2], r[3])), + core::vector3df(s[0], s[1], s[2]), + }; + joint->transform = transform; + return transform.buildMatrix(); } static core::matrix4 loadTransform(std::optional> transform, @@ -584,8 +575,7 @@ void SelfType::MeshExtractor::loadNode( const auto &node = m_gltf_model.nodes->at(nodeIdx); auto *joint = m_irr_model->addJoint(parent); const core::matrix4 transform = loadTransform(node.transform, joint); - joint->LocalMatrix = transform; - joint->GlobalMatrix = parent ? parent->GlobalMatrix * joint->LocalMatrix : joint->LocalMatrix; + joint->GlobalMatrix = parent ? parent->GlobalMatrix * transform : transform; if (node.name.has_value()) { joint->Name = node.name->c_str(); } @@ -642,7 +632,6 @@ void SelfType::MeshExtractor::loadAnimation(const std::size_t animIdx) { const auto &anim = m_gltf_model.animations->at(animIdx); for (const auto &channel : anim.channels) { - const auto &sampler = anim.samplers.at(channel.sampler); bool interpolate = ([&]() { @@ -663,6 +652,11 @@ void SelfType::MeshExtractor::loadAnimation(const std::size_t animIdx) throw std::runtime_error("no animated node"); auto *joint = m_loaded_nodes.at(*channel.target.node); + if (std::holds_alternative(joint->transform)) { + warn("nodes using matrix transforms must not be animated"); + continue; + } + switch (channel.target.path) { case tiniergltf::AnimationChannelTarget::Path::TRANSLATION: { const auto outputAccessor = Accessor::make(m_gltf_model, sampler.output); diff --git a/irr/src/CMakeLists.txt b/irr/src/CMakeLists.txt index 83da89c11..c752d7d31 100644 --- a/irr/src/CMakeLists.txt +++ b/irr/src/CMakeLists.txt @@ -320,7 +320,6 @@ set(IRRMESHLOADER add_library(IRRMESHOBJ OBJECT SkinnedMesh.cpp - CBoneSceneNode.cpp CMeshSceneNode.cpp CAnimatedMeshSceneNode.cpp ${IRRMESHLOADER} diff --git a/irr/src/CMeshCache.cpp b/irr/src/CMeshCache.cpp index 4f7e1203c..9a41aa582 100644 --- a/irr/src/CMeshCache.cpp +++ b/irr/src/CMeshCache.cpp @@ -35,7 +35,7 @@ void CMeshCache::removeMesh(const IMesh *const mesh) if (!mesh) return; for (u32 i = 0; i < Meshes.size(); ++i) { - if (Meshes[i].Mesh == mesh || (Meshes[i].Mesh && Meshes[i].Mesh->getMesh(0) == mesh)) { + if (Meshes[i].Mesh == mesh) { Meshes[i].Mesh->drop(); Meshes.erase(i); return; @@ -53,7 +53,7 @@ u32 CMeshCache::getMeshCount() const s32 CMeshCache::getMeshIndex(const IMesh *const mesh) const { for (u32 i = 0; i < Meshes.size(); ++i) { - if (Meshes[i].Mesh == mesh || (Meshes[i].Mesh && Meshes[i].Mesh->getMesh(0) == mesh)) + if (Meshes[i].Mesh == mesh) return (s32)i; } @@ -93,7 +93,7 @@ const io::SNamedPath &CMeshCache::getMeshName(const IMesh *const mesh) const return emptyNamedPath; for (u32 i = 0; i < Meshes.size(); ++i) { - if (Meshes[i].Mesh == mesh || (Meshes[i].Mesh && Meshes[i].Mesh->getMesh(0) == mesh)) + if (Meshes[i].Mesh == mesh) return Meshes[i].NamedPath; } @@ -115,7 +115,7 @@ bool CMeshCache::renameMesh(u32 index, const io::path &name) bool CMeshCache::renameMesh(const IMesh *const mesh, const io::path &name) { for (u32 i = 0; i < Meshes.size(); ++i) { - if (Meshes[i].Mesh == mesh || (Meshes[i].Mesh && Meshes[i].Mesh->getMesh(0) == mesh)) { + if (Meshes[i].Mesh == mesh) { Meshes[i].NamedPath.setPath(name); Meshes.sort(); return true; diff --git a/irr/src/CMeshManipulator.cpp b/irr/src/CMeshManipulator.cpp index 659e8dff8..535b74686 100644 --- a/irr/src/CMeshManipulator.cpp +++ b/irr/src/CMeshManipulator.cpp @@ -6,7 +6,6 @@ #include "SkinnedMesh.h" #include "SMesh.h" #include "CMeshBuffer.h" -#include "SAnimatedMesh.h" #include "os.h" #include @@ -178,34 +177,5 @@ SMesh *CMeshManipulator::createMeshCopy(scene::IMesh *mesh) const return clone; } -//! Returns amount of polygons in mesh. -s32 CMeshManipulator::getPolyCount(scene::IMesh *mesh) const -{ - if (!mesh) - return 0; - - s32 trianglecount = 0; - - for (u32 g = 0; g < mesh->getMeshBufferCount(); ++g) - trianglecount += mesh->getMeshBuffer(g)->getIndexCount() / 3; - - return trianglecount; -} - -//! Returns amount of polygons in mesh. -s32 CMeshManipulator::getPolyCount(scene::IAnimatedMesh *mesh) const -{ - if (mesh && mesh->getMaxFrameNumber() != 0) - return getPolyCount(mesh->getMesh(0)); - - return 0; -} - -//! create a new AnimatedMesh and adds the mesh to it -IAnimatedMesh *CMeshManipulator::createAnimatedMesh(scene::IMesh *mesh, scene::E_ANIMATED_MESH_TYPE type) const -{ - return new SAnimatedMesh(mesh, type); -} - } // end namespace scene } // end namespace irr diff --git a/irr/src/CMeshManipulator.h b/irr/src/CMeshManipulator.h index 0377d6a3a..c5632512d 100644 --- a/irr/src/CMeshManipulator.h +++ b/irr/src/CMeshManipulator.h @@ -31,15 +31,6 @@ public: //! Clones a static IMesh into a modifiable SMesh. SMesh *createMeshCopy(scene::IMesh *mesh) const override; - - //! Returns amount of polygons in mesh. - s32 getPolyCount(scene::IMesh *mesh) const override; - - //! Returns amount of polygons in mesh. - s32 getPolyCount(scene::IAnimatedMesh *mesh) const override; - - //! create a new AnimatedMesh and adds the mesh to it - IAnimatedMesh *createAnimatedMesh(scene::IMesh *mesh, scene::E_ANIMATED_MESH_TYPE type) const override; }; } // end namespace scene diff --git a/irr/src/CNullDriver.cpp b/irr/src/CNullDriver.cpp index 246f414de..f7d012913 100644 --- a/irr/src/CNullDriver.cpp +++ b/irr/src/CNullDriver.cpp @@ -1238,7 +1238,7 @@ void CNullDriver::addOcclusionQuery(scene::ISceneNode *node, const scene::IMesh else if (node->getType() == scene::ESNT_MESH) mesh = static_cast(node)->getMesh(); else - mesh = static_cast(node)->getMesh()->getMesh(0); + mesh = static_cast(node)->getMesh(); if (!mesh) return; } diff --git a/irr/src/COBJMeshFileLoader.cpp b/irr/src/COBJMeshFileLoader.cpp index 5c7f38950..bc51c10f2 100644 --- a/irr/src/COBJMeshFileLoader.cpp +++ b/irr/src/COBJMeshFileLoader.cpp @@ -7,7 +7,6 @@ #include "IVideoDriver.h" #include "SMesh.h" #include "SMeshBuffer.h" -#include "SAnimatedMesh.h" #include "IReadFile.h" #include "fast_atof.h" #include "coreutil.h" @@ -272,23 +271,19 @@ IAnimatedMesh *COBJMeshFileLoader::createMesh(io::IReadFile *file) } } - // Create the Animated mesh if there's anything in the mesh - SAnimatedMesh *animMesh = 0; - if (0 != mesh->getMeshBufferCount()) { - mesh->recalculateBoundingBox(); - animMesh = new SAnimatedMesh(); - animMesh->Type = EAMT_OBJ; - animMesh->addMesh(mesh); - animMesh->recalculateBoundingBox(); - } - // Clean up the allocate obj file contents delete[] buf; // more cleaning up cleanUp(); - mesh->drop(); - return animMesh; + // Nothing in the mesh + if (mesh->getMeshBufferCount() == 0) { + mesh->drop(); + return nullptr; + } + + mesh->recalculateBoundingBox(); + return mesh; } //! Read RGB color diff --git a/irr/src/CSceneManager.cpp b/irr/src/CSceneManager.cpp index 05f8de71a..b0b82b772 100644 --- a/irr/src/CSceneManager.cpp +++ b/irr/src/CSceneManager.cpp @@ -8,7 +8,6 @@ #include "CSceneManager.h" #include "IVideoDriver.h" #include "IFileSystem.h" -#include "SAnimatedMesh.h" #include "CMeshCache.h" #include "IGUIEnvironment.h" #include "IMaterialRenderer.h" diff --git a/irr/src/CXMeshFileLoader.cpp b/irr/src/CXMeshFileLoader.cpp index d93502d6b..f2c4e94e6 100644 --- a/irr/src/CXMeshFileLoader.cpp +++ b/irr/src/CXMeshFileLoader.cpp @@ -4,6 +4,7 @@ #include "CXMeshFileLoader.h" #include "SkinnedMesh.h" +#include "Transform.h" #include "os.h" #include "fast_atof.h" @@ -513,6 +514,7 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent) if (n.has_value()) { JointID = *n; joint = AnimatedMesh->getAllJoints()[JointID]; + joint->setParent(Parent); } } @@ -527,8 +529,6 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent) #ifdef _XREADER_DEBUG os::Printer::log("using joint ", name.c_str(), ELL_DEBUG); #endif - if (Parent) - Parent->Children.push_back(joint); } // Now inside a frame. @@ -552,12 +552,10 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent) if (!parseDataObjectFrame(joint)) return false; } else if (objectName == "FrameTransformMatrix") { - if (!parseDataObjectTransformationMatrix(joint->LocalMatrix)) + core::matrix4 matrix; + if (!parseDataObjectTransformationMatrix(matrix)) return false; - - // joint->LocalAnimatedMatrix - // joint->LocalAnimatedMatrix.makeInverse(); - // joint->LocalMatrix=tmp*joint->LocalAnimatedMatrix; + joint->transform = matrix; } else if (objectName == "Mesh") { /* frame.Meshes.push_back(SXMesh()); diff --git a/irr/src/SkinnedMesh.cpp b/irr/src/SkinnedMesh.cpp index 938a50e17..cf58121ba 100644 --- a/irr/src/SkinnedMesh.cpp +++ b/irr/src/SkinnedMesh.cpp @@ -7,7 +7,15 @@ #include "CBoneSceneNode.h" #include "IAnimatedMeshSceneNode.h" #include "SSkinMeshBuffer.h" +#include "Transform.h" +#include "aabbox3d.h" +#include "irrMath.h" +#include "matrix4.h" #include "os.h" +#include "vector3d.h" +#include +#include +#include #include #include @@ -48,183 +56,77 @@ void SkinnedMesh::setAnimationSpeed(f32 fps) FramesPerSecond = fps; } -//! returns the animated mesh based -IMesh *SkinnedMesh::getMesh(f32 frame) -{ - // animate(frame,startFrameLoop, endFrameLoop); - if (frame == -1) - return this; +// Keyframe Animation - animateMesh(frame); - skinMesh(); - return this; + +using VariantTransform = SkinnedMesh::SJoint::VariantTransform; +std::vector SkinnedMesh::animateMesh(f32 frame) +{ + assert(HasAnimation); + std::vector result; + result.reserve(AllJoints.size()); + for (auto *joint : AllJoints) + result.push_back(joint->animate(frame)); + return result; } -//-------------------------------------------------------------------------- -// Keyframe Animation -//-------------------------------------------------------------------------- - -//! Animates joints based on frame input -void SkinnedMesh::animateMesh(f32 frame) +core::aabbox3df SkinnedMesh::calculateBoundingBox( + const std::vector &global_transforms) { - if (!HasAnimation || LastAnimatedFrame == frame) - return; - - LastAnimatedFrame = frame; - SkinnedLastFrame = false; - - for (auto *joint : AllJoints) { - // The joints can be animated here with no input from their - // parents, but for setAnimationMode extra checks are needed - // to their parents - joint->keys.updateTransform(frame, - joint->Animatedposition, - joint->Animatedrotation, - joint->Animatedscale); + assert(global_transforms.size() == AllJoints.size()); + core::aabbox3df result = StaticPartsBox; + // skeletal animation + for (u16 i = 0; i < AllJoints.size(); ++i) { + auto box = AllJoints[i]->LocalBoundingBox; + global_transforms[i].transformBoxEx(box); + result.addInternalBox(box); } - - // Note: - // LocalAnimatedMatrix needs to be built at some point, but this function may be called lots of times for - // one render (to play two animations at the same time) LocalAnimatedMatrix only needs to be built once. - // a call to buildAllLocalAnimatedMatrices is needed before skinning the mesh, and before the user gets the joints to move - - //---------------- - // Temp! - buildAllLocalAnimatedMatrices(); - //----------------- - - updateBoundingBox(); -} - -void SkinnedMesh::buildAllLocalAnimatedMatrices() -{ - for (auto *joint : AllJoints) { - // Could be faster: - - if (!joint->keys.empty()) { - joint->GlobalSkinningSpace = false; - - // IRR_TEST_BROKEN_QUATERNION_USE: TODO - switched to getMatrix_transposed instead of getMatrix for downward compatibility. - // Not tested so far if this was correct or wrong before quaternion fix! - // Note that using getMatrix_transposed inverts the rotation. - joint->Animatedrotation.getMatrix_transposed(joint->LocalAnimatedMatrix); - - // --- joint->LocalAnimatedMatrix *= joint->Animatedrotation.getMatrix() --- - f32 *m1 = joint->LocalAnimatedMatrix.pointer(); - core::vector3df &Pos = joint->Animatedposition; - m1[0] += Pos.X * m1[3]; - m1[1] += Pos.Y * m1[3]; - m1[2] += Pos.Z * m1[3]; - m1[4] += Pos.X * m1[7]; - m1[5] += Pos.Y * m1[7]; - m1[6] += Pos.Z * m1[7]; - m1[8] += Pos.X * m1[11]; - m1[9] += Pos.Y * m1[11]; - m1[10] += Pos.Z * m1[11]; - m1[12] += Pos.X * m1[15]; - m1[13] += Pos.Y * m1[15]; - m1[14] += Pos.Z * m1[15]; - // ----------------------------------- - - if (!joint->keys.scale.empty()) { - /* - core::matrix4 scaleMatrix; - scaleMatrix.setScale(joint->Animatedscale); - joint->LocalAnimatedMatrix *= scaleMatrix; - */ - - // -------- joint->LocalAnimatedMatrix *= scaleMatrix ----------------- - core::matrix4 &mat = joint->LocalAnimatedMatrix; - mat[0] *= joint->Animatedscale.X; - mat[1] *= joint->Animatedscale.X; - mat[2] *= joint->Animatedscale.X; - mat[3] *= joint->Animatedscale.X; - mat[4] *= joint->Animatedscale.Y; - mat[5] *= joint->Animatedscale.Y; - mat[6] *= joint->Animatedscale.Y; - mat[7] *= joint->Animatedscale.Y; - mat[8] *= joint->Animatedscale.Z; - mat[9] *= joint->Animatedscale.Z; - mat[10] *= joint->Animatedscale.Z; - mat[11] *= joint->Animatedscale.Z; - // ----------------------------------- - } - } else { - joint->LocalAnimatedMatrix = joint->LocalMatrix; + // rigid animation + for (u16 i = 0; i < AllJoints.size(); ++i) { + for (u32 j : AllJoints[i]->AttachedMeshes) { + auto box = (*SkinningBuffers)[j]->BoundingBox; + global_transforms[i].transformBoxEx(box); + result.addInternalBox(box); } } - SkinnedLastFrame = false; + return result; } -void SkinnedMesh::buildAllGlobalAnimatedMatrices(SJoint *joint, SJoint *parentJoint) +// Software Skinning + +void SkinnedMesh::skinMesh(const std::vector &global_matrices) { - if (!joint) { - for (auto *rootJoint : RootJoints) - buildAllGlobalAnimatedMatrices(rootJoint, 0); - return; - } else { - // Find global matrix... - if (!parentJoint || joint->GlobalSkinningSpace) - joint->GlobalAnimatedMatrix = joint->LocalAnimatedMatrix; - else - joint->GlobalAnimatedMatrix = parentJoint->GlobalAnimatedMatrix * joint->LocalAnimatedMatrix; - } - - for (auto *childJoint : joint->Children) - buildAllGlobalAnimatedMatrices(childJoint, joint); -} - -//-------------------------------------------------------------------------- -// Software Skinning -//-------------------------------------------------------------------------- - -//! Preforms a software skin on this mesh based of joint positions -void SkinnedMesh::skinMesh() -{ - if (!HasAnimation || SkinnedLastFrame) + if (!HasAnimation) return; - //---------------- - // This is marked as "Temp!". A shiny dubloon to whomever can tell me why. - buildAllGlobalAnimatedMatrices(); - //----------------- - - SkinnedLastFrame = true; - if (!HardwareSkinning) { - // rigid animation - for (auto *joint : AllJoints) { - for (u32 attachedMeshIdx : joint->AttachedMeshes) { - SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx]; - Buffer->Transformation = joint->GlobalAnimatedMatrix; - } + // rigid animation + for (size_t i = 0; i < AllJoints.size(); ++i) { + auto *joint = AllJoints[i]; + for (u32 attachedMeshIdx : joint->AttachedMeshes) { + SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx]; + Buffer->Transformation = global_matrices[i]; } - - // clear skinning helper array - for (std::vector &buf : Vertices_Moved) - std::fill(buf.begin(), buf.end(), false); - - // skin starting with the root joints - for (auto *rootJoint : RootJoints) - skinJoint(rootJoint, 0); - - for (auto *buffer : *SkinningBuffers) - buffer->setDirty(EBT_VERTEX); } - updateBoundingBox(); -} -void SkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint) -{ - if (joint->Weights.size()) { - // Find this joints pull on vertices... + // clear skinning helper array + for (std::vector &buf : Vertices_Moved) + std::fill(buf.begin(), buf.end(), false); + + // skin starting with the root joints + for (size_t i = 0; i < AllJoints.size(); ++i) { + auto *joint = AllJoints[i]; + if (joint->Weights.empty()) + continue; + + // Find this joints pull on vertices // Note: It is assumed that the global inversed matrix has been calculated at this point. - core::matrix4 jointVertexPull = joint->GlobalAnimatedMatrix * joint->GlobalInversedMatrix.value(); + core::matrix4 jointVertexPull = global_matrices[i] * joint->GlobalInversedMatrix.value(); core::vector3df thisVertexMove, thisNormalMove; auto &buffersUsed = *SkinningBuffers; - // Skin Vertices Positions and Normals... + // Skin Vertices, Positions and Normals for (const auto &weight : joint->Weights) { // Pull this vertex... jointVertexPull.transformVect(thisVertexMove, weight.StaticPos); @@ -251,14 +153,11 @@ void SkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint) //*(weight._Pos) += thisVertexMove * weight.strength; } - - buffersUsed[weight.buffer_id]->boundingBoxNeedsRecalculated(); } } - // Skin all children - for (auto *childJoint : joint->Children) - skinJoint(childJoint, joint); + for (auto *buffer : *SkinningBuffers) + buffer->setDirty(EBT_VERTEX); } //! Gets joint count. @@ -310,7 +209,7 @@ IMeshBuffer *SkinnedMesh::getMeshBuffer(const video::SMaterial &material) const if (LocalBuffers[i]->getMaterial() == material) return LocalBuffers[i]; } - return 0; + return nullptr; } u32 SkinnedMesh::getTextureSlot(u32 meshbufNr) const @@ -337,29 +236,6 @@ void SkinnedMesh::setDirty(E_BUFFER_TYPE buffer) LocalBuffers[i]->setDirty(buffer); } -//! (This feature is not implemented in irrlicht yet) -bool SkinnedMesh::setHardwareSkinning(bool on) -{ - if (HardwareSkinning != on) { - if (on) { - - // set mesh to static pose... - for (auto *joint : AllJoints) { - for (const auto &weight : joint->Weights) { - const u16 buffer_id = weight.buffer_id; - const u32 vertex_id = weight.vertex_id; - LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos = weight.StaticPos; - LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = weight.StaticNormal; - LocalBuffers[buffer_id]->boundingBoxNeedsRecalculated(); - } - } - } - - HardwareSkinning = on; - } - return HardwareSkinning; -} - void SkinnedMesh::refreshJointCache() { // copy cache from the mesh... @@ -384,113 +260,192 @@ void SkinnedMesh::resetAnimation() LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = weight.StaticNormal; } } - SkinnedLastFrame = false; - LastAnimatedFrame = -1; } -void SkinnedMesh::calculateGlobalMatrices(SJoint *joint, SJoint *parentJoint) +//! Turns the given array of local matrices into an array of global matrices +//! by multiplying with respective parent matrices. +void SkinnedMesh::calculateGlobalMatrices(std::vector &matrices) const { - if (!joint && parentJoint) // bit of protection from endless loops - return; - - // Go through the root bones - if (!joint) { - for (auto *rootJoint : RootJoints) - calculateGlobalMatrices(rootJoint, nullptr); - return; + // Note that the joints are topologically sorted. + for (u16 i = 0; i < AllJoints.size(); ++i) { + if (auto parent_id = AllJoints[i]->ParentJointID) { + matrices[i] = matrices[*parent_id] * matrices[i]; + } } - - if (!parentJoint) - joint->GlobalMatrix = joint->LocalMatrix; - else - joint->GlobalMatrix = parentJoint->GlobalMatrix * joint->LocalMatrix; - - joint->LocalAnimatedMatrix = joint->LocalMatrix; - joint->GlobalAnimatedMatrix = joint->GlobalMatrix; - - if (!joint->GlobalInversedMatrix.has_value()) { // might be pre calculated - joint->GlobalInversedMatrix = joint->GlobalMatrix; - joint->GlobalInversedMatrix->makeInverse(); // slow - } - - for (auto *childJoint : joint->Children) - calculateGlobalMatrices(childJoint, joint); - SkinnedLastFrame = false; } -void SkinnedMesh::checkForAnimation() +bool SkinnedMesh::checkForAnimation() const { - // Check for animation... - HasAnimation = false; for (auto *joint : AllJoints) { if (!joint->keys.empty()) { - HasAnimation = true; - break; + return true; } } - // meshes with weights, are still counted as animated for ragdolls, etc - if (!HasAnimation) { - for (auto *joint : AllJoints) { - if (joint->Weights.size()) { - HasAnimation = true; - break; + // meshes with weights are animatable + for (auto *joint : AllJoints) { + if (!joint->Weights.empty()) { + return true; + } + } + + return false; +} + +void SkinnedMesh::prepareForSkinning() +{ + HasAnimation = checkForAnimation(); + if (!HasAnimation || PreparedForSkinning) + return; + + PreparedForSkinning = true; + + EndFrame = 0.0f; + for (const auto *joint : AllJoints) { + EndFrame = std::max(EndFrame, joint->keys.getEndFrame()); + } + + for (auto *joint : AllJoints) { + for (auto &weight : joint->Weights) { + const u16 buffer_id = weight.buffer_id; + const u32 vertex_id = weight.vertex_id; + + // check for invalid ids + if (buffer_id >= LocalBuffers.size()) { + os::Printer::log("Skinned Mesh: Weight buffer id too large", ELL_WARNING); + weight.buffer_id = weight.vertex_id = 0; + } else if (vertex_id >= LocalBuffers[buffer_id]->getVertexCount()) { + os::Printer::log("Skinned Mesh: Weight vertex id too large", ELL_WARNING); + weight.buffer_id = weight.vertex_id = 0; } } } - if (HasAnimation) { - EndFrame = 0.0f; - for (const auto *joint : AllJoints) { - EndFrame = std::max(EndFrame, joint->keys.getEndFrame()); + for (u32 i = 0; i < Vertices_Moved.size(); ++i) + for (u32 j = 0; j < Vertices_Moved[i].size(); ++j) + Vertices_Moved[i][j] = false; + + // For skinning: cache weight values for speed + for (auto *joint : AllJoints) { + for (auto &weight : joint->Weights) { + const u16 buffer_id = weight.buffer_id; + const u32 vertex_id = weight.vertex_id; + + weight.Moved = &Vertices_Moved[buffer_id][vertex_id]; + weight.StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos; + weight.StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal; } } - if (HasAnimation && !PreparedForSkinning) { - PreparedForSkinning = true; + normalizeWeights(); - // check for bugs: - for (auto *joint : AllJoints) { - for (auto &weight : joint->Weights) { - const u16 buffer_id = weight.buffer_id; - const u32 vertex_id = weight.vertex_id; + for (auto *joint : AllJoints) { + joint->keys.cleanup(); + } +} - // check for invalid ids - if (buffer_id >= LocalBuffers.size()) { - os::Printer::log("Skinned Mesh: Weight buffer id too large", ELL_WARNING); - weight.buffer_id = weight.vertex_id = 0; - } else if (vertex_id >= LocalBuffers[buffer_id]->getVertexCount()) { - os::Printer::log("Skinned Mesh: Weight vertex id too large", ELL_WARNING); - weight.buffer_id = weight.vertex_id = 0; +void SkinnedMesh::calculateStaticBoundingBox() +{ + std::vector> animated(getMeshBufferCount()); + for (u32 mb = 0; mb < getMeshBufferCount(); mb++) + animated[mb] = std::vector(getMeshBuffer(mb)->getVertexCount()); + + for (auto *joint : AllJoints) { + for (auto &weight : joint->Weights) { + const u16 buffer_id = weight.buffer_id; + const u32 vertex_id = weight.vertex_id; + animated[buffer_id][vertex_id] = true; + } + } + + bool first = true; + for (u16 mb = 0; mb < getMeshBufferCount(); mb++) { + for (u32 v = 0; v < getMeshBuffer(mb)->getVertexCount(); v++) { + if (!animated[mb][v]) { + auto pos = getMeshBuffer(mb)->getVertexBuffer()->getPosition(v); + if (!first) { + StaticPartsBox.addInternalPoint(pos); + } else { + StaticPartsBox.reset(pos); + first = false; } } } + } +} - // An array used in skinning - - for (u32 i = 0; i < Vertices_Moved.size(); ++i) - for (u32 j = 0; j < Vertices_Moved[i].size(); ++j) - Vertices_Moved[i][j] = false; - - // For skinning: cache weight values for speed - - for (auto *joint : AllJoints) { - for (auto &weight : joint->Weights) { - const u16 buffer_id = weight.buffer_id; - const u32 vertex_id = weight.vertex_id; - - weight.Moved = &Vertices_Moved[buffer_id][vertex_id]; - weight.StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos; - weight.StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal; - - // weight._Pos=&Buffers[buffer_id]->getVertex(vertex_id)->Pos; +void SkinnedMesh::calculateJointBoundingBoxes() +{ + for (auto *joint : AllJoints) { + bool first = true; + for (auto &weight : joint->Weights) { + if (weight.strength < 1e-6) + continue; + auto pos = weight.StaticPos; + joint->GlobalInversedMatrix.value().transformVect(pos); + if (!first) { + joint->LocalBoundingBox.addInternalPoint(pos); + } else { + joint->LocalBoundingBox.reset(pos); + first = false; } } - - // normalize weights - normalizeWeights(); } - SkinnedLastFrame = false; +} + +void SkinnedMesh::calculateBufferBoundingBoxes() +{ + for (u32 j = 0; j < LocalBuffers.size(); ++j) { + // If we use skeletal animation, this will just be a bounding box of the static pose; + // if we use rigid animation, this will correctly transform the points first. + LocalBuffers[j]->recalculateBoundingBox(); + } +} + +void SkinnedMesh::recalculateBaseBoundingBoxes() { + calculateStaticBoundingBox(); + calculateJointBoundingBoxes(); + calculateBufferBoundingBoxes(); +} + +void SkinnedMesh::topoSortJoints() +{ + size_t n = AllJoints.size(); + + std::vector new_to_old_id; + + std::vector> children(n); + for (u16 i = 0; i < n; ++i) { + if (auto parentId = AllJoints[i]->ParentJointID) + children[*parentId].push_back(i); + else + new_to_old_id.push_back(i); + } + + // Levelorder + for (u16 i = 0; i < n; ++i) { + new_to_old_id.insert(new_to_old_id.end(), + children[new_to_old_id[i]].begin(), + children[new_to_old_id[i]].end()); + } + + std::vector old_to_new_id(n); + for (u16 i = 0; i < n; ++i) + old_to_new_id[new_to_old_id[i]] = i; + + std::vector joints(n); + for (u16 i = 0; i < n; ++i) { + joints[i] = AllJoints[new_to_old_id[i]]; + joints[i]->JointID = i; + if (auto parentId = joints[i]->ParentJointID) + joints[i]->ParentJointID = old_to_new_id[*parentId]; + } + AllJoints = std::move(joints); + + for (u16 i = 0; i < n; ++i) { + if (auto pjid = AllJoints[i]->ParentJointID) + assert(*pjid < i); + } } //! called by loader after populating with mesh and bone data @@ -498,98 +453,44 @@ SkinnedMesh *SkinnedMeshBuilder::finalize() { os::Printer::log("Skinned Mesh - finalize", ELL_DEBUG); - // Make sure we recalc the next frame - LastAnimatedFrame = -1; - SkinnedLastFrame = false; - - // calculate bounding box - for (auto *buffer : LocalBuffers) { - buffer->recalculateBoundingBox(); - } - - if (AllJoints.size() || RootJoints.size()) { - // populate AllJoints or RootJoints, depending on which is empty - if (RootJoints.empty()) { - - for (auto *joint : AllJoints) { - - bool foundParent = false; - for (const auto *parentJoint : AllJoints) { - for (const auto *childJoint : parentJoint->Children) { - if (childJoint == joint) - foundParent = true; - } - } - - if (!foundParent) - RootJoints.push_back(joint); - } - } else { - AllJoints = RootJoints; - } - } - - // Set array sizes... + topoSortJoints(); + // Set array sizes for (u32 i = 0; i < LocalBuffers.size(); ++i) { Vertices_Moved.emplace_back(LocalBuffers[i]->getVertexCount()); } - checkForAnimation(); + prepareForSkinning(); - if (HasAnimation) { - for (auto *joint : AllJoints) { - joint->keys.cleanup(); - } - } - - // Needed for animation and skinning... - - calculateGlobalMatrices(0, 0); - - // rigid animation for non animated meshes + std::vector matrices; + matrices.reserve(AllJoints.size()); for (auto *joint : AllJoints) { + if (const auto *matrix = std::get_if(&joint->transform)) + matrices.push_back(*matrix); + else + matrices.push_back(std::get(joint->transform).buildMatrix()); + } + calculateGlobalMatrices(matrices); + + for (size_t i = 0; i < AllJoints.size(); ++i) { + auto *joint = AllJoints[i]; + if (!joint->GlobalInversedMatrix) { + joint->GlobalInversedMatrix = matrices[i]; + joint->GlobalInversedMatrix->makeInverse(); + } + // rigid animation for non animated meshes for (u32 attachedMeshIdx : joint->AttachedMeshes) { SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx]; - Buffer->Transformation = joint->GlobalAnimatedMatrix; + Buffer->Transformation = matrices[i]; } } - // calculate bounding box - if (LocalBuffers.empty()) - BoundingBox.reset(0, 0, 0); - else { - irr::core::aabbox3df bb(LocalBuffers[0]->BoundingBox); - LocalBuffers[0]->Transformation.transformBoxEx(bb); - BoundingBox.reset(bb); - - for (u32 j = 1; j < LocalBuffers.size(); ++j) { - bb = LocalBuffers[j]->BoundingBox; - LocalBuffers[j]->Transformation.transformBoxEx(bb); - - BoundingBox.addInternalBox(bb); - } - } + recalculateBaseBoundingBoxes(); + StaticPoseBox = calculateBoundingBox(matrices); return this; } -void SkinnedMesh::updateBoundingBox() -{ - if (!SkinningBuffers) - return; - - BoundingBox.reset(0, 0, 0); - - for (auto *buffer : *SkinningBuffers) { - buffer->recalculateBoundingBox(); - core::aabbox3df bb = buffer->BoundingBox; - buffer->Transformation.transformBoxEx(bb); - - BoundingBox.addInternalBox(bb); - } -} - scene::SSkinMeshBuffer *SkinnedMeshBuilder::addMeshBuffer() { scene::SSkinMeshBuffer *buffer = new scene::SSkinMeshBuffer(); @@ -607,14 +508,10 @@ void SkinnedMeshBuilder::addMeshBuffer(SSkinMeshBuffer *meshbuf) SkinnedMesh::SJoint *SkinnedMeshBuilder::addJoint(SJoint *parent) { SJoint *joint = new SJoint; + joint->setParent(parent); + joint->JointID = AllJoints.size(); AllJoints.push_back(joint); - if (!parent) { - // Add root joints to array in finalize() - } else { - // Set parent (Be careful of the mesh loader also setting the parent) - parent->Children.push_back(joint); - } return joint; } @@ -684,73 +581,6 @@ void SkinnedMesh::normalizeWeights() } } -void SkinnedMesh::recoverJointsFromMesh(std::vector &jointChildSceneNodes) -{ - for (u32 i = 0; i < AllJoints.size(); ++i) { - IBoneSceneNode *node = jointChildSceneNodes[i]; - SJoint *joint = AllJoints[i]; - node->setPosition(joint->LocalAnimatedMatrix.getTranslation()); - node->setRotation(joint->LocalAnimatedMatrix.getRotationDegrees()); - node->setScale(joint->LocalAnimatedMatrix.getScale()); - - node->updateAbsolutePosition(); - } -} - -void SkinnedMesh::transferJointsToMesh(const std::vector &jointChildSceneNodes) -{ - for (u32 i = 0; i < AllJoints.size(); ++i) { - const IBoneSceneNode *const node = jointChildSceneNodes[i]; - SJoint *joint = AllJoints[i]; - - joint->LocalAnimatedMatrix.setRotationDegrees(node->getRotation()); - joint->LocalAnimatedMatrix.setTranslation(node->getPosition()); - joint->LocalAnimatedMatrix *= core::matrix4().setScale(node->getScale()); - - joint->GlobalSkinningSpace = (node->getSkinningSpace() == EBSS_GLOBAL); - } - // Make sure we recalc the next frame - LastAnimatedFrame = -1; - SkinnedLastFrame = false; -} - -void SkinnedMesh::addJoints(std::vector &jointChildSceneNodes, - IAnimatedMeshSceneNode *node, ISceneManager *smgr) -{ - // Create new joints - for (u32 i = 0; i < AllJoints.size(); ++i) { - jointChildSceneNodes.push_back(new CBoneSceneNode(0, smgr, 0, i, AllJoints[i]->Name)); - } - - // Match up parents - for (u32 i = 0; i < jointChildSceneNodes.size(); ++i) { - const SJoint *const joint = AllJoints[i]; // should be fine - - s32 parentID = -1; - - for (u32 j = 0; (parentID == -1) && (j < AllJoints.size()); ++j) { - if (i != j) { - const SJoint *const parentTest = AllJoints[j]; - for (u32 n = 0; n < parentTest->Children.size(); ++n) { - if (parentTest->Children[n] == joint) { - parentID = j; - break; - } - } - } - } - - IBoneSceneNode *bone = jointChildSceneNodes[i]; - if (parentID != -1) - bone->setParent(jointChildSceneNodes[parentID]); - else - bone->setParent(node); - - bone->drop(); - } - SkinnedLastFrame = false; -} - void SkinnedMesh::convertMeshToTangents() { // now calculate tangents diff --git a/lib/tiniergltf/tiniergltf.hpp b/lib/tiniergltf/tiniergltf.hpp index 06e2f5356..81ac10ec8 100644 --- a/lib/tiniergltf/tiniergltf.hpp +++ b/lib/tiniergltf/tiniergltf.hpp @@ -916,12 +916,7 @@ struct Node { std::optional skin; std::optional> weights; Node(const Json::Value &o) - : transform(Matrix { - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - }) + : transform(TRS{}) { check(o.isObject()); if (o.isMember("camera")) { diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index e0ac5fff0..ecfe0f2de 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -178,15 +178,6 @@ static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill, matrix.setTextureScale(txs, tys); } -// Evaluate transform chain recursively; irrlicht does not do this for us -static void updatePositionRecursive(scene::ISceneNode *node) -{ - scene::ISceneNode *parent = node->getParent(); - if (parent) - updatePositionRecursive(parent); - node->updateAbsolutePosition(); -} - static bool logOnce(const std::ostringstream &from, std::ostream &log_to) { thread_local std::vector logged; @@ -682,7 +673,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) m_animated_meshnode = m_smgr->addAnimatedMeshSceneNode(mesh, m_matrixnode); m_animated_meshnode->grab(); mesh->drop(); // The scene node took hold of it - m_animated_meshnode->animateJoints(); // Needed for some animations m_animated_meshnode->setScale(m_prop.visual_size); // set vertex colors to ensure alpha is set @@ -693,6 +683,21 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) m_animated_meshnode->forEachMaterial([this] (auto &mat) { mat.BackfaceCulling = m_prop.backface_culling; }); + + m_animated_meshnode->setOnAnimateCallback([&](f32 dtime) { + for (auto &it : m_bone_override) { + auto* bone = m_animated_meshnode->getJointNode(it.first.c_str()); + if (!bone) + continue; + + BoneOverride &props = it.second; + props.dtime_passed += dtime; + + bone->setPosition(props.getPosition(bone->getPosition())); + bone->setRotation(props.getRotationEulerDeg(bone->getRotation())); + bone->setScale(props.getScale(bone->getScale())); + } + }); } else errorstream<<"GenericCAO::addToScene(): Could not load mesh "<updateAbsolutePosition(); - m_animated_meshnode->animateJoints(); - updateBones(dtime); - } } static void setMeshBufferTextureCoords(scene::IMeshBuffer *buf, const v2f *uv, u32 count) @@ -1394,44 +1386,6 @@ void GenericCAO::updateAnimationSpeed() m_animated_meshnode->setAnimationSpeed(m_animation_speed); } -void GenericCAO::updateBones(f32 dtime) -{ - if (!m_animated_meshnode) - return; - if (m_bone_override.empty()) { - m_animated_meshnode->setJointMode(scene::EJUOR_NONE); - return; - } - - m_animated_meshnode->setJointMode(scene::EJUOR_CONTROL); // To write positions to the mesh on render - for (auto &it : m_bone_override) { - std::string bone_name = it.first; - scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str()); - if (!bone) - continue; - - BoneOverride &props = it.second; - props.dtime_passed += dtime; - - bone->setPosition(props.getPosition(bone->getPosition())); - bone->setRotation(props.getRotationEulerDeg(bone->getRotation())); - bone->setScale(props.getScale(bone->getScale())); - } - - // The following is needed for set_bone_pos to propagate to - // attached objects correctly. - // Irrlicht ought to do this, but doesn't when using EJUOR_CONTROL. - for (u32 i = 0; i < m_animated_meshnode->getJointCount(); ++i) { - auto bone = m_animated_meshnode->getJointNode(i); - // Look for the root bone. - if (bone && bone->getParent() == m_animated_meshnode) { - // Update entire skeleton. - bone->updateAbsolutePositionOfAllChildren(); - break; - } - } -} - void GenericCAO::updateAttachments() { ClientActiveObject *parent = getParent(); @@ -1747,7 +1701,6 @@ void GenericCAO::processMessage(const std::string &data) } else { m_bone_override[bone] = props; } - // updateBones(); now called every step } else if (cmd == AO_CMD_ATTACH_TO) { u16 parent_id = readS16(is); std::string bone = deSerializeString16(is); diff --git a/src/client/content_cao.h b/src/client/content_cao.h index 9762684f6..2e765ab6e 100644 --- a/src/client/content_cao.h +++ b/src/client/content_cao.h @@ -286,8 +286,6 @@ public: void updateAnimationSpeed(); - void updateBones(f32 dtime); - void processMessage(const std::string &data) override; bool directReportPunch(v3f dir, const ItemStack *punchitem, diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp index 808dcdd18..45b665bba 100644 --- a/src/client/mesh.cpp +++ b/src/client/mesh.cpp @@ -10,10 +10,9 @@ #include #include #include -#include #include #include "S3DVertex.h" -#include "SMesh.h" +#include #include "SMeshBuffer.h" inline static void applyShadeFactor(video::SColor& color, float factor) @@ -97,11 +96,8 @@ scene::IAnimatedMesh* createCubeMesh(v3f scale) mesh->addMeshBuffer(buf); buf->drop(); } - - scene::SAnimatedMesh *anim_mesh = new scene::SAnimatedMesh(mesh); - mesh->drop(); - scaleMesh(anim_mesh, scale); // also recalculates bounding box - return anim_mesh; + scaleMesh(mesh, scale); // also recalculates bounding box + return mesh; } template diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 04f1959c1..9d54c3957 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -4,7 +4,6 @@ #include "nodedef.h" -#include "SAnimatedMesh.h" #include "itemdef.h" #if CHECK_CLIENT_BUILD() #include "client/mesh.h" @@ -964,13 +963,6 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc // Note: By freshly reading, we get an unencumbered mesh. if (scene::IMesh *src_mesh = client->getMesh(mesh)) { bool apply_bs = false; - // For frame-animated meshes, always get the first frame, - // which holds a model for which we can eventually get the static pose. - while (auto *src_meshes = dynamic_cast(src_mesh)) { - src_mesh = src_meshes->getMesh(0.0f); - src_mesh->grab(); - src_meshes->drop(); - } if (auto *skinned_mesh = dynamic_cast(src_mesh)) { // Compatibility: Animated meshes, as well as static gltf meshes, are not scaled by BS. // See https://github.com/luanti-org/luanti/pull/16112#issuecomment-2881860329 diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index 2a38af68d..159e816c7 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -59,6 +59,8 @@ set (UNITTEST_CLIENT_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_eventmanager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_gameui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_irr_gltf_mesh_loader.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_irr_x_mesh_loader.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_irr_matrix4.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_mesh_compare.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_keycode.cpp PARENT_SCOPE) diff --git a/src/unittest/test_irr_gltf_mesh_loader.cpp b/src/unittest/test_irr_gltf_mesh_loader.cpp index f689838db..6cd6f0f1d 100644 --- a/src/unittest/test_irr_gltf_mesh_loader.cpp +++ b/src/unittest/test_irr_gltf_mesh_loader.cpp @@ -394,36 +394,40 @@ SECTION("simple skin") const auto joints = csm->getAllJoints(); REQUIRE(joints.size() == 3); - const auto findJoint = [&](const std::function &predicate) { - for (std::size_t i = 0; i < joints.size(); ++i) { - if (predicate(joints[i])) { - return joints[i]; + const auto findJoint = [&](const std::function &predicate) { + for (const auto *joint : joints) { + if (predicate(joint)) { + return joint; } } throw std::runtime_error("joint not found"); }; // Check the node hierarchy - const auto parent = findJoint([](auto joint) { - return !joint->Children.empty(); + const auto child = findJoint([&](auto *joint) { + return !!joint->ParentJointID; }); - REQUIRE(parent->Children.size() == 1); - const auto child = parent->Children[0]; - REQUIRE(child != parent); + const auto *parent = joints.at(*child->ParentJointID); SECTION("transformations are correct") { - CHECK(parent->Animatedposition == v3f(0, 0, 0)); - CHECK(parent->Animatedrotation == irr::core::quaternion()); - CHECK(parent->Animatedscale == v3f(1, 1, 1)); - CHECK(parent->GlobalInversedMatrix == irr::core::matrix4()); - const v3f childTranslation(0, 1, 0); - CHECK(child->Animatedposition == childTranslation); - CHECK(child->Animatedrotation == irr::core::quaternion()); - CHECK(child->Animatedscale == v3f(1, 1, 1)); - irr::core::matrix4 inverseBindMatrix; - inverseBindMatrix.setTranslation(-childTranslation); - CHECK(child->GlobalInversedMatrix == inverseBindMatrix); + { + const auto &transform = std::get(parent->transform); + CHECK(transform.translation == v3f(0, 0, 0)); + CHECK(transform.rotation == irr::core::quaternion()); + CHECK(transform.scale == v3f(1, 1, 1)); + CHECK(parent->GlobalInversedMatrix == irr::core::matrix4()); + } + { + const auto &transform = std::get(child->transform); + const v3f translation(0, 1, 0); + CHECK(transform.translation == translation); + CHECK(transform.rotation == irr::core::quaternion()); + CHECK(transform.scale == v3f(1, 1, 1)); + irr::core::matrix4 inverseBindMatrix; + inverseBindMatrix.setTranslation(-translation); + CHECK(child->GlobalInversedMatrix == inverseBindMatrix); + } } SECTION("weights are correct") diff --git a/src/unittest/test_irr_x_mesh_loader.cpp b/src/unittest/test_irr_x_mesh_loader.cpp new file mode 100644 index 000000000..cb61ce7da --- /dev/null +++ b/src/unittest/test_irr_x_mesh_loader.cpp @@ -0,0 +1,111 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "catch_amalgamated.hpp" +#include "content/subgames.h" +#include "filesys.h" + +#include "irrlichttypes.h" +#include "irr_ptr.h" + +#include "EDriverTypes.h" +#include "IFileSystem.h" +#include "IReadFile.h" +#include "ISceneManager.h" +#include "SkinnedMesh.h" +#include "irrlicht.h" + +#include "catch.h" +#include "matrix4.h" + +TEST_CASE("x") { + +const auto gamespec = findSubgame("devtest"); + +if (!gamespec.isValid()) + SKIP(); + +irr::SIrrlichtCreationParameters p; +p.DriverType = video::EDT_NULL; +auto *driver = irr::createDeviceEx(p); + +REQUIRE(driver); + +auto *smgr = driver->getSceneManager(); +const auto loadMesh = [&] (const io::path& filepath) { + irr_ptr file(driver->getFileSystem()->createAndOpenFile(filepath)); + REQUIRE(file); + return smgr->getMesh(file.get()); +}; + +const static auto model_stem = gamespec.gamemods_path + + DIR_DELIM + "testentities" + DIR_DELIM + "models" + DIR_DELIM + "testentities_"; + +SECTION("cool guy") { + const auto *mesh = dynamic_cast(loadMesh(model_stem + "cool_guy.x")); + REQUIRE(mesh); + REQUIRE(mesh->getMeshBufferCount() == 1); + + auto getJointId = [&](auto name) { + return mesh->getJointNumber(name).value(); + }; + + const auto root = getJointId("Root"); + const auto armature = getJointId("Armature"); + const auto armature_body = getJointId("Armature_body"); + const auto armature_arm_r = getJointId("Armature_arm_r"); + + std::vector matrices; + matrices.reserve(mesh->getJointCount()); + for (auto *joint : mesh->getAllJoints()) { + if (const auto *matrix = std::get_if(&joint->transform)) + matrices.push_back(*matrix); + else + matrices.push_back(std::get(joint->transform).buildMatrix()); + } + auto local_matrices = matrices; + mesh->calculateGlobalMatrices(matrices); + + SECTION("joints are topologically sorted") { + REQUIRE(root < armature); + REQUIRE(armature < armature_body); + REQUIRE(armature_body < armature_arm_r); + } + + SECTION("parents are correct") { + const auto get_parent = [&](auto id) { + return mesh->getAllJoints()[id]->ParentJointID; + }; + REQUIRE(!get_parent(root)); + REQUIRE(get_parent(armature).value() == root); + REQUIRE(get_parent(armature_body).value() == armature); + REQUIRE(get_parent(armature_arm_r).value() == armature_body); + } + + SECTION("local matrices are correct") { + REQUIRE(local_matrices[root].equals(core::IdentityMatrix)); + REQUIRE(local_matrices[armature].equals(core::IdentityMatrix)); + REQUIRE(local_matrices[armature_body] == core::matrix4( + -1,0,0,0, + 0,0,1,0, + 0,1,0,0, + 0,2.571201,0,1 + )); + REQUIRE(local_matrices[armature_arm_r] == core::matrix4( + -0.047733,0.997488,-0.05233,0, + 0.901521,0.020464,-0.432251,0, + -0.430095,-0.067809,-0.900233, + 0,-0.545315,0,1,1 + )); + } + + SECTION("global matrices are correct") { + REQUIRE(matrices[armature_body] == local_matrices[armature_body]); + REQUIRE(matrices[armature_arm_r] == + matrices[armature_body] * local_matrices[armature_arm_r]); + } +} + +driver->closeDevice(); +driver->drop(); +}