1 /* 2 Copyright 2008-2025 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 if (params.length > 0 && params.length <= 2) { 511 this.evalParam = Type.createEvalFunction(board, params, 1); 512 } 513 514 this.update = function () { 515 var a = this.evalParam(0), 516 co = Math.cos(a), 517 si = Math.sin(a); 518 519 this.matrix[2][2] = co; 520 this.matrix[2][3] = -si; 521 this.matrix[3][2] = si; 522 this.matrix[3][3] = co; 523 }; 524 } else if (type === 'rotateY') { 525 if (params.length > 0 && params.length <= 2) { 526 this.evalParam = Type.createEvalFunction(board, params, 1); 527 } 528 this.update = function () { 529 var a = this.evalParam(0), 530 co = Math.cos(a), 531 si = Math.sin(a); 532 533 this.matrix[1][1] = co; 534 this.matrix[1][3] = si; 535 this.matrix[3][1] = -si; 536 this.matrix[3][3] = co; 537 }; 538 } else if (type === 'rotateZ') { 539 if (params.length > 0 && params.length <= 2) { 540 this.evalParam = Type.createEvalFunction(board, params, 1); 541 } 542 this.update = function () { 543 var a = this.evalParam(0), 544 co = Math.cos(a), 545 si = Math.sin(a); 546 547 this.matrix[1][1] = co; 548 this.matrix[1][2] = -si; 549 this.matrix[2][1] = si; 550 this.matrix[2][2] = co; 551 }; 552 } else if (type === 'rotate') { 553 if (params.length === 2) { 554 this.evalParam = Type.createEvalFunction(board, params, 2); 555 } 556 this.update = function () { 557 var a = this.evalParam(0), // angle 558 n = this.evalParam(1), // normal 559 co = Math.cos(a), 560 si = Math.sin(a), 561 n1, n2, n3, 562 nrm = Mat.norm(n); 563 564 if (n.length === 3) { 565 n1 = n[0] / nrm; 566 n2 = n[1] / nrm; 567 n3 = n[2] / nrm; 568 } else { 569 n1 = n[1] / nrm; 570 n2 = n[2] / nrm; 571 n3 = n[3] / nrm; 572 } 573 574 this.matrix = [ 575 [1, 0, 0, 0], 576 [0, n1 * n1 * (1 - co) + co, n1 * n2 * (1 - co) - n3 * si, n1 * n3 * (1 - co) + n2 * si], 577 [0, n2 * n1 * (1 - co) + n3 * si, n2 * n2 * (1 - co) + co, n2 * n3 * (1 - co) - n1 * si], 578 [0, n3 * n1 * (1 - co) - n2 * si, n3 * n2 * (1 - co) + n1 * si, n3 * n3 * (1 - co) + co] 579 ]; 580 }; 581 } 582 }, 583 584 /** 585 * Transform a point element, that are: {@link Point}, {@link Text}, {@link Image}, {@link Point3D}. 586 * First, the transformation matrix is updated, then do the matrix-vector-multiplication. 587 * <p> 588 * Restricted to 2D transformations. 589 * 590 * @private 591 * @param {JXG.GeometryElement} p element which is transformed 592 * @param {String} 'self' Apply the transformation to the initialCoords instead of the coords if this is set. 593 * @returns {Array} 594 */ 595 apply: function (p, self) { 596 var c; 597 598 this.update(); 599 if (this.is3D) { 600 c = p.coords; 601 } else if (Type.exists(self)) { 602 c = p.initialCoords.usrCoords; 603 } else { 604 c = p.coords.usrCoords; 605 } 606 607 return Mat.matVecMult(this.matrix, c); 608 }, 609 610 /** 611 * 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. 612 * If it is a free 2D point, then it can be dragged around later 613 * and will overwrite the transformed coordinates. 614 * @param {JXG.Point|Array} p 615 */ 616 applyOnce: function (p) { 617 var c, len, i; 618 619 if (!Type.isArray(p)) { 620 p = [p]; 621 } 622 623 len = p.length; 624 for (i = 0; i < len; i++) { 625 this.update(); 626 if (this.is3D) { 627 p[i].coords = Mat.matVecMult(this.matrix, p[i].coords); 628 } else { 629 c = Mat.matVecMult(this.matrix, p[i].coords.usrCoords); 630 p[i].coords.setCoordinates(Const.COORDS_BY_USER, c); 631 } 632 } 633 }, 634 635 /** 636 * Binds a transformation to a GeometryElement or an array of elements. In every update of the 637 * GeometryElement(s), the transformation is executed. That means, in order to immediately 638 * apply the transformation after calling bindTo, a call of board.update() has to follow. 639 * <p> 640 * The transformation is simply appended to the existing list of transformations of the object. 641 * It is not fused (melt) with an existing transformation. 642 * 643 * @param {Array|JXG.Object} el JXG.Object or array of JXG.Object to 644 * which the transformation is bound to. 645 * @see JXG.Transformation.meltTo 646 */ 647 bindTo: function (el) { 648 var i, len; 649 if (Type.isArray(el)) { 650 len = el.length; 651 652 for (i = 0; i < len; i++) { 653 el[i].transformations.push(this); 654 } 655 } else { 656 el.transformations.push(this); 657 } 658 }, 659 660 /** 661 * Binds a transformation to a GeometryElement or an array of elements. In every update of the 662 * GeometryElement(s), the transformation is executed. That means, in order to immediately 663 * apply the transformation after calling meltTo, a call of board.update() has to follow. 664 * <p> 665 * In case the last transformation of the element and this transformation are static, 666 * i.e. the transformation matrices do not depend on other elements, 667 * the transformation will be fused into (multiplied with) the last transformation of 668 * the element. Thus, the list of transformations is kept small. 669 * If the transformation will be the first transformation ot the element, it will be cloned 670 * to prevent side effects. 671 * 672 * @param {Array|JXG.Object} el JXG.Object or array of JXG.Objects to 673 * which the transformation is bound to. 674 * 675 * @see JXG.Transformation#bindTo 676 */ 677 meltTo: function (el) { 678 var i, elt, t; 679 680 if (Type.isArray(el)) { 681 for (i = 0; i < el.length; i++) { 682 this.meltTo(el[i]); 683 } 684 } else { 685 elt = el.transformations; 686 if (elt.length > 0 && 687 elt[elt.length - 1].isNumericMatrix && 688 this.isNumericMatrix 689 ) { 690 elt[elt.length - 1].melt(this); 691 } else { 692 // Use a clone of the transformation. 693 // Otherwise, if the transformation is meltTo twice 694 // the transformation will be changed. 695 t = this.clone(); 696 elt.push(t); 697 } 698 } 699 }, 700 701 /** 702 * Create a copy of the transformation in case it is static, i.e. 703 * if the transformation matrix does not depend on other elements. 704 * <p> 705 * If the transformation matrix is not static, null will be returned. 706 * 707 * @returns {JXG.Transformation} 708 */ 709 clone: function() { 710 var t = null; 711 712 if (this.isNumericMatrix) { 713 t = new JXG.Transformation(this.board, 'none', []); 714 t.matrix = this.matrix.slice(); 715 } 716 717 return t; 718 }, 719 720 /** 721 * Unused 722 * @deprecated Use setAttribute 723 * @param term 724 */ 725 setProperty: function (term) { 726 JXG.deprecated("Transformation.setProperty()", "Transformation.setAttribute()"); 727 }, 728 729 /** 730 * Empty method. Unused. 731 * @param {Object} term Key-value pairs of the attributes. 732 */ 733 setAttribute: function (term) {}, 734 735 /** 736 * Combine two transformations to one transformation. This only works if 737 * both of transformation matrices consist of numbers solely, and do not 738 * contain functions. 739 * 740 * Multiplies the transformation with a transformation t from the left. 741 * i.e. (this) = (t) join (this) 742 * @param {JXG.Transform} t Transformation which is the left multiplicand 743 * @returns {JXG.Transform} the transformation object. 744 */ 745 melt: function (t) { 746 var res = []; 747 748 this.update(); 749 t.update(); 750 751 res = Mat.matMatMult(t.matrix, this.matrix); 752 753 this.update = function () { 754 this.matrix = res; 755 }; 756 757 return this; 758 }, 759 760 // Documented in element.js 761 // Not yet, since transformations are not listed in board.objects. 762 getParents: function () { 763 var p = [[].concat.apply([], this.matrix)]; 764 765 if (this.parents.length !== 0) { 766 p = this.parents; 767 } 768 769 return p; 770 } 771 } 772 ); 773 774 /** 775 * @class Define projective 2D transformations like translation, rotation, reflection. 776 * @pseudo 777 * @description A transformation consists of a 3x3 matrix, i.e. it is a projective transformation. 778 * <p> 779 * Internally, a transformation is applied to an element by multiplying the 3x3 matrix from the left to 780 * the homogeneous coordinates of the element. JSXGraph represents homogeneous coordinates in the order 781 * (z, x, y). The matrix has the form 782 * <pre> 783 * ( a b c ) ( z ) 784 * ( d e f ) * ( x ) 785 * ( g h i ) ( y ) 786 * </pre> 787 * where in general a=1. If b = c = 0, the transformation is called <i>affine</i>. 788 * In this case, finite points will stay finite. This is not the case for general projective coordinates. 789 * <p> 790 * Transformations acting on texts and images are considered to be affine, i.e. b and c are ignored. 791 * 792 * @name Transformation 793 * @augments JXG.Transformation 794 * @constructor 795 * @type JXG.Transformation 796 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 797 * @param {number|function|JXG.GeometryElement} parameters The parameters depend on the transformation type, supplied as attribute 'type'. 798 * Possible transformation types are 799 * <ul> 800 * <li> 'translate' 801 * <li> 'scale' 802 * <li> 'reflect' 803 * <li> 'rotate' 804 * <li> 'shear' 805 * <li> 'generic' 806 * <li> 'matrix' 807 * </ul> 808 * <p>Valid parameters for these types are: 809 * <dl> 810 * <dt><b><tt>type:"translate"</tt></b></dt><dd><b>x, y</b> Translation vector (two numbers or functions). 811 * The transformation matrix for x = a and y = b has the form: 812 * <pre> 813 * ( 1 0 0) ( z ) 814 * ( a 1 0) * ( x ) 815 * ( b 0 1) ( y ) 816 * </pre> 817 * </dd> 818 * <dt><b><tt>type:"scale"</tt></b></dt><dd><b>scale_x, scale_y</b> Scale vector (two numbers or functions). 819 * The transformation matrix for scale_x = a and scale_y = b has the form: 820 * <pre> 821 * ( 1 0 0) ( z ) 822 * ( 0 a 0) * ( x ) 823 * ( 0 0 b) ( y ) 824 * </pre> 825 * </dd> 826 * <dt><b><tt>type:"rotate"</tt></b></dt><dd> <b>alpha, [point | x, y]</b> The parameters are the angle value in Radians 827 * (a number or function), and optionally a coordinate pair (two numbers or functions) or a point element defining the 828 * rotation center. If the rotation center is not given, the transformation rotates around (0,0). 829 * The transformation matrix for angle a and rotating around (0, 0) has the form: 830 * <pre> 831 * ( 1 0 0 ) ( z ) 832 * ( 0 cos(a) -sin(a)) * ( x ) 833 * ( 0 sin(a) cos(a) ) ( y ) 834 * </pre> 835 * </dd> 836 * <dt><b><tt>type:"shear"</tt></b></dt><dd><b>shear_x, shear_y</b> Shear vector (two numbers or functions). 837 * The transformation matrix for shear_x = a and shear_y = b has the form: 838 * <pre> 839 * ( 1 0 0) ( z ) 840 * ( 0 1 a) * ( x ) 841 * ( 0 b 1) ( y ) 842 * </pre> 843 * </dd> 844 * <dt><b><tt>type:"reflect"</tt></b></dt><dd>The parameters can either be: 845 * <ul> 846 * <li> <b>line</b> a line element, 847 * <li> <b>p, q</b> two point elements, 848 * <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). 849 * </ul> 850 * </dd> 851 * <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) 852 * for a generic projective transformation. 853 * The matrix has the form 854 * <pre> 855 * ( a b c ) ( z ) 856 * ( d e f ) * ( x ) 857 * ( g h i ) ( y ) 858 * </pre> 859 * </dd> 860 * <dt><b><tt>type:"matrix"</tt></b></dt><dd><b>M</b> 3x3 transformation matrix containing numbers or functions</dd> 861 * </dl> 862 * 863 * 864 * @see JXG.Transformation#setMatrix 865 * 866 * @example 867 * // The point B is determined by taking twice the vector A from the origin 868 * 869 * var p0 = board.create('point', [0, 3], {name: 'A'}), 870 * t = board.create('transform', [function(){ return p0.X(); }, "Y(A)"], {type: 'translate'}), 871 * p1 = board.create('point', [p0, t], {color: 'blue'}); 872 * 873 * </pre><div class="jxgbox" id="JXG14167b0c-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 874 * <script type="text/javascript"> 875 * (function() { 876 * var board = JXG.JSXGraph.initBoard('JXG14167b0c-2ad3-11e5-8dd9-901b0e1b8723', 877 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 878 * var p0 = board.create('point', [0, 3], {name: 'A'}), 879 * t = board.create('transform', [function(){ return p0.X(); }, "Y(A)"], {type:'translate'}), 880 * p1 = board.create('point', [p0, t], {color: 'blue'}); 881 * 882 * })(); 883 * 884 * </script><pre> 885 * 886 * @example 887 * // The point B is the result of scaling the point A with factor 2 in horizontal direction 888 * // and with factor 0.5 in vertical direction. 889 * 890 * var p1 = board.create('point', [1, 1]), 891 * t = board.create('transform', [2, 0.5], {type: 'scale'}), 892 * p2 = board.create('point', [p1, t], {color: 'blue'}); 893 * 894 * </pre><div class="jxgbox" id="JXGa6827a72-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 895 * <script type="text/javascript"> 896 * (function() { 897 * var board = JXG.JSXGraph.initBoard('JXGa6827a72-2ad3-11e5-8dd9-901b0e1b8723', 898 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 899 * var p1 = board.create('point', [1, 1]), 900 * t = board.create('transform', [2, 0.5], {type: 'scale'}), 901 * p2 = board.create('point', [p1, t], {color: 'blue'}); 902 * 903 * })(); 904 * 905 * </script><pre> 906 * 907 * @example 908 * // The point B is rotated around C which gives point D. The angle is determined 909 * // by the vertical height of point A. 910 * 911 * var p0 = board.create('point', [0, 3], {name: 'A'}), 912 * p1 = board.create('point', [1, 1]), 913 * p2 = board.create('point', [2, 1], {name:'C', fixed: true}), 914 * 915 * // angle, rotation center: 916 * t = board.create('transform', ['Y(A)', p2], {type: 'rotate'}), 917 * p3 = board.create('point', [p1, t], {color: 'blue'}); 918 * 919 * </pre><div class="jxgbox" id="JXG747cf11e-2ad4-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 920 * <script type="text/javascript"> 921 * (function() { 922 * var board = JXG.JSXGraph.initBoard('JXG747cf11e-2ad4-11e5-8dd9-901b0e1b8723', 923 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 924 * var p0 = board.create('point', [0, 3], {name: 'A'}), 925 * p1 = board.create('point', [1, 1]), 926 * p2 = board.create('point', [2, 1], {name:'C', fixed: true}), 927 * 928 * // angle, rotation center: 929 * t = board.create('transform', ['Y(A)', p2], {type: 'rotate'}), 930 * p3 = board.create('point', [p1, t], {color: 'blue'}); 931 * 932 * })(); 933 * 934 * </script><pre> 935 * 936 * @example 937 * // A concatenation of several transformations. 938 * var p1 = board.create('point', [1, 1]), 939 * t1 = board.create('transform', [-2, -1], {type: 'translate'}), 940 * t2 = board.create('transform', [Math.PI/4], {type: 'rotate'}), 941 * t3 = board.create('transform', [2, 1], {type: 'translate'}), 942 * p2 = board.create('point', [p1, [t1, t2, t3]], {color: 'blue'}); 943 * 944 * </pre><div class="jxgbox" id="JXGf516d3de-2ad5-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 945 * <script type="text/javascript"> 946 * (function() { 947 * var board = JXG.JSXGraph.initBoard('JXGf516d3de-2ad5-11e5-8dd9-901b0e1b8723', 948 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 949 * var p1 = board.create('point', [1, 1]), 950 * t1 = board.create('transform', [-2, -1], {type:'translate'}), 951 * t2 = board.create('transform', [Math.PI/4], {type:'rotate'}), 952 * t3 = board.create('transform', [2, 1], {type:'translate'}), 953 * p2 = board.create('point', [p1, [t1, t2, t3]], {color: 'blue'}); 954 * 955 * })(); 956 * 957 * </script><pre> 958 * 959 * @example 960 * // Reflection of point A 961 * var p1 = board.create('point', [1, 1]), 962 * p2 = board.create('point', [1, 3]), 963 * p3 = board.create('point', [-2, 0]), 964 * l = board.create('line', [p2, p3]), 965 * t = board.create('transform', [l], {type: 'reflect'}), // Possible are l, l.id, l.name 966 * p4 = board.create('point', [p1, t], {color: 'blue'}); 967 * 968 * </pre><div class="jxgbox" id="JXG6f374a04-2ad6-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 969 * <script type="text/javascript"> 970 * (function() { 971 * var board = JXG.JSXGraph.initBoard('JXG6f374a04-2ad6-11e5-8dd9-901b0e1b8723', 972 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 973 * var p1 = board.create('point', [1, 1]), 974 * p2 = board.create('point', [1, 3]), 975 * p3 = board.create('point', [-2, 0]), 976 * l = board.create('line', [p2, p3]), 977 * t = board.create('transform', [l], {type:'reflect'}), // Possible are l, l.id, l.name 978 * p4 = board.create('point', [p1, t], {color: 'blue'}); 979 * 980 * })(); 981 * 982 * </script><pre> 983 * 984 * @example 985 * // Type: 'matrix' 986 * var y = board.create('slider', [[-3, 1], [-3, 4], [0, 1, 6]]); 987 * var t1 = board.create('transform', [ 988 * [ 989 * [1, 0, 0], 990 * [0, 1, 0], 991 * [() => y.Value(), 0, 1] 992 * ] 993 * ], {type: 'matrix'}); 994 * 995 * var A = board.create('point', [2, -3]); 996 * var B = board.create('point', [A, t1]); 997 * 998 * </pre><div id="JXGd2bfd46c-3c0c-45c5-a92b-583fad0eb3ec" class="jxgbox" style="width: 300px; height: 300px;"></div> 999 * <script type="text/javascript"> 1000 * (function() { 1001 * var board = JXG.JSXGraph.initBoard('JXGd2bfd46c-3c0c-45c5-a92b-583fad0eb3ec', 1002 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1003 * var y = board.create('slider', [[-3, 1], [-3, 4], [0, 1, 6]]); 1004 * var t1 = board.create('transform', [ 1005 * [ 1006 * [1, 0, 0], 1007 * [0, 1, 0], 1008 * [() => y.Value(), 0, 1] 1009 * ] 1010 * ], {type: 'matrix'}); 1011 * 1012 * var A = board.create('point', [2, -3]); 1013 * var B = board.create('point', [A, t1]); 1014 * 1015 * })(); 1016 * 1017 * </script><pre> 1018 * 1019 * @example 1020 * // One time application of a transform to points A, B 1021 * var p1 = board.create('point', [1, 1]), 1022 * p2 = board.create('point', [-1, -2]), 1023 * t = board.create('transform', [3, 2], {type: 'shear'}); 1024 * t.applyOnce([p1, p2]); 1025 * 1026 * </pre><div class="jxgbox" id="JXGb6cee1c4-2ad6-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 1027 * <script type="text/javascript"> 1028 * (function() { 1029 * var board = JXG.JSXGraph.initBoard('JXGb6cee1c4-2ad6-11e5-8dd9-901b0e1b8723', 1030 * {boundingbox: [-8, 8, 8, -8], axis: true, showcopyright: false, shownavigation: false}); 1031 * var p1 = board.create('point', [1, 1]), 1032 * p2 = board.create('point', [-1, -2]), 1033 * t = board.create('transform', [3, 2], {type: 'shear'}); 1034 * t.applyOnce([p1, p2]); 1035 * 1036 * })(); 1037 * 1038 * </script><pre> 1039 * 1040 * @example 1041 * // Construct a square of side length 2 with the 1042 * // help of transformations 1043 * var sq = [], 1044 * right = board.create('transform', [2, 0], {type: 'translate'}), 1045 * up = board.create('transform', [0, 2], {type: 'translate'}), 1046 * pol, rot, p0; 1047 * 1048 * // The first point is free 1049 * sq[0] = board.create('point', [0, 0], {name: 'Drag me'}), 1050 * 1051 * // Construct the other free points by transformations 1052 * sq[1] = board.create('point', [sq[0], right]), 1053 * sq[2] = board.create('point', [sq[0], [right, up]]), 1054 * sq[3] = board.create('point', [sq[0], up]), 1055 * 1056 * // Polygon through these four points 1057 * pol = board.create('polygon', sq, { 1058 * fillColor:'blue', 1059 * gradient:'radial', 1060 * gradientsecondcolor:'white', 1061 * gradientSecondOpacity:'0' 1062 * }), 1063 * 1064 * p0 = board.create('point', [0, 3], {name: 'angle'}), 1065 * // Rotate the square around point sq[0] by dragging A vertically. 1066 * rot = board.create('transform', ['Y(angle)', sq[0]], {type: 'rotate'}); 1067 * 1068 * // Apply the rotation to all but the first point of the square 1069 * rot.bindTo(sq.slice(1)); 1070 * 1071 * </pre><div class="jxgbox" id="JXGc7f9097e-2ad7-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 1072 * <script type="text/javascript"> 1073 * (function() { 1074 * var board = JXG.JSXGraph.initBoard('JXGc7f9097e-2ad7-11e5-8dd9-901b0e1b8723', 1075 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1076 * // Construct a square of side length 2 with the 1077 * // help of transformations 1078 * var sq = [], 1079 * right = board.create('transform', [2, 0], {type: 'translate'}), 1080 * up = board.create('transform', [0, 2], {type: 'translate'}), 1081 * pol, rot, p0; 1082 * 1083 * // The first point is free 1084 * sq[0] = board.create('point', [0, 0], {name: 'Drag me'}), 1085 * 1086 * // Construct the other free points by transformations 1087 * sq[1] = board.create('point', [sq[0], right]), 1088 * sq[2] = board.create('point', [sq[0], [right, up]]), 1089 * sq[3] = board.create('point', [sq[0], up]), 1090 * 1091 * // Polygon through these four points 1092 * pol = board.create('polygon', sq, { 1093 * fillColor:'blue', 1094 * gradient:'radial', 1095 * gradientsecondcolor:'white', 1096 * gradientSecondOpacity:'0' 1097 * }), 1098 * 1099 * p0 = board.create('point', [0, 3], {name: 'angle'}), 1100 * // Rotate the square around point sq[0] by dragging A vertically. 1101 * rot = board.create('transform', ['Y(angle)', sq[0]], {type: 'rotate'}); 1102 * 1103 * // Apply the rotation to all but the first point of the square 1104 * rot.bindTo(sq.slice(1)); 1105 * 1106 * })(); 1107 * 1108 * </script><pre> 1109 * 1110 * @example 1111 * // Text transformation 1112 * var p0 = board.create('point', [0, 0], {name: 'p_0'}); 1113 * var p1 = board.create('point', [3, 0], {name: 'p_1'}); 1114 * var txt = board.create('text',[0.5, 0, 'Hello World'], {display:'html'}); 1115 * 1116 * // If p_0 is dragged, translate p_1 and text accordingly 1117 * var tOff = board.create('transform', [() => p0.X(), () => p0.Y()], {type:'translate'}); 1118 * tOff.bindTo(txt); 1119 * tOff.bindTo(p1); 1120 * 1121 * // Rotate text around p_0 by dragging point p_1 1122 * var tRot = board.create('transform', [ 1123 * () => Math.atan2(p1.Y() - p0.Y(), p1.X() - p0.X()), p0], {type:'rotate'}); 1124 * tRot.bindTo(txt); 1125 * 1126 * // Scale text by dragging point "p_1" 1127 * // We do this by 1128 * // - moving text by -p_0 (inverse of transformation tOff), 1129 * // - scale the text (because scaling is relative to (0,0)) 1130 * // - move the text back by +p_0 1131 * var tOffInv = board.create('transform', [ 1132 * () => -p0.X(), 1133 * () => -p0.Y() 1134 * ], {type:'translate'}); 1135 * var tScale = board.create('transform', [ 1136 * // Some scaling factor 1137 * () => p1.Dist(p0) / 3, 1138 * () => p1.Dist(p0) / 3 1139 * ], {type:'scale'}); 1140 * tOffInv.bindTo(txt); tScale.bindTo(txt); tOff.bindTo(txt); 1141 * 1142 * </pre><div id="JXG50d6d546-3b91-41dd-8c0f-3eaa6cff7e66" class="jxgbox" style="width: 300px; height: 300px;"></div> 1143 * <script type="text/javascript"> 1144 * (function() { 1145 * var board = JXG.JSXGraph.initBoard('JXG50d6d546-3b91-41dd-8c0f-3eaa6cff7e66', 1146 * {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false}); 1147 * var p0 = board.create('point', [0, 0], {name: 'p_0'}); 1148 * var p1 = board.create('point', [3, 0], {name: 'p_1'}); 1149 * var txt = board.create('text',[0.5, 0, 'Hello World'], {display:'html'}); 1150 * 1151 * // If p_0 is dragged, translate p_1 and text accordingly 1152 * var tOff = board.create('transform', [() => p0.X(), () => p0.Y()], {type:'translate'}); 1153 * tOff.bindTo(txt); 1154 * tOff.bindTo(p1); 1155 * 1156 * // Rotate text around p_0 by dragging point p_1 1157 * var tRot = board.create('transform', [ 1158 * () => Math.atan2(p1.Y() - p0.Y(), p1.X() - p0.X()), p0], {type:'rotate'}); 1159 * tRot.bindTo(txt); 1160 * 1161 * // Scale text by dragging point "p_1" 1162 * // We do this by 1163 * // - moving text by -p_0 (inverse of transformation tOff), 1164 * // - scale the text (because scaling is relative to (0,0)) 1165 * // - move the text back by +p_0 1166 * var tOffInv = board.create('transform', [ 1167 * () => -p0.X(), 1168 * () => -p0.Y() 1169 * ], {type:'translate'}); 1170 * var tScale = board.create('transform', [ 1171 * // Some scaling factor 1172 * () => p1.Dist(p0) / 3, 1173 * () => p1.Dist(p0) / 3 1174 * ], {type:'scale'}); 1175 * tOffInv.bindTo(txt); tScale.bindTo(txt); tOff.bindTo(txt); 1176 * 1177 * })(); 1178 * 1179 * </script><pre> 1180 * 1181 */ 1182 JXG.createTransform = function (board, parents, attributes) { 1183 return new JXG.Transformation(board, attributes.type, parents); 1184 }; 1185 1186 JXG.registerElement('transform', JXG.createTransform); 1187 1188 /** 1189 * @class Define projective 3D transformations like translation, rotation, reflection. 1190 * @pseudo 1191 * @description A transformation consists of a 4x4 matrix, i.e. it is a projective transformation. 1192 * <p> 1193 * Internally, a transformation is applied to an element by multiplying the 4x4 matrix from the left to 1194 * the homogeneous coordinates of the element. JSXGraph represents homogeneous coordinates in the order 1195 * (w, x, y, z). If the coordinate is a finite point, w=1. The matrix has the form 1196 * <pre> 1197 * ( a b c d) ( w ) 1198 * ( e f g h) * ( x ) 1199 * ( i j k l) ( y ) 1200 * ( m n o p) ( z ) 1201 * </pre> 1202 * where in general a=1. If b = c = d = 0, the transformation is called <i>affine</i>. 1203 * In this case, finite points will stay finite. This is not the case for general projective coordinates. 1204 * <p> 1205 * 1206 * @name Transformation3D 1207 * @augments JXG.Transformation 1208 * @constructor 1209 * @type JXG.Transformation 1210 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1211 * @param {number|function|JXG.GeometryElement3D} parameters The parameters depend on the transformation type, supplied as attribute 'type'. 1212 * Possible transformation types are 1213 * <ul> 1214 * <li> 'translate' 1215 * <li> 'scale' 1216 * <li> 'rotate' 1217 * <li> 'rotateX' 1218 * <li> 'rotateY' 1219 * <li> 'rotateZ' 1220 * </ul> 1221 * <p>Valid parameters for these types are: 1222 * <dl> 1223 * <dt><b><tt>type:"translate"</tt></b></dt><dd><b>x, y, z</b> Translation vector (three numbers or functions). 1224 * The transformation matrix for x = a, y = b, and z = c has the form: 1225 * <pre> 1226 * ( 1 0 0 0) ( w ) 1227 * ( a 1 0 0) * ( x ) 1228 * ( b 0 1 0) ( y ) 1229 * ( c 0 0 c) ( z ) 1230 * </pre> 1231 * </dd> 1232 * <dt><b><tt>type:"scale"</tt></b></dt><dd><b>scale_x, scale_y, scale_z</b> Scale vector (three numbers or functions). 1233 * The transformation matrix for scale_x = a, scale_y = b, scale_z = c has the form: 1234 * <pre> 1235 * ( 1 0 0 0) ( w ) 1236 * ( 0 a 0 0) * ( x ) 1237 * ( 0 0 b 0) ( y ) 1238 * ( 0 0 0 c) ( z ) 1239 * </pre> 1240 * </dd> 1241 * <dt><b><tt>type:"rotate"</tt></b></dt><dd><b>a, n</b> angle (in radians), normal. 1242 * Rotate with angle a around the normal vector n. 1243 * </dd> 1244 * <dt><b><tt>type:"rotateX"</tt></b></dt><dd><b>a</b> angle (in radians). 1245 * Rotate with angle a around the normal vector (1, 0, 0). 1246 * </dd> 1247 * <dt><b><tt>type:"rotateY"</tt></b></dt><dd><b>a</b> angle (in radians). 1248 * Rotate with angle a around the normal vector (0, 1, 0). 1249 * </dd> 1250 * <dt><b><tt>type:"rotateZ"</tt></b></dt><dd><b>a</b> angle (in radians). 1251 * Rotate with angle a around the normal vector (0, 0, 1). 1252 * </dd> 1253 * </dl> 1254 */ 1255 JXG.createTransform3D = function (board, parents, attributes) { 1256 return new JXG.Transformation(board, attributes.type, parents, true); 1257 }; 1258 1259 JXG.registerElement('transform3d', JXG.createTransform3D); 1260 1261 export default JXG.Transformation; 1262 1263