Skip to content

4. Customization

codex edited this page Mar 4, 2024 · 2 revisions

ParticleGeometry

ParticleGeometry is responsible for displaying particles belonging to a single group. How it particles are displayed is completely up to the geometry itself: the group doesn't care. That said, particle geometries should not change properties of particles; displaying is all they should do.

To correctly write a particle geometry, it is required to know how to generate meshes procedurally in jme.

public class MyParticleGeometry extends ParticleGeometry<ParticleData> {
    
    public MyParticleGeometry(ParticleGroup<ParticleData> group) {
        super(group);
    }
    
    @Override
    protected void initBuffers() {
        // initialize/reload vertex buffers here
    }
    
    @Override
    protected void updateMesh() {
        // update data in vertex buffers here
    }
    
}

In its constructor, ParticleGeometry assigns itself a blank mesh instance and sets itself to ignore transforms. Both are quite important for geometries wanting to display particles.

The ParticleGeometry class maintains three protected fields that are important for mesh generation.

  1. group: the current ParticleGroup being displayed by this geometry.
  2. capacity: the maximum number of particles this geometry should support. This is used to correctly size buffers during initialization.
  3. useSpriteSheet: if true, each particle's sprite index should be written to a buffer.

MeshUtils contains many useful static methods for creating and maintaining vertex buffers.

// initializing the position vertex buffer, with 4 vertices per possible particle (capacity)
int vertsPerParticle = 4;
FloatBuffer buffer = BufferUtils.createFloatBuffer(this.capacity * vertsPerParticle);
VertexBuffer pvb = MeshUtils.initializeVertexBuffer(mesh,
    VertexBuffer.Type.Position,
    VertexBuffer.Usage.Stream,
    VertexBuffer.Format.Float,
    buffer, vertsPerParticle
);
// write a vector to the position buffer
VertexBuffer pvb = mesh.getBuffer(VertexBuffer.Type.Position);
FloatBuffer buffer = (FloatBuffer)pvb.getData();
MeshUtils.writeVector3(buffer, new Vector3f(1, 2, 3));

For full code examples, see PointParticleGeometry, TriParticleGeometry, and InstancedParticleGeometry. A more complicated setup can viewed in TrailingGeometry.

EmissionVolume

Emission volumes are very simple to write.

public class MyEmissionVolume implements EmissionVolume {
    
    @Override
    public Vector3f getNextPosition(Transform transform) {
        // sets spawn position to 5 units left of the input translation
        return transform.getTranslation().add(5, 0, 0);
    }
    
}

Remember that the input transform does not necessarily need to be the particle group's world transform. It really could be anything! A rather wild example is the EmitFromParticles driver, which uses the transforms of particles in another group as the input transforms for the emission shape.

OverflowStrategy

Writing custom OverflowStrategies is not incredibly useful, but not very difficult.

public class MyOverflowStrategy implements OverflowStrategy<ParticleData> {
    
    @Override
    public boolean removeParticle(ParticleGroup<ParticleData> group, ParticleData particle) {
        // remove the first particle in the group
        return group.remove(0) == particle && particle != null;
    }
    
}

The particle argument represents the latest particle to be added. The method should return true if the overflow strategy removed that particle from the group (false otherwise). The particle argument can be null if the group does not "know" what particle was added last, which can happen if the capacity is bumped down below the current size.

Also, it is useful to know that particles are always added at the end of the group list. Therefore, particles found near the beginning of the list are the oldest particles, and particles near the end are the newest.

Clone this wiki locally