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