camera.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. #include "camera.h"
  2. #include "../../game/map/visibility_service.h"
  3. #include <QtMath>
  4. #include <cmath>
  5. #include <algorithm>
  6. #include <limits>
  7. namespace Render::GL {
  8. // -------- internal helpers, do not change public API --------
  9. namespace {
  10. constexpr float kEps = 1e-6f;
  11. constexpr float kTiny = 1e-4f;
  12. constexpr float kMinDist = 1.0f; // zoomDistance floor
  13. constexpr float kMaxDist = 500.0f; // zoomDistance ceiling
  14. constexpr float kMinFov = 1.0f; // perspective clamp (deg)
  15. constexpr float kMaxFov = 89.0f;
  16. inline bool finite(const QVector3D& v) {
  17. return qIsFinite(v.x()) && qIsFinite(v.y()) && qIsFinite(v.z());
  18. }
  19. inline bool finite(float v) { return qIsFinite(v); }
  20. inline QVector3D safeNormalize(const QVector3D& v,
  21. const QVector3D& fallback,
  22. float eps = kEps) {
  23. if (!finite(v)) return fallback;
  24. float len2 = v.lengthSquared();
  25. if (len2 < eps) return fallback;
  26. return v / std::sqrt(len2);
  27. }
  28. // Keep up-vector not collinear with front; pick a sane orthonormal basis.
  29. inline void orthonormalize(const QVector3D& frontIn,
  30. QVector3D& frontOut,
  31. QVector3D& rightOut,
  32. QVector3D& upOut) {
  33. QVector3D worldUp(0.f, 1.f, 0.f);
  34. QVector3D f = safeNormalize(frontIn, QVector3D(0, 0, -1));
  35. // If front is near-collinear with worldUp, choose another temp up.
  36. QVector3D u = (std::abs(QVector3D::dotProduct(f, worldUp)) > 1.f - 1e-3f)
  37. ? QVector3D(0, 0, 1)
  38. : worldUp;
  39. QVector3D r = QVector3D::crossProduct(f, u);
  40. if (r.lengthSquared() < kEps) r = QVector3D(1, 0, 0);
  41. r = r.normalized();
  42. u = QVector3D::crossProduct(r, f).normalized();
  43. frontOut = f;
  44. rightOut = r;
  45. upOut = u;
  46. }
  47. inline void clampOrthoBox(float& left, float& right,
  48. float& bottom, float& top) {
  49. if (left == right) {
  50. left -= 0.5f; right += 0.5f;
  51. } else if (left > right) {
  52. std::swap(left, right);
  53. }
  54. if (bottom == top) {
  55. bottom -= 0.5f; top += 0.5f;
  56. } else if (bottom > top) {
  57. std::swap(bottom, top);
  58. }
  59. }
  60. } // anonymous namespace
  61. // ------------------------------------------------------------
  62. Camera::Camera() { updateVectors(); }
  63. void Camera::setPosition(const QVector3D &position) {
  64. if (!finite(position)) return; // ignore invalid input
  65. m_position = position;
  66. clampAboveGround();
  67. // keep looking at current target; repair front/up basis if needed
  68. QVector3D newFront = (m_target - m_position);
  69. orthonormalize(newFront, m_front, m_right, m_up);
  70. }
  71. void Camera::setTarget(const QVector3D &target) {
  72. if (!finite(target)) return;
  73. m_target = target;
  74. QVector3D dir = (m_target - m_position);
  75. if (dir.lengthSquared() < kEps) {
  76. // Nudge target forward to avoid zero-length front
  77. m_target = m_position + (m_front.lengthSquared() < kEps
  78. ? QVector3D(0, 0, -1)
  79. : m_front);
  80. dir = (m_target - m_position);
  81. }
  82. orthonormalize(dir, m_front, m_right, m_up);
  83. clampAboveGround();
  84. }
  85. void Camera::setUp(const QVector3D &up) {
  86. if (!finite(up)) return;
  87. QVector3D upN = up;
  88. if (upN.lengthSquared() < kEps) upN = QVector3D(0, 1, 0);
  89. // Ensure up is not collinear with front; re-orthonormalize
  90. orthonormalize(m_target - m_position, m_front, m_right, m_up);
  91. }
  92. void Camera::lookAt(const QVector3D &position, const QVector3D &target,
  93. const QVector3D &up) {
  94. if (!finite(position) || !finite(target) || !finite(up)) return;
  95. m_position = position;
  96. m_target = (position == target)
  97. ? position + QVector3D(0, 0, -1)
  98. : target;
  99. QVector3D f = (m_target - m_position);
  100. m_up = up.lengthSquared() < kEps ? QVector3D(0, 1, 0) : up.normalized();
  101. orthonormalize(f, m_front, m_right, m_up);
  102. clampAboveGround();
  103. }
  104. void Camera::setPerspective(float fov, float aspect, float nearPlane,
  105. float farPlane) {
  106. if (!finite(fov) || !finite(aspect) || !finite(nearPlane) || !finite(farPlane))
  107. return;
  108. m_isPerspective = true;
  109. // Robust clamps
  110. m_fov = std::clamp(fov, kMinFov, kMaxFov);
  111. m_aspect = std::max(aspect, 1e-6f);
  112. m_nearPlane = std::max(nearPlane, 1e-4f);
  113. m_farPlane = std::max(farPlane, m_nearPlane + 1e-3f);
  114. }
  115. void Camera::setOrthographic(float left, float right, float bottom, float top,
  116. float nearPlane, float farPlane) {
  117. if (!finite(left) || !finite(right) || !finite(bottom) || !finite(top) ||
  118. !finite(nearPlane) || !finite(farPlane))
  119. return;
  120. m_isPerspective = false;
  121. clampOrthoBox(left, right, bottom, top);
  122. m_orthoLeft = left;
  123. m_orthoRight = right;
  124. m_orthoBottom = bottom;
  125. m_orthoTop = top;
  126. m_nearPlane = std::min(nearPlane, farPlane - 1e-3f);
  127. m_farPlane = std::max(farPlane, m_nearPlane + 1e-3f);
  128. }
  129. void Camera::moveForward(float distance) {
  130. if (!finite(distance)) return;
  131. m_position += m_front * distance;
  132. m_target = m_position + m_front;
  133. clampAboveGround();
  134. }
  135. void Camera::moveRight(float distance) {
  136. if (!finite(distance)) return;
  137. m_position += m_right * distance;
  138. m_target = m_position + m_front;
  139. clampAboveGround();
  140. }
  141. void Camera::moveUp(float distance) {
  142. if (!finite(distance)) return;
  143. m_position += QVector3D(0, 1, 0) * distance;
  144. clampAboveGround();
  145. m_target = m_position + m_front;
  146. }
  147. void Camera::zoom(float delta) {
  148. if (!finite(delta)) return;
  149. if (m_isPerspective) {
  150. m_fov = qBound(kMinFov, m_fov - delta, kMaxFov);
  151. } else {
  152. // Keep scale positive and bounded
  153. float scale = 1.0f + delta * 0.1f;
  154. if (!finite(scale) || scale <= 0.05f) scale = 0.05f;
  155. if (scale > 20.0f) scale = 20.0f;
  156. m_orthoLeft *= scale;
  157. m_orthoRight *= scale;
  158. m_orthoBottom *= scale;
  159. m_orthoTop *= scale;
  160. clampOrthoBox(m_orthoLeft, m_orthoRight, m_orthoBottom, m_orthoTop);
  161. }
  162. }
  163. void Camera::zoomDistance(float delta) {
  164. if (!finite(delta)) return;
  165. QVector3D offset = m_position - m_target;
  166. float r = offset.length();
  167. if (r < kTiny) r = kTiny;
  168. float factor = 1.0f - delta * 0.15f;
  169. if (!finite(factor)) factor = 1.0f;
  170. factor = std::clamp(factor, 0.1f, 10.0f);
  171. float newR = std::clamp(r * factor, kMinDist, kMaxDist);
  172. QVector3D dir = safeNormalize(offset, QVector3D(0,0,1));
  173. m_position = m_target + dir * newR;
  174. clampAboveGround();
  175. QVector3D f = (m_target - m_position);
  176. orthonormalize(f, m_front, m_right, m_up);
  177. }
  178. void Camera::rotate(float yaw, float pitch) { orbit(yaw, pitch); }
  179. void Camera::pan(float rightDist, float forwardDist) {
  180. if (!finite(rightDist) || !finite(forwardDist)) return;
  181. QVector3D right = m_right;
  182. QVector3D front = m_front;
  183. front.setY(0.0f);
  184. if (front.lengthSquared() > 0) front.normalize();
  185. QVector3D delta = right * rightDist + front * forwardDist;
  186. if (!finite(delta)) return;
  187. m_position += delta;
  188. m_target += delta;
  189. clampAboveGround();
  190. }
  191. void Camera::elevate(float dy) {
  192. if (!finite(dy)) return;
  193. m_position.setY(m_position.y() + dy);
  194. clampAboveGround();
  195. }
  196. void Camera::yaw(float degrees) {
  197. if (!finite(degrees)) return;
  198. // computeYawPitchFromOffset already guards degeneracy
  199. orbit(degrees, 0.0f);
  200. }
  201. void Camera::orbit(float yawDeg, float pitchDeg) {
  202. if (!finite(yawDeg) || !finite(pitchDeg)) return;
  203. QVector3D offset = m_position - m_target;
  204. float curYaw = 0.f, curPitch = 0.f;
  205. computeYawPitchFromOffset(offset, curYaw, curPitch);
  206. m_orbitStartYaw = curYaw;
  207. m_orbitStartPitch = curPitch;
  208. m_orbitTargetYaw = curYaw + yawDeg;
  209. m_orbitTargetPitch = qBound(m_pitchMinDeg, curPitch + pitchDeg, m_pitchMaxDeg);
  210. m_orbitTime = 0.0f;
  211. m_orbitPending = true;
  212. }
  213. void Camera::update(float dt) {
  214. if (!m_orbitPending) return;
  215. if (!finite(dt)) return;
  216. m_orbitTime += std::max(0.0f, dt);
  217. float t = (m_orbitDuration <= 0.0f)
  218. ? 1.0f
  219. : std::clamp(m_orbitTime / m_orbitDuration, 0.0f, 1.0f);
  220. // Smoothstep
  221. float s = t * t * (3.0f - 2.0f * t);
  222. // Interpolate yaw/pitch
  223. float newYaw = m_orbitStartYaw + (m_orbitTargetYaw - m_orbitStartYaw) * s;
  224. float newPitch = m_orbitStartPitch + (m_orbitTargetPitch - m_orbitStartPitch) * s;
  225. QVector3D offset = m_position - m_target;
  226. float r = offset.length();
  227. if (r < kTiny) r = kTiny;
  228. float yawRad = qDegreesToRadians(newYaw);
  229. float pitchRad = qDegreesToRadians(newPitch);
  230. QVector3D newDir(std::sin(yawRad) * std::cos(pitchRad),
  231. std::sin(pitchRad),
  232. std::cos(yawRad) * std::cos(pitchRad));
  233. QVector3D fwd = safeNormalize(newDir, m_front);
  234. m_position = m_target - fwd * r;
  235. clampAboveGround();
  236. orthonormalize((m_target - m_position), m_front, m_right, m_up);
  237. if (t >= 1.0f) {
  238. m_orbitPending = false;
  239. }
  240. }
  241. bool Camera::screenToGround(float sx, float sy, float screenW, float screenH,
  242. QVector3D &outWorld) const {
  243. if (screenW <= 0 || screenH <= 0) return false;
  244. if (!finite(sx) || !finite(sy)) return false;
  245. float x = (2.0f * sx / screenW) - 1.0f;
  246. float y = 1.0f - (2.0f * sy / screenH);
  247. bool ok = false;
  248. QMatrix4x4 invVP = (getProjectionMatrix() * getViewMatrix()).inverted(&ok);
  249. if (!ok) return false;
  250. QVector4D nearClip(x, y, 0.0f, 1.0f);
  251. QVector4D farClip (x, y, 1.0f, 1.0f);
  252. QVector4D nearWorld4 = invVP * nearClip;
  253. QVector4D farWorld4 = invVP * farClip;
  254. if (std::abs(nearWorld4.w()) < kEps || std::abs(farWorld4.w()) < kEps) return false;
  255. QVector3D rayOrigin = (nearWorld4 / nearWorld4.w()).toVector3D();
  256. QVector3D rayEnd = (farWorld4 / farWorld4.w()).toVector3D();
  257. if (!finite(rayOrigin) || !finite(rayEnd)) return false;
  258. QVector3D rayDir = safeNormalize(rayEnd - rayOrigin, QVector3D(0, -1, 0));
  259. if (std::abs(rayDir.y()) < kEps) return false;
  260. float t = (m_groundY - rayOrigin.y()) / rayDir.y();
  261. if (!finite(t) || t < 0.0f) return false;
  262. outWorld = rayOrigin + rayDir * t;
  263. return finite(outWorld);
  264. }
  265. bool Camera::worldToScreen(const QVector3D &world, int screenW, int screenH,
  266. QPointF &outScreen) const {
  267. if (screenW <= 0 || screenH <= 0) return false;
  268. if (!finite(world)) return false;
  269. QVector4D clip = getProjectionMatrix() * getViewMatrix() * QVector4D(world, 1.0f);
  270. if (std::abs(clip.w()) < kEps) return false;
  271. QVector3D ndc = (clip / clip.w()).toVector3D();
  272. if (!qIsFinite(ndc.x()) || !qIsFinite(ndc.y()) || !qIsFinite(ndc.z())) return false;
  273. if (ndc.z() < -1.0f || ndc.z() > 1.0f) return false;
  274. float sx = (ndc.x() * 0.5f + 0.5f) * float(screenW);
  275. float sy = (1.0f - (ndc.y() * 0.5f + 0.5f)) * float(screenH);
  276. outScreen = QPointF(sx, sy);
  277. return qIsFinite(sx) && qIsFinite(sy);
  278. }
  279. void Camera::updateFollow(const QVector3D &targetCenter) {
  280. if (!m_followEnabled) return;
  281. if (!finite(targetCenter)) return;
  282. if (m_followOffset.lengthSquared() < 1e-5f) {
  283. m_followOffset = m_position - m_target; // initialize lazily
  284. }
  285. QVector3D desiredPos = targetCenter + m_followOffset;
  286. QVector3D newPos =
  287. (m_followLerp >= 0.999f)
  288. ? desiredPos
  289. : (m_position + (desiredPos - m_position) * std::clamp(m_followLerp, 0.0f, 1.0f));
  290. if (!finite(newPos)) return;
  291. m_target = targetCenter;
  292. m_position = newPos;
  293. clampAboveGround();
  294. orthonormalize((m_target - m_position), m_front, m_right, m_up);
  295. }
  296. void Camera::setRTSView(const QVector3D &center, float distance, float angle,
  297. float yawDeg) {
  298. if (!finite(center) || !finite(distance) || !finite(angle) || !finite(yawDeg)) return;
  299. m_target = center;
  300. distance = std::max(distance, 0.01f);
  301. float pitchRad = qDegreesToRadians(angle);
  302. float yawRad = qDegreesToRadians(yawDeg);
  303. float y = distance * qSin(pitchRad);
  304. float horiz= distance * qCos(pitchRad);
  305. float x = std::sin(yawRad) * horiz;
  306. float z = std::cos(yawRad) * horiz;
  307. m_position = center + QVector3D(x, y, z);
  308. QVector3D f = (m_target - m_position);
  309. orthonormalize(f, m_front, m_right, m_up);
  310. clampAboveGround();
  311. }
  312. void Camera::setTopDownView(const QVector3D &center, float distance) {
  313. if (!finite(center) || !finite(distance)) return;
  314. m_target = center;
  315. m_position = center + QVector3D(0, std::max(distance, 0.01f), 0);
  316. m_up = QVector3D(0, 0, -1);
  317. m_front = safeNormalize((m_target - m_position), QVector3D(0,0,1));
  318. updateVectors();
  319. clampAboveGround();
  320. }
  321. QMatrix4x4 Camera::getViewMatrix() const {
  322. QMatrix4x4 view;
  323. view.lookAt(m_position, m_target, m_up);
  324. return view;
  325. }
  326. QMatrix4x4 Camera::getProjectionMatrix() const {
  327. QMatrix4x4 projection;
  328. if (m_isPerspective) {
  329. // perspective() assumes sane inputs — we enforce those in setters
  330. projection.perspective(m_fov, m_aspect, m_nearPlane, m_farPlane);
  331. } else {
  332. // get local copies because this method is const and clampOrthoBox
  333. // expects non-const references
  334. float left = m_orthoLeft;
  335. float right = m_orthoRight;
  336. float bottom = m_orthoBottom;
  337. float top = m_orthoTop;
  338. clampOrthoBox(left, right, bottom, top);
  339. projection.ortho(left, right, bottom, top, m_nearPlane, m_farPlane);
  340. }
  341. return projection;
  342. }
  343. QMatrix4x4 Camera::getViewProjectionMatrix() const {
  344. return getProjectionMatrix() * getViewMatrix();
  345. }
  346. float Camera::getDistance() const { return (m_position - m_target).length(); }
  347. float Camera::getPitchDeg() const {
  348. QVector3D off = m_position - m_target;
  349. QVector3D dir = -off;
  350. if (dir.lengthSquared() < 1e-6f) return 0.0f;
  351. float lenXZ = std::sqrt(dir.x() * dir.x() + dir.z() * dir.z());
  352. float pitchRad = std::atan2(dir.y(), lenXZ);
  353. return qRadiansToDegrees(pitchRad);
  354. }
  355. void Camera::updateVectors() {
  356. QVector3D f = (m_target - m_position);
  357. orthonormalize(f, m_front, m_right, m_up);
  358. }
  359. void Camera::clampAboveGround() {
  360. if (!qIsFinite(m_position.y())) return;
  361. if (m_position.y() < m_groundY + m_minHeight) {
  362. m_position.setY(m_groundY + m_minHeight);
  363. }
  364. auto &vis = Game::Map::VisibilityService::instance();
  365. if (vis.isInitialized()) {
  366. const float tile = vis.getTileSize();
  367. const float halfW = vis.getWidth() * 0.5f - 0.5f;
  368. const float halfH = vis.getHeight() * 0.5f - 0.5f;
  369. if (tile > 0.0f && halfW >= 0.0f && halfH >= 0.0f) {
  370. const float minX = -halfW * tile;
  371. const float maxX = halfW * tile;
  372. const float minZ = -halfH * tile;
  373. const float maxZ = halfH * tile;
  374. m_position.setX(std::clamp(m_position.x(), minX, maxX));
  375. m_position.setZ(std::clamp(m_position.z(), minZ, maxZ));
  376. m_target.setX(std::clamp(m_target.x(), minX, maxX));
  377. m_target.setZ(std::clamp(m_target.z(), minZ, maxZ));
  378. }
  379. }
  380. }
  381. void Camera::computeYawPitchFromOffset(const QVector3D &off, float &yawDeg,
  382. float &pitchDeg) const {
  383. QVector3D dir = -off;
  384. if (dir.lengthSquared() < 1e-6f) {
  385. yawDeg = 0.f; pitchDeg = 0.f; return;
  386. }
  387. float yaw = qRadiansToDegrees(std::atan2(dir.x(), dir.z()));
  388. float lenXZ = std::sqrt(dir.x() * dir.x() + dir.z() * dir.z());
  389. float pitch = qRadiansToDegrees(std::atan2(dir.y(), lenXZ));
  390. yawDeg = yaw;
  391. pitchDeg = pitch;
  392. }
  393. } // namespace Render::GL