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_cachedIndex != particleIndex)
110 getPosition(particleIndex);
111 return m_cachedNormal;
112}
113
114void QQuick3DParticleModelShape::setDelegate(QQmlComponent *delegate)
115{
116 if (delegate == m_delegate)
117 return;
118 m_delegate = delegate;
119 clearModelVertexPositions();
120 createModel();
121 Q_EMIT delegateChanged();
122}
123
124void QQuick3DParticleModelShape::createModel()
125{
126 delete m_model;
127 m_model = nullptr;
128 if (!m_delegate)
129 return;
130 auto *obj = m_delegate->create(m_delegate->creationContext());
131 m_model = qobject_cast<QQuick3DModel *>(obj);
132 if (!m_model)
133 delete obj;
134}
135
136QVector3D QQuick3DParticleModelShape::randomPositionModel(int particleIndex)
137{
138 if (m_model) {
139 calculateModelVertexPositions();
140
141 const QVector<QVector3D> &positions = m_vertexPositions;
142 if (positions.size() > 0) {
143 auto rand = m_system->rand();
144
145 // Calculate model triangle areas so that the random triangle selection can be weighted
146 // by the area. This way particles are uniformly emitted from the whole model.
147 if (m_modelTriangleAreas.size() == 0) {
148 m_modelTriangleAreas.reserve(positions.size() / 3);
149 for (int i = 0; i + 2 < positions.size(); i += 3) {
150 const QVector3D &v1 = positions[i];
151 const QVector3D &v2 = positions[i + 1];
152 const QVector3D &v3 = positions[i + 2];
153 const float area = QVector3D::crossProduct(v1 - v2, v1 - v3).length() * 0.5f;
154 m_modelTriangleAreasSum += area;
155 m_modelTriangleAreas.append(m_modelTriangleAreasSum);
156 m_modelTriangleCenter += v1 + v2 + v3;
157 }
158 m_modelTriangleCenter /= positions.size();
159 }
160
161 const float rndWeight = rand->get(particleIndex, QPRand::Shape1) * m_modelTriangleAreasSum;
162
163 // Use binary search to find the weighted random index
164 int index = std::lower_bound(m_modelTriangleAreas.begin(), m_modelTriangleAreas.end(), rndWeight) - m_modelTriangleAreas.begin();
165
166 const QVector3D &v1 = positions[index * 3];
167 const QVector3D &v2 = positions[index * 3 + 1];
168 const QVector3D &v3 = positions[index * 3 + 2];
169 const float a = rand->get(particleIndex, QPRand::Shape2);
170 const float b = rand->get(particleIndex, QPRand::Shape3);
171 const float aSqrt = qSqrt(a);
172
173 // Calculate a random point from the selected triangle
174 QVector3D pos = (1.0 - aSqrt) * v1 + (aSqrt * (1.0 - b)) * v2 + (b * aSqrt) * v3;
175
176 if (m_fill) {
177 // The model is filled by selecting a random point between a random surface point
178 // and the center of the model. The random point selection is exponentially weighted
179 // towards the surface so that particles aren't clustered in the center.
180 const float uniform = rand->get(particleIndex, QPRand::Shape4);
181 const float lambda = 5.0f;
182 const float alpha = -qLn(1 - (1 - qExp(-lambda)) * uniform) / lambda;
183 pos += (m_modelTriangleCenter - pos) * alpha;
184 } else {
185 m_cachedIndex = particleIndex;
186 m_cachedNormal = QVector3D::crossProduct(v2 - v1, v3 - v2).normalized();
187 }
188
189 auto *parent = parentNode();
190 if (parent) {
191 QMatrix4x4 mat;
192 mat.rotate(parent->rotation() * m_model->rotation());
193 if (!m_fill)
194 m_cachedNormal = mat.mapVector(m_cachedNormal);
195 return mat.mapVector(pos * parent->sceneScale() * m_model->scale());
196 }
197 }
198 }
199 return QVector3D(0, 0, 0);
200}
201
202void QQuick3DParticleModelShape::clearModelVertexPositions()
203{
204 m_vertexPositions.clear();
205 m_modelTriangleAreas.clear();
206 m_modelTriangleAreasSum = 0;
207}
208
209void QQuick3DParticleModelShape::calculateModelVertexPositions()
210{
211 if (m_vertexPositions.empty())
212 m_vertexPositions = positionsFromModel(m_model, nullptr, qmlContext(this));
213}
214
215QT_END_NAMESPACE