A particular star

Here’s a little doodle I put together using my particle engine, thought it looked cool.

http://server1.lazy-kitty.com/tests/particles_2021_09_14(sun)/

It’s mostly just curl noise. I tried to use complementary colors for the swirly things around the star just to add some motion to the scene.

Let me know what you think!

10 Likes

:star_struck: This is simply awesome!

1 Like

Beautiful! Any chance you could share some example code demonstrating how to interact with the particle system?

1 Like

Thanks for kind words @keith , I’m not sure how much use this will be, but I will provide a brief explainer of the system as well as the code for this example. First the code:

import { EngineHarness } from "../meep/engine/EngineHarness.js";
import { makeMirEngineConfig } from "../model/game/makeMirEngineConfig.js";
import EntityBuilder from "../meep/engine/ecs/EntityBuilder.js";
import { Transform } from "../meep/engine/ecs/transform/Transform.js";
import { OrbitingBehavior } from "../model/game/util/behavior/OrbitingBehavior.js";
import Vector3 from "../meep/core/geom/Vector3.js";
import LinearModifier from "../meep/core/model/stat/LinearModifier.js";
import { ParticleEmitter } from "../meep/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js";
import { enableEditor } from "../meep/editor/enableEditor.js";
import { initializeGameBinarySerializationRegistry } from "../model/game/GameBinarySerializationRegistry.js";
import { ObstacleGridDisplayProcess } from "../meep/editor/process/ObstacleGridDisplayProcess.js";
import { DisableGameUIProcess } from "../meep/editor/process/DisableGameUIProcess.js";
import { SymbolicDisplayProcess } from "../meep/editor/process/SymbolicDisplayProcess.js";

const particleJSON = {
        "position": {
            "x": 30,
            "y": 0,
            "z": 30
        },
        "scale": {
            "x": 1,
            "y": 1,
            "z": 1
        },
        "rotation": {
            "x": 0,
            "y": 0,
            "z": 0,
            "w": 1
        },
        "parameters": [
            {
                "name": "scale",
                "itemSize": 1,
                "defaultTrackValue": {
                    "itemSize": 1,
                    "data": [
                        1
                    ],
                    "positions": [
                        0
                    ]
                }
            },
            {
                "name": "color",
                "itemSize": 4,
                "defaultTrackValue": {
                    "itemSize": 4,
                    "data": [
                        1,
                        1,
                        1,
                        1
                    ],
                    "positions": [
                        0
                    ]
                }
            }
        ],
        "preWarm": false,
        "readDepth": true,
        "softDepth": false,
        "velocityAlign": true,
        "blendingMode": 0,
        "layers": [
            {
                "imageURL": "data/textures/particle/UETools/x64/Drop_02.png",
                "particleLife": {
                    "min": 2,
                    "max": 3
                },
                "particleSize": {
                    "min": 0.1,
                    "max": 0.1
                },
                "particleRotation": {
                    "min": 0,
                    "max": 0
                },
                "particleRotationSpeed": {
                    "min": 0,
                    "max": 0
                },
                "emissionShape": 0,
                "emissionFrom": 0,
                "emissionRate": 8000,
                "emissionImmediate": 0,
                "parameterTracks": [
                    {
                        "name": "color",
                        "track": {
                            "itemSize": 4,
                            "data": [
                                1,
                                1,
                                1,
                                0,
                                1,
                                0.8470588235294118,
                                0.33725490196078434,
                                1,
                                1,
                                0.4549019607843137,
                                0,
                                0.6627906976744186,
                                0.6901960784313725,
                                0.03529411764705882,
                                0.00392156862745098,
                                0
                            ],
                            "positions": [
                                0,
                                0.2,
                                0.6827956989247311,
                                1
                            ]
                        }
                    },
                    {
                        "name": "scale",
                        "track": {
                            "itemSize": 1,
                            "data": [
                                2,
                                1.0069296734999178,
                                0.9601493601377308,
                                0.4580576654746713
                            ],
                            "positions": [
                                0,
                                0.1,
                                0.7588235294117647,
                                0.8647058823529412
                            ]
                        }
                    }
                ],
                "position": {
                    "x": 0,
                    "y": 0,
                    "z": 0
                },
                "scale": {
                    "x": 2,
                    "y": 2,
                    "z": 2
                },
                "particleVelocityDirection": {
                    "direction": {
                        "x": 0,
                        "y": 0,
                        "z": -1
                    },
                    "angle": 0
                },
                "particleSpeed": {
                    "min": 0,
                    "max": 0
                },
                "steps": [
                    {
                        "type": "FixedPhysics",
                        "parameters": []
                    },
                    {
                        "type": "CurlNoiseVelocity",
                        "parameters": [
                            0.05,
                            0.05,
                            0.05,
                            1.2,
                            1.2,
                            1.2
                        ]
                    }
                ]
            },
            {
                "imageURL": "data/textures/particle/UETools/x64/Drop_02.png",
                "particleLife": {
                    "min": 2,
                    "max": 2.5
                },
                "particleSize": {
                    "min": 0.06,
                    "max": 0.06
                },
                "particleRotation": {
                    "min": 0,
                    "max": 0
                },
                "particleRotationSpeed": {
                    "min": 0,
                    "max": 0
                },
                "emissionShape": 0,
                "emissionFrom": 1,
                "emissionRate": 1500,
                "emissionImmediate": 0,
                "parameterTracks": [
                    {
                        "name": "color",
                        "track": {
                            "itemSize": 4,
                            "data": [
                                1,
                                1,
                                1,
                                0,
                                0.3843137254901961,
                                0.4627450980392157,
                                0.9882352941176471,
                                1,
                                1,
                                0.6980392156862745,
                                0,
                                0
                            ],
                            "positions": [
                                0,
                                0.2,
                                1
                            ]
                        }
                    },
                    {
                        "name": "scale",
                        "track": {
                            "itemSize": 1,
                            "data": [
                                2,
                                1.0069296734999178,
                                0.9510791111111112,
                                0.6394626460070629
                            ],
                            "positions": [
                                0,
                                0.1,
                                0.788235294117647,
                                1
                            ]
                        }
                    }
                ],
                "position": {
                    "x": 0,
                    "y": 0,
                    "z": 0
                },
                "scale": {
                    "x": 5,
                    "y": 5,
                    "z": 5
                },
                "particleVelocityDirection": {
                    "direction": {
                        "x": 0,
                        "y": 0,
                        "z": -1
                    },
                    "angle": 3.14
                },
                "particleSpeed": {
                    "min": 0,
                    "max": 0
                },
                "steps": [
                    {
                        "type": "FixedPhysics",
                        "parameters": []
                    },
                    {
                        "type": "CurlNoiseVelocity",
                        "parameters": [
                            0.5,
                            0.5,
                            0.5,
                            0.5,
                            0.5,
                            0.5
                        ]
                    }
                ]
            }
        ]
    };

/**
 *
 * @param {ProcessEngine} processEngine
 */
function initializeProcessEngine(processEngine) {
    processEngine.add(new ObstacleGridDisplayProcess());
    processEngine.add(new DisableGameUIProcess());
    processEngine.add(new SymbolicDisplayProcess());

    processEngine.startByName(ObstacleGridDisplayProcess.Id);
    processEngine.startByName(DisableGameUIProcess.Id);
    processEngine.startByName(SymbolicDisplayProcess.Id);
}

/**
 *
 * @param {EngineHarness} harness
 * @return {Promise<void>}
 */
async function init(harness) {
    const engine = harness.engine;

    await makeMirEngineConfig(engine).apply(engine);

    await harness.initialize();

    main(engine);
}


/**
 *
 * @param {Engine} engine
 */
function main(engine) {
    initializeGameBinarySerializationRegistry(engine.binarySerializationRegistry);

    enableEditor(engine, editor => {
        initializeProcessEngine(editor.processEngine);
    });

    EngineHarness.buildLights({ engine });

    const target = new Vector3(30, 0, 30);

    const camera_target = target.clone();

    const camera = EngineHarness.buildCamera({
        engine,
        target: camera_target,
        pitch: Math.PI * (0.3),
        distance: 6
    });

    EngineHarness.buildOrbitalCameraController({ cameraEntity: camera.entity, engine });


    const ecd = engine.entityManager.dataset;
    const orbitingBehavior = new OrbitingBehavior();

    orbitingBehavior.center.copy(target);

    orbitingBehavior.offset.copy(target);
    orbitingBehavior.offset._add(0, 1, 0);

    orbitingBehavior.rate = 0.4;
    orbitingBehavior.radius = 2.3;


    const emitter = ParticleEmitter.fromJSON(particleJSON);

    new EntityBuilder()
        .add(Transform.fromJSON({
            position: target
        }))
        .add(emitter)
        .build(ecd);

    engine.ticker.clock.speed.addModifier(new LinearModifier(1))
}


const engineHarness = new EngineHarness();

init(engineHarness);

Now the system. The system consists of following parts:

In a nutshell, you have emitters, emitters contain layers. Each layer has it’s own particle sizes, sprite images and everything else. There are a few common attributes across all layers of an emitter, these can be seen in the flags, the common attributes are there because of shader limitations, and nothing else.

When it comes to ParameterTrack class, this is basically an animation curve for a given particle attribute, such as color of a particle or its size. The animation is normalized to particle lifetime, so if animation curve for size starts at 0 and ends at 100, this means that when the particle is just born - its size will be 0 and just at the time of it’s death it will reach 100. Tracks are linearly interpolated, with an arbitrary number of entries, these are basically just interpolated lookup tables.

Hope that helps. I have since worked a lot on the particle system, and added various new features as well as removed some of the existing limitations. But since there’s little interest in the engine - I stopped updating the repository.

2 Likes

That’s awesome! I appreciate you taking the time to write a thorough response, and even include some class diagrams :slight_smile:

It will probably be some time before I have the chance to play around with this, but it’s on my list heh.

Have you considered abstracting out the particle system into a separate module? Or is it too tied into to other parts of the game engine to make that practical?

Obviously, don’t spend a ton of time on that if it isn’t something that you would enjoy/benefit from, but just a thought…

Either way, really cool work. Keep it up!
Keith

1 Like