polygonMesh.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. import { Logger } from "../Misc/logger.js";
  2. import { Vector3, Vector2 } from "../Maths/math.vector.js";
  3. import { VertexBuffer } from "../Buffers/buffer.js";
  4. import { Mesh } from "../Meshes/mesh.js";
  5. import { VertexData } from "../Meshes/mesh.vertexData.js";
  6. import { Path2 } from "../Maths/math.path.js";
  7. import { Epsilon } from "../Maths/math.constants.js";
  8. import { EngineStore } from "../Engines/engineStore.js";
  9. /**
  10. * Vector2 wth index property
  11. */
  12. class IndexedVector2 extends Vector2 {
  13. constructor(original,
  14. /** Index of the vector2 */
  15. index) {
  16. super(original.x, original.y);
  17. this.index = index;
  18. }
  19. }
  20. /**
  21. * Defines points to create a polygon
  22. */
  23. class PolygonPoints {
  24. constructor() {
  25. this.elements = [];
  26. }
  27. add(originalPoints) {
  28. const result = [];
  29. originalPoints.forEach((point) => {
  30. const newPoint = new IndexedVector2(point, this.elements.length);
  31. result.push(newPoint);
  32. this.elements.push(newPoint);
  33. });
  34. return result;
  35. }
  36. computeBounds() {
  37. const lmin = new Vector2(this.elements[0].x, this.elements[0].y);
  38. const lmax = new Vector2(this.elements[0].x, this.elements[0].y);
  39. this.elements.forEach((point) => {
  40. // x
  41. if (point.x < lmin.x) {
  42. lmin.x = point.x;
  43. }
  44. else if (point.x > lmax.x) {
  45. lmax.x = point.x;
  46. }
  47. // y
  48. if (point.y < lmin.y) {
  49. lmin.y = point.y;
  50. }
  51. else if (point.y > lmax.y) {
  52. lmax.y = point.y;
  53. }
  54. });
  55. return {
  56. min: lmin,
  57. max: lmax,
  58. width: lmax.x - lmin.x,
  59. height: lmax.y - lmin.y,
  60. };
  61. }
  62. }
  63. /**
  64. * Polygon
  65. * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/param#non-regular-polygon
  66. */
  67. export class Polygon {
  68. /**
  69. * Creates a rectangle
  70. * @param xmin bottom X coord
  71. * @param ymin bottom Y coord
  72. * @param xmax top X coord
  73. * @param ymax top Y coord
  74. * @returns points that make the resulting rectangle
  75. */
  76. static Rectangle(xmin, ymin, xmax, ymax) {
  77. return [new Vector2(xmin, ymin), new Vector2(xmax, ymin), new Vector2(xmax, ymax), new Vector2(xmin, ymax)];
  78. }
  79. /**
  80. * Creates a circle
  81. * @param radius radius of circle
  82. * @param cx scale in x
  83. * @param cy scale in y
  84. * @param numberOfSides number of sides that make up the circle
  85. * @returns points that make the resulting circle
  86. */
  87. static Circle(radius, cx = 0, cy = 0, numberOfSides = 32) {
  88. const result = [];
  89. let angle = 0;
  90. const increment = (Math.PI * 2) / numberOfSides;
  91. for (let i = 0; i < numberOfSides; i++) {
  92. result.push(new Vector2(cx + Math.cos(angle) * radius, cy + Math.sin(angle) * radius));
  93. angle -= increment;
  94. }
  95. return result;
  96. }
  97. /**
  98. * Creates a polygon from input string
  99. * @param input Input polygon data
  100. * @returns the parsed points
  101. */
  102. static Parse(input) {
  103. const floats = input
  104. .split(/[^-+eE.\d]+/)
  105. .map(parseFloat)
  106. .filter((val) => !isNaN(val));
  107. let i;
  108. const result = [];
  109. for (i = 0; i < (floats.length & 0x7ffffffe); i += 2) {
  110. result.push(new Vector2(floats[i], floats[i + 1]));
  111. }
  112. return result;
  113. }
  114. /**
  115. * Starts building a polygon from x and y coordinates
  116. * @param x x coordinate
  117. * @param y y coordinate
  118. * @returns the started path2
  119. */
  120. static StartingAt(x, y) {
  121. return Path2.StartingAt(x, y);
  122. }
  123. }
  124. /**
  125. * Builds a polygon
  126. * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/creation/param/polyMeshBuilder
  127. */
  128. export class PolygonMeshBuilder {
  129. _addToepoint(points) {
  130. for (const p of points) {
  131. this._epoints.push(p.x, p.y);
  132. }
  133. }
  134. /**
  135. * Creates a PolygonMeshBuilder
  136. * @param name name of the builder
  137. * @param contours Path of the polygon
  138. * @param scene scene to add to when creating the mesh
  139. * @param earcutInjection can be used to inject your own earcut reference
  140. */
  141. constructor(name, contours, scene, earcutInjection = earcut) {
  142. this._points = new PolygonPoints();
  143. this._outlinepoints = new PolygonPoints();
  144. this._holes = new Array();
  145. this._epoints = new Array();
  146. this._eholes = new Array();
  147. this.bjsEarcut = earcutInjection;
  148. this._name = name;
  149. this._scene = scene || EngineStore.LastCreatedScene;
  150. let points;
  151. if (contours instanceof Path2) {
  152. points = contours.getPoints();
  153. }
  154. else {
  155. points = contours;
  156. }
  157. this._addToepoint(points);
  158. this._points.add(points);
  159. this._outlinepoints.add(points);
  160. if (typeof this.bjsEarcut === "undefined") {
  161. Logger.Warn("Earcut was not found, the polygon will not be built.");
  162. }
  163. }
  164. /**
  165. * Adds a hole within the polygon
  166. * @param hole Array of points defining the hole
  167. * @returns this
  168. */
  169. addHole(hole) {
  170. this._points.add(hole);
  171. const holepoints = new PolygonPoints();
  172. holepoints.add(hole);
  173. this._holes.push(holepoints);
  174. this._eholes.push(this._epoints.length / 2);
  175. this._addToepoint(hole);
  176. return this;
  177. }
  178. /**
  179. * Creates the polygon
  180. * @param updatable If the mesh should be updatable
  181. * @param depth The depth of the mesh created
  182. * @param smoothingThreshold Dot product threshold for smoothed normals
  183. * @returns the created mesh
  184. */
  185. build(updatable = false, depth = 0, smoothingThreshold = 2) {
  186. const result = new Mesh(this._name, this._scene);
  187. const vertexData = this.buildVertexData(depth, smoothingThreshold);
  188. result.setVerticesData(VertexBuffer.PositionKind, vertexData.positions, updatable);
  189. result.setVerticesData(VertexBuffer.NormalKind, vertexData.normals, updatable);
  190. result.setVerticesData(VertexBuffer.UVKind, vertexData.uvs, updatable);
  191. result.setIndices(vertexData.indices);
  192. return result;
  193. }
  194. /**
  195. * Creates the polygon
  196. * @param depth The depth of the mesh created
  197. * @param smoothingThreshold Dot product threshold for smoothed normals
  198. * @returns the created VertexData
  199. */
  200. buildVertexData(depth = 0, smoothingThreshold = 2) {
  201. const result = new VertexData();
  202. const normals = [];
  203. const positions = [];
  204. const uvs = [];
  205. const bounds = this._points.computeBounds();
  206. this._points.elements.forEach((p) => {
  207. normals.push(0, 1.0, 0);
  208. positions.push(p.x, 0, p.y);
  209. uvs.push((p.x - bounds.min.x) / bounds.width, (p.y - bounds.min.y) / bounds.height);
  210. });
  211. const indices = [];
  212. const res = this.bjsEarcut(this._epoints, this._eholes, 2);
  213. for (let i = 0; i < res.length; i++) {
  214. indices.push(res[i]);
  215. }
  216. if (depth > 0) {
  217. const positionscount = positions.length / 3; //get the current pointcount
  218. this._points.elements.forEach((p) => {
  219. //add the elements at the depth
  220. normals.push(0, -1.0, 0);
  221. positions.push(p.x, -depth, p.y);
  222. uvs.push(1 - (p.x - bounds.min.x) / bounds.width, 1 - (p.y - bounds.min.y) / bounds.height);
  223. });
  224. const totalCount = indices.length;
  225. for (let i = 0; i < totalCount; i += 3) {
  226. const i0 = indices[i + 0];
  227. const i1 = indices[i + 1];
  228. const i2 = indices[i + 2];
  229. indices.push(i2 + positionscount);
  230. indices.push(i1 + positionscount);
  231. indices.push(i0 + positionscount);
  232. }
  233. //Add the sides
  234. this._addSide(positions, normals, uvs, indices, bounds, this._outlinepoints, depth, false, smoothingThreshold);
  235. this._holes.forEach((hole) => {
  236. this._addSide(positions, normals, uvs, indices, bounds, hole, depth, true, smoothingThreshold);
  237. });
  238. }
  239. result.indices = indices;
  240. result.positions = positions;
  241. result.normals = normals;
  242. result.uvs = uvs;
  243. return result;
  244. }
  245. /**
  246. * Adds a side to the polygon
  247. * @param positions points that make the polygon
  248. * @param normals normals of the polygon
  249. * @param uvs uvs of the polygon
  250. * @param indices indices of the polygon
  251. * @param bounds bounds of the polygon
  252. * @param points points of the polygon
  253. * @param depth depth of the polygon
  254. * @param flip flip of the polygon
  255. * @param smoothingThreshold
  256. */
  257. _addSide(positions, normals, uvs, indices, bounds, points, depth, flip, smoothingThreshold) {
  258. let startIndex = positions.length / 3;
  259. let ulength = 0;
  260. for (let i = 0; i < points.elements.length; i++) {
  261. const p = points.elements[i];
  262. const p1 = points.elements[(i + 1) % points.elements.length];
  263. positions.push(p.x, 0, p.y);
  264. positions.push(p.x, -depth, p.y);
  265. positions.push(p1.x, 0, p1.y);
  266. positions.push(p1.x, -depth, p1.y);
  267. const p0 = points.elements[(i + points.elements.length - 1) % points.elements.length];
  268. const p2 = points.elements[(i + 2) % points.elements.length];
  269. let vc = new Vector3(-(p1.y - p.y), 0, p1.x - p.x);
  270. let vp = new Vector3(-(p.y - p0.y), 0, p.x - p0.x);
  271. let vn = new Vector3(-(p2.y - p1.y), 0, p2.x - p1.x);
  272. if (!flip) {
  273. vc = vc.scale(-1);
  274. vp = vp.scale(-1);
  275. vn = vn.scale(-1);
  276. }
  277. const vc_norm = vc.normalizeToNew();
  278. let vp_norm = vp.normalizeToNew();
  279. let vn_norm = vn.normalizeToNew();
  280. const dotp = Vector3.Dot(vp_norm, vc_norm);
  281. if (dotp > smoothingThreshold) {
  282. if (dotp < Epsilon - 1) {
  283. vp_norm = new Vector3(p.x, 0, p.y).subtract(new Vector3(p1.x, 0, p1.y)).normalize();
  284. }
  285. else {
  286. // cheap average weighed by side length
  287. vp_norm = vp.add(vc).normalize();
  288. }
  289. }
  290. else {
  291. vp_norm = vc_norm;
  292. }
  293. const dotn = Vector3.Dot(vn, vc);
  294. if (dotn > smoothingThreshold) {
  295. if (dotn < Epsilon - 1) {
  296. // back to back
  297. vn_norm = new Vector3(p1.x, 0, p1.y).subtract(new Vector3(p.x, 0, p.y)).normalize();
  298. }
  299. else {
  300. // cheap average weighed by side length
  301. vn_norm = vn.add(vc).normalize();
  302. }
  303. }
  304. else {
  305. vn_norm = vc_norm;
  306. }
  307. uvs.push(ulength / bounds.width, 0);
  308. uvs.push(ulength / bounds.width, 1);
  309. ulength += vc.length();
  310. uvs.push(ulength / bounds.width, 0);
  311. uvs.push(ulength / bounds.width, 1);
  312. normals.push(vp_norm.x, vp_norm.y, vp_norm.z);
  313. normals.push(vp_norm.x, vp_norm.y, vp_norm.z);
  314. normals.push(vn_norm.x, vn_norm.y, vn_norm.z);
  315. normals.push(vn_norm.x, vn_norm.y, vn_norm.z);
  316. if (!flip) {
  317. indices.push(startIndex);
  318. indices.push(startIndex + 1);
  319. indices.push(startIndex + 2);
  320. indices.push(startIndex + 1);
  321. indices.push(startIndex + 3);
  322. indices.push(startIndex + 2);
  323. }
  324. else {
  325. indices.push(startIndex);
  326. indices.push(startIndex + 2);
  327. indices.push(startIndex + 1);
  328. indices.push(startIndex + 1);
  329. indices.push(startIndex + 2);
  330. indices.push(startIndex + 3);
  331. }
  332. startIndex += 4;
  333. }
  334. }
  335. }
  336. //# sourceMappingURL=polygonMesh.js.map