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