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 and determine it's z-index. 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 p.zIndex[face[j]] = Mat.innerProduct(this.view.matrix3DRotShift[3], c3d); 188 } 189 x.push(c2d[1]); 190 y.push(c2d[2]); 191 192 this.zIndex += p.zIndex[face[j]]; 193 } 194 if (le > 0) { 195 this.zIndex /= le; 196 } 197 if (le !== 2) { 198 // 2D faces and points are a closed loop 199 x.push(x[0]); 200 y.push(y[0]); 201 } 202 203 return { X: x, Y: y }; 204 }, 205 206 addTransform: function (el, transform) { 207 if (this.faceNumber === 0) { 208 this.addTransformGeneric(el, transform); 209 } 210 return this; 211 }, 212 213 updateTransform: function () { 214 var t, c, i, j, b; 215 216 if (this.faceNumber !== 0) { 217 return this; 218 } 219 220 if (this.transformations.length === 0 || this.baseElement === null) { 221 return this; 222 } 223 224 t = this.transformations; 225 for (i = 0; i < t.length; i++) { 226 t[i].update(); 227 } 228 229 if (this === this.baseElement) { 230 b = this.polyhedron; 231 } else { 232 b = this.baseElement.polyhedron; 233 } 234 for (i in b.coords) { 235 if (b.coords.hasOwnProperty(i)) { 236 c = b.coords[i]; 237 for (j = 0; j < t.length; j++) { 238 c = Mat.matVecMult(t[j].matrix, c); 239 } 240 this.polyhedron.coords[i] = c; 241 } 242 } 243 244 return this; 245 }, 246 247 update: function () { 248 var i, le, 249 phdr, nrm, 250 p1, p2, 251 face; 252 253 if (this.needsUpdate && !this.view.board._change3DView) { 254 phdr = this.polyhedron; 255 256 if (this.faceNumber === 0) { 257 // Update coordinates of all vertices 258 this.updateCoords() 259 .updateTransform(); 260 } 261 262 face = phdr.faces[this.faceNumber]; 263 le = face.length; 264 if (le < 3) { 265 // Get out of here if face is point or segment 266 return this; 267 } 268 269 // Update spanning vectors 270 p1 = phdr.coords[face[0]]; 271 p2 = phdr.coords[face[1]]; 272 this.vec1 = [p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2], p2[3] - p1[3]]; 273 274 p2 = phdr.coords[face[2]]; 275 this.vec2 = [p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2], p2[3] - p1[3]]; 276 277 // Update Hesse form, i.e. normal and d 278 this.normal = Mat.crossProduct(this.vec1.slice(1), this.vec2.slice(1)); 279 nrm = Mat.norm(this.normal); 280 this.normal.unshift(0); 281 282 if (Math.abs(nrm) > 1.e-12) { 283 for (i = 1; i < 4; i++) { 284 this.normal[i] /= nrm; 285 } 286 } 287 this.d = Mat.innerProduct(p1, this.normal, 4); 288 } 289 return this; 290 }, 291 292 updateRenderer: function () { 293 if (this.needsUpdate) { 294 this.needsUpdate = false; 295 this.shader(); 296 } 297 return this; 298 }, 299 300 /** 301 * Determines the lightness of the face (in the HSL color scheme). 302 * <p> 303 * Sets the fillColor of the adjoint 2D curve. 304 * @name shader 305 * @memberOf Face3D 306 * @function 307 * @returns {Number} zIndex of the face 308 */ 309 shader: function() { 310 var hue, sat, light, angle, hsl, 311 // bb = this.view.bbox3D, 312 minFace, maxFace, 313 minLight, maxLight; 314 315 316 if (this.evalVisProp('shader.enabled')) { 317 hue = this.evalVisProp('shader.hue'); 318 sat = this.evalVisProp('shader.saturation'); 319 minLight = this.evalVisProp('shader.minlightness'); 320 maxLight = this.evalVisProp('shader.maxlightness'); 321 322 if (this.evalVisProp('shader.type').toLowerCase() === 'angle') { 323 // Angle normal / eye 324 angle = Mat.innerProduct(this.view.matrix3DRotShift[3], this.normal); 325 angle = Math.abs(angle); 326 light = minLight + (maxLight - minLight) * angle; 327 } else { 328 // zIndex 329 maxFace = this.view.zIndexMax; 330 minFace = this.view.zIndexMin; 331 light = minLight + (maxLight - minLight) * ((this.zIndex - minFace) / (maxFace - minFace)); 332 } 333 334 // hsl = `hsl(${hue}, ${sat}%, ${light}%)`; 335 hsl = 'hsl(' + hue + ',' + sat +'%,' + light + '%)'; 336 337 this.element2D.visProp.fillcolor = hsl; 338 return this.zIndex; 339 } 340 } 341 } 342 ); 343 344 /** 345 * @class This element creates a 3D face. 346 * @pseudo 347 * @description A 3D faces is TODO 348 * 349 * @name Face3D 350 * @augments Curve 351 * @constructor 352 * @type Object 353 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 354 */ 355 JXG.createFace3D = function (board, parents, attributes) { 356 var view = parents[0], 357 polyhedron = parents[1], 358 faceNumber = parents[2], 359 attr, el; 360 361 // TODO Throw new Error 362 attr = Type.copyAttributes(attributes, board.options, "face3d"); 363 el = new JXG.Face3D(view, polyhedron, faceNumber, attr); 364 365 attr = el.setAttr2D(attr); 366 el.element2D = view.create("curve", [[], []], attr); 367 el.element2D.view = view; 368 369 /** 370 * @class 371 * @ignore 372 */ 373 el.element2D.updateDataArray = function () { 374 var ret = el.updateDataArray2D(); 375 this.dataX = ret.X; 376 this.dataY = ret.Y; 377 }; 378 el.addChild(el.element2D); 379 el.inherits.push(el.element2D); 380 el.element2D.setParents(el); 381 382 el.element2D.prepareUpdate().update(); 383 if (!board.isSuspendedUpdate) { 384 el.element2D.updateVisibility().updateRenderer(); 385 } 386 387 return el; 388 }; 389 390 JXG.registerElement("face3d", JXG.createFace3D); 391 392