Qt Quick 3D - Particles 3D Testbed Example

// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick
import QtQuick3D
import QtQuick3D.Particles3D
import QtQuick3D.Helpers

Item {
    anchors.fill: parent

    View3D {
        anchors.fill: parent

        environment: SceneEnvironment {
            clearColor: "#101010"
            backgroundMode: SceneEnvironment.Color
            antialiasingMode: settings.antialiasingMode
            antialiasingQuality: settings.antialiasingQuality
        }
        PerspectiveCamera {
            id: camera
            position: Qt.vector3d(0, 100, 600)
            clipFar: 2000
        }
        WasdController {
            controlledObject: camera
        }
        PointLight {
            id: pointLight
            position.y: 100
            brightness: 100
            quadraticFade: 50
        }

        Model {
            source: "#Cube"
            position.y: -100
            scale: Qt.vector3d(20, 1, 20)
            materials: PrincipledMaterial {
                baseColor: Qt.rgba(1.0, 0.8, 0.8, 1.0)
            }
        }
        Node {
            property real angle: 0.0
            NumberAnimation on angle {
                from: 0.0
                to: 360.0
                duration: 5000
                loops: Animation.Infinite
                running: true
            }
            eulerRotation.y: angle
            Model {
                id: sphere1
                source: "#Sphere"
                position.y: -50
                position.x: -100
                scale: Qt.vector3d(1.8, 1.8, 1.8)
                materials: PrincipledMaterial {
                    baseColor: Qt.rgba(0.8, 1.0, 0.8, 1.0)
                }
            }
            Model {
                id: sphere2
                source: "#Sphere"
                position.y: 0
                position.x: 300
                scale: Qt.vector3d(0.9, 0.9, 0.9)
                materials: PrincipledMaterial {
                    baseColor: Qt.rgba(0.8, 1.0, 0.8, 1.0)
                }
            }
        }

        ParticleSystem3D {
            id: psystem

            property real particleScaleFactor : 1.0 + Math.abs(Math.cos(2.0 * Math.PI * scaleTemp))
            property real scaleTemp: 0.0
            NumberAnimation on scaleTemp {
                from: 0.0
                to: 1.0
                duration: 400
                loops: Animation.Infinite
                running: true
            }


            LineParticle3D {
                id: particle
                sprite: Texture {
                    source: "images/qt_logo.png"
                }
                maxAmount: 200 + emitter.emitRate * emitter.lifeSpan / 1000
                color: Qt.rgba(1.0, 1.0, 1.0, sliderOpacity.sliderValue * 0.01)
                colorVariation: Qt.vector4d(0.6, 0.6, 0.6, 0.0)
                fadeInDuration: 500
                fadeOutDuration: 500
                segmentCount: sliderSegmentCount.sliderValue
                alphaFade: sliderOpacityFade.sliderValue * 0.01
                scaleMultiplier: sliderSizeFactor.sliderValue
                texcoordMultiplier: sliderTexcoordFactor.sliderValue
                particleScale: sliderSize.sliderValue
                eolFadeOutDuration: 500
                texcoordMode: LineParticle3D.Relative
                billboard: billboardCheckBox.checked
                length: lengthCheckBox.checked ? lengthSlider.sliderValue : -1.0
                lengthVariation: lengthVariationSlider.sliderValue
                lengthDeltaMin: lengthDeltaSlider.sliderValue
                sortMode: LineParticle3D.SortDistance
            }

            ParticleEmitter3D {
                id: emitter
                particle: particle
                position: Qt.vector3d(-550, 0, 0)
                scale: Qt.vector3d(2.0, 0.5, 2.0)
                shape: ParticleShape3D {
                    type: ParticleShape3D.Sphere
                }
                velocity: VectorDirection3D {
                    direction: Qt.vector3d(sliderVelocityY.sliderValue * 40, 0, 0)
                    directionVariation: Qt.vector3d(sliderVelocityY.sliderValue * 20, 15, 0)
                }
                emitRate: sliderEmitRate.sliderValue
                lifeSpan: 500 * 1000 / (sliderVelocityY.sliderValue * 40)
            }

            ParticleEmitter3D {
                id: emitter2
                particle: particle
                position: Qt.vector3d(-550, 0, 60)
                scale: Qt.vector3d(2.0, 0.5, 2.0)
                shape: ParticleShape3D {
                    type: ParticleShape3D.Sphere
                }
                velocity: VectorDirection3D {
                    direction: Qt.vector3d(sliderVelocityY.sliderValue * 40, 0, 0)
                    directionVariation: Qt.vector3d(sliderVelocityY.sliderValue * 20, 15, 0)
                }
                emitRate: sliderEmitRate.sliderValue
                lifeSpan: 500 * 1000 / (sliderVelocityY.sliderValue * 40)
            }
            ParticleEmitter3D {
                id: emitter3
                particle: particle
                position: Qt.vector3d(-550, 0, 120)
                scale: Qt.vector3d(2.0, 0.5, 2.0)
                shape: ParticleShape3D {
                    type: ParticleShape3D.Sphere
                }
                velocity: VectorDirection3D {
                    direction: Qt.vector3d(sliderVelocityY.sliderValue * 40, 0, 0)
                    directionVariation: Qt.vector3d(sliderVelocityY.sliderValue * 20, 15, 0)
                }
                emitRate: sliderEmitRate.sliderValue
                lifeSpan: 500 * 1000 / (sliderVelocityY.sliderValue * 40)
            }

            Repeller3D {
                particles: [particle]
                position: sphere1.scenePosition
                outerRadius: sliderRadius.sliderValue
                strength: sliderRepelStrength.sliderValue
            }
            Repeller3D {
                particles: [particle]
                position: sphere2.scenePosition
                outerRadius: sliderRadius.sliderValue * 0.5
                strength: sliderRepelStrength.sliderValue
            }
        }
    }

    SettingsView {
        CustomLabel {
            text: "Billboard particle"
        }
        CustomCheckBox {
            id: billboardCheckBox
            checked: true
        }
        CustomLabel {
            text: "Line Segment Count"
        }
        CustomSlider {
            id: sliderSegmentCount
            sliderValue: 10
            fromValue: 1
            toValue: 250
            sliderStepSize: 1
        }
        CustomLabel {
            text: "Line minimum length"
        }
        CustomSlider {
            id: lengthDeltaSlider
            sliderValue: 10.0
            fromValue: 0.0
            toValue: 50.0
        }
        CustomLabel {
            text: "Line Opacity Fade"
        }
        CustomSlider {
            id: sliderOpacityFade
            sliderValue: 0
            fromValue: 0
            toValue: 100
            sliderStepSize: 1
        }
        CustomLabel {
            text: "Size"
        }
        CustomSlider {
            id: sliderSize
            sliderValue: 1
            fromValue: 0.1
            toValue: 20.0
        }
        CustomLabel {
            text: "Line Size Modifier"
        }
        CustomSlider {
            id: sliderSizeFactor
            sliderValue: 1
            fromValue: 0.1
            toValue: 2.0
        }
        CustomLabel {
            text: "Line Texcoord Modifier"
        }
        CustomSlider {
            id: sliderTexcoordFactor
            sliderValue: 1
            fromValue: 0.01
            toValue: 20.0
        }
        CustomLabel {
            text: "Emit Rate"
        }
        CustomSlider {
            id: sliderEmitRate
            sliderValue: 50
            fromValue: 1
            toValue: 200
        }
        CustomLabel {
            text: "Start Velocity"
        }
        CustomSlider {
            id: sliderVelocityY
            sliderValue: 10
            fromValue: 0.1
            toValue: 50
        }
        CustomLabel {
            text: "Opacity"
        }
        CustomSlider {
            id: sliderOpacity
            sliderValue: 30.0
            fromValue: 0.0
            toValue: 100.0
        }
        CustomLabel {
            text: "Repel Radius"
        }
        CustomSlider {
            id: sliderRadius
            sliderValue: 1000
            fromValue: 1
            toValue: 2000
        }
        CustomLabel {
            text: "Repel Strength"
        }
        CustomSlider {
            id: sliderRepelStrength
            sliderValue: 50.0
            fromValue: 0.0
            toValue: 400.0
        }
        CustomLabel {
            text: "Fixed Length"
        }
        CustomCheckBox {
            id: lengthCheckBox
            checked: false
        }
        CustomLabel {
            text: "Line Length"
        }
        CustomSlider {
            id: lengthSlider
            sliderValue: 50.0
            fromValue: 10.0
            toValue: 200.0
        }
        CustomLabel {
            text: "Line Length Variation"
        }
        CustomSlider {
            id: lengthVariationSlider
            sliderValue: 0.0
            fromValue: 0.0
            toValue: 100.0
        }
    }

    LoggingView {
        anchors.bottom: parent.bottom
        particleSystems: [psystem]
    }
}