Skip to main content

CameraWidget.ixx File

ImGui widget for editing ECS camera and look-at components. More...

Included Headers

#include <string> #include <vector> #include "imgui.h" #include <helios.math.utils> #include <helios.math.types> #include <helios.engine.spatial.components.UpVector3DComponent> #include <helios.engine.spatial.components.TargetPosition3DComponent> #include <helios.engine.scene.components.CameraBindingComponent> #include <helios.engine.scene.components.PerspectiveCameraComponent> #include <helios.engine.rendering.viewport.types.ViewportHandle> #include <helios.engine.runtime.world.types.GameObjectHandle> #include <helios.engine.core.types.ComponentTypeTags> #include <helios.engine.spatial.components.Position3DComponent> #include <helios.engine.runtime.world.GameWorld> #include <helios.engine.core.components.DebugNameComponent> #include <helios.imgui.ImGuiWidget>

Namespaces Index

namespacehelios
namespaceimgui
namespacewidgets
namespacetypes
namespacecomponents

Classes Index

classCameraWidget

ECS-driven ImGui camera editor for viewport-bound cameras. More...

structViewportCameraEntry
structCameraSnapshot

Description

ImGui widget for editing ECS camera and look-at components.

File Listing

The file content with the documentation metadata removed is:

1
5module;
6
7#include <string>
8#include <vector>
9
10#include "imgui.h"
11
12export module helios.imgui.widgets.CameraWidget;
13
14import helios.imgui.ImGuiWidget;
15
16import helios.engine.core.components.DebugNameComponent;
17
18import helios.engine.runtime.world.GameWorld;
19import helios.engine.runtime.world.types.GameObjectHandle;
20import helios.engine.rendering.viewport.types.ViewportHandle;
21
22import helios.engine.scene.components.PerspectiveCameraComponent;
23import helios.engine.scene.components.CameraBindingComponent;
24import helios.engine.spatial.components.Position3DComponent;
25import helios.engine.spatial.components.TargetPosition3DComponent;
26import helios.engine.spatial.components.UpVector3DComponent;
27
28import helios.math.types;
29import helios.math.utils;
30import helios.engine.core.types.ComponentTypeTags;
31
32using namespace helios::engine::core::types;
33using namespace helios::engine::core::components;
34export namespace helios::imgui::widgets {
35
43 class CameraWidget : public ImGuiWidget {
44
45 using GameWorld = helios::engine::runtime::world::GameWorld;
46 using GameObjectHandle = helios::engine::runtime::world::types::GameObjectHandle;
47 using ViewportHandle = helios::engine::rendering::viewport::types::ViewportHandle;
48
49 using ViewportCameraBindingComponent = helios::engine::scene::components::CameraBindingComponent<ViewportHandle>;
50 using PerspectiveCameraComponent = helios::engine::scene::components::PerspectiveCameraComponent<GameObjectHandle>;
51 using Position3DComponent = helios::engine::spatial::components::Position3DComponent<GameObjectHandle, Local>;
52 using TargetPosition3DComponent = helios::engine::spatial::components::TargetPosition3DComponent<GameObjectHandle, World>;
53 using UpVector3DComponent = helios::engine::spatial::components::UpVector3DComponent<GameObjectHandle>;
54
55 struct ViewportCameraEntry {
56 ViewportHandle viewportHandle{};
57 GameObjectHandle cameraHandle{};
58 std::string label;
59 };
60
61 struct CameraSnapshot {
62 bool valid = false;
63 bool hasPosition = false;
64 bool hasTarget = false;
65 bool hasUp = false;
66 bool hasPerspective = false;
67
68 helios::math::vec3f position{0.0f, 0.0f, 1.0f};
69 helios::math::vec3f target{0.0f, 0.0f, 0.0f};
70 helios::math::vec3f up{0.0f, 1.0f, 0.0f};
71
72 float fovYDegrees = 90.0f;
73 float aspectRatio = 16.0f / 9.0f;
74 float zNear = 0.1f;
75 float zFar = 1000.0f;
76 };
77
78 GameWorld* gameWorld_ = nullptr;
79 GameObjectHandle cameraHandle_{};
80 std::vector<ViewportCameraEntry> viewportCameraEntries_{};
81 int selectedViewportCameraIndex_ = -1;
82
83 helios::math::vec3f tempPosition_{0.0f, 0.0f, 1.0f};
84 helios::math::vec3f tempTarget_{0.0f, 0.0f, 0.0f};
85 helios::math::vec3f tempUp_{0.0f, 1.0f, 0.0f};
86
87 float tempFovDegrees_ = 90.0f;
88 float tempAspectRatio_ = 16.0f / 9.0f;
89 float tempZNear_ = 0.1f;
90 float tempZFar_ = 1000.0f;
91
92 bool syncedFromEntity_ = false;
93 CameraSnapshot resetSnapshot_{};
94
95 [[nodiscard]] std::string makeViewportCameraLabel(
96 const ViewportHandle viewportHandle,
97 const GameObjectHandle cameraHandle
98 ) {
99 auto viewportEntity = gameWorld_->find(viewportHandle);
100 auto cameraEntity = gameWorld_->find(cameraHandle);
101
102 auto* viewportDebugNameCmp = viewportEntity ? viewportEntity->template get<DebugNameComponent<ViewportHandle>>() : nullptr;
103 auto* cameraDebugNameCmp = cameraEntity ? cameraEntity->template get<DebugNameComponent<GameObjectHandle>>() : nullptr;
104
105 return (viewportDebugNameCmp ? viewportDebugNameCmp->value : "Viewport " + std::to_string(viewportHandle.entityId))
106 + " -> " + (cameraDebugNameCmp ? cameraDebugNameCmp->value : "Camera " + std::to_string(cameraHandle.entityId));
107 }
108
109 void refreshViewportCameraEntries() {
110 const auto previousCameraHandle = cameraHandle_;
111 ViewportHandle previousViewportHandle{};
112 bool hasPreviousViewport = false;
113 if (selectedViewportCameraIndex_ >= 0
114 && selectedViewportCameraIndex_ < static_cast<int>(viewportCameraEntries_.size())) {
115 previousViewportHandle = viewportCameraEntries_[selectedViewportCameraIndex_].viewportHandle;
116 hasPreviousViewport = true;
117 }
118
119 viewportCameraEntries_.clear();
120
121 if (!gameWorld_) {
122 selectedViewportCameraIndex_ = -1;
123 cameraHandle_ = {};
124 syncedFromEntity_ = false;
125 return;
126 }
127
128 for (auto [viewportEntity, cameraBinding] : gameWorld_->template view<ViewportHandle, ViewportCameraBindingComponent>()) {
129 const auto viewportHandle = viewportEntity.handle();
130 const auto boundCameraHandle = cameraBinding->targetHandle();
131
132 viewportCameraEntries_.push_back(ViewportCameraEntry{
133 viewportHandle,
134 boundCameraHandle,
135 makeViewportCameraLabel(viewportHandle, boundCameraHandle)
136 });
137 }
138
139 if (viewportCameraEntries_.empty()) {
140 selectedViewportCameraIndex_ = -1;
141 cameraHandle_ = {};
142 syncedFromEntity_ = false;
143 return;
144 }
145
146 int matchingIndex = -1;
147 if (hasPreviousViewport) {
148 for (int i = 0; i < static_cast<int>(viewportCameraEntries_.size()); ++i) {
149 if (viewportCameraEntries_[i].viewportHandle == previousViewportHandle) {
150 matchingIndex = i;
151 break;
152 }
153 }
154 }
155
156 if (matchingIndex < 0) {
157 for (int i = 0; i < static_cast<int>(viewportCameraEntries_.size()); ++i) {
158 if (viewportCameraEntries_[i].cameraHandle == previousCameraHandle) {
159 matchingIndex = i;
160 break;
161 }
162 }
163 }
164
165 if (matchingIndex < 0) {
166 matchingIndex = 0;
167 }
168
169 if (selectedViewportCameraIndex_ != matchingIndex) {
170 selectedViewportCameraIndex_ = matchingIndex;
171 cameraHandle_ = viewportCameraEntries_[selectedViewportCameraIndex_].cameraHandle;
172 syncedFromEntity_ = false;
173 }
174 }
175
176 void selectViewportCameraIndex(const int index) {
177 if (index < 0 || index >= static_cast<int>(viewportCameraEntries_.size())) {
178 return;
179 }
180
181 if (selectedViewportCameraIndex_ == index) {
182 return;
183 }
184
185 selectedViewportCameraIndex_ = index;
186 cameraHandle_ = viewportCameraEntries_[selectedViewportCameraIndex_].cameraHandle;
187 syncedFromEntity_ = false;
188 }
189
190 template<typename TEntity>
191 [[nodiscard]] CameraSnapshot captureSnapshot(const TEntity& entity) const noexcept {
192 CameraSnapshot snapshot{};
193 snapshot.valid = true;
194
195 if (const auto* position = entity.template get<Position3DComponent>()) {
196 snapshot.hasPosition = true;
197 snapshot.position = position->value();
198 }
199
200 if (const auto* target = entity.template get<TargetPosition3DComponent>()) {
201 snapshot.hasTarget = true;
202 snapshot.target = target->value();
203 }
204
205 if (const auto* up = entity.template get<UpVector3DComponent>()) {
206 snapshot.hasUp = true;
207 snapshot.up = up->value();
208 }
209
210 if (const auto* camera = entity.template get<PerspectiveCameraComponent>()) {
211 snapshot.hasPerspective = true;
212 snapshot.fovYDegrees = helios::math::degrees(camera->fovY());
213 snapshot.aspectRatio = camera->aspectRatio();
214 snapshot.zNear = camera->zNear();
215 snapshot.zFar = camera->zFar();
216 }
217
218 return snapshot;
219 }
220
221 template<typename TEntity>
222 void syncFromEntity(TEntity& entity) noexcept {
223 if (auto* position = entity.template get<Position3DComponent>()) {
224 tempPosition_ = position->value();
225 }
226
227 if (auto* target = entity.template get<TargetPosition3DComponent>()) {
228 tempTarget_ = target->value();
229 }
230
231 if (auto* up = entity.template get<UpVector3DComponent>()) {
232 tempUp_ = up->value();
233 }
234
235 if (auto* camera = entity.template get<PerspectiveCameraComponent>()) {
236 tempFovDegrees_ = helios::math::degrees(camera->fovY());
237 tempAspectRatio_ = camera->aspectRatio();
238 tempZNear_ = camera->zNear();
239 tempZFar_ = camera->zFar();
240 }
241 }
242
243 template<typename TEntity>
244 bool writeProjectionToEntity(TEntity& entity) {
245 if (tempZNear_ <= 0.0f) {
246 tempZNear_ = 0.001f;
247 }
248
249 if (tempZFar_ <= tempZNear_) {
250 tempZFar_ = tempZNear_ + 0.01f;
251 }
252
253 if (tempAspectRatio_ <= 0.0f) {
254 tempAspectRatio_ = 1.0f;
255 }
256
257 auto* camera = entity.template get<PerspectiveCameraComponent>();
258 if (!camera) {
259 return false;
260 }
261
262 camera->setPerspective(
263 helios::math::radians(tempFovDegrees_),
264 tempAspectRatio_,
265 tempZNear_,
266 tempZFar_
267 );
268
269 return true;
270 }
271
272 public:
278 explicit CameraWidget(GameWorld& gameWorld) noexcept : gameWorld_(&gameWorld) {}
279
289 void draw() override {
290 ImGui::SetNextWindowSize(ImVec2(380, 520), ImGuiCond_FirstUseEver);
291
292 if (!ImGui::Begin("Camera ECS", nullptr, ImGuiWindowFlags_NoCollapse)) {
293 ImGui::End();
294 return;
295 }
296
297 if (!gameWorld_) {
298 ImGui::TextDisabled("GameWorld missing.");
299 ImGui::End();
300 return;
301 }
302
303 refreshViewportCameraEntries();
304 if (viewportCameraEntries_.empty()) {
305 ImGui::TextDisabled("No viewport has CameraBindingComponent.");
306 ImGui::End();
307 return;
308 }
309
310 const char* selectedLabel = "<none>";
311 if (selectedViewportCameraIndex_ >= 0 && selectedViewportCameraIndex_ < static_cast<int>(viewportCameraEntries_.size())) {
312 selectedLabel = viewportCameraEntries_[selectedViewportCameraIndex_].label.c_str();
313 }
314
315 if (ImGui::BeginCombo("Viewport / Camera", selectedLabel)) {
316 for (int i = 0; i < static_cast<int>(viewportCameraEntries_.size()); ++i) {
317 const bool isSelected = selectedViewportCameraIndex_ == i;
318 if (ImGui::Selectable(viewportCameraEntries_[i].label.c_str(), isSelected)) {
319 selectViewportCameraIndex(i);
320 }
321 if (isSelected) {
322 ImGui::SetItemDefaultFocus();
323 }
324 }
325 ImGui::EndCombo();
326 }
327
328 auto cameraEntityOptional = gameWorld_->find(cameraHandle_);
329 if (!cameraEntityOptional) {
330 ImGui::TextDisabled("Camera entity not found or stale handle.");
331 ImGui::End();
332 return;
333 }
334
335 auto& cameraEntity = *cameraEntityOptional;
336 if (!syncedFromEntity_) {
337 syncFromEntity(cameraEntity);
338 resetSnapshot_ = captureSnapshot(cameraEntity);
339 syncedFromEntity_ = true;
340 }
341
342 bool positionChanged = false;
343 bool targetChanged = false;
344 bool upChanged = false;
345 bool projectionChanged = false;
346 bool missingPosition = false;
347 bool missingTarget = false;
348 bool missingUp = false;
349 bool missingCamera = false;
350
351 ImGui::SeparatorText("LookAt Components");
352
353 ImGui::Text("Position3DComponent");
354 positionChanged |= ImGui::DragFloat("X##Pos", &tempPosition_[0], 0.1f, -10000.0f, 10000.0f, "%.3f");
355 positionChanged |= ImGui::DragFloat("Y##Pos", &tempPosition_[1], 0.1f, -10000.0f, 10000.0f, "%.3f");
356 positionChanged |= ImGui::DragFloat("Z##Pos", &tempPosition_[2], 0.1f, -10000.0f, 10000.0f, "%.3f");
357
358 ImGui::Spacing();
359 ImGui::Text("TargetPosition3DComponent");
360 targetChanged |= ImGui::DragFloat("X##Target", &tempTarget_[0], 0.1f, -10000.0f, 10000.0f, "%.3f");
361 targetChanged |= ImGui::DragFloat("Y##Target", &tempTarget_[1], 0.1f, -10000.0f, 10000.0f, "%.3f");
362 targetChanged |= ImGui::DragFloat("Z##Target", &tempTarget_[2], 0.1f, -10000.0f, 10000.0f, "%.3f");
363
364 ImGui::Spacing();
365 ImGui::Text("UpVector3DComponent");
366 upChanged |= ImGui::DragFloat("X##Up", &tempUp_[0], 0.01f, -1.0f, 1.0f, "%.3f");
367 upChanged |= ImGui::DragFloat("Y##Up", &tempUp_[1], 0.01f, -1.0f, 1.0f, "%.3f");
368 upChanged |= ImGui::DragFloat("Z##Up", &tempUp_[2], 0.01f, -1.0f, 1.0f, "%.3f");
369
370 ImGui::SameLine();
371 if (ImGui::SmallButton("Normalize##Up")) {
372 const float len = tempUp_.length();
373 if (len > 0.0001f) {
374 tempUp_ = tempUp_.normalize();
375 upChanged = true;
376 }
377 }
378
379 ImGui::SeparatorText("PerspectiveCameraComponent");
380 projectionChanged |= ImGui::SliderFloat("FOV Y", &tempFovDegrees_, 10.0f, 170.0f, "%.1f deg");
381 projectionChanged |= ImGui::DragFloat("Aspect", &tempAspectRatio_, 0.01f, 0.1f, 8.0f, "%.3f");
382 projectionChanged |= ImGui::DragFloat("Near", &tempZNear_, 0.001f, 0.001f, 1000.0f, "%.4f");
383 projectionChanged |= ImGui::DragFloat("Far", &tempZFar_, 1.0f, 0.01f, 1000000.0f, "%.2f");
384
385 if (positionChanged) {
386 auto* position = cameraEntity.template get<Position3DComponent>();
387 if (position) {
388 position->setValue(tempPosition_);
389 } else {
390 missingPosition = true;
391 }
392 }
393
394 if (targetChanged) {
395 auto* target = cameraEntity.template get<TargetPosition3DComponent>();
396 if (target) {
397 target->setValue(tempTarget_);
398 } else {
399 missingTarget = true;
400 }
401 }
402
403 if (upChanged) {
404 auto* up = cameraEntity.template get<UpVector3DComponent>();
405 if (up) {
406 up->setValue(tempUp_);
407 } else {
408 missingUp = true;
409 }
410 }
411
412 if (projectionChanged) {
413 missingCamera = !writeProjectionToEntity(cameraEntity);
414 }
415
416 ImGui::Spacing();
417 ImGui::BeginDisabled(!resetSnapshot_.valid);
418 if (ImGui::Button("Reset selected camera")) {
419 if (resetSnapshot_.hasPosition) {
420 auto* position = cameraEntity.template get<Position3DComponent>();
421 if (position) {
422 position->setValue(resetSnapshot_.position);
423 tempPosition_ = resetSnapshot_.position;
424 } else {
425 missingPosition = true;
426 }
427 }
428
429 if (resetSnapshot_.hasTarget) {
430 auto* target = cameraEntity.template get<TargetPosition3DComponent>();
431 if (target) {
432 target->setValue(resetSnapshot_.target);
433 tempTarget_ = resetSnapshot_.target;
434 } else {
435 missingTarget = true;
436 }
437 }
438
439 if (resetSnapshot_.hasUp) {
440 auto* up = cameraEntity.template get<UpVector3DComponent>();
441 if (up) {
442 up->setValue(resetSnapshot_.up);
443 tempUp_ = resetSnapshot_.up;
444 } else {
445 missingUp = true;
446 }
447 }
448
449 if (resetSnapshot_.hasPerspective) {
450 auto* perspective = cameraEntity.template get<PerspectiveCameraComponent>();
451 if (perspective) {
452 perspective->setPerspective(
453 helios::math::radians(resetSnapshot_.fovYDegrees),
454 resetSnapshot_.aspectRatio,
455 resetSnapshot_.zNear,
456 resetSnapshot_.zFar
457 );
458 tempFovDegrees_ = resetSnapshot_.fovYDegrees;
459 tempAspectRatio_ = resetSnapshot_.aspectRatio;
460 tempZNear_ = resetSnapshot_.zNear;
461 tempZFar_ = resetSnapshot_.zFar;
462 } else {
463 missingCamera = true;
464 }
465 }
466 }
467 ImGui::EndDisabled();
468
469 if (missingPosition || missingTarget || missingUp || missingCamera) {
470 ImGui::Separator();
471 ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.2f, 1.0f), "Missing component(s): values could not be written.");
472 if (missingPosition) {
473 ImGui::TextDisabled("- Position3DComponent");
474 }
475 if (missingTarget) {
476 ImGui::TextDisabled("- TargetPosition3DComponent");
477 }
478 if (missingUp) {
479 ImGui::TextDisabled("- UpVector3DComponent");
480 }
481 if (missingCamera) {
482 ImGui::TextDisabled("- PerspectiveCameraComponent");
483 }
484 }
485
486 ImGui::Separator();
487 ImGui::TextDisabled("Handle: entityId=%u versionId=%u", cameraHandle_.entityId, cameraHandle_.versionId);
488
489 ImGui::End();
490 }
491 };
492
493}

Generated via doxygen2docusaurus 2.0.0 by Doxygen 1.9.8.