Bank-Sitz-Fix: clearKfOffset-Timing, Approach-Distanz, Animationsübergänge

- clearKfOffset() erst nach get_up-Animation (Callback) statt sofort beim Start
  → kein Slide des Visuals während der Aufsteh-Animation
- Approach-Distanz zur Bank um 17.5cm verkürzt (läuft näher ran, sitzt tiefer)
- blockingAnimRemaining um 1 Frame (1/60s) gekürzt → verhindert Extra-Keyframe-Hold
  am Animationsende (noch zu beobachten)
- Diverses aus vorheriger Session: AnimSet-Editor, Navigation, Assets

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-26 22:51:11 +02:00
parent ba0b80f524
commit b44d583dc3
21 changed files with 287 additions and 43 deletions

View File

@@ -603,6 +603,17 @@ public class EditorApp extends Application {
updateSpawnFields(input.pickedSpawnInfo);
}
// Modell-Editor: gebakte Scale aus j3o erkannt → Spinner aktualisieren
if (input.modelEditorBakedScaleDetected) {
input.modelEditorBakedScaleDetected = false;
double bsX = input.modelEditorScaleX;
double bsY = input.modelEditorScaleY;
double bsZ = input.modelEditorScaleZ;
if (modelEditorSpinX != null) modelEditorSpinX.getValueFactory().setValue(bsX);
if (modelEditorSpinY != null) modelEditorSpinY.getValueFactory().setValue(bsY);
if (modelEditorSpinZ != null) modelEditorSpinZ.getValueFactory().setValue(bsZ);
}
// Modell-Editor: Bounds-Aktualisierung
if (input.modelEditorBoundsReady) {
input.modelEditorBoundsReady = false;
@@ -5999,6 +6010,8 @@ public class EditorApp extends Application {
input.modelInteractableOffsetY = meta.interactableOffsetY();
input.modelInteractableOffsetZ = meta.interactableOffsetZ();
input.modelInteractableRotY = meta.interactableRotY();
input.modelInteractableActive = meta.interactableType() == de.blight.common.model.InteractableType.BED
|| meta.interactableType() == de.blight.common.model.InteractableType.BENCH;
input.modelInteractableOffsetChanged = true;
Label restPosHint = new Label("Klicke auf das Modell um den Ruhepunkt zu setzen:");
@@ -6116,8 +6129,9 @@ public class EditorApp extends Application {
|| nv == de.blight.common.model.InteractableType.BENCH;
restPointBox.setVisible(show);
restPointBox.setManaged(show);
// Pfeil ein-/ausblenden — über SharedInput-Flag signalisieren
input.modelInteractableOffsetChanged = show;
// Pfeil ein-/ausblenden — immer rebuilden, damit Sichtbarkeit aktualisiert wird
input.modelInteractableActive = show;
input.modelInteractableOffsetChanged = true;
});
// ── Buttons ───────────────────────────────────────────────────────────
@@ -6654,8 +6668,9 @@ public class EditorApp extends Application {
de.blight.common.model.InteractableType interactableType,
float interactableOffsetX, float interactableOffsetY,
float interactableOffsetZ, float interactableRotY) {
// Scale wird in j3o eingebrannt → Meta bekommt immer 1.0 (kein doppelter Scale beim Laden)
de.blight.common.ModelMeta meta = new de.blight.common.ModelMeta(
name, category, tags, sx, sy, sz, uniform,
name, category, tags, 1f, 1f, 1f, uniform,
pivotY, placeY, solid, cast, receive, rndMin, rndMax,
lod1Path, lod2Path, 30f, 80f, 120f,
lights, emitters,
@@ -6712,12 +6727,22 @@ public class EditorApp extends Application {
// Asset-Tree aktualisieren
input.refreshAssets = true;
// Thumbnail generieren (JME3-Thread liest das Flag und rendert)
java.nio.file.Path finalJ3o = category.isEmpty() ? absolutePath
: ASSET_ROOT.resolve("Models")
.resolve(java.nio.file.Path.of(category.replace('/', java.io.File.separatorChar)))
.resolve(name.isEmpty() ? absolutePath.getFileName().toString()
: name.replaceAll("[\\\\/:*?\"<>|]", "_") + ".j3o");
// Scale in j3o einbrennen (JME3-Thread) muss vor Thumbnail passieren
if (sx != 1f || sy != 1f || sz != 1f) {
input.modelEditorScaleX = sx;
input.modelEditorScaleY = sy;
input.modelEditorScaleZ = sz;
input.modelEditorBakeScalePath = finalJ3o;
input.modelEditorBakeScaleRequest = true;
}
// Thumbnail generieren (JME3-Thread liest das Flag und rendert)
input.modelEditorThumbnailRequest = finalJ3o;
}

View File

@@ -674,6 +674,13 @@ public class SharedInput {
*/
public volatile java.nio.file.Path modelEditorThumbnailRequest = null;
/** JFX → JME: Scale in j3o einbrennen (für animierte Modelle als Spatial-Transform, sonst Vertex-Bake). */
public volatile boolean modelEditorBakeScaleRequest = false;
public volatile java.nio.file.Path modelEditorBakeScalePath = null;
/** JME → JFX: j3o hatte eine gebakte Scale, die von der Meta abwich Spinner aktualisieren. */
public volatile boolean modelEditorBakedScaleDetected = false;
/** JME → JFX: true wenn das geladene Modell eingebettete LOD-Kinder hat (kein separater Pfad nötig). */
public volatile boolean modelEditorHasEmbeddedLods = false;
@@ -882,6 +889,8 @@ public class SharedInput {
public volatile float modelInteractableOffsetZ = 0f;
public volatile float modelInteractableRotY = 0f;
public volatile boolean modelInteractableOffsetChanged = false;
/** true wenn Interactable-Typ aktiv (BED/BENCH) steuert Pfeil-Sichtbarkeit. */
public volatile boolean modelInteractableActive = false;
/** Gesetzt vom JME-Thread nach Raycast-Klick, damit JFX-Spinner aktualisiert werden. */
public volatile boolean modelInteractablePosSetFromJme = false;

View File

@@ -225,10 +225,9 @@ public class AnimPreviewState extends BaseAppState {
previewTarget.z + FastMath.cos(rotY) * FastMath.cos(rotX) * dist));
c.lookAt(previewTarget, Vector3f.UNIT_Y);
// Achsen: Größe proportional zur Kameradistanz, immer am Weltpunkt (0,0,0)
// Achsen: feste 1m Weltlänge (Schaft = 0.5 lokal → Scale 2.0)
if (axesNode != null) {
float s = previewCamDist * input.animPreviewZoom * 0.18f;
axesNode.setLocalScale(s);
axesNode.setLocalScale(2.0f);
axesNode.setLocalTranslation(Vector3f.ZERO);
}

View File

@@ -20,6 +20,7 @@ import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Cylinder;
import com.jme3.scene.shape.Dome;
import com.jme3.scene.shape.Sphere;
import com.jme3.anim.SkinningControl;
import com.jme3.util.BufferUtils;
import de.blight.editor.SharedInput;
import org.slf4j.Logger;
@@ -219,6 +220,18 @@ public class ModelEditorState extends BaseAppState {
input.modelEditorAttachedEmitters);
}
// Scale in j3o einbrennen (vor Thumbnail, damit Thumbnail die gebackene Datei erhält)
if (input.modelEditorBakeScaleRequest) {
input.modelEditorBakeScaleRequest = false;
Path bakePath = input.modelEditorBakeScalePath;
if (bakePath != null) {
bakeScaleIntoModel(bakePath,
input.modelEditorScaleX,
input.modelEditorScaleY,
input.modelEditorScaleZ);
}
}
// Thumbnail auf Anforderung generieren
Path thumbReq = input.modelEditorThumbnailRequest;
if (thumbReq != null && modelWrapper != null) {
@@ -350,6 +363,18 @@ public class ModelEditorState extends BaseAppState {
modelWrapper.attachChild(box);
}
// Gebakte Spatial-Scale erkennen: j3o hat explizit gesetzte Scale, Meta sagt 1.0
// → Scale aus j3o übernehmen und JavaFX-Spinner aktualisieren lassen
com.jme3.math.Vector3f jScale = modelWrapper.getChildren().isEmpty()
? com.jme3.math.Vector3f.UNIT_XYZ
: modelWrapper.getChild(0).getLocalScale();
if (Math.abs(jScale.y - 1f) > 0.001f && Math.abs(input.modelEditorScaleY - 1f) < 0.001f) {
input.modelEditorScaleX = jScale.x;
input.modelEditorScaleY = jScale.y;
input.modelEditorScaleZ = jScale.z;
input.modelEditorBakedScaleDetected = true;
}
// Skalierung aus SharedInput anwenden
applyScale(input.modelEditorScaleX, input.modelEditorScaleY, input.modelEditorScaleZ);
applyPivot(input.modelEditorPivotY);
@@ -767,6 +792,48 @@ public class ModelEditorState extends BaseAppState {
}
}
// ── Scale-Bake ────────────────────────────────────────────────────────────
/**
* Brennt den Scale (sx,sy,sz) in die j3o-Datei ein.
* Animierte Modelle (SkinningControl vorhanden): Scale als Spatial-Transform gespeichert.
* Statische Modelle: Scale in Vertex-Positionen gebacken (wie ModelImportState beim Import).
*/
private void bakeScaleIntoModel(Path j3oPath, float sx, float sy, float sz) {
try {
BinaryImporter importer = BinaryImporter.getInstance();
importer.setAssetManager(app.getAssetManager());
Savable savable = importer.load(j3oPath.toFile());
if (!(savable instanceof Spatial root)) {
log.warn("[ModelEditor] Bake: kein Spatial in {}", j3oPath.getFileName());
return;
}
if (hasSkinningControl(root)) {
root.setLocalScale(sx, sy, sz);
log.info("[ModelEditor] Animiert: Scale ({},{},{}) als Spatial-Transform gespeichert", sx, sy, sz);
} else {
root.setLocalScale(sx, sy, sz);
ModelImportState.stripControls(root);
ModelImportState.bakeTransform(root, new Matrix4f());
log.info("[ModelEditor] Statisch: Scale ({},{},{}) in Vertices gebacken", sx, sy, sz);
}
BinaryExporter.getInstance().save(root, j3oPath.toFile());
log.info("[ModelEditor] j3o nach Bake gespeichert: {}", j3oPath.getFileName());
} catch (Exception e) {
log.error("[ModelEditor] Scale-Bake fehlgeschlagen: {}", e.getMessage(), e);
}
}
private static boolean hasSkinningControl(Spatial s) {
if (s.getControl(SkinningControl.class) != null) return true;
if (s instanceof Node n) {
for (Spatial c : n.getChildren()) {
if (hasSkinningControl(c)) return true;
}
}
return false;
}
// ── Thumbnail ─────────────────────────────────────────────────────────────
private void generateThumbnail(Path j3oPath) {
@@ -842,7 +909,8 @@ public class ModelEditorState extends BaseAppState {
group.attachChild(shaft);
group.attachChild(head);
interactableArrowNode.attachChild(group);
interactableArrowNode.setCullHint(Spatial.CullHint.Inherit);
interactableArrowNode.setCullHint(
input.modelInteractableActive ? Spatial.CullHint.Inherit : Spatial.CullHint.Always);
}
/** Setzt den Pfeil sichtbar/unsichtbar. */

View File

@@ -619,7 +619,7 @@ public class ModelImportState extends BaseAppState {
bakeTransform(s, new Matrix4f());
}
private static void bakeTransform(Spatial s, Matrix4f accum) {
static void bakeTransform(Spatial s, Matrix4f accum) {
Matrix4f localMat = new Matrix4f();
s.getLocalTransform().toTransformMatrix(localMat);
Matrix4f combined = accum.mult(localMat);
@@ -637,7 +637,7 @@ public class ModelImportState extends BaseAppState {
}
}
private static void applyMatrixToMesh(Geometry g, Matrix4f mat) {
static void applyMatrixToMesh(Geometry g, Matrix4f mat) {
Mesh newMesh = g.getMesh().deepClone();
FloatBuffer pos = newMesh.getFloatBuffer(VertexBuffer.Type.Position);
@@ -673,7 +673,7 @@ public class ModelImportState extends BaseAppState {
g.setMesh(newMesh);
}
private static Matrix3f buildNormalMatrix(Matrix4f mat) {
static Matrix3f buildNormalMatrix(Matrix4f mat) {
Matrix3f m3 = new Matrix3f(
mat.m00, mat.m01, mat.m02,
mat.m10, mat.m11, mat.m12,
@@ -683,7 +683,7 @@ public class ModelImportState extends BaseAppState {
return m3;
}
private static void stripControls(Spatial s) {
static void stripControls(Spatial s) {
while (s.getNumControls() > 0) s.removeControl(s.getControl(0));
if (s instanceof Node n) n.getChildren().forEach(ModelImportState::stripControls);
}