1
1
/*
2
- * Copyright (c) 2009-2021 jMonkeyEngine
2
+ * Copyright (c) 2009-2025 jMonkeyEngine
3
3
* All rights reserved.
4
4
*
5
5
* Redistribution and use in source and binary forms, with or without
44
44
import com .jme3 .input .KeyInput ;
45
45
import com .jme3 .input .controls .ActionListener ;
46
46
import com .jme3 .input .controls .KeyTrigger ;
47
+ import com .jme3 .input .controls .Trigger ;
47
48
import com .jme3 .light .AmbientLight ;
48
49
import com .jme3 .light .DirectionalLight ;
49
50
import com .jme3 .material .Material ;
50
- import com .jme3 .math .ColorRGBA ;
51
51
import com .jme3 .math .FastMath ;
52
52
import com .jme3 .math .Vector3f ;
53
53
import com .jme3 .scene .Geometry ;
58
58
59
59
import jme3tools .optimize .LodGenerator ;
60
60
61
- public class TestLodGeneration extends SimpleApplication {
61
+ public class TestLodGeneration extends SimpleApplication implements ActionListener {
62
62
63
63
public static void main (String [] args ) {
64
64
TestLodGeneration app = new TestLodGeneration ();
65
65
app .start ();
66
66
}
67
67
68
- private boolean wireFrame = false ;
68
+ private boolean wireframe = false ;
69
+ // Current reduction value for LOD generation (0.0 to 1.0)
69
70
private float reductionValue = 0.0f ;
70
71
private int lodLevel = 0 ;
71
72
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 );
74
75
75
76
@ Override
76
77
public void simpleInitApp () {
77
78
79
+ // --- Lighting Setup ---
78
80
DirectionalLight dl = new DirectionalLight ();
79
81
dl .setDirection (new Vector3f (-1 , -1 , -1 ).normalizeLocal ());
80
82
rootNode .addLight (dl );
81
83
82
84
AmbientLight al = new AmbientLight ();
83
- al .setColor (ColorRGBA .White .mult (0.6f ));
84
85
rootNode .addLight (al );
85
86
87
+ // --- Model Loading and Setup ---
86
88
// model = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml");
87
89
Node model = (Node ) assetManager .loadModel ("Models/Jaime/Jaime.j3o" );
88
90
BoundingBox b = ((BoundingBox ) model .getWorldBound ());
89
91
model .setLocalScale (1.2f / (b .getYExtent () * 2 ));
90
92
// 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
91
95
for (Spatial spatial : model .getChildren ()) {
92
96
if (spatial instanceof Geometry ) {
93
97
listGeoms .add ((Geometry ) spatial );
94
98
}
95
99
}
96
100
97
- ChaseCamera chaseCam = new ChaseCamera ( cam , inputManager );
98
- model . addControl ( chaseCam );
101
+ // --- Camera Setup ---
102
+ ChaseCamera chaseCam = new ChaseCamera ( cam , model , inputManager );
99
103
chaseCam .setLookAtOffset (b .getCenter ());
100
104
chaseCam .setDefaultDistance (5 );
101
105
chaseCam .setMinVerticalRotation (-FastMath .HALF_PI + 0.01f );
102
106
chaseCam .setZoomSensitivity (0.5f );
103
107
104
108
SkinningControl skControl = model .getControl (SkinningControl .class );
105
109
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.
106
112
skControl .setEnabled (false );
107
113
}
108
114
115
+ // --- Initial LOD Generation ---
116
+ // Set initial reduction value and LOD level
109
117
reductionValue = 0.80f ;
110
118
lodLevel = 1 ;
119
+
120
+ // Generate LODs for each geometry in the model
111
121
for (final Geometry geom : listGeoms ) {
112
122
LodGenerator lodGenerator = new LodGenerator (geom );
113
123
lodGenerator .bakeLods (LodGenerator .TriangleReductionMethod .PROPORTIONAL , reductionValue );
114
124
geom .setLodLevel (lodLevel );
115
125
}
116
126
117
127
rootNode .attachChild (model );
128
+ // Disable the default fly camera as we are using a chase camera
118
129
flyCam .setEnabled (false );
119
130
120
- guiFont = assetManager . loadFont ( "Interface/Fonts/Default.fnt" );
131
+ // --- HUD Setup ---
121
132
hudText = new BitmapText (guiFont );
122
- hudText .setSize (guiFont .getCharSet ().getRenderedSize ());
123
133
hudText .setText (computeNbTri () + " tris" );
124
- hudText .setLocalTranslation (cam .getWidth () / 2 , hudText .getLineHeight (), 0 );
134
+ hudText .setLocalTranslation (cam .getWidth () / 2f , hudText .getLineHeight (), 0 );
125
135
guiNode .attachChild (hudText );
126
136
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 );
147
158
}
148
- }, "plus" , "minus" , "wireFrame" );
159
+ }
160
+ }
149
161
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 ));
153
166
}
154
167
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 );
157
171
}
158
172
159
173
@ Override
@@ -163,14 +177,20 @@ public void destroy() {
163
177
}
164
178
165
179
private void updateLod () {
180
+ // Clamp the reduction value between 0.0 and 1.0 to ensure it's within valid range
166
181
reductionValue = FastMath .clamp (reductionValue , 0.0f , 1.0f );
167
182
makeLod (LodGenerator .TriangleReductionMethod .PROPORTIONAL , reductionValue , 1 );
168
183
}
169
184
185
+ /**
186
+ * Computes the total number of triangles currently displayed by all geometries.
187
+ * @return The total number of triangles.
188
+ */
170
189
private int computeNbTri () {
171
190
int nbTri = 0 ;
172
191
for (Geometry geom : listGeoms ) {
173
192
Mesh mesh = geom .getMesh ();
193
+ // Check if the mesh has LOD levels
174
194
if (mesh .getNumLodLevels () > 0 ) {
175
195
nbTri += mesh .getLodLevel (lodLevel ).getNumElements ();
176
196
} else {
@@ -180,24 +200,46 @@ private int computeNbTri() {
180
200
return nbTri ;
181
201
}
182
202
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.
184
215
exec .execute (new Runnable () {
185
216
@ Override
186
217
public void run () {
187
218
for (final Geometry geom : listGeoms ) {
188
219
LodGenerator lodGenerator = new LodGenerator (geom );
189
- final VertexBuffer [] lods = lodGenerator .computeLods (method , value );
220
+ final VertexBuffer [] lods = lodGenerator .computeLods (reductionMethod , reductionPercentage );
190
221
222
+ // --- JME Thread Synchronization ---
223
+ // Mesh modifications and scene graph updates must be done on the main thread.
191
224
enqueue (new Callable <Void >() {
192
225
@ Override
193
226
public Void call () throws Exception {
194
227
geom .getMesh ().setLodLevels (lods );
228
+
229
+ // Reset lodLevel to 0 initially
195
230
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 ;
198
234
}
199
235
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 );
201
243
return null ;
202
244
}
203
245
});
0 commit comments