Loading...
Searching...
No Matches
TransformHelper.hpp
1#pragma once
2#include <ossia/dataflow/geometry_port.hpp>
3
4#include <QMatrix4x4>
5#include <QQuaternion>
6
7#include <cstring>
8#include <memory>
9#include <vector>
10
11namespace Threedim
12{
13
14// Shared TRS-matrix computation for halp nodes that output a
15// `halp::mesh`-style geometry with a `transform[16]` slot plus a
16// `dirty_transform` flag (BuffersToGeometry, BuffersToGeometry2,
17// VoxelLoader, ...). Call from operator() every frame: computes a
18// column-major 4x4 TRS matrix from the XYZ controls, writes it into
19// `out_transform16`, and returns true iff the matrix changed since
20// the last call (so the caller can set `dirty_transform` accordingly).
21//
22// Cached prev values live on the caller via the CachedTRS struct —
23// identical layout across the three call sites so each node just
24// declares one member `CachedTRS m_cachedTRS{}` and passes it in.
26{
27 float pos[3]{0, 0, 0};
28 float rot[3]{0, 0, 0};
29 float scale[3]{1, 1, 1};
30 bool valid{false};
31};
32
33// `Inputs` is duck-typed: must expose `.position.value.{x,y,z}`, etc.
34template <typename Inputs>
35inline bool
36computeTRSMatrix(const Inputs& inputs, float out_transform16[16], CachedTRS& cache)
37{
38 const float px = inputs.position.value.x;
39 const float py = inputs.position.value.y;
40 const float pz = inputs.position.value.z;
41 const float rx = inputs.rotation.value.x;
42 const float ry = inputs.rotation.value.y;
43 const float rz = inputs.rotation.value.z;
44 const float sx = inputs.scale.value.x;
45 const float sy = inputs.scale.value.y;
46 const float sz = inputs.scale.value.z;
47
48 const bool changed
49 = !cache.valid
50 || cache.pos[0] != px || cache.pos[1] != py || cache.pos[2] != pz
51 || cache.rot[0] != rx || cache.rot[1] != ry || cache.rot[2] != rz
52 || cache.scale[0] != sx || cache.scale[1] != sy || cache.scale[2] != sz;
53
54 if(!changed)
55 return false;
56
57 // Build column-major 4x4: translate * rotate * scale, matching the
58 // convention used across the 3D plugin (QMatrix4x4's constData()
59 // returns column-major).
60 QMatrix4x4 m;
61 m.translate(px, py, pz);
62 m.rotate(QQuaternion::fromEulerAngles(rx, ry, rz));
63 m.scale(sx, sy, sz);
64 std::memcpy(out_transform16, m.constData(), sizeof(float) * 16);
65
66 cache.pos[0] = px; cache.pos[1] = py; cache.pos[2] = pz;
67 cache.rot[0] = rx; cache.rot[1] = ry; cache.rot[2] = rz;
68 cache.scale[0] = sx; cache.scale[1] = sy; cache.scale[2] = sz;
69 cache.valid = true;
70 return true;
71}
72
73// Wrap a raw scene_state under a single parent scene_node whose first child
74// is a scene_transform carrying this node's position / rotation / scale
75// controls. FlattenVisitor processes payloads in order and transforms apply
76// to subsequent siblings, so the wrap applies the TRS to every descendant.
77//
78// Used by asset-loader-style nodes (FbxParser, GltfParser, AssetLoader) to
79// compose the control-knob transform on top of the as-loaded scene without
80// touching the raw state (kept stable so downstream identity caches stay
81// warm).
82//
83// `Inputs` is duck-typed: must expose `.position.value.{x,y,z}`,
84// `.rotation.value.{x,y,z}`, `.scale.value.{x,y,z}`.
85template <typename Inputs>
86inline std::shared_ptr<const ossia::scene_state> wrapSceneWithTransform(
87 const std::shared_ptr<const ossia::scene_state>& raw,
88 const Inputs& inputs, CachedTRS& cache, int64_t& version_counter,
89 const ossia::gpu_slot_ref& xform_ref = {})
90{
91 if(!raw)
92 return nullptr;
93
94 ossia::scene_transform xform;
95 xform.translation[0] = inputs.position.value.x;
96 xform.translation[1] = inputs.position.value.y;
97 xform.translation[2] = inputs.position.value.z;
98 auto q = QQuaternion::fromEulerAngles(
99 inputs.rotation.value.x, inputs.rotation.value.y,
100 inputs.rotation.value.z);
101 xform.rotation[0] = q.x();
102 xform.rotation[1] = q.y();
103 xform.rotation[2] = q.z();
104 xform.rotation[3] = q.scalar();
105 xform.scale[0] = inputs.scale.value.x;
106 xform.scale[1] = inputs.scale.value.y;
107 xform.scale[2] = inputs.scale.value.z;
108 // Stamp the producer's RawTransform slot ref (if any) so the
109 // preprocessor composes a world matrix at the matching offset.
110 xform.raw_slot = xform_ref;
111
112 auto children = std::make_shared<std::vector<ossia::scene_payload>>();
113 children->push_back(xform);
114 if(raw->roots)
115 for(const auto& root : *raw->roots)
116 children->push_back(root);
117
118 auto parent = std::make_shared<ossia::scene_node>();
119 parent->children = std::move(children);
120
121 auto new_roots = std::make_shared<std::vector<ossia::scene_node_ptr>>();
122 new_roots->push_back(std::move(parent));
123
124 auto wrapped = std::make_shared<ossia::scene_state>();
125 wrapped->roots = std::move(new_roots);
126 // Identity-preserving passthrough of every scene_state shared field so
127 // downstream caches stay warm.
128 wrapped->materials = raw->materials;
129 wrapped->animations = raw->animations;
130 wrapped->cameras = raw->cameras;
131 wrapped->skeletons = raw->skeletons;
132 wrapped->collections = raw->collections;
133 wrapped->environment = raw->environment;
134 wrapped->shadow_cascades = raw->shadow_cascades;
135 wrapped->inject_buffers = raw->inject_buffers;
136 wrapped->inject_textures = raw->inject_textures;
137 wrapped->time_seconds = raw->time_seconds;
138 wrapped->active_variant_index = raw->active_variant_index;
139 wrapped->variant_names = raw->variant_names;
140 wrapped->active_camera_id = raw->active_camera_id;
141 wrapped->version = ++version_counter;
142 wrapped->dirty_index = 1;
143
144 cache.pos[0] = inputs.position.value.x;
145 cache.pos[1] = inputs.position.value.y;
146 cache.pos[2] = inputs.position.value.z;
147 cache.rot[0] = inputs.rotation.value.x;
148 cache.rot[1] = inputs.rotation.value.y;
149 cache.rot[2] = inputs.rotation.value.z;
150 cache.scale[0] = inputs.scale.value.x;
151 cache.scale[1] = inputs.scale.value.y;
152 cache.scale[2] = inputs.scale.value.z;
153 cache.valid = true;
154
155 return wrapped;
156}
157
158// Test whether the controls differ from a prior cached snapshot, without
159// applying them. Gates a wrapSceneWithTransform() rebuild so a new wrapped
160// state is allocated only when a control moved.
161template <typename Inputs>
162inline bool transformChanged(const Inputs& inputs, const CachedTRS& cache)
163{
164 return !cache.valid
165 || cache.pos[0] != inputs.position.value.x
166 || cache.pos[1] != inputs.position.value.y
167 || cache.pos[2] != inputs.position.value.z
168 || cache.rot[0] != inputs.rotation.value.x
169 || cache.rot[1] != inputs.rotation.value.y
170 || cache.rot[2] != inputs.rotation.value.z
171 || cache.scale[0] != inputs.scale.value.x
172 || cache.scale[1] != inputs.scale.value.y
173 || cache.scale[2] != inputs.scale.value.z;
174}
175
176}
Definition TransformHelper.hpp:26