1 /*
  2     Copyright 2008-2025
  3         Matthias Ehmann,
  4         Carsten Miller,
  5         Andreas Walter,
  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, Float32Array: true */
 31 /*jslint nomen: true, plusplus: true, bitwise: true*/
 32 
 33 /**
 34  * @fileoverview In this file the namespace JXG.Parse3D is defined.
 35  */
 36 import JXG from "../jxg.js";
 37 // import Type from "../utils/type.js";
 38 
 39 /**
 40  * Namespace Parse3D. Contains parsers for 3D models like STL.
 41  * @namespace
 42  */
 43 JXG.Parse3D = {
 44 
 45     /**
 46      * Parser for the ASCII STL format, see https://en.wikipedia.org/wiki/STL_(file_format).
 47      * STL stands for stereo-lithography.
 48      *
 49      * @param {String} str String containing STL file format
 50      * @returns {Array} [[vertices, faces], ...] as list of polyhedra. Each entry is the input for a polyhedron3d.
 51      * @example
 52      *         const board = JXG.JSXGraph.initBoard(
 53      *             'jxgbox',
 54      *             {
 55      *                 boundingbox: [-8, 8, 8, -8],
 56      *                 minimizeReflow: 'svg',
 57      *                 axis: false,
 58      *                 showNavigation: false,
 59      *                 zoom: {
 60      *                     enabled: false
 61      *                 },
 62      *                 pan: {
 63      *                     enabled: false
 64      *                 }
 65      *             }
 66      *         );
 67      *
 68      *         var bound = [-1, 2];
 69      *         var view = board.create(
 70      *             'view3d',
 71      *             [[-5, -3], [8, 8],
 72      *             [bound, bound, bound]],
 73      *             {
 74      * 	            axesPosition: 'none',
 75      *                 projection: 'central',
 76      *                 trackball: { enabled: true },
 77      *                 depthOrder: { enabled: true },
 78      *                 xPlaneRear: { visible: false },
 79      *                 yPlaneRear: { visible: false },
 80      *                 zPlaneRear: { fillOpacity: 0.2, visible: true },
 81      *                 az: {
 82      *                     slider: {
 83      *                         visible: true,
 84      *                         start: 1.54
 85      *                     }
 86      *                 }
 87      *
 88      *             }
 89      *         );
 90      *
 91      *  // Tetrahedron
 92      *  var model = `solid m
 93      *  facet normal 0 0 0
 94      *    outer loop
 95      *      vertex 0 0 0
 96      *      vertex 1 0 0
 97      *      vertex 1 1 0
 98      *    endloop
 99      *   endfacet
100      *  facet normal 0 0 0
101      *    outer loop
102      *      vertex 0 0 0
103      *      vertex 1 0 0
104      *      vertex 0.5 0.5 1
105      *    endloop
106      *   endfacet
107      *  facet normal 0 0 0
108      *    outer loop
109      *      vertex 0 0 0
110      *      vertex 1 1 0
111      *      vertex 0.5 0.5 1
112      *    endloop
113      *   endfacet
114      *  facet normal 0 0 0
115      *    outer loop
116      *      vertex 1 0 0
117      *      vertex 1 1 0
118      *      vertex 0.5 0.5 1
119      *    endloop
120      *   endfacet
121      * endsolid m`;
122      *
123      * var m = JXG.Parse3D.STL(model);
124      *
125      *  for (let i = 0; i < m.length; i++) {
126      *      view.create('polyhedron3d', m[i], {
127      *           fillColorArray: [], // ['yellow', 'red', 'green', 'blue'],
128      *           layer: 12,
129      *           strokeWidth: 0,
130      *           shader: {
131      *               enabled: true,
132      *               type: 'angle',
133      *               hue: 0 + 60 * i,
134      *               saturation: 90,
135      *               minlightness: 60,
136      *               maxLightness: 80
137      *           },
138      *           fillOpacity: 0.8
139      *       });
140      *   }
141      *
142      * </pre><div id="JXG8fa8ce22-3613-452f-9775-69588a1c1e34" class="jxgbox" style="width: 300px; height: 300px;"></div>
143      * <script type="text/javascript">
144      *     (function() {
145      *         var board = JXG.JSXGraph.initBoard('JXG8fa8ce22-3613-452f-9775-69588a1c1e34', {
146      *                     showcopyright: false, shownavigation: false,
147      *                     boundingbox: [-8, 8, 8, -8],
148      *                     minimizeReflow: 'svg',
149      *                     axis: false,
150      *                     showNavigation: false,
151      *                     zoom: {
152      *                         enabled: false
153      *                     },
154      *                     pan: {
155      *                         enabled: false
156      *                     }
157      *                 }
158      *             );
159      *
160      *             var bound = [-1, 2]; // Tetrahedron
161      *             var view = board.create(
162      *                 'view3d',
163      *                 [[-5, -3], [8, 8],
164      *                 [bound, bound, bound]],
165      *                 {
166      *     	               axesPosition: 'none',
167      *                     projection: 'central',
168      *                     trackball: { enabled: true },
169      *                     depthOrder: { enabled: true },
170      *                     xPlaneRear: { visible: false },
171      *                     yPlaneRear: { visible: false },
172      *                     zPlaneRear: { fillOpacity: 0.2, visible: true },
173      *                     az: {
174      *                         slider: {
175      *                             visible: true,
176      *                             start: 1.54
177      *                         }
178      *                     }
179      *
180      *                 }
181      *             );
182      *
183      *   // Tetrahedron
184      *   var model = `solid m
185      *      facet normal 0 0 0
186      *        outer loop
187      *          vertex 0 0 0
188      *          vertex 1 0 0
189      *          vertex 1 1 0
190      *        endloop
191      *       endfacet
192      *      facet normal 0 0 0
193      *        outer loop
194      *          vertex 0 0 0
195      *          vertex 1 0 0
196      *          vertex 0.5 0.5 1
197      *        endloop
198      *       endfacet
199      *      facet normal 0 0 0
200      *        outer loop
201      *          vertex 0 0 0
202      *          vertex 1 1 0
203      *          vertex 0.5 0.5 1
204      *        endloop
205      *       endfacet
206      *      facet normal 0 0 0
207      *        outer loop
208      *          vertex 1 0 0
209      *          vertex 1 1 0
210      *          vertex 0.5 0.5 1
211      *        endloop
212      *       endfacet
213      *     endsolid m`;
214      *
215      *             var m = JXG.Parse3D.STL(model);
216      *
217      *  for (let i = 0; i < m.length; i++) {
218      *      view.create('polyhedron3d', m[i], {
219      *           fillColorArray: [], // ['yellow', 'red', 'green', 'blue'],
220      *           layer: 12,
221      *           strokeWidth: 0,
222      *           shader: {
223      *               enabled: true,
224      *               type: 'angle',
225      *               hue: 0 + 60 * i,
226      *               saturation: 90,
227      *               minlightness: 60,
228      *               maxLightness: 80
229      *           },
230      *           fillOpacity: 0.8
231      *       });
232      *   }
233      *     })();
234      *
235      * </script><pre>
236      *
237      */
238     STL: function (str) {
239         var i, j, pos, le,
240             li,
241             lines,
242             coords,
243             face_num, found,
244             polyhedra = [],
245             vertices = [],
246             faces = [];
247 
248         lines = str.split('\n');
249 
250         le = lines.length;
251         for (i = 0; i < le; i++) {
252             li = lines[i].trim();
253 
254             if (li.indexOf('solid') === 0) {
255                 // New model
256                 face_num = -1;
257                 vertices = [];
258                 faces = [];
259             } else if (li.indexOf('endsolid') === 0) {
260                 polyhedra.push([vertices.slice(), faces.slice()]);
261                 // break;
262             } else if (li.indexOf('facet') === 0) {
263                 face_num++;
264                 faces.push([]);
265             } else if (li.indexOf('outer loop') === 0 || li.indexOf('endloop') === 0) {
266                 continue;
267             } else if (li.indexOf('vertex') === 0) {
268                 coords = li.split(' ').slice(1).map((x) => parseFloat(x));
269                 found = false;
270                 for (j = 0; j < vertices.length; j++) {
271                     if (JXG.Math.Geometry.distance(vertices[j], coords, 3) < JXG.Math.eps) {
272                         // Debug:
273                         // console.log("Point already defined")
274                         found = true;
275                         pos = j;
276                         break;
277                     }
278                 }
279                 if (found === false) {
280                     pos = vertices.length;
281                     vertices.push(coords);
282                 }
283                 faces[face_num].push(pos);
284             }
285         }
286         // console.log('v:', vertices.length, 'f:', faces.length)
287 
288         // return [vertices, faces];
289         return polyhedra;
290     }
291 
292 };
293 
294 
295 export default JXG.Parse3D;
296