| 1 | // Copyright (C) 2021 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
| 3 | |
| 4 | #include "qquick3dparticlemodelparticle_p.h" |
| 5 | |
| 6 | QT_BEGIN_NAMESPACE |
| 7 | |
| 8 | /*! |
| 9 | \qmltype ModelParticle3D |
| 10 | \inherits Particle3D |
| 11 | \inqmlmodule QtQuick3D.Particles3D |
| 12 | \brief Particle using a Qt Quick 3D Model. |
| 13 | \since 6.2 |
| 14 | |
| 15 | The ModelParticle3D is a logical particle element that creates particles |
| 16 | from a Qt Quick 3D \l Model component. |
| 17 | */ |
| 18 | |
| 19 | QQuick3DParticleModelParticle::QQuick3DParticleModelParticle(QQuick3DNode *parent) |
| 20 | : QQuick3DParticle(parent) |
| 21 | , m_initialScale(1.0f, 1.0f, 1.0f) |
| 22 | { |
| 23 | QObject::connect(sender: this, signal: &QQuick3DParticle::maxAmountChanged, context: this, slot: [this]() { |
| 24 | handleMaxAmountChanged(amount: m_maxAmount); |
| 25 | }); |
| 26 | QObject::connect(sender: this, signal: &QQuick3DParticle::sortModeChanged, context: this, slot: [this]() { |
| 27 | handleSortModeChanged(mode: sortMode()); |
| 28 | }); |
| 29 | } |
| 30 | |
| 31 | void QQuick3DParticleModelParticle::handleMaxAmountChanged(int amount) |
| 32 | { |
| 33 | if (m_particleData.size() == amount) |
| 34 | return; |
| 35 | |
| 36 | m_particleData.resize(size: amount); |
| 37 | m_particleData.fill(t: {}); |
| 38 | } |
| 39 | |
| 40 | /*! |
| 41 | \qmlproperty Component ModelParticle3D::delegate |
| 42 | |
| 43 | The delegate provides a template defining each object instantiated by the particle. |
| 44 | |
| 45 | For example, to allocate 200 red cube particles: |
| 46 | |
| 47 | \qml |
| 48 | Component { |
| 49 | id: particleComponent |
| 50 | Model { |
| 51 | source: "#Cube" |
| 52 | scale: Qt.vector3d(0.2, 0.2, 0.2) |
| 53 | materials: DefaultMaterial { } |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | ModelParticle3D { |
| 58 | id: particleRed |
| 59 | delegate: particleComponent |
| 60 | maxAmount: 200 |
| 61 | color: "#ff0000" |
| 62 | } |
| 63 | \endqml |
| 64 | */ |
| 65 | QQmlComponent *QQuick3DParticleModelParticle::delegate() const |
| 66 | { |
| 67 | return m_delegate.data(); |
| 68 | } |
| 69 | |
| 70 | void QQuick3DParticleModelParticle::setDelegate(QQmlComponent *delegate) |
| 71 | { |
| 72 | if (delegate == m_delegate) |
| 73 | return; |
| 74 | m_delegate = delegate; |
| 75 | |
| 76 | regenerate(); |
| 77 | Q_EMIT delegateChanged(); |
| 78 | } |
| 79 | |
| 80 | class QQuick3DParticleInstanceTable : public QQuick3DInstancing |
| 81 | { |
| 82 | struct SortData |
| 83 | { |
| 84 | float age; |
| 85 | int index; |
| 86 | }; |
| 87 | |
| 88 | public: |
| 89 | QQuick3DParticleInstanceTable() {} |
| 90 | void clear() { m_instances.clear(); m_sortData.clear(); } |
| 91 | void commit() { sort(); markDirty(); } |
| 92 | void addInstance(const QVector3D &position, |
| 93 | const QVector3D &scale, |
| 94 | const QVector3D &eulerRotation, |
| 95 | const QColor &color, |
| 96 | float age) { |
| 97 | auto entry = calculateTableEntry(position, scale, eulerRotation, color); |
| 98 | m_instances.append(s: reinterpret_cast<char *>(&entry), len: sizeof(InstanceTableEntry)); |
| 99 | if (m_ageSorting) |
| 100 | m_sortData.append(t: {.age: age, .index: int(m_instances.size() / sizeof(InstanceTableEntry))}); |
| 101 | } |
| 102 | void setSorting(bool enable, bool inverted = false) |
| 103 | { |
| 104 | m_ageSorting = enable; |
| 105 | m_inverted = inverted; |
| 106 | } |
| 107 | protected: |
| 108 | QByteArray getInstanceBuffer(int *instanceCount) override |
| 109 | { |
| 110 | if (instanceCount) |
| 111 | *instanceCount = int(m_instances.size() / sizeof(InstanceTableEntry)); |
| 112 | |
| 113 | if (!m_ageSorting) |
| 114 | return m_instances; |
| 115 | return m_sortedInstances; |
| 116 | } |
| 117 | void sort() |
| 118 | { |
| 119 | if (!m_ageSorting) |
| 120 | return; |
| 121 | |
| 122 | if (m_inverted) { |
| 123 | std::sort(first: m_sortData.begin(), last: m_sortData.end(), comp: [&](const SortData &a, const SortData &b) { |
| 124 | return a.age < b.age; |
| 125 | }); |
| 126 | } else { |
| 127 | std::sort(first: m_sortData.begin(), last: m_sortData.end(), comp: [&](const SortData &a, const SortData &b) { |
| 128 | return a.age > b.age; |
| 129 | }); |
| 130 | } |
| 131 | m_sortedInstances.resize(size: m_instances.size()); |
| 132 | const InstanceTableEntry *src = reinterpret_cast<InstanceTableEntry *>(m_instances.data()); |
| 133 | InstanceTableEntry *dst = reinterpret_cast<InstanceTableEntry *>(m_sortedInstances.data()); |
| 134 | for (auto &e : m_sortData) |
| 135 | *dst++ = src[e.index]; |
| 136 | } |
| 137 | |
| 138 | private: |
| 139 | QList<SortData> m_sortData; |
| 140 | QByteArray m_instances; |
| 141 | QByteArray m_sortedInstances; |
| 142 | bool m_ageSorting = false; |
| 143 | bool m_inverted = false; |
| 144 | }; |
| 145 | |
| 146 | void QQuick3DParticleModelParticle::handleSortModeChanged(QQuick3DParticle::SortMode mode) |
| 147 | { |
| 148 | if (m_instanceTable) { |
| 149 | if (mode == QQuick3DParticle::SortNewest || mode == QQuick3DParticle::SortOldest) |
| 150 | m_instanceTable->setSorting(enable: true, inverted: mode == QQuick3DParticle::SortNewest); |
| 151 | else |
| 152 | m_instanceTable->setSorting(enable: false); |
| 153 | m_instanceTable->setDepthSortingEnabled(mode == QQuick3DParticle::SortDistance); |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | /*! |
| 158 | \qmlproperty Instancing ModelParticle3D::instanceTable |
| 159 | |
| 160 | The instanceTable provides access to the \l Instancing table of the model particle. |
| 161 | ModelParticle3D uses an internal instance table to implement efficient rendering. |
| 162 | This table can be applied to the \l{Model::instancing}{instancing} property of models |
| 163 | that are not part of the particle system. |
| 164 | |
| 165 | It is also possible to use this feature to provide an instancing table without showing any |
| 166 | particles. This is done by omitting the \l delegate property. For example: |
| 167 | |
| 168 | \qml |
| 169 | ParticleSystem3D { |
| 170 | id: psystem |
| 171 | ModelParticle3D { |
| 172 | id: particleRed |
| 173 | maxAmount: 200 |
| 174 | color: "#ff0000" |
| 175 | colorVariation: Qt.vector4d(0.5,0.5,0.5,0.5) |
| 176 | } |
| 177 | |
| 178 | ParticleEmitter3D { |
| 179 | particle: particleRed |
| 180 | velocity: VectorDirection3D { |
| 181 | direction: Qt.vector3d(-20, 200, 0) |
| 182 | directionVariation: Qt.vector3d(20, 20, 20) |
| 183 | } |
| 184 | particleScale: 0.2 |
| 185 | emitRate: 20 |
| 186 | lifeSpan: 2000 |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | Model { |
| 191 | source: "#Sphere" |
| 192 | instancing: particleRed.instanceTable |
| 193 | materials: PrincipledMaterial { |
| 194 | baseColor: "yellow" |
| 195 | } |
| 196 | } |
| 197 | \endqml |
| 198 | */ |
| 199 | |
| 200 | QQuick3DInstancing *QQuick3DParticleModelParticle::instanceTable() const |
| 201 | { |
| 202 | return m_instanceTable; |
| 203 | } |
| 204 | |
| 205 | void QQuick3DParticleModelParticle::clearInstanceTable() |
| 206 | { |
| 207 | if (m_instanceTable) |
| 208 | m_instanceTable->clear(); |
| 209 | } |
| 210 | |
| 211 | void QQuick3DParticleModelParticle::addInstance(const QVector3D &position, const QVector3D &scale, const QVector3D &eulerRotation, const QColor &color, float age) |
| 212 | { |
| 213 | if (m_instanceTable) |
| 214 | m_instanceTable->addInstance(position, scale, eulerRotation, color, age); |
| 215 | } |
| 216 | |
| 217 | void QQuick3DParticleModelParticle::commitInstance() |
| 218 | { |
| 219 | if (m_instanceTable) { |
| 220 | m_instanceTable->setHasTransparency(hasTransparency()); |
| 221 | m_instanceTable->commit(); |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | static void setInstancing(QQuick3DNode *node, QQuick3DInstancing *instanceTable, float bias) |
| 226 | { |
| 227 | auto *asModel = qobject_cast<QQuick3DModel *>(object: node); |
| 228 | if (asModel) { |
| 229 | asModel->setInstancing(instanceTable); |
| 230 | asModel->setDepthBias(bias); |
| 231 | } |
| 232 | const auto children = node->childItems(); |
| 233 | for (auto *child : children) { |
| 234 | auto *childNode = qobject_cast<QQuick3DNode *>(object: child); |
| 235 | if (childNode) |
| 236 | setInstancing(node: childNode, instanceTable, bias); |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | void QQuick3DParticleModelParticle::updateDepthBias(float bias) |
| 241 | { |
| 242 | setInstancing(node: m_node, instanceTable: m_instanceTable, bias); |
| 243 | } |
| 244 | |
| 245 | void QQuick3DParticleModelParticle::regenerate() |
| 246 | { |
| 247 | delete m_node; |
| 248 | m_node = nullptr; |
| 249 | |
| 250 | if (!isComponentComplete()) |
| 251 | return; |
| 252 | |
| 253 | if (!m_instanceTable) { |
| 254 | m_instanceTable = new QQuick3DParticleInstanceTable(); |
| 255 | m_instanceTable->setParent(this); |
| 256 | m_instanceTable->setParentItem(this); |
| 257 | emit instanceTableChanged(); |
| 258 | } else { |
| 259 | m_instanceTable->clear(); |
| 260 | } |
| 261 | |
| 262 | if (m_delegate.isNull()) |
| 263 | return; |
| 264 | |
| 265 | auto *obj = m_delegate->create(context: m_delegate->creationContext()); |
| 266 | |
| 267 | m_node = qobject_cast<QQuick3DNode *>(object: obj); |
| 268 | if (m_node) { |
| 269 | setInstancing(node: m_node, instanceTable: m_instanceTable, bias: depthBias()); |
| 270 | auto *particleSystem = system(); |
| 271 | m_node->setParent(particleSystem); |
| 272 | m_node->setParentItem(particleSystem); |
| 273 | } else { |
| 274 | delete obj; |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | void QQuick3DParticleModelParticle::componentComplete() |
| 279 | { |
| 280 | if (!system() && qobject_cast<QQuick3DParticleSystem *>(object: parentItem())) |
| 281 | setSystem(qobject_cast<QQuick3DParticleSystem *>(object: parentItem())); |
| 282 | |
| 283 | QQuick3DParticle::componentComplete(); |
| 284 | regenerate(); |
| 285 | } |
| 286 | |
| 287 | void QQuick3DParticleModelParticle::itemChange(QQuick3DObject::ItemChange change, const QQuick3DObject::ItemChangeData &value) |
| 288 | { |
| 289 | QQuick3DObject::itemChange(change, value); |
| 290 | if (change == ItemParentHasChanged) |
| 291 | regenerate(); |
| 292 | } |
| 293 | |
| 294 | QT_END_NAMESPACE |
| 295 | |