Revision control

Copy as Markdown

// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qquick3dparticlemodelparticle_p.h"
QT_BEGIN_NAMESPACE
/*!
\qmltype ModelParticle3D
\inherits Particle3D
\inqmlmodule QtQuick3D.Particles3D
\brief Particle using a Qt Quick 3D Model.
\since 6.2
The ModelParticle3D is a logical particle element that creates particles
from a Qt Quick 3D \l Model component.
*/
QQuick3DParticleModelParticle::QQuick3DParticleModelParticle(QQuick3DNode *parent)
: QQuick3DParticle(parent)
, m_initialScale(1.0f, 1.0f, 1.0f)
{
QObject::connect(this, &QQuick3DParticle::maxAmountChanged, this, [this]() {
handleMaxAmountChanged(m_maxAmount);
});
QObject::connect(this, &QQuick3DParticle::sortModeChanged, this, [this]() {
handleSortModeChanged(sortMode());
});
}
void QQuick3DParticleModelParticle::handleMaxAmountChanged(int amount)
{
if (m_particleData.size() == amount)
return;
m_particleData.resize(amount);
m_particleData.fill({});
}
/*!
\qmlproperty Component ModelParticle3D::delegate
The delegate provides a template defining each object instantiated by the particle.
For example, to allocate 200 red cube particles:
\qml
Component {
id: particleComponent
Model {
source: "#Cube"
scale: Qt.vector3d(0.2, 0.2, 0.2)
materials: DefaultMaterial { }
}
}
ModelParticle3D {
id: particleRed
delegate: particleComponent
maxAmount: 200
color: "#ff0000"
}
\endqml
\note The content instantiated from the delegate keeps its own
\l {QtQuick3D::Node::layers}{layers} assignment; the layers value of the
\l ParticleSystem3D is not applied to it. To render the particles on a
specific content layer, set the layers property on the nodes in the
delegate.
*/
QQmlComponent *QQuick3DParticleModelParticle::delegate() const
{
return m_delegate.data();
}
void QQuick3DParticleModelParticle::setDelegate(QQmlComponent *delegate)
{
if (delegate == m_delegate)
return;
m_delegate = delegate;
regenerate();
Q_EMIT delegateChanged();
}
class QQuick3DParticleInstanceTable : public QQuick3DInstancing
{
struct SortData
{
float age;
int index;
};
public:
QQuick3DParticleInstanceTable() {}
void clear() { m_instances.clear(); m_sortData.clear(); }
void commit() { sort(); markDirty(); }
void addInstance(const QVector3D &position,
const QVector3D &scale,
const QVector3D &eulerRotation,
const QColor &color,
float age) {
auto entry = calculateTableEntry(position, scale, eulerRotation, color);
m_instances.append(reinterpret_cast<char *>(&entry), sizeof(InstanceTableEntry));
if (m_ageSorting)
m_sortData.append({age, int(m_instances.size() / sizeof(InstanceTableEntry))});
}
void setSorting(bool enable, bool inverted = false)
{
m_ageSorting = enable;
m_inverted = inverted;
}
protected:
QByteArray getInstanceBuffer(int *instanceCount) override
{
if (instanceCount)
*instanceCount = int(m_instances.size() / sizeof(InstanceTableEntry));
if (!m_ageSorting)
return m_instances;
return m_sortedInstances;
}
void sort()
{
if (!m_ageSorting)
return;
if (m_inverted) {
std::sort(m_sortData.begin(), m_sortData.end(), [&](const SortData &a, const SortData &b) {
return a.age < b.age;
});
} else {
std::sort(m_sortData.begin(), m_sortData.end(), [&](const SortData &a, const SortData &b) {
return a.age > b.age;
});
}
m_sortedInstances.resize(m_instances.size());
const InstanceTableEntry *src = reinterpret_cast<InstanceTableEntry *>(m_instances.data());
InstanceTableEntry *dst = reinterpret_cast<InstanceTableEntry *>(m_sortedInstances.data());
for (auto &e : m_sortData)
*dst++ = src[e.index];
}
private:
QList<SortData> m_sortData;
QByteArray m_instances;
QByteArray m_sortedInstances;
bool m_ageSorting = false;
bool m_inverted = false;
};
void QQuick3DParticleModelParticle::handleSortModeChanged(QQuick3DParticle::SortMode mode)
{
if (m_instanceTable) {
if (mode == QQuick3DParticle::SortNewest || mode == QQuick3DParticle::SortOldest)
m_instanceTable->setSorting(true, mode == QQuick3DParticle::SortNewest);
else
m_instanceTable->setSorting(false);
m_instanceTable->setDepthSortingEnabled(mode == QQuick3DParticle::SortDistance);
}
}
/*!
\qmlproperty Instancing ModelParticle3D::instanceTable
The instanceTable provides access to the \l Instancing table of the model particle.
ModelParticle3D uses an internal instance table to implement efficient rendering.
This table can be applied to the \l{Model::instancing}{instancing} property of models
that are not part of the particle system.
It is also possible to use this feature to provide an instancing table without showing any
particles. This is done by omitting the \l delegate property. For example:
\qml
ParticleSystem3D {
id: psystem
ModelParticle3D {
id: particleRed
maxAmount: 200
color: "#ff0000"
colorVariation: Qt.vector4d(0.5,0.5,0.5,0.5)
}
ParticleEmitter3D {
particle: particleRed
velocity: VectorDirection3D {
direction: Qt.vector3d(-20, 200, 0)
directionVariation: Qt.vector3d(20, 20, 20)
}
particleScale: 0.2
emitRate: 20
lifeSpan: 2000
}
}
Model {
source: "#Sphere"
instancing: particleRed.instanceTable
materials: PrincipledMaterial {
baseColor: "yellow"
}
}
\endqml
*/
QQuick3DInstancing *QQuick3DParticleModelParticle::instanceTable() const
{
return m_instanceTable;
}
void QQuick3DParticleModelParticle::clearInstanceTable()
{
if (m_instanceTable)
m_instanceTable->clear();
}
void QQuick3DParticleModelParticle::addInstance(const QVector3D &position, const QVector3D &scale, const QVector3D &eulerRotation, const QColor &color, float age)
{
if (m_instanceTable)
m_instanceTable->addInstance(position, scale, eulerRotation, color, age);
}
void QQuick3DParticleModelParticle::commitInstance()
{
if (m_instanceTable) {
m_instanceTable->setHasTransparency(hasTransparency());
m_instanceTable->commit();
}
}
static void setInstancing(QQuick3DNode *node, QQuick3DInstancing *instanceTable, float bias)
{
auto *asModel = qobject_cast<QQuick3DModel *>(node);
if (asModel) {
asModel->setInstancing(instanceTable);
asModel->setDepthBias(bias);
}
const auto children = node->childItems();
for (auto *child : children) {
auto *childNode = qobject_cast<QQuick3DNode *>(child);
if (childNode)
setInstancing(childNode, instanceTable, bias);
}
}
void QQuick3DParticleModelParticle::updateDepthBias(float bias)
{
setInstancing(m_node, m_instanceTable, bias);
}
void QQuick3DParticleModelParticle::regenerate()
{
delete m_node;
m_node = nullptr;
if (!isComponentComplete() || !parentItem())
return;
if (!m_instanceTable) {
m_instanceTable = new QQuick3DParticleInstanceTable();
m_instanceTable->setParent(this);
m_instanceTable->setParentItem(this);
emit instanceTableChanged();
} else {
m_instanceTable->clear();
}
if (m_delegate.isNull())
return;
auto *obj = m_delegate->create(m_delegate->creationContext());
m_node = qobject_cast<QQuick3DNode *>(obj);
if (m_node) {
setInstancing(m_node, m_instanceTable, depthBias());
auto *particleSystem = system();
m_node->setParent(particleSystem);
m_node->setParentItem(particleSystem);
} else {
delete obj;
}
}
void QQuick3DParticleModelParticle::componentComplete()
{
if (!system() && qobject_cast<QQuick3DParticleSystem *>(parentItem()))
setSystem(qobject_cast<QQuick3DParticleSystem *>(parentItem()));
QQuick3DParticle::componentComplete();
regenerate();
}
void QQuick3DParticleModelParticle::itemChange(QQuick3DObject::ItemChange change, const QQuick3DObject::ItemChangeData &value)
{
QQuick3DObject::itemChange(change, value);
if (change == ItemParentHasChanged)
regenerate();
}
QT_END_NAMESPACE