Sicherstellen des Zustands der passt
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,18 +1,19 @@
|
||||
{
|
||||
"clips": [
|
||||
"get_up_sitting",
|
||||
"alive_again",
|
||||
"idle",
|
||||
"idle_jump",
|
||||
"pickup",
|
||||
"running",
|
||||
"running_jump",
|
||||
"sitting",
|
||||
"sit_down_bench",
|
||||
"sitting_bench",
|
||||
"sitting_floor",
|
||||
"sprinting",
|
||||
"stand_up",
|
||||
"stand_up_bench",
|
||||
"tpose",
|
||||
"walking",
|
||||
"sit_down_bench"
|
||||
"walking"
|
||||
],
|
||||
"actionMap": {
|
||||
"DEFAULT": "tpose",
|
||||
@@ -20,12 +21,13 @@
|
||||
"WALK": "walking",
|
||||
"RUN": "running",
|
||||
"SPRINT": "sprinting",
|
||||
"RUNNING_JUMP": "running_jump",
|
||||
"JUMP": "idle_jump",
|
||||
"RUNNING_JUMP": "running_jump",
|
||||
"PICK_UP": "pickup",
|
||||
"SITTING": "sitting",
|
||||
"SIT_DOWN": "sit_down_bench",
|
||||
"SIT_UP": "get_up_sitting"
|
||||
"SIT_UP": "stand_up_bench",
|
||||
"SITTING": "sitting_bench",
|
||||
"REVIVE": "alive_again"
|
||||
},
|
||||
"previewModelPath": "Models/Chars/mainchar.j3o",
|
||||
"motionKeyframes": {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<logger name="com.jme3.util.TangentBinormalGenerator" level="ERROR"/>
|
||||
<!-- Material warnt bei linear-color-space Texturen ohne passenden Parameter – bekannt, kein Fehler -->
|
||||
<logger name="com.jme3.material.Material" level="ERROR"/>
|
||||
<logger name="de.blight.game.animation.FootIKControl" level="DEBUG"/>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package de.blight.game.animation;
|
||||
|
||||
/**
|
||||
* Positions-/Rotations-Versatz für eine blockierende Animation.
|
||||
*
|
||||
* TX/TZ: Versatz in Charakter-lokalem Raum (TX=seitlich, TZ=vorwärts relativ zur Blickrichtung).
|
||||
* TY: Versatz in Welt-Y (hoch/runter).
|
||||
* RX/RY/RZ: Additiver Rotations-Versatz in Grad (Euler XYZ, relativ zur Startrotation).
|
||||
*/
|
||||
public class AnimOffset {
|
||||
|
||||
public float tx, ty, tz; // Positions-Versatz (Meter)
|
||||
public float rx, ry, rz; // Rotations-Versatz (Grad)
|
||||
|
||||
public AnimOffset() {}
|
||||
|
||||
public AnimOffset(float tx, float ty, float tz,
|
||||
float rx, float ry, float rz) {
|
||||
this.tx = tx; this.ty = ty; this.tz = tz;
|
||||
this.rx = rx; this.ry = ry; this.rz = rz;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
package de.blight.game.animation;
|
||||
|
||||
import com.jme3.anim.Armature;
|
||||
import com.jme3.anim.Joint;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.control.AbstractControl;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* 2-Bone-Foot-IK – hält die Füße an ihrer World-Space-Position fest.
|
||||
*
|
||||
* Muss NACH AnimComposer und SkinningControl zur Spatial hinzugefügt werden,
|
||||
* damit controlUpdate() nach der Animations-Anwendung läuft.
|
||||
*/
|
||||
public final class FootIKControl extends AbstractControl {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(FootIKControl.class);
|
||||
|
||||
private final Armature armature;
|
||||
|
||||
private final Joint leftThigh, leftCalf, leftFoot;
|
||||
private final Joint rightThigh, rightCalf, rightFoot;
|
||||
|
||||
// IK-Ziele in World-Space; null = IK inaktiv
|
||||
private Vector3f leftWorldTarget = null;
|
||||
private Vector3f rightWorldTarget = null;
|
||||
|
||||
private static final float MIN_CORRECTION_DIST = 0.005f;
|
||||
|
||||
private int debugFramesLeft = 0;
|
||||
|
||||
public FootIKControl(Armature armature) {
|
||||
this.armature = armature;
|
||||
leftThigh = findJoint(armature, "L_Thigh", "LeftUpLeg", "mixamorig:LeftUpLeg");
|
||||
leftCalf = findJoint(armature, "L_Calf", "LeftLeg", "mixamorig:LeftLeg");
|
||||
leftFoot = findJoint(armature, "L_Foot", "LeftFoot", "mixamorig:LeftFoot");
|
||||
rightThigh = findJoint(armature, "R_Thigh", "RightUpLeg", "mixamorig:RightUpLeg");
|
||||
rightCalf = findJoint(armature, "R_Calf", "RightLeg", "mixamorig:RightLeg");
|
||||
rightFoot = findJoint(armature, "R_Foot", "RightFoot", "mixamorig:RightFoot");
|
||||
log.info("[FootIK] Init: L={} R={}",
|
||||
leftFoot != null ? leftFoot.getName() : "n/a",
|
||||
rightFoot != null ? rightFoot.getName() : "n/a");
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixiert die Füße an ihrer aktuellen World-Space-Position.
|
||||
* Muss aufgerufen werden NACHDEM die neue Animation gesetzt wurde,
|
||||
* aber BEVOR der kfOffset die Armature verschoben hat.
|
||||
*/
|
||||
public void lockFeetAtCurrentWorldPos() {
|
||||
Spatial sp = getSpatial();
|
||||
if (sp == null || leftFoot == null || rightFoot == null) {
|
||||
return;
|
||||
}
|
||||
leftWorldTarget = sp.localToWorld(
|
||||
leftFoot.getModelTransform().getTranslation().clone(), new Vector3f());
|
||||
rightWorldTarget = sp.localToWorld(
|
||||
rightFoot.getModelTransform().getTranslation().clone(), new Vector3f());
|
||||
debugFramesLeft = 10;
|
||||
log.info("[FootIK] LockWorld: L={} R={}", fv(leftWorldTarget), fv(rightWorldTarget));
|
||||
}
|
||||
|
||||
public void releaseFeet() {
|
||||
leftWorldTarget = null;
|
||||
rightWorldTarget = null;
|
||||
debugFramesLeft = 0;
|
||||
log.info("[FootIK] Freigegeben");
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return leftWorldTarget != null || rightWorldTarget != null;
|
||||
}
|
||||
|
||||
// ── Control-Update ────────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
if (leftWorldTarget == null && rightWorldTarget == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Spatial sp = getSpatial();
|
||||
if (sp == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean dbg = debugFramesLeft > 0;
|
||||
if (dbg) {
|
||||
debugFramesLeft--;
|
||||
}
|
||||
|
||||
if (leftWorldTarget != null && leftThigh != null && leftCalf != null && leftFoot != null) {
|
||||
Vector3f modelTarget = sp.worldToLocal(leftWorldTarget, new Vector3f());
|
||||
solveLeg(leftThigh, leftCalf, leftFoot, modelTarget, dbg, "L");
|
||||
}
|
||||
if (rightWorldTarget != null && rightThigh != null && rightCalf != null && rightFoot != null) {
|
||||
Vector3f modelTarget = sp.worldToLocal(rightWorldTarget, new Vector3f());
|
||||
solveLeg(rightThigh, rightCalf, rightFoot, modelTarget, dbg, "R");
|
||||
}
|
||||
|
||||
armature.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void controlRender(RenderManager rm, ViewPort vp) {}
|
||||
|
||||
// ── 2-Bone-IK-Solver ─────────────────────────────────────────────────────
|
||||
|
||||
private void solveLeg(Joint thigh, Joint calf, Joint foot,
|
||||
Vector3f target, boolean dbg, String side) {
|
||||
Vector3f A = thigh.getModelTransform().getTranslation().clone();
|
||||
Vector3f B = calf.getModelTransform().getTranslation().clone();
|
||||
Vector3f C = foot.getModelTransform().getTranslation().clone();
|
||||
|
||||
float L1 = A.distance(B);
|
||||
float L2 = B.distance(C);
|
||||
if (L1 < 0.0001f || L2 < 0.0001f) {
|
||||
return;
|
||||
}
|
||||
|
||||
float footErr = C.distance(target);
|
||||
if (dbg) {
|
||||
log.info("[FootIK][{}] A={} B={} C={} T={} err={} L1={} L2={}",
|
||||
side, fv(A), fv(B), fv(C), fv(target),
|
||||
String.format("%.4f", footErr),
|
||||
String.format("%.3f", L1), String.format("%.3f", L2));
|
||||
}
|
||||
if (footErr < MIN_CORRECTION_DIST) {
|
||||
return;
|
||||
}
|
||||
|
||||
float d = A.distance(target);
|
||||
d = FastMath.clamp(d, Math.abs(L1 - L2) + 0.0001f, L1 + L2 - 0.0001f);
|
||||
|
||||
float cosA = (L1 * L1 + d * d - L2 * L2) / (2f * L1 * d);
|
||||
float angleA = FastMath.acos(FastMath.clamp(cosA, -1f, 1f));
|
||||
|
||||
Vector3f dirAT = target.subtract(A).normalizeLocal();
|
||||
Vector3f kneeDir = B.subtract(A).normalizeLocal();
|
||||
float proj = dirAT.dot(kneeDir);
|
||||
Vector3f perp = kneeDir.subtract(dirAT.mult(proj));
|
||||
if (perp.lengthSquared() < 0.0001f) {
|
||||
perp = findPerp(dirAT);
|
||||
}
|
||||
perp.normalizeLocal();
|
||||
|
||||
Vector3f B_new = A.add(
|
||||
dirAT.mult(L1 * FastMath.cos(angleA)).addLocal(
|
||||
perp.mult(L1 * FastMath.sin(angleA)))
|
||||
);
|
||||
|
||||
// Oberschenkel rotieren
|
||||
Vector3f curThighDir = B.subtract(A).normalizeLocal();
|
||||
Vector3f dstThighDir = B_new.subtract(A).normalizeLocal();
|
||||
Quaternion thighArc = rotArc(curThighDir, dstThighDir);
|
||||
Quaternion thighModelRot = thigh.getModelTransform().getRotation().clone();
|
||||
Quaternion newThighModelRot = thighArc.mult(thighModelRot);
|
||||
Joint thighParent = thigh.getParent();
|
||||
Quaternion parentRot = thighParent != null
|
||||
? thighParent.getModelTransform().getRotation() : new Quaternion();
|
||||
thigh.setLocalRotation(parentRot.inverse().mult(newThighModelRot));
|
||||
|
||||
// Unterschenkel rotieren — Calf-Richtung NACH der Thigh-Rotation verwenden
|
||||
Vector3f curCalfDirOrig = C.subtract(B).normalizeLocal();
|
||||
Vector3f curCalfDirRotated = thighArc.mult(curCalfDirOrig);
|
||||
Vector3f dstCalfDir = target.subtract(B_new).normalizeLocal();
|
||||
Quaternion calfArc = rotArc(curCalfDirRotated, dstCalfDir);
|
||||
Quaternion oldCalfLocalRot = calf.getLocalRotation().clone();
|
||||
Quaternion actualCalfModel = newThighModelRot.mult(oldCalfLocalRot);
|
||||
Quaternion newCalfModelRot = calfArc.mult(actualCalfModel);
|
||||
calf.setLocalRotation(newThighModelRot.inverse().mult(newCalfModelRot));
|
||||
|
||||
if (dbg) {
|
||||
float thighAngle = thighArc.toAngleAxis(new Vector3f()) * FastMath.RAD_TO_DEG;
|
||||
float calfAngle = calfArc.toAngleAxis(new Vector3f()) * FastMath.RAD_TO_DEG;
|
||||
log.info("[FootIK][{}] B_new={} thigh={}° calf={}°",
|
||||
side, fv(B_new),
|
||||
String.format("%.1f", thighAngle),
|
||||
String.format("%.1f", calfAngle));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Hilfsmethoden ────────────────────────────────────────────────────────
|
||||
|
||||
private static Quaternion rotArc(Vector3f from, Vector3f to) {
|
||||
float dot = FastMath.clamp(from.dot(to), -1f, 1f);
|
||||
if (dot > 0.9999f) {
|
||||
return new Quaternion();
|
||||
}
|
||||
if (dot < -0.9999f) {
|
||||
return new Quaternion().fromAngleAxis(FastMath.PI, findPerp(from));
|
||||
}
|
||||
Vector3f axis = from.cross(to).normalizeLocal();
|
||||
Quaternion q = new Quaternion();
|
||||
q.fromAngleAxis(FastMath.acos(dot), axis);
|
||||
return q;
|
||||
}
|
||||
|
||||
private static Vector3f findPerp(Vector3f v) {
|
||||
Vector3f p = v.cross(Vector3f.UNIT_X);
|
||||
if (p.lengthSquared() < 0.0001f) {
|
||||
p = v.cross(Vector3f.UNIT_Z);
|
||||
}
|
||||
return p.normalizeLocal();
|
||||
}
|
||||
|
||||
private static Joint findJoint(Armature arm, String... names) {
|
||||
for (String n : names) {
|
||||
Joint j = arm.getJoint(n);
|
||||
if (j != null) {
|
||||
return j;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String fv(Vector3f v) {
|
||||
return String.format("(%.3f,%.3f,%.3f)", v.x, v.y, v.z);
|
||||
}
|
||||
}
|
||||
@@ -557,7 +557,7 @@ public class PlayerInputControl {
|
||||
public void clearKfOffset() {
|
||||
log.info("[KF] clearKfOffset() → Lerp zu 0, current=({},{},{})", kfOffsetCurrent.x, kfOffsetCurrent.y, kfOffsetCurrent.z);
|
||||
kfOffsetTarget.set(0, 0, 0);
|
||||
kfOffsetSpeed = 5.0f; // 0.25m in ~3 Frames bei 60fps
|
||||
kfOffsetSpeed = 5.0f;
|
||||
}
|
||||
|
||||
private void clearMotionKfOffset() {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>ERROR</level>
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} %-5level [%logger{30}] %msg%n%ex</pattern>
|
||||
|
||||
Reference in New Issue
Block a user