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