Loading...
Searching...
No Matches
Camera.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
9#include <QMatrix4x4>
10#include <QQuaternion>
11#include <QVector3D>
12
13#include <cmath>
14#include <cstdint>
15#include <memory>
16#include <vector>
17
18class QRhiResourceUpdateBatch;
19
20namespace score::gfx
21{
22class RenderList;
23struct Edge;
24}
25
26namespace Threedim
27{
28
29// Local -Z points from eye to target; reference up is +Y, or -Z / +Z when
30// looking straight down / up; roll_degrees turns around the view axis.
31inline QQuaternion
32cameraOrientation(QVector3D eye, QVector3D target, float roll_degrees) noexcept
33{
34 const QQuaternion roll
35 = QQuaternion::fromAxisAndAngle(0.f, 0.f, 1.f, roll_degrees);
36 QVector3D back = eye - target;
37 if(back.lengthSquared() <= 1e-8f)
38 return roll;
39 back.normalize();
40
41 QVector3D up(0.f, 1.f, 0.f);
42 QVector3D right = QVector3D::crossProduct(up, back);
43 if(right.lengthSquared() < 1e-12f)
44 {
45 up = QVector3D(0.f, 0.f, back.y() > 0.f ? -1.f : 1.f);
46 right = QVector3D::crossProduct(up, back);
47 }
48 right.normalize();
49 const QVector3D trueUp = QVector3D::crossProduct(back, right);
50 return QQuaternion::fromAxes(right, trueUp, back) * roll;
51}
52
53// Scene-producing camera node. Emits a scene_spec containing:
54// - a scene_node with an id derived from this node's uuid (so the flatten
55// visitor can attribute the camera back to it),
56// - a scene_transform placing the camera at eye looking at target,
57// - a camera_component carrying yfov / znear / zfar.
58//
59// ScenePreprocessor packs every camera it collects into its Camera UBO
60// output — when merged with a scene tree this camera becomes one entry in
61// that array. active_camera_id defaults to this node's id so a single
62// Camera is picked up automatically.
63class Camera
64{
65public:
66 halp_meta(name, "Camera")
67 halp_meta(c_name, "camera_avnd")
68 halp_meta(category, "Visuals/3D/Scene")
69 halp_meta(description, "Create a scene camera with eye, target, field-of-view and clipping controls.")
70 halp_meta(authors, "ossia team")
71 halp_meta(uuid, "4c91b5e2-8d76-4ab3-9f14-6e0d8b3a2c57")
72
73 struct ins
74 {
75 // Port-driven rebuild: every control carries an `update(Camera&)`
76 // callback that fires only when its value changes, triggering a
77 // `rebuild()` on the Camera. `operator()()` then just republishes
78 // the already-built m_state — no per-frame memcmp, no per-frame
79 // version bump, no merge_scenes / preprocessor thrash.
80 //
81 // halp::range only supports scalar inits (broadcast across x/y/z), so
82 // the non-uniform defaults are applied in the subclass constructor.
83 struct Eye : halp::xyz_spinboxes_f32<"Eye", halp::range{-10000., 10000., 0.}>
84 {
85 Eye() { value = {1.f, 1.f, 1.f}; }
86 void update(Camera& n) { n.rebuild(); }
87 } eye;
88 struct : halp::xyz_spinboxes_f32<"Target", halp::range{-10000., 10000., 0.}>
89 { void update(Camera& n) { n.rebuild(); } } target;
90 struct : halp::hslider_f32<"FOV", halp::range{5., 170., 60.}>
91 { void update(Camera& n) { n.rebuild(); } } fov;
92 struct : halp::hslider_f32<"Near", halp::range{0.001, 10., 0.1}>
93 { void update(Camera& n) { n.rebuild(); } } near_plane;
94 struct : halp::hslider_f32<"Far", halp::range{1., 100000., 1000.}>
95 { void update(Camera& n) { n.rebuild(); } } far_plane;
96 struct : halp::hslider_f32<"Roll", halp::range{-180., 180., 0.}>
97 { void update(Camera& n) { n.rebuild(); } } roll;
98 } inputs;
99
100 struct outs
101 {
102 struct
103 {
104 halp_meta(name, "Scene");
105 ossia::scene_spec scene;
106 uint8_t dirty{0};
107 } scene_out;
108 } outputs;
109
110 // Stable scene_node_id for this camera across frames. Set once in the
111 // first call. Used as scene_state::active_camera_id so ScenePreprocessor
112 // picks THIS camera even when other cameras show up in merged scenes.
113 ossia::scene_node_id m_id{};
114 std::shared_ptr<ossia::scene_state> m_state;
115 int64_t m_version{0};
116 // Dirty bits to stamp on the next emission. Accumulated in rebuild()
117 // and cleared after operator()() publishes them. When no control
118 // changed this frame, operator()() republishes the same m_state with
119 // dirty=0 so the preprocessor's pointer+version comparison short-
120 // circuits the rebuild path.
121 uint8_t m_pending_dirty{ossia::scene_port::dirty_transform};
122 // Stable ids for the single scene_transform + camera_component this
123 // node emits (minted on first rebuild).
124 uint64_t m_xform_stable_id{};
125 uint64_t m_camera_stable_id{};
126
127 // Rebuild m_state from current inputs. Called from every port's
128 // `update()` callback (fires only on control changes), and once from
129 // `operator()()` on the first tick to seed m_state.
130 void rebuild()
131 {
132 if(!m_state)
133 {
134 m_state = std::make_shared<ossia::scene_state>();
135 // Deterministic, non-zero id keyed on this node's address. Non-zero
136 // so merge_scenes' active_camera_id resolution treats it as "set".
137 m_id.value = reinterpret_cast<std::uintptr_t>(this) | 0x1u;
138 }
139 if(m_camera_stable_id == 0) m_camera_stable_id = ossia::mint_stable_id();
140 if(m_xform_stable_id == 0) m_xform_stable_id = ossia::mint_stable_id();
141
142 // Rebuild as {scene_transform, camera_component} inside a scene_node.
143 auto cam = std::make_shared<ossia::camera_component>();
144 cam->stable_id = m_camera_stable_id;
145 cam->projection = ossia::camera_projection::perspective;
146 cam->yfov = inputs.fov.value * float(M_PI) / 180.f;
147 cam->znear = inputs.near_plane.value;
148 cam->zfar = inputs.far_plane.value;
149 // Propagate the RawCamera arena slot ref (populated in init()).
150 cam->raw_slot = m_camera_ref;
151
152 // Encode the world transform as TRS for the scene_transform payload.
153 ossia::scene_transform xform;
154 xform.stable_id = m_xform_stable_id;
155 xform.translation[0] = inputs.eye.value.x;
156 xform.translation[1] = inputs.eye.value.y;
157 xform.translation[2] = inputs.eye.value.z;
158 const QQuaternion q = orientation();
159 xform.rotation[0] = q.x();
160 xform.rotation[1] = q.y();
161 xform.rotation[2] = q.z();
162 xform.rotation[3] = q.scalar();
163 xform.scale[0] = 1.f;
164 xform.scale[1] = 1.f;
165 xform.scale[2] = 1.f;
166 // Propagate the RawTransform slot ref (populated in init()).
167 xform.raw_slot = m_xform_ref;
168
169 auto children = std::make_shared<std::vector<ossia::scene_payload>>();
170 children->push_back(xform);
171 children->push_back(ossia::camera_component_ptr(std::move(cam)));
172
173 auto node = std::make_shared<ossia::scene_node>();
174 node->id = m_id;
175 node->children = std::move(children);
176
177 auto roots
178 = std::make_shared<std::vector<ossia::scene_node_ptr>>();
179 roots->push_back(std::move(node));
180
181 m_state->roots = std::move(roots);
182 m_state->active_camera_id = m_id;
183 m_version++;
184 m_state->version = m_version;
185 m_pending_dirty = ossia::scene_port::dirty_transform;
186 }
187
188 QQuaternion orientation() const noexcept
189 {
190 return cameraOrientation(
191 QVector3D(inputs.eye.value.x, inputs.eye.value.y, inputs.eye.value.z),
192 QVector3D(
193 inputs.target.value.x, inputs.target.value.y, inputs.target.value.z),
194 inputs.roll.value);
195 }
196
197 void operator()()
198 {
199 if(!m_state)
200 rebuild();
201 outputs.scene_out.scene.state = m_state;
202 outputs.scene_out.dirty = m_pending_dirty;
203 m_pending_dirty = 0;
204 }
205
206 // Render-thread hooks. init claims a RawCamera and a RawTransform slot;
207 // update packs eye / target / up / yfov / znear / zfar into a
208 // RawCameraData and uploads; release returns the slots.
209 void init(score::gfx::RenderList& r, QRhiResourceUpdateBatch& res);
210 void update(
211 score::gfx::RenderList& r, QRhiResourceUpdateBatch& res,
213 void release(score::gfx::RenderList& r);
214
216 score::gfx::GpuResourceRegistry::Slot raw_transform_slot;
217
218 // Ossia-facing snapshots, stamped on the emitted components'
219 // raw_slot fields so the preprocessor can locate this camera's
220 // GPU bytes via isLive() + offset. Written once in init().
221 ossia::gpu_slot_ref m_camera_ref{};
222 ossia::gpu_slot_ref m_xform_ref{};
223};
224
225}
Definition Camera.hpp:64
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 Camera.hpp:84
Definition Camera.hpp:74
Definition Camera.hpp:101
Connection between two score::gfx::Port.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:236
Definition GpuResourceRegistry.hpp:84