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 ();
1515
1616 public static final int FPS_60 = 1000 ;
1717 public static final int FPS_24 = 400 ;
@@ -21,6 +21,20 @@ public class Animation {
2121 public static Vector3f TRANSLATE = new Vector3f ();
2222 protected static Vector3f SCALE = new Vector3f (1 , 1 , 1 );
2323 protected static Vector3f TRANSLATION = new Vector3f ();
24+
25+ private static final Matrix4f [] MATRIX = new Matrix4f [220 ];
26+ private static final Matrix4fStack GLOBAL = new Matrix4fStack (220 );
27+ private final static Matrix4f IDENTITY = new Matrix4f ();
28+ private static final Matrix4f TEMP_GLOBAL_TRANSFORM = new Matrix4f ();
29+ private static final Matrix4f TEMP_BONE_RESULT = new Matrix4f ();
30+ private static final Vector3f TEMP_ORIGIN_VECTOR = new Vector3f ();
31+
32+ static {
33+ for (int i = 0 ; i < MATRIX .length ; i ++) {
34+ MATRIX [i ] = new Matrix4f ();
35+ }
36+ }
37+
2438 public final String name ;
2539 public final double animationDuration ;
2640 protected final Skeleton skeleton ;
@@ -29,6 +43,9 @@ public class Animation {
2943 private final AnimationNode [] animationNodes ;
3044 public Map <String , Offset > offsets ;
3145
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 ;
@@ -96,41 +113,111 @@ public float getAnimationTime(double secondsPassed) {
96113 }
97114
98115 public Matrix4f [] getFrameTransform (AnimationInstance instance ) {
99- var boneTransforms = new Matrix4f [this .skeleton .jointMap .size ()];
100- readNodeHierarchy (instance .getCurrentTime (), skeleton .rootNode , new Matrix4f ().identity (), boneTransforms , false );
101- for (int i = 0 ; i < boneTransforms .length ; i ++) {
102- if (boneTransforms [i ] == null ) boneTransforms [i ] = new Matrix4f ();
116+
117+ if (cachedBoneTransforms == null || cachedBoneTransforms .length != skeleton .jointMap .size ()) {
118+ cachedBoneTransforms = new Matrix4f [skeleton .jointMap .size ()];
119+ for (int i = 0 ; i < cachedBoneTransforms .length ; i ++) {
120+ cachedBoneTransforms [i ] = new Matrix4f ();
121+ }
103122 }
104123
105- return boneTransforms ;
124+ // Reset all transforms to identity before populating
125+ for (Matrix4f mat : cachedBoneTransforms ) {
126+ mat .identity ();
127+ }
128+
129+ GLOBAL .identity ();
130+
131+ readNodeHierarchyPooled (instance .getCurrentTime (), skeleton .rootNode , cachedBoneTransforms , false , 0 );
132+ return cachedBoneTransforms ;
106133 }
107134
108135 public void getFrameOffset (AnimationInstance instance ) {
109- this .offsets .forEach ((k , v ) -> {
136+ for (Map .Entry <String , Offset > entry : this .offsets .entrySet ()) {
137+ String k = entry .getKey ();
138+ Offset v = entry .getValue ();
110139 var offsetInstance = instance .offsets .computeIfAbsent (k , a -> new Transform ());
111140 offsetInstance .offset ().zero ();
112141 offsetInstance .scale ().set (1 , 1 );
113142
114143 offsets .get (k ).calcOffset (instance .getCurrentTime (), offsetInstance );
115- });
144+ }
116145 }
117146
118147 public Matrix4f [] getFrameTransform (double secondsPassed ) {
119- var boneTransforms = new Matrix4f [this .skeleton .jointMap .size ()];
120- readNodeHierarchy (getAnimationTime (secondsPassed ), skeleton .rootNode , new Matrix4f ().identity (), boneTransforms , false );
148+ if (cachedBoneTransforms == null || cachedBoneTransforms .length != skeleton .jointMap .size ()) {
149+ cachedBoneTransforms = new Matrix4f [skeleton .jointMap .size ()];
150+ for (int i = 0 ; i < cachedBoneTransforms .length ; i ++) {
151+ cachedBoneTransforms [i ] = new Matrix4f ();
152+ }
153+ }
121154
122- for (int i = 0 ; i < boneTransforms . length ; i ++ ) {
123- if ( boneTransforms [ i ] == null ) boneTransforms [ i ] = new Matrix4f ();
155+ for (Matrix4f mat : cachedBoneTransforms ) {
156+ mat . identity ();
124157 }
125158
126- return boneTransforms ;
159+ readNodeHierarchyPooled (getAnimationTime (secondsPassed ), skeleton .rootNode , cachedBoneTransforms , false , 0 );
160+ return cachedBoneTransforms ;
127161 }
128162
129- private static final Matrix4f matrix = new Matrix4f ();
163+ public void readNodeHierarchyPooled (float animTime , ModelNode node , Matrix4f [] boneTransforms , boolean offsetUsed , int depth ) {
164+
165+
166+ var name = node .name ;
167+ var nodeTransform = MATRIX [depth ].set (node .transform ); // Reuses existing static 'matrix' field
168+
169+ var animationNodeId = skeleton .boneIdMap .getOrDefault (name , -1 );
170+ var bone = skeleton .get (name );
171+
172+ if (animationNodeId != -1 ) {
173+ var animNode = animationNodes [animationNodeId ];
174+
175+ if (animNode != null ) {
176+ var scale = ignoreScaling ? SCALE : AnimationMath .calcInterpolatedScaling (animTime , animNode );
177+ var rotation = AnimationMath .calcInterpolatedRotation (animTime , animNode );
178+
179+ // Reuse pooled Vector3f for "origin" case
180+ Vector3f translation ;
181+ if (name .equalsIgnoreCase ("origin" )) {
182+ translation = TEMP_ORIGIN_VECTOR ;
183+ } else {
184+ translation = AnimationMath .calcInterpolatedPosition (animTime , animNode );
185+ }
186+
187+ if (!offsetUsed ) {
188+ offsetUsed = true ;
189+ translation .add (rootOffset .position ());
190+ rotation .mul (rootOffset .rotation ());
191+ }
192+
193+ if (!isIdentityTransform (translation , scale , rotation , 1e-5f )) nodeTransform .identity ().translationRotateScale (translation , rotation , scale );
194+ }
195+ }
196+
197+
198+
199+ // Reuse pooled Matrix4f for globalTransform
200+ TEMP_GLOBAL_TRANSFORM .set (GLOBAL ).mul (nodeTransform );
201+
202+ if (bone != null && animationNodeId >= 0 && animationNodeId < boneTransforms .length ) {
203+ // Write directly into pre-allocated array slot
204+ TEMP_GLOBAL_TRANSFORM .mul (bone .inverseBindMatrix , boneTransforms [animationNodeId ]);
205+ }
206+
207+ var nextDepth = depth + 1 ;
208+
209+ GLOBAL .pushMatrix ();
210+ GLOBAL .set (TEMP_GLOBAL_TRANSFORM );
211+
212+ for (var child : node .children ) {
213+ readNodeHierarchyPooled (animTime , child , boneTransforms , offsetUsed , nextDepth );
214+ }
215+ GLOBAL .popMatrix ();
216+ }
130217
131218 public void readNodeHierarchy (float animTime , ModelNode node , Matrix4f parentTransform , Matrix4f [] boneTransforms , boolean offsetUsed ) {
132219 var name = node .name ;
133- var nodeTransform = matrix .set (node .transform );
220+ var nodeTransform = MATRIX [ 0 ] .set (node .transform );
134221
135222 var animationNodeId = skeleton .boneIdMap .getOrDefault (name , -1 );
136223 var bone = skeleton .get (name );
@@ -245,6 +332,30 @@ public void calcOffset(float animTime, Transform instance) {
245332 instance .scale ().set (uScale , vScale );
246333 }
247334 }
335+
336+ public static boolean isIdentityTransform (
337+ Vector3f translation ,
338+ Vector3f scale ,
339+ Quaternionf rotation ,
340+ float eps
341+ ) {
342+ boolean tX = translation .x == 0.0f ;
343+ boolean tY = translation .y == 0.0f ;
344+ boolean tZ = translation .z == 0.0f ;
345+
346+ boolean sX = scale .x == 0.0f ;
347+ boolean sY = scale .y == 0.0f ;
348+ boolean sZ = scale .z == 0.0f ;
349+
350+ boolean rX = rotation .x == 0.0f ;
351+ boolean rY = rotation .y == 0.0f ;
352+ boolean rZ = rotation .z == 0.0f ;
353+ boolean rW = rotation .w == 1.0f ;
354+
355+ return tX && tY && tZ
356+ && sX && sY && sZ
357+ && rX && rY && rZ && rW ;
358+ }
248359}
249360
250361
0 commit comments