1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3dparticlecustomshape_p.h"
5#include "qquick3dparticlerandomizer_p.h"
6#include "qquick3dparticlesystem_p.h"
7#include "qquick3dparticleutils_p.h"
8#include "qquick3dparticleshapedatautils_p.h"
9#include <QtQml/qqmlcontext.h>
10#include <QtQml/qqmlfile.h>
11#include <QtCore/qfile.h>
12
13QT_BEGIN_NAMESPACE
14
15/*!
16 \qmltype ParticleCustomShape3D
17 \inherits ParticleAbtractShape3D
18 \inqmlmodule QtQuick3D.Particles3D
19 \brief Loads custom particle shapes for emitters and affectors.
20 \since 6.3
21
22 The ParticleCustomShape3D element can be used to load custom particle shapes.
23
24 For example, to emit particles from positions defined in heart.cbor:
25
26 \qml
27 ParticleEmitter3D {
28 shape: ParticleCustomShape3D {
29 source: "heart.cbor"
30 }
31 ...
32 }
33 \endqml
34
35 The format of CBOR shape files is following:
36 \badcode
37 [
38 "QQ3D_SHAPE", // string
39 version, // integer
40 [
41 posX, // float
42 posY, // float
43 posZ, // float
44 posX, // float
45 ...
46 ]
47 ]
48 \endcode
49
50 To assist in generating these shape files you can use the shapegen tool.
51*/
52
53QQuick3DParticleCustomShape::QQuick3DParticleCustomShape(QObject *parent)
54 : QQuick3DParticleAbstractShape(parent)
55{
56}
57
58/*!
59 \qmlproperty url ParticleCustomShape3D::source
60
61 This property holds the location of the shape file.
62*/
63
64QUrl QQuick3DParticleCustomShape::source() const
65{
66 return m_source;
67}
68
69/*!
70 \qmlproperty bool ParticleCustomShape3D::randomizeData
71
72 This property holds whether the particles are used in random order instead
73 of in the order they are specified in the source.
74
75 The default value is \c false.
76*/
77bool QQuick3DParticleCustomShape::randomizeData() const
78{
79 return m_random;
80}
81
82void QQuick3DParticleCustomShape::setSource(const QUrl &source)
83{
84 if (m_source == source)
85 return;
86
87 m_source = source;
88
89 loadFromSource();
90 Q_EMIT sourceChanged();
91}
92
93void QQuick3DParticleCustomShape::setRandomizeData(bool random)
94{
95 if (m_random == random)
96 return;
97
98 m_random = random;
99 if (m_random)
100 m_randomizeDirty = true;
101 Q_EMIT randomizeDataChanged();
102}
103
104void QQuick3DParticleCustomShape::loadFromSource()
105{
106 m_positions.clear();
107
108 // Get path to file
109 const QQmlContext *context = qmlContext(this);
110 QString dataFilePath = QQmlFile::urlToLocalFileOrQrc(context ? context->resolvedUrl(m_source) : m_source);
111
112 QFile dataFile(dataFilePath);
113 if (!dataFile.open(flags: QIODevice::ReadOnly)) {
114 // Invalid file
115 qWarning() << "Unable to open file:" << dataFilePath;
116 return;
117 }
118 QCborStreamReader reader(&dataFile);
119
120 // Check that file is proper CBOR and get the version
121 int version = QQuick3DParticleShapeDataUtils::readShapeHeader(reader);
122
123 if (version == -1) {
124 // Invalid file
125 qWarning() << "Invalid shape data version:" << version;
126 return;
127 }
128
129 // Start positions array
130 reader.enterContainer();
131
132 while (reader.lastError() == QCborError::NoError && reader.hasNext()) {
133 QVector3D pos = QQuick3DParticleShapeDataUtils::readValue(reader, type: QMetaType::QVector3D).value<QVector3D>();
134 m_positions.append(t: pos);
135 }
136
137 // Leave positions array
138 reader.leaveContainer();
139
140 // Leave root array
141 reader.leaveContainer();
142
143 if (m_random)
144 m_randomizeDirty = true;
145}
146
147void QQuick3DParticleCustomShape::doRandomizeData()
148{
149 if (!m_system || m_positions.isEmpty())
150 return;
151
152 auto rand = m_system->rand();
153 int seed = rand->get(particleIndex: 0, user: QPRand::Shape1) * float(INT_MAX);
154 std::shuffle(first: m_positions.begin(), last: m_positions.end(), g: std::default_random_engine(seed));
155
156 m_randomizeDirty = false;
157}
158
159QVector3D QQuick3DParticleCustomShape::getPosition(int particleIndex)
160{
161 auto *parent = parentNode();
162 if (!parent || m_positions.isEmpty())
163 return QVector3D();
164
165 if (m_randomizeDirty)
166 doRandomizeData();
167
168 int index = particleIndex % m_positions.size();
169 return m_positions.at(i: index) * parent->scale();
170}
171
172QT_END_NAMESPACE
173

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