Skip to content

Feat: EmitterMeshFaceShape - enhance particle emission uniformity #2503

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2021 jMonkeyEngine
* Copyright (c) 2009-2025 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -36,6 +36,7 @@
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils;

import java.util.ArrayList;
import java.util.List;

Expand All @@ -52,79 +53,131 @@ public EmitterMeshFaceShape() {
}

/**
* Constructor. It stores a copy of vertex list of all meshes.
* @param meshes
* a list of meshes that will form the emitter's shape
* Constructor. Initializes the emitter shape with a list of meshes.
* The vertices and normals for all triangles of these meshes are
* extracted and stored internally.
*
* @param meshes a list of {@link Mesh} objects that will define the
* shape from which particles are emitted.
*/
public EmitterMeshFaceShape(List<Mesh> meshes) {
super(meshes);
}

/**
* Sets the meshes for this emitter shape. This method extracts all
* triangle vertices and computes their normals, storing them internally
* for subsequent particle emission.
*
* @param meshes a list of {@link Mesh} objects to set as the emitter's shape.
*/
@Override
public void setMeshes(List<Mesh> meshes) {
this.vertices = new ArrayList<List<Vector3f>>(meshes.size());
this.normals = new ArrayList<List<Vector3f>>(meshes.size());

for (Mesh mesh : meshes) {
Vector3f[] vertexTable = BufferUtils.getVector3Array(mesh.getFloatBuffer(Type.Position));
int[] indices = new int[3];
List<Vector3f> vertices = new ArrayList<>(mesh.getTriangleCount() * 3);
List<Vector3f> normals = new ArrayList<>(mesh.getTriangleCount());
List<Vector3f> meshVertices = new ArrayList<>(mesh.getTriangleCount() * 3);
List<Vector3f> meshNormals = new ArrayList<>(mesh.getTriangleCount());

for (int i = 0; i < mesh.getTriangleCount(); ++i) {
mesh.getTriangle(i, indices);
vertices.add(vertexTable[indices[0]]);
vertices.add(vertexTable[indices[1]]);
vertices.add(vertexTable[indices[2]]);
normals.add(FastMath.computeNormal(vertexTable[indices[0]], vertexTable[indices[1]], vertexTable[indices[2]]));

Vector3f v1 = vertexTable[indices[0]];
Vector3f v2 = vertexTable[indices[1]];
Vector3f v3 = vertexTable[indices[2]];

// Add all three vertices of the triangle
meshVertices.add(v1);
meshVertices.add(v2);
meshVertices.add(v3);

// Compute and add the normal for the current triangle face
meshNormals.add(FastMath.computeNormal(v1, v2, v3));
}
this.vertices.add(vertices);
this.normals.add(normals);
this.vertices.add(meshVertices);
this.normals.add(meshNormals);
}
}

/**
* Randomly selects a point on a random face.
* Randomly selects a point on a random face of one of the stored meshes.
* The point is generated using barycentric coordinates to ensure uniform
* distribution within the selected triangle.
*
* @param store
* storage for the coordinates of the selected point
* @param store a {@link Vector3f} object where the coordinates of the
* selected point will be stored.
*/
@Override
public void getRandomPoint(Vector3f store) {
int meshIndex = FastMath.nextRandomInt(0, vertices.size() - 1);
List<Vector3f> currVertices = vertices.get(meshIndex);
int numVertices = currVertices.size();

// the index of the first vertex of a face (must be dividable by 3)
int vertIndex = FastMath.nextRandomInt(0, vertices.get(meshIndex).size() / 3 - 1) * 3;
// put the point somewhere between the first and the second vertex of a face
float moveFactor = FastMath.nextRandomFloat();
store.set(Vector3f.ZERO);
store.addLocal(vertices.get(meshIndex).get(vertIndex));
store.addLocal((vertices.get(meshIndex).get(vertIndex + 1).x - vertices.get(meshIndex).get(vertIndex).x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).y - vertices.get(meshIndex).get(vertIndex).y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).z - vertices.get(meshIndex).get(vertIndex).z) * moveFactor);
// move the result towards the last face vertex
moveFactor = FastMath.nextRandomFloat();
store.addLocal((vertices.get(meshIndex).get(vertIndex + 2).x - store.x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).y - store.y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).z - store.z) * moveFactor);
int faceIndex = FastMath.nextRandomInt(0, numVertices / 3 - 1);
int vertIndex = faceIndex * 3;

// Generate the random point on the triangle
generateRandomPointOnTriangle(currVertices, vertIndex, store);
}

/**
* Randomly selects a point on a random face.
* The {@code normal} argument is set to the normal of the selected face.
* Randomly selects a point on a random face of one of the stored meshes,
* and also sets the normal of that selected face.
* The point is generated using barycentric coordinates for uniform distribution.
*
* @param store
* storage for the coordinates of the selected point
* @param normal
* storage for the normal of the selected face
* @param store a {@link Vector3f} object where the coordinates of the
* selected point will be stored.
* @param normal a {@link Vector3f} object where the normal of the
* selected face will be stored.
*/
@Override
public void getRandomPointAndNormal(Vector3f store, Vector3f normal) {
int meshIndex = FastMath.nextRandomInt(0, vertices.size() - 1);
List<Vector3f> currVertices = vertices.get(meshIndex);
int numVertices = currVertices.size();

// the index of the first vertex of a face (must be dividable by 3)
int faceIndex = FastMath.nextRandomInt(0, vertices.get(meshIndex).size() / 3 - 1);
int faceIndex = FastMath.nextRandomInt(0, numVertices / 3 - 1);
int vertIndex = faceIndex * 3;
// put the point somewhere between the first and the second vertex of a face
float moveFactor = FastMath.nextRandomFloat();
store.set(Vector3f.ZERO);
store.addLocal(vertices.get(meshIndex).get(vertIndex));
store.addLocal((vertices.get(meshIndex).get(vertIndex + 1).x - vertices.get(meshIndex).get(vertIndex).x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).y - vertices.get(meshIndex).get(vertIndex).y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).z - vertices.get(meshIndex).get(vertIndex).z) * moveFactor);
// move the result towards the last face vertex
moveFactor = FastMath.nextRandomFloat();
store.addLocal((vertices.get(meshIndex).get(vertIndex + 2).x - store.x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).y - store.y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).z - store.z) * moveFactor);

// Generate the random point on the triangle
generateRandomPointOnTriangle(currVertices, vertIndex, store);
// Set the normal from the pre-computed normals list for the selected face
normal.set(normals.get(meshIndex).get(faceIndex));
}

/**
* Internal method to generate a random point within a specific triangle
* using barycentric coordinates.
*
* @param currVertices The list of vertices for the current mesh.
* @param vertIndex The starting index of the triangle's first vertex
* within the {@code currVertices} list.
* @param store A {@link Vector3f} object where the calculated point will be stored.
*/
private void generateRandomPointOnTriangle(List<Vector3f> currVertices, int vertIndex, Vector3f store) {

Vector3f v1 = currVertices.get(vertIndex);
Vector3f v2 = currVertices.get(vertIndex + 1);
Vector3f v3 = currVertices.get(vertIndex + 2);

// Generate random barycentric coordinates
float u = FastMath.nextRandomFloat();
float v = FastMath.nextRandomFloat();

if ((u + v) > 1) {
u = 1 - u;
v = 1 - v;
}

// P = v1 + u * (v2 - v1) + v * (v3 - v1)
store.x = v1.x + u * (v2.x - v1.x) + v * (v3.x - v1.x);
store.y = v1.y + u * (v2.y - v1.y) + v * (v3.y - v1.y);
store.z = v1.z + u * (v2.z - v1.z) + v * (v3.z - v1.z);
}

}