A particular star

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