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// Qt-Security score:significant reason:default
4
5
10#include <QtQml/qqmlfile.h>
11#include <QtQuick3D/private/qquick3dmodel_p.h>
12#include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
13#include <algorithm>
14
15QT_BEGIN_NAMESPACE
16
17/*!
18 \qmltype ParticleModelShape3D
19 \inherits ParticleAbstractShape3D
20 \inqmlmodule QtQuick3D.Particles3D
21 \brief Offers particle shape from model for emitters and affectors.
22 \since 6.2
23
24 The ParticleModelShape3D element can be used to get particle shape from a 3D model.
25
26 For example, to emit particles from outlines of a model shape:
27
28 \qml
29 Component {
30 id: suzanneComponent
31 Model {
32 source: "meshes/suzanne.mesh"
33 scale: Qt.vector3d(100, 100, 100)
34 }
35 }
36
37 ParticleEmitter3D {
38 shape: ParticleModelShape3D {
39 model: suzanneComponent
40 fill: false
41 }
42 ...
43 }
44 \endqml
45*/
46
47QQuick3DParticleModelShape::QQuick3DParticleModelShape(QObject *parent)
48 : QQuick3DParticleAbstractShape(parent)
49{
50
51}
52
53QQuick3DParticleModelShape::~QQuick3DParticleModelShape()
54{
55 delete m_model;
56}
57
58/*!
59 \qmlproperty bool ParticleModelShape3D::fill
60
61 This property defines if the shape should be filled or just use the shape outlines.
62
63 The default value is \c true.
64*/
65bool QQuick3DParticleModelShape::fill() const
66{
67 return m_fill;
68}
69
70/*!
71 \qmlproperty Component ParticleModelShape3D::delegate
72 The delegate provides a template defining the model for the ParticleModelShape3D.
73 For example, using the default sphere model with default material
74 \qml
75 Component {
76 id: modelComponent
77 Model {
78 source: "#Sphere"
79 scale: Qt.vector3d(0.5, 0.5, 0.5)
80 materials: DefaultMaterial { diffuseColor: "red" }
81 }
82 }
83 ParticleModelShape3D {
84 delegate: modelComponent
85 }
86 \endqml
87*/
88QQmlComponent *QQuick3DParticleModelShape::delegate() const
89{
90 return m_delegate;
91}
92
93void QQuick3DParticleModelShape::setFill(bool fill)
94{
95 if (m_fill == fill)
96 return;
97
98 m_fill = fill;
99 Q_EMIT fillChanged();
100}
101
102QVector3D QQuick3DParticleModelShape::getPosition(int particleIndex)
103{
104 return randomPositionModel(particleIndex);
105}
106
107QVector3D QQuick3DParticleModelShape::getSurfaceNormal(int particleIndex)
108{
109 if (m_fill)
110 return QVector3D();
111 if (m_cachedIndex != particleIndex)
112 getPosition(particleIndex);
113 return m_cachedNormal;
114}
115
116void QQuick3DParticleModelShape::setDelegate(QQmlComponent *delegate)
117{
118 if (delegate == m_delegate)
119 return;
120 m_delegate = delegate;
121 clearModelVertexPositions();
122 createModel();
123 Q_EMIT delegateChanged();
124}
125
126void QQuick3DParticleModelShape::createModel()
127{
128 delete m_model;
129 m_model = nullptr;
130 if (!m_delegate)
131 return;
132 auto *obj = m_delegate->create(m_delegate->creationContext());
133 m_model = qobject_cast<QQuick3DModel *>(obj);
134 if (!m_model)
135 delete obj;
136}
137
138QVector3D QQuick3DParticleModelShape::randomPositionModel(int particleIndex)
139{
140 if (m_model) {
141 calculateModelVertexPositions();
142
143 const QVector<QVector3D> &positions = m_vertexPositions;
144 if (positions.size() > 0) {
145 auto rand = m_system->rand();
146
147 // Calculate model triangle areas so that the random triangle selection can be weighted
148 // by the area. This way particles are uniformly emitted from the whole model.
149 if (m_modelTriangleAreas.size() == 0) {
150 m_modelTriangleAreas.reserve(positions.size() / 3);
151 for (int i = 0; i + 2 < positions.size(); i += 3) {
152 const QVector3D &v1 = positions[i];
153 const QVector3D &v2 = positions[i + 1];
154 const QVector3D &v3 = positions[i + 2];
155 const float area = QVector3D::crossProduct(v1 - v2, v1 - v3).length() * 0.5f;
156 m_modelTriangleAreasSum += area;
157 m_modelTriangleAreas.append(m_modelTriangleAreasSum);
158 m_modelTriangleCenter += v1 + v2 + v3;
159 }
160 m_modelTriangleCenter /= positions.size();
161 }
162
163 const float rndWeight = rand->get(particleIndex, QPRand::Shape1) * m_modelTriangleAreasSum;
164
165 // Use binary search to find the weighted random index
166 int index = std::lower_bound(m_modelTriangleAreas.begin(), m_modelTriangleAreas.end(), rndWeight) - m_modelTriangleAreas.begin();
167
168 const QVector3D &v1 = positions[index * 3];
169 const QVector3D &v2 = positions[index * 3 + 1];
170 const QVector3D &v3 = positions[index * 3 + 2];
171 const float a = rand->get(particleIndex, QPRand::Shape2);
172 const float b = rand->get(particleIndex, QPRand::Shape3);
173 const float aSqrt = qSqrt(a);
174
175 // Calculate a random point from the selected triangle
176 QVector3D pos = (1.0 - aSqrt) * v1 + (aSqrt * (1.0 - b)) * v2 + (b * aSqrt) * v3;
177
178 if (m_fill) {
179 // The model is filled by selecting a random point between a random surface point
180 // and the center of the model. The random point selection is exponentially weighted
181 // towards the surface so that particles aren't clustered in the center.
182 const float uniform = rand->get(particleIndex, QPRand::Shape4);
183 const float lambda = 5.0f;
184 const float alpha = -qLn(1 - (1 - qExp(-lambda)) * uniform) / lambda;
185 pos += (m_modelTriangleCenter - pos) * alpha;
186 } else {
187 m_cachedIndex = particleIndex;
188 m_cachedNormal = QVector3D::crossProduct(v2 - v1, v3 - v2).normalized();
189 }
190
191 auto *parent = parentNode();
192 if (parent) {
193 QMatrix4x4 mat;
194 mat.rotate(parent->rotation() * m_model->rotation());
195 if (!m_fill)
196 m_cachedNormal = mat.mapVector(m_cachedNormal);
197 return mat.mapVector(pos * parent->sceneScale() * m_model->scale());
198 }
199 }
200 }
201 return QVector3D(0, 0, 0);
202}
203
204void QQuick3DParticleModelShape::clearModelVertexPositions()
205{
206 m_vertexPositions.clear();
207 m_modelTriangleAreas.clear();
208 m_modelTriangleAreasSum = 0;
209}
210
211void QQuick3DParticleModelShape::calculateModelVertexPositions()
212{
213 if (m_vertexPositions.empty())
214 m_vertexPositions = positionsFromModel(m_model, nullptr, qmlContext(this));
215}
216
217QT_END_NAMESPACE