LOD für Palmen und Trauerweiden angepasst
This commit is contained in:
@@ -34,10 +34,12 @@ public class ModelLodControl extends AbstractControl {
|
||||
/** Erzwingt LOD2 für alle Instanzen. Wird per Konsolen-Befehl "set debugLod2 true" gesetzt. */
|
||||
public static volatile boolean debugLod2 = false;
|
||||
|
||||
private final Camera cam;
|
||||
private final float lod1DistSq;
|
||||
private final float lod2DistSq;
|
||||
private int currentSlot = 0; // 0=lod0, 1=lod1, 2=lod2, -1=ausgeblendet
|
||||
private final Camera cam;
|
||||
private final float lod1DistSq;
|
||||
private final float lod2DistSq;
|
||||
private float[] lodFactorRef = {1.0f};
|
||||
private int currentSlot = 0; // 0=lod0, 1=lod1, 2=lod2, -1=ausgeblendet
|
||||
private boolean firstUpdate = true;
|
||||
|
||||
// ── Embedded-LOD-Modus ────────────────────────────────────────────────────
|
||||
private final Spatial embLod0;
|
||||
@@ -93,6 +95,8 @@ public class ModelLodControl extends AbstractControl {
|
||||
this.lodLoadCallback = cb;
|
||||
}
|
||||
|
||||
public void setLodFactorRef(float[] ref) { this.lodFactorRef = ref; }
|
||||
|
||||
@Override
|
||||
protected void controlRender(RenderManager rm, ViewPort vp) {}
|
||||
|
||||
@@ -113,6 +117,15 @@ public class ModelLodControl extends AbstractControl {
|
||||
float dz = cam.getLocation().z - wt.z;
|
||||
float distSq = dx*dx + dy*dy + dz*dz;
|
||||
|
||||
if (firstUpdate) {
|
||||
firstUpdate = false;
|
||||
log.info("[LOD] Control aktiv: {} | embedded={} | lod1={}m lod2={}m | pos=({},{},{})",
|
||||
spatial.getName(), embLod0 != null,
|
||||
String.format("%.0f", Math.sqrt(lod1DistSq == Float.MAX_VALUE ? 0 : lod1DistSq)),
|
||||
String.format("%.0f", Math.sqrt(lod2DistSq == Float.MAX_VALUE ? 0 : lod2DistSq)),
|
||||
String.format("%.1f", wt.x), String.format("%.1f", wt.y), String.format("%.1f", wt.z));
|
||||
}
|
||||
|
||||
boolean embedded = embLod0 != null;
|
||||
|
||||
boolean hasLod1 = embedded ? embLod1 != null : lod1Path != null;
|
||||
@@ -121,13 +134,17 @@ public class ModelLodControl extends AbstractControl {
|
||||
// Sichtbarkeit hängt nur vom LOD-System ab — kein separates Culling.
|
||||
// Fehlt LOD1 aber LOD2 vorhanden: bei lod1Distance direkt zu LOD2 wechseln (nicht ausblenden).
|
||||
// Fehlt beides (z.B. Kaktusfeige): ab lod1Distance ausblenden.
|
||||
float f = lodFactorRef[0];
|
||||
float d1Sq = lod1DistSq == Float.MAX_VALUE ? Float.MAX_VALUE : lod1DistSq * f * f;
|
||||
float d2Sq = lod2DistSq == Float.MAX_VALUE ? Float.MAX_VALUE : lod2DistSq * f * f;
|
||||
|
||||
int targetSlot;
|
||||
if (debugLod2) {
|
||||
// Debug: erzwingt die niedrigste verfügbare LOD-Stufe
|
||||
targetSlot = hasLod2 ? 2 : (hasLod1 ? 1 : 0);
|
||||
} else if (distSq >= lod2DistSq) {
|
||||
} else if (distSq >= d2Sq) {
|
||||
targetSlot = hasLod2 ? 2 : -1;
|
||||
} else if (distSq >= lod1DistSq) {
|
||||
} else if (distSq >= d1Sq) {
|
||||
if (hasLod1) targetSlot = 1;
|
||||
else if (hasLod2) targetSlot = 2; // kein LOD1, aber LOD2 vorhanden → Impostor früher
|
||||
else targetSlot = -1; // weder LOD1 noch LOD2 → ausblenden
|
||||
|
||||
@@ -53,9 +53,9 @@ public class WorldObjectsState extends BaseAppState {
|
||||
private static final float IMPOSTOR_CAP_AMB_G = 0.30f;
|
||||
private static final float IMPOSTOR_CAP_AMB_B = 0.32f;
|
||||
private float debugTimer = 0f;
|
||||
private float lodFactor = 1.0f;
|
||||
private final float[] lodFactorRef = {1.0f};
|
||||
|
||||
public void setLodFactor(float f) { this.lodFactor = f; }
|
||||
public void setLodFactor(float f) { this.lodFactorRef[0] = f; }
|
||||
|
||||
/** RigidBodyControl pro Interactable-ID, damit Kollision während Animationen deaktiviert werden kann. */
|
||||
private final Map<String, RigidBodyControl> interactableRbcs = new HashMap<>();
|
||||
@@ -253,6 +253,7 @@ public class WorldObjectsState extends BaseAppState {
|
||||
// Exportiertes Mesh hat Vorrang vor modelPath
|
||||
String path = (m.meshFile() != null && !m.meshFile().isBlank())
|
||||
? m.meshFile() : m.modelPath();
|
||||
log.info("[WorldObjects-LOD] buildSpatial: {} lod1={}m lod2={}m", path, m.lod1Distance(), m.lod2Distance());
|
||||
|
||||
Spatial spatial;
|
||||
if (path.startsWith("@")) {
|
||||
@@ -265,8 +266,29 @@ public class WorldObjectsState extends BaseAppState {
|
||||
}
|
||||
spatial.setName("obj_" + path);
|
||||
|
||||
// LOD-Distanzen: ModelMeta zur Laufzeit bevorzugen (überschreibt veraltete Scene-TSV-Werte,
|
||||
// die 0 sein können wenn das Modell vor Einführung der Meta-Datei platziert wurde).
|
||||
float lodDist1 = m.lod1Distance();
|
||||
float lodDist2 = m.lod2Distance();
|
||||
if (!path.startsWith("@")) {
|
||||
de.blight.common.ModelMeta mm = tryLoadMeta(path);
|
||||
if (mm != null && (mm.lod1Distance() > 0f || mm.lod2Distance() > 0f)) {
|
||||
lodDist1 = mm.lod1Distance();
|
||||
lodDist2 = mm.lod2Distance();
|
||||
}
|
||||
}
|
||||
// Fallback für j3os, die eingebettete LOD-Knoten haben aber keine ModelMeta
|
||||
// (Modelle, die vor Einführung der Meta-Datei erzeugt und platziert wurden).
|
||||
// Vorhandensein von "lod1" oder "lod2" als Kind beweist, dass LOD beabsichtigt war.
|
||||
if (lodDist1 == 0f && lodDist2 == 0f && spatial instanceof Node tn
|
||||
&& (tn.getChild("lod1") != null || tn.getChild("lod2") != null)) {
|
||||
lodDist1 = 50f;
|
||||
lodDist2 = 150f;
|
||||
log.debug("[WorldObjects-LOD] {} – keine Meta, LOD-Knoten gefunden → Fallback 50/150m", path);
|
||||
}
|
||||
|
||||
// LOD — kein cullDistance mehr; Objekte ohne LOD-Stufe werden ab der jeweiligen Distanz ausgeblendet
|
||||
if (m.lod1Distance() > 0f || m.lod2Distance() > 0f) {
|
||||
if (lodDist1 > 0f || lodDist2 > 0f) {
|
||||
if (spatial instanceof Node treeNode) {
|
||||
Spatial lod0 = treeNode.getChild("lod0");
|
||||
Spatial lod1 = treeNode.getChild("lod1");
|
||||
@@ -284,7 +306,36 @@ public class WorldObjectsState extends BaseAppState {
|
||||
path, treeNode.getChildren().size(),
|
||||
lod0.getName(), lod1 != null ? "'" + lod1.getName() + "'" : "null",
|
||||
lod2 != null ? "'" + lod2.getName() + "'" : "null",
|
||||
m.lod1Distance() * lodFactor, m.lod2Distance() * lodFactor);
|
||||
lodDist1 * lodFactorRef[0], lodDist2 * lodFactorRef[0]);
|
||||
// Pivot-Korrektur: Falls der Mesh-Ursprung des Generators nicht bei Y=0 liegt,
|
||||
// treeNode in lodRoot einbetten und lod0 verschieben (wie Fall B).
|
||||
treeNode.updateGeometricState();
|
||||
if (lod0.getWorldBound() instanceof com.jme3.bounding.BoundingBox lod0Bb) {
|
||||
float groundY = lod0Bb.getCenter().y - lod0Bb.getYExtent();
|
||||
log.info("[WorldObjects-LOD] Pivot: {} groundY={}m", path, String.format("%.2f", groundY));
|
||||
if (groundY > 0.01f) {
|
||||
Node lodRoot = new Node("lodRoot_" + path);
|
||||
lodRoot.attachChild(treeNode);
|
||||
treeNode.setLocalTranslation(0f, -groundY, 0f);
|
||||
lod0.setCullHint(Spatial.CullHint.Inherit);
|
||||
if (lod1 != null) lod1.setCullHint(Spatial.CullHint.Always);
|
||||
if (lod2 != null) {
|
||||
lod2.setCullHint(Spatial.CullHint.Always);
|
||||
if (lod2 instanceof Node lod2Node && !lod2Node.getChildren().isEmpty()
|
||||
&& isImpostorNode(lod2Node)) {
|
||||
initImpostor(lod2Node);
|
||||
}
|
||||
}
|
||||
if (lod1 != null || lod2 != null) {
|
||||
ModelLodControl lodCtrl = new ModelLodControl(
|
||||
app.getCamera(), lod0, lod1, lod2,
|
||||
lodDist1, lodDist2);
|
||||
lodCtrl.setLodFactorRef(lodFactorRef);
|
||||
treeNode.addControl(lodCtrl);
|
||||
}
|
||||
return lodRoot;
|
||||
}
|
||||
}
|
||||
lod0.setCullHint(Spatial.CullHint.Inherit);
|
||||
if (lod1 != null) lod1.setCullHint(Spatial.CullHint.Always);
|
||||
if (lod2 != null) {
|
||||
@@ -297,9 +348,11 @@ public class WorldObjectsState extends BaseAppState {
|
||||
// ModelLodControl nur wenn tatsächliche LOD-Varianten vorhanden — sonst
|
||||
// würde das Objekt ab lod1Distance komplett ausgeblendet (targetSlot=-1).
|
||||
if (lod1 != null || lod2 != null) {
|
||||
treeNode.addControl(new ModelLodControl(
|
||||
ModelLodControl lodCtrl = new ModelLodControl(
|
||||
app.getCamera(), lod0, lod1, lod2,
|
||||
m.lod1Distance() * lodFactor, m.lod2Distance() * lodFactor));
|
||||
lodDist1, lodDist2);
|
||||
lodCtrl.setLodFactorRef(lodFactorRef);
|
||||
treeNode.addControl(lodCtrl);
|
||||
}
|
||||
return treeNode;
|
||||
}
|
||||
@@ -316,7 +369,7 @@ public class WorldObjectsState extends BaseAppState {
|
||||
if (lod2 != null) {
|
||||
log.info("[WorldObjects-LOD] Fall B: {} | Kinder={} | lod2='{}' | dist=0/{}m",
|
||||
path, treeNode.getChildren().size(), lod2.getName(),
|
||||
m.lod2Distance() * lodFactor);
|
||||
lodDist2 * lodFactorRef[0]);
|
||||
treeNode.detachChild(lod2);
|
||||
Node lodRoot = new Node("lodRoot_" + path);
|
||||
lodRoot.attachChild(treeNode); // index 0 = lod0 (volle Qualität)
|
||||
@@ -340,9 +393,11 @@ public class WorldObjectsState extends BaseAppState {
|
||||
&& isImpostorNode(lod2Node)) {
|
||||
initImpostor(lod2Node);
|
||||
}
|
||||
lodRoot.addControl(new ModelLodControl(
|
||||
ModelLodControl lodCtrl = new ModelLodControl(
|
||||
app.getCamera(), treeNode, null, lod2,
|
||||
m.lod1Distance() * lodFactor, m.lod2Distance() * lodFactor));
|
||||
lodDist1, lodDist2);
|
||||
lodCtrl.setLodFactorRef(lodFactorRef);
|
||||
lodRoot.addControl(lodCtrl);
|
||||
return lodRoot;
|
||||
}
|
||||
}
|
||||
@@ -358,7 +413,8 @@ public class WorldObjectsState extends BaseAppState {
|
||||
ModelLodControl ctrl = new ModelLodControl(
|
||||
assets, app.getCamera(),
|
||||
lp1, lp2,
|
||||
m.lod1Distance() * lodFactor, m.lod2Distance() * lodFactor);
|
||||
lodDist1, lodDist2);
|
||||
ctrl.setLodFactorRef(lodFactorRef);
|
||||
ctrl.setLodLoadCallback(lod -> {
|
||||
convertUnshadedToPbr(lod);
|
||||
collectSceneLitMaterials(lod);
|
||||
@@ -371,6 +427,17 @@ public class WorldObjectsState extends BaseAppState {
|
||||
return spatial;
|
||||
}
|
||||
|
||||
private de.blight.common.ModelMeta tryLoadMeta(String assetPath) {
|
||||
try {
|
||||
java.nio.file.Path root = de.blight.game.animation.AnimationLibrary.findAssetRoot();
|
||||
java.nio.file.Path j3o = root.resolve(assetPath);
|
||||
if (!java.nio.file.Files.exists(j3o)) return null;
|
||||
return de.blight.common.ModelMetaIO.load(j3o);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isImpostorNode(Node n) {
|
||||
if (n.getControl(BillboardControl.class) != null) {
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user