Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
debug_camera.cpp
Go to the documentation of this file.
1// debug_camera.cpp — デバッグ表示のカメラ。考え方は debug_camera.h。
3
4#include <algorithm>
5#include <array>
6#include <cmath>
7#include <numbers>
8
10 namespace {
11
12 constexpr float ORBIT_DEGREES_PER_PIXEL = 0.3f;
13 constexpr float MAX_PITCH_DEGREES = 89.0f;
14 constexpr float ZOOM_PER_STEP = 0.9f; // ホイール 1 刻みで距離を 0.9 倍
15 constexpr float ORTHOGRAPHIC_BACK_OFF = 1000.0f; // 平行投影の始点を下げる距離(格子の全部が前に来るように)
16 constexpr float PARALLEL_EPSILON = 1e-6f;
17
18 float Radians(float degrees) {
19 return degrees * std::numbers::pi_v<float> / 180.0f;
20 }
21
23 return {left.y * right.z - left.z * right.y, left.z * right.x - left.x * right.z,
24 left.x * right.y - left.y * right.x};
25 }
26
28 const float length = std::sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z);
29 return length > 0.0f ? vector * (1.0f / length) : vector;
30 }
31
32 // 画面の縦の半分が注視点の距離で何セルか
33 float HalfHeightAtTarget(float distance) {
34 return distance * std::tan(Radians(OrbitCamera::VERTICAL_FIELD_OF_VIEW_DEGREES) * 0.5f);
35 }
36
42
43 // 回る軸は −y。forward = cos(pitch)·(sin(yaw), 0, cos(yaw)) + sin(pitch)·(0, 1, 0)
44 // (pitch が正 = 上(−y)から見下ろす = 下(+y)を向く)
46 const float yaw = Radians(state.yawDegrees);
47 const float pitch = Radians(state.pitchDegrees);
48 const Vector3 forward{std::cos(pitch) * std::sin(yaw), std::sin(pitch), std::cos(pitch) * std::cos(yaw)};
49 const Vector3 worldUp{0.0f, -1.0f, 0.0f};
50 const Vector3 right = Normalize(Cross(forward, worldUp));
51
52 return {.forward = forward, .right = right, .up = Cross(right, forward)};
53 }
54
55 } // namespace
56
58 return {left.x + right.x, left.y + right.y, left.z + right.z};
59 }
60
62 return {left.x - right.x, left.y - right.y, left.z - right.z};
63 }
64
65 Vector3 operator*(Vector3 vector, float scale) {
66 return {vector.x * scale, vector.y * scale, vector.z * scale};
67 }
68
69 // --- 操作 ---
70
71 // 右へドラッグすると格子が右へ回る(カメラは左へ)。下へドラッグすると上から見下ろす
72 void OrbitCamera::Orbit(float deltaXPixels, float deltaYPixels) {
73 m_state.yawDegrees -= deltaXPixels * ORBIT_DEGREES_PER_PIXEL;
74 m_state.yawDegrees = std::remainder(m_state.yawDegrees, 360.0f);
75 m_state.pitchDegrees = std::clamp(m_state.pitchDegrees + deltaYPixels * ORBIT_DEGREES_PER_PIXEL,
76 -MAX_PITCH_DEGREES, MAX_PITCH_DEGREES);
77 }
78
79 // 注視点の距離で、ドラッグした画素のぶんだけ中身がポインタについてくるように動かす
80 void OrbitCamera::Pan(float deltaXPixels, float deltaYPixels, uint32_t viewportHeight) {
81 if (viewportHeight == 0)
82 return;
83
84 const Axes axes = CameraAxes(m_state);
85 const float cellsPerPixel = 2.0f * HalfHeightAtTarget(m_state.distance) / static_cast<float>(viewportHeight);
86 m_state.target = m_state.target - axes.right * (deltaXPixels * cellsPerPixel) +
87 axes.up * (deltaYPixels * cellsPerPixel);
88 }
89
90 void OrbitCamera::Zoom(int32_t wheelSteps) {
91 m_state.distance *= std::pow(ZOOM_PER_STEP, static_cast<float>(wheelSteps));
92 m_state.distance = std::clamp(m_state.distance, MIN_DISTANCE, MAX_DISTANCE);
93 }
94
95 void OrbitCamera::Focus(Vector3 target, float distance) {
96 m_state.target = target;
97 m_state.distance = std::clamp(distance, MIN_DISTANCE, MAX_DISTANCE);
98 }
99
100 // --- 描画と拾うための形 ---
101
102 CameraBasis OrbitCamera::Basis(uint32_t width, uint32_t height) const {
103 const Axes axes = CameraAxes(m_state);
104 const float aspect = height > 0 ? static_cast<float>(width) / static_cast<float>(height) : 1.0f;
105 if (m_state.orthographic) {
106 // 注視点の距離の透視と同じ大きさに見える幅
107 const float halfHeight = HalfHeightAtTarget(m_state.distance);
108
109 return {.position = m_state.target - axes.forward * ORTHOGRAPHIC_BACK_OFF,
110 .forward = axes.forward,
111 .right = axes.right * (halfHeight * aspect),
112 .up = axes.up * halfHeight,
113 .orthographic = true};
114 }
115
116 const float halfHeight = std::tan(Radians(VERTICAL_FIELD_OF_VIEW_DEGREES) * 0.5f);
117
118 return {.position = m_state.target - axes.forward * m_state.distance,
119 .forward = axes.forward,
120 .right = axes.right * (halfHeight * aspect),
121 .up = axes.up * halfHeight,
122 .orthographic = false};
123 }
124
125 CameraRay RayThroughPixel(const CameraBasis& basis, float pixelX, float pixelY, uint32_t width, uint32_t height) {
126 const float u = width > 0 ? 2.0f * pixelX / static_cast<float>(width) - 1.0f : 0.0f;
127 const float v = height > 0 ? 1.0f - 2.0f * pixelY / static_cast<float>(height) : 0.0f;
128 const Vector3 offset = basis.right * u + basis.up * v;
129 if (basis.orthographic)
130 return {.origin = basis.position + offset, .direction = basis.forward};
131
132 return {.origin = basis.position, .direction = basis.forward + offset};
133 }
134
135 std::optional<CellCoordinate> PickSliceCell(const CameraRay& ray, uint32_t axis, uint32_t plane,
136 uint32_t gridSize) {
137 if (axis > 2 || plane >= gridSize)
138 return std::nullopt;
139
140 const float along = ray.direction[axis];
141 if (std::abs(along) < PARALLEL_EPSILON)
142 return std::nullopt;
143
144 const float distance = (static_cast<float>(plane) + 0.5f - ray.origin[axis]) / along;
145 if (distance < 0.0f)
146 return std::nullopt;
147
148 const Vector3 hit = ray.origin + ray.direction * distance;
149
150 // 断面の軸は plane そのもの。他の 2 つの軸は格子の中なら切り捨て
151 std::array<uint32_t, 3> cell{};
152 for (uint32_t index = 0; index < 3; ++index) {
153 if (index == axis) {
154 cell[index] = plane;
155 continue;
156 }
157
158 const float value = std::floor(hit[index]);
159 // NaN も外
160 if (!(value >= 0.0f && value < static_cast<float>(gridSize)))
161 return std::nullopt;
162
163 cell[index] = static_cast<uint32_t>(value);
164 }
165
166 return CellCoordinate{.x = cell[0], .y = cell[1], .z = cell[2]};
167 }
168
169 CellCoordinate CellOnSlice(uint32_t axis, uint32_t plane, uint32_t u, uint32_t v) {
170 if (axis == 0)
171 return {.x = plane, .y = u, .z = v};
172
173 if (axis == 1)
174 return {.x = u, .y = plane, .z = v};
175
176 return {.x = u, .y = v, .z = plane};
177 }
178
179} // namespace bicameral::render
CameraBasis Basis(uint32_t width, uint32_t height) const
static constexpr float VERTICAL_FIELD_OF_VIEW_DEGREES
void Focus(Vector3 target, float distance)
void Pan(float deltaXPixels, float deltaYPixels, uint32_t viewportHeight)
static constexpr float MAX_DISTANCE
static constexpr float MIN_DISTANCE
void Orbit(float deltaXPixels, float deltaYPixels)
void Zoom(int32_t wheelSteps)
Vector3 operator+(Vector3 left, Vector3 right)
CellCoordinate CellOnSlice(uint32_t axis, uint32_t plane, uint32_t u, uint32_t v)
Vector3 operator-(Vector3 left, Vector3 right)
Vector3 operator*(Vector3 vector, float scale)
CameraRay RayThroughPixel(const CameraBasis &basis, float pixelX, float pixelY, uint32_t width, uint32_t height)
std::optional< CellCoordinate > PickSliceCell(const CameraRay &ray, uint32_t axis, uint32_t plane, uint32_t gridSize)