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
4444import com .jme3 .input .KeyInput ;
4545import com .jme3 .input .controls .ActionListener ;
4646import com .jme3 .input .controls .KeyTrigger ;
47+ import com .jme3 .input .controls .Trigger ;
4748import com .jme3 .light .AmbientLight ;
4849import com .jme3 .light .DirectionalLight ;
4950import com .jme3 .material .Material ;
50- import com .jme3 .math .ColorRGBA ;
5151import com .jme3 .math .FastMath ;
5252import com .jme3 .math .Vector3f ;
5353import com .jme3 .scene .Geometry ;
5858
5959import 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