camera.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. #include "camera.h"
  2. #include "../../game/map/visibility_service.h"
  3. #include <QtMath>
  4. #include <algorithm>
  5. #include <cmath>
  6. #include <limits>
  7. namespace Render::GL {
  8. namespace {
  9. constexpr float kEps = 1e-6f;
  10. constexpr float kTiny = 1e-4f;
  11. constexpr float kMinDist = 1.0f;
  12. constexpr float kMaxDist = 200.0f;
  13. constexpr float kMinFov = 1.0f;
  14. constexpr float kMaxFov = 89.0f;
  15. constexpr float kMinMarginPercent = 0.03f;
  16. constexpr float kMaxMarginPercent = 0.10f;
  17. constexpr float kBoundarySmoothness = 0.3f;
  18. inline bool finite(const QVector3D &v) {
  19. return qIsFinite(v.x()) && qIsFinite(v.y()) && qIsFinite(v.z());
  20. }
  21. inline bool finite(float v) { return qIsFinite(v); }
  22. inline QVector3D safeNormalize(const QVector3D &v, const QVector3D &fallback,
  23. float eps = kEps) {
  24. if (!finite(v))
  25. return fallback;
  26. float len2 = v.lengthSquared();
  27. if (len2 < eps)
  28. return fallback;
  29. return v / std::sqrt(len2);
  30. }
  31. inline void orthonormalize(const QVector3D &frontIn, QVector3D &frontOut,
  32. QVector3D &rightOut, QVector3D &upOut) {
  33. QVector3D worldUp(0.f, 1.f, 0.f);
  34. QVector3D f = safeNormalize(frontIn, QVector3D(0, 0, -1));
  35. QVector3D u = (std::abs(QVector3D::dotProduct(f, worldUp)) > 1.f - 1e-3f)
  36. ? QVector3D(0, 0, 1)
  37. : worldUp;
  38. QVector3D r = QVector3D::crossProduct(f, u);
  39. if (r.lengthSquared() < kEps)
  40. 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, float &bottom,
  48. float &top) {
  49. if (left == right) {
  50. left -= 0.5f;
  51. right += 0.5f;
  52. } else if (left > right) {
  53. std::swap(left, right);
  54. }
  55. if (bottom == top) {
  56. bottom -= 0.5f;
  57. top += 0.5f;
  58. } else if (bottom > top) {
  59. std::swap(bottom, top);
  60. }
  61. }
  62. inline float calculateDynamicMargin(float baseMargin, float cameraHeight,
  63. float pitchDeg) {
  64. float heightFactor = std::clamp(cameraHeight / 50.0f, 0.5f, 2.0f);
  65. float pitchFactor = std::clamp(1.0f - std::abs(pitchDeg) / 90.0f, 0.5f, 1.5f);
  66. return baseMargin * heightFactor * pitchFactor;
  67. }
  68. inline float smoothApproach(float current, float target, float smoothness) {
  69. if (std::abs(current - target) < kTiny)
  70. return target;
  71. return current +
  72. (target - current) * std::clamp(1.0f - smoothness, 0.01f, 0.99f);
  73. }
  74. } // namespace
  75. Camera::Camera() { updateVectors(); }
  76. void Camera::setPosition(const QVector3D &position) {
  77. if (!finite(position))
  78. return;
  79. m_position = position;
  80. applySoftBoundaries();
  81. QVector3D newFront = (m_target - m_position);
  82. orthonormalize(newFront, m_front, m_right, m_up);
  83. }
  84. void Camera::setTarget(const QVector3D &target) {
  85. if (!finite(target))
  86. return;
  87. m_target = target;
  88. applySoftBoundaries();
  89. QVector3D dir = (m_target - m_position);
  90. if (dir.lengthSquared() < kEps) {
  91. m_target = m_position +
  92. (m_front.lengthSquared() < kEps ? QVector3D(0, 0, -1) : m_front);
  93. dir = (m_target - m_position);
  94. }
  95. orthonormalize(dir, m_front, m_right, m_up);
  96. }
  97. void Camera::setUp(const QVector3D &up) {
  98. if (!finite(up))
  99. return;
  100. QVector3D upN = up;
  101. if (upN.lengthSquared() < kEps)
  102. upN = QVector3D(0, 1, 0);
  103. orthonormalize(m_target - m_position, m_front, m_right, m_up);
  104. }
  105. void Camera::lookAt(const QVector3D &position, const QVector3D &target,
  106. const QVector3D &up) {
  107. if (!finite(position) || !finite(target) || !finite(up))
  108. return;
  109. m_position = position;
  110. m_target = (position == target) ? position + QVector3D(0, 0, -1) : target;
  111. applySoftBoundaries();
  112. QVector3D f = (m_target - m_position);
  113. m_up = up.lengthSquared() < kEps ? QVector3D(0, 1, 0) : up.normalized();
  114. orthonormalize(f, m_front, m_right, m_up);
  115. }
  116. void Camera::setPerspective(float fov, float aspect, float nearPlane,
  117. float farPlane) {
  118. if (!finite(fov) || !finite(aspect) || !finite(nearPlane) ||
  119. !finite(farPlane))
  120. return;
  121. m_isPerspective = true;
  122. m_fov = std::clamp(fov, kMinFov, kMaxFov);
  123. m_aspect = std::max(aspect, 1e-6f);
  124. m_nearPlane = std::max(nearPlane, 1e-4f);
  125. m_farPlane = std::max(farPlane, m_nearPlane + 1e-3f);
  126. }
  127. void Camera::setOrthographic(float left, float right, float bottom, float top,
  128. float nearPlane, float farPlane) {
  129. if (!finite(left) || !finite(right) || !finite(bottom) || !finite(top) ||
  130. !finite(nearPlane) || !finite(farPlane))
  131. return;
  132. m_isPerspective = false;
  133. clampOrthoBox(left, right, bottom, top);
  134. m_orthoLeft = left;
  135. m_orthoRight = right;
  136. m_orthoBottom = bottom;
  137. m_orthoTop = top;
  138. m_nearPlane = std::min(nearPlane, farPlane - 1e-3f);
  139. m_farPlane = std::max(farPlane, m_nearPlane + 1e-3f);
  140. }
  141. void Camera::moveForward(float distance) {
  142. if (!finite(distance))
  143. return;
  144. m_position += m_front * distance;
  145. m_target = m_position + m_front;
  146. applySoftBoundaries();
  147. }
  148. void Camera::moveRight(float distance) {
  149. if (!finite(distance))
  150. return;
  151. m_position += m_right * distance;
  152. m_target = m_position + m_front;
  153. applySoftBoundaries();
  154. }
  155. void Camera::moveUp(float distance) {
  156. if (!finite(distance))
  157. return;
  158. m_position += QVector3D(0, 1, 0) * distance;
  159. m_target = m_position + m_front;
  160. applySoftBoundaries();
  161. }
  162. void Camera::zoom(float delta) {
  163. if (!finite(delta))
  164. return;
  165. if (m_isPerspective) {
  166. m_fov = qBound(kMinFov, m_fov - delta, kMaxFov);
  167. } else {
  168. float scale = 1.0f + delta * 0.1f;
  169. if (!finite(scale) || scale <= 0.05f)
  170. scale = 0.05f;
  171. if (scale > 20.0f)
  172. scale = 20.0f;
  173. m_orthoLeft *= scale;
  174. m_orthoRight *= scale;
  175. m_orthoBottom *= scale;
  176. m_orthoTop *= scale;
  177. clampOrthoBox(m_orthoLeft, m_orthoRight, m_orthoBottom, m_orthoTop);
  178. }
  179. }
  180. void Camera::zoomDistance(float delta) {
  181. if (!finite(delta))
  182. return;
  183. QVector3D offset = m_position - m_target;
  184. float r = offset.length();
  185. if (r < kTiny)
  186. r = kTiny;
  187. float factor = 1.0f - delta * 0.15f;
  188. if (!finite(factor))
  189. factor = 1.0f;
  190. factor = std::clamp(factor, 0.1f, 10.0f);
  191. float newR = std::clamp(r * factor, kMinDist, kMaxDist);
  192. QVector3D dir = safeNormalize(offset, QVector3D(0, 0, 1));
  193. QVector3D newPos = m_target + dir * newR;
  194. m_position = newPos;
  195. applySoftBoundaries();
  196. QVector3D f = (m_target - m_position);
  197. orthonormalize(f, m_front, m_right, m_up);
  198. }
  199. void Camera::rotate(float yaw, float pitch) { orbit(yaw, pitch); }
  200. void Camera::pan(float rightDist, float forwardDist) {
  201. if (!finite(rightDist) || !finite(forwardDist))
  202. return;
  203. QVector3D right = m_right;
  204. QVector3D front = m_front;
  205. front.setY(0.0f);
  206. if (front.lengthSquared() > 0)
  207. front.normalize();
  208. QVector3D delta = right * rightDist + front * forwardDist;
  209. if (!finite(delta))
  210. return;
  211. m_position += delta;
  212. m_target += delta;
  213. applySoftBoundaries(true);
  214. }
  215. void Camera::elevate(float dy) {
  216. if (!finite(dy))
  217. return;
  218. m_position.setY(m_position.y() + dy);
  219. applySoftBoundaries();
  220. }
  221. void Camera::yaw(float degrees) {
  222. if (!finite(degrees))
  223. return;
  224. orbit(degrees, 0.0f);
  225. }
  226. void Camera::orbit(float yawDeg, float pitchDeg) {
  227. if (!finite(yawDeg) || !finite(pitchDeg))
  228. return;
  229. QVector3D offset = m_position - m_target;
  230. float curYaw = 0.f, curPitch = 0.f;
  231. computeYawPitchFromOffset(offset, curYaw, curPitch);
  232. m_orbitStartYaw = curYaw;
  233. m_orbitStartPitch = curPitch;
  234. m_orbitTargetYaw = curYaw + yawDeg;
  235. m_orbitTargetPitch =
  236. qBound(m_pitchMinDeg, curPitch + pitchDeg, m_pitchMaxDeg);
  237. m_orbitTime = 0.0f;
  238. m_orbitPending = true;
  239. }
  240. void Camera::update(float dt) {
  241. if (!m_orbitPending)
  242. return;
  243. if (!finite(dt))
  244. return;
  245. m_orbitTime += std::max(0.0f, dt);
  246. float t = (m_orbitDuration <= 0.0f)
  247. ? 1.0f
  248. : std::clamp(m_orbitTime / m_orbitDuration, 0.0f, 1.0f);
  249. float s = t * t * (3.0f - 2.0f * t);
  250. float newYaw = m_orbitStartYaw + (m_orbitTargetYaw - m_orbitStartYaw) * s;
  251. float newPitch =
  252. m_orbitStartPitch + (m_orbitTargetPitch - m_orbitStartPitch) * s;
  253. QVector3D offset = m_position - m_target;
  254. float r = offset.length();
  255. if (r < kTiny)
  256. r = kTiny;
  257. float yawRad = qDegreesToRadians(newYaw);
  258. float pitchRad = qDegreesToRadians(newPitch);
  259. QVector3D newDir(std::sin(yawRad) * std::cos(pitchRad), std::sin(pitchRad),
  260. std::cos(yawRad) * std::cos(pitchRad));
  261. QVector3D fwd = safeNormalize(newDir, m_front);
  262. m_position = m_target - fwd * r;
  263. applySoftBoundaries();
  264. orthonormalize((m_target - m_position), m_front, m_right, m_up);
  265. if (t >= 1.0f) {
  266. m_orbitPending = false;
  267. }
  268. }
  269. bool Camera::screenToGround(qreal sx, qreal sy, qreal screenW, qreal screenH,
  270. QVector3D &outWorld) const {
  271. if (screenW <= 0 || screenH <= 0)
  272. return false;
  273. if (!qIsFinite(sx) || !qIsFinite(sy))
  274. return false;
  275. double x = (2.0 * sx / screenW) - 1.0;
  276. double y = 1.0 - (2.0 * sy / screenH);
  277. bool ok = false;
  278. QMatrix4x4 invVP = (getProjectionMatrix() * getViewMatrix()).inverted(&ok);
  279. if (!ok)
  280. return false;
  281. QVector4D nearClip(float(x), float(y), 0.0f, 1.0f);
  282. QVector4D farClip(float(x), float(y), 1.0f, 1.0f);
  283. QVector4D nearWorld4 = invVP * nearClip;
  284. QVector4D farWorld4 = invVP * farClip;
  285. if (std::abs(nearWorld4.w()) < kEps || std::abs(farWorld4.w()) < kEps)
  286. return false;
  287. QVector3D rayOrigin = (nearWorld4 / nearWorld4.w()).toVector3D();
  288. QVector3D rayEnd = (farWorld4 / farWorld4.w()).toVector3D();
  289. if (!finite(rayOrigin) || !finite(rayEnd))
  290. return false;
  291. QVector3D rayDir = safeNormalize(rayEnd - rayOrigin, QVector3D(0, -1, 0));
  292. if (std::abs(rayDir.y()) < kEps)
  293. return false;
  294. float t = (m_groundY - rayOrigin.y()) / rayDir.y();
  295. if (!finite(t) || t < 0.0f)
  296. return false;
  297. outWorld = rayOrigin + rayDir * t;
  298. return finite(outWorld);
  299. }
  300. bool Camera::worldToScreen(const QVector3D &world, qreal screenW, qreal screenH,
  301. QPointF &outScreen) const {
  302. if (screenW <= 0 || screenH <= 0)
  303. return false;
  304. if (!finite(world))
  305. return false;
  306. QVector4D clip =
  307. getProjectionMatrix() * getViewMatrix() * QVector4D(world, 1.0f);
  308. if (std::abs(clip.w()) < kEps)
  309. return false;
  310. QVector3D ndc = (clip / clip.w()).toVector3D();
  311. if (!qIsFinite(ndc.x()) || !qIsFinite(ndc.y()) || !qIsFinite(ndc.z()))
  312. return false;
  313. if (ndc.z() < -1.0f || ndc.z() > 1.0f)
  314. return false;
  315. qreal sx = (ndc.x() * 0.5 + 0.5) * screenW;
  316. qreal sy = (1.0 - (ndc.y() * 0.5 + 0.5)) * screenH;
  317. outScreen = QPointF(sx, sy);
  318. return qIsFinite(sx) && qIsFinite(sy);
  319. }
  320. void Camera::updateFollow(const QVector3D &targetCenter) {
  321. if (!m_followEnabled)
  322. return;
  323. if (!finite(targetCenter))
  324. return;
  325. if (m_followOffset.lengthSquared() < 1e-5f) {
  326. m_followOffset = m_position - m_target;
  327. }
  328. QVector3D desiredPos = targetCenter + m_followOffset;
  329. QVector3D newPos =
  330. (m_followLerp >= 0.999f)
  331. ? desiredPos
  332. : (m_position +
  333. (desiredPos - m_position) * std::clamp(m_followLerp, 0.0f, 1.0f));
  334. if (!finite(newPos))
  335. return;
  336. m_target = targetCenter;
  337. m_position = newPos;
  338. applySoftBoundaries();
  339. orthonormalize((m_target - m_position), m_front, m_right, m_up);
  340. }
  341. void Camera::setRTSView(const QVector3D &center, float distance, float angle,
  342. float yawDeg) {
  343. if (!finite(center) || !finite(distance) || !finite(angle) || !finite(yawDeg))
  344. return;
  345. m_target = center;
  346. distance = std::max(distance, 0.01f);
  347. float pitchRad = qDegreesToRadians(angle);
  348. float yawRad = qDegreesToRadians(yawDeg);
  349. float y = distance * qSin(pitchRad);
  350. float horiz = distance * qCos(pitchRad);
  351. float x = std::sin(yawRad) * horiz;
  352. float z = std::cos(yawRad) * horiz;
  353. m_position = center + QVector3D(x, y, z);
  354. QVector3D f = (m_target - m_position);
  355. orthonormalize(f, m_front, m_right, m_up);
  356. applySoftBoundaries();
  357. }
  358. void Camera::setTopDownView(const QVector3D &center, float distance) {
  359. if (!finite(center) || !finite(distance))
  360. return;
  361. m_target = center;
  362. m_position = center + QVector3D(0, std::max(distance, 0.01f), 0);
  363. m_up = QVector3D(0, 0, -1);
  364. m_front = safeNormalize((m_target - m_position), QVector3D(0, 0, 1));
  365. updateVectors();
  366. applySoftBoundaries();
  367. }
  368. QMatrix4x4 Camera::getViewMatrix() const {
  369. QMatrix4x4 view;
  370. view.lookAt(m_position, m_target, m_up);
  371. return view;
  372. }
  373. QMatrix4x4 Camera::getProjectionMatrix() const {
  374. QMatrix4x4 projection;
  375. if (m_isPerspective) {
  376. projection.perspective(m_fov, m_aspect, m_nearPlane, m_farPlane);
  377. } else {
  378. float left = m_orthoLeft;
  379. float right = m_orthoRight;
  380. float bottom = m_orthoBottom;
  381. float top = m_orthoTop;
  382. clampOrthoBox(left, right, bottom, top);
  383. projection.ortho(left, right, bottom, top, m_nearPlane, m_farPlane);
  384. }
  385. return projection;
  386. }
  387. QMatrix4x4 Camera::getViewProjectionMatrix() const {
  388. return getProjectionMatrix() * getViewMatrix();
  389. }
  390. float Camera::getDistance() const { return (m_position - m_target).length(); }
  391. float Camera::getPitchDeg() const {
  392. QVector3D off = m_position - m_target;
  393. QVector3D dir = -off;
  394. if (dir.lengthSquared() < 1e-6f)
  395. return 0.0f;
  396. float lenXZ = std::sqrt(dir.x() * dir.x() + dir.z() * dir.z());
  397. float pitchRad = std::atan2(dir.y(), lenXZ);
  398. return qRadiansToDegrees(pitchRad);
  399. }
  400. void Camera::updateVectors() {
  401. QVector3D f = (m_target - m_position);
  402. orthonormalize(f, m_front, m_right, m_up);
  403. }
  404. void Camera::applySoftBoundaries(bool isPanning) {
  405. if (!qIsFinite(m_position.y())) {
  406. return;
  407. }
  408. if (m_position.y() < m_groundY + m_minHeight) {
  409. m_position.setY(m_groundY + m_minHeight);
  410. }
  411. auto &vis = Game::Map::VisibilityService::instance();
  412. if (!vis.isInitialized()) {
  413. return;
  414. }
  415. const float tile = vis.getTileSize();
  416. const float halfW = vis.getWidth() * 0.5f - 0.5f;
  417. const float halfH = vis.getHeight() * 0.5f - 0.5f;
  418. if (tile <= 0.0f || halfW < 0.0f || halfH < 0.0f) {
  419. return;
  420. }
  421. const float mapMinX = -halfW * tile;
  422. const float mapMaxX = halfW * tile;
  423. const float mapMinZ = -halfH * tile;
  424. const float mapMaxZ = halfH * tile;
  425. float cameraHeight = m_position.y() - m_groundY;
  426. float pitchDeg = getPitchDeg();
  427. float mapWidth = mapMaxX - mapMinX;
  428. float mapDepth = mapMaxZ - mapMinZ;
  429. float baseMarginX =
  430. mapWidth * std::lerp(kMinMarginPercent, kMaxMarginPercent,
  431. std::min(cameraHeight / 50.0f, 1.0f));
  432. float baseMarginZ =
  433. mapDepth * std::lerp(kMinMarginPercent, kMaxMarginPercent,
  434. std::min(cameraHeight / 50.0f, 1.0f));
  435. float marginX = calculateDynamicMargin(baseMarginX, cameraHeight, pitchDeg);
  436. float marginZ = calculateDynamicMargin(baseMarginZ, cameraHeight, pitchDeg);
  437. float extMinX = mapMinX - marginX;
  438. float extMaxX = mapMaxX + marginX;
  439. float extMinZ = mapMinZ - marginZ;
  440. float extMaxZ = mapMaxZ + marginZ;
  441. QVector3D targetToPos = m_position - m_target;
  442. float targetToPosDist = targetToPos.length();
  443. QVector3D positionAdjustment(0, 0, 0);
  444. QVector3D targetAdjustment(0, 0, 0);
  445. if (m_position.x() < extMinX)
  446. positionAdjustment.setX(extMinX - m_position.x());
  447. else if (m_position.x() > extMaxX)
  448. positionAdjustment.setX(extMaxX - m_position.x());
  449. if (m_position.z() < extMinZ)
  450. positionAdjustment.setZ(extMinZ - m_position.z());
  451. else if (m_position.z() > extMaxZ)
  452. positionAdjustment.setZ(extMaxZ - m_position.z());
  453. if (m_target.x() < mapMinX)
  454. targetAdjustment.setX(mapMinX - m_target.x());
  455. else if (m_target.x() > mapMaxX)
  456. targetAdjustment.setX(mapMaxX - m_target.x());
  457. if (m_target.z() < mapMinZ)
  458. targetAdjustment.setZ(mapMinZ - m_target.z());
  459. else if (m_target.z() > mapMaxZ)
  460. targetAdjustment.setZ(mapMaxZ - m_target.z());
  461. if (isPanning) {
  462. if ((positionAdjustment.x() > 0 && m_lastPosition.x() < m_position.x()) ||
  463. (positionAdjustment.x() < 0 && m_lastPosition.x() > m_position.x())) {
  464. positionAdjustment.setX(0);
  465. }
  466. if ((positionAdjustment.z() > 0 && m_lastPosition.z() < m_position.z()) ||
  467. (positionAdjustment.z() < 0 && m_lastPosition.z() > m_position.z())) {
  468. positionAdjustment.setZ(0);
  469. }
  470. }
  471. if (!positionAdjustment.isNull()) {
  472. m_position += positionAdjustment * (isPanning ? 0.7f : kBoundarySmoothness);
  473. }
  474. if (!targetAdjustment.isNull()) {
  475. m_target += targetAdjustment * (isPanning ? 0.7f : kBoundarySmoothness);
  476. if (targetToPosDist > kTiny) {
  477. QVector3D dir = targetToPos.normalized();
  478. m_position = m_target + dir * targetToPosDist;
  479. }
  480. }
  481. m_lastPosition = m_position;
  482. }
  483. void Camera::clampAboveGround() {
  484. if (!qIsFinite(m_position.y())) {
  485. return;
  486. }
  487. if (m_position.y() < m_groundY + m_minHeight) {
  488. m_position.setY(m_groundY + m_minHeight);
  489. }
  490. }
  491. void Camera::computeYawPitchFromOffset(const QVector3D &off, float &yawDeg,
  492. float &pitchDeg) const {
  493. QVector3D dir = -off;
  494. if (dir.lengthSquared() < 1e-6f) {
  495. yawDeg = 0.f;
  496. pitchDeg = 0.f;
  497. return;
  498. }
  499. float yaw = qRadiansToDegrees(std::atan2(dir.x(), dir.z()));
  500. float lenXZ = std::sqrt(dir.x() * dir.x() + dir.z() * dir.z());
  501. float pitch = qRadiansToDegrees(std::atan2(dir.y(), lenXZ));
  502. yawDeg = yaw;
  503. pitchDeg = pitch;
  504. }
  505. } // namespace Render::GL