1 /* 2 Copyright 2005-2026 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Alfred Wassermann 7 8 This file is part of JSXGraph. 9 10 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 11 12 You can redistribute it and/or modify it under the terms of the 13 14 * GNU Lesser General Public License as published by 15 the Free Software Foundation, either version 3 of the License, or 16 (at your option) any later version 17 OR 18 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 19 20 JSXGraph is distributed in the hope that it will be useful, 21 but WITHOUT ANY WARRANTY; without even the implied warranty of 22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 GNU Lesser General Public License for more details. 24 25 You should have received a copy of the GNU Lesser General Public License and 26 the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/> 27 and <https://opensource.org/licenses/MIT/>. 28 */ 29 30 /*global JXG: true, define: true*/ 31 /*jslint nomen: true, plusplus: true*/ 32 /*eslint no-loss-of-precision: off */ 33 34 /** 35 * @fileoverview In this file the namespace JXG.Math.Tiling is defined, which holds numerical 36 * algorithms for creating meshes for surface3d elements. 37 */ 38 import Mat from "./math.js"; 39 40 /** 41 * The JXG.Math.Tiling namespace. 42 * @name JXG.Math.Tiling 43 * @exports Mat.Numerics as JXG.Math.Tiling 44 * @namespace 45 */ 46 Mat.Tiling = { 47 /** 48 * Triangulate (partition it into triangles) a given two dimensional domain. 49 * The number of triangles the original rectangle is divided into depends on the parameters stepsU and stepsV. 50 * Input are the ranges of u and v, as well as stepsU and stepsV which are static. 51 * If the optional parameter stepsV is not given or is equal to 0, the rectangle is partitioned into 52 * nearly equilateral triangles. 53 * Otherwise, the shape of the triangles depends on the ratio of stepsU / stepsV. 54 * @name triangulation 55 * @param {JXG.ParametricSurface3D|JXG.Plane3D} el element which is displayed using a polyhedron3d. 56 * From this element its function F is used. 57 * @param {Array} rg_u Begin and end of first direction (numbers or functions) 58 * @param {Array} rg_v Begin and end of second direction (numbers or functions) 59 * @param {Number} stepsU Immutable 60 * @param {Number} [stepsV=0] Immutable 61 * @returns [coords,faces] 62 * @memberof JXG.Math.Tiling 63 * 64 * @example 65 * var rg = board.create('slider', [[-7, -7], [3, -7], [1, 4, 5]], { name: 'rg' }); // range 66 * 67 * var box = [-5, 5]; 68 * var view = board.create('view3d', 69 * [[-5, -3], [8, 8], 70 * [box, box, box]], 71 * { 72 * projection: 'central', 73 * xPlaneRear: { visible: false }, 74 * yPlaneRear: { visible: false }, 75 * zPlaneRear: { visible: false } 76 * }); 77 * 78 * var range = [() => -rg.Value(), () => rg.Value()], 79 * stepsU = 25, 80 * // stepsV = 25, 81 * F = (x, y) => [x, y, 3 * Math.sin(Math.sqrt(x ** 2 + y ** 2))]; 82 * 83 * view.setView(0, Math.PI / 2); // Set view from above 84 * 85 * var el = { F: F }; // Fake surface3d element 86 * 87 * // stepsV not supplied -> approx. equilater triangles 88 * var surface = JXG.Math.Tiling.triangulation(el, range, range, stepsU); 89 * var pol = view.create('polyhedron3d', surface, { 90 * shader: { 91 * enabled: true, 92 * light: { 93 * dir: 0 94 * } 95 * }, 96 * fillColorArray: ['white'], 97 * fillOpacity: 0.9, 98 * strokeWidth: 0.2 99 * }); 100 * 101 * </pre><div id="JXG37cb3ef3-a818-4114-97b6-954d0189e0fb" class="jxgbox" style="width: 300px; height: 300px;"></div> 102 * <script type="text/javascript"> 103 * (function() { 104 * var board = JXG.JSXGraph.initBoard('JXG37cb3ef3-a818-4114-97b6-954d0189e0fb', 105 * {boundingbox: [-8, 8, 8,-8], axis: false, pan: { enabled: false }, showcopyright: false, shownavigation: false}); 106 * var rg = board.create('slider', [[-7, -7], [3, -7], [1, 4, 5]], { name: 'rg' }); 107 * 108 * var box = [-5, 5]; 109 * var view = board.create('view3d', 110 * [[-5, -3], [8, 8], 111 * [box, box, box]], 112 * { 113 * projection: 'central', 114 * xPlaneRear: { visible: false }, 115 * yPlaneRear: { visible: false }, 116 * zPlaneRear: { visible: false } 117 * }); 118 * 119 * var range = [() => -rg.Value(), () => rg.Value()], 120 * stepsU = 25, 121 * stepsV = 25, 122 * F = (x, y) => [x, y, 3 * Math.sin(Math.sqrt(x ** 2 + y ** 2))]; 123 * view.setView(0, Math.PI / 2); 124 * 125 * var el = { F: F }; // Fake surface3d element 126 * 127 * var surface = JXG.Math.Tiling.triangulation(el, range, range, stepsU); 128 * var pol = view.create('polyhedron3d', surface, { 129 * shader: { 130 * enabled: true, 131 * light: { 132 * dir: 0 133 * } 134 * }, 135 * fillColorArray: ['white'], 136 * fillOpacity: 0.9, 137 * strokeWidth: 0.2 138 * }); 139 * 140 * })(); 141 * 142 * </script><pre> 143 * 144 */ 145 triangulation: function (el, rg_u, rg_v, stepsU, stepsV) { 146 var i, j, le, 147 up, 148 ru, rv, du, dv, 149 vertices = [], 150 faces = []; 151 152 if (stepsV === undefined || stepsV === 0) { 153 // Approximate equilateral triangles 154 ru = JXG.evaluate(rg_u); 155 rv = JXG.evaluate(rg_v); 156 du = (ru[1] - ru[0]) / stepsU; 157 dv = du * Math.sqrt(3) / 2; 158 stepsV = Math.round(Math.abs(rv[1] - rv[0]) / dv); 159 } 160 for (j = 0; j <= stepsV; j++) { 161 up = (j % 2 === 0) ? stepsU : stepsU + 1; 162 for (i = 0; i <= up; i++) { 163 // Generate vertices 164 vertices.push( 165 (function (ii, jj, up) { 166 var s = ii; 167 if (jj % 2 === 1) { 168 s = (ii === up) ? (ii - 1) : ((ii > 0) ? (ii - 0.5) : s); 169 } 170 171 return function () { 172 var ru = JXG.evaluate(rg_u), 173 rv = JXG.evaluate(rg_v), 174 u = ru[0] + s * (ru[1] - ru[0]) / stepsU, 175 v = rv[0] + jj * (rv[1] - rv[0]) / stepsV, 176 val = el.F(u, v); 177 // return [u, v, val]; 178 return (val.length === 4) ? val.slice(1) : val; 179 }; 180 })(i, j, up) 181 ); 182 183 // Generate faces 184 if (j > 0) { 185 le = vertices.length - 1; 186 if (j % 2 === 1) { 187 if (i > 0) { 188 faces.push([le - 1, le, le - 2 - stepsU]); 189 if (i < up) { 190 faces.push([le, le - 1 - stepsU, le - 2 - stepsU]); 191 } else { 192 faces.push([le - 1, le, le - 2 - stepsU]); 193 } 194 } 195 } else { 196 if (i > 0) { 197 faces.push([le, le - 2 - stepsU, le - 1]); 198 } 199 faces.push([le, le - 1 - stepsU, le - 2 - stepsU]); 200 } 201 } 202 } 203 } 204 return [vertices, faces]; 205 }, 206 207 /** 208 * Rectangulate (partition it into rectangles) a given two dimensional domain. 209 * The number of rectangles the original rectangle is divided into depends on the parameters stepsU and stepsV. 210 * Input are the ranges of u and v, as well as stepsU and stepsV which are static. 211 * @name rectangulation 212 * @param {JXG.ParametricSurface3D|JXG.Plane3D} el element which is displayed using a polyhedron3d. 213 * From this element its function F is used. 214 * @param {Array} rg_u Begin and end of first direction (numbers or functions) 215 * @param {Array} rg_v Begin and end of second direction (numbers or functions) 216 * @param {Number} stepsU Immutable 217 * @param {Number} stepsV Immutable 218 * @returns [coords,faces] 219 * @memberof JXG.Math.Tiling 220 * 221 * @example 222 * var rg = board.create('slider', [[-7, -7], [3, -7], [1, 4, 5]], { name: 'rg' }); 223 * 224 * var box = [-5, 5]; 225 * var view = board.create('view3d', 226 * [[-5, -3], [8, 8], 227 * [box, box, box]], 228 * { 229 * projection: 'central', 230 * // axesPosition: 'center', 231 * xPlaneRear: { visible: false }, 232 * yPlaneRear: { visible: false }, 233 * zPlaneRear: { visible: false } 234 * }); 235 * 236 * var range = [() => -rg.Value(), () => rg.Value()], 237 * stepsU = 15, 238 * stepsV = 5, 239 * F = (x, y) => [x, y, 3 * Math.sin(Math.sqrt(x ** 2 + y ** 2))]; 240 * 241 * view.setView(0, Math.PI / 2); // Set view from above 242 * 243 * var el = { F: F }; // Fake surface3d element 244 * 245 * var surface = JXG.Math.Tiling.rectangulation(el, range, range, stepsU, stepsV); 246 * var pol = view.create('polyhedron3d', surface, { 247 * shader: { 248 * enabled: true, 249 * light: { 250 * dir: 0 251 * } 252 * }, 253 * fillColorArray: ['white'], 254 * fillOpacity: 0.9, 255 * strokeWidth: 0.2 256 * }); 257 * 258 * </pre><div id="JXG8c99ffea-7edc-494a-b416-34d9c154d310" class="jxgbox" style="width: 300px; height: 300px;"></div> 259 * <script type="text/javascript"> 260 * (function() { 261 * var board = JXG.JSXGraph.initBoard('JXG8c99ffea-7edc-494a-b416-34d9c154d310', 262 * {boundingbox: [-8, 8, 8,-8], axis: false, pan: { enabled: false }, showcopyright: false, shownavigation: false}); 263 * var rg = board.create('slider', [[-7, -7], [3, -7], [1, 4, 5]], { name: 'rg' }); 264 * 265 * var box = [-5, 5]; 266 * var view = board.create('view3d', 267 * [[-5, -3], [8, 8], 268 * [box, box, box]], 269 * { 270 * projection: 'central', 271 * // axesPosition: 'center', 272 * xPlaneRear: { visible: false }, 273 * yPlaneRear: { visible: false }, 274 * zPlaneRear: { visible: false } 275 * }); 276 * 277 * var range = [() => -rg.Value(), () => rg.Value()], 278 * stepsU = 15, 279 * stepsV = 5, 280 * F = (x, y) => [x, y, 3 * Math.sin(Math.sqrt(x ** 2 + y ** 2))]; 281 * 282 * view.setView(0, Math.PI / 2); 283 * 284 * var el = { F: F }; // Fake surface3d element 285 * 286 * var surface = JXG.Math.Tiling.rectangulation(el, range, range, stepsU, stepsV); 287 * var pol = view.create('polyhedron3d', surface, { 288 * shader: { 289 * enabled: true, 290 * light: { 291 * dir: 0 292 * } 293 * }, 294 * fillColorArray: ['white'], 295 * fillOpacity: 0.9, 296 * strokeWidth: 0.2 297 * }); 298 * 299 * })(); 300 * 301 * </script><pre> 302 * 303 */ 304 rectangulation: function (el, rg_u, rg_v, stepsU, stepsV) { 305 var vertices = [], 306 faces = [], 307 i, j, le; 308 309 for (j = 0; j <= stepsV; j++) { 310 for (i = 0; i <= stepsU; i++) { 311 vertices.push( 312 (function (ii, jj) { 313 return function () { 314 var ru = JXG.evaluate(rg_u), 315 rv = JXG.evaluate(rg_v), 316 u = ru[0] + ii * (ru[1] - ru[0]) / stepsU, 317 v = rv[0] + jj * (rv[1] - rv[0]) / stepsV, 318 val = el.F(u, v); 319 return (val.length === 4) ? val.slice(1) : val; 320 }; 321 })(i, j) 322 ); 323 if (i > 0 && j > 0) { 324 le = vertices.length - 1; 325 faces.push( 326 // [le - 1 - stepsU - 1, le - 1 - stepsU, le, le - 1] 327 [le - 1, le, le - 1 - stepsU, le - 1 - stepsU - 1] 328 ); 329 } 330 } 331 } 332 return [vertices, faces]; 333 } 334 335 // triangulation_old: function (p1, p2, p3, p4, stepsU, stepsV) { 336 // // Vectors used for checking if the given coordinates create a rectangle 337 // var vec1 = [p2[0] - p1[0], p2[1] - p1[1]], 338 // vec2 = [p3[0] - p2[0], p3[1] - p2[1]], 339 // vec3 = [p4[0] - p3[0], p4[1] - p3[1]], 340 // vec4 = [p1[0] - p4[0], p1[1] - p4[1]], 341 342 // coords = [], 343 // faces = [], 344 // width, height, 345 // wSide, hSide, 346 // triangleWidth, triangleHeight, 347 // s1, s2, 348 // i, j, 349 // numRows, 350 // widthX, widthY, heightX, heightY, 351 // oddPoints, 352 // evenPoints; 353 354 // // Check if the given coordinates create a rectangle, otherwise an exception is thrown 355 // if ( 356 // vec1[0] * vec4[0] + vec1[1] * vec4[1] !== 0 || 357 // vec2[0] * vec1[0] + vec2[1] * vec1[1] !== 0 || 358 // vec3[0] * vec2[0] + vec3[1] * vec2[1] !== 0 || 359 // vec4[0] * vec3[0] + vec4[1] * vec3[1] !== 0 360 // ) { 361 // throw new Error(" the board created is not rectangle "); 362 // } 363 364 // // Set initial values for wSide, hSide 365 // wSide = [0, 0]; 366 // hSide = [0, 0]; 367 368 // // Check for longer side of rectangle: 369 // // longer side is appointed height, shorter side is appointed width 370 // s1 = Math.sqrt((p2[0] - p1[0]) * (p2[0] - p1[0]) + (p2[1] - p1[1]) * (p2[1] - p1[1])); 371 // s2 = Math.sqrt((p3[0] - p2[0]) * (p3[0] - p2[0]) + (p3[1] - p2[1]) * (p3[1] - p2[1])); 372 // if (s1 <= s2) { 373 // width = s1; 374 // height = s2; 375 // // Determine start and end points of the width-side and height-side 376 // wSide = [p1, p2]; 377 // hSide = [p2, p3]; 378 // } else { 379 // width = s2; 380 // height = s1; 381 // // Determine start and end points of the width-side and height-side 382 // wSide = [p2, p3]; 383 // hSide = [p1, p2]; 384 // } 385 // // Calculate height and width of the triangles and number of rows 386 // triangleWidth = width / stepsU; 387 388 // if (stepsV === undefined || stepsV === 0) { 389 // // Equilateral triangles, depending on parameter "stepsU" 390 // triangleHeight = (triangleWidth * Math.sqrt(3)) / 2; 391 // numRows = Math.round(height / triangleHeight); 392 // } else { 393 // // Two parameters stepsU, stepsV 394 // numRows = stepsV; 395 // triangleHeight = height / numRows; 396 // } 397 398 // // Calculate values of "shifting vectors" 399 // widthX = (wSide[1][0] - wSide[0][0]) / stepsU; 400 // widthY = (wSide[1][1] - wSide[0][1]) / stepsU; 401 // heightX = (hSide[1][0] - hSide[0][0]) / numRows; 402 // heightY = (hSide[1][1] - hSide[0][1]) / numRows; 403 404 // oddPoints = []; 405 // evenPoints = []; 406 407 // // Push coordinates of the base point (p1) 408 // coords.push([p1[0], p1[1]]); 409 // evenPoints.push(coords.length - 1); 410 411 // // Calculate point coordinates of layer 0 and store indices in evenPoints 412 // for (i = 1; i <= stepsU; i++) { 413 // coords.push([p1[0] + i * widthX, p1[1] + i * widthY]); // Points first line 414 // evenPoints.push(coords.length - 1); 415 // } 416 417 // for (i = 1; i <= numRows; i++) { 418 // if (i % 2 === 0) { 419 // // Points and faces of layers with an even index 420 421 // evenPoints = []; 422 // // Calculate first point coordinates within the layer 423 // coords.push([ 424 // p1[0] + i * heightX, 425 // p1[1] + i * heightY 426 // ]); 427 // // evenPoints stores index of the first point of the layer 428 // evenPoints.push(coords.length - 1); 429 // // Calculate all other point coordinates within the layer 430 // for (j = 1; j <= stepsU; j++) { 431 // coords.push([ 432 // coords[evenPoints[0]][0] + j * widthX, 433 // coords[evenPoints[0]][1] + j * widthY 434 // ]); 435 // //evenPoints stores index of the most recently calculated point of the layer 436 // evenPoints.push(coords.length - 1); 437 // } 438 // // Connect the faces of the row by grouping indices of points 439 // faces.push([evenPoints[0], oddPoints[0], oddPoints[1]]); 440 // for (j = 1; j <= stepsU; j++) { 441 // faces.push([evenPoints[j - 1], oddPoints[j], evenPoints[j]]); 442 // faces.push([evenPoints[j], oddPoints[j], oddPoints[j + 1]]); 443 // } 444 // } else { 445 // // Points and faces of layers with an odd index 446 447 // oddPoints = []; 448 // // Calculate first point coordinates within the layer 449 // coords.push([ 450 // p1[0] + i * heightX, 451 // p1[1] + i * heightY 452 // ]); 453 // // oddPoints stores index of the first point of the layer 454 // oddPoints.push(coords.length - 1); 455 // // Calculate all other point coordinates within the layer except for the last point 456 // for (j = 1; j <= stepsU; j++) { 457 // coords.push([ 458 // coords[oddPoints[0]][0] + j * widthX - widthX / 2, 459 // coords[oddPoints[0]][1] + j * widthY - widthY / 2 460 // ]); 461 // // oddPoints stores index of the most recently calculated point of the layer 462 // oddPoints.push(coords.length - 1); 463 // } 464 // // Calculate last point coordinates within the layer 465 // coords.push([ 466 // coords[oddPoints[0]][0] + stepsU * widthX, 467 // coords[oddPoints[0]][1] + stepsU * widthY 468 // ]); 469 // // oddPoints stores index of last point within the layer 470 // oddPoints.push(coords.length - 1); 471 // // Connect the faces of the row by grouping indices of points 472 // faces.push([oddPoints[0], evenPoints[0], oddPoints[1]]); 473 // for (j = 1; j <= stepsU; j++) { 474 // faces.push([oddPoints[j], evenPoints[j - 1], evenPoints[j]]); 475 // faces.push([oddPoints[j], evenPoints[j], oddPoints[j + 1]]); 476 // } 477 // } 478 // } 479 480 // return [coords, faces]; 481 // }, 482 483 // rectangulation_old: function (p1, p2, p3, p4, stepsU, stepsV) { 484 // // Vectors used for checking if the given coordinates create a rectangle 485 // var vec1 = [p2[0] - p1[0], p2[1] - p1[1]], 486 // vec2 = [p3[0] - p2[0], p3[1] - p2[1]], 487 // vec3 = [p4[0] - p3[0], p4[1] - p3[1]], 488 // vec4 = [p1[0] - p4[0], p1[1] - p4[1]], 489 490 // coords = [], 491 // faces = [], 492 // wSide, hSide, 493 // s1, s2, 494 // i, j, 495 // widthX, widthY, 496 // heightX, heightY, 497 // startPointLayer; 498 499 // // Check if the given coordinates create a rectangle, otherwise an exception is thrown 500 // if ( 501 // vec1[0] * vec4[0] + vec1[1] * vec4[1] !== 0 || 502 // vec2[0] * vec1[0] + vec2[1] * vec1[1] !== 0 || 503 // vec3[0] * vec2[0] + vec3[1] * vec2[1] !== 0 || 504 // vec4[0] * vec3[0] + vec4[1] * vec3[1] !== 0 505 // ) { 506 // throw new Error("rectangulation_old: area is not rectangle "); 507 // // console.log("rectangulation_old: area is not rectangle "); 508 // } 509 510 // // Set initial values for wSide, hSide 511 // wSide = [0, 0]; 512 // hSide = [0, 0]; 513 514 // // Check for longer side of rectangle: 515 // // longer side is appointed height, shorter side is appointed width 516 // // s1 = Math.sqrt((p2[0] - p1[0]) * (p2[0] - p1[0]) + (p2[1] - p1[1]) * (p2[1] - p1[1])); 517 // // s2 = Math.sqrt((p3[0] - p2[0]) * (p3[0] - p2[0]) + (p3[1] - p2[1]) * (p3[1] - p2[1])); 518 // s1 = Mat.hypot(vec1[0], vec1[1]); 519 // s2 = Mat.hypot(vec2[0], vec2[1]); 520 521 // if (s1 <= s2) { 522 // // Determine start and end points of the width-side and height-side 523 // wSide = [p1, p2]; 524 // hSide = [p2, p3]; 525 // } else { 526 // // Determine start and end points of the width-side and height-side 527 // wSide = [p2, p3]; 528 // hSide = [p1, p2]; 529 // } 530 531 // // Calculate values of "shifting vectors" 532 // widthX = (wSide[1][0] - wSide[0][0]) / stepsV; 533 // widthY = (wSide[1][1] - wSide[0][1]) / stepsV; 534 // heightX = (hSide[1][0] - hSide[0][0]) / stepsU; 535 // heightY = (hSide[1][1] - hSide[0][1]) / stepsU; 536 537 // // Initialize startPointLayer that stores coordinates of the first point of the current layer 538 // startPointLayer = []; 539 540 // // Push coordinates of base point (p1) 541 // coords.push([p1[0], p1[1]]); 542 // startPointLayer = coords[0]; 543 544 // // Calculate point coordinates of layer 0 545 // for (j = 1; j <= stepsV; j++) { 546 // coords.push([startPointLayer[0] + j * widthX, startPointLayer[1] + j * widthY]); 547 // } 548 549 // for (i = 1; i <= stepsU; i++) { 550 // startPointLayer = []; 551 552 // // Calculate point coordinates of first point of layer 553 // coords.push([ 554 // p1[0] + i * heightX, 555 // p1[1] + i * heightY 556 // ]); 557 // startPointLayer = coords[coords.length - 1]; 558 559 // // Calculating remaining point coordinates of layer 560 // for (j = 1; j <= stepsV; j++) { 561 // coords.push([ 562 // startPointLayer[0] + j * widthX, 563 // startPointLayer[1] + j * widthY 564 // ]); 565 // } 566 567 // // Connect rectangles by grouping indices of points 568 // for ( 569 // j = coords.length - stepsV - 1; 570 // j < coords.length - 1; 571 // j++ 572 // ) { 573 // faces.push([j, j - stepsV - 1, j - stepsV, j + 1]); 574 // } 575 // } 576 577 // return [coords, faces]; 578 // }, 579 580 // /** 581 // * This function creates an array of dynamic 3-dimensional points 582 // * based on an array of pairs of two coordinates. 583 // * It uses a mathematical function to assign a third coordinate (the z-coordinate) 584 // * to each pair of two coordinates. 585 // * The 3-dimensional points are not stored directly. 586 // * Instead the array stores JavaScript functions that utilize the mentioned mathematical function 587 // * to return an array of three coordinates. 588 // * This allows the recognition and proper visualization of changes to the underlying 589 // * mathematical function. 590 // * @name mapMeshTo3D 591 // * @param {Array} surface 592 // * @param {Parametricsurface3d} el 593 // * @returns {Array} dynamicPoints array of [x, y, z] coordinates 594 // * 595 // * @private 596 // * @memberof JXG.Math.Tiling 597 // */ 598 // mapMeshTo3D: function (surface, el) { 599 // var dynamicPoints = [], i; 600 601 // for (i = 0; i < surface[0].length; i++) { 602 // dynamicPoints.push( 603 // (function (u, v) { 604 // return function(x, y) { return el.F(u, v); }; 605 // })(surface[0][i][0], surface[0][i][1]) 606 // ); // Capture values explicitly 607 // // dynamicPoints.push( 608 // // (function (u, v) { 609 // // return function(x, y) { return el.F(Type.evaluate(u), Type.evaluate(v)); }; 610 // // })(surface[0][i][0], surface[0][i][1]) 611 // // ); // Capture values explicitly 612 // } 613 614 // return dynamicPoints; 615 // } 616 }; 617 618 export default Mat.Tiling; 619