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