mirror of
https://github.com/simplysanatori/KartFrame.git
synced 2026-10-17 14:01:21 +02:00
138 lines
7.0 KiB
C++
138 lines
7.0 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
#pragma once
|
|
#include "vr/mkw_vr_first_person.h"
|
|
|
|
namespace mkw::vr::body_ik {
|
|
using Vec = detail::Vec3;
|
|
inline Vec Add(Vec a, Vec b) { return {a.x+b.x,a.y+b.y,a.z+b.z}; }
|
|
inline Vec Sub(Vec a, Vec b) { return {a.x-b.x,a.y-b.y,a.z-b.z}; }
|
|
inline Vec Mul(Vec a, float s) { return {a.x*s,a.y*s,a.z*s}; }
|
|
inline float Length(Vec a) { return std::sqrt(detail::Dot(a,a)); }
|
|
inline Vec Position(const Mtx34& m) { return {m[3],m[7],m[11]}; }
|
|
// Controller grip tracks the palm, not the character's wrist joint. Rotate
|
|
// the wrist-to-palm offset with the hand so it works in every orientation.
|
|
inline Vec WristForPalm(Vec palm, const Mtx34& hand, Vec palm_axis, float distance) {
|
|
auto axis=detail::TransformDirection(hand,palm_axis);
|
|
if(!detail::Normalize(axis)) return palm;
|
|
return Sub(palm,Mul(axis,distance));
|
|
}
|
|
inline bool CalibrateGripOffset(const Mtx34& grip, Vec wrist, Vec& offset) {
|
|
Mtx34 inverse{};
|
|
if(!InvertMtx(grip,inverse)) return false;
|
|
offset=detail::TransformPoint(inverse,wrist.x,wrist.y,wrist.z);
|
|
return detail::IsFiniteFloat(&offset.x) && detail::IsFiniteFloat(&offset.y) &&
|
|
detail::IsFiniteFloat(&offset.z) && Length(offset)<=0.3f;
|
|
}
|
|
inline bool CalibrateGripPose(const Mtx34& grip, const Mtx34& wrist, Mtx34& correction) {
|
|
Mtx34 inverse{};
|
|
if(!detail::IsFiniteMtx34(wrist) || !InvertMtx(grip,inverse)) return false;
|
|
correction=ComposeMtx(inverse,wrist);
|
|
return detail::IsFiniteMtx34(correction) && Length(Position(correction))<=0.3f;
|
|
}
|
|
struct Arm { Vec elbow{}, wrist{}; bool valid=false; };
|
|
|
|
// Driver animation IDs from the native 24-byte animation table: drive=0,
|
|
// dash=1, drift_l/r=2/3, wheelie=4, wait/back=5..7, steering=15..19.
|
|
// Both driver animation layers must be driving poses before exposing legs.
|
|
inline bool IsDrivingLegAnimation(uint16_t animation) {
|
|
return animation==0 || (animation>=2 && animation<=7) ||
|
|
(animation>=15 && animation<=19);
|
|
}
|
|
|
|
struct LegAnimationGate {
|
|
uint64_t trick_hold_until=0, turbo_hold_until=0;
|
|
bool AllowNative(bool trick, bool mini_turbo, bool drifting, uint64_t now,
|
|
bool character_action=false) {
|
|
// A new drift may start before the previous boost finishes. Keep its
|
|
// leg articulation usable, but do not release a trick hold early.
|
|
if(trick) trick_hold_until=now+250000000ull;
|
|
if(character_action || (mini_turbo && !drifting)) turbo_hold_until=now+250000000ull;
|
|
return !trick && !character_action && now>=trick_hold_until &&
|
|
(drifting || (!mini_turbo && now>=turbo_hold_until));
|
|
}
|
|
};
|
|
|
|
// Keep a little bend without forcing a long, tightly folded arm when the
|
|
// controller is close to the chest. Distances scale with the character/world.
|
|
inline float FitArmReach(float natural, float requested, float units_per_meter) {
|
|
if(natural<0.001f || units_per_meter<=0) return 1.0f;
|
|
return std::clamp(std::max(0.30f*units_per_meter,requested*1.04f)/natural,0.25f,3.5f);
|
|
}
|
|
|
|
// Keep joint articulation relative to a stable seated parent. Native root
|
|
// translation/rotation (drift lean or a rider jump) cannot move the torso.
|
|
inline bool AnimateSeatedLimb(const Mtx34& seated_parent, const Mtx34& animated_parent,
|
|
const Mtx34& animated_joint, Mtx34& result) {
|
|
Mtx34 inverse{};
|
|
if(!InvertMtx(animated_parent,inverse)) return false;
|
|
result=ComposeMtx(seated_parent,ComposeMtx(inverse,animated_joint));
|
|
return detail::IsFiniteMtx34(result);
|
|
}
|
|
|
|
// Analytic two-bone IK, with a stable elbow pole and no bone stretching.
|
|
// All positions and lengths share the character's current scaled world units.
|
|
inline Arm Solve(Vec shoulder, Vec elbow, Vec wrist, Vec target, Vec pole) {
|
|
const float upper=Length(Sub(elbow,shoulder)), lower=Length(Sub(wrist,elbow));
|
|
if (!detail::IsFiniteFloat(&upper) || !detail::IsFiniteFloat(&lower) || upper<0.001f || lower<0.001f)
|
|
return {};
|
|
Vec axis=Sub(target,shoulder);
|
|
float distance=Length(axis);
|
|
if (!detail::IsFiniteFloat(&distance)) return {};
|
|
if (!detail::Normalize(axis)) { axis=Sub(wrist,shoulder); if (!detail::Normalize(axis)) return {}; }
|
|
const float epsilon=std::min(upper,lower)*0.001f;
|
|
distance=std::clamp(distance,std::abs(upper-lower)+epsilon,upper+lower-epsilon);
|
|
Vec bend=Sub(pole,Mul(axis,detail::Dot(pole,axis)));
|
|
if (!detail::Normalize(bend)) {
|
|
bend=Sub(Sub(elbow,shoulder),Mul(axis,detail::Dot(Sub(elbow,shoulder),axis)));
|
|
if (!detail::Normalize(bend)) {
|
|
bend=detail::Cross(axis,{0,1,0});
|
|
if (!detail::Normalize(bend)) { bend=detail::Cross(axis,{1,0,0}); detail::Normalize(bend); }
|
|
}
|
|
}
|
|
const float along=(upper*upper-lower*lower+distance*distance)/(2*distance);
|
|
const float height=std::sqrt(std::max(0.0f,upper*upper-along*along));
|
|
return {Add(shoulder,Add(Mul(axis,along),Mul(bend,height))),Add(shoulder,Mul(axis,distance)),true};
|
|
}
|
|
|
|
// Rotate about a joint to align its old segment to the solved one. Opposite
|
|
// vectors use a perpendicular axis rather than an undefined cross product.
|
|
inline Mtx34 Align(Vec origin, Vec from, Vec to) {
|
|
if (!detail::Normalize(from) || !detail::Normalize(to)) return kIdentityMtx34;
|
|
const float c=std::clamp(detail::Dot(from,to),-1.0f,1.0f);
|
|
Vec axis=detail::Cross(from,to);
|
|
float s=Length(axis);
|
|
if (s<1e-5f) {
|
|
if (c>0) return kIdentityMtx34;
|
|
axis=detail::Cross(from,{0,1,0});
|
|
if (!detail::Normalize(axis)) { axis=detail::Cross(from,{1,0,0}); detail::Normalize(axis); }
|
|
s=0;
|
|
} else axis=Mul(axis,1/s);
|
|
const float x=axis.x,y=axis.y,z=axis.z,k=1-c;
|
|
Mtx34 m{x*x*k+c,x*y*k-z*s,x*z*k+y*s,0,
|
|
y*x*k+z*s,y*y*k+c,y*z*k-x*s,0,
|
|
z*x*k-y*s,z*y*k+x*s,z*z*k+c,0};
|
|
const auto rotated=detail::TransformDirection(m,origin);
|
|
m[3]=origin.x-rotated.x;m[7]=origin.y-rotated.y;m[11]=origin.z-rotated.z;
|
|
return m;
|
|
}
|
|
|
|
// Fit a skinned limb to a new segment without widening its cross section.
|
|
inline Mtx34 MapSegment(Vec old_origin, Vec new_origin, Vec old_segment, Vec new_segment,
|
|
float cross_section_scale=1.0f) {
|
|
const float old_length=Length(old_segment), new_length=Length(new_segment);
|
|
if (old_length<0.0001f || new_length<0.0001f) return kIdentityMtx34;
|
|
Vec axis=Mul(old_segment,1/old_length);
|
|
const float k=new_length/old_length-cross_section_scale;
|
|
Mtx34 stretch=kIdentityMtx34;
|
|
stretch[0]=stretch[5]=stretch[10]=cross_section_scale;
|
|
const float v[3]{axis.x,axis.y,axis.z};
|
|
for(int row=0;row<3;++row) for(int col=0;col<3;++col) stretch[row*4+col]+=k*v[row]*v[col];
|
|
const auto shifted=detail::TransformDirection(stretch,old_origin);
|
|
stretch[3]=old_origin.x-shifted.x;stretch[7]=old_origin.y-shifted.y;stretch[11]=old_origin.z-shifted.z;
|
|
auto result=ComposeMtx(Align(old_origin,old_segment,new_segment),stretch);
|
|
const auto delta=Sub(new_origin,old_origin);
|
|
result[3]+=delta.x;result[7]+=delta.y;result[11]+=delta.z;
|
|
return result;
|
|
}
|
|
} // namespace mkw::vr::body_ik
|