All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.talosvfx.talos.runtime.simulation.TinyEmitter Maven / Gradle / Ivy

The newest version!
package com.talosvfx.talos.runtime.simulation;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ObjectMap;
import com.badlogic.gdx.utils.Pool;
import com.badlogic.gdx.utils.TimeUtils;
import com.talosvfx.talos.runtime.*;
import com.talosvfx.talos.runtime.modules.EmitterModule;
import com.talosvfx.talos.runtime.modules.ParticleModule;


public class TinyEmitter implements IEmitter {

    private final ParticleEffectInstance parentParticleInstance;
    private ParticleEmitterDescriptor emitterGraph;
    private EmitterModule emitterModule;
    private ParticleModule particleModule;
    private ScopePayload scopePayload;

    private float delay;
    private float delayTimer;
    private float duration;
    private float rate;
    private float alpha;
    private float particlesToEmmit;

    private float timePassed = 0;
    private float cursor = 0;
    private Array timeline = new Array();

    private Array tmp = new Array();
    private Array activeParticles = new Array();
    public ObjectMap newMap = new ObjectMap();
    public ObjectMap recordMap = new ObjectMap();
    private final Pool particlePool = new Pool() {
        @Override
        protected Particle newObject () {
            return new Particle();
        }
    };

    Vector2 effectPosition = new Vector2();
    public Color tint = new Color(Color.WHITE);

    private boolean isVisible = true;
    private boolean paused = false;
    private boolean isContinuous = false;
    private boolean isAttached = false;
    private boolean isComplete = false;
    public boolean isAdditive = true;
    private boolean isStopped = false;
    private boolean isBlendAdd = false;

    public TinyEmitter(ParticleEmitterDescriptor moduleGraph, ParticleEffectInstance particleEffectInstance) {
        this.emitterGraph = moduleGraph;
        parentParticleInstance = particleEffectInstance;
        setScope(particleEffectInstance.getScope()); //Default set to the parent payload instance
        init();
    }

    public void simulate() {
        if(emitterGraph == null) return;
        timeline.clear();
        timePassed = 0;
        long startTime = TimeUtils.nanoTime();
        System.out.println("starting tiny particle simulation");

        if(emitterModule == null) return;
        float step = 1/30f; // simulating 30 frames per second
        while (alpha < 1f) {
            updateSimulation(step);
        }
        long endTime = TimeUtils.nanoTime();
        System.out.println("time at the end: "+ timePassed);
        System.out.println("timeline size: "+ timeline.size);
        System.out.println();

        System.out.println("Simulation complete in: " + TimeUtils.nanosToMillis(endTime - startTime) + "ms");
    }

    public void init() {
        emitterModule = emitterGraph.getEmitterModule();
        particleModule = emitterGraph.getParticleModule();
        if (emitterModule == null)
            return;

        duration = emitterModule.getDuration();
        delay = emitterModule.getDelay();
        isContinuous = emitterModule.isContinuous();

        delayTimer = delay;

        alpha = 0f;
        particlesToEmmit = 1f; // always emmit one first
        isComplete = false;

        simulate();
    }

    public void updateSimulation (float delta) {
        emitterModule.getScope().set(ScopePayload.EMITTER_ALPHA, alpha);
        emitterModule.getScope().set(ScopePayload.REQUESTER_ID, 1.1f);
        duration = emitterModule.getDuration();
        rate = emitterModule.getRate();

        float normDelta = delta/duration;

        float deltaLeftover = 0;
        if(alpha + normDelta > 1f) {
            deltaLeftover = (1f - alpha) * duration;
            alpha = 1f;
        } else {
            alpha += normDelta;
            deltaLeftover = delta;
        }

        emitterModule.getScope().set(ScopePayload.EMITTER_ALPHA, alpha);

        if (alpha < 1f || (alpha == 1f && deltaLeftover > 0)) { // emission only here
            // let's emmit
            particlesToEmmit += rate * deltaLeftover;

            int count = (int)particlesToEmmit;
            for (int i = 0; i < count; i++) {
                if (emitterGraph.getParticleModule() != null) {
                    // emit a particle here and record it's data
                    emitParticle();
                }
            }
            particlesToEmmit -= count;
        }

        emitterGraph.resetRequesters();

        timePassed += delta;
    }

    @Override
    public ParticleEmitterDescriptor getEmitterGraph () {
        return emitterGraph;
    }

    @Override
    public boolean isVisible () {
        return isVisible;
    }

    @Override
    public boolean isAdditive () {
        return isAdditive;
    }

    @Override
    public boolean isBlendAdd () {
        return isBlendAdd;
    }

    @Override
    public Array getActiveParticles () {
        return activeParticles;
    }

    /**
     * really not gonna, just pretending
     */
    private void emitParticle() {
        float seed = MathUtils.random();
        particleModule.getScope().set(ScopePayload.EMITTER_ALPHA, alpha);
        particleModule.getScope().set(ScopePayload.PARTICLE_ALPHA, 0);
        particleModule.getScope().set(ScopePayload.PARTICLE_SEED, seed);
        particleModule.getScope().set(ScopePayload.REQUESTER_ID, seed);
        particleModule.getScope().set(ScopePayload.EMITTER_ALPHA_AT_P_INIT, alpha);

        float life = particleModule.getLife();

        ParticleRecord record = new ParticleRecord();
        record.start = timePassed;
        record.end = timePassed + life;
        record.seed = seed;

        timeline.add(record);
    }

    @Override
    public float getAlpha () {
        return alpha;
    }

    @Override
    public ParticleModule getParticleModule () {
        return particleModule;
    }

    @Override
    public EmitterModule getEmitterModule () {
        return emitterModule;
    }

    @Override
    public Vector2 getEffectPosition () {
        return effectPosition;
    }

    @Override
    public ScopePayload getScope () {
        return scopePayload;
    }

    @Override
    public Color getTint () {
        return tint;
    }

    @Override
    public void setScope (ScopePayload scope) {
        this.scopePayload = scope;
    }

    @Override
    public int getActiveParticleCount () {
        return activeParticles.size;
    }

    @Override
    public boolean isContinuous () {
        return isContinuous;
    }

    @Override
    public boolean isComplete () {
        return isComplete;
    }

    public void stop() {
        alpha = 1f;
        isStopped = true;
    }

    public void pause() {
        paused = true;
    }

    public void resume() {
        paused = false;
    }

    @Override
    public void restart () {
        delayTimer = delay;
        alpha = 0;
        isComplete = false;
        particlesToEmmit = 1f;
        isStopped = false;
    }

    @Override
    public float getDelayRemaining () {
        return delayTimer;
    }

    @Override
    public void update (float delta) {
        seek(cursor + delta);
    }

    public class ParticleRecord {
        public float start;
        public float end;
        public float seed;
    }

    public void seek(float time) {
        if(timeline.size == 0) return; // nothing to do

        int index = timeline.size - 1;
        ParticleRecord last = timeline.get(index);

        float duration = last.start;
        time %= duration;

        if (time < last.start) {
            // need to find where we are, approximate first, then search for fastest result
            int approximation = MathUtils.clamp(Math.round((time/last.start) * timeline.size), 0, timeline.size - 1);
            int found = -1;
            int i = 0;
            if(checkRecordToBeTheSeekPosition(approximation, time)) {
                found = approximation;
            }
            while(found == -1) {
                boolean left = checkRecordToBeTheSeekPosition(approximation - i, time);
                boolean right = checkRecordToBeTheSeekPosition(approximation + i, time);
                if(left) {
                    found = approximation - i;
                }
                if(right) {
                    found = approximation + i;
                }
                i++;
                if(approximation - i < 0 && approximation + i > timeline.size-1) {
                    found = index;
                }
            }
            index = found;
            System.out.println("seek search complete in " + i + " steps");
        }

        // now time to gather particles that have collisions, till first one that does not
        int count = 0;
        float delta = time - cursor;
        cursor = time;
        newMap.clear();
        activeParticles.clear();
        while(index >= 0 && timeline.get(index).end > time) {
            count++;

            ParticleRecord record = timeline.get(index);
            float particleAlpha = (time - record.start) / (record.end - record.start);
            float particleSeed = record.seed;

            Particle particle;
            boolean initCalled = false;
            if (recordMap.containsKey(record)) {
                particle = recordMap.get(record);
            } else {
                particle = particlePool.obtain();
                particle.init(this, particleSeed);
                initCalled = true;
                recordMap.put(record, particle);
            }

            if(Math.abs(delta) > 0.1f) {
                // we need to re-seek this state from left
                if(!initCalled) {
                    particle.init(this);
                }

                // now fast forward
                float pos = 0f;
                float miniDelta = 1/30f;
                int ffCount = 0;
                while(pos < time - record.start) {
                    pos += miniDelta;
                    if (pos > time - record.start) {
                        miniDelta = time - record.start - pos;
                        pos = time - record.start;
                    }
                    particle.update(miniDelta);
                    ffCount++;
                }
                System.out.println("fast forwarded in " + ffCount + " steps");
            } else {
                particle.applyAlpha(particleAlpha, delta);
            }

            newMap.put(record, particle);
            activeParticles.add(particle);

            index--;
        }

        tmp.clear();
        for(ParticleRecord oldRecord: recordMap.keys()) {
            if(!newMap.containsKey(oldRecord)) {
                particlePool.free(recordMap.get(oldRecord));
                tmp.add(oldRecord);
            }
        }
        for(ParticleRecord oldRecord : tmp) {
            recordMap.remove(oldRecord);
        }

        System.out.println("found " + count + " collisions");
    }

    private boolean checkRecordToBeTheSeekPosition(int index, float time) {
        if(index < 0 || index >= timeline.size) return false;

        ParticleRecord record =  timeline.get(index);

        if(index >= timeline.size - 1) {
            if (record.start < time) {
                return true;
            } else {
                return false;
            }
        }

        ParticleRecord recordNext =  timeline.get(index + 1);

        if(record.start <= time && recordNext.start >= time) {
            return true;
        }

        return false;
    }

    public void setVisible(boolean isVisible) {
        this.isVisible = isVisible;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy