1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-04-21 03:21:51 +02:00

add simple IK solver for legs and arms

This commit is contained in:
XProger 2020-03-24 06:26:12 +03:00
parent b28c12d1d4
commit 97e8d1db91
7 changed files with 348 additions and 36 deletions

View File

@ -57,6 +57,7 @@ struct Camera : ICamera {
int speed;
bool smooth;
bool spectatorVR;
bool spectator;
vec3 specPos, specPosSmooth;
vec3 specRot, specRotSmooth;
@ -67,6 +68,7 @@ struct Camera : ICamera {
this->owner = owner;
reset();
spectatorVR = false;
spectator = false;
specTimer = 0.0f;
}
@ -559,6 +561,10 @@ struct Camera : ICamera {
specTimer = 0.0f;
}
if (!spectator && spectatorVR) {
mViewInv = mat4(owner->mainLightPos, owner->pos, vec3(0, -1, 0));
}
if (spectator) {
vec2 L = specJoy.L;
vec2 R = specJoy.R;

View File

@ -1432,8 +1432,11 @@ struct Controller {
return;
animation.getJoints(getMatrix(), -1, true, joints);
jointsFrame = Core::stats.frame;
updateIK();
}
virtual void updateIK() {}
Basis& getJoint(int index) {
updateJoints();

View File

@ -44,6 +44,8 @@
#define LARA_WADE_MAX_DEPTH 730.0f
#define LARA_SWIM_MIN_DEPTH 512.0f
#define LARA_HEEL_HEIGHT 48.0f
#define LARA_MIN_SPECULAR 0.03f
#define LARA_WET_SPECULAR 0.5f
#define LARA_WET_TIMER (LARA_WET_SPECULAR / 16.0f) // 4 sec
@ -79,7 +81,7 @@ struct Lara : Character {
ANIM_STAND = 11,
ANIM_LANDING = 24,
ANIM_LANDING_HIGH = 24,
ANIM_CLIMB_JUMP = 26,
@ -107,6 +109,8 @@ struct Lara : Character {
ANIM_SLIDE_FORTH = 70,
ANIM_LANDING_LOW = 82,
ANIM_FALL_BACK = 93,
ANIM_HANG = 96,
@ -326,6 +330,8 @@ struct Lara : Character {
bool dozy;
bool canJump;
bool useIK;
bool useIKAim;
int32 networkInput;
@ -642,6 +648,9 @@ struct Lara : Character {
} else
animation.setAnim(ANIM_STAND);
}
useIK = true;
useIKAim = false;
}
virtual ~Lara() {
@ -947,6 +956,27 @@ struct Lara : Character {
|| state == STATE_STEP_LEFT;
}
bool canIKLegs() {
return (animation.index != ANIM_RUN_ASCEND_LEFT
&& animation.index != ANIM_RUN_ASCEND_RIGHT
&& animation.index != ANIM_WALK_ASCEND_LEFT
&& animation.index != ANIM_WALK_ASCEND_RIGHT
&& animation.index != ANIM_WALK_DESCEND_RIGHT
&& animation.index != ANIM_WALK_DESCEND_LEFT
&& animation.index != ANIM_BACK_DESCEND_LEFT
&& animation.index != ANIM_BACK_DESCEND_RIGHT)
&& (state == STATE_WALK
|| state == STATE_RUN
|| state == STATE_STOP
|| state == STATE_FAST_BACK
|| state == STATE_TURN_RIGHT
|| state == STATE_TURN_LEFT
|| state == STATE_BACK
|| state == STATE_FAST_TURN
|| state == STATE_STEP_RIGHT
|| state == STATE_STEP_LEFT);
}
bool wpnReady() {
return arms[0].anim != Weapon::Anim::PREPARE && arms[0].anim != Weapon::Anim::UNHOLSTER && arms[0].anim != Weapon::Anim::HOLSTER;
}
@ -1018,6 +1048,11 @@ struct Lara : Character {
}
void wpnFire() {
if (useIKAim) {
doShot(Input::joy[0].down[jkA], Input::joy[1].down[jkA]);
return;
}
bool armShot[2] = { false, false };
for (int i = 0; i < 2; i++) {
Arm &arm = arms[i];
@ -1084,10 +1119,19 @@ struct Lara : Character {
game->addMuzzleFlash(this, i ? LARA_LGUN_JOINT : LARA_RGUN_JOINT, i ? LARA_LGUN_OFFSET : LARA_RGUN_OFFSET, 1 + camera->cameraIndex);
// TODO: use new trace code
int joint = wpnCurrent == TR::Entity::SHOTGUN ? 8 : (i ? 11 : 8);
vec3 p = getJoint(joint).pos;
vec3 d = arm->rotAbs * vec3(0, 0, 1);
vec3 t = p + d * (24.0f * 1024.0f) + ((vec3(randf(), randf(), randf()) * 2.0f) - vec3(1.0f)) * 1024.0f;
vec3 p, d, t;
if (useIKAim) {
int joint = wpnCurrent == TR::Entity::SHOTGUN ? JOINT_ARM_R3 : (i ? JOINT_ARM_L3 : JOINT_ARM_R3);
p = getJoint(joint).pos;
d = getJoint(joint).rot * vec3(0, 1, 0);
t = p + d * 15.0f * 1024.0f;
} else {
int joint = wpnCurrent == TR::Entity::SHOTGUN ? JOINT_ARM_R1 : (i ? JOINT_ARM_L1 : JOINT_ARM_R1);
p = getJoint(joint).pos;
d = arm->rotAbs * vec3(0, 0, 1);
t = p + d * (15.0f * 1024.0f) + ((vec3(randf(), randf(), randf()) * 2.0f) - vec3(1.0f)) * 1024.0f;
}
int room;
vec3 hit = trace(getRoomIndex(), p, t, room, false);
@ -1140,6 +1184,12 @@ struct Lara : Character {
}
if (!emptyHands()) {
if (useIK) {
//wpnFire();
//return;
}
bool isRifle = wpnCurrent == TR::Entity::SHOTGUN;
for (int i = 0; i < 2; i++) {
@ -2397,7 +2447,7 @@ struct Lara : Character {
}
if (state == STATE_FALL && health > 0.0f)
animation.setAnim(ANIM_LANDING);
animation.setAnim(ANIM_LANDING_HIGH);
}
}
return STAND_GROUND;
@ -3401,7 +3451,7 @@ struct Lara : Character {
w *= TURN_FAST;
else if (state == STATE_FAST_BACK)
w *= TURN_FAST_BACK;
else if (state == STATE_TURN_LEFT || state == STATE_TURN_RIGHT || state == STATE_WALK || (state == STATE_STOP && animation.index == ANIM_LANDING))
else if (state == STATE_TURN_LEFT || state == STATE_TURN_RIGHT || state == STATE_WALK || (state == STATE_STOP && animation.index == ANIM_LANDING_HIGH))
w *= TURN_NORMAL;
else if (state == STATE_FORWARD_JUMP || state == STATE_BACK || state == STATE_WADE)
w *= TURN_SLOW;
@ -3514,7 +3564,7 @@ struct Lara : Character {
vTilt *= 2.0f;
vTilt *= rotFactor.y;
bool VR = (Core::settings.detail.stereo == Core::Settings::STEREO_VR) && camera->firstPerson;
updateTilt((input & WALK) == 0 && (state == STATE_RUN || (state == STATE_STOP && animation.index == ANIM_LANDING) || stand == STAND_UNDERWATER) && !VR, vTilt.x, vTilt.y);
updateTilt((input & WALK) == 0 && (state == STATE_RUN || (state == STATE_STOP && animation.index == ANIM_LANDING_HIGH) || stand == STAND_UNDERWATER) && !VR, vTilt.x, vTilt.y);
collisionOffset = vec3(0.0f);
@ -3871,6 +3921,119 @@ struct Lara : Character {
visibleMask ^= 0xFFFFFFFF;
}
}
void solveJointsArm(int j0, int j1, int j2) {
int index = j0 == JOINT_ARM_R1 ? 0 : 1;
Basis &hJoint = getJoint(jointHead);
vec3 hPos = hJoint.pos - hJoint.rot * vec3(0, 48, -24);
vec3 target = Input::hmd.controllers[index].getPos();
target += hPos;
vec3 start = joints[j0].pos;
vec3 middle = joints[j1].pos;
vec3 end = target;
float length1 = (middle - start).length();
float length2 = (joints[j2].pos - middle).length();
vec3 dir = end - start;
float length = dir.length();
if (length > length1 + length2) {
end = start + dir * ((length1 + length2 - 0.1f) / length);
}
vec3 down = vec3(0.0f, 1.0f, 0.0f);
vec3 pole = start + (getDir().cross(down) * (index == 0 ? -1.0f : 1.0f) + down) * 1000.0f;
if (ikSolve3D(start, end, pole, length1, length2, middle))
{
joints[j0] = Basis(start, middle, joints[j0].rot * vec3(1.0f, 0.0f, 0.0f));
joints[j1] = Basis(middle, end, joints[j1].rot * vec3(1.0f, 0.0f, 0.0f));
joints[j2].pos = end;
}
joints[j2].rot = Input::hmd.controllers[index].getRot();
}
void solveJointsLeg(int j0, int j1, int j2, float footHeight) {
vec3 start = joints[j0].pos;
vec3 middle = joints[j1].pos;
vec3 end = joints[j2].pos;
float length1 = (middle - start).length();
float length2 = (end - middle).length();
if (end.y > footHeight) {
end.y = footHeight;
}
vec3 pole = middle + (middle - start + middle - end) * 1024.0f;
if (ikSolve3D(start, end, pole, length1, length2, middle)) {
joints[j0] = Basis(start, middle, joints[j0].rot * vec3(1.0f, 0.0f, 0.0f));
joints[j1] = Basis(middle, end, joints[j1].rot * vec3(1.0f, 0.0f, 0.0f));
joints[j2].pos = end;
}
/*
vec3 pole = start + getDir() * 10000.0f;
if (ikSolve3D(start, end, pole, length1, length2, middle)) {
float angle = middle.xy().angle();
quat q(vec3(1, 0, 0), PI * 0.5f - angle);
joints[j0].rot = joints[j0].rot * q;
TR::Node *node = (TR::Node*)&level->nodesData[getModel()->node];
TR::Node *t = node + j0;
joints[j1].rotate(q.conjugate());
joints[j1].pos = joints[j0].pos + joints[j0].rot * vec3(float(t->x), float(t->y), float(t->z));
t = node + j1;
joints[j2].pos = joints[j1].pos + joints[j1].rot * vec3(float(t->x), float(t->y), float(t->z));
}
*/
}
float getFloorHeight(const vec3 &pos) {
int16 roomIndex = getRoomIndex();
TR::Room::Sector *sector = level->getSector(roomIndex, pos);
if (!sector) {
return this->pos.y;
}
return level->getFloor(sector, pos);// - LARA_HEEL_HEIGHT;
}
virtual void updateIK() override {
if (useIK && canIKLegs()) {
float ikPivotOffset = -Input::hmd.head.getPos().y * ONE_METER;
float footHeightL = getFloorHeight(joints[JOINT_LEG_L3].pos);
float footHeightR = getFloorHeight(joints[JOINT_LEG_R3].pos);
if (fabsf(footHeightL - footHeightR) < 256.0f) {
ikPivotOffset += max(footHeightL, footHeightR) - pos.y;
}
for (int i = 0; i < getModel()->mCount; i++) {
joints[i].pos.y += ikPivotOffset;
}
solveJointsLeg(JOINT_LEG_L1, JOINT_LEG_L2, JOINT_LEG_L3, footHeightL - LARA_HEEL_HEIGHT);
solveJointsLeg(JOINT_LEG_R1, JOINT_LEG_R2, JOINT_LEG_R3, footHeightR - LARA_HEEL_HEIGHT);
}
if (useIKAim) {
solveJointsArm(JOINT_ARM_L1, JOINT_ARM_L2, JOINT_ARM_L3);
solveJointsArm(JOINT_ARM_R1, JOINT_ARM_R2, JOINT_ARM_R3);
}
}
};
#endif

View File

@ -3252,8 +3252,34 @@ struct Level : IGame {
}
if (Core::eye == 0.0f && Core::settings.detail.isStereo()) {
Lara *lara = (Lara*)getLara(0);
if (Core::settings.detail.stereo == Core::Settings::STEREO_VR) {
if (lara && lara->camera && !lara->camera->firstPerson) {
lara->camera->changeView(true);
}
}
renderEye(-1, showUI, invBG);
renderEye(+1, showUI, invBG);
#ifdef _OS_WIN
uint8 stereo = Core::settings.detail.stereo;
Core::settings.detail.stereo = Core::Settings::STEREO_OFF;
if (lara) {
float dt = Core::deltaTime;
Core::deltaTime = 1.0f;
// lara->camera->spectatorVR = true;
lara->camera->update();
Core::deltaTime = dt;
}
renderEye(0, showUI, invBG);
Core::settings.detail.stereo = stereo;
if (lara) {
lara->camera->spectatorVR = false;
}
#endif
} else {
renderEye(int(Core::eye), showUI, invBG);
}

View File

@ -1,18 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LocalDebuggerWorkingDirectory>..\..\..\bin\TR1_PSX</LocalDebuggerWorkingDirectory>
<LocalDebuggerWorkingDirectory>C:\Projects\TR\TR1_PSX</LocalDebuggerWorkingDirectory>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
<LocalDebuggerCommandArguments>
</LocalDebuggerCommandArguments>
<LocalDebuggerCommandArguments>PSXDATA/LEVEL2.PSX</LocalDebuggerCommandArguments>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile|Win32'">
<LocalDebuggerWorkingDirectory>..\..\..\bin\TR1_PSX</LocalDebuggerWorkingDirectory>
<LocalDebuggerWorkingDirectory>C:\Projects\TR\TR1_PSX</LocalDebuggerWorkingDirectory>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LocalDebuggerWorkingDirectory>..\..\..\bin\TR1_PSX</LocalDebuggerWorkingDirectory>
<LocalDebuggerWorkingDirectory>C:\Projects\TR\TR1_PSX</LocalDebuggerWorkingDirectory>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
<LocalDebuggerCommandArguments>PSXDATA/GYM.PSX</LocalDebuggerCommandArguments>
<LocalDebuggerCommandArguments>PSXDATA/LEVEL2.PSX</LocalDebuggerCommandArguments>
</PropertyGroup>
</Project>

View File

@ -584,7 +584,7 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPara
case WM_ACTIVATE :
if (XInputEnable)
XInputEnable(wParam != WA_INACTIVE);
Input::reset();
//Input::reset();
break;
case WM_SIZE:
Core::width = LOWORD(lParam);
@ -812,6 +812,8 @@ void vrUpdate() {
vr::VRCompositor()->WaitGetPoses(tPose, vr::k_unMaxTrackedDeviceCount, NULL, 0);
static bool forceUpdatePose = false;
for (int id = 0; id < vr::k_unMaxTrackedDeviceCount; id++) {
vr::TrackedDevicePose_t &pose = tPose[id];
@ -825,8 +827,9 @@ void vrUpdate() {
mat4 pR = convToMat4(hmd->GetProjectionMatrix(vr::Eye_Right, 8.0f, 45.0f * 1024.0f));
mat4 head = convToMat4(pose.mDeviceToAbsoluteTracking);
if (Input::hmd.zero.x == INF) {
if (Input::hmd.zero.x == INF || forceUpdatePose) {
Input::hmd.zero = head.getPos();
forceUpdatePose = false;
}
head.setPos(head.getPos() - Input::hmd.zero);
@ -850,32 +853,41 @@ void vrUpdate() {
// continue;
}
Input::setJoyDown(0, jkLeft, IS_DOWN(vr::k_EButton_DPad_Left));
Input::setJoyDown(0, jkUp, IS_DOWN(vr::k_EButton_DPad_Up));
Input::setJoyDown(0, jkRight, IS_DOWN(vr::k_EButton_DPad_Right));
Input::setJoyDown(0, jkDown, IS_DOWN(vr::k_EButton_DPad_Down));
if (IS_DOWN(vr::k_EButton_Axis0)) {
Input::setJoyPos(0, jkL, vec2(state.rAxis[0].x, -state.rAxis[0].y));
if (IS_DOWN(vr::k_EButton_SteamVR_Touchpad)) {
forceUpdatePose = true;
}
Input::setJoyDown(0, jkA, IS_DOWN(vr::k_EButton_Axis1) ? (state.rAxis[1].x > 0.5) : false);
Input::setJoyDown(0, jkY, IS_DOWN(vr::k_EButton_Grip));
Input::setJoyDown(0, jkX, IS_DOWN(vr::k_EButton_ApplicationMenu));
// TODO
int joyIndex;
switch (hmd->GetControllerRoleForTrackedDeviceIndex(id)) {
case vr::TrackedControllerRole_LeftHand :
// TODO
break;
case vr::TrackedControllerRole_RightHand :
// TODO
break;
default : ;
case vr::TrackedControllerRole_RightHand : joyIndex = 0; break;
case vr::TrackedControllerRole_LeftHand : joyIndex = 1; break;
default : joyIndex = -1; break;
}
break;
if (joyIndex == -1) {
break;
}
Input::setJoyPos(joyIndex, jkL, vec2(state.rAxis[0].x, -state.rAxis[0].y));
Input::setJoyDown(joyIndex, jkA, IS_DOWN(vr::k_EButton_Axis1) ? (state.rAxis[1].x > 0.25) : false);
Input::setJoyDown(joyIndex, jkY, IS_DOWN(vr::k_EButton_Grip));
Input::setJoyDown(joyIndex, jkX, IS_DOWN(vr::k_EButton_ApplicationMenu));
mat4 m = convToMat4(pose.mDeviceToAbsoluteTracking);
m.setPos((m.getPos() - Input::hmd.zero) * ONE_METER);
mat4 scaleBasis(
1, 0, 0, 0,
0, -1, 0, 0,
0, 0, -1, 0,
0, 0, 0, 1);
m = scaleBasis * m * scaleBasis.inverse();
Input::hmd.controllers[joyIndex] = m;
#undef IS_DOWN
break;
}
}
}

View File

@ -1057,6 +1057,22 @@ struct Basis {
Basis(const quat &rot, const vec3 &pos) : rot(rot), pos(pos), w(1.0f) {}
Basis(const mat4 &matrix) : rot(matrix.getRot()), pos(matrix.getPos()), w(1.0f) {}
Basis(const vec3 &start, const vec3 &end, const vec3 &right) {
vec3 u = (end - start).normal();
vec3 d = right.cross(u).normal();
vec3 r = u.cross(d);
mat4 m;
m.up() = vec4(u, 0.0f);
m.dir() = vec4(d, 0.0f);
m.right() = vec4(r, 0.0f);
m.offset() = vec4(0.0f, 0.0f, 0.0f, 1.0f);
identity();
translate(start);
rotate(m.getRot().normal());
}
void identity() {
rot = quat(0, 0, 0, 1);
pos = vec3(0, 0, 0);
@ -1094,6 +1110,93 @@ struct Basis {
}
};
int qSolve(float a, float b, float c, float &x1, float &x2) {
if (fabsf(a) < EPS) {
if (fabsf(b) < EPS) {
return 0;
}
x1 = x2 = -c / b;
return 1;
}
float d = b * b - 4.0f * a * c;
if (d < 0.0f) {
return 0;
}
float inv2a = 1.0f / (2.0f * a);
if (d < EPS) {
x1 = x2 = -b * inv2a;
return 1;
}
d = sqrtf(d);
x1 = (-b - d) * inv2a;
x2 = (-b + d) * inv2a;
return 2;
}
bool ikSolve2D(const vec2 &end, float length1, float length2, vec2 &middle) {
float length = end.length();
if (length > length1 + length2) {
return false;
}
bool flipXY = end.x < end.y;
float a, b;
if (flipXY) {
a = end.y;
b = end.x;
} else {
a = end.x;
b = end.y;
}
ASSERT(fabsf(a) > EPS);
float m = (SQR(length1) - SQR(length2) + SQR(a) + SQR(b)) / (2.0f * a);
float n = b / a;
float y1, y2;
if (qSolve(1.0f + SQR(n), -2.0f * m * n, SQR(m) - SQR(length1), y1, y2)) {
if (flipXY) {
middle.x = y2;
middle.y = m - n * y2;
} else {
middle.y = y2;
middle.x = m - n * y2;
}
return true;
}
middle = end * (length1 / length);
return false;
}
bool ikSolve3D(const vec3 &start, const vec3 &end, const vec3 &pole, float length1, float length2, vec3 &middle) {
vec3 d = (end - start).normal();
vec3 n = d.cross(pole - start).normal();
mat4 m;
m.right() = vec4(n.cross(d), 0.0f);
m.up() = vec4(d, 0.0f);
m.dir() = vec4(n, 0.0f);
m.offset() = vec4(start, 1.0f);
bool res = ikSolve2D((m.inverse() * end).xy(), length1, length2, middle.xy());
middle.z = 0.0f;
middle = m * middle;
return res;
}
struct ubyte2 {
uint8 x, y;
};