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,
136             phi = JXG.Math.Geometry.rad(A,B,C),
137             i,
138             n = Math.ceil(phi/Math.PI*90)+1,
139             delta = phi/n,
140             x = B.X(),
141             y = B.Y(),
142             v,
143             det, p0c, p1c, p2c;
144 
145         if (this.useDirection) {  // This is true for circumCircleArcs. In that case there is
146                                   // a fourth parent element: [center, point1, point3, point2]
147             p0c = parents[1].coords.usrCoords;
148             p1c = parents[3].coords.usrCoords;
149             p2c = parents[2].coords.usrCoords;
150             det = (p0c[1]-p2c[1])*(p0c[2]-p1c[2]) - (p0c[2]-p2c[2])*(p0c[1]-p1c[1]);
151             if(det < 0) {
152                 this.radiuspoint = parents[1];
153                 this.anglepoint = parents[2];
154             } else {
155                 this.radiuspoint = parents[2];
156                 this.anglepoint = parents[1];
157             }
158         }
159         this.dataX = [A.X()];
160         this.dataY = [A.Y()];
161 
162         for (beta=delta,i=1; i<=n; i++, beta+=delta) {
163             co = Math.cos(beta);
164             si = Math.sin(beta);
165             matrix = [[1,            0,   0],
166                       [x*(1-co)+y*si,co,-si],
167                       [y*(1-co)-x*si,si, co]];
168             v = JXG.Math.matVecMult(matrix,A.coords.usrCoords);
169             this.dataX.push(v[1]/v[0]);
170             this.dataY.push(v[2]/v[0]);
171         }
172         
173         this.updateStdform();
174         this.updateQuadraticform();
175     };
176 
177     /**
178      * Determines the arc's current radius. I.e. the distance between {@link Arc#center} and {@link Arc#radiuspoint}.
179      * @memberOf Arc.prototype
180      * @name Radius
181      * @function
182      * @returns {Number} The arc's radius
183      */
184     el.Radius = function() {
185         return this.radiuspoint.Dist(this.center);
186     };
187 
188     /**
189      * @deprecated Use {@link Arc#Radius}
190      * @memberOf Arc.prototype
191      * @name getRadius
192      * @function
193      * @returns {Number}
194      */
195     el.getRadius = function() {
196         return this.Radius();
197     };
198 
199     // documented in geometry element
200     el.hasPoint = function (x, y) {
201         var prec = this.board.options.precision.hasPoint/(this.board.unitX),
202             checkPoint = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x,y], this.board),
203             r = this.Radius(),
204             dist = this.center.coords.distance(JXG.COORDS_BY_USER,checkPoint),
205             has = (Math.abs(dist-r) < prec),
206             angle;
207             
208         if(has) {
209             angle = JXG.Math.Geometry.rad(this.radiuspoint,this.center,checkPoint.usrCoords.slice(1));
210             if (angle>JXG.Math.Geometry.rad(this.radiuspoint,this.center,this.anglepoint)) { has = false; }
211         }
212         return has;    
213     };
214 
215     /**
216      * Checks whether (x,y) is within the sector defined by the arc.
217      * @memberOf Arc.prototype
218      * @name hasPointSector
219      * @function
220      * @param {Number} x Coordinate in x direction, screen coordinates.
221      * @param {Number} y Coordinate in y direction, screen coordinates.
222      * @returns {Boolean} True if (x,y) is within the sector defined by the arc, False otherwise.
223      */
224     el.hasPointSector = function (x, y) { 
225         var checkPoint = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x,y], this.board),
226             r = this.Radius(),
227             dist = this.center.coords.distance(JXG.COORDS_BY_USER,checkPoint),
228             has = (dist<r),
229             angle;
230         
231         if(has) {
232             angle = JXG.Math.Geometry.rad(this.radiuspoint,this.center,checkPoint.usrCoords.slice(1));
233             if (angle>JXG.Math.Geometry.rad(this.radiuspoint,this.center,this.anglepoint)) { has = false; }
234         }
235         return has;    
236     };
237 
238     // documented in geometry element
239     el.getTextAnchor = function() {
240         return this.center.coords;
241     };
242 
243     // documented in geometry element
244     el.getLabelAnchor = function() {
245         var angle = JXG.Math.Geometry.rad(this.radiuspoint, this.center, this.anglepoint),
246             dx = 10/(this.board.unitX),
247             dy = 10/(this.board.unitY),
248             p2c = this.point2.coords.usrCoords,
249             pmc = this.center.coords.usrCoords,
250             bxminusax = p2c[1] - pmc[1],
251             byminusay = p2c[2] - pmc[2],
252             coords, vecx, vecy, len;
253 
254         if(this.label.content != null) {                          
255             this.label.content.relativeCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [0,0],this.board);                      
256         }  
257 
258         coords = new JXG.Coords(JXG.COORDS_BY_USER, 
259                         [pmc[1]+ Math.cos(angle*0.5)*bxminusax - Math.sin(angle*0.5)*byminusay, 
260                         pmc[2]+ Math.sin(angle*0.5)*bxminusax + Math.cos(angle*0.5)*byminusay], 
261                         this.board);
262 
263         vecx = coords.usrCoords[1] - pmc[1];
264         vecy = coords.usrCoords[2] - pmc[2];
265     
266         len = Math.sqrt(vecx*vecx+vecy*vecy);
267         vecx = vecx*(len+dx)/len;
268         vecy = vecy*(len+dy)/len;
269 
270         return new JXG.Coords(JXG.COORDS_BY_USER, [pmc[1]+vecx,pmc[2]+vecy], this.board);
271     };
272     
273     /**
274      * TODO description
275      */
276     el.updateQuadraticform = function () {
277         var m = this.center,
278             mX = m.X(), mY = m.Y(), r = this.Radius();
279         this.quadraticform = [[mX*mX+mY*mY-r*r,-mX,-mY],
280             [-mX,1,0],
281             [-mY,0,1]
282         ];
283     };
284 
285     /**
286      * TODO description
287      */
288     el.updateStdform = function () {
289         this.stdform[3] = 0.5;
290         this.stdform[4] = this.Radius();
291         this.stdform[1] = -this.center.coords.usrCoords[1];
292         this.stdform[2] = -this.center.coords.usrCoords[2];
293         this.normalize();
294     };
295 
296     el.prepareUpdate().update();
297     return el;
298 };
299 
300 JXG.JSXGraph.registerElement('arc', JXG.createArc);
301 
302 /**
303  * @class A semicircle is a special arc defined by two points. The arc hits both points.
304  * @pseudo
305  * @name Semicircle
306  * @augments Arc
307  * @constructor
308  * @type Arc
309  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
310  * @param {JXG.Point_JXG.Point} p1,p2 The result will be a composition of an arc drawn clockwise from <tt>p1</tt> and
311  * <tt>p2</tt> and the midpoint of <tt>p1</tt> and <tt>p2</tt>.
312  * @example
313  * // Create an arc out of three free points
314  * var p1 = board.create('point', [4.5, 2.0]);
315  * var p2 = board.create('point', [1.0, 0.5]);
316  *
317  * var a = board.create('semicircle', [p1, p2]);
318  * </pre><div id="5385d349-75d7-4078-b732-9ae808db1b0e" style="width: 300px; height: 300px;"></div>
319  * <script type="text/javascript">
320  * (function () {
321  *   var board = JXG.JSXGraph.initBoard('5385d349-75d7-4078-b732-9ae808db1b0e', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
322  *       p1 = board.create('point', [4.5, 2.0]),
323  *       p2 = board.create('point', [1.0, 0.5]),
324  *
325  *       sc = board.create('semicircle', [p1, p2]);
326  * })();
327  * </script><pre>
328  */
329 JXG.createSemicircle = function(board, parents, attributes) {
330     var el, mp, attr;
331     
332 
333     // we need 2 points
334     if ( (JXG.isPoint(parents[0])) && (JXG.isPoint(parents[1])) ) {
335 
336         attr = JXG.copyAttributes(attributes, board.options, 'semicircle', 'midpoint');
337         mp = board.create('midpoint', [parents[0], parents[1]], attr);
338 
339         mp.dump = false;
340 
341         attr = JXG.copyAttributes(attributes, board.options, 'semicircle');
342         el = board.create('arc', [mp, parents[1], parents[0]], attr);
343 
344         el.elType = 'semicircle';
345         el.parents = [parents[0].id, parents[1].id];
346         el.subs = {
347             midpoint: mp
348         };
349 
350         /**
351          * The midpoint of the two defining points.
352          * @memberOf Semicircle.prototype
353          * @name midpoint
354          * @type Midpoint
355          */
356         el.midpoint = mp;
357     } else
358         throw new Error("JSXGraph: Can't create Semicircle with parent types '" + 
359                         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
360                         "\nPossible parent types: [point,point]");
361 
362     return el;
363 };
364 
365 JXG.JSXGraph.registerElement('semicircle', JXG.createSemicircle);
366 
367 /**
368  * @class A circumcircle arc is an {@link Arc} defined by three points. All three points lie on the arc.
369  * @pseudo
370  * @name CircumcircleArc
371  * @augments Arc
372  * @constructor
373  * @type Arc
374  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
375  * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The result will be a composition of an arc of the circumcircle of
376  * <tt>p1</tt>, <tt>p2</tt>, and <tt>p3</tt> and the midpoint of the circumcircle of the three points. The arc is drawn
377  * counter-clockwise from <tt>p1</tt> over <tt>p2</tt> to <tt>p3</tt>.
378  * @example
379  * // Create a circum circle arc out of three free points
380  * var p1 = board.create('point', [2.0, 2.0]);
381  * var p2 = board.create('point', [1.0, 0.5]);
382  * var p3 = board.create('point', [3.5, 1.0]);
383  *
384  * var a = board.create('arc', [p1, p2, p3]);
385  * </pre><div id="87125fd4-823a-41c1-88ef-d1a1369504e3" style="width: 300px; height: 300px;"></div>
386  * <script type="text/javascript">
387  * (function () {
388  *   var board = JXG.JSXGraph.initBoard('87125fd4-823a-41c1-88ef-d1a1369504e3', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
389  *       p1 = board.create('point', [2.0, 2.0]),
390  *       p2 = board.create('point', [1.0, 0.5]),
391  *       p3 = board.create('point', [3.5, 1.0]),
392  *
393  *       cca = board.create('circumcirclearc', [p1, p2, p3]);
394  * })();
395  * </script><pre>
396  */
397 JXG.createCircumcircleArc = function(board, parents, attributes) {
398     var el, mp, attr;
399     
400     // We need three points
401     if ( (JXG.isPoint(parents[0])) && (JXG.isPoint(parents[1])) && (JXG.isPoint(parents[2]))) {
402 
403         attr = JXG.copyAttributes(attributes, board.options, 'circumcirclearc', 'center');
404         mp = board.create('circumcenter',[parents[0], parents[1], parents[2]], attr);
405 
406         mp.dump = false;
407 
408         attr = JXG.copyAttributes(attributes, board.options, 'circumcirclearc');
409         attr.usedirection = true;
410         el = board.create('arc', [mp, parents[0], parents[2], parents[1]], attr);
411 
412         el.elType = 'circumcirclearc';
413         el.parents = [parents[0].id, parents[1].id, parents[2].id];
414         el.subs = {
415             center: mp
416         };
417 
418         /**
419          * The midpoint of the circumcircle of the three points defining the circumcircle arc.
420          * @memberOf CircumcircleArc.prototype
421          * @name center
422          * @type Circumcenter
423          */
424         el.center = mp;
425     } else
426         throw new Error("JSXGraph: create Circumcircle Arc with parent types '" + 
427                         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'." +
428                         "\nPossible parent types: [point,point,point]");
429 
430     return el;
431 };
432 
433 JXG.JSXGraph.registerElement('circumcirclearc', JXG.createCircumcircleArc);
434