Loading...
Searching...
No Matches
CameraArray.hpp
1#pragma once
2#include <halp/controls.hpp>
3#include <halp/meta.hpp>
4
5#include <ossia/dataflow/geometry_port.hpp>
6
7#include <Gfx/Graph/GpuResourceRegistry.hpp>
8#include <Gfx/Graph/SceneGPUState.hpp> // sizeof(score::gfx::RawCameraData) in operator()()
9
10#include <QQuaternion>
11#include <QVector3D>
12
13#include <array>
14#include <cmath>
15#include <cstdint>
16#include <memory>
17#include <vector>
18
19class QRhiResourceUpdateBatch;
20
21namespace score::gfx
22{
23class RenderList;
24struct Edge;
25}
26
27namespace Threedim
28{
29
30// Scene-producing node that emits a six-camera array laid out for cubemap
31// / multiview rendering. Each camera is a scene_node with a
32// scene_transform + camera_component payload; ScenePreprocessor's flatten
33// visitor picks them up into FlatScene::cameras, and
34// packAndUploadCameras packs them into the Camera UBO aux-buffer on
35// Geometry Out. Multiview shaders (MULTIVIEW=6) then index camera[0..5]
36// via gl_ViewIndex.
37//
38// Face convention follows the GL cubemap layout:
39// camera[0] = +X, [1] = -X, [2] = +Y, [3] = -Y, [4] = +Z, [5] = -Z
40// Each face uses a 90° square FOV with aspect 1:1 — consumers should
41// render into a cube render target at any square resolution.
43{
44public:
45 halp_meta(name, "Camera Array")
46 halp_meta(c_name, "camera_array_avnd")
47 halp_meta(category, "Visuals/3D/Scene")
48 halp_meta(description, "Create six cameras oriented along cubemap faces for multiview rendering.")
49 halp_meta(authors, "ossia team")
50 halp_meta(uuid, "7a3e8d2f-1b94-4c6a-b7f5-8e2d0c1a4b93")
51
52 // Six GL-ordered cubemap faces at 90° FoV, aspect 1:1. Suitable as
53 // both a reflection probe array and a point-shadow cube array — the
54 // distinction is downstream (which render target / depth-only flag),
55 // not in the camera math here.
56 struct ins
57 {
58 // Port-driven rebuild: each control's update() callback fires
59 // CameraArray::rebuild() on change. operator()() republishes.
60 struct : halp::xyz_spinboxes_f32<"Origin", halp::range{-10000., 10000., 0.}>
61 { void update(CameraArray& n) { n.rebuild(); } } origin;
62 struct : halp::hslider_f32<"Near", halp::range{0.001, 10., 0.1}>
63 { void update(CameraArray& n) { n.rebuild(); } } near_plane;
64 struct : halp::hslider_f32<"Far", halp::range{1., 100000., 1000.}>
65 { void update(CameraArray& n) { n.rebuild(); } } far_plane;
66 } inputs;
67
68 struct outs
69 {
70 struct
71 {
72 halp_meta(name, "Scene");
73 ossia::scene_spec scene;
74 uint8_t dirty{0};
75 } scene_out;
76 } outputs;
77
78 // Canonical cubemap face orientations in the GL convention:
79 // { forward, up }. right = forward × up.
80 struct Face
81 {
82 float forward[3];
83 float up[3];
84 };
85
86 // Six deterministic ids rooted at this node's address — each face
87 // needs a stable, distinct scene_node_id so merge_scenes treats them
88 // as six separate cameras (same-id camera entries would collapse).
89 std::array<ossia::scene_node_id, 6> m_ids{};
90 std::shared_ptr<ossia::scene_state> m_state;
91 int64_t m_version{0};
92 uint8_t m_pending_dirty{ossia::scene_port::dirty_transform};
93
94 void rebuild()
95 {
96 if(!m_state)
97 {
98 m_state = std::make_shared<ossia::scene_state>();
99 // Seed six distinct ids from this node's address. OR the per-face
100 // index in so they're all non-zero AND all distinct.
101 const auto base = reinterpret_cast<std::uintptr_t>(this);
102 for(int i = 0; i < 6; ++i)
103 m_ids[std::size_t(i)].value = (base ^ (std::uintptr_t(i + 1) << 1)) | 0x1u;
104 }
105
106 static constexpr std::array<Face, 6> kFaces{{
107 {{ 1.f, 0.f, 0.f}, {0.f, -1.f, 0.f}}, // +X
108 {{-1.f, 0.f, 0.f}, {0.f, -1.f, 0.f}}, // -X
109 {{ 0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}}, // +Y
110 {{ 0.f, -1.f, 0.f}, {0.f, 0.f, -1.f}}, // -Y
111 {{ 0.f, 0.f, 1.f}, {0.f, -1.f, 0.f}}, // +Z
112 {{ 0.f, 0.f, -1.f}, {0.f, -1.f, 0.f}}, // -Z
113 }};
114
115 const float near_f = inputs.near_plane.value;
116 const float far_f = inputs.far_plane.value;
117 const float eye[3]
118 = {inputs.origin.value.x, inputs.origin.value.y,
119 inputs.origin.value.z};
120
121 auto roots
122 = std::make_shared<std::vector<ossia::scene_node_ptr>>();
123 roots->reserve(6);
124
125 for(int i = 0; i < 6; ++i)
126 {
127 auto cam = std::make_shared<ossia::camera_component>();
128 cam->projection = ossia::camera_projection::perspective;
129 cam->yfov = float(M_PI) / 2.f; // 90° per face for a seamless cube
130 cam->aspect_ratio = 1.f;
131 cam->aspect_ratio_set = true;
132 cam->znear = near_f;
133 cam->zfar = far_f;
134 // Each face owns its own RawCamera slot; stamp its ref directly.
135 if(m_array_ref[std::size_t(i)].valid())
136 {
137 cam->raw_slot = m_array_ref[std::size_t(i)];
138 cam->raw_slot.size = uint32_t(sizeof(score::gfx::RawCameraData));
139 }
140
141 ossia::scene_transform xform;
142 xform.translation[0] = eye[0];
143 xform.translation[1] = eye[1];
144 xform.translation[2] = eye[2];
145
146 // Same rationale as Camera.hpp: Qt's QQuaternion::fromDirection
147 // maps local +Z to `direction`, but GL cameras look along local -Z
148 // — pass the negated forward so local -Z ends up pointing along
149 // +forward (the face-direction).
150 QVector3D fwd(
151 kFaces[std::size_t(i)].forward[0], kFaces[std::size_t(i)].forward[1],
152 kFaces[std::size_t(i)].forward[2]);
153 QVector3D up(
154 kFaces[std::size_t(i)].up[0], kFaces[std::size_t(i)].up[1],
155 kFaces[std::size_t(i)].up[2]);
156 QQuaternion q = QQuaternion::fromDirection(-fwd, up);
157 xform.rotation[0] = q.x();
158 xform.rotation[1] = q.y();
159 xform.rotation[2] = q.z();
160 xform.rotation[3] = q.scalar();
161 xform.scale[0] = 1.f;
162 xform.scale[1] = 1.f;
163 xform.scale[2] = 1.f;
164 // Per-face RawTransform slot ref — same shape as the camera
165 // array ref, offset bumped to the i-th RawLocalTransform slot.
166 if(m_xform_array_ref[std::size_t(i)].valid())
167 {
168 xform.raw_slot = m_xform_array_ref[std::size_t(i)];
169 xform.raw_slot.size
170 = uint32_t(sizeof(score::gfx::RawLocalTransform));
171 }
172
173 auto children
174 = std::make_shared<std::vector<ossia::scene_payload>>();
175 children->push_back(xform);
176 children->push_back(ossia::camera_component_ptr(std::move(cam)));
177
178 auto node = std::make_shared<ossia::scene_node>();
179 node->id = m_ids[std::size_t(i)];
180 node->children = std::move(children);
181
182 roots->push_back(std::move(node));
183 }
184
185 m_state->roots = std::move(roots);
186 // Face 0 (+X) acts as the "active" camera for non-multiview consumers
187 // that only read the first entry. Multiview shaders ignore this and
188 // index all six via gl_ViewIndex.
189 m_state->active_camera_id = m_ids[0];
190 m_version++;
191 m_state->version = m_version;
192 m_pending_dirty = ossia::scene_port::dirty_transform;
193 }
194
195 void operator()()
196 {
197 if(!m_state)
198 rebuild();
199 outputs.scene_out.scene.state = m_state;
200 outputs.scene_out.dirty = m_pending_dirty;
201 m_pending_dirty = 0;
202 }
203
204 // Render-thread hooks. A single RawCamera slot holds all six faces
205 // contiguously (6 × RawCameraData).
206 void init(score::gfx::RenderList& r, QRhiResourceUpdateBatch& res);
207 void update(
208 score::gfx::RenderList& r, QRhiResourceUpdateBatch& res,
210 void release(score::gfx::RenderList& r);
211
212 // One slot + ref per face: the fixed-stride arenas cannot hand out a
213 // contiguous 6-wide block, so each face owns an independent slot.
214 score::gfx::GpuResourceRegistry::Slot raw_camera_slot[6];
215 score::gfx::GpuResourceRegistry::Slot raw_transform_slot[6];
216 ossia::gpu_slot_ref m_array_ref[6]{};
217 ossia::gpu_slot_ref m_xform_array_ref[6]{};
218};
219
220}
Definition CameraArray.hpp:43
List of nodes to be rendered to an output.
Definition RenderList.hpp:33
Graphics rendering pipeline for ossia score.
Definition Filter/PreviewWidget.hpp:11
Definition CameraArray.hpp:81
Definition CameraArray.hpp:57
Definition CameraArray.hpp:69
Connection between two score::gfx::Port.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:236
Definition GpuResourceRegistry.hpp:84
Definition SceneGPUState.hpp:297
Definition SceneGPUState.hpp:354