瀏覽代碼

Merge pull request #2503 from capdevon/capdevon-EmitterMeshFaceShape

Feat: EmitterMeshFaceShape - enhance particle emission uniformity
Ryan McDonough 2 月之前
父節點
當前提交
4db274c4f3
共有 1 個文件被更改,包括 92 次插入39 次删除
  1. 92 39
      jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshFaceShape.java

+ 92 - 39
jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshFaceShape.java

@@ -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
@@ -36,6 +36,7 @@ import com.jme3.math.Vector3f;
 import com.jme3.scene.Mesh;
 import com.jme3.scene.VertexBuffer.Type;
 import com.jme3.util.BufferUtils;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -52,79 +53,131 @@ public class EmitterMeshFaceShape extends EmitterMeshVertexShape {
     }
 
     /**
-     * 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);
+    }
+
 }