Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
debug_view_controller.cpp
Go to the documentation of this file.
1// debug_view_controller.cpp — デバッグ表示の操作。考え方は debug_view_controller.h。
3
4#include <algorithm>
5#include <array>
6#include <format>
7#include <utility>
8
9#include "core/log.h"
10
11namespace bicameral::render {
12 namespace {
13
14 constexpr uint32_t SLICE_FAST_STEP = 8; // Shift を押しながら断面を動かす幅
15
16 // 覗き窓で潜った段 k のカメラの距離 = これ × 2^-k(セル)。画面の高さに段のブロックが約 2.5 個入る
17 constexpr float PEEK_BASE_DISTANCE = 24.0f;
18 constexpr uint32_t KEY_PAGE_UP = 0x21; // VK_PRIOR
19 constexpr uint32_t KEY_PAGE_DOWN = 0x22; // VK_NEXT
20
21 std::string_view ModeName(DebugViewMode mode) {
22 switch (mode) {
23 case DebugViewMode::Volume: return "ボリューム";
24 case DebugViewMode::MaximumProjection: return "最大値の投影";
25 case DebugViewMode::Slice: return "断面";
26 }
27
28 return "?";
29 }
30
31 std::string_view QuantityName(DebugViewQuantity quantity) {
32 switch (quantity) {
33 case DebugViewQuantity::Temperature: return "温度";
34 case DebugViewQuantity::OxygenDepletion: return "O2 の減り";
35 case DebugViewQuantity::CarbonDioxide: return "CO2";
36 case DebugViewQuantity::Carbon: return "炭";
37 }
38
39 return "?";
40 }
41
42 constexpr std::array<char, 3> AXIS_NAMES = {'x', 'y', 'z'};
43
44 std::array<float, 4> ToFloat4(Vector3 vector) {
45 return {vector.x, vector.y, vector.z, 0.0f};
46 }
47
48 } // namespace
49
51 const OrbitCameraState& camera)
52 : m_gridSize(gridSize), m_settings(settings), m_camera(camera) {
53 m_settings.sliceAxis = std::min(m_settings.sliceAxis, 2u);
54 m_settings.slicePosition = std::min(m_settings.slicePosition, gridSize - 1);
55 }
56
57 std::vector<CellCoordinate> DebugViewController::HandleInput(std::span<const InputEvent> events, uint32_t width,
58 uint32_t height) {
59 std::vector<CellCoordinate> pokes;
60 for (const InputEvent& event : events) {
61 if (event.kind == InputKind::KeyDown)
62 HandleKey(event, width, height);
63 else if (event.kind == InputKind::Wheel)
64 m_camera.Zoom(event.wheelSteps);
65 else
66 HandlePointer(event, width, height, pokes);
67 }
68
69 return pokes;
70 }
71
72 // --- ポインタ ---
73
74 void DebugViewController::HandlePointer(const InputEvent& event, uint32_t width, uint32_t height,
75 std::vector<CellCoordinate>& pokes) {
76 const bool down = event.kind == InputKind::ButtonDown;
77 if (event.kind == InputKind::PointerMove) {
78 const auto deltaX = static_cast<float>(event.x - m_lastX);
79 const auto deltaY = static_cast<float>(event.y - m_lastY);
80 if (m_rotating)
81 m_camera.Orbit(deltaX, deltaY);
82
83 if (m_panning)
84 m_camera.Pan(deltaX, deltaY, height);
85 } else if (event.button == PointerButton::Right)
86 m_rotating = down;
87 else if (event.button == PointerButton::Middle)
88 m_panning = down;
89 else if (down) {
90 // 左クリック: 画素の中心を通る光線が断面に当たったセルをつつく。Shift なら光線のまま物を押す(当たる物は GPU が決める)
91 const CameraRay ray = RayThroughPixel(m_camera.Basis(width, height), static_cast<float>(event.x) + 0.5f,
92 static_cast<float>(event.y) + 0.5f, width, height);
93 if (event.shift)
94 m_pushRays.push_back(ray);
95
96 const auto cell = PickSliceCell(ray, m_settings.sliceAxis, m_settings.slicePosition, m_gridSize);
97 if (cell && !event.shift)
98 pokes.push_back(*cell);
99 }
100
101 m_lastX = event.x;
102 m_lastY = event.y;
103 }
104
105 // --- キー ---
106
107 void DebugViewController::HandleKey(const InputEvent& event, uint32_t width, uint32_t height) {
108 if (event.key == 'P' || event.key == KEY_PAGE_UP || event.key == KEY_PAGE_DOWN) {
109 HandlePeekKey(event, width, height);
110 return;
111 }
112
113 const DebugViewSettings before = m_settings;
114 const uint32_t step = event.shift ? SLICE_FAST_STEP : 1;
115 switch (event.key) {
116 case '1': m_settings.mode = DebugViewMode::Volume; break;
117 case '2': m_settings.mode = DebugViewMode::MaximumProjection; break;
118 case '3': m_settings.mode = DebugViewMode::Slice; break;
119 case 'X': m_settings.sliceAxis = 0; break;
120 case 'Y': m_settings.sliceAxis = 1; break;
121 case 'Z': m_settings.sliceAxis = 2; break;
122 case 'Q': m_settings.slicePosition -= std::min(step, m_settings.slicePosition); break;
123 case 'E': m_settings.slicePosition = std::min(m_settings.slicePosition + step, m_gridSize - 1); break;
124 case 'B': m_settings.showActiveBlocks = !m_settings.showActiveBlocks; break;
125 case 'L': m_settings.logarithmic = !m_settings.logarithmic; break;
126 case 'C':
127 m_settings.quantity = static_cast<DebugViewQuantity>((static_cast<uint32_t>(m_settings.quantity) + 1) %
129 break;
130 case 'O':
131 m_camera.ToggleProjection();
132 Log(Channel::Render, Level::Info, "カメラ: {}", m_camera.State().orthographic ? "平行投影" : "透視");
133 return;
134 case 'R':
135 m_camera.Reset();
136 Log(Channel::Render, Level::Info, "カメラを戻した");
137 return;
138 case 'T': m_traceRequested = true; return;
139 default: return;
140 }
141
142 const bool changed = before.mode != m_settings.mode || before.sliceAxis != m_settings.sliceAxis ||
143 before.slicePosition != m_settings.slicePosition ||
144 before.showActiveBlocks != m_settings.showActiveBlocks ||
145 before.logarithmic != m_settings.logarithmic || before.quantity != m_settings.quantity;
146
147 if (changed)
148 Log(Channel::Render, Level::Info, "表示: {}", Describe());
149 }
150
151 // --- 覗き窓 ---
152
153 // P: 最後のポインタの位置の光線が断面に当たったセルを覗く(Shift + P でやめる)。PageDown・PageUp: 潜る・浮かぶ
154 void DebugViewController::HandlePeekKey(const InputEvent& event, uint32_t width, uint32_t height) {
155 if (event.key == 'P' && event.shift) {
157 m_peek.peeking = false;
158 Log(Channel::Render, Level::Info, "覗き窓: やめた");
159 return;
160 }
161
162 if (event.key == 'P') {
163 const CameraRay ray = RayThroughPixel(m_camera.Basis(width, height), static_cast<float>(m_lastX) + 0.5f,
164 static_cast<float>(m_lastY) + 0.5f, width, height);
165 const auto cell = PickSliceCell(ray, m_settings.sliceAxis, m_settings.slicePosition, m_gridSize);
166 if (cell)
167 Peek(*cell, m_peek.depth);
168
169 return;
170 }
171
172 if (!m_peek.peeking)
173 return;
174
175 const uint32_t before = m_peek.depth;
176 if (event.key == KEY_PAGE_DOWN)
177 m_peek.depth = std::min(m_peek.depth + 1, PEEK_MAX_DEPTH);
178 else
179 m_peek.depth -= std::min(m_peek.depth, 1u);
180
181 if (m_peek.depth == before)
182 return;
183
184 FocusOnPeek();
185 Log(Channel::Render, Level::Info, "覗き窓: 段 {}(1 セル = {} mm)", m_peek.depth, 500.0 / (1u << m_peek.depth));
186 }
187
188 void DebugViewController::Peek(CellCoordinate cell, uint32_t depth) {
189 m_peekChanged = m_peekChanged || !m_peek.peeking || m_peek.cell != cell;
190 m_peek = {.peeking = true, .cell = cell, .depth = std::min(depth, PEEK_MAX_DEPTH)};
191 FocusOnPeek();
192 Log(Channel::Render, Level::Info, "覗き窓: ({}, {}, {}) を覗く・段 {}", cell.x, cell.y, cell.z, m_peek.depth);
193 }
194
195 // 潜った段 k のブロックの真ん中に寄る(距離 PEEK_BASE_DISTANCE × 2^-k)。段 0 は覗いているセルの真ん中。
196 // 段 k のブロックは、点(セルの真ん中 = c × 2^k + 2^(k-1))を含む 8 の倍数に揃ったブロック
197 // (子の原点 = 2 × (親の原点 + 4 × 八分の一) なので、どの段の原点も 8 の倍数。multires.hlsli の MrChildOrigin・ADR-0015)
199 const uint32_t depth = m_peek.depth;
200 const auto scale = static_cast<float>(1u << depth);
201 const auto center = [depth, scale](uint32_t cell) {
202 if (depth == 0)
203 return static_cast<float>(cell) + 0.5f;
204
205 const uint64_t point = (uint64_t{cell} << depth) + (uint64_t{1} << (depth - 1));
206 const uint64_t blockCenter = (point & ~uint64_t{7}) + 4;
207
208 return static_cast<float>(blockCenter) / scale;
209 };
210
211 m_camera.Focus({center(m_peek.cell.x), center(m_peek.cell.y), center(m_peek.cell.z)},
212 PEEK_BASE_DISTANCE / scale);
213 }
214
216 const bool changed = m_peekChanged;
217 m_peekChanged = false;
218
219 return changed;
220 }
221
223 const bool requested = m_traceRequested;
224 m_traceRequested = false;
225
226 return requested;
227 }
228
229 std::vector<CameraRay> DebugViewController::TakePushRays() {
230 return std::exchange(m_pushRays, {});
231 }
232
233 // --- 描画へ ---
234
235 ProbeViewConstants DebugViewController::Constants(uint32_t extractionIndex, uint32_t width, uint32_t height) const {
236 const CameraBasis basis = m_camera.Basis(width, height);
237 uint32_t flags = 0;
238 if (m_settings.showActiveBlocks)
240
241 if (m_settings.logarithmic)
242 flags |= VIEW_FLAG_LOGARITHMIC;
243
244 flags |= static_cast<uint32_t>(m_settings.quantity) << VIEW_QUANTITY_SHIFT;
245 if (m_peek.peeking)
246 flags |= m_peek.depth << VIEW_PEEK_DEPTH_SHIFT;
247
248 return {.extractionIndex = extractionIndex,
249 .width = width,
250 .height = height,
251 .flags = flags,
252 .mode = static_cast<uint32_t>(m_settings.mode),
253 .sliceAxis = m_settings.sliceAxis,
254 .slicePosition = m_settings.slicePosition,
255 .orthographic = basis.orthographic ? 1u : 0u,
256 .position = ToFloat4(basis.position),
257 .forward = ToFloat4(basis.forward),
258 .right = ToFloat4(basis.right),
259 .up = ToFloat4(basis.up)};
260 }
261
262 std::string DebugViewController::Describe() const {
263 return std::format("{} 断面 {} = {} 活性なブロック {} 色 {}({})", ModeName(m_settings.mode),
264 AXIS_NAMES[m_settings.sliceAxis], m_settings.slicePosition,
265 m_settings.showActiveBlocks ? "あり" : "なし", QuantityName(m_settings.quantity),
266 m_settings.logarithmic ? "対数" : "線形");
267 }
268
269 bool ParseDebugViewMode(std::string_view name, DebugViewMode& mode) {
270 if (name == "volume")
272 else if (name == "mip")
274 else if (name == "slice")
276 else
277 return false;
278
279 return true;
280 }
281
282} // namespace bicameral::render
std::vector< CellCoordinate > HandleInput(std::span< const InputEvent > events, uint32_t width, uint32_t height)
void HandlePeekKey(const InputEvent &event, uint32_t width, uint32_t height)
void HandlePointer(const InputEvent &event, uint32_t width, uint32_t height, std::vector< CellCoordinate > &pokes)
void HandleKey(const InputEvent &event, uint32_t width, uint32_t height)
void Peek(CellCoordinate cell, uint32_t depth)
DebugViewController(uint32_t gridSize, const DebugViewSettings &settings, const OrbitCameraState &camera)
ProbeViewConstants Constants(uint32_t extractionIndex, uint32_t width, uint32_t height) const
constexpr uint32_t VIEW_FLAG_LOGARITHMIC
constexpr uint32_t PEEK_MAX_DEPTH
CameraRay RayThroughPixel(const CameraBasis &basis, float pixelX, float pixelY, uint32_t width, uint32_t height)
constexpr uint32_t VIEW_PEEK_DEPTH_SHIFT
bool ParseDebugViewMode(std::string_view name, DebugViewMode &mode)
std::optional< CellCoordinate > PickSliceCell(const CameraRay &ray, uint32_t axis, uint32_t plane, uint32_t gridSize)
constexpr uint32_t VIEW_QUANTITY_SHIFT
constexpr uint32_t VIEW_FLAG_ACTIVE_BLOCKS
constexpr uint32_t DEBUG_VIEW_QUANTITY_COUNT
void Log(Channel channel, Level level, FormatWithLocation< std::type_identity_t< Args >... > pattern, Args &&... args)
Definition log.h:140
InputKind kind
Definition input.h:23
PointerButton button
Definition input.h:26