diff --git a/blight-game/src/main/java/de/blight/game/control/NpcWeaponAttachmentControl.java b/blight-game/src/main/java/de/blight/game/control/NpcWeaponAttachmentControl.java index ad5db4b..2e9cc16 100644 --- a/blight-game/src/main/java/de/blight/game/control/NpcWeaponAttachmentControl.java +++ b/blight-game/src/main/java/de/blight/game/control/NpcWeaponAttachmentControl.java @@ -222,39 +222,36 @@ public class NpcWeaponAttachmentControl extends AbstractControl { s.setLocalTranslation(weaponPos); } - /** - * Zweihänder: Schwert dreht sich frei um Griff1 (rechte Handteller-Innenfläche) als Pivot; - * die Richtung wird so gewählt, dass Griff2 so nah wie möglich am linken Handteller liegt. - */ private void applyTwoHandTransform(Spatial s, Vector3f bonePosInChar, Quaternion boneRotInChar, ItemGrip grip, Quaternion charWorldRotInv, Vector3f charWorldPos) { Quaternion gripRot = new Quaternion().fromAngles( DEG_TO_RAD * grip.getRotX(), DEG_TO_RAD * grip.getRotY(), DEG_TO_RAD * grip.getRotZ()); Quaternion animDelta = (handBindRotInChar != null) ? boneRotInChar.mult(handBindRotInChar.inverse()) : boneRotInChar; - boolean bladeAtMinusZ3 = grip.getGrip2PosZ() > grip.getPosZ(); - Quaternion bladeFlip3 = bladeAtMinusZ3 + boolean bladeAtMinusZ = grip.getGrip2PosZ() > grip.getPosZ(); + Quaternion bladeFlip = bladeAtMinusZ ? new Quaternion().fromAngles(com.jme3.math.FastMath.PI, 0f, 0f) : new Quaternion(); - Quaternion weaponRot = animDelta.mult(bladeFlip3).mult(gripRot.inverse()); + Quaternion weaponRot = animDelta.mult(bladeFlip).mult(gripRot.inverse()); - // Griff-Achse im Modell-Space: Griff1 → Griff2 Vector3f g1 = new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ()); Vector3f g2 = new Vector3f(grip.getGrip2PosX(), grip.getGrip2PosY(), grip.getGrip2PosZ()); - Vector3f modelGripAxis = g2.subtract(g1).normalizeLocal(); - Vector3f currentGripAxis = weaponRot.mult(modelGripAxis); + Vector3f leftHandInChar = charWorldRotInv.mult(leftForeArmNode.getWorldTranslation().subtract(charWorldPos)); + Quaternion leftBoneRotInChar = charWorldRotInv.mult(leftForeArmNode.getWorldRotation()); + Vector3f leftHandballen = (palmOffsetLocal != null) + ? leftHandInChar.subtract(leftBoneRotInChar.mult(palmOffsetLocal.mult(0.5f))) + : leftHandInChar; - // Zielachse: rechtes Handgelenk → linkes Handgelenk im Char-Space - Vector3f rightWristInChar = charWorldRotInv.mult(handNode.getWorldTranslation().subtract(charWorldPos)); - Vector3f leftWristInChar = charWorldRotInv.mult(leftForeArmNode.getWorldTranslation().subtract(charWorldPos)); - Vector3f targetGripAxis = leftWristInChar.subtract(rightWristInChar).normalizeLocal(); + Vector3f currentGripDir = weaponRot.mult(g2.subtract(g1).normalizeLocal()); + Vector3f targetDir = leftHandInChar.subtract(bonePosInChar).normalizeLocal(); + float dot = currentGripDir.dot(targetDir); + if (dot > 0f) { + weaponRot = swingFromTo(currentGripDir, targetDir).mult(weaponRot); + } - Quaternion swingCorr = swingFromTo(currentGripAxis, targetGripAxis); - weaponRot = swingCorr.mult(weaponRot); - - // Grip1 direkt am Handteller platzieren (bonePosInChar enthält palmOffset bereits). - // Kein zusätzlicher HAND_GRIP_OFFSET – wir haben die exakten Greifpositionen. - Vector3f weaponPos = bonePosInChar.subtract(weaponRot.mult(g1)); + Vector3f midGrip = g1.add(g2).multLocal(0.5f); + Vector3f midTarget = bonePosInChar.add(leftHandballen).multLocal(0.5f); + Vector3f weaponPos = midTarget.subtract(weaponRot.mult(midGrip)); s.setLocalRotation(weaponRot); s.setLocalTranslation(weaponPos); } @@ -262,15 +259,15 @@ public class NpcWeaponAttachmentControl extends AbstractControl { private static Quaternion swingFromTo(Vector3f from, Vector3f to) { float dot = com.jme3.math.FastMath.clamp(from.dot(to), -1f, 1f); Vector3f axis = from.cross(to); - float axisLen = axis.length(); - if (axisLen < 0.001f) { + float len = axis.length(); + if (len < 0.001f) { if (dot > 0f) return new Quaternion(); Vector3f perp = Math.abs(from.x) <= Math.abs(from.y) ? from.cross(Vector3f.UNIT_X).normalizeLocal() : from.cross(Vector3f.UNIT_Y).normalizeLocal(); return new Quaternion().fromAngleNormalAxis(com.jme3.math.FastMath.PI, perp); } - return new Quaternion().fromAngleNormalAxis(com.jme3.math.FastMath.acos(dot), axis.divideLocal(axisLen)); + return new Quaternion().fromAngleNormalAxis(com.jme3.math.FastMath.acos(dot), axis.divideLocal(len)); } private static final Quaternion BACK_ROT_NORM = new Quaternion(0f, -(float) Math.sqrt(0.5), (float) Math.sqrt(0.5), 0f); diff --git a/blight-game/src/main/java/de/blight/game/control/WeaponAttachmentControl.java b/blight-game/src/main/java/de/blight/game/control/WeaponAttachmentControl.java index 4bcf44a..98f3565 100644 --- a/blight-game/src/main/java/de/blight/game/control/WeaponAttachmentControl.java +++ b/blight-game/src/main/java/de/blight/game/control/WeaponAttachmentControl.java @@ -70,7 +70,8 @@ public class WeaponAttachmentControl extends AbstractControl { private Item lastEquippedWeapon; private boolean lastDrawnState = false; private Spatial weaponSpatial; - private boolean gripDiagLogged = false; + private boolean gripDiagLogged = false; + private int twoHandDiagFrame = 0; private Item lastEquippedShield; private Spatial shieldSpatial; @@ -270,44 +271,42 @@ public class WeaponAttachmentControl extends AbstractControl { weaponSpatial.setLocalTranslation(weaponPos); } - /** - * Zweihänder-Constraint: orientiert die Waffe so, dass die Linie grip1→grip2 (Modell-Space) - * auf die Linie RightHand→LeftHand (Char-Space) zeigt. Roll bleibt aus gripRot erhalten. - */ private void applyTwoHandTransform(Vector3f bonePosInChar, Quaternion boneRotInChar, ItemGrip grip, Quaternion charWorldRotInv, Vector3f charWorldPos) { - // Erst Standard-Einhand-Rotation berechnen (gibt Roll-Basis aus gripRot) + // Basis-Rotation aus rechter Hand (gibt Roll/Handflächenausrichtung) Quaternion gripRot = new Quaternion().fromAngles( DEG_TO_RAD * grip.getRotX(), DEG_TO_RAD * grip.getRotY(), DEG_TO_RAD * grip.getRotZ()); Quaternion animDelta = (handBindRotInChar != null) ? boneRotInChar.mult(handBindRotInChar.inverse()) : boneRotInChar; - boolean bladeAtMinusZ3 = grip.getGrip2PosZ() > grip.getPosZ(); - Quaternion bladeFlip3 = bladeAtMinusZ3 + boolean bladeAtMinusZ = grip.getGrip2PosZ() > grip.getPosZ(); + Quaternion bladeFlip = bladeAtMinusZ ? new Quaternion().fromAngles(com.jme3.math.FastMath.PI, 0f, 0f) : new Quaternion(); - Quaternion weaponRot = animDelta.mult(bladeFlip3).mult(gripRot.inverse()); + Quaternion weaponRot = animDelta.mult(bladeFlip).mult(gripRot.inverse()); - // Griff-Achse im Modell-Space: grip1 → grip2 + // Griff-Achse g1→g2 in CharSpace; Ziel = rechteHand→linkeHand Vector3f g1 = new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ()); Vector3f g2 = new Vector3f(grip.getGrip2PosX(), grip.getGrip2PosY(), grip.getGrip2PosZ()); - Vector3f modelGripAxis = g2.subtract(g1).normalizeLocal(); + Vector3f leftHandInChar = charWorldRotInv.mult(leftForeArmNode.getWorldTranslation().subtract(charWorldPos)); + // Linke Hand: Handballen-Korrektur (Palm-Offset entgegen Fingerrichtung) – nur für midTarget, nicht für targetDir + Quaternion leftBoneRotInChar = charWorldRotInv.mult(leftForeArmNode.getWorldRotation()); + Vector3f leftHandballen = (palmOffsetLocal != null) + ? leftHandInChar.subtract(leftBoneRotInChar.mult(palmOffsetLocal.mult(0.5f))) + : leftHandInChar; - // Aktuelle Griff-Achse im Char-Space (nach Einhand-Rotation) - Vector3f currentGripAxis = weaponRot.mult(modelGripAxis); + Vector3f currentGripDir = weaponRot.mult(g2.subtract(g1).normalizeLocal()); + Vector3f targetDir = leftHandInChar.subtract(bonePosInChar).normalizeLocal(); + float dot = currentGripDir.dot(targetDir); - // Ziel-Achse: rechtes Handgelenk → linkes Handgelenk im Char-Space - Vector3f rightWristInChar = charWorldRotInv.mult(handNode.getWorldTranslation().subtract(charWorldPos)); - Vector3f leftWristInChar = charWorldRotInv.mult(leftForeArmNode.getWorldTranslation().subtract(charWorldPos)); - Vector3f targetGripAxis = leftWristInChar.subtract(rightWristInChar).normalizeLocal(); + if (dot > 0f) { + weaponRot = swingFromTo(currentGripDir, targetDir).mult(weaponRot); + } - // Minimale Swing-Rotation: dreht currentGripAxis → targetGripAxis, kein Roll-Einfluss - Quaternion swingCorr = swingFromTo(currentGripAxis, targetGripAxis); - weaponRot = swingCorr.mult(weaponRot); - - // Grip1 direkt am Handteller platzieren (bonePosInChar enthält palmOffset bereits). - // Kein zusätzlicher HAND_GRIP_OFFSET – wir haben die exakten Greifpositionen. - Vector3f weaponPos = bonePosInChar.subtract(weaponRot.mult(g1)); + // Midpoint-Anker: Mitte von g1–g2 auf Mitte beider Handballen legen + Vector3f midGrip = g1.add(g2).multLocal(0.5f); + Vector3f midTarget = bonePosInChar.add(leftHandballen).multLocal(0.5f); + Vector3f weaponPos = midTarget.subtract(weaponRot.mult(midGrip)); weaponSpatial.setLocalRotation(weaponRot); weaponSpatial.setLocalTranslation(weaponPos); } @@ -315,15 +314,15 @@ public class WeaponAttachmentControl extends AbstractControl { private static Quaternion swingFromTo(Vector3f from, Vector3f to) { float dot = com.jme3.math.FastMath.clamp(from.dot(to), -1f, 1f); Vector3f axis = from.cross(to); - float axisLen = axis.length(); - if (axisLen < 0.001f) { + float len = axis.length(); + if (len < 0.001f) { if (dot > 0f) return new Quaternion(); Vector3f perp = Math.abs(from.x) <= Math.abs(from.y) ? from.cross(Vector3f.UNIT_X).normalizeLocal() : from.cross(Vector3f.UNIT_Y).normalizeLocal(); return new Quaternion().fromAngleNormalAxis(com.jme3.math.FastMath.PI, perp); } - return new Quaternion().fromAngleNormalAxis(com.jme3.math.FastMath.acos(dot), axis.divideLocal(axisLen)); + return new Quaternion().fromAngleNormalAxis(com.jme3.math.FastMath.acos(dot), axis.divideLocal(len)); } private static float round1(float v) { return Math.round(v * 10f) / 10f; } diff --git a/blight-game/src/main/java/de/blight/game/state/WorldItemsState.java b/blight-game/src/main/java/de/blight/game/state/WorldItemsState.java index 9ee8f30..6d4b6a2 100644 --- a/blight-game/src/main/java/de/blight/game/state/WorldItemsState.java +++ b/blight-game/src/main/java/de/blight/game/state/WorldItemsState.java @@ -246,7 +246,7 @@ public class WorldItemsState extends BaseAppState { boolean doLog = hoverLogTimer >= 1f && !items.isEmpty(); if (hoverLogTimer >= 1f) hoverLogTimer = 0f; if (doLog) { - log.info("[Hover] items={} screenW={} screenH={} centerX={} hMargin={}", + log.debug("[Hover] items={} screenW={} screenH={} centerX={} hMargin={}", items.size(), cam.getWidth(), cam.getHeight(), centerX, hMargin); } @@ -261,39 +261,39 @@ public class WorldItemsState extends BaseAppState { // Distanz vom Charakter (nicht von der Kamera) float charDist = worldPos.distance(charPos); - if (doLog) log.info("[Hover] item[{}] id={} charDist={}", i, pi.itemId(), charDist); + if (doLog) log.debug("[Hover] item[{}] id={} charDist={}", i, pi.itemId(), charDist); if (charDist > INTERACT_RANGE) { - if (doLog) log.info("[Hover] → skip: außerhalb Reichweite ({} > {})", charDist, INTERACT_RANGE); + if (doLog) log.debug("[Hover] → skip: außerhalb Reichweite ({} > {})", charDist, INTERACT_RANGE); continue; } // Muss vor der Kamera liegen Vector3f camToItem = worldPos.subtract(cam.getLocation()); if (camToItem.dot(cam.getDirection()) <= 0f) { - if (doLog) log.info("[Hover] → skip: hinter der Kamera"); + if (doLog) log.debug("[Hover] → skip: hinter der Kamera"); continue; } // Bildschirmprojektion (von der Kamera aus) Vector3f screen = cam.getScreenCoordinates(worldPos); float hDiff = Math.abs(screen.x - centerX); - if (doLog) log.info("[Hover] screen=({},{},{}) hDiff={} hMargin={}", + if (doLog) log.debug("[Hover] screen=({},{},{}) hDiff={} hMargin={}", (int)screen.x, (int)screen.y, screen.z, (int)hDiff, (int)hMargin); if (hDiff > hMargin) { - if (doLog) log.info("[Hover] → skip: horizontal außerhalb Mitte"); + if (doLog) log.debug("[Hover] → skip: horizontal außerhalb Mitte"); continue; } if (screen.y < 0f || screen.y > screenH) { - if (doLog) log.info("[Hover] → skip: vertikal außerhalb Bildschirm"); + if (doLog) log.debug("[Hover] → skip: vertikal außerhalb Bildschirm"); continue; } // Verdeckungsprüfung: Strahl von Kamera zum Item float camDist = camToItem.length(); if (isOccluded(camToItem.normalize(), camDist, i, doLog)) { - if (doLog) log.info("[Hover] → skip: verdeckt"); + if (doLog) log.debug("[Hover] → skip: verdeckt"); continue; } @@ -303,7 +303,7 @@ public class WorldItemsState extends BaseAppState { } } - if (doLog) log.info("[Hover] → hoveredIdx={}", bestIdx); + if (doLog) log.debug("[Hover] → hoveredIdx={}", bestIdx); setHovered(bestIdx); } @@ -312,25 +312,25 @@ public class WorldItemsState extends BaseAppState { CollisionResults results = new CollisionResults(); rootNode.collideWith(ray, results); - if (doLog) log.info("[Hover] occlusion: {} Treffer, itemDist={:.2f}", results.size(), itemDist); + if (doLog) log.debug("[Hover] occlusion: {} Treffer, itemDist={:.2f}", results.size(), itemDist); for (int j = 0; j < results.size(); j++) { CollisionResult cr = results.getCollision(j); float d = cr.getDistance(); if (d < 1.5f) { - if (doLog) log.info("[Hover] [{}] d={:.2f} → Nah-Clip skip ({})", j, d, cr.getGeometry().getName()); + if (doLog) log.debug("[Hover] [{}] d={:.2f} → Nah-Clip skip ({})", j, d, cr.getGeometry().getName()); continue; } if (d > itemDist - 0.15f) { - if (doLog) log.info("[Hover] [{}] d={:.2f} → am/hinter Item, kein Blocker ({})", j, d, cr.getGeometry().getName()); + if (doLog) log.debug("[Hover] [{}] d={:.2f} → am/hinter Item, kein Blocker ({})", j, d, cr.getGeometry().getName()); continue; } int vidx = findVisualIndex(cr.getGeometry()); if (vidx == itemIdx) { - if (doLog) log.info("[Hover] [{}] d={:.2f} → Item selbst ({})", j, d, cr.getGeometry().getName()); + if (doLog) log.debug("[Hover] [{}] d={:.2f} → Item selbst ({})", j, d, cr.getGeometry().getName()); continue; } - if (doLog) log.info("[Hover] [{}] d={:.2f} → BLOCKIERT durch '{}'", j, d, cr.getGeometry().getName()); + if (doLog) log.debug("[Hover] [{}] d={:.2f} → BLOCKIERT durch '{}'", j, d, cr.getGeometry().getName()); return true; } return false;