1 /* 2 Copyright 2008-2024 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