      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 /*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 import Mat from "../math/math.js";
     35
     36 /**
     37  * 3D faces
     38  * @class Creates a new 3D face object. Do not use this constructor to create a 3D curve. Use {@link JXG.View3D#create} with type {@link Face3D} instead.
     39  *
     40  * @augments JXG.GeometryElement3D
     41  * @augments JXG.GeometryElement
     42  * @param {View3D} view
     43  * @param {Function} F
     44  * @param {Function} X
     45  * @param {Function} Y
     46  * @param {Function} Z
     47  * @param {Array} range
     48  * @param {Object} attributes
     49  * @see JXG.Board#generateName
     50  */
     51 JXG.Face3D = function (view, polyhedron, faceNumber, attributes) {
     52     this.constructor(view.board, attributes, Const.OBJECT_TYPE_FACE3D, Const.OBJECT_CLASS_3D);
     53     this.constructor3D(view, "face3d");
     54
     55     this.board.finalizeAdding(this);
     56
     57     /**
     58      * Link to the defining data of the parent polyhedron3d.
     59      * @name Face3D#polyhedron
     60      * @type Object
     61      * @see Polyhedron3D#def
     62      */
     63     this.polyhedron = polyhedron;
     64
     65     /**
     66      * Index of the face in the list of faces of the polyhedron
     67      * @name Face3D#faceNumber
     68      * @type Number
     69      */
     70     this.faceNumber = faceNumber;
     71
     72     /**
     73      * Normal vector for the face. Array of length 4.
     74      * @name Face3D#normal
     75      * @type array
     76      */
     77     this.normal = [0, 0, 0, 0];
     78
     79     /**
     80      * Hesse right hand side of the plane that contains the face.
     81      * @name Face3D#d
     82      * @type Number
     83      */
     84     this.d = 0;
     85
     86     /**
     87      * First basis vector of the face. Vector of length 4.
     88      * @name Face3D#vec1
     89      * @type Array
     90      */
     91     this.vec1 = [0, 0, 0, 0];
     92
     93     /**
     94      * Second basis vector of the face. Vector of length 4.
     95      * @name Face3D#vec2
     96      * @type Array
     97      */
     98     this.vec2 = [0, 0, 0, 0];
     99
    100     if (this.faceNumber === 0) {
    101         this.updateCoords();
    102     }
    103
    104     this.methodMap = Type.deepCopy(this.methodMap, {
    105         // TODO
    106     });
    107 };
    108 JXG.Face3D.prototype = new JXG.GeometryElement();
    109 Type.copyPrototypeMethods(JXG.Face3D, JXG.GeometryElement3D, "constructor3D");
    110
    111 JXG.extend(
    112     JXG.Face3D.prototype,
    113     /** @lends JXG.Face3D.prototype */ {
    114
    115         /**
    116          * Update the coordinates of all vertices of the polyhedron
    117          * @function
    118          * @name Face3D#updateCoords
    119          * @returns {Face3D} reference to itself
    120          */
    121         updateCoords: function() {
    122             var i, j, le, p,
    123                 def = this.polyhedron;
    124
    125             for (i in def.vertices) {
    126                 p = def.vertices[i];
    127                 if (Type.isFunction(p)) {
    128                     def.coords[i] = Type.evaluate(p);
    129                 } else if (Type.isArray(p)) {
    130                     def.coords[i] = [];
    131                     le = p.length;
    132                     for (j = 0; j < le; j++) {
    133                         def.coords[i][j] = Type.evaluate(p[j]);
    134                     }
    135                 } else {
    136                     p = def.view.select(p);
    137                     if (Type.isPoint3D(p)) {
    138                         def.coords[i] = p.coords;
    139                     } else {
    140                         throw new Error('Polyhedron3D.updateCoords: unknown vertices type!');
    141                     }
    142                 }
    143                 if (def.coords[i].length === 3) {
    144                     def.coords[i].unshift(1);
    145                 }
    146             }
    147
    148             return this;
    149         },
    150
    151         /**
    152          * Update the 2d coordinates of the face
    153          * @function
    154          * @name Face3D#updateDataArray2D
    155          * @returns {Object} {X:[], Y:[]}
    156          */
    157         updateDataArray2D: function () {
    158             var j, le,
    159                 c3d, c2d,
    160                 x = [],
    161                 y = [],
    162                 p = this.polyhedron,
    163                 face = p.faces[this.faceNumber];
    164
    165             if (this.faceNumber === 0) {
    166                 // coords2D equal to [] means, projection is needed down below.
    167                 // Thus, every vertex is projected only once.
    168                 for (j in p.vertices) {
    169                     p.coords2D[j] = [];
    170                 }
    171             }
    172
    173             // Add the projected coordinates of the vertices of this face
    174             // to the 2D curve.
    175             // If not done yet, project the 3D vertices of this face to 2D.
    176             le = face.length;
    177             this.zIndex = 0.0;
    178             for (j = 0; j < le; j++) {
    179                 c2d = p.coords2D[face[j]];
    180                 if (c2d.length === 0) {
    181                     // if coords2D.length > 0, it has already be projected
    182                     // in another face3d.
    183                     c3d = p.coords[face[j]];
    184                     c2d = this.view.project3DTo2D(c3d);
    185                     p.coords2D[face[j]] = c2d;
    186                     p.zIndex[face[j]] = Mat.matVecMult(this.view.matrix3DRotShift, c3d)[3];
    187                 }
    188                 x.push(c2d[1]);
    189                 y.push(c2d[2]);
    190
    191                 this.zIndex += p.zIndex[face[j]];
    192             }
    193             if (le > 0) {
    194                 this.zIndex /= le;
    195             }
    196             if (le !== 2) {
    197                 // 2D faces and points are a closed loop
    198                 x.push(x[0]);
    199                 y.push(y[0]);
    200             }
    201
    202             return { X: x, Y: y };
    203         },
    204
    205         addTransform: function (el, transform) {
    206             if (this.faceNumber === 0) {
    207                 this.addTransformGeneric(el, transform);
    208             }
    209             return this;
    210         },
    211
    212         updateTransform: function () {
    213             var t, c, i, j, b;
    214
    215             if (this.faceNumber !== 0) {
    216                 return this;
    217             }
    218
    219             if (this.transformations.length === 0 || this.baseElement === null) {
    220                 return this;
    221             }
    222
    223             t = this.transformations;
    224             for (i = 0; i < t.length; i++) {
    225                 t[i].update();
    226             }
    227
    228             if (this === this.baseElement) {
    229                 b = this.polyhedron;
    230             } else {
    231                 b = this.baseElement.polyhedron;
    232             }
    233             for (i in b.coords) {
    234                 if (b.coords.hasOwnProperty(i)) {
    235                     c = b.coords[i];
    236                     for (j = 0; j < t.length; j++) {
    237                         c = Mat.matVecMult(t[j].matrix, c);
    238                     }
    239                     this.polyhedron.coords[i] = c;
    240                 }
    241             }
    242
    243             return this;
    244         },
    245
    246         update: function () {
    247             var i, le,
    248                 phdr, nrm,
    249                 p1, p2,
    250                 face;
    251
    252             if (this.needsUpdate && !this.view.board._change3DView) {
    253                 phdr = this.polyhedron;
    254
    255                 if (this.faceNumber === 0) {
    256                     // Update coordinates of all vertices
    257                     this.updateCoords()
    258                         .updateTransform();
    259                 }
    260
    261                 face = phdr.faces[this.faceNumber];
    262                 le = face.length;
    263                 if (le < 3) {
    264                     // Get out of here if face is point or segment
    265                     return this;
    266                 }
    267
    268                 // Update spanning vectors
    269                 p1 = phdr.coords[face[0]];
    270                 p2 = phdr.coords[face[1]];
    271                 this.vec1 = [p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2], p2[3] - p1[3]];
    272
    273                 p2 = phdr.coords[face[2]];
    274                 this.vec2 = [p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2], p2[3] - p1[3]];
    275
    276                 // Update Hesse form, i.e. normal and d
    277                 this.normal = Mat.crossProduct(this.vec1.slice(1), this.vec2.slice(1));
    278                 nrm = Mat.norm(this.normal);
    279                 this.normal.unshift(0);
    280
    281                 if (Math.abs(nrm) > 1.e-12) {
    282                     for (i = 1; i < 4; i++) {
    283                         this.normal[i] /= nrm;
    284                     }
    285                 }
    286                 this.d = Mat.innerProduct(p1, this.normal, 4);
    287             }
    288             return this;
    289         },
    290
    291         updateRenderer: function () {
    292             if (this.needsUpdate) {
    293                 this.needsUpdate = false;
    294                 this.shader();
    295             }
    296             return this;
    297         },
    298
    299         /**
    300          * Determines the lightness of the face (in the HSL color scheme).
    301          * <p>
    302          * Sets the fillColor of the adjoint 2D curve.
    303          * @name shader
    304          * @memberOf Face3D
    305          * @function
    306          * @returns {Number} zIndex of the face
    307          */
    308         shader: function() {
    309             var hue, sat, light, angle, hsl,
    310                 // bb = this.view.bbox3D,
    311                 minFace, maxFace,
    312                 minLight, maxLight;
    313
    314
    315             if (this.evalVisProp('shader.enabled')) {
    316                 hue = this.evalVisProp('shader.hue');
    317                 sat = this.evalVisProp('shader.saturation');
    318                 minLight = this.evalVisProp('shader.minlightness');
    319                 maxLight = this.evalVisProp('shader.maxlightness');
    320
    321                 if (this.evalVisProp('shader.type').toLowerCase() === 'angle') {
    322                     // Angle normal / eye
    323                     angle = Mat.innerProduct(this.view.matrix3DRotShift[3], this.normal);
    324                     angle = Math.abs(angle);
    325                     light = minLight + (maxLight - minLight) * angle;
    326                 } else {
    327                     // zIndex
    328                     maxFace = this.view.zIndexMax;
    329                     minFace = this.view.zIndexMin;
    330                     light = minLight + (maxLight - minLight) * ((this.zIndex - minFace) / (maxFace - minFace));
    331                 }
    332
    333                 // hsl = `hsl(${hue}, ${sat}%, ${light}%)`;
    334                 hsl = 'hsl(' + hue + ',' + sat +'%,' + light + '%)';
    335
    336                 this.element2D.visProp.fillcolor = hsl;
    337                 return this.zIndex;
    338             }
    339         }
    340     }
    341 );
    342
    343 /**
    344  * @class This element creates a 3D face.
    345  * @pseudo
    346  * @description A 3D faces is TODO
    347  *
    348  * @name Face3D
    349  * @augments Curve
    350  * @constructor
    351  * @type Object
    352  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    353   */
    354 JXG.createFace3D = function (board, parents, attributes) {
    355     var view = parents[0],
    356         polyhedron = parents[1],
    357         faceNumber = parents[2],
    358         attr, el;
    359
    360     // TODO Throw new Error
    361     attr = Type.copyAttributes(attributes, board.options, "face3d");
    362     el = new JXG.Face3D(view, polyhedron, faceNumber, attr);
    363
    364     attr = el.setAttr2D(attr);
    365     el.element2D = view.create("curve", [[], []], attr);
    366     el.element2D.view = view;
    367
    368     /**
    369      * @class
    370      * @ignore
    371      */
    372     el.element2D.updateDataArray = function () {
    373         var ret = el.updateDataArray2D();
    374         this.dataX = ret.X;
    375         this.dataY = ret.Y;
    376     };
    377     el.addChild(el.element2D);
    378     el.inherits.push(el.element2D);
    379     el.element2D.setParents(el);
    380
    381     el.element2D.prepareUpdate().update();
    382     if (!board.isSuspendedUpdate) {
    383         el.element2D.updateVisibility().updateRenderer();
    384     }
    385
    386     return el;
    387 };
    388
    389 JXG.registerElement("face3d", JXG.createFace3D);
    390
    391