Skip to content

Commit fab7f60

Browse files
Copilotriccardobl
andcommitted
Changes before error encountered
Agent-Logs-Url: https://github.com/jMonkeyEngine/jmonkeyengine/sessions/0b0ee217-37c6-4d49-9616-3c58c9350a06 Co-authored-by: riccardobl <4943530+riccardobl@users.noreply.github.com>
1 parent 166a5d7 commit fab7f60

1 file changed

Lines changed: 135 additions & 14 deletions

File tree

jme3-core/src/main/java/com/jme3/anim/SkinningControl.java

Lines changed: 135 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060
import java.io.IOException;
6161
import java.nio.Buffer;
6262
import java.nio.FloatBuffer;
63+
import java.util.HashMap;
64+
import java.util.Map;
6365
import java.util.logging.Level;
6466
import java.util.logging.Logger;
6567

@@ -140,6 +142,22 @@ public class SkinningControl extends AbstractControl implements JmeCloneable {
140142
*/
141143
private boolean updateBounds = false;
142144

145+
/**
146+
* Maximum number of vertices processed per frame when updating bounding
147+
* volumes during hardware skinning. Processing is spread across multiple
148+
* frames (the cursor resumes where it left off), keeping each frame's cost
149+
* bounded. {@link Integer#MAX_VALUE} (the default) means all vertices are
150+
* processed in one frame, matching the old behaviour.
151+
*/
152+
private int boundingUpdateBudget = Integer.MAX_VALUE;
153+
154+
/**
155+
* Per-geometry state for incremental bounding-volume updates. Only used
156+
* when {@link #boundingUpdateBudget} is smaller than the vertex count of
157+
* the mesh, allowing the work to be spread over several frames.
158+
*/
159+
private transient Map<Geometry, BoundsUpdateState> boundsUpdateStates = new HashMap<>();
160+
143161
private MatParamOverride numberOfJointsParam = new MatParamOverride(VarType.Int, "NumberOfBones", null);
144162
private MatParamOverride jointMatricesParam = new MatParamOverride(VarType.Matrix4Array, "BoneMatrices", null);
145163

@@ -281,6 +299,43 @@ public boolean isUpdateBounds() {
281299
return updateBounds;
282300
}
283301

302+
/**
303+
* Sets the maximum number of vertices considered per frame when updating
304+
* bounding volumes during hardware skinning. Use this to distribute the
305+
* CPU cost of the bounds update across several frames instead of paying the
306+
* full price in a single frame.
307+
*
308+
* <p>The update cursor advances by at most {@code budget} vertices each
309+
* frame and resumes where it left off the next frame. When a full pass over
310+
* all vertices is complete, the geometry's model bound is refreshed with
311+
* the newly computed values. In the meantime, the geometry keeps the bound
312+
* from the previous completed pass.
313+
*
314+
* <p>A value of {@link Integer#MAX_VALUE} (the default) processes all
315+
* vertices in one frame, matching the original behaviour. Values &le; 0
316+
* are treated as {@link Integer#MAX_VALUE}.
317+
*
318+
* @param budget max vertices per frame (any positive integer; values
319+
* &le; 0 are normalized to {@link Integer#MAX_VALUE})
320+
* @see #getBoundingUpdateBudget()
321+
* @see #setUpdateBounds(boolean)
322+
*/
323+
public void setBoundingUpdateBudget(int budget) {
324+
this.boundingUpdateBudget = (budget <= 0) ? Integer.MAX_VALUE : budget;
325+
boundsUpdateStates.clear();
326+
}
327+
328+
/**
329+
* Returns the maximum number of vertices processed per frame when updating
330+
* bounding volumes during hardware skinning.
331+
*
332+
* @return the bounding-update vertex budget
333+
* @see #setBoundingUpdateBudget(int)
334+
*/
335+
public int getBoundingUpdateBudget() {
336+
return boundingUpdateBudget;
337+
}
338+
284339
/**
285340
* Recursively finds and adds animated geometries to the targets list.
286341
*
@@ -803,17 +858,23 @@ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexB
803858
}
804859

805860
/**
806-
* Computes the bounding volume of an animated mesh from the bind pose
807-
* positions and the current skinning matrices, then sets it on the geometry.
808-
* This is used during hardware skinning to keep culling correct, since the
809-
* GPU-transformed vertex positions are not reflected in the CPU-side vertex
810-
* buffer.
861+
* Computes (or incrementally advances) the bounding volume of an animated
862+
* mesh from the bind-pose positions and the current skinning matrices, then
863+
* sets it on the geometry once a full pass is complete.
864+
*
865+
* <p>When {@link #boundingUpdateBudget} is smaller than the vertex count,
866+
* at most {@code boundingUpdateBudget} vertices are processed each call.
867+
* The per-geometry {@link BoundsUpdateState} records the cursor and the
868+
* in-progress min/max so subsequent calls resume from where they left off.
869+
* The geometry's bound is only updated after all vertices have been visited
870+
* in a single pass; in the meantime it keeps the bound from the last
871+
* completed pass.
811872
*
812873
* @param geometry the geometry whose bound needs to be updated
813874
* @param mesh the animated mesh
814875
* @param offsetMatrices the bone offset matrices for this frame
815876
*/
816-
private static void updateSkinnedMeshBound(Geometry geometry, Mesh mesh,
877+
private void updateSkinnedMeshBound(Geometry geometry, Mesh mesh,
817878
Matrix4f[] offsetMatrices) {
818879
VertexBuffer bindPosVB = mesh.getBuffer(Type.BindPosePosition);
819880
if (bindPosVB == null) {
@@ -831,27 +892,56 @@ private static void updateSkinnedMeshBound(Geometry geometry, Mesh mesh,
831892
int fourMinusMaxWeights = 4 - maxWeightsPerVert;
832893

833894
FloatBuffer bindPos = (FloatBuffer) bindPosVB.getData();
834-
bindPos.rewind();
835895
IndexBuffer boneIndex = IndexBuffer.wrapIndexBuffer(boneIndexVB.getData());
836896
FloatBuffer boneWeightBuf = (FloatBuffer) boneWeightVB.getData();
837-
boneWeightBuf.rewind();
838897
// Use array() when available (heap buffer), otherwise copy to a local array.
839898
float[] weights;
840899
if (boneWeightBuf.hasArray()) {
841900
weights = boneWeightBuf.array();
842901
} else {
843902
weights = new float[boneWeightBuf.limit()];
903+
boneWeightBuf.rewind();
844904
boneWeightBuf.get(weights);
845905
}
846-
int idxWeights = 0;
847906

848907
int numVerts = bindPos.limit() / 3;
849-
float minX = Float.POSITIVE_INFINITY, minY = Float.POSITIVE_INFINITY,
850-
minZ = Float.POSITIVE_INFINITY;
851-
float maxX = Float.NEGATIVE_INFINITY, maxY = Float.NEGATIVE_INFINITY,
852-
maxZ = Float.NEGATIVE_INFINITY;
853908

854-
for (int v = 0; v < numVerts; v++) {
909+
// Decide whether we need incremental (multi-frame) processing.
910+
boolean incremental = (boundingUpdateBudget < numVerts);
911+
BoundsUpdateState state = null;
912+
if (incremental) {
913+
state = boundsUpdateStates.get(geometry);
914+
if (state == null) {
915+
state = new BoundsUpdateState();
916+
boundsUpdateStates.put(geometry, state);
917+
}
918+
}
919+
920+
// Starting vertex and accumulated min/max for this pass.
921+
int startVertex;
922+
float minX, minY, minZ, maxX, maxY, maxZ;
923+
if (state != null && state.nextVertex > 0) {
924+
// Resume an in-progress pass.
925+
startVertex = state.nextVertex;
926+
minX = state.minX; minY = state.minY; minZ = state.minZ;
927+
maxX = state.maxX; maxY = state.maxY; maxZ = state.maxZ;
928+
} else {
929+
// Start a fresh pass.
930+
startVertex = 0;
931+
minX = Float.POSITIVE_INFINITY; minY = Float.POSITIVE_INFINITY;
932+
minZ = Float.POSITIVE_INFINITY;
933+
maxX = Float.NEGATIVE_INFINITY; maxY = Float.NEGATIVE_INFINITY;
934+
maxZ = Float.NEGATIVE_INFINITY;
935+
}
936+
937+
int budget = incremental ? boundingUpdateBudget : numVerts;
938+
int endVertex = Math.min(startVertex + budget, numVerts);
939+
940+
// Position the bind-pose buffer at the correct vertex.
941+
bindPos.position(startVertex * 3);
942+
int idxWeights = startVertex * 4;
943+
944+
for (int v = startVertex; v < endVertex; v++) {
855945
float vtx = bindPos.get();
856946
float vty = bindPos.get();
857947
float vtz = bindPos.get();
@@ -884,6 +974,19 @@ private static void updateSkinnedMeshBound(Geometry geometry, Mesh mesh,
884974
if (rz > maxZ) maxZ = rz;
885975
}
886976

977+
if (endVertex < numVerts) {
978+
// Pass not yet complete – save state and wait for the next frame.
979+
state.nextVertex = endVertex;
980+
state.minX = minX; state.minY = minY; state.minZ = minZ;
981+
state.maxX = maxX; state.maxY = maxY; state.maxZ = maxZ;
982+
return;
983+
}
984+
985+
// Full pass complete – reset cursor and commit the bounding box.
986+
if (state != null) {
987+
state.nextVertex = 0;
988+
}
989+
887990
// Reuse the existing BoundingBox if possible to avoid allocation.
888991
BoundingVolume bv = mesh.getBound();
889992
BoundingBox bbox;
@@ -904,6 +1007,21 @@ private static void updateSkinnedMeshBound(Geometry geometry, Mesh mesh,
9041007
geometry.setModelBound(bbox);
9051008
}
9061009

1010+
/**
1011+
* Holds the incremental bounding-volume update state for a single geometry
1012+
* when {@link #boundingUpdateBudget} limits processing to fewer than all
1013+
* vertices per frame.
1014+
*/
1015+
private static final class BoundsUpdateState {
1016+
int nextVertex = 0;
1017+
float minX = Float.POSITIVE_INFINITY;
1018+
float minY = Float.POSITIVE_INFINITY;
1019+
float minZ = Float.POSITIVE_INFINITY;
1020+
float maxX = Float.NEGATIVE_INFINITY;
1021+
float maxY = Float.NEGATIVE_INFINITY;
1022+
float maxZ = Float.NEGATIVE_INFINITY;
1023+
}
1024+
9071025
/**
9081026
* Serialize this Control to the specified exporter, for example when saving
9091027
* to a J3O file.
@@ -917,6 +1035,7 @@ public void write(JmeExporter ex) throws IOException {
9171035
OutputCapsule oc = ex.getCapsule(this);
9181036
oc.write(armature, "armature", null);
9191037
oc.write(updateBounds, "updateBounds", false);
1038+
oc.write(boundingUpdateBudget, "boundingUpdateBudget", Integer.MAX_VALUE);
9201039
}
9211040

9221041
/**
@@ -932,6 +1051,7 @@ public void read(JmeImporter im) throws IOException {
9321051
InputCapsule in = im.getCapsule(this);
9331052
armature = (Armature) in.readSavable("armature", null);
9341053
updateBounds = in.readBoolean("updateBounds", false);
1054+
boundingUpdateBudget = in.readInt("boundingUpdateBudget", Integer.MAX_VALUE);
9351055

9361056
for (MatParamOverride mpo : spatial.getLocalMatParamOverrides().getArray()) {
9371057
if (mpo.getName().equals("NumberOfBones") || mpo.getName().equals("BoneMatrices")) {
@@ -949,6 +1069,7 @@ public void read(JmeImporter im) throws IOException {
9491069
*/
9501070
private void updateAnimationTargets(Spatial spatial) {
9511071
targets.clear();
1072+
boundsUpdateStates.clear();
9521073
collectAnimatedGeometries(spatial);
9531074
}
9541075

0 commit comments

Comments
 (0)