From 5965a82c9356449f0ffe70c2ed3b41ba9716e0db Mon Sep 17 00:00:00 2001 From: Try Date: Sat, 17 Jan 2026 15:50:12 +0100 Subject: [PATCH 01/27] split mouse based rotation from script driven one: they can have different interpolation now #866 --- game/camera.cpp | 30 ++++++++++++++++-------------- game/camera.h | 3 ++- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index 4019a37de..8f78ff1af 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -48,7 +48,7 @@ void Camera::reset(const Npc* pl) { dst.range = userRange*(def.max_range-def.min_range)+def.min_range; dst.target = pl ? pl->cameraBone() : Vec3(); - dst.spin.x = def.best_elevation; + dst.spin.x = 0; //def.best_elevation; dst.spin.y = pl ? pl->rotation() : 0; src.spin = dst.spin; @@ -606,10 +606,11 @@ void Camera::followCamera(Vec3& pos, Vec3 dest, float dtF) { pos = dest; } -void Camera::followAng(Vec3& spin, Vec3 dest, float dtF) { - const auto& def = cameraDef(); - followAng(spin.x,dest.x,def.velo_rot,dtF); - followAng(spin.y,dest.y,def.velo_rot,dtF); +void Camera::followAng(Vec3& spin, Vec3 dest, float dtF, bool ver) { + const auto& def = cameraDef(); + const float velo = ver ? def.velo_rot : 9.f; + followAng(spin.x,dest.x,velo,dtF); + followAng(spin.y,dest.y,velo,dtF); } void Camera::followAng(float& ang, float dest, float speed, float dtF) { @@ -620,7 +621,7 @@ void Camera::followAng(float& ang, float dest, float speed, float dtF) { return; } - static const float min=-45, max=45; + static const float min=-120, max=120; if(da>max+1.f) { shift = (da-max); } @@ -690,14 +691,15 @@ void Camera::calcControlPoints(float dtF) { range = 0; } - followAng(src.spin, dst.spin+rotBest, dtF); + followAng(rotEleAz, Vec3(def.best_elevation, def.best_azimuth, 0), dtF, true); + followAng(src.spin, dst.spin, dtF, false); if(!isMarvin()) - followAng(rotOffset, rotOffsetDef, dtF); + followAng(rotOffset, rotOffsetDef, dtF, true); Matrix4x4 rotOffsetMat; rotOffsetMat.identity(); - rotOffsetMat.rotateOY(180-src.spin.y); - rotOffsetMat.rotateOX(src.spin.x); + rotOffsetMat.rotateOY(180-src.spin.y-rotEleAz.y); + rotOffsetMat.rotateOX(src.spin.x+rotEleAz.x); rotOffsetMat.project(targetOffset); Vec3 dir = {0,0,1}; @@ -729,7 +731,7 @@ void Camera::calcControlPoints(float dtF) { if(def.collision!=0) { // range = calcCameraColision(camTg,origin,src.spin,range); // origin = cameraPos - dir*range; - origin = calcCameraColision(camTg,origin,src.spin+offsetAng,range); + origin = calcCameraColision(camTg,origin,src.spin+rotEleAz+offsetAng,range); range = (origin - camTg).length(); } @@ -855,7 +857,7 @@ void Camera::resetDst() { if(isMarvin()) return; const auto& def = cameraDef(); - dst.spin.x = def.best_elevation; + // dst.spin.x = def.best_elevation; dst.range = def.best_range; } @@ -913,12 +915,12 @@ Matrix4x4 Camera::viewProj() const { } Matrix4x4 Camera::view() const { - auto spin = src.spin+offsetAng; + auto spin = src.spin+rotEleAz+offsetAng; return mkView(origin,spin); } Matrix4x4 Camera::viewLwc() const { - auto spin = src.spin+offsetAng; + auto spin = src.spin+rotEleAz+offsetAng; return mkView(Vec3(0),spin); } diff --git a/game/camera.h b/game/camera.h index 703bd8962..a55326611 100644 --- a/game/camera.h +++ b/game/camera.h @@ -133,6 +133,7 @@ class Camera final { Tempest::Vec3 cameraPos = {}; Tempest::Vec3 origin = {}; Tempest::Vec3 rotOffset = {}; + Tempest::Vec3 rotEleAz = {}; Tempest::Vec3 offsetAng = {}; State src, dst; @@ -182,7 +183,7 @@ class Camera final { void followCamera(Tempest::Vec3& pos, Tempest::Vec3 dest, float dtF); void followPos (Tempest::Vec3& pos, Tempest::Vec3 dest, float dtF); - void followAng (Tempest::Vec3& spin, Tempest::Vec3 dest, float dtF); + void followAng (Tempest::Vec3& spin, Tempest::Vec3 dest, float dtF, bool ver); static void followAng (float& ang, float dest, float speed, float dtF); const zenkit::ICamera& cameraDef() const; From 5a0bdd720c13784b25afba2afa884552d1b221d1 Mon Sep 17 00:00:00 2001 From: Try Date: Sat, 17 Jan 2026 22:36:02 +0100 Subject: [PATCH 02/27] camera in progress --- game/camera.cpp | 39 +++++++++++++++++++++++---------------- game/camera.h | 3 +++ 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index 8f78ff1af..4956ed2a2 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -169,14 +169,14 @@ void Camera::setMarvinMode(Camera::MarvinMode nextMod) { rotOffset.y = 0; } if(nextMod==M_Pinned) { - const auto& def = cameraDef(); + //const auto& def = cameraDef(); auto offset = origin; Matrix4x4 rotMat = pl->cameraMatrix(false); rotMat.inverse(); rotMat.project(offset); pin.origin = offset; - pin.spin.x = src.spin.x - def.best_elevation; + pin.spin.x = src.spin.x; pin.spin.y = src.spin.y - (pl ? pl->rotation() : 0); } } @@ -265,7 +265,7 @@ Matrix4x4 Camera::projective() const { Matrix4x4 Camera::viewShadowLwc(const Tempest::Vec3& lightDir, size_t layer) const { auto vp = viewProjLwc(); - float rotation = (180+src.spin.y-rotOffset.y); + float rotation = (180+angles.y-rotOffset.y); // if(layer==0) // return viewShadowVsm(cameraPos-origin,rotation,vp,lightDir); return mkViewShadow(cameraPos-origin,rotation,vp,lightDir,layer); @@ -320,7 +320,7 @@ Matrix4x4 Camera::mkViewShadowVsm(const Vec3& cameraPos, const Vec3& ldir) const Matrix4x4 Camera::viewShadow(const Vec3& lightDir, size_t layer) const { auto vp = viewProj(); - float rotation = (180+src.spin.y-rotOffset.y); + float rotation = (180+angles.y-rotOffset.y); // if(layer==0) // return viewShadowVsm(cameraPos,rotation,vp,lightDir); return mkViewShadow(cameraPos,rotation,vp,lightDir,layer); @@ -501,12 +501,12 @@ const zenkit::ICamera& Camera::cameraDef() const { void Camera::clampRotation(Tempest::Vec3& spin) { const auto& def = cameraDef(); - float maxElev = isMarvin() ? 90 : def.max_elevation; - float minElev = isMarvin() ? -90 : def.min_elevation; + float maxElev = isMarvin() ? +90 : (def.max_elevation - rotEleAz.x); + float minElev = isMarvin() ? -90 : (def.min_elevation - rotEleAz.x); if(spin.x>maxElev) spin.x = maxElev; if(spin.xcameraMatrix(false); auto offset = pin.origin; rotMat.project(offset); - origin = offset; src.target = dst.target; src.spin = dst.spin + pin.spin; offsetAng = Vec3(); + + origin = offset; + angles = src.spin + rotEleAz + offsetAng; return; } if(def.collision!=0) { // range = calcCameraColision(camTg,origin,src.spin,range); // origin = cameraPos - dir*range; - origin = calcCameraColision(camTg,origin,src.spin+rotEleAz+offsetAng,range); + origin = calcCameraColision(camTg,origin,angles,range); range = (origin - camTg).length(); } @@ -750,6 +757,7 @@ void Camera::calcControlPoints(float dtF) { rotOffsetMat.rotateOY(180-src.spin.y); rotOffsetMat.project(offset); origin += offset; + angles = src.spin + rotEleAz + offsetAng; } } @@ -859,6 +867,7 @@ void Camera::resetDst() { const auto& def = cameraDef(); // dst.spin.x = def.best_elevation; dst.range = def.best_range; + dst.spin = Vec3(0); } void Camera::debugDraw(DbgPainter& p) { @@ -915,13 +924,11 @@ Matrix4x4 Camera::viewProj() const { } Matrix4x4 Camera::view() const { - auto spin = src.spin+rotEleAz+offsetAng; - return mkView(origin,spin); + return mkView(origin, angles); } Matrix4x4 Camera::viewLwc() const { - auto spin = src.spin+rotEleAz+offsetAng; - return mkView(Vec3(0),spin); + return mkView(Vec3(0), angles); } Matrix4x4 Camera::viewProjLwc() const { diff --git a/game/camera.h b/game/camera.h index a55326611..b9d55e98c 100644 --- a/game/camera.h +++ b/game/camera.h @@ -132,9 +132,12 @@ class Camera final { Tempest::Vec3 cameraPos = {}; Tempest::Vec3 origin = {}; + Tempest::Vec3 angles = {}; + Tempest::Vec3 rotOffset = {}; Tempest::Vec3 rotEleAz = {}; Tempest::Vec3 offsetAng = {}; + State src, dst; Pin pin; From 459dd86d6e8bc2b8fa27e124c329a355153a87b2 Mon Sep 17 00:00:00 2001 From: Try Date: Sat, 17 Jan 2026 23:01:05 +0100 Subject: [PATCH 03/27] update save-file format --- game/camera.cpp | 8 ++++++-- game/game/serialize.h | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index 4956ed2a2..0dbd4491c 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -59,14 +59,18 @@ void Camera::reset(const Npc* pl) { void Camera::save(Serialize &s) { s.write(src.range, src.target, src.spin, dst.range, dst.target, dst.spin); - s.write(cameraPos,origin,rotOffset); + s.write(cameraPos,origin,angles); + s.write(rotOffset,rotEleAz,offsetAng); } void Camera::load(Serialize &s, Npc* pl) { reset(pl); + if(s.version()<54) + return; s.read(src.range, src.target, src.spin, dst.range, dst.target, dst.spin); - s.read(cameraPos,origin,rotOffset); + s.read(cameraPos,origin,angles); + s.read(rotOffset,rotEleAz,offsetAng); } void Camera::changeZoom(int delta) { diff --git a/game/game/serialize.h b/game/game/serialize.h index f4c8c4b73..d7ea4211f 100644 --- a/game/game/serialize.h +++ b/game/game/serialize.h @@ -33,7 +33,7 @@ class SaveGameHeader; class Serialize { public: enum Version : uint16_t { - Current = 53, + Current = 54, MinVersion = 36, Last_2025 = 53, From 56efc04453545ee526baccc50e11e95ef9362822 Mon Sep 17 00:00:00 2001 From: Try Date: Sun, 18 Jan 2026 00:58:35 +0100 Subject: [PATCH 04/27] tune camera values #866 --- game/camera.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index 0dbd4491c..14b0bded2 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -504,9 +504,10 @@ const zenkit::ICamera& Camera::cameraDef() const { } void Camera::clampRotation(Tempest::Vec3& spin) { + //NOTE: min elevation is zero for nomal camera. assume that it's ignored by vanilla const auto& def = cameraDef(); float maxElev = isMarvin() ? +90 : (def.max_elevation - rotEleAz.x); - float minElev = isMarvin() ? -90 : (def.min_elevation - rotEleAz.x); + float minElev = isMarvin() ? -90 : -60; //(def.min_elevation - rotEleAz.x); if(spin.x>maxElev) spin.x = maxElev; if(spin.xmax+1.f) { shift = (da-max); } From 2cf5cc541056843798f7da86391378f81e53c46d Mon Sep 17 00:00:00 2001 From: Try Date: Sun, 18 Jan 2026 01:38:32 +0100 Subject: [PATCH 05/27] tune player controls --- game/game/playercontrol.cpp | 28 +++++++++++++--------------- game/game/playercontrol.h | 3 +-- game/mainwindow.cpp | 10 +++++----- game/ui/touchinput.cpp | 2 +- 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/game/game/playercontrol.cpp b/game/game/playercontrol.cpp index c7c96ce61..1b6d1c5e1 100644 --- a/game/game/playercontrol.cpp +++ b/game/game/playercontrol.cpp @@ -247,14 +247,10 @@ bool PlayerControl::isPressed(KeyCodec::Action a) const { return ctrl[a]; } -void PlayerControl::onRotateMouse(float dAngle) { - dAngle = std::max(-40.f,std::min(dAngle,40.f)); - rotMouse += dAngle*0.3f; - } - -void PlayerControl::onRotateMouseDy(float dAngle) { - dAngle = std::max(-100.f,std::min(dAngle,100.f)); - rotMouseY += dAngle*0.2f; +void PlayerControl::onRotateMouse(float dAngleX, float dAngleY) { + // dAngleY = std::clamp(dAngleY, -100.f, 100.f); + rotMouse += dAngleX; + rotMouseY += dAngleY; } void PlayerControl::tickFocus() { @@ -563,8 +559,8 @@ bool PlayerControl::tickMove(uint64_t dt) { if(pl==nullptr) return true; - static const float speedRotX = 750.f; - rotMouse = std::min(std::abs(rotMouse), speedRotX*dtF) * (rotMouse>=0 ? 1 : -1); + // static const float speedRotX = 750.f; + // rotMouse = std::min(std::abs(rotMouse), speedRotX*dtF) * (rotMouse>=0 ? 1 : -1); implMove(dt); float runAngle = pl->runAngle(); @@ -664,7 +660,8 @@ void PlayerControl::implMove(uint64_t dt) { return; } - int rotation=0; + //float rotCam = 0; + int rotation = 0; if(allowRot) { if(this->wantsToTurnLeft()) { rot += rspeed; @@ -680,7 +677,8 @@ void PlayerControl::implMove(uint64_t dt) { if(rotMouse>0) rotation = -1; else rotation = 1; - rot +=rotMouse; + rot +=rotMouse; + //rotCam += rotMouse; rotMouse = 0; } rotY+=rotMouseY; @@ -1054,7 +1052,7 @@ void PlayerControl::assignRunAngle(Npc& pl, float rotation, uint64_t dt) { if(angle>rotation) dest = -std::min(-dangle,maxV); - float a = std::clamp(dtF*2.5f, 0.f, 1.f); + float a = std::clamp(dtF*1.5f, 0.f, 1.f); runAngleDest = runAngleDest*(1.f-a)+dest*a; runAngleSmooth = wrld.tickCount() + 200; } @@ -1065,7 +1063,7 @@ void PlayerControl::setAnimRotate(Npc& pl, float rotation, int anim, bool force, float dangle = (rotation-angle)/dtF; auto& wrld = pl.world(); - if(std::fabs(dangle)<100.f && !force) // 100 deg per second threshold + if(std::fabs(dangle)<30.f && !force) // 100 deg per second threshold anim = 0; if(anim!=0 && pl.isAttackAnim()) anim = 0; @@ -1073,7 +1071,7 @@ void PlayerControl::setAnimRotate(Npc& pl, float rotation, int anim, bool force, force = true; if(!force && wrld.tickCount()onRotateMouse(PointF(dpScaled.y,-dpScaled.x)); if(!inventory.isActive()) { - player.onRotateMouse (-dpScaled.x); - player.onRotateMouseDy(-dpScaled.y); + player.onRotateMouse(-dpScaled.x, -dpScaled.y); } dMouse = Point(); diff --git a/game/ui/touchinput.cpp b/game/ui/touchinput.cpp index 195ae9629..23aa35a96 100644 --- a/game/ui/touchinput.cpp +++ b/game/ui/touchinput.cpp @@ -32,7 +32,7 @@ void TouchInput::mouseDragEvent(Tempest::MouseEvent& e) { mpos = e.pos(); if(std::abs(dp.x)>0) { const float scale = 4.f; - ctrl.onRotateMouse(float(-dp.x) * scale); + ctrl.onRotateMouse(float(-dp.x) * scale, 0.f); } } From 94aaed55d782a8d5a1f1ee7200f0a06fab24f2fa Mon Sep 17 00:00:00 2001 From: Try Date: Sun, 18 Jan 2026 22:10:45 +0100 Subject: [PATCH 06/27] higher multiplicator and no clamping --- game/camera.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index 14b0bded2..61482ea95 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -613,7 +613,7 @@ void Camera::followCamera(Vec3& pos, Vec3 dest, float dtF) { void Camera::followAng(Vec3& spin, Vec3 dest, float dtF, bool ver) { const auto& def = cameraDef(); - const float velo = ver ? def.velo_rot : 12.f; + const float velo = ver ? def.velo_rot : 15.f; followAng(spin.x,dest.x,velo,dtF); followAng(spin.y,dest.y,velo,dtF); } @@ -628,10 +628,10 @@ void Camera::followAng(float& ang, float dest, float speed, float dtF) { static const float min=-90, max=90; if(da>max+1.f) { - shift = (da-max); + //shift = (da-max); } if(da Date: Mon, 19 Jan 2026 00:23:22 +0100 Subject: [PATCH 07/27] align follow to vanilla --- game/camera.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/game/camera.cpp b/game/camera.cpp index 61482ea95..9faab53f5 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -612,8 +612,11 @@ void Camera::followCamera(Vec3& pos, Vec3 dest, float dtF) { } void Camera::followAng(Vec3& spin, Vec3 dest, float dtF, bool ver) { + // tuned agains vanilla: by running in cyrcle up+left and measuring azimuth diference in cam-debug + static float gvelo = 7.5f; + const auto& def = cameraDef(); - const float velo = ver ? def.velo_rot : 15.f; + const float velo = ver ? def.velo_rot : gvelo; followAng(spin.x,dest.x,velo,dtF); followAng(spin.y,dest.y,velo,dtF); } From a9561d1d6ab5bc73da129e60d2256479540b97b3 Mon Sep 17 00:00:00 2001 From: Try Date: Mon, 19 Jan 2026 00:23:39 +0100 Subject: [PATCH 08/27] logging --- game/mainwindow.cpp | 13 ++++++++++++- game/utils/mouseutil.cpp | 3 +++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/game/mainwindow.cpp b/game/mainwindow.cpp index 8d3b228bb..bc5b1a132 100644 --- a/game/mainwindow.cpp +++ b/game/mainwindow.cpp @@ -339,6 +339,15 @@ void MainWindow::tickMouse() { return; } + static bool once = false; + if(!once) { + once = true; + Log::d("mouse sensitivity = ", MouseUtil::mouseSysSpeed()); + } + + if(dMouse==Point()) + return; + const bool camLookaroundInverse = Gothic::inst().settingsGetI("GAME","camLookaroundInverse"); const float mouseSensitivity = Gothic::inst().settingsGetF("GAME","mouseSensitivity")/MouseUtil::mouseSysSpeed(); PointF dpScaled = PointF(float(dMouse.x)*mouseSensitivity,float(dMouse.y)*mouseSensitivity); @@ -349,7 +358,9 @@ void MainWindow::tickMouse() { static float mul = 125.f; dpScaled *= mul; - //dpScaled.y /= 7.f; + + Log::d("mouse dMouse = ", dMouse.x, ", ", dMouse.y); + Log::d("mouse dpScaled = ", dpScaled.x, ", ", dpScaled.y); camera->onRotateMouse(PointF(dpScaled.y,-dpScaled.x)); if(!inventory.isActive()) { diff --git a/game/utils/mouseutil.cpp b/game/utils/mouseutil.cpp index 5d78f29e4..acd3bca22 100644 --- a/game/utils/mouseutil.cpp +++ b/game/utils/mouseutil.cpp @@ -1,6 +1,7 @@ #include "mouseutil.h" #include +#include #ifdef __WINDOWS__ #include @@ -13,6 +14,8 @@ float MouseUtil::mouseSysSpeed() { #ifdef __WINDOWS__ int mouseSpeed = 10; if(SystemParametersInfoA(SPI_GETMOUSESPEED, 0, &mouseSpeed, 0)) { + // MSDN says in between 1..20, but clamp to be extra safe + mouseSpeed = std::clamp(mouseSpeed, 1, 20); return float(mouseSpeed)/10.f; } return 1.f; From 31439355e746fa0ea202bb48957d5920187b2a98 Mon Sep 17 00:00:00 2001 From: Try Date: Thu, 22 Jan 2026 23:15:21 +0100 Subject: [PATCH 09/27] camera in progress --- game/camera.cpp | 462 +++++++++++++++++++++++++++++--------------- game/camera.h | 20 +- game/mainwindow.cpp | 21 +- game/mainwindow.h | 2 +- 4 files changed, 333 insertions(+), 172 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index 9faab53f5..cdf0784cd 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -6,6 +6,7 @@ #include "world/objects/interactive.h" #include "world/world.h" #include "game/definitions/cameradefinitions.h" +#include "graphics/mesh/animmath.h" #include "game/serialize.h" #include "utils/gthfont.h" #include "utils/dbgpainter.h" @@ -31,6 +32,48 @@ static Vec3 angleMod(Vec3 a) { return a; } +static zenkit::Quat fromAngles(Vec3 angles) { + float roll = float(angles.x*M_PI)/180.f; + float yaw = float(angles.y*M_PI)/180.f; + float pitch = 0.f; + + float cr = std::cos(roll * 0.5f); + float sr = std::sin(roll * 0.5f); + float cp = std::cos(pitch * 0.5f); + float sp = std::sin(pitch * 0.5f); + float cy = std::cos(yaw * 0.5f); + float sy = std::sin(yaw * 0.5f); + + zenkit::Quat q; + q.w = cr * cp * cy + sr * sp * sy; + q.x = sr * cp * cy - cr * sp * sy; + q.y = cr * sp * cy + sr * cp * sy; + q.z = cr * cp * sy - sr * sp * cy; + + return q; + } + +static Vec3 toAngles(zenkit::Quat q) { + float roll, pitch, yaw; + + // roll (x-axis rotation) + float sinr_cosp = 2 * (q.w * q.x + q.y * q.z); + float cosr_cosp = 1 - 2 * (q.x * q.x + q.y * q.y); + roll = std::atan2(sinr_cosp, cosr_cosp); + + // pitch (y-axis rotation) + float sinp = std::sqrt(1 + 2 * (q.w * q.y - q.x * q.z)); + float cosp = std::sqrt(1 - 2 * (q.w * q.y - q.x * q.z)); + pitch = 2.f * std::atan2(sinp, cosp) - float(M_PI / 2.0); + + // yaw (z-axis rotation) + float siny_cosp = 2 * (q.w * q.z + q.x * q.y); + float cosy_cosp = 1 - 2 * (q.y * q.y + q.z * q.z); + yaw = std::atan2(siny_cosp, cosy_cosp); + + return Vec3(roll,yaw,pitch)*float(180.f/M_PI); + } + float Camera::maxDist = 150; float Camera::baseSpeeed = 200; float Camera::offsetAngleMul = 0.1f; @@ -45,15 +88,21 @@ void Camera::reset() { void Camera::reset(const Npc* pl) { const auto& def = cameraDef(); + if(userRange<=0) { + userRange = (def.best_range - def.min_range)/(def.max_range - def.min_range); + } dst.range = userRange*(def.max_range-def.min_range)+def.min_range; dst.target = pl ? pl->cameraBone() : Vec3(); - dst.spin.x = 0; //def.best_elevation; + dst.spin.x = 0; dst.spin.y = pl ? pl->rotation() : 0; + dst.spin += Vec3(def.best_elevation, + def.best_azimuth, + 0); src.spin = dst.spin; - calcControlPoints(-1.f); + tickThirdPerson(-1.f); } void Camera::save(Serialize &s) { @@ -149,40 +198,53 @@ void Camera::setMode(Camera::Mode m) { } if(auto pl = Gothic::inst().player()) { + dst.spin.x = 0; dst.spin.y = pl->rotation(); } + + const auto& def = cameraDef(); + auto rotBest = Vec3(def.best_elevation, + def.best_azimuth, + 0); + dst.spin += rotBest; } void Camera::setMarvinMode(Camera::MarvinMode nextMod) { if(camMarvinMod==nextMod) return; - if(auto pl = Gothic::inst().player()) { - if(camMarvinMod==M_Pinned) { - src.spin = dst.spin; - float range = src.range*100.f; - Vec3 dir = {0,0,1}; - Matrix4x4 rotOffsetMat; - rotOffsetMat.identity(); - rotOffsetMat.rotateOY(180-src.spin.y); - rotOffsetMat.rotateOX(src.spin.x); - rotOffsetMat.project(dir); - dst.target = origin +dir*range; - src.target = dst.target; - cameraPos = src.target; - rotOffset.y = 0; - } - if(nextMod==M_Pinned) { - //const auto& def = cameraDef(); - auto offset = origin; - Matrix4x4 rotMat = pl->cameraMatrix(false); + /* + if(camMarvinMod==M_Pinned) { + src.spin = dst.spin; + float range = src.range*100.f; + Vec3 dir = {0,0,1}; + Matrix4x4 rotOffsetMat; + rotOffsetMat.identity(); + rotOffsetMat.rotateOY(180-src.spin.y); + rotOffsetMat.rotateOX(src.spin.x); + rotOffsetMat.project(dir); + dst.target = origin +dir*range; + src.target = dst.target; + cameraPos = src.target; + rotOffset.y = 0; + } + */ - rotMat.inverse(); - rotMat.project(offset); - pin.origin = offset; - pin.spin.x = src.spin.x; - pin.spin.y = src.spin.y - (pl ? pl->rotation() : 0); - } + if(nextMod==M_Pinned) { + const auto pl = Gothic::inst().player(); + + auto offset = origin - dst.target; + Matrix4x4 rotMat = pl!=nullptr ? pl->cameraMatrix(false) : Matrix4x4::mkIdentity(); + rotMat.inverse(); + rotMat.project(offset); + + pin.origin = offset;//origin - dst.target; + pin.spin = angles; + } + else if(nextMod==M_Free) { + dst.spin = angles; + dst.target = origin; + src = dst; } camMarvinMod = nextMod; } @@ -269,7 +331,7 @@ Matrix4x4 Camera::projective() const { Matrix4x4 Camera::viewShadowLwc(const Tempest::Vec3& lightDir, size_t layer) const { auto vp = viewProjLwc(); - float rotation = (180+angles.y-rotOffset.y); + float rotation = (180+angles.y); // if(layer==0) // return viewShadowVsm(cameraPos-origin,rotation,vp,lightDir); return mkViewShadow(cameraPos-origin,rotation,vp,lightDir,layer); @@ -324,7 +386,7 @@ Matrix4x4 Camera::mkViewShadowVsm(const Vec3& cameraPos, const Vec3& ldir) const Matrix4x4 Camera::viewShadow(const Vec3& lightDir, size_t layer) const { auto vp = viewProj(); - float rotation = (180+angles.y-rotOffset.y); + float rotation = (180+angles.y); // if(layer==0) // return viewShadowVsm(cameraPos,rotation,vp,lightDir); return mkViewShadow(cameraPos,rotation,vp,lightDir,layer); @@ -503,25 +565,13 @@ const zenkit::ICamera& Camera::cameraDef() const { return camd.stdCam(); } -void Camera::clampRotation(Tempest::Vec3& spin) { - //NOTE: min elevation is zero for nomal camera. assume that it's ignored by vanilla - const auto& def = cameraDef(); - float maxElev = isMarvin() ? +90 : (def.max_elevation - rotEleAz.x); - float minElev = isMarvin() ? -90 : -60; //(def.min_elevation - rotEleAz.x); - if(spin.x>maxElev) - spin.x = maxElev; - if(spin.xcameraMatrix(false) : Matrix4x4::mkIdentity(); + + auto offset = pin.origin; + rotMat.project(offset); + origin = dst.target + offset; + angles = pin.spin; + break; + } + } auto world = Gothic::inst().world(); if(world!=nullptr) { @@ -670,128 +771,135 @@ void Camera::tick(uint64_t dt) { } } -void Camera::calcControlPoints(float dtF) { +void Camera::tickFirstPerson(float /*dtF*/) { + const auto pl = Gothic::inst().player(); + const auto rotMat = pl!=nullptr ? pl->cameraMatrix(true) : Matrix4x4::mkIdentity(); + + //dst.spin.y = pl!=nullptr ? pl->rotation() : 0; + //dst.spin.z = 0; + + dst.spin = clampRotation(dst.spin); + src.spin = dst.spin; + src.target = dst.target; + + origin = dst.target; + angles = dst.spin; + + /* + Matrix4x4 rotOffsetMat; + rotOffsetMat.identity(); + rotOffsetMat.rotateOY(src.spin.y); + rotOffsetMat.project(offset); + */ + + Vec3 offset = {0,0,20}; + rotMat.project(offset); + origin = offset; + //origin += offset; + } + +void Camera::tickThirdPerson(float dtF) { const auto& def = cameraDef(); + + auto mkRotMatrix = [](Vec3 spin){ + auto rotOffsetMat = Matrix4x4::mkIdentity(); + rotOffsetMat.rotateOY(180-spin.y); + rotOffsetMat.rotateOX(spin.x); + rotOffsetMat.rotateOZ(spin.z); + return rotOffsetMat; + }; + auto targetOffset = Vec3(def.target_offset_x, def.target_offset_y, def.target_offset_z); auto rotOffsetDef = Vec3(def.rot_offset_x, def.rot_offset_y, def.rot_offset_z); - auto rotBest = Vec3(0,def.best_azimuth,0); - if(!isMarvin()) { - followAng(rotEleAz, Vec3(def.best_elevation, def.best_azimuth, 0), dtF, true); - followAng(rotOffset, rotOffsetDef, dtF, true); - } - clampRotation(dst.spin); + dst.spin = clampRotation(dst.spin); + followAng(rotOffset, rotOffsetDef, dtF); - float range = src.range*100.f; - if(camMod==Dialog) { - // TODO: DialogCams.zen - range = dlgDist; - src.spin = dst.spin; - src.target = dst.target; - cameraPos = src.target; - rotOffset = Vec3(); - rotEleAz = Vec3(); - rotOffsetDef = Vec3(); - rotBest = Vec3(); - //spin.y += def.bestAzimuth; - } - - if(isCutscene()) { - rotOffset = rotOffsetDef; - range = 0; - } + followSpin(src.spin, dst.spin, dtF); + //src.spin = dst.spin; - followAng(src.spin, dst.spin, dtF, false); - - Matrix4x4 rotOffsetMat; - rotOffsetMat.identity(); - rotOffsetMat.rotateOY(180-src.spin.y-rotEleAz.y); - rotOffsetMat.rotateOX(src.spin.x+rotEleAz.x); + const auto rotOffsetMat = mkRotMatrix(src.spin); rotOffsetMat.project(targetOffset); - Vec3 dir = {0,0,1}; + Vec3 dir = {0,0,-1}; rotOffsetMat.project(dir); - auto target = dst.target + targetOffset; - followPos(src.target,target,dtF); - - auto camTg = src.target;//clampPos(src.target,target); - followCamera(cameraPos,src.target,dtF); - - origin = cameraPos - dir*range; - angles = src.spin + rotEleAz + offsetAng; - - if(camMarvinMod==M_Free || isCutscene()) { - return; + followPos(src.target, dst.target + targetOffset, dtF); + //src.target = dst.target + targetOffset; + + // range = calcCameraColision(camTg,origin,src.spin,range); + // origin = cameraPos - dir*range; + float range = src.range*100.f; + auto target = src.target + dir*range; + origin = target; + //origin = origin+(target-origin)*std::min(1.f, 15.f*dtF); + + const auto offsetAng = calcOffsetAngles(origin, dst.target + targetOffset + dir*range, dst.target); + angles = src.spin - rotOffset + offsetAng; + + if(true && def.collision!=0) { + auto rangles = src.spin - rotOffsetDef; + auto dview = (origin - dst.target); + range = calcCameraColision2(dst.target,origin,rangles,dview.length()); + + origin = dst.target + Vec3::normalize(dview)*range; + /* NOTE: with range < 80, camera gradually moves up in vanilla + if(range<80) + rotBest.x = 80; + */ } - const auto pl = Gothic::inst().player(); - if(camMarvinMod==M_Pinned && camMod!=Dialog && pl!=nullptr) { - auto rotMat = pl->cameraMatrix(false); - auto offset = pin.origin; - rotMat.project(offset); - src.target = dst.target; - src.spin = dst.spin + pin.spin; - offsetAng = Vec3(); - - origin = offset; - angles = src.spin + rotEleAz + offsetAng; - return; + static bool dbg = false; + if(dbg) { + origin = dst.target + targetOffset + dir*range; + angles = dst.spin + rotOffset; } + } - if(def.collision!=0) { - // range = calcCameraColision(camTg,origin,src.spin,range); - // origin = cameraPos - dir*range; - origin = calcCameraColision(camTg,origin,angles,range); - range = (origin - camTg).length(); - } +float Camera::calcCameraColision2(const Tempest::Vec3& target, const Tempest::Vec3& origin, const Tempest::Vec3& angles, float dist) const { + //static float minDist = 20; + static float padding = 20; + static int n = 1, nn=1; - auto baseOrigin = target - dir*range; - if(camMod==Dialog) - offsetAng = Vec3(); else - offsetAng = calcOffsetAngles(origin,baseOrigin,dst.target); + auto world = Gothic::inst().world(); + if(world==nullptr) + return dist; - if(fpEnable && camMarvinMod==M_Normal) { - origin = dst.target; - offsetAng = Vec3(); + Matrix4x4 vinv = projective(); + vinv.mul(mkView(origin,angles)); + vinv.inverse(); - Vec3 offset = {0,0,20}; - Matrix4x4 rotOffsetMat; - rotOffsetMat.identity(); - rotOffsetMat.rotateOY(180-src.spin.y); - rotOffsetMat.project(offset); - origin += offset; - angles = src.spin + rotEleAz + offsetAng; - } - } + auto& physic = *world->physic(); + auto dview = (origin - target); -Vec3 Camera::calcOffsetAngles(const Vec3& origin, const Vec3& target) const { - auto sXZ = origin-target; - float y0 = std::atan2(sXZ.x,sXZ.z)*180.f/float(M_PI); - float x0 = std::atan2(sXZ.y,Vec2(sXZ.x,sXZ.z).length())*180.f/float(M_PI); + raysCasted = 0; + float distM = dist; + for(int i=-1;i<=n;++i) + for(int r=-n;r<=n;++r) { + raysCasted++; + float u = float(i)/float(nn),v = float(r)/float(nn); + Tempest::Vec3 r1 = {u,v,depthNear}; + vinv.project(r1); + auto dr = (r1 - target); + dr = dr * (dist+padding) / (dr.length()+0.00001f); - return Vec3(x0,-y0,0); - } + auto rc = physic.ray(target, target+dr); + if(!rc.hasCol) + continue; -Vec3 Camera::calcOffsetAngles(Vec3 srcOrigin, Vec3 dstOrigin, Vec3 target) const { - auto src = srcOrigin-target; src.y = 0; - auto dst = dstOrigin-target; dst.y = 0; + auto tr = (rc.v - target); + float dist1 = Vec3::dotProduct(dview,tr)/dist; - auto dot = Vec3::dotProduct(src,dst); - float k = 0; - if(dst.length()>minLength) { - k = dot/dst.length(); - k = std::max(0.f,std::min(k/100.f,1.f)); - } + dist1 = std::max(dist1-padding, 0); + if(dist1rotation(), 0}; + spin -= dspin; + + spin.x = std::clamp(spin.x, minElev, maxElev); + spin.y = std::clamp(spin.y, minAzim, maxAzim); + return spin + dspin; + } + +Vec3 Camera::calcOffsetAngles(const Vec3& origin, const Vec3& target) const { + auto sXZ = origin-target; + float y0 = std::atan2(sXZ.x,sXZ.z)*180.f/float(M_PI); + float x0 = std::atan2(sXZ.y,Vec2(sXZ.x,sXZ.z).length())*180.f/float(M_PI); + + return Vec3(x0,-y0,0); + } + +Vec3 Camera::calcOffsetAngles(Vec3 srcOrigin, Vec3 dstOrigin, Vec3 target) const { + auto src = srcOrigin-target; src.y = 0; + auto dst = dstOrigin-target; dst.y = 0; + + auto dot = Vec3::dotProduct(src,dst); + float k = 0; + if(dst.length()>minLength) { + k = dot/dst.length(); + k = std::max(0.f,std::min(k/100.f,1.f)); + } + + auto a0 = calcOffsetAngles(srcOrigin,target); + auto a1 = calcOffsetAngles(dstOrigin,target); + auto da = angleMod(a1-a0); + return da*k*offsetAngleMul; + } + Matrix4x4 Camera::mkView(const Vec3& pos, const Vec3& spin) const { Matrix4x4 view; view.identity(); @@ -863,9 +1015,9 @@ Matrix4x4 Camera::mkView(const Vec3& pos, const Vec3& spin) const { Matrix4x4 Camera::mkRotation(const Vec3& spin) const { Matrix4x4 view; view.identity(); - view.rotateOX(spin.x-rotOffset.x); - view.rotateOY(spin.y-rotOffset.y); - view.rotateOZ(spin.z-rotOffset.z); + view.rotateOX(spin.x); + view.rotateOY(spin.y); + view.rotateOZ(spin.z); return view; } @@ -907,9 +1059,9 @@ void Camera::debugDraw(DbgPainter& p) { buf = string_frm("Range To Player : ", (dst.target-origin).length()); p.drawText(8,y,buf); y += fnt.pixelSize(); - buf = string_frm("Azimuth : ", angleMod(dst.spin.y-src.spin.y)); + buf = string_frm("Azimuth : ", angleMod(dst.spin.y-angles.y)); p.drawText(8,y,buf); y += fnt.pixelSize(); - buf = string_frm("Elevation : ", rotOffset.x-src.spin.x); + buf = string_frm("Elevation : ", rotOffset.x+angles.x); p.drawText(8,y,buf); y += fnt.pixelSize(); } diff --git a/game/camera.h b/game/camera.h index b9d55e98c..d18a0ffa0 100644 --- a/game/camera.h +++ b/game/camera.h @@ -49,7 +49,7 @@ class Camera final { void reset(const Npc* pl); void save(Serialize &s); - void load(Serialize &s,Npc* pl); + void load(Serialize &s, Npc* pl); void changeZoom(int delta); void setViewport(uint32_t w, uint32_t h); @@ -134,8 +134,8 @@ class Camera final { Tempest::Vec3 origin = {}; Tempest::Vec3 angles = {}; - Tempest::Vec3 rotOffset = {}; Tempest::Vec3 rotEleAz = {}; + Tempest::Vec3 rotOffset = {}; Tempest::Vec3 offsetAng = {}; State src, dst; @@ -143,7 +143,7 @@ class Camera final { Pin pin; float dlgDist = 0; - float userRange = 0.13f; + float userRange = 0; float targetVelo = 0; float veloTrans = 0; @@ -168,13 +168,18 @@ class Camera final { static float offsetAngleMul; static const float minLength; - void calcControlPoints(float dtF); + void tickFirstPerson(float dtF); + void tickThirdPerson(float dtF); + + Tempest::Vec3 clampRotation(Tempest::Vec3 spin); + float calcCameraColision2(const Tempest::Vec3& target, const Tempest::Vec3& origin, const Tempest::Vec3& rotSpin, float dist) const; + Tempest::Vec3 calcCameraColision(const Tempest::Vec3& target, const Tempest::Vec3& origin, const Tempest::Vec3& rotSpin, float dist) const; Tempest::Vec3 calcOffsetAngles(const Tempest::Vec3& srcOrigin, const Tempest::Vec3& target) const; Tempest::Vec3 calcOffsetAngles(Tempest::Vec3 srcOrigin, Tempest::Vec3 dstOrigin, Tempest::Vec3 target) const; - Tempest::Vec3 calcCameraColision(const Tempest::Vec3& target, const Tempest::Vec3& origin, const Tempest::Vec3& rotSpin, float dist) const; void implMove(Tempest::KeyEvent::KeyType t, uint64_t dt); + Tempest::Matrix4x4 mkView (const Tempest::Vec3& pos, const Tempest::Vec3& spin) const; Tempest::Matrix4x4 mkRotation(const Tempest::Vec3& spin) const; Tempest::Matrix4x4 mkViewShadow(const Tempest::Vec3& cameraPos, float rotation, @@ -182,11 +187,10 @@ class Camera final { Tempest::Matrix4x4 mkViewShadowVsm(const Tempest::Vec3& cameraPos, const Tempest::Vec3& ldir) const; void resetDst(); - void clampRotation(Tempest::Vec3& spin); - void followCamera(Tempest::Vec3& pos, Tempest::Vec3 dest, float dtF); void followPos (Tempest::Vec3& pos, Tempest::Vec3 dest, float dtF); - void followAng (Tempest::Vec3& spin, Tempest::Vec3 dest, float dtF, bool ver); + void followSpin (Tempest::Vec3& spin, Tempest::Vec3 dest, float dtF); + void followAng (Tempest::Vec3& spin, Tempest::Vec3 dest, float dtF); static void followAng (float& ang, float dest, float speed, float dtF); const zenkit::ICamera& cameraDef() const; diff --git a/game/mainwindow.cpp b/game/mainwindow.cpp index bc5b1a132..4523a95eb 100644 --- a/game/mainwindow.cpp +++ b/game/mainwindow.cpp @@ -326,7 +326,7 @@ void MainWindow::processMouse(MouseEvent& event, bool enable) { } } -void MainWindow::tickMouse() { +void MainWindow::tickMouse(uint64_t dt) { auto camera = Gothic::inst().camera(); if(dialogs.hasContent() || Gothic::inst().isPause() || camera==nullptr || camera->isCutscene()) { dMouse = Point(); @@ -356,11 +356,16 @@ void MainWindow::tickMouse() { if(camLookaroundInverse) dpScaled.y *= -1.f; - static float mul = 125.f; + static float mul = 270.f; dpScaled *= mul; - Log::d("mouse dMouse = ", dMouse.x, ", ", dMouse.y); - Log::d("mouse dpScaled = ", dpScaled.x, ", ", dpScaled.y); + static float psMax = 720.f; + const float dtF = float(dt)/1000.f; + dpScaled.x = std::clamp(dpScaled.x, -(psMax*dtF), psMax*dtF); + dpScaled.y = std::clamp(dpScaled.y, -(psMax*dtF), psMax*dtF); + + // Log::d("mouse dMouse = ", dMouse.x, ", ", dMouse.y); + // Log::d("mouse dpScaled = ", dpScaled.x, ", ", dpScaled.y); camera->onRotateMouse(PointF(dpScaled.y,-dpScaled.x)); if(!inventory.isActive()) { @@ -887,11 +892,11 @@ uint64_t MainWindow::tick() { else if(runtimeMode==R_Suspended) { auto camera = Gothic::inst().camera(); if(camera!=nullptr && camera->isFree()) { - player.tickCameraMove(dt); - tickMouse(); + //player.tickCameraMove(dt); + tickMouse(dt); } update(); - return dt; + return 0; } dialogs.tick(dt); @@ -903,7 +908,7 @@ uint64_t MainWindow::tick() { ;//clearInput(); if(document.isActive()) clearInput(); - tickMouse(); + tickMouse(dt); player.tickMove(dt); update(); return dt; diff --git a/game/mainwindow.h b/game/mainwindow.h index ab2fb0940..833e58894 100644 --- a/game/mainwindow.h +++ b/game/mainwindow.h @@ -89,7 +89,7 @@ class MainWindow : public Tempest::Window { void setFullscreen(bool fs); void processMouse(Tempest::MouseEvent& event, bool enable); - void tickMouse(); + void tickMouse(uint64_t dt); void onSettings(); void setupUi(); From 9c6d2eee7b7be73167f33dc4f929254296db2726 Mon Sep 17 00:00:00 2001 From: Try Date: Mon, 26 Jan 2026 23:39:59 +0100 Subject: [PATCH 10/27] restore dialog camera --- game/camera.cpp | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index cdf0784cd..ee4056140 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -600,7 +600,7 @@ void Camera::implMove(Tempest::Event::KeyType key, uint64_t dt) { void Camera::setPosition(const Tempest::Vec3& pos) { dst.target = pos; src.target = dst.target; - cameraPos = dst.target; + origin = dst.target; } void Camera::setDestPosition(const Tempest::Vec3& pos) { @@ -727,9 +727,16 @@ void Camera::tick(uint64_t dt) { switch (camMarvinMod) { case M_Normal: { - if(fpEnable) { + if(isCutscene()) { + src.target = dst.target; + src.spin = dst.spin; + origin = src.target; + angles = src.spin; + } + else if(fpEnable && camMod!=Dialog) { tickFirstPerson(dtF); - } else { + } + else { tickThirdPerson(dtF); } break; @@ -782,20 +789,10 @@ void Camera::tickFirstPerson(float /*dtF*/) { src.spin = dst.spin; src.target = dst.target; - origin = dst.target; - angles = dst.spin; - - /* - Matrix4x4 rotOffsetMat; - rotOffsetMat.identity(); - rotOffsetMat.rotateOY(src.spin.y); - rotOffsetMat.project(offset); - */ - Vec3 offset = {0,0,20}; rotMat.project(offset); origin = offset; - //origin += offset; + angles = dst.spin; } void Camera::tickThirdPerson(float dtF) { @@ -833,7 +830,7 @@ void Camera::tickThirdPerson(float dtF) { // range = calcCameraColision(camTg,origin,src.spin,range); // origin = cameraPos - dir*range; - float range = src.range*100.f; + float range = (camMod==Dialog) ? dlgDist : src.range*100.f; auto target = src.target + dir*range; origin = target; //origin = origin+(target-origin)*std::min(1.f, 15.f*dtF); @@ -841,6 +838,14 @@ void Camera::tickThirdPerson(float dtF) { const auto offsetAng = calcOffsetAngles(origin, dst.target + targetOffset + dir*range, dst.target); angles = src.spin - rotOffset + offsetAng; + if(camMod==Dialog) { + // TODO: DialogCams.zen + src.spin = dst.spin; + src.target = dst.target; + origin = src.target + dir*range; + angles = src.spin; + } + if(true && def.collision!=0) { auto rangles = src.spin - rotOffsetDef; auto dview = (origin - dst.target); From a54972a5d74bacd6aba16967fc16c4e858fd4806 Mon Sep 17 00:00:00 2001 From: Try Date: Wed, 28 Jan 2026 22:42:54 +0100 Subject: [PATCH 11/27] camera in progress --- game/camera.cpp | 114 ++++++++++++++++++++++++++++-------------------- game/camera.h | 7 ++- 2 files changed, 71 insertions(+), 50 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index ee4056140..e7648b7c5 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -329,20 +329,12 @@ Matrix4x4 Camera::projective() const { return ret; } -Matrix4x4 Camera::viewShadowLwc(const Tempest::Vec3& lightDir, size_t layer) const { - auto vp = viewProjLwc(); - float rotation = (180+angles.y); - // if(layer==0) - // return viewShadowVsm(cameraPos-origin,rotation,vp,lightDir); - return mkViewShadow(cameraPos-origin,rotation,vp,lightDir,layer); - } - Matrix4x4 Camera::viewShadowVsm(const Tempest::Vec3& ldir) const { - return mkViewShadowVsm(cameraPos,ldir); + return mkViewShadowVsm(src.target,ldir); } Matrix4x4 Camera::viewShadowVsmLwc(const Tempest::Vec3& ldir) const { - return mkViewShadowVsm(cameraPos-origin,ldir); + return mkViewShadowVsm(src.target-origin,ldir); } Matrix4x4 Camera::mkViewShadowVsm(const Vec3& cameraPos, const Vec3& ldir) const { @@ -389,7 +381,15 @@ Matrix4x4 Camera::viewShadow(const Vec3& lightDir, size_t layer) const { float rotation = (180+angles.y); // if(layer==0) // return viewShadowVsm(cameraPos,rotation,vp,lightDir); - return mkViewShadow(cameraPos,rotation,vp,lightDir,layer); + return mkViewShadow(src.target,rotation,vp,lightDir,layer); + } + +Matrix4x4 Camera::viewShadowLwc(const Tempest::Vec3& lightDir, size_t layer) const { + auto vp = viewProjLwc(); + float rotation = (180+angles.y); + // if(layer==0) + // return viewShadowVsm(cameraPos-origin,rotation,vp,lightDir); + return mkViewShadow(src.target-origin,rotation,vp,lightDir,layer); } Matrix4x4 Camera::mkViewShadow(const Vec3& cameraPos, float rotation, const Tempest::Matrix4x4& viewProj, const Vec3& lightDir, size_t layer) const { @@ -796,6 +796,7 @@ void Camera::tickFirstPerson(float /*dtF*/) { } void Camera::tickThirdPerson(float dtF) { + //const auto pl = Gothic::inst().player(); const auto& def = cameraDef(); auto mkRotMatrix = [](Vec3 spin){ @@ -813,49 +814,52 @@ void Camera::tickThirdPerson(float dtF) { def.rot_offset_y, def.rot_offset_z); - dst.spin = clampRotation(dst.spin); - followAng(rotOffset, rotOffsetDef, dtF); + if(camMod==Dialog) { + // TODO: DialogCams.zen + src.target = dst.target; + src.spin = dst.spin; + rotOffset = Vec3(0); + } else { + dst.spin = clampRotation(dst.spin); - followSpin(src.spin, dst.spin, dtF); - //src.spin = dst.spin; + followAng(rotOffset, rotOffsetDef, dtF); + followSpin(src.spin, dst.spin, dtF); + followPos (src.target, dst.target, dtF); + //src.target = dst.target; + } - const auto rotOffsetMat = mkRotMatrix(src.spin); - rotOffsetMat.project(targetOffset); + const auto targetOffsetMat = mkRotMatrix(src.spin); + targetOffsetMat.project(targetOffset); - Vec3 dir = {0,0,-1}; + const auto rotOffsetMat = mkRotMatrix(src.spin); + auto dir = Vec3{0,0,-1}; rotOffsetMat.project(dir); - followPos(src.target, dst.target + targetOffset, dtF); - //src.target = dst.target + targetOffset; - // range = calcCameraColision(camTg,origin,src.spin,range); // origin = cameraPos - dir*range; float range = (camMod==Dialog) ? dlgDist : src.range*100.f; - auto target = src.target + dir*range; - origin = target; - //origin = origin+(target-origin)*std::min(1.f, 15.f*dtF); - - const auto offsetAng = calcOffsetAngles(origin, dst.target + targetOffset + dir*range, dst.target); - angles = src.spin - rotOffset + offsetAng; + angles = src.spin - rotOffset; - if(camMod==Dialog) { - // TODO: DialogCams.zen - src.spin = dst.spin; - src.target = dst.target; - origin = src.target + dir*range; - angles = src.spin; + if(true && def.collision!=0) { + // testd in marvin: collision is calculated from 'target', not from npc + range = calcCameraColision2(src.target,dir,angles,range); + // NOTE: with range < 80, camera gradually moves up in vanilla + if(range<80) { + range = 150; + angles.x = 90; + + const auto rotOffsetMat = mkRotMatrix(angles); + dir = Vec3{0,0,-1}; + rotOffsetMat.project(dir); + } } - if(true && def.collision!=0) { - auto rangles = src.spin - rotOffsetDef; - auto dview = (origin - dst.target); - range = calcCameraColision2(dst.target,origin,rangles,dview.length()); - - origin = dst.target + Vec3::normalize(dview)*range; - /* NOTE: with range < 80, camera gradually moves up in vanilla - if(range<80) - rotBest.x = 80; - */ + origin = src.target + targetOffset + dir*range; + //origin = origin+(target-origin)*std::min(1.f, 15.f*dtF); + + if(camMod==Dialog) { + //origin = src.target + dir*range; + //angles = src.spin; } static bool dbg = false; @@ -865,14 +869,16 @@ void Camera::tickThirdPerson(float dtF) { } } -float Camera::calcCameraColision2(const Tempest::Vec3& target, const Tempest::Vec3& origin, const Tempest::Vec3& angles, float dist) const { +float Camera::calcCameraColision2(const Tempest::Vec3& target, const Tempest::Vec3& dir, const Tempest::Vec3& angles, float range) const { //static float minDist = 20; static float padding = 20; static int n = 1, nn=1; auto world = Gothic::inst().world(); if(world==nullptr) - return dist; + return range; + + const auto origin = target + dir*range; Matrix4x4 vinv = projective(); vinv.mul(mkView(origin,angles)); @@ -882,7 +888,7 @@ float Camera::calcCameraColision2(const Tempest::Vec3& target, const Tempest::Ve auto dview = (origin - target); raysCasted = 0; - float distM = dist; + float distM = range; for(int i=-1;i<=n;++i) for(int r=-n;r<=n;++r) { raysCasted++; @@ -890,14 +896,14 @@ float Camera::calcCameraColision2(const Tempest::Vec3& target, const Tempest::Ve Tempest::Vec3 r1 = {u,v,depthNear}; vinv.project(r1); auto dr = (r1 - target); - dr = dr * (dist+padding) / (dr.length()+0.00001f); + dr = dr * (range+padding) / (dr.length()+0.00001f); auto rc = physic.ray(target, target+dr); if(!rc.hasCol) continue; auto tr = (rc.v - target); - float dist1 = Vec3::dotProduct(dview,tr)/dist; + float dist1 = Vec3::dotProduct(dview,tr)/range; dist1 = std::max(dist1-padding, 0); if(dist1 Date: Thu, 29 Jan 2026 23:03:42 +0100 Subject: [PATCH 12/27] camera in progress --- game/camera.cpp | 170 +++++++++++++++++++----------------------------- game/camera.h | 14 ++-- 2 files changed, 71 insertions(+), 113 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index e7648b7c5..a3fff23e6 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -108,8 +108,8 @@ void Camera::reset(const Npc* pl) { void Camera::save(Serialize &s) { s.write(src.range, src.target, src.spin, dst.range, dst.target, dst.spin); - s.write(cameraPos,origin,angles); - s.write(rotOffset,rotEleAz,offsetAng); + s.write(origin,angles); + s.write(rotOffset); } void Camera::load(Serialize &s, Npc* pl) { @@ -118,8 +118,8 @@ void Camera::load(Serialize &s, Npc* pl) { return; s.read(src.range, src.target, src.spin, dst.range, dst.target, dst.spin); - s.read(cameraPos,origin,angles); - s.read(rotOffset,rotEleAz,offsetAng); + s.read(origin,angles); + s.read(rotOffset); } void Camera::changeZoom(int delta) { @@ -674,11 +674,11 @@ void Camera::followSpin(Tempest::Vec3& spin, Tempest::Vec3 dest, float dtF) { #if 1 static float gvelo = 8.85f; - const zenkit::Quat sx = fromAngles(src.spin); - const zenkit::Quat dx = fromAngles(dst.spin); + const zenkit::Quat sx = fromAngles(spin); + const zenkit::Quat dx = fromAngles(dest); const zenkit::Quat s = slerp(sx, dx, dtF*gvelo); - src.spin = toAngles(s); + spin = toAngles(s); #else const zenkit::Quat d = fromAngles(dst.spin); src.spin = toAngles(d); @@ -813,54 +813,66 @@ void Camera::tickThirdPerson(float dtF) { auto rotOffsetDef = Vec3(def.rot_offset_x, def.rot_offset_y, def.rot_offset_z); + auto range = (camMod==Dialog) ? dlgDist : src.range*100.f; if(camMod==Dialog) { // TODO: DialogCams.zen src.target = dst.target; src.spin = dst.spin; rotOffset = Vec3(0); - } else { + } + + if(camMod!=Dialog) { dst.spin = clampRotation(dst.spin); - followAng(rotOffset, rotOffsetDef, dtF); - followSpin(src.spin, dst.spin, dtF); - followPos (src.target, dst.target, dtF); - //src.target = dst.target; + followAng (rotOffset, rotOffsetDef, dtF); + followSpin(src.spin, dst.spin, dtF); + //src.spin = dst.spin; } - const auto targetOffsetMat = mkRotMatrix(src.spin); - targetOffsetMat.project(targetOffset); - const auto rotOffsetMat = mkRotMatrix(src.spin); + if(camMod!=Dialog) { + rotOffsetMat.project(targetOffset); + // vanilla clamps target-offset according to collision + if(def.collision!=0) { + targetOffset = calcCameraColision(dst.target, targetOffset); + } + // and has leah-like follow for target+offset + followPos(src.target, dst.target+targetOffset, dtF); + } + auto dir = Vec3{0,0,-1}; rotOffsetMat.project(dir); - // range = calcCameraColision(camTg,origin,src.spin,range); - // origin = cameraPos - dir*range; - float range = (camMod==Dialog) ? dlgDist : src.range*100.f; - angles = src.spin - rotOffset; - + auto rotation = src.spin - rotOffset; if(true && def.collision!=0) { - // testd in marvin: collision is calculated from 'target', not from npc - range = calcCameraColision2(src.target,dir,angles,range); + // testd in marvin: collision is calculated from offseted 'target', not from npc + range = calcCameraColision(src.target,dir,rotation,range); // NOTE: with range < 80, camera gradually moves up in vanilla - if(range<80) { - range = 150; - angles.x = 90; - - const auto rotOffsetMat = mkRotMatrix(angles); + static float thresholdMax = 85.f; + static float thresholdMin = 75.f; + if(rangephysic(); - auto dview = (origin - target); - - raysCasted = 0; - float distM = dist; - for(int i=-n;i<=n;++i) - for(int r=-n;r<=n;++r) { - raysCasted++; - float u = float(i)/float(nn),v = float(r)/float(nn); - Tempest::Vec3 r1 = {u,v,depthNear}; - vinv.project(r1); - auto dr = (r1 - target); - dr = dr * (dist+padding) / (dr.length()+0.00001f); - - auto rc = physic.ray(target, target+dr); - if(!rc.hasCol) - continue; - - auto tr = (rc.v - target); - float dist1 = Vec3::dotProduct(dview,tr)/dist; + return dir; - dist1 = std::max(dist1-padding, 0); - if(dist1physic(); + const auto len = dir.length() + padding; + const auto nrm = Vec3::normalize(dir); - auto dp = Vec3::normalize(origin-target)*distM; - static float dd = 100.f; - if(dp.y>0 && dp.y
Date: Sun, 1 Feb 2026 16:52:35 +0100 Subject: [PATCH 13/27] origin interpolation + "look-at" --- game/camera.cpp | 73 ++++++++++++++++---------------- game/camera.h | 9 ++-- game/game/playercontrol.cpp | 2 +- game/graphics/worldview.cpp | 2 +- game/mainwindow.cpp | 8 ++-- game/marvin.cpp | 2 +- game/ui/dialogmenu.cpp | 2 +- game/world/triggers/cscamera.cpp | 2 +- game/world/worldobjects.cpp | 2 +- 9 files changed, 52 insertions(+), 50 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index a3fff23e6..ec1b81499 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -98,7 +98,7 @@ void Camera::reset(const Npc* pl) { dst.spin.y = pl ? pl->rotation() : 0; dst.spin += Vec3(def.best_elevation, def.best_azimuth, - 0); + def.best_rot_z); src.spin = dst.spin; @@ -205,7 +205,7 @@ void Camera::setMode(Camera::Mode m) { const auto& def = cameraDef(); auto rotBest = Vec3(def.best_elevation, def.best_azimuth, - 0); + def.best_rot_z); dst.spin += rotBest; } @@ -315,6 +315,16 @@ void Camera::setDestSpin(const PointF& p) { dst.spin.x = 90; } +void Camera::setTarget(const Tempest::Vec3& pos) { + dst.target = pos; + src.target = pos; + } + +void Camera::setDestTarget(const Tempest::Vec3& pos) { + //if(camMarvinMod!=M_Free && (camMarvinMod!=M_Freeze || camMod==Dialog)) + dst.target = pos; + } + void Camera::onRotateMouse(const PointF& dpos) { if(camMarvinMod==M_Freeze) return; @@ -598,14 +608,7 @@ void Camera::implMove(Tempest::Event::KeyType key, uint64_t dt) { } void Camera::setPosition(const Tempest::Vec3& pos) { - dst.target = pos; - src.target = dst.target; - origin = dst.target; - } - -void Camera::setDestPosition(const Tempest::Vec3& pos) { - if(camMarvinMod!=M_Free && (camMarvinMod!=M_Freeze || camMod==Dialog)) - dst.target = pos; + origin = pos; } void Camera::setDialogDistance(float d) { @@ -634,7 +637,7 @@ void Camera::followPos(Vec3& pos, Vec3 dest, float dtF) { if(inertiaTarget) { veloTrans = std::min(def.velo_trans*100, targetVelo*mul); } else { - veloTrans = def.velo_trans*100; + veloTrans = def.velo_trans; } float tr = std::min(veloTrans*dtF,len); @@ -673,7 +676,7 @@ void Camera::followSpin(Tempest::Vec3& spin, Tempest::Vec3 dest, float dtF) { } #if 1 - static float gvelo = 8.85f; + static float gvelo = 10.f; const zenkit::Quat sx = fromAngles(spin); const zenkit::Quat dx = fromAngles(dest); @@ -698,14 +701,6 @@ void Camera::followAng(float& ang, float dest, float speed, float dtF) { ang = dest; return; } - - static const float min=-90, max=90; - if(da>max+1.f) { - //shift = (da-max); - } - if(darotationRad(); @@ -1060,7 +1059,7 @@ PointF Camera::destSpin() const { return PointF(dst.spin.x,dst.spin.y); } -Vec3 Camera::destPosition() const { +Vec3 Camera::destTarget() const { return dst.target; } diff --git a/game/camera.h b/game/camera.h index a2a27fe82..c1978b081 100644 --- a/game/camera.h +++ b/game/camera.h @@ -87,14 +87,15 @@ class Camera final { Tempest::PointF spin() const; Tempest::PointF destSpin() const; - - Tempest::Vec3 destPosition() const; + Tempest::Vec3 destTarget() const; void setSpin(const Tempest::PointF& p); void setDestSpin(const Tempest::PointF& p); + void setTarget(const Tempest::Vec3& pos); + void setDestTarget(const Tempest::Vec3& pos); + void setPosition(const Tempest::Vec3& pos); - void setDestPosition(const Tempest::Vec3& pos); void setDialogDistance(float d); @@ -171,6 +172,8 @@ class Camera final { float calcCameraColision(const Tempest::Vec3& target, const Tempest::Vec3& dir, const Tempest::Vec3& rotSpin, float dist) const; Tempest::Vec3 calcCameraColision(const Tempest::Vec3& from, const Tempest::Vec3& dir) const; + Tempest::Vec3 calcLookAtAngles(const Tempest::Vec3& origin, const Tempest::Vec3& target, const Tempest::Vec3& rotOffset) const; + Tempest::Vec3 calcOffsetAngles(Tempest::Vec3 srcOrigin, Tempest::Vec3 dstOrigin, Tempest::Vec3 target) const; Tempest::Vec3 calcOffsetAngles(const Tempest::Vec3& srcOrigin, const Tempest::Vec3& target) const; diff --git a/game/game/playercontrol.cpp b/game/game/playercontrol.cpp index 1b6d1c5e1..1a0a709f6 100644 --- a/game/game/playercontrol.cpp +++ b/game/game/playercontrol.cpp @@ -1043,7 +1043,7 @@ void PlayerControl::assignRunAngle(Npc& pl, float rotation, uint64_t dt) { return; } - const float maxV = 15.0f; + const float maxV = 12.5f; dangle = std::pow(std::abs(dangle)/maxV,2.f)*maxV*sgn; float dest = 0; diff --git a/game/graphics/worldview.cpp b/game/graphics/worldview.cpp index 26e931264..6a0f03922 100644 --- a/game/graphics/worldview.cpp +++ b/game/graphics/worldview.cpp @@ -37,7 +37,7 @@ bool WorldView::isInPfxRange(const Vec3& pos) const { void WorldView::tick(uint64_t /*dt*/) { auto& cam = owner.gameSession().camera(); - pfxGroup.setViewerPos(cam.destPosition()); + pfxGroup.setViewerPos(cam.destTarget()); } void WorldView::resetRendering() { diff --git a/game/mainwindow.cpp b/game/mainwindow.cpp index 4523a95eb..f66c0a5e8 100644 --- a/game/mainwindow.cpp +++ b/game/mainwindow.cpp @@ -935,19 +935,19 @@ void MainWindow::tickCamera(uint64_t dt) { const bool fs = SystemApi::isFullscreen(hwnd()); if(!fs && mouseP[Event::ButtonLeft]) { camera.setSpin(camera.destSpin()); - camera.setDestPosition(pos); + camera.setDestTarget(pos); } else if(dialogs.isActive() && !dialogs.isMobsiDialog()) { dialogs.dialogCamera(camera); } else if(inventory.isActive()) { - camera.setDestPosition(pos); + camera.setDestTarget(pos); } else if(player.focus().npc!=nullptr && meleeFocus && pl!=nullptr) { auto spin = camera.destSpin(); spin.y = pl->rotation(); camera.setDestSpin(spin); - camera.setDestPosition(pos); + camera.setDestTarget(pos); } else if(pl!=nullptr) { auto spin = camera.destSpin(); @@ -956,7 +956,7 @@ void MainWindow::tickCamera(uint64_t dt) { if(pl->isDive() && !camera.isMarvin()) spin.x = -pl->rotationY(); camera.setDestSpin(spin); - camera.setDestPosition(pos); + camera.setDestTarget(pos); } } diff --git a/game/marvin.cpp b/game/marvin.cpp index bae8bd27a..76790806f 100644 --- a/game/marvin.cpp +++ b/game/marvin.cpp @@ -341,7 +341,7 @@ bool Marvin::exec(std::string_view v) { Npc* player = Gothic::inst().player(); if(c==nullptr || player==nullptr) return false; - auto pos = c->destPosition(); + auto pos = c->destTarget(); player->setPosition(pos.x,pos.y,pos.z); player->updateTransform(); c->reset(); diff --git a/game/ui/dialogmenu.cpp b/game/ui/dialogmenu.cpp index dfab7d113..8334db643 100644 --- a/game/ui/dialogmenu.cpp +++ b/game/ui/dialogmenu.cpp @@ -174,7 +174,7 @@ void DialogMenu::dialogCamera(Camera& camera) { if(pl && other){ auto p0 = pl ->cameraBone(); auto p1 = other->cameraBone(); - camera.setPosition((p0+p1)*0.5f + Vec3(0,50,0)); + camera.setTarget((p0+p1)*0.5f + Vec3(0,50,0)); p0 -= p1; if(pl==other) { diff --git a/game/world/triggers/cscamera.cpp b/game/world/triggers/cscamera.cpp index 0e989e9be..6f7319be1 100644 --- a/game/world/triggers/cscamera.cpp +++ b/game/world/triggers/cscamera.cpp @@ -224,7 +224,7 @@ Vec3 CsCamera::position() { PointF CsCamera::spin(Tempest::Vec3& d) { if(targetSpline.size()==0) - d = d - Gothic::inst().camera()->destPosition(); + d = d - Gothic::inst().camera()->originLwc(); else if(targetSpline.size()==1) d = targetSpline.keyframe[0].c[3] - d; else if(targetSpline.size()>1) { diff --git a/game/world/worldobjects.cpp b/game/world/worldobjects.cpp index cb3e3af5e..1c57bac6a 100644 --- a/game/world/worldobjects.cpp +++ b/game/world/worldobjects.cpp @@ -219,7 +219,7 @@ void WorldObjects::tick(uint64_t dt, uint64_t dtPlayer) { const float nearDist = 3000*3000; const float farDist = 6000*6000; - auto cpos = camera!=nullptr ? camera->destPosition() : Vec3(); + auto cpos = camera!=nullptr ? camera->originLwc() : Vec3(); auto plPos = pl!=nullptr ? pl->position() : cpos; for(auto& i:npcArr) { float dist = (i->position()-plPos).quadLength(); From 8738b37d53581e89c04e00ae600cc978c8b3c4f7 Mon Sep 17 00:00:00 2001 From: Try Date: Sun, 1 Feb 2026 16:58:43 +0100 Subject: [PATCH 14/27] fixup cutscenes --- game/camera.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index ec1b81499..cb3e30c5e 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -723,10 +723,11 @@ void Camera::tick(uint64_t dt) { switch (camMarvinMod) { case M_Normal: { if(isCutscene()) { - src.target = dst.target; - src.spin = dst.spin; - origin = src.target; - angles = src.spin; + src.target = origin; + dst.target = origin; + + angles = dst.spin; + src.spin = dst.spin; } else if(fpEnable && camMod!=Dialog) { tickFirstPerson(dtF); From 0c765f7f093af060f258e8c21089875e32e01e16 Mon Sep 17 00:00:00 2001 From: Try Date: Sun, 1 Feb 2026 21:48:51 +0100 Subject: [PATCH 15/27] camera in progress --- game/camera.cpp | 189 +++++++++++++++++--------------------------- game/camera.h | 13 ++- game/mainwindow.cpp | 4 +- 3 files changed, 82 insertions(+), 124 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index cb3e30c5e..10906b0cf 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -88,9 +88,8 @@ void Camera::reset() { void Camera::reset(const Npc* pl) { const auto& def = cameraDef(); - if(userRange<=0) { - userRange = (def.best_range - def.min_range)/(def.max_range - def.min_range); - } + + userRange = (def.best_range - def.min_range)/(def.max_range - def.min_range); dst.range = userRange*(def.max_range-def.min_range)+def.min_range; dst.target = pl ? pl->cameraBone() : Vec3(); @@ -185,7 +184,7 @@ void Camera::setMode(Camera::Mode m) { if(camMod==m) return; - const bool reset = (m==Inventory || camMod==Inventory || camMod==Dialog || camMod==Dive || m==Fall || camMod==Fall); + const bool reset = true; //(m==Inventory || camMod==Inventory || camMod==Dialog || camMod==Dive || m==Fall || camMod==Fall); camMod = m; if(camMarvinMod==M_Freeze) @@ -193,8 +192,6 @@ void Camera::setMode(Camera::Mode m) { if(reset) { resetDst(); - // auto ang = calcOffsetAngles(src.target,dst.target); - // dst.spin.x = ang.x; } if(auto pl = Gothic::inst().player()) { @@ -301,18 +298,12 @@ void Camera::toggleDebug() { } void Camera::setSpin(const PointF &p) { - dst.spin = Vec3(p.x,p.y,0); + dst.spin = angleMod(Vec3(p.x,p.y,0)); src.spin = dst.spin; } void Camera::setDestSpin(const PointF& p) { - if(camMarvinMod==M_Free || camMarvinMod==M_Freeze) - return; - dst.spin = Vec3(p.x,p.y,0); - if(dst.spin.x<-90) - dst.spin.x = -90; - if(dst.spin.x>90) - dst.spin.x = 90; + dst.spin = /*angleMod*/(Vec3(p.x,p.y,0)); } void Camera::setTarget(const Tempest::Vec3& pos) { @@ -321,13 +312,10 @@ void Camera::setTarget(const Tempest::Vec3& pos) { } void Camera::setDestTarget(const Tempest::Vec3& pos) { - //if(camMarvinMod!=M_Free && (camMarvinMod!=M_Freeze || camMod==Dialog)) dst.target = pos; } void Camera::onRotateMouse(const PointF& dpos) { - if(camMarvinMod==M_Freeze) - return; dst.spin.x += dpos.x; dst.spin.y += dpos.y; } @@ -615,35 +603,37 @@ void Camera::setDialogDistance(float d) { dlgDist = d; } -void Camera::followPos(Vec3& pos, Vec3 dest, float dtF) { - const auto& def = cameraDef(); - +Vec3 Camera::followTarget(Vec3 pos, Vec3 dest, float dtF) { auto dp = (dest-pos); auto len = dp.length(); if(dtF<=0.f) { - pos = dest; - return; + return dest; } if(len<=minLength) { - return; + return dest; } - static float mul = 2.1f; - static float mul2 = 10.f; - targetVelo = targetVelo + (len-targetVelo)*std::min(1.f,dtF*mul2); - + // no idea how it trully meant to work in vanilla game if(inertiaTarget) { - veloTrans = std::min(def.velo_trans*100, targetVelo*mul); + static float mul = 2.1f; + static float mul1 = 10.f; + + static float velo = 40.f; + targetVelo = targetVelo + (len-targetVelo)*std::min(1.f,dtF*mul1); + veloTrans = std::min(velo*100, targetVelo*mul); + + float tr = std::min(veloTrans*dtF,len); + float k = tr/len; + pos += dp*k; } else { - veloTrans = def.velo_trans; + static float mul2 = 3.f; + float tr = std::min(mul2*len*dtF, len); + float k = tr/len; + pos += dp*k; } - float tr = std::min(veloTrans*dtF,len); - float k = tr/len; - pos += dp*k; - /* { auto diff = dp*k; @@ -655,36 +645,31 @@ void Camera::followPos(Vec3& pos, Vec3 dest, float dtF) { prevSpeed = speed; } */ - } -void Camera::followCamera(Vec3& pos, Vec3 dest, float dtF) { - if(dtF<0.f) { - pos = dest; - return; - } + return pos; + } - const auto& def = cameraDef(); - if(!def.translate) - return; - pos = pos + (dest-pos)*dtF; +Tempest::Vec3 Camera::followTrans(Vec3 pos, Tempest::Vec3 dest, float dtF, float velo) { + if(dtF<0) + return dest; + static float k = 0.25f; + return pos + (dest-pos)*std::min(1.f, k*velo*dtF); } -void Camera::followSpin(Tempest::Vec3& spin, Tempest::Vec3 dest, float dtF) { - if(dtF<0.f) { - spin = dest; - return; - } +Tempest::Vec3 Camera::followRot(Vec3 spin, Tempest::Vec3 dest, float dtF, float velo) { + if(dtF<0.f) + return dest; #if 1 - static float gvelo = 10.f; + // static float gvelo = 10.f; const zenkit::Quat sx = fromAngles(spin); const zenkit::Quat dx = fromAngles(dest); - const zenkit::Quat s = slerp(sx, dx, dtF*gvelo); - spin = toAngles(s); + const zenkit::Quat s = slerp(sx, dx, std::min(1.f, dtF*velo)); + return toAngles(s); #else - const zenkit::Quat d = fromAngles(dst.spin); - src.spin = toAngles(d); + const zenkit::Quat d = fromAngles(dest); + return toAngles(d); #endif } @@ -713,11 +698,13 @@ void Camera::tick(uint64_t dt) { { const auto& def = cameraDef(); dst.range = def.min_range + (def.max_range-def.min_range)*userRange; - const float zSpeed = 5.f; - const float dz = dst.range-src.range; - src.range+=dz*std::min(1.f,2.f*zSpeed*dtF); + src.range = dst.range; } + // normalize angles in -180..180 range, for convinience + // dst.spin = angleMod(dst.spin); + // src.spin = angleMod(src.spin); + auto prev = origin; switch (camMarvinMod) { @@ -742,10 +729,9 @@ void Camera::tick(uint64_t dt) { break; } case M_Free: { - followSpin(src.spin, dst.spin, dtF); src.target = dst.target; - origin = src.target; - angles = src.spin; + origin = dst.target; + angles = followRot(angles, dst.spin, dtF, 10.f); break; } case M_Pinned: { @@ -792,7 +778,6 @@ void Camera::tickFirstPerson(float /*dtF*/) { } void Camera::tickThirdPerson(float dtF) { - //const auto pl = Gothic::inst().player(); const auto& def = cameraDef(); auto mkRotMatrix = [](Vec3 spin){ @@ -814,60 +799,55 @@ void Camera::tickThirdPerson(float dtF) { if(camMod==Dialog) { // TODO: DialogCams.zen src.target = dst.target; - src.spin = dst.spin; rotOffset = Vec3(0); } if(camMod!=Dialog) { - dst.spin = clampRotation(dst.spin); - - followAng (rotOffset, rotOffsetDef, dtF); - //followSpin(src.spin, dst.spin, dtF); - src.spin = dst.spin; + dst.spin = clampRotation(dst.spin); + rotOffset = followRot(rotOffset, rotOffsetDef, dtF, def.velo_rot); } - const auto rotOffsetMat = mkRotMatrix(src.spin); + // degenerated interpolant - TODO: remove it + src.spin = dst.spin; + + const auto rotOffsetMat = mkRotMatrix(dst.spin); if(camMod!=Dialog) { rotOffsetMat.project(targetOffset); // vanilla clamps target-offset according to collision if(def.collision!=0) { targetOffset = calcCameraColision(dst.target, targetOffset); } - // and has leah-like follow for target+offset - followPos(src.target, dst.target+targetOffset, dtF); + // and has leash-like follow for target+offset + // ignores def.translate + src.target = followTarget(src.target, dst.target+targetOffset, dtF); } auto dir = Vec3{0,0,-1}; rotOffsetMat.project(dir); if(true && def.collision!=0) { - auto rotation = calcLookAtAngles(src.target + dir*range, src.target, rotOffset); + auto rotation = calcLookAtAngles(src.target + dir*range, src.target, rotOffset, dst.spin); // testd in marvin: collision is calculated from offseted 'target', not from npc range = calcCameraColision(src.target,dir,rotation,range); // NOTE: with range < 80, camera gradually moves up in vanilla if(range<80.f) { - range = 150; - rotation.x = 90; - const auto rotOffsetMat = mkRotMatrix(rotation);//! + range = 150; // also collision?! + rotation.x = 80; + rotation.y = dst.spin.y; + const auto rotOffsetMat = mkRotMatrix(rotation); dir = Vec3{0,0,-1}; rotOffsetMat.project(dir); } } - auto followTranslation = [](Vec3 a, Vec3 b, float dtF, float velo) { - if(dtF<0) - return b; - static float k = 10.f; - return a + (b-a)*std::min(1.f, k*dtF); - }; - - origin = followTranslation(origin, src.target + dir*range, (camMod!=Dialog ? dtF : -1.f), def.velo_trans); - angles = calcLookAtAngles(origin, src.target, rotOffset); + if(def.translate!=0) + origin = followTrans(origin, src.target + dir*range, (camMod!=Dialog ? dtF : -1.f), def.velo_trans); + angles = calcLookAtAngles(origin, src.target, rotOffset, dst.spin); static bool dbg = false; if(dbg) { origin = dst.target + targetOffset + dir*range; - angles = calcLookAtAngles(origin, dst.target, rotOffset); + angles = calcLookAtAngles(origin, dst.target, rotOffset, dst.spin); } } @@ -915,11 +895,15 @@ float Camera::calcCameraColision(const Tempest::Vec3& target, const Tempest::Vec return distM; } -Vec3 Camera::calcLookAtAngles(const Tempest::Vec3& origin, const Tempest::Vec3& target, const Vec3& rotOffset) const { +Vec3 Camera::calcLookAtAngles(const Tempest::Vec3& origin, const Tempest::Vec3& target, const Vec3& rotOffset, const Vec3& defSpin) const { auto sXZ = (origin - target); - float y0 = std::atan2(sXZ.x,sXZ.z)*180.f/float(M_PI); - float x0 = std::atan2(sXZ.y,Vec2(sXZ.x,sXZ.z).length())*180.f/float(M_PI); + float lenXZ = Vec2(sXZ.x,sXZ.z).length(); + float y0 = std::atan2(sXZ.x, sXZ.z)*180.f/float(M_PI); + float x0 = std::atan2(sXZ.y, lenXZ)*180.f/float(M_PI); + + if(lenXZ < 4.f) + y0 = -defSpin.y; return Vec3(x0,-y0,0) - rotOffset; } @@ -944,7 +928,7 @@ Vec3 Camera::calcCameraColision(const Tempest::Vec3& from, const Tempest::Vec3& Vec3 Camera::clampRotation(Tempest::Vec3 spin) { //NOTE: min elevation is zero for nomal camera. assume that it's ignored by vanilla - float maxElev = +90; + float maxElev = +85; float minElev = -60; float maxAzim = +180; float minAzim = -180; @@ -953,37 +937,12 @@ Vec3 Camera::clampRotation(Tempest::Vec3 spin) { if(pl==nullptr) return spin; - Vec3 dspin = Vec3{0, pl->rotation(), 0}; - spin -= dspin; + const auto plSpin = (Vec3{0, pl->rotation(), 0}); + spin = spin - plSpin; spin.x = std::clamp(spin.x, minElev, maxElev); spin.y = std::clamp(spin.y, minAzim, maxAzim); - return spin + dspin; - } - -Vec3 Camera::calcOffsetAngles(const Vec3& origin, const Vec3& target) const { - auto sXZ = origin-target; - float y0 = std::atan2(sXZ.x,sXZ.z)*180.f/float(M_PI); - float x0 = std::atan2(sXZ.y,Vec2(sXZ.x,sXZ.z).length())*180.f/float(M_PI); - - return Vec3(x0,-y0,0); - } - -Vec3 Camera::calcOffsetAngles(Vec3 srcOrigin, Vec3 dstOrigin, Vec3 target) const { - auto src = srcOrigin-target; src.y = 0; - auto dst = dstOrigin-target; dst.y = 0; - - auto dot = Vec3::dotProduct(src,dst); - float k = 0; - if(dst.length()>minLength) { - k = dot/dst.length(); - k = std::max(0.f,std::min(k/100.f,1.f)); - } - - auto a0 = calcOffsetAngles(srcOrigin,target); - auto a1 = calcOffsetAngles(dstOrigin,target); - auto da = angleMod(a1-a0); - return da*k*offsetAngleMul; + return (spin + plSpin); } void Camera::resetDst() { diff --git a/game/camera.h b/game/camera.h index c1978b081..b0252d63b 100644 --- a/game/camera.h +++ b/game/camera.h @@ -172,10 +172,8 @@ class Camera final { float calcCameraColision(const Tempest::Vec3& target, const Tempest::Vec3& dir, const Tempest::Vec3& rotSpin, float dist) const; Tempest::Vec3 calcCameraColision(const Tempest::Vec3& from, const Tempest::Vec3& dir) const; - Tempest::Vec3 calcLookAtAngles(const Tempest::Vec3& origin, const Tempest::Vec3& target, const Tempest::Vec3& rotOffset) const; - - Tempest::Vec3 calcOffsetAngles(Tempest::Vec3 srcOrigin, Tempest::Vec3 dstOrigin, Tempest::Vec3 target) const; - Tempest::Vec3 calcOffsetAngles(const Tempest::Vec3& srcOrigin, const Tempest::Vec3& target) const; + Tempest::Vec3 calcLookAtAngles(const Tempest::Vec3& origin, const Tempest::Vec3& target, + const Tempest::Vec3& rotOffset, const Tempest::Vec3& defSpin) const; void resetDst(); @@ -187,9 +185,10 @@ class Camera final { const Tempest::Matrix4x4& viewProj, const Tempest::Vec3& lightDir, size_t layer) const; Tempest::Matrix4x4 mkViewShadowVsm(const Tempest::Vec3& cameraPos, const Tempest::Vec3& ldir) const; - void followCamera(Tempest::Vec3& pos, Tempest::Vec3 dest, float dtF); - void followPos (Tempest::Vec3& pos, Tempest::Vec3 dest, float dtF); - void followSpin (Tempest::Vec3& spin, Tempest::Vec3 dest, float dtF); + Tempest::Vec3 followTarget(Tempest::Vec3 pos, Tempest::Vec3 dest, float dtF); + Tempest::Vec3 followTrans (Tempest::Vec3 pos, Tempest::Vec3 dest, float dtF, float velo); + Tempest::Vec3 followRot (Tempest::Vec3 spin, Tempest::Vec3 dest, float dtF, float velo); + void followAng (Tempest::Vec3& spin, Tempest::Vec3 dest, float dtF); static void followAng (float& ang, float dest, float speed, float dtF); diff --git a/game/mainwindow.cpp b/game/mainwindow.cpp index f66c0a5e8..566607274 100644 --- a/game/mainwindow.cpp +++ b/game/mainwindow.cpp @@ -931,7 +931,7 @@ void MainWindow::tickCamera(uint64_t dt) { ws==WeaponState::W2H); auto pos = pl!=nullptr ? pl->cameraBone(camera.isFirstPerson()) : Vec3(); - if(!camera.isCutscene()) { + if(!camera.isCutscene() && !camera.isFree()) { const bool fs = SystemApi::isFullscreen(hwnd()); if(!fs && mouseP[Event::ButtonLeft]) { camera.setSpin(camera.destSpin()); @@ -949,7 +949,7 @@ void MainWindow::tickCamera(uint64_t dt) { camera.setDestSpin(spin); camera.setDestTarget(pos); } - else if(pl!=nullptr) { + else if(pl!=nullptr && !camera.isFree()) { auto spin = camera.destSpin(); if(pl->interactive()==nullptr && !pl->isDown()) spin.y = pl->rotation(); From 3daa2190950fe33ef8cfbe0a9bd25ba965e92933 Mon Sep 17 00:00:00 2001 From: Try Date: Mon, 2 Feb 2026 00:29:24 +0100 Subject: [PATCH 16/27] refactor --- game/camera.cpp | 204 ++++++++++++++---------------------- game/camera.h | 24 ++--- game/graphics/worldview.cpp | 2 +- game/mainwindow.cpp | 18 ++-- 4 files changed, 100 insertions(+), 148 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index 10906b0cf..192e72d74 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -24,13 +24,13 @@ static float angleMod(float a) { a-=360.f; return a; } - +/* static Vec3 angleMod(Vec3 a) { a.x = angleMod(a.x); a.y = angleMod(a.y); a.z = angleMod(a.z); return a; - } + }*/ static zenkit::Quat fromAngles(Vec3 angles) { float roll = float(angles.x*M_PI)/180.f; @@ -90,35 +90,33 @@ void Camera::reset(const Npc* pl) { const auto& def = cameraDef(); userRange = (def.best_range - def.min_range)/(def.max_range - def.min_range); - dst.range = userRange*(def.max_range-def.min_range)+def.min_range; - dst.target = pl ? pl->cameraBone() : Vec3(); - - dst.spin.x = 0; - dst.spin.y = pl ? pl->rotation() : 0; - dst.spin += Vec3(def.best_elevation, - def.best_azimuth, - def.best_rot_z); + state.range = userRange*(def.max_range-def.min_range)+def.min_range; + state.target = pl ? pl->cameraBone() : Vec3(); - src.spin = dst.spin; + state.spin.x = 0; + state.spin.y = pl ? pl->rotation() : 0; + state.spin += Vec3(def.best_elevation, + def.best_azimuth, + def.best_rot_z); tickThirdPerson(-1.f); } void Camera::save(Serialize &s) { - s.write(src.range, src.target, src.spin, - dst.range, dst.target, dst.spin); + s.write(state.range, state.spin, state.target, inter.target); s.write(origin,angles); - s.write(rotOffset); + s.write(inter.rotOffset); + + // s.write(state.range); } void Camera::load(Serialize &s, Npc* pl) { reset(pl); if(s.version()<54) return; - s.read(src.range, src.target, src.spin, - dst.range, dst.target, dst.spin); + s.read(state.range, state.spin, state.target, inter.target); s.read(origin,angles); - s.read(rotOffset); + s.read(inter.rotOffset); } void Camera::changeZoom(int delta) { @@ -195,53 +193,35 @@ void Camera::setMode(Camera::Mode m) { } if(auto pl = Gothic::inst().player()) { - dst.spin.x = 0; - dst.spin.y = pl->rotation(); + state.spin = Vec3(0, pl->rotation(), 0); } const auto& def = cameraDef(); auto rotBest = Vec3(def.best_elevation, def.best_azimuth, def.best_rot_z); - dst.spin += rotBest; + state.spin += rotBest; } void Camera::setMarvinMode(Camera::MarvinMode nextMod) { if(camMarvinMod==nextMod) return; - /* - if(camMarvinMod==M_Pinned) { - src.spin = dst.spin; - float range = src.range*100.f; - Vec3 dir = {0,0,1}; - Matrix4x4 rotOffsetMat; - rotOffsetMat.identity(); - rotOffsetMat.rotateOY(180-src.spin.y); - rotOffsetMat.rotateOX(src.spin.x); - rotOffsetMat.project(dir); - dst.target = origin +dir*range; - src.target = dst.target; - cameraPos = src.target; - rotOffset.y = 0; - } - */ - if(nextMod==M_Pinned) { const auto pl = Gothic::inst().player(); - auto offset = origin - dst.target; + auto offset = origin; Matrix4x4 rotMat = pl!=nullptr ? pl->cameraMatrix(false) : Matrix4x4::mkIdentity(); rotMat.inverse(); rotMat.project(offset); - pin.origin = offset;//origin - dst.target; + pin.origin = offset; pin.spin = angles; } else if(nextMod==M_Free) { - dst.spin = angles; - dst.target = origin; - src = dst; + state.spin = angles; + state.target = origin; + inter.target = origin; } camMarvinMod = nextMod; } @@ -298,26 +278,16 @@ void Camera::toggleDebug() { } void Camera::setSpin(const PointF &p) { - dst.spin = angleMod(Vec3(p.x,p.y,0)); - src.spin = dst.spin; - } - -void Camera::setDestSpin(const PointF& p) { - dst.spin = /*angleMod*/(Vec3(p.x,p.y,0)); + state.spin = /*angleMod*/(Vec3(p.x,p.y,0)); } void Camera::setTarget(const Tempest::Vec3& pos) { - dst.target = pos; - src.target = pos; - } - -void Camera::setDestTarget(const Tempest::Vec3& pos) { - dst.target = pos; + state.target = pos; } void Camera::onRotateMouse(const PointF& dpos) { - dst.spin.x += dpos.x; - dst.spin.y += dpos.y; + state.spin.x += dpos.x; + state.spin.y += dpos.y; } Matrix4x4 Camera::projective() const { @@ -328,11 +298,11 @@ Matrix4x4 Camera::projective() const { } Matrix4x4 Camera::viewShadowVsm(const Tempest::Vec3& ldir) const { - return mkViewShadowVsm(src.target,ldir); + return mkViewShadowVsm(inter.target,ldir); } Matrix4x4 Camera::viewShadowVsmLwc(const Tempest::Vec3& ldir) const { - return mkViewShadowVsm(src.target-origin,ldir); + return mkViewShadowVsm(inter.target-origin,ldir); } Matrix4x4 Camera::mkViewShadowVsm(const Vec3& cameraPos, const Vec3& ldir) const { @@ -379,7 +349,7 @@ Matrix4x4 Camera::viewShadow(const Vec3& lightDir, size_t layer) const { float rotation = (180+angles.y); // if(layer==0) // return viewShadowVsm(cameraPos,rotation,vp,lightDir); - return mkViewShadow(src.target,rotation,vp,lightDir,layer); + return mkViewShadow(inter.target,rotation,vp,lightDir,layer); } Matrix4x4 Camera::viewShadowLwc(const Tempest::Vec3& lightDir, size_t layer) const { @@ -387,7 +357,7 @@ Matrix4x4 Camera::viewShadowLwc(const Tempest::Vec3& lightDir, size_t layer) con float rotation = (180+angles.y); // if(layer==0) // return viewShadowVsm(cameraPos-origin,rotation,vp,lightDir); - return mkViewShadow(src.target-origin,rotation,vp,lightDir,layer); + return mkViewShadow(inter.target-origin,rotation,vp,lightDir,layer); } Matrix4x4 Camera::mkViewShadow(const Vec3& cameraPos, float rotation, const Tempest::Matrix4x4& viewProj, const Vec3& lightDir, size_t layer) const { @@ -572,27 +542,27 @@ void Camera::implMove(Tempest::Event::KeyType key, uint64_t dt) { float cx = std::cos(angles.x*k); if(key==KeyEvent::K_A) { - dst.target.x += dpos*c; - dst.target.z += dpos*s; + origin.x += dpos*c; + origin.z += dpos*s; } if(key==KeyEvent::K_D) { - dst.target.x -= dpos*c; - dst.target.z -= dpos*s; + origin.x -= dpos*c; + origin.z -= dpos*s; } if(key==KeyEvent::K_W) { - dst.target.x += dpos*s*cx; - dst.target.z -= dpos*c*cx; - dst.target.y -= dpos*sx; + origin.x += dpos*s*cx; + origin.z -= dpos*c*cx; + origin.y -= dpos*sx; } if(key==KeyEvent::K_S) { - dst.target.x -= dpos*s*cx; - dst.target.z += dpos*c*cx; - dst.target.y += dpos*sx; + origin.x -= dpos*s*cx; + origin.z += dpos*c*cx; + origin.y += dpos*sx; } if(key==KeyEvent::K_Q) - dst.spin.y += dRot; + state.spin.y += dRot; if(key==KeyEvent::K_E) - dst.spin.y -= dRot; + state.spin.y -= dRot; } void Camera::setPosition(const Tempest::Vec3& pos) { @@ -697,8 +667,7 @@ void Camera::tick(uint64_t dt) { { const auto& def = cameraDef(); - dst.range = def.min_range + (def.max_range-def.min_range)*userRange; - src.range = dst.range; + state.range = def.min_range + (def.max_range-def.min_range)*userRange; } // normalize angles in -180..180 range, for convinience @@ -710,11 +679,7 @@ void Camera::tick(uint64_t dt) { switch (camMarvinMod) { case M_Normal: { if(isCutscene()) { - src.target = origin; - dst.target = origin; - - angles = dst.spin; - src.spin = dst.spin; + // nop } else if(fpEnable && camMod!=Dialog) { tickFirstPerson(dtF); @@ -729,9 +694,7 @@ void Camera::tick(uint64_t dt) { break; } case M_Free: { - src.target = dst.target; - origin = dst.target; - angles = followRot(angles, dst.spin, dtF, 10.f); + angles = followRot(angles, state.spin, dtF, 10.f); break; } case M_Pinned: { @@ -740,7 +703,7 @@ void Camera::tick(uint64_t dt) { auto offset = pin.origin; rotMat.project(offset); - origin = dst.target + offset; + origin = offset; angles = pin.spin; break; } @@ -752,7 +715,7 @@ void Camera::tick(uint64_t dt) { auto& physic = *world->physic(); if(pl!=nullptr && !pl->isInWater()) { - inWater = physic.cameraRay(src.target, origin).waterCol % 2; + inWater = physic.cameraRay(inter.target, origin).waterCol % 2; } else { // NOTE: find a way to avoid persistent tracking inWater = inWater ^ (physic.cameraRay(prev, origin).waterCol % 2); @@ -764,17 +727,13 @@ void Camera::tickFirstPerson(float /*dtF*/) { const auto pl = Gothic::inst().player(); const auto rotMat = pl!=nullptr ? pl->cameraMatrix(true) : Matrix4x4::mkIdentity(); - //dst.spin.y = pl!=nullptr ? pl->rotation() : 0; - //dst.spin.z = 0; - - dst.spin = clampRotation(dst.spin); - src.spin = dst.spin; - src.target = dst.target; + state.spin = clampRotation(state.spin); + inter.target = state.target; Vec3 offset = {0,0,20}; rotMat.project(offset); origin = offset; - angles = dst.spin; + angles = state.spin; } void Camera::tickThirdPerson(float dtF) { @@ -794,46 +753,43 @@ void Camera::tickThirdPerson(float dtF) { auto rotOffsetDef = Vec3(def.rot_offset_x, def.rot_offset_y, def.rot_offset_z); - auto range = (camMod==Dialog) ? dlgDist : src.range*100.f; + auto range = (camMod==Dialog) ? dlgDist : state.range*100.f; if(camMod==Dialog) { // TODO: DialogCams.zen - src.target = dst.target; - rotOffset = Vec3(0); + inter.target = state.target; + inter.rotOffset = Vec3(0); } if(camMod!=Dialog) { - dst.spin = clampRotation(dst.spin); - rotOffset = followRot(rotOffset, rotOffsetDef, dtF, def.velo_rot); + state.spin = clampRotation(state.spin); + inter.rotOffset = followRot(inter.rotOffset, rotOffsetDef, dtF, def.velo_rot); } - // degenerated interpolant - TODO: remove it - src.spin = dst.spin; - - const auto rotOffsetMat = mkRotMatrix(dst.spin); + const auto rotOffsetMat = mkRotMatrix(state.spin); if(camMod!=Dialog) { rotOffsetMat.project(targetOffset); // vanilla clamps target-offset according to collision if(def.collision!=0) { - targetOffset = calcCameraColision(dst.target, targetOffset); + targetOffset = calcCameraColision(state.target, targetOffset); } // and has leash-like follow for target+offset // ignores def.translate - src.target = followTarget(src.target, dst.target+targetOffset, dtF); + inter.target = followTarget(inter.target, state.target+targetOffset, dtF); } auto dir = Vec3{0,0,-1}; rotOffsetMat.project(dir); if(true && def.collision!=0) { - auto rotation = calcLookAtAngles(src.target + dir*range, src.target, rotOffset, dst.spin); + auto rotation = calcLookAtAngles(inter.target + dir*range, inter.target, inter.rotOffset, state.spin); // testd in marvin: collision is calculated from offseted 'target', not from npc - range = calcCameraColision(src.target,dir,rotation,range); + range = calcCameraColision(inter.target, dir, rotation, range); // NOTE: with range < 80, camera gradually moves up in vanilla if(range<80.f) { range = 150; // also collision?! rotation.x = 80; - rotation.y = dst.spin.y; + rotation.y = state.spin.y; const auto rotOffsetMat = mkRotMatrix(rotation); dir = Vec3{0,0,-1}; rotOffsetMat.project(dir); @@ -841,13 +797,13 @@ void Camera::tickThirdPerson(float dtF) { } if(def.translate!=0) - origin = followTrans(origin, src.target + dir*range, (camMod!=Dialog ? dtF : -1.f), def.velo_trans); - angles = calcLookAtAngles(origin, src.target, rotOffset, dst.spin); + origin = followTrans(origin, inter.target + dir*range, (camMod!=Dialog ? dtF : -1.f), def.velo_trans); + angles = calcLookAtAngles(origin, inter.target, inter.rotOffset, state.spin); static bool dbg = false; if(dbg) { - origin = dst.target + targetOffset + dir*range; - angles = calcLookAtAngles(origin, dst.target, rotOffset, dst.spin); + origin = state.target + targetOffset + dir*range; + angles = calcLookAtAngles(origin, state.target, inter.rotOffset, state.spin); } } @@ -949,9 +905,11 @@ void Camera::resetDst() { if(isMarvin()) return; const auto& def = cameraDef(); - // dst.spin.x = def.best_elevation; - dst.range = def.best_range; - dst.spin = Vec3(0); + + state.range = def.best_range; + state.spin = Vec3(0); + + //userRange = (def.best_range - def.min_range)/(def.max_range - def.min_range); } Matrix4x4 Camera::mkView(const Vec3& pos, const Vec3& spin) const { @@ -980,14 +938,14 @@ void Camera::debugDraw(DbgPainter& p) { return; p.setPen(Color(0,1,0)); - p.drawLine(dst.target, src.target); - p.drawLine(src.target, origin); + p.drawLine(state.target, inter.target); + p.drawLine(inter.target, origin); if(auto pl = Gothic::inst().player()) { float a = pl->rotationRad(); float c = std::cos(a), s = std::sin(a); auto ln = Vec3(c,0,s)*25.f; - p.drawLine(src.target-ln, src.target+ln); + p.drawLine(inter.target-ln, inter.target+ln); } auto& fnt = Resources::font(1.0); @@ -996,31 +954,27 @@ void Camera::debugDraw(DbgPainter& p) { string_frm buf("RaysCasted: ",raysCasted); p.drawText(8,y,buf); y += fnt.pixelSize(); - buf = string_frm("PlayerPos : ",dst.target.x, ' ', dst.target.y, ' ', dst.target.z); + buf = string_frm("PlayerPos : ",state.target.x, ' ', state.target.y, ' ', state.target.z); p.drawText(8,y,buf); y += fnt.pixelSize(); buf = string_frm("targetVelo : ",targetVelo); p.drawText(8,y,buf); y += fnt.pixelSize()*4; - buf = string_frm("Range To Player : ", (src.target-origin).length()); + buf = string_frm("Range To Player : ", (inter.target-origin).length()); p.drawText(8,y,buf); y += fnt.pixelSize(); - buf = string_frm("Azimuth : ", angleMod(dst.spin.y-angles.y)); + buf = string_frm("Azimuth : ", angleMod(state.spin.y-angles.y)); p.drawText(8,y,buf); y += fnt.pixelSize(); - buf = string_frm("Elevation : ", rotOffset.x+angles.x); + buf = string_frm("Elevation : ", inter.rotOffset.x+angles.x); p.drawText(8,y,buf); y += fnt.pixelSize(); } PointF Camera::spin() const { - return PointF(src.spin.x,src.spin.y); - } - -PointF Camera::destSpin() const { - return PointF(dst.spin.x,dst.spin.y); + return PointF(state.spin.x,state.spin.y); } Vec3 Camera::destTarget() const { - return dst.target; + return state.target; } Matrix4x4 Camera::viewProj() const { diff --git a/game/camera.h b/game/camera.h index b0252d63b..02d98e33a 100644 --- a/game/camera.h +++ b/game/camera.h @@ -85,18 +85,12 @@ class Camera final { void tick(uint64_t dt); void debugDraw(DbgPainter& p); - Tempest::PointF spin() const; - Tempest::PointF destSpin() const; + Tempest::PointF spin() const; Tempest::Vec3 destTarget() const; void setSpin(const Tempest::PointF& p); - void setDestSpin(const Tempest::PointF& p); - void setTarget(const Tempest::Vec3& pos); - void setDestTarget(const Tempest::Vec3& pos); - void setPosition(const Tempest::Vec3& pos); - void setDialogDistance(float d); void onRotateMouse(const Tempest::PointF& dpos); @@ -120,23 +114,27 @@ class Camera final { float zFar() const; private: + struct Pin { + Tempest::Vec3 origin = {}; + Tempest::Vec3 spin = {}; + }; + struct State { float range = 3.f; Tempest::Vec3 spin = {}; Tempest::Vec3 target = {}; }; - struct Pin { - Tempest::Vec3 origin = {}; - Tempest::Vec3 spin = {}; + struct Interpolated { + Tempest::Vec3 target = {}; + Tempest::Vec3 rotOffset = {}; }; Tempest::Vec3 origin = {}; Tempest::Vec3 angles = {}; - Tempest::Vec3 rotOffset = {}; - - State src, dst; + State state; + Interpolated inter; Pin pin; float dlgDist = 0; diff --git a/game/graphics/worldview.cpp b/game/graphics/worldview.cpp index 6a0f03922..730ba2fff 100644 --- a/game/graphics/worldview.cpp +++ b/game/graphics/worldview.cpp @@ -37,7 +37,7 @@ bool WorldView::isInPfxRange(const Vec3& pos) const { void WorldView::tick(uint64_t /*dt*/) { auto& cam = owner.gameSession().camera(); - pfxGroup.setViewerPos(cam.destTarget()); + pfxGroup.setViewerPos(cam.originLwc()); } void WorldView::resetRendering() { diff --git a/game/mainwindow.cpp b/game/mainwindow.cpp index 566607274..ec6f02e11 100644 --- a/game/mainwindow.cpp +++ b/game/mainwindow.cpp @@ -934,29 +934,29 @@ void MainWindow::tickCamera(uint64_t dt) { if(!camera.isCutscene() && !camera.isFree()) { const bool fs = SystemApi::isFullscreen(hwnd()); if(!fs && mouseP[Event::ButtonLeft]) { - camera.setSpin(camera.destSpin()); - camera.setDestTarget(pos); + camera.setSpin(camera.spin()); + camera.setTarget(pos); } else if(dialogs.isActive() && !dialogs.isMobsiDialog()) { dialogs.dialogCamera(camera); } else if(inventory.isActive()) { - camera.setDestTarget(pos); + camera.setTarget(pos); } else if(player.focus().npc!=nullptr && meleeFocus && pl!=nullptr) { - auto spin = camera.destSpin(); + auto spin = camera.spin(); spin.y = pl->rotation(); - camera.setDestSpin(spin); - camera.setDestTarget(pos); + camera.setSpin(spin); + camera.setTarget(pos); } else if(pl!=nullptr && !camera.isFree()) { - auto spin = camera.destSpin(); + auto spin = camera.spin(); if(pl->interactive()==nullptr && !pl->isDown()) spin.y = pl->rotation(); if(pl->isDive() && !camera.isMarvin()) spin.x = -pl->rotationY(); - camera.setDestSpin(spin); - camera.setDestTarget(pos); + camera.setSpin(spin); + camera.setTarget(pos); } } From 67a7221da559a553632da22a183126a815bb1404 Mon Sep 17 00:00:00 2001 From: Try Date: Mon, 2 Feb 2026 22:11:39 +0100 Subject: [PATCH 17/27] fixup cscamera --- game/camera.cpp | 67 ++++++++++++++++++-------------- game/camera.h | 6 +-- game/world/triggers/cscamera.cpp | 7 ++-- 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index 192e72d74..be88a4936 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -178,25 +178,41 @@ void Camera::moveRight(uint64_t dt) { implMove(KeyEvent::K_D,dt); } -void Camera::setMode(Camera::Mode m) { +void Camera::setMode(const Camera::Mode m) { if(camMod==m) return; - const bool reset = true; //(m==Inventory || camMod==Inventory || camMod==Dialog || camMod==Dive || m==Fall || camMod==Fall); + auto isRegular = [](const Camera::Mode m){ + return m==Normal || m==Inventory || m==Melee || m==Ranged || m==Magic; + }; + + const auto prev = camMod; camMod = m; - if(camMarvinMod==M_Freeze) + //const bool reset = true; //(m==Inventory || camMod==Inventory || camMod==Dialog || camMod==Dive || m==Fall || camMod==Fall); + const bool reset = !(isRegular(prev) && isRegular(m)); + + if(prev==Mode::Cutscene) { + state.spin = angles; + state.target = origin; + inter.target = origin; + } + + if(camMarvinMod==M_Free || camMarvinMod==M_Freeze) return; + const auto& def = cameraDef(); + if(reset) { - resetDst(); + state.range = def.best_range; + state.spin = Vec3(0); + //userRange = (def.best_range - def.min_range)/(def.max_range - def.min_range); } if(auto pl = Gothic::inst().player()) { state.spin = Vec3(0, pl->rotation(), 0); } - const auto& def = cameraDef(); auto rotBest = Vec3(def.best_elevation, def.best_azimuth, def.best_rot_z); @@ -270,7 +286,6 @@ void Camera::setLookBack(bool lb) { if(lbEnable==lb) return; lbEnable = lb; - resetDst(); } void Camera::toggleDebug() { @@ -285,6 +300,18 @@ void Camera::setTarget(const Tempest::Vec3& pos) { state.target = pos; } +void Camera::setAngles(const Tempest::PointF& p) { + angles = /*angleMod*/(Vec3(p.x,p.y,0)); + } + +void Camera::setPosition(const Tempest::Vec3& pos) { + origin = pos; + } + +void Camera::setDialogDistance(float d) { + dlgDist = d; + } + void Camera::onRotateMouse(const PointF& dpos) { state.spin.x += dpos.x; state.spin.y += dpos.y; @@ -565,14 +592,6 @@ void Camera::implMove(Tempest::Event::KeyType key, uint64_t dt) { state.spin.y -= dRot; } -void Camera::setPosition(const Tempest::Vec3& pos) { - origin = pos; - } - -void Camera::setDialogDistance(float d) { - dlgDist = d; - } - Vec3 Camera::followTarget(Vec3 pos, Vec3 dest, float dtF) { auto dp = (dest-pos); auto len = dp.length(); @@ -663,6 +682,10 @@ void Camera::tick(uint64_t dt) { if(Gothic::inst().isPause() || (camMarvinMod==M_Freeze && camMod!=Dialog)) return; + if(isCutscene()) { + return; // handle pass thru water ? + } + const float dtF = float(dt)/1000.f; { @@ -678,10 +701,7 @@ void Camera::tick(uint64_t dt) { switch (camMarvinMod) { case M_Normal: { - if(isCutscene()) { - // nop - } - else if(fpEnable && camMod!=Dialog) { + if(fpEnable && camMod!=Dialog) { tickFirstPerson(dtF); } else { @@ -901,17 +921,6 @@ Vec3 Camera::clampRotation(Tempest::Vec3 spin) { return (spin + plSpin); } -void Camera::resetDst() { - if(isMarvin()) - return; - const auto& def = cameraDef(); - - state.range = def.best_range; - state.spin = Vec3(0); - - //userRange = (def.best_range - def.min_range)/(def.max_range - def.min_range); - } - Matrix4x4 Camera::mkView(const Vec3& pos, const Vec3& spin) const { Matrix4x4 view; view.identity(); diff --git a/game/camera.h b/game/camera.h index 02d98e33a..a85b901cc 100644 --- a/game/camera.h +++ b/game/camera.h @@ -62,7 +62,7 @@ class Camera final { void moveLeft(uint64_t dt); void moveRight(uint64_t dt); - void setMode(Mode m); + void setMode(const Mode m); void setMarvinMode(MarvinMode m); bool isMarvin() const; bool isFree() const; @@ -90,6 +90,8 @@ class Camera final { void setSpin(const Tempest::PointF& p); void setTarget(const Tempest::Vec3& pos); + + void setAngles(const Tempest::PointF& pos); void setPosition(const Tempest::Vec3& pos); void setDialogDistance(float d); @@ -173,8 +175,6 @@ class Camera final { Tempest::Vec3 calcLookAtAngles(const Tempest::Vec3& origin, const Tempest::Vec3& target, const Tempest::Vec3& rotOffset, const Tempest::Vec3& defSpin) const; - void resetDst(); - void implMove(Tempest::KeyEvent::KeyType t, uint64_t dt); Tempest::Matrix4x4 mkView (const Tempest::Vec3& pos, const Tempest::Vec3& spin) const; diff --git a/game/world/triggers/cscamera.cpp b/game/world/triggers/cscamera.cpp index 6f7319be1..368c31aa3 100644 --- a/game/world/triggers/cscamera.cpp +++ b/game/world/triggers/cscamera.cpp @@ -166,7 +166,7 @@ void CsCamera::onTrigger(const TriggerEvent& evt) { auto cPos = position(); camera.setMode(Camera::Mode::Cutscene); camera.setPosition(cPos); - camera.setSpin(spin(cPos)); + camera.setAngles(spin(cPos)); } } @@ -207,7 +207,7 @@ void CsCamera::tick(uint64_t /*dt*/) { if(camera.isCutscene()) { auto cPos = position(); camera.setPosition(cPos); - camera.setSpin(spin(cPos)); + camera.setAngles(spin(cPos)); } } @@ -238,6 +238,5 @@ PointF CsCamera::spin(Tempest::Vec3& d) { if(d.x!=0.f || d.z!=0.f) spinY = 90 + k * std::atan2(d.z,d.x); - auto& def = Gothic::cameraDef().stdCam(); - return {-spinX + def.rot_offset_x, spinY + def.rot_offset_y}; + return {-spinX, spinY}; } From 04cf89934e5fbf8cc0aa3eab50d8d3dbd0cdd5fd Mon Sep 17 00:00:00 2001 From: Try Date: Mon, 2 Feb 2026 22:55:54 +0100 Subject: [PATCH 18/27] followTrans intrumentation --- game/camera.cpp | 12 +++++++++++- game/camera.h | 4 +++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index be88a4936..cad1f87b5 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -1,5 +1,6 @@ #include "camera.h" +#include #include #include "world/objects/npc.h" @@ -641,6 +642,16 @@ Vec3 Camera::followTarget(Vec3 pos, Vec3 dest, float dtF) { Tempest::Vec3 Camera::followTrans(Vec3 pos, Tempest::Vec3 dest, float dtF, float velo) { if(dtF<0) return dest; + + /* + static uint64_t time = 0; + if((dest-pos).length()<1 || velo==0) { + auto tx = Tempest::Application::tickCount(); + if(velo>0) + Log::d("time = ", tx-time); + time = tx; + } + */ static float k = 0.25f; return pos + (dest-pos)*std::min(1.f, k*velo*dtF); } @@ -650,7 +661,6 @@ Tempest::Vec3 Camera::followRot(Vec3 spin, Tempest::Vec3 dest, float dtF, float return dest; #if 1 - // static float gvelo = 10.f; const zenkit::Quat sx = fromAngles(spin); const zenkit::Quat dx = fromAngles(dest); diff --git a/game/camera.h b/game/camera.h index a85b901cc..13d6bd571 100644 --- a/game/camera.h +++ b/game/camera.h @@ -139,10 +139,12 @@ class Camera final { Interpolated inter; Pin pin; + float veloTrans = 0; + Tempest::Vec3 veloTrans3 = {}; + float dlgDist = 0; float userRange = 0; float targetVelo = 0; - float veloTrans = 0; Tempest::Matrix4x4 proj; uint32_t vpWidth=0; From 9f3481894350777e900e4be2d7bfc801f382bd44 Mon Sep 17 00:00:00 2001 From: Try Date: Tue, 3 Feb 2026 00:12:40 +0100 Subject: [PATCH 19/27] inertiaTarget in progress --- game/camera.cpp | 22 ++++++++++++++++++++++ game/camera.h | 5 ++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index cad1f87b5..d7582d8b3 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -606,6 +606,22 @@ Vec3 Camera::followTarget(Vec3 pos, Vec3 dest, float dtF) { } // no idea how it trully meant to work in vanilla game + if(inertiaTarget) { + static float mul11 = 8.f; + static float mul12 = 2.f; + + veloTrans = veloTrans+(dp-veloTrans)*std::min(1.f, mul11*dtF); + pos += veloTrans*std::min(1.f, mul12*dtF); + return pos; + } else { + static float mul21 = 3.f; + + veloTrans = dp; + pos += veloTrans*std::min(1.f, mul21*dtF); + return pos; + } + + /* if(inertiaTarget) { static float mul = 2.1f; static float mul1 = 10.f; @@ -623,6 +639,7 @@ Vec3 Camera::followTarget(Vec3 pos, Vec3 dest, float dtF) { float k = tr/len; pos += dp*k; } + */ /* { @@ -739,6 +756,11 @@ void Camera::tick(uint64_t dt) { } } + { + //NOTE: in vanilla output of velocity is garbage in general, but zero for static camera + targetVelo = (origin - prev).length()/dtF; + } + auto world = Gothic::inst().world(); if(world!=nullptr) { auto pl = isFree() ? nullptr : world->player(); diff --git a/game/camera.h b/game/camera.h index 13d6bd571..7390f9d40 100644 --- a/game/camera.h +++ b/game/camera.h @@ -139,12 +139,11 @@ class Camera final { Interpolated inter; Pin pin; - float veloTrans = 0; - Tempest::Vec3 veloTrans3 = {}; + Tempest::Vec3 veloTrans = {}; + float targetVelo = 0; float dlgDist = 0; float userRange = 0; - float targetVelo = 0; Tempest::Matrix4x4 proj; uint32_t vpWidth=0; From 5fe2ec0c14e5261153625e401c2814249843fc58 Mon Sep 17 00:00:00 2001 From: Try Date: Tue, 3 Feb 2026 00:38:57 +0100 Subject: [PATCH 20/27] serialization --- game/camera.cpp | 33 +++++++++++++++------------------ game/camera.h | 12 ++++++------ 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index d7582d8b3..355693a6d 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -104,20 +104,20 @@ void Camera::reset(const Npc* pl) { } void Camera::save(Serialize &s) { - s.write(state.range, state.spin, state.target, inter.target); - s.write(origin,angles); - s.write(inter.rotOffset); - - // s.write(state.range); + s.write(state.range, state.spin, state.target); + s.write(inter.target, inter.rotOffset); + s.write(origin,angles,veloTrans); + s.write(userRange); } void Camera::load(Serialize &s, Npc* pl) { reset(pl); if(s.version()<54) return; - s.read(state.range, state.spin, state.target, inter.target); - s.read(origin,angles); - s.read(inter.rotOffset); + s.read(state.range, state.spin, state.target); + s.read(inter.target, inter.rotOffset); + s.read(origin,angles,veloTrans); + s.read(userRange); } void Camera::changeZoom(int delta) { @@ -187,18 +187,15 @@ void Camera::setMode(const Camera::Mode m) { return m==Normal || m==Inventory || m==Melee || m==Ranged || m==Magic; }; - const auto prev = camMod; - camMod = m; - - //const bool reset = true; //(m==Inventory || camMod==Inventory || camMod==Dialog || camMod==Dive || m==Fall || camMod==Fall); - const bool reset = !(isRegular(prev) && isRegular(m)); - - if(prev==Mode::Cutscene) { + const bool reset = !(isRegular(camMod) && isRegular(m)); + if(camMod==Mode::Cutscene) { state.spin = angles; state.target = origin; inter.target = origin; } + camMod = m; + if(camMarvinMod==M_Free || camMarvinMod==M_Freeze) return; @@ -206,8 +203,8 @@ void Camera::setMode(const Camera::Mode m) { if(reset) { state.range = def.best_range; + userRange = (def.best_range - def.min_range)/(def.max_range - def.min_range); state.spin = Vec3(0); - //userRange = (def.best_range - def.min_range)/(def.max_range - def.min_range); } if(auto pl = Gothic::inst().player()) { @@ -310,7 +307,7 @@ void Camera::setPosition(const Tempest::Vec3& pos) { } void Camera::setDialogDistance(float d) { - dlgDist = d; + dlgRange = d; } void Camera::onRotateMouse(const PointF& dpos) { @@ -805,7 +802,7 @@ void Camera::tickThirdPerson(float dtF) { auto rotOffsetDef = Vec3(def.rot_offset_x, def.rot_offset_y, def.rot_offset_z); - auto range = (camMod==Dialog) ? dlgDist : state.range*100.f; + auto range = (camMod==Dialog) ? dlgRange : state.range*100.f; if(camMod==Dialog) { // TODO: DialogCams.zen diff --git a/game/camera.h b/game/camera.h index 7390f9d40..dcf7e8596 100644 --- a/game/camera.h +++ b/game/camera.h @@ -132,18 +132,18 @@ class Camera final { Tempest::Vec3 rotOffset = {}; }; - Tempest::Vec3 origin = {}; - Tempest::Vec3 angles = {}; - State state; Interpolated inter; Pin pin; - Tempest::Vec3 veloTrans = {}; + Tempest::Vec3 origin = {}; + Tempest::Vec3 angles = {}; + Tempest::Vec3 veloTrans = {}; + float userRange = 0; + float targetVelo = 0; - float dlgDist = 0; - float userRange = 0; + float dlgRange = 0; Tempest::Matrix4x4 proj; uint32_t vpWidth=0; From 37476b607384e5d49e4a71567242df0b4f4af12b Mon Sep 17 00:00:00 2001 From: Try Date: Tue, 3 Feb 2026 19:34:05 +0100 Subject: [PATCH 21/27] tie run angle to camera azimuth --- game/camera.cpp | 4 ++++ game/camera.h | 1 + game/game/playercontrol.cpp | 28 ++++++++-------------------- game/game/playercontrol.h | 1 - 4 files changed, 13 insertions(+), 21 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index 355693a6d..2c6bef896 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -1007,6 +1007,10 @@ void Camera::debugDraw(DbgPainter& p) { p.drawText(8,y,buf); y += fnt.pixelSize(); } +float Camera::azimuth() const { + return angleMod(state.spin.y-angles.y); + } + PointF Camera::spin() const { return PointF(state.spin.x,state.spin.y); } diff --git a/game/camera.h b/game/camera.h index dcf7e8596..c44c94573 100644 --- a/game/camera.h +++ b/game/camera.h @@ -87,6 +87,7 @@ class Camera final { Tempest::PointF spin() const; Tempest::Vec3 destTarget() const; + float azimuth() const; void setSpin(const Tempest::PointF& p); void setTarget(const Tempest::Vec3& pos); diff --git a/game/game/playercontrol.cpp b/game/game/playercontrol.cpp index 1a0a709f6..2ebda6da9 100644 --- a/game/game/playercontrol.cpp +++ b/game/game/playercontrol.cpp @@ -1032,29 +1032,17 @@ void PlayerControl::quitPicklock(Npc& pl) { void PlayerControl::assignRunAngle(Npc& pl, float rotation, uint64_t dt) { float dtF = (float(dt)/1000.f); - float angle = pl.rotation(); - float dangle = (rotation-angle)/dtF; - float sgn = (dangle>0 ? 1 : -1); - auto& wrld = pl.world(); + auto camera = Gothic::inst().camera(); - if(std::fabs(dangle)<0.1f || pl.walkMode()!=WalkBit::WM_Run) { - if(runAngleSmoothazimuth(); + const float maxV = 14.5f; + dest = std::min(std::abs(az), maxV)*(az>=0 ? 1 : -1); } - const float maxV = 12.5f; - dangle = std::pow(std::abs(dangle)/maxV,2.f)*maxV*sgn; - - float dest = 0; - if(anglerotation) - dest = -std::min(-dangle,maxV); - - float a = std::clamp(dtF*1.5f, 0.f, 1.f); - runAngleDest = runAngleDest*(1.f-a)+dest*a; - runAngleSmooth = wrld.tickCount() + 200; + float a = std::min(dtF*5.f, 1.f); + runAngleDest = runAngleDest*(1.f-a)+dest*a; } void PlayerControl::setAnimRotate(Npc& pl, float rotation, int anim, bool force, uint64_t dt) { diff --git a/game/game/playercontrol.h b/game/game/playercontrol.h index 048fd700d..3b6106263 100644 --- a/game/game/playercontrol.h +++ b/game/game/playercontrol.h @@ -146,7 +146,6 @@ class PlayerControl final { size_t pickLockProgress = 0; float runAngleDest = 0.f; - uint64_t runAngleSmooth = 0; uint64_t turnAniSmooth = 0; int rotationAni = 0; bool g2Ctrl = false; From 6dff2a770645b985ae7842b1b0669ff324a3fc20 Mon Sep 17 00:00:00 2001 From: Try Date: Tue, 3 Feb 2026 20:59:48 +0100 Subject: [PATCH 22/27] cleanups --- game/game/playercontrol.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/game/game/playercontrol.cpp b/game/game/playercontrol.cpp index 2ebda6da9..1c835a942 100644 --- a/game/game/playercontrol.cpp +++ b/game/game/playercontrol.cpp @@ -559,8 +559,6 @@ bool PlayerControl::tickMove(uint64_t dt) { if(pl==nullptr) return true; - // static const float speedRotX = 750.f; - // rotMouse = std::min(std::abs(rotMouse), speedRotX*dtF) * (rotMouse>=0 ? 1 : -1); implMove(dt); float runAngle = pl->runAngle(); @@ -660,8 +658,7 @@ void PlayerControl::implMove(uint64_t dt) { return; } - //float rotCam = 0; - int rotation = 0; + int rotation = 0; if(allowRot) { if(this->wantsToTurnLeft()) { rot += rspeed; @@ -677,8 +674,7 @@ void PlayerControl::implMove(uint64_t dt) { if(rotMouse>0) rotation = -1; else rotation = 1; - rot +=rotMouse; - //rotCam += rotMouse; + rot += rotMouse; rotMouse = 0; } rotY+=rotMouseY; From 13bc26613174bff0a3648197194678a98db79688 Mon Sep 17 00:00:00 2001 From: Try Date: Tue, 3 Feb 2026 22:46:33 +0100 Subject: [PATCH 23/27] cleanup --- game/game/playercontrol.cpp | 3 +-- game/mainwindow.cpp | 7 ------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/game/game/playercontrol.cpp b/game/game/playercontrol.cpp index 1c835a942..824b6002d 100644 --- a/game/game/playercontrol.cpp +++ b/game/game/playercontrol.cpp @@ -248,7 +248,6 @@ bool PlayerControl::isPressed(KeyCodec::Action a) const { } void PlayerControl::onRotateMouse(float dAngleX, float dAngleY) { - // dAngleY = std::clamp(dAngleY, -100.f, 100.f); rotMouse += dAngleX; rotMouseY += dAngleY; } @@ -1047,7 +1046,7 @@ void PlayerControl::setAnimRotate(Npc& pl, float rotation, int anim, bool force, float dangle = (rotation-angle)/dtF; auto& wrld = pl.world(); - if(std::fabs(dangle)<30.f && !force) // 100 deg per second threshold + if(std::fabs(dangle)<30.f && !force) // 30 deg per second threshold anim = 0; if(anim!=0 && pl.isAttackAnim()) anim = 0; diff --git a/game/mainwindow.cpp b/game/mainwindow.cpp index ec6f02e11..ead2b7762 100644 --- a/game/mainwindow.cpp +++ b/game/mainwindow.cpp @@ -339,12 +339,6 @@ void MainWindow::tickMouse(uint64_t dt) { return; } - static bool once = false; - if(!once) { - once = true; - Log::d("mouse sensitivity = ", MouseUtil::mouseSysSpeed()); - } - if(dMouse==Point()) return; @@ -892,7 +886,6 @@ uint64_t MainWindow::tick() { else if(runtimeMode==R_Suspended) { auto camera = Gothic::inst().camera(); if(camera!=nullptr && camera->isFree()) { - //player.tickCameraMove(dt); tickMouse(dt); } update(); From 12456ff8dc01822ea55403236461ded62f4685ba Mon Sep 17 00:00:00 2001 From: Try Date: Tue, 3 Feb 2026 22:56:42 +0100 Subject: [PATCH 24/27] cleanup --- game/camera.cpp | 25 +------------------------ game/camera.h | 3 --- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index 2c6bef896..bf13f951c 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -75,10 +75,7 @@ static Vec3 toAngles(zenkit::Quat q) { return Vec3(roll,yaw,pitch)*float(180.f/M_PI); } -float Camera::maxDist = 150; -float Camera::baseSpeeed = 200; -float Camera::offsetAngleMul = 0.1f; -const float Camera::minLength = 0.0001f; +const float Camera::minLength = 0.0001f; Camera::Camera() { } @@ -618,26 +615,6 @@ Vec3 Camera::followTarget(Vec3 pos, Vec3 dest, float dtF) { return pos; } - /* - if(inertiaTarget) { - static float mul = 2.1f; - static float mul1 = 10.f; - - static float velo = 40.f; - targetVelo = targetVelo + (len-targetVelo)*std::min(1.f,dtF*mul1); - veloTrans = std::min(velo*100, targetVelo*mul); - - float tr = std::min(veloTrans*dtF,len); - float k = tr/len; - pos += dp*k; - } else { - static float mul2 = 3.f; - float tr = std::min(mul2*len*dtF, len); - float k = tr/len; - pos += dp*k; - } - */ - /* { auto diff = dp*k; diff --git a/game/camera.h b/game/camera.h index c44c94573..28e45e5c6 100644 --- a/game/camera.h +++ b/game/camera.h @@ -162,9 +162,6 @@ class Camera final { mutable int raysCasted = 0; - static float maxDist; - static float baseSpeeed; - static float offsetAngleMul; static const float minLength; void tickFirstPerson(float dtF); From 8060a191ac1b66e03b2670f57abca1b62a966e0b Mon Sep 17 00:00:00 2001 From: Try Date: Tue, 3 Feb 2026 23:31:54 +0100 Subject: [PATCH 25/27] initial code for reading dialog-cameras --- game/game/definitions/cameradefinitions.cpp | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/game/game/definitions/cameradefinitions.cpp b/game/game/definitions/cameradefinitions.cpp index f082014b0..e448bdc68 100644 --- a/game/game/definitions/cameradefinitions.cpp +++ b/game/game/definitions/cameradefinitions.cpp @@ -1,8 +1,13 @@ #include "cameradefinitions.h" +#include +#include + #include "gothic.h" #include "utils/string_frm.h" +using namespace Tempest; + CameraDefinitions::CameraDefinitions() { auto vm = Gothic::inst().createPhoenixVm("Camera.dat"); @@ -28,6 +33,28 @@ CameraDefinitions::CameraDefinitions() { camModSwim = getCam("CAMMODSWIM"); camModDive = getCam("CAMMODDIVE"); camModFall = getCam("CAMMODFALL"); + + // dialog presets: unused yet + std::vector cameras; + try { + std::unique_ptr read; + auto zen = Resources::openReader("DialogCams.ZEN", read); + + while(!read->eof()) { + zenkit::ArchiveObject obj {}; + zenkit::VCutsceneCamera preset {}; + zen->read_object_begin(obj); + preset.load(*zen, Gothic::inst().version().game == 1 ? zenkit::GameVersion::GOTHIC_1 + : zenkit::GameVersion::GOTHIC_2); + cameras.emplace_back(std::move(preset)); + if(!zen->read_object_end()) { + zen->skip_object(true); + } + } + } + catch(...) { + Log::e("unable to load Zen-file: \"DialogCams.ZEN\""); + } } const zenkit::ICamera& CameraDefinitions::mobsiCam(std::string_view tag, std::string_view pos) const { From 394d36514906c91a5f5de1b460318acc9cc15fb2 Mon Sep 17 00:00:00 2001 From: Try Date: Tue, 3 Feb 2026 23:32:47 +0100 Subject: [PATCH 26/27] [R camera] check sign of focus-projection --- game/mainwindow.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/game/mainwindow.cpp b/game/mainwindow.cpp index ead2b7762..2f7edc92b 100644 --- a/game/mainwindow.cpp +++ b/game/mainwindow.cpp @@ -561,8 +561,14 @@ void MainWindow::paintFocus(Painter& p, const Focus& focus, const Matrix4x4& vp) if(pl==nullptr) return; + auto pw = 1.f; auto pos = focus.displayPosition(); - vp.project(pos.x,pos.y,pos.z); + vp.project(pos.x,pos.y,pos.z,pw); + + if(pw<=0.f) + return; + + pos /= pw; int ix = int((0.5f*pos.x+0.5f)*float(w())); int iy = int((0.5f*pos.y+0.5f)*float(h())); From 8576c0d17998820ada70f0af6f752caa72774298 Mon Sep 17 00:00:00 2001 From: Try Date: Wed, 4 Feb 2026 00:38:13 +0100 Subject: [PATCH 27/27] improve first-person camera --- game/camera.cpp | 15 +++++++++++---- game/camera.h | 1 + game/mainwindow.cpp | 16 +++++++++------- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index bf13f951c..4c2a13854 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -196,6 +196,13 @@ void Camera::setMode(const Camera::Mode m) { if(camMarvinMod==M_Free || camMarvinMod==M_Freeze) return; + if(camMod==Camera::FirstPerson) { + if(auto pl = Gothic::inst().player()) { + state.spin = Vec3(0, pl->rotation(), 0); + } + return; + } + const auto& def = cameraDef(); if(reset) { @@ -208,9 +215,9 @@ void Camera::setMode(const Camera::Mode m) { state.spin = Vec3(0, pl->rotation(), 0); } - auto rotBest = Vec3(def.best_elevation, - def.best_azimuth, - def.best_rot_z); + auto rotBest = Vec3(def.best_elevation, + def.best_azimuth, + def.best_rot_z); state.spin += rotBest; } @@ -702,7 +709,7 @@ void Camera::tick(uint64_t dt) { switch (camMarvinMod) { case M_Normal: { - if(fpEnable && camMod!=Dialog) { + if(camMod==Camera::FirstPerson) { tickFirstPerson(dtF); } else { diff --git a/game/camera.h b/game/camera.h index 28e45e5c6..be86c78ed 100644 --- a/game/camera.h +++ b/game/camera.h @@ -30,6 +30,7 @@ class Camera final { Dive, Fall, Cutscene, + FirstPerson, }; enum MarvinMode { diff --git a/game/mainwindow.cpp b/game/mainwindow.cpp index 2f7edc92b..34985d74e 100644 --- a/game/mainwindow.cpp +++ b/game/mainwindow.cpp @@ -967,10 +967,15 @@ void MainWindow::tickCamera(uint64_t dt) { } Camera::Mode MainWindow::solveCameraMode() const { - if(auto camera = Gothic::inst().camera()) { - if(camera->isFree()) - return Camera::Normal; - } + const auto camera = Gothic::inst().camera(); + if(camera!=nullptr && camera->isFree()) + return Camera::Normal; + + if(dialogs.isActive()) + return Camera::Dialog; + + if(camera!=nullptr && camera->isFirstPerson()) + return Camera::FirstPerson; if(inventory.isOpen()==InventoryMenu::State::Equip || inventory.isOpen()==InventoryMenu::State::Ransack) @@ -981,9 +986,6 @@ Camera::Mode MainWindow::solveCameraMode() const { return Camera::Mobsi; } - if(dialogs.isActive()) - return Camera::Dialog; - if(auto pl = Gothic::inst().player()) { if(pl->isDead()) return Camera::Death;