Skip to content

Commit

Permalink
feat: Particle System Gen./Aff. Registration (#3918)
Browse files Browse the repository at this point in the history
Co-authored-by: Max <maximborsch@gmail.com>
  • Loading branch information
pollend and Meta-Maxim authored May 1, 2020
1 parent 85ffedf commit 64266ff
Show file tree
Hide file tree
Showing 17 changed files with 251 additions and 181 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,22 @@
*/
package org.terasology.particles.updating;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.terasology.engine.module.ModuleManager;
import org.terasology.entitySystem.Component;
import org.terasology.entitySystem.entity.EntityRef;
import org.terasology.particles.components.ParticleEmitterComponent;
import org.terasology.particles.components.affectors.VelocityAffectorComponent;
import org.terasology.particles.components.generators.EnergyRangeGeneratorComponent;
import org.terasology.particles.functions.affectors.AffectorFunction;
import org.terasology.particles.functions.affectors.VelocityAffectorFunction;
import org.terasology.particles.functions.generators.EnergyRangeGeneratorFunction;
import org.terasology.particles.functions.generators.GeneratorFunction;
import org.terasology.physics.Physics;
import org.terasology.physics.engine.PhysicsEngine;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;

import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
Expand All @@ -45,51 +41,43 @@
public class ParticleUpdaterImplTest {

private ParticleUpdater particleUpdater;
private BiMap<Class<Component>, GeneratorFunction> registeredGeneratorFunctions;
private BiMap<Class<Component>, AffectorFunction> registeredAffectorFunctions;

@BeforeEach
public void setUp() throws Exception {
Physics physics = mock(PhysicsEngine.class);
particleUpdater = new ParticleUpdaterImpl(physics);
registeredGeneratorFunctions = HashBiMap.create();
registeredAffectorFunctions = HashBiMap.create();
ModuleManager moduleManager = mock(ModuleManager.class);
particleUpdater = new ParticleUpdaterImpl(physics, moduleManager);
}

@Test
public void testNullEmitterRegistration() {
Assertions.assertThrows(IllegalArgumentException.class,
() -> particleUpdater.register(null));
Assertions.assertThrows(IllegalArgumentException.class,() -> {
particleUpdater.addEmitter(null);
});
}

@Test
public void testNonEmitterRegistration() {
EntityRef emitterEntity = mock(EntityRef.class);
when(emitterEntity.getComponent(ParticleEmitterComponent.class)).thenReturn(null);

Assertions.assertThrows(IllegalArgumentException.class,
()-> particleUpdater.register(emitterEntity));
Assertions.assertThrows(IllegalArgumentException.class,() -> {
particleUpdater.addEmitter(emitterEntity);
});
}

@Test
public void testEmitterRegistration() {
EntityRef emitterEntity = mock(EntityRef.class);
when(emitterEntity.getComponent(ParticleEmitterComponent.class)).thenReturn(new ParticleEmitterComponent());

particleUpdater.register(emitterEntity);
particleUpdater.addEmitter(emitterEntity);
}

private Iterator<Component> getTestGeneratorsAndAffectors() {
Collection<Component> components = new LinkedList<>();
components.add(new EnergyRangeGeneratorComponent(0.5f, 1f));
components.add(new VelocityAffectorComponent());

EnergyRangeGeneratorFunction energyRangeGeneratorFunction = new EnergyRangeGeneratorFunction();
registeredGeneratorFunctions.put(((GeneratorFunction) energyRangeGeneratorFunction).getComponentClass(), energyRangeGeneratorFunction);

VelocityAffectorFunction velocityAffectorFunction = new VelocityAffectorFunction();
registeredAffectorFunctions.put(((AffectorFunction) velocityAffectorFunction).getComponentClass(), velocityAffectorFunction);

return components.iterator();
}

Expand All @@ -103,14 +91,14 @@ public void testEmitterConfiguration() {
particleEmitterComponent.ownerEntity = emitterEntity;
when(emitterEntity.getComponent(ParticleEmitterComponent.class)).thenReturn(particleEmitterComponent);

particleUpdater.register(emitterEntity);
particleUpdater.configureEmitter(particleEmitterComponent, registeredAffectorFunctions, registeredGeneratorFunctions);
particleUpdater.addEmitter(emitterEntity);
particleUpdater.configureEmitter(particleEmitterComponent);

for (Component component : (Iterable<Component>) () -> componentIterator) {
if (registeredGeneratorFunctions.containsKey(component.getClass())) {
assertTrue(particleEmitterComponent.generatorFunctionMap.containsKey(component));
} else if (registeredGeneratorFunctions.containsKey(component.getClass())) {
if (component.getClass() == EnergyRangeGeneratorComponent.class) {
assertTrue(particleEmitterComponent.generatorFunctionMap.containsKey(component));
} else if (component.getClass() == VelocityAffectorComponent.class) {
assertTrue(particleEmitterComponent.affectorFunctionMap.containsKey(component));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,26 @@

import org.terasology.entitySystem.Component;
import org.terasology.module.sandbox.API;
import org.terasology.particles.functions.affectors.AffectorFunction;
import org.terasology.particles.functions.generators.GeneratorFunction;
import org.terasology.particles.rendering.ParticleRenderingData;

import java.util.stream.Stream;

/**
* Component system responsible for keeping track of all ParticleSystem components and updating them.
* Component system responsible for keeping track of all {@link org.terasology.particles.components.ParticleEmitterComponent} components and updating them.
* Also maintains a registry of generator and affector functions to be used when processing generators
* and affectors during a particle system update.
*/

@API
public interface ParticleSystemManager {

void registerAffectorFunction(AffectorFunction affectorFunction);

void registerGeneratorFunction(GeneratorFunction generatorFunction);

/**
* Gets all current emitters that have a given particle data component and returns a stream of all particle pools and their associated data for rendering.
* A particle data component stores information used to define how the particles of the emitter it is attached to are rendered.
*
* @param particleDataComponent The particle data component to select emitters by.
*
* @return A stream of {@link ParticleRenderingData} to be used by particle renderers.
*/
Stream<ParticleRenderingData> getParticleEmittersByDataComponent(Class<? extends Component> particleDataComponent);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@
*/
package org.terasology.particles;

import com.google.common.base.Preconditions;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.engine.module.ModuleManager;
import org.terasology.entitySystem.Component;
import org.terasology.entitySystem.entity.EntityRef;
import org.terasology.entitySystem.entity.lifecycleEvents.BeforeDeactivateComponent;
Expand All @@ -33,41 +29,32 @@
import org.terasology.module.sandbox.API;
import org.terasology.particles.components.ParticleEmitterComponent;
import org.terasology.particles.events.ParticleSystemUpdateEvent;
import org.terasology.particles.functions.affectors.AccelerationAffectorFunction;
import org.terasology.particles.functions.affectors.AffectorFunction;
import org.terasology.particles.functions.affectors.VelocityAffectorFunction;
import org.terasology.particles.functions.generators.ColorRangeGeneratorFunction;
import org.terasology.particles.functions.generators.EnergyRangeGeneratorFunction;
import org.terasology.particles.functions.generators.GeneratorFunction;
import org.terasology.particles.functions.generators.PositionRangeGeneratorFunction;
import org.terasology.particles.functions.generators.ScaleRangeGeneratorFunction;
import org.terasology.particles.functions.generators.TextureOffsetGeneratorFunction;
import org.terasology.particles.functions.generators.VelocityRangeGeneratorFunction;
import org.terasology.particles.rendering.ParticleRenderingData;
import org.terasology.particles.updating.ParticleUpdater;
import org.terasology.particles.updating.ParticleUpdaterImpl;
import org.terasology.physics.Physics;
import org.terasology.registry.In;
import org.terasology.registry.Share;

import java.util.stream.Stream;

/**
* See ParticleSystemManager for more information.
* A particle system manager implementation using events.
* <p>
* See {@link ParticleSystemManager} for more information.
*/
@API
@Share(ParticleSystemManager.class)
@RegisterSystem(RegisterMode.CLIENT)
public class ParticleSystemManagerImpl extends BaseComponentSystem implements UpdateSubscriberSystem, ParticleSystemManager {

private static final Logger logger = LoggerFactory.getLogger(ParticleSystemManagerImpl.class);

@In
private Physics physics;

private ParticleUpdater particleUpdater;
@In
private ModuleManager moduleManager;

private BiMap<Class<Component>, GeneratorFunction> registeredGeneratorFunctions = HashBiMap.create();
private BiMap<Class<Component>, AffectorFunction> registeredAffectorFunctions = HashBiMap.create();
private ParticleUpdater particleUpdater;


@ReceiveEvent(components = {ParticleEmitterComponent.class})
Expand All @@ -77,43 +64,42 @@ public void onEmitterActivated(OnActivatedComponent event, EntityRef entity, Par
if (particleEmitterComponent.particlePool == null) {
particleEmitterComponent.particlePool = new ParticlePool(particleEmitterComponent.maxParticles);
}
particleUpdater.register(entity);
particleUpdater.configureEmitter(particleEmitterComponent, registeredAffectorFunctions, registeredGeneratorFunctions);
particleUpdater.addEmitter(entity);
particleUpdater.configureEmitter(particleEmitterComponent);
}

@ReceiveEvent(components = {ParticleEmitterComponent.class})
public void onEmitterChanged(ParticleSystemUpdateEvent event, EntityRef entity, ParticleEmitterComponent emitter) {
particleUpdater.configureEmitter(emitter, registeredAffectorFunctions, registeredGeneratorFunctions);
particleUpdater.configureEmitter(emitter);
}

@ReceiveEvent(components = {ParticleEmitterComponent.class})
public void onEmitterDeactivated(BeforeDeactivateComponent event, EntityRef entity, ParticleEmitterComponent particleEmitterComponent) {
particleUpdater.dispose(entity);
particleUpdater.removeEmitter(entity);
}


/**
* Creates and initializes a new {@link ParticleUpdater}.
*/
public void initialise() {
particleUpdater = ParticleUpdater.create(physics);

registerGeneratorFunction(new EnergyRangeGeneratorFunction());
registerGeneratorFunction(new VelocityRangeGeneratorFunction());
registerGeneratorFunction(new ColorRangeGeneratorFunction());
registerGeneratorFunction(new PositionRangeGeneratorFunction());
registerGeneratorFunction(new ScaleRangeGeneratorFunction());
registerGeneratorFunction(new TextureOffsetGeneratorFunction());

registerAffectorFunction(new VelocityAffectorFunction());
registerAffectorFunction(new AccelerationAffectorFunction());
particleUpdater = new ParticleUpdaterImpl(physics, moduleManager);
particleUpdater.initialize();
}

/**
* De-registers all affector and generator functions and disposes the {@link ParticleUpdater}
*/
@Override
public void shutdown() {
registeredAffectorFunctions.clear();
registeredGeneratorFunctions.clear();

particleUpdater.dispose();
particleUpdater = null;
}

/**
* Updates all particle emitters, first spawning new particles and then applying affectors.
*
* @param delta The time (in seconds) since the last engine update.
*/
public void update(float delta) {
particleUpdater.update(delta);
}
Expand All @@ -128,24 +114,4 @@ public Stream<ParticleRenderingData> getParticleEmittersByDataComponent(Class<?
particleEmitterComponent.particlePool
));
}

@Override
public void registerGeneratorFunction(GeneratorFunction generatorFunction) {
Preconditions.checkArgument(!registeredGeneratorFunctions.containsKey(generatorFunction.getComponentClass()),
"Tried to register an GeneratorFunction for %s twice", generatorFunction
);

logger.info("Registering GeneratorFunction for Component class {}", generatorFunction.getComponentClass());
registeredGeneratorFunctions.put(generatorFunction.getComponentClass(), generatorFunction);
}

@Override
public void registerAffectorFunction(AffectorFunction affectorFunction) {
Preconditions.checkArgument(!registeredAffectorFunctions.containsKey(affectorFunction.getComponentClass()),
"Tried to register an AffectorFunction for %s twice", affectorFunction
);

logger.info("Registering AffectorFunction for Component class {}", affectorFunction.getComponentClass());
registeredAffectorFunctions.put(affectorFunction.getComponentClass(), affectorFunction);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,9 @@
*/
public abstract class ParticleSystemFunction<T> {
private final int rawDataMask;
private final Class<T> component;

public ParticleSystemFunction(Class<T> component, ParticleDataMask dataMask, ParticleDataMask... dataMasks) {
public ParticleSystemFunction(ParticleDataMask dataMask, ParticleDataMask... dataMasks) {
this.rawDataMask = ParticleDataMask.toInt(dataMask, dataMasks);
this.component = component;
}

@Override
public final int hashCode() {
return component.hashCode();
}

@Override
public final boolean equals(final Object object) {
if (object != null && object.getClass().equals(this.getClass())) {
ParticleSystemFunction other = (ParticleSystemFunction) object;
return other.getComponentClass().equals(this.getComponentClass());
}

return false;
}

public final Class<T> getComponentClass() {
return component;
}

public final int getDataMask() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2017 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.particles.functions;

import org.terasology.entitySystem.Component;
import org.terasology.module.sandbox.API;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* This annotation is used to mark a {@link ParticleSystemFunction} to be registered in by the {@link org.terasology.particles.updating.ParticleUpdater}.
*/
@API
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RegisterParticleSystemFunction {}
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@
import org.terasology.particles.ParticleData;
import org.terasology.particles.ParticleDataMask;
import org.terasology.particles.components.affectors.AccelerationAffectorComponent;
import org.terasology.particles.functions.RegisterParticleSystemFunction;
import org.terasology.utilities.random.Random;

/**
*
*/
@RegisterParticleSystemFunction()
public final class AccelerationAffectorFunction extends AffectorFunction<AccelerationAffectorComponent> {
public AccelerationAffectorFunction() {
super(AccelerationAffectorComponent.class, ParticleDataMask.VELOCITY);
super(ParticleDataMask.VELOCITY);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@

@API
public abstract class AffectorFunction<T extends Component> extends ParticleSystemFunction<T> implements Cloneable {
public AffectorFunction(Class<T> affectorComponent, ParticleDataMask dataMask, ParticleDataMask... dataMasks) {
super(affectorComponent, dataMask, dataMasks);
public AffectorFunction(ParticleDataMask dataMask, ParticleDataMask... dataMasks) {
super(dataMask, dataMasks);
}

public abstract void update(T component, ParticleData particleData, Random random, float delta);
Expand Down
Loading

0 comments on commit 64266ff

Please sign in to comment.