[Solved] Custom Point Sprite Mesh

I’ve been reading this old thread about rendering point sprites through the Particle.j3md by basically setting up the values the emitter usually does internally. The point being that each sprite can then have a specifically set location. For my specific usage I’ll need it to render in the guiNode.

I’ve went through the particle emitter code and found that the ParticlePointMesh.java contains most of the relevant code, but the reproduced code in the MCVE below doesn’t seem to be rendering anything at all. It’s possible I’ve missed something obvious but I have no clue as to what.

package mainpkg;

import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.prefs.BackingStoreException;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Mesh.Mode;
import com.jme3.scene.Node;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.scene.shape.Box;
import com.jme3.util.BufferUtils;

public class Main extends SimpleApplication {

    private static Node cubes = new Node();
	private static Geometry points;
	private static Mesh mesh;
    
    public static void main(String[] args) throws BackingStoreException {
        Main app = new Main();
        app.start();
    }

    @Override
    public void simpleInitApp() {
    	
    	flyCam.setMoveSpeed(20);
    	
    	// some sample cubes to represent locations where the sprites should appear
    	Material m = new Material(assetManager,"Common/MatDefs/Misc/Unshaded.j3md");
    	for (int i = 0; i < 50; i++) {
			Geometry g = new Geometry("",new Box(0.2f,0.2f,0.2f));
			g.setLocalTranslation(FastMath.nextRandomFloat()*50-25, FastMath.nextRandomFloat()*50-25, FastMath.nextRandomFloat()*50-25);
			g.setMaterial(m);
			cubes.attachChild(g);
		}
    	rootNode.attachChild(cubes);
    	
    	//materials and mesh for point sprites    	
        Material mat = new Material(assetManager,"Common/MatDefs/Misc/Particle.j3md");
		mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/shockwave.png"));
        mat.setFloat("Quadratic", 6f);
        
		mesh = new Mesh();
		mesh.setMode(Mode.Points);
        
		points = new Geometry("",mesh);
		points.setShadowMode(ShadowMode.Off);
        points.setMaterial(mat);
        guiNode.attachChild(points);
    	
    }
    
    static int spritecount = 0;
	
	public static void updateCount(){
		spritecount = cubes.getChildren().size();
		
		// set positions
        FloatBuffer pb = BufferUtils.createVector3Buffer(spritecount);
        VertexBuffer buf = mesh.getBuffer(VertexBuffer.Type.Position);
        if (buf != null) {
            buf.updateData(pb);
        } else {
            VertexBuffer pvb = new VertexBuffer(VertexBuffer.Type.Position);
            pvb.setupData(Usage.Stream, 3, Format.Float, pb);
            mesh.setBuffer(pvb);
        }

        // set colors
        ByteBuffer cb = BufferUtils.createByteBuffer(spritecount * 4);
        buf = mesh.getBuffer(VertexBuffer.Type.Color);
        if (buf != null) {
            buf.updateData(cb);
        } else {
            VertexBuffer cvb = new VertexBuffer(VertexBuffer.Type.Color);
            cvb.setupData(Usage.Stream, 4, Format.UnsignedByte, cb);
            cvb.setNormalized(true);
            mesh.setBuffer(cvb);
        }

        // set sizes
        FloatBuffer sb = BufferUtils.createFloatBuffer(spritecount);
        buf = mesh.getBuffer(VertexBuffer.Type.Size);
        if (buf != null) {
            buf.updateData(sb);
        } else {
            VertexBuffer svb = new VertexBuffer(VertexBuffer.Type.Size);
            svb.setupData(Usage.Stream, 1, Format.Float, sb);
            mesh.setBuffer(svb);
        }

        // set UV-scale
        FloatBuffer tb = BufferUtils.createFloatBuffer(spritecount*4);
        buf = mesh.getBuffer(VertexBuffer.Type.TexCoord);
        if (buf != null) {
            buf.updateData(tb);
        } else {
            VertexBuffer tvb = new VertexBuffer(VertexBuffer.Type.TexCoord);
            tvb.setupData(Usage.Stream, 4, Format.Float, tb);
            mesh.setBuffer(tvb);
        }
        
        mesh.updateCounts();
        mesh.updateBound();
	}

    @Override
	public void simpleUpdate(float tpf) {

		if (cubes.getChildren().size() != spritecount)
			updateCount();

		VertexBuffer pvb = mesh.getBuffer(VertexBuffer.Type.Position);
		FloatBuffer positions = (FloatBuffer) pvb.getData();

		VertexBuffer cvb = mesh.getBuffer(VertexBuffer.Type.Color);
		ByteBuffer colors = (ByteBuffer) cvb.getData();

		VertexBuffer svb = mesh.getBuffer(VertexBuffer.Type.Size);
		FloatBuffer sizes = (FloatBuffer) svb.getData();

		VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.TexCoord);
		FloatBuffer texcoords = (FloatBuffer) tvb.getData();

		float sizeScale = ((float) getContext().getSettings().getHeight()) / 1080f;

		// update data in vertex buffers
		positions.rewind();
		colors.rewind();
		sizes.rewind();
		texcoords.rewind();

		for (int x = 0; x < cubes.getChildren().size(); x++) {

			Vector3f screenpos = cam.getScreenCoordinates(cubes.getChild(x).getWorldTranslation());

			positions.put(screenpos.x).put(screenpos.y).put(0f);
			sizes.put(100 * sizeScale);
			colors.putInt((new ColorRGBA(1f, 0f, 0f, 1f)).asIntABGR());
			texcoords.put(0).put(0).put(1).put(1);
		}

		positions.flip();
		colors.flip();
		sizes.flip();
		texcoords.flip();

		// force renderer to re-send data to GPU
		pvb.updateData(positions);
		cvb.updateData(colors);
		svb.updateData(sizes);
		tvb.updateData(texcoords);

		mesh.updateBound();
    }

    @Override
    public void simpleRender(RenderManager rm) {
    	
    }
}

The way I’ve set it up here is that I create a bunch of cubes and then dynamically size the buffers of the point mesh to match the array size, then project the cube locations to the screen to render the point sprites to those locaitons in the gui node.

Yet quite obviously nothing renders aside from the cubes. I’ve confirmed that the projected gui locations are correct.

The size of the sprites themselves are usually calculated in the shader… are you sure they aren’t just teeny-tiny (in GUI terms) little sprites?

I’ve tried values from 0.1 to 10000, with no apparent change. I’m pretty sure it’s gotta be some more major error that completely prevents it from rendering anything.

Does it render ok in the regular root node?

I’ve created my own custom particle shaders a few times and never had an issue. I’ve even had them in the GUI node before, I think.

Doesn’t seem to be working there either. This isn’t a custom shader though, it’s with Particle.j3md which is generally proven to work by now. There must be something wrong with the buffer setup, but I have no idea what.

You usually say “the code looks fine from here” when someone doesn’t post any code, but i have a feeling that you didn’t even look at the one I posted. Am I wrong?

Nothing jumps out at me in the code on a quick glance.

When I learned how to do point sprites, I took the TestPointCloud or whatever and used that as my base… broke it, fixed it, etc…

Hmm, haven’t heard of that one before, I’ll check it out.

Edit: The only one I’ve managed to find is this one which doesn’t really include anything helpful.

I think this is the one I was thinking of:

When I learned this originally, I started with that… then moved the mesh code out of the emitter… which broke it, so I fixed it, then changed something else which broke it, then I fixed that too. Starting with working code is the key, though.

As I recall, that example just creates a big cubic cloud of point sprites.

Ah, thank you.

mat.setBoolean("PointSprite", true);

Apparently I have forgotten that line.

For anyone coming across this in the future, this is how the test case should look like once you add that:

Just a follow up, I’ve noticed a slight problem with sprite scaling…

If I render sprites of the exact same size like so (for example) I get this annoying scaling problem where sprites near zero are rendered as larger

	for (int x = 1; x < 20; x++) {
		for (int y = 1; y < 20; y++) {
			positions.put(x * (wid/20)).put(y * (hei/20)).put(0);
			sizes.put(6f);
			colors.putInt((new ColorRGBA(1f, 0f, 0f, 1f)).asIntABGR());
			texcoords.put(0).put(0).put(1).put(1);
		}
	}

I assume this has something to do with scaling by distance, but how the hell does one turn it off?

Messing around with the Quadratic parameter seems to affect this but It mostly just makes it more pronounced no matter what value I pick, aside from zero which seems to scale each sprite to 0.

Perhaps I could just go into the shader and rip out the part that does the camera transform?

Edit: Yep, seems like no other way to disable it.

1 Like