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