Spawn Punkte korrigiert
This commit is contained in:
@@ -1537,6 +1537,7 @@ public class EditorApp extends Application {
|
|||||||
});
|
});
|
||||||
playToolBtn.setOnAction(e -> {
|
playToolBtn.setOnAction(e -> {
|
||||||
input.activeLayer = SharedInput.LAYER_PLAY_TOOL;
|
input.activeLayer = SharedInput.LAYER_PLAY_TOOL;
|
||||||
|
input.playToolMode = SharedInput.PlayToolMode.EDIT;
|
||||||
root.setRight(buildPlayToolPanel());
|
root.setRight(buildPlayToolPanel());
|
||||||
});
|
});
|
||||||
voxelBtn.setOnAction(e -> {
|
voxelBtn.setOnAction(e -> {
|
||||||
@@ -8258,6 +8259,11 @@ public class EditorApp extends Application {
|
|||||||
p.setProperty("cam.z", String.valueOf(input.camZ));
|
p.setProperty("cam.z", String.valueOf(input.camZ));
|
||||||
p.setProperty("cam.yaw", String.valueOf(input.camYaw));
|
p.setProperty("cam.yaw", String.valueOf(input.camYaw));
|
||||||
p.setProperty("cam.pitch", String.valueOf(input.camPitch));
|
p.setProperty("cam.pitch", String.valueOf(input.camPitch));
|
||||||
|
if (!Float.isNaN(input.tempSpawnX) && !Float.isNaN(input.tempSpawnZ)) {
|
||||||
|
p.setProperty("tempSpawn.x", String.valueOf(input.tempSpawnX));
|
||||||
|
p.setProperty("tempSpawn.z", String.valueOf(input.tempSpawnZ));
|
||||||
|
p.setProperty("tempSpawn.yaw", String.valueOf(input.tempSpawnYaw));
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(BlightHome.resolve("config"));
|
Files.createDirectories(BlightHome.resolve("config"));
|
||||||
try (java.io.Writer w = Files.newBufferedWriter(BlightHome.resolve("config", "editor.prefs"))) {
|
try (java.io.Writer w = Files.newBufferedWriter(BlightHome.resolve("config", "editor.prefs"))) {
|
||||||
@@ -8857,9 +8863,8 @@ public class EditorApp extends Application {
|
|||||||
Button setTempBtn = new Button("📍 Temp. Spawn hier setzen");
|
Button setTempBtn = new Button("📍 Temp. Spawn hier setzen");
|
||||||
setTempBtn.setMaxWidth(Double.MAX_VALUE);
|
setTempBtn.setMaxWidth(Double.MAX_VALUE);
|
||||||
setTempBtn.setTooltip(new javafx.scene.control.Tooltip(
|
setTempBtn.setTooltip(new javafx.scene.control.Tooltip(
|
||||||
"Setzt Yaw = Kamera-Blickrichtung und aktiviert Klick-Modus im Viewport"));
|
"Aktiviert Klick-Modus im Viewport zum Setzen des temporären Spawnpunkts"));
|
||||||
setTempBtn.setOnAction(e -> {
|
setTempBtn.setOnAction(e -> {
|
||||||
input.tempSpawnYaw = camYawToSpawnYaw(input.camYaw);
|
|
||||||
input.playToolMode = SharedInput.PlayToolMode.SET_TEMP;
|
input.playToolMode = SharedInput.PlayToolMode.SET_TEMP;
|
||||||
setStatus("L-Klick im Viewport → temporären Spawnpunkt setzen");
|
setStatus("L-Klick im Viewport → temporären Spawnpunkt setzen");
|
||||||
});
|
});
|
||||||
@@ -8867,24 +8872,13 @@ public class EditorApp extends Application {
|
|||||||
Button setPermBtn = new Button("🏁 Perm. Spawn hier setzen");
|
Button setPermBtn = new Button("🏁 Perm. Spawn hier setzen");
|
||||||
setPermBtn.setMaxWidth(Double.MAX_VALUE);
|
setPermBtn.setMaxWidth(Double.MAX_VALUE);
|
||||||
setPermBtn.setTooltip(new javafx.scene.control.Tooltip(
|
setPermBtn.setTooltip(new javafx.scene.control.Tooltip(
|
||||||
"Setzt Yaw = Kamera-Blickrichtung und aktiviert Klick-Modus im Viewport"));
|
"Aktiviert Klick-Modus im Viewport zum Setzen des permanenten Spawnpunkts"));
|
||||||
setPermBtn.setOnAction(e -> {
|
setPermBtn.setOnAction(e -> {
|
||||||
input.permSpawnYaw = camYawToSpawnYaw(input.camYaw);
|
|
||||||
input.playToolMode = SharedInput.PlayToolMode.SET_PERM;
|
input.playToolMode = SharedInput.PlayToolMode.SET_PERM;
|
||||||
setStatus("L-Klick im Viewport → permanenten Spawnpunkt setzen");
|
setStatus("L-Klick im Viewport → permanenten Spawnpunkt setzen");
|
||||||
});
|
});
|
||||||
|
|
||||||
ToggleButton editModeBtn = new ToggleButton("✎ Bearbeiten");
|
inner.getChildren().addAll(setTempBtn, setPermBtn, new Separator());
|
||||||
editModeBtn.setMaxWidth(Double.MAX_VALUE);
|
|
||||||
editModeBtn.setTooltip(new javafx.scene.control.Tooltip(
|
|
||||||
"Drag auf Marker → Position; Drag auf Pfeilspitze → Richtung drehen"));
|
|
||||||
editModeBtn.setOnAction(e -> {
|
|
||||||
input.playToolMode = editModeBtn.isSelected()
|
|
||||||
? SharedInput.PlayToolMode.EDIT
|
|
||||||
: SharedInput.PlayToolMode.NONE;
|
|
||||||
});
|
|
||||||
|
|
||||||
inner.getChildren().addAll(setTempBtn, setPermBtn, editModeBtn, new Separator());
|
|
||||||
|
|
||||||
// ── Koordinatenanzeige ────────────────────────────────────────────────
|
// ── Koordinatenanzeige ────────────────────────────────────────────────
|
||||||
tempSpawnCoordsLabel = new Label(tempSpawnCoordsText());
|
tempSpawnCoordsLabel = new Label(tempSpawnCoordsText());
|
||||||
@@ -8945,11 +8939,6 @@ public class EditorApp extends Application {
|
|||||||
return panel;
|
return panel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Konvertiert den Editor-Kamera-Yaw in den Spawnpunkt-Yaw (0=+Z, 90=+X, UZS von oben). */
|
|
||||||
private static float camYawToSpawnYaw(float camYaw) {
|
|
||||||
return ((180f + camYaw) % 360f + 360f) % 360f;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String tempSpawnCoordsText() {
|
private String tempSpawnCoordsText() {
|
||||||
if (Float.isNaN(input.tempSpawnX) || Float.isNaN(input.tempSpawnZ)) return "(nicht gesetzt)";
|
if (Float.isNaN(input.tempSpawnX) || Float.isNaN(input.tempSpawnZ)) return "(nicht gesetzt)";
|
||||||
return String.format("X=%.1f Z=%.1f Yaw=%.0f°", input.tempSpawnX, input.tempSpawnZ, input.tempSpawnYaw);
|
return String.format("X=%.1f Z=%.1f Yaw=%.0f°", input.tempSpawnX, input.tempSpawnZ, input.tempSpawnYaw);
|
||||||
|
|||||||
@@ -14,14 +14,13 @@ import com.jme3.terrain.geomipmap.TerrainQuad;
|
|||||||
import de.blight.editor.SharedInput;
|
import de.blight.editor.SharedInput;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rendert Spawn-Marker im Editor (temp=grün, perm=blau) mit Richtungspfeilen.
|
* Rendert Spawn-Marker im Editor (temp=grün, perm=blau) mit nordweisendem Richtungspfeil.
|
||||||
|
* Pfeil folgt der Terrain-Neigung, zeigt aber immer Richtung Norden (+Z).
|
||||||
*
|
*
|
||||||
* Modi:
|
* Modi:
|
||||||
* NONE – Marker sichtbar, aber keine Interaktion
|
* EDIT – Standard; Drag auf Marker-Scheibe → Position, Drag auf Pfeil → Richtung
|
||||||
* SET_TEMP – Nächster Viewport-Klick setzt temp. Spawnpunkt
|
* SET_TEMP – Nächster Viewport-Klick setzt temp. Spawnpunkt (danach → EDIT)
|
||||||
* SET_PERM – Nächster Viewport-Klick setzt perm. Spawnpunkt
|
* SET_PERM – Nächster Viewport-Klick setzt perm. Spawnpunkt (danach → EDIT)
|
||||||
* EDIT – Drag auf Marker-Körper → Positionieren;
|
|
||||||
* Drag auf Pfeilspitze → Richtung drehen (via XZ-Projektion)
|
|
||||||
*/
|
*/
|
||||||
public class PlayToolState extends BaseAppState {
|
public class PlayToolState extends BaseAppState {
|
||||||
|
|
||||||
@@ -46,12 +45,6 @@ public class PlayToolState extends BaseAppState {
|
|||||||
private Node tempArrowNode;
|
private Node tempArrowNode;
|
||||||
private Node permArrowNode;
|
private Node permArrowNode;
|
||||||
|
|
||||||
// Separate Geometrien für Ray-Cast-Erkennung
|
|
||||||
private Geometry tempBodyGeom;
|
|
||||||
private Geometry permBodyGeom;
|
|
||||||
private Geometry tempTipGeom;
|
|
||||||
private Geometry permTipGeom;
|
|
||||||
|
|
||||||
private DragTarget dragTarget = DragTarget.NONE;
|
private DragTarget dragTarget = DragTarget.NONE;
|
||||||
private float lastDragX;
|
private float lastDragX;
|
||||||
private float lastDragY;
|
private float lastDragY;
|
||||||
@@ -72,10 +65,6 @@ public class PlayToolState extends BaseAppState {
|
|||||||
|
|
||||||
tempArrowNode = (Node) tempMarkerNode.getChild("arrow");
|
tempArrowNode = (Node) tempMarkerNode.getChild("arrow");
|
||||||
permArrowNode = (Node) permMarkerNode.getChild("arrow");
|
permArrowNode = (Node) permMarkerNode.getChild("arrow");
|
||||||
tempBodyGeom = (Geometry) tempMarkerNode.getChild("body_temp");
|
|
||||||
permBodyGeom = (Geometry) permMarkerNode.getChild("body_perm");
|
|
||||||
tempTipGeom = (Geometry) tempArrowNode.getChild("tip_temp");
|
|
||||||
permTipGeom = (Geometry) permArrowNode.getChild("tip_perm");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -138,9 +127,10 @@ public class PlayToolState extends BaseAppState {
|
|||||||
if (pt != null) {
|
if (pt != null) {
|
||||||
input.tempSpawnX = pt.x;
|
input.tempSpawnX = pt.x;
|
||||||
input.tempSpawnZ = pt.z;
|
input.tempSpawnZ = pt.z;
|
||||||
|
input.tempSpawnYaw = 0f;
|
||||||
input.pickedSpawnInfo = pt.x + "|" + pt.z;
|
input.pickedSpawnInfo = pt.x + "|" + pt.z;
|
||||||
input.spawnPickChanged = true;
|
input.spawnPickChanged = true;
|
||||||
input.playToolMode = SharedInput.PlayToolMode.NONE;
|
input.playToolMode = SharedInput.PlayToolMode.EDIT;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -150,9 +140,10 @@ public class PlayToolState extends BaseAppState {
|
|||||||
if (pt != null) {
|
if (pt != null) {
|
||||||
input.permSpawnX = pt.x;
|
input.permSpawnX = pt.x;
|
||||||
input.permSpawnZ = pt.z;
|
input.permSpawnZ = pt.z;
|
||||||
|
input.permSpawnYaw = 0f;
|
||||||
input.pickedPermSpawnInfo = pt.x + "|" + pt.z;
|
input.pickedPermSpawnInfo = pt.x + "|" + pt.z;
|
||||||
input.permSpawnChanged = true;
|
input.permSpawnChanged = true;
|
||||||
input.playToolMode = SharedInput.PlayToolMode.NONE;
|
input.playToolMode = SharedInput.PlayToolMode.EDIT;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -165,30 +156,86 @@ public class PlayToolState extends BaseAppState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private DragTarget detectDragTarget(Ray ray) {
|
private DragTarget detectDragTarget(Ray ray) {
|
||||||
// Pfeilspitzen zuerst prüfen (kleineres Ziel, höhere Priorität)
|
DragTarget temp = detectDragTargetForSpawn(ray,
|
||||||
if (tempTipGeom != null && !Float.isNaN(input.tempSpawnX)) {
|
input.tempSpawnX, input.tempSpawnZ, input.tempSpawnYaw,
|
||||||
CollisionResults res = new CollisionResults();
|
DragTarget.POS_TEMP, DragTarget.ROT_TEMP);
|
||||||
tempTipGeom.collideWith(ray, res);
|
if (temp != DragTarget.NONE) return temp;
|
||||||
if (res.size() > 0) return DragTarget.ROT_TEMP;
|
return detectDragTargetForSpawn(ray,
|
||||||
|
input.permSpawnX, input.permSpawnZ, input.permSpawnYaw,
|
||||||
|
DragTarget.POS_PERM, DragTarget.ROT_PERM);
|
||||||
}
|
}
|
||||||
if (permTipGeom != null && !Float.isNaN(input.permSpawnX)) {
|
|
||||||
CollisionResults res = new CollisionResults();
|
private DragTarget detectDragTargetForSpawn(Ray ray, float sx, float sz, float yawDeg,
|
||||||
permTipGeom.collideWith(ray, res);
|
DragTarget posTarget, DragTarget rotTarget) {
|
||||||
if (res.size() > 0) return DragTarget.ROT_PERM;
|
if (Float.isNaN(sx) || Float.isNaN(sz)) return DragTarget.NONE;
|
||||||
|
float markerY = terrainY(sx, sz);
|
||||||
|
Vector3f center = new Vector3f(sx, markerY + 0.06f, sz);
|
||||||
|
Vector3f up = terrainNormal(sx, sz);
|
||||||
|
|
||||||
|
// ── Scheibe: Ray-Ebene-Schnitt mit Terrain-Normalebene ───────────────────
|
||||||
|
float denom = ray.getDirection().dot(up);
|
||||||
|
if (Math.abs(denom) > 1e-6f) {
|
||||||
|
float t = center.subtract(ray.getOrigin()).dot(up) / denom;
|
||||||
|
if (t > 0f) {
|
||||||
|
Vector3f hit = ray.getOrigin().add(ray.getDirection().mult(t));
|
||||||
|
if (hit.distanceSquared(center) <= MARKER_RADIUS * MARKER_RADIUS) {
|
||||||
|
return posTarget;
|
||||||
}
|
}
|
||||||
if (tempBodyGeom != null && !Float.isNaN(input.tempSpawnX)) {
|
|
||||||
CollisionResults res = new CollisionResults();
|
|
||||||
tempBodyGeom.collideWith(ray, res);
|
|
||||||
if (res.size() > 0) return DragTarget.POS_TEMP;
|
|
||||||
}
|
}
|
||||||
if (permBodyGeom != null && !Float.isNaN(input.permSpawnX)) {
|
|
||||||
CollisionResults res = new CollisionResults();
|
|
||||||
permBodyGeom.collideWith(ray, res);
|
|
||||||
if (res.size() > 0) return DragTarget.POS_PERM;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Pfeil: 3D Ray-zu-Segment-Distanz (korrekt für geneigte Terrain) ─────
|
||||||
|
float rad = yawDeg * FastMath.DEG_TO_RAD;
|
||||||
|
Vector3f flatF = new Vector3f(FastMath.sin(rad), 0f, -FastMath.cos(rad));
|
||||||
|
Vector3f fwd = flatF.subtract(up.mult(up.dot(flatF)));
|
||||||
|
if (fwd.lengthSquared() > 1e-6f) {
|
||||||
|
fwd.normalizeLocal();
|
||||||
|
Vector3f arrowEnd = center.add(fwd.mult(SHAFT_LEN + TIP_LEN));
|
||||||
|
if (rayToSegDistSq(ray.getOrigin(), ray.getDirection(), center, arrowEnd) < 0.4f * 0.4f) {
|
||||||
|
return rotTarget;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return DragTarget.NONE;
|
return DragTarget.NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimaler quadratischer Abstand zwischen einem Strahl (Ursprung o, normalisierte Richtung d)
|
||||||
|
* und einem Linienabschnitt [segA, segB].
|
||||||
|
*/
|
||||||
|
private static float rayToSegDistSq(Vector3f o, Vector3f d, Vector3f segA, Vector3f segB) {
|
||||||
|
Vector3f v = segB.subtract(segA);
|
||||||
|
Vector3f w = o.subtract(segA);
|
||||||
|
float b = d.dot(v);
|
||||||
|
float c = v.dot(v);
|
||||||
|
float e = v.dot(w);
|
||||||
|
float f = d.dot(w);
|
||||||
|
float D = c - b * b;
|
||||||
|
|
||||||
|
float sc, tc;
|
||||||
|
if (D < 1e-8f) {
|
||||||
|
sc = 0f;
|
||||||
|
tc = (c > 1e-8f) ? FastMath.clamp(e / c, 0f, 1f) : 0f;
|
||||||
|
} else {
|
||||||
|
sc = (b * e - c * f) / D;
|
||||||
|
tc = (e - b * f) / D;
|
||||||
|
if (sc < 0f) {
|
||||||
|
sc = 0f;
|
||||||
|
tc = FastMath.clamp(e / c, 0f, 1f);
|
||||||
|
} else if (tc < 0f) {
|
||||||
|
tc = 0f;
|
||||||
|
sc = Math.max(0f, -f);
|
||||||
|
} else if (tc > 1f) {
|
||||||
|
tc = 1f;
|
||||||
|
sc = Math.max(0f, b - f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f pR = o.add(d.mult(sc));
|
||||||
|
Vector3f pS = segA.add(v.mult(tc));
|
||||||
|
return pR.distanceSquared(pS);
|
||||||
|
}
|
||||||
|
|
||||||
// ── Drag-Handler ──────────────────────────────────────────────────────────
|
// ── Drag-Handler ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private void handleDrag(SharedInput.PlayToolDrag drag) {
|
private void handleDrag(SharedInput.PlayToolDrag drag) {
|
||||||
@@ -227,8 +274,8 @@ public class PlayToolState extends BaseAppState {
|
|||||||
float dx = proj.x - input.tempSpawnX;
|
float dx = proj.x - input.tempSpawnX;
|
||||||
float dz = proj.z - input.tempSpawnZ;
|
float dz = proj.z - input.tempSpawnZ;
|
||||||
if (dx * dx + dz * dz > 0.01f) {
|
if (dx * dx + dz * dz > 0.01f) {
|
||||||
// spawnYaw: 0=+Z, 90=+X → atan2(dx, dz)
|
// yaw=0=Norden=-Z → atan2(dx, -dz)
|
||||||
float yaw = (float) Math.toDegrees(Math.atan2(dx, dz));
|
float yaw = (float) Math.toDegrees(Math.atan2(dx, -dz));
|
||||||
input.tempSpawnYaw = ((yaw % 360f) + 360f) % 360f;
|
input.tempSpawnYaw = ((yaw % 360f) + 360f) % 360f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,7 +287,7 @@ public class PlayToolState extends BaseAppState {
|
|||||||
float dx = proj.x - input.permSpawnX;
|
float dx = proj.x - input.permSpawnX;
|
||||||
float dz = proj.z - input.permSpawnZ;
|
float dz = proj.z - input.permSpawnZ;
|
||||||
if (dx * dx + dz * dz > 0.01f) {
|
if (dx * dx + dz * dz > 0.01f) {
|
||||||
float yaw = (float) Math.toDegrees(Math.atan2(dx, dz));
|
float yaw = (float) Math.toDegrees(Math.atan2(dx, -dz));
|
||||||
input.permSpawnYaw = ((yaw % 360f) + 360f) % 360f;
|
input.permSpawnYaw = ((yaw % 360f) + 360f) % 360f;
|
||||||
input.permSpawnChanged = true;
|
input.permSpawnChanged = true;
|
||||||
}
|
}
|
||||||
@@ -280,7 +327,7 @@ public class PlayToolState extends BaseAppState {
|
|||||||
arrowNode.attachChild(shaftGeom);
|
arrowNode.attachChild(shaftGeom);
|
||||||
|
|
||||||
// Pfeilspitze: Kegel mit breiter Basis bei z=-TIP_LEN/2 → nach rotate: Basis bei y=SHAFT_LEN (Schaft-Ende)
|
// Pfeilspitze: Kegel mit breiter Basis bei z=-TIP_LEN/2 → nach rotate: Basis bei y=SHAFT_LEN (Schaft-Ende)
|
||||||
Cylinder tip = new Cylinder(4, 8, TIP_RADIUS, 0.001f, TIP_LEN, true, false);
|
Cylinder tip = new Cylinder(4, 8, 0.001f, TIP_RADIUS, TIP_LEN, true, false);
|
||||||
Geometry tipGeom = new Geometry("tip_" + id, tip);
|
Geometry tipGeom = new Geometry("tip_" + id, tip);
|
||||||
tipGeom.setMaterial(mat);
|
tipGeom.setMaterial(mat);
|
||||||
tipGeom.rotate(-FastMath.HALF_PI, 0f, 0f);
|
tipGeom.rotate(-FastMath.HALF_PI, 0f, 0f);
|
||||||
@@ -304,28 +351,26 @@ public class PlayToolState extends BaseAppState {
|
|||||||
|
|
||||||
if (arrowNode == null) return;
|
if (arrowNode == null) return;
|
||||||
|
|
||||||
// Terrain-Normale als lokale Oben-Richtung
|
|
||||||
Vector3f up = terrainNormal(x, z);
|
Vector3f up = terrainNormal(x, z);
|
||||||
|
|
||||||
// Yaw-Richtung als horizontaler Richtungsvektor (0=+Z, 90=+X)
|
// yaw=0 = Norden = -Z (JME3 Kamera blickt entlang -Z als Norden)
|
||||||
float rad = yawDeg * FastMath.DEG_TO_RAD;
|
float rad = yawDeg * FastMath.DEG_TO_RAD;
|
||||||
Vector3f flatForward = new Vector3f(FastMath.sin(rad), 0f, FastMath.cos(rad));
|
Vector3f flatForward = new Vector3f(FastMath.sin(rad), 0f, -FastMath.cos(rad));
|
||||||
|
|
||||||
// right = up × flatForward (liegt auf der Terrain-Oberfläche, senkrecht zur Richtung)
|
// Auf Terrain-Ebene projizieren
|
||||||
Vector3f right = up.cross(flatForward);
|
Vector3f fwd = flatForward.subtract(up.mult(up.dot(flatForward)));
|
||||||
if (right.lengthSquared() < 1e-6f) {
|
if (fwd.lengthSquared() < 1e-6f) {
|
||||||
// Sonderfall: Terrain fast senkrecht → horizontale Rotation als Fallback
|
arrowNode.setLocalRotation(Quaternion.IDENTITY);
|
||||||
arrowNode.setLocalRotation(new Quaternion().fromAngles(FastMath.HALF_PI, rad, 0f));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
right.normalizeLocal();
|
fwd.normalizeLocal();
|
||||||
// forward = right × up (auf Terrain-Fläche projiziertes Forward in Yaw-Richtung)
|
|
||||||
Vector3f forward = right.cross(up).normalizeLocal();
|
// det=+1: col0 = fwd × up
|
||||||
|
Vector3f right = fwd.cross(up).normalizeLocal();
|
||||||
|
|
||||||
// Rotationsmatrix: local X → right, local Y → forward (Pfeil), local Z → up (Terrain-Normal)
|
|
||||||
Matrix3f rot = new Matrix3f();
|
Matrix3f rot = new Matrix3f();
|
||||||
rot.setColumn(0, right);
|
rot.setColumn(0, right);
|
||||||
rot.setColumn(1, forward);
|
rot.setColumn(1, fwd);
|
||||||
rot.setColumn(2, up);
|
rot.setColumn(2, up);
|
||||||
arrowNode.setLocalRotation(new Quaternion().fromRotationMatrix(rot));
|
arrowNode.setLocalRotation(new Quaternion().fromRotationMatrix(rot));
|
||||||
}
|
}
|
||||||
@@ -333,6 +378,14 @@ public class PlayToolState extends BaseAppState {
|
|||||||
// ── Terrain-Hilfsmethoden ─────────────────────────────────────────────────
|
// ── Terrain-Hilfsmethoden ─────────────────────────────────────────────────
|
||||||
|
|
||||||
private Vector3f hitTerrain(Ray ray) {
|
private Vector3f hitTerrain(Ray ray) {
|
||||||
|
VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class);
|
||||||
|
if (ves != null) {
|
||||||
|
Vector3f pt = ves.clickAt(ray);
|
||||||
|
if (pt != null && ves.terrainTypeAt(pt.x, pt.z) == VoxelEditorState.TerrainType.VOXEL_UNBAKED) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return pt;
|
||||||
|
}
|
||||||
if (terrain == null) return null;
|
if (terrain == null) return null;
|
||||||
CollisionResults hits = new CollisionResults();
|
CollisionResults hits = new CollisionResults();
|
||||||
terrain.collideWith(ray, hits);
|
terrain.collideWith(ray, hits);
|
||||||
@@ -341,6 +394,8 @@ public class PlayToolState extends BaseAppState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private float terrainY(float x, float z) {
|
private float terrainY(float x, float z) {
|
||||||
|
VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class);
|
||||||
|
if (ves != null) return ves.heightAt(x, z);
|
||||||
if (terrain == null) return 0f;
|
if (terrain == null) return 0f;
|
||||||
float h = terrain.getHeight(new Vector2f(x, z));
|
float h = terrain.getHeight(new Vector2f(x, z));
|
||||||
return Float.isNaN(h) ? 0f : h;
|
return Float.isNaN(h) ? 0f : h;
|
||||||
|
|||||||
@@ -210,6 +210,12 @@ public class TerrainEditorState extends BaseAppState {
|
|||||||
parsePref(p, "cam.z", 0f));
|
parsePref(p, "cam.z", 0f));
|
||||||
camYaw = (float) Math.toRadians(parsePref(p, "cam.yaw", 0f));
|
camYaw = (float) Math.toRadians(parsePref(p, "cam.yaw", 0f));
|
||||||
camPitch = (float) Math.toRadians(parsePref(p, "cam.pitch", (float) Math.toDegrees(DEFAULT_PITCH)));
|
camPitch = (float) Math.toRadians(parsePref(p, "cam.pitch", (float) Math.toDegrees(DEFAULT_PITCH)));
|
||||||
|
if (p.containsKey("tempSpawn.x") && p.containsKey("tempSpawn.z")) {
|
||||||
|
input.tempSpawnX = parsePref(p, "tempSpawn.x", Float.NaN);
|
||||||
|
input.tempSpawnZ = parsePref(p, "tempSpawn.z", Float.NaN);
|
||||||
|
input.tempSpawnYaw = parsePref(p, "tempSpawn.yaw", 0f);
|
||||||
|
input.spawnPickChanged = true;
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.warn("Kamera-Prefs nicht ladbar", e);
|
log.warn("Kamera-Prefs nicht ladbar", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package de.blight.game;
|
package de.blight.game;
|
||||||
|
|
||||||
|
import com.jme3.app.Application;
|
||||||
import com.jme3.app.SimpleApplication;
|
import com.jme3.app.SimpleApplication;
|
||||||
import com.jme3.app.state.ScreenshotAppState;
|
import com.jme3.app.state.ScreenshotAppState;
|
||||||
import com.jme3.input.KeyInput;
|
import com.jme3.input.KeyInput;
|
||||||
@@ -329,6 +330,35 @@ public class BlightGame extends SimpleApplication {
|
|||||||
});
|
});
|
||||||
stateManager.attach(console);
|
stateManager.attach(console);
|
||||||
|
|
||||||
|
// ── Debug-Stats (F3): oben-links, default aus ────────────────────────────
|
||||||
|
com.jme3.app.StatsAppState defaultStats = stateManager.getState(com.jme3.app.StatsAppState.class);
|
||||||
|
if (defaultStats != null) stateManager.detach(defaultStats);
|
||||||
|
com.jme3.app.StatsAppState statsState = new com.jme3.app.StatsAppState() {
|
||||||
|
@Override
|
||||||
|
public void initialize(com.jme3.app.state.AppStateManager sm, com.jme3.app.Application a) {
|
||||||
|
super.initialize(sm, a);
|
||||||
|
setDisplayFps(false);
|
||||||
|
setDisplayStatView(false);
|
||||||
|
int h = a.getCamera().getHeight();
|
||||||
|
if (fpsText != null) fpsText.setLocalTranslation(0, h, 0);
|
||||||
|
if (statsView != null) {
|
||||||
|
float fpsH = fpsText != null ? fpsText.getLineHeight() : 0f;
|
||||||
|
statsView.setLocalTranslation(0, h - fpsH, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
stateManager.attach(statsState);
|
||||||
|
final boolean[] statsVisible = {false};
|
||||||
|
inputManager.addMapping("ToggleStats", new KeyTrigger(KeyInput.KEY_F3));
|
||||||
|
inputManager.addListener((ActionListener) (name, isPressed, tpf) -> {
|
||||||
|
if (!isPressed) return;
|
||||||
|
com.jme3.app.StatsAppState s = stateManager.getState(com.jme3.app.StatsAppState.class);
|
||||||
|
if (s == null) return;
|
||||||
|
statsVisible[0] = !statsVisible[0];
|
||||||
|
s.setDisplayFps(statsVisible[0]);
|
||||||
|
s.setDisplayStatView(statsVisible[0]);
|
||||||
|
}, "ToggleStats");
|
||||||
|
|
||||||
// ── Debug: Nebel-Toggle (F7) ─────────────────────────────────────────────
|
// ── Debug: Nebel-Toggle (F7) ─────────────────────────────────────────────
|
||||||
inputManager.addMapping("DebugFog", new KeyTrigger(KeyInput.KEY_F7));
|
inputManager.addMapping("DebugFog", new KeyTrigger(KeyInput.KEY_F7));
|
||||||
inputManager.addListener((ActionListener) (name, isPressed, tpf) -> {
|
inputManager.addListener((ActionListener) (name, isPressed, tpf) -> {
|
||||||
|
|||||||
@@ -38,6 +38,13 @@ public class ThirdPersonCamera {
|
|||||||
private float distance = BASE_DISTANCE;
|
private float distance = BASE_DISTANCE;
|
||||||
private boolean paused = false;
|
private boolean paused = false;
|
||||||
|
|
||||||
|
// Intro-Kameradrehung (neues Spiel): yaw animiert von vorne nach hinten
|
||||||
|
private boolean introRotating = false;
|
||||||
|
private float introStartYaw;
|
||||||
|
private float introEndYaw;
|
||||||
|
private float introDuration;
|
||||||
|
private float introTimer;
|
||||||
|
|
||||||
|
|
||||||
public ThirdPersonCamera(Camera cam, InputManager inputManager, KeyBindings keyBindings) {
|
public ThirdPersonCamera(Camera cam, InputManager inputManager, KeyBindings keyBindings) {
|
||||||
this.cam = cam;
|
this.cam = cam;
|
||||||
@@ -65,7 +72,7 @@ public class ThirdPersonCamera {
|
|||||||
inputManager.addMapping("CamTurnRight", new KeyTrigger(keyBindings.turnRight));
|
inputManager.addMapping("CamTurnRight", new KeyTrigger(keyBindings.turnRight));
|
||||||
|
|
||||||
AnalogListener analogListener = (name, value, tpf) -> {
|
AnalogListener analogListener = (name, value, tpf) -> {
|
||||||
if (paused) return;
|
if (paused || introRotating) return;
|
||||||
switch (name) {
|
switch (name) {
|
||||||
// Horizontale Rotation — Maus
|
// Horizontale Rotation — Maus
|
||||||
case "MouseX" -> yaw -= value * MOUSE_SENSITIVITY;
|
case "MouseX" -> yaw -= value * MOUSE_SENSITIVITY;
|
||||||
@@ -87,6 +94,13 @@ public class ThirdPersonCamera {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void update(float tpf) {
|
public void update(float tpf) {
|
||||||
|
if (introRotating) {
|
||||||
|
introTimer += tpf;
|
||||||
|
float t = FastMath.clamp(introTimer / introDuration, 0f, 1f);
|
||||||
|
yaw = introStartYaw + (introEndYaw - introStartYaw) * t;
|
||||||
|
if (t >= 1f) introRotating = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (target == null || paused) return;
|
if (target == null || paused) return;
|
||||||
|
|
||||||
Vector3f pivot = target.getWorldTranslation().add(0, TARGET_HEIGHT, 0);
|
Vector3f pivot = target.getWorldTranslation().add(0, TARGET_HEIGHT, 0);
|
||||||
@@ -101,6 +115,23 @@ public class ThirdPersonCamera {
|
|||||||
cam.lookAt(pivot, Vector3f.UNIT_Y);
|
cam.lookAt(pivot, Vector3f.UNIT_Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Aktueller Yaw-Winkel (für CharacterControl nutzbar). */
|
/** Aktueller Yaw-Winkel (für CharacterControl nutzbar). */
|
||||||
public float getYaw() { return yaw; }
|
public float getYaw() { return yaw; }
|
||||||
|
|
||||||
|
/** Setzt initialen Kamera-Yaw passend zum Spawn-Yaw (Kamera hinter dem Charakter). */
|
||||||
|
public void setInitialYaw(float spawnYawDegrees) {
|
||||||
|
this.yaw = -spawnYawDegrees * FastMath.DEG_TO_RAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Startet die Intro-Drehung: Kamera dreht sich im Uhrzeigersinn 180° (von vorne nach hinten)
|
||||||
|
* über die angegebene Dauer. Während der Drehung ist Mauseingabe blockiert.
|
||||||
|
*/
|
||||||
|
public void startIntroRotation(float duration) {
|
||||||
|
introStartYaw = this.yaw;
|
||||||
|
introEndYaw = this.yaw - FastMath.PI; // 180° CW von oben gesehen
|
||||||
|
introDuration = duration;
|
||||||
|
introTimer = 0f;
|
||||||
|
introRotating = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -557,7 +557,11 @@ public class WorldScene extends BaseAppState {
|
|||||||
characterVisual.setCullHint(Spatial.CullHint.Inherit);
|
characterVisual.setCullHint(Spatial.CullHint.Inherit);
|
||||||
}
|
}
|
||||||
|
|
||||||
playerInput.setInitialFacing(spawnYaw);
|
playerInput.setInitialFacing(spawnYaw + 180f);
|
||||||
|
|
||||||
|
boolean newGame = "true".equals(System.getProperty("blight.new.game"));
|
||||||
|
// Neues Spiel: Kamera startet gegenüber der Blickrichtung (von vorne) und dreht sich 180° CW
|
||||||
|
thirdPersonCam.setInitialYaw(newGame ? spawnYaw + 180f : spawnYaw);
|
||||||
|
|
||||||
String reviveClip = de.blight.game.animation.AnimationLibrary.getClipForAction(
|
String reviveClip = de.blight.game.animation.AnimationLibrary.getClipForAction(
|
||||||
AnimationLibrary.findAssetRoot(), setName, de.blight.game.animation.AnimationAction.REVIVE);
|
AnimationLibrary.findAssetRoot(), setName, de.blight.game.animation.AnimationAction.REVIVE);
|
||||||
@@ -568,9 +572,9 @@ public class WorldScene extends BaseAppState {
|
|||||||
drownState.setReviveInfo(reviveClip, reviveLength);
|
drownState.setReviveInfo(reviveClip, reviveLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("true".equals(System.getProperty("blight.new.game"))) {
|
if (newGame) {
|
||||||
app.getStateManager().attach(
|
app.getStateManager().attach(
|
||||||
new de.blight.game.state.NewGameIntroState(playerInput, reviveClip, reviveLength));
|
new de.blight.game.state.NewGameIntroState(playerInput, thirdPersonCam, reviveClip, reviveLength));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,8 +95,8 @@ public class CompassHudState extends BaseAppState {
|
|||||||
if (compassNode == null) { return; }
|
if (compassNode == null) { return; }
|
||||||
|
|
||||||
Vector3f dir = cam.getDirection();
|
Vector3f dir = cam.getDirection();
|
||||||
// yaw=0 = Blick nach +Z (Nord); positiv = Osten
|
// yaw=0 = Blick nach -Z (Nord); JME3-Kamera blickt in -Z bei Norden
|
||||||
float yaw = FastMath.atan2(dir.x, dir.z);
|
float yaw = FastMath.atan2(dir.x, -dir.z);
|
||||||
|
|
||||||
// Rose dreht sich so, dass Nord immer in der echten Nord-Richtung liegt
|
// Rose dreht sich so, dass Nord immer in der echten Nord-Richtung liegt
|
||||||
roseRot.fromAngleAxis(-yaw, Vector3f.UNIT_Z);
|
roseRot.fromAngleAxis(-yaw, Vector3f.UNIT_Z);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import com.jme3.renderer.queue.RenderQueue;
|
|||||||
import com.jme3.scene.Geometry;
|
import com.jme3.scene.Geometry;
|
||||||
import com.jme3.scene.shape.Quad;
|
import com.jme3.scene.shape.Quad;
|
||||||
import de.blight.game.control.PlayerInputControl;
|
import de.blight.game.control.PlayerInputControl;
|
||||||
|
import de.blight.game.control.ThirdPersonCamera;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ public class NewGameIntroState extends BaseAppState {
|
|||||||
private enum Phase { BLACK, FADING_IN, REVIVE_PLAYING, DONE }
|
private enum Phase { BLACK, FADING_IN, REVIVE_PLAYING, DONE }
|
||||||
|
|
||||||
private final PlayerInputControl playerInput;
|
private final PlayerInputControl playerInput;
|
||||||
|
private final ThirdPersonCamera thirdPersonCam;
|
||||||
private final String reviveClip;
|
private final String reviveClip;
|
||||||
private final float reviveLength;
|
private final float reviveLength;
|
||||||
|
|
||||||
@@ -42,8 +44,10 @@ public class NewGameIntroState extends BaseAppState {
|
|||||||
private Phase phase;
|
private Phase phase;
|
||||||
private float timer;
|
private float timer;
|
||||||
|
|
||||||
public NewGameIntroState(PlayerInputControl playerInput, String reviveClip, float reviveLength) {
|
public NewGameIntroState(PlayerInputControl playerInput, ThirdPersonCamera thirdPersonCam,
|
||||||
|
String reviveClip, float reviveLength) {
|
||||||
this.playerInput = playerInput;
|
this.playerInput = playerInput;
|
||||||
|
this.thirdPersonCam = thirdPersonCam;
|
||||||
this.reviveClip = reviveClip;
|
this.reviveClip = reviveClip;
|
||||||
this.reviveLength = reviveLength;
|
this.reviveLength = reviveLength;
|
||||||
}
|
}
|
||||||
@@ -85,11 +89,15 @@ public class NewGameIntroState extends BaseAppState {
|
|||||||
case BLACK -> {
|
case BLACK -> {
|
||||||
timer -= tpf;
|
timer -= tpf;
|
||||||
if (timer <= 0f) {
|
if (timer <= 0f) {
|
||||||
// REVIVE unmittelbar vor dem Einblenden einfrieren
|
|
||||||
playerInput.startFrozenRevive(reviveClip);
|
playerInput.startFrozenRevive(reviveClip);
|
||||||
|
// Drehung startet sofort mit Fade-In: Gesamtdauer = Einblenden + REVIVE
|
||||||
|
if (thirdPersonCam != null) {
|
||||||
|
thirdPersonCam.startIntroRotation(FADE_DURATION + reviveLength);
|
||||||
|
}
|
||||||
phase = Phase.FADING_IN;
|
phase = Phase.FADING_IN;
|
||||||
timer = FADE_DURATION;
|
timer = FADE_DURATION;
|
||||||
log.info("[NewGameIntro] BLACK fertig – REVIVE eingefroren, starte FADING_IN ({} s)", FADE_DURATION);
|
log.info("[NewGameIntro] BLACK fertig – REVIVE eingefroren, Kamera dreht {} s, starte FADING_IN",
|
||||||
|
FADE_DURATION + reviveLength);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case FADING_IN -> {
|
case FADING_IN -> {
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user