boxBuilder.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import { Matrix, Vector4 } from "../../Maths/math.vector.js";
  2. import { Color4 } from "../../Maths/math.color.js";
  3. import { Mesh } from "../mesh.js";
  4. import { VertexData } from "../mesh.vertexData.js";
  5. import { CompatibilityOptions } from "../../Compat/compatibilityOptions.js";
  6. import { CreateGroundVertexData } from "./groundBuilder.js";
  7. /**
  8. * Creates the VertexData for a box
  9. * @param options an object used to set the following optional parameters for the box, required but can be empty
  10. * * size sets the width, height and depth of the box to the value of size, optional default 1
  11. * * width sets the width (x direction) of the box, overwrites the width set by size, optional, default size
  12. * * height sets the height (y direction) of the box, overwrites the height set by size, optional, default size
  13. * * depth sets the depth (z direction) of the box, overwrites the depth set by size, optional, default size
  14. * * faceUV an array of 6 Vector4 elements used to set different images to each box side
  15. * * faceColors an array of 6 Color3 elements used to set different colors to each box side
  16. * * sideOrientation optional and takes the values : Mesh.FRONTSIDE (default), Mesh.BACKSIDE or Mesh.DOUBLESIDE
  17. * * 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)
  18. * * 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)
  19. * @returns the VertexData of the box
  20. */
  21. export function CreateBoxVertexData(options) {
  22. const nbFaces = 6;
  23. let indices = [0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23];
  24. const normals = [
  25. 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0,
  26. 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0,
  27. ];
  28. const uvs = [];
  29. let positions = [];
  30. const width = options.width || options.size || 1;
  31. const height = options.height || options.size || 1;
  32. const depth = options.depth || options.size || 1;
  33. const wrap = options.wrap || false;
  34. let topBaseAt = options.topBaseAt === void 0 ? 1 : options.topBaseAt;
  35. let bottomBaseAt = options.bottomBaseAt === void 0 ? 0 : options.bottomBaseAt;
  36. topBaseAt = (topBaseAt + 4) % 4; // places values as 0 to 3
  37. bottomBaseAt = (bottomBaseAt + 4) % 4; // places values as 0 to 3
  38. const topOrder = [2, 0, 3, 1];
  39. const bottomOrder = [2, 0, 1, 3];
  40. let topIndex = topOrder[topBaseAt];
  41. let bottomIndex = bottomOrder[bottomBaseAt];
  42. let basePositions = [
  43. 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1,
  44. 1, 1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1,
  45. ];
  46. if (wrap) {
  47. indices = [2, 3, 0, 2, 0, 1, 4, 5, 6, 4, 6, 7, 9, 10, 11, 9, 11, 8, 12, 14, 15, 12, 13, 14];
  48. basePositions = [
  49. -1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1,
  50. ];
  51. let topFaceBase = [
  52. [1, 1, 1],
  53. [-1, 1, 1],
  54. [-1, 1, -1],
  55. [1, 1, -1],
  56. ];
  57. let bottomFaceBase = [
  58. [-1, -1, 1],
  59. [1, -1, 1],
  60. [1, -1, -1],
  61. [-1, -1, -1],
  62. ];
  63. const topFaceOrder = [17, 18, 19, 16];
  64. const bottomFaceOrder = [22, 23, 20, 21];
  65. while (topIndex > 0) {
  66. topFaceBase.unshift(topFaceBase.pop());
  67. topFaceOrder.unshift(topFaceOrder.pop());
  68. topIndex--;
  69. }
  70. while (bottomIndex > 0) {
  71. bottomFaceBase.unshift(bottomFaceBase.pop());
  72. bottomFaceOrder.unshift(bottomFaceOrder.pop());
  73. bottomIndex--;
  74. }
  75. topFaceBase = topFaceBase.flat();
  76. bottomFaceBase = bottomFaceBase.flat();
  77. basePositions = basePositions.concat(topFaceBase).concat(bottomFaceBase);
  78. indices.push(topFaceOrder[0], topFaceOrder[2], topFaceOrder[3], topFaceOrder[0], topFaceOrder[1], topFaceOrder[2]);
  79. indices.push(bottomFaceOrder[0], bottomFaceOrder[2], bottomFaceOrder[3], bottomFaceOrder[0], bottomFaceOrder[1], bottomFaceOrder[2]);
  80. }
  81. const scaleArray = [width / 2, height / 2, depth / 2];
  82. positions = basePositions.reduce((accumulator, currentValue, currentIndex) => accumulator.concat(currentValue * scaleArray[currentIndex % 3]), []);
  83. const sideOrientation = options.sideOrientation === 0 ? 0 : options.sideOrientation || VertexData.DEFAULTSIDE;
  84. const faceUV = options.faceUV || new Array(6);
  85. const faceColors = options.faceColors;
  86. const colors = [];
  87. // default face colors and UV if undefined
  88. for (let f = 0; f < 6; f++) {
  89. if (faceUV[f] === undefined) {
  90. faceUV[f] = new Vector4(0, 0, 1, 1);
  91. }
  92. if (faceColors && faceColors[f] === undefined) {
  93. faceColors[f] = new Color4(1, 1, 1, 1);
  94. }
  95. }
  96. // Create each face in turn.
  97. for (let index = 0; index < nbFaces; index++) {
  98. uvs.push(faceUV[index].z, CompatibilityOptions.UseOpenGLOrientationForUV ? 1.0 - faceUV[index].w : faceUV[index].w);
  99. uvs.push(faceUV[index].x, CompatibilityOptions.UseOpenGLOrientationForUV ? 1.0 - faceUV[index].w : faceUV[index].w);
  100. uvs.push(faceUV[index].x, CompatibilityOptions.UseOpenGLOrientationForUV ? 1.0 - faceUV[index].y : faceUV[index].y);
  101. uvs.push(faceUV[index].z, CompatibilityOptions.UseOpenGLOrientationForUV ? 1.0 - faceUV[index].y : faceUV[index].y);
  102. if (faceColors) {
  103. for (let c = 0; c < 4; c++) {
  104. colors.push(faceColors[index].r, faceColors[index].g, faceColors[index].b, faceColors[index].a);
  105. }
  106. }
  107. }
  108. // sides
  109. VertexData._ComputeSides(sideOrientation, positions, indices, normals, uvs, options.frontUVs, options.backUVs);
  110. // Result
  111. const vertexData = new VertexData();
  112. vertexData.indices = indices;
  113. vertexData.positions = positions;
  114. vertexData.normals = normals;
  115. vertexData.uvs = uvs;
  116. if (faceColors) {
  117. const totalColors = sideOrientation === VertexData.DOUBLESIDE ? colors.concat(colors) : colors;
  118. vertexData.colors = totalColors;
  119. }
  120. return vertexData;
  121. }
  122. /**
  123. * Creates the VertexData for a segmented box
  124. * @param options an object used to set the following optional parameters for the box, required but can be empty
  125. * * size sets the width, height and depth of the box to the value of size, optional default 1
  126. * * width sets the width (x direction) of the box, overwrites the width set by size, optional, default size
  127. * * height sets the height (y direction) of the box, overwrites the height set by size, optional, default size
  128. * * depth sets the depth (z direction) of the box, overwrites the depth set by size, optional, default size
  129. * * segments sets the number of segments on the all axis (1 by default)
  130. * * widthSegments sets the number of segments on the x axis (1 by default)
  131. * * heightSegments sets the number of segments on the y axis (1 by default)
  132. * * depthSegments sets the number of segments on the z axis (1 by default)
  133. * @returns the VertexData of the box
  134. */
  135. export function CreateSegmentedBoxVertexData(options) {
  136. const width = options.width || options.size || 1;
  137. const height = options.height || options.size || 1;
  138. const depth = options.depth || options.size || 1;
  139. const widthSegments = (options.widthSegments || options.segments || 1) | 0;
  140. const heightSegments = (options.heightSegments || options.segments || 1) | 0;
  141. const depthSegments = (options.depthSegments || options.segments || 1) | 0;
  142. const rotationMatrix = new Matrix();
  143. const translationMatrix = new Matrix();
  144. const transformMatrix = new Matrix();
  145. const bottomPlane = CreateGroundVertexData({ width: width, height: depth, subdivisionsX: widthSegments, subdivisionsY: depthSegments });
  146. Matrix.TranslationToRef(0, -height / 2, 0, translationMatrix);
  147. Matrix.RotationZToRef(Math.PI, rotationMatrix);
  148. rotationMatrix.multiplyToRef(translationMatrix, transformMatrix);
  149. bottomPlane.transform(transformMatrix);
  150. const topPlane = CreateGroundVertexData({ width: width, height: depth, subdivisionsX: widthSegments, subdivisionsY: depthSegments });
  151. Matrix.TranslationToRef(0, height / 2, 0, transformMatrix);
  152. topPlane.transform(transformMatrix);
  153. const negXPlane = CreateGroundVertexData({ width: height, height: depth, subdivisionsX: heightSegments, subdivisionsY: depthSegments });
  154. Matrix.TranslationToRef(-width / 2, 0, 0, translationMatrix);
  155. Matrix.RotationZToRef(Math.PI / 2, rotationMatrix);
  156. rotationMatrix.multiplyToRef(translationMatrix, transformMatrix);
  157. negXPlane.transform(transformMatrix);
  158. const posXPlane = CreateGroundVertexData({ width: height, height: depth, subdivisionsX: heightSegments, subdivisionsY: depthSegments });
  159. Matrix.TranslationToRef(width / 2, 0, 0, translationMatrix);
  160. Matrix.RotationZToRef(-Math.PI / 2, rotationMatrix);
  161. rotationMatrix.multiplyToRef(translationMatrix, transformMatrix);
  162. posXPlane.transform(transformMatrix);
  163. const negZPlane = CreateGroundVertexData({ width: width, height: height, subdivisionsX: widthSegments, subdivisionsY: heightSegments });
  164. Matrix.TranslationToRef(0, 0, -depth / 2, translationMatrix);
  165. Matrix.RotationXToRef(-Math.PI / 2, rotationMatrix);
  166. rotationMatrix.multiplyToRef(translationMatrix, transformMatrix);
  167. negZPlane.transform(transformMatrix);
  168. const posZPlane = CreateGroundVertexData({ width: width, height: height, subdivisionsX: widthSegments, subdivisionsY: heightSegments });
  169. Matrix.TranslationToRef(0, 0, depth / 2, translationMatrix);
  170. Matrix.RotationXToRef(Math.PI / 2, rotationMatrix);
  171. rotationMatrix.multiplyToRef(translationMatrix, transformMatrix);
  172. posZPlane.transform(transformMatrix);
  173. // Result
  174. bottomPlane.merge([topPlane, posXPlane, negXPlane, negZPlane, posZPlane], true);
  175. return bottomPlane;
  176. }
  177. /**
  178. * Creates a box mesh
  179. * * The parameter `size` sets the size (float) of each box side (default 1)
  180. * * You can set some different box dimensions by using the parameters `width`, `height` and `depth` (all by default have the same value of `size`)
  181. * * 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)
  182. * * Please read this tutorial : https://doc.babylonjs.com/features/featuresDeepDive/materials/using/texturePerBoxFace
  183. * * You can also set the mesh side orientation with the values : BABYLON.Mesh.FRONTSIDE (default), BABYLON.Mesh.BACKSIDE or BABYLON.Mesh.DOUBLESIDE
  184. * * 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/features/featuresDeepDive/mesh/creation/set#side-orientation
  185. * * The mesh can be set to updatable with the boolean parameter `updatable` (default false) if its internal geometry is supposed to change once created
  186. * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/set#box
  187. * @param name defines the name of the mesh
  188. * @param options defines the options used to create the mesh
  189. * @param scene defines the hosting scene
  190. * @returns the box mesh
  191. */
  192. export function CreateBox(name, options = {}, scene = null) {
  193. const box = new Mesh(name, scene);
  194. options.sideOrientation = Mesh._GetDefaultSideOrientation(options.sideOrientation);
  195. box._originalBuilderSideOrientation = options.sideOrientation;
  196. const vertexData = CreateBoxVertexData(options);
  197. vertexData.applyToMesh(box, options.updatable);
  198. return box;
  199. }
  200. /**
  201. * Class containing static functions to help procedurally build meshes
  202. * @deprecated please use CreateBox directly
  203. */
  204. export const BoxBuilder = {
  205. // eslint-disable-next-line @typescript-eslint/naming-convention
  206. CreateBox,
  207. };
  208. // Side effects
  209. VertexData.CreateBox = CreateBoxVertexData;
  210. Mesh.CreateBox = (name, size, scene = null, updatable, sideOrientation) => {
  211. const options = {
  212. size,
  213. sideOrientation,
  214. updatable,
  215. };
  216. return CreateBox(name, options, scene);
  217. };
  218. //# sourceMappingURL=boxBuilder.js.map