1 /*
  2     Copyright 2008-2026
  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 /*global JXG:true, define: true*/
 30 
 31 import JXG from "../jxg.js";
 32 import Const from "../base/constants.js";
 33 import Type from "../utils/type.js";
 34 
 35 /**
 36  * A 3D polyhedron is a basic geometric element.
 37  * @class Creates a new 3D point object. Do not use this constructor to create a 3D point. Use {@link JXG.View3D#create} with
 38  * type {@link Polyhedron3D} instead.
 39  *
 40  * @augments JXG.GeometryElement3D
 41  * @augments JXG.GeometryElement
 42  * @param {JXG.View3D} view The 3D view the polyhedron is drawn on.
 43  * @param {Object} polyhedron Defining data for the polyhedron, i.e. vertice coordinates and faces, which are lists of vertex numbers or keys. The structure of this object is
 44  * <pre>polyhedron = {
 45  *        view: view,
 46  *        vertices: {},
 47  *        coords: {},
 48  *        coords2D: {},
 49  *        zIndex: {},
 50  *        faces: []
 51  *  };</pre>
 52  * @param {Array} faces List of face3d objects. These have been already generated in `createPolyhedron3D`.
 53  * @param {Object} attributes An object containing visual properties like in {@link JXG.Options#polyhedron3d} and {@link JXG.Options#elements},
 54  * and optionally a name and an id.
 55  */
 56 JXG.Polyhedron3D = function (view, polyhedron, faces, attributes) {
 57     var e,
 58         genericMethods,
 59         generateMethod,
 60         that = this;
 61 
 62     this.constructor(view.board, attributes, Const.OBJECT_TYPE_POLYHEDRON3D, Const.OBJECT_CLASS_3D);
 63     this.constructor3D(view, 'polyhedron3d');
 64 
 65     this.board.finalizeAdding(this);
 66 
 67     this.elType = 'polyhedron3d';
 68 
 69     /**
 70      * List of Face3D objects.
 71      * @name Polyhedron3D#faces
 72      * @type Array
 73      */
 74     this.faces = faces;
 75 
 76     /**
 77      * Number of faces
 78      * @name Polyhedron3D#numberFaces
 79      * @type Number
 80      */
 81     this.numberFaces = faces.length;
 82 
 83     /**
 84      * Contains the defining data of the polyhedron:
 85      * Definitions of vertices and a list of vertices for each face. This is pretty much the input given
 86      * in the construction of the polyhedron plus internal data.
 87      * @name Polyhedron3D#def
 88      * @type Object
 89      * @example
 90      *  polyhedron = {
 91      *      view: view,
 92      *      vertices: {},
 93      *      coords: {},
 94      *      coords2D: {},
 95      *      zIndex: {},
 96      *      faces: []
 97      *  };
 98      */
 99     this.def = polyhedron;
100 
101     // Simultaneous methods for all faces
102     genericMethods = [
103         "setAttribute",
104         "setParents",
105         "prepareUpdate",
106         "updateRenderer",
107         "update",
108         "fullUpdate",
109         "highlight",
110         "noHighlight"
111     ];
112 
113     /**
114      * Generate methods like `updateRenderer` or `setAttribute` which call simultaneously
115      * the method with the same name for each face3d element of the polyhedron.
116      * @param {String} what Method name
117      * @returns {Function} Function consisting of a loop that calls the method for each face.
118      */
119     generateMethod = function (what) {
120         return function () {
121             var i;
122 
123             for (i in that.faces) {
124                 if (that.faces.hasOwnProperty(i)) {
125                     if (Type.exists(that.faces[i][what])) {
126                         that.faces[i][what].apply(that.faces[i], arguments);
127                     }
128                 }
129             }
130             return that;
131         };
132     };
133 
134     for (e = 0; e < genericMethods.length; e++) {
135         this[genericMethods[e]] = generateMethod(genericMethods[e]);
136     }
137 
138     this.methodMap = Type.deepCopy(this.methodMap, {
139         setAttribute: "setAttribute",
140         setParents: "setParents",
141         addTransform: "addTransform"
142     });
143 };
144 JXG.Polyhedron3D.prototype = new JXG.GeometryElement();
145 Type.copyPrototypeMethods(JXG.Polyhedron3D, JXG.GeometryElement3D, 'constructor3D');
146 
147 JXG.extend(
148     JXG.Polyhedron3D.prototype,
149     /** @lends JXG.Polyhedron3D.prototype */ {
150 
151         // Already documented in element3d.js
152         addTransform: function (el, transform) {
153             if (this.faces.length > 0 && el.faces.length > 0) {
154                 this.faces[0].addTransform(el.faces[0], transform);
155             } else {
156                 throw new Error("Adding transformation failed. At least one of the two polyhedra has no faces.");
157             }
158             return this;
159         },
160 
161         /**
162          * Output polyhedron in ASCII STL format.
163          * Normals are ignored and output as 0 0 0.
164          *
165          * @param {String} name Set name of the model, overwrites property name
166          * @returns String
167          */
168         toSTL: function(name) {
169             var i, j, v, f, c, le,
170                 txt = 'solid ';
171 
172             if (name === undefined) {
173                 name = this.name;
174             }
175 
176             txt += name + '\n';
177 
178             for (i = 0; i < this.def.faces.length; i++) {
179                 txt += ' facet normal 0 0 0\n  outer loop\n';
180                 f = this.def.faces[i];
181                 le = f.length;
182                 v = this.def.coords;
183                 for (j = 0; j < le; j++) {
184                     c = v[f[j]];
185                     txt += '   vertex ' + c[1] + ' ' + c[2] + ' ' + c[3] + '\n';
186                 }
187                 txt += '  endloop\n endfacet\n';
188             }
189             txt += 'endsolid ' + name + '\n';
190 
191             return txt;
192         }
193     }
194 );
195 
196 /**
197  * @class A polyhedron in a 3D view consists of faces.
198  * @pseudo
199  * @description Create a polyhedron in a 3D view consisting of faces. Faces can
200  * be 0-, 1- or 2-dimensional.
201  *
202  * @name Polyhedron3D
203  * @augments JXG.GeometryElement3D
204  * @constructor
205  * @type Object
206  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
207  * @param {} TODO
208  *
209  * @example
210  * var box = [-4, 4];
211  * var view = board.create(
212  *     'view3d',
213  *     [[-5, -3], [8, 8],
214  *     [box, box, box]],
215  *     {
216  *         projection: 'parallel',
217  *         trackball: { enabled: false },
218  *         depthOrder: {
219  *             enabled: true
220  *         },
221  *         xPlaneRear: { visible: false },
222  *         yPlaneRear: { visible: false },
223  *         zPlaneRear: { fillOpacity: 0.2 }
224  *     }
225  * );
226  * var cube = view.create('polyhedron3d', [
227  * [
228  *     [-3, -3, -3],
229  *     [3, -3, -3],
230  *     [3, 3, -3],
231  *     [-3, 3, -3],
232  *
233  *     [-3, -3, 3],
234  *     [3, -3, 3],
235  *     [3, 3, 3],
236  *     [-3, 3, 3]
237  * ],
238  * [
239  *     [0, 1, 2, 3],
240  *     [0, 1, 5, 4],
241  *     [[1, 2, 6, 5], { fillColor: 'black', fillOpacity: 0.5, strokeWidth: 5 }],
242  *     [2, 3, 7, 6],
243  *     [3, 0, 4, 7],
244  *     [4, 5, 6, 7]
245  * ]
246  * ], {
247  * fillColorArray: ['blue', 'red', 'yellow']
248  * });
249  *
250  * </pre><div id="JXG2ab3325b-4171-4a00-9896-a1b886969e18" class="jxgbox" style="width: 300px; height: 300px;"></div>
251  * <script type="text/javascript">
252  *     (function() {
253  *         var board = JXG.JSXGraph.initBoard('JXG2ab3325b-4171-4a00-9896-a1b886969e18',
254  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
255  *     var box = [-4, 4];
256  *     var view = board.create(
257  *         'view3d',
258  *         [[-5, -3], [8, 8],
259  *         [box, box, box]],
260  *         {
261  *             projection: 'parallel',
262  *             trackball: { enabled: false },
263  *             depthOrder: {
264  *                 enabled: true
265  *             },
266  *             xPlaneRear: { visible: false },
267  *             yPlaneRear: { visible: false },
268  *             zPlaneRear: { fillOpacity: 0.2 }
269  *         }
270  *     );
271  *     var cube = view.create('polyhedron3d', [
272  *     [
273  *         [-3, -3, -3],
274  *         [3, -3, -3],
275  *         [3, 3, -3],
276  *         [-3, 3, -3],
277  *
278  *         [-3, -3, 3],
279  *         [3, -3, 3],
280  *         [3, 3, 3],
281  *         [-3, 3, 3]
282  *     ],
283  *     [
284  *         [0, 1, 2, 3],
285  *         [0, 1, 5, 4],
286  *         [[1, 2, 6, 5], { fillColor: 'black', fillOpacity: 0.5, strokeWidth: 5 }],
287  *         [2, 3, 7, 6],
288  *         [3, 0, 4, 7],
289  *         [4, 5, 6, 7]
290  *     ]
291  *     ], {
292  *     fillColorArray: ['blue', 'red', 'yellow']
293  *     });
294  *
295  *     })();
296  *
297  * </script><pre>
298  *
299  * @example
300  * var box = [-4, 4];
301  * var view = board.create(
302  *     'view3d',
303  *     [[-5, -3], [8, 8],
304  *     [box, box, box]],
305  *     {
306  *         projection: 'parallel',
307  *         trackball: { enabled: false },
308  *         depthOrder: {
309  *             enabled: true
310  *         },
311  *         xPlaneRear: { visible: false },
312  *         yPlaneRear: { visible: false },
313  *         zPlaneRear: { fillOpacity: 0.2 }
314  *     }
315  * );
316  * var aa = view.create('point3d', [-3, -3, -3], { name: 'A', layer: 12});
317  * var bb = view.create('point3d', [() => aa.X(), () => aa.Y(), 3], { name: 'B', fixed: true, layer: 12});
318  * var cube = view.create('polyhedron3d', [
319  *     {
320  *         a: 'A',
321  *         b: [3, -3, -3],
322  *         c: [3, 3, -3],
323  *         d: [-3, 3, -3],
324  *
325  *         e: bb,
326  *         f: [3, -3, 3],
327  *         g: [3, 3, 3],
328  *         h: [-3, 3, 3]
329  *     },
330  *     [
331  *         ['a', 'b', 'c', 'd'],
332  *         ['a', 'b', 'f', 'e'],
333  *         ['b', 'c', 'g', 'f'],
334  *         ['c', 'd', 'h', 'g'],
335  *         ['d', 'a', 'e', 'h'],
336  *         ['e', 'f', 'g', 'h'],
337  *
338  *         ['a', 'g'], // Edge
339  *         ['f']       // Vertex
340  *     ]
341  * ], {
342  *     fillColorArray: ['blue', 'red', 'yellow'],
343  *     fillOpacity: 0.4,
344  *     layer: 12
345  * });
346  * cube.faces[6].setAttribute({ strokeWidth: 5 });
347  * cube.faces[7].setAttribute({ strokeWidth: 10 });
348  *
349  * </pre><div id="JXG1e862f44-3e38-424b-98d5-f972338a8b7f" class="jxgbox" style="width: 300px; height: 300px;"></div>
350  * <script type="text/javascript">
351  *     (function() {
352  *         var board = JXG.JSXGraph.initBoard('JXG1e862f44-3e38-424b-98d5-f972338a8b7f',
353  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
354  *     var box = [-4, 4];
355  *     var view = board.create(
356  *         'view3d',
357  *         [[-5, -3], [8, 8],
358  *         [box, box, box]],
359  *         {
360  *             projection: 'parallel',
361  *             trackball: { enabled: false },
362  *             depthOrder: {
363  *                 enabled: true
364  *             },
365  *             xPlaneRear: { visible: false },
366  *             yPlaneRear: { visible: false },
367  *             zPlaneRear: { fillOpacity: 0.2 }
368  *         }
369  *     );
370  *     var aa = view.create('point3d', [-3, -3, -3], { name: 'A', layer: 12});
371  *     var bb = view.create('point3d', [() => aa.X(), () => aa.Y(), 3], { name: 'B', fixed: true, layer: 12});
372  *     var cube = view.create('polyhedron3d', [
373  *         {
374  *             a: 'A',
375  *             b: [3, -3, -3],
376  *             c: [3, 3, -3],
377  *             d: [-3, 3, -3],
378  *
379  *             e: bb,
380  *             f: [3, -3, 3],
381  *             g: [3, 3, 3],
382  *             h: [-3, 3, 3]
383  *         },
384  *         [
385  *             ['a', 'b', 'c', 'd'],
386  *             ['a', 'b', 'f', 'e'],
387  *             ['b', 'c', 'g', 'f'],
388  *             ['c', 'd', 'h', 'g'],
389  *             ['d', 'a', 'e', 'h'],
390  *             ['e', 'f', 'g', 'h'],
391  *
392  *             ['a', 'g'], // Edge
393  *             ['f']       // Vertex
394  *         ]
395  *     ], {
396  *         fillColorArray: ['blue', 'red', 'yellow'],
397  *         fillOpacity: 0.4,
398  *         layer: 12
399  *     });
400  *     cube.faces[6].setAttribute({ strokeWidth: 5 });
401  *     cube.faces[7].setAttribute({ strokeWidth: 10 });
402  *
403  *     })();
404  *
405  * </script><pre>
406  *
407  * @example
408  * var box = [-4, 4];
409  * var view = board.create(
410  *     'view3d',
411  *     [[-5, -3], [8, 8],
412  *     [box, box, box]],
413  *     {
414  *         projection: 'parallel',
415  *         trackball: { enabled: false },
416  *         depthOrder: {
417  *             enabled: true
418  *         },
419  *         xPlaneRear: { visible: false },
420  *         yPlaneRear: { visible: false },
421  *         zPlaneRear: { fillOpacity: 0.2 }
422  *     }
423  * );
424  * var s = board.create('slider', [[-4, -6], [4, -6], [0, 2, 4]], { name: 's' });
425  * var cube = view.create('polyhedron3d', [
426  *     [
427  *         () => { let f = s.Value(); return [-f, -f, -f]; },
428  *         () => { let f = s.Value(); return [f, -f, -f]; },
429  *         () => { let f = s.Value(); return [f, f, -f]; },
430  *         () => { let f = s.Value(); return [-f, f, -f]; },
431  *
432  *         () => { let f = s.Value(); return [-f, -f, f]; },
433  *         () => { let f = s.Value(); return [f, -f, f]; },
434  *         () => { let f = s.Value(); return [f, f, f]; },
435  *         // () => { let f = s.Value(); return [-f, f, f]; }
436  *         [ () => -s.Value(),  () => s.Value(), () => s.Value() ]
437  *     ],
438  *     [
439  *         [0, 1, 2, 3],
440  *         [0, 1, 5, 4],
441  *         [1, 2, 6, 5],
442  *         [2, 3, 7, 6],
443  *         [3, 0, 4, 7],
444  *         [4, 5, 6, 7],
445  *     ]
446  * ], {
447  *     strokeWidth: 3,
448  *     fillOpacity: 0.6,
449  *     fillColorArray: ['blue', 'red', 'yellow'],
450  *     shader: {
451  *         enabled:false
452  *     }
453  * });
454  *
455  * </pre><div id="JXG6f27584b-b648-4743-a864-a6c559ead00e" class="jxgbox" style="width: 300px; height: 300px;"></div>
456  * <script type="text/javascript">
457  *     (function() {
458  *         var board = JXG.JSXGraph.initBoard('JXG6f27584b-b648-4743-a864-a6c559ead00e',
459  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
460  *     var box = [-4, 4];
461  *     var view = board.create(
462  *         'view3d',
463  *         [[-5, -3], [8, 8],
464  *         [box, box, box]],
465  *         {
466  *             projection: 'parallel',
467  *             trackball: { enabled: false },
468  *             depthOrder: {
469  *                 enabled: true
470  *             },
471  *             xPlaneRear: { visible: false },
472  *             yPlaneRear: { visible: false },
473  *             zPlaneRear: { fillOpacity: 0.2 }
474  *         }
475  *     );
476  *     var s = board.create('slider', [[-4, -6], [4, -6], [0, 2, 4]], { name: 's' });
477  *     var cube = view.create('polyhedron3d', [
478  *         [
479  *             () => { let f = s.Value(); return [-f, -f, -f]; },
480  *             () => { let f = s.Value(); return [f, -f, -f]; },
481  *             () => { let f = s.Value(); return [f, f, -f]; },
482  *             () => { let f = s.Value(); return [-f, f, -f]; },
483  *
484  *             () => { let f = s.Value(); return [-f, -f, f]; },
485  *             () => { let f = s.Value(); return [f, -f, f]; },
486  *             () => { let f = s.Value(); return [f, f, f]; },
487  *             // () => { let f = s.Value(); return [-f, f, f]; }
488  *             [ () => -s.Value(),  () => s.Value(), () => s.Value() ]
489  *         ],
490  *         [
491  *             [0, 1, 2, 3],
492  *             [0, 1, 5, 4],
493  *             [1, 2, 6, 5],
494  *             [2, 3, 7, 6],
495  *             [3, 0, 4, 7],
496  *             [4, 5, 6, 7],
497  *         ]
498  *     ], {
499  *         strokeWidth: 3,
500  *         fillOpacity: 0.6,
501  *         fillColorArray: ['blue', 'red', 'yellow'],
502  *         shader: {
503  *             enabled:false
504  *         }
505  *     });
506  *
507  *     })();
508  *
509  * </script><pre>
510  *
511  * @example
512  * var box = [-4, 4];
513  * var view = board.create(
514  *     'view3d',
515  *     [[-5, -3], [8, 8],
516  *     [box, box, box]],
517  *     {
518  *         projection: 'parallel',
519  *         trackball: { enabled: false },
520  *         depthOrder: {
521  *             enabled: true
522  *         },
523  *         xPlaneRear: { visible: false },
524  *         yPlaneRear: { visible: false },
525  *         zPlaneRear: { fillOpacity: 0.2 }
526  *     }
527  * );
528  * let rho = 1.6180339887;
529  * let vertexList = [
530  *     [0, -1, -rho], [0, +1, -rho], [0, -1, rho], [0, +1, rho],
531  *     [1, rho, 0], [-1, rho, 0], [1, -rho, 0], [-1, -rho, 0],
532  *     [-rho, 0, 1], [-rho, 0, -1], [rho, 0, 1], [rho, 0, -1]
533  * ];
534  * let faceArray = [
535  *     [4, 1, 11],
536  *     [11, 1, 0],
537  *     [6, 11, 0],
538  *     [0, 1, 9],
539  *     [11, 10, 4],
540  *     [9, 1, 5],
541  *     [8, 9, 5],
542  *     [5, 3, 8],
543  *     [6, 10, 11],
544  *     [2, 3, 10],
545  *     [2, 10, 6],
546  *     [8, 3, 2],
547  *     [3, 4, 10],
548  *     [7, 8, 2],
549  *     [9, 8, 7],
550  *     [0, 9, 7],
551  *     [4, 3, 5],
552  *     [5, 1, 4],
553  *     [0, 7, 6],
554  *     [7, 2, 6]
555  * ];
556  * var ico = view.create('polyhedron3d', [vertexList, faceArray], {
557  * fillColorArray: [],
558  * fillOpacity: 1,
559  * strokeWidth: 0.1,
560  * layer: 12,
561  * shader: {
562  *     enabled: true,
563  *     type: 'angle',
564  *     hue: 60,
565  *     saturation: 90,
566  *     minlightness: 60,
567  *     maxLightness: 80
568  * }
569  * });
570  *
571  * </pre><div id="JXGfea93484-96e9-4eb5-9e45-bb53d612aead" class="jxgbox" style="width: 300px; height: 300px;"></div>
572  * <script type="text/javascript">
573  *     (function() {
574  *         var board = JXG.JSXGraph.initBoard('JXGfea93484-96e9-4eb5-9e45-bb53d612aead',
575  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
576  *     var box = [-4, 4];
577  *     var view = board.create(
578  *         'view3d',
579  *         [[-5, -3], [8, 8],
580  *         [box, box, box]],
581  *         {
582  *             projection: 'parallel',
583  *             trackball: { enabled: false },
584  *             depthOrder: {
585  *                 enabled: true
586  *             },
587  *             xPlaneRear: { visible: false },
588  *             yPlaneRear: { visible: false },
589  *             zPlaneRear: { fillOpacity: 0.2 }
590  *         }
591  *     );
592  *     let rho = 1.6180339887;
593  *     let vertexList = [
594  *     [0, -1, -rho], [0, +1, -rho], [0, -1, rho], [0, +1, rho],
595  *     [1, rho, 0], [-1, rho, 0], [1, -rho, 0], [-1, -rho, 0],
596  *     [-rho, 0, 1], [-rho, 0, -1], [rho, 0, 1], [rho, 0, -1]
597  *     ];
598  *     let faceArray = [
599  *     [4, 1, 11],
600  *     [11, 1, 0],
601  *     [6, 11, 0],
602  *     [0, 1, 9],
603  *     [11, 10, 4],
604  *     [9, 1, 5],
605  *     [8, 9, 5],
606  *     [5, 3, 8],
607  *     [6, 10, 11],
608  *     [2, 3, 10],
609  *     [2, 10, 6],
610  *     [8, 3, 2],
611  *     [3, 4, 10],
612  *     [7, 8, 2],
613  *     [9, 8, 7],
614  *     [0, 9, 7],
615  *     [4, 3, 5],
616  *     [5, 1, 4],
617  *     [0, 7, 6],
618  *     [7, 2, 6]
619  *     ];
620  *     var ico = view.create('polyhedron3d', [vertexList, faceArray], {
621  *     fillColorArray: [],
622  *     fillOpacity: 1,
623  *     strokeWidth: 0.1,
624  *     layer: 12,
625  *     shader: {
626  *         enabled: true,
627  *         type: 'angle',
628  *         hue: 60,
629  *         saturation: 90,
630  *         minlightness: 60,
631  *         maxLightness: 80
632  *     }
633  *     });
634  *
635  *     })();
636  *
637  * </script><pre>
638  *
639  */
640 JXG.createPolyhedron3D = function (board, parents, attributes) {
641     var view = parents[0],
642         i, le,
643         face, f,
644         el,
645         attr, attr_polyhedron,
646         faceList = [],
647         base = null,
648         transform = null,
649 
650         polyhedron = {
651             view: view,
652             vertices: {},
653             coords: {},
654             coords2D: {},
655             zIndex: {},
656             faces: []
657         };
658 
659     if (Type.exists(parents[1].type) && parents[1].type === Const.OBJECT_TYPE_POLYHEDRON3D) {
660         // Polyhedron from baseElement and transformations
661         base = parents[1];
662         transform = parents[2];
663         polyhedron.vertices = base.def.vertices;
664         polyhedron.faces = base.def.faces;
665     } else {
666         // Copy vertices into a dict
667         if (Type.isArray(parents[1])) {
668             le = parents[1].length;
669             for (i = 0; i < le; i++) {
670                 polyhedron.vertices[i] = parents[1][i];
671             }
672         } else if (Type.isObject(parents[1])) {
673             for (i in parents[1]) {
674                 if (parents[1].hasOwnProperty(i)) {
675                     polyhedron.vertices[i] = parents[1][i];
676                 }
677             }
678         }
679         polyhedron.faces = parents[2];
680     }
681 
682     attr_polyhedron = Type.copyAttributes(attributes, board.options, 'polyhedron3d');
683 
684     console.time('polyhedron');
685 
686     view.board.suspendUpdate();
687     // Create face3d elements
688     le = polyhedron.faces.length;
689     for (i = 0; i < le; i++) {
690         attr = Type.copyAttributes(attributes, board.options, 'face3d');
691         if (attr_polyhedron.fillcolorarray.length > 0) {
692             attr.fillcolor = attr_polyhedron.fillcolorarray[i % attr_polyhedron.fillcolorarray.length];
693         }
694         f = polyhedron.faces[i];
695 
696         if (Type.isArray(f) && f.length === 2 && Type.isObject(f[1]) && Type.isArray(f[0])) {
697             // Handle case that face is of type [[points], {attr}]
698             Type.mergeAttr(attr, f[1]);
699             // Normalize face array, i.e. don't store attributes of that face in polyhedron
700             polyhedron.faces[i] = f[0];
701         }
702 
703         face = view.create('face3d', [polyhedron, i], attr);
704         faceList.push(face);
705     }
706     el = new JXG.Polyhedron3D(view, polyhedron, faceList, attr_polyhedron);
707     el.setParents(el); // Sets el as parent to all faces.
708     for (i = 0; i < le; i++) {
709         el.inherits.push(el.faces[i]);
710         // el.addChild(el.faces[i]); // This takes very long time. We avoid it with
711                                      // a special treatment in removeObject
712     }
713     if (base !== null) {
714         el.addTransform(base, transform);
715         el.addParents(base);
716     }
717     view.board.unsuspendUpdate();
718 
719     console.timeEnd('polyhedron');
720 
721     return el;
722 };
723 
724 JXG.registerElement("polyhedron3d", JXG.createPolyhedron3D);
725