diff --git a/src/camera.h b/src/camera.h index 7d2ad7c..9d6d140 100644 --- a/src/camera.h +++ b/src/camera.h @@ -31,6 +31,10 @@ #define CAM_LOOK_ANGLE_XMIN (-75.0f * DEG2RAD) #define CAM_LOOK_ANGLE_Y ( 80.0f * DEG2RAD) +#define SPECTATOR_TIMER 1.0f +#define SPECTATOR_POS_SPEED 4096.0f +#define SPECTATOR_ROT_SPEED PIH +#define SPECTATOR_SMOOTH 4.0f struct Camera : ICamera { IGame *game; @@ -52,9 +56,18 @@ struct Camera : ICamera { int speed; bool smooth; + bool spectator; + vec3 specPos, specPosSmooth; + vec3 specRot, specRotSmooth; + float specTimer; + int16 specRoom; + Camera(IGame *game, Character *owner) : ICamera(), game(game), level(game->getLevel()), frustum(new Frustum()), timer(-1.0f), viewIndex(-1), viewIndexLast(-1), viewTarget(NULL) { this->owner = owner; reset(); + + spectator = false; + specTimer = 0.0f; } void reset() { @@ -92,7 +105,7 @@ struct Camera : ICamera { } virtual int getRoomIndex() const { - return eye.room; + return spectator ? specRoom : eye.room; } void updateListener(const mat4 &matrix) { @@ -404,8 +417,11 @@ struct Camera : ICamera { if (mode == MODE_LOOK) { float d = 3.0f * Core::deltaTime; - lookAngle.x += Input::joy[cameraIndex].L.y * d; - lookAngle.y += Input::joy[cameraIndex].L.x * d; + vec2 L = Input::joy[cameraIndex].L; + if (L.length() < JOY_DEAD_ZONE) L = vec2(0.0f); + + lookAngle.x += L.y * d; + lookAngle.y += L.x * d; if (Input::state[cameraIndex][cUp]) lookAngle.x -= d; if (Input::state[cameraIndex][cDown]) lookAngle.x += d; @@ -504,11 +520,6 @@ struct Camera : ICamera { level->getSector(eye.room, eye.pos); - if (Core::settings.detail.stereo == Core::Settings::STEREO_VR) - updateListener(mViewInv * Input::hmd.head); - else - updateListener(mViewInv); - smooth = true; viewIndexLast = viewIndex; @@ -527,6 +538,75 @@ struct Camera : ICamera { viewTargetLast = viewTarget; viewTarget = NULL; } + + Input::Joystick &specJoy = Input::joy[cameraIndex]; + + if (specJoy.down[jkL] && specJoy.down[jkR]) { + specTimer += Core::deltaTime; + if (specTimer > SPECTATOR_TIMER) { + spectator = !spectator; + specTimer = 0.0f; + specPos = eye.pos; + specRot = targetAngle; + specRot.z = PI; + specRot.y += PI; + specPosSmooth = specPos; + specRotSmooth = specRot; + specRoom = eye.room; + } + } else { + specTimer = 0.0f; + } + + if (spectator) { + vec2 L = specJoy.L; + vec2 R = specJoy.R; + float U = specJoy.RT; + float D = specJoy.LT; + + // apply dead zone + if (L.length() < 0.05f) L = vec2(0.0f); + if (R.length() < 0.05f) R = vec2(0.0f); + if (U < 0.05) U = 0.0f; + if (D < 0.05) D = 0.0f; + + vec3 dir = vec3(L.x, D - U, L.y) * (SPECTATOR_POS_SPEED * Core::deltaTime); + vec2 rot = R * (SPECTATOR_ROT_SPEED * Core::deltaTime); + + vec3 d = vec3(specRot.x, specRot.y); + vec3 r = d.cross(vec3(0.0f, 1.0f, 0.0f)).normal(); + + specPos += r * dir.x + vec3(0.0f, dir.y, 0.0f) + d * dir.z; + + specRot.x += rot.y; + specRot.y += rot.x; + specRot.x = clamp(specRot.x, -PIH, +PIH); + + specPosSmooth = specPosSmooth.lerp(specPos, SPECTATOR_SMOOTH * Core::deltaTime); + specRotSmooth = specRotSmooth.lerp(specRot, SPECTATOR_SMOOTH * Core::deltaTime); + + mViewInv.identity(); + mViewInv.translate(specPosSmooth); + mViewInv.rotateY(specRotSmooth.y); + mViewInv.rotateX(specRotSmooth.x); + mViewInv.rotateZ(specRotSmooth.z); + + level->getSector(specRoom, specPos); + /* + for (int i = 0; i < level->roomsCount; i++) { + TR::Room &room = level->rooms[i]; + if (room.contains(specPos)) { + eye.room = i; + break; + } + } + */ + } + + if (Core::settings.detail.stereo == Core::Settings::STEREO_VR) + updateListener(mViewInv * Input::hmd.head); + else + updateListener(mViewInv); } virtual void setup(bool calcMatrices) { diff --git a/src/format.h b/src/format.h index d378023..0842e48 100644 --- a/src/format.h +++ b/src/format.h @@ -1893,6 +1893,10 @@ namespace TR { uint32 meshIndex; // index into static meshes array } *meshes; + bool contains(const vec3 &p) const { + return p.x >= info.x && p.x <= info.x + xSectors * 1024 && p.z >= info.z && p.z <= info.z + zSectors * 1024 && p.y >= info.yTop && p.y <= info.yBottom; + } + vec3 getOffset() const { return vec3(float(info.x), 0.0f, float(info.z)); } diff --git a/src/input.h b/src/input.h index 202f828..ac0cc33 100644 --- a/src/input.h +++ b/src/input.h @@ -6,6 +6,7 @@ #define INPUT_JOY_COUNT 4 #define MAX_PLAYERS COUNT(Core::settings.controls) +#define JOY_DEAD_ZONE 0.3f namespace Input { InputKey lastKey; diff --git a/src/lang.h b/src/lang.h index f8d6323..b70bc27 100644 --- a/src/lang.h +++ b/src/lang.h @@ -296,7 +296,8 @@ const char *helpText = "Swan dive - Up & Walk & Jump@" "First Person View - Look & Action@" "DOZY on - Look & Duck & Action & Jump@" - "DOZY off - Walk"; + "DOZY off - Walk@" + "Free Camera - hold L & R stick"; #include "lang/en.h" #include "lang/fr.h" diff --git a/src/lara.h b/src/lara.h index 61ecefd..d931f8e 100644 --- a/src/lara.h +++ b/src/lara.h @@ -3102,28 +3102,35 @@ struct Lara : Character { if (input & LOOK) return input; + if (camera->spectator) + return input; + Input::Joystick &joy = Input::joy[Core::settings.controls[pid].joyIndex]; - if (!((state == STATE_STOP || state == STATE_SURF_TREAD || state == STATE_HANG) && fabsf(joy.L.x) < 0.5f && fabsf(joy.L.y) < 0.5f)) { + vec2 L = joy.L; + + if (L.length() < JOY_DEAD_ZONE) L = vec2(0.0f); // dead zone + + if (!((state == STATE_STOP || state == STATE_SURF_TREAD || state == STATE_HANG) && fabsf(L.x) < 0.5f && fabsf(L.y) < 0.5f)) { bool moving = state == STATE_RUN || state == STATE_WALK || state == STATE_BACK || state == STATE_FAST_BACK || state == STATE_SURF_SWIM || state == STATE_SURF_BACK || state == STATE_FORWARD_JUMP; if (!moving) { - if (fabsf(joy.L.x) < fabsf(joy.L.y)) - joy.L.x = 0.0f; + if (fabsf(L.x) < fabsf(L.y)) + L.x = 0.0f; else - joy.L.y = 0.0f; + L.y = 0.0f; } - if (joy.L.x != 0.0f) { - input |= (joy.L.x < 0.0f) ? LEFT : RIGHT; + if (L.x != 0.0f) { + input |= (L.x < 0.0f) ? LEFT : RIGHT; if (moving || stand == STAND_UNDERWATER || stand == STAND_ONWATER) - rotFactor.y = min(fabsf(joy.L.x) / 0.9f, 1.0f); + rotFactor.y = min(fabsf(L.x) / 0.9f, 1.0f); } - if (joy.L.y != 0.0f) { - input |= (joy.L.y < 0.0f) ? FORTH : BACK; + if (L.y != 0.0f) { + input |= (L.y < 0.0f) ? FORTH : BACK; if (stand == STAND_UNDERWATER) - rotFactor.x = min(fabsf(joy.L.y) / 0.9f, 1.0f); + rotFactor.x = min(fabsf(L.y) / 0.9f, 1.0f); } } diff --git a/src/platform/web/main.cpp b/src/platform/web/main.cpp index c7ece32..57194a0 100644 --- a/src/platform/web/main.cpp +++ b/src/platform/web/main.cpp @@ -103,15 +103,8 @@ JoyKey joyToInputKey(int code) { return jkNone; } -#define JOY_DEAD_ZONE_STICK 0.3f #define JOY_DEAD_ZONE_TRIGGER 0.01f -vec2 joyAxis(float x, float y) { - if (fabsf(x) > JOY_DEAD_ZONE_STICK || fabsf(y) > JOY_DEAD_ZONE_STICK) - return vec2(x, y); - return vec2(0.0f); -} - vec2 joyTrigger(float x) { return vec2(x > JOY_DEAD_ZONE_TRIGGER ? x : 0.0f, 0.0f); } @@ -146,8 +139,8 @@ void joyUpdate() { Input::setJoyPos(j, key, joyTrigger(state.analogButton[i])); } - Input::setJoyPos(j, jkL, joyAxis(state.axis[0], state.axis[1])); - Input::setJoyPos(j, jkR, joyAxis(state.axis[2], state.axis[3])); + Input::setJoyPos(j, jkL, vec2(state.axis[0], state.axis[1])); + Input::setJoyPos(j, jkR, vec2(state.axis[2], state.axis[3])); } } diff --git a/src/platform/win/main.cpp b/src/platform/win/main.cpp index bb63048..e506b12 100644 --- a/src/platform/win/main.cpp +++ b/src/platform/win/main.cpp @@ -115,7 +115,6 @@ DWORD (WINAPI *XInputSetState) (DWORD dwUserIndex, XINPUT_VIBRATION* pVibration) void (WINAPI *XInputEnable) (BOOL enable) = NULL; #define XInputGetProc(x) (x = (decltype(x))GetProcAddress(h, #x)) -#define JOY_DEAD_ZONE_STICK 0.3f #define JOY_DEAD_ZONE_TRIGGER 0.01f #define JOY_MIN_UPDATE_FX_TIME 50 @@ -188,15 +187,10 @@ float joyAxis(int x, int xMin, int xMax) { vec2 joyDir(float ax, float ay) { vec2 dir = vec2(ax, ay); float dist = min(1.0f, dir.length()); - if (dist < JOY_DEAD_ZONE_STICK) dist = 0.0f; return dir.normal() * dist; } -int joyDeadZone(int value, int zone) { - return (value < -zone || value > zone) ? value : 0; -} - void joyUpdate() { for (int j = 0; j < INPUT_JOY_COUNT; j++) { if (!joyDevice[j].ready) continue; @@ -208,12 +202,12 @@ void joyUpdate() { if (XInputGetState(j, &state) == ERROR_SUCCESS) { //osJoyVibrate(j, state.Gamepad.bLeftTrigger / 255.0f, state.Gamepad.bRightTrigger / 255.0f); // vibration test - Input::setJoyPos(j, jkL, joyDir(joyAxis(joyDeadZone( state.Gamepad.sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE), -32768, 32767), - joyAxis(joyDeadZone(-state.Gamepad.sThumbLY, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE), -32768, 32767))); - Input::setJoyPos(j, jkR, joyDir(joyAxis(joyDeadZone( state.Gamepad.sThumbRX, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE), -32768, 32767), - joyAxis(joyDeadZone(-state.Gamepad.sThumbRY, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE), -32768, 32767))); - Input::setJoyPos(j, jkLT, vec2(joyDeadZone(state.Gamepad.bLeftTrigger, XINPUT_GAMEPAD_TRIGGER_THRESHOLD) / 255.0f, 0.0f)); - Input::setJoyPos(j, jkRT, vec2(joyDeadZone(state.Gamepad.bRightTrigger, XINPUT_GAMEPAD_TRIGGER_THRESHOLD) / 255.0f, 0.0f)); + Input::setJoyPos(j, jkL, joyDir(joyAxis( state.Gamepad.sThumbLX, -32768, 32767), + joyAxis(-state.Gamepad.sThumbLY, -32768, 32767))); + Input::setJoyPos(j, jkR, joyDir(joyAxis( state.Gamepad.sThumbRX, -32768, 32767), + joyAxis(-state.Gamepad.sThumbRY, -32768, 32767))); + Input::setJoyPos(j, jkLT, vec2(state.Gamepad.bLeftTrigger / 255.0f, 0.0f)); + Input::setJoyPos(j, jkRT, vec2(state.Gamepad.bRightTrigger/ 255.0f, 0.0f)); static const JoyKey keys[] = { jkUp, jkDown, jkLeft, jkRight, jkStart, jkSelect, jkL, jkR, jkLB, jkRB, jkNone, jkNone, jkA, jkB, jkX, jkY }; for (int i = 0; i < 16; i++)