Renders the widget using ImGui immediate-mode API.
Called once per frame by the owning overlay. Implementers should call ImGui functions (e.g., `ImGui::Begin()`, `ImGui::Text()`) to build the UI.
214 ImGui::SetNextWindowSize(ImVec2(320, 620), ImGuiCond_FirstUseEver);
215
216 if (!ImGui::Begin("Camera Control", nullptr, ImGuiWindowFlags_NoCollapse)) {
217 ImGui::End();
218 return;
219 }
220
221 if (cameras_.empty()) {
222 ImGui::TextDisabled("No cameras registered.");
223 ImGui::End();
224 return;
225 }
226
227
228 ImGui::PushItemWidth(-100);
229 if (ImGui::BeginCombo("##Camera", cameras_[selectedCameraIndex_].name.c_str())) {
230 for (int i = 0; i < static_cast<int>(cameras_.size()); ++i) {
231 const bool isSelected = (selectedCameraIndex_ == i);
232 if (ImGui::Selectable(cameras_[i].name.c_str(), isSelected)) {
233 selectedCameraIndex_ = i;
234 syncTempValuesFromCamera();
235 }
236 if (isSelected) {
237 ImGui::SetItemDefaultFocus();
238 }
239 }
240 ImGui::EndCombo();
241 }
242 ImGui::PopItemWidth();
243
244 ImGui::SameLine();
245 if (ImGui::Button("Reset")) {
246 resetToInitialValues();
247 }
248
249 auto* node = getCurrentCameraNode();
250 auto* cameraPtr = getActiveCamera();
251 if (!node || !cameraPtr) {
252 ImGui::End();
253 return;
254 }
255 auto& camera = *cameraPtr;
256
257 ImGui::Separator();
258 ImGui::Spacing();
259
260 bool translationChanged = false;
261 bool lookAtChanged = false;
262
263
264 ImGui::Text("Position (Translation)");
265 translationChanged |= ImGui::DragFloat("X##Pos", &tempTranslation_[0], 0.1f, -100.0f, 100.0f, "%.2f");
266 translationChanged |= ImGui::DragFloat("Y##Pos", &tempTranslation_[1], 0.1f, -100.0f, 100.0f, "%.2f");
267 translationChanged |= ImGui::DragFloat("Z##Pos", &tempTranslation_[2], 0.1f, -100.0f, 100.0f, "%.2f");
268
269 ImGui::Spacing();
270
271
272 ImGui::Text("LookAt Space");
273 const int mode = (lookAtSpace_ == LookAtSpace::Local) ? 0 : 1;
274
275 if (ImGui::RadioButton("Local (parent/model space)", mode == 0)) {
276 lookAtSpace_ = LookAtSpace::Local;
277 lookAtChanged = true;
278 }
279 ImGui::SameLine();
280 if (ImGui::RadioButton("World", mode == 1)) {
281 lookAtSpace_ = LookAtSpace::World;
282 lookAtChanged = true;
283 }
284
285 ImGui::Spacing();
286
287
288 ImGui::Separator();
289 ImGui::Text("Apply Behavior");
290 ImGui::Checkbox("Translation", &applyTranslationOnChange_);
291 ImGui::SameLine();
292 ImGui::Checkbox("LookAt", &applyLookAtOnChange_);
293 ImGui::SameLine();
294 ImGui::Checkbox("Both", &applyBothOnAnyChange_);
295 if (ImGui::IsItemHovered()) {
296 ImGui::SetTooltip("If enabled, changing either Position or LookAt re-applies BOTH.");
297 }
298
299 ImGui::Separator();
300 ImGui::Spacing();
301
302
303 ImGui::Text("Look-At Target");
304 lookAtChanged |= ImGui::DragFloat("X##Target", &tempLookAtTarget_[0], 0.1f, -100.0f, 100.0f, "%.2f");
305 lookAtChanged |= ImGui::DragFloat("Y##Target", &tempLookAtTarget_[1], 0.1f, -100.0f, 100.0f, "%.2f");
306 lookAtChanged |= ImGui::DragFloat("Z##Target", &tempLookAtTarget_[2], 0.1f, -100.0f, 100.0f, "%.2f");
307
308 ImGui::Spacing();
309
310 ImGui::Text("Up Vector");
311 lookAtChanged |= ImGui::DragFloat("X##Up", &tempUp_[0], 0.01f, -1.0f, 1.0f, "%.3f");
312 lookAtChanged |= ImGui::DragFloat("Y##Up", &tempUp_[1], 0.01f, -1.0f, 1.0f, "%.3f");
313 lookAtChanged |= ImGui::DragFloat("Z##Up", &tempUp_[2], 0.01f, -1.0f, 1.0f, "%.3f");
314
315 ImGui::SameLine();
316 if (ImGui::SmallButton("N##NormalizeUp")) {
317 float len = tempUp_.length();
318 if (len > 0.0001f) {
319 tempUp_ = tempUp_.normalize();
320 lookAtChanged = true;
321 }
322 }
323 if (ImGui::IsItemHovered()) {
324 ImGui::SetTooltip("Normalize up vector");
325 }
326
327
328 if (translationChanged || lookAtChanged) {
329 applyTransformToNode(node, translationChanged, lookAtChanged);
330 }
331
332 ImGui::Separator();
333 ImGui::Spacing();
334
335
336 ImGui::Text("Projection");
337
338 bool projectionChanged = false;
339 projectionChanged |= ImGui::SliderFloat("FOV", &tempFovDegrees_, 30.0f, 120.0f, "%.1f deg");
340 projectionChanged |= ImGui::DragFloat("Near Plane", &tempZNear_, 0.01f, 0.001f, tempZFar_ - 0.01f, "%.3f");
341 projectionChanged |= ImGui::DragFloat("Far Plane", &tempZFar_, 1.0f, tempZNear_ + 0.01f, 100000.0f, "%.1f");
342
343 if (projectionChanged) {
344 camera.setPerspective(helios::math::radians(tempFovDegrees_), tempAspectRatio_, tempZNear_, tempZFar_);
345 }
346
347 ImGui::Spacing();
348
349 ImGui::Text("Aspect Ratio");
350 if (ImGui::DragFloat("##Aspect", &tempAspectRatio_, 0.01f, 0.5f, 4.0f, "%.3f")) {
351 camera.setAspectRatio(tempAspectRatio_);
352 }
353
354 if (ImGui::Button("16:9")) {
355 tempAspectRatio_ = 16.0f / 9.0f;
356 camera.setAspectRatio(tempAspectRatio_);
357 }
358 ImGui::SameLine();
359 if (ImGui::Button("21:9")) {
360 tempAspectRatio_ = 21.0f / 9.0f;
361 camera.setAspectRatio(tempAspectRatio_);
362 }
363 ImGui::SameLine();
364 if (ImGui::Button("32:9")) {
365 tempAspectRatio_ = 32.0f / 9.0f;
366 camera.setAspectRatio(tempAspectRatio_);
367 }
368 ImGui::SameLine();
369 if (ImGui::Button("4:3")) {
370 tempAspectRatio_ = 4.0f / 3.0f;
371 camera.setAspectRatio(tempAspectRatio_);
372 }
373 ImGui::SameLine();
374 if (ImGui::Button("1:1")) {
375 tempAspectRatio_ = 1.0f;
376 camera.setAspectRatio(tempAspectRatio_);
377 }
378
379 ImGui::Separator();
380 ImGui::Spacing();
381
382
383 ImGui::Text("Quick View Presets");
384 if (ImGui::Button("Front")) {
385 tempTranslation_ = {0.0f, 0.0f, 5.0f};
386 tempLookAtTarget_ = {0.0f, 0.0f, 0.0f};
387 tempUp_ = {0.0f, 1.0f, 0.0f};
388 applyTransformToNode(node, true, true);
389 }
390 ImGui::SameLine();
391 if (ImGui::Button("Top")) {
392 tempTranslation_ = {0.0f, 5.0f, 0.001f};
393 tempLookAtTarget_ = {0.0f, 0.0f, 0.0f};
394 tempUp_ = {0.0f, 0.0f, -1.0f};
395 applyTransformToNode(node, true, true);
396 }
397 ImGui::SameLine();
398 if (ImGui::Button("Side")) {
399 tempTranslation_ = {5.0f, 0.0f, 0.0f};
400 tempLookAtTarget_ = {0.0f, 0.0f, 0.0f};
401 tempUp_ = {0.0f, 1.0f, 0.0f};
402 applyTransformToNode(node, true, true);
403 }
404 ImGui::SameLine();
405 if (ImGui::Button("Iso")) {
406 tempTranslation_ = {5.0f, 5.0f, 5.0f};
407 tempLookAtTarget_ = {0.0f, 0.0f, 0.0f};
408 tempUp_ = {0.0f, 1.0f, 0.0f};
409 applyTransformToNode(node, true, true);
410 }
411
412 ImGui::Separator();
413
415 float distance = diff.length();
416
417 ImGui::TextDisabled("Pos: (%.1f, %.1f, %.1f) | Target: (%.1f, %.1f, %.1f)",
418 tempTranslation_[0], tempTranslation_[1], tempTranslation_[2],
419 tempLookAtTarget_[0], tempLookAtTarget_[1], tempLookAtTarget_[2]);
420 ImGui::TextDisabled("Distance: %.2f | FOV: %.0f° | Z: [%.2f, %.0f]",
421 distance, tempFovDegrees_, tempZNear_, tempZFar_);
422
423 ImGui::End();
424 }