소스 검색

Break Down Mesh Builder Dependency

sebavan 6 년 전
부모
커밋
f0730961c8
51개의 변경된 파일3687개의 추가작업 그리고 3768개의 파일을 삭제
  1. 7 0
      Tools/Gulp/helpers/gulp-validateImports.js
  2. 3 1
      dist/preview release/what's new.md
  3. 2 2
      gui/src/3D/controls/button3D.ts
  4. 6 5
      gui/src/3D/controls/holographicButton.ts
  5. 2 2
      inspector/src/components/actionTabs/tabs/propertyGrids/meshes/meshPropertyGridComponent.tsx
  6. 2 1
      readme-es6.md
  7. 2 0
      src/Behaviors/Meshes/pointerDragBehavior.ts
  8. 4 0
      src/Cameras/VR/vrExperienceHelper.ts
  9. 4 3
      src/Debug/physicsViewer.ts
  10. 2 0
      src/Debug/rayHelper.ts
  11. 3 3
      src/Debug/skeletonViewer.ts
  12. 4 3
      src/Gizmos/axisDragGizmo.ts
  13. 4 3
      src/Gizmos/axisScaleGizmo.ts
  14. 21 16
      src/Gizmos/boundingBoxGizmo.ts
  15. 3 0
      src/Gizmos/planeRotationGizmo.ts
  16. 3 3
      src/Gizmos/scaleGizmo.ts
  17. 4 0
      src/Helpers/environmentHelper.ts
  18. 1 1
      src/Helpers/photoDome.ts
  19. 2 1
      src/Helpers/sceneHelpers.ts
  20. 1 1
      src/Helpers/videoDome.ts
  21. 2 85
      src/Loading/Plugins/babylonFileLoader.ts
  22. 4 0
      src/Loading/sceneLoader.ts
  23. 154 0
      src/Meshes/Builders/boxBuilder.ts
  24. 324 0
      src/Meshes/Builders/cylinderBuilder.ts
  25. 252 0
      src/Meshes/Builders/decalBuilder.ts
  26. 98 0
      src/Meshes/Builders/discBuilder.ts
  27. 410 0
      src/Meshes/Builders/groundBuilder.ts
  28. 298 0
      src/Meshes/Builders/icoSphereBuilder.ts
  29. 17 0
      src/Meshes/Builders/index.ts
  30. 82 0
      src/Meshes/Builders/latheBuilder.ts
  31. 275 0
      src/Meshes/Builders/linesBuilder.ts
  32. 106 0
      src/Meshes/Builders/planeBuilder.ts
  33. 157 0
      src/Meshes/Builders/polygonBuilder.ts
  34. 175 0
      src/Meshes/Builders/polyhedronBuilder.ts
  35. 398 0
      src/Meshes/Builders/ribbonBuilder.ts
  36. 211 0
      src/Meshes/Builders/shapeBuilder.ts
  37. 120 0
      src/Meshes/Builders/sphereBuilder.ts
  38. 115 0
      src/Meshes/Builders/torusBuilder.ts
  39. 143 0
      src/Meshes/Builders/torusKnotBuilder.ts
  40. 161 0
      src/Meshes/Builders/tubeBuilder.ts
  41. 1 769
      src/Meshes/geometry.ts
  42. 2 1
      src/Meshes/index.ts
  43. 19 3
      src/Meshes/mesh.ts
  44. 21 1615
      src/Meshes/mesh.vertexData.ts
  45. 47 1213
      src/Meshes/meshBuilder.ts
  46. 2 29
      src/Misc/sceneSerializer.ts
  47. 2 2
      src/Particles/particleSystemSet.ts
  48. 2 2
      src/Particles/solidParticleSystem.ts
  49. 5 4
      src/Physics/physicsHelper.ts
  50. 2 0
      src/PostProcesses/volumetricLightScatteringPostProcess.ts
  51. 2 0
      src/Rendering/boundingBoxRenderer.ts

+ 7 - 0
Tools/Gulp/helpers/gulp-validateImports.js

@@ -8,6 +8,7 @@ var colorConsole = require("../../NodeHelpers/colorConsole");
 var config = require("../../Config/config");
 
 const indexExlclusion = ["States", "EmitterTypes"];
+const forbiddenImports = ["Meshes/meshBuilder"];
 
 const mapping = { };
 config.modules.forEach(moduleName => {
@@ -48,6 +49,12 @@ var validatePath = function(fileLocation, directory, module, lineNumber, errors)
             }
         }
     }
+
+    for (let forbiddenImport of forbiddenImports) {
+        if (module.endsWith(forbiddenImport)) {
+            errors.push(`Line ${lineNumber} Imports ${module} is forbidden for tree shaking.`);
+        }
+    }
 }
 
 var validateImports = function(data, fileLocation, options) {

+ 3 - 1
dist/preview release/what's new.md

@@ -218,4 +218,6 @@
 - no more `babylon.no-module.max.js` javascript version has the Webpack UMD bundle covers both ([Sebavan]
 (https://github.com/Sebavan))
 - no more `es6.js` javascript as it is now available as a true es6 npm package ([Sebavan](https://github.com/Sebavan))
-- no more `babylon.worker.js` javascript following the lack of usage from the feature ([Sebavan](https://github.com/Sebavan))
+- no more `babylon.worker.js` javascript following the lack of usage from the feature ([Sebavan]
+(https://github.com/Sebavan))
+- no more `Primitive Geometries` as they were not in use since 2.0 ([Sebavan](https://github.com/Sebavan))

+ 2 - 2
gui/src/3D/controls/button3D.ts

@@ -2,7 +2,7 @@ import { int, Nullable } from "babylonjs/types";
 import { Color3, Vector4 } from "babylonjs/Maths/math";
 import { TransformNode } from "babylonjs/Meshes/transformNode";
 import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
-import { MeshBuilder } from "babylonjs/Meshes/meshBuilder";
+import { BoxBuilder } from "babylonjs/Meshes/Builders/boxBuilder";
 import { Material } from "babylonjs/Materials/material";
 import { StandardMaterial } from "babylonjs/Materials/standardMaterial";
 import { Texture } from "babylonjs/Materials/Textures/texture";
@@ -152,7 +152,7 @@ export class Button3D extends AbstractButton3D {
         }
         faceUV[1] = new Vector4(0, 0, 1, 1);
 
-        let mesh = MeshBuilder.CreateBox(this.name + "_rootMesh", {
+        let mesh = BoxBuilder.CreateBox(this.name + "_rootMesh", {
             width: 1.0,
             height: 1.0,
             depth: 0.08,

+ 6 - 5
gui/src/3D/controls/holographicButton.ts

@@ -6,7 +6,8 @@ import { Color3, Vector3 } from "babylonjs/Maths/math";
 import { StandardMaterial } from "babylonjs/Materials/standardMaterial";
 import { TransformNode } from "babylonjs/Meshes/transformNode";
 import { Mesh } from "babylonjs/Meshes/mesh";
-import { MeshBuilder } from "babylonjs/Meshes/meshBuilder";
+import { PlaneBuilder } from "babylonjs/Meshes/Builders/planeBuilder";
+import { BoxBuilder } from "babylonjs/Meshes/Builders/boxBuilder";
 import { FadeInOutBehavior } from "babylonjs/Behaviors/Meshes/fadeInOutBehavior";
 import { Scene } from "babylonjs/scene";
 
@@ -65,8 +66,8 @@ export class HolographicButton extends Button3D {
         }
         if (!this._tooltipFade) {
             // Create tooltip with mesh and text
-            this._tooltipMesh = MeshBuilder.CreatePlane("", { size: 1 }, this._backPlate._scene);
-            var tooltipBackground = MeshBuilder.CreatePlane("", { size: 1, sideOrientation: Mesh.DOUBLESIDE }, this._backPlate._scene);
+            this._tooltipMesh = PlaneBuilder.CreatePlane("", { size: 1 }, this._backPlate._scene);
+            var tooltipBackground = PlaneBuilder.CreatePlane("", { size: 1, sideOrientation: Mesh.DOUBLESIDE }, this._backPlate._scene);
             var mat = new StandardMaterial("", this._backPlate._scene);
             mat.diffuseColor = Color3.FromHexString("#212121");
             tooltipBackground.material = mat;
@@ -235,13 +236,13 @@ export class HolographicButton extends Button3D {
 
     // Mesh association
     protected _createNode(scene: Scene): TransformNode {
-        this._backPlate = MeshBuilder.CreateBox(this.name + "BackMesh", {
+        this._backPlate = BoxBuilder.CreateBox(this.name + "BackMesh", {
             width: 1.0,
             height: 1.0,
             depth: 0.08
         }, scene);
 
-        this._frontPlate = MeshBuilder.CreateBox(this.name + "FrontMesh", {
+        this._frontPlate = BoxBuilder.CreateBox(this.name + "FrontMesh", {
             width: 1.0,
             height: 1.0,
             depth: 0.08

+ 2 - 2
inspector/src/components/actionTabs/tabs/propertyGrids/meshes/meshPropertyGridComponent.tsx

@@ -5,7 +5,7 @@ import { Tools } from "babylonjs/Misc/tools";
 import { Color3, Vector3 } from "babylonjs/Maths/math";
 import { Mesh } from "babylonjs/Meshes/mesh";
 import { VertexBuffer } from "babylonjs/Meshes/buffer";
-import { MeshBuilder } from "babylonjs/Meshes/meshBuilder";
+import { LinesBuilder } from "babylonjs/Meshes/Builders/linesBuilder";
 import { PhysicsImpostor } from "babylonjs/Physics/physicsImpostor";
 import { Scene } from "babylonjs/scene";
 
@@ -60,7 +60,7 @@ export class MeshPropertyGridComponent extends React.Component<IMeshPropertyGrid
             lines.push([v1, v2]);
         }
 
-        var normalLines = MeshBuilder.CreateLineSystem("normalLines", { lines: lines }, scene);
+        var normalLines = LinesBuilder.CreateLineSystem("normalLines", { lines: lines }, scene);
         normalLines.color = color;
         normalLines.parent = mesh;
 

+ 2 - 1
readme-es6.md

@@ -53,7 +53,8 @@ import { Mesh } from "@babylonjs/core/Meshes/mesh";
 // Side-effects only imports allowing the standard material to be used as default.
 import "@babylonjs/core/Materials/standardMaterial";
 // Side-effects only imports allowing Mesh to create default shapes (to enhance tree shaking, the construction methods on mesh are not available if the meshbuilder has not been imported).
-import "@babylonjs/core/Meshes/meshBuilder";
+import "@babylonjs/core/Meshes/Builders/sphereBuilder";
+import "@babylonjs/core/Meshes/Builders/boxBuilder";
 
 const canvas = document.getElementById("renderCanvas") as HTMLCanvasElement;
 const engine = new Engine(canvas);

+ 2 - 0
src/Behaviors/Meshes/pointerDragBehavior.ts

@@ -9,6 +9,8 @@ import { PointerInfo, PointerEventTypes } from "../../Events/pointerEvents";
 import { Ray } from "../../Culling/ray";
 import { PivotTools } from '../../Misc/pivotTools';
 
+import "../../Meshes/Builders/planeBuilder";
+
 /**
  * A behavior that when attached to a mesh will allow the mesh to be dragged around the screen based on pointer events
  */

+ 4 - 0
src/Cameras/VR/vrExperienceHelper.ts

@@ -27,6 +27,10 @@ import { ImageProcessingPostProcess } from "../../PostProcesses/imageProcessingP
 import { SineEase, EasingFunction, CircleEase } from "../../Animations/easing";
 import { Animation } from "../../Animations/animation";
 
+import "../../Meshes/Builders/groundBuilder";
+import "../../Meshes/Builders/torusBuilder";
+import "../../Meshes/Builders/cylinderBuilder";
+
 /**
  * Options to modify the vr teleportation behavior.
  */

+ 4 - 3
src/Debug/physicsViewer.ts

@@ -2,7 +2,8 @@ import { Nullable } from "../types";
 import { Scene } from "../scene";
 import { AbstractMesh } from "../Meshes/abstractMesh";
 import { Mesh } from "../Meshes/mesh";
-import { MeshBuilder } from "../Meshes/meshBuilder";
+import { BoxBuilder } from "../Meshes/Builders/boxBuilder";
+import { SphereBuilder } from "../Meshes/Builders/sphereBuilder";
 import { Quaternion, Color3 } from "../Maths/math";
 import { Material } from "../Materials/material";
 import { EngineStore } from "../Engines/engineStore";
@@ -164,7 +165,7 @@ export class PhysicsViewer {
 
     private _getDebugBoxMesh(scene: Scene): AbstractMesh {
         if (!this._debugBoxMesh) {
-            this._debugBoxMesh = MeshBuilder.CreateBox('physicsBodyBoxViewMesh', { size: 1 }, scene);
+            this._debugBoxMesh = BoxBuilder.CreateBox('physicsBodyBoxViewMesh', { size: 1 }, scene);
             this._debugBoxMesh.rotationQuaternion = Quaternion.Identity();
             this._debugBoxMesh.material = this._getDebugMaterial(scene);
         }
@@ -174,7 +175,7 @@ export class PhysicsViewer {
 
     private _getDebugSphereMesh(scene: Scene): AbstractMesh {
         if (!this._debugSphereMesh) {
-            this._debugSphereMesh = MeshBuilder.CreateSphere('physicsBodySphereViewMesh', { diameter: 1 }, scene);
+            this._debugSphereMesh = SphereBuilder.CreateSphere('physicsBodySphereViewMesh', { diameter: 1 }, scene);
             this._debugSphereMesh.rotationQuaternion = Quaternion.Identity();
             this._debugSphereMesh.material = this._getDebugMaterial(scene);
         }

+ 2 - 0
src/Debug/rayHelper.ts

@@ -6,6 +6,8 @@ import { AbstractMesh } from "../Meshes/abstractMesh";
 import { Mesh } from "../Meshes/mesh";
 import { LinesMesh } from "../Meshes/linesMesh";
 
+import "../Meshes/Builders/linesBuilder";
+
 /**
  * As raycast might be hard to debug, the RayHelper can help rendering the different rays
  * in order to better appreciate the issue one might have.

+ 3 - 3
src/Debug/skeletonViewer.ts

@@ -5,7 +5,7 @@ import { Bone } from "../Bones/bone";
 import { Skeleton } from "../Bones/skeleton";
 import { AbstractMesh } from "../Meshes/abstractMesh";
 import { LinesMesh } from "../Meshes/linesMesh";
-import { MeshBuilder } from "../Meshes/meshBuilder";
+import { LinesBuilder } from "../Meshes/Builders/linesBuilder";
 import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
 
 /**
@@ -161,10 +161,10 @@ export class SkeletonViewer {
         const targetScene = this._utilityLayer.utilityLayerScene;
 
         if (!this._debugMesh) {
-            this._debugMesh = MeshBuilder.CreateLineSystem("", { lines: this._debugLines, updatable: true, instance: null }, targetScene);
+            this._debugMesh = LinesBuilder.CreateLineSystem("", { lines: this._debugLines, updatable: true, instance: null }, targetScene);
             this._debugMesh.renderingGroupId = this.renderingGroupId;
         } else {
-            MeshBuilder.CreateLineSystem("", { lines: this._debugLines, updatable: true, instance: this._debugMesh }, targetScene);
+            LinesBuilder.CreateLineSystem("", { lines: this._debugLines, updatable: true, instance: this._debugMesh }, targetScene);
         }
         this._debugMesh.position.copyFrom(this.mesh.position);
         this._debugMesh.color = this.color;

+ 4 - 3
src/Gizmos/axisDragGizmo.ts

@@ -6,7 +6,8 @@ import { TransformNode } from "../Meshes/transformNode";
 import { AbstractMesh } from "../Meshes/abstractMesh";
 import { Mesh } from "../Meshes/mesh";
 import { LinesMesh } from "../Meshes/linesMesh";
-import { MeshBuilder } from "../Meshes/meshBuilder";
+import { CylinderBuilder } from "../Meshes/Builders/cylinderBuilder";
+import { LinesBuilder } from "../Meshes/Builders/linesBuilder";
 import { PointerDragBehavior } from "../Behaviors/Meshes/pointerDragBehavior";
 import { _TimeToken } from "../Instrumentation/timeToken";
 import { _DepthCullingState, _StencilState, _AlphaState } from "../States/index";
@@ -36,8 +37,8 @@ export class AxisDragGizmo extends Gizmo {
     /** @hidden */
     public static _CreateArrow(scene: Scene, material: StandardMaterial): TransformNode {
         var arrow = new TransformNode("arrow", scene);
-        var cylinder = MeshBuilder.CreateCylinder("cylinder", { diameterTop: 0, height: 1.5, diameterBottom: 0.75, tessellation: 96 }, scene);
-        var line = MeshBuilder.CreateLines("line", { points: [new Vector3(0, 0, 0), new Vector3(0, 1.1, 0)] }, scene);
+        var cylinder = CylinderBuilder.CreateCylinder("cylinder", { diameterTop: 0, height: 1.5, diameterBottom: 0.75, tessellation: 96 }, scene);
+        var line = LinesBuilder.CreateLines("line", { points: [new Vector3(0, 0, 0), new Vector3(0, 1.1, 0)] }, scene);
         line.color = material.emissiveColor;
         cylinder.parent = arrow;
         line.parent = arrow;

+ 4 - 3
src/Gizmos/axisScaleGizmo.ts

@@ -5,7 +5,8 @@ import { Vector3, Color3 } from "../Maths/math";
 import { AbstractMesh } from "../Meshes/abstractMesh";
 import { Mesh } from "../Meshes/mesh";
 import { LinesMesh } from "../Meshes/linesMesh";
-import { MeshBuilder } from "../Meshes/meshBuilder";
+import { BoxBuilder } from "../Meshes/Builders/boxBuilder";
+import { LinesBuilder } from "../Meshes/Builders/linesBuilder";
 import { StandardMaterial } from "../Materials/standardMaterial";
 import { PointerDragBehavior } from "../Behaviors/Meshes/pointerDragBehavior";
 import { _TimeToken } from "../Instrumentation/timeToken";
@@ -55,8 +56,8 @@ export class AxisScaleGizmo extends Gizmo {
 
         // Build mesh on root node
         var arrow = new AbstractMesh("", gizmoLayer.utilityLayerScene);
-        var arrowMesh = MeshBuilder.CreateBox("yPosMesh", { size: 0.4 }, gizmoLayer.utilityLayerScene);
-        var arrowTail = MeshBuilder.CreateLines("yPosMesh", { points: [new Vector3(0, 0, 0), new Vector3(0, 1.1, 0)] }, gizmoLayer.utilityLayerScene);
+        var arrowMesh = BoxBuilder.CreateBox("yPosMesh", { size: 0.4 }, gizmoLayer.utilityLayerScene);
+        var arrowTail = LinesBuilder.CreateLines("yPosMesh", { points: [new Vector3(0, 0, 0), new Vector3(0, 1.1, 0)] }, gizmoLayer.utilityLayerScene);
         arrowTail.color = this._coloredMaterial.emissiveColor;
         arrow.addChild(arrowMesh);
         arrow.addChild(arrowTail);

+ 21 - 16
src/Gizmos/boundingBoxGizmo.ts

@@ -6,7 +6,9 @@ import { Scene } from "../scene";
 import { Quaternion, Matrix, Vector3, Color3, Epsilon } from "../Maths/math";
 import { AbstractMesh } from "../Meshes/abstractMesh";
 import { Mesh } from "../Meshes/mesh";
-import { MeshBuilder } from "../Meshes/meshBuilder";
+import { SphereBuilder } from "../Meshes/Builders/sphereBuilder";
+import { BoxBuilder } from "../Meshes/Builders/boxBuilder";
+import { LinesBuilder } from "../Meshes/Builders/linesBuilder";
 import { PointerDragBehavior } from "../Behaviors/Meshes/pointerDragBehavior";
 import { _TimeToken } from "../Instrumentation/timeToken";
 import { _DepthCullingState, _StencilState, _AlphaState } from "../States/index";
@@ -14,6 +16,9 @@ import { Gizmo } from "./gizmo";
 import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
 import { StandardMaterial } from "../Materials/standardMaterial";
 import { PivotTools } from "../Misc/pivotTools";
+
+import "../Meshes/Builders/boxBuilder";
+
 /**
  * Bounding box gizmo
  */
@@ -110,18 +115,18 @@ export class BoundingBoxGizmo extends Gizmo {
         this._lineBoundingBox = new AbstractMesh("", gizmoLayer.utilityLayerScene);
         this._lineBoundingBox.rotationQuaternion = new Quaternion();
         var lines = [];
-        lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(0, 0, 0), new Vector3(this._boundingDimensions.x, 0, 0)] }, gizmoLayer.utilityLayerScene));
-        lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(0, 0, 0), new Vector3(0, this._boundingDimensions.y, 0)] }, gizmoLayer.utilityLayerScene));
-        lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(0, 0, 0), new Vector3(0, 0, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
-        lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(this._boundingDimensions.x, 0, 0), new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, 0)] }, gizmoLayer.utilityLayerScene));
-        lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(this._boundingDimensions.x, 0, 0), new Vector3(this._boundingDimensions.x, 0, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
-        lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(0, this._boundingDimensions.y, 0), new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, 0)] }, gizmoLayer.utilityLayerScene));
-        lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(0, this._boundingDimensions.y, 0), new Vector3(0, this._boundingDimensions.y, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
-        lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(0, 0, this._boundingDimensions.z), new Vector3(this._boundingDimensions.x, 0, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
-        lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(0, 0, this._boundingDimensions.z), new Vector3(0, this._boundingDimensions.y, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
-        lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, this._boundingDimensions.z), new Vector3(0, this._boundingDimensions.y, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
-        lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, this._boundingDimensions.z), new Vector3(this._boundingDimensions.x, 0, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
-        lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, this._boundingDimensions.z), new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, 0)] }, gizmoLayer.utilityLayerScene));
+        lines.push(LinesBuilder.CreateLines("lines", { points: [new Vector3(0, 0, 0), new Vector3(this._boundingDimensions.x, 0, 0)] }, gizmoLayer.utilityLayerScene));
+        lines.push(LinesBuilder.CreateLines("lines", { points: [new Vector3(0, 0, 0), new Vector3(0, this._boundingDimensions.y, 0)] }, gizmoLayer.utilityLayerScene));
+        lines.push(LinesBuilder.CreateLines("lines", { points: [new Vector3(0, 0, 0), new Vector3(0, 0, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
+        lines.push(LinesBuilder.CreateLines("lines", { points: [new Vector3(this._boundingDimensions.x, 0, 0), new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, 0)] }, gizmoLayer.utilityLayerScene));
+        lines.push(LinesBuilder.CreateLines("lines", { points: [new Vector3(this._boundingDimensions.x, 0, 0), new Vector3(this._boundingDimensions.x, 0, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
+        lines.push(LinesBuilder.CreateLines("lines", { points: [new Vector3(0, this._boundingDimensions.y, 0), new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, 0)] }, gizmoLayer.utilityLayerScene));
+        lines.push(LinesBuilder.CreateLines("lines", { points: [new Vector3(0, this._boundingDimensions.y, 0), new Vector3(0, this._boundingDimensions.y, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
+        lines.push(LinesBuilder.CreateLines("lines", { points: [new Vector3(0, 0, this._boundingDimensions.z), new Vector3(this._boundingDimensions.x, 0, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
+        lines.push(LinesBuilder.CreateLines("lines", { points: [new Vector3(0, 0, this._boundingDimensions.z), new Vector3(0, this._boundingDimensions.y, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
+        lines.push(LinesBuilder.CreateLines("lines", { points: [new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, this._boundingDimensions.z), new Vector3(0, this._boundingDimensions.y, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
+        lines.push(LinesBuilder.CreateLines("lines", { points: [new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, this._boundingDimensions.z), new Vector3(this._boundingDimensions.x, 0, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
+        lines.push(LinesBuilder.CreateLines("lines", { points: [new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, this._boundingDimensions.z), new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, 0)] }, gizmoLayer.utilityLayerScene));
         lines.forEach((l) => {
             l.color = color;
             l.position.addInPlace(new Vector3(-this._boundingDimensions.x / 2, -this._boundingDimensions.y / 2, -this._boundingDimensions.z / 2));
@@ -134,7 +139,7 @@ export class BoundingBoxGizmo extends Gizmo {
         this._rotateSpheresParent = new AbstractMesh("", gizmoLayer.utilityLayerScene);
         this._rotateSpheresParent.rotationQuaternion = new Quaternion();
         for (let i = 0; i < 12; i++) {
-            let sphere = MeshBuilder.CreateSphere("", { diameter: 1 }, gizmoLayer.utilityLayerScene);
+            let sphere = SphereBuilder.CreateSphere("", { diameter: 1 }, gizmoLayer.utilityLayerScene);
             sphere.rotationQuaternion = new Quaternion();
             sphere.material = coloredMaterial;
 
@@ -224,7 +229,7 @@ export class BoundingBoxGizmo extends Gizmo {
         for (var i = 0; i < 2; i++) {
             for (var j = 0; j < 2; j++) {
                 for (var k = 0; k < 2; k++) {
-                    let box = MeshBuilder.CreateBox("", { size: 1 }, gizmoLayer.utilityLayerScene);
+                    let box = BoxBuilder.CreateBox("", { size: 1 }, gizmoLayer.utilityLayerScene);
                     box.material = coloredMaterial;
 
                     // Dragging logic
@@ -544,7 +549,7 @@ export class BoundingBoxGizmo extends Gizmo {
         mesh.position.set(0, 0, 0);
 
         // Update bounding dimensions/positions
-        var box = MeshBuilder.CreateBox("box", { size: 1 }, mesh.getScene());
+        var box = BoxBuilder.CreateBox("box", { size: 1 }, mesh.getScene());
         var boundingMinMax = mesh.getHierarchyBoundingVectors();
         boundingMinMax.max.subtractToRef(boundingMinMax.min, box.scaling);
 

+ 3 - 0
src/Gizmos/planeRotationGizmo.ts

@@ -11,6 +11,9 @@ import { _DepthCullingState, _StencilState, _AlphaState } from "../States/index"
 import { Gizmo } from "./gizmo";
 import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
 import { StandardMaterial } from "../Materials/standardMaterial";
+
+import "../Meshes/Builders/linesBuilder";
+
 /**
  * Single plane rotation gizmo
  */

+ 3 - 3
src/Gizmos/scaleGizmo.ts

@@ -3,7 +3,7 @@ import { Observable } from "../Misc/observable";
 import { Nullable } from "../types";
 import { Vector3, Color3 } from "../Maths/math";
 import { AbstractMesh } from "../Meshes/abstractMesh";
-import { Mesh } from "../Meshes/mesh";
+import { PolyhedronBuilder } from "../Meshes/Builders/polyhedronBuilder";
 import { Gizmo } from "./gizmo";
 import { AxisScaleGizmo } from "./axisScaleGizmo";
 import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
@@ -56,10 +56,10 @@ export class ScaleGizmo extends Gizmo {
         this.uniformScaleGizmo = new AxisScaleGizmo(new Vector3(0, 1, 0), Color3.Yellow().scale(0.5), gizmoLayer);
         this.uniformScaleGizmo.updateGizmoRotationToMatchAttachedMesh = false;
         this.uniformScaleGizmo.uniformScaling = true;
-        var uniformScalingMesh = Mesh.CreatePolyhedron("", { type: 1 }, this.uniformScaleGizmo.gizmoLayer.utilityLayerScene);
+        var uniformScalingMesh = PolyhedronBuilder.CreatePolyhedron("", { type: 1 }, this.uniformScaleGizmo.gizmoLayer.utilityLayerScene);
         uniformScalingMesh.scaling.scaleInPlace(0.02);
         uniformScalingMesh.visibility = 0;
-        var octahedron = Mesh.CreatePolyhedron("", { type: 1 }, this.uniformScaleGizmo.gizmoLayer.utilityLayerScene);
+        var octahedron = PolyhedronBuilder.CreatePolyhedron("", { type: 1 }, this.uniformScaleGizmo.gizmoLayer.utilityLayerScene);
         octahedron.scaling.scaleInPlace(0.007);
         uniformScalingMesh.addChild(octahedron);
         this.uniformScaleGizmo.setCustomMesh(uniformScalingMesh, true);

+ 4 - 0
src/Helpers/environmentHelper.ts

@@ -13,6 +13,10 @@ import { BackgroundMaterial } from "../Materials/Background/backgroundMaterial";
 import { _TimeToken } from "../Instrumentation/timeToken";
 import { _DepthCullingState, _StencilState, _AlphaState } from "../States/index";
 import { Constants } from "../Engines/constants";
+
+import "../Meshes/Builders/planeBuilder";
+import "../Meshes/Builders/boxBuilder";
+
 /**
  * Represents the different options available during the creation of
  * a Environment helper.

+ 1 - 1
src/Helpers/photoDome.ts

@@ -7,7 +7,7 @@ import { Texture } from "../Materials/Textures/texture";
 import { BackgroundMaterial } from "../Materials/Background/backgroundMaterial";
 import { _TimeToken } from "../Instrumentation/timeToken";
 import { _DepthCullingState, _StencilState, _AlphaState } from "../States/index";
-import "../Meshes/meshBuilder";
+import "../Meshes/Builders/sphereBuilder";
 
 /**
  * Display a 360 degree photo on an approximately spherical surface, useful for VR applications or skyboxes.

+ 2 - 1
src/Helpers/sceneHelpers.ts

@@ -17,7 +17,8 @@ import { WebXRInput } from "../Cameras/XR/webXRInput";
 import { WebXREnterExitUI } from "../Cameras/XR/webXREnterExitUI";
 import { WebXRExperienceHelper } from "../Cameras/XR/webXRExperienceHelper";
 import { VRExperienceHelperOptions, VRExperienceHelper } from "../Cameras/VR/vrExperienceHelper";
-import "../Meshes/meshBuilder";
+
+import "../Meshes/Builders/boxBuilder";
 
 /** @hidden */
 export var _forceSceneHelpersToBundle = true;

+ 1 - 1
src/Helpers/videoDome.ts

@@ -6,7 +6,7 @@ import { _DepthCullingState, _StencilState, _AlphaState } from "../States/index"
 import { Texture } from "../Materials/Textures/texture";
 import { VideoTexture, VideoTextureSettings } from "../Materials/Textures/videoTexture";
 import { BackgroundMaterial } from "../Materials/Background/backgroundMaterial";
-import "../Meshes/meshBuilder";
+import "../Meshes/Builders/sphereBuilder";
 
 /**
  * Display a 360 degree video on an approximately spherical surface, useful for VR applications or skyboxes.

+ 2 - 85
src/Loading/Plugins/babylonFileLoader.ts

@@ -5,7 +5,7 @@ import { Scene } from "../../scene";
 import { Vector3, Color3, Color4 } from "../../Maths/math";
 import { Mesh } from "../../Meshes/mesh";
 import { AbstractMesh } from "../../Meshes/abstractMesh";
-import { Geometry, BoxGeometry, SphereGeometry, CylinderGeometry, TorusGeometry, GroundGeometry, PlaneGeometry, TorusKnotGeometry } from "../../Meshes/geometry";
+import { Geometry } from "../../Meshes/geometry";
 import { TransformNode } from "../../Meshes/transformNode";
 import { Material } from "../../Materials/material";
 import { MultiMaterial } from "../../Materials/multiMaterial";
@@ -155,68 +155,6 @@ var loadAssetContainer = (scene: Scene, data: string, rootUrl: string, onError?:
         var geometries = parsedData.geometries;
         if (geometries !== undefined && geometries !== null) {
             var addedGeometry = new Array<Nullable<Geometry>>();
-            // Boxes
-            var boxes = geometries.boxes;
-            if (boxes !== undefined && boxes !== null) {
-                for (index = 0, cache = boxes.length; index < cache; index++) {
-                    var parsedBox = boxes[index];
-                    addedGeometry.push(BoxGeometry.Parse(parsedBox, scene));
-                }
-            }
-
-            // Spheres
-            var spheres = geometries.spheres;
-            if (spheres !== undefined && spheres !== null) {
-                for (index = 0, cache = spheres.length; index < cache; index++) {
-                    var parsedSphere = spheres[index];
-                    addedGeometry.push(SphereGeometry.Parse(parsedSphere, scene));
-                }
-            }
-
-            // Cylinders
-            var cylinders = geometries.cylinders;
-            if (cylinders !== undefined && cylinders !== null) {
-                for (index = 0, cache = cylinders.length; index < cache; index++) {
-                    var parsedCylinder = cylinders[index];
-                    addedGeometry.push(CylinderGeometry.Parse(parsedCylinder, scene));
-                }
-            }
-
-            // Toruses
-            var toruses = geometries.toruses;
-            if (toruses !== undefined && toruses !== null) {
-                for (index = 0, cache = toruses.length; index < cache; index++) {
-                    var parsedTorus = toruses[index];
-                    addedGeometry.push(TorusGeometry.Parse(parsedTorus, scene));
-                }
-            }
-
-            // Grounds
-            var grounds = geometries.grounds;
-            if (grounds !== undefined && grounds !== null) {
-                for (index = 0, cache = grounds.length; index < cache; index++) {
-                    var parsedGround = grounds[index];
-                    addedGeometry.push(GroundGeometry.Parse(parsedGround, scene));
-                }
-            }
-
-            // Planes
-            var planes = geometries.planes;
-            if (planes !== undefined && planes !== null) {
-                for (index = 0, cache = planes.length; index < cache; index++) {
-                    var parsedPlane = planes[index];
-                    addedGeometry.push(PlaneGeometry.Parse(parsedPlane, scene));
-                }
-            }
-
-            // TorusKnots
-            var torusKnots = geometries.torusKnots;
-            if (torusKnots !== undefined && torusKnots !== null) {
-                for (index = 0, cache = torusKnots.length; index < cache; index++) {
-                    var parsedTorusKnot = torusKnots[index];
-                    addedGeometry.push(TorusKnotGeometry.Parse(parsedTorusKnot, scene));
-                }
-            }
 
             // VertexData
             var vertexData = geometries.vertexData;
@@ -438,28 +376,7 @@ SceneLoader.RegisterPlugin({
                                         parsedData.geometries[geometryType].forEach((parsedGeometryData: any) => {
                                             if (parsedGeometryData.id === parsedMesh.geometryId) {
                                                 switch (geometryType) {
-                                                    case "boxes":
-                                                        BoxGeometry.Parse(parsedGeometryData, scene);
-                                                        break;
-                                                    case "spheres":
-                                                        SphereGeometry.Parse(parsedGeometryData, scene);
-                                                        break;
-                                                    case "cylinders":
-                                                        CylinderGeometry.Parse(parsedGeometryData, scene);
-                                                        break;
-                                                    case "toruses":
-                                                        TorusGeometry.Parse(parsedGeometryData, scene);
-                                                        break;
-                                                    case "grounds":
-                                                        GroundGeometry.Parse(parsedGeometryData, scene);
-                                                        break;
-                                                    case "planes":
-                                                        PlaneGeometry.Parse(parsedGeometryData, scene);
-                                                        break;
-                                                    case "torusKnots":
-                                                        TorusKnotGeometry.Parse(parsedGeometryData, scene);
-                                                        break;
-                                                    case "vertexData":
+                                                       case "vertexData":
                                                         Geometry.Parse(parsedGeometryData, scene, rootUrl);
                                                         break;
                                                 }

+ 4 - 0
src/Loading/sceneLoader.ts

@@ -368,6 +368,10 @@ export class SceneLoader {
             plugin = <any>registeredPlugin.plugin;
         }
 
+        if (plugin) {
+            throw "The loader plugin corresponding to the file type you are trying to load has not been found. If using es6, please import the plugin you wish to use before.";
+        }
+
         let useArrayBuffer = registeredPlugin.isBinary;
         let offlineProvider: IOfflineProvider;
 

+ 154 - 0
src/Meshes/Builders/boxBuilder.ts

@@ -0,0 +1,154 @@
+import { Nullable } from "../../types";
+import { Scene } from "../../scene";
+import { Vector4, Color4, Vector3 } from "../../Maths/math";
+import { Mesh, _CreationDataStorage } from "../mesh";
+import { VertexData } from "../mesh.vertexData";
+
+VertexData.CreateBox = function(options: { size?: number, width?: number, height?: number, depth?: number, faceUV?: Vector4[], faceColors?: Color4[], sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4 }): VertexData {
+    var normalsSource = [
+        new Vector3(0, 0, 1),
+        new Vector3(0, 0, -1),
+        new Vector3(1, 0, 0),
+        new Vector3(-1, 0, 0),
+        new Vector3(0, 1, 0),
+        new Vector3(0, -1, 0)
+    ];
+
+    var indices = [];
+    var positions = [];
+    var normals = [];
+    var uvs = [];
+
+    var width = options.width || options.size || 1;
+    var height = options.height || options.size || 1;
+    var depth = options.depth || options.size || 1;
+    var sideOrientation = (options.sideOrientation === 0) ? 0 : options.sideOrientation || VertexData.DEFAULTSIDE;
+    var faceUV: Vector4[] = options.faceUV || new Array<Vector4>(6);
+    var faceColors = options.faceColors;
+    var colors = [];
+
+    // default face colors and UV if undefined
+    for (var f = 0; f < 6; f++) {
+        if (faceUV[f] === undefined) {
+            faceUV[f] = new Vector4(0, 0, 1, 1);
+        }
+        if (faceColors && faceColors[f] === undefined) {
+            faceColors[f] = new Color4(1, 1, 1, 1);
+        }
+    }
+
+    var scaleVector = new Vector3(width / 2, height / 2, depth / 2);
+
+    // Create each face in turn.
+    for (var index = 0; index < normalsSource.length; index++) {
+        var normal = normalsSource[index];
+
+        // Get two vectors perpendicular to the face normal and to each other.
+        var side1 = new Vector3(normal.y, normal.z, normal.x);
+        var side2 = Vector3.Cross(normal, side1);
+
+        // Six indices (two triangles) per face.
+        var verticesLength = positions.length / 3;
+        indices.push(verticesLength);
+        indices.push(verticesLength + 1);
+        indices.push(verticesLength + 2);
+
+        indices.push(verticesLength);
+        indices.push(verticesLength + 2);
+        indices.push(verticesLength + 3);
+
+        // Four vertices per face.
+        var vertex = normal.subtract(side1).subtract(side2).multiply(scaleVector);
+        positions.push(vertex.x, vertex.y, vertex.z);
+        normals.push(normal.x, normal.y, normal.z);
+        uvs.push(faceUV[index].z, faceUV[index].w);
+        if (faceColors) {
+            colors.push(faceColors[index].r, faceColors[index].g, faceColors[index].b, faceColors[index].a);
+        }
+
+        vertex = normal.subtract(side1).add(side2).multiply(scaleVector);
+        positions.push(vertex.x, vertex.y, vertex.z);
+        normals.push(normal.x, normal.y, normal.z);
+        uvs.push(faceUV[index].x, faceUV[index].w);
+        if (faceColors) {
+            colors.push(faceColors[index].r, faceColors[index].g, faceColors[index].b, faceColors[index].a);
+        }
+
+        vertex = normal.add(side1).add(side2).multiply(scaleVector);
+        positions.push(vertex.x, vertex.y, vertex.z);
+        normals.push(normal.x, normal.y, normal.z);
+        uvs.push(faceUV[index].x, faceUV[index].y);
+        if (faceColors) {
+            colors.push(faceColors[index].r, faceColors[index].g, faceColors[index].b, faceColors[index].a);
+        }
+
+        vertex = normal.add(side1).subtract(side2).multiply(scaleVector);
+        positions.push(vertex.x, vertex.y, vertex.z);
+        normals.push(normal.x, normal.y, normal.z);
+        uvs.push(faceUV[index].z, faceUV[index].y);
+        if (faceColors) {
+            colors.push(faceColors[index].r, faceColors[index].g, faceColors[index].b, faceColors[index].a);
+        }
+    }
+
+    // sides
+    VertexData._ComputeSides(sideOrientation, positions, indices, normals, uvs, options.frontUVs, options.backUVs);
+
+    // Result
+    var vertexData = new VertexData();
+
+    vertexData.indices = indices;
+    vertexData.positions = positions;
+    vertexData.normals = normals;
+    vertexData.uvs = uvs;
+
+    if (faceColors) {
+        var totalColors = (sideOrientation === VertexData.DOUBLESIDE) ? colors.concat(colors) : colors;
+        vertexData.colors = totalColors;
+    }
+
+    return vertexData;
+};
+
+Mesh.CreateBox = (name: string, size: number, scene: Nullable<Scene> = null, updatable?: boolean, sideOrientation?: number): Mesh => {
+    var options = {
+        size: size,
+        sideOrientation: sideOrientation,
+        updatable: updatable
+    };
+
+    return BoxBuilder.CreateBox(name, options, scene);
+};
+
+/**
+ * Class containing static functions to help procedurally build meshes
+ */
+export class BoxBuilder {
+    /**
+     * Creates a box mesh
+     * * The parameter `size` sets the size (float) of each box side (default 1)
+     * * You can set some different box dimensions by using the parameters `width`, `height` and `depth` (all by default have the same value of `size`)
+     * * You can set different colors and different images to each box side by using the parameters `faceColors` (an array of 6 Color3 elements) and `faceUV` (an array of 6 Vector4 elements)
+     * * Please read this tutorial : https://doc.babylonjs.com/how_to/createbox_per_face_textures_and_colors
+     * * You can also set the mesh side orientation with the values : BABYLON.Mesh.FRONTSIDE (default), BABYLON.Mesh.BACKSIDE or BABYLON.Mesh.DOUBLESIDE
+     * * If you create a double-sided mesh, you can choose what parts of the texture image to crop and stick respectively on the front and the back sides with the parameters `frontUVs` and `backUVs` (Vector4). Detail here : https://doc.babylonjs.com/babylon101/discover_basic_elements#side-orientation
+     * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created
+     * @see https://doc.babylonjs.com/how_to/set_shapes#box
+     * @param name defines the name of the mesh
+     * @param options defines the options used to create the mesh
+     * @param scene defines the hosting scene
+     * @returns the box mesh
+     */
+    public static CreateBox(name: string, options: { size?: number, width?: number, height?: number, depth?: number, faceUV?: Vector4[], faceColors?: Color4[], sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4, updatable?: boolean }, scene: Nullable<Scene> = null): Mesh {
+        var box = new Mesh(name, scene);
+
+        options.sideOrientation = Mesh._GetDefaultSideOrientation(options.sideOrientation);
+        box._originalBuilderSideOrientation = options.sideOrientation;
+
+        var vertexData = VertexData.CreateBox(options);
+
+        vertexData.applyToMesh(box, options.updatable);
+
+        return box;
+    }
+}

+ 324 - 0
src/Meshes/Builders/cylinderBuilder.ts

@@ -0,0 +1,324 @@
+import { Color4, Vector4, Vector3, Axis, Vector2 } from "../../Maths/math";
+import { Mesh, _CreationDataStorage } from "../mesh";
+import { VertexData } from "../mesh.vertexData";
+import { Scene } from "../../scene";
+import { Nullable } from "../../types";
+
+VertexData.CreateCylinder = function(options: { height?: number, diameterTop?: number, diameterBottom?: number, diameter?: number, tessellation?: number, subdivisions?: number, arc?: number, faceColors?: Color4[], faceUV?: Vector4[], hasRings?: boolean, enclose?: boolean, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4 }): VertexData {
+    var height: number = options.height || 2;
+    var diameterTop: number = (options.diameterTop === 0) ? 0 : options.diameterTop || options.diameter || 1;
+    var diameterBottom: number = (options.diameterBottom === 0) ? 0 : options.diameterBottom || options.diameter || 1;
+    var tessellation: number = options.tessellation || 24;
+    var subdivisions: number = options.subdivisions || 1;
+    var hasRings: boolean = options.hasRings ? true : false;
+    var enclose: boolean = options.enclose ? true : false;
+    var arc: number = options.arc && (options.arc <= 0 || options.arc > 1) ? 1.0 : options.arc || 1.0;
+    var sideOrientation: number = (options.sideOrientation === 0) ? 0 : options.sideOrientation || VertexData.DEFAULTSIDE;
+    var faceUV: Vector4[] = options.faceUV || new Array<Vector4>(3);
+    var faceColors = options.faceColors;
+    // default face colors and UV if undefined
+    var quadNb: number = (arc !== 1 && enclose) ? 2 : 0;
+    var ringNb: number = (hasRings) ? subdivisions : 1;
+    var surfaceNb: number = 2 + (1 + quadNb) * ringNb;
+    var f: number;
+
+    for (f = 0; f < surfaceNb; f++) {
+        if (faceColors && faceColors[f] === undefined) {
+            faceColors[f] = new Color4(1, 1, 1, 1);
+        }
+    }
+    for (f = 0; f < surfaceNb; f++) {
+        if (faceUV && faceUV[f] === undefined) {
+            faceUV[f] = new Vector4(0, 0, 1, 1);
+        }
+    }
+
+    var indices = new Array<number>();
+    var positions = new Array<number>();
+    var normals = new Array<number>();
+    var uvs = new Array<number>();
+    var colors = new Array<number>();
+
+    var angle_step = Math.PI * 2 * arc / tessellation;
+    var angle: number;
+    var h: number;
+    var radius: number;
+    var tan = (diameterBottom - diameterTop) / 2 / height;
+    var ringVertex: Vector3 = Vector3.Zero();
+    var ringNormal: Vector3 = Vector3.Zero();
+    var ringFirstVertex: Vector3 = Vector3.Zero();
+    var ringFirstNormal: Vector3 = Vector3.Zero();
+    var quadNormal: Vector3 = Vector3.Zero();
+    var Y: Vector3 = Axis.Y;
+
+    // positions, normals, uvs
+    var i: number;
+    var j: number;
+    var r: number;
+    var ringIdx: number = 1;
+    var s: number = 1;      // surface index
+    var cs: number = 0;
+    var v: number = 0;
+
+    for (i = 0; i <= subdivisions; i++) {
+        h = i / subdivisions;
+        radius = (h * (diameterTop - diameterBottom) + diameterBottom) / 2;
+        ringIdx = (hasRings && i !== 0 && i !== subdivisions) ? 2 : 1;
+        for (r = 0; r < ringIdx; r++) {
+            if (hasRings) {
+                s += r;
+            }
+            if (enclose) {
+                s += 2 * r;
+            }
+            for (j = 0; j <= tessellation; j++) {
+                angle = j * angle_step;
+
+                // position
+                ringVertex.x = Math.cos(-angle) * radius;
+                ringVertex.y = -height / 2 + h * height;
+                ringVertex.z = Math.sin(-angle) * radius;
+
+                // normal
+                if (diameterTop === 0 && i === subdivisions) {
+                    // if no top cap, reuse former normals
+                    ringNormal.x = normals[normals.length - (tessellation + 1) * 3];
+                    ringNormal.y = normals[normals.length - (tessellation + 1) * 3 + 1];
+                    ringNormal.z = normals[normals.length - (tessellation + 1) * 3 + 2];
+                }
+                else {
+                    ringNormal.x = ringVertex.x;
+                    ringNormal.z = ringVertex.z;
+                    ringNormal.y = Math.sqrt(ringNormal.x * ringNormal.x + ringNormal.z * ringNormal.z) * tan;
+                    ringNormal.normalize();
+                }
+
+                // keep first ring vertex values for enclose
+                if (j === 0) {
+                    ringFirstVertex.copyFrom(ringVertex);
+                    ringFirstNormal.copyFrom(ringNormal);
+                }
+
+                positions.push(ringVertex.x, ringVertex.y, ringVertex.z);
+                normals.push(ringNormal.x, ringNormal.y, ringNormal.z);
+                if (hasRings) {
+                    v = (cs !== s) ? faceUV[s].y : faceUV[s].w;
+                } else {
+                    v = faceUV[s].y + (faceUV[s].w - faceUV[s].y) * h;
+                }
+                uvs.push(faceUV[s].x + (faceUV[s].z - faceUV[s].x) * j / tessellation, v);
+                if (faceColors) {
+                    colors.push(faceColors[s].r, faceColors[s].g, faceColors[s].b, faceColors[s].a);
+                }
+            }
+
+            // if enclose, add four vertices and their dedicated normals
+            if (arc !== 1 && enclose) {
+                positions.push(ringVertex.x, ringVertex.y, ringVertex.z);
+                positions.push(0, ringVertex.y, 0);
+                positions.push(0, ringVertex.y, 0);
+                positions.push(ringFirstVertex.x, ringFirstVertex.y, ringFirstVertex.z);
+                Vector3.CrossToRef(Y, ringNormal, quadNormal);
+                quadNormal.normalize();
+                normals.push(quadNormal.x, quadNormal.y, quadNormal.z, quadNormal.x, quadNormal.y, quadNormal.z);
+                Vector3.CrossToRef(ringFirstNormal, Y, quadNormal);
+                quadNormal.normalize();
+                normals.push(quadNormal.x, quadNormal.y, quadNormal.z, quadNormal.x, quadNormal.y, quadNormal.z);
+                if (hasRings) {
+                    v = (cs !== s) ? faceUV[s + 1].y : faceUV[s + 1].w;
+                } else {
+                    v = faceUV[s + 1].y + (faceUV[s + 1].w - faceUV[s + 1].y) * h;
+                }
+                uvs.push(faceUV[s + 1].x, v);
+                uvs.push(faceUV[s + 1].z, v);
+                if (hasRings) {
+                    v = (cs !== s) ? faceUV[s + 2].y : faceUV[s + 2].w;
+                } else {
+                    v = faceUV[s + 2].y + (faceUV[s + 2].w - faceUV[s + 2].y) * h;
+                }
+                uvs.push(faceUV[s + 2].x, v);
+                uvs.push(faceUV[s + 2].z, v);
+                if (faceColors) {
+                    colors.push(faceColors[s + 1].r, faceColors[s + 1].g, faceColors[s + 1].b, faceColors[s + 1].a);
+                    colors.push(faceColors[s + 1].r, faceColors[s + 1].g, faceColors[s + 1].b, faceColors[s + 1].a);
+                    colors.push(faceColors[s + 2].r, faceColors[s + 2].g, faceColors[s + 2].b, faceColors[s + 2].a);
+                    colors.push(faceColors[s + 2].r, faceColors[s + 2].g, faceColors[s + 2].b, faceColors[s + 2].a);
+                }
+            }
+            if (cs !== s) {
+                cs = s;
+            }
+
+        }
+
+    }
+
+    // indices
+    var e: number = (arc !== 1 && enclose) ? tessellation + 4 : tessellation;     // correction of number of iteration if enclose
+    var s: number;
+    i = 0;
+    for (s = 0; s < subdivisions; s++) {
+        let i0: number = 0;
+        let i1: number = 0;
+        let i2: number = 0;
+        let i3: number = 0;
+        for (j = 0; j < tessellation; j++) {
+            i0 = i * (e + 1) + j;
+            i1 = (i + 1) * (e + 1) + j;
+            i2 = i * (e + 1) + (j + 1);
+            i3 = (i + 1) * (e + 1) + (j + 1);
+            indices.push(i0, i1, i2);
+            indices.push(i3, i2, i1);
+        }
+        if (arc !== 1 && enclose) {      // if enclose, add two quads
+            indices.push(i0 + 2, i1 + 2, i2 + 2);
+            indices.push(i3 + 2, i2 + 2, i1 + 2);
+            indices.push(i0 + 4, i1 + 4, i2 + 4);
+            indices.push(i3 + 4, i2 + 4, i1 + 4);
+        }
+        i = (hasRings) ? (i + 2) : (i + 1);
+    }
+
+    // Caps
+    var createCylinderCap = (isTop: boolean) => {
+        var radius = isTop ? diameterTop / 2 : diameterBottom / 2;
+        if (radius === 0) {
+            return;
+        }
+
+        // Cap positions, normals & uvs
+        var angle;
+        var circleVector;
+        var i: number;
+        var u: Vector4 = (isTop) ? faceUV[surfaceNb - 1] : faceUV[0];
+        var c: Nullable<Color4> = null;
+        if (faceColors) {
+            c = (isTop) ? faceColors[surfaceNb - 1] : faceColors[0];
+        }
+        // cap center
+        var vbase = positions.length / 3;
+        var offset = isTop ? height / 2 : -height / 2;
+        var center = new Vector3(0, offset, 0);
+        positions.push(center.x, center.y, center.z);
+        normals.push(0, isTop ? 1 : -1, 0);
+        uvs.push(u.x + (u.z - u.x) * 0.5, u.y + (u.w - u.y) * 0.5);
+        if (c) {
+            colors.push(c.r, c.g, c.b, c.a);
+        }
+
+        var textureScale = new Vector2(0.5, 0.5);
+        for (i = 0; i <= tessellation; i++) {
+            angle = Math.PI * 2 * i * arc / tessellation;
+            var cos = Math.cos(-angle);
+            var sin = Math.sin(-angle);
+            circleVector = new Vector3(cos * radius, offset, sin * radius);
+            var textureCoordinate = new Vector2(cos * textureScale.x + 0.5, sin * textureScale.y + 0.5);
+            positions.push(circleVector.x, circleVector.y, circleVector.z);
+            normals.push(0, isTop ? 1 : -1, 0);
+            uvs.push(u.x + (u.z - u.x) * textureCoordinate.x, u.y + (u.w - u.y) * textureCoordinate.y);
+            if (c) {
+                colors.push(c.r, c.g, c.b, c.a);
+            }
+        }
+        // Cap indices
+        for (i = 0; i < tessellation; i++) {
+            if (!isTop) {
+                indices.push(vbase);
+                indices.push(vbase + (i + 1));
+                indices.push(vbase + (i + 2));
+            }
+            else {
+                indices.push(vbase);
+                indices.push(vbase + (i + 2));
+                indices.push(vbase + (i + 1));
+            }
+        }
+    };
+
+    // add caps to geometry
+    createCylinderCap(false);
+    createCylinderCap(true);
+
+    // Sides
+    VertexData._ComputeSides(sideOrientation, positions, indices, normals, uvs, options.frontUVs, options.backUVs);
+
+    var vertexData = new VertexData();
+
+    vertexData.indices = indices;
+    vertexData.positions = positions;
+    vertexData.normals = normals;
+    vertexData.uvs = uvs;
+    if (faceColors) {
+        vertexData.colors = colors;
+    }
+
+    return vertexData;
+};
+
+Mesh.CreateCylinder = (name: string, height: number, diameterTop: number, diameterBottom: number, tessellation: number, subdivisions: any, scene?: Scene, updatable?: any, sideOrientation?: number): Mesh => {
+    if (scene === undefined || !(scene instanceof Scene)) {
+        if (scene !== undefined) {
+            sideOrientation = updatable || Mesh.DEFAULTSIDE;
+            updatable = scene;
+        }
+        scene = <Scene>subdivisions;
+        subdivisions = 1;
+    }
+
+    var options = {
+        height: height,
+        diameterTop: diameterTop,
+        diameterBottom: diameterBottom,
+        tessellation: tessellation,
+        subdivisions: subdivisions,
+        sideOrientation: sideOrientation,
+        updatable: updatable
+    };
+
+    return CylinderBuilder.CreateCylinder(name, options, scene);
+};
+
+/**
+ * Class containing static functions to help procedurally build meshes
+ */
+export class CylinderBuilder {
+    /**
+     * Creates a cylinder or a cone mesh
+     * * The parameter `height` sets the height size (float) of the cylinder/cone (float, default 2).
+     * * The parameter `diameter` sets the diameter of the top and bottom cap at once (float, default 1).
+     * * The parameters `diameterTop` and `diameterBottom` overwrite the parameter `diameter` and set respectively the top cap and bottom cap diameter (floats, default 1). The parameter "diameterBottom" can't be zero.
+     * * The parameter `tessellation` sets the number of cylinder sides (positive integer, default 24). Set it to 3 to get a prism for instance.
+     * * The parameter `subdivisions` sets the number of rings along the cylinder height (positive integer, default 1).
+     * * The parameter `hasRings` (boolean, default false) makes the subdivisions independent from each other, so they become different faces.
+     * * The parameter `enclose`  (boolean, default false) adds two extra faces per subdivision to a sliced cylinder to close it around its height axis.
+     * * The parameter `arc` (float, default 1) is the ratio (max 1) to apply to the circumference to slice the cylinder.
+     * * You can set different colors and different images to each box side by using the parameters `faceColors` (an array of n Color3 elements) and `faceUV` (an array of n Vector4 elements).
+     * * The value of n is the number of cylinder faces. If the cylinder has only 1 subdivisions, n equals : top face + cylinder surface + bottom face = 3
+     * * Now, if the cylinder has 5 independent subdivisions (hasRings = true), n equals : top face + 5 stripe surfaces + bottom face = 2 + 5 = 7
+     * * Finally, if the cylinder has 5 independent subdivisions and is enclose, n equals : top face + 5 x (stripe surface + 2 closing faces) + bottom face = 2 + 5 * 3 = 17
+     * * Each array (color or UVs) is always ordered the same way : the first element is the bottom cap, the last element is the top cap. The other elements are each a ring surface.
+     * * If `enclose` is false, a ring surface is one element.
+     * * If `enclose` is true, a ring surface is 3 successive elements in the array : the tubular surface, then the two closing faces.
+     * * Example how to set colors and textures on a sliced cylinder : https://www.html5gamedevs.com/topic/17945-creating-a-closed-slice-of-a-cylinder/#comment-106379
+     * * You can also set the mesh side orientation with the values : BABYLON.Mesh.FRONTSIDE (default), BABYLON.Mesh.BACKSIDE or BABYLON.Mesh.DOUBLESIDE
+     * * If you create a double-sided mesh, you can choose what parts of the texture image to crop and stick respectively on the front and the back sides with the parameters `frontUVs` and `backUVs` (Vector4). Detail here : https://doc.babylonjs.com/babylon101/discover_basic_elements#side-orientation
+     * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created.
+     * @param name defines the name of the mesh
+     * @param options defines the options used to create the mesh
+     * @param scene defines the hosting scene
+     * @returns the cylinder mesh
+     * @see https://doc.babylonjs.com/how_to/set_shapes#cylinder-or-cone
+     */
+    public static CreateCylinder(name: string, options: { height?: number, diameterTop?: number, diameterBottom?: number, diameter?: number, tessellation?: number, subdivisions?: number, arc?: number, faceColors?: Color4[], faceUV?: Vector4[], updatable?: boolean, hasRings?: boolean, enclose?: boolean, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4 }, scene: any): Mesh {
+        var cylinder = new Mesh(name, scene);
+
+        options.sideOrientation = Mesh._GetDefaultSideOrientation(options.sideOrientation);
+        cylinder._originalBuilderSideOrientation = options.sideOrientation;
+
+        var vertexData = VertexData.CreateCylinder(options);
+
+        vertexData.applyToMesh(cylinder, options.updatable);
+
+        return cylinder;
+    }
+}

+ 252 - 0
src/Meshes/Builders/decalBuilder.ts

@@ -0,0 +1,252 @@
+import { Nullable, IndicesArray } from "../../types";
+import { Vector3, Matrix, PositionNormalVertex } from "../../Maths/math";
+import { Mesh, _CreationDataStorage } from "../mesh";
+import { VertexBuffer } from "../buffer";
+import { VertexData } from "../mesh.vertexData";
+import { AbstractMesh } from "../abstractMesh";
+import { Camera } from "../../Cameras/camera";
+
+Mesh.CreateDecal = (name: string, sourceMesh: AbstractMesh, position: Vector3, normal: Vector3, size: Vector3, angle: number): Mesh => {
+    var options = {
+        position: position,
+        normal: normal,
+        size: size,
+        angle: angle
+    };
+
+    return DecalBuilder.CreateDecal(name, sourceMesh, options);
+};
+
+/**
+ * Class containing static functions to help procedurally build meshes
+ */
+export class DecalBuilder {
+    /**
+     * Creates a decal mesh.
+     * A decal is a mesh usually applied as a model onto the surface of another mesh. So don't forget the parameter `sourceMesh` depicting the decal
+     * * The parameter `position` (Vector3, default `(0, 0, 0)`) sets the position of the decal in World coordinates
+     * * The parameter `normal` (Vector3, default `Vector3.Up`) sets the normal of the mesh where the decal is applied onto in World coordinates
+     * * The parameter `size` (Vector3, default `(1, 1, 1)`) sets the decal scaling
+     * * The parameter `angle` (float in radian, default 0) sets the angle to rotate the decal
+     * @param name defines the name of the mesh
+     * @param sourceMesh defines the mesh where the decal must be applied
+     * @param options defines the options used to create the mesh
+     * @param scene defines the hosting scene
+     * @returns the decal mesh
+     * @see https://doc.babylonjs.com/how_to/decals
+     */
+    public static CreateDecal(name: string, sourceMesh: AbstractMesh, options: { position?: Vector3, normal?: Vector3, size?: Vector3, angle?: number }): Mesh {
+        var indices = <IndicesArray>sourceMesh.getIndices();
+        var positions = sourceMesh.getVerticesData(VertexBuffer.PositionKind);
+        var normals = sourceMesh.getVerticesData(VertexBuffer.NormalKind);
+        var position = options.position || Vector3.Zero();
+        var normal = options.normal || Vector3.Up();
+        var size = options.size || Vector3.One();
+        var angle = options.angle || 0;
+
+        // Getting correct rotation
+        if (!normal) {
+            var target = new Vector3(0, 0, 1);
+            var camera = <Camera>sourceMesh.getScene().activeCamera;
+            var cameraWorldTarget = Vector3.TransformCoordinates(target, camera.getWorldMatrix());
+
+            normal = camera.globalPosition.subtract(cameraWorldTarget);
+        }
+
+        var yaw = -Math.atan2(normal.z, normal.x) - Math.PI / 2;
+        var len = Math.sqrt(normal.x * normal.x + normal.z * normal.z);
+        var pitch = Math.atan2(normal.y, len);
+
+        // Matrix
+        var decalWorldMatrix = Matrix.RotationYawPitchRoll(yaw, pitch, angle).multiply(Matrix.Translation(position.x, position.y, position.z));
+        var inverseDecalWorldMatrix = Matrix.Invert(decalWorldMatrix);
+        var meshWorldMatrix = sourceMesh.getWorldMatrix();
+        var transformMatrix = meshWorldMatrix.multiply(inverseDecalWorldMatrix);
+
+        var vertexData = new VertexData();
+        vertexData.indices = [];
+        vertexData.positions = [];
+        vertexData.normals = [];
+        vertexData.uvs = [];
+
+        var currentVertexDataIndex = 0;
+
+        var extractDecalVector3 = (indexId: number): PositionNormalVertex => {
+            var result = new PositionNormalVertex();
+            if (!indices || !positions || !normals) {
+                return result;
+            }
+
+            var vertexId = indices[indexId];
+            result.position = new Vector3(positions[vertexId * 3], positions[vertexId * 3 + 1], positions[vertexId * 3 + 2]);
+
+            // Send vector to decal local world
+            result.position = Vector3.TransformCoordinates(result.position, transformMatrix);
+
+            // Get normal
+            result.normal = new Vector3(normals[vertexId * 3], normals[vertexId * 3 + 1], normals[vertexId * 3 + 2]);
+            result.normal = Vector3.TransformNormal(result.normal, transformMatrix);
+
+            return result;
+        }; // Inspired by https://github.com/mrdoob/three.js/blob/eee231960882f6f3b6113405f524956145148146/examples/js/geometries/DecalGeometry.js
+        var clip = (vertices: PositionNormalVertex[], axis: Vector3): PositionNormalVertex[] => {
+            if (vertices.length === 0) {
+                return vertices;
+            }
+
+            var clipSize = 0.5 * Math.abs(Vector3.Dot(size, axis));
+
+            var clipVertices = (v0: PositionNormalVertex, v1: PositionNormalVertex): PositionNormalVertex => {
+                var clipFactor = Vector3.GetClipFactor(v0.position, v1.position, axis, clipSize);
+
+                return new PositionNormalVertex(
+                    Vector3.Lerp(v0.position, v1.position, clipFactor),
+                    Vector3.Lerp(v0.normal, v1.normal, clipFactor)
+                );
+            };
+            var result = new Array<PositionNormalVertex>();
+
+            for (var index = 0; index < vertices.length; index += 3) {
+                var v1Out: boolean;
+                var v2Out: boolean;
+                var v3Out: boolean;
+                var total = 0;
+                let nV1: Nullable<PositionNormalVertex> = null;
+                let nV2: Nullable<PositionNormalVertex> = null;
+                let nV3: Nullable<PositionNormalVertex> = null;
+                let nV4: Nullable<PositionNormalVertex> = null;
+
+                var d1 = Vector3.Dot(vertices[index].position, axis) - clipSize;
+                var d2 = Vector3.Dot(vertices[index + 1].position, axis) - clipSize;
+                var d3 = Vector3.Dot(vertices[index + 2].position, axis) - clipSize;
+
+                v1Out = d1 > 0;
+                v2Out = d2 > 0;
+                v3Out = d3 > 0;
+
+                total = (v1Out ? 1 : 0) + (v2Out ? 1 : 0) + (v3Out ? 1 : 0);
+
+                switch (total) {
+                    case 0:
+                        result.push(vertices[index]);
+                        result.push(vertices[index + 1]);
+                        result.push(vertices[index + 2]);
+                        break;
+                    case 1:
+
+                        if (v1Out) {
+                            nV1 = vertices[index + 1];
+                            nV2 = vertices[index + 2];
+                            nV3 = clipVertices(vertices[index], nV1);
+                            nV4 = clipVertices(vertices[index], nV2);
+                        }
+
+                        if (v2Out) {
+                            nV1 = vertices[index];
+                            nV2 = vertices[index + 2];
+                            nV3 = clipVertices(vertices[index + 1], nV1);
+                            nV4 = clipVertices(vertices[index + 1], nV2);
+
+                            result.push(nV3);
+                            result.push(nV2.clone());
+                            result.push(nV1.clone());
+
+                            result.push(nV2.clone());
+                            result.push(nV3.clone());
+                            result.push(nV4);
+                            break;
+                        }
+                        if (v3Out) {
+                            nV1 = vertices[index];
+                            nV2 = vertices[index + 1];
+                            nV3 = clipVertices(vertices[index + 2], nV1);
+                            nV4 = clipVertices(vertices[index + 2], nV2);
+                        }
+
+                        if (nV1 && nV2 && nV3 && nV4) {
+                            result.push(nV1.clone());
+                            result.push(nV2.clone());
+                            result.push(nV3);
+
+                            result.push(nV4);
+                            result.push(nV3.clone());
+                            result.push(nV2.clone());
+                        }
+                        break;
+                    case 2:
+                        if (!v1Out) {
+                            nV1 = vertices[index].clone();
+                            nV2 = clipVertices(nV1, vertices[index + 1]);
+                            nV3 = clipVertices(nV1, vertices[index + 2]);
+                            result.push(nV1);
+                            result.push(nV2);
+                            result.push(nV3);
+                        }
+                        if (!v2Out) {
+                            nV1 = vertices[index + 1].clone();
+                            nV2 = clipVertices(nV1, vertices[index + 2]);
+                            nV3 = clipVertices(nV1, vertices[index]);
+                            result.push(nV1);
+                            result.push(nV2);
+                            result.push(nV3);
+                        }
+                        if (!v3Out) {
+                            nV1 = vertices[index + 2].clone();
+                            nV2 = clipVertices(nV1, vertices[index]);
+                            nV3 = clipVertices(nV1, vertices[index + 1]);
+                            result.push(nV1);
+                            result.push(nV2);
+                            result.push(nV3);
+                        }
+                        break;
+                    case 3:
+                        break;
+                }
+            }
+
+            return result;
+        };
+        for (var index = 0; index < indices.length; index += 3) {
+            var faceVertices = new Array<PositionNormalVertex>();
+
+            faceVertices.push(extractDecalVector3(index));
+            faceVertices.push(extractDecalVector3(index + 1));
+            faceVertices.push(extractDecalVector3(index + 2));
+
+            // Clip
+            faceVertices = clip(faceVertices, new Vector3(1, 0, 0));
+            faceVertices = clip(faceVertices, new Vector3(-1, 0, 0));
+            faceVertices = clip(faceVertices, new Vector3(0, 1, 0));
+            faceVertices = clip(faceVertices, new Vector3(0, -1, 0));
+            faceVertices = clip(faceVertices, new Vector3(0, 0, 1));
+            faceVertices = clip(faceVertices, new Vector3(0, 0, -1));
+
+            if (faceVertices.length === 0) {
+                continue;
+            }
+
+            // Add UVs and get back to world
+            for (var vIndex = 0; vIndex < faceVertices.length; vIndex++) {
+                var vertex = faceVertices[vIndex];
+
+                //TODO check for Int32Array | Uint32Array | Uint16Array
+                (<number[]>vertexData.indices).push(currentVertexDataIndex);
+                vertex.position.toArray(vertexData.positions, currentVertexDataIndex * 3);
+                vertex.normal.toArray(vertexData.normals, currentVertexDataIndex * 3);
+                (<number[]>vertexData.uvs).push(0.5 + vertex.position.x / size.x);
+                (<number[]>vertexData.uvs).push(0.5 + vertex.position.y / size.y);
+
+                currentVertexDataIndex++;
+            }
+        }
+
+        // Return mesh
+        var decal = new Mesh(name, sourceMesh.getScene());
+        vertexData.applyToMesh(decal);
+
+        decal.position = position.clone();
+        decal.rotation = new Vector3(pitch, yaw, angle);
+
+        return decal;
+    }
+}

+ 98 - 0
src/Meshes/Builders/discBuilder.ts

@@ -0,0 +1,98 @@
+import { Nullable } from "../../types";
+import { Scene } from "../../scene";
+import { Vector4 } from "../../Maths/math";
+import { Mesh, _CreationDataStorage } from "../mesh";
+import { VertexData } from "../mesh.vertexData";
+
+VertexData.CreateDisc = function(options: { radius?: number, tessellation?: number, arc?: number, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4 }): VertexData {
+    var positions = new Array<number>();
+    var indices = new Array<number>();
+    var normals = new Array<number>();
+    var uvs = new Array<number>();
+
+    var radius = options.radius || 0.5;
+    var tessellation = options.tessellation || 64;
+    var arc: number = options.arc && (options.arc <= 0 || options.arc > 1) ? 1.0 : options.arc || 1.0;
+    var sideOrientation = (options.sideOrientation === 0) ? 0 : options.sideOrientation || VertexData.DEFAULTSIDE;
+
+    // positions and uvs
+    positions.push(0, 0, 0);    // disc center first
+    uvs.push(0.5, 0.5);
+
+    var theta = Math.PI * 2 * arc;
+    var step = theta / tessellation;
+    for (var a = 0; a < theta; a += step) {
+        var x = Math.cos(a);
+        var y = Math.sin(a);
+        var u = (x + 1) / 2;
+        var v = (1 - y) / 2;
+        positions.push(radius * x, radius * y, 0);
+        uvs.push(u, v);
+    }
+    if (arc === 1) {
+        positions.push(positions[3], positions[4], positions[5]); // close the circle
+        uvs.push(uvs[2], uvs[3]);
+    }
+
+    //indices
+    var vertexNb = positions.length / 3;
+    for (var i = 1; i < vertexNb - 1; i++) {
+        indices.push(i + 1, 0, i);
+    }
+
+    // result
+    VertexData.ComputeNormals(positions, indices, normals);
+    VertexData._ComputeSides(sideOrientation, positions, indices, normals, uvs, options.frontUVs, options.backUVs);
+
+    var vertexData = new VertexData();
+
+    vertexData.indices = indices;
+    vertexData.positions = positions;
+    vertexData.normals = normals;
+    vertexData.uvs = uvs;
+
+    return vertexData;
+};
+
+Mesh.CreateDisc = (name: string, radius: number, tessellation: number, scene: Nullable<Scene> = null, updatable?: boolean, sideOrientation?: number): Mesh => {
+    var options = {
+        radius: radius,
+        tessellation: tessellation,
+        sideOrientation: sideOrientation,
+        updatable: updatable
+    };
+
+    return DiscBuilder.CreateDisc(name, options, scene);
+};
+
+/**
+ * Class containing static functions to help procedurally build meshes
+ */
+export class DiscBuilder {
+    /**
+     * Creates a plane polygonal mesh.  By default, this is a disc
+     * * The parameter `radius` sets the radius size (float) of the polygon (default 0.5)
+     * * The parameter `tessellation` sets the number of polygon sides (positive integer, default 64). So a tessellation valued to 3 will build a triangle, to 4 a square, etc
+     * * You can create an unclosed polygon with the parameter `arc` (positive float, default 1), valued between 0 and 1, what is the ratio of the circumference : 2 x PI x ratio
+     * * You can also set the mesh side orientation with the values : BABYLON.Mesh.FRONTSIDE (default), BABYLON.Mesh.BACKSIDE or BABYLON.Mesh.DOUBLESIDE
+     * * If you create a double-sided mesh, you can choose what parts of the texture image to crop and stick respectively on the front and the back sides with the parameters `frontUVs` and `backUVs` (Vector4). Detail here : https://doc.babylonjs.com/babylon101/discover_basic_elements#side-orientation
+     * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created
+     * @param name defines the name of the mesh
+     * @param options defines the options used to create the mesh
+     * @param scene defines the hosting scene
+     * @returns the plane polygonal mesh
+     * @see https://doc.babylonjs.com/how_to/set_shapes#disc-or-regular-polygon
+     */
+    public static CreateDisc(name: string, options: { radius?: number, tessellation?: number, arc?: number, updatable?: boolean, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4 }, scene: Nullable<Scene> = null): Mesh {
+        var disc = new Mesh(name, scene);
+
+        options.sideOrientation = Mesh._GetDefaultSideOrientation(options.sideOrientation);
+        disc._originalBuilderSideOrientation = options.sideOrientation;
+
+        var vertexData = VertexData.CreateDisc(options);
+
+        vertexData.applyToMesh(disc, options.updatable);
+
+        return disc;
+    }
+}

+ 410 - 0
src/Meshes/Builders/groundBuilder.ts

@@ -0,0 +1,410 @@
+import { Scene } from "../../scene";
+import { Color3, Vector3, Epsilon } from "../../Maths/math";
+import { Mesh, _CreationDataStorage } from "../mesh";
+import { VertexData } from "../mesh.vertexData";
+import { GroundMesh } from "../groundMesh";
+import { Tools } from "../../Misc/tools";
+
+VertexData.CreateGround = function(options: { width?: number, height?: number, subdivisions?: number, subdivisionsX?: number, subdivisionsY?: number }): VertexData {
+    var indices = [];
+    var positions = [];
+    var normals = [];
+    var uvs = [];
+    var row: number, col: number;
+
+    var width: number = options.width || 1;
+    var height: number = options.height || 1;
+    var subdivisionsX: number = options.subdivisionsX || options.subdivisions || 1;
+    var subdivisionsY: number = options.subdivisionsY || options.subdivisions || 1;
+
+    for (row = 0; row <= subdivisionsY; row++) {
+        for (col = 0; col <= subdivisionsX; col++) {
+            var position = new Vector3((col * width) / subdivisionsX - (width / 2.0), 0, ((subdivisionsY - row) * height) / subdivisionsY - (height / 2.0));
+            var normal = new Vector3(0, 1.0, 0);
+
+            positions.push(position.x, position.y, position.z);
+            normals.push(normal.x, normal.y, normal.z);
+            uvs.push(col / subdivisionsX, 1.0 - row / subdivisionsY);
+        }
+    }
+
+    for (row = 0; row < subdivisionsY; row++) {
+        for (col = 0; col < subdivisionsX; col++) {
+            indices.push(col + 1 + (row + 1) * (subdivisionsX + 1));
+            indices.push(col + 1 + row * (subdivisionsX + 1));
+            indices.push(col + row * (subdivisionsX + 1));
+
+            indices.push(col + (row + 1) * (subdivisionsX + 1));
+            indices.push(col + 1 + (row + 1) * (subdivisionsX + 1));
+            indices.push(col + row * (subdivisionsX + 1));
+        }
+    }
+
+    // Result
+    var vertexData = new VertexData();
+
+    vertexData.indices = indices;
+    vertexData.positions = positions;
+    vertexData.normals = normals;
+    vertexData.uvs = uvs;
+
+    return vertexData;
+};
+
+VertexData.CreateTiledGround = function(options: { xmin: number, zmin: number, xmax: number, zmax: number, subdivisions?: { w: number; h: number; }, precision?: { w: number; h: number; } }): VertexData {
+    var xmin = (options.xmin !== undefined && options.xmin !== null) ? options.xmin : -1.0;
+    var zmin = (options.zmin !== undefined && options.zmin !== null) ? options.zmin : -1.0;
+    var xmax = (options.xmax !== undefined && options.xmax !== null) ? options.xmax : 1.0;
+    var zmax = (options.zmax !== undefined && options.zmax !== null) ? options.zmax : 1.0;
+    var subdivisions = options.subdivisions || { w: 1, h: 1 };
+    var precision = options.precision || { w: 1, h: 1 };
+
+    var indices = new Array<number>();
+    var positions = new Array<number>();
+    var normals = new Array<number>();
+    var uvs = new Array<number>();
+    var row: number, col: number, tileRow: number, tileCol: number;
+
+    subdivisions.h = (subdivisions.h < 1) ? 1 : subdivisions.h;
+    subdivisions.w = (subdivisions.w < 1) ? 1 : subdivisions.w;
+    precision.w = (precision.w < 1) ? 1 : precision.w;
+    precision.h = (precision.h < 1) ? 1 : precision.h;
+
+    var tileSize = {
+        'w': (xmax - xmin) / subdivisions.w,
+        'h': (zmax - zmin) / subdivisions.h
+    };
+
+    function applyTile(xTileMin: number, zTileMin: number, xTileMax: number, zTileMax: number) {
+        // Indices
+        var base = positions.length / 3;
+        var rowLength = precision.w + 1;
+        for (row = 0; row < precision.h; row++) {
+            for (col = 0; col < precision.w; col++) {
+                var square = [
+                    base + col + row * rowLength,
+                    base + (col + 1) + row * rowLength,
+                    base + (col + 1) + (row + 1) * rowLength,
+                    base + col + (row + 1) * rowLength
+                ];
+
+                indices.push(square[1]);
+                indices.push(square[2]);
+                indices.push(square[3]);
+                indices.push(square[0]);
+                indices.push(square[1]);
+                indices.push(square[3]);
+            }
+        }
+
+        // Position, normals and uvs
+        var position = Vector3.Zero();
+        var normal = new Vector3(0, 1.0, 0);
+        for (row = 0; row <= precision.h; row++) {
+            position.z = (row * (zTileMax - zTileMin)) / precision.h + zTileMin;
+            for (col = 0; col <= precision.w; col++) {
+                position.x = (col * (xTileMax - xTileMin)) / precision.w + xTileMin;
+                position.y = 0;
+
+                positions.push(position.x, position.y, position.z);
+                normals.push(normal.x, normal.y, normal.z);
+                uvs.push(col / precision.w, row / precision.h);
+            }
+        }
+    }
+
+    for (tileRow = 0; tileRow < subdivisions.h; tileRow++) {
+        for (tileCol = 0; tileCol < subdivisions.w; tileCol++) {
+            applyTile(
+                xmin + tileCol * tileSize.w,
+                zmin + tileRow * tileSize.h,
+                xmin + (tileCol + 1) * tileSize.w,
+                zmin + (tileRow + 1) * tileSize.h
+            );
+        }
+    }
+
+    // Result
+    var vertexData = new VertexData();
+
+    vertexData.indices = indices;
+    vertexData.positions = positions;
+    vertexData.normals = normals;
+    vertexData.uvs = uvs;
+
+    return vertexData;
+};
+
+VertexData.CreateGroundFromHeightMap = function(options: { width: number, height: number, subdivisions: number, minHeight: number, maxHeight: number, colorFilter: Color3, buffer: Uint8Array, bufferWidth: number, bufferHeight: number, alphaFilter: number }): VertexData {
+    var indices = [];
+    var positions = [];
+    var normals = [];
+    var uvs = [];
+    var row, col;
+    var filter = options.colorFilter || new Color3(0.3, 0.59, 0.11);
+    var alphaFilter = options.alphaFilter || 0.0;
+
+    // Vertices
+    for (row = 0; row <= options.subdivisions; row++) {
+        for (col = 0; col <= options.subdivisions; col++) {
+            var position = new Vector3((col * options.width) / options.subdivisions - (options.width / 2.0), 0, ((options.subdivisions - row) * options.height) / options.subdivisions - (options.height / 2.0));
+
+            // Compute height
+            var heightMapX = (((position.x + options.width / 2) / options.width) * (options.bufferWidth - 1)) | 0;
+            var heightMapY = ((1.0 - (position.z + options.height / 2) / options.height) * (options.bufferHeight - 1)) | 0;
+
+            var pos = (heightMapX + heightMapY * options.bufferWidth) * 4;
+            var r = options.buffer[pos] / 255.0;
+            var g = options.buffer[pos + 1] / 255.0;
+            var b = options.buffer[pos + 2] / 255.0;
+            var a = options.buffer[pos + 3] / 255.0;
+
+            var gradient = r * filter.r + g * filter.g + b * filter.b;
+
+            // If our alpha channel is not within our filter then we will assign a 'special' height
+            // Then when building the indices, we will ignore any vertex that is using the special height
+            if (a >= alphaFilter) {
+                position.y = options.minHeight + (options.maxHeight - options.minHeight) * gradient;
+            }
+            else {
+                position.y = options.minHeight - Epsilon; // We can't have a height below minHeight, normally.
+            }
+
+            // Add  vertex
+            positions.push(position.x, position.y, position.z);
+            normals.push(0, 0, 0);
+            uvs.push(col / options.subdivisions, 1.0 - row / options.subdivisions);
+        }
+    }
+
+    // Indices
+    for (row = 0; row < options.subdivisions; row++) {
+        for (col = 0; col < options.subdivisions; col++) {
+            // Calculate Indices
+            var idx1 = (col + 1 + (row + 1) * (options.subdivisions + 1));
+            var idx2 = (col + 1 + row * (options.subdivisions + 1));
+            var idx3 = (col + row * (options.subdivisions + 1));
+            var idx4 = (col + (row + 1) * (options.subdivisions + 1));
+
+            // Check that all indices are visible (based on our special height)
+            // Only display the vertex if all Indices are visible
+            // Positions are stored x,y,z for each vertex, hence the * 3 and + 1 for height
+            var isVisibleIdx1 = positions[idx1 * 3 + 1] >= options.minHeight;
+            var isVisibleIdx2 = positions[idx2 * 3 + 1] >= options.minHeight;
+            var isVisibleIdx3 = positions[idx3 * 3 + 1] >= options.minHeight;
+            if (isVisibleIdx1 && isVisibleIdx2 && isVisibleIdx3) {
+                indices.push(idx1);
+                indices.push(idx2);
+                indices.push(idx3);
+            }
+
+            var isVisibleIdx4 = positions[idx4 * 3 + 1] >= options.minHeight;
+            if (isVisibleIdx4 && isVisibleIdx1 && isVisibleIdx3) {
+                indices.push(idx4);
+                indices.push(idx1);
+                indices.push(idx3);
+            }
+        }
+    }
+
+    // Normals
+    VertexData.ComputeNormals(positions, indices, normals);
+
+    // Result
+    var vertexData = new VertexData();
+
+    vertexData.indices = indices;
+    vertexData.positions = positions;
+    vertexData.normals = normals;
+    vertexData.uvs = uvs;
+
+    return vertexData;
+};
+
+Mesh.CreateGround = (name: string, width: number, height: number, subdivisions: number, scene?: Scene, updatable?: boolean): Mesh => {
+    var options = {
+        width: width,
+        height: height,
+        subdivisions: subdivisions,
+        updatable: updatable
+    };
+
+    return GroundBuilder.CreateGround(name, options, scene);
+};
+
+Mesh.CreateTiledGround = (name: string, xmin: number, zmin: number, xmax: number, zmax: number, subdivisions: { w: number; h: number; }, precision: { w: number; h: number; }, scene: Scene, updatable?: boolean): Mesh => {
+    var options = {
+        xmin: xmin,
+        zmin: zmin,
+        xmax: xmax,
+        zmax: zmax,
+        subdivisions: subdivisions,
+        precision: precision,
+        updatable: updatable
+    };
+
+    return GroundBuilder.CreateTiledGround(name, options, scene);
+};
+
+Mesh.CreateGroundFromHeightMap = (name: string, url: string, width: number, height: number, subdivisions: number, minHeight: number, maxHeight: number, scene: Scene, updatable?: boolean, onReady?: (mesh: GroundMesh) => void, alphaFilter?: number): GroundMesh => {
+    var options = {
+        width: width,
+        height: height,
+        subdivisions: subdivisions,
+        minHeight: minHeight,
+        maxHeight: maxHeight,
+        updatable: updatable,
+        onReady: onReady,
+        alphaFilter: alphaFilter
+    };
+
+    return GroundBuilder.CreateGroundFromHeightMap(name, url, options, scene);
+};
+
+/**
+ * Class containing static functions to help procedurally build meshes
+ */
+export class GroundBuilder {
+    /**
+     * Creates a ground mesh
+     * * The parameters `width` and `height` (floats, default 1) set the width and height sizes of the ground
+     * * The parameter `subdivisions` (positive integer) sets the number of subdivisions per side
+     * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created
+     * @param name defines the name of the mesh
+     * @param options defines the options used to create the mesh
+     * @param scene defines the hosting scene
+     * @returns the ground mesh
+     * @see https://doc.babylonjs.com/how_to/set_shapes#ground
+     */
+    public static CreateGround(name: string, options: { width?: number, height?: number, subdivisions?: number, subdivisionsX?: number, subdivisionsY?: number, updatable?: boolean }, scene: any): Mesh {
+        var ground = new GroundMesh(name, scene);
+        ground._setReady(false);
+        ground._subdivisionsX = options.subdivisionsX || options.subdivisions || 1;
+        ground._subdivisionsY = options.subdivisionsY || options.subdivisions || 1;
+        ground._width = options.width || 1;
+        ground._height = options.height || 1;
+        ground._maxX = ground._width / 2;
+        ground._maxZ = ground._height / 2;
+        ground._minX = -ground._maxX;
+        ground._minZ = -ground._maxZ;
+
+        var vertexData = VertexData.CreateGround(options);
+
+        vertexData.applyToMesh(ground, options.updatable);
+
+        ground._setReady(true);
+
+        return ground;
+    }
+
+    /**
+     * Creates a tiled ground mesh
+     * * The parameters `xmin` and `xmax` (floats, default -1 and 1) set the ground minimum and maximum X coordinates
+     * * The parameters `zmin` and `zmax` (floats, default -1 and 1) set the ground minimum and maximum Z coordinates
+     * * The parameter `subdivisions` is a javascript object `{w: positive integer, h: positive integer}` (default `{w: 6, h: 6}`). `w` and `h` are the numbers of subdivisions on the ground width and height. Each subdivision is called a tile
+     * * The parameter `precision` is a javascript object `{w: positive integer, h: positive integer}` (default `{w: 2, h: 2}`). `w` and `h` are the numbers of subdivisions on the ground width and height of each tile
+     * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created.
+     * @param name defines the name of the mesh
+     * @param options defines the options used to create the mesh
+     * @param scene defines the hosting scene
+     * @returns the tiled ground mesh
+     * @see https://doc.babylonjs.com/how_to/set_shapes#tiled-ground
+     */
+    public static CreateTiledGround(name: string, options: { xmin: number, zmin: number, xmax: number, zmax: number, subdivisions?: { w: number; h: number; }, precision?: { w: number; h: number; }, updatable?: boolean }, scene: Scene): Mesh {
+        var tiledGround = new Mesh(name, scene);
+
+        var vertexData = VertexData.CreateTiledGround(options);
+
+        vertexData.applyToMesh(tiledGround, options.updatable);
+
+        return tiledGround;
+    }
+
+    /**
+     * Creates a ground mesh from a height map
+     * * The parameter `url` sets the URL of the height map image resource.
+     * * The parameters `width` and `height` (positive floats, default 10) set the ground width and height sizes.
+     * * The parameter `subdivisions` (positive integer, default 1) sets the number of subdivision per side.
+     * * The parameter `minHeight` (float, default 0) is the minimum altitude on the ground.
+     * * The parameter `maxHeight` (float, default 1) is the maximum altitude on the ground.
+     * * The parameter `colorFilter` (optional Color3, default (0.3, 0.59, 0.11) ) is the filter to apply to the image pixel colors to compute the height.
+     * * The parameter `onReady` is a javascript callback function that will be called  once the mesh is just built (the height map download can last some time).
+     * * The parameter `alphaFilter` will filter any data where the alpha channel is below this value, defaults 0 (all data visible)
+     * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created.
+     * @param name defines the name of the mesh
+     * @param url defines the url to the height map
+     * @param options defines the options used to create the mesh
+     * @param scene defines the hosting scene
+     * @returns the ground mesh
+     * @see https://doc.babylonjs.com/babylon101/height_map
+     * @see https://doc.babylonjs.com/how_to/set_shapes#ground-from-a-height-map
+     */
+    public static CreateGroundFromHeightMap(name: string, url: string, options: { width?: number, height?: number, subdivisions?: number, minHeight?: number, maxHeight?: number, colorFilter?: Color3, alphaFilter?: number, updatable?: boolean, onReady?: (mesh: GroundMesh) => void }, scene: Scene): GroundMesh {
+        var width = options.width || 10.0;
+        var height = options.height || 10.0;
+        var subdivisions = options.subdivisions || 1 | 0;
+        var minHeight = options.minHeight || 0.0;
+        var maxHeight = options.maxHeight || 1.0;
+        var filter = options.colorFilter || new Color3(0.3, 0.59, 0.11);
+        var alphaFilter = options.alphaFilter || 0.0;
+        var updatable = options.updatable;
+        var onReady = options.onReady;
+
+        var ground = new GroundMesh(name, scene);
+        ground._subdivisionsX = subdivisions;
+        ground._subdivisionsY = subdivisions;
+        ground._width = width;
+        ground._height = height;
+        ground._maxX = ground._width / 2.0;
+        ground._maxZ = ground._height / 2.0;
+        ground._minX = -ground._maxX;
+        ground._minZ = -ground._maxZ;
+
+        ground._setReady(false);
+
+        var onload = (img: HTMLImageElement) => {
+            // Getting height map data
+            var canvas = document.createElement("canvas");
+            var context = canvas.getContext("2d");
+
+            if (!context) {
+                throw new Error("Unable to get 2d context for CreateGroundFromHeightMap");
+            }
+
+            if (scene.isDisposed) {
+                return;
+            }
+
+            var bufferWidth = img.width;
+            var bufferHeight = img.height;
+            canvas.width = bufferWidth;
+            canvas.height = bufferHeight;
+
+            context.drawImage(img, 0, 0);
+
+            // Create VertexData from map data
+            // Cast is due to wrong definition in lib.d.ts from ts 1.3 - https://github.com/Microsoft/TypeScript/issues/949
+            var buffer = <Uint8Array>(<any>context.getImageData(0, 0, bufferWidth, bufferHeight).data);
+            var vertexData = VertexData.CreateGroundFromHeightMap({
+                width: width, height: height,
+                subdivisions: subdivisions,
+                minHeight: minHeight, maxHeight: maxHeight, colorFilter: filter,
+                buffer: buffer, bufferWidth: bufferWidth, bufferHeight: bufferHeight,
+                alphaFilter: alphaFilter
+            });
+
+            vertexData.applyToMesh(ground, updatable);
+
+            //execute ready callback, if set
+            if (onReady) {
+                onReady(ground);
+            }
+
+            ground._setReady(true);
+        };
+
+        Tools.LoadImage(url, onload, () => { }, scene.offlineProvider);
+
+        return ground;
+    }
+}

+ 298 - 0
src/Meshes/Builders/icoSphereBuilder.ts

@@ -0,0 +1,298 @@
+import { Scene } from "../../scene";
+import { Vector4, Vector3, Vector2 } from "../../Maths/math";
+import { Mesh, _CreationDataStorage } from "../mesh";
+import { VertexData } from "../mesh.vertexData";
+
+VertexData.CreateIcoSphere = function(options: { radius?: number, radiusX?: number, radiusY?: number, radiusZ?: number, flat?: boolean, subdivisions?: number, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4 }): VertexData {
+    var sideOrientation = options.sideOrientation || VertexData.DEFAULTSIDE;
+    var radius = options.radius || 1;
+    var flat = (options.flat === undefined) ? true : options.flat;
+    var subdivisions = options.subdivisions || 4;
+    var radiusX = options.radiusX || radius;
+    var radiusY = options.radiusY || radius;
+    var radiusZ = options.radiusZ || radius;
+
+    var t = (1 + Math.sqrt(5)) / 2;
+
+    // 12 vertex x,y,z
+    var ico_vertices = [
+        -1, t, -0, 1, t, 0, -1, -t, 0, 1, -t, 0, // v0-3
+        0, -1, -t, 0, 1, -t, 0, -1, t, 0, 1, t, // v4-7
+        t, 0, 1, t, 0, -1, -t, 0, 1, -t, 0, -1  // v8-11
+    ];
+
+    // index of 3 vertex makes a face of icopshere
+    var ico_indices = [
+        0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 12, 22, 23,
+        1, 5, 20, 5, 11, 4, 23, 22, 13, 22, 18, 6, 7, 1, 8,
+        14, 21, 4, 14, 4, 2, 16, 13, 6, 15, 6, 19, 3, 8, 9,
+        4, 21, 5, 13, 17, 23, 6, 13, 22, 19, 6, 18, 9, 8, 1
+    ];
+    // vertex for uv have aliased position, not for UV
+    var vertices_unalias_id = [
+        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+        // vertex alias
+        0,  // 12: 0 + 12
+        2,  // 13: 2 + 11
+        3,  // 14: 3 + 11
+        3,  // 15: 3 + 12
+        3,  // 16: 3 + 13
+        4,  // 17: 4 + 13
+        7,  // 18: 7 + 11
+        8,  // 19: 8 + 11
+        9,  // 20: 9 + 11
+        9,  // 21: 9 + 12
+        10, // 22: A + 12
+        11 // 23: B + 12
+    ];
+
+    // uv as integer step (not pixels !)
+    var ico_vertexuv = [
+        5, 1, 3, 1, 6, 4, 0, 0,  // v0-3
+        5, 3, 4, 2, 2, 2, 4, 0,  // v4-7
+        2, 0, 1, 1, 6, 0, 6, 2,  // v8-11
+        // vertex alias (for same vertex on different faces)
+        0, 4, // 12: 0 + 12
+        3, 3, // 13: 2 + 11
+        4, 4, // 14: 3 + 11
+        3, 1, // 15: 3 + 12
+        4, 2, // 16: 3 + 13
+        4, 4, // 17: 4 + 13
+        0, 2, // 18: 7 + 11
+        1, 1, // 19: 8 + 11
+        2, 2, // 20: 9 + 11
+        3, 3, // 21: 9 + 12
+        1, 3, // 22: A + 12
+        2, 4  // 23: B + 12
+    ];
+
+    // Vertices[0, 1, ...9, A, B] : position on UV plane
+    // '+' indicate duplicate position to be fixed (3,9:0,2,3,4,7,8,A,B)
+    // First island of uv mapping
+    // v = 4h          3+  2
+    // v = 3h        9+  4
+    // v = 2h      9+  5   B
+    // v = 1h    9   1   0
+    // v = 0h  3   8   7   A
+    //     u = 0 1 2 3 4 5 6  *a
+
+    // Second island of uv mapping
+    // v = 4h  0+  B+  4+
+    // v = 3h    A+  2+
+    // v = 2h  7+  6   3+
+    // v = 1h    8+  3+
+    // v = 0h
+    //     u = 0 1 2 3 4 5 6  *a
+
+    // Face layout on texture UV mapping
+    // ============
+    // \ 4  /\ 16 /   ======
+    //  \  /  \  /   /\ 11 /
+    //   \/ 7  \/   /  \  /
+    //    =======  / 10 \/
+    //   /\ 17 /\  =======
+    //  /  \  /  \ \ 15 /\
+    // / 8  \/ 12 \ \  /  \
+    // ============  \/ 6  \
+    // \ 18 /\  ============
+    //  \  /  \ \ 5  /\ 0  /
+    //   \/ 13 \ \  /  \  /
+    //   =======  \/ 1  \/
+    //       =============
+    //      /\ 19 /\  2 /\
+    //     /  \  /  \  /  \
+    //    / 14 \/ 9  \/  3 \
+    //   ===================
+
+    // uv step is u:1 or 0.5, v:cos(30)=sqrt(3)/2, ratio approx is 84/97
+    var ustep = 138 / 1024;
+    var vstep = 239 / 1024;
+    var uoffset = 60 / 1024;
+    var voffset = 26 / 1024;
+    // Second island should have margin, not to touch the first island
+    // avoid any borderline artefact in pixel rounding
+    var island_u_offset = -40 / 1024;
+    var island_v_offset = +20 / 1024;
+    // face is either island 0 or 1 :
+    // second island is for faces : [4, 7, 8, 12, 13, 16, 17, 18]
+    var island = [
+        0, 0, 0, 0, 1, //  0 - 4
+        0, 0, 1, 1, 0, //  5 - 9
+        0, 0, 1, 1, 0, //  10 - 14
+        0, 1, 1, 1, 0 //  15 - 19
+    ];
+
+    var indices = new Array<number>();
+    var positions = new Array<number>();
+    var normals = new Array<number>();
+    var uvs = new Array<number>();
+
+    var current_indice = 0;
+    // prepare array of 3 vector (empty) (to be worked in place, shared for each face)
+    var face_vertex_pos = new Array(3);
+    var face_vertex_uv = new Array(3);
+    var v012;
+    for (v012 = 0; v012 < 3; v012++) {
+        face_vertex_pos[v012] = Vector3.Zero();
+        face_vertex_uv[v012] = Vector2.Zero();
+    }
+    // create all with normals
+    for (var face = 0; face < 20; face++) {
+        // 3 vertex per face
+        for (v012 = 0; v012 < 3; v012++) {
+            // look up vertex 0,1,2 to its index in 0 to 11 (or 23 including alias)
+            var v_id = ico_indices[3 * face + v012];
+            // vertex have 3D position (x,y,z)
+            face_vertex_pos[v012].copyFromFloats(
+                ico_vertices[3 * vertices_unalias_id[v_id]],
+                ico_vertices[3 * vertices_unalias_id[v_id] + 1],
+                ico_vertices[3 * vertices_unalias_id[v_id] + 2]);
+            // Normalize to get normal, then scale to radius
+            face_vertex_pos[v012].normalize().scaleInPlace(radius);
+
+            // uv Coordinates from vertex ID
+            face_vertex_uv[v012].copyFromFloats(
+                ico_vertexuv[2 * v_id] * ustep + uoffset + island[face] * island_u_offset,
+                ico_vertexuv[2 * v_id + 1] * vstep + voffset + island[face] * island_v_offset);
+        }
+
+        // Subdivide the face (interpolate pos, norm, uv)
+        // - pos is linear interpolation, then projected to sphere (converge polyhedron to sphere)
+        // - norm is linear interpolation of vertex corner normal
+        //   (to be checked if better to re-calc from face vertex, or if approximation is OK ??? )
+        // - uv is linear interpolation
+        //
+        // Topology is as below for sub-divide by 2
+        // vertex shown as v0,v1,v2
+        // interp index is i1 to progress in range [v0,v1[
+        // interp index is i2 to progress in range [v0,v2[
+        // face index as  (i1,i2)  for /\  : (i1,i2),(i1+1,i2),(i1,i2+1)
+        //            and (i1,i2)' for \/  : (i1+1,i2),(i1+1,i2+1),(i1,i2+1)
+        //
+        //
+        //                    i2    v2
+        //                    ^    ^
+        //                   /    / \
+        //                  /    /   \
+        //                 /    /     \
+        //                /    / (0,1) \
+        //               /    #---------\
+        //              /    / \ (0,0)'/ \
+        //             /    /   \     /   \
+        //            /    /     \   /     \
+        //           /    / (0,0) \ / (1,0) \
+        //          /    #---------#---------\
+        //              v0                    v1
+        //
+        //              --------------------> i1
+        //
+        // interp of (i1,i2):
+        //  along i2 :  x0=lerp(v0,v2, i2/S) <---> x1=lerp(v1,v2, i2/S)
+        //  along i1 :  lerp(x0,x1, i1/(S-i2))
+        //
+        // centroid of triangle is needed to get help normal computation
+        //  (c1,c2) are used for centroid location
+
+        var interp_vertex = (i1: number, i2: number, c1: number, c2: number) => {
+            // vertex is interpolated from
+            //   - face_vertex_pos[0..2]
+            //   - face_vertex_uv[0..2]
+            var pos_x0 = Vector3.Lerp(face_vertex_pos[0], face_vertex_pos[2], i2 / subdivisions);
+            var pos_x1 = Vector3.Lerp(face_vertex_pos[1], face_vertex_pos[2], i2 / subdivisions);
+            var pos_interp = (subdivisions === i2) ? face_vertex_pos[2] : Vector3.Lerp(pos_x0, pos_x1, i1 / (subdivisions - i2));
+            pos_interp.normalize();
+
+            var vertex_normal;
+            if (flat) {
+                // in flat mode, recalculate normal as face centroid normal
+                var centroid_x0 = Vector3.Lerp(face_vertex_pos[0], face_vertex_pos[2], c2 / subdivisions);
+                var centroid_x1 = Vector3.Lerp(face_vertex_pos[1], face_vertex_pos[2], c2 / subdivisions);
+                vertex_normal = Vector3.Lerp(centroid_x0, centroid_x1, c1 / (subdivisions - c2));
+            } else {
+                // in smooth mode, recalculate normal from each single vertex position
+                vertex_normal = new Vector3(pos_interp.x, pos_interp.y, pos_interp.z);
+            }
+            // Vertex normal need correction due to X,Y,Z radius scaling
+            vertex_normal.x /= radiusX;
+            vertex_normal.y /= radiusY;
+            vertex_normal.z /= radiusZ;
+            vertex_normal.normalize();
+
+            var uv_x0 = Vector2.Lerp(face_vertex_uv[0], face_vertex_uv[2], i2 / subdivisions);
+            var uv_x1 = Vector2.Lerp(face_vertex_uv[1], face_vertex_uv[2], i2 / subdivisions);
+            var uv_interp = (subdivisions === i2) ? face_vertex_uv[2] : Vector2.Lerp(uv_x0, uv_x1, i1 / (subdivisions - i2));
+            positions.push(pos_interp.x * radiusX, pos_interp.y * radiusY, pos_interp.z * radiusZ);
+            normals.push(vertex_normal.x, vertex_normal.y, vertex_normal.z);
+            uvs.push(uv_interp.x, uv_interp.y);
+            // push each vertex has member of a face
+            // Same vertex can bleong to multiple face, it is pushed multiple time (duplicate vertex are present)
+            indices.push(current_indice);
+            current_indice++;
+        };
+
+        for (var i2 = 0; i2 < subdivisions; i2++) {
+            for (var i1 = 0; i1 + i2 < subdivisions; i1++) {
+                // face : (i1,i2)  for /\  :
+                // interp for : (i1,i2),(i1+1,i2),(i1,i2+1)
+                interp_vertex(i1, i2, i1 + 1.0 / 3, i2 + 1.0 / 3);
+                interp_vertex(i1 + 1, i2, i1 + 1.0 / 3, i2 + 1.0 / 3);
+                interp_vertex(i1, i2 + 1, i1 + 1.0 / 3, i2 + 1.0 / 3);
+                if (i1 + i2 + 1 < subdivisions) {
+                    // face : (i1,i2)' for \/  :
+                    // interp for (i1+1,i2),(i1+1,i2+1),(i1,i2+1)
+                    interp_vertex(i1 + 1, i2, i1 + 2.0 / 3, i2 + 2.0 / 3);
+                    interp_vertex(i1 + 1, i2 + 1, i1 + 2.0 / 3, i2 + 2.0 / 3);
+                    interp_vertex(i1, i2 + 1, i1 + 2.0 / 3, i2 + 2.0 / 3);
+                }
+            }
+        }
+    }
+
+    // Sides
+    VertexData._ComputeSides(sideOrientation, positions, indices, normals, uvs, options.frontUVs, options.backUVs);
+
+    // Result
+    var vertexData = new VertexData();
+    vertexData.indices = indices;
+    vertexData.positions = positions;
+    vertexData.normals = normals;
+    vertexData.uvs = uvs;
+    return vertexData;
+};
+
+Mesh.CreateIcoSphere = (name: string, options: { radius?: number, flat?: boolean, subdivisions?: number, sideOrientation?: number, updatable?: boolean }, scene: Scene): Mesh => {
+    return IcoSphereBuilder.CreateIcoSphere(name, options, scene);
+};
+
+/**
+ * Class containing static functions to help procedurally build meshes
+ */
+export class IcoSphereBuilder {
+    /**
+     * Creates a sphere based upon an icosahedron with 20 triangular faces which can be subdivided
+     * * The parameter `radius` sets the radius size (float) of the icosphere (default 1)
+     * * You can set some different icosphere dimensions, for instance to build an ellipsoid, by using the parameters `radiusX`, `radiusY` and `radiusZ` (all by default have the same value of `radius`)
+     * * The parameter `subdivisions` sets the number of subdivisions (postive integer, default 4). The more subdivisions, the more faces on the icosphere whatever its size
+     * * The parameter `flat` (boolean, default true) gives each side its own normals. Set it to false to get a smooth continuous light reflection on the surface
+     * * You can also set the mesh side orientation with the values : BABYLON.Mesh.FRONTSIDE (default), BABYLON.Mesh.BACKSIDE or BABYLON.Mesh.DOUBLESIDE
+     * * If you create a double-sided mesh, you can choose what parts of the texture image to crop and stick respectively on the front and the back sides with the parameters `frontUVs` and `backUVs` (Vector4). Detail here : https://doc.babylonjs.com/babylon101/discover_basic_elements#side-orientation
+     * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created
+     * @param name defines the name of the mesh
+     * @param options defines the options used to create the mesh
+     * @param scene defines the hosting scene
+     * @returns the icosahedron mesh
+     * @see https://doc.babylonjs.com/how_to/polyhedra_shapes#icosphere
+     */
+    public static CreateIcoSphere(name: string, options: { radius?: number, radiusX?: number, radiusY?: number, radiusZ?: number, flat?: boolean, subdivisions?: number, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4, updatable?: boolean }, scene: Scene): Mesh {
+        var sphere = new Mesh(name, scene);
+
+        options.sideOrientation = Mesh._GetDefaultSideOrientation(options.sideOrientation);
+        sphere._originalBuilderSideOrientation = options.sideOrientation;
+
+        var vertexData = VertexData.CreateIcoSphere(options);
+
+        vertexData.applyToMesh(sphere, options.updatable);
+
+        return sphere;
+    }
+}

+ 17 - 0
src/Meshes/Builders/index.ts

@@ -0,0 +1,17 @@
+export * from "./boxBuilder";
+export * from "./discBuilder";
+export * from "./ribbonBuilder";
+export * from "./sphereBuilder";
+export * from "./cylinderBuilder";
+export * from "./torusBuilder";
+export * from "./torusKnotBuilder";
+export * from "./linesBuilder";
+export * from "./polygonBuilder";
+export * from "./shapeBuilder";
+export * from "./latheBuilder";
+export * from "./planeBuilder";
+export * from "./groundBuilder";
+export * from "./tubeBuilder";
+export * from "./polyhedronBuilder";
+export * from "./icoSphereBuilder";
+export * from "./decalBuilder";

+ 82 - 0
src/Meshes/Builders/latheBuilder.ts

@@ -0,0 +1,82 @@
+import { Scene } from "../../scene";
+import { Vector3, Vector4 } from "../../Maths/math";
+import { Mesh, _CreationDataStorage } from "../mesh";
+import { RibbonBuilder } from "./ribbonBuilder";
+
+Mesh.CreateLathe = (name: string, shape: Vector3[], radius: number, tessellation: number, scene: Scene, updatable?: boolean, sideOrientation?: number): Mesh => {
+    var options = {
+        shape: shape,
+        radius: radius,
+        tessellation: tessellation,
+        sideOrientation: sideOrientation,
+        updatable: updatable
+    };
+
+    return LatheBuilder.CreateLathe(name, options, scene);
+};
+
+/**
+ * Class containing static functions to help procedurally build meshes
+ */
+export class LatheBuilder {
+    /**
+     * Creates lathe mesh.
+     * The lathe is a shape with a symetry axis : a 2D model shape is rotated around this axis to design the lathe
+     * * The parameter `shape` is a required array of successive Vector3. This array depicts the shape to be rotated in its local space : the shape must be designed in the xOy plane and will be rotated around the Y axis. It's usually a 2D shape, so the Vector3 z coordinates are often set to zero
+     * * The parameter `radius` (positive float, default 1) is the radius value of the lathe
+     * * The parameter `tessellation` (positive integer, default 64) is the side number of the lathe
+     * * The parameter `clip` (positive integer, default 0) is the number of sides to not create without effecting the general shape of the sides
+     * * The parameter `arc` (positive float, default 1) is the ratio of the lathe. 0.5 builds for instance half a lathe, so an opened shape
+     * * The parameter `closed` (boolean, default true) opens/closes the lathe circumference. This should be set to false when used with the parameter "arc"
+     * * The parameter `cap` sets the way the extruded shape is capped. Possible values : BABYLON.Mesh.NO_CAP (default), BABYLON.Mesh.CAP_START, BABYLON.Mesh.CAP_END, BABYLON.Mesh.CAP_ALL
+     * * You can also set the mesh side orientation with the values : BABYLON.Mesh.FRONTSIDE (default), BABYLON.Mesh.BACKSIDE or BABYLON.Mesh.DOUBLESIDE
+     * * If you create a double-sided mesh, you can choose what parts of the texture image to crop and stick respectively on the front and the back sides with the parameters `frontUVs` and `backUVs` (Vector4). Detail here : https://doc.babylonjs.com/babylon101/discover_basic_elements#side-orientation
+     * * The optional parameter `invertUV` (boolean, default false) swaps in the geometry the U and V coordinates to apply a texture
+     * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created
+     * @param name defines the name of the mesh
+     * @param options defines the options used to create the mesh
+     * @param scene defines the hosting scene
+     * @returns the lathe mesh
+     * @see https://doc.babylonjs.com/how_to/parametric_shapes#lathe
+     */
+    public static CreateLathe(name: string, options: { shape: Vector3[], radius?: number, tessellation?: number, clip?: number, arc?: number, closed?: boolean, updatable?: boolean, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4, cap?: number, invertUV?: boolean }, scene: Scene): Mesh {
+        var arc: number = options.arc ? ((options.arc <= 0 || options.arc > 1) ? 1.0 : options.arc) : 1.0;
+        var closed: boolean = (options.closed === undefined) ? true : options.closed;
+        var shape = options.shape;
+        var radius = options.radius || 1;
+        var tessellation = options.tessellation || 64;
+        var clip = options.clip || 0;
+        var updatable = options.updatable;
+        var sideOrientation = Mesh._GetDefaultSideOrientation(options.sideOrientation);
+        var cap = options.cap || Mesh.NO_CAP;
+        var pi2 = Math.PI * 2;
+        var paths = new Array();
+        var invertUV = options.invertUV || false;
+
+        var i = 0;
+        var p = 0;
+        var step = pi2 / tessellation * arc;
+        var rotated;
+        var path = new Array<Vector3>();
+        for (i = 0; i <= tessellation - clip; i++) {
+            var path: Vector3[] = [];
+            if (cap == Mesh.CAP_START || cap == Mesh.CAP_ALL) {
+                path.push(new Vector3(0, shape[0].y, 0));
+                path.push(new Vector3(Math.cos(i * step) * shape[0].x * radius, shape[0].y, Math.sin(i * step) * shape[0].x * radius));
+            }
+            for (p = 0; p < shape.length; p++) {
+                rotated = new Vector3(Math.cos(i * step) * shape[p].x * radius, shape[p].y, Math.sin(i * step) * shape[p].x * radius);
+                path.push(rotated);
+            }
+            if (cap == Mesh.CAP_END || cap == Mesh.CAP_ALL) {
+                path.push(new Vector3(Math.cos(i * step) * shape[shape.length - 1].x * radius, shape[shape.length - 1].y, Math.sin(i * step) * shape[shape.length - 1].x * radius));
+                path.push(new Vector3(0, shape[shape.length - 1].y, 0));
+            }
+            paths.push(path);
+        }
+
+        // lathe ribbon
+        var lathe = RibbonBuilder.CreateRibbon(name, { pathArray: paths, closeArray: closed, sideOrientation: sideOrientation, updatable: updatable, invertUV: invertUV, frontUVs: options.frontUVs, backUVs: options.backUVs }, scene);
+        return lathe;
+    }
+}

+ 275 - 0
src/Meshes/Builders/linesBuilder.ts

@@ -0,0 +1,275 @@
+import { Vector3, Color4 } from "../../Maths/math";
+import { _CreationDataStorage, Mesh } from "../mesh";
+import { VertexData } from "../mesh.vertexData";
+import { FloatArray, Nullable } from "../../types";
+import { LinesMesh } from "../../Meshes/linesMesh";
+import { Scene } from "../../scene";
+import { VertexBuffer } from "../../Meshes/buffer";
+
+VertexData.CreateLineSystem = function(options: { lines: Vector3[][], colors?: Nullable<Color4[][]> }): VertexData {
+    var indices = [];
+    var positions = [];
+    var lines = options.lines;
+    var colors = options.colors;
+    var vertexColors = [];
+    var idx = 0;
+
+    for (var l = 0; l < lines.length; l++) {
+        var points = lines[l];
+        for (var index = 0; index < points.length; index++) {
+            positions.push(points[index].x, points[index].y, points[index].z);
+            if (colors) {
+                var color = colors[l];
+                vertexColors.push(color[index].r, color[index].g, color[index].b, color[index].a);
+            }
+            if (index > 0) {
+                indices.push(idx - 1);
+                indices.push(idx);
+            }
+            idx++;
+        }
+    }
+    var vertexData = new VertexData();
+    vertexData.indices = indices;
+    vertexData.positions = positions;
+    if (colors) {
+        vertexData.colors = vertexColors;
+    }
+    return vertexData;
+};
+
+VertexData.CreateDashedLines = function(options: { points: Vector3[], dashSize?: number, gapSize?: number, dashNb?: number }): VertexData {
+    var dashSize = options.dashSize || 3;
+    var gapSize = options.gapSize || 1;
+    var dashNb = options.dashNb || 200;
+    var points = options.points;
+
+    var positions = new Array<number>();
+    var indices = new Array<number>();
+
+    var curvect = Vector3.Zero();
+    var lg = 0;
+    var nb = 0;
+    var shft = 0;
+    var dashshft = 0;
+    var curshft = 0;
+    var idx = 0;
+    var i = 0;
+    for (i = 0; i < points.length - 1; i++) {
+        points[i + 1].subtractToRef(points[i], curvect);
+        lg += curvect.length();
+    }
+    shft = lg / dashNb;
+    dashshft = dashSize * shft / (dashSize + gapSize);
+    for (i = 0; i < points.length - 1; i++) {
+        points[i + 1].subtractToRef(points[i], curvect);
+        nb = Math.floor(curvect.length() / shft);
+        curvect.normalize();
+        for (var j = 0; j < nb; j++) {
+            curshft = shft * j;
+            positions.push(points[i].x + curshft * curvect.x, points[i].y + curshft * curvect.y, points[i].z + curshft * curvect.z);
+            positions.push(points[i].x + (curshft + dashshft) * curvect.x, points[i].y + (curshft + dashshft) * curvect.y, points[i].z + (curshft + dashshft) * curvect.z);
+            indices.push(idx, idx + 1);
+            idx += 2;
+        }
+    }
+
+    // Result
+    var vertexData = new VertexData();
+    vertexData.positions = positions;
+    vertexData.indices = indices;
+
+    return vertexData;
+};
+
+Mesh.CreateLines = (name: string, points: Vector3[], scene: Nullable<Scene> = null, updatable: boolean = false, instance: Nullable<LinesMesh> = null): LinesMesh => {
+    var options = {
+        points: points,
+        updatable: updatable,
+        instance: instance
+    };
+    return LinesBuilder.CreateLines(name, options, scene);
+};
+
+Mesh.CreateDashedLines = (name: string, points: Vector3[], dashSize: number, gapSize: number, dashNb: number, scene: Nullable<Scene> = null, updatable?: boolean, instance?: LinesMesh): LinesMesh => {
+    var options = {
+        points: points,
+        dashSize: dashSize,
+        gapSize: gapSize,
+        dashNb: dashNb,
+        updatable: updatable,
+        instance: instance
+    };
+    return LinesBuilder.CreateDashedLines(name, options, scene);
+};
+
+/**
+ * Class containing static functions to help procedurally build meshes
+ */
+export class LinesBuilder {
+    /**
+     * Creates a line system mesh. A line system is a pool of many lines gathered in a single mesh
+     * * A line system mesh is considered as a parametric shape since it has no predefined original shape. Its shape is determined by the passed array of lines as an input parameter
+     * * Like every other parametric shape, it is dynamically updatable by passing an existing instance of LineSystem to this static function
+     * * The parameter `lines` is an array of lines, each line being an array of successive Vector3
+     * * The optional parameter `instance` is an instance of an existing LineSystem object to be updated with the passed `lines` parameter
+     * * The optional parameter `colors` is an array of line colors, each line colors being an array of successive Color4, one per line point
+     * * The optional parameter `useVertexAlpha` is to be set to `false` (default `true`) when you don't need the alpha blending (faster)
+     * * Updating a simple Line mesh, you just need to update every line in the `lines` array : https://doc.babylonjs.com/how_to/how_to_dynamically_morph_a_mesh#lines-and-dashedlines
+     * * When updating an instance, remember that only line point positions can change, not the number of points, neither the number of lines
+     * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created
+     * @see https://doc.babylonjs.com/how_to/parametric_shapes#line-system
+     * @param name defines the name of the new line system
+     * @param options defines the options used to create the line system
+     * @param scene defines the hosting scene
+     * @returns a new line system mesh
+     */
+    public static CreateLineSystem(name: string, options: { lines: Vector3[][], updatable?: boolean, instance?: Nullable<LinesMesh>, colors?: Nullable<Color4[][]>, useVertexAlpha?: boolean }, scene: Nullable<Scene>): LinesMesh {
+        var instance = options.instance;
+        var lines = options.lines;
+        var colors = options.colors;
+
+        if (instance) { // lines update
+            var positions = instance.getVerticesData(VertexBuffer.PositionKind)!;
+            var vertexColor;
+            var lineColors;
+            if (colors) {
+                vertexColor = instance.getVerticesData(VertexBuffer.ColorKind)!;
+            }
+            var i = 0;
+            var c = 0;
+            for (var l = 0; l < lines.length; l++) {
+                var points = lines[l];
+                for (var p = 0; p < points.length; p++) {
+                    positions[i] = points[p].x;
+                    positions[i + 1] = points[p].y;
+                    positions[i + 2] = points[p].z;
+                    if (colors && vertexColor) {
+                        lineColors = colors[l];
+                        vertexColor[c] = lineColors[p].r;
+                        vertexColor[c + 1] = lineColors[p].g;
+                        vertexColor[c + 2] = lineColors[p].b;
+                        vertexColor[c + 3] = lineColors[p].a;
+                        c += 4;
+                    }
+                    i += 3;
+                }
+            }
+            instance.updateVerticesData(VertexBuffer.PositionKind, positions, false, false);
+            if (colors && vertexColor) {
+                instance.updateVerticesData(VertexBuffer.ColorKind, vertexColor, false, false);
+            }
+            return instance;
+        }
+
+        // line system creation
+        var useVertexColor = (colors) ? true : false;
+        var lineSystem = new LinesMesh(name, scene, null, undefined, undefined, useVertexColor, options.useVertexAlpha);
+        var vertexData = VertexData.CreateLineSystem(options);
+        vertexData.applyToMesh(lineSystem, options.updatable);
+        return lineSystem;
+    }
+
+    /**
+     * Creates a line mesh
+     * A line mesh is considered as a parametric shape since it has no predefined original shape. Its shape is determined by the passed array of points as an input parameter
+     * * Like every other parametric shape, it is dynamically updatable by passing an existing instance of LineMesh to this static function
+     * * The parameter `points` is an array successive Vector3
+     * * The optional parameter `instance` is an instance of an existing LineMesh object to be updated with the passed `points` parameter : https://doc.babylonjs.com/how_to/how_to_dynamically_morph_a_mesh#lines-and-dashedlines
+     * * The optional parameter `colors` is an array of successive Color4, one per line point
+     * * The optional parameter `useVertexAlpha` is to be set to `false` (default `true`) when you don't need alpha blending (faster)
+     * * When updating an instance, remember that only point positions can change, not the number of points
+     * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created
+     * @see https://doc.babylonjs.com/how_to/parametric_shapes#lines
+     * @param name defines the name of the new line system
+     * @param options defines the options used to create the line system
+     * @param scene defines the hosting scene
+     * @returns a new line mesh
+     */
+    public static CreateLines(name: string, options: { points: Vector3[], updatable?: boolean, instance?: Nullable<LinesMesh>, colors?: Color4[], useVertexAlpha?: boolean }, scene: Nullable<Scene> = null): LinesMesh {
+        var colors = (options.colors) ? [options.colors] : null;
+        var lines = LinesBuilder.CreateLineSystem(name, { lines: [options.points], updatable: options.updatable, instance: options.instance, colors: colors, useVertexAlpha: options.useVertexAlpha }, scene);
+        return lines;
+    }
+
+    /**
+     * Creates a dashed line mesh
+     * * A dashed line mesh is considered as a parametric shape since it has no predefined original shape. Its shape is determined by the passed array of points as an input parameter
+     * * Like every other parametric shape, it is dynamically updatable by passing an existing instance of LineMesh to this static function
+     * * The parameter `points` is an array successive Vector3
+     * * The parameter `dashNb` is the intended total number of dashes (positive integer, default 200)
+     * * The parameter `dashSize` is the size of the dashes relatively the dash number (positive float, default 3)
+     * * The parameter `gapSize` is the size of the gap between two successive dashes relatively the dash number (positive float, default 1)
+     * * The optional parameter `instance` is an instance of an existing LineMesh object to be updated with the passed `points` parameter : https://doc.babylonjs.com/how_to/how_to_dynamically_morph_a_mesh#lines-and-dashedlines
+     * * When updating an instance, remember that only point positions can change, not the number of points
+     * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created
+     * @param name defines the name of the mesh
+     * @param options defines the options used to create the mesh
+     * @param scene defines the hosting scene
+     * @returns the dashed line mesh
+     * @see https://doc.babylonjs.com/how_to/parametric_shapes#dashed-lines
+     */
+    public static CreateDashedLines(name: string, options: { points: Vector3[], dashSize?: number, gapSize?: number, dashNb?: number, updatable?: boolean, instance?: LinesMesh }, scene: Nullable<Scene> = null): LinesMesh {
+        var points = options.points;
+        var instance = options.instance;
+        var gapSize = options.gapSize || 1;
+        var dashSize = options.dashSize || 3;
+
+        if (instance) {  //  dashed lines update
+            var positionFunction = (positions: FloatArray): void => {
+                var curvect = Vector3.Zero();
+                var nbSeg = positions.length / 6;
+                var lg = 0;
+                var nb = 0;
+                var shft = 0;
+                var dashshft = 0;
+                var curshft = 0;
+                var p = 0;
+                var i = 0;
+                var j = 0;
+                for (i = 0; i < points.length - 1; i++) {
+                    points[i + 1].subtractToRef(points[i], curvect);
+                    lg += curvect.length();
+                }
+                shft = lg / nbSeg;
+                let dashSize = instance!._creationDataStorage!.dashSize;
+                let gapSize = instance!._creationDataStorage!.gapSize;
+                dashshft = dashSize * shft / (dashSize + gapSize);
+                for (i = 0; i < points.length - 1; i++) {
+                    points[i + 1].subtractToRef(points[i], curvect);
+                    nb = Math.floor(curvect.length() / shft);
+                    curvect.normalize();
+                    j = 0;
+                    while (j < nb && p < positions.length) {
+                        curshft = shft * j;
+                        positions[p] = points[i].x + curshft * curvect.x;
+                        positions[p + 1] = points[i].y + curshft * curvect.y;
+                        positions[p + 2] = points[i].z + curshft * curvect.z;
+                        positions[p + 3] = points[i].x + (curshft + dashshft) * curvect.x;
+                        positions[p + 4] = points[i].y + (curshft + dashshft) * curvect.y;
+                        positions[p + 5] = points[i].z + (curshft + dashshft) * curvect.z;
+                        p += 6;
+                        j++;
+                    }
+                }
+                while (p < positions.length) {
+                    positions[p] = points[i].x;
+                    positions[p + 1] = points[i].y;
+                    positions[p + 2] = points[i].z;
+                    p += 3;
+                }
+            };
+            instance.updateMeshPositions(positionFunction, false);
+            return instance;
+        }
+        // dashed lines creation
+        var dashedLines = new LinesMesh(name, scene);
+        var vertexData = VertexData.CreateDashedLines(options);
+        vertexData.applyToMesh(dashedLines, options.updatable);
+
+        dashedLines._creationDataStorage = new _CreationDataStorage();
+        dashedLines._creationDataStorage.dashSize = dashSize;
+        dashedLines._creationDataStorage.gapSize = gapSize;
+        return dashedLines;
+    }
+}

+ 106 - 0
src/Meshes/Builders/planeBuilder.ts

@@ -0,0 +1,106 @@
+import { Scene } from "../../scene";
+import { Vector4, Plane } from "../../Maths/math";
+import { Mesh, _CreationDataStorage } from "../mesh";
+import { VertexData } from "../mesh.vertexData";
+
+VertexData.CreatePlane = function(options: { size?: number, width?: number, height?: number, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4 }): VertexData {
+    var indices = [];
+    var positions = [];
+    var normals = [];
+    var uvs = [];
+
+    var width: number = options.width || options.size || 1;
+    var height: number = options.height || options.size || 1;
+    var sideOrientation = (options.sideOrientation === 0) ? 0 : options.sideOrientation || VertexData.DEFAULTSIDE;
+
+    // Vertices
+    var halfWidth = width / 2.0;
+    var halfHeight = height / 2.0;
+
+    positions.push(-halfWidth, -halfHeight, 0);
+    normals.push(0, 0, -1.0);
+    uvs.push(0.0, 0.0);
+
+    positions.push(halfWidth, -halfHeight, 0);
+    normals.push(0, 0, -1.0);
+    uvs.push(1.0, 0.0);
+
+    positions.push(halfWidth, halfHeight, 0);
+    normals.push(0, 0, -1.0);
+    uvs.push(1.0, 1.0);
+
+    positions.push(-halfWidth, halfHeight, 0);
+    normals.push(0, 0, -1.0);
+    uvs.push(0.0, 1.0);
+
+    // Indices
+    indices.push(0);
+    indices.push(1);
+    indices.push(2);
+
+    indices.push(0);
+    indices.push(2);
+    indices.push(3);
+
+    // Sides
+    VertexData._ComputeSides(sideOrientation, positions, indices, normals, uvs, options.frontUVs, options.backUVs);
+
+    // Result
+    var vertexData = new VertexData();
+
+    vertexData.indices = indices;
+    vertexData.positions = positions;
+    vertexData.normals = normals;
+    vertexData.uvs = uvs;
+
+    return vertexData;
+};
+
+Mesh.CreatePlane = (name: string, size: number, scene: Scene, updatable?: boolean, sideOrientation?: number): Mesh => {
+    var options = {
+        size: size,
+        width: size,
+        height: size,
+        sideOrientation: sideOrientation,
+        updatable: updatable
+    };
+
+    return PlaneBuilder.CreatePlane(name, options, scene);
+};
+
+/**
+ * Class containing static functions to help procedurally build meshes
+ */
+export class PlaneBuilder {
+    /**
+     * Creates a plane mesh
+     * * The parameter `size` sets the size (float) of both sides of the plane at once (default 1)
+     * * You can set some different plane dimensions by using the parameters `width` and `height` (both by default have the same value of `size`)
+     * * The parameter `sourcePlane` is a Plane instance. It builds a mesh plane from a Math plane
+     * * You can also set the mesh side orientation with the values : BABYLON.Mesh.FRONTSIDE (default), BABYLON.Mesh.BACKSIDE or BABYLON.Mesh.DOUBLESIDE
+     * * If you create a double-sided mesh, you can choose what parts of the texture image to crop and stick respectively on the front and the back sides with the parameters `frontUVs` and `backUVs` (Vector4). Detail here : https://doc.babylonjs.com/babylon101/discover_basic_elements#side-orientation
+     * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created
+     * @param name defines the name of the mesh
+     * @param options defines the options used to create the mesh
+     * @param scene defines the hosting scene
+     * @returns the plane mesh
+     * @see https://doc.babylonjs.com/how_to/set_shapes#plane
+     */
+    public static CreatePlane(name: string, options: { size?: number, width?: number, height?: number, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4, updatable?: boolean, sourcePlane?: Plane }, scene: Scene): Mesh {
+        var plane = new Mesh(name, scene);
+
+        options.sideOrientation = Mesh._GetDefaultSideOrientation(options.sideOrientation);
+        plane._originalBuilderSideOrientation = options.sideOrientation;
+
+        var vertexData = VertexData.CreatePlane(options);
+
+        vertexData.applyToMesh(plane, options.updatable);
+
+        if (options.sourcePlane) {
+            plane.translate(options.sourcePlane.normal, -options.sourcePlane.d);
+            plane.setDirection(options.sourcePlane.normal.scale(-1));
+        }
+
+        return plane;
+    }
+}

+ 157 - 0
src/Meshes/Builders/polygonBuilder.ts

@@ -0,0 +1,157 @@
+import { Scene } from "../../scene";
+import { Vector3, Vector2, Color4, Vector4 } from "../../Maths/math";
+import { Mesh, _CreationDataStorage } from "../mesh";
+import { VertexData } from "../mesh.vertexData";
+import { PolygonMeshBuilder } from "../polygonMesh";
+import { FloatArray, IndicesArray } from "../../types";
+import { VertexBuffer } from "../../Meshes/buffer";
+
+declare var earcut: any;
+
+VertexData.CreatePolygon = function(polygon: Mesh, sideOrientation: number, fUV?: Vector4[], fColors?: Color4[], frontUVs?: Vector4, backUVs?: Vector4) {
+    var faceUV: Vector4[] = fUV || new Array<Vector4>(3);
+    var faceColors = fColors;
+    var colors = [];
+
+    // default face colors and UV if undefined
+    for (var f = 0; f < 3; f++) {
+        if (faceUV[f] === undefined) {
+            faceUV[f] = new Vector4(0, 0, 1, 1);
+        }
+        if (faceColors && faceColors[f] === undefined) {
+            faceColors[f] = new Color4(1, 1, 1, 1);
+        }
+    }
+
+    var positions = <FloatArray>polygon.getVerticesData(VertexBuffer.PositionKind);
+    var normals = <FloatArray>polygon.getVerticesData(VertexBuffer.NormalKind);
+    var uvs = <FloatArray>polygon.getVerticesData(VertexBuffer.UVKind);
+    var indices = <IndicesArray>polygon.getIndices();
+
+    // set face colours and textures
+    var idx: number = 0;
+    var face: number = 0;
+    for (var index = 0; index < normals.length; index += 3) {
+        //Edge Face  no. 1
+        if (Math.abs(normals[index + 1]) < 0.001) {
+            face = 1;
+        }
+        //Top Face  no. 0
+        if (Math.abs(normals[index + 1] - 1) < 0.001) {
+            face = 0;
+        }
+        //Bottom Face  no. 2
+        if (Math.abs(normals[index + 1] + 1) < 0.001) {
+            face = 2;
+        }
+        idx = index / 3;
+        uvs[2 * idx] = (1 - uvs[2 * idx]) * faceUV[face].x + uvs[2 * idx] * faceUV[face].z;
+        uvs[2 * idx + 1] = (1 - uvs[2 * idx + 1]) * faceUV[face].y + uvs[2 * idx + 1] * faceUV[face].w;
+        if (faceColors) {
+            colors.push(faceColors[face].r, faceColors[face].g, faceColors[face].b, faceColors[face].a);
+        }
+    }
+
+    // sides
+    VertexData._ComputeSides(sideOrientation, positions, indices, normals, uvs, frontUVs, backUVs);
+
+    // Result
+    var vertexData = new VertexData();
+    vertexData.indices = indices;
+    vertexData.positions = positions;
+    vertexData.normals = normals;
+    vertexData.uvs = uvs;
+
+    if (faceColors) {
+        var totalColors = (sideOrientation === VertexData.DOUBLESIDE) ? colors.concat(colors) : colors;
+        vertexData.colors = totalColors;
+    }
+
+    return vertexData;
+};
+
+Mesh.CreatePolygon = (name: string, shape: Vector3[], scene: Scene, holes?: Vector3[][], updatable?: boolean, sideOrientation?: number, earcutInjection = earcut): Mesh => {
+    var options = {
+        shape: shape,
+        holes: holes,
+        updatable: updatable,
+        sideOrientation: sideOrientation
+    };
+    return PolygonBuilder.CreatePolygon(name, options, scene, earcutInjection);
+};
+
+Mesh.ExtrudePolygon = (name: string, shape: Vector3[], depth: number, scene: Scene, holes?: Vector3[][], updatable?: boolean, sideOrientation?: number, earcutInjection = earcut): Mesh => {
+    var options = {
+        shape: shape,
+        holes: holes,
+        depth: depth,
+        updatable: updatable,
+        sideOrientation: sideOrientation
+    };
+    return PolygonBuilder.ExtrudePolygon(name, options, scene, earcutInjection);
+};
+
+/**
+ * Class containing static functions to help procedurally build meshes
+ */
+export class PolygonBuilder {
+    /**
+     * Creates a polygon mesh
+     * The polygon's shape will depend on the input parameters and is constructed parallel to a ground mesh
+     * * The parameter `shape` is a required array of successive Vector3 representing the corners of the polygon in th XoZ plane, that is y = 0 for all vectors
+     * * You can set the mesh side orientation with the values : Mesh.FRONTSIDE (default), Mesh.BACKSIDE or Mesh.DOUBLESIDE
+     * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created
+     * * If you create a double-sided mesh, you can choose what parts of the texture image to crop and stick respectively on the front and the back sides with the parameters `frontUVs` and `backUVs` (Vector4)
+     * * Remember you can only change the shape positions, not their number when updating a polygon
+     * @param name defines the name of the mesh
+     * @param options defines the options used to create the mesh
+     * @param scene defines the hosting scene
+     * @param earcutInjection can be used to inject your own earcut reference
+     * @returns the polygon mesh
+     */
+    public static CreatePolygon(name: string, options: { shape: Vector3[], holes?: Vector3[][], depth?: number, faceUV?: Vector4[], faceColors?: Color4[], updatable?: boolean, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4, }, scene: Scene, earcutInjection = earcut): Mesh {
+        options.sideOrientation = Mesh._GetDefaultSideOrientation(options.sideOrientation);
+        var shape = options.shape;
+        var holes = options.holes || [];
+        var depth = options.depth || 0;
+        var contours: Array<Vector2> = [];
+        var hole: Array<Vector2> = [];
+
+        for (var i = 0; i < shape.length; i++) {
+            contours[i] = new Vector2(shape[i].x, shape[i].z);
+        }
+        var epsilon = 0.00000001;
+        if (contours[0].equalsWithEpsilon(contours[contours.length - 1], epsilon)) {
+            contours.pop();
+        }
+
+        var polygonTriangulation = new PolygonMeshBuilder(name, contours, scene, earcutInjection);
+        for (var hNb = 0; hNb < holes.length; hNb++) {
+            hole = [];
+            for (var hPoint = 0; hPoint < holes[hNb].length; hPoint++) {
+                hole.push(new Vector2(holes[hNb][hPoint].x, holes[hNb][hPoint].z));
+            }
+            polygonTriangulation.addHole(hole);
+        }
+        var polygon = polygonTriangulation.build(options.updatable, depth);
+        polygon._originalBuilderSideOrientation = options.sideOrientation;
+        var vertexData = VertexData.CreatePolygon(polygon, options.sideOrientation, options.faceUV, options.faceColors, options.frontUVs, options.backUVs);
+        vertexData.applyToMesh(polygon, options.updatable);
+
+        return polygon;
+    }
+
+    /**
+     * Creates an extruded polygon mesh, with depth in the Y direction.
+     * * You can set different colors and different images to the top, bottom and extruded side by using the parameters `faceColors` (an array of 3 Color3 elements) and `faceUV` (an array of 3 Vector4 elements)
+     * @see https://doc.babylonjs.com/how_to/createbox_per_face_textures_and_colors
+     * @param name defines the name of the mesh
+     * @param options defines the options used to create the mesh
+     * @param scene defines the hosting scene
+     * @param earcutInjection can be used to inject your own earcut reference
+     * @returns the polygon mesh
+     */
+    public static ExtrudePolygon(name: string, options: { shape: Vector3[], holes?: Vector3[][], depth?: number, faceUV?: Vector4[], faceColors?: Color4[], updatable?: boolean, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4 }, scene: Scene, earcutInjection = earcut): Mesh {
+        return PolygonBuilder.CreatePolygon(name, options, scene, earcutInjection);
+    }
+}

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 175 - 0
src/Meshes/Builders/polyhedronBuilder.ts


+ 398 - 0
src/Meshes/Builders/ribbonBuilder.ts

@@ -0,0 +1,398 @@
+import { Nullable, FloatArray } from "../../types";
+import { Scene } from "../../scene";
+import { Vector3, Vector2, Color4, Tmp, Vector4 } from "../../Maths/math";
+import { Mesh, _CreationDataStorage } from "../mesh";
+import { VertexBuffer } from "../buffer";
+import { VertexData } from "../mesh.vertexData";
+import { BoundingInfo } from "../../Culling/boundingInfo";
+
+VertexData.CreateRibbon = function(options: { pathArray: Vector3[][], closeArray?: boolean, closePath?: boolean, offset?: number, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4, invertUV?: boolean, uvs?: Vector2[], colors?: Color4[] }): VertexData {
+    var pathArray: Vector3[][] = options.pathArray;
+    var closeArray: boolean = options.closeArray || false;
+    var closePath: boolean = options.closePath || false;
+    var invertUV: boolean = options.invertUV || false;
+    var defaultOffset: number = Math.floor(pathArray[0].length / 2);
+    var offset: number = options.offset || defaultOffset;
+    offset = offset > defaultOffset ? defaultOffset : Math.floor(offset); // offset max allowed : defaultOffset
+    var sideOrientation: number = (options.sideOrientation === 0) ? 0 : options.sideOrientation || VertexData.DEFAULTSIDE;
+    var customUV = options.uvs;
+    var customColors = options.colors;
+
+    var positions: number[] = [];
+    var indices: number[] = [];
+    var normals: number[] = [];
+    var uvs: number[] = [];
+
+    var us: number[][] = [];        		// us[path_id] = [uDist1, uDist2, uDist3 ... ] distances between points on path path_id
+    var vs: number[][] = [];        		// vs[i] = [vDist1, vDist2, vDist3, ... ] distances between points i of consecutives paths from pathArray
+    var uTotalDistance: number[] = []; 		// uTotalDistance[p] : total distance of path p
+    var vTotalDistance: number[] = []; 		//  vTotalDistance[i] : total distance between points i of first and last path from pathArray
+    var minlg: number;          	        // minimal length among all paths from pathArray
+    var lg: number[] = [];        		    // array of path lengths : nb of vertex per path
+    var idx: number[] = [];       		    // array of path indexes : index of each path (first vertex) in the total vertex number
+    var p: number;							// path iterator
+    var i: number;							// point iterator
+    var j: number;							// point iterator
+
+    // if single path in pathArray
+    if (pathArray.length < 2) {
+        var ar1: Vector3[] = [];
+        var ar2: Vector3[] = [];
+        for (i = 0; i < pathArray[0].length - offset; i++) {
+            ar1.push(pathArray[0][i]);
+            ar2.push(pathArray[0][i + offset]);
+        }
+        pathArray = [ar1, ar2];
+    }
+
+    // positions and horizontal distances (u)
+    var idc: number = 0;
+    var closePathCorr: number = (closePath) ? 1 : 0;    // the final index will be +1 if closePath
+    var path: Vector3[];
+    var l: number;
+    minlg = pathArray[0].length;
+    var vectlg: number;
+    var dist: number;
+    for (p = 0; p < pathArray.length; p++) {
+        uTotalDistance[p] = 0;
+        us[p] = [0];
+        path = pathArray[p];
+        l = path.length;
+        minlg = (minlg < l) ? minlg : l;
+
+        j = 0;
+        while (j < l) {
+            positions.push(path[j].x, path[j].y, path[j].z);
+            if (j > 0) {
+                vectlg = path[j].subtract(path[j - 1]).length();
+                dist = vectlg + uTotalDistance[p];
+                us[p].push(dist);
+                uTotalDistance[p] = dist;
+            }
+            j++;
+        }
+
+        if (closePath) {        // an extra hidden vertex is added in the "positions" array
+            j--;
+            positions.push(path[0].x, path[0].y, path[0].z);
+            vectlg = path[j].subtract(path[0]).length();
+            dist = vectlg + uTotalDistance[p];
+            us[p].push(dist);
+            uTotalDistance[p] = dist;
+        }
+
+        lg[p] = l + closePathCorr;
+        idx[p] = idc;
+        idc += (l + closePathCorr);
+    }
+
+    // vertical distances (v)
+    var path1: Vector3[];
+    var path2: Vector3[];
+    var vertex1: Nullable<Vector3> = null;
+    var vertex2: Nullable<Vector3> = null;
+    for (i = 0; i < minlg + closePathCorr; i++) {
+        vTotalDistance[i] = 0;
+        vs[i] = [0];
+        for (p = 0; p < pathArray.length - 1; p++) {
+            path1 = pathArray[p];
+            path2 = pathArray[p + 1];
+            if (i === minlg) {   // closePath
+                vertex1 = path1[0];
+                vertex2 = path2[0];
+            }
+            else {
+                vertex1 = path1[i];
+                vertex2 = path2[i];
+            }
+            vectlg = vertex2.subtract(vertex1).length();
+            dist = vectlg + vTotalDistance[i];
+            vs[i].push(dist);
+            vTotalDistance[i] = dist;
+        }
+
+        if (closeArray && vertex2 && vertex1) {
+            path1 = pathArray[p];
+            path2 = pathArray[0];
+            if (i === minlg) {   // closePath
+                vertex2 = path2[0];
+            }
+            vectlg = vertex2.subtract(vertex1).length();
+            dist = vectlg + vTotalDistance[i];
+            vTotalDistance[i] = dist;
+        }
+    }
+
+    // uvs
+    var u: number;
+    var v: number;
+    if (customUV) {
+        for (p = 0; p < customUV.length; p++) {
+            uvs.push(customUV[p].x, customUV[p].y);
+        }
+    }
+    else {
+        for (p = 0; p < pathArray.length; p++) {
+            for (i = 0; i < minlg + closePathCorr; i++) {
+                u = (uTotalDistance[p] != 0.0) ? us[p][i] / uTotalDistance[p] : 0.0;
+                v = (vTotalDistance[i] != 0.0) ? vs[i][p] / vTotalDistance[i] : 0.0;
+                if (invertUV) {
+                    uvs.push(v, u);
+                } else {
+                    uvs.push(u, v);
+                }
+            }
+        }
+    }
+
+    // indices
+    p = 0;                    					// path index
+    var pi: number = 0;                    		// positions array index
+    var l1: number = lg[p] - 1;           		// path1 length
+    var l2: number = lg[p + 1] - 1;         	// path2 length
+    var min: number = (l1 < l2) ? l1 : l2;   	// current path stop index
+    var shft: number = idx[1] - idx[0];         // shift
+    var path1nb: number = closeArray ? lg.length : lg.length - 1;     // number of path1 to iterate	on
+
+    while (pi <= min && p < path1nb) {       	//  stay under min and don't go over next to last path
+        // draw two triangles between path1 (p1) and path2 (p2) : (p1.pi, p2.pi, p1.pi+1) and (p2.pi+1, p1.pi+1, p2.pi) clockwise
+
+        indices.push(pi, pi + shft, pi + 1);
+        indices.push(pi + shft + 1, pi + 1, pi + shft);
+        pi += 1;
+        if (pi === min) {                   			// if end of one of two consecutive paths reached, go to next existing path
+            p++;
+            if (p === lg.length - 1) {                 // last path of pathArray reached <=> closeArray == true
+                shft = idx[0] - idx[p];
+                l1 = lg[p] - 1;
+                l2 = lg[0] - 1;
+            }
+            else {
+                shft = idx[p + 1] - idx[p];
+                l1 = lg[p] - 1;
+                l2 = lg[p + 1] - 1;
+            }
+            pi = idx[p];
+            min = (l1 < l2) ? l1 + pi : l2 + pi;
+        }
+    }
+
+    // normals
+    VertexData.ComputeNormals(positions, indices, normals);
+
+    if (closePath) {        // update both the first and last vertex normals to their average value
+        var indexFirst: number = 0;
+        var indexLast: number = 0;
+        for (p = 0; p < pathArray.length; p++) {
+            indexFirst = idx[p] * 3;
+            if (p + 1 < pathArray.length) {
+                indexLast = (idx[p + 1] - 1) * 3;
+            }
+            else {
+                indexLast = normals.length - 3;
+            }
+            normals[indexFirst] = (normals[indexFirst] + normals[indexLast]) * 0.5;
+            normals[indexFirst + 1] = (normals[indexFirst + 1] + normals[indexLast + 1]) * 0.5;
+            normals[indexFirst + 2] = (normals[indexFirst + 2] + normals[indexLast + 2]) * 0.5;
+            normals[indexLast] = normals[indexFirst];
+            normals[indexLast + 1] = normals[indexFirst + 1];
+            normals[indexLast + 2] = normals[indexFirst + 2];
+        }
+    }
+
+    // sides
+    VertexData._ComputeSides(sideOrientation, positions, indices, normals, uvs, options.frontUVs, options.backUVs);
+
+    // Colors
+    let colors: Nullable<Float32Array> = null;
+    if (customColors) {
+        colors = new Float32Array(customColors.length * 4);
+        for (var c = 0; c < customColors.length; c++) {
+            colors[c * 4] = customColors[c].r;
+            colors[c * 4 + 1] = customColors[c].g;
+            colors[c * 4 + 2] = customColors[c].b;
+            colors[c * 4 + 3] = customColors[c].a;
+        }
+    }
+
+    // Result
+    var vertexData = new VertexData();
+    var positions32 = new Float32Array(positions);
+    var normals32 = new Float32Array(normals);
+    var uvs32 = new Float32Array(uvs);
+
+    vertexData.indices = indices;
+    vertexData.positions = positions32;
+    vertexData.normals = normals32;
+    vertexData.uvs = uvs32;
+    if (colors) {
+        vertexData.set(colors, VertexBuffer.ColorKind);
+    }
+
+    if (closePath) {
+        (<any>vertexData)._idx = idx;
+    }
+
+    return vertexData;
+};
+
+Mesh.CreateRibbon = (name: string, pathArray: Vector3[][], closeArray: boolean = false, closePath: boolean, offset: number, scene?: Scene, updatable: boolean = false, sideOrientation?: number, instance?: Mesh) => {
+    return RibbonBuilder.CreateRibbon(name, {
+        pathArray: pathArray,
+        closeArray: closeArray,
+        closePath: closePath,
+        offset: offset,
+        updatable: updatable,
+        sideOrientation: sideOrientation,
+        instance: instance
+    }, scene);
+};
+
+/**
+ * Class containing static functions to help procedurally build meshes
+ */
+export class RibbonBuilder {
+    /**
+     * Creates a ribbon mesh. The ribbon is a parametric shape.  It has no predefined shape. Its final shape will depend on the input parameters
+     * * The parameter `pathArray` is a required array of paths, what are each an array of successive Vector3. The pathArray parameter depicts the ribbon geometry
+     * * The parameter `closeArray` (boolean, default false) creates a seam between the first and the last paths of the path array
+     * * The parameter `closePath` (boolean, default false) creates a seam between the first and the last points of each path of the path array
+     * * The parameter `offset` (positive integer, default : rounded half size of the pathArray length), is taken in account only if the `pathArray` is containing a single path
+     * * It's the offset to join the points from the same path. Ex : offset = 10 means the point 1 is joined to the point 11
+     * * The optional parameter `instance` is an instance of an existing Ribbon object to be updated with the passed `pathArray` parameter : https://doc.babylonjs.com/how_to/how_to_dynamically_morph_a_mesh#ribbon
+     * * You can also set the mesh side orientation with the values : BABYLON.Mesh.FRONTSIDE (default), BABYLON.Mesh.BACKSIDE or BABYLON.Mesh.DOUBLESIDE
+     * * If you create a double-sided mesh, you can choose what parts of the texture image to crop and stick respectively on the front and the back sides with the parameters `frontUVs` and `backUVs` (Vector4). Detail here : https://doc.babylonjs.com/babylon101/discover_basic_elements#side-orientation
+     * * The optional parameter `invertUV` (boolean, default false) swaps in the geometry the U and V coordinates to apply a texture
+     * * The parameter `uvs` is an optional flat array of `Vector2` to update/set each ribbon vertex with its own custom UV values instead of the computed ones
+     * * The parameters `colors` is an optional flat array of `Color4` to set/update each ribbon vertex with its own custom color values
+     * * Note that if you use the parameters `uvs` or `colors`, the passed arrays must be populated with the right number of elements, it is to say the number of ribbon vertices. Remember that if you set `closePath` to `true`, there's one extra vertex per path in the geometry
+     * * Moreover, you can use the parameter `color` with `instance` (to update the ribbon), only if you previously used it at creation time
+     * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created
+     * @param name defines the name of the mesh
+     * @param options defines the options used to create the mesh
+     * @param scene defines the hosting scene
+     * @returns the ribbon mesh
+     * @see https://doc.babylonjs.com/how_to/ribbon_tutorial
+     * @see https://doc.babylonjs.com/how_to/parametric_shapes
+     */
+    public static CreateRibbon(name: string, options: { pathArray: Vector3[][], closeArray?: boolean, closePath?: boolean, offset?: number, updatable?: boolean, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4, instance?: Mesh, invertUV?: boolean, uvs?: Vector2[], colors?: Color4[] }, scene: Nullable<Scene> = null): Mesh {
+        var pathArray = options.pathArray;
+        var closeArray = options.closeArray;
+        var closePath = options.closePath;
+        var sideOrientation = Mesh._GetDefaultSideOrientation(options.sideOrientation);
+        var instance = options.instance;
+        var updatable = options.updatable;
+
+        if (instance) {   // existing ribbon instance update
+            // positionFunction : ribbon case
+            // only pathArray and sideOrientation parameters are taken into account for positions update
+            const minimum = Tmp.Vector3[0].setAll(Number.MAX_VALUE);
+            const maximum = Tmp.Vector3[1].setAll(-Number.MAX_VALUE);
+            var positionFunction = (positions: FloatArray) => {
+                var minlg = pathArray[0].length;
+                var mesh = (<Mesh>instance);
+                var i = 0;
+                var ns = (mesh._originalBuilderSideOrientation === Mesh.DOUBLESIDE) ? 2 : 1;
+                for (var si = 1; si <= ns; ++si) {
+                    for (var p = 0; p < pathArray.length; ++p) {
+                        var path = pathArray[p];
+                        var l = path.length;
+                        minlg = (minlg < l) ? minlg : l;
+                        for (let j = 0; j < minlg; ++j) {
+                            const pathPoint = path[j];
+                            positions[i] = pathPoint.x;
+                            positions[i + 1] = pathPoint.y;
+                            positions[i + 2] = pathPoint.z;
+                            minimum.minimizeInPlaceFromFloats(pathPoint.x, pathPoint.y, pathPoint.z);
+                            maximum.maximizeInPlaceFromFloats(pathPoint.x, pathPoint.y, pathPoint.z);
+                            i += 3;
+                        }
+                        if (mesh._creationDataStorage && mesh._creationDataStorage.closePath) {
+                            const pathPoint = path[0];
+                            positions[i] = pathPoint.x;
+                            positions[i + 1] = pathPoint.y;
+                            positions[i + 2] = pathPoint.z;
+                            i += 3;
+                        }
+                    }
+                }
+            };
+            var positions = <FloatArray>instance.getVerticesData(VertexBuffer.PositionKind);
+            positionFunction(positions);
+            if (instance._boundingInfo) {
+                instance._boundingInfo.reConstruct(minimum, maximum, instance._worldMatrix);
+            }
+            else {
+                instance._boundingInfo = new BoundingInfo(minimum, maximum, instance._worldMatrix);
+            }
+            instance.updateVerticesData(VertexBuffer.PositionKind, positions, false, false);
+            if (options.colors) {
+                var colors = <FloatArray>instance.getVerticesData(VertexBuffer.ColorKind);
+                for (var c = 0, colorIndex = 0; c < options.colors.length; c++ , colorIndex += 4) {
+                    const color = options.colors[c];
+                    colors[colorIndex] = color.r;
+                    colors[colorIndex + 1] = color.g;
+                    colors[colorIndex + 2] = color.b;
+                    colors[colorIndex + 3] = color.a;
+                }
+                instance.updateVerticesData(VertexBuffer.ColorKind, colors, false, false);
+            }
+            if (options.uvs) {
+                var uvs = <FloatArray>instance.getVerticesData(VertexBuffer.UVKind);
+                for (var i = 0; i < options.uvs.length; i++) {
+                    uvs[i * 2] = options.uvs[i].x;
+                    uvs[i * 2 + 1] = options.uvs[i].y;
+                }
+                instance.updateVerticesData(VertexBuffer.UVKind, uvs, false, false);
+            }
+            if (!instance.areNormalsFrozen || instance.isFacetDataEnabled) {
+                var indices = instance.getIndices();
+                var normals = <FloatArray>instance.getVerticesData(VertexBuffer.NormalKind);
+                var params = instance.isFacetDataEnabled ? instance.getFacetDataParameters() : null;
+                VertexData.ComputeNormals(positions, indices, normals, params);
+
+                if (instance._creationDataStorage && instance._creationDataStorage.closePath) {
+                    var indexFirst: number = 0;
+                    var indexLast: number = 0;
+                    for (var p = 0; p < pathArray.length; p++) {
+                        indexFirst = instance._creationDataStorage!.idx[p] * 3;
+                        if (p + 1 < pathArray.length) {
+                            indexLast = (instance._creationDataStorage!.idx[p + 1] - 1) * 3;
+                        }
+                        else {
+                            indexLast = normals.length - 3;
+                        }
+                        normals[indexFirst] = (normals[indexFirst] + normals[indexLast]) * 0.5;
+                        normals[indexFirst + 1] = (normals[indexFirst + 1] + normals[indexLast + 1]) * 0.5;
+                        normals[indexFirst + 2] = (normals[indexFirst + 2] + normals[indexLast + 2]) * 0.5;
+                        normals[indexLast] = normals[indexFirst];
+                        normals[indexLast + 1] = normals[indexFirst + 1];
+                        normals[indexLast + 2] = normals[indexFirst + 2];
+                    }
+                }
+                if (!(instance.areNormalsFrozen)) {
+                    instance.updateVerticesData(VertexBuffer.NormalKind, normals, false, false);
+                }
+            }
+
+            return instance;
+        }
+        else {  // new ribbon creation
+
+            var ribbon = new Mesh(name, scene);
+            ribbon._originalBuilderSideOrientation = sideOrientation;
+            ribbon._creationDataStorage = new _CreationDataStorage();
+
+            var vertexData = VertexData.CreateRibbon(options);
+            if (closePath) {
+                ribbon._creationDataStorage.idx = (<any>vertexData)._idx;
+            }
+            ribbon._creationDataStorage.closePath = closePath;
+            ribbon._creationDataStorage.closeArray = closeArray;
+
+            vertexData.applyToMesh(ribbon, updatable);
+
+            return ribbon;
+        }
+    }
+}

+ 211 - 0
src/Meshes/Builders/shapeBuilder.ts

@@ -0,0 +1,211 @@
+import { Nullable } from "../../types";
+import { Scene } from "../../scene";
+import { Vector3, Tmp, Vector4, Path3D, Matrix } from "../../Maths/math";
+import { Mesh, _CreationDataStorage } from "../mesh";
+import { RibbonBuilder } from "./ribbonBuilder";
+
+Mesh.ExtrudeShape = (name: string, shape: Vector3[], path: Vector3[], scale: number, rotation: number, cap: number, scene: Nullable<Scene> = null, updatable?: boolean, sideOrientation?: number, instance?: Mesh): Mesh => {
+    var options = {
+        shape: shape,
+        path: path,
+        scale: scale,
+        rotation: rotation,
+        cap: (cap === 0) ? 0 : cap || Mesh.NO_CAP,
+        sideOrientation: sideOrientation,
+        instance: instance,
+        updatable: updatable
+    };
+
+    return ShapeBuilder.ExtrudeShape(name, options, scene);
+};
+
+Mesh.ExtrudeShapeCustom = (name: string, shape: Vector3[], path: Vector3[], scaleFunction: Function, rotationFunction: Function, ribbonCloseArray: boolean, ribbonClosePath: boolean, cap: number, scene: Scene, updatable?: boolean, sideOrientation?: number, instance?: Mesh): Mesh => {
+    var options = {
+        shape: shape,
+        path: path,
+        scaleFunction: scaleFunction,
+        rotationFunction: rotationFunction,
+        ribbonCloseArray: ribbonCloseArray,
+        ribbonClosePath: ribbonClosePath,
+        cap: (cap === 0) ? 0 : cap || Mesh.NO_CAP,
+        sideOrientation: sideOrientation,
+        instance: instance,
+        updatable: updatable
+    };
+
+    return ShapeBuilder.ExtrudeShapeCustom(name, options, scene);
+};
+
+/**
+ * Class containing static functions to help procedurally build meshes
+ */
+export class ShapeBuilder {
+    /**
+     * Creates an extruded shape mesh. The extrusion is a parametric shape. It has no predefined shape. Its final shape will depend on the input parameters.
+     * * The parameter `shape` is a required array of successive Vector3. This array depicts the shape to be extruded in its local space : the shape must be designed in the xOy plane and will be extruded along the Z axis.
+     * * The parameter `path` is a required array of successive Vector3. This is the axis curve the shape is extruded along.
+     * * The parameter `rotation` (float, default 0 radians) is the angle value to rotate the shape each step (each path point), from the former step (so rotation added each step) along the curve.
+     * * The parameter `scale` (float, default 1) is the value to scale the shape.
+     * * The parameter `cap` sets the way the extruded shape is capped. Possible values : BABYLON.Mesh.NO_CAP (default), BABYLON.Mesh.CAP_START, BABYLON.Mesh.CAP_END, BABYLON.Mesh.CAP_ALL
+     * * The optional parameter `instance` is an instance of an existing ExtrudedShape object to be updated with the passed `shape`, `path`, `scale` or `rotation` parameters : https://doc.babylonjs.com/how_to/how_to_dynamically_morph_a_mesh#extruded-shape
+     * * Remember you can only change the shape or path point positions, not their number when updating an extruded shape.
+     * * You can also set the mesh side orientation with the values : BABYLON.Mesh.FRONTSIDE (default), BABYLON.Mesh.BACKSIDE or BABYLON.Mesh.DOUBLESIDE
+     * * If you create a double-sided mesh, you can choose what parts of the texture image to crop and stick respectively on the front and the back sides with the parameters `frontUVs` and `backUVs` (Vector4). Detail here : https://doc.babylonjs.com/babylon101/discover_basic_elements#side-orientation
+     * * The optional parameter `invertUV` (boolean, default false) swaps in the geometry the U and V coordinates to apply a texture.
+     * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created.
+     * @param name defines the name of the mesh
+     * @param options defines the options used to create the mesh
+     * @param scene defines the hosting scene
+     * @returns the extruded shape mesh
+     * @see https://doc.babylonjs.com/how_to/parametric_shapes
+     * @see https://doc.babylonjs.com/how_to/parametric_shapes#extruded-shapes
+     */
+    public static ExtrudeShape(name: string, options: { shape: Vector3[], path: Vector3[], scale?: number, rotation?: number, cap?: number, updatable?: boolean, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4, instance?: Mesh, invertUV?: boolean }, scene: Nullable<Scene> = null): Mesh {
+        var path = options.path;
+        var shape = options.shape;
+        var scale = options.scale || 1;
+        var rotation = options.rotation || 0;
+        var cap = (options.cap === 0) ? 0 : options.cap || Mesh.NO_CAP;
+        var updatable = options.updatable;
+        var sideOrientation = Mesh._GetDefaultSideOrientation(options.sideOrientation);
+        var instance = options.instance || null;
+        var invertUV = options.invertUV || false;
+
+        return ShapeBuilder._ExtrudeShapeGeneric(name, shape, path, scale, rotation, null, null, false, false, cap, false, scene, updatable ? true : false, sideOrientation, instance, invertUV, options.frontUVs || null, options.backUVs || null);
+    }
+
+    /**
+     * Creates an custom extruded shape mesh.
+     * The custom extrusion is a parametric shape. It has no predefined shape. Its final shape will depend on the input parameters.
+     * * The parameter `shape` is a required array of successive Vector3. This array depicts the shape to be extruded in its local space : the shape must be designed in the xOy plane and will be extruded along the Z axis.
+     * * The parameter `path` is a required array of successive Vector3. This is the axis curve the shape is extruded along.
+     * * The parameter `rotationFunction` (JS function) is a custom Javascript function called on each path point. This function is passed the position i of the point in the path and the distance of this point from the begining of the path
+     * * It must returns a float value that will be the rotation in radians applied to the shape on each path point.
+     * * The parameter `scaleFunction` (JS function) is a custom Javascript function called on each path point. This function is passed the position i of the point in the path and the distance of this point from the begining of the path
+     * * It must returns a float value that will be the scale value applied to the shape on each path point
+     * * The parameter `ribbonClosePath` (boolean, default false) forces the extrusion underlying ribbon to close all the paths in its `pathArray`
+     * * The parameter `ribbonCloseArray` (boolean, default false) forces the extrusion underlying ribbon to close its `pathArray`
+     * * The parameter `cap` sets the way the extruded shape is capped. Possible values : BABYLON.Mesh.NO_CAP (default), BABYLON.Mesh.CAP_START, BABYLON.Mesh.CAP_END, BABYLON.Mesh.CAP_ALL
+     * * The optional parameter `instance` is an instance of an existing ExtrudedShape object to be updated with the passed `shape`, `path`, `scale` or `rotation` parameters : https://doc.babylonjs.com/how_to/how_to_dynamically_morph_a_mesh#extruded-shape
+     * * Remember you can only change the shape or path point positions, not their number when updating an extruded shape
+     * * You can also set the mesh side orientation with the values : BABYLON.Mesh.FRONTSIDE (default), BABYLON.Mesh.BACKSIDE or BABYLON.Mesh.DOUBLESIDE
+     * * If you create a double-sided mesh, you can choose what parts of the texture image to crop and stick respectively on the front and the back sides with the parameters `frontUVs` and `backUVs` (Vector4). Detail here : https://doc.babylonjs.com/babylon101/discover_basic_elements#side-orientation
+     * * The optional parameter `invertUV` (boolean, default false) swaps in the geometry the U and V coordinates to apply a texture
+     * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created
+     * @param name defines the name of the mesh
+     * @param options defines the options used to create the mesh
+     * @param scene defines the hosting scene
+     * @returns the custom extruded shape mesh
+     * @see https://doc.babylonjs.com/how_to/parametric_shapes#custom-extruded-shapes
+     * @see https://doc.babylonjs.com/how_to/parametric_shapes
+     * @see https://doc.babylonjs.com/how_to/parametric_shapes#extruded-shapes
+     */
+    public static ExtrudeShapeCustom(name: string, options: { shape: Vector3[], path: Vector3[], scaleFunction?: any, rotationFunction?: any, ribbonCloseArray?: boolean, ribbonClosePath?: boolean, cap?: number, updatable?: boolean, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4, instance?: Mesh, invertUV?: boolean }, scene: Scene): Mesh {
+        var path = options.path;
+        var shape = options.shape;
+        var scaleFunction = options.scaleFunction || (() => { return 1; });
+        var rotationFunction = options.rotationFunction || (() => { return 0; });
+        var ribbonCloseArray = options.ribbonCloseArray || false;
+        var ribbonClosePath = options.ribbonClosePath || false;
+        var cap = (options.cap === 0) ? 0 : options.cap || Mesh.NO_CAP;
+        var updatable = options.updatable;
+        var sideOrientation = Mesh._GetDefaultSideOrientation(options.sideOrientation);
+        var instance = options.instance;
+        var invertUV = options.invertUV || false;
+        return ShapeBuilder._ExtrudeShapeGeneric(name, shape, path, null, null, scaleFunction, rotationFunction, ribbonCloseArray, ribbonClosePath, cap, true, scene, updatable ? true : false, sideOrientation, instance || null, invertUV, options.frontUVs || null, options.backUVs || null);
+    }
+
+    private static _ExtrudeShapeGeneric(name: string, shape: Vector3[], curve: Vector3[], scale: Nullable<number>, rotation: Nullable<number>, scaleFunction: Nullable<{ (i: number, distance: number): number; }>,
+        rotateFunction: Nullable<{ (i: number, distance: number): number; }>, rbCA: boolean, rbCP: boolean, cap: number, custom: boolean,
+        scene: Nullable<Scene>, updtbl: boolean, side: number, instance: Nullable<Mesh>, invertUV: boolean, frontUVs: Nullable<Vector4>, backUVs: Nullable<Vector4>): Mesh {
+        // extrusion geometry
+        var extrusionPathArray = (shape: Vector3[], curve: Vector3[], path3D: Path3D, shapePaths: Vector3[][], scale: Nullable<number>, rotation: Nullable<number>,
+            scaleFunction: Nullable<{ (i: number, distance: number): number; }>, rotateFunction: Nullable<{ (i: number, distance: number): number; }>, cap: number, custom: boolean) => {
+            var tangents = path3D.getTangents();
+            var normals = path3D.getNormals();
+            var binormals = path3D.getBinormals();
+            var distances = path3D.getDistances();
+
+            var angle = 0;
+            var returnScale: { (i: number, distance: number): number; } = () => { return scale !== null ? scale : 1; };
+            var returnRotation: { (i: number, distance: number): number; } = () => { return rotation !== null ? rotation : 0; };
+            var rotate: { (i: number, distance: number): number; } = custom && rotateFunction ? rotateFunction : returnRotation;
+            var scl: { (i: number, distance: number): number; } = custom && scaleFunction ? scaleFunction : returnScale;
+            var index = (cap === Mesh.NO_CAP || cap === Mesh.CAP_END) ? 0 : 2;
+            var rotationMatrix: Matrix = Tmp.Matrix[0];
+
+            for (var i = 0; i < curve.length; i++) {
+                var shapePath = new Array<Vector3>();
+                var angleStep = rotate(i, distances[i]);
+                var scaleRatio = scl(i, distances[i]);
+                for (var p = 0; p < shape.length; p++) {
+                    Matrix.RotationAxisToRef(tangents[i], angle, rotationMatrix);
+                    var planed = ((tangents[i].scale(shape[p].z)).add(normals[i].scale(shape[p].x)).add(binormals[i].scale(shape[p].y)));
+                    var rotated = shapePath[p] ? shapePath[p] : Vector3.Zero();
+                    Vector3.TransformCoordinatesToRef(planed, rotationMatrix, rotated);
+                    rotated.scaleInPlace(scaleRatio).addInPlace(curve[i]);
+                    shapePath[p] = rotated;
+                }
+                shapePaths[index] = shapePath;
+                angle += angleStep;
+                index++;
+            }
+            // cap
+            var capPath = (shapePath: Vector3[]) => {
+                var pointCap = Array<Vector3>();
+                var barycenter = Vector3.Zero();
+                var i: number;
+                for (i = 0; i < shapePath.length; i++) {
+                    barycenter.addInPlace(shapePath[i]);
+                }
+                barycenter.scaleInPlace(1.0 / shapePath.length);
+                for (i = 0; i < shapePath.length; i++) {
+                    pointCap.push(barycenter);
+                }
+                return pointCap;
+            };
+            switch (cap) {
+                case Mesh.NO_CAP:
+                    break;
+                case Mesh.CAP_START:
+                    shapePaths[0] = capPath(shapePaths[2]);
+                    shapePaths[1] = shapePaths[2];
+                    break;
+                case Mesh.CAP_END:
+                    shapePaths[index] = shapePaths[index - 1];
+                    shapePaths[index + 1] = capPath(shapePaths[index - 1]);
+                    break;
+                case Mesh.CAP_ALL:
+                    shapePaths[0] = capPath(shapePaths[2]);
+                    shapePaths[1] = shapePaths[2];
+                    shapePaths[index] = shapePaths[index - 1];
+                    shapePaths[index + 1] = capPath(shapePaths[index - 1]);
+                    break;
+                default:
+                    break;
+            }
+            return shapePaths;
+        };
+        var path3D;
+        var pathArray;
+        if (instance) { // instance update
+            let storage = instance._creationDataStorage!;
+            path3D = storage.path3D.update(curve);
+            pathArray = extrusionPathArray(shape, curve, storage.path3D, storage.pathArray, scale, rotation, scaleFunction, rotateFunction, storage.cap, custom);
+            instance = Mesh.CreateRibbon("", pathArray, false, false, 0, scene || undefined, false, 0, instance);
+
+            return instance;
+        }
+        // extruded shape creation
+        path3D = <any>new Path3D(curve);
+        var newShapePaths = new Array<Array<Vector3>>();
+        cap = (cap < 0 || cap > 3) ? 0 : cap;
+        pathArray = extrusionPathArray(shape, curve, path3D, newShapePaths, scale, rotation, scaleFunction, rotateFunction, cap, custom);
+        var extrudedGeneric = RibbonBuilder.CreateRibbon(name, { pathArray: pathArray, closeArray: rbCA, closePath: rbCP, updatable: updtbl, sideOrientation: side, invertUV: invertUV, frontUVs: frontUVs || undefined, backUVs: backUVs || undefined }, scene);
+        extrudedGeneric._creationDataStorage!.pathArray = pathArray;
+        extrudedGeneric._creationDataStorage!.path3D = path3D;
+        extrudedGeneric._creationDataStorage!.cap = cap;
+
+        return extrudedGeneric;
+    }
+}

+ 120 - 0
src/Meshes/Builders/sphereBuilder.ts

@@ -0,0 +1,120 @@
+import { Vector4, Vector3, Matrix } from "../../Maths/math";
+import { Mesh, _CreationDataStorage } from "../mesh";
+import { VertexData } from "../mesh.vertexData";
+import { Scene } from "../../scene";
+
+VertexData.CreateSphere = function(options: { segments?: number, diameter?: number, diameterX?: number, diameterY?: number, diameterZ?: number, arc?: number, slice?: number, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4 }): VertexData {
+    var segments: number = options.segments || 32;
+    var diameterX: number = options.diameterX || options.diameter || 1;
+    var diameterY: number = options.diameterY || options.diameter || 1;
+    var diameterZ: number = options.diameterZ || options.diameter || 1;
+    var arc: number = options.arc && (options.arc <= 0 || options.arc > 1) ? 1.0 : options.arc || 1.0;
+    var slice: number = options.slice && (options.slice <= 0) ? 1.0 : options.slice || 1.0;
+    var sideOrientation = (options.sideOrientation === 0) ? 0 : options.sideOrientation || VertexData.DEFAULTSIDE;
+
+    var radius = new Vector3(diameterX / 2, diameterY / 2, diameterZ / 2);
+
+    var totalZRotationSteps = 2 + segments;
+    var totalYRotationSteps = 2 * totalZRotationSteps;
+
+    var indices = [];
+    var positions = [];
+    var normals = [];
+    var uvs = [];
+
+    for (var zRotationStep = 0; zRotationStep <= totalZRotationSteps; zRotationStep++) {
+        var normalizedZ = zRotationStep / totalZRotationSteps;
+        var angleZ = normalizedZ * Math.PI * slice;
+
+        for (var yRotationStep = 0; yRotationStep <= totalYRotationSteps; yRotationStep++) {
+            var normalizedY = yRotationStep / totalYRotationSteps;
+
+            var angleY = normalizedY * Math.PI * 2 * arc;
+
+            var rotationZ = Matrix.RotationZ(-angleZ);
+            var rotationY = Matrix.RotationY(angleY);
+            var afterRotZ = Vector3.TransformCoordinates(Vector3.Up(), rotationZ);
+            var complete = Vector3.TransformCoordinates(afterRotZ, rotationY);
+
+            var vertex = complete.multiply(radius);
+            var normal = complete.divide(radius).normalize();
+
+            positions.push(vertex.x, vertex.y, vertex.z);
+            normals.push(normal.x, normal.y, normal.z);
+            uvs.push(normalizedY, normalizedZ);
+        }
+
+        if (zRotationStep > 0) {
+            var verticesCount = positions.length / 3;
+            for (var firstIndex = verticesCount - 2 * (totalYRotationSteps + 1); (firstIndex + totalYRotationSteps + 2) < verticesCount; firstIndex++) {
+                indices.push((firstIndex));
+                indices.push((firstIndex + 1));
+                indices.push(firstIndex + totalYRotationSteps + 1);
+
+                indices.push((firstIndex + totalYRotationSteps + 1));
+                indices.push((firstIndex + 1));
+                indices.push((firstIndex + totalYRotationSteps + 2));
+            }
+        }
+    }
+
+    // Sides
+    VertexData._ComputeSides(sideOrientation, positions, indices, normals, uvs, options.frontUVs, options.backUVs);
+
+    // Result
+    var vertexData = new VertexData();
+
+    vertexData.indices = indices;
+    vertexData.positions = positions;
+    vertexData.normals = normals;
+    vertexData.uvs = uvs;
+
+    return vertexData;
+};
+
+Mesh.CreateSphere = (name: string, segments: number, diameter: number, scene?: Scene, updatable?: boolean, sideOrientation?: number): Mesh => {
+    var options = {
+        segments: segments,
+        diameterX: diameter,
+        diameterY: diameter,
+        diameterZ: diameter,
+        sideOrientation: sideOrientation,
+        updatable: updatable
+    };
+
+    return SphereBuilder.CreateSphere(name, options, scene);
+};
+
+/**
+ * Class containing static functions to help procedurally build meshes
+ */
+export class SphereBuilder {
+    /**
+     * Creates a sphere mesh
+     * * The parameter `diameter` sets the diameter size (float) of the sphere (default 1)
+     * * You can set some different sphere dimensions, for instance to build an ellipsoid, by using the parameters `diameterX`, `diameterY` and `diameterZ` (all by default have the same value of `diameter`)
+     * * The parameter `segments` sets the sphere number of horizontal stripes (positive integer, default 32)
+     * * You can create an unclosed sphere with the parameter `arc` (positive float, default 1), valued between 0 and 1, what is the ratio of the circumference (latitude) : 2 x PI x ratio
+     * * You can create an unclosed sphere on its height with the parameter `slice` (positive float, default1), valued between 0 and 1, what is the height ratio (longitude)
+     * * You can also set the mesh side orientation with the values : BABYLON.Mesh.FRONTSIDE (default), BABYLON.Mesh.BACKSIDE or BABYLON.Mesh.DOUBLESIDE
+     * * If you create a double-sided mesh, you can choose what parts of the texture image to crop and stick respectively on the front and the back sides with the parameters `frontUVs` and `backUVs` (Vector4). Detail here : https://doc.babylonjs.com/babylon101/discover_basic_elements#side-orientation
+     * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created
+     * @param name defines the name of the mesh
+     * @param options defines the options used to create the mesh
+     * @param scene defines the hosting scene
+     * @returns the sphere mesh
+     * @see https://doc.babylonjs.com/how_to/set_shapes#sphere
+     */
+    public static CreateSphere(name: string, options: { segments?: number, diameter?: number, diameterX?: number, diameterY?: number, diameterZ?: number, arc?: number, slice?: number, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4, updatable?: boolean }, scene: any): Mesh {
+        var sphere = new Mesh(name, scene);
+
+        options.sideOrientation = Mesh._GetDefaultSideOrientation(options.sideOrientation);
+        sphere._originalBuilderSideOrientation = options.sideOrientation;
+
+        var vertexData = VertexData.CreateSphere(options);
+
+        vertexData.applyToMesh(sphere, options.updatable);
+
+        return sphere;
+    }
+}

+ 115 - 0
src/Meshes/Builders/torusBuilder.ts

@@ -0,0 +1,115 @@
+import { Vector4, Matrix, Vector3, Vector2 } from "../../Maths/math";
+import { Mesh, _CreationDataStorage } from "../mesh";
+import { VertexData } from "../mesh.vertexData";
+import { Scene } from "../../scene";
+
+VertexData.CreateTorus = function(options: { diameter?: number, thickness?: number, tessellation?: number, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4 }) {
+    var indices = [];
+    var positions = [];
+    var normals = [];
+    var uvs = [];
+
+    var diameter = options.diameter || 1;
+    var thickness = options.thickness || 0.5;
+    var tessellation = options.tessellation || 16;
+    var sideOrientation = (options.sideOrientation === 0) ? 0 : options.sideOrientation || VertexData.DEFAULTSIDE;
+
+    var stride = tessellation + 1;
+
+    for (var i = 0; i <= tessellation; i++) {
+        var u = i / tessellation;
+
+        var outerAngle = i * Math.PI * 2.0 / tessellation - Math.PI / 2.0;
+
+        var transform = Matrix.Translation(diameter / 2.0, 0, 0).multiply(Matrix.RotationY(outerAngle));
+
+        for (var j = 0; j <= tessellation; j++) {
+            var v = 1 - j / tessellation;
+
+            var innerAngle = j * Math.PI * 2.0 / tessellation + Math.PI;
+            var dx = Math.cos(innerAngle);
+            var dy = Math.sin(innerAngle);
+
+            // Create a vertex.
+            var normal = new Vector3(dx, dy, 0);
+            var position = normal.scale(thickness / 2);
+            var textureCoordinate = new Vector2(u, v);
+
+            position = Vector3.TransformCoordinates(position, transform);
+            normal = Vector3.TransformNormal(normal, transform);
+
+            positions.push(position.x, position.y, position.z);
+            normals.push(normal.x, normal.y, normal.z);
+            uvs.push(textureCoordinate.x, textureCoordinate.y);
+
+            // And create indices for two triangles.
+            var nextI = (i + 1) % stride;
+            var nextJ = (j + 1) % stride;
+
+            indices.push(i * stride + j);
+            indices.push(i * stride + nextJ);
+            indices.push(nextI * stride + j);
+
+            indices.push(i * stride + nextJ);
+            indices.push(nextI * stride + nextJ);
+            indices.push(nextI * stride + j);
+        }
+    }
+
+    // Sides
+    VertexData._ComputeSides(sideOrientation, positions, indices, normals, uvs, options.frontUVs, options.backUVs);
+
+    // Result
+    var vertexData = new VertexData();
+
+    vertexData.indices = indices;
+    vertexData.positions = positions;
+    vertexData.normals = normals;
+    vertexData.uvs = uvs;
+
+    return vertexData;
+};
+
+Mesh.CreateTorus = (name: string, diameter: number, thickness: number, tessellation: number, scene?: Scene, updatable?: boolean, sideOrientation?: number): Mesh => {
+    var options = {
+        diameter: diameter,
+        thickness: thickness,
+        tessellation: tessellation,
+        sideOrientation: sideOrientation,
+        updatable: updatable
+    };
+
+    return TorusBuilder.CreateTorus(name, options, scene);
+};
+
+/**
+ * Class containing static functions to help procedurally build meshes
+ */
+export class TorusBuilder {
+    /**
+     * Creates a torus mesh
+     * * The parameter `diameter` sets the diameter size (float) of the torus (default 1)
+     * * The parameter `thickness` sets the diameter size of the tube of the torus (float, default 0.5)
+     * * The parameter `tessellation` sets the number of torus sides (postive integer, default 16)
+     * * You can also set the mesh side orientation with the values : BABYLON.Mesh.FRONTSIDE (default), BABYLON.Mesh.BACKSIDE or BABYLON.Mesh.DOUBLESIDE
+     * * If you create a double-sided mesh, you can choose what parts of the texture image to crop and stick respectively on the front and the back sides with the parameters `frontUVs` and `backUVs` (Vector4). Detail here : https://doc.babylonjs.com/babylon101/discover_basic_elements#side-orientation
+     * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created.
+     * @param name defines the name of the mesh
+     * @param options defines the options used to create the mesh
+     * @param scene defines the hosting scene
+     * @returns the torus mesh
+     * @see https://doc.babylonjs.com/how_to/set_shapes#torus
+     */
+    public static CreateTorus(name: string, options: { diameter?: number, thickness?: number, tessellation?: number, updatable?: boolean, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4 }, scene: any): Mesh {
+        var torus = new Mesh(name, scene);
+
+        options.sideOrientation = Mesh._GetDefaultSideOrientation(options.sideOrientation);
+        torus._originalBuilderSideOrientation = options.sideOrientation;
+
+        var vertexData = VertexData.CreateTorus(options);
+
+        vertexData.applyToMesh(torus, options.updatable);
+
+        return torus;
+    }
+}

+ 143 - 0
src/Meshes/Builders/torusKnotBuilder.ts

@@ -0,0 +1,143 @@
+import { Vector4, Vector3 } from "../../Maths/math";
+import { Mesh, _CreationDataStorage } from "../mesh";
+import { VertexData } from "../mesh.vertexData";
+import { Scene } from "../../scene";
+
+VertexData.CreateTorusKnot = function(options: { radius?: number, tube?: number, radialSegments?: number, tubularSegments?: number, p?: number, q?: number, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4 }): VertexData {
+    var indices = new Array<number>();
+    var positions = new Array<number>();
+    var normals = new Array<number>();
+    var uvs = new Array<number>();
+
+    var radius = options.radius || 2;
+    var tube = options.tube || 0.5;
+    var radialSegments = options.radialSegments || 32;
+    var tubularSegments = options.tubularSegments || 32;
+    var p = options.p || 2;
+    var q = options.q || 3;
+    var sideOrientation = (options.sideOrientation === 0) ? 0 : options.sideOrientation || VertexData.DEFAULTSIDE;
+
+    // Helper
+    var getPos = (angle: number) => {
+
+        var cu = Math.cos(angle);
+        var su = Math.sin(angle);
+        var quOverP = q / p * angle;
+        var cs = Math.cos(quOverP);
+
+        var tx = radius * (2 + cs) * 0.5 * cu;
+        var ty = radius * (2 + cs) * su * 0.5;
+        var tz = radius * Math.sin(quOverP) * 0.5;
+
+        return new Vector3(tx, ty, tz);
+    };
+
+    // Vertices
+    var i: number;
+    var j: number;
+    for (i = 0; i <= radialSegments; i++) {
+        var modI = i % radialSegments;
+        var u = modI / radialSegments * 2 * p * Math.PI;
+        var p1 = getPos(u);
+        var p2 = getPos(u + 0.01);
+        var tang = p2.subtract(p1);
+        var n = p2.add(p1);
+
+        var bitan = Vector3.Cross(tang, n);
+        n = Vector3.Cross(bitan, tang);
+
+        bitan.normalize();
+        n.normalize();
+
+        for (j = 0; j < tubularSegments; j++) {
+            var modJ = j % tubularSegments;
+            var v = modJ / tubularSegments * 2 * Math.PI;
+            var cx = -tube * Math.cos(v);
+            var cy = tube * Math.sin(v);
+
+            positions.push(p1.x + cx * n.x + cy * bitan.x);
+            positions.push(p1.y + cx * n.y + cy * bitan.y);
+            positions.push(p1.z + cx * n.z + cy * bitan.z);
+
+            uvs.push(i / radialSegments);
+            uvs.push(j / tubularSegments);
+        }
+    }
+
+    for (i = 0; i < radialSegments; i++) {
+        for (j = 0; j < tubularSegments; j++) {
+            var jNext = (j + 1) % tubularSegments;
+            var a = i * tubularSegments + j;
+            var b = (i + 1) * tubularSegments + j;
+            var c = (i + 1) * tubularSegments + jNext;
+            var d = i * tubularSegments + jNext;
+
+            indices.push(d); indices.push(b); indices.push(a);
+            indices.push(d); indices.push(c); indices.push(b);
+        }
+    }
+
+    // Normals
+    VertexData.ComputeNormals(positions, indices, normals);
+
+    // Sides
+    VertexData._ComputeSides(sideOrientation, positions, indices, normals, uvs, options.frontUVs, options.backUVs);
+
+    // Result
+    var vertexData = new VertexData();
+
+    vertexData.indices = indices;
+    vertexData.positions = positions;
+    vertexData.normals = normals;
+    vertexData.uvs = uvs;
+
+    return vertexData;
+};
+
+Mesh.CreateTorusKnot = (name: string, radius: number, tube: number, radialSegments: number, tubularSegments: number, p: number, q: number, scene?: Scene, updatable?: boolean, sideOrientation?: number): Mesh => {
+    var options = {
+        radius: radius,
+        tube: tube,
+        radialSegments: radialSegments,
+        tubularSegments: tubularSegments,
+        p: p,
+        q: q,
+        sideOrientation: sideOrientation,
+        updatable: updatable
+    };
+
+    return TorusKnotBuilder.CreateTorusKnot(name, options, scene);
+};
+
+/**
+ * Class containing static functions to help procedurally build meshes
+ */
+export class TorusKnotBuilder {
+    /**
+     * Creates a torus knot mesh
+     * * The parameter `radius` sets the global radius size (float) of the torus knot (default 2)
+     * * The parameter `radialSegments` sets the number of sides on each tube segments (positive integer, default 32)
+     * * The parameter `tubularSegments` sets the number of tubes to decompose the knot into (positive integer, default 32)
+     * * The parameters `p` and `q` are the number of windings on each axis (positive integers, default 2 and 3)
+     * * You can also set the mesh side orientation with the values : BABYLON.Mesh.FRONTSIDE (default), BABYLON.Mesh.BACKSIDE or BABYLON.Mesh.DOUBLESIDE
+     * * If you create a double-sided mesh, you can choose what parts of the texture image to crop and stick respectively on the front and the back sides with the parameters `frontUVs` and `backUVs` (Vector4). Detail here : https://doc.babylonjs.com/babylon101/discover_basic_elements#side-orientation
+     * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created.
+     * @param name defines the name of the mesh
+     * @param options defines the options used to create the mesh
+     * @param scene defines the hosting scene
+     * @returns the torus knot mesh
+     * @see  https://doc.babylonjs.com/how_to/set_shapes#torus-knot
+     */
+    public static CreateTorusKnot(name: string, options: { radius?: number, tube?: number, radialSegments?: number, tubularSegments?: number, p?: number, q?: number, updatable?: boolean, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4 }, scene: any): Mesh {
+        var torusKnot = new Mesh(name, scene);
+
+        options.sideOrientation = Mesh._GetDefaultSideOrientation(options.sideOrientation);
+        torusKnot._originalBuilderSideOrientation = options.sideOrientation;
+
+        var vertexData = VertexData.CreateTorusKnot(options);
+
+        vertexData.applyToMesh(torusKnot, options.updatable);
+
+        return torusKnot;
+    }
+}

+ 161 - 0
src/Meshes/Builders/tubeBuilder.ts

@@ -0,0 +1,161 @@
+import { Nullable } from "../../types";
+import { Scene } from "../../scene";
+import { Vector3, Tmp, Vector4, Path3D, Matrix } from "../../Maths/math";
+import { Mesh, _CreationDataStorage } from "../mesh";
+import { RibbonBuilder } from "./ribbonBuilder";
+
+Mesh.CreateTube = (name: string, path: Vector3[], radius: number, tessellation: number, radiusFunction: { (i: number, distance: number): number; }, cap: number, scene: Scene, updatable?: boolean, sideOrientation?: number, instance?: Mesh): Mesh => {
+    var options = {
+        path: path,
+        radius: radius,
+        tessellation: tessellation,
+        radiusFunction: radiusFunction,
+        arc: 1,
+        cap: cap,
+        updatable: updatable,
+        sideOrientation: sideOrientation,
+        instance: instance
+    };
+    return TubeBuilder.CreateTube(name, options, scene);
+};
+
+/**
+ * Class containing static functions to help procedurally build meshes
+ */
+export class TubeBuilder {
+    /**
+     * Creates a tube mesh.
+     * The tube is a parametric shape. It has no predefined shape. Its final shape will depend on the input parameters
+     * * The parameter `path` is a required array of successive Vector3. It is the curve used as the axis of the tube
+     * * The parameter `radius` (positive float, default 1) sets the tube radius size
+     * * The parameter `tessellation` (positive float, default 64) is the number of sides on the tubular surface
+     * * The parameter `radiusFunction` (javascript function, default null) is a vanilla javascript function. If it is not null, it overwrittes the parameter `radius`
+     * * This function is called on each point of the tube path and is passed the index `i` of the i-th point and the distance of this point from the first point of the path. It must return a radius value (positive float)
+     * * The parameter `arc` (positive float, maximum 1, default 1) is the ratio to apply to the tube circumference : 2 x PI x arc
+     * * The parameter `cap` sets the way the extruded shape is capped. Possible values : BABYLON.Mesh.NO_CAP (default), BABYLON.Mesh.CAP_START, BABYLON.Mesh.CAP_END, BABYLON.Mesh.CAP_ALL
+     * * The optional parameter `instance` is an instance of an existing Tube object to be updated with the passed `pathArray` parameter : https://doc.babylonjs.com/how_to/how_to_dynamically_morph_a_mesh#tube
+     * * You can also set the mesh side orientation with the values : BABYLON.Mesh.FRONTSIDE (default), BABYLON.Mesh.BACKSIDE or BABYLON.Mesh.DOUBLESIDE
+     * * If you create a double-sided mesh, you can choose what parts of the texture image to crop and stick respectively on the front and the back sides with the parameters `frontUVs` and `backUVs` (Vector4). Detail here : https://doc.babylonjs.com/babylon101/discover_basic_elements#side-orientation
+     * * The optional parameter `invertUV` (boolean, default false) swaps in the geometry the U and V coordinates to apply a texture
+     * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created
+     * @param name defines the name of the mesh
+     * @param options defines the options used to create the mesh
+     * @param scene defines the hosting scene
+     * @returns the tube mesh
+     * @see https://doc.babylonjs.com/how_to/parametric_shapes
+     * @see https://doc.babylonjs.com/how_to/set_shapes#tube
+     */
+    public static CreateTube(name: string, options: { path: Vector3[], radius?: number, tessellation?: number, radiusFunction?: { (i: number, distance: number): number; }, cap?: number, arc?: number, updatable?: boolean, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4, instance?: Mesh, invertUV?: boolean }, scene: Scene): Mesh {
+        var path = options.path;
+        var instance = options.instance;
+        var radius = 1.0;
+
+        if (options.radius !== undefined) {
+            radius = options.radius;
+        } else if (instance) {
+            radius = instance._creationDataStorage!.radius;
+        }
+
+        var tessellation = options.tessellation || 64 | 0;
+        var radiusFunction = options.radiusFunction || null;
+        var cap = options.cap || Mesh.NO_CAP;
+        var invertUV = options.invertUV || false;
+        var updatable = options.updatable;
+        var sideOrientation = Mesh._GetDefaultSideOrientation(options.sideOrientation);
+        options.arc = options.arc && (options.arc <= 0.0 || options.arc > 1.0) ? 1.0 : options.arc || 1.0;
+
+        // tube geometry
+        var tubePathArray = (path: Vector3[], path3D: Path3D, circlePaths: Vector3[][], radius: number, tessellation: number,
+            radiusFunction: Nullable<{ (i: number, distance: number): number; }>, cap: number, arc: number) => {
+            var tangents = path3D.getTangents();
+            var normals = path3D.getNormals();
+            var distances = path3D.getDistances();
+            var pi2 = Math.PI * 2;
+            var step = pi2 / tessellation * arc;
+            var returnRadius: { (i: number, distance: number): number; } = () => radius;
+            var radiusFunctionFinal: { (i: number, distance: number): number; } = radiusFunction || returnRadius;
+
+            var circlePath: Vector3[];
+            var rad: number;
+            var normal: Vector3;
+            var rotated: Vector3;
+            var rotationMatrix: Matrix = Tmp.Matrix[0];
+            var index = (cap === Mesh.NO_CAP || cap === Mesh.CAP_END) ? 0 : 2;
+            for (var i = 0; i < path.length; i++) {
+                rad = radiusFunctionFinal(i, distances[i]); // current radius
+                circlePath = Array<Vector3>();              // current circle array
+                normal = normals[i];                        // current normal
+                for (var t = 0; t < tessellation; t++) {
+                    Matrix.RotationAxisToRef(tangents[i], step * t, rotationMatrix);
+                    rotated = circlePath[t] ? circlePath[t] : Vector3.Zero();
+                    Vector3.TransformCoordinatesToRef(normal, rotationMatrix, rotated);
+                    rotated.scaleInPlace(rad).addInPlace(path[i]);
+                    circlePath[t] = rotated;
+                }
+                circlePaths[index] = circlePath;
+                index++;
+            }
+            // cap
+            var capPath = (nbPoints: number, pathIndex: number): Array<Vector3> => {
+                var pointCap = Array<Vector3>();
+                for (var i = 0; i < nbPoints; i++) {
+                    pointCap.push(path[pathIndex]);
+                }
+                return pointCap;
+            };
+            switch (cap) {
+                case Mesh.NO_CAP:
+                    break;
+                case Mesh.CAP_START:
+                    circlePaths[0] = capPath(tessellation, 0);
+                    circlePaths[1] = circlePaths[2].slice(0);
+                    break;
+                case Mesh.CAP_END:
+                    circlePaths[index] = circlePaths[index - 1].slice(0);
+                    circlePaths[index + 1] = capPath(tessellation, path.length - 1);
+                    break;
+                case Mesh.CAP_ALL:
+                    circlePaths[0] = capPath(tessellation, 0);
+                    circlePaths[1] = circlePaths[2].slice(0);
+                    circlePaths[index] = circlePaths[index - 1].slice(0);
+                    circlePaths[index + 1] = capPath(tessellation, path.length - 1);
+                    break;
+                default:
+                    break;
+            }
+            return circlePaths;
+        };
+
+        var path3D;
+        var pathArray;
+        if (instance) { // tube update
+            let storage = instance._creationDataStorage!;
+            var arc = options.arc || storage.arc;
+            path3D = storage.path3D.update(path);
+            pathArray = tubePathArray(path, path3D, storage.pathArray, radius, storage.tessellation, radiusFunction, storage.cap, arc);
+            instance = RibbonBuilder.CreateRibbon("", { pathArray: pathArray, instance: instance });
+            // Update mode, no need to recreate the storage.
+            storage.path3D = path3D;
+            storage.pathArray = pathArray;
+            storage.arc = arc;
+            storage.radius = radius;
+
+            return instance;
+        }
+
+        // tube creation
+        path3D = <any>new Path3D(path);
+        var newPathArray = new Array<Array<Vector3>>();
+        cap = (cap < 0 || cap > 3) ? 0 : cap;
+        pathArray = tubePathArray(path, path3D, newPathArray, radius, tessellation, radiusFunction, cap, options.arc);
+        var tube = RibbonBuilder.CreateRibbon(name, { pathArray: pathArray, closePath: true, closeArray: false, updatable: updatable, sideOrientation: sideOrientation, invertUV: invertUV, frontUVs: options.frontUVs, backUVs: options.backUVs }, scene);
+        tube._creationDataStorage!.pathArray = pathArray;
+        tube._creationDataStorage!.path3D = path3D;
+        tube._creationDataStorage!.tessellation = tessellation;
+        tube._creationDataStorage!.cap = cap;
+        tube._creationDataStorage!.arc = options.arc;
+        tube._creationDataStorage!.radius = radius;
+
+        return tube;
+    }
+}

+ 1 - 769
src/Meshes/geometry.ts

@@ -1474,772 +1474,4 @@ export class Geometry implements IGetSetVerticesData {
 
         return geometry;
     }
-}
-
-// Primitives
-
-/// Abstract class
-/**
- * Abstract class used to provide common services for all typed geometries
- * @hidden
- */
-export class _PrimitiveGeometry extends Geometry {
-
-    private _beingRegenerated: boolean;
-
-    /**
-     * Creates a new typed geometry
-     * @param id defines the unique ID of the geometry
-     * @param scene defines the hosting scene
-     * @param _canBeRegenerated defines if the geometry supports being regenerated with new parameters (false by default)
-     * @param mesh defines the hosting mesh (can be null)
-     */
-    constructor(id: string, scene: Scene, private _canBeRegenerated: boolean = false, mesh: Nullable<Mesh> = null) {
-        super(id, scene, undefined, false, mesh); // updatable = false to be sure not to update vertices
-        this._beingRegenerated = true;
-        this.regenerate();
-        this._beingRegenerated = false;
-    }
-
-    /**
-     * Gets a value indicating if the geometry supports being regenerated with new parameters (false by default)
-     * @returns true if the geometry can be regenerated
-     */
-    public canBeRegenerated(): boolean {
-        return this._canBeRegenerated;
-    }
-
-    /**
-     * If the geometry supports regeneration, the function will recreates the geometry with updated parameter values
-     */
-    public regenerate(): void {
-        if (!this._canBeRegenerated) {
-            return;
-        }
-        this._beingRegenerated = true;
-        this.setAllVerticesData(this._regenerateVertexData(), false);
-        this._beingRegenerated = false;
-    }
-
-    /**
-     * Clone the geometry
-     * @param id defines the unique ID of the new geometry
-     * @returns the new geometry
-     */
-    public asNewGeometry(id: string): Geometry {
-        return super.copy(id);
-    }
-
-    // overrides
-    public setAllVerticesData(vertexData: VertexData, updatable?: boolean): void {
-        if (!this._beingRegenerated) {
-            return;
-        }
-        super.setAllVerticesData(vertexData, false);
-    }
-
-    public setVerticesData(kind: string, data: FloatArray, updatable?: boolean): void {
-        if (!this._beingRegenerated) {
-            return;
-        }
-        super.setVerticesData(kind, data, false);
-    }
-
-    // to override
-    /** @hidden */
-    public _regenerateVertexData(): VertexData {
-        throw new Error("Abstract method");
-    }
-
-    public copy(id: string): Geometry {
-        throw new Error("Must be overriden in sub-classes.");
-    }
-
-    public serialize(): any {
-        var serializationObject = super.serialize();
-
-        serializationObject.canBeRegenerated = this.canBeRegenerated();
-
-        return serializationObject;
-    }
-}
-
-/**
- * Creates a ribbon geometry
- * @description See http://doc.babylonjs.com/how_to/ribbon_tutorial, http://doc.babylonjs.com/resources/maths_make_ribbons
- */
-export class RibbonGeometry extends _PrimitiveGeometry {
-
-    /**
-     * Creates a ribbon geometry
-     * @param id defines the unique ID of the geometry
-     * @param scene defines the hosting scene
-     * @param pathArray defines the array of paths to use
-     * @param closeArray defines if the last path and the first path must be  joined
-     * @param closePath defines if the last and first points of each path in your pathArray must be joined
-     * @param offset defines the offset between points
-     * @param canBeRegenerated defines if the geometry supports being regenerated with new parameters (false by default)
-     * @param mesh defines the hosting mesh (can be null)
-     * @param side defines if the created geometry is double sided or not (default is Mesh.DEFAULTSIDE)
-     */
-    constructor(
-        id: string, scene: Scene,
-        /**
-         * Defines the array of paths to use
-         */
-        public pathArray: Vector3[][],
-        /**
-         * Defines if the last and first points of each path in your pathArray must be joined
-         */
-        public closeArray: boolean,
-        /**
-         * Defines if the last and first points of each path in your pathArray must be joined
-         */
-        public closePath: boolean,
-        /**
-         * Defines the offset between points
-         */
-        public offset: number,
-        canBeRegenerated?: boolean,
-        mesh?: Mesh,
-        /**
-         * Defines if the created geometry is double sided or not (default is Mesh.DEFAULTSIDE)
-         */
-        public side: number = VertexData.DEFAULTSIDE) {
-        super(id, scene, canBeRegenerated, mesh);
-    }
-
-    /** @hidden */
-    public _regenerateVertexData(): VertexData {
-        return VertexData.CreateRibbon({ pathArray: this.pathArray, closeArray: this.closeArray, closePath: this.closePath, offset: this.offset, sideOrientation: this.side });
-    }
-
-    public copy(id: string): Geometry {
-        return new RibbonGeometry(id, this.getScene(), this.pathArray, this.closeArray, this.closePath, this.offset, this.canBeRegenerated(), undefined, this.side);
-    }
-}
-
-/**
- * Creates a box geometry
- * @description see http://doc.babylonjs.com/how_to/set_shapes#box
- */
-export class BoxGeometry extends _PrimitiveGeometry {
-
-    /**
-     * Creates a box geometry
-     * @param id defines the unique ID of the geometry
-     * @param scene defines the hosting scene
-     * @param size defines the zise of the box (width, height and depth are the same)
-     * @param canBeRegenerated defines if the geometry supports being regenerated with new parameters (false by default)
-     * @param mesh defines the hosting mesh (can be null)
-     * @param side defines if the created geometry is double sided or not (default is Mesh.DEFAULTSIDE)
-     */
-    constructor(
-        id: string, scene: Scene,
-        /**
-         * Defines the zise of the box (width, height and depth are the same)
-         */
-        public size: number,
-        canBeRegenerated?: boolean,
-        mesh: Nullable<Mesh> = null,
-        /**
-         * Defines if the created geometry is double sided or not (default is Mesh.DEFAULTSIDE)
-         */
-        public side: number = VertexData.DEFAULTSIDE) {
-        super(id, scene, canBeRegenerated, mesh);
-    }
-
-    /** @hidden */
-    public _regenerateVertexData(): VertexData {
-        return VertexData.CreateBox({ size: this.size, sideOrientation: this.side });
-    }
-
-    public copy(id: string): Geometry {
-        return new BoxGeometry(id, this.getScene(), this.size, this.canBeRegenerated(), undefined, this.side);
-    }
-
-    public serialize(): any {
-        var serializationObject = super.serialize();
-
-        serializationObject.size = this.size;
-
-        return serializationObject;
-    }
-
-    public static Parse(parsedBox: any, scene: Scene): Nullable<BoxGeometry> {
-        if (scene.getGeometryByID(parsedBox.id)) {
-            return null; // null since geometry could be something else than a box...
-        }
-
-        var box = new BoxGeometry(parsedBox.id, scene, parsedBox.size, parsedBox.canBeRegenerated, null);
-        if (Tags) {
-            Tags.AddTagsTo(box, parsedBox.tags);
-        }
-
-        scene.pushGeometry(box, true);
-
-        return box;
-    }
-}
-
-/**
- * Creates a sphere geometry
- * @description see http://doc.babylonjs.com/how_to/set_shapes#sphere
- */
-export class SphereGeometry extends _PrimitiveGeometry {
-
-    /**
-     * Create a new sphere geometry
-     * @param id defines the unique ID of the geometry
-     * @param scene defines the hosting scene
-     * @param segments defines the number of segments to use to create the sphere
-     * @param diameter defines the diameter of the sphere
-     * @param canBeRegenerated defines if the geometry supports being regenerated with new parameters (false by default)
-     * @param mesh defines the hosting mesh (can be null)
-     * @param side defines if the created geometry is double sided or not (default is Mesh.DEFAULTSIDE)
-     */
-    constructor(
-        id: string, scene: Scene,
-        /**
-         * Defines the number of segments to use to create the sphere
-         */
-        public segments: number,
-        /**
-         * Defines the diameter of the sphere
-         */
-        public diameter: number,
-        canBeRegenerated?: boolean,
-        mesh: Nullable<Mesh> = null,
-        /**
-         * Defines if the created geometry is double sided or not (default is Mesh.DEFAULTSIDE)
-         */
-        public side: number = VertexData.DEFAULTSIDE) {
-        super(id, scene, canBeRegenerated, mesh);
-    }
-
-    /** @hidden */
-    public _regenerateVertexData(): VertexData {
-        return VertexData.CreateSphere({ segments: this.segments, diameter: this.diameter, sideOrientation: this.side });
-    }
-
-    public copy(id: string): Geometry {
-        return new SphereGeometry(id, this.getScene(), this.segments, this.diameter, this.canBeRegenerated(), null, this.side);
-    }
-
-    public serialize(): any {
-        var serializationObject = super.serialize();
-
-        serializationObject.segments = this.segments;
-        serializationObject.diameter = this.diameter;
-
-        return serializationObject;
-    }
-
-    public static Parse(parsedSphere: any, scene: Scene): Nullable<SphereGeometry> {
-        if (scene.getGeometryByID(parsedSphere.id)) {
-            return null; // null since geometry could be something else than a sphere...
-        }
-
-        var sphere = new SphereGeometry(parsedSphere.id, scene, parsedSphere.segments, parsedSphere.diameter, parsedSphere.canBeRegenerated, null);
-        if (Tags) {
-            Tags.AddTagsTo(sphere, parsedSphere.tags);
-        }
-
-        scene.pushGeometry(sphere, true);
-
-        return sphere;
-    }
-}
-
-/**
- * Creates a disc geometry
- * @description see http://doc.babylonjs.com/how_to/set_shapes#disc-or-regular-polygon
- */
-export class DiscGeometry extends _PrimitiveGeometry {
-
-    /**
-     * Creates a new disc geometry
-     * @param id defines the unique ID of the geometry
-     * @param scene defines the hosting scene
-     * @param radius defines the radius of the disc
-     * @param tessellation defines the tesselation factor to apply to the disc
-     * @param canBeRegenerated defines if the geometry supports being regenerated with new parameters (false by default)
-     * @param mesh defines the hosting mesh (can be null)
-     * @param side defines if the created geometry is double sided or not (default is Mesh.DEFAULTSIDE)
-     */
-    constructor(
-        id: string, scene: Scene,
-        /**
-         * Defines the radius of the disc
-         */
-        public radius: number,
-        /**
-         * Defines the tesselation factor to apply to the disc
-         */
-        public tessellation: number,
-        canBeRegenerated?: boolean,
-        mesh: Nullable<Mesh> = null,
-        /**
-         * Defines if the created geometry is double sided or not (default is Mesh.DEFAULTSIDE)
-         */
-        public side: number = VertexData.DEFAULTSIDE) {
-        super(id, scene, canBeRegenerated, mesh);
-    }
-
-    /** @hidden */
-    public _regenerateVertexData(): VertexData {
-        return VertexData.CreateDisc({ radius: this.radius, tessellation: this.tessellation, sideOrientation: this.side });
-    }
-
-    public copy(id: string): Geometry {
-        return new DiscGeometry(id, this.getScene(), this.radius, this.tessellation, this.canBeRegenerated(), null, this.side);
-    }
-}
-
-/**
- * Creates a new cylinder geometry
- * @description see http://doc.babylonjs.com/how_to/set_shapes#cylinder-or-cone
- */
-export class CylinderGeometry extends _PrimitiveGeometry {
-
-    /**
-     * Creates a new cylinder geometry
-     * @param id defines the unique ID of the geometry
-     * @param scene defines the hosting scene
-     * @param height defines the height of the cylinder
-     * @param diameterTop defines the diameter of the cylinder's top cap
-     * @param diameterBottom defines the diameter of the cylinder's bottom cap
-     * @param tessellation defines the tessellation factor to apply to the cylinder (number of radial sides)
-     * @param subdivisions defines the number of subdivisions to apply to the cylinder (number of rings) (1 by default)
-     * @param canBeRegenerated defines if the geometry supports being regenerated with new parameters (false by default)
-     * @param mesh defines the hosting mesh (can be null)
-     * @param side defines if the created geometry is double sided or not (default is Mesh.DEFAULTSIDE)
-     */
-    constructor(
-        id: string, scene: Scene,
-        /**
-         * Defines the height of the cylinder
-         */
-        public height: number,
-        /**
-         * Defines the diameter of the cylinder's top cap
-         */
-        public diameterTop: number,
-        /**
-         * Defines the diameter of the cylinder's bottom cap
-         */
-        public diameterBottom: number,
-        /**
-         * Defines the tessellation factor to apply to the cylinder
-         */
-        public tessellation: number,
-        /**
-         * Defines the number of subdivisions to apply to the cylinder (1 by default)
-         */
-        public subdivisions: number = 1,
-        canBeRegenerated?: boolean, mesh:
-            Nullable<Mesh> = null,
-        /**
-         * Defines if the created geometry is double sided or not (default is Mesh.DEFAULTSIDE)
-         */
-        public side: number = VertexData.DEFAULTSIDE) {
-        super(id, scene, canBeRegenerated, mesh);
-    }
-
-    /** @hidden */
-    public _regenerateVertexData(): VertexData {
-        return VertexData.CreateCylinder({ height: this.height, diameterTop: this.diameterTop, diameterBottom: this.diameterBottom, tessellation: this.tessellation, subdivisions: this.subdivisions, sideOrientation: this.side });
-    }
-
-    public copy(id: string): Geometry {
-        return new CylinderGeometry(id, this.getScene(), this.height, this.diameterTop, this.diameterBottom, this.tessellation, this.subdivisions, this.canBeRegenerated(), null, this.side);
-    }
-
-    public serialize(): any {
-        var serializationObject = super.serialize();
-
-        serializationObject.height = this.height;
-        serializationObject.diameterTop = this.diameterTop;
-        serializationObject.diameterBottom = this.diameterBottom;
-        serializationObject.tessellation = this.tessellation;
-
-        return serializationObject;
-    }
-
-    public static Parse(parsedCylinder: any, scene: Scene): Nullable<CylinderGeometry> {
-        if (scene.getGeometryByID(parsedCylinder.id)) {
-            return null; // null since geometry could be something else than a cylinder...
-        }
-
-        var cylinder = new CylinderGeometry(parsedCylinder.id, scene, parsedCylinder.height, parsedCylinder.diameterTop, parsedCylinder.diameterBottom, parsedCylinder.tessellation, parsedCylinder.subdivisions, parsedCylinder.canBeRegenerated, null);
-        if (Tags) {
-            Tags.AddTagsTo(cylinder, parsedCylinder.tags);
-        }
-
-        scene.pushGeometry(cylinder, true);
-
-        return cylinder;
-    }
-}
-
-/**
- * Creates a new torus geometry
- * @description see http://doc.babylonjs.com/how_to/set_shapes#torus
- */
-export class TorusGeometry extends _PrimitiveGeometry {
-
-    /**
-     * Creates a new torus geometry
-     * @param id defines the unique ID of the geometry
-     * @param scene defines the hosting scene
-     * @param diameter defines the diameter of the torus
-     * @param thickness defines the thickness of the torus (ie. internal diameter)
-     * @param tessellation defines the tesselation factor to apply to the torus (number of segments along the circle)
-     * @param canBeRegenerated defines if the geometry supports being regenerated with new parameters (false by default)
-     * @param mesh defines the hosting mesh (can be null)
-     * @param side defines if the created geometry is double sided or not (default is Mesh.DEFAULTSIDE)
-     */
-    constructor(
-        id: string, scene: Scene,
-        /**
-         * Defines the diameter of the torus
-         */
-        public diameter: number,
-        /**
-         * Defines the thickness of the torus (ie. internal diameter)
-         */
-        public thickness: number,
-        /**
-         * Defines the tesselation factor to apply to the torus
-         */
-        public tessellation: number,
-        canBeRegenerated?: boolean,
-        mesh: Nullable<Mesh> = null,
-        /**
-         * Defines if the created geometry is double sided or not (default is Mesh.DEFAULTSIDE)
-         */
-        public side: number = VertexData.DEFAULTSIDE) {
-        super(id, scene, canBeRegenerated, mesh);
-    }
-
-    /** @hidden */
-    public _regenerateVertexData(): VertexData {
-        return VertexData.CreateTorus({ diameter: this.diameter, thickness: this.thickness, tessellation: this.tessellation, sideOrientation: this.side });
-    }
-
-    public copy(id: string): Geometry {
-        return new TorusGeometry(id, this.getScene(), this.diameter, this.thickness, this.tessellation, this.canBeRegenerated(), null, this.side);
-    }
-
-    public serialize(): any {
-        var serializationObject = super.serialize();
-
-        serializationObject.diameter = this.diameter;
-        serializationObject.thickness = this.thickness;
-        serializationObject.tessellation = this.tessellation;
-
-        return serializationObject;
-    }
-
-    public static Parse(parsedTorus: any, scene: Scene): Nullable<TorusGeometry> {
-        if (scene.getGeometryByID(parsedTorus.id)) {
-            return null; // null since geometry could be something else than a torus...
-        }
-
-        var torus = new TorusGeometry(parsedTorus.id, scene, parsedTorus.diameter, parsedTorus.thickness, parsedTorus.tessellation, parsedTorus.canBeRegenerated, null);
-        if (Tags) {
-            Tags.AddTagsTo(torus, parsedTorus.tags);
-        }
-
-        scene.pushGeometry(torus, true);
-
-        return torus;
-    }
-}
-
-/**
- * Creates a new ground geometry
- * @description see http://doc.babylonjs.com/how_to/set_shapes#ground
- */
-export class GroundGeometry extends _PrimitiveGeometry {
-
-    /**
-     * Creates a new ground geometry
-     * @param id defines the unique ID of the geometry
-     * @param scene defines the hosting scene
-     * @param width defines the width of the ground
-     * @param height defines the height of the ground
-     * @param subdivisions defines the subdivisions to apply to the ground
-     * @param canBeRegenerated defines if the geometry supports being regenerated with new parameters (false by default)
-     * @param mesh defines the hosting mesh (can be null)
-     */
-    constructor(
-        id: string, scene: Scene,
-        /**
-         * Defines the width of the ground
-         */
-        public width: number,
-        /**
-         * Defines the height of the ground
-         */
-        public height: number,
-        /**
-         * Defines the subdivisions to apply to the ground
-         */
-        public subdivisions: number,
-        canBeRegenerated?: boolean,
-        mesh: Nullable<Mesh> = null) {
-        super(id, scene, canBeRegenerated, mesh);
-    }
-
-    /** @hidden */
-    public _regenerateVertexData(): VertexData {
-        return VertexData.CreateGround({ width: this.width, height: this.height, subdivisions: this.subdivisions });
-    }
-
-    public copy(id: string): Geometry {
-        return new GroundGeometry(id, this.getScene(), this.width, this.height, this.subdivisions, this.canBeRegenerated(), null);
-    }
-
-    public serialize(): any {
-        var serializationObject = super.serialize();
-
-        serializationObject.width = this.width;
-        serializationObject.height = this.height;
-        serializationObject.subdivisions = this.subdivisions;
-
-        return serializationObject;
-    }
-
-    public static Parse(parsedGround: any, scene: Scene): Nullable<GroundGeometry> {
-        if (scene.getGeometryByID(parsedGround.id)) {
-            return null; // null since geometry could be something else than a ground...
-        }
-
-        var ground = new GroundGeometry(parsedGround.id, scene, parsedGround.width, parsedGround.height, parsedGround.subdivisions, parsedGround.canBeRegenerated, null);
-        if (Tags) {
-            Tags.AddTagsTo(ground, parsedGround.tags);
-        }
-
-        scene.pushGeometry(ground, true);
-
-        return ground;
-    }
-}
-
-/**
- * Creates a tiled ground geometry
- * @description see http://doc.babylonjs.com/how_to/set_shapes#tiled-ground
- */
-export class TiledGroundGeometry extends _PrimitiveGeometry {
-
-    /**
-     * Creates a tiled ground geometry
-     * @param id defines the unique ID of the geometry
-     * @param scene defines the hosting scene
-     * @param xmin defines the minimum value on X axis
-     * @param zmin defines the minimum value on Z axis
-     * @param xmax defines the maximum value on X axis
-     * @param zmax defines the maximum value on Z axis
-     * @param subdivisions defines the subdivisions to apply to the ground (number of subdivisions (tiles) on the height and the width of the map)
-     * @param precision defines the precision to use when computing the tiles
-     * @param canBeRegenerated defines if the geometry supports being regenerated with new parameters (false by default)
-     * @param mesh defines the hosting mesh (can be null)
-     */
-    constructor(
-        id: string, scene: Scene,
-        /**
-         * Defines the minimum value on X axis
-         */
-        public xmin: number,
-        /**
-         * Defines the minimum value on Z axis
-         */
-        public zmin: number,
-        /**
-         * Defines the maximum value on X axis
-         */
-        public xmax: number,
-        /**
-         * Defines the maximum value on Z axis
-         */
-        public zmax: number,
-        /**
-         * Defines the subdivisions to apply to the ground
-         */
-        public subdivisions: { w: number; h: number; },
-        /**
-         * Defines the precision to use when computing the tiles
-         */
-        public precision: { w: number; h: number; },
-        canBeRegenerated?: boolean,
-        mesh: Nullable<Mesh> = null) {
-        super(id, scene, canBeRegenerated, mesh);
-    }
-
-    /** @hidden */
-    public _regenerateVertexData(): VertexData {
-        return VertexData.CreateTiledGround({ xmin: this.xmin, zmin: this.zmin, xmax: this.xmax, zmax: this.zmax, subdivisions: this.subdivisions, precision: this.precision });
-    }
-
-    public copy(id: string): Geometry {
-        return new TiledGroundGeometry(id, this.getScene(), this.xmin, this.zmin, this.xmax, this.zmax, this.subdivisions, this.precision, this.canBeRegenerated(), null);
-    }
-}
-
-/**
- * Creates a plane geometry
- * @description see http://doc.babylonjs.com/how_to/set_shapes#plane
- */
-export class PlaneGeometry extends _PrimitiveGeometry {
-
-    /**
-     * Creates a plane geometry
-     * @param id defines the unique ID of the geometry
-     * @param scene defines the hosting scene
-     * @param size defines the size of the plane (width === height)
-     * @param canBeRegenerated defines if the geometry supports being regenerated with new parameters (false by default)
-     * @param mesh defines the hosting mesh (can be null)
-     * @param side defines if the created geometry is double sided or not (default is Mesh.DEFAULTSIDE)
-     */
-    constructor(
-        id: string, scene: Scene,
-        /**
-         * Defines the size of the plane (width === height)
-         */
-        public size: number,
-        canBeRegenerated?: boolean,
-        mesh: Nullable<Mesh> = null,
-        /**
-         * Defines if the created geometry is double sided or not (default is Mesh.DEFAULTSIDE)
-         */
-        public side: number = VertexData.DEFAULTSIDE) {
-        super(id, scene, canBeRegenerated, mesh);
-    }
-
-    /** @hidden */
-    public _regenerateVertexData(): VertexData {
-        return VertexData.CreatePlane({ size: this.size, sideOrientation: this.side });
-    }
-
-    public copy(id: string): Geometry {
-        return new PlaneGeometry(id, this.getScene(), this.size, this.canBeRegenerated(), null, this.side);
-    }
-
-    public serialize(): any {
-        var serializationObject = super.serialize();
-
-        serializationObject.size = this.size;
-
-        return serializationObject;
-    }
-
-    public static Parse(parsedPlane: any, scene: Scene): Nullable<PlaneGeometry> {
-        if (scene.getGeometryByID(parsedPlane.id)) {
-            return null; // null since geometry could be something else than a ground...
-        }
-
-        var plane = new PlaneGeometry(parsedPlane.id, scene, parsedPlane.size, parsedPlane.canBeRegenerated, null);
-        if (Tags) {
-            Tags.AddTagsTo(plane, parsedPlane.tags);
-        }
-
-        scene.pushGeometry(plane, true);
-
-        return plane;
-    }
-}
-
-/**
- * Creates a torus knot geometry
- * @description see http://doc.babylonjs.com/how_to/set_shapes#torus-knot
- */
-export class TorusKnotGeometry extends _PrimitiveGeometry {
-
-    /**
-     * Creates a torus knot geometry
-     * @param id defines the unique ID of the geometry
-     * @param scene defines the hosting scene
-     * @param radius defines the radius of the torus knot
-     * @param tube defines the thickness of the torus knot tube
-     * @param radialSegments defines the number of radial segments
-     * @param tubularSegments defines the number of tubular segments
-     * @param p defines the first number of windings
-     * @param q defines the second number of windings
-     * @param canBeRegenerated defines if the geometry supports being regenerated with new parameters (false by default)
-     * @param mesh defines the hosting mesh (can be null)
-     * @param side defines if the created geometry is double sided or not (default is Mesh.DEFAULTSIDE)
-     */
-    constructor(
-        id: string, scene: Scene,
-        /**
-         * Defines the radius of the torus knot
-         */
-        public radius: number,
-        /**
-         * Defines the thickness of the torus knot tube
-         */
-        public tube: number,
-        /**
-         * Defines the number of radial segments
-         */
-        public radialSegments: number,
-        /**
-         * Defines the number of tubular segments
-         */
-        public tubularSegments: number,
-        /**
-         * Defines the first number of windings
-         */
-        public p: number,
-        /**
-         * Defines the second number of windings
-         */
-        public q: number,
-        canBeRegenerated?: boolean,
-        mesh: Nullable<Mesh> = null,
-        /**
-         * Defines if the created geometry is double sided or not (default is Mesh.DEFAULTSIDE)
-         */
-        public side: number = VertexData.DEFAULTSIDE) {
-        super(id, scene, canBeRegenerated, mesh);
-    }
-
-    /** @hidden */
-    public _regenerateVertexData(): VertexData {
-        return VertexData.CreateTorusKnot({ radius: this.radius, tube: this.tube, radialSegments: this.radialSegments, tubularSegments: this.tubularSegments, p: this.p, q: this.q, sideOrientation: this.side });
-    }
-
-    public copy(id: string): Geometry {
-        return new TorusKnotGeometry(id, this.getScene(), this.radius, this.tube, this.radialSegments, this.tubularSegments, this.p, this.q, this.canBeRegenerated(), null, this.side);
-    }
-
-    public serialize(): any {
-        var serializationObject = super.serialize();
-
-        serializationObject.radius = this.radius;
-        serializationObject.tube = this.tube;
-        serializationObject.radialSegments = this.radialSegments;
-        serializationObject.tubularSegments = this.tubularSegments;
-        serializationObject.p = this.p;
-        serializationObject.q = this.q;
-
-        return serializationObject;
-    }
-
-    public static Parse(parsedTorusKnot: any, scene: Scene): Nullable<TorusKnotGeometry> {
-        if (scene.getGeometryByID(parsedTorusKnot.id)) {
-            return null; // null since geometry could be something else than a ground...
-        }
-
-        var torusKnot = new TorusKnotGeometry(parsedTorusKnot.id, scene, parsedTorusKnot.radius, parsedTorusKnot.tube, parsedTorusKnot.radialSegments, parsedTorusKnot.tubularSegments, parsedTorusKnot.p, parsedTorusKnot.q, parsedTorusKnot.canBeRegenerated, null);
-        if (Tags) {
-            Tags.AddTagsTo(torusKnot, parsedTorusKnot.tags);
-        }
-
-        scene.pushGeometry(torusKnot, true);
-
-        return torusKnot;
-    }
-}
-    //}
+}

+ 2 - 1
src/Meshes/index.ts

@@ -13,4 +13,5 @@ export * from "./meshSimplification";
 export * from "./meshSimplificationSceneComponent";
 export * from "./polygonMesh";
 export * from "./subMesh";
-export * from "./transformNode";
+export * from "./transformNode";
+export * from "./Builders";

+ 19 - 3
src/Meshes/mesh.ts

@@ -130,6 +130,24 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
      */
     public static readonly CAP_ALL = 3;
 
+    /**
+     * Gets the default side orientation.
+     * @param orientation the orientation to value to attempt to get
+     * @returns the default orientation
+     * @hidden
+     */
+    public static _GetDefaultSideOrientation(orientation?: number): number {
+        if (orientation == Mesh.DOUBLESIDE) {
+            return Mesh.DOUBLESIDE;
+        }
+
+        if (orientation === undefined || orientation === null) {
+            return Mesh.FRONTSIDE;
+        }
+
+        return orientation;
+    }
+
     // Events
     private _onBeforeRenderObservable: Nullable<Observable<Mesh>>;
     private _onBeforeBindObservable: Nullable<Observable<Mesh>>;
@@ -3670,6 +3688,4 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
             this.instances.pop();
         }
     }
-}
-
-//import { MeshBuilder } from "./meshBuilder";
+}

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 21 - 1615
src/Meshes/mesh.vertexData.ts


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 47 - 1213
src/Meshes/meshBuilder.ts


+ 2 - 29
src/Misc/sceneSerializer.ts

@@ -1,6 +1,5 @@
-import { Geometry, BoxGeometry, SphereGeometry, CylinderGeometry, TorusGeometry, GroundGeometry, TorusKnotGeometry, _PrimitiveGeometry } from "../Meshes/geometry";
+import { Geometry } from "../Meshes/geometry";
 import { Mesh } from "../Meshes/mesh";
-import { Plane } from "../Maths/math";
 import { Constants } from "../Engines/constants";
 import { MultiMaterial } from "../Materials/multiMaterial";
 import { Material } from "../Materials/material";
@@ -18,33 +17,7 @@ var serializeGeometry = (geometry: Geometry, serializationGeometries: any): any
         return;
     }
 
-    if (geometry instanceof BoxGeometry) {
-        serializationGeometries.boxes.push(geometry.serialize());
-    }
-    else if (geometry instanceof SphereGeometry) {
-        serializationGeometries.spheres.push(geometry.serialize());
-    }
-    else if (geometry instanceof CylinderGeometry) {
-        serializationGeometries.cylinders.push(geometry.serialize());
-    }
-    else if (geometry instanceof TorusGeometry) {
-        serializationGeometries.toruses.push(geometry.serialize());
-    }
-    else if (geometry instanceof GroundGeometry) {
-        serializationGeometries.grounds.push(geometry.serialize());
-    }
-    else if (geometry instanceof Plane) {
-        serializationGeometries.planes.push(geometry.serialize());
-    }
-    else if (geometry instanceof TorusKnotGeometry) {
-        serializationGeometries.torusKnots.push(geometry.serialize());
-    }
-    else if (geometry instanceof _PrimitiveGeometry) {
-        throw new Error("Unknown primitive type");
-    }
-    else {
-        serializationGeometries.vertexData.push(geometry.serializeVerticeData());
-    }
+    serializationGeometries.vertexData.push(geometry.serializeVerticeData());
 
     (<any>serializedGeometries)[geometry.id] = true;
 };

+ 2 - 2
src/Particles/particleSystemSet.ts

@@ -2,7 +2,7 @@ import { Nullable } from "../types";
 import { Color3 } from "../Maths/math";
 import { TransformNode } from "../Meshes/transformNode";
 import { AbstractMesh } from "../Meshes/abstractMesh";
-import { MeshBuilder } from "../Meshes/meshBuilder";
+import { SphereBuilder } from "../Meshes/Builders/sphereBuilder";
 import { IParticleSystem } from "./IParticleSystem";
 import { GPUParticleSystem } from "./gpuParticleSystem";
 import { EngineStore } from "../Engines/engineStore";
@@ -54,7 +54,7 @@ export class ParticleSystemSet implements IDisposable {
             renderingGroupId: renderingGroupId
         };
 
-        let emitterMesh = MeshBuilder.CreateSphere("emitterSphere", { diameter: options.diameter, segments: options.segments }, scene);
+        let emitterMesh = SphereBuilder.CreateSphere("emitterSphere", { diameter: options.diameter, segments: options.segments }, scene);
         emitterMesh.renderingGroupId = renderingGroupId;
 
         var material = new StandardMaterial("emitterSphereMaterial", scene);

+ 2 - 2
src/Particles/solidParticleSystem.ts

@@ -3,7 +3,7 @@ import { Color4, Vector3, Matrix, Tmp, Quaternion, Axis } from "../Maths/math";
 import { VertexBuffer } from "../Meshes/buffer";
 import { VertexData } from "../Meshes/mesh.vertexData";
 import { Mesh } from "../Meshes/mesh";
-import { MeshBuilder } from "../Meshes/meshBuilder";
+import { DiscBuilder } from "../Meshes/Builders/discBuilder";
 import { EngineStore } from "../Engines/engineStore";
 import { Scene, IDisposable } from "../scene";
 import { DepthSortedParticle, SolidParticle, ModelShape } from "./solidParticle";
@@ -156,7 +156,7 @@ export class SolidParticleSystem implements IDisposable {
      */
     public buildMesh(): Mesh {
         if (this.nbParticles === 0) {
-            var triangle = MeshBuilder.CreateDisc("", { radius: 1, tessellation: 3 }, this._scene);
+            var triangle = DiscBuilder.CreateDisc("", { radius: 1, tessellation: 3 }, this._scene);
             this.addShape(triangle, 1);
             triangle.dispose();
         }

+ 5 - 4
src/Physics/physicsHelper.ts

@@ -3,7 +3,8 @@ import { Logger } from "../Misc/logger";
 import { Vector3 } from "../Maths/math";
 import { AbstractMesh } from "../Meshes/abstractMesh";
 import { Mesh } from "../Meshes/mesh";
-import { MeshBuilder } from "../Meshes/meshBuilder";
+import { SphereBuilder } from "../Meshes/Builders/sphereBuilder";
+import { CylinderBuilder } from "../Meshes/Builders/cylinderBuilder";
 import { Ray } from "../Culling/ray";
 import { Scene } from "../scene";
 import { IPhysicsEngine } from "./IPhysicsEngine";
@@ -281,7 +282,7 @@ export class PhysicsRadialExplosionEvent {
 
     private _prepareSphere(): void {
         if (!this._sphere) {
-            this._sphere = MeshBuilder.CreateSphere("radialExplosionEventSphere", this._sphereOptions, this._scene);
+            this._sphere = SphereBuilder.CreateSphere("radialExplosionEventSphere", this._sphereOptions, this._scene);
             this._sphere.isVisible = false;
         }
     }
@@ -516,7 +517,7 @@ export class PhysicsUpdraftEvent {
 
     private _prepareCylinder(): void {
         if (!this._cylinder) {
-            this._cylinder = MeshBuilder.CreateCylinder("updraftEventCylinder", {
+            this._cylinder = CylinderBuilder.CreateCylinder("updraftEventCylinder", {
                 height: this._height,
                 diameter: this._radius * 2,
             }, this._scene);
@@ -674,7 +675,7 @@ export class PhysicsVortexEvent {
 
     private _prepareCylinder(): void {
         if (!this._cylinder) {
-            this._cylinder = MeshBuilder.CreateCylinder("vortexEventCylinder", {
+            this._cylinder = CylinderBuilder.CreateCylinder("vortexEventCylinder", {
                 height: this._height,
                 diameter: this._radius * 2,
             }, this._scene);

+ 2 - 0
src/PostProcesses/volumetricLightScatteringPostProcess.ts

@@ -16,6 +16,8 @@ import { PostProcess } from "./postProcess";
 import { Constants } from "../Engines/constants";
 import { Scene } from "../scene";
 
+import "../Meshes/Builders/planeBuilder";
+
 import "../Shaders/depth.vertex";
 import "../Shaders/volumetricLightScattering.fragment";
 import "../Shaders/volumetricLightScatteringPass.fragment";

+ 2 - 0
src/Rendering/boundingBoxRenderer.ts

@@ -12,6 +12,8 @@ import { Effect } from "../Materials/effect";
 import { Material } from "../Materials/material";
 import { ShaderMaterial } from "../Materials/shaderMaterial";
 
+import "../Meshes/Builders/boxBuilder";
+
 import "../Shaders/color.fragment";
 import "../Shaders/color.vertex";