Forráskód Böngészése

Merge pull request #8885 from Pryme8/p8/CapsuleMesh

Capsule MeshBuilder
David Catuhe 5 éve
szülő
commit
137590d6fb

+ 312 - 0
src/Meshes/Builders/capsuleBuilder.ts

@@ -0,0 +1,312 @@
+import { VertexData } from "../mesh.vertexData";
+import { Vector2, Vector3 } from "../../Maths/math.vector";
+import { Mesh, _CreationDataStorage } from "../mesh";
+    /**
+     * Scripts based off of https://github.com/maximeq/three-js-capsule-geometry/blob/master/src/CapsuleBufferGeometry.js
+     * @param options the constructors options used to shape the mesh.
+     * @returns the capsule VertexData
+     * @see https://doc.babylonjs.com/how_to/capsule_shape
+     */
+VertexData.CreateCapsule = function(
+    options: ICreateCapsuleOptions = {
+        orientation : Vector3.Up(),
+        subdivisions: 2,
+        tessellation: 16,
+        height: 1,
+        radius: 0.25,
+        capSubdivisions: 6
+    }): VertexData {
+    //let path = options.orientation || Vector3.Up()
+    let subdivisions = Math.max(options.subdivisions ? options.subdivisions : 2, 1);
+    let tessellation = Math.max(options.tessellation ? options.tessellation : 16, 3);
+    let height = Math.max(options.height ? options.height : 2, 0.);
+    let radius = Math.max(options.radius ? options.radius : 1, 0.);
+    let capDetail = Math.max(options.capSubdivisions ? options.capSubdivisions : 6, 1);
+
+    let  radialSegments = tessellation;
+    let  heightSegments = subdivisions;
+
+    let radiusTop = Math.max(options.radiusTop ? options.radiusTop : radius, 0.);
+    let radiusBottom = Math.max(options.radiusBottom ? options.radiusBottom : radius, 0.);
+
+    let thetaStart = 0.0;
+    let thetaLength = (2.0 * Math.PI);
+
+    let capsTopSegments = Math.max(options.topCapSubdivisions ? options.topCapSubdivisions : capDetail, 1);
+    let capsBottomSegments = Math.max(options.bottomCapSubdivisions ? options.bottomCapSubdivisions : capDetail, 1);
+
+    var alpha = Math.acos((radiusBottom - radiusTop) / height);
+
+    var indices = [];
+    var vertices = [];
+    var normals = [];
+    var uvs = [];
+
+    var index = 0,
+        //indexOffset = 0,
+        indexArray = [],
+        halfHeight = height / 2;
+
+    var x, y;
+    var normal = Vector3.Zero();
+    var vertex = Vector3.Zero();
+
+        var cosAlpha = Math.cos(alpha);
+        var sinAlpha = Math.sin(alpha);
+
+        var cone_length =
+            new Vector2(
+                radiusTop * sinAlpha,
+                halfHeight + radiusTop * cosAlpha
+                ).subtract(new Vector2(
+                    radiusBottom * sinAlpha,
+                    -halfHeight + radiusBottom * cosAlpha
+                )
+            ).length();
+
+        // Total length for v texture coord
+        var vl = radiusTop * alpha
+                 + cone_length
+                 + radiusBottom * (Math.PI / 2 - alpha);
+
+        //var groupCount = 0;
+
+        // generate vertices, normals and uvs
+
+        var v = 0;
+        for (y = 0; y <= capsTopSegments; y++) {
+
+            var indexRow = [];
+
+            var a = Math.PI / 2 - alpha * (y / capsTopSegments);
+
+            v += radiusTop * alpha / capsTopSegments;
+
+            var cosA = Math.cos(a);
+            var sinA = Math.sin(a);
+
+            // calculate the radius of the current row
+            var _radius = cosA * radiusTop;
+
+            for (x = 0; x <= radialSegments; x ++) {
+
+                var u = x / radialSegments;
+
+                var theta = u * thetaLength + thetaStart;
+
+                var sinTheta = Math.sin(theta);
+                var cosTheta = Math.cos(theta);
+
+                // vertex
+                vertex.x = _radius * sinTheta;
+                vertex.y = halfHeight + sinA * radiusTop;
+                vertex.z = _radius * cosTheta;
+                vertices.push(vertex.x, vertex.y, vertex.z);
+
+                // normal
+                normal.set(cosA * sinTheta, sinA, cosA * cosTheta);
+                normals.push(normal.x, normal.y, normal.z);
+                // uv
+                uvs.push(u, 1 - v / vl);
+                // save index of vertex in respective row
+                indexRow.push(index);
+                // increase index
+                index ++;
+            }
+
+            // now save vertices of the row in our index array
+            indexArray.push(indexRow);
+
+        }
+
+        var cone_height = height + cosAlpha * radiusTop - cosAlpha * radiusBottom;
+        var slope = sinAlpha * (radiusBottom - radiusTop) / cone_height;
+        for (y = 1; y <= heightSegments; y++) {
+
+            var indexRow = [];
+
+            v += cone_length / heightSegments;
+
+            // calculate the radius of the current row
+            var _radius = sinAlpha * (y * (radiusBottom - radiusTop) / heightSegments + radiusTop);
+
+            for (x = 0; x <= radialSegments; x ++) {
+
+                var u = x / radialSegments;
+
+                var theta = u * thetaLength + thetaStart;
+
+                var sinTheta = Math.sin(theta);
+                var cosTheta = Math.cos(theta);
+
+                // vertex
+                vertex.x = _radius * sinTheta;
+                vertex.y = halfHeight + cosAlpha * radiusTop - y * cone_height / heightSegments;
+                vertex.z = _radius * cosTheta;
+                vertices.push(vertex.x, vertex.y, vertex.z);
+
+                // normal
+                normal.set(sinTheta, slope, cosTheta).normalize();
+                normals.push(normal.x, normal.y, normal.z);
+
+                // uv
+                uvs.push(u, 1 - v / vl);
+
+                // save index of vertex in respective row
+                indexRow.push(index);
+
+                // increase index
+                index ++;
+
+            }
+
+            // now save vertices of the row in our index array
+            indexArray.push(indexRow);
+
+        }
+
+        for (y = 1; y <= capsBottomSegments; y++) {
+
+            var indexRow = [];
+
+            var a = (Math.PI / 2 - alpha) - (Math.PI - alpha) * (y / capsBottomSegments);
+
+            v += radiusBottom * alpha / capsBottomSegments;
+
+            var cosA = Math.cos(a);
+            var sinA = Math.sin(a);
+
+            // calculate the radius of the current row
+            var _radius = cosA * radiusBottom;
+
+            for (x = 0; x <= radialSegments; x ++) {
+
+                var u = x / radialSegments;
+
+                var theta = u * thetaLength + thetaStart;
+
+                var sinTheta = Math.sin(theta);
+                var cosTheta = Math.cos(theta);
+
+                // vertex
+                vertex.x = _radius * sinTheta;
+                vertex.y = -halfHeight + sinA * radiusBottom;
+                vertex.z = _radius * cosTheta;
+                vertices.push(vertex.x, vertex.y, vertex.z);
+
+                // normal
+                normal.set(cosA * sinTheta, sinA, cosA * cosTheta);
+                normals.push(normal.x, normal.y, normal.z);
+
+                // uv
+                uvs.push(u, 1 - v / vl);
+
+                // save index of vertex in respective row
+                indexRow.push(index);
+                // increase index
+                index ++;
+            }
+            // now save vertices of the row in our index array
+            indexArray.push(indexRow);
+        }
+        // generate indices
+        for (x = 0; x < radialSegments; x ++) {
+            for (y = 0; y < capsTopSegments + heightSegments + capsBottomSegments; y ++) {
+                // we use the index array to access the correct indices
+                var i1 = indexArray[ y ][ x ];
+                var i2 = indexArray[ y + 1 ][ x ];
+                var i3 = indexArray[ y + 1 ][ x + 1 ];
+                var i4 = indexArray[ y ][ x + 1 ];
+                // face one
+                indices.push(i1);
+                indices.push(i2);
+                indices.push(i4);
+                // face two
+                indices.push(i2);
+                indices.push(i3);
+                indices.push(i4);
+            }
+        }
+        indices = indices.reverse();
+
+       let vDat = new VertexData();
+       vDat.positions = vertices;
+       vDat.normals = normals;
+       vDat.uvs = uvs;
+       vDat.indices = indices;
+
+       return vDat;
+};
+
+/**
+ * The options Interface for creating a Capsule Mesh
+ */
+export interface ICreateCapsuleOptions{
+    /** The Orientation of the capsule.  Default : Vector3.Up() */
+    orientation: Vector3;
+
+    /** Number of sub segments on the tube section of the capsule running parallel to orientation. */
+    subdivisions: number;
+
+    /** Number of cylindrical segments on the capsule. */
+    tessellation: number;
+
+    /** Height or Length of the capsule. */
+    height: number;
+
+    /** Radius of the capsule. */
+    radius: number;
+
+    /** Height or Length of the capsule. */
+    capSubdivisions: number;
+
+    /** Overwrite for the top radius. */
+    radiusTop?: number;
+
+    /** Overwrite for the bottom radius. */
+    radiusBottom?: number;
+
+    /** Overwrite for the top capSubdivisions. */
+    topCapSubdivisions?: number;
+
+    /** Overwrite for the bottom capSubdivisions. */
+    bottomCapSubdivisions?: number;
+}
+
+/**
+ * Creates a capsule or a pill mesh
+ * @param name defines the name of the mesh.
+ * @param options the constructors options used to shape the mesh.
+ * @param scene defines the scene the mesh is scoped to.
+ * @returns the capsule mesh
+ * @see https://doc.babylonjs.com/how_to/capsule_shape
+ */
+Mesh.CreateCapsule = (name: string, options: ICreateCapsuleOptions, scene): Mesh => {
+    return CapsuleBuilder.CreateCapsule(name, options, scene);
+};
+/**
+ * Class containing static functions to help procedurally build meshes
+ */
+export class CapsuleBuilder {
+    /**
+     * Creates a capsule or a pill mesh
+     * @param name defines the name of the mesh
+     * @param options The constructors options.
+     * @param scene The scene the mesh is scoped to.
+     * @returns Capsule Mesh
+     */
+    public static CreateCapsule(name: string, options: ICreateCapsuleOptions = {
+            orientation : Vector3.Up(),
+            subdivisions: 2,
+            tessellation: 16,
+            height: 1,
+            radius: 0.25,
+            capSubdivisions: 6
+        }, scene: any): Mesh {
+
+        var capsule = new Mesh(name, scene);
+        var vertexData = VertexData.CreateCapsule(options);
+        vertexData.applyToMesh(capsule);
+        return capsule;
+    }
+}

+ 3 - 1
src/Meshes/Builders/index.ts

@@ -17,4 +17,6 @@ export * from "./groundBuilder";
 export * from "./tubeBuilder";
 export * from "./polyhedronBuilder";
 export * from "./icoSphereBuilder";
-export * from "./decalBuilder";
+export * from "./decalBuilder";
+export * from "./icoSphereBuilder";
+export * from "./capsuleBuilder";

+ 11 - 0
src/Meshes/mesh.ts

@@ -35,6 +35,7 @@ import { Path3D } from '../Maths/math.path';
 import { Plane } from '../Maths/math.plane';
 import { TransformNode } from './transformNode';
 import { CanvasGenerator } from '../Misc/canvasGenerator';
+import { ICreateCapsuleOptions } from './Builders/capsuleBuilder';
 
 declare type LinesMesh = import("./linesMesh").LinesMesh;
 declare type InstancedMesh = import("./instancedMesh").InstancedMesh;
@@ -4019,6 +4020,16 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
         throw _DevTools.WarnImport("MeshBuilder");
     }
 
+    /** Creates a Capsule Mesh
+     * @param name defines the name of the mesh.
+     * @param options the constructors options used to shape the mesh.
+     * @param scene defines the scene the mesh is scoped to.
+     * @returns the capsule mesh
+     * @see https://doc.babylonjs.com/how_to/capsule_shape
+     */
+    public static CreateCapsule(name: string, options: ICreateCapsuleOptions, scene: Scene): Mesh {
+        throw _DevTools.WarnImport("MeshBuilder");
+    }
     // Skeletons
 
     /**

+ 19 - 0
src/Meshes/mesh.vertexData.ts

@@ -8,6 +8,8 @@ import { Logger } from '../Misc/logger';
 declare type Geometry = import("../Meshes/geometry").Geometry;
 declare type Mesh = import("../Meshes/mesh").Mesh;
 
+import { ICreateCapsuleOptions } from "./Builders/capsuleBuilder";
+
 /**
  * Define an interface for all classes that will get and set the data on vertices
  */
@@ -1041,6 +1043,23 @@ export class VertexData {
         throw _DevTools.WarnImport("polyhedronBuilder");
     }
 
+    //
+    /**
+     * Creates the VertexData for a Capsule, inspired from https://github.com/maximeq/three-js-capsule-geometry/blob/master/src/CapsuleBufferGeometry.js
+     * @param options an object used to set the following optional parameters for the capsule, required but can be empty
+     * @returns the VertexData of the Capsule
+     */
+    public static CreateCapsule(options: ICreateCapsuleOptions = {
+        orientation : Vector3.Up(),
+        subdivisions: 2,
+        tessellation: 16,
+        height: 1,
+        radius: 0.25,
+        capSubdivisions: 6
+    }): VertexData {
+        throw _DevTools.WarnImport("capsuleBuilder");
+    }
+
     // based on http://code.google.com/p/away3d/source/browse/trunk/fp10/Away3D/src/away3d/primitives/TorusKnot.as?spec=svn2473&r=2473
     /**
      * Creates the VertexData for a TorusKnot

+ 20 - 1
src/Meshes/meshBuilder.ts

@@ -17,7 +17,7 @@ import { TubeBuilder } from "./Builders/tubeBuilder";
 import { PolyhedronBuilder } from "./Builders/polyhedronBuilder";
 import { IcoSphereBuilder } from "./Builders/icoSphereBuilder";
 import { DecalBuilder } from "./Builders/decalBuilder";
-
+import { CapsuleBuilder, ICreateCapsuleOptions } from "./Builders/capsuleBuilder";
 import { Vector4, Vector3, Vector2 } from "../Maths/math.vector";
 import { Nullable } from "../types";
 import { Scene } from "../scene";
@@ -546,4 +546,23 @@ export class MeshBuilder {
     public static CreateDecal(name: string, sourceMesh: AbstractMesh, options: { position?: Vector3, normal?: Vector3, size?: Vector3, angle?: number }): Mesh {
         return DecalBuilder.CreateDecal(name, sourceMesh, options);
     }
+
+    /**
+     * Creates a Capsule Mesh
+     * @param name defines the name of the mesh.
+     * @param options the constructors options used to shape the mesh.
+     * @param scene defines the scene the mesh is scoped to.
+     * @returns the capsule mesh
+     * @see https://doc.babylonjs.com/how_to/capsule_shape
+     */
+    public static CreateCapsule(name: string, options: ICreateCapsuleOptions = {
+        orientation : Vector3.Up(),
+        subdivisions: 2,
+        tessellation: 16,
+        height: 1,
+        radius: 0.25,
+        capSubdivisions: 6
+    }, scene: Nullable<Scene> = null): Mesh {
+        return CapsuleBuilder.CreateCapsule(name, options, scene);
+    }
 }

BIN
tests/validation/ReferenceImages/CreateCapsule.png


+ 5 - 0
tests/validation/config.json

@@ -333,6 +333,11 @@
             "replace": "updateVerticesDataDirectly, updateVerticesData"
         },
         {
+            "title": "Capsule",
+            "playgroundId": "#JAFIIU",
+            "referenceImage": "CreateCapsule.png"
+        },
+        {
             "title": "Bones",
             "scriptToRun": "/Demos/Bones/bones.js",
             "functionToCall": "CreateBonesTestScene",