1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3dparticlemodelparticle_p.h"
5
6QT_BEGIN_NAMESPACE
7
8/*!
9 \qmltype ModelParticle3D
10 \inherits Particle3D
11 \inqmlmodule QtQuick3D.Particles3D
12 \brief Particle using a Qt Quick 3D Model.
13 \since 6.2
14
15 The ModelParticle3D is a logical particle element that creates particles
16 from a Qt Quick 3D \l Model component.
17*/
18
19QQuick3DParticleModelParticle::QQuick3DParticleModelParticle(QQuick3DNode *parent)
20 : QQuick3DParticle(parent)
21 , m_initialScale(1.0f, 1.0f, 1.0f)
22{
23 QObject::connect(sender: this, signal: &QQuick3DParticle::maxAmountChanged, slot: [this]() {
24 handleMaxAmountChanged(amount: m_maxAmount);
25 });
26 QObject::connect(sender: this, signal: &QQuick3DParticle::sortModeChanged, slot: [this]() {
27 handleSortModeChanged(mode: sortMode());
28 });
29}
30
31void QQuick3DParticleModelParticle::handleMaxAmountChanged(int amount)
32{
33 if (m_particleData.size() == amount)
34 return;
35
36 m_particleData.resize(size: amount);
37 m_particleData.fill(t: {});
38}
39
40/*!
41 \qmlproperty Component ModelParticle3D::delegate
42
43 The delegate provides a template defining each object instantiated by the particle.
44
45 For example, to allocate 200 red cube particles:
46
47 \qml
48 Component {
49 id: particleComponent
50 Model {
51 source: "#Cube"
52 scale: Qt.vector3d(0.2, 0.2, 0.2)
53 materials: DefaultMaterial { }
54 }
55 }
56
57 ModelParticle3D {
58 id: particleRed
59 delegate: particleComponent
60 maxAmount: 200
61 color: "#ff0000"
62 }
63 \endqml
64*/
65QQmlComponent *QQuick3DParticleModelParticle::delegate() const
66{
67 return m_delegate.data();
68}
69
70void QQuick3DParticleModelParticle::setDelegate(QQmlComponent *delegate)
71{
72 if (delegate == m_delegate)
73 return;
74 m_delegate = delegate;
75
76 regenerate();
77 Q_EMIT delegateChanged();
78}
79
80class QQuick3DParticleInstanceTable : public QQuick3DInstancing
81{
82 struct SortData
83 {
84 float age;
85 int index;
86 };
87
88public:
89 QQuick3DParticleInstanceTable() {}
90 void clear() { m_instances.clear(); m_sortData.clear(); }
91 void commit() { sort(); markDirty(); }
92 void addInstance(const QVector3D &position,
93 const QVector3D &scale,
94 const QVector3D &eulerRotation,
95 const QColor &color,
96 float age) {
97 auto entry = calculateTableEntry(position, scale, eulerRotation, color);
98 m_instances.append(s: reinterpret_cast<char *>(&entry), len: sizeof(InstanceTableEntry));
99 if (m_ageSorting)
100 m_sortData.append(t: {.age: age, .index: int(m_instances.size() / sizeof(InstanceTableEntry))});
101 }
102 void setSorting(bool enable, bool inverted = false)
103 {
104 m_ageSorting = enable;
105 m_inverted = inverted;
106 }
107protected:
108 QByteArray getInstanceBuffer(int *instanceCount) override
109 {
110 if (instanceCount)
111 *instanceCount = int(m_instances.size() / sizeof(InstanceTableEntry));
112
113 if (!m_ageSorting)
114 return m_instances;
115 return m_sortedInstances;
116 }
117 void sort()
118 {
119 if (!m_ageSorting)
120 return;
121
122 if (m_inverted) {
123 std::sort(first: m_sortData.begin(), last: m_sortData.end(), comp: [&](const SortData &a, const SortData &b) {
124 return a.age < b.age;
125 });
126 } else {
127 std::sort(first: m_sortData.begin(), last: m_sortData.end(), comp: [&](const SortData &a, const SortData &b) {
128 return a.age > b.age;
129 });
130 }
131 m_sortedInstances.resize(size: m_instances.size());
132 const InstanceTableEntry *src = reinterpret_cast<InstanceTableEntry *>(m_instances.data());
133 InstanceTableEntry *dst = reinterpret_cast<InstanceTableEntry *>(m_sortedInstances.data());
134 for (auto &e : m_sortData)
135 *dst++ = src[e.index];
136 }
137
138private:
139 QList<SortData> m_sortData;
140 QByteArray m_instances;
141 QByteArray m_sortedInstances;
142 bool m_ageSorting = false;
143 bool m_inverted = false;
144};
145
146void QQuick3DParticleModelParticle::handleSortModeChanged(QQuick3DParticle::SortMode mode)
147{
148 if (m_instanceTable) {
149 if (mode == QQuick3DParticle::SortNewest || mode == QQuick3DParticle::SortOldest)
150 m_instanceTable->setSorting(enable: true, inverted: mode == QQuick3DParticle::SortNewest);
151 else
152 m_instanceTable->setSorting(enable: false);
153 m_instanceTable->setDepthSortingEnabled(mode == QQuick3DParticle::SortDistance);
154 }
155}
156
157/*!
158 \qmlproperty Instancing ModelParticle3D::instanceTable
159
160 The instanceTable provides access to the \l Instancing table of the model particle.
161 ModelParticle3D uses an internal instance table to implement efficient rendering.
162 This table can be applied to the \l{Model::instancing}{instancing} property of models
163 that are not part of the particle system.
164
165 It is also possible to use this feature to provide an instancing table without showing any
166 particles. This is done by omitting the \l delegate property. For example:
167
168 \qml
169 ParticleSystem3D {
170 id: psystem
171 ModelParticle3D {
172 id: particleRed
173 maxAmount: 200
174 color: "#ff0000"
175 colorVariation: Qt.vector4d(0.5,0.5,0.5,0.5)
176 }
177
178 ParticleEmitter3D {
179 particle: particleRed
180 velocity: VectorDirection3D {
181 direction: Qt.vector3d(-20, 200, 0)
182 directionVariation: Qt.vector3d(20, 20, 20)
183 }
184 particleScale: 0.2
185 emitRate: 20
186 lifeSpan: 2000
187 }
188 }
189
190 Model {
191 source: "#Sphere"
192 instancing: particleRed.instanceTable
193 materials: PrincipledMaterial {
194 baseColor: "yellow"
195 }
196 }
197 \endqml
198*/
199
200QQuick3DInstancing *QQuick3DParticleModelParticle::instanceTable() const
201{
202 return m_instanceTable;
203}
204
205void QQuick3DParticleModelParticle::clearInstanceTable()
206{
207 if (m_instanceTable)
208 m_instanceTable->clear();
209}
210
211void QQuick3DParticleModelParticle::addInstance(const QVector3D &position, const QVector3D &scale, const QVector3D &eulerRotation, const QColor &color, float age)
212{
213 if (m_instanceTable)
214 m_instanceTable->addInstance(position, scale, eulerRotation, color, age);
215}
216
217void QQuick3DParticleModelParticle::commitInstance()
218{
219 if (m_instanceTable) {
220 m_instanceTable->setHasTransparency(hasTransparency());
221 m_instanceTable->commit();
222 }
223}
224
225static void setInstancing(QQuick3DNode *node, QQuick3DInstancing *instanceTable, float bias)
226{
227 auto *asModel = qobject_cast<QQuick3DModel *>(object: node);
228 if (asModel) {
229 asModel->setInstancing(instanceTable);
230 asModel->setDepthBias(bias);
231 }
232 const auto children = node->childItems();
233 for (auto *child : children) {
234 auto *childNode = qobject_cast<QQuick3DNode *>(object: child);
235 if (childNode)
236 setInstancing(node: childNode, instanceTable, bias);
237 }
238}
239
240void QQuick3DParticleModelParticle::updateDepthBias(float bias)
241{
242 setInstancing(node: m_node, instanceTable: m_instanceTable, bias);
243}
244
245void QQuick3DParticleModelParticle::regenerate()
246{
247 delete m_node;
248 m_node = nullptr;
249
250 if (!isComponentComplete())
251 return;
252
253 if (!m_instanceTable) {
254 m_instanceTable = new QQuick3DParticleInstanceTable();
255 m_instanceTable->setParent(this);
256 m_instanceTable->setParentItem(this);
257 emit instanceTableChanged();
258 } else {
259 m_instanceTable->clear();
260 }
261
262 if (m_delegate.isNull())
263 return;
264
265 auto *obj = m_delegate->create(context: m_delegate->creationContext());
266
267 m_node = qobject_cast<QQuick3DNode *>(object: obj);
268 if (m_node) {
269 setInstancing(node: m_node, instanceTable: m_instanceTable, bias: depthBias());
270 auto *particleSystem = system();
271 m_node->setParent(particleSystem);
272 m_node->setParentItem(particleSystem);
273 } else {
274 delete obj;
275 }
276}
277
278void QQuick3DParticleModelParticle::componentComplete()
279{
280 if (!system() && qobject_cast<QQuick3DParticleSystem *>(object: parentItem()))
281 setSystem(qobject_cast<QQuick3DParticleSystem *>(object: parentItem()));
282
283 QQuick3DParticle::componentComplete();
284 regenerate();
285}
286
287void QQuick3DParticleModelParticle::itemChange(QQuick3DObject::ItemChange change, const QQuick3DObject::ItemChangeData &value)
288{
289 QQuick3DObject::itemChange(change, value);
290 if (change == ItemParentHasChanged)
291 regenerate();
292}
293
294QT_END_NAMESPACE
295

source code of qtquick3d/src/quick3dparticles/qquick3dparticlemodelparticle.cpp