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