33import gg .generations .rarecandy .pokeutils .ModelNode ;
44import gg .generations .rarecandy .pokeutils .SkeletalTransform ;
55import org .joml .Matrix4f ;
6+ import org .joml .Matrix4fStack ;
67import org .joml .Quaternionf ;
78import org .joml .Vector3f ;
89
1112import java .util .function .BiConsumer ;
1213
1314public class Animation {
14- private final static Matrix4f IDENTITY = new Matrix4f ();
15-
1615 public static final int FPS_60 = 1000 ;
1716 public static final int FPS_24 = 400 ;
1817 public static final int GLB_SPEED = 30 ;
@@ -21,6 +20,20 @@ public class Animation {
2120 public static Vector3f TRANSLATE = new Vector3f ();
2221 protected static Vector3f SCALE = new Vector3f (1 , 1 , 1 );
2322 protected static Vector3f TRANSLATION = new Vector3f ();
23+
24+ private static final Matrix4f [] MATRIX = new Matrix4f [220 ];
25+ private static final Matrix4fStack GLOBAL = new Matrix4fStack (220 );
26+ private final static Matrix4f IDENTITY = new Matrix4f ();
27+ private static final Matrix4f TEMP_GLOBAL_TRANSFORM = new Matrix4f ();
28+ private static final Matrix4f TEMP_BONE_RESULT = new Matrix4f ();
29+ private static final Vector3f TEMP_ORIGIN_VECTOR = new Vector3f ();
30+
31+ static {
32+ for (int i = 0 ; i < MATRIX .length ; i ++) {
33+ MATRIX [i ] = new Matrix4f ();
34+ }
35+ }
36+
2437 public final int id ;
2538 public final double animationDuration ;
2639 protected final Skeleton skeleton ;
@@ -29,6 +42,10 @@ public class Animation {
2942 private final AnimationNode [] animationNodes ;
3043 public Offset [] offsets ;
3144
45+
46+ private Matrix4f [] cachedBoneTransforms ;
47+ private final Matrix4f cachedIdentity = new Matrix4f ().identity ();
48+
3249 public float ticksPerSecond ;
3350 public boolean loops ;
3451 public boolean ignoreInstancedTime = false ;
@@ -92,13 +109,23 @@ public float getAnimationTime(double secondsPassed) {
92109 }
93110
94111 public Matrix4f [] getFrameTransform (AnimationInstance instance ) {
95- var boneTransforms = new Matrix4f [this .skeleton .jointMap .size ()];
96- readNodeHierarchy (instance .getCurrentTime (), skeleton .rootNode , new Matrix4f ().identity (), boneTransforms , false );
97- for (int i = 0 ; i < boneTransforms .length ; i ++) {
98- if (boneTransforms [i ] == null ) boneTransforms [i ] = new Matrix4f ();
112+
113+ if (cachedBoneTransforms == null || cachedBoneTransforms .length != skeleton .jointMap .size ()) {
114+ cachedBoneTransforms = new Matrix4f [skeleton .jointMap .size ()];
115+ for (int i = 0 ; i < cachedBoneTransforms .length ; i ++) {
116+ cachedBoneTransforms [i ] = new Matrix4f ();
117+ }
118+ }
119+
120+ // Reset all transforms to identity before populating
121+ for (Matrix4f mat : cachedBoneTransforms ) {
122+ mat .identity ();
99123 }
100124
101- return boneTransforms ;
125+ GLOBAL .identity ();
126+
127+ readNodeHierarchy (instance .getCurrentTime (), skeleton .rootNode , cachedBoneTransforms , false , 0 );
128+ return cachedBoneTransforms ;
102129 }
103130
104131 public void getFrameOffset (AnimationInstance instance ) {
@@ -114,21 +141,26 @@ public void getFrameOffset(AnimationInstance instance) {
114141 }
115142
116143 public Matrix4f [] getFrameTransform (double secondsPassed ) {
117- var boneTransforms = new Matrix4f [this .skeleton .jointMap .size ()];
118- readNodeHierarchy (getAnimationTime (secondsPassed ), skeleton .rootNode , new Matrix4f ().identity (), boneTransforms , false );
144+ if (cachedBoneTransforms == null || cachedBoneTransforms .length != skeleton .jointMap .size ()) {
145+ cachedBoneTransforms = new Matrix4f [skeleton .jointMap .size ()];
146+ for (int i = 0 ; i < cachedBoneTransforms .length ; i ++) {
147+ cachedBoneTransforms [i ] = new Matrix4f ();
148+ }
149+ }
119150
120- for (int i = 0 ; i < boneTransforms . length ; i ++ ) {
121- if ( boneTransforms [ i ] == null ) boneTransforms [ i ] = new Matrix4f ();
151+ for (Matrix4f mat : cachedBoneTransforms ) {
152+ mat . identity ();
122153 }
123154
124- return boneTransforms ;
155+ readNodeHierarchy (getAnimationTime (secondsPassed ), skeleton .rootNode , cachedBoneTransforms , false , 0 );
156+ return cachedBoneTransforms ;
125157 }
126158
127- private static final Matrix4f matrix = new Matrix4f ();
159+ public void readNodeHierarchy (float animTime , ModelNode node , Matrix4f [] boneTransforms , boolean offsetUsed , int depth ) {
160+
128161
129- public void readNodeHierarchy (float animTime , ModelNode node , Matrix4f parentTransform , Matrix4f [] boneTransforms , boolean offsetUsed ) {
130162 var name = node .name ;
131- var nodeTransform = matrix .set (node .transform );
163+ var nodeTransform = MATRIX [ depth ] .set (node .transform ); // Reuses existing static 'matrix' field
132164
133165 var animationNodeId = skeleton .boneIdMap .getOrDefault (name , -1 );
134166 var bone = skeleton .get (name );
@@ -139,26 +171,44 @@ public void readNodeHierarchy(float animTime, ModelNode node, Matrix4f parentTra
139171 if (animNode != null ) {
140172 var scale = ignoreScaling ? SCALE : AnimationMath .calcInterpolatedScaling (animTime , animNode );
141173 var rotation = AnimationMath .calcInterpolatedRotation (animTime , animNode );
142- var translation = name .equalsIgnoreCase ("origin" ) ? new Vector3f () : AnimationMath .calcInterpolatedPosition (animTime , animNode );
143174
144- if (!offsetUsed ) {
175+ // Reuse pooled Vector3f for "origin" case
176+ Vector3f translation ;
177+ if (name .equalsIgnoreCase ("origin" )) {
178+ translation = TEMP_ORIGIN_VECTOR .set (0 );
179+ } else {
180+ translation = AnimationMath .calcInterpolatedPosition (animTime , animNode );
181+ }
182+
183+ if (!offsetUsed ) {
145184 offsetUsed = true ;
146185 translation .add (rootOffset .position ());
147186 rotation .mul (rootOffset .rotation ());
148187 }
149188
150- nodeTransform .identity ().translationRotateScale (translation , rotation , scale );
189+ if (! isIdentityTransform ( translation , scale , rotation , 1e-5f )) nodeTransform .identity ().translationRotateScale (translation , rotation , scale );
151190 }
152191 }
153192
154- var globalTransform = parentTransform .mul (nodeTransform , new Matrix4f ());
155193
156- if (bone != null ) {
157- boneTransforms [animationNodeId ] = globalTransform .mul (bone .inverseBindMatrix , new Matrix4f ());
194+
195+ // Reuse pooled Matrix4f for globalTransform
196+ TEMP_GLOBAL_TRANSFORM .set (GLOBAL ).mul (nodeTransform );
197+
198+ if (bone != null && animationNodeId >= 0 && animationNodeId < boneTransforms .length ) {
199+ // Write directly into pre-allocated array slot
200+ TEMP_GLOBAL_TRANSFORM .mul (bone .inverseBindMatrix , boneTransforms [animationNodeId ]);
158201 }
159202
160- for (var child : node .children )
161- readNodeHierarchy (animTime , child , globalTransform , boneTransforms , offsetUsed );
203+ var nextDepth = depth + 1 ;
204+
205+ GLOBAL .pushMatrix ();
206+ GLOBAL .set (TEMP_GLOBAL_TRANSFORM );
207+
208+ for (var child : node .children ) {
209+ readNodeHierarchy (animTime , child , boneTransforms , offsetUsed , nextDepth );
210+ }
211+ GLOBAL .popMatrix ();
162212 }
163213
164214 private boolean isNaN (Matrix4f nodeTransform ) {
@@ -243,6 +293,30 @@ public void calcOffset(float animTime, Transform instance) {
243293 instance .scale ().set (uScale , vScale );
244294 }
245295 }
296+
297+ public static boolean isIdentityTransform (
298+ Vector3f translation ,
299+ Vector3f scale ,
300+ Quaternionf rotation ,
301+ float eps
302+ ) {
303+ boolean tX = translation .x == 0.0f ;
304+ boolean tY = translation .y == 0.0f ;
305+ boolean tZ = translation .z == 0.0f ;
306+
307+ boolean sX = scale .x == 0.0f ;
308+ boolean sY = scale .y == 0.0f ;
309+ boolean sZ = scale .z == 0.0f ;
310+
311+ boolean rX = rotation .x == 0.0f ;
312+ boolean rY = rotation .y == 0.0f ;
313+ boolean rZ = rotation .z == 0.0f ;
314+ boolean rW = rotation .w == 1.0f ;
315+
316+ return tX && tY && tZ
317+ && sX && sY && sZ
318+ && rX && rY && rZ && rW ;
319+ }
246320}
247321
248322
0 commit comments