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 | |
13 | QT_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 | |
53 | QQuick3DParticleCustomShape::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 | |
64 | QUrl 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 | */ |
77 | bool QQuick3DParticleCustomShape::randomizeData() const |
78 | { |
79 | return m_random; |
80 | } |
81 | |
82 | void 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 | |
93 | void 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 | |
104 | void 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 | |
147 | void 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 | |
159 | QVector3D 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 | |
172 | QT_END_NAMESPACE |
173 | |