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.