From cbd1bbcb934105aaeecc0f17511c6482b76ff8d2 Mon Sep 17 00:00:00 2001 From: codex <103840984+codex128@users.noreply.github.com> Date: Sat, 6 Jan 2024 08:22:58 -0500 Subject: [PATCH] added support for dynamic capacity --- src/codex/vfx/mesh/MeshPrototype.java | 3 + src/codex/vfx/mesh/Quad.java | 39 +++++ src/codex/vfx/particles/ParticleGroup.java | 135 ++++++++++++++++-- .../vfx/particles/drivers/BehaviorDriver.java | 24 ++++ .../geometry/TriParticleGeometry.java | 6 + src/codex/vfx/utils/VfxUtils.java | 12 ++ test/codex/vfx/test/TestBasicParticles.java | 67 ++++----- test/codex/vfx/test/TestFilterKernels.java | 6 +- test/codex/vfx/test/TestMultipleSystems.java | 2 +- test/codex/vfx/test/TestTrailingEffects.java | 3 +- 10 files changed, 241 insertions(+), 56 deletions(-) create mode 100644 src/codex/vfx/mesh/Quad.java create mode 100644 src/codex/vfx/particles/drivers/BehaviorDriver.java diff --git a/src/codex/vfx/mesh/MeshPrototype.java b/src/codex/vfx/mesh/MeshPrototype.java index 9424218..5f414fc 100644 --- a/src/codex/vfx/mesh/MeshPrototype.java +++ b/src/codex/vfx/mesh/MeshPrototype.java @@ -16,6 +16,9 @@ */ public class MeshPrototype { + /** + * Prototype of a 1x1 quad consisting of two triangles. + */ public static final MeshPrototype QUAD = new MeshPrototype().setVerts( new Vector3f(-1, 1, 0), new Vector3f(1, 1, 0), diff --git a/src/codex/vfx/mesh/Quad.java b/src/codex/vfx/mesh/Quad.java new file mode 100644 index 0000000..9806551 --- /dev/null +++ b/src/codex/vfx/mesh/Quad.java @@ -0,0 +1,39 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package codex.vfx.mesh; + +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; + +/** + * + * @author codex + */ +public class Quad extends Mesh { + + private static final float[] uvs = {1, 1, 1, 0, 0, 0, 0, 1}; + private static final int[] index = {0, 2, 1, 0, 3, 2}; + + public Quad(Vector3f normal, Vector3f up, float width, float height, float originX, float originY) { + Vector3f across = normal.cross(up); + Vector3f climb = normal.cross(across); + Vector3f[] verts = { + across.mult(-width*originX).addLocal(climb.mult(-height*originY)), + across.mult(-width*originX).addLocal(climb.mult(height*originY)), + across.mult(width*originX).addLocal(climb.mult(height*originY)), + across.mult(width*originX).addLocal(climb.mult(-height*originY)), + }; + Vector3f[] normals = {normal, normal, normal, normal}; + setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(verts)); + setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(normals)); + setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(uvs)); + setBuffer(Type.Index, 3, BufferUtils.createIntBuffer(index)); + updateCounts(); + updateBound(); + } + +} diff --git a/src/codex/vfx/particles/ParticleGroup.java b/src/codex/vfx/particles/ParticleGroup.java index 405d417..eb1d0dd 100644 --- a/src/codex/vfx/particles/ParticleGroup.java +++ b/src/codex/vfx/particles/ParticleGroup.java @@ -32,20 +32,40 @@ public class ParticleGroup extends Node implements VirtualE private OverflowStrategy overflow = OverflowStrategy.CullNew; private EmissionVolume volume = new EmissionPoint(); private int capacity; + private int dynamicSizingStep = -1; private float updateSpeed = 1f, decay = 1f; private float time = 0f, delay = 0f; private boolean playing = true, worldPlayState = true; private boolean inheritDecayRate = false; + /** + * Creates a particle group with zero capacity. + */ public ParticleGroup() { this(ParticleGroup.class.getSimpleName(), 0); } + /** + * Creates a particle group with zero capacity. + * + * @param name name of this spatial + */ public ParticleGroup(String name) { this(name, 0); } + /** + * Creates a particle group with the given capacity. + * + * @param capacity + */ public ParticleGroup(int capacity) { this(ParticleGroup.class.getSimpleName(), capacity); } + /** + * Creates a particle group with the given capacity. + * + * @param name name of this spatial + * @param capacity + */ public ParticleGroup(String name, int capacity) { super(name); assert capacity >= 0 : "Group capacity must be greater than or equal to zero."; @@ -95,7 +115,9 @@ protected void update(boolean update, float tpf, float decay) { if (worldPlayState) { time += t; } - t *= (inDelayZone() ? 0 : 1); + if (inDelayZone()) { + t = 0; + } decay *= this.decay; if (worldPlayState) { updateParticles(t, (inheritDecayRate ? decay : this.decay)); @@ -122,8 +144,12 @@ protected void updateParticles(float tpf, float decay) { protected boolean addParticle(T particle) { if (capacity > 0 && worldPlayState && particles.add(particle)) { - if (particles.size() > capacity && overflow.removeParticle(this, particle)) { - return false; + if (particles.size() > capacity) { + if (dynamicSizingStep >= 0) { + capacity = particles.size()+dynamicSizingStep; + } else if (overflow.removeParticle(this, particle)) { + return false; + } } for (ParticleDriver d : drivers) { d.particleAdded(this, particle); @@ -135,7 +161,7 @@ protected boolean addParticle(T particle) { protected boolean validateGroupSize(boolean exception) { if (particles.size() > capacity) { if (exception) { - throw new IllegalStateException("Number of particles cannot exceed the group capacity."); + throw new IllegalStateException("Internal Error: number of particles exceeded the group capacity."); } return false; } @@ -174,8 +200,12 @@ public int addAll(Collection particles) { } int c = 0; for (T p : particles) { - if (this.particles.size() >= capacity && !overflow.removeParticle(this, p)) { - continue; + if (this.particles.size() >= capacity) { + if (dynamicSizingStep >= 0) { + capacity = this.particles.size()+dynamicSizingStep; + } else if (!overflow.removeParticle(this, p)) { + continue; + } } if (addParticle(p)) c++; } @@ -351,6 +381,30 @@ public void setInitialDelay(float delay) { public void setInheritDecayRate(boolean inheritDecayRate) { this.inheritDecayRate = inheritDecayRate; } + /** + * Sets this group to dynamically resize its capacity to accept more particles. + *

+ * The integer value indicates how much above the necessary capacity + * the group's capacity is set to. A dynamic sizing step of 3 would resize + * the group to be 3 greater than the current number of particles. Resizing + * only occurs when the number of particles would exceed the group's capacity. + *

+ * This feature can potentially put more strain on the system, because particle + * geometries have to create new buffers to match the changing capacity. It is + * recommended to set the dynamic resizing step as high as reasonably possible + * in order to cut down of the number of reallocations the geometries must do. + * That is, if this feature must be used at all. + *

+ * To disable this feature, set the dynamic sizing step to less than zero. + *

+ * default={@code -1} (disabled) + * + * @param step + */ + @VfxAttribute(name="dynamicSizingStep") + public void setDynamicSizingStep(int step) { + this.dynamicSizingStep = step; + } /** * Sets this group's play state to true. @@ -396,19 +450,35 @@ public boolean flipPlayState() { return (playing = !playing); } /** - * Resets the group by removing all particles and reseting the simulation time. + * Resets the group by removing all particles, reseting the simulation time, + * and reseting child particle groups. *

* Drivers are also notified, so they can reset themselves accordingly. *
Will not reset if paused. */ @VfxCommand(name="reset") public void reset() { + reset(true); + } + /** + * Resets the group by removing all particles, reseting the simulation time, + * and (optionally) reseting child particle groups. + *

+ * Drivers are also notified, so they can reset themselves accordingly. + *
Will not reset if paused. + * + * @param resetChildren if true, child particle groups will also be reset + */ + public void reset(boolean resetChildren) { if (worldPlayState) { particles.clear(); time = 0; for (ParticleDriver d : drivers) { d.groupReset(this); } + if (resetChildren) for (ParticleGroup g : childGroups) { + g.reset(true); + } } } @@ -424,6 +494,8 @@ public T get(int i) { /** * Gets the list of particles this group controls. *

+ * Note: {@code ParticleGroup} is iterable in this respect. + *

* Do not modify the returned list! * * @return @@ -431,10 +503,18 @@ public T get(int i) { public ArrayList getParticleList() { return particles; } + /** + * + * @return + */ @VfxAttribute(name="overflowStrategy", input=false) public OverflowStrategy getOverflowStrategy() { return overflow; } + /** + * + * @return + */ @VfxAttribute(name="volume", input=false) public EmissionVolume getVolume() { return volume; @@ -449,6 +529,16 @@ public EmissionVolume getVolume() { public LinkedList getChildGroupList() { return childGroups; } + /** + * Gets the list of drivers controlling this particle group. + *

+ * Do not modify the returned list! + * + * @return + */ + public LinkedList> getDriverList() { + return drivers; + } /** * Get the parent particle group of this group. * @@ -468,7 +558,7 @@ public boolean isEmpty() { } /** * Returns true if the number of particles in this group is - * greater than or equal to the capacity. + * equal to its capacity. * * @return */ @@ -487,6 +577,11 @@ public boolean isFull() { public boolean getLocalPlayState() { return playing; } + /** + * Returns true if the current time is within this group's delay zone. + * + * @return + */ public boolean inDelayZone() { return time < delay; } @@ -508,22 +603,46 @@ public int size() { public int capacity() { return capacity; } + /** + * + * @return + */ @Override public float getUpdateSpeed() { return updateSpeed; } + /** + * + * @return + */ @VfxAttribute(name="decayRate", input=false) public float getDecayRate() { return decay; } + /** + * + * @return + */ @Override public float getInitialDelay() { return delay; } + /** + * + * @return + */ @VfxAttribute(name="inheritDecayRate", input=false) public boolean isInheritDecayRate() { return inheritDecayRate; } + /** + * + * @return + */ + @VfxAttribute(name="dynamicSizingStep", input=false) + public int getDynamicSizingStep() { + return dynamicSizingStep; + } /** * Fetches the world update speed of this group. * diff --git a/src/codex/vfx/particles/drivers/BehaviorDriver.java b/src/codex/vfx/particles/drivers/BehaviorDriver.java new file mode 100644 index 0000000..bead934 --- /dev/null +++ b/src/codex/vfx/particles/drivers/BehaviorDriver.java @@ -0,0 +1,24 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package codex.vfx.particles.drivers; + +import codex.vfx.particles.Particle; +import codex.vfx.particles.ParticleGroup; + +/** + * + * @author codex + * @param + */ +public abstract class BehaviorDriver implements ParticleDriver { + + @Override + public void updateGroup(ParticleGroup group, float tpf) {} + @Override + public void particleAdded(ParticleGroup group, T particle) {} + @Override + public void groupReset(ParticleGroup group) {} + +} diff --git a/src/codex/vfx/particles/geometry/TriParticleGeometry.java b/src/codex/vfx/particles/geometry/TriParticleGeometry.java index 204e7b6..e641f91 100644 --- a/src/codex/vfx/particles/geometry/TriParticleGeometry.java +++ b/src/codex/vfx/particles/geometry/TriParticleGeometry.java @@ -30,6 +30,12 @@ public class TriParticleGeometry extends ParticleGeometry { private final MeshPrototype prototype; private int size = -1; + /** + * Creates a geometry displaying the particle group with the mesh prototype. + * + * @param group + * @param prototype + */ public TriParticleGeometry(ParticleGroup group, MeshPrototype prototype) { super(group); this.prototype = prototype; diff --git a/src/codex/vfx/utils/VfxUtils.java b/src/codex/vfx/utils/VfxUtils.java index 5a615e7..566067a 100644 --- a/src/codex/vfx/utils/VfxUtils.java +++ b/src/codex/vfx/utils/VfxUtils.java @@ -49,6 +49,18 @@ public static Vector3f random(Vector3f a, Vector3f b) { ); } + /** + * Constructs a random unit vector with its magnitude between + * the minimum and maximum distances. + * + * @param minDist + * @param maxDist + * @return + */ + public static Vector3f random(float minDist, float maxDist) { + return gen.nextUnitVector3f().multLocal(gen.nextFloat(minDist, maxDist)); + } + /** * Generates a random vector within a box. * diff --git a/test/codex/vfx/test/TestBasicParticles.java b/test/codex/vfx/test/TestBasicParticles.java index c34e3ad..928ad66 100644 --- a/test/codex/vfx/test/TestBasicParticles.java +++ b/test/codex/vfx/test/TestBasicParticles.java @@ -5,8 +5,6 @@ package codex.vfx.test; import codex.boost.ColorHSBA; -import codex.boost.Timer; -import codex.boost.TimerListener; import codex.vfx.mesh.MeshPrototype; import codex.vfx.particles.ParticleData; import codex.vfx.particles.ParticleGroup; @@ -20,21 +18,17 @@ import com.jme3.renderer.queue.RenderQueue; import com.jme3.scene.Spatial; import codex.vfx.particles.OverflowStrategy; +import codex.vfx.particles.drivers.emission.Emitter; +import codex.vfx.particles.drivers.emission.ParticleFactory; +import codex.vfx.particles.tweens.Value; +import codex.vfx.utils.VfxUtils; /** * Tests basic particles. - *

- * Created before drivers were mainstream, so particles are created manually. * * @author codex */ -public class TestBasicParticles extends DemoApplication implements TimerListener { - - private ParticleGroup group; - private TriParticleGeometry geometry; - private final Timer timer = new Timer(.1f); - private final int particlesPerEmission = 5; - private final float gravity = 5f; +public class TestBasicParticles extends DemoApplication { public static void main(String[] args) { new TestBasicParticles().start(); @@ -43,15 +37,30 @@ public static void main(String[] args) { @Override public void demoInitApp() { - group = new ParticleGroup(200); - group.setOverflowStrategy(OverflowStrategy.CullNew); + ParticleGroup group = new ParticleGroup(200); + group.setLocalTranslation(0, 3, 0); + group.setOverflowStrategy(OverflowStrategy.CullOld); group.addDriver(ParticleDriver.force(new Vector3f(0f, -3f, 0f))); group.addDriver(ParticleDriver.Position); group.addDriver(ParticleDriver.Angle); + Emitter e = Emitter.create(); + e.setParticlesPerEmission(Value.constant(5)); + e.setEmissionRate(Value.constant(.1f)); + group.addDriver(e); + group.addDriver(new ParticleFactory() { + @Override + public void particleAdded(ParticleGroup group, ParticleData p) { + p.setLife(4f); + p.setPosition(group.getVolume().getNextPosition(group.getWorldTransform())); + p.color.set(new ColorHSBA(FastMath.nextRandomFloat(), 1f, .5f, 1f).toRGBA()); + p.linearVelocity = VfxUtils.gen.nextUnitVector3f().multLocal(VfxUtils.gen.nextFloat(4)); + p.setScale(FastMath.rand.nextFloat(.05f, .2f)); + p.angleSpeed.set(FastMath.rand.nextFloat(-5f, 5f)); + } + }); rootNode.attachChild(group); - geometry = new TriParticleGeometry(group, MeshPrototype.QUAD); - geometry.setLocalTranslation(0, 3, 0); + TriParticleGeometry geometry = new TriParticleGeometry(group, MeshPrototype.QUAD); geometry.setCullHint(Spatial.CullHint.Never); geometry.setQueueBucket(RenderQueue.Bucket.Transparent); Material mat = new Material(assetManager, "MatDefs/particles.j3md"); @@ -59,35 +68,9 @@ public void demoInitApp() { mat.setTransparent(true); geometry.setMaterial(mat); rootNode.attachChild(geometry); - - timer.setCycleMode(Timer.CycleMode.INFINITE); - timer.addListener(this); - timer.start(); } @Override - public void demoUpdate(float tpf) { - timer.update(tpf); - } - @Override - public void onTimerFinish(Timer timer) { - for (int i = 0; i < particlesPerEmission; i++) { - ParticleData p = new ParticleData(4f); - p.setPosition(geometry.getWorldTranslation()); - p.color.set(new ColorHSBA(FastMath.nextRandomFloat(), 1f, .5f, 1f).toRGBA()); - p.linearVelocity = nextRandomVector().multLocal(2f); - p.setScale(FastMath.rand.nextFloat(.05f, .2f)); - p.angleSpeed.set(FastMath.rand.nextFloat(-5f, 5f)); - group.add(p); - } - } - - private Vector3f nextRandomVector() { - return new Vector3f( - FastMath.rand.nextFloat(-1, 1), - FastMath.rand.nextFloat(-1, 1), - FastMath.rand.nextFloat(-1, 1) - ).normalizeLocal(); - } + public void demoUpdate(float tpf) {} } diff --git a/test/codex/vfx/test/TestFilterKernels.java b/test/codex/vfx/test/TestFilterKernels.java index 93cbab2..b4c480a 100644 --- a/test/codex/vfx/test/TestFilterKernels.java +++ b/test/codex/vfx/test/TestFilterKernels.java @@ -19,9 +19,9 @@ public class TestFilterKernels extends DemoApplication { // Matrix defining how the kernel samples surrounding pixels. // Feel free to play around with this. private final Matrix3f kernel = new Matrix3f( - 2, 2, 2, - 2, -15, 2, - 2, 2, 2 + -2, -2, -2, + -2, 15, -2, + -2, -2, -2 ); public static void main(String[] args) { diff --git a/test/codex/vfx/test/TestMultipleSystems.java b/test/codex/vfx/test/TestMultipleSystems.java index ca0dcb0..ff8f35f 100644 --- a/test/codex/vfx/test/TestMultipleSystems.java +++ b/test/codex/vfx/test/TestMultipleSystems.java @@ -56,7 +56,7 @@ public static void main(String[] args) { public void simpleInitApp() { flyCam.setMoveSpeed(40); - viewPort.setBackgroundColor(new ColorRGBA(.01f, .01f, .01f, 1f)); + //viewPort.setBackgroundColor(new ColorRGBA(.01f, .01f, .01f, 1f)); rootNode.addLight(VfxUtils.loadLightProbe(assetManager, "Scenes/City_Night_Lights.j3o")); rootNode.addLight(new AmbientLight(new ColorRGBA(.3f, 0f, 0f, 1f))); diff --git a/test/codex/vfx/test/TestTrailingEffects.java b/test/codex/vfx/test/TestTrailingEffects.java index 4f43787..6e914ad 100644 --- a/test/codex/vfx/test/TestTrailingEffects.java +++ b/test/codex/vfx/test/TestTrailingEffects.java @@ -39,7 +39,7 @@ public static void main(String[] args) { @Override public void demoInitApp() { - particles = new ParticleGroup(50); + particles = new ParticleGroup<>(50); particles.setOverflowStrategy(OverflowStrategy.CullOld); particles.setVolume(new EmissionPoint()); Emitter e = Emitter.create(); @@ -61,7 +61,6 @@ public void particleAdded(ParticleGroup group, ParticleData p) { geometry.setCullHint(Spatial.CullHint.Never); Material mat = new Material(assetManager, "MatDefs/trail.j3md"); TextureKey texKey = new TextureKey("Textures/wispy-trail.png"); - texKey.setTextureTypeHint(Texture.Type.TwoDimensional); texKey.setGenerateMips(false); Texture tex = assetManager.loadTexture(texKey); mat.setTexture("Texture", tex);