From 97e8d1db91b4f7cc558e27a7030c473d19f44072 Mon Sep 17 00:00:00 2001 From: XProger <xproger@list.ru> Date: Tue, 24 Mar 2020 06:26:12 +0300 Subject: [PATCH] add simple IK solver for legs and arms --- src/camera.h | 6 + src/controller.h | 3 + src/lara.h | 179 +++++++++++++++++++++++-- src/level.h | 26 ++++ src/platform/win/OpenLara.vcxproj.user | 11 +- src/platform/win/main.cpp | 56 +++++--- src/utils.h | 103 ++++++++++++++ 7 files changed, 348 insertions(+), 36 deletions(-) diff --git a/src/camera.h b/src/camera.h index db7700d..10742f9 100644 --- a/src/camera.h +++ b/src/camera.h @@ -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; diff --git a/src/controller.h b/src/controller.h index 7b134de..bff84d2 100644 --- a/src/controller.h +++ b/src/controller.h @@ -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(); diff --git a/src/lara.h b/src/lara.h index 4df8196..849bf82 100644 --- a/src/lara.h +++ b/src/lara.h @@ -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 diff --git a/src/level.h b/src/level.h index 91964de..0cc0215 100644 --- a/src/level.h +++ b/src/level.h @@ -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); } diff --git a/src/platform/win/OpenLara.vcxproj.user b/src/platform/win/OpenLara.vcxproj.user index 96911a8..644eef1 100644 --- a/src/platform/win/OpenLara.vcxproj.user +++ b/src/platform/win/OpenLara.vcxproj.user @@ -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> \ No newline at end of file diff --git a/src/platform/win/main.cpp b/src/platform/win/main.cpp index 0d314e2..13909c1 100644 --- a/src/platform/win/main.cpp +++ b/src/platform/win/main.cpp @@ -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; } } } diff --git a/src/utils.h b/src/utils.h index d5f1178..251da25 100644 --- a/src/utils.h +++ b/src/utils.h @@ -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; };