Skip to content

Commit b1bbedf

Browse files
authored
Update LodControl.java
1 parent 389d91a commit b1bbedf

File tree

1 file changed

+102
-52
lines changed

1 file changed

+102
-52
lines changed

jme3-core/src/main/java/com/jme3/scene/control/LodControl.java

Lines changed: 102 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
*/
3232
package com.jme3.scene.control;
3333

34+
import com.jme3.util.AreaUtils;
3435
import com.jme3.bounding.BoundingVolume;
3536
import com.jme3.export.InputCapsule;
3637
import com.jme3.export.JmeExporter;
@@ -45,19 +46,31 @@
4546
import com.jme3.scene.Spatial;
4647
import com.jme3.util.clone.JmeCloneable;
4748
import java.io.IOException;
49+
import java.util.logging.Level;
50+
import java.util.logging.Logger;
4851

4952
/**
50-
* Determines what Level of Detail a spatial should be, based on how many pixels
51-
* on the screen the spatial is taking up. The more pixels covered, the more
52-
* detailed the spatial should be. It calculates the area of the screen that the
53-
* spatial covers by using its bounding box. When initializing, it will ask the
54-
* spatial for how many triangles it has for each LOD. It then uses that, along
55-
* with the trisPerPixel value to determine what LOD it should be at. It
56-
* requires the camera to do this. The controlRender method is called each frame
57-
* and will update the spatial's LOD if the camera has moved by a specified
58-
* amount.
53+
* `LodControl` automatically adjusts the Level of Detail (LOD) for a
54+
* {@link com.jme3.scene.Geometry Geometry} based on its projected screen area.
55+
* The more pixels a spatial covers on the screen, the higher its detail level
56+
* will be. This control uses the geometry's bounding volume and the active
57+
* camera to estimate the screen coverage.
58+
* <p>
59+
* Upon attachment to a {@link com.jme3.scene.Geometry Geometry}, it queries
60+
* the mesh for the triangle counts at each available LOD level. During
61+
* rendering, it continuously monitors the geometry's screen size and
62+
* updates the LOD level if the change in screen area (or distance)
63+
* exceeds a specified tolerance.
64+
* </p>
65+
* <p>
66+
* This control is ideal for optimizing rendering performance by ensuring
67+
* that objects only use the necessary amount of detail based on their
68+
* visual prominence on screen.
69+
* </p>
5970
*/
60-
public class LodControl extends AbstractControl implements Cloneable, JmeCloneable {
71+
public class LodControl extends AbstractControl implements JmeCloneable {
72+
73+
private static final Logger logger = Logger.getLogger(LodControl.class.getName());
6174

6275
private float trisPerPixel = 1f;
6376
private float distTolerance = 1f;
@@ -67,18 +80,22 @@ public class LodControl extends AbstractControl implements Cloneable, JmeCloneab
6780
private int[] numTris;
6881

6982
/**
70-
* Creates a new
71-
* <code>LodControl</code>.
83+
* Creates a new `LodControl`.
84+
* <p>
85+
* You must attach this control to a {@link com.jme3.scene.Geometry}
86+
* for it to function correctly.
87+
* </p>
7288
*/
7389
public LodControl() {
7490
}
7591

7692
/**
77-
* Returns the distance tolerance for changing LOD.
78-
*
79-
* @return the distance tolerance for changing LOD.
93+
* Returns the distance tolerance for changing LOD. The LOD level will
94+
* only be changed if the geometry's distance from the camera has changed
95+
* by more than this tolerance since the last update. This prevents
96+
* frequent LOD flickering when the camera moves slightly.
8097
*
81-
* @see #setDistTolerance(float)
98+
* @return The distance tolerance in world units.
8299
*/
83100
public float getDistTolerance() {
84101
return distTolerance;
@@ -87,105 +104,138 @@ public float getDistTolerance() {
87104
/**
88105
* Specifies the distance tolerance for changing the LOD level on the
89106
* geometry. The LOD level will only get changed if the geometry has moved
90-
* this distance beyond the current LOD level.
107+
* this distance beyond the current LOD level. Setting a higher tolerance
108+
* reduces LOD changes but might make transitions less precise.
91109
*
92-
* @param distTolerance distance tolerance for changing LOD
110+
* @param distTolerance The distance tolerance in world units (must be non-negative).
111+
* @throws IllegalArgumentException if `distTolerance` is negative.
93112
*/
94113
public void setDistTolerance(float distTolerance) {
114+
if (distTolerance < 0) {
115+
throw new IllegalArgumentException("Distance tolerance cannot be negative.");
116+
}
95117
this.distTolerance = distTolerance;
96118
}
97119

98120
/**
99121
* Returns the triangles per pixel value.
100122
*
101123
* @return the triangles per pixel value.
102-
*
103-
* @see #setTrisPerPixel(float)
104124
*/
105125
public float getTrisPerPixel() {
106126
return trisPerPixel;
107127
}
108128

109129
/**
110-
* Sets the triangles per pixel value. The
111-
* <code>LodControl</code> will use this value as an error metric to
112-
* determine which LOD level to use based on the geometry's area on the
113-
* screen.
130+
* Sets the triangles per pixel value. The `LodControl` uses this value
131+
* as an error metric to determine which LOD level to use based on the
132+
* geometry's projected area on the screen. A higher value means the
133+
* object will appear more detailed for a given screen size.
114134
*
115-
* @param trisPerPixel triangles per pixel
135+
* @param trisPerPixel The desired number of triangles per screen pixel (must be positive).
136+
* @throws IllegalArgumentException if `trisPerPixel` is zero or negative.
116137
*/
117138
public void setTrisPerPixel(float trisPerPixel) {
139+
if (trisPerPixel <= 0) {
140+
throw new IllegalArgumentException("Triangles per pixel must be positive.");
141+
}
118142
this.trisPerPixel = trisPerPixel;
119143
}
120144

121145
@Override
122146
public void setSpatial(Spatial spatial) {
123147
if (spatial != null && !(spatial instanceof Geometry)) {
124-
throw new IllegalArgumentException("LodControl can only be attached to Geometry!");
148+
throw new IllegalArgumentException("Invalid Spatial type for LodControl. " +
149+
"Expected a Geometry, but got: " + spatial.getClass().getSimpleName());
125150
}
126151

127152
super.setSpatial(spatial);
128-
129-
if(spatial != null) {
153+
154+
if (spatial != null) {
130155
Geometry geom = (Geometry) spatial;
131156
Mesh mesh = geom.getMesh();
132157
numLevels = mesh.getNumLodLevels();
133-
numTris = new int[numLevels];
134-
for (int i = numLevels - 1; i >= 0; i--) {
135-
numTris[i] = mesh.getTriangleCount(i);
158+
159+
logger.log(Level.INFO, "LodControl attached to Geometry ''{0}''. Detected {1} LOD levels.",
160+
new Object[]{spatial.getName(), numLevels});
161+
162+
if (numLevels > 0) {
163+
numTris = new int[numLevels];
164+
// Store triangle counts from lowest LOD (highest index) to highest LOD (index 0)
165+
// This makes the lookup loop in controlRender more efficient.
166+
for (int i = 0; i < numLevels; i++) {
167+
numTris[i] = mesh.getTriangleCount(i);
168+
}
169+
} else {
170+
logger.log(Level.WARNING, "LodControl attached to Geometry {0} but its Mesh has no LOD levels defined. " +
171+
"Control will have no effect.", spatial.getName());
136172
}
137173
} else {
138174
numLevels = 0;
139175
numTris = null;
176+
lastDistance = 0f;
177+
lastLevel = 0;
140178
}
141179
}
142180

143181
@Override
144182
public Object jmeClone() {
145-
LodControl clone = (LodControl)super.jmeClone();
183+
LodControl clone = (LodControl) super.jmeClone();
146184
clone.lastDistance = 0;
147185
clone.lastLevel = 0;
148-
clone.numTris = numTris != null ? numTris.clone() : null;
186+
clone.numTris = (numTris != null) ? numTris.clone() : null;
149187
return clone;
150-
}
188+
}
151189

152190
@Override
153191
protected void controlUpdate(float tpf) {
192+
// LOD is determined during controlRender to react to camera changes
154193
}
155194

156195
@Override
157196
protected void controlRender(RenderManager rm, ViewPort vp) {
197+
if (numLevels == 0) {
198+
return;
199+
}
200+
158201
BoundingVolume bv = spatial.getWorldBound();
159202

160203
Camera cam = vp.getCamera();
161204
float atanNH = FastMath.atan(cam.getFrustumNear() * cam.getFrustumTop());
162205
float ratio = (FastMath.PI / (8f * atanNH));
163-
float newDistance = bv.distanceTo(vp.getCamera().getLocation()) / ratio;
164-
int level;
165-
166-
if (Math.abs(newDistance - lastDistance) <= distTolerance) {
167-
level = lastLevel; // we haven't moved relative to the model, send the old measurement back.
168-
} else if (lastDistance > newDistance && lastLevel == 0) {
169-
level = lastLevel; // we're already at the lowest setting and we just got closer to the model, no need to keep trying.
170-
} else if (lastDistance < newDistance && lastLevel == numLevels - 1) {
171-
level = lastLevel; // we're already at the highest setting and we just got further from the model, no need to keep trying.
206+
float currDistance = bv.distanceTo(cam.getLocation()) / ratio;
207+
int newLevel;
208+
209+
if (Math.abs(currDistance - lastDistance) <= distTolerance) {
210+
newLevel = lastLevel; // we haven't moved relative to the model
211+
} else if (lastDistance > currDistance && lastLevel == 0) {
212+
newLevel = lastLevel; // Getting closer, already at highest detail (level 0)
213+
} else if (lastDistance < currDistance && lastLevel == numLevels - 1) {
214+
newLevel = lastLevel; // Getting further, already at lowest detail
172215
} else {
173-
lastDistance = newDistance;
216+
// Update lastDistance for subsequent checks
217+
lastDistance = currDistance;
174218

175219
// estimate area of polygon via bounding volume
176220
float area = AreaUtils.calcScreenArea(bv, lastDistance, cam.getWidth());
177221
float trisToDraw = area * trisPerPixel;
178-
level = numLevels - 1;
179-
for (int i = numLevels; --i >= 0;) {
180-
if (trisToDraw - numTris[i] < 0) {
181-
break;
222+
223+
// Find the appropriate LOD level
224+
// Start with the lowest detail (highest index)
225+
newLevel = numLevels - 1;
226+
// Iterate from highest detail (index 0) to lowest, breaking when trisToDraw is enough
227+
for (int i = 0; i < numLevels; i++) {
228+
if (trisToDraw >= numTris[i]) {
229+
newLevel = i;
230+
break; // Found the highest detail level that satisfies the criteria
182231
}
183-
level = i;
184232
}
185-
lastLevel = level;
233+
// Only set LOD if it's actually changing to avoid unnecessary calls
234+
if (newLevel != lastLevel) {
235+
spatial.setLodLevel(newLevel);
236+
}
237+
lastLevel = newLevel;
186238
}
187-
188-
spatial.setLodLevel(level);
189239
}
190240

191241
@Override

0 commit comments

Comments
 (0)