1 | // Copyright (C) 2019 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "qquick3dobject.h" |
5 | #include "qquick3dobject_p.h" |
6 | #include "qquick3dscenemanager_p.h" |
7 | #include "qquick3ditem2d_p.h" |
8 | #include "qquick3dmodel_p.h" |
9 | |
10 | #include <QtQuick3DRuntimeRender/private/qssgrendergraphobject_p.h> |
11 | |
12 | #include <QtQml/private/qqmlglobal_p.h> |
13 | #include <QtQuick/private/qquickstategroup_p.h> |
14 | #include <QtQuick/private/qquickstate_p.h> |
15 | #include <QtQuick/private/qquickitem_p.h> |
16 | |
17 | #include <private/qv4object_p.h> |
18 | #include <private/qv4qobjectwrapper_p.h> |
19 | |
20 | QT_BEGIN_NAMESPACE |
21 | |
22 | |
23 | /*! |
24 | \qmltype Object3D |
25 | \inqmlmodule QtQuick3D |
26 | \instantiates QQuick3DObject |
27 | \inherits QtObject |
28 | \brief Abstract base type of all 3D nodes and resources. |
29 | |
30 | Object3D is the base class for all Qt Quick 3D types. This includes: |
31 | |
32 | \list |
33 | |
34 | \li Spatial types that represent objects in the 3D scene, these will normally have a position and/or a direction. |
35 | For example, \l Model, \l Camera, or \l Light. Such types inherit from \l Node, which in turn inherits from Object3D. |
36 | |
37 | \li Resource types that do not themselves represent an object in the 3D world, but rather serve |
38 | as components to \l Node subclasses, providing data of some kind. This includes, among |
39 | others, \l Material, \l Geometry, and \l Texture. |
40 | |
41 | \endlist |
42 | |
43 | In addition to the above types, Object3D can also serve as the parent for \l{Item}{Qt |
44 | Quick items}, as well as arbitrary QObject instances. For more information on adding |
45 | 2D items to the 3D scene, refer to \l{Qt Quick 3D Scenes with 2D Content}. |
46 | |
47 | \sa Node |
48 | */ |
49 | |
50 | /*! |
51 | \class QQuick3DObject |
52 | \inmodule QtQuick3D |
53 | \since 5.15 |
54 | \brief Base class of all 3D nodes and resources. |
55 | |
56 | Object3D is the base class for all Qt Quick 3D scene objects. Currently the |
57 | types available in C++ are: |
58 | |
59 | \list |
60 | \li QQuick3DGeometry |
61 | \li QQuick3DTextureData |
62 | \endlist |
63 | |
64 | Both of these types are resource objects which directly inherit QQuick3DObject. |
65 | |
66 | It should not be necessary to use QQuick3DObject directly anywhere currently |
67 | because it is just an interface for supporting spatial items and resources in |
68 | a 3D scene, as well as exposing similar functionality as QQuickItem for 3D |
69 | scene content. |
70 | */ |
71 | |
72 | QQuick3DObject::QQuick3DObject(QQuick3DObject *parent) |
73 | : QObject(*(new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::Unknown)), parent) |
74 | { |
75 | Q_D(QQuick3DObject); |
76 | d->init(parent); |
77 | } |
78 | |
79 | QQuick3DObject::~QQuick3DObject() |
80 | { |
81 | Q_D(QQuick3DObject); |
82 | // XXX todo - optimize |
83 | while (!d->childItems.isEmpty()) |
84 | d->childItems.constFirst()->setParentItem(nullptr); |
85 | |
86 | delete d->_stateGroup; |
87 | d->_stateGroup = nullptr; |
88 | delete d->contentItem2d; |
89 | d->contentItem2d = nullptr; |
90 | |
91 | if (d->parentItem) |
92 | setParentItem(nullptr); |
93 | |
94 | if (d->sceneRefCount > 1) |
95 | d->sceneRefCount = 1; // Make sure the scene is set to null in next call to derefSceneManager(). |
96 | |
97 | if (!d->parentItem && d->sceneManager) |
98 | QQuick3DObjectPrivate::derefSceneManager(obj: this); |
99 | } |
100 | |
101 | void QQuick3DObject::update() |
102 | { |
103 | Q_D(QQuick3DObject); |
104 | d->dirty(QQuick3DObjectPrivate::Content); |
105 | } |
106 | |
107 | /*! |
108 | \qmlproperty Object3D QtQuick::Object3D::parent |
109 | This property holds the parent of the Object3D in a 3D scene. |
110 | |
111 | \note An Object3D's parent may not necessarily be the same as its object |
112 | parent. This is necessary because the object parent may be an item that is |
113 | not of type Object3D, for example the root object in a scene. |
114 | */ |
115 | /*! |
116 | \property QQuick3DObject::parent |
117 | This property holds the parent of the Object3D in a 3D scene. |
118 | |
119 | \note An Object3D's parent may not necessarily be the same as its object |
120 | parent. This is necessary because the object parent may be an item that is |
121 | not of type Object3D, for example the root object in a scene. |
122 | |
123 | \note Currently for 3D items to be correctly handled by the scene manager |
124 | when parenting 3D objects from C++ it is necessary to call |
125 | QQuick3DObject::setParentItem before the QObject::setParent. This requirement is |
126 | likely to change in a future release though. |
127 | \code |
128 | QQuick3DObject *newItem = new QQuick3DObject(); |
129 | newItem->setParentItem(parentItem); |
130 | newItem->setParent(parentItem); |
131 | \endcode |
132 | */ |
133 | |
134 | void QQuick3DObject::setParentItem(QQuick3DObject *parentItem) |
135 | { |
136 | Q_D(QQuick3DObject); |
137 | if (parentItem == d->parentItem) |
138 | return; |
139 | |
140 | if (parentItem) { |
141 | QQuick3DObject *itemAncestor = parentItem; |
142 | while (itemAncestor != nullptr) { |
143 | if (Q_UNLIKELY(itemAncestor == this)) { |
144 | qWarning() << "QSSGObject::setParentItem: Parent" << parentItem << "is already part of the subtree of" << this; |
145 | return; |
146 | } |
147 | itemAncestor = itemAncestor->parentItem(); |
148 | } |
149 | } |
150 | |
151 | d->removeFromDirtyList(); |
152 | |
153 | QQuick3DObject *oldParentItem = d->parentItem; |
154 | |
155 | if (oldParentItem) { |
156 | QQuick3DObjectPrivate *op = QQuick3DObjectPrivate::get(item: oldParentItem); |
157 | |
158 | op->removeChild(this); |
159 | } else if (d->sceneManager) { |
160 | d->sceneManager->parentlessItems.remove(value: this); |
161 | } |
162 | |
163 | const auto parentSceneManager = parentItem ? QQuick3DObjectPrivate::get(item: parentItem)->sceneManager : nullptr; |
164 | if (d->sceneManager == parentSceneManager) { |
165 | // Avoid freeing and reallocating resources if the window stays the same. |
166 | d->parentItem = parentItem; |
167 | } else { |
168 | if (d->sceneManager) |
169 | QQuick3DObjectPrivate::derefSceneManager(obj: this); |
170 | d->parentItem = parentItem; |
171 | if (parentSceneManager) |
172 | QQuick3DObjectPrivate::refSceneManager(obj: this, mgr&: *parentSceneManager); |
173 | } |
174 | |
175 | d->dirty(QQuick3DObjectPrivate::ParentChanged); |
176 | |
177 | if (d->parentItem) |
178 | QQuick3DObjectPrivate::get(item: d->parentItem)->addChild(this); |
179 | else if (d->sceneManager) |
180 | d->sceneManager->parentlessItems.insert(value: this); |
181 | |
182 | d->itemChange(ItemParentHasChanged, d->parentItem); |
183 | |
184 | emit parentChanged(); |
185 | } |
186 | |
187 | QString QQuick3DObject::state() const |
188 | { |
189 | Q_D(const QQuick3DObject); |
190 | return d->state(); |
191 | } |
192 | |
193 | void QQuick3DObject::setState(const QString &state) |
194 | { |
195 | Q_D(QQuick3DObject); |
196 | d->setState(state); |
197 | } |
198 | |
199 | QList<QQuick3DObject *> QQuick3DObject::childItems() const |
200 | { |
201 | Q_D(const QQuick3DObject); |
202 | return d->childItems; |
203 | } |
204 | |
205 | QQuick3DObject *QQuick3DObject::parentItem() const |
206 | { |
207 | Q_D(const QQuick3DObject); |
208 | return d->parentItem; |
209 | } |
210 | |
211 | QSSGRenderGraphObject *QQuick3DObject::updateSpatialNode(QSSGRenderGraphObject *node) |
212 | { |
213 | Q_QUICK3D_PROFILE_ASSIGN_ID_SG(this, node); |
214 | return node; |
215 | } |
216 | |
217 | void QQuick3DObject::markAllDirty() |
218 | { |
219 | } |
220 | |
221 | void QQuick3DObject::itemChange(QQuick3DObject::ItemChange, const QQuick3DObject::ItemChangeData &) |
222 | { |
223 | } |
224 | |
225 | QQuick3DObject::QQuick3DObject(QQuick3DObjectPrivate &dd, QQuick3DObject *parent) |
226 | : QObject(dd, parent) |
227 | { |
228 | Q_D(QQuick3DObject); |
229 | d->init(parent); |
230 | } |
231 | |
232 | void QQuick3DObject::classBegin() |
233 | { |
234 | Q_D(QQuick3DObject); |
235 | d->componentComplete = false; |
236 | if (d->_stateGroup) |
237 | d->_stateGroup->classBegin(); |
238 | } |
239 | |
240 | void QQuick3DObject::componentComplete() |
241 | { |
242 | Q_D(QQuick3DObject); |
243 | d->componentComplete = true; |
244 | if (d->_stateGroup) |
245 | d->_stateGroup->componentComplete(); |
246 | |
247 | if (d->sceneManager && d->dirtyAttributes) { |
248 | d->addToDirtyList(); |
249 | } |
250 | Q_QUICK3D_PROFILE_REGISTER_D(this); |
251 | } |
252 | |
253 | bool QQuick3DObject::isComponentComplete() const |
254 | { |
255 | Q_D(const QQuick3DObject); |
256 | return d->componentComplete; |
257 | } |
258 | |
259 | void QQuick3DObject::preSync() |
260 | { |
261 | |
262 | } |
263 | |
264 | QQuick3DObjectPrivate::QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type t) |
265 | : _stateGroup(nullptr) |
266 | , dirtyAttributes(0) |
267 | , nextDirtyItem(nullptr) |
268 | , prevDirtyItem(nullptr) |
269 | , sceneManager(nullptr) |
270 | , sceneRefCount(0) |
271 | , parentItem(nullptr) |
272 | , subFocusItem(nullptr) |
273 | , type(t) |
274 | { |
275 | } |
276 | |
277 | QQuick3DObjectPrivate::~QQuick3DObjectPrivate() |
278 | { |
279 | } |
280 | |
281 | void QQuick3DObjectPrivate::init(QQuick3DObject *parent) |
282 | { |
283 | Q_Q(QQuick3DObject); |
284 | |
285 | if (parent) |
286 | q->setParentItem(parent); |
287 | } |
288 | |
289 | /*! |
290 | \qmlproperty list<Object> QtQuick3D::Object3D::data |
291 | \qmldefault |
292 | |
293 | The data property allows you to freely mix Object3D children and resources |
294 | in an object. If you assign a Object3D to the data list it becomes a child |
295 | and if you assign any other object type, it is added as a resource. |
296 | |
297 | So you can write: |
298 | \qml |
299 | Object3D { |
300 | Node {} |
301 | DirectionalLight {} |
302 | Timer {} |
303 | } |
304 | \endqml |
305 | |
306 | instead of: |
307 | \qml |
308 | Item { |
309 | children: [ |
310 | Node {}, |
311 | DirectionalLight {} |
312 | ] |
313 | resources: [ |
314 | Timer {} |
315 | ] |
316 | } |
317 | \endqml |
318 | |
319 | It should not generally be necessary to refer to the \c data property, |
320 | as it is the default property for Object3D and thus all child objects are |
321 | automatically assigned to this property. |
322 | */ |
323 | |
324 | /*! |
325 | \property QQuick3DObject::data |
326 | \internal |
327 | */ |
328 | QQmlListProperty<QObject> QQuick3DObjectPrivate::data() |
329 | { |
330 | return QQmlListProperty<QObject>(q_func(), |
331 | nullptr, |
332 | QQuick3DObjectPrivate::data_append, |
333 | QQuick3DObjectPrivate::data_count, |
334 | QQuick3DObjectPrivate::data_at, |
335 | QQuick3DObjectPrivate::data_clear); |
336 | } |
337 | |
338 | /*! |
339 | \property QQuick3DObject::resources |
340 | \internal |
341 | */ |
342 | QQmlListProperty<QObject> QQuick3DObjectPrivate::resources() |
343 | { |
344 | return QQmlListProperty<QObject>(q_func(), |
345 | nullptr, |
346 | QQuick3DObjectPrivate::resources_append, |
347 | QQuick3DObjectPrivate::resources_count, |
348 | QQuick3DObjectPrivate::resources_at, |
349 | QQuick3DObjectPrivate::resources_clear); |
350 | } |
351 | |
352 | /*! |
353 | \qmlproperty list<Object3D> QtQuick3D::Object3D::children |
354 | \qmlproperty list<Object> QtQuick3D::Object3D::resources |
355 | |
356 | The children property contains the list of visual children of this object. |
357 | The resources property contains non-visual resources that you want to |
358 | reference by name. |
359 | |
360 | It is not generally necessary to refer to these properties when adding |
361 | child objects or resources, as the default \l data property will |
362 | automatically assign child objects to the \c children and \c resources |
363 | properties as appropriate. See the \l QtQuick3D::Object3D::data |
364 | documentation for details. |
365 | |
366 | \note QtQuick3D::Object3D::resources does not return a list of 3D resources |
367 | despite the name. The name comes from the semantics of QQuickItem. 3D |
368 | resources are subclasses of QQuickObjec3D and thus will be returned in the |
369 | list of QtQuick3D::Objec3D::children. |
370 | */ |
371 | /*! |
372 | \property QQuick3DObject::children |
373 | \internal |
374 | */ |
375 | QQmlListProperty<QQuick3DObject> QQuick3DObjectPrivate::children() |
376 | { |
377 | return QQmlListProperty<QQuick3DObject>(q_func(), |
378 | nullptr, |
379 | QQuick3DObjectPrivate::children_append, |
380 | QQuick3DObjectPrivate::children_count, |
381 | QQuick3DObjectPrivate::children_at, |
382 | QQuick3DObjectPrivate::children_clear); |
383 | } |
384 | |
385 | /*! |
386 | \qmlproperty list<State> QtQuick3D::Object3D::states |
387 | |
388 | This property holds the list of possible states for this object. To change |
389 | the state of this object, set the \l state property to one of these states, |
390 | or set the \l state property to an empty string to revert the object to its |
391 | default state. |
392 | |
393 | This property is specified as a list of \l State objects. For example, |
394 | below is an QtQuick3D::Node with "above_state" and "below_state" states: |
395 | |
396 | \qml |
397 | import QtQuick |
398 | import QtQuick3D |
399 | |
400 | Node { |
401 | id: root |
402 | y: 0 |
403 | |
404 | states: [ |
405 | State { |
406 | name: "above_state" |
407 | PropertyChanges { target: root; y: 100 } |
408 | }, |
409 | State { |
410 | name: "below_state" |
411 | PropertyChanges { target: root; y: -100 } |
412 | } |
413 | ] |
414 | } |
415 | \endqml |
416 | |
417 | See \l{Qt Quick States} and \l{Animation and Transitions in Qt Quick} for |
418 | more details on using states and transitions. |
419 | |
420 | \note This property works the same as QtQuick::Item::states but is |
421 | necessary because QtQuick3D::Object3D is not a QtQuick::Item subclass. |
422 | |
423 | \sa QtQuick3D::Object3D::transitions |
424 | */ |
425 | |
426 | /*! |
427 | \property QQuick3DObject::states |
428 | \internal |
429 | */ |
430 | QQmlListProperty<QQuickState> QQuick3DObjectPrivate::states() |
431 | { |
432 | return _states()->statesProperty(); |
433 | } |
434 | |
435 | /*! |
436 | \qmlproperty list<Transition> QtQuick3D::Object3D::transitions |
437 | |
438 | This property holds the list of transitions for this object. These define the |
439 | transitions to be applied to the object whenever it changes its \l state. |
440 | |
441 | This property is specified as a list of \l Transition objects. For example: |
442 | |
443 | \qml |
444 | import QtQuick |
445 | import QtQuick3D |
446 | |
447 | Node { |
448 | transitions: [ |
449 | Transition { |
450 | //... |
451 | }, |
452 | Transition { |
453 | //... |
454 | } |
455 | ] |
456 | } |
457 | \endqml |
458 | |
459 | See \l{Qt Quick States} and \l{Animation and Transitions in Qt Quick} for |
460 | more details on using states and transitions. |
461 | |
462 | \note This property works the same as QtQuick::Item::transitions but is |
463 | necessary because QtQuick3D::Object3D is not a QtQuick::Item subclass. |
464 | |
465 | \sa QtQuick3D::Object3D::states |
466 | */ |
467 | /*! |
468 | \property QQuick3DObject::transitions |
469 | \internal |
470 | */ |
471 | |
472 | QQmlListProperty<QQuickTransition> QQuick3DObjectPrivate::transitions() |
473 | { |
474 | return _states()->transitionsProperty(); |
475 | } |
476 | |
477 | /*! |
478 | \qmlproperty string QtQuick3D::Object3D::state |
479 | |
480 | This property holds the name of the current state of the object. |
481 | |
482 | If the item is in its default state, that is, no explicit state has been |
483 | set, then this property holds an empty string. Likewise, you can return |
484 | an item to its default state by setting this property to an empty string. |
485 | |
486 | \sa {Qt Quick States} |
487 | */ |
488 | /*! |
489 | \property QQuick3DObject::state |
490 | |
491 | This property holds the name of the current state of the object. |
492 | |
493 | If the item is in its default state, that is, no explicit state has been |
494 | set, then this property holds an empty string. Likewise, you can return |
495 | an item to its default state by setting this property to an empty string. |
496 | |
497 | \sa {Qt Quick States} |
498 | */ |
499 | |
500 | QString QQuick3DObjectPrivate::state() const |
501 | { |
502 | return _stateGroup ? _stateGroup->state() : QString(); |
503 | } |
504 | |
505 | void QQuick3DObjectPrivate::setState(const QString &state) |
506 | { |
507 | _states()->setState(state); |
508 | } |
509 | |
510 | void QQuick3DObjectPrivate::data_append(QQmlListProperty<QObject> *prop, QObject *o) |
511 | { |
512 | if (!o) |
513 | return; |
514 | |
515 | QQuick3DObject *that = static_cast<QQuick3DObject *>(prop->object); |
516 | QQuick3DObjectPrivate *privateItem = QQuick3DObjectPrivate::get(item: that); |
517 | |
518 | if (QQuick3DObject *item = qmlobject_cast<QQuick3DObject *>(object: o)) { |
519 | item->setParentItem(that); |
520 | |
521 | } else { |
522 | QQuickItem *quickItem = qobject_cast<QQuickItem *>(o); |
523 | if (quickItem) { |
524 | if (!privateItem->contentItem2d) { |
525 | privateItem->contentItem2d = new QQuick3DItem2D(quickItem); |
526 | privateItem->contentItem2d->setParent(that); |
527 | privateItem->contentItem2d->setParentItem(that); |
528 | } else { |
529 | privateItem->contentItem2d->addChildItem(item: quickItem); |
530 | } |
531 | qmlobject_connect(privateItem->contentItem2d, QQuick3DItem2D, SIGNAL(allChildrenRemoved()), |
532 | that, QQuick3DObject, SLOT(_q_cleanupContentItem2D())); |
533 | } else { |
534 | o->setParent(that); |
535 | } |
536 | } |
537 | resources_append(prop, o); |
538 | } |
539 | |
540 | qsizetype QQuick3DObjectPrivate::data_count(QQmlListProperty<QObject> *property) |
541 | { |
542 | QQuick3DObject *item = static_cast<QQuick3DObject *>(property->object); |
543 | QQuick3DObjectPrivate *privateItem = QQuick3DObjectPrivate::get(item); |
544 | QQmlListProperty<QObject> resourcesProperty = privateItem->resources(); |
545 | QQmlListProperty<QQuick3DObject> childrenProperty = privateItem->children(); |
546 | |
547 | return resources_count(&resourcesProperty) + children_count(&childrenProperty); |
548 | } |
549 | |
550 | QObject *QQuick3DObjectPrivate::data_at(QQmlListProperty<QObject> *property, qsizetype i) |
551 | { |
552 | QQuick3DObject *item = static_cast<QQuick3DObject *>(property->object); |
553 | QQuick3DObjectPrivate *privateItem = QQuick3DObjectPrivate::get(item); |
554 | QQmlListProperty<QObject> resourcesProperty = privateItem->resources(); |
555 | QQmlListProperty<QQuick3DObject> childrenProperty = privateItem->children(); |
556 | |
557 | int resourcesCount = resources_count(&resourcesProperty); |
558 | if (i < resourcesCount) |
559 | return resources_at(&resourcesProperty, i); |
560 | const int j = i - resourcesCount; |
561 | if (j < children_count(&childrenProperty)) |
562 | return children_at(&childrenProperty, j); |
563 | return nullptr; |
564 | } |
565 | |
566 | void QQuick3DObjectPrivate::data_clear(QQmlListProperty<QObject> *property) |
567 | { |
568 | QQuick3DObject *item = static_cast<QQuick3DObject *>(property->object); |
569 | QQuick3DObjectPrivate *privateItem = QQuick3DObjectPrivate::get(item); |
570 | QQmlListProperty<QObject> resourcesProperty = privateItem->resources(); |
571 | QQmlListProperty<QQuick3DObject> childrenProperty = privateItem->children(); |
572 | |
573 | resources_clear(&resourcesProperty); |
574 | children_clear(&childrenProperty); |
575 | } |
576 | |
577 | QObject *QQuick3DObjectPrivate::resources_at(QQmlListProperty<QObject> *prop, qsizetype index) |
578 | { |
579 | QQuick3DObjectPrivate *quickItemPrivate = QQuick3DObjectPrivate::get(item: static_cast<QQuick3DObject *>(prop->object)); |
580 | return quickItemPrivate->extra.isAllocated() ? quickItemPrivate->extra->resourcesList.value(i: index) : 0; |
581 | } |
582 | |
583 | void QQuick3DObjectPrivate::resources_append(QQmlListProperty<QObject> *prop, QObject *object) |
584 | { |
585 | QQuick3DObject *quickItem = static_cast<QQuick3DObject *>(prop->object); |
586 | QQuick3DObjectPrivate *quickItemPrivate = QQuick3DObjectPrivate::get(item: quickItem); |
587 | if (!quickItemPrivate->extra.value().resourcesList.contains(t: object)) { |
588 | quickItemPrivate->extra.value().resourcesList.append(t: object); |
589 | // clang-format off |
590 | qmlobject_connect(object, QObject, SIGNAL(destroyed(QObject*)), |
591 | quickItem, QQuick3DObject, SLOT(_q_resourceObjectDeleted(QObject*))); |
592 | // clang-format on |
593 | } |
594 | } |
595 | |
596 | qsizetype QQuick3DObjectPrivate::resources_count(QQmlListProperty<QObject> *prop) |
597 | { |
598 | QQuick3DObjectPrivate *quickItemPrivate = QQuick3DObjectPrivate::get(item: static_cast<QQuick3DObject *>(prop->object)); |
599 | return quickItemPrivate->extra.isAllocated() ? quickItemPrivate->extra->resourcesList.size() : 0; |
600 | } |
601 | |
602 | void QQuick3DObjectPrivate::resources_clear(QQmlListProperty<QObject> *prop) |
603 | { |
604 | QQuick3DObject *quickItem = static_cast<QQuick3DObject *>(prop->object); |
605 | QQuick3DObjectPrivate *quickItemPrivate = QQuick3DObjectPrivate::get(item: quickItem); |
606 | if (quickItemPrivate->extra.isAllocated()) { // If extra is not allocated resources is empty. |
607 | for (QObject *object : std::as_const(t&: quickItemPrivate->extra->resourcesList)) { |
608 | // clang-format off |
609 | qmlobject_disconnect(object, QObject, SIGNAL(destroyed(QObject*)), |
610 | quickItem, QQuick3DObject, SLOT(_q_resourceObjectDeleted(QObject*))); |
611 | // clang-format on |
612 | } |
613 | quickItemPrivate->extra->resourcesList.clear(); |
614 | } |
615 | } |
616 | |
617 | void QQuick3DObjectPrivate::children_append(QQmlListProperty<QQuick3DObject> *prop, QQuick3DObject *o) |
618 | { |
619 | if (!o) |
620 | return; |
621 | |
622 | QQuick3DObject *that = static_cast<QQuick3DObject *>(prop->object); |
623 | if (o->parentItem() == that) |
624 | o->setParentItem(nullptr); |
625 | |
626 | o->setParentItem(that); |
627 | } |
628 | |
629 | qsizetype QQuick3DObjectPrivate::children_count(QQmlListProperty<QQuick3DObject> *prop) |
630 | { |
631 | QQuick3DObjectPrivate *p = QQuick3DObjectPrivate::get(item: static_cast<QQuick3DObject *>(prop->object)); |
632 | return p->childItems.size(); |
633 | } |
634 | |
635 | QQuick3DObject *QQuick3DObjectPrivate::children_at(QQmlListProperty<QQuick3DObject> *prop, qsizetype index) |
636 | { |
637 | QQuick3DObjectPrivate *p = QQuick3DObjectPrivate::get(item: static_cast<QQuick3DObject *>(prop->object)); |
638 | if (index >= p->childItems.size() || index < 0) |
639 | return nullptr; |
640 | |
641 | return p->childItems.at(i: index); |
642 | } |
643 | |
644 | void QQuick3DObjectPrivate::children_clear(QQmlListProperty<QQuick3DObject> *prop) |
645 | { |
646 | QQuick3DObject *that = static_cast<QQuick3DObject *>(prop->object); |
647 | QQuick3DObjectPrivate *p = QQuick3DObjectPrivate::get(item: that); |
648 | while (!p->childItems.isEmpty()) |
649 | p->childItems.at(i: 0)->setParentItem(nullptr); |
650 | } |
651 | |
652 | void QQuick3DObjectPrivate::_q_resourceObjectDeleted(QObject *object) |
653 | { |
654 | if (extra.isAllocated() && extra->resourcesList.contains(t: object)) |
655 | extra->resourcesList.removeAll(t: object); |
656 | } |
657 | |
658 | void QQuick3DObjectPrivate::_q_cleanupContentItem2D() |
659 | { |
660 | // Only Schedule the item for deletion, as this may get called |
661 | // as a result of teardown as well leading to a double delete |
662 | if (contentItem2d) |
663 | contentItem2d->deleteLater(); |
664 | contentItem2d = nullptr; |
665 | } |
666 | |
667 | void QQuick3DObjectPrivate::addItemChangeListener(QQuick3DObjectChangeListener *listener, ChangeTypes types) |
668 | { |
669 | changeListeners.append(t: ChangeListener(listener, types)); |
670 | } |
671 | |
672 | void QQuick3DObjectPrivate::updateOrAddItemChangeListener(QQuick3DObjectChangeListener *listener, ChangeTypes types) |
673 | { |
674 | const ChangeListener changeListener(listener, types); |
675 | const int index = changeListeners.indexOf(t: changeListener); |
676 | if (index > -1) |
677 | changeListeners[index].types = changeListener.types; |
678 | else |
679 | changeListeners.append(t: changeListener); |
680 | } |
681 | |
682 | void QQuick3DObjectPrivate::removeItemChangeListener(QQuick3DObjectChangeListener *listener, ChangeTypes types) |
683 | { |
684 | ChangeListener change(listener, types); |
685 | changeListeners.removeOne(t: change); |
686 | } |
687 | |
688 | QQuickStateGroup *QQuick3DObjectPrivate::_states() |
689 | { |
690 | Q_Q(QQuick3DObject); |
691 | if (!_stateGroup) { |
692 | _stateGroup = new QQuickStateGroup; |
693 | if (!componentComplete) |
694 | _stateGroup->classBegin(); |
695 | // clang-format off |
696 | qmlobject_connect(_stateGroup, QQuickStateGroup, SIGNAL(stateChanged(QString)), |
697 | q, QQuick3DObject, SIGNAL(stateChanged())); |
698 | // clang-format on |
699 | } |
700 | |
701 | return _stateGroup; |
702 | } |
703 | |
704 | QString QQuick3DObjectPrivate::dirtyToString() const |
705 | { |
706 | #define DIRTY_TO_STRING(value) \ |
707 | if (dirtyAttributes & value) { \ |
708 | if (!rv.isEmpty()) \ |
709 | rv.append(QLatin1Char('|')); \ |
710 | rv.append(QLatin1String(#value)); \ |
711 | } |
712 | |
713 | // QString rv = QLatin1String("0x") + QString::number(dirtyAttributes, 16); |
714 | QString rv; |
715 | |
716 | DIRTY_TO_STRING(TransformOrigin); |
717 | DIRTY_TO_STRING(Transform); |
718 | DIRTY_TO_STRING(BasicTransform); |
719 | DIRTY_TO_STRING(Position); |
720 | DIRTY_TO_STRING(Size); |
721 | DIRTY_TO_STRING(ZValue); |
722 | DIRTY_TO_STRING(Content); |
723 | DIRTY_TO_STRING(Smooth); |
724 | DIRTY_TO_STRING(OpacityValue); |
725 | DIRTY_TO_STRING(ChildrenChanged); |
726 | DIRTY_TO_STRING(ChildrenStackingChanged); |
727 | DIRTY_TO_STRING(ParentChanged); |
728 | DIRTY_TO_STRING(Clip); |
729 | DIRTY_TO_STRING(Window); |
730 | DIRTY_TO_STRING(EffectReference); |
731 | DIRTY_TO_STRING(Visible); |
732 | DIRTY_TO_STRING(HideReference); |
733 | DIRTY_TO_STRING(Antialiasing); |
734 | DIRTY_TO_STRING(InstanceRootChanged); |
735 | |
736 | return rv; |
737 | } |
738 | |
739 | void QQuick3DObjectPrivate::dirty(QQuick3DObjectPrivate::DirtyType type) |
740 | { |
741 | // NOTE: Models that get an instance root has an "external" node that affects its transform |
742 | // we therefore give models with an instance root a lower priority in the update list (See: addToDirtyList()). |
743 | // For this to work we need to re-evaluate models priority when the instance root changes. |
744 | if ((type & InstanceRootChanged) != 0) |
745 | removeFromDirtyList(); |
746 | |
747 | if (!(dirtyAttributes & type) || (sceneManager && !prevDirtyItem)) { |
748 | dirtyAttributes |= type; |
749 | if (sceneManager && componentComplete) { |
750 | addToDirtyList(); |
751 | } |
752 | } |
753 | } |
754 | |
755 | void QQuick3DObjectPrivate::addToDirtyList() |
756 | { |
757 | Q_Q(QQuick3DObject); |
758 | |
759 | Q_ASSERT(sceneManager); |
760 | if (!prevDirtyItem) { |
761 | Q_ASSERT(!nextDirtyItem); |
762 | |
763 | if (QSSGRenderGraphObject::isNodeType(type)) { |
764 | // NOTE: We do special handling of models with an instance root (that is not itself...) |
765 | // to ensure those models are processed after instance root nodes. |
766 | const bool hasInstanceRoot = (type == Type::Model && static_cast<QQuick3DModel *>(q)->instanceRoot() && static_cast<QQuick3DModel *>(q)->instanceRoot() != q); |
767 | const auto dirtyListIdx = !hasInstanceRoot ? QQuick3DSceneManager::nodeListIndex(type) |
768 | : size_t(QQuick3DSceneManager::NodePriority::ModelWithInstanceRoot); |
769 | nextDirtyItem = sceneManager->dirtyNodes[dirtyListIdx]; |
770 | if (nextDirtyItem) |
771 | QQuick3DObjectPrivate::get(item: nextDirtyItem)->prevDirtyItem = &nextDirtyItem; |
772 | prevDirtyItem = &sceneManager->dirtyNodes[dirtyListIdx]; |
773 | sceneManager->dirtyNodes[dirtyListIdx] = q; |
774 | } else if (QSSGRenderGraphObject::isExtension(type)) { |
775 | const auto dirtyListIdx = QQuick3DSceneManager::extensionListIndex(type); |
776 | nextDirtyItem = sceneManager->dirtyExtensions[dirtyListIdx]; |
777 | if (nextDirtyItem) |
778 | QQuick3DObjectPrivate::get(item: nextDirtyItem)->prevDirtyItem = &nextDirtyItem; |
779 | prevDirtyItem = &sceneManager->dirtyExtensions[dirtyListIdx]; |
780 | sceneManager->dirtyExtensions[dirtyListIdx] = q; |
781 | } else { |
782 | const auto dirtyListIdx = QQuick3DSceneManager::resourceListIndex(type); |
783 | nextDirtyItem = sceneManager->dirtyResources[dirtyListIdx]; |
784 | if (nextDirtyItem) |
785 | QQuick3DObjectPrivate::get(item: nextDirtyItem)->prevDirtyItem = &nextDirtyItem; |
786 | prevDirtyItem = &sceneManager->dirtyResources[dirtyListIdx]; |
787 | sceneManager->dirtyResources[dirtyListIdx] = q; |
788 | } |
789 | } |
790 | sceneManager->dirtyItem(item: q); |
791 | Q_ASSERT(prevDirtyItem); |
792 | } |
793 | |
794 | void QQuick3DObjectPrivate::removeFromDirtyList() |
795 | { |
796 | if (prevDirtyItem) { |
797 | if (nextDirtyItem) |
798 | QQuick3DObjectPrivate::get(item: nextDirtyItem)->prevDirtyItem = prevDirtyItem; |
799 | *prevDirtyItem = nextDirtyItem; |
800 | prevDirtyItem = nullptr; |
801 | nextDirtyItem = nullptr; |
802 | } |
803 | Q_ASSERT(!prevDirtyItem); |
804 | Q_ASSERT(!nextDirtyItem); |
805 | } |
806 | |
807 | void QQuick3DObjectPrivate::setCulled(bool cull) |
808 | { |
809 | if (cull == culled) |
810 | return; |
811 | |
812 | culled = cull; |
813 | if ((cull && ++extra.value().hideRefCount == 1) || (!cull && --extra.value().hideRefCount == 0)) |
814 | dirty(type: HideReference); |
815 | } |
816 | |
817 | void QQuick3DObjectPrivate::addChild(QQuick3DObject *child) |
818 | { |
819 | Q_Q(QQuick3DObject); |
820 | |
821 | Q_ASSERT(!childItems.contains(child)); |
822 | |
823 | childItems.append(t: child); |
824 | |
825 | dirty(type: QQuick3DObjectPrivate::ChildrenChanged); |
826 | |
827 | itemChange(QQuick3DObject::ItemChildAddedChange, child); |
828 | |
829 | emit q->childrenChanged(); |
830 | } |
831 | |
832 | void QQuick3DObjectPrivate::removeChild(QQuick3DObject *child) |
833 | { |
834 | Q_Q(QQuick3DObject); |
835 | |
836 | Q_ASSERT(child); |
837 | Q_ASSERT(childItems.contains(child)); |
838 | childItems.removeOne(t: child); |
839 | Q_ASSERT(!childItems.contains(child)); |
840 | |
841 | dirty(type: QQuick3DObjectPrivate::ChildrenChanged); |
842 | |
843 | itemChange(QQuick3DObject::ItemChildRemovedChange, child); |
844 | |
845 | emit q->childrenChanged(); |
846 | } |
847 | |
848 | void QQuick3DObjectPrivate::siblingOrderChanged() |
849 | { |
850 | Q_Q(QQuick3DObject); |
851 | if (!changeListeners.isEmpty()) { |
852 | const auto listeners = changeListeners; // NOTE: intentional copy (QTBUG-54732) |
853 | for (const QQuick3DObjectPrivate::ChangeListener &change : listeners) { |
854 | if (change.types & QQuick3DObjectPrivate::SiblingOrder) { |
855 | change.listener->itemSiblingOrderChanged(q); |
856 | } |
857 | } |
858 | } |
859 | } |
860 | |
861 | void QQuick3DObjectPrivate::refSceneManager(QQuick3DSceneManager &c) |
862 | { |
863 | // An item needs a scene manager if it is referenced by another item which has a scene manager. |
864 | // Typically the item is referenced by a parent, but can also be referenced by a |
865 | // ShaderEffect or ShaderEffectSource. 'sceneRefCount' counts how many items with |
866 | // a scene manager is referencing this item. When the reference count goes from zero to one, |
867 | // or one to zero, the scene manager of this item is updated and propagated to the children. |
868 | // As long as the reference count stays above zero, the scene manager is unchanged. |
869 | // refSceneManager() increments the reference count. |
870 | // derefSceneManager() decrements the reference count. |
871 | |
872 | Q_Q(QQuick3DObject); |
873 | |
874 | // Handle the case where the view has been deleted while the object lives on. |
875 | if (sceneManager.isNull() && sceneRefCount == 1) |
876 | sceneRefCount = 0; |
877 | |
878 | Q_ASSERT((sceneManager != nullptr) == (sceneRefCount > 0)); |
879 | if (++sceneRefCount > 1) { |
880 | // Sanity check. Even if there's a different scene manager the window should be the same. |
881 | if (c.window() != sceneManager->window()) { |
882 | qWarning(msg: "QSSGObject: Cannot use same item on different windows at the same time." ); |
883 | return; |
884 | } |
885 | |
886 | // NOTE: Simple tracking for resources that are shared between scenes. |
887 | if (&c != sceneManager) { |
888 | sharedResource = true; |
889 | for (int ii = 0; ii < childItems.size(); ++ii) { |
890 | QQuick3DObject *child = childItems.at(i: ii); |
891 | QQuick3DObjectPrivate::get(item: child)->sharedResource = sharedResource; |
892 | QQuick3DObjectPrivate::refSceneManager(obj: child, mgr&: c); |
893 | } |
894 | } |
895 | |
896 | return; // Scene manager already set. |
897 | } |
898 | |
899 | Q_ASSERT(sceneManager == nullptr); |
900 | sceneManager = &c; |
901 | |
902 | // if (polishScheduled) |
903 | // QSSGWindowPrivate::get(window)->itemsToPolish.append(q); |
904 | |
905 | if (!parentItem) |
906 | sceneManager->parentlessItems.insert(value: q); |
907 | else |
908 | sharedResource = QQuick3DObjectPrivate::get(item: parentItem)->sharedResource; |
909 | |
910 | for (int ii = 0; ii < childItems.size(); ++ii) { |
911 | QQuick3DObject *child = childItems.at(i: ii); |
912 | QQuick3DObjectPrivate::get(item: child)->sharedResource = sharedResource; |
913 | QQuick3DObjectPrivate::refSceneManager(obj: child, mgr&: c); |
914 | } |
915 | |
916 | dirty(type: Window); |
917 | |
918 | itemChange(QQuick3DObject::ItemSceneChange, &c); |
919 | } |
920 | |
921 | void QQuick3DObjectPrivate::derefSceneManager() |
922 | { |
923 | Q_Q(QQuick3DObject); |
924 | |
925 | if (!sceneManager) |
926 | return; // This can happen when destroying recursive shader effect sources. |
927 | |
928 | if (--sceneRefCount > 0) |
929 | return; // There are still other references, so don't set the scene manager to null yet. |
930 | |
931 | removeFromDirtyList(); |
932 | if (sceneManager) |
933 | sceneManager->dirtyBoundingBoxList.removeAll(t: q); |
934 | |
935 | if (spatialNode) |
936 | sceneManager->cleanup(item: spatialNode); |
937 | if (!parentItem) |
938 | sceneManager->parentlessItems.remove(value: q); |
939 | |
940 | spatialNode = nullptr; |
941 | |
942 | for (int ii = 0; ii < childItems.size(); ++ii) { |
943 | QQuick3DObject *child = childItems.at(i: ii); |
944 | QQuick3DObjectPrivate::derefSceneManager(obj: child); |
945 | } |
946 | |
947 | sceneManager = nullptr; |
948 | |
949 | dirty(type: Window); |
950 | |
951 | itemChange(QQuick3DObject::ItemSceneChange, sceneManager.data()); |
952 | } |
953 | |
954 | void QQuick3DObjectPrivate::updateSubFocusItem(QQuick3DObject *scope, bool focus) |
955 | { |
956 | Q_Q(QQuick3DObject); |
957 | Q_ASSERT(scope); |
958 | |
959 | QQuick3DObjectPrivate *scopePrivate = QQuick3DObjectPrivate::get(item: scope); |
960 | |
961 | QQuick3DObject *oldSubFocusItem = scopePrivate->subFocusItem; |
962 | // Correct focus chain in scope |
963 | if (oldSubFocusItem) { |
964 | QQuick3DObject *sfi = scopePrivate->subFocusItem->parentItem(); |
965 | while (sfi && sfi != scope) { |
966 | QQuick3DObjectPrivate::get(item: sfi)->subFocusItem = nullptr; |
967 | sfi = sfi->parentItem(); |
968 | } |
969 | } |
970 | |
971 | if (focus) { |
972 | scopePrivate->subFocusItem = q; |
973 | QQuick3DObject *sfi = scopePrivate->subFocusItem->parentItem(); |
974 | while (sfi && sfi != scope) { |
975 | QQuick3DObjectPrivate::get(item: sfi)->subFocusItem = q; |
976 | sfi = sfi->parentItem(); |
977 | } |
978 | } else { |
979 | scopePrivate->subFocusItem = nullptr; |
980 | } |
981 | } |
982 | |
983 | void QQuick3DObjectPrivate::itemChange(QQuick3DObject::ItemChange change, const QQuick3DObject::ItemChangeData &data) |
984 | { |
985 | Q_Q(QQuick3DObject); |
986 | switch (change) { |
987 | case QQuick3DObject::ItemRotationHasChanged: |
988 | // TODO: |
989 | qWarning(msg: "ItemRoationHasChange is unhandled!!!!" ); |
990 | break; |
991 | case QQuick3DObject::ItemChildAddedChange: { |
992 | q->itemChange(change, data); |
993 | if (!changeListeners.isEmpty()) { |
994 | const auto listeners = changeListeners; // NOTE: intentional copy (QTBUG-54732) |
995 | for (const QQuick3DObjectPrivate::ChangeListener &change : listeners) { |
996 | if (change.types & QQuick3DObjectPrivate::Children) { |
997 | change.listener->itemChildAdded(q, data.item); |
998 | } |
999 | } |
1000 | } |
1001 | break; |
1002 | } |
1003 | case QQuick3DObject::ItemChildRemovedChange: { |
1004 | q->itemChange(change, data); |
1005 | if (!changeListeners.isEmpty()) { |
1006 | const auto listeners = changeListeners; // NOTE: intentional copy (QTBUG-54732) |
1007 | for (const QQuick3DObjectPrivate::ChangeListener &change : listeners) { |
1008 | if (change.types & QQuick3DObjectPrivate::Children) { |
1009 | change.listener->itemChildRemoved(q, data.item); |
1010 | } |
1011 | } |
1012 | } |
1013 | break; |
1014 | } |
1015 | case QQuick3DObject::ItemSceneChange: |
1016 | q->itemChange(change, data); |
1017 | break; |
1018 | case QQuick3DObject::ItemVisibleHasChanged: { |
1019 | q->itemChange(change, data); |
1020 | if (!changeListeners.isEmpty()) { |
1021 | const auto listeners = changeListeners; // NOTE: intentional copy (QTBUG-54732) |
1022 | for (const QQuick3DObjectPrivate::ChangeListener &change : listeners) { |
1023 | if (change.types & QQuick3DObjectPrivate::Visibility) { |
1024 | change.listener->itemVisibilityChanged(q); |
1025 | } |
1026 | } |
1027 | } |
1028 | break; |
1029 | } |
1030 | case QQuick3DObject::ItemEnabledHasChanged: { |
1031 | q->itemChange(change, data); |
1032 | if (!changeListeners.isEmpty()) { |
1033 | const auto listeners = changeListeners; // NOTE: intentional copy (QTBUG-54732) |
1034 | for (const QQuick3DObjectPrivate::ChangeListener &change : listeners) { |
1035 | if (change.types & QQuick3DObjectPrivate::Enabled) { |
1036 | change.listener->itemEnabledChanged(q); |
1037 | } |
1038 | } |
1039 | } |
1040 | break; |
1041 | } |
1042 | case QQuick3DObject::ItemParentHasChanged: { |
1043 | q->itemChange(change, data); |
1044 | if (!changeListeners.isEmpty()) { |
1045 | const auto listeners = changeListeners; // NOTE: intentional copy (QTBUG-54732) |
1046 | for (const QQuick3DObjectPrivate::ChangeListener &change : listeners) { |
1047 | if (change.types & QQuick3DObjectPrivate::Parent) { |
1048 | change.listener->itemParentChanged(q, data.item); |
1049 | } |
1050 | } |
1051 | } |
1052 | break; |
1053 | } |
1054 | case QQuick3DObject::ItemOpacityHasChanged: { |
1055 | q->itemChange(change, data); |
1056 | if (!changeListeners.isEmpty()) { |
1057 | const auto listeners = changeListeners; // NOTE: intentional copy (QTBUG-54732) |
1058 | for (const QQuick3DObjectPrivate::ChangeListener &change : listeners) { |
1059 | if (change.types & QQuick3DObjectPrivate::Opacity) { |
1060 | change.listener->itemOpacityChanged(q); |
1061 | } |
1062 | } |
1063 | } |
1064 | break; |
1065 | } |
1066 | case QQuick3DObject::ItemActiveFocusHasChanged: |
1067 | q->itemChange(change, data); |
1068 | break; |
1069 | case QQuick3DObject::ItemAntialiasingHasChanged: |
1070 | // fall through |
1071 | case QQuick3DObject::ItemDevicePixelRatioHasChanged: |
1072 | q->itemChange(change, data); |
1073 | break; |
1074 | } |
1075 | } |
1076 | |
1077 | namespace QV4 { |
1078 | namespace Heap { |
1079 | struct QSSGItemWrapper : public QObjectWrapper |
1080 | { |
1081 | static void markObjects(QV4::Heap::Base *that, QV4::MarkStack *markStack); |
1082 | }; |
1083 | } |
1084 | } |
1085 | |
1086 | struct QSSGItemWrapper : public QV4::QObjectWrapper |
1087 | { |
1088 | V4_OBJECT2(QSSGItemWrapper, QV4::QObjectWrapper) |
1089 | }; |
1090 | |
1091 | DEFINE_OBJECT_VTABLE(QSSGItemWrapper); |
1092 | |
1093 | void QV4::Heap::QSSGItemWrapper::markObjects(QV4::Heap::Base *that, QV4::MarkStack *markStack) |
1094 | { |
1095 | QObjectWrapper *This = static_cast<QObjectWrapper *>(that); |
1096 | if (QQuick3DObject *item = static_cast<QQuick3DObject *>(This->object())) { |
1097 | for (QQuick3DObject *child : std::as_const(t&: QQuick3DObjectPrivate::get(item)->childItems)) |
1098 | QV4::QObjectWrapper::markWrapper(object: child, markStack); |
1099 | } |
1100 | QObjectWrapper::markObjects(that, markStack); |
1101 | } |
1102 | |
1103 | quint64 QQuick3DObjectPrivate::_q_createJSWrapper(QV4::ExecutionEngine *engine) |
1104 | { |
1105 | return (engine->memoryManager->allocate<QSSGItemWrapper>(args: q_func()))->asReturnedValue(); |
1106 | } |
1107 | |
1108 | QQuick3DObjectPrivate::ExtraData::() : hideRefCount(0) {} |
1109 | |
1110 | QT_END_NAMESPACE |
1111 | |
1112 | #include "moc_qquick3dobject.cpp" |
1113 | |