Renders the camera editor UI and writes changes into ECS components.
349 ImGui::SetNextWindowSize(ImVec2(380, 520), ImGuiCond_FirstUseEver);
350
351 if (!ImGui::Begin("Camera ECS", nullptr, ImGuiWindowFlags_NoCollapse)) {
352 ImGui::End();
353 return;
354 }
355
356 if (!gameWorld_) {
357 ImGui::TextDisabled("GameWorld missing.");
358 ImGui::End();
359 return;
360 }
361
362 refreshViewportCameraEntries();
363
364 if (viewportCameraEntries_.empty()) {
365 ImGui::TextDisabled("No viewport has CameraBindingComponent.");
366 ImGui::End();
367 return;
368 }
369
370 const char* selectedLabel = "<none>";
371 if (selectedViewportCameraIndex_ >= 0
372 && selectedViewportCameraIndex_ < static_cast<int>(viewportCameraEntries_.size())) {
373 selectedLabel = viewportCameraEntries_[selectedViewportCameraIndex_].label.c_str();
374 }
375
376 if (ImGui::BeginCombo("Viewport / Camera", selectedLabel)) {
377 for (int i = 0; i < static_cast<int>(viewportCameraEntries_.size()); ++i) {
378 const bool isSelected = selectedViewportCameraIndex_ == i;
379
380 if (ImGui::Selectable(viewportCameraEntries_[i].label.c_str(), isSelected)) {
381 selectViewportCameraIndex(i);
382 }
383
384 if (isSelected) {
385 ImGui::SetItemDefaultFocus();
386 }
387 }
388
389 ImGui::EndCombo();
390 }
391
392 auto cameraEntityOptional = gameWorld_->find(cameraHandle_);
393 if (!cameraEntityOptional) {
394 ImGui::TextDisabled("Camera entity not found or stale handle.");
395 ImGui::End();
396 return;
397 }
398
399 auto& cameraEntity = *cameraEntityOptional;
400
401 if (!syncedFromEntity_) {
402 syncFromEntity(cameraEntity);
403 resetSnapshot_ = captureSnapshot(cameraEntity);
404 syncedFromEntity_ = true;
405 }
406
407 bool positionChanged = false;
408 bool rotationChanged = false;
409 bool projectionChanged = false;
410
411 bool missingPosition = false;
412 bool missingRotation = false;
413 bool missingCamera = false;
414
415 ImGui::SeparatorText("Spatial Components");
416
417 ImGui::Text("Position3DComponent");
418 positionChanged |= ImGui::DragFloat(
419 "X##Pos",
420 &tempPosition_[0],
421 0.1f,
422 -10000.0f,
423 10000.0f,
424 "%.3f"
425 );
426
427 positionChanged |= ImGui::DragFloat(
428 "Y##Pos",
429 &tempPosition_[1],
430 0.1f,
431 -10000.0f,
432 10000.0f,
433 "%.3f"
434 );
435
436 positionChanged |= ImGui::DragFloat(
437 "Z##Pos",
438 &tempPosition_[2],
439 0.1f,
440 -10000.0f,
441 10000.0f,
442 "%.3f"
443 );
444
445 ImGui::Spacing();
446 ImGui::Text("YawPitchRollComponent (first-person yaw/pitch/roll)");
447
448 const auto pi = static_cast<float>(std::numbers::pi);
449 const auto halfPi = pi * 0.5f;
450
451 rotationChanged |= ImGui::SliderFloat(
452 "Pitch##Rot",
453 &tempRotation_[1],
454 -halfPi + 0.001f,
455 halfPi - 0.001f,
456 "%.3f rad"
457 );
458
459 rotationChanged |= ImGui::SliderFloat(
460 "Yaw##Rot",
461 &tempRotation_[0],
462 -pi,
463 pi,
464 "%.3f rad"
465 );
466
471 rotationChanged |= ImGui::SliderFloat(
472 "Roll##Rot",
473 &tempRotation_[2],
474 -pi,
475 pi,
476 "%.3f rad"
477 );
478
479 ImGui::SeparatorText("PerspectiveCameraComponent");
480
481 projectionChanged |= ImGui::SliderFloat(
482 "FOV Y",
483 &tempFovDegrees_,
484 10.0f,
485 170.0f,
486 "%.1f deg"
487 );
488
489 projectionChanged |= ImGui::DragFloat(
490 "Aspect",
491 &tempAspectRatio_,
492 0.01f,
493 0.1f,
494 8.0f,
495 "%.3f"
496 );
497
498 projectionChanged |= ImGui::DragFloat(
499 "Near",
500 &tempZNear_,
501 0.001f,
502 0.001f,
503 1000.0f,
504 "%.4f"
505 );
506
507 projectionChanged |= ImGui::DragFloat(
508 "Far",
509 &tempZFar_,
510 1.0f,
511 0.01f,
512 1000000.0f,
513 "%.2f"
514 );
515
516 if (positionChanged) {
517 if (auto* position = cameraEntity.get<Position3DComponent>()) {
518 cameraEntity.setTrackedValue(position, tempPosition_);
519 } else {
520 missingPosition = true;
521 }
522 }
523
524 if (rotationChanged) {
525 missingRotation = !writeYawPitchRollToEntity(cameraEntity);
526 }
527
528 if (projectionChanged) {
529 missingCamera = !writeProjectionToEntity(cameraEntity);
530 }
531
532 ImGui::Spacing();
533 ImGui::BeginDisabled(!resetSnapshot_.valid);
534
535 if (ImGui::Button("Reset selected camera")) {
536 if (resetSnapshot_.hasPosition) {
537 if (auto* position = cameraEntity.get<Position3DComponent>()) {
538 cameraEntity.setTrackedValue(position, resetSnapshot_.position);
539 tempPosition_ = resetSnapshot_.position;
540 } else {
541 missingPosition = true;
542 }
543 }
544
545 if (resetSnapshot_.hasRotation) {
546 tempRotation_ = resetSnapshot_.rotation;
547 missingRotation = !writeYawPitchRollToEntity(cameraEntity);
548 }
549
550 if (resetSnapshot_.hasPerspective) {
551 if (auto* perspective = cameraEntity.get<PerspectiveCameraComponent>()) {
552 cameraEntity.setTrackedValue(perspective, helios::math::vec4f{
553 helios::math::radians(resetSnapshot_.fovYDegrees),
554 resetSnapshot_.aspectRatio,
555 resetSnapshot_.zNear,
556 resetSnapshot_.zFar
557 });
558 tempFovDegrees_ = resetSnapshot_.fovYDegrees;
559 tempAspectRatio_ = resetSnapshot_.aspectRatio;
560 tempZNear_ = resetSnapshot_.zNear;
561 tempZFar_ = resetSnapshot_.zFar;
562 } else {
563 missingCamera = true;
564 }
565 }
566 }
567
568 ImGui::EndDisabled();
569
570 if (missingPosition || missingRotation || missingCamera) {
571 ImGui::Separator();
572 ImGui::TextColored(
573 ImVec4(1.0f, 0.75f, 0.2f, 1.0f),
574 "Missing component(s): values could not be written."
575 );
576
577 if (missingPosition) {
578 ImGui::TextDisabled("- Position3DComponent");
579 }
580
581 if (missingRotation) {
582 ImGui::TextDisabled("- YawPitchRollComponent");
583 }
584
585 if (missingCamera) {
586 ImGui::TextDisabled("- PerspectiveCameraComponent");
587 }
588 }
589
590 ImGui::Separator();
591 ImGui::TextDisabled(
592 "Handle: entityId=%u versionId=%u",
593 cameraHandle_.entityId,
594 cameraHandle_.versionId
595 );
596
597 ImGui::End();
598 }