Skip to content

Commit 6c02ec9

Browse files
authored
Merge pull request #2457 from capdevon/capdevon-TestLodGeneration
jme3-examples: TestLodGeneration - test code optimization
2 parents 0f094fc + 306f921 commit 6c02ec9

File tree

1 file changed

+85
-43
lines changed

1 file changed

+85
-43
lines changed

jme3-examples/src/main/java/jme3test/stress/TestLodGeneration.java

Lines changed: 85 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009-2021 jMonkeyEngine
2+
* Copyright (c) 2009-2025 jMonkeyEngine
33
* All rights reserved.
44
*
55
* Redistribution and use in source and binary forms, with or without
@@ -44,10 +44,10 @@
4444
import com.jme3.input.KeyInput;
4545
import com.jme3.input.controls.ActionListener;
4646
import com.jme3.input.controls.KeyTrigger;
47+
import com.jme3.input.controls.Trigger;
4748
import com.jme3.light.AmbientLight;
4849
import com.jme3.light.DirectionalLight;
4950
import com.jme3.material.Material;
50-
import com.jme3.math.ColorRGBA;
5151
import com.jme3.math.FastMath;
5252
import com.jme3.math.Vector3f;
5353
import com.jme3.scene.Geometry;
@@ -58,102 +58,116 @@
5858

5959
import jme3tools.optimize.LodGenerator;
6060

61-
public class TestLodGeneration extends SimpleApplication {
61+
public class TestLodGeneration extends SimpleApplication implements ActionListener {
6262

6363
public static void main(String[] args) {
6464
TestLodGeneration app = new TestLodGeneration();
6565
app.start();
6666
}
6767

68-
private boolean wireFrame = false;
68+
private boolean wireframe = false;
69+
// Current reduction value for LOD generation (0.0 to 1.0)
6970
private float reductionValue = 0.0f;
7071
private int lodLevel = 0;
7172
private BitmapText hudText;
72-
final private List<Geometry> listGeoms = new ArrayList<>();
73-
final private ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(5);
73+
private final List<Geometry> listGeoms = new ArrayList<>();
74+
private final ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(5);
7475

7576
@Override
7677
public void simpleInitApp() {
7778

79+
// --- Lighting Setup ---
7880
DirectionalLight dl = new DirectionalLight();
7981
dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
8082
rootNode.addLight(dl);
8183

8284
AmbientLight al = new AmbientLight();
83-
al.setColor(ColorRGBA.White.mult(0.6f));
8485
rootNode.addLight(al);
8586

87+
// --- Model Loading and Setup ---
8688
// model = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml");
8789
Node model = (Node) assetManager.loadModel("Models/Jaime/Jaime.j3o");
8890
BoundingBox b = ((BoundingBox) model.getWorldBound());
8991
model.setLocalScale(1.2f / (b.getYExtent() * 2));
9092
// model.setLocalTranslation(0,-(b.getCenter().y - b.getYExtent())* model.getLocalScale().y, 0);
93+
94+
// Iterate through the model's children and collect all Geometry objects
9195
for (Spatial spatial : model.getChildren()) {
9296
if (spatial instanceof Geometry) {
9397
listGeoms.add((Geometry) spatial);
9498
}
9599
}
96100

97-
ChaseCamera chaseCam = new ChaseCamera(cam, inputManager);
98-
model.addControl(chaseCam);
101+
// --- Camera Setup ---
102+
ChaseCamera chaseCam = new ChaseCamera(cam, model, inputManager);
99103
chaseCam.setLookAtOffset(b.getCenter());
100104
chaseCam.setDefaultDistance(5);
101105
chaseCam.setMinVerticalRotation(-FastMath.HALF_PI + 0.01f);
102106
chaseCam.setZoomSensitivity(0.5f);
103107

104108
SkinningControl skControl = model.getControl(SkinningControl.class);
105109
if (skControl != null) {
110+
// Disable skinning control if found. This is an optimization for static LOD generation
111+
// as skinning computation is not needed when generating LODs.
106112
skControl.setEnabled(false);
107113
}
108114

115+
// --- Initial LOD Generation ---
116+
// Set initial reduction value and LOD level
109117
reductionValue = 0.80f;
110118
lodLevel = 1;
119+
120+
// Generate LODs for each geometry in the model
111121
for (final Geometry geom : listGeoms) {
112122
LodGenerator lodGenerator = new LodGenerator(geom);
113123
lodGenerator.bakeLods(LodGenerator.TriangleReductionMethod.PROPORTIONAL, reductionValue);
114124
geom.setLodLevel(lodLevel);
115125
}
116126

117127
rootNode.attachChild(model);
128+
// Disable the default fly camera as we are using a chase camera
118129
flyCam.setEnabled(false);
119130

120-
guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
131+
// --- HUD Setup ---
121132
hudText = new BitmapText(guiFont);
122-
hudText.setSize(guiFont.getCharSet().getRenderedSize());
123133
hudText.setText(computeNbTri() + " tris");
124-
hudText.setLocalTranslation(cam.getWidth() / 2, hudText.getLineHeight(), 0);
134+
hudText.setLocalTranslation(cam.getWidth() / 2f, hudText.getLineHeight(), 0);
125135
guiNode.attachChild(hudText);
126136

127-
inputManager.addListener(new ActionListener() {
128-
@Override
129-
public void onAction(String name, boolean isPressed, float tpf) {
130-
if (isPressed) {
131-
if (name.equals("plus")) {
132-
reductionValue += 0.05f;
133-
updateLod();
134-
}
135-
if (name.equals("minus")) {
136-
reductionValue -= 0.05f;
137-
updateLod();
138-
}
139-
if (name.equals("wireFrame")) {
140-
wireFrame = !wireFrame;
141-
for (Geometry geom : listGeoms) {
142-
Material mat = geom.getMaterial();
143-
mat.getAdditionalRenderState().setWireframe(wireFrame);
144-
}
145-
}
146-
}
137+
// Register input mappings for user interaction
138+
registerInputMappings();
139+
}
140+
141+
@Override
142+
public void onAction(String name, boolean isPressed, float tpf) {
143+
if (!isPressed) return;
144+
145+
if (name.equals("plus")) {
146+
reductionValue += 0.05f;
147+
updateLod();
148+
149+
} else if (name.equals("minus")) {
150+
reductionValue -= 0.05f;
151+
updateLod();
152+
153+
} else if (name.equals("wireframe")) {
154+
wireframe = !wireframe;
155+
for (Geometry geom : listGeoms) {
156+
Material mat = geom.getMaterial();
157+
mat.getAdditionalRenderState().setWireframe(wireframe);
147158
}
148-
}, "plus", "minus", "wireFrame");
159+
}
160+
}
149161

150-
inputManager.addMapping("plus", new KeyTrigger(KeyInput.KEY_ADD));
151-
inputManager.addMapping("minus", new KeyTrigger(KeyInput.KEY_SUBTRACT));
152-
inputManager.addMapping("wireFrame", new KeyTrigger(KeyInput.KEY_SPACE));
162+
private void registerInputMappings() {
163+
addMapping("plus", new KeyTrigger(KeyInput.KEY_P));
164+
addMapping("minus", new KeyTrigger(KeyInput.KEY_L));
165+
addMapping("wireframe", new KeyTrigger(KeyInput.KEY_SPACE));
153166
}
154167

155-
@Override
156-
public void simpleUpdate(float tpf) {
168+
private void addMapping(String mappingName, Trigger... triggers) {
169+
inputManager.addMapping(mappingName, triggers);
170+
inputManager.addListener(this, mappingName);
157171
}
158172

159173
@Override
@@ -163,14 +177,20 @@ public void destroy() {
163177
}
164178

165179
private void updateLod() {
180+
// Clamp the reduction value between 0.0 and 1.0 to ensure it's within valid range
166181
reductionValue = FastMath.clamp(reductionValue, 0.0f, 1.0f);
167182
makeLod(LodGenerator.TriangleReductionMethod.PROPORTIONAL, reductionValue, 1);
168183
}
169184

185+
/**
186+
* Computes the total number of triangles currently displayed by all geometries.
187+
* @return The total number of triangles.
188+
*/
170189
private int computeNbTri() {
171190
int nbTri = 0;
172191
for (Geometry geom : listGeoms) {
173192
Mesh mesh = geom.getMesh();
193+
// Check if the mesh has LOD levels
174194
if (mesh.getNumLodLevels() > 0) {
175195
nbTri += mesh.getLodLevel(lodLevel).getNumElements();
176196
} else {
@@ -180,24 +200,46 @@ private int computeNbTri() {
180200
return nbTri;
181201
}
182202

183-
private void makeLod(final LodGenerator.TriangleReductionMethod method, final float value, final int ll) {
203+
/**
204+
* Generates and applies LOD levels to the geometries in a background thread.
205+
*
206+
* @param reductionMethod The triangle reduction method to use (e.g., PROPORTIONAL).
207+
* @param reductionPercentage The percentage of triangles to reduce (0.0 to 1.0).
208+
* @param targetLodLevel The index of the LOD level to set active after generation.
209+
*/
210+
private void makeLod(final LodGenerator.TriangleReductionMethod reductionMethod,
211+
final float reductionPercentage, final int targetLodLevel) {
212+
213+
// --- Asynchronous LOD Generation ---
214+
// Execute the LOD generation process in the background thread pool.
184215
exec.execute(new Runnable() {
185216
@Override
186217
public void run() {
187218
for (final Geometry geom : listGeoms) {
188219
LodGenerator lodGenerator = new LodGenerator(geom);
189-
final VertexBuffer[] lods = lodGenerator.computeLods(method, value);
220+
final VertexBuffer[] lods = lodGenerator.computeLods(reductionMethod, reductionPercentage);
190221

222+
// --- JME Thread Synchronization ---
223+
// Mesh modifications and scene graph updates must be done on the main thread.
191224
enqueue(new Callable<Void>() {
192225
@Override
193226
public Void call() throws Exception {
194227
geom.getMesh().setLodLevels(lods);
228+
229+
// Reset lodLevel to 0 initially
195230
lodLevel = 0;
196-
if (geom.getMesh().getNumLodLevels() > ll) {
197-
lodLevel = ll;
231+
// If the generated LOD levels are more than the target, set to target LOD
232+
if (geom.getMesh().getNumLodLevels() > targetLodLevel) {
233+
lodLevel = targetLodLevel;
198234
}
199235
geom.setLodLevel(lodLevel);
200-
hudText.setText(computeNbTri() + " tris");
236+
237+
int nbTri = computeNbTri();
238+
hudText.setText(nbTri + " tris");
239+
240+
// Print debug information to the console
241+
System.out.println(geom + " lodLevel: " + lodLevel + ", numLodLevels: " + geom.getMesh().getNumLodLevels()
242+
+ ", reductionValue: " + reductionValue + ", triangles: " + nbTri);
201243
return null;
202244
}
203245
});

0 commit comments

Comments
 (0)