1 /* 2 Copyright 2008-2026 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/> 29 and <https://opensource.org/licenses/MIT/>. 30 */ 31 32 /*global JXG: true, define: true*/ 33 /*jslint nomen: true, plusplus: true*/ 34 35 /** 36 * @fileoverview This file contains code for transformations of geometrical objects. 37 */ 38 39 import JXG from "../jxg.js"; 40 import Const from "./constants.js"; 41 import Mat from "../math/math.js"; 42 import Type from "../utils/type.js"; 43 44 /** 45 * A transformation consists of a 3x3 matrix, i.e. it is a projective transformation. 46 * @class Creates a new transformation object. Do not use this constructor to create a transformation. 47 * Use {@link JXG.Board#create} with 48 * type {@link Transformation} instead. 49 * @constructor 50 * @param {JXG.Board} board The board the transformation is part of. 51 * @param {String} type Can be 52 * <ul><li> 'translate' 53 * <li> 'scale' 54 * <li> 'reflect' 55 * <li> 'rotate' 56 * <li> 'shear' 57 * <li> 'generic' 58 * <li> 'matrix' 59 * </ul> 60 * @param {Object} params The parameters depend on the transformation type 61 * 62 * <p> 63 * Translation matrix: 64 * <pre> 65 * ( 1 0 0) ( z ) 66 * ( a 1 0) * ( x ) 67 * ( b 0 1) ( y ) 68 * </pre> 69 * 70 * <p> 71 * Scale matrix: 72 * <pre> 73 * ( 1 0 0) ( z ) 74 * ( 0 a 0) * ( x ) 75 * ( 0 0 b) ( y ) 76 * </pre> 77 * 78 * <p> 79 * A rotation matrix with angle a (in Radians) 80 * <pre> 81 * ( 1 0 0 ) ( z ) 82 * ( 0 cos(a) -sin(a)) * ( x ) 83 * ( 0 sin(a) cos(a) ) ( y ) 84 * </pre> 85 * 86 * <p> 87 * Shear matrix: 88 * <pre> 89 * ( 1 0 0) ( z ) 90 * ( 0 1 a) * ( x ) 91 * ( 0 b 1) ( y ) 92 * </pre> 93 * 94 * <p>Generic transformation: 95 * <pre> 96 * ( a b c ) ( z ) 97 * ( d e f ) * ( x ) 98 * ( g h i ) ( y ) 99 * </pre> 100 * 101 */ 102 JXG.Transformation = function (board, type, params, is3D) { 103 this.elementClass = Const.OBJECT_CLASS_OTHER; 104 this.type = Const.OBJECT_TYPE_TRANSFORMATION; 105 106 if (is3D) { 107 this.is3D = true; 108 this.matrix = [ 109 [1, 0, 0, 0], 110 [0, 1, 0, 0], 111 [0, 0, 1, 0], 112 [0, 0, 0, 1] 113 ]; 114 } else { 115 this.is3D = false; 116 this.matrix = [ 117 [1, 0, 0], 118 [0, 1, 0], 119 [0, 0, 1] 120 ]; 121 } 122 123 this.board = board; 124 this.isNumericMatrix = false; 125 if (this.is3D) { 126 this.setMatrix3D(params[0] /* view3d */, type, params.slice(1)); 127 } else { 128 this.setMatrix(board, type, params); 129 } 130 131 this.methodMap = { 132 apply: "apply", 133 applyOnce: "applyOnce", 134 bindTo: "bindTo", 135 bind: "bindTo", 136 melt: "melt", 137 meltTo: "meltTo" 138 }; 139 }; 140 141 JXG.Transformation.prototype = {}; 142 143 JXG.extend( 144 JXG.Transformation.prototype, 145 /** @lends JXG.Transformation.prototype */ { 146 /** 147 * Updates the numerical data for the transformation, i.e. the entry of the subobject matrix. 148 * @returns {JXG.Transform} returns pointer to itself 149 */ 150 update: function () { 151 return this; 152 }, 153 154 /** 155 * Set the transformation matrix for different types of standard transforms. 156 * @param {JXG.Board} board 157 * @param {String} type Transformation type, possible values are 158 * 'translate', 'scale', 'reflect', 'rotate', 159 * 'shear', 'generic'. 160 * @param {Array} params Parameters for the various transformation types. 161 * 162 * <p>A transformation with a generic matrix looks like: 163 * <pre> 164 * ( a b c ) ( z ) 165 * ( d e f ) * ( x ) 166 * ( g h i ) ( y ) 167 * </pre> 168 * 169 * The transformation matrix then looks like: 170 * <p> 171 * Translation matrix: 172 * <pre> 173 * ( 1 0 0) ( z ) 174 * ( a 1 0) * ( x ) 175 * ( b 0 1) ( y ) 176 * </pre> 177 * 178 * <p> 179 * Scale matrix: 180 * <pre> 181 * ( 1 0 0) ( z ) 182 * ( 0 a 0) * ( x ) 183 * ( 0 0 b) ( y ) 184 * </pre> 185 * 186 * <p> 187 * A rotation matrix with angle a (in Radians) 188 * <pre> 189 * ( 1 0 0 ) ( z ) 190 * ( 0 cos(a) -sin(a)) * ( x ) 191 * ( 0 sin(a) cos(a) ) ( y ) 192 * </pre> 193 * 194 * <p> 195 * Shear matrix: 196 * <pre> 197 * ( 1 0 0) ( z ) 198 * ( 0 1 a) * ( x ) 199 * ( 0 b 1) ( y ) 200 * </pre> 201 * 202 * <p>Generic transformation (9 parameters): 203 * <pre> 204 * ( a b c ) ( z ) 205 * ( d e f ) * ( x ) 206 * ( g h i ) ( y ) 207 * </pre> 208 * 209 * <p>Matrix: 210 * <pre> 211 * ( ) ( z ) 212 * ( M ) * ( x ) 213 * ( ) ( y ) 214 * </pre> 215 */ 216 setMatrix: function (board, type, params) { 217 var i; 218 // e, obj; // Handle dependencies 219 220 this.isNumericMatrix = true; 221 for (i = 0; i < params.length; i++) { 222 if (typeof params[i] !== 'number') { 223 this.isNumericMatrix = false; 224 break; 225 } 226 } 227 228 if (type === 'translate') { 229 if (params.length !== 2) { 230 throw new Error("JSXGraph: translate transformation needs 2 parameters."); 231 } 232 this.evalParam = Type.createEvalFunction(board, params, 2); 233 this.update = function () { 234 this.matrix[1][0] = this.evalParam(0); 235 this.matrix[2][0] = this.evalParam(1); 236 }; 237 } else if (type === 'scale') { 238 if (params.length !== 2) { 239 throw new Error("JSXGraph: scale transformation needs 2 parameters."); 240 } 241 this.evalParam = Type.createEvalFunction(board, params, 2); 242 this.update = function () { 243 this.matrix[1][1] = this.evalParam(0); // x 244 this.matrix[2][2] = this.evalParam(1); // y 245 }; 246 // Input: line or two points 247 } else if (type === 'reflect') { 248 // line or two points 249 if (params.length < 4) { 250 params[0] = board.select(params[0]); 251 } 252 253 // two points 254 if (params.length === 2) { 255 params[1] = board.select(params[1]); 256 } 257 258 // 4 coordinates [px,py,qx,qy] 259 if (params.length === 4) { 260 this.evalParam = Type.createEvalFunction(board, params, 4); 261 } 262 263 this.update = function () { 264 var x, y, z, xoff, yoff, d, v, p; 265 // Determine homogeneous coordinates of reflections axis 266 // line 267 if (params.length === 1) { 268 v = params[0].stdform; 269 } else if (params.length === 2) { 270 // two points 271 v = Mat.crossProduct( 272 params[1].coords.usrCoords, 273 params[0].coords.usrCoords 274 ); 275 } else if (params.length === 4) { 276 // two points coordinates [px,py,qx,qy] 277 v = Mat.crossProduct( 278 [1, this.evalParam(2), this.evalParam(3)], 279 [1, this.evalParam(0), this.evalParam(1)] 280 ); 281 } 282 283 // Project origin to the line. This gives a finite point p 284 x = v[1]; 285 y = v[2]; 286 z = v[0]; 287 p = [-z * x, -z * y, x * x + y * y]; 288 d = p[2]; 289 290 // Normalize p 291 xoff = p[0] / p[2]; 292 yoff = p[1] / p[2]; 293 294 // x, y is the direction of the line 295 x = -v[2]; 296 y = v[1]; 297 298 this.matrix[1][1] = (x * x - y * y) / d; 299 this.matrix[1][2] = (2 * x * y) / d; 300 this.matrix[2][1] = this.matrix[1][2]; 301 this.matrix[2][2] = -this.matrix[1][1]; 302 this.matrix[1][0] = 303 xoff * (1 - this.matrix[1][1]) - yoff * this.matrix[1][2]; 304 this.matrix[2][0] = 305 yoff * (1 - this.matrix[2][2]) - xoff * this.matrix[2][1]; 306 }; 307 } else if (type === 'rotate') { 308 if (params.length === 3) { 309 // angle, x, y 310 this.evalParam = Type.createEvalFunction(board, params, 3); 311 } else if (params.length > 0 && params.length <= 2) { 312 // angle, p or angle 313 this.evalParam = Type.createEvalFunction(board, params, 1); 314 315 if (params.length === 2 && !Type.isArray(params[1])) { 316 params[1] = board.select(params[1]); 317 } 318 } 319 320 this.update = function () { 321 var x, 322 y, 323 beta = this.evalParam(0), 324 co = Math.cos(beta), 325 si = Math.sin(beta); 326 327 this.matrix[1][1] = co; 328 this.matrix[1][2] = -si; 329 this.matrix[2][1] = si; 330 this.matrix[2][2] = co; 331 332 // rotate around [x,y] otherwise rotate around [0,0] 333 if (params.length > 1) { 334 if (params.length === 3) { 335 x = this.evalParam(1); 336 y = this.evalParam(2); 337 } else { 338 if (Type.isArray(params[1])) { 339 x = params[1][0]; 340 y = params[1][1]; 341 } else { 342 x = params[1].X(); 343 y = params[1].Y(); 344 } 345 } 346 this.matrix[1][0] = x * (1 - co) + y * si; 347 this.matrix[2][0] = y * (1 - co) - x * si; 348 } 349 }; 350 } else if (type === 'shear') { 351 if (params.length !== 2) { 352 throw new Error("JSXGraph: shear transformation needs 2 parameters."); 353 } 354 355 this.evalParam = Type.createEvalFunction(board, params, 2); 356 this.update = function () { 357 this.matrix[1][2] = this.evalParam(0); 358 this.matrix[2][1] = this.evalParam(1); 359 }; 360 } else if (type === 'generic') { 361 if (params.length !== 9) { 362 throw new Error("JSXGraph: generic transformation needs 9 parameters."); 363 } 364 365 this.evalParam = Type.createEvalFunction(board, params, 9); 366 367 this.update = function () { 368 this.matrix[0][0] = this.evalParam(0); 369 this.matrix[0][1] = this.evalParam(1); 370 this.matrix[0][2] = this.evalParam(2); 371 this.matrix[1][0] = this.evalParam(3); 372 this.matrix[1][1] = this.evalParam(4); 373 this.matrix[1][2] = this.evalParam(5); 374 this.matrix[2][0] = this.evalParam(6); 375 this.matrix[2][1] = this.evalParam(7); 376 this.matrix[2][2] = this.evalParam(8); 377 }; 378 } else if (type === 'matrix') { 379 if (params.length !== 1) { 380 throw new Error("JSXGraph: transformation of type 'matrix' needs 1 parameter."); 381 } 382 383 this.evalParam = params[0].slice(); 384 this.update = function () { 385 var i, j; 386 for (i = 0; i < 3; i++) { 387 for (j = 0; j < 3; j++) { 388 this.matrix[i][j] = Type.evaluate(this.evalParam[i][j]); 389 } 390 } 391 }; 392 } 393 394 // Handle dependencies 395 // NO: transformations do not have method addParents 396 // if (Type.exists(this.evalParam)) { 397 // for (e in this.evalParam.deps) { 398 // obj = this.evalParam.deps[e]; 399 // this.addParents(obj); 400 // obj.addChild(this); 401 // } 402 // } 403 }, 404 405 /** 406 * Set the 3D transformation matrix for different types of standard transforms. 407 * @param {JXG.Board} board 408 * @param {String} type Transformation type, possible values are 409 * 'translate', 'scale', 'rotate', 410 * 'rotateX', 'rotateY', 'rotateZ', 411 * 'shear', 'generic'. 412 * @param {Array} params Parameters for the various transformation types. 413 * 414 * <p>A transformation with a generic matrix looks like: 415 * <pre> 416 * ( a b c d) ( w ) 417 * ( e f g h) * ( x ) 418 * ( i j k l) ( y ) 419 * ( m n o p) ( z ) 420 * </pre> 421 * 422 * The transformation matrix then looks like: 423 * <p> 424 * Translation matrix: 425 * <pre> 426 * ( 1 0 0 0) ( w ) 427 * ( a 1 0 0) * ( x ) 428 * ( b 0 1 0) ( y ) 429 * ( c 0 0 1) ( z ) 430 * </pre> 431 * 432 * <p> 433 * Scale matrix: 434 * <pre> 435 * ( 1 0 0 0) ( w ) 436 * ( 0 a 0 0) * ( x ) 437 * ( 0 0 b 0) ( y ) 438 * ( 0 0 0 c) ( z ) 439 * </pre> 440 * 441 * <p> 442 * rotateX: a rotation matrix with angle a (in Radians) 443 * <pre> 444 * ( 1 0 0 ) ( z ) 445 * ( 0 1 0 0 ) * ( x ) 446 * ( 0 0 cos(a) -sin(a)) * ( x ) 447 * ( 0 0 sin(a) cos(a) ) ( y ) 448 * </pre> 449 * 450 * <p> 451 * rotateY: a rotation matrix with angle a (in Radians) 452 * <pre> 453 * ( 1 0 0 ) ( z ) 454 * ( 0 cos(a) 0 -sin(a)) * ( x ) 455 * ( 0 0 1 0 ) * ( x ) 456 * ( 0 sin(a) 0 cos(a) ) ( y ) 457 * </pre> 458 * 459 * <p> 460 * rotateZ: a rotation matrix with angle a (in Radians) 461 * <pre> 462 * ( 1 0 0 ) ( z ) 463 * ( 0 cos(a) -sin(a) 0 ) * ( x ) 464 * ( 0 sin(a) cos(a) 0 ) ( y ) 465 * ( 0 0 0 1 ) * ( x ) 466 * </pre> 467 * 468 * <p> 469 * rotate: a rotation matrix with angle a (in Radians) 470 * and normal <i>n</i>. 471 * 472 */ 473 setMatrix3D: function(view, type, params) { 474 var i, 475 board = view.board; 476 477 this.isNumericMatrix = true; 478 for (i = 0; i < params.length; i++) { 479 if (typeof params[i] !== 'number') { 480 this.isNumericMatrix = false; 481 break; 482 } 483 } 484 485 if (type === 'translate') { 486 if (params.length !== 3) { 487 throw new Error("JSXGraph: 3D translate transformation needs 3 parameters."); 488 } 489 this.evalParam = Type.createEvalFunction(board, params, 3); 490 this.update = function () { 491 this.matrix[1][0] = this.evalParam(0); 492 this.matrix[2][0] = this.evalParam(1); 493 this.matrix[3][0] = this.evalParam(2); 494 }; 495 } else if (type === 'scale') { 496 if (params.length !== 3 && params.length !== 4) { 497 throw new Error("JSXGraph: 3D scale transformation needs either 3 or 4 parameters."); 498 } 499 this.evalParam = Type.createEvalFunction(board, params, 3); 500 this.update = function () { 501 var x = this.evalParam(0), 502 y = this.evalParam(1), 503 z = this.evalParam(2); 504 505 this.matrix[1][1] = x; 506 this.matrix[2][2] = y; 507 this.matrix[3][3] = z; 508 }; 509 } else if (type === 'rotateX') { 510 params.splice(1, 0, [1, 0, 0]); 511 this.setMatrix3D(view, 'rotate', params); 512 } else if (type === 'rotateY') { 513 params.splice(1, 0, [0, 1, 0]); 514 this.setMatrix3D(view, 'rotate', params); 515 } else if (type === 'rotateZ') { 516 params.splice(1, 0, [0, 0, 1]); 517 this.setMatrix3D(view, 'rotate', params); 518 } else if (type === 'rotate') { 519 if (params.length < 2) { 520 throw new Error("JSXGraph: 3D rotate transformation needs 2 or 3 parameters."); 521 } 522 if (params.length === 3 && !Type.isFunction(params[2]) && !Type.isArray(params[2])) { 523 this.evalParam = Type.createEvalFunction(board, params, 2); 524 params[2] = view.select(params[2]); 525 } else { 526 this.evalParam = Type.createEvalFunction(board, params, params.length); 527 } 528 this.update = function () { 529 var a = this.evalParam(0), // angle 530 n = this.evalParam(1), // normal 531 p = [1, 0, 0, 0], 532 co = Math.cos(a), 533 si = Math.sin(a), 534 n1, n2, n3, 535 m1 = [ 536 [1, 0, 0, 0], 537 [0, 1, 0, 0], 538 [0, 0, 1, 0], 539 [0, 0, 0, 1] 540 ], 541 m2 = [ 542 [1, 0, 0, 0], 543 [0, 1, 0, 0], 544 [0, 0, 1, 0], 545 [0, 0, 0, 1] 546 ], 547 nrm = Mat.norm(n); 548 549 if (n.length === 3) { 550 n1 = n[0] / nrm; 551 n2 = n[1] / nrm; 552 n3 = n[2] / nrm; 553 } else { 554 n1 = n[1] / nrm; 555 n2 = n[2] / nrm; 556 n3 = n[3] / nrm; 557 } 558 if (params.length === 3) { 559 if (params.length === 3 && Type.exists(params[2].is3D)) { 560 p = params[2].coords.slice(); 561 } else { 562 p = this.evalParam(2); 563 } 564 if (p.length === 3) { 565 p.unshift(1); 566 } 567 m1[1][0] = -p[1]; 568 m1[2][0] = -p[2]; 569 m1[3][0] = -p[3]; 570 571 m2[1][0] = p[1]; 572 m2[2][0] = p[2]; 573 m2[3][0] = p[3]; 574 } 575 576 this.matrix = [ 577 [1, 0, 0, 0], 578 [0, n1 * n1 * (1 - co) + co, n1 * n2 * (1 - co) - n3 * si, n1 * n3 * (1 - co) + n2 * si], 579 [0, n2 * n1 * (1 - co) + n3 * si, n2 * n2 * (1 - co) + co, n2 * n3 * (1 - co) - n1 * si], 580 [0, n3 * n1 * (1 - co) - n2 * si, n3 * n2 * (1 - co) + n1 * si, n3 * n3 * (1 - co) + co] 581 ]; 582 this.matrix = Mat.matMatMult(this.matrix, m1); 583 this.matrix = Mat.matMatMult(m2, this.matrix); 584 }; 585 } else if (type === 'affine') { 586 if (params.length !== 9) { 587 throw new Error("JSXGraph: 3D transformation of type 'affine' needs 9 parameters."); 588 } 589 590 this.evalParam = Type.createEvalFunction(board, params, 9); 591 this.update = function () { 592 this.matrix[1][1] = this.evalParam(0); 593 this.matrix[1][2] = this.evalParam(1); 594 this.matrix[1][3] = this.evalParam(2); 595 this.matrix[2][1] = this.evalParam(3); 596 this.matrix[2][2] = this.evalParam(4); 597 this.matrix[2][3] = this.evalParam(5); 598 this.matrix[3][1] = this.evalParam(6); 599 this.matrix[3][2] = this.evalParam(7); 600 this.matrix[3][3] = this.evalParam(8); 601 }; 602 } else if (type === 'affinematrix') { 603 if (params.length !== 1) { 604 throw new Error("JSXGraph: 3D transformation of type 'affinematrix' needs 1 parameter."); 605 } 606 607 this.evalParam = params[0].slice(); 608 this.update = function () { 609 var i, j; 610 for (i = 0; i < 3; i++) { 611 for (j = 0; j < 3; j++) { 612 this.matrix[i + 1][j + 1] = Type.evaluate(this.evalParam[i][j]); 613 } 614 } 615 }; 616 } else if (type === 'generic') { 617 if (params.length !== 16) { 618 throw new Error("JSXGraph: 3D transformation of type 'generic' needs 16 parameters."); 619 } 620 621 this.evalParam = Type.createEvalFunction(board, params, 6); 622 this.update = function () { 623 this.matrix[0][0] = this.evalParam(0); 624 this.matrix[0][1] = this.evalParam(1); 625 this.matrix[0][2] = this.evalParam(2); 626 this.matrix[0][3] = this.evalParam(3); 627 this.matrix[1][0] = this.evalParam(4); 628 this.matrix[1][1] = this.evalParam(5); 629 this.matrix[1][2] = this.evalParam(6); 630 this.matrix[1][3] = this.evalParam(7); 631 this.matrix[2][0] = this.evalParam(8); 632 this.matrix[2][1] = this.evalParam(9); 633 this.matrix[2][2] = this.evalParam(10); 634 this.matrix[2][3] = this.evalParam(11); 635 this.matrix[3][0] = this.evalParam(12); 636 this.matrix[3][1] = this.evalParam(13); 637 this.matrix[3][2] = this.evalParam(14); 638 this.matrix[3][3] = this.evalParam(15); 639 }; 640 } else if (type === 'matrix') { 641 if (params.length !== 1) { 642 throw new Error("JSXGraph: 3D transformation of type 'matrix' needs 1 parameter."); 643 } 644 645 this.evalParam = params[0].slice(); 646 this.update = function () { 647 var i, j; 648 for (i = 0; i < 4; i++) { 649 for (j = 0; j < 4; j++) { 650 this.matrix[i][j] = Type.evaluate(this.evalParam[i][j]); 651 } 652 } 653 }; 654 } 655 }, 656 657 /** 658 * Transform a point element, that are: {@link Point}, {@link Text}, {@link Image}, {@link Point3D}. 659 * First, the transformation matrix is updated, then do the matrix-vector-multiplication. 660 * <p> 661 * Restricted to 2D transformations. 662 * 663 * @private 664 * @param {JXG.GeometryElement} p element which is transformed 665 * @param {String} 'self' Apply the transformation to the initialCoords instead of the coords if this is set. 666 * @returns {Array} 667 */ 668 apply: function (p, self) { 669 var c; 670 671 this.update(); 672 if (this.is3D) { 673 c = p.coords; 674 } else if (Type.exists(self)) { 675 c = p.initialCoords.usrCoords; 676 } else { 677 c = p.coords.usrCoords; 678 } 679 680 return Mat.matVecMult(this.matrix, c); 681 }, 682 683 /** 684 * Applies a transformation once to a point element, that are: {@link Point}, {@link Text}, {@link Image}, {@link Point3D} or to an array of such elements. 685 * If it is a free 2D point, then it can be dragged around later 686 * and will overwrite the transformed coordinates. 687 * @param {JXG.Point|Array} p 688 */ 689 applyOnce: function (p) { 690 var c, len, i; 691 692 if (!Type.isArray(p)) { 693 p = [p]; 694 } 695 696 len = p.length; 697 for (i = 0; i < len; i++) { 698 this.update(); 699 if (this.is3D) { 700 p[i].coords = Mat.matVecMult(this.matrix, p[i].coords); 701 } else { 702 c = Mat.matVecMult(this.matrix, p[i].coords.usrCoords); 703 p[i].coords.setCoordinates(Const.COORDS_BY_USER, c); 704 } 705 } 706 }, 707 708 /** 709 * Binds a transformation to a GeometryElement or an array of elements. In every update of the 710 * GeometryElement(s), the transformation is executed. That means, in order to immediately 711 * apply the transformation after calling bindTo, a call of board.update() has to follow. 712 * <p> 713 * The transformation is simply appended to the existing list of transformations of the object. 714 * It is not fused (melt) with an existing transformation. 715 * 716 * @param {Array|JXG.Object} el JXG.Object or array of JXG.Object to 717 * which the transformation is bound to. 718 * @see JXG.Transformation.meltTo 719 */ 720 bindTo: function (el) { 721 var i, len; 722 if (Type.isArray(el)) { 723 len = el.length; 724 725 for (i = 0; i < len; i++) { 726 el[i].transformations.push(this); 727 } 728 } else { 729 el.transformations.push(this); 730 } 731 }, 732 733 /** 734 * Binds a transformation to a GeometryElement or an array of elements. In every update of the 735 * GeometryElement(s), the transformation is executed. That means, in order to immediately 736 * apply the transformation after calling meltTo, a call of board.update() has to follow. 737 * <p> 738 * In case the last transformation of the element and this transformation are static, 739 * i.e. the transformation matrices do not depend on other elements, 740 * the transformation will be fused into (multiplied with) the last transformation of 741 * the element. Thus, the list of transformations is kept small. 742 * If the transformation will be the first transformation ot the element, it will be cloned 743 * to prevent side effects. 744 * 745 * @param {Array|JXG.Object} el JXG.Object or array of JXG.Objects to 746 * which the transformation is bound to. 747 * 748 * @see JXG.Transformation#bindTo 749 */ 750 meltTo: function (el) { 751 var i, elt, t; 752 753 if (Type.isArray(el)) { 754 for (i = 0; i < el.length; i++) { 755 this.meltTo(el[i]); 756 } 757 } else { 758 elt = el.transformations; 759 if (elt.length > 0 && 760 elt[elt.length - 1].isNumericMatrix && 761 this.isNumericMatrix 762 ) { 763 elt[elt.length - 1].melt(this); 764 } else { 765 // Use a clone of the transformation. 766 // Otherwise, if the transformation is meltTo twice 767 // the transformation will be changed. 768 t = this.clone(); 769 elt.push(t); 770 } 771 } 772 }, 773 774 /** 775 * Create a copy of the transformation in case it is static, i.e. 776 * if the transformation matrix does not depend on other elements. 777 * <p> 778 * If the transformation matrix is not static, null will be returned. 779 * 780 * @returns {JXG.Transformation} 781 */ 782 clone: function() { 783 var t = null; 784 785 if (this.isNumericMatrix) { 786 t = new JXG.Transformation(this.board, 'none', []); 787 t.matrix = this.matrix.slice(); 788 } 789 790 return t; 791 }, 792 793 /** 794 * Unused 795 * @deprecated Use setAttribute 796 * @param term 797 */ 798 setProperty: function (term) { 799 JXG.deprecated("Transformation.setProperty()", "Transformation.setAttribute()"); 800 }, 801 802 /** 803 * Empty method. Unused. 804 * @param {Object} term Key-value pairs of the attributes. 805 */ 806 setAttribute: function (term) {}, 807 808 /** 809 * Combine two transformations to one transformation. This only works if 810 * both of transformation matrices consist of numbers solely, and do not 811 * contain functions. 812 * 813 * Multiplies the transformation with a transformation t from the left. 814 * i.e. (this) = (t) join (this) 815 * @param {JXG.Transform} t Transformation which is the left multiplicand 816 * @returns {JXG.Transform} the transformation object. 817 */ 818 melt: function (t) { 819 var res = []; 820 821 this.update(); 822 t.update(); 823 824 res = Mat.matMatMult(t.matrix, this.matrix); 825 826 this.update = function () { 827 this.matrix = res; 828 }; 829 830 return this; 831 }, 832 833 // Documented in element.js 834 // Not yet, since transformations are not listed in board.objects. 835 getParents: function () { 836 var p = [[].concat.apply([], this.matrix)]; 837 838 if (this.parents.length !== 0) { 839 p = this.parents; 840 } 841 842 return p; 843 } 844 } 845 ); 846 847 /** 848 * @class Define projective 2D transformations like translation, rotation, reflection. 849 * @pseudo 850 * @description A transformation consists of a 3x3 matrix, i.e. it is a projective transformation. 851 * <p> 852 * Internally, a transformation is applied to an element by multiplying the 3x3 matrix from the left to 853 * the homogeneous coordinates of the element. JSXGraph represents homogeneous coordinates in the order 854 * (z, x, y). The matrix has the form 855 * <pre> 856 * ( a b c ) ( z ) 857 * ( d e f ) * ( x ) 858 * ( g h i ) ( y ) 859 * </pre> 860 * where in general a=1. If b = c = 0, the transformation is called <i>affine</i>. 861 * In this case, finite points will stay finite. This is not the case for general projective coordinates. 862 * <p> 863 * Transformations acting on texts and images are considered to be affine, i.e. b and c are ignored. 864 * 865 * @name Transformation 866 * @augments JXG.Transformation 867 * @constructor 868 * @type JXG.Transformation 869 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 870 * @param {number|function|JXG.GeometryElement} parameters The parameters depend on the transformation type, supplied as attribute 'type'. 871 * Possible transformation types are 872 * <ul> 873 * <li> 'translate' 874 * <li> 'scale' 875 * <li> 'reflect' 876 * <li> 'rotate' 877 * <li> 'shear' 878 * <li> 'generic' 879 * <li> 'matrix' 880 * </ul> 881 * <p>Valid parameters for these types are: 882 * <dl> 883 * <dt><b><tt>type:"translate"</tt></b></dt><dd><b>x, y</b> Translation vector (two numbers or functions). 884 * The transformation matrix for x = a and y = b has the form: 885 * <pre> 886 * ( 1 0 0) ( z ) 887 * ( a 1 0) * ( x ) 888 * ( b 0 1) ( y ) 889 * </pre> 890 * </dd> 891 * <dt><b><tt>type:"scale"</tt></b></dt><dd><b>scale_x, scale_y</b> Scale vector (two numbers or functions). 892 * The transformation matrix for scale_x = a and scale_y = b has the form: 893 * <pre> 894 * ( 1 0 0) ( z ) 895 * ( 0 a 0) * ( x ) 896 * ( 0 0 b) ( y ) 897 * </pre> 898 * </dd> 899 * <dt><b><tt>type:"rotate"</tt></b></dt><dd> <b>alpha, [point | x, y]</b> The parameters are the angle value in Radians 900 * (a number or function), and optionally a coordinate pair (two numbers or functions) or a point element defining the 901 * rotation center. If the rotation center is not given, the transformation rotates around (0,0). 902 * The transformation matrix for angle a and rotating around (0, 0) has the form: 903 * <pre> 904 * ( 1 0 0 ) ( z ) 905 * ( 0 cos(a) -sin(a)) * ( x ) 906 * ( 0 sin(a) cos(a) ) ( y ) 907 * </pre> 908 * </dd> 909 * <dt><b><tt>type:"shear"</tt></b></dt><dd><b>shear_x, shear_y</b> Shear vector (two numbers or functions). 910 * The transformation matrix for shear_x = a and shear_y = b has the form: 911 * <pre> 912 * ( 1 0 0) ( z ) 913 * ( 0 1 a) * ( x ) 914 * ( 0 b 1) ( y ) 915 * </pre> 916 * </dd> 917 * <dt><b><tt>type:"reflect"</tt></b></dt><dd>The parameters can either be: 918 * <ul> 919 * <li> <b>line</b> a line element, 920 * <li> <b>p, q</b> two point elements, 921 * <li> <b>p_x, p_y, q_x, q_y</b> four numbers or functions determining a line through points (p_x, p_y) and (q_x, q_y). 922 * </ul> 923 * </dd> 924 * <dt><b><tt>type:"generic"</tt></b></dt><dd><b>a, b, c, d, e, f, g, h, i</b> Nine matrix entries (numbers or functions) 925 * for a generic projective transformation. 926 * The matrix has the form 927 * <pre> 928 * ( a b c ) ( z ) 929 * ( d e f ) * ( x ) 930 * ( g h i ) ( y ) 931 * </pre> 932 * </dd> 933 * <dt><b><tt>type:"matrix"</tt></b></dt><dd><b>M</b> 3x3 transformation matrix containing numbers or functions</dd> 934 * </dl> 935 * 936 * 937 * @see JXG.Transformation#setMatrix 938 * 939 * @example 940 * // The point B is determined by taking twice the vector A from the origin 941 * 942 * var p0 = board.create('point', [0, 3], {name: 'A'}), 943 * t = board.create('transform', [function(){ return p0.X(); }, "Y(A)"], {type: 'translate'}), 944 * p1 = board.create('point', [p0, t], {color: 'blue'}); 945 * 946 * </pre><div class="jxgbox" id="JXG14167b0c-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 947 * <script type="text/javascript"> 948 * (function() { 949 * var board = JXG.JSXGraph.initBoard('JXG14167b0c-2ad3-11e5-8dd9-901b0e1b8723', 950 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 951 * var p0 = board.create('point', [0, 3], {name: 'A'}), 952 * t = board.create('transform', [function(){ return p0.X(); }, "Y(A)"], {type:'translate'}), 953 * p1 = board.create('point', [p0, t], {color: 'blue'}); 954 * 955 * })(); 956 * 957 * </script><pre> 958 * 959 * @example 960 * // The point B is the result of scaling the point A with factor 2 in horizontal direction 961 * // and with factor 0.5 in vertical direction. 962 * 963 * var p1 = board.create('point', [1, 1]), 964 * t = board.create('transform', [2, 0.5], {type: 'scale'}), 965 * p2 = board.create('point', [p1, t], {color: 'blue'}); 966 * 967 * </pre><div class="jxgbox" id="JXGa6827a72-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 968 * <script type="text/javascript"> 969 * (function() { 970 * var board = JXG.JSXGraph.initBoard('JXGa6827a72-2ad3-11e5-8dd9-901b0e1b8723', 971 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 972 * var p1 = board.create('point', [1, 1]), 973 * t = board.create('transform', [2, 0.5], {type: 'scale'}), 974 * p2 = board.create('point', [p1, t], {color: 'blue'}); 975 * 976 * })(); 977 * 978 * </script><pre> 979 * 980 * @example 981 * // The point B is rotated around C which gives point D. The angle is determined 982 * // by the vertical height of point A. 983 * 984 * var p0 = board.create('point', [0, 3], {name: 'A'}), 985 * p1 = board.create('point', [1, 1]), 986 * p2 = board.create('point', [2, 1], {name:'C', fixed: true}), 987 * 988 * // angle, rotation center: 989 * t = board.create('transform', ['Y(A)', p2], {type: 'rotate'}), 990 * p3 = board.create('point', [p1, t], {color: 'blue'}); 991 * 992 * </pre><div class="jxgbox" id="JXG747cf11e-2ad4-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 993 * <script type="text/javascript"> 994 * (function() { 995 * var board = JXG.JSXGraph.initBoard('JXG747cf11e-2ad4-11e5-8dd9-901b0e1b8723', 996 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 997 * var p0 = board.create('point', [0, 3], {name: 'A'}), 998 * p1 = board.create('point', [1, 1]), 999 * p2 = board.create('point', [2, 1], {name:'C', fixed: true}), 1000 * 1001 * // angle, rotation center: 1002 * t = board.create('transform', ['Y(A)', p2], {type: 'rotate'}), 1003 * p3 = board.create('point', [p1, t], {color: 'blue'}); 1004 * 1005 * })(); 1006 * 1007 * </script><pre> 1008 * 1009 * @example 1010 * // A concatenation of several transformations. 1011 * var p1 = board.create('point', [1, 1]), 1012 * t1 = board.create('transform', [-2, -1], {type: 'translate'}), 1013 * t2 = board.create('transform', [Math.PI/4], {type: 'rotate'}), 1014 * t3 = board.create('transform', [2, 1], {type: 'translate'}), 1015 * p2 = board.create('point', [p1, [t1, t2, t3]], {color: 'blue'}); 1016 * 1017 * </pre><div class="jxgbox" id="JXGf516d3de-2ad5-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 1018 * <script type="text/javascript"> 1019 * (function() { 1020 * var board = JXG.JSXGraph.initBoard('JXGf516d3de-2ad5-11e5-8dd9-901b0e1b8723', 1021 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1022 * var p1 = board.create('point', [1, 1]), 1023 * t1 = board.create('transform', [-2, -1], {type:'translate'}), 1024 * t2 = board.create('transform', [Math.PI/4], {type:'rotate'}), 1025 * t3 = board.create('transform', [2, 1], {type:'translate'}), 1026 * p2 = board.create('point', [p1, [t1, t2, t3]], {color: 'blue'}); 1027 * 1028 * })(); 1029 * 1030 * </script><pre> 1031 * 1032 * @example 1033 * // Reflection of point A 1034 * var p1 = board.create('point', [1, 1]), 1035 * p2 = board.create('point', [1, 3]), 1036 * p3 = board.create('point', [-2, 0]), 1037 * l = board.create('line', [p2, p3]), 1038 * t = board.create('transform', [l], {type: 'reflect'}), // Possible are l, l.id, l.name 1039 * p4 = board.create('point', [p1, t], {color: 'blue'}); 1040 * 1041 * </pre><div class="jxgbox" id="JXG6f374a04-2ad6-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 1042 * <script type="text/javascript"> 1043 * (function() { 1044 * var board = JXG.JSXGraph.initBoard('JXG6f374a04-2ad6-11e5-8dd9-901b0e1b8723', 1045 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1046 * var p1 = board.create('point', [1, 1]), 1047 * p2 = board.create('point', [1, 3]), 1048 * p3 = board.create('point', [-2, 0]), 1049 * l = board.create('line', [p2, p3]), 1050 * t = board.create('transform', [l], {type:'reflect'}), // Possible are l, l.id, l.name 1051 * p4 = board.create('point', [p1, t], {color: 'blue'}); 1052 * 1053 * })(); 1054 * 1055 * </script><pre> 1056 * 1057 * @example 1058 * // Type: 'matrix' 1059 * var y = board.create('slider', [[-3, 1], [-3, 4], [0, 1, 6]]); 1060 * var t1 = board.create('transform', [ 1061 * [ 1062 * [1, 0, 0], 1063 * [0, 1, 0], 1064 * [() => y.Value(), 0, 1] 1065 * ] 1066 * ], {type: 'matrix'}); 1067 * 1068 * var A = board.create('point', [2, -3]); 1069 * var B = board.create('point', [A, t1]); 1070 * 1071 * </pre><div id="JXGd2bfd46c-3c0c-45c5-a92b-583fad0eb3ec" class="jxgbox" style="width: 300px; height: 300px;"></div> 1072 * <script type="text/javascript"> 1073 * (function() { 1074 * var board = JXG.JSXGraph.initBoard('JXGd2bfd46c-3c0c-45c5-a92b-583fad0eb3ec', 1075 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1076 * var y = board.create('slider', [[-3, 1], [-3, 4], [0, 1, 6]]); 1077 * var t1 = board.create('transform', [ 1078 * [ 1079 * [1, 0, 0], 1080 * [0, 1, 0], 1081 * [() => y.Value(), 0, 1] 1082 * ] 1083 * ], {type: 'matrix'}); 1084 * 1085 * var A = board.create('point', [2, -3]); 1086 * var B = board.create('point', [A, t1]); 1087 * 1088 * })(); 1089 * 1090 * </script><pre> 1091 * 1092 * @example 1093 * // One time application of a transform to points A, B 1094 * var p1 = board.create('point', [1, 1]), 1095 * p2 = board.create('point', [-1, -2]), 1096 * t = board.create('transform', [3, 2], {type: 'shear'}); 1097 * t.applyOnce([p1, p2]); 1098 * 1099 * </pre><div class="jxgbox" id="JXGb6cee1c4-2ad6-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 1100 * <script type="text/javascript"> 1101 * (function() { 1102 * var board = JXG.JSXGraph.initBoard('JXGb6cee1c4-2ad6-11e5-8dd9-901b0e1b8723', 1103 * {boundingbox: [-8, 8, 8, -8], axis: true, showcopyright: false, shownavigation: false}); 1104 * var p1 = board.create('point', [1, 1]), 1105 * p2 = board.create('point', [-1, -2]), 1106 * t = board.create('transform', [3, 2], {type: 'shear'}); 1107 * t.applyOnce([p1, p2]); 1108 * 1109 * })(); 1110 * 1111 * </script><pre> 1112 * 1113 * @example 1114 * // Construct a square of side length 2 with the 1115 * // help of transformations 1116 * var sq = [], 1117 * right = board.create('transform', [2, 0], {type: 'translate'}), 1118 * up = board.create('transform', [0, 2], {type: 'translate'}), 1119 * pol, rot, p0; 1120 * 1121 * // The first point is free 1122 * sq[0] = board.create('point', [0, 0], {name: 'Drag me'}), 1123 * 1124 * // Construct the other free points by transformations 1125 * sq[1] = board.create('point', [sq[0], right]), 1126 * sq[2] = board.create('point', [sq[0], [right, up]]), 1127 * sq[3] = board.create('point', [sq[0], up]), 1128 * 1129 * // Polygon through these four points 1130 * pol = board.create('polygon', sq, { 1131 * fillColor:'blue', 1132 * gradient:'radial', 1133 * gradientsecondcolor:'white', 1134 * gradientSecondOpacity:'0' 1135 * }), 1136 * 1137 * p0 = board.create('point', [0, 3], {name: 'angle'}), 1138 * // Rotate the square around point sq[0] by dragging A vertically. 1139 * rot = board.create('transform', ['Y(angle)', sq[0]], {type: 'rotate'}); 1140 * 1141 * // Apply the rotation to all but the first point of the square 1142 * rot.bindTo(sq.slice(1)); 1143 * 1144 * </pre><div class="jxgbox" id="JXGc7f9097e-2ad7-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 1145 * <script type="text/javascript"> 1146 * (function() { 1147 * var board = JXG.JSXGraph.initBoard('JXGc7f9097e-2ad7-11e5-8dd9-901b0e1b8723', 1148 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1149 * // Construct a square of side length 2 with the 1150 * // help of transformations 1151 * var sq = [], 1152 * right = board.create('transform', [2, 0], {type: 'translate'}), 1153 * up = board.create('transform', [0, 2], {type: 'translate'}), 1154 * pol, rot, p0; 1155 * 1156 * // The first point is free 1157 * sq[0] = board.create('point', [0, 0], {name: 'Drag me'}), 1158 * 1159 * // Construct the other free points by transformations 1160 * sq[1] = board.create('point', [sq[0], right]), 1161 * sq[2] = board.create('point', [sq[0], [right, up]]), 1162 * sq[3] = board.create('point', [sq[0], up]), 1163 * 1164 * // Polygon through these four points 1165 * pol = board.create('polygon', sq, { 1166 * fillColor:'blue', 1167 * gradient:'radial', 1168 * gradientsecondcolor:'white', 1169 * gradientSecondOpacity:'0' 1170 * }), 1171 * 1172 * p0 = board.create('point', [0, 3], {name: 'angle'}), 1173 * // Rotate the square around point sq[0] by dragging A vertically. 1174 * rot = board.create('transform', ['Y(angle)', sq[0]], {type: 'rotate'}); 1175 * 1176 * // Apply the rotation to all but the first point of the square 1177 * rot.bindTo(sq.slice(1)); 1178 * 1179 * })(); 1180 * 1181 * </script><pre> 1182 * 1183 * @example 1184 * // Text transformation 1185 * var p0 = board.create('point', [0, 0], {name: 'p_0'}); 1186 * var p1 = board.create('point', [3, 0], {name: 'p_1'}); 1187 * var txt = board.create('text',[0.5, 0, 'Hello World'], {display:'html'}); 1188 * 1189 * // If p_0 is dragged, translate p_1 and text accordingly 1190 * var tOff = board.create('transform', [() => p0.X(), () => p0.Y()], {type:'translate'}); 1191 * tOff.bindTo(txt); 1192 * tOff.bindTo(p1); 1193 * 1194 * // Rotate text around p_0 by dragging point p_1 1195 * var tRot = board.create('transform', [ 1196 * () => Math.atan2(p1.Y() - p0.Y(), p1.X() - p0.X()), p0], {type:'rotate'}); 1197 * tRot.bindTo(txt); 1198 * 1199 * // Scale text by dragging point "p_1" 1200 * // We do this by 1201 * // - moving text by -p_0 (inverse of transformation tOff), 1202 * // - scale the text (because scaling is relative to (0,0)) 1203 * // - move the text back by +p_0 1204 * var tOffInv = board.create('transform', [ 1205 * () => -p0.X(), 1206 * () => -p0.Y() 1207 * ], {type:'translate'}); 1208 * var tScale = board.create('transform', [ 1209 * // Some scaling factor 1210 * () => p1.Dist(p0) / 3, 1211 * () => p1.Dist(p0) / 3 1212 * ], {type:'scale'}); 1213 * tOffInv.bindTo(txt); tScale.bindTo(txt); tOff.bindTo(txt); 1214 * 1215 * </pre><div id="JXG50d6d546-3b91-41dd-8c0f-3eaa6cff7e66" class="jxgbox" style="width: 300px; height: 300px;"></div> 1216 * <script type="text/javascript"> 1217 * (function() { 1218 * var board = JXG.JSXGraph.initBoard('JXG50d6d546-3b91-41dd-8c0f-3eaa6cff7e66', 1219 * {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false}); 1220 * var p0 = board.create('point', [0, 0], {name: 'p_0'}); 1221 * var p1 = board.create('point', [3, 0], {name: 'p_1'}); 1222 * var txt = board.create('text',[0.5, 0, 'Hello World'], {display:'html'}); 1223 * 1224 * // If p_0 is dragged, translate p_1 and text accordingly 1225 * var tOff = board.create('transform', [() => p0.X(), () => p0.Y()], {type:'translate'}); 1226 * tOff.bindTo(txt); 1227 * tOff.bindTo(p1); 1228 * 1229 * // Rotate text around p_0 by dragging point p_1 1230 * var tRot = board.create('transform', [ 1231 * () => Math.atan2(p1.Y() - p0.Y(), p1.X() - p0.X()), p0], {type:'rotate'}); 1232 * tRot.bindTo(txt); 1233 * 1234 * // Scale text by dragging point "p_1" 1235 * // We do this by 1236 * // - moving text by -p_0 (inverse of transformation tOff), 1237 * // - scale the text (because scaling is relative to (0,0)) 1238 * // - move the text back by +p_0 1239 * var tOffInv = board.create('transform', [ 1240 * () => -p0.X(), 1241 * () => -p0.Y() 1242 * ], {type:'translate'}); 1243 * var tScale = board.create('transform', [ 1244 * // Some scaling factor 1245 * () => p1.Dist(p0) / 3, 1246 * () => p1.Dist(p0) / 3 1247 * ], {type:'scale'}); 1248 * tOffInv.bindTo(txt); tScale.bindTo(txt); tOff.bindTo(txt); 1249 * 1250 * })(); 1251 * 1252 * </script><pre> 1253 * 1254 */ 1255 JXG.createTransform = function (board, parents, attributes) { 1256 return new JXG.Transformation(board, attributes.type, parents); 1257 }; 1258 1259 JXG.registerElement('transform', JXG.createTransform); 1260 1261 /** 1262 * @class Define projective 3D transformations like translation, rotation, reflection. 1263 * @pseudo 1264 * @description A transformation consists of a 4x4 matrix, i.e. it is a projective transformation. 1265 * <p> 1266 * Internally, a transformation is applied to an element by multiplying the 4x4 matrix from the left to 1267 * the homogeneous coordinates of the element. JSXGraph represents homogeneous coordinates in the order 1268 * (w, x, y, z). If the coordinate is a finite point, w=1. The matrix has the form 1269 * <pre> 1270 * ( a b c d) ( w ) 1271 * ( e f g h) * ( x ) 1272 * ( i j k l) ( y ) 1273 * ( m n o p) ( z ) 1274 * </pre> 1275 * where in general a=1. If b = c = d = 0, the transformation is called <i>affine</i>. 1276 * In this case, finite points will stay finite. This is not the case for general projective coordinates. 1277 * <p> 1278 * 1279 * @name Transformation3D 1280 * @augments JXG.Transformation 1281 * @constructor 1282 * @type JXG.Transformation 1283 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1284 * @param {number|function|JXG.GeometryElement3D} parameters The parameters depend on the transformation type, supplied as attribute 'type'. 1285 * Possible transformation types are 1286 * <ul> 1287 * <li> 'translate' 1288 * <li> 'scale' 1289 * <li> 'rotate' 1290 * <li> 'rotateX' 1291 * <li> 'rotateY' 1292 * <li> 'rotateZ' 1293 * </ul> 1294 * <p>Valid parameters for these types are: 1295 * <dl> 1296 * <dt><b><tt>type:"translate"</tt></b></dt><dd><b>x, y, z</b> Translation vector (three numbers or functions). 1297 * The transformation matrix for x = a, y = b, and z = c has the form: 1298 * <pre> 1299 * ( 1 0 0 0) ( w ) 1300 * ( a 1 0 0) * ( x ) 1301 * ( b 0 1 0) ( y ) 1302 * ( c 0 0 c) ( z ) 1303 * </pre> 1304 * </dd> 1305 * <dt><b><tt>type:"scale"</tt></b></dt><dd><b>scale_x, scale_y, scale_z</b> Scale vector (three numbers or functions). 1306 * The transformation matrix for scale_x = a, scale_y = b, scale_z = c has the form: 1307 * <pre> 1308 * ( 1 0 0 0) ( w ) 1309 * ( 0 a 0 0) * ( x ) 1310 * ( 0 0 b 0) ( y ) 1311 * ( 0 0 0 c) ( z ) 1312 * </pre> 1313 * </dd> 1314 * <dt><b><tt>type:"rotate"</tt></b></dt><dd><b>a, n, [p=[0,0,0]]</b> angle (in radians), normal, [point]. 1315 * Rotate with angle a around the normal vector n through the point p. 1316 * </dd> 1317 * <dt><b><tt>type:"rotateX"</tt></b></dt><dd><b>a, [p=[0,0,0]]</b> angle (in radians), [point]. 1318 * Rotate with angle a around the normal vector (1, 0, 0) through the point p. 1319 * </dd> 1320 * <dt><b><tt>type:"rotateY"</tt></b></dt><dd><b>a, [p=[0,0,0]]</b> angle (in radians), [point]. 1321 * Rotate with angle a around the normal vector (0, 1, 0) through the point p. 1322 * </dd> 1323 * <dt><b><tt>type:"rotateZ"</tt></b></dt><dd><b>a, [p=[0,0,0]]</b> angle (in radians), [point]. 1324 * Rotate with angle a around the normal vector (0, 0, 1) through the point p. 1325 * </dd> 1326 * </dl> 1327 * 1328 * @example 1329 * var bound = [-5, 5]; 1330 * var view = board.create('view3d', 1331 * [ 1332 * [-5, -5], [8, 8], 1333 * [bound, bound, bound] 1334 * ], { 1335 * projection: "central", 1336 * depthOrder: { enabled: true }, 1337 * axesPosition: 'border' // 'center', 'none' 1338 * } 1339 * ); 1340 * 1341 * var slider = board.create('slider', [[-4, 6], [0, 6], [0, 0, 5]]); 1342 * 1343 * var p1 = view.create('point3d', [1, 2, 2], { name: 'drag me', size: 5 }); 1344 * 1345 * // Translate from p1 by fixed amount 1346 * var t1 = view.create('transform3d', [2, 3, 2], { type: 'translate' }); 1347 * // Translate from p1 by dynamic amount 1348 * var t2 = view.create('transform3d', [() => slider.Value() + 3, 0, 0], { type: 'translate' }); 1349 * 1350 * view.create('point3d', [p1, t1], { name: 'translate fixed', size: 5 }); 1351 * view.create('point3d', [p1, t2], { name: 'translate by func', size: 5 }); 1352 * 1353 * </pre><div id="JXG2409bb0a-90d7-4c1e-ae9f-85e8a776acec" class="jxgbox" style="width: 300px; height: 300px;"></div> 1354 * <script type="text/javascript"> 1355 * (function() { 1356 * var board = JXG.JSXGraph.initBoard('JXG2409bb0a-90d7-4c1e-ae9f-85e8a776acec', 1357 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1358 * var bound = [-5, 5]; 1359 * var view = board.create('view3d', 1360 * [ 1361 * [-5, -5], [8, 8], 1362 * [bound, bound, bound] 1363 * ], { 1364 * projection: "central", 1365 * depthOrder: { enabled: true }, 1366 * axesPosition: 'border' // 'center', 'none' 1367 * } 1368 * ); 1369 * 1370 * var slider = board.create('slider', [[-4, 6], [0, 6], [0, 0, 5]]); 1371 * 1372 * var p1 = view.create('point3d', [1, 2, 2], { name: 'drag me', size: 5 }); 1373 * 1374 * // Translate from p1 by fixed amount 1375 * var t1 = view.create('transform3d', [2, 3, 2], { type: 'translate' }); 1376 * // Translate from p1 by dynamic amount 1377 * var t2 = view.create('transform3d', [() => slider.Value() + 3, 0, 0], { type: 'translate' }); 1378 * 1379 * view.create('point3d', [p1, t1], { name: 'translate fixed', size: 5 }); 1380 * view.create('point3d', [p1, t2], { name: 'translate by func', size: 5 }); 1381 * 1382 * })(); 1383 * 1384 * </script><pre> 1385 * 1386 */ 1387 JXG.createTransform3D = function (board, parents, attributes) { 1388 return new JXG.Transformation(board, attributes.type, parents, true); 1389 }; 1390 1391 JXG.registerElement('transform3d', JXG.createTransform3D); 1392 1393 export default JXG.Transformation; 1394 1395