VisuTwin Canvas
C++ 3D Engine — Metal Backend
Loading...
Searching...
No Matches
graphNode.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025-2026 Arnis Lektauers
3//
4// Created by Arnis Lektauers on 07.10.2025.
5//
6#include "graphNode.h"
7
9
10namespace visutwin::canvas
11{
13 {
14
15 }
16
17 void GraphNode::dirtifyLocal()
18 {
19 if (!_dirtyLocal) {
20 _dirtyLocal = true;
21 if (!_dirtyWorld) {
22 dirtifyWorld();
23 }
24 }
25 }
26
27 void GraphNode::dirtifyWorld()
28 {
29 if (!_dirtyWorld) {
30 unfreezeParentToRoot();
31 }
32 dirtifyWorldInternal();
33 }
34
35 void GraphNode::dirtifyWorldInternal()
36 {
37 if (!_dirtyWorld) {
38 _frozen = false;
39 _dirtyWorld = true;
40 for (auto* child : _children) {
41 if (!child->_dirtyWorld) {
42 child->dirtifyWorldInternal();
43 }
44 }
45 }
46 _dirtyNormal = true;
47 _worldScaleSign = 0;
48 _aabbVer++;
49 }
50
51 void GraphNode::unfreezeParentToRoot()
52 {
53 auto* p = _parent;
54 while (p) {
55 p->_frozen = false;
56 p = p->_parent;
57 }
58 }
59
64
66 if (_parent == nullptr) {
67 _localRotation = rotation;
68 } else {
69 _localRotation = _parent->rotation().invert() * rotation;
70 }
71
72 if (!_dirtyLocal) {
73 dirtifyLocal();
74 }
75 }
76
78 {
79 _localRotation = rotation;
80
81 if (!_dirtyLocal) {
82 dirtifyLocal();
83 }
84 }
85
87 if (!_dirtyLocal && !_dirtyWorld) {
88 return _worldTransform;
89 }
90
91 if (_parent) {
92 _parent->worldTransform();
93 }
94
95 sync();
96 return _worldTransform;
97 }
98
100 {
101 if (_worldScaleSign == 0) {
102 const auto& wt = worldTransform();
103 const float m00 = wt.getElement(0, 0);
104 const float m01 = wt.getElement(0, 1);
105 const float m02 = wt.getElement(0, 2);
106 const float m10 = wt.getElement(1, 0);
107 const float m11 = wt.getElement(1, 1);
108 const float m12 = wt.getElement(1, 2);
109 const float m20 = wt.getElement(2, 0);
110 const float m21 = wt.getElement(2, 1);
111 const float m22 = wt.getElement(2, 2);
112 const float det3 = m00 * (m11 * m22 - m12 * m21) -
113 m01 * (m10 * m22 - m12 * m20) +
114 m02 * (m10 * m21 - m11 * m20);
115 _worldScaleSign = det3 < 0.0f ? -1 : 1;
116 }
117 return static_cast<float>(_worldScaleSign);
118 }
119
120 void GraphNode::sync()
121 {
122 if (_dirtyLocal) {
123 _localTransform = Matrix4::trs(_localPosition, _localRotation, _localScale);
124 _dirtyLocal = false;
125 }
126
127 if (_dirtyWorld) {
128 if (_parent == nullptr) {
129 _worldTransform = _localTransform;
130 } else {
131 if (_scaleCompensation) {
132 // Scale compensation logic (complex)
133 Vector3 parentWorldScale;
134
135 Vector3 scale = _localScale;
136
137 if (auto* parentToUseScaleFrom = _parent) {
138 while (parentToUseScaleFrom && parentToUseScaleFrom->_scaleCompensation) {
139 parentToUseScaleFrom = parentToUseScaleFrom->_parent;
140 }
141 if (parentToUseScaleFrom) {
142 parentToUseScaleFrom = parentToUseScaleFrom->_parent;
143 if (parentToUseScaleFrom) {
144 parentWorldScale = parentToUseScaleFrom->worldTransform().getScale();
145 scale = parentWorldScale * _localScale;
146 }
147 }
148 }
149
150 const Quaternion scaleCompensateRot2 = Quaternion::fromMatrix4(_parent->worldTransform());
151 const Quaternion scaleCompensateRot = scaleCompensateRot2 * _localRotation;
152
153 Vector3 scaleCompensatePos;
154 Matrix4 tmatrix = _parent->worldTransform();
155 if (_parent->_scaleCompensation) {
156 Vector3 scaleCompensateScaleForParent = parentWorldScale * _parent->localScale();
157 scaleCompensatePos = _parent->worldTransform().getTranslation();
158 tmatrix = Matrix4::trs(scaleCompensatePos, scaleCompensateRot2, scaleCompensateScaleForParent);
159 }
160 scaleCompensatePos = tmatrix.transformPoint(_localPosition);
161
162 _worldTransform = Matrix4::trs(scaleCompensatePos, scaleCompensateRot, scale);
163 } else {
164 _worldTransform = _parent->worldTransform().mulAffine(_localTransform);
165 }
166 }
167
168 _dirtyWorld = false;
169 }
170 }
171
172 void GraphNode::setLocalEulerAngles(float x, float y, float z)
173 {
174 _localRotation = Quaternion::fromEulerAngles(x, y, z);
175
176 if (!_dirtyLocal) {
177 dirtifyLocal();
178 }
179 }
180
181 void GraphNode::translateLocal(float x, float y, float z)
182 {
183 //
184 // Transform the translation vector by the local rotation, then add to local position.
185 const Vector3 offset = _localRotation * Vector3(x, y, z);
186 _localPosition = _localPosition + offset;
187
188 if (!_dirtyLocal) {
189 dirtifyLocal();
190 }
191 }
192
193 void GraphNode::rotateLocal(float x, float y, float z)
194 {
196 _localRotation = _localRotation * rotation;
197
198 if (!_dirtyLocal) {
199 dirtifyLocal();
200 }
201 }
202
204 {
205 prepareInsertChild(node);
206 _children.push_back(node);
207 onInsertChild(node);
208 }
209
210 bool GraphNode::isDescendantOf(const GraphNode* node) const
211 {
212 auto* parent = _parent;
213 while (parent) {
214 if (parent == node) {
215 return true;
216 }
217 parent = parent->_parent;
218 }
219 return false;
220 }
221
223 {
224 if (_parent) {
225 _parent->removeChild(this);
226 }
227 }
228
230 {
231 auto it = std::find(_children.begin(), _children.end(), child);
232 if (it == _children.end()) {
233 return;
234 }
235
236 _children.erase(it);
237 child->_parent = nullptr;
238 child->fireOnHierarchy("remove", "removehierarchy", this);
239 fire("childremove", child);
240 }
241
242 void GraphNode::fireOnHierarchy(const std::string& name, const std::string& nameHierarchy, GraphNode* parent)
243 {
244 fire(name, parent);
245 for (auto* child : _children) {
246 child->fireOnHierarchy(nameHierarchy, nameHierarchy, parent);
247 }
248 }
249
250 void GraphNode::prepareInsertChild(GraphNode* node)
251 {
252 node->remove();
253
254 assert(node != this && (std::string("GraphNode ") + node->name() + " cannot be a child of itself").c_str());
255 assert(!isDescendantOf(node) && (std::string("GraphNode ") + node->name() + " cannot add an ancestor as a child").c_str());
256 }
257
258 void GraphNode::onInsertChild(GraphNode* node)
259 {
260 node->_parent = this;
261
262 // A child is enabled-in-hierarchy only if BOTH the parent hierarchy
263 // is enabled AND the child's own _enabled flag is true.
264 // Mirrors upstream: node._enabledInHierarchy = parent.enabled && node._enabled
265 bool enabledInHierarchy = enabled() && node->_enabled;
266 if (node->_enabledInHierarchy != enabledInHierarchy) {
267 node->_enabledInHierarchy = enabledInHierarchy;
268 node->notifyHierarchyStateChanged(node, enabledInHierarchy);
269 }
270
271 node->updateGraphDepth();
272 node->dirtifyWorld();
273
274 if (_frozen) {
275 node->unfreezeParentToRoot();
276 }
277
278 node->fireOnHierarchy("insert", "inserthierarchy", this);
279 fire("childinsert", node);
280 }
281
283 {
285
286 for (auto* child : node->_children) {
287 if (child->_enabled) {
289 }
290 }
291 }
292
293 void GraphNode::setEnabled(bool value)
294 {
295 // Mirrors upstream GraphNode enabled setter.
296 if (_enabled != value) {
297 _enabled = value;
298
299 // If enabling, propagate only when parent is also enabled.
300 // If disabling, always propagate.
301 if ((value && _parent && _parent->enabled()) || !value) {
302 notifyHierarchyStateChanged(this, value);
303 }
304 }
305 }
306
308 {
309 _enabledInHierarchy = enabled;
310 if (enabled && !_frozen) {
311 unfreezeParentToRoot();
312 }
313 }
314
315 void GraphNode::updateGraphDepth()
316 {
317 _graphDepth = _parent ? _parent->_graphDepth + 1 : 0;
318
319 for (auto* child : _children) {
320 child->updateGraphDepth();
321 }
322 }
323
324 void GraphNode::setLocalPosition(float x, float y, float z)
325 {
326 _localPosition = Vector3(x, y, z);
327
328 if (!_dirtyLocal) {
329 dirtifyLocal();
330 }
331 }
332
334 {
335 setLocalPosition(position.getX(), position.getY(), position.getZ());
336 }
337
338 void GraphNode::setLocalScale(float x, float y, float z)
339 {
340 setLocalScale(Vector3(x, y, z));
341 }
342
344 {
345 _localScale = scale;
346
347 if (!_dirtyLocal) {
348 dirtifyLocal();
349 }
350 }
351
353 {
354 _position = worldTransform().getTranslation();
355 return _position;
356 }
357
358 void GraphNode::setPosition(float x, float y, float z)
359 {
360 setPosition(Vector3(x, y, z));
361 }
362
364 {
365 if (_parent == nullptr) {
366 _localPosition = position;
367 } else {
368 const Matrix4 invParentWtm = _parent->worldTransform().inverse();
369 _localPosition = invParentWtm.transformPoint(position);
370 }
371
372 if (!_dirtyLocal) {
373 dirtifyLocal();
374 }
375 }
376
378 {
379 if (_name == name) {
380 return this;
381 }
382
383 for (auto* child : _children) {
384 if (!child) {
385 continue;
386 }
387
388 if (GraphNode* found = child->findByName(name)) {
389 return found;
390 }
391 }
392
393 return nullptr;
394 }
395
396 std::vector<GraphNode*> GraphNode::find(const std::function<bool(GraphNode*)>& predicate)
397 {
398 std::vector<GraphNode*> results;
399
400 if (predicate(this)) {
401 results.push_back(this);
402 }
403
404 for (auto* child : _children) {
405 if (!child) {
406 continue;
407 }
408 auto childResults = child->find(predicate);
409 results.insert(results.end(), childResults.begin(), childResults.end());
410 }
411
412 return results;
413 }
414}
EventHandler * fire(const std::string &name, Args &&... args)
Hierarchical scene graph node with local/world transforms and parent-child relationships.
Definition graphNode.h:28
GraphNode * findByName(const std::string &name)
bool isDescendantOf(const GraphNode *node) const
void setPosition(float x, float y, float z)
GraphNode(const std::string &name="Untitled")
Definition graphNode.h:30
void notifyHierarchyStateChanged(GraphNode *node, bool enabled)
void setLocalEulerAngles(float x, float y, float z)
void setLocalPosition(float x, float y, float z)
void addChild(GraphNode *node)
virtual void onHierarchyStateChanged(bool enabled)
std::vector< GraphNode * > find(const std::function< bool(GraphNode *)> &predicate)
Find all descendants (and self) matching a predicate.
void translateLocal(float x, float y, float z)
void setRotation(const Quaternion &rotation)
Definition graphNode.cpp:65
GraphNode * parent() const
Definition graphNode.h:88
void removeChild(GraphNode *child)
const Matrix4 & worldTransform()
Definition graphNode.cpp:86
const std::string & name() const
Definition graphNode.h:35
void setEnabled(bool value)
void setLocalRotation(const Quaternion &rotation)
Definition graphNode.cpp:77
void rotateLocal(float x, float y, float z)
void setLocalScale(float x, float y, float z)
4x4 column-major transformation matrix with SIMD acceleration.
Definition matrix4.h:31
Vector3 transformPoint(const Vector3 &v) const
Matrix4 inverse() const
Vector3 getTranslation() const
static Matrix4 trs(const Vector3 &t, const Quaternion &r, const Vector3 &s)
Unit quaternion for rotation representation with SIMD-accelerated slerp and multiply.
Definition quaternion.h:20
static Quaternion fromEulerAngles(float ax, float ay, float az)
static Quaternion fromMatrix4(const Matrix4 &m)
3D vector for positions, directions, and normals with multi-backend SIMD acceleration.
Definition vector3.h:29