123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428 |
- import { Vector3 } from "../../Maths/math.vector.js";
- import { Color3 } from "../../Maths/math.color.js";
- import { Mesh } from "../mesh.js";
- import { VertexData } from "../mesh.vertexData.js";
- import { GroundMesh } from "../groundMesh.js";
- import { Tools } from "../../Misc/tools.js";
- import { EngineStore } from "../../Engines/engineStore.js";
- import { Epsilon } from "../../Maths/math.constants.js";
- import { CompatibilityOptions } from "../../Compat/compatibilityOptions.js";
- /**
- * Creates the VertexData for a Ground
- * @param options an object used to set the following optional parameters for the Ground, required but can be empty
- * @param options.width the width (x direction) of the ground, optional, default 1
- * @param options.height the height (z direction) of the ground, optional, default 1
- * @param options.subdivisions the number of subdivisions per side, optional, default 1
- * @param options.subdivisionsX the number of subdivisions in the x direction, overrides options.subdivisions, optional, default undefined
- * @param options.subdivisionsY the number of subdivisions in the y direction, overrides options.subdivisions, optional, default undefined
- * @returns the VertexData of the Ground
- */
- export function CreateGroundVertexData(options) {
- const indices = [];
- const positions = [];
- const normals = [];
- const uvs = [];
- let row, col;
- const width = options.width || 1;
- const height = options.height || 1;
- const subdivisionsX = (options.subdivisionsX || options.subdivisions || 1) | 0;
- const subdivisionsY = (options.subdivisionsY || options.subdivisions || 1) | 0;
- for (row = 0; row <= subdivisionsY; row++) {
- for (col = 0; col <= subdivisionsX; col++) {
- const position = new Vector3((col * width) / subdivisionsX - width / 2.0, 0, ((subdivisionsY - row) * height) / subdivisionsY - height / 2.0);
- const 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, CompatibilityOptions.UseOpenGLOrientationForUV ? row / subdivisionsY : 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
- const vertexData = new VertexData();
- vertexData.indices = indices;
- vertexData.positions = positions;
- vertexData.normals = normals;
- vertexData.uvs = uvs;
- return vertexData;
- }
- /**
- * Creates the VertexData for a TiledGround by subdividing the ground into tiles
- * @param options an object used to set the following optional parameters for the Ground
- * @param options.xmin ground minimum X coordinate, default -1
- * @param options.zmin ground minimum Z coordinate, default -1
- * @param options.xmax ground maximum X coordinate, default 1
- * @param options.zmax ground maximum Z coordinate, default 1
- * @param options.subdivisions a javascript object {w: positive integer, h: positive integer}, `w` and `h` are the numbers of subdivisions on the ground width and height creating 'tiles', default {w: 6, h: 6}
- * @param options.subdivisions.w positive integer, default 6
- * @param options.subdivisions.h positive integer, default 6
- * @param options.precision a javascript object {w: positive integer, h: positive integer}, `w` and `h` are the numbers of subdivisions on the tile width and height, default {w: 2, h: 2}
- * @param options.precision.w positive integer, default 2
- * @param options.precision.h positive integer, default 2
- * @returns the VertexData of the TiledGround
- */
- export function CreateTiledGroundVertexData(options) {
- const xmin = options.xmin !== undefined && options.xmin !== null ? options.xmin : -1.0;
- const zmin = options.zmin !== undefined && options.zmin !== null ? options.zmin : -1.0;
- const xmax = options.xmax !== undefined && options.xmax !== null ? options.xmax : 1.0;
- const zmax = options.zmax !== undefined && options.zmax !== null ? options.zmax : 1.0;
- const subdivisions = options.subdivisions || { w: 1, h: 1 };
- const precision = options.precision || { w: 1, h: 1 };
- const indices = [];
- const positions = [];
- const normals = [];
- const uvs = [];
- let row, col, tileRow, tileCol;
- 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;
- const tileSize = {
- w: (xmax - xmin) / subdivisions.w,
- h: (zmax - zmin) / subdivisions.h,
- };
- function applyTile(xTileMin, zTileMin, xTileMax, zTileMax) {
- // Indices
- const base = positions.length / 3;
- const rowLength = precision.w + 1;
- for (row = 0; row < precision.h; row++) {
- for (col = 0; col < precision.w; col++) {
- const 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
- const position = Vector3.Zero();
- const 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
- const vertexData = new VertexData();
- vertexData.indices = indices;
- vertexData.positions = positions;
- vertexData.normals = normals;
- vertexData.uvs = uvs;
- return vertexData;
- }
- /**
- * Creates the VertexData of the Ground designed from a heightmap
- * @param options an object used to set the following parameters for the Ground, required and provided by CreateGroundFromHeightMap
- * @param options.width the width (x direction) of the ground
- * @param options.height the height (z direction) of the ground
- * @param options.subdivisions the number of subdivisions per side
- * @param options.minHeight the minimum altitude on the ground, optional, default 0
- * @param options.maxHeight the maximum altitude on the ground, optional default 1
- * @param options.colorFilter the filter to apply to the image pixel colors to compute the height, optional Color3, default (0.3, 0.59, 0.11)
- * @param options.buffer the array holding the image color data
- * @param options.bufferWidth the width of image
- * @param options.bufferHeight the height of image
- * @param options.alphaFilter Remove any data where the alpha channel is below this value, defaults 0 (all data visible)
- * @param options.heightBuffer a array of floats where the height data can be saved, if its length is greater than zero.
- * @returns the VertexData of the Ground designed from a heightmap
- */
- export function CreateGroundFromHeightMapVertexData(options) {
- const indices = [];
- const positions = [];
- const normals = [];
- const uvs = [];
- let row, col;
- const filter = options.colorFilter || new Color3(0.3, 0.59, 0.11);
- const alphaFilter = options.alphaFilter || 0.0;
- let invert = false;
- if (options.minHeight > options.maxHeight) {
- invert = true;
- const temp = options.maxHeight;
- options.maxHeight = options.minHeight;
- options.minHeight = temp;
- }
- // Vertices
- for (row = 0; row <= options.subdivisions; row++) {
- for (col = 0; col <= options.subdivisions; col++) {
- const 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
- const heightMapX = (((position.x + options.width / 2) / options.width) * (options.bufferWidth - 1)) | 0;
- const heightMapY = ((1.0 - (position.z + options.height / 2) / options.height) * (options.bufferHeight - 1)) | 0;
- const pos = (heightMapX + heightMapY * options.bufferWidth) * 4;
- let r = options.buffer[pos] / 255.0;
- let g = options.buffer[pos + 1] / 255.0;
- let b = options.buffer[pos + 2] / 255.0;
- const a = options.buffer[pos + 3] / 255.0;
- if (invert) {
- r = 1.0 - r;
- g = 1.0 - g;
- b = 1.0 - b;
- }
- const 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.
- }
- if (options.heightBuffer) {
- // set the height buffer information in row major order.
- options.heightBuffer[row * (options.subdivisions + 1) + col] = position.y;
- }
- // 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
- const idx1 = col + 1 + (row + 1) * (options.subdivisions + 1);
- const idx2 = col + 1 + row * (options.subdivisions + 1);
- const idx3 = col + row * (options.subdivisions + 1);
- const 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
- const isVisibleIdx1 = positions[idx1 * 3 + 1] >= options.minHeight;
- const isVisibleIdx2 = positions[idx2 * 3 + 1] >= options.minHeight;
- const isVisibleIdx3 = positions[idx3 * 3 + 1] >= options.minHeight;
- if (isVisibleIdx1 && isVisibleIdx2 && isVisibleIdx3) {
- indices.push(idx1);
- indices.push(idx2);
- indices.push(idx3);
- }
- const 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
- const vertexData = new VertexData();
- vertexData.indices = indices;
- vertexData.positions = positions;
- vertexData.normals = normals;
- vertexData.uvs = uvs;
- return vertexData;
- }
- /**
- * Creates a ground mesh
- * @param name defines the name of the mesh
- * @param options defines the options used to create the mesh
- * @param options.width set the width size (float, default 1)
- * @param options.height set the height size (float, default 1)
- * @param options.subdivisions sets the number of subdivision per side (default 1)
- * @param options.subdivisionsX sets the number of subdivision on the X axis (overrides subdivisions)
- * @param options.subdivisionsY sets the number of subdivision on the Y axis (overrides subdivisions)
- * @param options.updatable defines if the mesh must be flagged as updatable (default false)
- * @param scene defines the hosting scene
- * @returns the ground mesh
- * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/set#ground
- */
- export function CreateGround(name, options = {}, scene) {
- const 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;
- const vertexData = CreateGroundVertexData(options);
- vertexData.applyToMesh(ground, options.updatable);
- ground._setReady(true);
- return ground;
- }
- /**
- * Creates a tiled ground mesh
- * @param name defines the name of the mesh
- * @param options defines the options used to create the mesh
- * @param options.xmin ground minimum X coordinate (float, default -1)
- * @param options.zmin ground minimum Z coordinate (float, default -1)
- * @param options.xmax ground maximum X coordinate (float, default 1)
- * @param options.zmax ground maximum Z coordinate (float, default 1)
- * @param options.subdivisions 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
- * @param options.subdivisions.w positive integer, default 6
- * @param options.subdivisions.h positive integer, default 6
- * @param options.precision 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
- * @param options.precision.w positive integer, default 2
- * @param options.precision.h positive integer, default 2
- * @param options.updatable boolean, default false, true if the mesh must be flagged as updatable
- * @param scene defines the hosting scene
- * @returns the tiled ground mesh
- * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/set#tiled-ground
- */
- export function CreateTiledGround(name, options, scene = null) {
- const tiledGround = new Mesh(name, scene);
- const vertexData = CreateTiledGroundVertexData(options);
- vertexData.applyToMesh(tiledGround, options.updatable);
- return tiledGround;
- }
- /**
- * Creates a ground mesh from a height map. The height map download can take some frames,
- * so the mesh is not immediately ready. To wait for the mesh to be completely built,
- * you should use the `onReady` callback option.
- * @param name defines the name of the mesh
- * @param url sets the URL of the height map image resource.
- * @param options defines the options used to create the mesh
- * @param options.width sets the ground width size (positive float, default 10)
- * @param options.height sets the ground height size (positive float, default 10)
- * @param options.subdivisions sets the number of subdivision per side (positive integer, default 1)
- * @param options.minHeight is the minimum altitude on the ground (float, default 0)
- * @param options.maxHeight is the maximum altitude on the ground (float, default 1)
- * @param options.colorFilter is the filter to apply to the image pixel colors to compute the height (optional Color3, default (0.3, 0.59, 0.11) )
- * @param options.alphaFilter will filter any data where the alpha channel is below this value, defaults 0 (all data visible)
- * @param options.updatable defines if the mesh must be flagged as updatable
- * @param options.onReady is a javascript callback function that will be called once the mesh is just built (the height map download can last some time)
- * @param options.onError is a javascript callback function that will be called if there is an error
- * @param options.passHeightBufferInCallback a boolean that indicates if the calculated height data will be passed in the onReady callback. Useful if you need the height data for physics, for example.
- * @param scene defines the hosting scene
- * @returns the ground mesh
- * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/set/height_map
- * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/set#ground-from-a-height-map
- */
- export function CreateGroundFromHeightMap(name, url, options = {}, scene = null) {
- const width = options.width || 10.0;
- const height = options.height || 10.0;
- const subdivisions = options.subdivisions || 1 | 0;
- const minHeight = options.minHeight || 0.0;
- const maxHeight = options.maxHeight || 1.0;
- const filter = options.colorFilter || new Color3(0.3, 0.59, 0.11);
- const alphaFilter = options.alphaFilter || 0.0;
- const updatable = options.updatable;
- const onReady = options.onReady;
- scene = scene || EngineStore.LastCreatedScene;
- const 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);
- let heightBuffer;
- if (options.passHeightBufferInCallback) {
- heightBuffer = new Float32Array((subdivisions + 1) * (subdivisions + 1));
- }
- const onBufferLoaded = (buffer, bufferWidth, bufferHeight) => {
- const vertexData = CreateGroundFromHeightMapVertexData({
- width: width,
- height: height,
- subdivisions: subdivisions,
- minHeight: minHeight,
- maxHeight: maxHeight,
- colorFilter: filter,
- buffer: buffer,
- bufferWidth: bufferWidth,
- bufferHeight: bufferHeight,
- alphaFilter: alphaFilter,
- heightBuffer,
- });
- vertexData.applyToMesh(ground, updatable);
- //execute ready callback, if set
- if (onReady) {
- onReady(ground, heightBuffer);
- }
- ground._setReady(true);
- };
- if (typeof url === "string") {
- const onload = (img) => {
- const bufferWidth = img.width;
- const bufferHeight = img.height;
- if (scene.isDisposed) {
- return;
- }
- const buffer = scene?.getEngine().resizeImageBitmap(img, bufferWidth, bufferHeight);
- onBufferLoaded(buffer, bufferWidth, bufferHeight);
- };
- Tools.LoadImage(url, onload, options.onError ? options.onError : () => { }, scene.offlineProvider);
- }
- else {
- onBufferLoaded(url.data, url.width, url.height);
- }
- return ground;
- }
- /**
- * Class containing static functions to help procedurally build meshes
- * @deprecated use the functions directly from the module
- */
- export const GroundBuilder = {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- CreateGround,
- // eslint-disable-next-line @typescript-eslint/naming-convention
- CreateGroundFromHeightMap,
- // eslint-disable-next-line @typescript-eslint/naming-convention
- CreateTiledGround,
- };
- VertexData.CreateGround = CreateGroundVertexData;
- VertexData.CreateTiledGround = CreateTiledGroundVertexData;
- VertexData.CreateGroundFromHeightMap = CreateGroundFromHeightMapVertexData;
- Mesh.CreateGround = (name, width, height, subdivisions, scene, updatable) => {
- const options = {
- width,
- height,
- subdivisions,
- updatable,
- };
- return CreateGround(name, options, scene);
- };
- Mesh.CreateTiledGround = (name, xmin, zmin, xmax, zmax, subdivisions, precision, scene, updatable) => {
- const options = {
- xmin,
- zmin,
- xmax,
- zmax,
- subdivisions,
- precision,
- updatable,
- };
- return CreateTiledGround(name, options, scene);
- };
- Mesh.CreateGroundFromHeightMap = (name, url, width, height, subdivisions, minHeight, maxHeight, scene, updatable, onReady, alphaFilter) => {
- const options = {
- width,
- height,
- subdivisions,
- minHeight,
- maxHeight,
- updatable,
- onReady,
- alphaFilter,
- };
- return CreateGroundFromHeightMap(name, url, options, scene);
- };
- //# sourceMappingURL=groundBuilder.js.map
|