Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquick3dparticlemodelshape.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
7#include <QtCore/qdir.h>
8#include <QtQml/qqmlfile.h>
9#include <QtQuick3D/private/qquick3dmodel_p.h>
10#include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
11#include <algorithm>
12
13QT_BEGIN_NAMESPACE
14
15/*!
16 \qmltype ParticleModelShape3D
17 \inherits ParticleAbstractShape3D
18 \inqmlmodule QtQuick3D.Particles3D
19 \brief Offers particle shape from model for emitters and affectors.
20 \since 6.2
21
22 The ParticleModelShape3D element can be used to get particle shape from a 3D model.
23
24 For example, to emit particles from outlines of a model shape:
25
26 \qml
27 Component {
28 id: suzanneComponent
29 Model {
30 source: "meshes/suzanne.mesh"
31 scale: Qt.vector3d(100, 100, 100)
32 }
33 }
34
35 ParticleEmitter3D {
36 shape: ParticleModelShape3D {
37 model: suzanneComponent
38 fill: false
39 }
40 ...
41 }
42 \endqml
43*/
44
45QQuick3DParticleModelShape::QQuick3DParticleModelShape(QObject *parent)
46 : QQuick3DParticleAbstractShape(parent)
47{
48
49}
50
51QQuick3DParticleModelShape::~QQuick3DParticleModelShape()
52{
53 delete m_model;
54}
55
56/*!
57 \qmlproperty bool ParticleModelShape3D::fill
58
59 This property defines if the shape should be filled or just use the shape outlines.
60
61 The default value is \c true.
62*/
63bool QQuick3DParticleModelShape::fill() const
64{
65 return m_fill;
66}
67
68/*!
69 \qmlproperty Component ParticleModelShape3D::delegate
70 The delegate provides a template defining the model for the ParticleModelShape3D.
71 For example, using the default sphere model with default material
72 \qml
73 Component {
74 id: modelComponent
75 Model {
76 source: "#Sphere"
77 scale: Qt.vector3d(0.5, 0.5, 0.5)
78 materials: DefaultMaterial { diffuseColor: "red" }
79 }
80 }
81 ParticleModelShape3D {
82 delegate: modelComponent
83 }
84 \endqml
85*/
86QQmlComponent *QQuick3DParticleModelShape::delegate() const
87{
88 return m_delegate;
89}
90
91void QQuick3DParticleModelShape::setFill(bool fill)
92{
93 if (m_fill == fill)
94 return;
95
96 m_fill = fill;
97 Q_EMIT fillChanged();
98}
99
100QVector3D QQuick3DParticleModelShape::getPosition(int particleIndex)
101{
102 return randomPositionModel(particleIndex);
103}
104
105QVector3D QQuick3DParticleModelShape::getSurfaceNormal(int particleIndex)
106{
107 if (m_cachedIndex != particleIndex)
108 getPosition(particleIndex);
109 return m_cachedNormal;
110}
111
112static QSSGMesh::Mesh loadModelShapeMesh(const QString &source)
113{
114 QString src = source;
115 if (source.startsWith(QLatin1Char('#'))) {
116 src = QSSGBufferManager::primitivePath(source);
117 src.prepend(QLatin1String(":/"));
118 }
119 src = QDir::cleanPath(src);
120 if (src.startsWith(QLatin1String("qrc:/")))
121 src = src.mid(3);
122 QSSGMesh::Mesh mesh;
123 QFileInfo fileInfo = QFileInfo(src);
124 if (fileInfo.exists()) {
125 QFile file(fileInfo.absoluteFilePath());
126 if (!file.open(QFile::ReadOnly))
127 return {};
128 mesh = QSSGMesh::Mesh::loadMesh(&file);
129 }
130 return mesh;
131}
132
133void QQuick3DParticleModelShape::setDelegate(QQmlComponent *delegate)
134{
135 if (delegate == m_delegate)
136 return;
137 m_delegate = delegate;
138 clearModelVertexPositions();
139 createModel();
140 Q_EMIT delegateChanged();
141}
142
143void QQuick3DParticleModelShape::createModel()
144{
145 delete m_model;
146 m_model = nullptr;
147 if (!m_delegate)
148 return;
149 auto *obj = m_delegate->create(m_delegate->creationContext());
150 m_model = qobject_cast<QQuick3DModel *>(obj);
151 if (!m_model)
152 delete obj;
153}
154
155QVector3D QQuick3DParticleModelShape::randomPositionModel(int particleIndex)
156{
157 if (m_model) {
158 calculateModelVertexPositions();
159
160 const QVector<QVector3D> &positions = m_vertexPositions;
161 if (positions.size() > 0) {
162 auto rand = m_system->rand();
163
164 // Calculate model triangle areas so that the random triangle selection can be weighted
165 // by the area. This way particles are uniformly emitted from the whole model.
166 if (m_modelTriangleAreas.size() == 0) {
167 m_modelTriangleAreas.reserve(positions.size() / 3);
168 for (int i = 0; i + 2 < positions.size(); i += 3) {
169 const QVector3D &v1 = positions[i];
170 const QVector3D &v2 = positions[i + 1];
171 const QVector3D &v3 = positions[i + 2];
172 const float area = QVector3D::crossProduct(v1 - v2, v1 - v3).length() * 0.5f;
173 m_modelTriangleAreasSum += area;
174 m_modelTriangleAreas.append(m_modelTriangleAreasSum);
175 m_modelTriangleCenter += v1 + v2 + v3;
176 }
177 m_modelTriangleCenter /= positions.size();
178 }
179
180 const float rndWeight = rand->get(particleIndex, QPRand::Shape1) * m_modelTriangleAreasSum;
181
182 // Use binary search to find the weighted random index
183 int index = std::lower_bound(m_modelTriangleAreas.begin(), m_modelTriangleAreas.end(), rndWeight) - m_modelTriangleAreas.begin();
184
185 const QVector3D &v1 = positions[index * 3];
186 const QVector3D &v2 = positions[index * 3 + 1];
187 const QVector3D &v3 = positions[index * 3 + 2];
188 const float a = rand->get(particleIndex, QPRand::Shape2);
189 const float b = rand->get(particleIndex, QPRand::Shape3);
190 const float aSqrt = qSqrt(a);
191
192 // Calculate a random point from the selected triangle
193 QVector3D pos = (1.0 - aSqrt) * v1 + (aSqrt * (1.0 - b)) * v2 + (b * aSqrt) * v3;
194
195 if (m_fill) {
196 // The model is filled by selecting a random point between a random surface point
197 // and the center of the model. The random point selection is exponentially weighted
198 // towards the surface so that particles aren't clustered in the center.
199 const float uniform = rand->get(particleIndex, QPRand::Shape4);
200 const float lambda = 5.0f;
201 const float alpha = -qLn(1 - (1 - qExp(-lambda)) * uniform) / lambda;
202 pos += (m_modelTriangleCenter - pos) * alpha;
203 } else {
204 m_cachedIndex = particleIndex;
205 m_cachedNormal = QVector3D::crossProduct(v2 - v1, v3 - v2).normalized();
206 }
207
208 auto *parent = parentNode();
209 if (parent) {
210 QMatrix4x4 mat;
211 mat.rotate(parent->rotation() * m_model->rotation());
212 if (!m_fill)
213 m_cachedNormal = mat.mapVector(m_cachedNormal);
214 return mat.mapVector(pos * parent->sceneScale() * m_model->scale());
215 }
216 }
217 }
218 return QVector3D(0, 0, 0);
219}
220
221void QQuick3DParticleModelShape::clearModelVertexPositions()
222{
223 m_vertexPositions.clear();
224 m_modelTriangleAreas.clear();
225 m_modelTriangleAreasSum = 0;
226}
227
228void QQuick3DParticleModelShape::calculateModelVertexPositions()
229{
230 if (m_vertexPositions.empty()) {
231 QVector<QVector3D> indicedPositions;
232 QVector<QVector3D> positions;
233
234 if (m_model->geometry()) {
235 QQuick3DGeometry *geometry = m_model->geometry();
236 bool hasIndexBuffer = false;
237 QQuick3DGeometry::Attribute::ComponentType indexBufferFormat;
238 int posOffset = 0;
239 QQuick3DGeometry::Attribute::ComponentType posType = QQuick3DGeometry::Attribute::U16Type;
240 for (int i = 0; i < geometry->attributeCount(); ++i) {
241 auto attribute = geometry->attribute(i);
242 if (attribute.semantic == QQuick3DGeometry::Attribute::PositionSemantic) {
243 posOffset = attribute.offset;
244 posType = attribute.componentType;
245 } else if (attribute.semantic == QQuick3DGeometry::Attribute::IndexSemantic) {
246 hasIndexBuffer = true;
247 indexBufferFormat = attribute.componentType;
248 }
249 }
250 if (posType == QQuick3DGeometry::Attribute::F32Type) {
251 const auto &data = geometry->vertexData();
252 int stride = geometry->stride();
253 for (int i = 0; i < data.size(); i += stride) {
254 float v[3];
255 memcpy(v, data + posOffset + i, sizeof(v));
256 positions.append(QVector3D(v[0], v[1], v[2]));
257 }
258 if (hasIndexBuffer) {
259 const auto &data = geometry->vertexData();
260 int indexSize = 4;
261 if (indexBufferFormat == QQuick3DGeometry::Attribute::U16Type)
262 indexSize = 2;
263 for (int i = 0; i < data.size(); i += indexSize) {
264 qsizetype index = 0;
265 memcpy(&index, data + i, indexSize);
266 if (positions.size() > index)
267 indicedPositions.append(positions[index]);
268 }
269 }
270 }
271 } else {
272 const QQmlContext *context = qmlContext(this);
273 QString src = m_model->source().toString();
274 if (context && !src.startsWith(QLatin1Char('#')))
275 src = QQmlFile::urlToLocalFileOrQrc(context->resolvedUrl(m_model->source()));
276 QSSGMesh::Mesh mesh = loadModelShapeMesh(src);
277 if (!mesh.isValid())
278 return;
279 if (mesh.drawMode() != QSSGMesh::Mesh::DrawMode::Triangles)
280 return;
281
282 auto entries = mesh.vertexBuffer().entries;
283 int posOffset = 0;
284 int posCount = 0;
285 // Just set 'posType' to something to avoid invalid 'maybe-uninitialized' warning
286 QSSGMesh::Mesh::ComponentType posType = QSSGMesh::Mesh::ComponentType::UnsignedInt8;
287 for (int i = 0; i < entries.size(); ++i) {
288 const char *nameStr = entries[i].name.constData();
289 if (!strcmp(nameStr, QSSGMesh::MeshInternal::getPositionAttrName())) {
290 posOffset = entries[i].offset;
291 posCount = entries[i].componentCount;
292 posType = entries[i].componentType;
293 break;
294 }
295 }
296 if (posCount == 3 && posType == QSSGMesh::Mesh::ComponentType::Float32) {
297 const auto &data = mesh.vertexBuffer().data;
298 int stride = mesh.vertexBuffer().stride;
299 for (int i = 0; i < data.size(); i += stride) {
300 float v[3];
301 memcpy(v, data + posOffset + i, sizeof(v));
302 positions.append(QVector3D(v[0], v[1], v[2]));
303 }
304 const auto &indexData = mesh.indexBuffer().data;
305 int indexSize = QSSGMesh::MeshInternal::byteSizeForComponentType(mesh.indexBuffer().componentType);
306 for (int i = 0; i < indexData.size(); i += indexSize) {
307 qsizetype index = 0;
308 memcpy(&index, indexData + i, indexSize);
309 if (positions.size() > index)
310 indicedPositions.append(positions[index]);
311 }
312 }
313 }
314 if (!indicedPositions.empty())
315 m_vertexPositions = indicedPositions;
316 else
317 m_vertexPositions = positions;
318 }
319}
320
321QT_END_NAMESPACE
static QSSGMesh::Mesh loadModelShapeMesh(const QString &source)