From f04671ae2290b4bdda6761e3a410c3476f62b5e9 Mon Sep 17 00:00:00 2001 From: thokkat Date: Mon, 13 Nov 2023 18:19:06 +0000 Subject: [PATCH] CsCamera initial (#531) * CsCamera initial * address code review --- game/camera.cpp | 12 ++- game/camera.h | 2 + game/game/playercontrol.cpp | 6 ++ game/mainwindow.cpp | 65 ++++++----- game/marvin.cpp | 10 ++ game/marvin.h | 1 + game/world/triggers/cscamera.cpp | 180 ++++++++++++++++++++++++++++++- game/world/triggers/cscamera.h | 30 +++++- game/world/world.cpp | 8 ++ game/world/world.h | 2 + game/world/worldobjects.cpp | 11 +- game/world/worldobjects.h | 4 + 12 files changed, 298 insertions(+), 33 deletions(-) diff --git a/game/camera.cpp b/game/camera.cpp index 8021c91e8..8f1b09b41 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -624,6 +624,12 @@ void Camera::calcControlPoints(float dtF) { rotBest = Vec3(); //spin.y += def.bestAzimuth; } + auto world = Gothic::inst().world(); + if(world!=nullptr && world->currentCs()!=nullptr) { + range = 0; + rotOffset = Vec3(); + rotOffsetDef = Vec3(); + } followAng(src.spin, dst.spin+rotBest, dtF); if(!isMarvin()) @@ -645,7 +651,7 @@ void Camera::calcControlPoints(float dtF) { followCamera(cameraPos,src.target,dtF); origin = cameraPos - dir*range; - if(camMarvinMod==M_Free) { + if(camMarvinMod==M_Free || (world!=nullptr && world->currentCs()!=nullptr)) { return; } @@ -829,6 +835,10 @@ PointF Camera::destSpin() const { return PointF(dst.spin.x,dst.spin.y); } +Vec3 Camera::destPosition() const { + return dst.target; + } + Matrix4x4 Camera::viewProj() const { Matrix4x4 ret=projective(); ret.mul(view()); diff --git a/game/camera.h b/game/camera.h index 2eac49c92..bc640cf99 100644 --- a/game/camera.h +++ b/game/camera.h @@ -87,6 +87,8 @@ class Camera final { Tempest::PointF spin() const; Tempest::PointF destSpin() const; + Tempest::Vec3 destPosition() const; + void setSpin(const Tempest::PointF& p); void setDestSpin(const Tempest::PointF& p); diff --git a/game/game/playercontrol.cpp b/game/game/playercontrol.cpp index 293f8a5dc..afd3eab13 100644 --- a/game/game/playercontrol.cpp +++ b/game/game/playercontrol.cpp @@ -52,6 +52,9 @@ void PlayerControl::onKeyPressed(KeyCodec::Action a, Tempest::KeyEvent::KeyType auto ws = pl ? pl->weaponState() : WeaponState::NoWeapon; uint8_t slot = pl ? pl->inventory().currentSpellSlot() : Item::NSLOT; + if(w!=nullptr && w->currentCs()!=nullptr) + return; + handleMovementAction(KeyCodec::ActionMapping{a,mapping}, true); if(pl!=nullptr && pl->interactive()!=nullptr && c!=nullptr && !c->isFree()) { @@ -514,6 +517,9 @@ bool PlayerControl::tickMove(uint64_t dt) { Npc* pl = w->player(); auto camera = Gothic::inst().camera(); + if(w->currentCs()!=nullptr) + return true; + if(camera!=nullptr && (camera->isFree() || pl==nullptr)) { rotMouse = 0; if(ctrl[KeyCodec::Left] || (ctrl[KeyCodec::RotateL] && ctrl[KeyCodec::Jump])) { diff --git a/game/mainwindow.cpp b/game/mainwindow.cpp index ace08ecd2..8ac854c87 100644 --- a/game/mainwindow.cpp +++ b/game/mainwindow.cpp @@ -314,7 +314,8 @@ void MainWindow::processMouse(MouseEvent& event, bool enable) { } void MainWindow::tickMouse() { - if(dialogs.hasContent() || Gothic::inst().isPause()) { + auto world = Gothic::inst().world(); + if(dialogs.hasContent() || Gothic::inst().isPause() || (world!=nullptr && world->currentCs()!=nullptr)) { dMouse = Point(); return; } @@ -336,7 +337,8 @@ void MainWindow::tickMouse() { if(camLookaroundInverse) dpScaled.y *= -1.f; - if(auto camera = Gothic::inst().camera()) + auto camera = Gothic::inst().camera(); + if(camera!=nullptr) camera->onRotateMouse(PointF(dpScaled.y,-dpScaled.x)); if(!inventory.isActive()) { player.onRotateMouse (-dpScaled.x); @@ -531,7 +533,7 @@ void MainWindow::paintFocus(Painter& p, const Focus& focus, const Matrix4x4& vp) auto world = Gothic::inst().world(); auto pl = world==nullptr ? nullptr : world->player(); - if(pl==nullptr) + if(pl==nullptr || world->currentCs()!=nullptr) return; auto pos = focus.displayPosition(); @@ -851,9 +853,10 @@ void MainWindow::updateAnimation(uint64_t dt) { } void MainWindow::tickCamera(uint64_t dt) { + auto world = Gothic::inst().world(); auto pcamera = Gothic::inst().camera(); auto pl = Gothic::inst().player(); - if(pcamera==nullptr || pl==nullptr) + if(world==nullptr || pcamera==nullptr || pl==nullptr) return; auto& camera = *pcamera; @@ -863,36 +866,38 @@ void MainWindow::tickCamera(uint64_t dt) { ws==WeaponState::W2H); auto pos = pl->cameraBone(camera.isFirstPerson()); - const bool fs = SystemApi::isFullscreen(hwnd()); - if(!fs && mouseP[Event::ButtonLeft]) { - camera.setSpin(camera.destSpin()); - camera.setDestPosition(pos); - } - else if(dialogs.isActive() && !dialogs.isMobsiDialog()) { - dialogs.dialogCamera(camera); - } - else if(inventory.isActive()) { - camera.setDestPosition(pos); - } - else if(player.focus().npc!=nullptr && meleeFocus) { - auto spin = camera.destSpin(); - spin.y = pl->rotation(); - camera.setDestSpin(spin); - camera.setDestPosition(pos); - } - else { - auto spin = camera.destSpin(); - if(pl->interactive()==nullptr && !pl->isDown()) + if(world->currentCs()==nullptr) { + const bool fs = SystemApi::isFullscreen(hwnd()); + if(!fs && mouseP[Event::ButtonLeft]) { + camera.setSpin(camera.destSpin()); + camera.setDestPosition(pos); + } + else if(dialogs.isActive() && !dialogs.isMobsiDialog()) { + dialogs.dialogCamera(camera); + } + else if(inventory.isActive()) { + camera.setDestPosition(pos); + } + else if(player.focus().npc!=nullptr && meleeFocus) { + auto spin = camera.destSpin(); spin.y = pl->rotation(); - if(pl->isDive() && !camera.isMarvin()) - spin.x = -pl->rotationY(); - camera.setDestSpin(spin); - camera.setDestPosition(pos); + camera.setDestSpin(spin); + camera.setDestPosition(pos); + } + else { + auto spin = camera.destSpin(); + if(pl->interactive()==nullptr && !pl->isDown()) + spin.y = pl->rotation(); + if(pl->isDive() && !camera.isMarvin()) + spin.x = -pl->rotationY(); + camera.setDestSpin(spin); + camera.setDestPosition(pos); + } } if(dt==0) return; - if(camera.isToggleEnabled()) + if(camera.isToggleEnabled() && world->currentCs()==nullptr) camera.setMode(solveCameraMode()); camera.tick(dt); } @@ -977,6 +982,8 @@ void MainWindow::saveGame(std::string_view slot, std::string_view name) { if(dialogs.isActive()) return; + if(auto w = Gothic::inst().world(); w!=nullptr && w->currentCs()!=nullptr) + return; Gothic::inst().startSave(std::move(textureCast(tex)),[slot=std::string(slot),name=std::string(name),pm](std::unique_ptr&& game){ if(!game) diff --git a/game/marvin.cpp b/game/marvin.cpp index 825739bf4..29740c39a 100644 --- a/game/marvin.cpp +++ b/game/marvin.cpp @@ -7,6 +7,7 @@ #include "utils/string_frm.h" #include "world/objects/npc.h" +#include "world/triggers/abstracttrigger.h" #include "camera.h" #include "gothic.h" @@ -125,6 +126,7 @@ Marvin::Marvin() { {"toggle camdebug", C_ToggleCamDebug}, {"toggle camera", C_ToggleCamera}, {"toggle inertiatarget", C_ToggleInertia}, + {"ztoggle timedemo", C_ZToggleTimeDemo}, {"insert %c", C_Insert}, {"toggle gi", C_ToggleGI}, @@ -330,6 +332,14 @@ bool Marvin::exec(std::string_view v) { c->setInertiaTargetEnable(!c->isInertiaTargetEnabled()); return true; } + case C_ZToggleTimeDemo: { + World* world = Gothic::inst().world(); + if(world==nullptr) + return false; + const TriggerEvent evt("TIMEDEMO","",world->tickCount(),TriggerEvent::T_Trigger); + world->triggerEvent(evt); + return true; + } case C_ToggleDesktop: { Gothic::inst().toggleDesktop(); return true; diff --git a/game/marvin.h b/game/marvin.h index 289d1b132..599eb5127 100644 --- a/game/marvin.h +++ b/game/marvin.h @@ -38,6 +38,7 @@ class Marvin { C_ToggleCamDebug, C_ToggleCamera, C_ToggleInertia, + C_ZToggleTimeDemo, C_AiGoTo, C_GoToPos, diff --git a/game/world/triggers/cscamera.cpp b/game/world/triggers/cscamera.cpp index 903fea5c6..f45a75c10 100644 --- a/game/world/triggers/cscamera.cpp +++ b/game/world/triggers/cscamera.cpp @@ -2,12 +2,190 @@ #include +#include "gothic.h" + using namespace Tempest; CsCamera::CsCamera(Vob* parent, World& world, const phoenix::vobs::cs_camera& cam, Flags flags) :AbstractTrigger(parent,world,cam,flags) { + + if(cam.position_count<1 || cam.total_duration<0) + return; + + if(cam.trajectory_for==phoenix::camera_trajectory::object || cam.target_trajectory_for==phoenix::camera_trajectory::object) { + Log::d("Object camera not implemented, \"", name() , "\""); + return; + } + + duration = cam.total_duration; + delay = cam.auto_untrigger_last_delay; + + for(uint32_t i=0;ioriginal_pose[3][0],f->original_pose[3][1],f->original_pose[3][2]); + kF.time = f->time; + if(isize()); + for(uint32_t i=0;i+1keyframe[i]; + Vec3 p0 = i==0 ? kF.c[3] : spl->keyframe[i-1].c[3]; + Vec3 p1 = kF.c[3]; + Vec3 p2 = spl->keyframe[i+1].c[3]; + Vec3 p3 = i+2==size ? kF.c[3] : spl->keyframe[i+2].c[3]; + Vec3 dd = (p2-p0)*0.5f; + Vec3 sd = (p3-p1)*0.5f; + kF.c[0] = 2 * p1 - 2*p2 + dd + sd; + kF.c[1] = -3 * p1 + 3*p2 - 2*dd - sd; + kF.c[2] = std::move(dd); + } + + if(size<2) + continue; + assert(spl->keyframe.front().time==0); + assert(spl->keyframe.back().time==duration); + const float slow = 0; + const float linear = duration; + const float fast = 2 * duration; + uint32_t start = spl==&posSpline ? 0 : uint32_t(cam.position_count); + uint32_t end = spl==&posSpline ? uint32_t(cam.position_count-1) : uint32_t(cam.frames.size()-1); + auto mType0 = cam.frames[start]->motion_type; + auto mType1 = cam.frames[end]->motion_type; + float d0 = slow; + float d1 = slow; + if(mType0!=phoenix::camera_motion::slow && mType1!=phoenix::camera_motion::slow) { + d0 = linear; + d1 = linear; + } + else if(mType0==phoenix::camera_motion::slow && mType1!=phoenix::camera_motion::slow) { + d0 = slow; + d1 = fast; + } + else if(mType0!=phoenix::camera_motion::slow && mType1==phoenix::camera_motion::slow) { + d0 = fast; + d1 = slow; + } + + spl->c[0] = -2*duration + d0 + d1; + spl->c[1] = 3*duration - 2*d0 - d1; + spl->c[2] = d0; + } } void CsCamera::onTrigger(const TriggerEvent& evt) { - Log::d("TODO: cs-camera, \"", name() , "\""); + if(active || posSpline.size()==0) + return; + + auto world = Gothic::inst().world(); + active = true; + time = 0; + posSpline.splTime = 0; + targetSpline.splTime = 0; + if(world->currentCs()==nullptr) { + auto camera = Gothic::inst().camera(); + camera->reset(); + camera->setMode(Camera::Mode::Normal); + godMode = Gothic::inst().isGodMode(); + Gothic::inst().setGodMode(true); + } + world->setCurrentCs(this); + enableTicks(); + } + +void CsCamera::onUntrigger(const TriggerEvent& evt) { + clear(); + } + +void CsCamera::clear() { + if(!active) + return; + active = false; + disableTicks(); + auto world = Gothic::inst().world(); + if(world->currentCs()==this) { + auto camera = Gothic::inst().camera(); + world->setCurrentCs(nullptr); + camera->reset(); + Gothic::inst().setGodMode(godMode); + } + } + +void CsCamera::tick(uint64_t dt) { + auto world = Gothic::inst().world(); + time += float(dt)/1000.f; + + if(time>duration+delay || world->currentCs()!=this) { + clear(); + return; + } + + if(time>duration) + return; + + auto camera = Gothic::inst().camera(); + auto cPos = position(); + camera->setPosition(cPos); + camera->setSpin(spin(cPos)); + } + +Vec3 CsCamera::position() { + Vec3 pos; + if(posSpline.size()==1) { + pos = posSpline.keyframe[0].c[3]; + } else { + posSpline.setSplTime(time/duration); + pos = posSpline.position(); + } + return pos; + } + +PointF CsCamera::spin(Tempest::Vec3& d) { + if(targetSpline.size()==0) + d = d - Gothic::inst().camera()->destPosition(); + else if(targetSpline.size()==1) + d = targetSpline.keyframe[0].c[3] - d; + else if(targetSpline.size()>1) { + targetSpline.setSplTime(time/duration); + d = targetSpline.position() - d; + } + + float k = 180.f/float(M_PI); + float spinX = k * std::asin(d.y/d.length()); + float spinY = -90; + if(d.x!=0.f || d.z!=0.f) + spinY = 90 + k * std::atan2(d.z,d.x); + return {-spinX,spinY}; + } + +void CsCamera::KbSpline::setSplTime(float time) { + float t = applyMotionScaling(time); + uint32_t n = uint32_t(splTime); + auto kF0 = &keyframe[n]; + auto kF1 = &keyframe[n+1]; + if(t>kF1->time) { + splTime = std::ceil(splTime); + n = uint32_t(splTime); + kF0 = kF1; + kF1 = &keyframe[n+1]; + } + assert(ntime) / (kF1->time - kF0->time); + assert(u>=0 && u<=1); + splTime = float(n) + u; + } + +Vec3 CsCamera::KbSpline::position() const { + float n; + float t = std::modf(splTime,&n); + auto& kF = keyframe[uint32_t(n)]; + return ((kF.c[0]*t + kF.c[1])*t + kF.c[2])*t + kF.c[3]; + } + +float CsCamera::KbSpline::applyMotionScaling(float t) const { + return std::min(((c[0]*t + c[1])*t + c[2])*t,keyframe.back().time); } diff --git a/game/world/triggers/cscamera.h b/game/world/triggers/cscamera.h index d3c00b6e9..8cbcb73d6 100644 --- a/game/world/triggers/cscamera.h +++ b/game/world/triggers/cscamera.h @@ -9,8 +9,36 @@ class CsCamera : public AbstractTrigger { public: CsCamera(Vob* parent, World& world, const phoenix::vobs::cs_camera& data, Flags flags); + private: + struct KeyFrame { + float time = 0; + Tempest::Vec3 c[4] = {}; + }; + + struct KbSpline { + float c[3] = {}; + float splTime = 0; + std::vector keyframe; + size_t size() const { return keyframe.size(); } + auto position() const -> Tempest::Vec3; + void setSplTime(float t); + float applyMotionScaling(float t) const; + }; + void onTrigger(const TriggerEvent& evt) override; + void onUntrigger(const TriggerEvent& evt) override; + void tick(uint64_t dt) override; - private: + void clear(); + + auto position() -> Tempest::Vec3; + auto spin(Tempest::Vec3& d) -> Tempest::PointF; + bool active = false; + bool godMode; + float duration; + float delay; + float time; + KbSpline posSpline = {}; + KbSpline targetSpline = {}; }; diff --git a/game/world/world.cpp b/game/world/world.cpp index 5b7763054..11451d42f 100644 --- a/game/world/world.cpp +++ b/game/world/world.cpp @@ -507,6 +507,14 @@ void World::disableTicks(AbstractTrigger& t) { wobj.disableTicks(t); } +void World::setCurrentCs(CsCamera* cs) { + wobj.setCurrentCs(cs); + } + +CsCamera* World::currentCs() const { + return wobj.currentCs(); + } + void World::enableCollizionZone(CollisionZone& z) { wobj.enableCollizionZone(z); } diff --git a/game/world/world.h b/game/world/world.h index 6a8696763..e51abcdbd 100644 --- a/game/world/world.h +++ b/game/world/world.h @@ -124,6 +124,8 @@ class World final { void execTriggerEvent(const TriggerEvent& e); void enableTicks (AbstractTrigger& t); void disableTicks(AbstractTrigger& t); + void setCurrentCs(CsCamera* cs); + CsCamera* currentCs() const; void enableCollizionZone (CollisionZone& z); void disableCollizionZone(CollisionZone& z); diff --git a/game/world/worldobjects.cpp b/game/world/worldobjects.cpp index df2118165..13e9b18a9 100644 --- a/game/world/worldobjects.cpp +++ b/game/world/worldobjects.cpp @@ -161,7 +161,8 @@ void WorldObjects::tick(uint64_t dt, uint64_t dtPlayer) { }); } - const bool freeCam = (Gothic::inst().camera()!=nullptr && Gothic::inst().camera()->isFree()); + auto camera = Gothic::inst().camera(); + const bool freeCam = (camera!=nullptr && (camera->isFree() || currentCs()!=nullptr)); const auto pl = owner.player(); for(size_t i=0; i triggersTk; std::vector sndPerc; std::vector triggerEvents; + CsCamera* currentCsCamera = nullptr; template auto findObj(T &src, const Npc &pl, const SearchOpt& opt) -> typename std::remove_reference::type;