1 /* 2 Copyright 2008-2011 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: you can redistribute it and/or modify 13 it under the terms of the GNU Lesser General Public License as published by 14 the Free Software Foundation, either version 3 of the License, or 15 (at your option) any later version. 16 17 JSXGraph is distributed in the hope that it will be useful, 18 but WITHOUT ANY WARRANTY; without even the implied warranty of 19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 GNU Lesser General Public License for more details. 21 22 You should have received a copy of the GNU Lesser General Public License 23 along with JSXGraph. If not, see <http://www.gnu.org/licenses/>. 24 */ 25 26 /** 27 * @fileoverview In this file the geometry object Arc is defined. Arc stores all 28 * style and functional properties that are required to draw an arc on a board. 29 */ 30 31 /** 32 * @class An arc is a segment of the circumference of a circle. It is defined by a center, one point that 33 * defines the radius, and a third point that defines the angle of the arc. 34 * @pseudo 35 * @name Arc 36 * @augments Curve 37 * @constructor 38 * @type JXG.Curve 39 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 40 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The result will be an arc of a circle around p1 through p2. The arc is drawn 41 * counter-clockwise from p2 to p3. 42 * @example 43 * // Create an arc out of three free points 44 * var p1 = board.create('point', [2.0, 2.0]); 45 * var p2 = board.create('point', [1.0, 0.5]); 46 * var p3 = board.create('point', [3.5, 1.0]); 47 * 48 * var a = board.create('arc', [p1, p2, p3]); 49 * </pre><div id="114ef584-4a5e-4686-8392-c97501befb5b" style="width: 300px; height: 300px;"></div> 50 * <script type="text/javascript"> 51 * (function () { 52 * var board = JXG.JSXGraph.initBoard('114ef584-4a5e-4686-8392-c97501befb5b', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 53 * p1 = board.create('point', [2.0, 2.0]), 54 * p2 = board.create('point', [1.0, 0.5]), 55 * p3 = board.create('point', [3.5, 1.0]), 56 * 57 * a = board.create('arc', [p1, p2, p3]); 58 * })(); 59 * </script><pre> 60 */ 61 JXG.createArc = function(board, parents, attributes) { 62 var el, attr, i; 63 64 65 // this method is used to create circumccirclearcs, too. if a circumcirclearc is created we get a fourth 66 // point, that's why we need to check that case, too. 67 if(!(parents = JXG.checkParents('arc', parents, [ 68 [JXG.OBJECT_CLASS_POINT, JXG.OBJECT_CLASS_POINT, JXG.OBJECT_CLASS_POINT], 69 [JXG.OBJECT_CLASS_POINT, JXG.OBJECT_CLASS_POINT, JXG.OBJECT_CLASS_POINT, JXG.OBJECT_CLASS_POINT]]))) { 70 throw new Error("JSXGraph: Can't create Arc with parent types '" + 71 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + 72 (typeof parents[2]) + "'." + 73 "\nPossible parent types: [point,point,point]"); 74 } 75 76 attr = JXG.copyAttributes(attributes, board.options, 'arc'); 77 el = board.create('curve', [[0],[0]], attr); 78 79 el.elType = 'arc'; 80 81 el.parents = []; 82 for (i = 0; i < parents.length; i++) { 83 if (parents[i].id) { 84 el.parents.push(parents[i].id); 85 } 86 } 87 88 /** 89 * documented in JXG.GeometryElement 90 * @ignore 91 */ 92 el.type = JXG.OBJECT_TYPE_ARC; 93 94 /** 95 * Center of the arc. 96 * @memberOf Arc.prototype 97 * @name center 98 * @type JXG.Point 99 */ 100 el.center = JXG.getReference(board, parents[0]); 101 102 /** 103 * Point defining the arc's radius. 104 * @memberOf Arc.prototype 105 * @name radiuspoint 106 * @type JXG.Point 107 */ 108 el.radiuspoint = JXG.getReference(board, parents[1]); 109 el.point2 = el.radiuspoint; 110 111 /** 112 * The point defining the arc's angle. 113 * @memberOf Arc.prototype 114 * @name anglepoint 115 * @type JXG.Point 116 */ 117 el.anglepoint = JXG.getReference(board, parents[2]); 118 el.point3 = el.anglepoint; 119 120 // Add arc as child to defining points 121 el.center.addChild(el); 122 el.radiuspoint.addChild(el); 123 el.anglepoint.addChild(el); 124 125 /** 126 * TODO 127 */ 128 el.useDirection = attr['usedirection']; // useDirection is necessary for circumCircleArcs 129 130 // documented in JXG.Curve 131 el.updateDataArray = function() { 132 var A = this.radiuspoint, 133 B = this.center, 134 C = this.anglepoint, 135 beta, co, si, matrix, phi, i, 136 x = B.X(), 137 y = B.Y(), 138 z = B.Z(), 139 v, det, p0c, p1c, p2c, 140 p1, p2, p3, p4, 141 k, ax, ay, bx, by, d, r, sgn = 1.0, 142 PI2 = Math.PI*0.5; 143 144 phi = JXG.Math.Geometry.rad(A,B,C); 145 146 if ((this.visProp.type=='minor' && phi>Math.PI) 147 || (this.visProp.type=='major' && phi<Math.PI)) { 148 phi = 2*Math.PI - phi; 149 sgn = -1.0; 150 } 151 152 if (this.useDirection) { // This is true for circumCircleArcs. In that case there is 153 // a fourth parent element: [center, point1, point3, point2] 154 p0c = parents[1].coords.usrCoords; 155 p1c = parents[3].coords.usrCoords; 156 p2c = parents[2].coords.usrCoords; 157 det = (p0c[1]-p2c[1])*(p0c[2]-p1c[2]) - (p0c[2]-p2c[2])*(p0c[1]-p1c[1]); 158 if (det < 0) { 159 this.radiuspoint = parents[1]; 160 this.anglepoint = parents[2]; 161 } else { 162 this.radiuspoint = parents[2]; 163 this.anglepoint = parents[1]; 164 } 165 } 166 167 p1 = [A.Z(), A.X(), A.Y()]; 168 p4 = p1.slice(0); 169 r = B.Dist(A); 170 x /= z; 171 y /= z; 172 this.dataX = [p1[1]/p1[0]]; 173 this.dataY = [p1[2]/p1[0]]; 174 while (phi>JXG.Math.eps) { 175 if (phi>=PI2) { 176 beta = PI2; 177 phi -= PI2; 178 } else { 179 beta = phi; 180 phi = 0.0; 181 } 182 183 co = Math.cos(sgn*beta); 184 si = Math.sin(sgn*beta); 185 matrix = [[1, 0, 0], // z missing 186 [x*(1-co)+y*si,co,-si], 187 [y*(1-co)-x*si,si, co]]; 188 v = JXG.Math.matVecMult(matrix, p1); 189 p4 = [v[0]/v[0], v[1]/v[0], v[2]/v[0]]; 190 191 ax = p1[1]-x; 192 ay = p1[2]-y; 193 bx = p4[1]-x; 194 by = p4[2]-y; 195 196 d = Math.sqrt((ax+bx)*(ax+bx) + (ay+by)*(ay+by)); 197 //if (beta>Math.PI) { d *= -1; } 198 199 if (Math.abs(by-ay)>JXG.Math.eps) { 200 k = (ax+bx)*(r/d-0.5)/(by-ay)*8.0/3.0; 201 } else { 202 k = (ay+by)*(r/d-0.5)/(ax-bx)*8.0/3.0; 203 } 204 205 p2 = [1, p1[1]-k*ay, p1[2]+k*ax ]; 206 p3 = [1, p4[1]+k*by, p4[2]-k*bx ]; 207 208 this.dataX = this.dataX.concat([p2[1], p3[1], p4[1]]); 209 this.dataY = this.dataY.concat([p2[2], p3[2], p4[2]]); 210 p1 = p4.slice(0); 211 } 212 this.bezierDegree = 3; 213 214 this.updateStdform(); 215 this.updateQuadraticform(); 216 }; 217 218 /** 219 * Determines the arc's current radius. I.e. the distance between {@link Arc#center} and {@link Arc#radiuspoint}. 220 * @memberOf Arc.prototype 221 * @name Radius 222 * @function 223 * @returns {Number} The arc's radius 224 */ 225 el.Radius = function() { 226 return this.radiuspoint.Dist(this.center); 227 }; 228 229 /** 230 * @deprecated Use {@link Arc#Radius} 231 * @memberOf Arc.prototype 232 * @name getRadius 233 * @function 234 * @returns {Number} 235 */ 236 el.getRadius = function() { 237 return this.Radius(); 238 }; 239 240 // documented in geometry element 241 el.hasPoint = function (x, y) { 242 var prec = this.board.options.precision.hasPoint/(this.board.unitX), 243 r = this.Radius(), 244 dist, checkPoint, 245 has, angle, alpha, beta, 246 invMat, c; 247 248 checkPoint = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x,y], this.board); 249 250 if (this.transformations.length>0) { 251 /** 252 * Transform the mouse/touch coordinates 253 * back to the original position of the curve. 254 */ 255 this.updateTransformMatrix(); 256 invMat = JXG.Math.inverse(this.transformMat); 257 c = JXG.Math.matVecMult(invMat, checkPoint.usrCoords); 258 checkPoint = new JXG.Coords(JXG.COORDS_BY_USER, c, this.board); 259 } 260 261 dist = this.center.coords.distance(JXG.COORDS_BY_USER, checkPoint); 262 has = (Math.abs(dist-r) < prec); 263 264 /** 265 * At that point we know that the user has touched the circle line. 266 */ 267 if (has) { 268 angle = JXG.Math.Geometry.rad(this.radiuspoint,this.center,checkPoint.usrCoords.slice(1)); 269 alpha = 0.0; 270 beta = JXG.Math.Geometry.rad(this.radiuspoint,this.center,this.anglepoint); 271 if ((this.visProp.type=='minor' && beta>Math.PI) 272 || (this.visProp.type=='major' && beta<Math.PI)) { 273 alpha = beta; 274 beta = 2*Math.PI; 275 } 276 if (angle<alpha || angle>beta) { 277 has = false; 278 } 279 } 280 return has; 281 }; 282 283 /** 284 * Checks whether (x,y) is within the sector defined by the arc. 285 * @memberOf Arc.prototype 286 * @name hasPointSector 287 * @function 288 * @param {Number} x Coordinate in x direction, screen coordinates. 289 * @param {Number} y Coordinate in y direction, screen coordinates. 290 * @returns {Boolean} True if (x,y) is within the sector defined by the arc, False otherwise. 291 */ 292 el.hasPointSector = function (x, y) { 293 var checkPoint = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x,y], this.board), 294 r = this.Radius(), 295 dist = this.center.coords.distance(JXG.COORDS_BY_USER,checkPoint), 296 has = (dist<r), 297 angle, alpha, beta; 298 299 if (has) { 300 angle = JXG.Math.Geometry.rad(this.radiuspoint,this.center,checkPoint.usrCoords.slice(1)); 301 alpha = 0.0; 302 beta = JXG.Math.Geometry.rad(this.radiuspoint,this.center,this.anglepoint); 303 if ((this.visProp.type=='minor' && beta>Math.PI) 304 || (this.visProp.type=='major' && beta<Math.PI)) { 305 alpha = beta; 306 beta = 2*Math.PI; 307 } 308 if (angle<alpha || angle>beta) { 309 has = false; 310 } 311 } 312 return has; 313 }; 314 315 // documented in geometry element 316 el.getTextAnchor = function() { 317 return this.center.coords; 318 }; 319 320 // documented in geometry element 321 el.getLabelAnchor = function() { 322 var angle, 323 dx = 10/(this.board.unitX), 324 dy = 10/(this.board.unitY), 325 p2c = this.point2.coords.usrCoords, 326 pmc = this.center.coords.usrCoords, 327 bxminusax = p2c[1] - pmc[1], 328 byminusay = p2c[2] - pmc[2], 329 coords, vecx, vecy, len; 330 331 if(this.label.content != null) { 332 this.label.content.relativeCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [0,0],this.board); 333 } 334 335 angle = JXG.Math.Geometry.rad(this.radiuspoint, this.center, this.anglepoint); 336 if ((this.visProp.type=='minor' && angle>Math.PI) 337 || (this.visProp.type=='major' && angle<Math.PI)) { 338 angle = -(2*Math.PI - angle); 339 } 340 341 coords = new JXG.Coords(JXG.COORDS_BY_USER, 342 [pmc[1]+ Math.cos(angle*0.5)*bxminusax - Math.sin(angle*0.5)*byminusay, 343 pmc[2]+ Math.sin(angle*0.5)*bxminusax + Math.cos(angle*0.5)*byminusay], 344 this.board); 345 346 vecx = coords.usrCoords[1] - pmc[1]; 347 vecy = coords.usrCoords[2] - pmc[2]; 348 349 len = Math.sqrt(vecx*vecx+vecy*vecy); 350 vecx = vecx*(len+dx)/len; 351 vecy = vecy*(len+dy)/len; 352 353 return new JXG.Coords(JXG.COORDS_BY_USER, [pmc[1]+vecx,pmc[2]+vecy], this.board); 354 }; 355 356 /** 357 * TODO description 358 */ 359 el.updateQuadraticform = function () { 360 var m = this.center, 361 mX = m.X(), mY = m.Y(), r = this.Radius(); 362 this.quadraticform = [[mX*mX+mY*mY-r*r,-mX,-mY], 363 [-mX,1,0], 364 [-mY,0,1] 365 ]; 366 }; 367 368 /** 369 * TODO description 370 */ 371 el.updateStdform = function () { 372 this.stdform[3] = 0.5; 373 this.stdform[4] = this.Radius(); 374 this.stdform[1] = -this.center.coords.usrCoords[1]; 375 this.stdform[2] = -this.center.coords.usrCoords[2]; 376 this.normalize(); 377 }; 378 379 el.prepareUpdate().update(); 380 return el; 381 }; 382 383 JXG.JSXGraph.registerElement('arc', JXG.createArc); 384 385 /** 386 * @class A semicircle is a special arc defined by two points. The arc hits both points. 387 * @pseudo 388 * @name Semicircle 389 * @augments Arc 390 * @constructor 391 * @type Arc 392 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 393 * @param {JXG.Point_JXG.Point} p1,p2 The result will be a composition of an arc drawn clockwise from <tt>p1</tt> and 394 * <tt>p2</tt> and the midpoint of <tt>p1</tt> and <tt>p2</tt>. 395 * @example 396 * // Create an arc out of three free points 397 * var p1 = board.create('point', [4.5, 2.0]); 398 * var p2 = board.create('point', [1.0, 0.5]); 399 * 400 * var a = board.create('semicircle', [p1, p2]); 401 * </pre><div id="5385d349-75d7-4078-b732-9ae808db1b0e" style="width: 300px; height: 300px;"></div> 402 * <script type="text/javascript"> 403 * (function () { 404 * var board = JXG.JSXGraph.initBoard('5385d349-75d7-4078-b732-9ae808db1b0e', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 405 * p1 = board.create('point', [4.5, 2.0]), 406 * p2 = board.create('point', [1.0, 0.5]), 407 * 408 * sc = board.create('semicircle', [p1, p2]); 409 * })(); 410 * </script><pre> 411 */ 412 JXG.createSemicircle = function(board, parents, attributes) { 413 var el, mp, attr; 414 415 416 // we need 2 points 417 if ( (JXG.isPoint(parents[0])) && (JXG.isPoint(parents[1])) ) { 418 419 attr = JXG.copyAttributes(attributes, board.options, 'semicircle', 'midpoint'); 420 mp = board.create('midpoint', [parents[0], parents[1]], attr); 421 422 mp.dump = false; 423 424 attr = JXG.copyAttributes(attributes, board.options, 'semicircle'); 425 el = board.create('arc', [mp, parents[1], parents[0]], attr); 426 427 el.elType = 'semicircle'; 428 el.parents = [parents[0].id, parents[1].id]; 429 el.subs = { 430 midpoint: mp 431 }; 432 433 /** 434 * The midpoint of the two defining points. 435 * @memberOf Semicircle.prototype 436 * @name midpoint 437 * @type Midpoint 438 */ 439 el.midpoint = el.center = mp; 440 } else 441 throw new Error("JSXGraph: Can't create Semicircle with parent types '" + 442 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 443 "\nPossible parent types: [point,point]"); 444 445 return el; 446 }; 447 448 JXG.JSXGraph.registerElement('semicircle', JXG.createSemicircle); 449 450 /** 451 * @class A circumcircle arc is an {@link Arc} defined by three points. All three points lie on the arc. 452 * @pseudo 453 * @name CircumcircleArc 454 * @augments Arc 455 * @constructor 456 * @type Arc 457 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 458 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The result will be a composition of an arc of the circumcircle of 459 * <tt>p1</tt>, <tt>p2</tt>, and <tt>p3</tt> and the midpoint of the circumcircle of the three points. The arc is drawn 460 * counter-clockwise from <tt>p1</tt> over <tt>p2</tt> to <tt>p3</tt>. 461 * @example 462 * // Create a circum circle arc out of three free points 463 * var p1 = board.create('point', [2.0, 2.0]); 464 * var p2 = board.create('point', [1.0, 0.5]); 465 * var p3 = board.create('point', [3.5, 1.0]); 466 * 467 * var a = board.create('arc', [p1, p2, p3]); 468 * </pre><div id="87125fd4-823a-41c1-88ef-d1a1369504e3" style="width: 300px; height: 300px;"></div> 469 * <script type="text/javascript"> 470 * (function () { 471 * var board = JXG.JSXGraph.initBoard('87125fd4-823a-41c1-88ef-d1a1369504e3', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 472 * p1 = board.create('point', [2.0, 2.0]), 473 * p2 = board.create('point', [1.0, 0.5]), 474 * p3 = board.create('point', [3.5, 1.0]), 475 * 476 * cca = board.create('circumcirclearc', [p1, p2, p3]); 477 * })(); 478 * </script><pre> 479 */ 480 JXG.createCircumcircleArc = function(board, parents, attributes) { 481 var el, mp, attr; 482 483 // We need three points 484 if ( (JXG.isPoint(parents[0])) && (JXG.isPoint(parents[1])) && (JXG.isPoint(parents[2]))) { 485 486 attr = JXG.copyAttributes(attributes, board.options, 'circumcirclearc', 'center'); 487 mp = board.create('circumcenter',[parents[0], parents[1], parents[2]], attr); 488 489 mp.dump = false; 490 491 attr = JXG.copyAttributes(attributes, board.options, 'circumcirclearc'); 492 attr.usedirection = true; 493 el = board.create('arc', [mp, parents[0], parents[2], parents[1]], attr); 494 495 el.elType = 'circumcirclearc'; 496 el.parents = [parents[0].id, parents[1].id, parents[2].id]; 497 el.subs = { 498 center: mp 499 }; 500 501 /** 502 * The midpoint of the circumcircle of the three points defining the circumcircle arc. 503 * @memberOf CircumcircleArc.prototype 504 * @name center 505 * @type Circumcenter 506 */ 507 el.center = mp; 508 } else 509 throw new Error("JSXGraph: create Circumcircle Arc with parent types '" + 510 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'." + 511 "\nPossible parent types: [point,point,point]"); 512 513 return el; 514 }; 515 516 JXG.JSXGraph.registerElement('circumcirclearc', JXG.createCircumcircleArc); 517 518 /** 519 * @class A minor arc is a segment of the circumference of a circle having measure less than or equal to 520 * 180 degrees (pi radians). It is defined by a center, one point that 521 * defines the radius, and a third point that defines the angle of the arc. 522 * @pseudo 523 * @name MinorArc 524 * @augments Curve 525 * @constructor 526 * @type JXG.Curve 527 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 528 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 . Minor arc is an arc of a circle around p1 having measure less than or equal to 529 * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3. 530 * @example 531 * // Create an arc out of three free points 532 * var p1 = board.create('point', [2.0, 2.0]); 533 * var p2 = board.create('point', [1.0, 0.5]); 534 * var p3 = board.create('point', [3.5, 1.0]); 535 * 536 * var a = board.create('arc', [p1, p2, p3]); 537 * </pre><div id="af27ddcc-265f-428f-90dd-d31ace945800" style="width: 300px; height: 300px;"></div> 538 * <script type="text/javascript"> 539 * (function () { 540 * var board = JXG.JSXGraph.initBoard('af27ddcc-265f-428f-90dd-d31ace945800', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 541 * p1 = board.create('point', [2.0, 2.0]), 542 * p2 = board.create('point', [1.0, 0.5]), 543 * p3 = board.create('point', [3.5, 1.0]), 544 * 545 * a = board.create('minorarc', [p1, p2, p3]); 546 * })(); 547 * </script><pre> 548 */ 549 550 JXG.createMinorArc = function(board, parents, attributes) { 551 attributes.type = 'minor'; 552 return JXG.createArc(board, parents, attributes); 553 }; 554 555 JXG.JSXGraph.registerElement('minorarc', JXG.createMinorArc); 556 557 /** 558 * @class A major arc is a segment of the circumference of a circle having measure greater than or equal to 559 * 180 degrees (pi radians). It is defined by a center, one point that 560 * defines the radius, and a third point that defines the angle of the arc. 561 * @pseudo 562 * @name MinorArc 563 * @augments Curve 564 * @constructor 565 * @type JXG.Curve 566 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 567 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 . Major arc is an arc of a circle around p1 having measure greater than or equal to 568 * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3. 569 * @example 570 * // Create an arc out of three free points 571 * var p1 = board.create('point', [2.0, 2.0]); 572 * var p2 = board.create('point', [1.0, 0.5]); 573 * var p3 = board.create('point', [3.5, 1.0]); 574 * 575 * var a = board.create('arc', [p1, p2, p3]); 576 * </pre><div id="83c6561f-7561-4047-b98d-036248a00932" style="width: 300px; height: 300px;"></div> 577 * <script type="text/javascript"> 578 * (function () { 579 * var board = JXG.JSXGraph.initBoard('83c6561f-7561-4047-b98d-036248a00932', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}), 580 * p1 = board.create('point', [2.0, 2.0]), 581 * p2 = board.create('point', [1.0, 0.5]), 582 * p3 = board.create('point', [3.5, 1.0]), 583 * 584 * a = board.create('majorarc', [p1, p2, p3]); 585 * })(); 586 * </script><pre> 587 */ 588 JXG.createMajorArc = function(board, parents, attributes) { 589 attributes.type = 'major'; 590 return JXG.createArc(board, parents, attributes); 591 }; 592 593 JXG.JSXGraph.registerElement('majorarc', JXG.createMajorArc); 594