groundMesh.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. import { Vector3, Vector2, TmpVectors, Vector4 } from "../Maths/math.vector.js";
  2. import { VertexBuffer } from "../Buffers/buffer.js";
  3. import { Mesh } from "../Meshes/mesh.js";
  4. Mesh._GroundMeshParser = (parsedMesh, scene) => {
  5. return GroundMesh.Parse(parsedMesh, scene);
  6. };
  7. /**
  8. * Mesh representing the ground
  9. */
  10. export class GroundMesh extends Mesh {
  11. constructor(name, scene) {
  12. super(name, scene);
  13. /** If octree should be generated */
  14. this.generateOctree = false;
  15. }
  16. /**
  17. * "GroundMesh"
  18. * @returns "GroundMesh"
  19. */
  20. getClassName() {
  21. return "GroundMesh";
  22. }
  23. /**
  24. * The minimum of x and y subdivisions
  25. */
  26. get subdivisions() {
  27. return Math.min(this._subdivisionsX, this._subdivisionsY);
  28. }
  29. /**
  30. * X subdivisions
  31. */
  32. get subdivisionsX() {
  33. return this._subdivisionsX;
  34. }
  35. /**
  36. * Y subdivisions
  37. */
  38. get subdivisionsY() {
  39. return this._subdivisionsY;
  40. }
  41. /**
  42. * This function will divide the mesh into submeshes and update an octree to help to select the right submeshes
  43. * for rendering, picking and collision computations. Please note that you must have a decent number of submeshes
  44. * to get performance improvements when using an octree.
  45. * @param chunksCount the number of submeshes the mesh will be divided into
  46. * @param octreeBlocksSize the maximum size of the octree blocks (Default: 32)
  47. */
  48. optimize(chunksCount, octreeBlocksSize = 32) {
  49. this._subdivisionsX = chunksCount;
  50. this._subdivisionsY = chunksCount;
  51. this.subdivide(chunksCount);
  52. // Call the octree system optimization if it is defined.
  53. const thisAsAny = this;
  54. if (thisAsAny.createOrUpdateSubmeshesOctree) {
  55. thisAsAny.createOrUpdateSubmeshesOctree(octreeBlocksSize);
  56. }
  57. }
  58. /**
  59. * Returns a height (y) value in the World system :
  60. * the ground altitude at the coordinates (x, z) expressed in the World system.
  61. * @param x x coordinate
  62. * @param z z coordinate
  63. * @returns the ground y position if (x, z) are outside the ground surface.
  64. */
  65. getHeightAtCoordinates(x, z) {
  66. const world = this.getWorldMatrix();
  67. const invMat = TmpVectors.Matrix[5];
  68. world.invertToRef(invMat);
  69. const tmpVect = TmpVectors.Vector3[8];
  70. Vector3.TransformCoordinatesFromFloatsToRef(x, 0.0, z, invMat, tmpVect); // transform x,z in the mesh local space
  71. x = tmpVect.x;
  72. z = tmpVect.z;
  73. if (x < this._minX || x >= this._maxX || z <= this._minZ || z > this._maxZ) {
  74. return this.position.y;
  75. }
  76. if (!this._heightQuads || this._heightQuads.length == 0) {
  77. this._initHeightQuads();
  78. this._computeHeightQuads();
  79. }
  80. const facet = this._getFacetAt(x, z);
  81. const y = -(facet.x * x + facet.z * z + facet.w) / facet.y;
  82. // return y in the World system
  83. Vector3.TransformCoordinatesFromFloatsToRef(0.0, y, 0.0, world, tmpVect);
  84. return tmpVect.y;
  85. }
  86. /**
  87. * Returns a normalized vector (Vector3) orthogonal to the ground
  88. * at the ground coordinates (x, z) expressed in the World system.
  89. * @param x x coordinate
  90. * @param z z coordinate
  91. * @returns Vector3(0.0, 1.0, 0.0) if (x, z) are outside the ground surface.
  92. */
  93. getNormalAtCoordinates(x, z) {
  94. const normal = new Vector3(0.0, 1.0, 0.0);
  95. this.getNormalAtCoordinatesToRef(x, z, normal);
  96. return normal;
  97. }
  98. /**
  99. * Updates the Vector3 passed a reference with a normalized vector orthogonal to the ground
  100. * at the ground coordinates (x, z) expressed in the World system.
  101. * Doesn't update the reference Vector3 if (x, z) are outside the ground surface.
  102. * @param x x coordinate
  103. * @param z z coordinate
  104. * @param ref vector to store the result
  105. * @returns the GroundMesh.
  106. */
  107. getNormalAtCoordinatesToRef(x, z, ref) {
  108. const world = this.getWorldMatrix();
  109. const tmpMat = TmpVectors.Matrix[5];
  110. world.invertToRef(tmpMat);
  111. const tmpVect = TmpVectors.Vector3[8];
  112. Vector3.TransformCoordinatesFromFloatsToRef(x, 0.0, z, tmpMat, tmpVect); // transform x,z in the mesh local space
  113. x = tmpVect.x;
  114. z = tmpVect.z;
  115. if (x < this._minX || x > this._maxX || z < this._minZ || z > this._maxZ) {
  116. return this;
  117. }
  118. if (!this._heightQuads || this._heightQuads.length == 0) {
  119. this._initHeightQuads();
  120. this._computeHeightQuads();
  121. }
  122. const facet = this._getFacetAt(x, z);
  123. Vector3.TransformNormalFromFloatsToRef(facet.x, facet.y, facet.z, world, ref);
  124. return this;
  125. }
  126. /**
  127. * Force the heights to be recomputed for getHeightAtCoordinates() or getNormalAtCoordinates()
  128. * if the ground has been updated.
  129. * This can be used in the render loop.
  130. * @returns the GroundMesh.
  131. */
  132. updateCoordinateHeights() {
  133. if (!this._heightQuads || this._heightQuads.length == 0) {
  134. this._initHeightQuads();
  135. }
  136. this._computeHeightQuads();
  137. return this;
  138. }
  139. // Returns the element "facet" from the heightQuads array relative to (x, z) local coordinates
  140. _getFacetAt(x, z) {
  141. // retrieve col and row from x, z coordinates in the ground local system
  142. const col = Math.floor(((x + this._maxX) * this._subdivisionsX) / this._width);
  143. const row = Math.floor((-(z + this._maxZ) * this._subdivisionsY) / this._height + this._subdivisionsY);
  144. const quad = this._heightQuads[row * this._subdivisionsX + col];
  145. let facet;
  146. if (z < quad.slope.x * x + quad.slope.y) {
  147. facet = quad.facet1;
  148. }
  149. else {
  150. facet = quad.facet2;
  151. }
  152. return facet;
  153. }
  154. // Creates and populates the heightMap array with "facet" elements :
  155. // a quad is two triangular facets separated by a slope, so a "facet" element is 1 slope + 2 facets
  156. // slope : Vector2(c, h) = 2D diagonal line equation setting apart two triangular facets in a quad : z = cx + h
  157. // facet1 : Vector4(a, b, c, d) = first facet 3D plane equation : ax + by + cz + d = 0
  158. // facet2 : Vector4(a, b, c, d) = second facet 3D plane equation : ax + by + cz + d = 0
  159. // Returns the GroundMesh.
  160. _initHeightQuads() {
  161. const subdivisionsX = this._subdivisionsX;
  162. const subdivisionsY = this._subdivisionsY;
  163. this._heightQuads = new Array();
  164. for (let row = 0; row < subdivisionsY; row++) {
  165. for (let col = 0; col < subdivisionsX; col++) {
  166. const quad = { slope: Vector2.Zero(), facet1: new Vector4(0.0, 0.0, 0.0, 0.0), facet2: new Vector4(0.0, 0.0, 0.0, 0.0) };
  167. this._heightQuads[row * subdivisionsX + col] = quad;
  168. }
  169. }
  170. return this;
  171. }
  172. // Compute each quad element values and update the heightMap array :
  173. // slope : Vector2(c, h) = 2D diagonal line equation setting apart two triangular facets in a quad : z = cx + h
  174. // facet1 : Vector4(a, b, c, d) = first facet 3D plane equation : ax + by + cz + d = 0
  175. // facet2 : Vector4(a, b, c, d) = second facet 3D plane equation : ax + by + cz + d = 0
  176. // Returns the GroundMesh.
  177. _computeHeightQuads() {
  178. const positions = this.getVerticesData(VertexBuffer.PositionKind);
  179. if (!positions) {
  180. return this;
  181. }
  182. const v1 = TmpVectors.Vector3[3];
  183. const v2 = TmpVectors.Vector3[2];
  184. const v3 = TmpVectors.Vector3[1];
  185. const v4 = TmpVectors.Vector3[0];
  186. const v1v2 = TmpVectors.Vector3[4];
  187. const v1v3 = TmpVectors.Vector3[5];
  188. const v1v4 = TmpVectors.Vector3[6];
  189. const norm1 = TmpVectors.Vector3[7];
  190. const norm2 = TmpVectors.Vector3[8];
  191. let i = 0;
  192. let j = 0;
  193. let k = 0;
  194. let cd = 0; // 2D slope coefficient : z = cd * x + h
  195. let h = 0;
  196. let d1 = 0; // facet plane equation : ax + by + cz + d = 0
  197. let d2 = 0;
  198. const subdivisionsX = this._subdivisionsX;
  199. const subdivisionsY = this._subdivisionsY;
  200. for (let row = 0; row < subdivisionsY; row++) {
  201. for (let col = 0; col < subdivisionsX; col++) {
  202. i = col * 3;
  203. j = row * (subdivisionsX + 1) * 3;
  204. k = (row + 1) * (subdivisionsX + 1) * 3;
  205. v1.x = positions[j + i];
  206. v1.y = positions[j + i + 1];
  207. v1.z = positions[j + i + 2];
  208. v2.x = positions[j + i + 3];
  209. v2.y = positions[j + i + 4];
  210. v2.z = positions[j + i + 5];
  211. v3.x = positions[k + i];
  212. v3.y = positions[k + i + 1];
  213. v3.z = positions[k + i + 2];
  214. v4.x = positions[k + i + 3];
  215. v4.y = positions[k + i + 4];
  216. v4.z = positions[k + i + 5];
  217. // 2D slope V1V4
  218. cd = (v4.z - v1.z) / (v4.x - v1.x);
  219. h = v1.z - cd * v1.x; // v1 belongs to the slope
  220. // facet equations :
  221. // we compute each facet normal vector
  222. // the equation of the facet plane is : norm.x * x + norm.y * y + norm.z * z + d = 0
  223. // we compute the value d by applying the equation to v1 which belongs to the plane
  224. // then we store the facet equation in a Vector4
  225. v2.subtractToRef(v1, v1v2);
  226. v3.subtractToRef(v1, v1v3);
  227. v4.subtractToRef(v1, v1v4);
  228. Vector3.CrossToRef(v1v4, v1v3, norm1); // caution : CrossToRef uses the Tmp class
  229. Vector3.CrossToRef(v1v2, v1v4, norm2);
  230. norm1.normalize();
  231. norm2.normalize();
  232. d1 = -(norm1.x * v1.x + norm1.y * v1.y + norm1.z * v1.z);
  233. d2 = -(norm2.x * v2.x + norm2.y * v2.y + norm2.z * v2.z);
  234. const quad = this._heightQuads[row * subdivisionsX + col];
  235. quad.slope.copyFromFloats(cd, h);
  236. quad.facet1.copyFromFloats(norm1.x, norm1.y, norm1.z, d1);
  237. quad.facet2.copyFromFloats(norm2.x, norm2.y, norm2.z, d2);
  238. }
  239. }
  240. return this;
  241. }
  242. /**
  243. * Serializes this ground mesh
  244. * @param serializationObject object to write serialization to
  245. */
  246. serialize(serializationObject) {
  247. super.serialize(serializationObject);
  248. serializationObject.subdivisionsX = this._subdivisionsX;
  249. serializationObject.subdivisionsY = this._subdivisionsY;
  250. serializationObject.minX = this._minX;
  251. serializationObject.maxX = this._maxX;
  252. serializationObject.minZ = this._minZ;
  253. serializationObject.maxZ = this._maxZ;
  254. serializationObject.width = this._width;
  255. serializationObject.height = this._height;
  256. }
  257. /**
  258. * Parses a serialized ground mesh
  259. * @param parsedMesh the serialized mesh
  260. * @param scene the scene to create the ground mesh in
  261. * @returns the created ground mesh
  262. */
  263. static Parse(parsedMesh, scene) {
  264. const result = new GroundMesh(parsedMesh.name, scene);
  265. result._subdivisionsX = parsedMesh.subdivisionsX || 1;
  266. result._subdivisionsY = parsedMesh.subdivisionsY || 1;
  267. result._minX = parsedMesh.minX;
  268. result._maxX = parsedMesh.maxX;
  269. result._minZ = parsedMesh.minZ;
  270. result._maxZ = parsedMesh.maxZ;
  271. result._width = parsedMesh.width;
  272. result._height = parsedMesh.height;
  273. return result;
  274. }
  275. }
  276. //# sourceMappingURL=groundMesh.js.map