1 | // Copyright (C) 2021 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "qquick3dparticlemodelblendparticle_p.h" |
5 | #include "qquick3dparticleemitter_p.h" |
6 | #include "qquick3dparticlerandomizer_p.h" |
7 | |
8 | #include <QtCore/qdir.h> |
9 | #include <QtQml/qqmlfile.h> |
10 | |
11 | #include <QtQuick3D/private/qquick3dobject_p.h> |
12 | #include <QtQuick3D/private/qquick3dgeometry_p.h> |
13 | |
14 | #include <QtQuick3DUtils/private/qssgutils_p.h> |
15 | #include <QtQuick3DRuntimeRender/private/qssgrenderparticles_p.h> |
16 | #include <QtQuick3DRuntimeRender/private/qssgrendergeometry_p.h> |
17 | #include <QtQuick3DRuntimeRender/private/qssgrendermodel_p.h> |
18 | #include <QtQuick3DUtils/private/qssgmesh_p.h> |
19 | |
20 | QT_BEGIN_NAMESPACE |
21 | |
22 | /*! |
23 | \qmltype ModelBlendParticle3D |
24 | \inherits Particle3D |
25 | \inqmlmodule QtQuick3D.Particles3D |
26 | \brief Blends particle effect with a 3D model. |
27 | |
28 | The type provides a way to blend particle effect with a 3D model. The provided model needs to be |
29 | triangle-based. Each triangle in the model is converted into a particle, which are then used by |
30 | the emitter. Instead of particle shader, the model is shaded using the \l {Model::materials}{material} |
31 | specified in the model. The way the effect is blended is determined by the \l modelBlendMode. |
32 | |
33 | The possible modes are: |
34 | \list |
35 | \li \b Construct, where the model gets constructed from the particles. |
36 | \li \b Explode, where the model gets converted into particles. |
37 | \li \b Transfer, where Construct and Explode are combined to create an effect where the model is |
38 | transferred from one place to another. |
39 | \endlist |
40 | |
41 | By default the particles are emitted in the order they are specified in the model unless \l emitMode is set |
42 | to \c Random or \l emitMode is set to \c Activation and \l activationNode is set. |
43 | |
44 | Some features defined in base class and emitters are not functional with this type: |
45 | \list |
46 | \li \l Particle3D::alignMode is not functional since the particles can be in arbitrary orientation |
47 | in the model. |
48 | \li\l Particle3D::sortMode is not functional since the particles are always rendered in the order |
49 | the primitives are specified in the model. |
50 | \li \l ParticleEmitter3D::depthBias is not functional since the model depth bias is used instead. |
51 | \endlist |
52 | |
53 | \note The default \l {Particle3D::fadeInEffect}{fadeInEffect} and \l {Particle3D::fadeInEffect}{fadeOutEffect} |
54 | are \c Particle3D.FadeNone. |
55 | */ |
56 | |
57 | QQuick3DParticleModelBlendParticle::QQuick3DParticleModelBlendParticle(QQuick3DNode *parent) |
58 | : QQuick3DParticle(*new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::ModelBlendParticle), parent) |
59 | { |
60 | setFadeInEffect(QQuick3DParticle::FadeNone); |
61 | setFadeOutEffect(QQuick3DParticle::FadeNone); |
62 | QQuick3DParticle::doSetMaxAmount(amount: 0); |
63 | } |
64 | |
65 | QQuick3DParticleModelBlendParticle::~QQuick3DParticleModelBlendParticle() |
66 | { |
67 | delete m_model; |
68 | delete m_modelGeometry; |
69 | } |
70 | |
71 | /*! |
72 | \qmlproperty Component ModelBlendParticle3D::delegate |
73 | |
74 | The delegate provides a template defining the model for the ModelBlendParticle3D. |
75 | |
76 | For example, using the default sphere model with default material |
77 | |
78 | \qml |
79 | Component { |
80 | id: modelComponent |
81 | Model { |
82 | source: "#Sphere" |
83 | scale: Qt.vector3d(0.5, 0.5, 0.5) |
84 | materials: DefaultMaterial { diffuseColor: "red" } |
85 | } |
86 | } |
87 | |
88 | ModelBlendParticle3D { |
89 | id: particleRedSphere |
90 | delegate: modelComponent |
91 | } |
92 | \endqml |
93 | */ |
94 | QQmlComponent *QQuick3DParticleModelBlendParticle::delegate() const |
95 | { |
96 | return m_delegate; |
97 | } |
98 | |
99 | void QQuick3DParticleModelBlendParticle::setDelegate(QQmlComponent *delegate) |
100 | { |
101 | if (delegate == m_delegate) |
102 | return; |
103 | m_delegate = delegate; |
104 | |
105 | reset(); |
106 | regenerate(); |
107 | Q_EMIT delegateChanged(); |
108 | } |
109 | |
110 | /*! |
111 | \qmlproperty Node ModelBlendParticle3D::endNode |
112 | |
113 | This property holds the node that specifies the transformation for the model at the end |
114 | of particle effect. It defines the size, position and rotation where the model is constructed |
115 | when using the \c ModelBlendParticle3D.Construct and \c ModelBlendParticle3D.Transfer blend modes. |
116 | */ |
117 | QQuick3DNode *QQuick3DParticleModelBlendParticle::endNode() const |
118 | { |
119 | return m_endNode; |
120 | } |
121 | |
122 | /*! |
123 | \qmlproperty enumeration ModelBlendParticle3D::ModelBlendMode |
124 | |
125 | Defines the blending mode for the particle effect. |
126 | |
127 | \value ModelBlendParticle3D.Explode |
128 | The model gets exploded i.e. the particles are emitted from the position of the model. |
129 | \value ModelBlendParticle3D.Construct |
130 | The model gets constructed i.e the particles fly from the emitter and construct the model at the end. |
131 | \value ModelBlendParticle3D.Transfer |
132 | Combines Explode and Transfer for the same model. |
133 | */ |
134 | /*! |
135 | \qmlproperty ModelBlendMode ModelBlendParticle3D::modelBlendMode |
136 | |
137 | This property holds blending mode for the particle effect. |
138 | */ |
139 | QQuick3DParticleModelBlendParticle::ModelBlendMode QQuick3DParticleModelBlendParticle::modelBlendMode() const |
140 | { |
141 | return m_modelBlendMode; |
142 | } |
143 | |
144 | /*! |
145 | \qmlproperty int ModelBlendParticle3D::endTime |
146 | |
147 | This property holds the end time of the particle in milliseconds. The end time is used during construction |
148 | and defines duration from particle lifetime at the end where the effect is blended with |
149 | the model positions. Before the end time the particles positions are defined only by the |
150 | particle effect, but during end time the particle position is blended linearly with the model |
151 | end position. |
152 | */ |
153 | int QQuick3DParticleModelBlendParticle::endTime() const |
154 | { |
155 | return m_endTime; |
156 | } |
157 | |
158 | /*! |
159 | \qmlproperty Node ModelBlendParticle3D::activationNode |
160 | |
161 | This property holds a node that activates particles and overrides the reqular emit routine. |
162 | The activation node can be used to control how the particles are emitted spatially when the |
163 | model is exploded/constructed from the particles. |
164 | The activation node emits a particle if the center of that particle is on the positive half |
165 | of the z-axis of the activation node. Animating the activation node to move trough the model |
166 | will cause the particles to be emitted sequentially along the path the activation node moves. |
167 | */ |
168 | QQuick3DNode *QQuick3DParticleModelBlendParticle::activationNode() const |
169 | { |
170 | return m_activationNode; |
171 | } |
172 | |
173 | /*! |
174 | \qmlproperty enumeration ModelBlendParticle3D::ModelBlendEmitMode |
175 | |
176 | Defines the emit mode of the particles |
177 | |
178 | \value ModelBlendParticle3D.Sequential |
179 | The particles are emitted in the order they are defined in the model. |
180 | \value ModelBlendParticle3D.Random |
181 | The particles are emitted in random order. |
182 | \value ModelBlendParticle3D.Activation |
183 | The particles are emitted when they are activated by the \l activationNode. |
184 | */ |
185 | /*! |
186 | \qmlproperty bool ModelBlendParticle3D::emitMode |
187 | |
188 | This property holds the emit mode of the particles. |
189 | */ |
190 | QQuick3DParticleModelBlendParticle::ModelBlendEmitMode QQuick3DParticleModelBlendParticle::emitMode() const |
191 | { |
192 | return m_emitMode; |
193 | } |
194 | |
195 | void QQuick3DParticleModelBlendParticle::setEndNode(QQuick3DNode *node) |
196 | { |
197 | if (m_endNode == node) |
198 | return; |
199 | if (m_endNode) |
200 | QObject::disconnect(receiver: this); |
201 | |
202 | m_endNode = node; |
203 | |
204 | if (m_endNode) { |
205 | QObject::connect(sender: m_endNode, signal: &QQuick3DNode::positionChanged, context: this, slot: &QQuick3DParticleModelBlendParticle::handleEndNodeChanged); |
206 | QObject::connect(sender: m_endNode, signal: &QQuick3DNode::rotationChanged, context: this, slot: &QQuick3DParticleModelBlendParticle::handleEndNodeChanged); |
207 | QObject::connect(sender: m_endNode, signal: &QQuick3DNode::scaleChanged, context: this, slot: &QQuick3DParticleModelBlendParticle::handleEndNodeChanged); |
208 | } |
209 | handleEndNodeChanged(); |
210 | Q_EMIT endNodeChanged(); |
211 | } |
212 | |
213 | void QQuick3DParticleModelBlendParticle::setModelBlendMode(ModelBlendMode mode) |
214 | { |
215 | if (m_modelBlendMode == mode) |
216 | return; |
217 | m_modelBlendMode = mode; |
218 | reset(); |
219 | Q_EMIT modelBlendModeChanged(); |
220 | } |
221 | |
222 | void QQuick3DParticleModelBlendParticle::setEndTime(int endTime) |
223 | { |
224 | if (endTime == m_endTime) |
225 | return; |
226 | m_endTime = endTime; |
227 | Q_EMIT endTimeChanged(); |
228 | } |
229 | |
230 | void QQuick3DParticleModelBlendParticle::setActivationNode(QQuick3DNode *activationNode) |
231 | { |
232 | if (m_activationNode == activationNode) |
233 | return; |
234 | |
235 | m_activationNode = activationNode; |
236 | Q_EMIT activationNodeChanged(); |
237 | } |
238 | |
239 | void QQuick3DParticleModelBlendParticle::setEmitMode(ModelBlendEmitMode emitMode) |
240 | { |
241 | if (m_emitMode == emitMode) |
242 | return; |
243 | |
244 | m_emitMode = emitMode; |
245 | Q_EMIT emitModeChanged(); |
246 | } |
247 | |
248 | void QQuick3DParticleModelBlendParticle::regenerate() |
249 | { |
250 | delete m_model; |
251 | m_model = nullptr; |
252 | |
253 | if (!isComponentComplete()) |
254 | return; |
255 | |
256 | if (!m_delegate) |
257 | return; |
258 | |
259 | if (QQuick3DParticleSystem::isGloballyDisabled()) |
260 | return; |
261 | |
262 | auto *obj = m_delegate->create(context: m_delegate->creationContext()); |
263 | |
264 | m_model = qobject_cast<QQuick3DModel *>(object: obj); |
265 | if (m_model) { |
266 | updateParticles(); |
267 | auto *psystem = QQuick3DParticle::system(); |
268 | m_model->setParent(psystem); |
269 | m_model->setParentItem(psystem); |
270 | } else { |
271 | delete obj; |
272 | } |
273 | handleEndNodeChanged(); |
274 | } |
275 | |
276 | static QSSGMesh::Mesh loadModelBlendParticleMesh(const QString &source) |
277 | { |
278 | QString src = source; |
279 | if (source.startsWith(c: QLatin1Char('#'))) { |
280 | src = QSSGBufferManager::primitivePath(primitive: source); |
281 | src.prepend(s: QLatin1String(":/" )); |
282 | } |
283 | src = QDir::cleanPath(path: src); |
284 | if (src.startsWith(s: QLatin1String("qrc:/" ))) |
285 | src = src.mid(position: 3); |
286 | |
287 | QFile file(src); |
288 | if (!file.open(flags: QFile::ReadOnly)) |
289 | return {}; |
290 | return QSSGMesh::Mesh::loadMesh(device: &file); |
291 | } |
292 | |
293 | static QVector3D getPosition(const quint8 *srcVertices, quint32 idx, quint32 vertexStride, quint32 posOffset) |
294 | { |
295 | const quint8 *vertex = srcVertices + idx * vertexStride; |
296 | return *reinterpret_cast<const QVector3D *>(vertex + posOffset); |
297 | } |
298 | |
299 | static float calcTriangleRadius(const QVector3D ¢er, const QVector3D &p0, const QVector3D &p1, const QVector3D &p2) |
300 | { |
301 | return qMax(a: center.distanceToPoint(point: p1), b: qMax(a: center.distanceToPoint(point: p2), b: center.distanceToPoint(point: p0))); |
302 | } |
303 | |
304 | static void copyToUnindexedVertices(QByteArray &unindexedVertexData, |
305 | QVector<QVector3D> ¢erData, |
306 | float &maxTriangleRadius, |
307 | const QByteArray &vertexBufferData, |
308 | quint32 vertexStride, |
309 | quint32 posOffset, |
310 | const QByteArray &indexBufferData, |
311 | bool u16Indices, |
312 | quint32 primitiveCount) |
313 | { |
314 | const quint8 *srcVertices = reinterpret_cast<const quint8 *>(vertexBufferData.data()); |
315 | quint8 *dst = reinterpret_cast<quint8 *>(unindexedVertexData.data()); |
316 | const quint16 *indexData16 = reinterpret_cast<const quint16 *>(indexBufferData.begin()); |
317 | const quint32 *indexData32 = reinterpret_cast<const quint32 *>(indexBufferData.begin()); |
318 | const float c_div3 = 1.0f / 3.0f; |
319 | for (quint32 i = 0; i < primitiveCount; i++) { |
320 | quint32 i0, i1, i2; |
321 | if (u16Indices) { |
322 | i0 = indexData16[3 * i]; |
323 | i1 = indexData16[3 * i + 1]; |
324 | i2 = indexData16[3 * i + 2]; |
325 | } else { |
326 | i0 = indexData32[3 * i]; |
327 | i1 = indexData32[3 * i + 1]; |
328 | i2 = indexData32[3 * i + 2]; |
329 | } |
330 | QVector3D p0 = getPosition(srcVertices, idx: i0, vertexStride, posOffset); |
331 | QVector3D p1 = getPosition(srcVertices, idx: i1, vertexStride, posOffset); |
332 | QVector3D p2 = getPosition(srcVertices, idx: i2, vertexStride, posOffset); |
333 | QVector3D center = (p0 + p1 + p2) * c_div3; |
334 | centerData[i] = center; |
335 | maxTriangleRadius = qMax(a: maxTriangleRadius, b: calcTriangleRadius(center, p0, p1, p2)); |
336 | memcpy(dest: dst, src: srcVertices + i0 * vertexStride, n: vertexStride); |
337 | dst += vertexStride; |
338 | memcpy(dest: dst, src: srcVertices + i1 * vertexStride, n: vertexStride); |
339 | dst += vertexStride; |
340 | memcpy(dest: dst, src: srcVertices + i2 * vertexStride, n: vertexStride); |
341 | dst += vertexStride; |
342 | } |
343 | } |
344 | |
345 | static void getVertexCenterData(QVector<QVector3D> ¢erData, |
346 | float &maxTriangleRadius, |
347 | const QByteArray &vertexBufferData, |
348 | quint32 vertexStride, |
349 | quint32 posOffset, |
350 | quint32 primitiveCount) |
351 | { |
352 | const quint8 *srcVertices = reinterpret_cast<const quint8 *>(vertexBufferData.data()); |
353 | const float c_div3 = 1.0f / 3.0f; |
354 | for (quint32 i = 0; i < primitiveCount; i++) { |
355 | QVector3D p0 = getPosition(srcVertices, idx: 3 * i, vertexStride, posOffset); |
356 | QVector3D p1 = getPosition(srcVertices, idx: 3 * i + 1, vertexStride, posOffset); |
357 | QVector3D p2 = getPosition(srcVertices, idx: 3 * i + 2, vertexStride, posOffset); |
358 | QVector3D center = (p0 + p1 + p2) * c_div3; |
359 | centerData[i] = center; |
360 | maxTriangleRadius = qMax(a: maxTriangleRadius, b: calcTriangleRadius(center, p0, p1, p2)); |
361 | } |
362 | } |
363 | |
364 | void QQuick3DParticleModelBlendParticle::updateParticles() |
365 | { |
366 | m_maxTriangleRadius = 0.f; |
367 | |
368 | // The primitives needs to be triangle list without indexing, because each triangle |
369 | // needs to be it's own primitive and we need vertex index to get the particle index, |
370 | // which we don't get with indexed primitives |
371 | if (m_model->geometry()) { |
372 | QQuick3DGeometry *geometry = m_model->geometry(); |
373 | if (geometry->primitiveType() != QQuick3DGeometry::PrimitiveType::Triangles) { |
374 | qWarning () << "ModelBlendParticle3D: Invalid geometry primitive type, must be Triangles. " ; |
375 | return; |
376 | } |
377 | auto vertexBuffer = geometry->vertexData(); |
378 | auto indexBuffer = geometry->indexData(); |
379 | |
380 | if (!vertexBuffer.size()) { |
381 | qWarning () << "ModelBlendParticle3D: Invalid geometry, vertexData is empty. " ; |
382 | return; |
383 | } |
384 | |
385 | const auto attributeBySemantic = [&](const QQuick3DGeometry *geometry, QQuick3DGeometry::Attribute::Semantic semantic) { |
386 | for (int i = 0; i < geometry->attributeCount(); i++) { |
387 | const auto attr = geometry->attribute(index: i); |
388 | if (attr.semantic == semantic) |
389 | return attr; |
390 | } |
391 | Q_ASSERT(0); |
392 | return QQuick3DGeometry::Attribute(); |
393 | }; |
394 | |
395 | if (indexBuffer.size()) { |
396 | m_modelGeometry = new QQuick3DGeometry; |
397 | |
398 | m_modelGeometry->setBounds(min: geometry->boundsMin(), max: geometry->boundsMax()); |
399 | m_modelGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles); |
400 | m_modelGeometry->setStride(geometry->stride()); |
401 | |
402 | for (int i = 0; i < geometry->attributeCount(); i++) { |
403 | auto attr = geometry->attribute(index: i); |
404 | if (attr.semantic != QQuick3DGeometry::Attribute::IndexSemantic) |
405 | m_modelGeometry->addAttribute(att: attr); |
406 | } |
407 | |
408 | // deindex data |
409 | QByteArray unindexedVertexData; |
410 | quint32 primitiveCount = indexBuffer.size(); |
411 | auto indexAttribute = attributeBySemantic(geometry, QQuick3DGeometry::Attribute::IndexSemantic); |
412 | bool u16IndexType = indexAttribute.componentType == QQuick3DGeometry::Attribute::U16Type; |
413 | if (u16IndexType) |
414 | primitiveCount /= 6; |
415 | else |
416 | primitiveCount /= 12; |
417 | |
418 | unindexedVertexData.resize(size: geometry->stride() * primitiveCount * 3); |
419 | m_centerData.resize(size: primitiveCount); |
420 | m_particleCount = primitiveCount; |
421 | copyToUnindexedVertices(unindexedVertexData, |
422 | centerData&: m_centerData, |
423 | maxTriangleRadius&: m_maxTriangleRadius, |
424 | vertexBufferData: vertexBuffer, |
425 | vertexStride: geometry->stride(), |
426 | posOffset: attributeBySemantic(geometry, QQuick3DGeometry::Attribute::PositionSemantic).offset, |
427 | indexBufferData: indexBuffer, |
428 | u16Indices: u16IndexType, |
429 | primitiveCount); |
430 | |
431 | m_modelGeometry->setVertexData(unindexedVertexData); |
432 | m_model->setGeometry(m_modelGeometry); |
433 | } else { |
434 | // can use provided geometry directly |
435 | quint32 primitiveCount = vertexBuffer.size() / geometry->stride() / 3; |
436 | m_centerData.resize(size: primitiveCount); |
437 | m_particleCount = primitiveCount; |
438 | getVertexCenterData(centerData&: m_centerData, |
439 | maxTriangleRadius&: m_maxTriangleRadius, |
440 | vertexBufferData: vertexBuffer, |
441 | vertexStride: geometry->stride(), |
442 | posOffset: attributeBySemantic(geometry, QQuick3DGeometry::Attribute::PositionSemantic).offset, |
443 | primitiveCount); |
444 | } |
445 | } else { |
446 | const QQmlContext *context = qmlContext(this); |
447 | QString src = m_model->source().toString(); |
448 | if (context && !src.startsWith(c: QLatin1Char('#'))) |
449 | src = QQmlFile::urlToLocalFileOrQrc(context->resolvedUrl(m_model->source())); |
450 | QSSGMesh::Mesh mesh = loadModelBlendParticleMesh(source: src); |
451 | if (!mesh.isValid()) { |
452 | qWarning () << "ModelBlendParticle3D: Unable to load mesh: " << src; |
453 | return; |
454 | } |
455 | if (mesh.drawMode() != QSSGMesh::Mesh::DrawMode::Triangles) { |
456 | qWarning () << "ModelBlendParticle3D: Invalid mesh primitive type, must be Triangles. " ; |
457 | return; |
458 | } |
459 | |
460 | m_modelGeometry = new QQuick3DGeometry; |
461 | |
462 | const auto vertexBuffer = mesh.vertexBuffer(); |
463 | const auto indexBuffer = mesh.indexBuffer(); |
464 | |
465 | const auto entryOffset = [&](const QSSGMesh::Mesh::VertexBuffer &vb, const QByteArray &name) -> int { |
466 | for (const auto &e : vb.entries) { |
467 | if (e.name == name) { |
468 | Q_ASSERT(e.componentType == QSSGMesh::Mesh::ComponentType::Float32); |
469 | return e.offset; |
470 | } |
471 | } |
472 | Q_ASSERT(0); |
473 | return -1; |
474 | }; |
475 | const auto toAttribute = [&](const QSSGMesh::Mesh::VertexBufferEntry &e) -> QQuick3DGeometry::Attribute { |
476 | QQuick3DGeometry::Attribute a; |
477 | a.componentType = QQuick3DGeometryPrivate::toComponentType(componentType: e.componentType); |
478 | a.offset = e.offset; |
479 | a.semantic = QQuick3DGeometryPrivate::semanticFromName(name: e.name); |
480 | return a; |
481 | }; |
482 | |
483 | const auto indexedPrimitiveCount = [&](const QSSGMesh::Mesh::IndexBuffer &indexBuffer) -> quint32 { |
484 | if (indexBuffer.componentType == QSSGMesh::Mesh::ComponentType::UnsignedInt16) |
485 | return quint32(indexBuffer.data.size() / sizeof(quint16) / 3); |
486 | return quint32(indexBuffer.data.size() / sizeof(quint32) / 3); |
487 | }; |
488 | |
489 | if (indexBuffer.data.size()) { |
490 | // deindex data |
491 | QByteArray unindexedVertexData; |
492 | quint32 primitiveCount = indexedPrimitiveCount(indexBuffer); |
493 | bool u16IndexType = indexBuffer.componentType == QSSGMesh::Mesh::ComponentType::UnsignedInt16; |
494 | unindexedVertexData.resize(size: vertexBuffer.stride * primitiveCount * 3); |
495 | m_centerData.resize(size: primitiveCount); |
496 | m_particleCount = primitiveCount; |
497 | |
498 | copyToUnindexedVertices(unindexedVertexData, |
499 | centerData&: m_centerData, |
500 | maxTriangleRadius&: m_maxTriangleRadius, |
501 | vertexBufferData: vertexBuffer.data, |
502 | vertexStride: vertexBuffer.stride, |
503 | posOffset: entryOffset(vertexBuffer, QByteArray(QSSGMesh::MeshInternal::getPositionAttrName())), |
504 | indexBufferData: indexBuffer.data, |
505 | u16Indices: u16IndexType, |
506 | primitiveCount); |
507 | m_modelGeometry->setBounds(min: mesh.subsets().first().bounds.min, max: mesh.subsets().first().bounds.max); |
508 | m_modelGeometry->setStride(vertexBuffer.stride); |
509 | m_modelGeometry->setVertexData(unindexedVertexData); |
510 | m_modelGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles); |
511 | } else { |
512 | // can use vertexbuffer directly |
513 | quint32 primitiveCount = vertexBuffer.data.size() / vertexBuffer.stride / 3; |
514 | m_centerData.resize(size: primitiveCount); |
515 | m_particleCount = primitiveCount; |
516 | getVertexCenterData(centerData&: m_centerData, |
517 | maxTriangleRadius&: m_maxTriangleRadius, |
518 | vertexBufferData: vertexBuffer.data, |
519 | vertexStride: vertexBuffer.stride, |
520 | posOffset: entryOffset(vertexBuffer, QByteArray(QSSGMesh::MeshInternal::getPositionAttrName())), |
521 | primitiveCount); |
522 | m_modelGeometry->setBounds(min: mesh.subsets().first().bounds.min, max: mesh.subsets().first().bounds.max); |
523 | m_modelGeometry->setStride(vertexBuffer.stride); |
524 | m_modelGeometry->setVertexData(vertexBuffer.data); |
525 | m_modelGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles); |
526 | } |
527 | for (auto &e : vertexBuffer.entries) |
528 | m_modelGeometry->addAttribute(att: toAttribute(e)); |
529 | for (auto &s : mesh.subsets()) |
530 | m_modelGeometry->addSubset(offset: s.offset, count: s.count, boundsMin: s.bounds.min, boundsMax: s.bounds.max, name: s.name); |
531 | |
532 | m_model->setSource({}); |
533 | m_model->setGeometry(m_modelGeometry); |
534 | } |
535 | |
536 | QMatrix4x4 transform = m_model->sceneTransform(); |
537 | if (m_model->parentNode()) |
538 | transform = m_model->parentNode()->sceneTransform().inverted() * transform; |
539 | const QVector3D scale = QSSGUtils::mat44::getScale(m: transform); |
540 | // Take max component scale for a conservative bounds estimation |
541 | const float scaleMax = qMax(a: scale.x(), b: qMax(a: scale.y(), b: scale.z())); |
542 | m_maxTriangleRadius *= scaleMax; |
543 | |
544 | m_triangleParticleData.resize(size: m_particleCount); |
545 | m_particleData.resize(size: m_particleCount); |
546 | m_particleData.fill(t: {}); |
547 | for (int i = 0; i < m_particleCount; i++) { |
548 | m_triangleParticleData[i].center = m_centerData[i]; |
549 | m_centerData[i] = transform.map(point: m_centerData[i]); |
550 | if (m_modelBlendMode == Construct) { |
551 | m_triangleParticleData[i].size = 0.0f; |
552 | } else { |
553 | m_triangleParticleData[i].size = 1.0f; |
554 | m_triangleParticleData[i].position = m_centerData[i]; |
555 | } |
556 | } |
557 | QQuick3DParticle::doSetMaxAmount(amount: m_particleCount); |
558 | } |
559 | |
560 | QSSGRenderGraphObject *QQuick3DParticleModelBlendParticle::updateSpatialNode(QSSGRenderGraphObject *node) |
561 | { |
562 | if (!m_model) |
563 | return node; |
564 | auto *spatialNode = QQuick3DObjectPrivate::get(item: m_model)->spatialNode; |
565 | if (!spatialNode) { |
566 | spatialNode = QQuick3DObjectPrivate::updateSpatialNode(o: m_model, n: nullptr); |
567 | QQuick3DObjectPrivate::get(item: m_model)->spatialNode = spatialNode; |
568 | Q_QUICK3D_PROFILE_ASSIGN_ID_SG(this, spatialNode); |
569 | } |
570 | auto *geometrySpatialNode = QQuick3DObjectPrivate::get(item: m_modelGeometry)->spatialNode; |
571 | if (geometrySpatialNode) |
572 | Q_QUICK3D_PROFILE_ASSIGN_ID_SG(this, geometrySpatialNode); |
573 | |
574 | QSSGRenderModel *model = static_cast<QSSGRenderModel *>(spatialNode); |
575 | |
576 | if (!model->particleBuffer) { |
577 | QSSGParticleBuffer *buffer = model->particleBuffer = new QSSGParticleBuffer; |
578 | buffer->resize(particleCount: m_particleCount, particleSize: sizeof(QSSGTriangleParticle)); |
579 | } |
580 | QQuick3DParticleSystem *psystem = QQuick3DParticle::system(); |
581 | QMatrix4x4 particleMatrix = psystem->sceneTransform().inverted() * m_model->sceneTransform(); |
582 | model->particleMatrix = particleMatrix.inverted(); |
583 | model->hasTransparency = fadeInEffect() == QQuick3DParticle::FadeOpacity || fadeOutEffect() == QQuick3DParticle::FadeOpacity; |
584 | updateParticleBuffer(buffer: model->particleBuffer, sceneTransform: psystem->sceneTransform()); |
585 | |
586 | return node; |
587 | } |
588 | |
589 | void QQuick3DParticleModelBlendParticle::componentComplete() |
590 | { |
591 | if (!system() && qobject_cast<QQuick3DParticleSystem *>(object: parentItem())) |
592 | setSystem(qobject_cast<QQuick3DParticleSystem *>(object: parentItem())); |
593 | |
594 | // don't call particles componentComplete, we don't wan't to emit maxAmountChanged yet |
595 | QQuick3DObject::componentComplete(); |
596 | regenerate(); |
597 | } |
598 | |
599 | void QQuick3DParticleModelBlendParticle::doSetMaxAmount(int) |
600 | { |
601 | qWarning() << "ModelBlendParticle3D.maxAmount: Unable to set maximum amount, because it is set from the model." ; |
602 | return; |
603 | } |
604 | |
605 | int QQuick3DParticleModelBlendParticle::nextCurrentIndex(const QQuick3DParticleEmitter *emitter) |
606 | { |
607 | if (!m_perEmitterData.contains(key: emitter)) { |
608 | m_perEmitterData.insert(key: emitter, value: PerEmitterData()); |
609 | auto &perEmitter = m_perEmitterData[emitter]; |
610 | perEmitter.emitter = emitter; |
611 | perEmitter.emitterIndex = m_nextEmitterIndex++; |
612 | } |
613 | auto &perEmitter = m_perEmitterData[emitter]; |
614 | int index = QQuick3DParticle::nextCurrentIndex(emitter); |
615 | if (m_triangleParticleData[index].emitterIndex != perEmitter.emitterIndex) { |
616 | if (m_triangleParticleData[index].emitterIndex >= 0) |
617 | perEmitterData(emitterIndex: m_triangleParticleData[index].emitterIndex).particleCount--; |
618 | perEmitter.particleCount++; |
619 | } |
620 | m_triangleParticleData[index].emitterIndex = perEmitter.emitterIndex; |
621 | return index; |
622 | } |
623 | |
624 | |
625 | void QQuick3DParticleModelBlendParticle::setParticleData(int particleIndex, |
626 | const QVector3D &position, |
627 | const QVector3D &rotation, |
628 | const QVector4D &color, |
629 | float size, float age) |
630 | { |
631 | auto &dst = m_triangleParticleData[particleIndex]; |
632 | dst = {.position: position, .rotation: rotation, .center: dst.center, .color: color, .age: age, .size: size, .emitterIndex: dst.emitterIndex}; |
633 | m_dataChanged = true; |
634 | } |
635 | |
636 | QQuick3DParticleModelBlendParticle::PerEmitterData &QQuick3DParticleModelBlendParticle::perEmitterData(int emitterIndex) |
637 | { |
638 | for (auto &perEmitter : m_perEmitterData) { |
639 | if (perEmitter.emitterIndex == emitterIndex) |
640 | return perEmitter; |
641 | } |
642 | return n_noPerEmitterData; |
643 | } |
644 | |
645 | void QQuick3DParticleModelBlendParticle::updateParticleBuffer(QSSGParticleBuffer *buffer, const QMatrix4x4 &sceneTransform) |
646 | { |
647 | const auto &particles = m_triangleParticleData; |
648 | |
649 | if (!buffer || !m_dataChanged) |
650 | return; |
651 | |
652 | const int particleCount = m_particleCount; |
653 | |
654 | char *dest = buffer->pointer(); |
655 | const TriangleParticleData *src = particles.data(); |
656 | const int pps = buffer->particlesPerSlice(); |
657 | const int ss = buffer->sliceStride(); |
658 | const int slices = buffer->sliceCount(); |
659 | const float c_degToRad = float(M_PI / 180.0f); |
660 | int i = 0; |
661 | QSSGBounds3 bounds; |
662 | for (int s = 0; s < slices; s++) { |
663 | QSSGTriangleParticle *dp = reinterpret_cast<QSSGTriangleParticle *>(dest); |
664 | for (int p = 0; p < pps && i < particleCount; ) { |
665 | if (src->size > 0.0f) |
666 | bounds.include(v: src->position); |
667 | dp->position = src->position; |
668 | dp->rotation = src->rotation * c_degToRad; |
669 | dp->color = src->color; |
670 | dp->age = src->age; |
671 | dp->center = src->center; |
672 | dp->size = src->size; |
673 | dp++; |
674 | p++; |
675 | i++; |
676 | src++; |
677 | } |
678 | dest += ss; |
679 | } |
680 | |
681 | bounds.fatten(distance: m_maxTriangleRadius); |
682 | bounds.transform(inMatrix: sceneTransform); |
683 | buffer->setBounds(bounds); |
684 | m_dataChanged = false; |
685 | } |
686 | |
687 | void QQuick3DParticleModelBlendParticle::itemChange(QQuick3DObject::ItemChange change, |
688 | const QQuick3DObject::ItemChangeData &value) |
689 | { |
690 | QQuick3DObject::itemChange(change, value); |
691 | if (change == ItemParentHasChanged && value.sceneManager) |
692 | regenerate(); |
693 | } |
694 | |
695 | void QQuick3DParticleModelBlendParticle::reset() |
696 | { |
697 | QQuick3DParticle::reset(); |
698 | if (m_particleCount) { |
699 | for (int i = 0; i < m_particleCount; i++) { |
700 | if (m_modelBlendMode == Construct) { |
701 | m_triangleParticleData[i].size = 0.0f; |
702 | } else { |
703 | m_triangleParticleData[i].size = 1.0f; |
704 | m_triangleParticleData[i].position = m_triangleParticleData[i].center; |
705 | } |
706 | } |
707 | } |
708 | } |
709 | |
710 | QVector3D QQuick3DParticleModelBlendParticle::particleCenter(int particleIndex) const |
711 | { |
712 | return m_centerData[particleIndex]; |
713 | } |
714 | |
715 | bool QQuick3DParticleModelBlendParticle::lastParticle() const |
716 | { |
717 | return m_currentIndex >= m_maxAmount - 1; |
718 | } |
719 | |
720 | static QMatrix3x3 qt_fromEulerRotation(const QVector3D &eulerRotation) |
721 | { |
722 | float x = qDegreesToRadians(degrees: eulerRotation.x()); |
723 | float y = qDegreesToRadians(degrees: eulerRotation.y()); |
724 | float z = qDegreesToRadians(degrees: eulerRotation.z()); |
725 | float a = cos(x: x); |
726 | float b = sin(x: x); |
727 | float c = cos(x: y); |
728 | float d = sin(x: y); |
729 | float e = cos(x: z); |
730 | float f = sin(x: z); |
731 | QMatrix3x3 ret; |
732 | float bd = b * d; |
733 | float ad = a * d; |
734 | ret(0,0) = c * e; |
735 | ret(0,1) = -c * f; |
736 | ret(0,2) = d; |
737 | ret(1,0) = bd * e + a * f; |
738 | ret(1,1) = a * e - bd * f; |
739 | ret(1,2) = -b * c; |
740 | ret(2,0) = b * f - ad * e; |
741 | ret(2,1) = ad * f + b * e; |
742 | ret(2,2) = a * c; |
743 | return ret; |
744 | } |
745 | |
746 | void QQuick3DParticleModelBlendParticle::handleEndNodeChanged() |
747 | { |
748 | if (m_endNode && m_model) { |
749 | if (!m_model->rotation().isIdentity()) { |
750 | // Use the same function as the shader for end node rotation so that they produce same matrix |
751 | QMatrix3x3 r1 = qt_fromEulerRotation(eulerRotation: m_endNode->eulerRotation()); |
752 | QMatrix3x3 r2 = m_model->rotation().toRotationMatrix(); |
753 | QMatrix3x3 r = r2 * r1.transposed() * r2.transposed(); |
754 | m_endNodeRotation = m_endNode->eulerRotation(); |
755 | m_endRotationMatrix = QMatrix4x4(r); |
756 | } else { |
757 | m_endNodeRotation = m_endNode->eulerRotation(); |
758 | m_endRotationMatrix = QMatrix4x4(m_endNode->rotation().toRotationMatrix().transposed()); |
759 | } |
760 | m_endNodePosition = m_endNode->position(); |
761 | m_endNodeScale = m_endNode->scale(); |
762 | } else { |
763 | m_endNodePosition = QVector3D(); |
764 | m_endNodeRotation = QVector3D(); |
765 | m_endNodeScale = QVector3D(1.0f, 1.0f, 1.0f); |
766 | m_endRotationMatrix.setToIdentity(); |
767 | } |
768 | } |
769 | |
770 | QVector3D QQuick3DParticleModelBlendParticle::particleEndPosition(int idx) const |
771 | { |
772 | return m_endRotationMatrix.map(point: QVector3D(m_endNodeScale * m_centerData[idx])) + m_endNodePosition; |
773 | } |
774 | |
775 | QVector3D QQuick3DParticleModelBlendParticle::particleEndRotation(int) const |
776 | { |
777 | return m_endNodeRotation; |
778 | } |
779 | |
780 | int QQuick3DParticleModelBlendParticle::randomIndex(int particleIndex) |
781 | { |
782 | if (m_randomParticles.isEmpty()) { |
783 | m_randomParticles.resize(size: m_maxAmount); |
784 | for (int i = 0; i < m_maxAmount; i++) |
785 | m_randomParticles[i] = i; |
786 | |
787 | // Randomize particle indices just once |
788 | QRandomGenerator rand(system()->rand()->generator()); |
789 | for (int i = 0; i < m_maxAmount; i++) { |
790 | int ridx = rand.generate() % m_maxAmount; |
791 | if (i != ridx) |
792 | qSwap(value1&: m_randomParticles[i], value2&: m_randomParticles[ridx]); |
793 | } |
794 | } |
795 | return m_randomParticles[particleIndex]; |
796 | } |
797 | |
798 | QT_END_NAMESPACE |
799 | |