groundMesh.ts 13 KB

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