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 /** 42 * The JXG.Math.Tiling namespace. 43 * @name JXG.Math.Tiling 44 * @exports Mat.Numerics as JXG.Math.Tiling 45 * @namespace 46 */ 47 Mat.Tiling = { 48 /** 49 * A function which is used to triangulate a given rectangle. 50 * The rectangle is represented by four points p1, p2, p3, p4 (arrays of coordinates) given as parameters. 51 * It is triangulated in rows. 52 * The number and shape of the triangles depends on parameters stepsU and stepsV. 53 * If the optional parameter stepsV is not given or is equal to 0, the rectangle is partitioned into 54 * equilateral triangles. Otherwise, the shape of the triangles depends on the ratio of stepsU / stepsV. 55 * @name triangulation 56 * @param {Array} p1 57 * @param {Array} p2 58 * @param {Array} p3 59 * @param {Array} p4 60 * @param {Number} stepsU 61 * @param {Number} [stepsV=0] 62 * @returns [coords,faces] 63 * @memberof JXG.Math.Tiling 64 * 65 * @throws {Exception} if the given object- represented by 4 points- is no rectangle ,an exception is thrown 66 * @example 67 * var i, 68 * surface = JXG.Math.Tiling.triangulation([0,0], [0,4], [2,4], [2,0], 6); 69 * for (i = 0; i < surface[1].length; i++) { 70 * board.create('polygon',[ 71 * surface[0][surface[1][i][0]], 72 * surface[0][surface[1][i][1]], 73 * surface[0][surface[1][i][2]] 74 * ]); 75 * } 76 * 77 * 78 * </pre><div id="JXG948307f3-fc6b-4dc6-92d6-40eaa7918ee0" class="jxgbox" style="width: 300px; height: 300px;"></div> 79 * <script type="text/javascript"> 80 * (function() { 81 * var board = JXG.JSXGraph.initBoard('JXG948307f3-fc6b-4dc6-92d6-40eaa7918ee0', 82 * {boundingbox: [-1, 5, 3,-1], axis: true, showcopyright: false, shownavigation: false}); 83 * var i, 84 * surface = JXG.Math.Tiling.triangulation([0,0],[0,4],[2,4],[2,0], 6); 85 * for (i = 0; i < surface[1].length; i++) { 86 * board.create('polygon',[ 87 * surface[0][surface[1][i][0]], 88 * surface[0][surface[1][i][1]], 89 * surface[0][surface[1][i][2]] 90 * ]); 91 * } 92 * })(); 93 * 94 * </script><pre> 95 * 96 * @example 97 * var i, 98 * surface = JXG.Math.Tiling.triangulation([0,0], [0,4], [2,4], [2,0], 7, 3); 99 * for (i = 0; i < surface[1].length; i++) { 100 * board.create('polygon',[ 101 * surface[0][surface[1][i][0]], 102 * surface[0][surface[1][i][1]], 103 * surface[0][surface[1][i][2]] 104 * ]); 105 * } 106 * 107 * 108 * </pre><div id="JXGee3b922e-0dcb-47f2-9ef8-c79cb13dd733" class="jxgbox" style="width: 300px; height: 300px;"></div> 109 * <script type="text/javascript"> 110 * (function() { 111 * var board = JXG.JSXGraph.initBoard('JXGee3b922e-0dcb-47f2-9ef8-c79cb13dd733', 112 * {boundingbox: [-1, 5, 3,-1], axis: true, showcopyright: false, shownavigation: false}); 113 * var i, 114 * surface = JXG.Math.Tiling.triangulation([0,0],[0,4],[2,4],[2,0], 7, 3); 115 * for (i = 0; i < surface[1].length; i++) { 116 * board.create('polygon',[ 117 * surface[0][surface[1][i][0]], 118 * surface[0][surface[1][i][1]], 119 * surface[0][surface[1][i][2]] 120 * ]); 121 * } 122 * })(); 123 * 124 * </script><pre> 125 * 126 * 127 */ 128 triangulation: function (p1, p2, p3, p4, stepsU, stepsV) { 129 // Vectors used for checking if the given coordinates create a rectangle 130 var vec1 = [p2[0] - p1[0], p2[1] - p1[1]], 131 vec2 = [p3[0] - p2[0], p3[1] - p2[1]], 132 vec3 = [p4[0] - p3[0], p4[1] - p3[1]], 133 vec4 = [p1[0] - p4[0], p1[1] - p4[1]], 134 135 coords = [], 136 faces = [], 137 width, height, 138 wSide, hSide, 139 triangleWidth, triangleHeight, 140 s1, s2, 141 i, j, 142 numRows, 143 widthX, widthY, heightX, heightY, 144 oddPoints, 145 evenPoints; 146 147 // Check if the given coordinates create a rectangle, otherwise an exception is thrown 148 if ( 149 vec1[0] * vec4[0] + vec1[1] * vec4[1] !== 0 || 150 vec2[0] * vec1[0] + vec2[1] * vec1[1] !== 0 || 151 vec3[0] * vec2[0] + vec3[1] * vec2[1] !== 0 || 152 vec4[0] * vec3[0] + vec4[1] * vec3[1] !== 0 153 ) { 154 throw new Error(" the board created is not rectangle "); 155 } 156 157 // Set initial values for wSide, hSide 158 wSide = [0, 0]; 159 hSide = [0, 0]; 160 161 // Check for longer side of rectangle: 162 // longer side is appointed height, shorter side is appointed width 163 s1 = Math.sqrt((p2[0] - p1[0]) * (p2[0] - p1[0]) + (p2[1] - p1[1]) * (p2[1] - p1[1])); 164 s2 = Math.sqrt((p3[0] - p2[0]) * (p3[0] - p2[0]) + (p3[1] - p2[1]) * (p3[1] - p2[1])); 165 if (s1 <= s2) { 166 width = s1; 167 height = s2; 168 // Determine start and end points of the width-side and height-side 169 wSide = [p1, p2]; 170 hSide = [p2, p3]; 171 } else { 172 width = s2; 173 height = s1; 174 // Determine start and end points of the width-side and height-side 175 wSide = [p2, p3]; 176 hSide = [p1, p2]; 177 } 178 // Calculate height and width of the triangles and number of rows 179 triangleWidth = width / stepsU; 180 181 if (stepsV === undefined || stepsV === 0) { 182 // Equilateral triangles, depending on parameter "stepsU" 183 triangleHeight = (triangleWidth * Math.sqrt(3)) / 2; 184 numRows = Math.round(height / triangleHeight); 185 } else { 186 // Two parameters stepsU, stepsV 187 numRows = stepsV; 188 triangleHeight = height / numRows; 189 } 190 191 // Calculate values of "shifting vectors" 192 widthX = (wSide[1][0] - wSide[0][0]) / stepsU; 193 widthY = (wSide[1][1] - wSide[0][1]) / stepsU; 194 heightX = (hSide[1][0] - hSide[0][0]) / numRows; 195 heightY = (hSide[1][1] - hSide[0][1]) / numRows; 196 197 oddPoints = []; 198 evenPoints = []; 199 200 // Push coordinates of the base point (p1) 201 coords.push([p1[0], p1[1]]); 202 evenPoints.push(coords.length - 1); 203 204 // Calculate point coordinates of layer 0 and store indices in evenPoints 205 for (i = 1; i <= stepsU; i++) { 206 coords.push([p1[0] + i * widthX, p1[1] + i * widthY]); // Points first line 207 evenPoints.push(coords.length - 1); 208 } 209 210 for (i = 1; i <= numRows; i++) { 211 if (i % 2 === 0) { 212 // Points and faces of layers with an even index 213 214 evenPoints = []; 215 // Calculate first point coordinates within the layer 216 coords.push([ 217 p1[0] + i * heightX, 218 p1[1] + i * heightY 219 ]); 220 // evenPoints stores index of the first point of the layer 221 evenPoints.push(coords.length - 1); 222 // Calculate all other point coordinates within the layer 223 for (j = 1; j <= stepsU; j++) { 224 coords.push([ 225 coords[evenPoints[0]][0] + j * widthX, 226 coords[evenPoints[0]][1] + j * widthY 227 ]); 228 //evenPoints stores index of the most recently calculated point of the layer 229 evenPoints.push(coords.length - 1); 230 } 231 // Connect the faces of the row by grouping indices of points 232 faces.push([evenPoints[0], oddPoints[0], oddPoints[1]]); 233 for (j = 1; j <= stepsU; j++) { 234 faces.push([evenPoints[j - 1], oddPoints[j], evenPoints[j]]); 235 faces.push([evenPoints[j], oddPoints[j], oddPoints[j + 1]]); 236 } 237 } else { 238 // Points and faces of layers with an odd index 239 240 oddPoints = []; 241 // Calculate first point coordinates within the layer 242 coords.push([ 243 p1[0] + i * heightX, 244 p1[1] + i * heightY 245 ]); 246 // oddPoints stores index of the first point of the layer 247 oddPoints.push(coords.length - 1); 248 // Calculate all other point coordinates within the layer except for the last point 249 for (j = 1; j <= stepsU; j++) { 250 coords.push([ 251 coords[oddPoints[0]][0] + j * widthX - widthX / 2, 252 coords[oddPoints[0]][1] + j * widthY - widthY / 2 253 ]); 254 // oddPoints stores index of the most recently calculated point of the layer 255 oddPoints.push(coords.length - 1); 256 } 257 // Calculate last point coordinates within the layer 258 coords.push([ 259 coords[oddPoints[0]][0] + stepsU * widthX, 260 coords[oddPoints[0]][1] + stepsU * widthY 261 ]); 262 // oddPoints stores index of last point within the layer 263 oddPoints.push(coords.length - 1); 264 // Connect the faces of the row by grouping indices of points 265 faces.push([oddPoints[0], evenPoints[0], oddPoints[1]]); 266 for (j = 1; j <= stepsU; j++) { 267 faces.push([oddPoints[j], evenPoints[j - 1], evenPoints[j]]); 268 faces.push([oddPoints[j], evenPoints[j], oddPoints[j + 1]]); 269 } 270 } 271 } 272 273 return [coords, faces]; 274 }, 275 276 /** 277 * A function, which is used to rectangulate a given rectangle. 278 * The rectangle is rectangulated in rows. The number of rectangles the original 279 * rectangle is divided into depends on the parameters stepsU and stepsV. 280 * The rectangle is represented by the 4 points (arrays of coordinates) given as 281 * parameters. 282 * @name rectangulation 283 * @type Array 284 * @throws {Exception} if the given object - represented by four points - is no rectangle, exception is thrown 285 * @param {Array} p1 286 * @param {Array} p2 287 * @param {Array} p3 288 * @param {Array} p4 289 * @param {Number} stepsU 290 * @param {Number} stepsV 291 * @returns [coords,faces] 292 * @memberof JXG.Math.Tiling 293 * 294 * @example 295 * var i, 296 * surface = JXG.Math.Toiling.rectangulation([0,0], [0,5], [2,5], [2,0], 6, 6); 297 * for (i = 0; i < surface[1].length; i++) { 298 * board.create('polygon',[ 299 * surface[0][surface[1][i][0]], 300 * surface[0][surface[1][i][1]], 301 * surface[0][surface[1][i][2]], 302 * surface[0][surface[1][i][3]] 303 * ]); 304 * } 305 * 306 * </pre><div id="JXG05cfba29-be76-482f-9d49-8ee4c36033e4" class="jxgbox" style="width: 300px; height: 300px;"></div> 307 * <script type="text/javascript"> 308 * (function() { 309 * var board = JXG.JSXGraph.initBoard('JXG05cfba29-be76-482f-9d49-8ee4c36033e4', 310 * {boundingbox: [-1, 6, 6,-1], axis: true, showcopyright: false, shownavigation: false}); 311 * var i, 312 * surface = JXG.Math.Tiling.rectangulation([0,0],[0,5],[2,5],[2,0],6,6); 313 * for (i=0; i<surface[1].length; i++) { 314 * board.create('polygon',[ 315 * surface[0][surface[1][i][0]], 316 * surface[0][surface[1][i][1]], 317 * surface[0][surface[1][i][2]], 318 * surface[0][surface[1][i][3]] 319 * ]); 320 * } 321 * 322 * })(); 323 * 324 * </script><pre> 325 * 326 * 327 * */ 328 rectangulation: function (p1, p2, p3, p4, stepsU, stepsV) { 329 // Vectors used for checking if the given coordinates create a rectangle 330 var vec1 = [p2[0] - p1[0], p2[1] - p1[1]], 331 vec2 = [p3[0] - p2[0], p3[1] - p2[1]], 332 vec3 = [p4[0] - p3[0], p4[1] - p3[1]], 333 vec4 = [p1[0] - p4[0], p1[1] - p4[1]], 334 335 coords = [], 336 faces = [], 337 wSide, hSide, 338 s1, s2, 339 i, j, 340 widthX, widthY, 341 heightX, heightY, 342 startPointLayer; 343 344 // Check if the given coordinates create a rectangle, otherwise an exception is thrown 345 if ( 346 vec1[0] * vec4[0] + vec1[1] * vec4[1] !== 0 || 347 vec2[0] * vec1[0] + vec2[1] * vec1[1] !== 0 || 348 vec3[0] * vec2[0] + vec3[1] * vec2[1] !== 0 || 349 vec4[0] * vec3[0] + vec4[1] * vec3[1] !== 0 350 ) { 351 throw new Error(" the board created is not rectangle "); 352 } 353 354 // Set initial values for wSide, hSide 355 wSide = [0, 0]; 356 hSide = [0, 0]; 357 358 // Check for longer side of rectangle: 359 // longer side is appointed height, shorter side is appointed width 360 s1 = Math.sqrt((p2[0] - p1[0]) * (p2[0] - p1[0]) + (p2[1] - p1[1]) * (p2[1] - p1[1])); 361 s2 = Math.sqrt((p3[0] - p2[0]) * (p3[0] - p2[0]) + (p3[1] - p2[1]) * (p3[1] - p2[1])); 362 363 if (s1 <= s2) { 364 // Determine start and end points of the width-side and height-side 365 wSide = [p1, p2]; 366 hSide = [p2, p3]; 367 } else { 368 // Determine start and end points of the width-side and height-side 369 wSide = [p2, p3]; 370 hSide = [p1, p2]; 371 } 372 373 // Calculate values of "shifting vectors" 374 widthX = (wSide[1][0] - wSide[0][0]) / stepsV; 375 widthY = (wSide[1][1] - wSide[0][1]) / stepsV; 376 heightX = (hSide[1][0] - hSide[0][0]) / stepsU; 377 heightY = (hSide[1][1] - hSide[0][1]) / stepsU; 378 379 // Initialize startPointLayer, which saves coordinates of the first point of the current layer 380 startPointLayer = []; 381 382 // Push coordinates of base point (p1) 383 coords.push([p1[0], p1[1]]); 384 startPointLayer = coords[0]; 385 386 // Calculate point coordinates of layer 0 387 for (j = 1; j <= stepsV; j++) { 388 coords.push([startPointLayer[0] + j * widthX, startPointLayer[1] + j * widthY]); 389 } 390 391 for (i = 1; i <= stepsU; i++) { 392 startPointLayer = []; 393 394 // Calculate point coordinates of first point of layer 395 coords.push([ 396 p1[0] + i * heightX, 397 p1[1] + i * heightY 398 ]); 399 startPointLayer = coords[coords.length - 1]; 400 401 // Calculating remaining point coordinates of layer 402 for (j = 1; j <= stepsV; j++) { 403 coords.push([ 404 startPointLayer[0] + j * widthX, 405 startPointLayer[1] + j * widthY 406 ]); 407 } 408 409 // Connect rectangles by grouping indices of points 410 for ( 411 j = coords.length - stepsV - 1; 412 j < coords.length - 1; 413 j++ 414 ) { 415 faces.push([j, j - stepsV - 1, j - stepsV, j + 1]); 416 } 417 } 418 419 return [coords, faces]; 420 }, 421 422 /** 423 * This function creates an array of dynamic 3-dimensional points 424 * based on an array of pairs of two coordinates. 425 * It uses a mathematical function to assign a third coordinate (the z-coordinate) 426 * to each pair of two coordinates. 427 * The 3-dimensional points are not stored directly. 428 * Instead the array stores JavaScript functions that utilize the mentioned mathematical function 429 * to return an array of three coordinates. 430 * This allows the recognition and proper visualization of changes to the underlying 431 * mathematical function. 432 * @name mapMeshTo3D 433 * @param {Array} surface 434 * @param {Parametricsurface3d} el 435 * @returns {Array} dynamicPoints array of [x, y, z] coordinates 436 * 437 * @private 438 * @memberof JXG.Math.Tiling 439 */ 440 mapMeshTo3D: function (surface, el) { 441 var dynamicPoints = [], i; 442 443 for (i = 0; i < surface[0].length; i++) { 444 dynamicPoints.push( 445 (function (u, v) { 446 return function(x, y) { return el.F(u, v); }; 447 })(surface[0][i][0], surface[0][i][1]) 448 ); // Capture values explicitly 449 } 450 451 return dynamicPoints; 452 } 453 }; 454 455 export default Mat.Tiling; 456