diff --git a/game/camera.cpp b/game/camera.cpp index 4019a37de..4c2a13854 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -1,11 +1,13 @@ #include "camera.h" +#include #include #include "world/objects/npc.h" #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" @@ -23,18 +25,57 @@ 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; + 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; -const float Camera::minLength = 0.0001f; +const float Camera::minLength = 0.0001f; Camera::Camera() { } @@ -45,28 +86,35 @@ void Camera::reset() { void Camera::reset(const Npc* pl) { const auto& def = cameraDef(); - 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.y = pl ? pl->rotation() : 0; + userRange = (def.best_range - def.min_range)/(def.max_range - def.min_range); + 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); - calcControlPoints(-1.f); + tickThirdPerson(-1.f); } 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(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); - s.read(src.range, src.target, src.spin, - dst.range, dst.target, dst.spin); - s.read(cameraPos,origin,rotOffset); + if(s.version()<54) + return; + 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) { @@ -128,57 +176,70 @@ 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 = (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 bool reset = !(isRegular(camMod) && isRegular(m)); + if(camMod==Mode::Cutscene) { + state.spin = angles; + state.target = origin; + inter.target = origin; + } + camMod = m; - if(camMarvinMod==M_Freeze) + 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) { - resetDst(); - // auto ang = calcOffsetAngles(src.target,dst.target); - // dst.spin.x = ang.x; + state.range = def.best_range; + userRange = (def.best_range - def.min_range)/(def.max_range - def.min_range); + state.spin = Vec3(0); } if(auto pl = Gothic::inst().player()) { - dst.spin.y = pl->rotation(); + state.spin = Vec3(0, pl->rotation(), 0); } + + auto rotBest = Vec3(def.best_elevation, + def.best_azimuth, + def.best_rot_z); + state.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(nextMod==M_Pinned) { + const auto pl = Gothic::inst().player(); - rotMat.inverse(); - rotMat.project(offset); - pin.origin = offset; - pin.spin.x = src.spin.x - def.best_elevation; - pin.spin.y = src.spin.y - (pl ? pl->rotation() : 0); - } + auto offset = origin; + Matrix4x4 rotMat = pl!=nullptr ? pl->cameraMatrix(false) : Matrix4x4::mkIdentity(); + rotMat.inverse(); + rotMat.project(offset); + + pin.origin = offset; + pin.spin = angles; + } + else if(nextMod==M_Free) { + state.spin = angles; + state.target = origin; + inter.target = origin; } camMarvinMod = nextMod; } @@ -227,7 +288,6 @@ void Camera::setLookBack(bool lb) { if(lbEnable==lb) return; lbEnable = lb; - resetDst(); } void Camera::toggleDebug() { @@ -235,25 +295,28 @@ void Camera::toggleDebug() { } void Camera::setSpin(const PointF &p) { - dst.spin = Vec3(p.x,p.y,0); - src.spin = dst.spin; + state.spin = /*angleMod*/(Vec3(p.x,p.y,0)); } -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; +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) { + dlgRange = d; } void Camera::onRotateMouse(const PointF& dpos) { - if(camMarvinMod==M_Freeze) - return; - dst.spin.x += dpos.x; - dst.spin.y += dpos.y; + state.spin.x += dpos.x; + state.spin.y += dpos.y; } Matrix4x4 Camera::projective() const { @@ -263,20 +326,12 @@ Matrix4x4 Camera::projective() const { return ret; } -Matrix4x4 Camera::viewShadowLwc(const Tempest::Vec3& lightDir, size_t layer) const { - auto vp = viewProjLwc(); - float rotation = (180+src.spin.y-rotOffset.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(inter.target,ldir); } Matrix4x4 Camera::viewShadowVsmLwc(const Tempest::Vec3& ldir) const { - return mkViewShadowVsm(cameraPos-origin,ldir); + return mkViewShadowVsm(inter.target-origin,ldir); } Matrix4x4 Camera::mkViewShadowVsm(const Vec3& cameraPos, const Vec3& ldir) const { @@ -320,10 +375,18 @@ 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); // if(layer==0) // return viewShadowVsm(cameraPos,rotation,vp,lightDir); - return mkViewShadow(cameraPos,rotation,vp,lightDir,layer); + return mkViewShadow(inter.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(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 { @@ -499,92 +562,65 @@ const zenkit::ICamera& Camera::cameraDef() const { return camd.stdCam(); } -void Camera::clampRotation(Tempest::Vec3& spin) { - const auto& def = cameraDef(); - float maxElev = isMarvin() ? 90 : def.max_elevation; - float minElev = isMarvin() ? -90 : def.min_elevation; - if(spin.x>maxElev) - spin.x = maxElev; - if(spin.x0) + Log::d("time = ", tx-time); + time = tx; + } + */ + static float k = 0.25f; + return pos + (dest-pos)*std::min(1.f, k*velo*dtF); + } + +Tempest::Vec3 Camera::followRot(Vec3 spin, Tempest::Vec3 dest, float dtF, float velo) { + if(dtF<0.f) + return dest; + +#if 1 + const zenkit::Quat sx = fromAngles(spin); + const zenkit::Quat dx = fromAngles(dest); + + const zenkit::Quat s = slerp(sx, dx, std::min(1.f, dtF*velo)); + return toAngles(s); +#else + const zenkit::Quat d = fromAngles(dest); + return toAngles(d); +#endif } 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); + followAng(spin.x, dest.x, def.velo_rot, dtF); + followAng(spin.y, dest.y, def.velo_rot, dtF); } void Camera::followAng(float& ang, float dest, float speed, float dtF) { float da = angleMod(dest-ang); - float shift = da*speed*std::min(1.f, dtF); + float shift = da*std::min(1.f, speed*dtF); if(std::abs(da)<=0.0001f || dtF<0.f) { ang = dest; return; } - - static const float min=-45, max=45; - if(da>max+1.f) { - shift = (da-max); - } - if(dacameraMatrix(false) : Matrix4x4::mkIdentity(); + + auto offset = pin.origin; + rotMat.project(offset); + origin = offset; + angles = pin.spin; + break; + } + } + + { + //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) { @@ -653,7 +748,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); @@ -661,173 +756,182 @@ 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(); + + state.spin = clampRotation(state.spin); + inter.target = state.target; + + Vec3 offset = {0,0,20}; + rotMat.project(offset); + origin = offset; + angles = state.spin; + } + +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); - - clampRotation(dst.spin); + auto range = (camMod==Dialog) ? dlgRange : state.range*100.f; - 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(); - rotOffsetDef = Vec3(); - rotBest = Vec3(); - //spin.y += def.bestAzimuth; - } - if(isCutscene()) { - rotOffset = rotOffsetDef; - range = 0; + inter.target = state.target; + inter.rotOffset = Vec3(0); } - followAng(src.spin, dst.spin+rotBest, dtF); - if(!isMarvin()) - followAng(rotOffset, rotOffsetDef, dtF); - - Matrix4x4 rotOffsetMat; - rotOffsetMat.identity(); - rotOffsetMat.rotateOY(180-src.spin.y); - rotOffsetMat.rotateOX(src.spin.x); - rotOffsetMat.project(targetOffset); - - 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; - if(camMarvinMod==M_Free || isCutscene()) { - return; - } - - 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); - origin = offset; - src.target = dst.target; - src.spin = dst.spin + pin.spin; - offsetAng = Vec3(); - return; + if(camMod!=Dialog) { + state.spin = clampRotation(state.spin); + inter.rotOffset = followRot(inter.rotOffset, rotOffsetDef, dtF, def.velo_rot); } - if(def.collision!=0) { - // range = calcCameraColision(camTg,origin,src.spin,range); - // origin = cameraPos - dir*range; - origin = calcCameraColision(camTg,origin,src.spin+offsetAng,range); - range = (origin - camTg).length(); + 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(state.target, targetOffset); + } + // and has leash-like follow for target+offset + // ignores def.translate + inter.target = followTarget(inter.target, state.target+targetOffset, dtF); } - auto baseOrigin = target - dir*range; - if(camMod==Dialog) - offsetAng = Vec3(); else - offsetAng = calcOffsetAngles(origin,baseOrigin,dst.target); - - if(fpEnable && camMarvinMod==M_Normal) { - origin = dst.target; - offsetAng = Vec3(); + auto dir = Vec3{0,0,-1}; + rotOffsetMat.project(dir); - Vec3 offset = {0,0,20}; - Matrix4x4 rotOffsetMat; - rotOffsetMat.identity(); - rotOffsetMat.rotateOY(180-src.spin.y); - rotOffsetMat.project(offset); - origin += offset; + if(true && def.collision!=0) { + 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(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 = state.spin.y; + const auto rotOffsetMat = mkRotMatrix(rotation); + dir = Vec3{0,0,-1}; + rotOffsetMat.project(dir); + } } - } - -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; + if(def.translate!=0) + origin = followTrans(origin, inter.target + dir*range, (camMod!=Dialog ? dtF : -1.f), def.velo_trans); + angles = calcLookAtAngles(origin, inter.target, inter.rotOffset, state.spin); - 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)); + static bool dbg = false; + if(dbg) { + origin = state.target + targetOffset + dir*range; + angles = calcLookAtAngles(origin, state.target, inter.rotOffset, state.spin); } - - auto a0 = calcOffsetAngles(srcOrigin,target); - auto a1 = calcOffsetAngles(dstOrigin,target); - auto da = angleMod(a1-a0); - return da*k*offsetAngleMul; } -Vec3 Camera::calcCameraColision(const Vec3& target, const Vec3& origin, const Vec3& rotSpin, float dist) const { - if(camMod==Dialog) - dist = dlgDist; +float Camera::calcCameraColision(const Tempest::Vec3& target, const Tempest::Vec3& dir, const Tempest::Vec3& angles, float range) const { + //static float minDist = 20; + static float padding = 25; + static int n = 1, nn=1; auto world = Gothic::inst().world(); if(world==nullptr) - return origin; + return range; - //static float minDist = 20; - static float padding = 50; - static int n = 1, nn=1; + const auto origin = target + dir*range; - Matrix4x4 vinv=projective(); - vinv.mul(mkView(origin,rotSpin)); + Matrix4x4 vinv = projective(); + vinv.mul(mkView(origin,angles)); vinv.inverse(); auto& physic = *world->physic(); auto dview = (origin - target); raysCasted = 0; - float distM = dist; - for(int i=-n;i<=n;++i) + float distM = range; + 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); + 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(dist10 && dp.y
physic(); + const auto len = dir.length() + padding; + const auto nrm = Vec3::normalize(dir); + + auto rc = physic.ray(from, from+nrm*len); + if(!rc.hasCol) + return dir; + + return nrm*std::max(len*rc.hitFraction-padding, 0.f); + } + +Vec3 Camera::clampRotation(Tempest::Vec3 spin) { + //NOTE: min elevation is zero for nomal camera. assume that it's ignored by vanilla + float maxElev = +85; + float minElev = -60; + float maxAzim = +180; + float minAzim = -180; + + const auto pl = Gothic::inst().player(); + if(pl==nullptr) + return spin; + + 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 + plSpin); } Matrix4x4 Camera::mkView(const Vec3& pos, const Vec3& spin) const { @@ -845,32 +949,25 @@ 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; } -void Camera::resetDst() { - if(isMarvin()) - return; - const auto& def = cameraDef(); - dst.spin.x = def.best_elevation; - dst.range = def.best_range; - } - void Camera::debugDraw(DbgPainter& p) { if(!dbg) return; p.setPen(Color(0,1,0)); - p.drawLine(dst.target, src.target); + 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); @@ -879,31 +976,31 @@ 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 : ", (dst.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-src.spin.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-src.spin.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); +float Camera::azimuth() const { + return angleMod(state.spin.y-angles.y); } -PointF Camera::destSpin() const { - return PointF(dst.spin.x,dst.spin.y); +PointF Camera::spin() const { + return PointF(state.spin.x,state.spin.y); } -Vec3 Camera::destPosition() const { - return dst.target; +Vec3 Camera::destTarget() const { + return state.target; } Matrix4x4 Camera::viewProj() const { @@ -913,13 +1010,11 @@ Matrix4x4 Camera::viewProj() const { } Matrix4x4 Camera::view() const { - auto spin = src.spin+offsetAng; - return mkView(origin,spin); + return mkView(origin, angles); } Matrix4x4 Camera::viewLwc() const { - auto spin = src.spin+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 703bd8962..be86c78ed 100644 --- a/game/camera.h +++ b/game/camera.h @@ -30,6 +30,7 @@ class Camera final { Dive, Fall, Cutscene, + FirstPerson, }; enum MarvinMode { @@ -49,7 +50,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); @@ -62,7 +63,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; @@ -85,17 +86,15 @@ class Camera final { void tick(uint64_t dt); void debugDraw(DbgPainter& p); - Tempest::PointF spin() const; - Tempest::PointF destSpin() const; - - Tempest::Vec3 destPosition() const; + Tempest::PointF spin() const; + Tempest::Vec3 destTarget() const; + float azimuth() const; void setSpin(const Tempest::PointF& p); - void setDestSpin(const Tempest::PointF& p); + void setTarget(const Tempest::Vec3& pos); + void setAngles(const Tempest::PointF& pos); void setPosition(const Tempest::Vec3& pos); - void setDestPosition(const Tempest::Vec3& pos); - void setDialogDistance(float d); void onRotateMouse(const Tempest::PointF& dpos); @@ -119,29 +118,34 @@ 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 cameraPos = {}; - Tempest::Vec3 origin = {}; - Tempest::Vec3 rotOffset = {}; - Tempest::Vec3 offsetAng = {}; - State src, dst; - + State state; + Interpolated inter; Pin pin; - float dlgDist = 0; - float userRange = 0.13f; + Tempest::Vec3 origin = {}; + Tempest::Vec3 angles = {}; + Tempest::Vec3 veloTrans = {}; + float userRange = 0; + float targetVelo = 0; - float veloTrans = 0; + + float dlgRange = 0; Tempest::Matrix4x4 proj; uint32_t vpWidth=0; @@ -159,29 +163,30 @@ class Camera final { mutable int raysCasted = 0; - static float maxDist; - static float baseSpeeed; - static float offsetAngleMul; static const float minLength; - void calcControlPoints(float dtF); + void tickFirstPerson(float dtF); + void tickThirdPerson(float dtF); - 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; + Tempest::Vec3 clampRotation(Tempest::Vec3 spin); + 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& defSpin) 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, 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 resetDst(); - void clampRotation(Tempest::Vec3& spin); + 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 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); static void followAng (float& ang, float dest, float speed, float dtF); 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 { diff --git a/game/game/playercontrol.cpp b/game/game/playercontrol.cpp index c7c96ce61..824b6002d 100644 --- a/game/game/playercontrol.cpp +++ b/game/game/playercontrol.cpp @@ -247,14 +247,9 @@ 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) { + rotMouse += dAngleX; + rotMouseY += dAngleY; } void PlayerControl::tickFocus() { @@ -563,8 +558,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(); @@ -664,7 +657,7 @@ void PlayerControl::implMove(uint64_t dt) { return; } - int rotation=0; + int rotation = 0; if(allowRot) { if(this->wantsToTurnLeft()) { rot += rspeed; @@ -680,7 +673,7 @@ void PlayerControl::implMove(uint64_t dt) { if(rotMouse>0) rotation = -1; else rotation = 1; - rot +=rotMouse; + rot += rotMouse; rotMouse = 0; } rotY+=rotMouseY; @@ -1034,29 +1027,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 = 15.0f; - 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*2.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) { @@ -1065,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)<100.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; @@ -1073,7 +1054,7 @@ void PlayerControl::setAnimRotate(Npc& pl, float rotation, int anim, bool force, force = true; if(!force && wrld.tickCount()isCutscene()) { dMouse = Point(); @@ -339,21 +339,31 @@ void MainWindow::tickMouse() { return; } + 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); dpScaled.x/=float(w()); dpScaled.y/=float(h()); - - dpScaled*=1000.f; - dpScaled.y /= 7.f; if(camLookaroundInverse) dpScaled.y *= -1.f; + static float mul = 270.f; + dpScaled *= mul; + + 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()) { - player.onRotateMouse (-dpScaled.x); - player.onRotateMouseDy(-dpScaled.y); + player.onRotateMouse(-dpScaled.x, -dpScaled.y); } dMouse = Point(); @@ -551,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())); @@ -876,11 +892,10 @@ uint64_t MainWindow::tick() { else if(runtimeMode==R_Suspended) { auto camera = Gothic::inst().camera(); if(camera!=nullptr && camera->isFree()) { - player.tickCameraMove(dt); - tickMouse(); + tickMouse(dt); } update(); - return dt; + return 0; } dialogs.tick(dt); @@ -892,7 +907,7 @@ uint64_t MainWindow::tick() { ;//clearInput(); if(document.isActive()) clearInput(); - tickMouse(); + tickMouse(dt); player.tickMove(dt); update(); return dt; @@ -915,32 +930,32 @@ 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()); - camera.setDestPosition(pos); + camera.setSpin(camera.spin()); + camera.setTarget(pos); } else if(dialogs.isActive() && !dialogs.isMobsiDialog()) { dialogs.dialogCamera(camera); } else if(inventory.isActive()) { - camera.setDestPosition(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.setDestPosition(pos); + camera.setSpin(spin); + camera.setTarget(pos); } - else if(pl!=nullptr) { - auto spin = camera.destSpin(); + else if(pl!=nullptr && !camera.isFree()) { + 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.setDestPosition(pos); + camera.setSpin(spin); + camera.setTarget(pos); } } @@ -952,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) @@ -966,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; 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(); 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/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); } } 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; diff --git a/game/world/triggers/cscamera.cpp b/game/world/triggers/cscamera.cpp index 0e989e9be..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)); } } @@ -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) { @@ -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}; } 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();