Преглед изворни кода

Merge pull request #6293 from BabylonJSGuide/master

Added CreateTilePlane and CreateTiledBox
David Catuhe пре 6 година
родитељ
комит
e0982a4e1c

+ 1 - 0
dist/what's new.md

@@ -66,6 +66,7 @@
 - Added per mesh culling strategy ([jerome](https://github.com/jbousquie))
 
 ## Updates
+- Added new CreateTiledPlane and CreateTiledBox ([JohnK](https://github.com/BabylonJSGuide/)
 
 ### GUI
 

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

@@ -1,4 +1,5 @@
 export * from "./boxBuilder";
+export * from "./tiledBoxBuilder";
 export * from "./discBuilder";
 export * from "./ribbonBuilder";
 export * from "./sphereBuilder";
@@ -11,6 +12,7 @@ export * from "./polygonBuilder";
 export * from "./shapeBuilder";
 export * from "./latheBuilder";
 export * from "./planeBuilder";
+export * from "./tiledPlaneBuilder";
 export * from "./groundBuilder";
 export * from "./tubeBuilder";
 export * from "./polyhedronBuilder";

+ 230 - 0
src/Meshes/Builders/tiledBoxBuilder.ts

@@ -0,0 +1,230 @@
+import { Nullable } from "../../types";
+import { Scene } from "../../scene";
+import { Matrix, Vector3, Vector4, Color4 } from "../../Maths/math";
+import { Mesh, _CreationDataStorage } from "../mesh";
+import { VertexData } from "../mesh.vertexData";
+
+VertexData.CreateTiledBox = function(options: { pattern?: number, size?: number, width?: number, height?: number, depth: number, tileSize?: number, tileWidth?: number, tileHeight?: number, faceUV?: Vector4[], faceColors?: Color4[], alignHorizontal?: number, alignVertical?: number, sideOrientation?: number }): VertexData {
+    var nbFaces = 6;
+
+    var faceUV: Vector4[] = options.faceUV || new Array<Vector4>(6);
+    var faceColors = options.faceColors;
+
+    var flipTile = options.pattern || Mesh.NO_FLIP;
+
+    var width = options.width || options.size || 1;
+    var height = options.height || options.size || 1;
+    var depth = options.depth || options.size || 1;
+    var tileWidth = options.tileWidth || options.tileSize || 1;
+    var tileHeight = options.tileHeight || options.tileSize || 1;
+    var alignH = options.alignHorizontal || 0;
+    var alignV = options.alignVertical || 0;
+
+    var sideOrientation = (options.sideOrientation === 0) ? 0 : options.sideOrientation || VertexData.DEFAULTSIDE;
+
+    // default face colors and UV if undefined
+    for (var f = 0; f < nbFaces; 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 halfWidth = width / 2;
+    var halfHeight = height / 2;
+    var halfDepth = depth / 2;
+
+    var faceVertexData: Array<VertexData> = [];
+
+    for (var f = 0; f < 2; f++) { //front and back
+        faceVertexData[f] = VertexData.CreateTiledPlane({
+            pattern: flipTile,
+            tileWidth: tileWidth,
+            tileHeight: tileHeight,
+            width: width,
+            height: height,
+            alignVertical: alignV,
+            alignHorizontal: alignH,
+            sideOrientation: sideOrientation});
+    }
+
+    for (var f = 2; f < 4; f++) { //sides
+        faceVertexData[f] = VertexData.CreateTiledPlane({
+            pattern: flipTile,
+            tileWidth: tileWidth,
+            tileHeight: tileHeight,
+            width: depth,
+            height: height,
+            alignVertical: alignV,
+            alignHorizontal: alignH,
+            sideOrientation: sideOrientation});
+    }
+
+    var baseAlignV = alignV;
+    if (alignV === Mesh.BOTTOM) {
+        baseAlignV = Mesh.TOP;
+    }
+    else if (alignV === Mesh.TOP) {
+        baseAlignV = Mesh.BOTTOM;
+    }
+
+    for (var f = 4; f < 6; f++) { //top and bottom
+        faceVertexData[f] = VertexData.CreateTiledPlane({
+            pattern: flipTile,
+            tileWidth: tileWidth,
+            tileHeight: tileHeight,
+            width: width,
+            height: depth,
+            alignVertical: baseAlignV,
+            alignHorizontal: alignH,
+            sideOrientation: sideOrientation});
+    }
+
+    var positions: Array<number> = [];
+    var normals: Array<number> = [];
+    var uvs: Array<number> = [];
+    var indices: Array<number> = [];
+    var colors: Array<number> = [];
+    var facePositions: Array<Array<Vector3>> = [];
+    var faceNormals: Array<Array<Vector3>> = [];
+
+    var newFaceUV: Array<Array<number>> = [];
+    var len: number = 0;
+    var lu: number = 0;
+
+    var li: number = 0;
+
+    for (var f = 0; f < nbFaces; f++) {
+        var len = faceVertexData[f].positions!.length;
+        facePositions[f] = [];
+        faceNormals[f] = [];
+        for (var p = 0; p < len / 3; p++) {
+            facePositions[f].push(new Vector3(faceVertexData[f].positions![ 3 * p], faceVertexData[f].positions![ 3 * p + 1], faceVertexData[f].positions![ 3 * p + 2]));
+            faceNormals[f].push(new Vector3(faceVertexData[f].normals![ 3 * p], faceVertexData[f].normals![ 3 * p + 1], faceVertexData[f].normals![ 3 * p + 2]));
+        }
+        // uvs
+        lu = faceVertexData[f].uvs!.length;
+        newFaceUV[f] = [];
+        for (var i = 0; i < lu; i += 2) {
+            newFaceUV[f][i] = faceUV[f].x + (faceUV[f].z - faceUV[f].x) * faceVertexData[f].uvs![i];
+            newFaceUV[f][i + 1] = faceUV[f].y + (faceUV[f].w - faceUV[f].y) * faceVertexData[f].uvs![i + 1];
+        }
+        uvs = uvs.concat(newFaceUV[f]);
+        indices = indices.concat(<Array<number>>faceVertexData[f].indices!.map((x: number) => x + li));
+        li += facePositions[f].length;
+        if (faceColors) {
+            for (var c = 0; c < 4; c++) {
+                colors.push(faceColors[f].r, faceColors[f].g, faceColors[f].b, faceColors[f].a);
+            }
+        }
+    }
+
+    var vec0 = new Vector3(0, 0, halfDepth);
+    var mtrx0 = Matrix.RotationY(Math.PI);
+    positions = facePositions[0].map((entry) => Vector3.TransformNormal(entry, mtrx0).add(vec0)).map((entry) => [entry.x, entry.y, entry.z]).reduce(
+        (accumulator: Array<number>, currentValue) => accumulator.concat(currentValue),
+        []
+    );
+    normals = faceNormals[0].map((entry) => Vector3.TransformNormal(entry, mtrx0)).map((entry) => [entry.x, entry.y, entry.z]).reduce(
+        (accumulator: Array<number>, currentValue) => accumulator.concat(currentValue),
+        []
+    );
+    positions = positions.concat(facePositions[1].map((entry) => entry.subtract(vec0)).map((entry) => [entry.x, entry.y, entry.z]).reduce(
+        (accumulator: Array<number>, currentValue) => accumulator.concat(currentValue),
+        []
+    ));
+    normals = normals.concat(faceNormals[1].map((entry) => [entry.x, entry.y, entry.z]).reduce(
+        (accumulator: Array<number>, currentValue) => accumulator.concat(currentValue),
+        []
+    ));
+
+    var vec2 = new Vector3(halfWidth, 0, 0);
+    var mtrx2 = Matrix.RotationY(-Math.PI / 2);
+    positions = positions.concat(facePositions[2].map((entry) => Vector3.TransformNormal(entry, mtrx2).add(vec2)).map((entry) => [entry.x, entry.y, entry.z]).reduce(
+        (accumulator: Array<number>, currentValue) => accumulator.concat(currentValue),
+        []
+    ));
+    normals = normals.concat(faceNormals[2].map((entry) => Vector3.TransformNormal(entry, mtrx2)).map((entry) => [entry.x, entry.y, entry.z]).reduce(
+        (accumulator: Array<number>, currentValue) => accumulator.concat(currentValue),
+        []
+    ));
+    var mtrx3 = Matrix.RotationY(Math.PI / 2);
+    positions = positions.concat(facePositions[3].map((entry) => Vector3.TransformNormal(entry, mtrx3).subtract(vec2)).map((entry) => [entry.x, entry.y, entry.z]).reduce(
+        (accumulator: Array<number>, currentValue) => accumulator.concat(currentValue),
+        []
+    ));
+    normals = normals.concat(faceNormals[3].map((entry) => Vector3.TransformNormal(entry, mtrx3)).map((entry) => [entry.x, entry.y, entry.z]).reduce(
+        (accumulator: Array<number>, currentValue) => accumulator.concat(currentValue),
+        []
+    ));
+
+    var vec4 = new Vector3(0, halfHeight, 0);
+    var mtrx4 = Matrix.RotationX(Math.PI / 2);
+    positions = positions.concat(facePositions[4].map((entry) => Vector3.TransformNormal(entry, mtrx4).add(vec4)).map((entry) => [entry.x, entry.y, entry.z]).reduce(
+        (accumulator: Array<number>, currentValue) => accumulator.concat(currentValue),
+        []
+    ));
+    normals = normals.concat(faceNormals[4].map((entry) => Vector3.TransformNormal(entry, mtrx4)).map((entry) => [entry.x, entry.y, entry.z]).reduce(
+        (accumulator: Array<number>, currentValue) => accumulator.concat(currentValue),
+        []
+    ));
+    var mtrx5 = Matrix.RotationX(-Math.PI / 2);
+    positions = positions.concat(facePositions[5].map((entry) => Vector3.TransformNormal(entry, mtrx5).subtract(vec4)).map((entry) => [entry.x, entry.y, entry.z]).reduce(
+        (accumulator: Array<number>, currentValue) => accumulator.concat(currentValue),
+        []
+    ));
+    normals = normals.concat(faceNormals[5].map((entry) => Vector3.TransformNormal(entry, mtrx5)).map((entry) => [entry.x, entry.y, entry.z]).reduce(
+        (accumulator: Array<number>, currentValue) => accumulator.concat(currentValue),
+        []
+    ));
+
+    // sides
+    VertexData._ComputeSides(sideOrientation, positions, indices, normals, uvs);
+
+    // 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;
+};
+
+/**
+ * Class containing static functions to help procedurally build meshes
+ */
+export class TiledBoxBuilder {
+    /**
+     * Creates a box mesh
+     * faceTiles sets the pattern, tile size and number of tiles for a face     * * 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
+     * @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 CreateTiledBox(name: string, options: { pattern?: number, width?: number, height?: number, depth?: number, tileSize?: number, tileWidth?: number, tileHeight?: number, alignHorizontal?: number, alignVertical?: number, faceUV?: Vector4[], faceColors?: Color4[], sideOrientation?: number, 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.CreateTiledBox(options);
+
+        vertexData.applyToMesh(box, options.updatable);
+
+        return box;
+    }
+}

+ 439 - 0
src/Meshes/Builders/tiledPlaneBuilder.ts

@@ -0,0 +1,439 @@
+import { Nullable } from "../../types";
+import { Scene } from "../../scene";
+import { Vector4 } from "../../Maths/math";
+import { Mesh, _CreationDataStorage } from "../mesh";
+import { VertexData } from "../mesh.vertexData";
+
+VertexData.CreateTiledPlane = function(options: { pattern?: number, tileSize?: number, tileWidth?: number, tileHeight?: number, size?: number, width?: number, height?: number, alignHorizontal?: number, alignVertical?: number, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4}): VertexData {
+
+    var flipTile = options.pattern || Mesh.NO_FLIP;
+    var tileWidth = options.tileWidth || options.tileSize || 1;
+    var tileHeight = options.tileHeight || options.tileSize || 1;
+    var alignH = options.alignHorizontal || 0;
+    var alignV = options.alignVertical || 0;
+
+    var width = options.width || options.size || 1;
+    var tilesX = Math.floor(width / tileWidth);
+    var offsetX = width - tilesX * tileWidth;
+
+    var height = options.height || options.size || 1;
+    var tilesY = Math.floor(height / tileHeight);
+    var offsetY = height - tilesY * tileHeight;
+
+    var halfWidth = tileWidth * tilesX / 2;
+    var halfHeight = tileHeight * tilesY / 2;
+
+    var adjustX = 0;
+    var adjustY = 0;
+    var startX = 0;
+    var startY = 0;
+    var endX = 0;
+    var endY = 0;
+
+    //Part Tiles
+    if (offsetX > 0 || offsetY > 0) {
+        startX = -halfWidth;
+        startY = -halfHeight;
+        var endX = halfWidth;
+        var endY = halfHeight;
+
+        switch (alignH) {
+            case Mesh.CENTER:
+                offsetX /= 2;
+                startX -= offsetX;
+                endX += offsetX;
+                break;
+            case Mesh.LEFT:
+                endX += offsetX;
+                adjustX = -offsetX / 2;
+                break;
+            case Mesh.RIGHT:
+                startX -= offsetX;
+                adjustX = offsetX / 2;
+                break;
+        }
+
+        switch (alignV) {
+            case Mesh.CENTER:
+                offsetY /= 2;
+                startY -= offsetY;
+                endY += offsetY;
+                break;
+            case Mesh.BOTTOM:
+                endY += offsetY;
+                adjustY = -offsetY / 2;
+                break;
+            case Mesh.TOP:
+                startY -= offsetY;
+                adjustY = offsetY / 2;
+                break;
+        }
+    }
+
+    var positions = [];
+    var normals = [];
+    var uvBase = [];
+    uvBase[0] = [0, 0, 1, 0, 1, 1, 0, 1];
+    uvBase[1] = [0, 0, 1, 0, 1, 1, 0, 1];
+    if (flipTile === Mesh.ROTATE_TILE || flipTile === Mesh.ROTATE_ROW) {
+        uvBase[1] = [1, 1, 0, 1, 0, 0, 1, 0];
+    }
+    if (flipTile === Mesh.FLIP_TILE || flipTile === Mesh.FLIP_ROW) {
+        uvBase[1] = [1, 0, 0, 0, 0, 1, 1, 1];
+    }
+    if (flipTile === Mesh.FLIP_N_ROTATE_TILE || flipTile === Mesh.FLIP_N_ROTATE_ROW) {
+        uvBase[1] = [0, 1, 1, 1, 1, 0, 0, 0];
+    }
+    var uvs: Array<number> = [];
+    var colors = [];
+    var indices = [];
+    var index = 0;
+    for (var y = 0; y < tilesY; y++) {
+        for (var x = 0; x < tilesX; x++) {
+            positions.push(-halfWidth + x * tileWidth + adjustX, -halfHeight + y * tileHeight + adjustY, 0);
+            positions.push(-halfWidth + (x + 1) * tileWidth + adjustX, -halfHeight + y * tileHeight + adjustY, 0);
+            positions.push(-halfWidth + (x + 1) * tileWidth + adjustX, -halfHeight + (y + 1) * tileHeight + adjustY, 0);
+            positions.push(-halfWidth + x * tileWidth + adjustX, -halfHeight + (y + 1) * tileHeight + adjustY, 0);
+            indices.push(index, index + 1, index + 3, index + 1, index + 2, index + 3);
+            if (flipTile === Mesh.FLIP_TILE || flipTile === Mesh.ROTATE_TILE || flipTile === Mesh.FLIP_N_ROTATE_TILE) {
+                uvs = uvs.concat(uvBase[(x % 2 + y % 2) % 2]);
+            }
+            else if (flipTile === Mesh.FLIP_ROW || flipTile === Mesh.ROTATE_ROW || flipTile === Mesh.FLIP_N_ROTATE_ROW) {
+                uvs = uvs.concat(uvBase[y % 2]);
+            }
+            else {
+
+                uvs = uvs.concat(uvBase[0]);
+            }
+            colors.push(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+            normals.push(0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1);
+            index += 4;
+        }
+    }
+
+    //Part Tiles
+    if (offsetX > 0 || offsetY > 0) {
+
+        var partialBottomRow: boolean = (offsetY > 0 && (alignV === Mesh.CENTER || alignV === Mesh.TOP));
+        var partialTopRow: boolean = (offsetY > 0 && (alignV === Mesh.CENTER || alignV === Mesh.BOTTOM));
+        var partialLeftCol: boolean = (offsetX > 0 && (alignH === Mesh.CENTER || alignH === Mesh.RIGHT));
+        var partialRightCol: boolean = (offsetX > 0 && (alignH === Mesh.CENTER || alignH === Mesh.LEFT));
+        var uvPart: Array<number> = [];
+        var a, b, c, d: number;
+
+        //corners
+        if (partialBottomRow && partialLeftCol) { //bottom left corner
+            positions.push(startX + adjustX, startY + adjustY, 0);
+            positions.push(-halfWidth + adjustX, startY + adjustY, 0);
+            positions.push(-halfWidth + adjustX, startY + offsetY + adjustY, 0);
+            positions.push(startX + adjustX, startY + offsetY + adjustY, 0);
+            indices.push(index, index + 1, index + 3, index + 1, index + 2, index + 3);
+            index += 4;
+            a = 1 - offsetX / tileWidth;
+            b = 1 - offsetY / tileHeight;
+            c = 1;
+            d = 1;
+            uvPart = [a, b, c, b, c, d, a, d];
+            if (flipTile === Mesh.ROTATE_ROW) {
+                uvPart = [1 - a, 1 - b, 1 - c, 1 - b, 1 - c, 1 - d, 1 - a, 1 - d];
+            }
+            if (flipTile === Mesh.FLIP_ROW) {
+                uvPart = [1 - a, b, 1 - c, b, 1 - c, d, 1 - a, d];
+            }
+            if (flipTile === Mesh.FLIP_N_ROTATE_ROW) {
+                uvPart = [a, 1 - b, c, 1 - b, c, 1 - d, a, 1 - d];
+            }
+            uvs = uvs.concat(uvPart);
+            colors.push(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+            normals.push(0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1);
+        }
+
+        if (partialBottomRow && partialRightCol) { //bottom right corner
+            positions.push(halfWidth + adjustX, startY + adjustY, 0);
+            positions.push(endX + adjustX, startY + adjustY, 0);
+            positions.push(endX + adjustX, startY + offsetY + adjustY, 0);
+            positions.push(halfWidth + adjustX, startY + offsetY + adjustY, 0);
+            indices.push(index, index + 1, index + 3, index + 1, index + 2, index + 3);
+            index += 4;
+            a = 0,
+            b = 1 - offsetY / tileHeight;
+            c = offsetX / tileWidth;
+            d = 1;
+            uvPart = [a, b, c, b, c, d, a, d];
+            if (flipTile === Mesh.ROTATE_ROW || (flipTile === Mesh.ROTATE_TILE && (tilesX % 2) === 0)) {
+                uvPart = [1 - a, 1 - b, 1 - c, 1 - b, 1 - c, 1 - d, 1 - a, 1 - d];
+            }
+            if (flipTile === Mesh.FLIP_ROW || (flipTile === Mesh.FLIP_TILE && (tilesX % 2) === 0)) {
+                uvPart = [1 - a, b, 1 - c, b, 1 - c, d, 1 - a, d];
+            }
+            if (flipTile === Mesh.FLIP_N_ROTATE_ROW || (flipTile === Mesh.FLIP_N_ROTATE_TILE && (tilesX % 2) === 0)) {
+                uvPart = [a, 1 - b, c, 1 - b, c, 1 - d, a, 1 - d];
+            }
+            uvs = uvs.concat(uvPart);
+            colors.push(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+            normals.push(0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1);
+        }
+
+        if (partialTopRow && partialLeftCol) {//top left corner
+            positions.push(startX + adjustX, halfHeight + adjustY, 0);
+            positions.push(-halfWidth + adjustX, halfHeight + adjustY, 0);
+            positions.push(-halfWidth + adjustX, endY + adjustY, 0);
+            positions.push(startX + adjustX, endY + adjustY, 0);
+            indices.push(index, index + 1, index + 3, index + 1, index + 2, index + 3);
+            index += 4;
+            a = 1 - offsetX / tileWidth;
+            b = 0;
+            c = 1;
+            d = offsetY / tileHeight;
+            uvPart = [a, b, c, b, c, d, a, d];
+            if ((flipTile === Mesh.ROTATE_ROW && (tilesY % 2) === 1) || (flipTile === Mesh.ROTATE_TILE && (tilesY % 1) === 0)) {
+                uvPart = [1 - a, 1 - b, 1 - c, 1 - b, 1 - c, 1 - d, 1 - a, 1 - d];
+            }
+            if ((flipTile === Mesh.FLIP_ROW && (tilesY % 2) === 1) || (flipTile === Mesh.FLIP_TILE && (tilesY % 2) === 0)) {
+                uvPart = [1 - a, b, 1 - c, b, 1 - c, d, 1 - a, d];
+            }
+            if ((flipTile === Mesh.FLIP_N_ROTATE_ROW && (tilesY % 2) === 1) || (flipTile === Mesh.FLIP_N_ROTATE_TILE && (tilesY % 2) === 0)) {
+                uvPart = [a, 1 - b, c, 1 - b, c, 1 - d, a, 1 - d];
+            }
+            uvs = uvs.concat(uvPart);
+            colors.push(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+            normals.push(0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1);
+        }
+
+        if (partialTopRow && partialRightCol) { //top right corner
+            positions.push(halfWidth + adjustX, halfHeight + adjustY, 0);
+            positions.push(endX + adjustX, halfHeight + adjustY, 0);
+            positions.push(endX + adjustX, endY + adjustY, 0);
+            positions.push(halfWidth + adjustX, endY + adjustY, 0);
+            indices.push(index, index + 1, index + 3, index + 1, index + 2, index + 3);
+            index += 4;
+            a = 0,
+            b = 0;
+            c = offsetX / tileWidth;
+            d = offsetY / tileHeight;
+            uvPart = [a, b, c, b, c, d, a, d];
+            if ((flipTile === Mesh.ROTATE_ROW && (tilesY % 2) === 1) || (flipTile === Mesh.ROTATE_TILE && (tilesY + tilesX) % 2 === 1)) {
+                uvPart = [1 - a, 1 - b, 1 - c, 1 - b, 1 - c, 1 - d, 1 - a, 1 - d];
+            }
+            if ((flipTile === Mesh.FLIP_ROW && (tilesY % 2) === 1) || (flipTile === Mesh.FLIP_TILE && (tilesY + tilesX) % 2 === 1)) {
+                uvPart = [1 - a, b, 1 - c, b, 1 - c, d, 1 - a, d];
+            }
+            if ((flipTile === Mesh.FLIP_N_ROTATE_ROW && (tilesY % 2) === 1) || (flipTile === Mesh.FLIP_N_ROTATE_TILE && (tilesY + tilesX) % 2 === 1)) {
+                uvPart = [a, 1 - b, c, 1 - b, c, 1 - d, a, 1 - d];
+            }
+            uvs = uvs.concat(uvPart);
+            colors.push(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+            normals.push(0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1);
+        }
+
+        //part rows
+        if (partialBottomRow) {
+            var uvBaseBR = [];
+            a = 0;
+            b = 1 - offsetY / tileHeight;
+            c = 1;
+            d = 1;
+            uvBaseBR[0] = [a, b, c, b, c, d, a, d];
+            uvBaseBR[1] = [a, b, c, b, c, d, a, d];
+            if (flipTile === Mesh.ROTATE_TILE || flipTile === Mesh.ROTATE_ROW) {
+                uvBaseBR[1] = [1 - a, 1 - b, 1 - c, 1 - b, 1 - c, 1 - d, 1 - a, 1 - d];
+            }
+            if (flipTile === Mesh.FLIP_TILE || flipTile === Mesh.FLIP_ROW) {
+                uvBaseBR[1] = [1 - a, b, 1 - c, b, 1 - c, d, 1 - a, d];
+            }
+            if (flipTile === Mesh.FLIP_N_ROTATE_TILE || flipTile === Mesh.FLIP_N_ROTATE_ROW) {
+                uvBaseBR[1] = [a, 1 - b, c, 1 - b, c, 1 - d, a, 1 - d];
+            }
+            for (var x = 0; x < tilesX; x++) {
+                positions.push(-halfWidth + x * tileWidth + adjustX, startY + adjustY, 0);
+                positions.push(-halfWidth + (x + 1) * tileWidth + adjustX, startY + adjustY, 0);
+                positions.push(-halfWidth + (x + 1) * tileWidth + adjustX, startY + offsetY + adjustY, 0);
+                positions.push(-halfWidth + x * tileWidth + adjustX, startY + offsetY + adjustY, 0);
+                indices.push(index, index + 1, index + 3, index + 1, index + 2, index + 3);
+                index += 4;
+                if (flipTile === Mesh.FLIP_TILE || flipTile === Mesh.ROTATE_TILE || flipTile === Mesh.FLIP_N_ROTATE_TILE) {
+                    uvs = uvs.concat(uvBaseBR[(x + 1) % 2]);
+                }
+                else if (flipTile === Mesh.FLIP_ROW || flipTile === Mesh.ROTATE_ROW || flipTile === Mesh.FLIP_N_ROTATE_ROW) {
+                    uvs = uvs.concat(uvBaseBR[1]);
+                }
+                else {
+                    uvs = uvs.concat(uvBaseBR[0]);
+                }
+                colors.push(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+                normals.push(0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1);
+            }
+        }
+
+        if (partialTopRow) {
+            var uvBaseTR = [];
+            a = 0;
+            b = 0;
+            c = 1;
+            d = offsetY / tileHeight;
+            uvBaseTR[0] = [a, b, c, b, c, d, a, d];
+            uvBaseTR[1] = [a, b, c, b, c, d, a, d];
+            if (flipTile === Mesh.ROTATE_TILE || flipTile === Mesh.ROTATE_ROW) {
+                uvBaseTR[1] = [1 - a, 1 - b, 1 - c, 1 - b, 1 - c, 1 - d, 1 - a, 1 - d];
+            }
+            if (flipTile === Mesh.FLIP_TILE || flipTile === Mesh.FLIP_ROW) {
+                uvBaseTR[1] = [1 - a, b, 1 - c, b, 1 - c, d, 1 - a, d];
+            }
+            if (flipTile === Mesh.FLIP_N_ROTATE_TILE || flipTile === Mesh.FLIP_N_ROTATE_ROW) {
+                uvBaseTR[1] = [a, 1 - b, c, 1 - b, c, 1 - d, a, 1 - d];
+            }
+            for (var x = 0; x < tilesX; x++) {
+                positions.push(-halfWidth + x * tileWidth + adjustX, endY - offsetY + adjustY, 0);
+                positions.push(-halfWidth + (x + 1) * tileWidth + adjustX, endY - offsetY + adjustY, 0);
+                positions.push(-halfWidth + (x + 1) * tileWidth + adjustX, endY + adjustY, 0);
+                positions.push(-halfWidth + x * tileWidth + adjustX, endY + adjustY, 0);
+                indices.push(index, index + 1, index + 3, index + 1, index + 2, index + 3);
+                index += 4;
+                if (flipTile === Mesh.FLIP_TILE || flipTile === Mesh.ROTATE_TILE || flipTile === Mesh.FLIP_N_ROTATE_TILE) {
+                    uvs = uvs.concat(uvBaseTR[(x + tilesY) % 2]);
+                }
+                else if (flipTile === Mesh.FLIP_ROW || flipTile === Mesh.ROTATE_ROW || flipTile === Mesh.FLIP_N_ROTATE_ROW) {
+                    uvs = uvs.concat(uvBaseTR[tilesY % 2]);
+                }
+                else {
+                    uvs = uvs.concat(uvBaseTR[0]);
+                }
+                colors.push(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+                normals.push(0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1);
+            }
+        }
+
+        if (partialLeftCol) {
+            var uvBaseLC = [];
+            a = 1 - offsetX / tileWidth;
+            b = 0;
+            c = 1;
+            d = 1;
+            uvBaseLC[0] = [a, b, c, b, c, d, a, d];
+            uvBaseLC[1] = [a, b, c, b, c, d, a, d];
+            if (flipTile === Mesh.ROTATE_TILE || flipTile === Mesh.ROTATE_ROW) {
+                uvBaseLC[1] = [1 - a, 1 - b, 1 - c, 1 - b, 1 - c, 1 - d, 1 - a, 1 - d];
+            }
+            if (flipTile === Mesh.FLIP_TILE || flipTile === Mesh.FLIP_ROW) {
+                uvBaseLC[1] = [1 - a, b, 1 - c, b, 1 - c, d, 1 - a, d];
+            }
+            if (flipTile === Mesh.FLIP_N_ROTATE_TILE || flipTile === Mesh.FLIP_N_ROTATE_ROW) {
+                uvBaseLC[1] = [a, 1 - b, c, 1 - b, c, 1 - d, a, 1 - d];
+            }
+            for (var y = 0; y < tilesY; y++) {
+                positions.push(startX + adjustX, -halfHeight + y * tileHeight + adjustY, 0);
+                positions.push(startX + offsetX + adjustX, -halfHeight + y * tileHeight + adjustY, 0);
+                positions.push(startX + offsetX + adjustX, -halfHeight + (y + 1) * tileHeight + adjustY, 0);
+                positions.push(startX + adjustX, -halfHeight + (y + 1) * tileHeight + adjustY, 0);
+                indices.push(index, index + 1, index + 3, index + 1, index + 2, index + 3);
+                index += 4;
+                if (flipTile === Mesh.FLIP_TILE || flipTile === Mesh.ROTATE_TILE || flipTile === Mesh.FLIP_N_ROTATE_TILE) {
+                    uvs = uvs.concat(uvBaseLC[(y  + 1) % 2]);
+                }
+                else if (flipTile === Mesh.FLIP_ROW || flipTile === Mesh.ROTATE_ROW || flipTile === Mesh.FLIP_N_ROTATE_ROW) {
+                    uvs = uvs.concat(uvBaseLC[y % 2]);
+                }
+                else {
+                    uvs = uvs.concat(uvBaseLC[0]);
+                }
+                colors.push(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+                normals.push(0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1);
+            }
+        }
+
+        if (partialRightCol) {
+            var uvBaseRC = [];
+            a = 0;
+            b = 0;
+            c = offsetX / tileHeight;
+            d = 1;
+            uvBaseRC[0] = [a, b, c, b, c, d, a, d];
+            uvBaseRC[1] = [a, b, c, b, c, d, a, d];
+            if (flipTile === Mesh.ROTATE_TILE || flipTile === Mesh.ROTATE_ROW) {
+                uvBaseRC[1] = [1 - a, 1 - b, 1 - c, 1 - b, 1 - c, 1 - d, 1 - a, 1 - d];
+            }
+            if (flipTile === Mesh.FLIP_TILE || flipTile === Mesh.FLIP_ROW) {
+                uvBaseRC[1] = [1 - a, b, 1 - c, b, 1 - c, d, 1 - a, d];
+            }
+            if (flipTile === Mesh.FLIP_N_ROTATE_TILE || flipTile === Mesh.FLIP_N_ROTATE_ROW) {
+                uvBaseRC[1] = [a, 1 - b, c, 1 - b, c, 1 - d, a, 1 - d];
+            }
+            for (var y = 0; y < tilesY; y++) {
+                positions.push(endX - offsetX + adjustX, -halfHeight + y * tileHeight + adjustY, 0);
+                positions.push(endX + adjustX, -halfHeight + y * tileHeight + adjustY, 0);
+                positions.push(endX + adjustX, -halfHeight + (y + 1) * tileHeight + adjustY, 0);
+                positions.push(endX - offsetX + adjustX, -halfHeight + (y + 1) * tileHeight + adjustY, 0);
+                indices.push(index, index + 1, index + 3, index + 1, index + 2, index + 3);
+                index += 4;
+                if (flipTile === Mesh.FLIP_TILE || flipTile === Mesh.ROTATE_TILE || flipTile === Mesh.FLIP_N_ROTATE_TILE) {
+                    uvs = uvs.concat(uvBaseRC[(y + tilesX) % 2]);
+                }
+                else if (flipTile === Mesh.FLIP_ROW || flipTile === Mesh.ROTATE_ROW || flipTile === Mesh.FLIP_N_ROTATE_ROW) {
+                    uvs = uvs.concat(uvBaseRC[y % 2]);
+                }
+                else {
+                    uvs = uvs.concat(uvBaseRC[0]);
+                }
+                colors.push(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+                normals.push(0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1);
+            }
+        }
+    }
+
+    var sideOrientation = (options.sideOrientation === 0) ? 0 : options.sideOrientation || VertexData.DEFAULTSIDE;
+
+    // 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;
+
+    var totalColors = (sideOrientation === VertexData.DOUBLESIDE) ? colors.concat(colors) : colors;
+    vertexData.colors = totalColors;
+
+    return vertexData;
+};
+
+/**
+ * Class containing static functions to help procedurally build meshes
+ */
+export class TiledPlaneBuilder {
+    /**
+     * Creates a tiled plane mesh
+     * * The parameter `pattern` will, depending on value, do nothing or
+     * * * flip (reflect about central vertical) alternate tiles across and up
+     * * * flip every tile on alternate rows
+     * * * rotate (180 degs) alternate tiles across and up
+     * * * rotate every tile on alternate rows
+     * * * flip and rotate alternate tiles across and up
+     * * * flip and rotate every tile on alternate rows
+     * * The parameter `tileSize` sets the size (float) of each tile side (default 1)
+     * * You can set some different tile dimensions by using the parameters `tileWidth` and `tileHeight` (both by default have the same value of `tileSize`)
+     * * 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
+     * * sideOrientation optional and takes the values : Mesh.FRONTSIDE (default), Mesh.BACKSIDE or Mesh.DOUBLESIDE
+     * * frontUvs only usable when you create a double-sided mesh, used to choose what parts of the texture image to crop and apply on the front side, optional, default vector4 (0, 0, 1, 1)
+     * * backUVs only usable when you create a double-sided mesh, used to choose what parts of the texture image to crop and apply on the back side, optional, default vector4 (0, 0, 1, 1)
+     * @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 CreateTiledPlane(name: string, options: { pattern?: number, tileSize?: number, tileWidth?: number, tileHeight?: number, size?: number, width?: number, height?: number, alignHorizontal?: number, alignVertical?: number, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4, updatable?: boolean}, scene: Nullable<Scene> = null): Mesh {
+        var plane = new Mesh(name, scene);
+
+        options.sideOrientation = Mesh._GetDefaultSideOrientation(options.sideOrientation);
+        plane._originalBuilderSideOrientation = options.sideOrientation;
+
+        var vertexData = VertexData.CreateTiledPlane(options);
+
+        vertexData.applyToMesh(plane, options.updatable);
+
+        return plane;
+    }
+}

+ 48 - 0
src/Meshes/mesh.ts

@@ -146,6 +146,54 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
      * Mesh cap setting : two caps, one at the beginning  and one at the end of the mesh
      */
     public static readonly CAP_ALL = 3;
+    /**
+     * Mesh pattern setting : no flip or rotate
+     */
+    public static readonly NO_FLIP = 0;
+    /**
+     * Mesh pattern setting : flip (reflect in y axis) alternate tiles on each row or column
+     */
+    public static readonly FLIP_TILE = 1;
+    /**
+     * Mesh pattern setting : rotate (180degs) alternate tiles on each row or column
+     */
+    public static readonly ROTATE_TILE = 2;
+    /**
+     * Mesh pattern setting : flip (reflect in y axis) all tiles on alternate rows
+     */
+    public static readonly FLIP_ROW = 3;
+    /**
+     * Mesh pattern setting : rotate (180degs) all tiles on alternate rows
+     */
+    public static readonly ROTATE_ROW = 4;
+    /**
+     * Mesh pattern setting : flip and rotate alternate tiles on each row or column
+     */
+    public static readonly FLIP_N_ROTATE_TILE = 5;
+    /**
+     * Mesh pattern setting : rotate pattern and rotate
+     */
+    public static readonly FLIP_N_ROTATE_ROW = 6;
+    /**
+     * Mesh tile positioning : part tiles same on left/right or top/bottom
+     */
+    public static readonly CENTER = 0;
+    /**
+     * Mesh tile positioning : part tiles on left
+     */
+    public static readonly LEFT = 1;
+    /**
+     * Mesh tile positioning : part tiles on right
+     */
+    public static readonly RIGHT = 2;
+    /**
+     * Mesh tile positioning : part tiles on top
+     */
+    public static readonly TOP = 3;
+    /**
+     * Mesh tile positioning : part tiles on bottom
+     */
+    public static readonly BOTTOM = 4;
 
     /**
      * Gets the default side orientation.

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

@@ -791,6 +791,35 @@ export class VertexData {
     }
 
     /**
+     * Creates the VertexData for a tiled box
+     * @param options an object used to set the following optional parameters for the box, required but can be empty
+      * * faceTiles sets the pattern, tile size and number of tiles for a face
+      * * faceUV an array of 6 Vector4 elements used to set different images to each box side
+      * * faceColors an array of 6 Color3 elements used to set different colors to each box side
+      * * sideOrientation optional and takes the values : Mesh.FRONTSIDE (default), Mesh.BACKSIDE or Mesh.DOUBLESIDE
+     * @returns the VertexData of the box
+     */
+    public static CreateTiledBox(options: { pattern?: number, width?: number, height?: number, depth?: number, tileSize?: number, tileWidth?: number, tileHeight?: number, alignHorizontal?: number, alignVertical?: number, faceUV?: Vector4[], faceColors?: Color4[], sideOrientation?: number }): VertexData {
+        throw _DevTools.WarnImport("tiledBoxBuilder");
+    }
+
+    /**
+     * Creates the VertexData for a tiled plane
+     * @param options an object used to set the following optional parameters for the box, required but can be empty
+      * * pattern a limited pattern arrangement depending on the number
+      * * tileSize sets the width, height and depth of the tile to the value of size, optional default 1
+      * * tileWidth sets the width (x direction) of the tile, overwrites the width set by size, optional, default size
+      * * tileHeight sets the height (y direction) of the tile, overwrites the height set by size, optional, default size
+      * * sideOrientation optional and takes the values : Mesh.FRONTSIDE (default), Mesh.BACKSIDE or Mesh.DOUBLESIDE
+      * * frontUvs only usable when you create a double-sided mesh, used to choose what parts of the texture image to crop and apply on the front side, optional, default vector4 (0, 0, 1, 1)
+      * * backUVs only usable when you create a double-sided mesh, used to choose what parts of the texture image to crop and apply on the back side, optional, default vector4 (0, 0, 1, 1)
+     * @returns the VertexData of the tiled plane
+     */
+    public static CreateTiledPlane(options: { pattern?: number, tileSize?: number, tileWidth?: number, tileHeight?: number, size?: number, width?: number, height?: number, alignHorizontal?: number, alignVertical?: number, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4 }): VertexData {
+        throw _DevTools.WarnImport("tiledPlaneBuilder");
+    }
+
+    /**
      * Creates the VertexData for an ellipsoid, defaults to a sphere
      * @param options an object used to set the following optional parameters for the box, required but can be empty
       * * segments sets the number of horizontal strips optional, default 32

+ 31 - 0
src/Meshes/meshBuilder.ts

@@ -1,6 +1,7 @@
 import { RibbonBuilder } from "./Builders/ribbonBuilder";
 import { DiscBuilder } from "./Builders/discBuilder";
 import { BoxBuilder } from "./Builders/boxBuilder";
+import { TiledBoxBuilder } from "./Builders/tiledBoxBuilder";
 import { SphereBuilder } from "./Builders/sphereBuilder";
 import { CylinderBuilder } from "./Builders/cylinderBuilder";
 import { TorusBuilder } from "./Builders/torusBuilder";
@@ -10,6 +11,7 @@ import { PolygonBuilder } from "./Builders/polygonBuilder";
 import { ShapeBuilder } from "./Builders/shapeBuilder";
 import { LatheBuilder } from "./Builders/latheBuilder";
 import { PlaneBuilder } from "./Builders/planeBuilder";
+import { TiledPlaneBuilder } from "./Builders/tiledPlaneBuilder";
 import { GroundBuilder } from "./Builders/groundBuilder";
 import { TubeBuilder } from "./Builders/tubeBuilder";
 import { PolyhedronBuilder } from "./Builders/polyhedronBuilder";
@@ -50,6 +52,19 @@ export class MeshBuilder {
     }
 
     /**
+     * Creates a tiled box mesh
+     * * faceTiles sets the pattern, tile size and number of tiles for a face
+     * * 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 box mesh
+     */
+    public static CreateTiledBox(name: string, options: { pattern?: number, size?: number, width?: number, height?: number, depth: number, tileSize?: number, tileWidth?: number, tileHeight?: number, faceUV?: Vector4[], faceColors?: Color4[], alignHorizontal?: number, alignVertical?: number, sideOrientation?: number, updatable?: boolean}, scene: Nullable<Scene> = null): Mesh {
+        return TiledBoxBuilder.CreateTiledBox(name, options, scene);
+    }
+
+    /**
      * 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`)
@@ -342,6 +357,22 @@ export class MeshBuilder {
     }
 
     /**
+     * Creates a tiled plane mesh
+     * * You can set a limited pattern arrangement with the tiles
+     * * 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 CreateTiledPlane(name: string, options: { pattern?: number, tileSize?: number, tileWidth?: number, tileHeight?: number, size?: number, width?: number, height?: number, alignHorizontal?: number, alignVertical?: number, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4, updatable?: boolean}, scene: Nullable<Scene> = null): Mesh {
+        return TiledPlaneBuilder.CreateTiledPlane(name, options, scene);
+    }
+
+    /**
      * 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`)