Files
blight/blight-game/src/main/java/de/blight/game/control/NpcWeaponAttachmentControl.java

375 lines
20 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package de.blight.game.control;
import com.jme3.asset.AssetManager;
import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import de.blight.common.model.Item;
import de.blight.common.model.ItemGrip;
import de.blight.common.model.ItemSubCategory;
import de.blight.common.model.ShieldAttachmentIO;
import de.blight.common.model.ShieldCategoryAttachment;
import de.blight.common.model.WeaponAttachmentIO;
import de.blight.common.model.WeaponCategoryAttachment;
import de.blight.game.animation.RetargetingSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Hängt Waffe und Schild am NPC – 1:1 dieselbe Logik wie WeaponAttachmentControl (Held).
* - weggesteckt (drawn=false) → Spine2-Knochen (Rücken)
* - gezückt (drawn=true) → RightHand (Waffe) / LeftHand (Schild am Arm)
*/
public class NpcWeaponAttachmentControl extends AbstractControl {
private static final Logger log = LoggerFactory.getLogger(NpcWeaponAttachmentControl.class);
private static final String BONE_SPINE = "mixamorig:Spine2";
private static final String BONE_HAND = "mixamorig:RightHand";
private static final String BONE_PALM = "mixamorig:RightHandMiddle1";
private static final String BONE_LEFT_FOREARM = "mixamorig:LeftHand";
private static final float DEG_TO_RAD = (float) Math.PI / 180f;
private static final float HALF_PI = (float) Math.PI * 0.5f;
private static final Vector3f BACK_GRIP_OFFSET = new Vector3f(0.0f, 0.40f, -0.13f);
private static final Vector3f TWO_HAND_BACK_GRIP_OFFSET = new Vector3f(0.0f, 0.40f, -0.03f);
private static final Vector3f HAND_GRIP_OFFSET = new Vector3f(
ItemGrip.HAND_GRIP_OFFSET_X, ItemGrip.HAND_GRIP_OFFSET_Y, ItemGrip.HAND_GRIP_OFFSET_Z);
private static final Vector3f SHIELD_BACK_OFFSET = new Vector3f(0.12f, -0.10f, -0.10f);
private static final float SHIELD_ARM_ROT_X = -HALF_PI;
private static final float SHIELD_ARM_ROT_Y = 0f;
private static final float SHIELD_ARM_ROT_Z = 0f;
private static final float SHIELD_ARM_ELBOW_OFFSET = 0.25f;
private final AssetManager assetManager;
private final Item weaponItem;
private final Item shieldItem;
private com.jme3.anim.SkinningControl skinCtrl;
private Node backNode;
private Node handNode;
private Node palmNodeRef;
private Node leftForeArmNode;
private Quaternion handBindRotInChar;
private Quaternion leftForeArmBindRotInChar;
private Vector3f palmOffsetLocal;
private int palmOffsetFrameDelay = 0;
private Spatial weaponSpatial;
private Spatial shieldSpatial;
private boolean drawn = false;
private boolean weaponHidden = false;
private boolean shieldHidden = false;
public NpcWeaponAttachmentControl(AssetManager assetManager, Item weaponItem, Item shieldItem) {
this.assetManager = assetManager;
this.weaponItem = weaponItem;
this.shieldItem = shieldItem;
}
public void setDrawn(boolean drawn) {
this.drawn = drawn;
}
public void hideWeapon() {
weaponHidden = true;
if (weaponSpatial != null) { weaponSpatial.setCullHint(Spatial.CullHint.Always); }
}
public void hideShield() {
shieldHidden = true;
if (shieldSpatial != null) { shieldSpatial.setCullHint(Spatial.CullHint.Always); }
}
@Override
protected void controlUpdate(float tpf) {
if (skinCtrl == null) {
skinCtrl = RetargetingSystem.findSkinningControl(spatial);
if (skinCtrl == null) { return; }
try { backNode = skinCtrl.getAttachmentsNode(BONE_SPINE); }
catch (Exception e) { log.warn("[NpcWeaponAttach] Bone '{}' nicht gefunden: {}", BONE_SPINE, e.getMessage()); }
try { handNode = skinCtrl.getAttachmentsNode(BONE_HAND); }
catch (Exception e) { log.warn("[NpcWeaponAttach] Bone '{}' nicht gefunden: {}", BONE_HAND, e.getMessage()); }
try {
palmNodeRef = skinCtrl.getAttachmentsNode(BONE_PALM);
palmOffsetFrameDelay = 2;
} catch (Exception e) { log.warn("[NpcWeaponAttach] Bone '{}' nicht gefunden: {}", BONE_PALM, e.getMessage()); }
try { leftForeArmNode = skinCtrl.getAttachmentsNode(BONE_LEFT_FOREARM); }
catch (Exception e) { log.warn("[NpcWeaponAttach] Bone '{}' nicht gefunden: {}", BONE_LEFT_FOREARM, e.getMessage()); }
handBindRotInChar = extractJointBindRot(skinCtrl, BONE_HAND);
leftForeArmBindRotInChar = extractJointBindRot(skinCtrl, BONE_LEFT_FOREARM);
log.info("[NpcWeaponAttach] Setup: backNode={} handNode={} palmNode={} forearmNode={} weapon={} shield={}",
backNode != null, handNode != null, palmNodeRef != null, leftForeArmNode != null,
weaponItem != null ? weaponItem.getItemId() : "null",
shieldItem != null ? shieldItem.getItemId() : "null");
if (weaponItem != null) {
weaponSpatial = loadAndAttach(weaponItem);
if (weaponHidden && weaponSpatial != null) { weaponSpatial.setCullHint(Spatial.CullHint.Always); }
}
if (shieldItem != null) {
shieldSpatial = loadAndAttach(shieldItem);
if (shieldHidden && shieldSpatial != null) { shieldSpatial.setCullHint(Spatial.CullHint.Always); }
}
log.info("[NpcWeaponAttach] Geladen: weaponSpatial={} shieldSpatial={}",
weaponSpatial != null, shieldSpatial != null);
}
// Palm-Offset nach frühestens 2 Frames berechnen (SkinningControl muss vorher gelaufen sein)
if (palmOffsetFrameDelay > 0 && palmNodeRef != null && handNode != null) {
palmOffsetFrameDelay--;
if (palmOffsetFrameDelay == 0) {
palmOffsetLocal = computePalmOffset(handNode, palmNodeRef);
}
}
Quaternion charWorldRotInv = spatial.getWorldRotation().inverse();
Vector3f charWorldPos = spatial.getWorldTranslation();
// ── Waffe ────────────────────────────────────────────────────────────────
if (weaponSpatial != null && !weaponHidden) {
Node boneNode = drawn ? handNode : backNode;
if (boneNode != null) {
Vector3f boneInChar = charWorldRotInv.mult(boneNode.getWorldTranslation().subtract(charWorldPos));
Quaternion boneRotInChar = charWorldRotInv.mult(boneNode.getWorldRotation());
if (drawn) {
ItemGrip wGrip = weaponItem != null ? weaponItem.getGrip() : null;
boolean isTwoHand = weaponItem != null && weaponItem.getSubCategory() == ItemSubCategory.TWO_HANDED_SWORD;
Vector3f attachInChar = (palmOffsetLocal != null)
? boneInChar.add(boneRotInChar.mult(palmOffsetLocal.mult(0.5f)))
: boneInChar;
boolean usesTwoHand = isTwoHand && leftForeArmNode != null
&& wGrip != null
&& (wGrip.getGrip2PosX() != 0f || wGrip.getGrip2PosY() != 0f || wGrip.getGrip2PosZ() != 0f);
WeaponCategoryAttachment cfg = WeaponAttachmentIO.get(
weaponItem != null ? weaponItem.getSubCategory() : null);
if (usesTwoHand) {
applyTwoHandTransform(weaponSpatial, attachInChar, boneRotInChar,
wGrip, charWorldRotInv, charWorldPos, cfg);
} else {
applyHandTransform(weaponSpatial, attachInChar, boneRotInChar, wGrip, cfg);
}
} else {
boolean twoHanded = weaponItem != null
&& weaponItem.getSubCategory() == ItemSubCategory.TWO_HANDED_SWORD;
applyBackTransform(weaponSpatial, boneInChar, boneRotInChar,
weaponItem != null ? weaponItem.getGrip() : null, twoHanded);
}
}
}
// ── Schild ────────────────────────────────────────────────────────────────
if (shieldSpatial != null && !shieldHidden) {
ItemGrip grip = shieldItem != null ? shieldItem.getGrip() : null;
if (drawn && leftForeArmNode != null) {
Vector3f boneInChar = charWorldRotInv.mult(leftForeArmNode.getWorldTranslation().subtract(charWorldPos));
Quaternion boneRotInChar = charWorldRotInv.mult(leftForeArmNode.getWorldRotation());
ShieldCategoryAttachment shieldCfg = ShieldAttachmentIO.get(
shieldItem != null ? shieldItem.getSubCategory() : null);
applyShieldArmTransform(shieldSpatial, boneInChar, boneRotInChar, grip, shieldCfg);
shieldSpatial.setCullHint(Spatial.CullHint.Inherit);
} else if (!drawn && backNode != null) {
Vector3f boneInChar = charWorldRotInv.mult(backNode.getWorldTranslation().subtract(charWorldPos));
Quaternion boneRotInChar = charWorldRotInv.mult(backNode.getWorldRotation());
applyShieldBackTransform(shieldSpatial, boneInChar, boneRotInChar, grip);
shieldSpatial.setCullHint(Spatial.CullHint.Inherit);
} else {
shieldSpatial.setCullHint(Spatial.CullHint.Always);
}
}
}
private Spatial loadAndAttach(Item item) {
if (item.getModelRef() == null || item.getModelRef().getPath() == null) { return null; }
try {
Spatial s = assetManager.loadModel(item.getModelRef().getPath());
((Node) spatial).attachChild(s);
return s;
} catch (Exception e) {
log.warn("[NpcWeaponAttach] Modell nicht ladbar: {} — {}", item.getModelRef().getPath(), e.getMessage());
return null;
}
}
// ── Hand-Transform (1:1 von WeaponAttachmentControl) ─────────────────────────
private void applyHandTransform(Spatial s, Vector3f bonePosInChar,
Quaternion boneRotInChar, ItemGrip grip,
WeaponCategoryAttachment cfg) {
Quaternion gripRot = (grip != null)
? new Quaternion().fromAngles(DEG_TO_RAD * grip.getRotX(),
DEG_TO_RAD * grip.getRotY(),
DEG_TO_RAD * grip.getRotZ())
: new Quaternion();
Vector3f gripPos = (grip != null)
? new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ())
: Vector3f.ZERO;
Quaternion animDelta = (handBindRotInChar != null)
? boneRotInChar.mult(handBindRotInChar.inverse())
: boneRotInChar;
boolean bladeAtMinusZ = grip != null && (
(grip.getGrip2PosX() != 0f || grip.getGrip2PosY() != 0f || grip.getGrip2PosZ() != 0f)
? grip.getGrip2PosZ() > grip.getPosZ()
: grip.getPosZ() > 0f);
Quaternion bladeFlip = bladeAtMinusZ
? new Quaternion().fromAngles(com.jme3.math.FastMath.PI, 0f, 0f)
: new Quaternion();
Quaternion weaponRot = animDelta.mult(bladeFlip).mult(gripRot.inverse());
Vector3f handOff = bladeAtMinusZ
? new Vector3f(cfg.getHandOffX(), cfg.getHandOffY(), -cfg.getHandOffZ())
: new Vector3f(cfg.getHandOffX(), cfg.getHandOffY(), cfg.getHandOffZ());
Vector3f weaponPos = bonePosInChar.subtract(weaponRot.mult(gripPos));
weaponPos = weaponPos.add(weaponRot.mult(handOff));
if (grip != null && (grip.getHandOffsetX() != 0f || grip.getHandOffsetY() != 0f || grip.getHandOffsetZ() != 0f)) {
weaponPos = weaponPos.add(grip.getHandOffsetX(), grip.getHandOffsetY(), grip.getHandOffsetZ());
}
s.setLocalRotation(weaponRot);
s.setLocalTranslation(weaponPos);
}
private void applyTwoHandTransform(Spatial s, Vector3f bonePosInChar, Quaternion boneRotInChar,
ItemGrip grip, Quaternion charWorldRotInv, Vector3f charWorldPos,
WeaponCategoryAttachment cfg) {
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 bladeAtMinusZ = grip.getGrip2PosZ() > grip.getPosZ();
Quaternion bladeFlip = bladeAtMinusZ
? new Quaternion().fromAngles(com.jme3.math.FastMath.PI, 0f, 0f)
: new Quaternion();
Quaternion weaponRot = animDelta.mult(bladeFlip).mult(gripRot.inverse());
Vector3f g1 = new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ());
Vector3f g2 = new Vector3f(grip.getGrip2PosX(), grip.getGrip2PosY(), grip.getGrip2PosZ());
Vector3f handOff = bladeAtMinusZ
? new Vector3f(cfg.getHandOffX(), cfg.getHandOffY(), -cfg.getHandOffZ())
: new Vector3f(cfg.getHandOffX(), cfg.getHandOffY(), cfg.getHandOffZ());
Vector3f hand2Off = bladeAtMinusZ
? new Vector3f(cfg.getHand2OffX(), cfg.getHand2OffY(), -cfg.getHand2OffZ())
: new Vector3f(cfg.getHand2OffX(), cfg.getHand2OffY(), cfg.getHand2OffZ());
Vector3f g1eff = g1.subtract(handOff);
Vector3f g2eff = g2.add(hand2Off);
Vector3f leftHandInChar = charWorldRotInv.mult(leftForeArmNode.getWorldTranslation().subtract(charWorldPos));
Vector3f currentGripDir = weaponRot.mult(g2eff.subtract(g1eff).normalizeLocal());
Vector3f targetDir = leftHandInChar.subtract(bonePosInChar).normalizeLocal();
weaponRot = swingFromTo(currentGripDir, targetDir).mult(weaponRot);
Vector3f weaponPos = bonePosInChar.subtract(weaponRot.mult(g1eff));
s.setLocalRotation(weaponRot);
s.setLocalTranslation(weaponPos);
}
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 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(len));
}
private static final Quaternion BACK_ROT_NORM = new Quaternion(0f, -(float) Math.sqrt(0.5), (float) Math.sqrt(0.5), 0f);
private void applyBackTransform(Spatial s, Vector3f boneInChar,
Quaternion boneRotInChar, ItemGrip grip, boolean twoHanded) {
Quaternion gripRot = (grip != null)
? new Quaternion().fromAngles(DEG_TO_RAD * grip.getRotX(),
DEG_TO_RAD * grip.getRotY(),
DEG_TO_RAD * grip.getRotZ())
: new Quaternion();
boolean bladeAtMinusZ2 = grip != null && (
(grip.getGrip2PosX() != 0f || grip.getGrip2PosY() != 0f || grip.getGrip2PosZ() != 0f)
? grip.getGrip2PosZ() > grip.getPosZ()
: grip.getPosZ() > 0f);
Quaternion bladeFlip2 = bladeAtMinusZ2
? new Quaternion().fromAngles(com.jme3.math.FastMath.PI, 0f, 0f)
: new Quaternion();
Quaternion weaponRot = boneRotInChar.mult(BACK_ROT_NORM).mult(bladeFlip2).mult(gripRot.inverse());
Vector3f target = boneInChar.add(boneRotInChar.mult(
twoHanded ? TWO_HAND_BACK_GRIP_OFFSET : BACK_GRIP_OFFSET));
// X=0: Waffenmitte (nicht handspezifischer Griffpunkt) auf Wirbelsäule zentrieren
Vector3f gripPos = grip != null
? new Vector3f(0f, grip.getPosY(), grip.getPosZ()) : Vector3f.ZERO;
Vector3f backPos = target.subtract(weaponRot.mult(gripPos));
s.setLocalRotation(weaponRot);
s.setLocalTranslation(backPos);
}
// ── Schild-Transforms (1:1 von WeaponAttachmentControl) ──────────────────────
private void applyShieldArmTransform(Spatial s, Vector3f bonePosInChar,
Quaternion boneRotInChar, ItemGrip grip,
ShieldCategoryAttachment cfg) {
Quaternion animDelta = (leftForeArmBindRotInChar != null)
? boneRotInChar.mult(leftForeArmBindRotInChar.inverse())
: boneRotInChar;
Quaternion armRot = new Quaternion().fromAngles(
DEG_TO_RAD * cfg.getRotX(), DEG_TO_RAD * cfg.getRotY(), DEG_TO_RAD * cfg.getRotZ());
Quaternion shieldRot = animDelta.mult(armRot);
Vector3f gripPos = (grip != null)
? new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ()) : Vector3f.ZERO;
Vector3f anchor = bonePosInChar.add(boneRotInChar.mult(new Vector3f(0f, -cfg.getElbowOffset(), 0f)));
s.setLocalRotation(shieldRot);
s.setLocalTranslation(anchor.subtract(shieldRot.mult(gripPos)));
}
private void applyShieldBackTransform(Spatial s, Vector3f boneInChar,
Quaternion boneRotInChar, ItemGrip grip) {
Quaternion backRot = new Quaternion().fromAngles(0f, (float) Math.PI, 0f);
Quaternion shieldRot = boneRotInChar.mult(backRot);
Vector3f target = boneInChar.add(boneRotInChar.mult(SHIELD_BACK_OFFSET));
Vector3f gripPos = (grip != null)
? new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ()) : Vector3f.ZERO;
s.setLocalRotation(shieldRot);
s.setLocalTranslation(target.subtract(shieldRot.mult(gripPos)));
}
// ── Hilfsmethoden ────────────────────────────────────────────────────────────
private static Vector3f computePalmOffset(Node handNode, Node palmNode) {
Vector3f deltaWorld = palmNode.getWorldTranslation().subtract(handNode.getWorldTranslation());
return handNode.getWorldRotation().inverse().mult(deltaWorld);
}
private static Quaternion extractJointBindRot(com.jme3.anim.SkinningControl sc, String jointName) {
com.jme3.anim.Armature arm = sc.getArmature();
if (arm == null) { return null; }
com.jme3.anim.Joint joint = arm.getJoint(jointName);
if (joint == null) { return null; }
Matrix4f invBind = joint.getInverseModelBindMatrix();
if (invBind == null) { return null; }
Matrix4f bindMat = invBind.invert();
if (bindMat == null) { return null; }
return bindMat.toRotationQuat().normalizeLocal();
}
@Override
public void setSpatial(Spatial spatial) {
super.setSpatial(spatial);
if (spatial == null) {
if (weaponSpatial != null) { weaponSpatial.removeFromParent(); weaponSpatial = null; }
if (shieldSpatial != null) { shieldSpatial.removeFromParent(); shieldSpatial = null; }
}
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {}
}