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