From 1b509c3a5280e7536d0e4e99c888d40bdd4a4d87 Mon Sep 17 00:00:00 2001 From: Mario Date: Thu, 10 Sep 2026 20:54:36 +0200 Subject: [PATCH] Performance Tweaks --- .../game/control/PlayerInputControl.java | 66 ++++++++++--------- .../game/control/ThirdPersonCamera.java | 23 ++++--- .../java/de/blight/game/scene/WorldScene.java | 21 ++++-- .../de/blight/game/state/WorldNpcsState.java | 32 +++++---- 4 files changed, 85 insertions(+), 57 deletions(-) diff --git a/blight-game/src/main/java/de/blight/game/control/PlayerInputControl.java b/blight-game/src/main/java/de/blight/game/control/PlayerInputControl.java index ac19e5f..eefe054 100644 --- a/blight-game/src/main/java/de/blight/game/control/PlayerInputControl.java +++ b/blight-game/src/main/java/de/blight/game/control/PlayerInputControl.java @@ -120,6 +120,15 @@ public class PlayerInputControl { /** Autopilot: wenn gesetzt, geht der Charakter automatisch in diese (normalisierte) Richtung. */ private Vector3f autopilotDir = null; + // Pre-allokierte Scratch-Objekte — keine Heap-Allokation pro Frame in update() + private final Vector3f scratchCamDir = new Vector3f(); + private final Vector3f scratchCamLeft = new Vector3f(); + private final Vector3f scratchMoveDir = new Vector3f(); + private final Vector3f scratchWalk = new Vector3f(); + private final Quaternion scratchRot = new Quaternion(); + private final Quaternion scratchCurRot = new Quaternion(); + private final Vector3f scratchTransl = new Vector3f(); + private final ActionListener actionListener = (name, isPressed, tpf) -> { if (paused) return; switch (name) { @@ -477,7 +486,8 @@ public class PlayerInputControl { float t = Math.min(1f, animOffsetElapsed / AnimPlayer.BLEND_DURATION); animOffsetCurrent.set(animOffsetFrom); animOffsetCurrent.interpolateLocal(animOffsetTarget, t); - visual.setLocalTranslation(visualBaseTranslation.clone().addLocal(animOffsetCurrent)); + scratchTransl.set(visualBaseTranslation).addLocal(animOffsetCurrent); + visual.setLocalTranslation(scratchTransl); } if (inputsBlocked) return; @@ -513,19 +523,17 @@ public class PlayerInputControl { turnRemaining -= tpf; physicsChar.setWalkDirection(Vector3f.ZERO); if (visual != null && turnDir != null) { - Quaternion targetRot = new Quaternion(); - targetRot.lookAt(turnDir, Vector3f.UNIT_Y); - Quaternion current = visual.getLocalRotation().clone(); - current.slerp(targetRot, ROTATE_SPEED * tpf); - visual.setLocalRotation(current); + scratchRot.lookAt(turnDir, Vector3f.UNIT_Y); + scratchCurRot.set(visual.getLocalRotation()); + scratchCurRot.slerp(scratchRot, ROTATE_SPEED * tpf); + visual.setLocalRotation(scratchCurRot); } if (turnRemaining <= 0f) { turnActive = false; // Zielrotation exakt einrasten if (visual != null && turnDir != null) { - Quaternion snap = new Quaternion(); - snap.lookAt(turnDir, Vector3f.UNIT_Y); - visual.setLocalRotation(snap); + scratchRot.lookAt(turnDir, Vector3f.UNIT_Y); + visual.setLocalRotation(scratchRot); } Runnable cb = turnCallback; turnCallback = null; @@ -557,13 +565,12 @@ public class PlayerInputControl { // Autopilot: Charakter läuft automatisch in eine vorgegebene Richtung (WALK-Animation) if (autopilotDir != null) { - physicsChar.setWalkDirection(autopilotDir.mult(MOVE_SPEED * WALK_MULT)); + physicsChar.setWalkDirection(scratchWalk.set(autopilotDir).multLocal(MOVE_SPEED * WALK_MULT)); if (visual != null) { - Quaternion targetRot = new Quaternion(); - targetRot.lookAt(autopilotDir, Vector3f.UNIT_Y); - Quaternion current = visual.getLocalRotation().clone(); - current.slerp(targetRot, ROTATE_SPEED * tpf); - visual.setLocalRotation(current); + scratchRot.lookAt(autopilotDir, Vector3f.UNIT_Y); + scratchCurRot.set(visual.getLocalRotation()); + scratchCurRot.slerp(scratchRot, ROTATE_SPEED * tpf); + visual.setLocalRotation(scratchCurRot); } if (AnimationAction.WALK != currentAnim) { playAction(AnimationAction.WALK); @@ -573,16 +580,16 @@ public class PlayerInputControl { } // Normale Spielereingabe (WASD) - Vector3f camDir = cam.getDirection().clone().setY(0).normalizeLocal(); - Vector3f camLeft = cam.getLeft().clone().setY(0).normalizeLocal(); + cam.getDirection(scratchCamDir).setY(0).normalizeLocal(); + cam.getLeft(scratchCamLeft).setY(0).normalizeLocal(); - Vector3f moveDir = new Vector3f(); - if (forward) moveDir.addLocal(camDir); - if (backward) moveDir.subtractLocal(camDir); - if (left) moveDir.addLocal(camLeft); - if (right) moveDir.subtractLocal(camLeft); + scratchMoveDir.set(0f, 0f, 0f); + if (forward) scratchMoveDir.addLocal(scratchCamDir); + if (backward) scratchMoveDir.subtractLocal(scratchCamDir); + if (left) scratchMoveDir.addLocal(scratchCamLeft); + if (right) scratchMoveDir.subtractLocal(scratchCamLeft); - boolean moving = moveDir.lengthSquared() > 0.001f; + boolean moving = scratchMoveDir.lengthSquared() > 0.001f; // Wassertiefe bestimmen – einmal pro Frame, auch in pollFootsteps() genutzt Vector3f charPos = physicsChar.getPhysicsLocation(); @@ -590,18 +597,17 @@ public class PlayerInputControl { boolean inDeepWater = currentWaterDepth > WATER_WADING_DEPTH; if (moving) { - moveDir.normalizeLocal(); + scratchMoveDir.normalizeLocal(); float speed = (inDeepWater || walk) ? MOVE_SPEED * WALK_MULT : sprint ? MOVE_SPEED * SPRINT_MULT : MOVE_SPEED; - physicsChar.setWalkDirection(moveDir.mult(speed)); + physicsChar.setWalkDirection(scratchWalk.set(scratchMoveDir).multLocal(speed)); if (visual != null) { - Quaternion targetRot = new Quaternion(); - targetRot.lookAt(moveDir, Vector3f.UNIT_Y); - Quaternion current = visual.getLocalRotation().clone(); - current.slerp(targetRot, ROTATE_SPEED * tpf); - visual.setLocalRotation(current); + scratchRot.lookAt(scratchMoveDir, Vector3f.UNIT_Y); + scratchCurRot.set(visual.getLocalRotation()); + scratchCurRot.slerp(scratchRot, ROTATE_SPEED * tpf); + visual.setLocalRotation(scratchCurRot); } } else { physicsChar.setWalkDirection(Vector3f.ZERO); diff --git a/blight-game/src/main/java/de/blight/game/control/ThirdPersonCamera.java b/blight-game/src/main/java/de/blight/game/control/ThirdPersonCamera.java index 7607b75..da3c9e6 100644 --- a/blight-game/src/main/java/de/blight/game/control/ThirdPersonCamera.java +++ b/blight-game/src/main/java/de/blight/game/control/ThirdPersonCamera.java @@ -48,6 +48,11 @@ public class ThirdPersonCamera { private float introDuration; private float introTimer; + // Pre-allokierte Scratch-Vektoren — keine Heap-Allokation pro Frame + private final Vector3f scratchPivot = new Vector3f(); + private final Vector3f scratchCamPos = new Vector3f(); + private final Vector3f scratchDir = new Vector3f(); + public ThirdPersonCamera(Camera cam, InputManager inputManager, KeyBindings keyBindings) { this.cam = cam; @@ -111,28 +116,28 @@ public class ThirdPersonCamera { if (target == null || paused) return; - Vector3f pivot = target.getWorldTranslation().add(0, TARGET_HEIGHT, 0); + scratchPivot.set(target.getWorldTranslation()).addLocal(0f, TARGET_HEIGHT, 0f); // Sphärische Koordinaten → kartesisch float x = distance * FastMath.cos(pitch) * FastMath.sin(yaw); float y = distance * FastMath.sin(pitch); float z = distance * FastMath.cos(pitch) * FastMath.cos(yaw); - Vector3f camPos = pivot.add(x, y, z); + scratchCamPos.set(scratchPivot).addLocal(x, y, z); if (raycastProvider != null) { - float fraction = raycastProvider.apply(pivot, camPos); + float fraction = raycastProvider.apply(scratchPivot, scratchCamPos); if (fraction < 1f) { - Vector3f dir = camPos.subtract(pivot); - float rayLen = dir.length(); - dir.normalizeLocal(); + scratchDir.set(scratchCamPos).subtractLocal(scratchPivot); + float rayLen = scratchDir.length(); + scratchDir.normalizeLocal(); float safeLen = Math.max(MIN_DISTANCE * 0.5f, fraction * rayLen - CAM_CLEARANCE); - camPos = pivot.add(dir.mult(safeLen)); + scratchCamPos.set(scratchPivot).addLocal(scratchDir.multLocal(safeLen)); } } - cam.setLocation(camPos); - cam.lookAt(pivot, Vector3f.UNIT_Y); + cam.setLocation(scratchCamPos); + cam.lookAt(scratchPivot, Vector3f.UNIT_Y); } /** Aktueller Yaw-Winkel (für CharacterControl nutzbar). */ diff --git a/blight-game/src/main/java/de/blight/game/scene/WorldScene.java b/blight-game/src/main/java/de/blight/game/scene/WorldScene.java index be16f44..45e9233 100644 --- a/blight-game/src/main/java/de/blight/game/scene/WorldScene.java +++ b/blight-game/src/main/java/de/blight/game/scene/WorldScene.java @@ -108,6 +108,13 @@ public class WorldScene extends BaseAppState { private de.blight.game.state.DrownState drownState; private WaterInteractionState waterInteractionState; + // Pre-allokierte Scratch-Objekte — keine Heap-Allokation pro Frame in update() + private final Vector3f scratchLightDir = new Vector3f(); + private final Vector3f scratchSunColor = new Vector3f(); + private final Vector3f scratchAmbColor = new Vector3f(); + private final ColorRGBA scratchEmissive = new ColorRGBA(); + private final Vector3f scratchSunPos = new Vector3f(); + public WorldScene(KeyBindings keyBindings, GraphicsSettings graphicsSettings) { this.keyBindings = keyBindings; this.graphicsSettings = graphicsSettings; @@ -466,23 +473,25 @@ public class WorldScene extends BaseAppState { musicSystem.setDaytime(dayNight.getSunElevation() >= 0f); if (terrainMaterial != null && dayNight != null && dayNight.getSunLight() != null) { - terrainMaterial.setVector3("LightDir", dayNight.getSunDirection().negate()); + scratchLightDir.set(dayNight.getSunDirection()).negateLocal(); + terrainMaterial.setVector3("LightDir", scratchLightDir); ColorRGBA sc = dayNight.getSunLight().getColor(); ColorRGBA ac = dayNight.getCustomAmbient(); - terrainMaterial.setVector3("SunColor", new Vector3f(sc.r, sc.g, sc.b)); - terrainMaterial.setVector3("AmbientColor", new Vector3f(ac.r, ac.g, ac.b)); + terrainMaterial.setVector3("SunColor", scratchSunColor.set(sc.r, sc.g, sc.b)); + terrainMaterial.setVector3("AmbientColor", scratchAmbColor.set(ac.r, ac.g, ac.b)); de.blight.game.state.RainState rs = app.getStateManager().getState(de.blight.game.state.RainState.class); if (rs != null) terrainMaterial.setFloat("Wetness", rs.getWetness()); // Charakter-PBR: Emissive = customAmbient (IBL-Fallback ohne fertige LightProbe) + scratchEmissive.set(ac.r, ac.g, ac.b, 1f); for (Material mat : characterPbrMaterials) { - mat.setColor("Emissive", new ColorRGBA(ac.r, ac.g, ac.b, 1f)); + mat.setColor("Emissive", scratchEmissive); } if (lightScatterFilter != null) { Vector3f sunDir = dayNight.getSunDirection(); // Weltposition der Sonne: von der Kamera aus weit entfernt entgegen der Lichtrichtung - Vector3f sunWorld = app.getCamera().getLocation().add(sunDir.negate().mult(10000f)); - lightScatterFilter.setLightPosition(sunWorld); + scratchSunPos.set(sunDir).negateLocal().multLocal(10000f).addLocal(app.getCamera().getLocation()); + lightScatterFilter.setLightPosition(scratchSunPos); // Deaktivieren wenn Sonne hinter der Kamera (gleiche Richtung wie Kamera schaut) lightScatterFilter.setEnabled(sunDir.dot(app.getCamera().getDirection()) < 0.2f); } diff --git a/blight-game/src/main/java/de/blight/game/state/WorldNpcsState.java b/blight-game/src/main/java/de/blight/game/state/WorldNpcsState.java index caa43c9..6064b6c 100644 --- a/blight-game/src/main/java/de/blight/game/state/WorldNpcsState.java +++ b/blight-game/src/main/java/de/blight/game/state/WorldNpcsState.java @@ -147,6 +147,12 @@ public class WorldNpcsState extends BaseAppState { private float revertTimer = 0f; private float revertDelay = 0f; + // Pre-allokierte Scratch-Objekte — keine Heap-Allokation in hot paths + private final List toRemoveBuf = new ArrayList<>(); + private final Set spawnedIdsBuf = new HashSet<>(); + private final Quaternion scratchQ = new Quaternion(); + private final com.jme3.math.ColorRGBA scratchEmissive = new com.jme3.math.ColorRGBA(); + // ── Konstruktor ─────────────────────────────────────────────────────────── public WorldNpcsState(KeyBindings keyBindings, CharacterControl physicsChar, @@ -244,14 +250,14 @@ public class WorldNpcsState extends BaseAppState { Vector3f playerPos = physicsChar.getPhysicsLocation(); // Bestehende NPCs prüfen: Position aktualisieren oder entladen - List toRemove = new ArrayList<>(); + toRemoveBuf.clear(); for (SpawnedNpc s : spawned) { Vector3f pos = resolveRoutinePosition(s.npc(), currentHour); float dist = pos != null ? dist2d(playerPos, pos) : Float.MAX_VALUE; if (pos == null || dist > UNLOAD_RADIUS) { s.visual().removeFromParent(); - toRemove.add(s); + toRemoveBuf.add(s); if (rotNpc == s) rotNpc = null; if (revertNpc == s) revertNpc = null; } else { @@ -260,27 +266,28 @@ public class WorldNpcsState extends BaseAppState { playNpcAction(s, activityActionFor(s.npc(), currentHour)); } } - spawned.removeAll(toRemove); + spawned.removeAll(toRemoveBuf); // PBR-Emissive der verbleibenden NPCs mit aktuellem Ambient aktualisieren if (dayNight != null) { com.jme3.math.ColorRGBA ac = dayNight.getCustomAmbient(); + scratchEmissive.set(ac.r, ac.g, ac.b, 1f); for (SpawnedNpc s : spawned) { for (Material mat : s.pbrMaterials) { - mat.setColor("Emissive", new com.jme3.math.ColorRGBA(ac.r, ac.g, ac.b, 1f)); + mat.setColor("Emissive", scratchEmissive); } } } // IDs der bereits gespawnten NPCs für schnelle Prüfung - Set spawnedIds = new HashSet<>(); + spawnedIdsBuf.clear(); for (SpawnedNpc s : spawned) { - spawnedIds.add(s.npc().getCharacterId()); + spawnedIdsBuf.add(s.npc().getCharacterId()); } // Neue NPCs einladen wenn in Reichweite for (NPC npc : allNpcs) { - if (spawnedIds.contains(npc.getCharacterId())) continue; + if (spawnedIdsBuf.contains(npc.getCharacterId())) continue; Vector3f pos = resolveRoutinePosition(npc, currentHour); if (pos == null) continue; if (dist2d(playerPos, pos) > LOAD_RADIUS) continue; @@ -292,8 +299,9 @@ public class WorldNpcsState extends BaseAppState { de.blight.game.scene.WorldScene.collectPbrMaterials(vis, s.pbrMaterials); if (dayNight != null && !s.pbrMaterials.isEmpty()) { com.jme3.math.ColorRGBA ac = dayNight.getCustomAmbient(); + scratchEmissive.set(ac.r, ac.g, ac.b, 1f); for (Material mat : s.pbrMaterials) { - mat.setColor("Emissive", new com.jme3.math.ColorRGBA(ac.r, ac.g, ac.b, 1f)); + mat.setColor("Emissive", scratchEmissive); } } spawned.add(s); @@ -482,8 +490,8 @@ public class WorldNpcsState extends BaseAppState { if (rotNpc == null) return; rotElapsed += tpf; float p = FastMath.clamp(rotElapsed / ROT_DURATION, 0f, 1f); - Quaternion q = new Quaternion().slerp(rotFrom, rotTo, p); - rotNpc.visual().setLocalRotation(q); + scratchQ.slerp(rotFrom, rotTo, p); + rotNpc.visual().setLocalRotation(scratchQ); if (p >= 1f) rotNpc = null; } @@ -492,8 +500,8 @@ public class WorldNpcsState extends BaseAppState { revertTimer += tpf; if (revertTimer < revertDelay) return; float p = FastMath.clamp((revertTimer - revertDelay) / REVERT_DURATION, 0f, 1f); - Quaternion q = new Quaternion().slerp(revertFrom, revertTo, p); - revertNpc.visual().setLocalRotation(q); + scratchQ.slerp(revertFrom, revertTo, p); + revertNpc.visual().setLocalRotation(scratchQ); if (p >= 1f) revertNpc = null; }