groundMesh.ts 12 KB

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