1 /*
  2     Copyright 2008-2010
  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 This element is used to provide a constructor for arc elements.
 33  * @pseudo
 34  * @description An is a segment of the circumference of a circle.
 35  * @name Arc
 36  * @augments Curve
 37  * @constructor
 38  * @type JXG.Curve
 39  * @throws {Exception} 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  *   var arex1_board = JXG.JSXGraph.initBoard('114ef584-4a5e-4686-8392-c97501befb5b', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
 52  * var arex1_p1 = arex1_board.create('point', [2.0, 2.0]);
 53  * var arex1_p2 = arex1_board.create('point', [1.0, 0.5]);
 54  * var arex1_p3 = arex1_board.create('point', [3.5, 1.0]);
 55  *
 56  * var arex1_a = arex1_board.create('arc', [arex1_p1, arex1_p2, arex1_p3]);
 57  * </script><pre>
 58  */
 59 JXG.createArc = function(board, parents, attributes) {
 60     var el, defaults, key, options;
 61         
 62     // Alles 3 Punkte?
 63     if ( !(JXG.isPoint(parents[0]) && JXG.isPoint(parents[1]) && JXG.isPoint(parents[2]))) {
 64         throw new Error("JSXGraph: Can't create Arc with parent types '" + 
 65                         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + 
 66                         (typeof parents[2]) + "'." +
 67                         "\nPossible parent types: [point,point,point]");
 68     }
 69 
 70     // Read the default values from Options and use them in case they are not set by the user
 71     // in attributes
 72     defaults = {
 73         withLabel: JXG.readOption(board.options,'elements','withLabel'),
 74         layer: JXG.readOption(board.options,'layer','arc'),
 75         useDirection:false      // useDirection is necessary for circumCircleArcs
 76     };
 77     defaults['strokeWidth'] =  board.options.elements['strokeWidth'];
 78     options = board.options.arc;
 79     for (key in options) {
 80         defaults[key] = options[key];
 81     }
 82     attributes = JXG.checkAttributes(attributes, defaults);
 83         
 84     el = board.create('curve',[[0],[0]],attributes);
 85     el.type = JXG.OBJECT_TYPE_ARC;
 86 
 87     /**
 88      * Midpoint of the arc.
 89      * @type JXG.Point
 90      */
 91     el.midpoint = JXG.getReference(board, parents[0]);
 92 
 93     /**
 94      * Point defining the arcs circle.
 95      * @type JXG.Point
 96      */
 97     el.point2 = JXG.getReference(board, parents[1]);
 98 
 99     /**
100      * The point defining the angle of the arc.
101      * @type JXG.Point
102      */
103     el.point3 = JXG.getReference(board, parents[2]);
104 
105     /* Add arc as child to defining points */
106     el.midpoint.addChild(el);
107     el.point2.addChild(el);
108     el.point3.addChild(el);
109     
110     el.useDirection = attributes['useDirection'];      // useDirection is necessary for circumCircleArcs
111 
112     el.updateDataArray = function() {
113         var A = this.point2,
114             B = this.midpoint,
115             C = this.point3,
116             beta, co, si, matrix,
117             phi = JXG.Math.Geometry.rad(A,B,C),
118             i,
119             //n = 100, 
120             n = Math.ceil(phi/Math.PI*90)+1,
121             delta = phi/n, //Math.PI/90.0,
122             x = B.X(),
123             y = B.Y(),
124             v,
125             det, p0c, p1c, p2c;
126 
127         if (this.useDirection) {  // This is true for circumCircleArcs. In that case there is
128                                   // a fourth parent element: [midpoint, point1, point3, point2]
129             p0c = parents[1].coords.usrCoords;
130             p1c = parents[3].coords.usrCoords;
131             p2c = parents[2].coords.usrCoords;
132             det = (p0c[1]-p2c[1])*(p0c[2]-p1c[2]) - (p0c[2]-p2c[2])*(p0c[1]-p1c[1]);
133             if(det < 0) {
134                 this.point2 = parents[1];
135                 this.point3 = parents[2];
136             } else {
137                 this.point2 = parents[2];
138                 this.point3 = parents[1];
139             }
140         }
141         this.dataX = [A.X()];
142         this.dataY = [A.Y()];
143 
144         for (beta=delta,i=1; i<=n; i++, beta+=delta) {
145             co = Math.cos(beta);
146             si = Math.sin(beta);
147             matrix = [[1,            0,   0],
148                       [x*(1-co)+y*si,co,-si],
149                       [y*(1-co)-x*si,si, co]];
150             v = JXG.Math.matVecMult(matrix,A.coords.usrCoords);
151             this.dataX.push(v[1]/v[0]);
152             this.dataY.push(v[2]/v[0]);
153         }
154     };
155 
156     /**
157      * Calculates the arcs radius.
158      * @returns {Number} The arcs radius
159      */
160     el.Radius = function() {
161         return this.point2.Dist(this.midpoint);
162     };
163 
164     /**
165      * @deprecated Use Radius()
166      */
167     el.getRadius = function() {
168         return this.Radius();
169     };
170 
171     /**
172      *Checks whether (x,y) is near the arc.
173      * @param {Number} x Coordinate in x direction, screen coordinates.
174      * @param {Number} y Coordinate in y direction, screen coordinates.
175      * @returns {Boolean} True if (x,y) is near the arc, False otherwise.
176      */
177     el.hasPoint = function (x, y) { 
178         var prec = this.board.options.precision.hasPoint/(this.board.stretchX),
179             checkPoint = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x,y], this.board),
180             r = this.Radius(),
181             dist = this.midpoint.coords.distance(JXG.COORDS_BY_USER,checkPoint),
182             has = (Math.abs(dist-r) < prec),
183             angle;
184             
185         if(has) {
186             angle = JXG.Math.Geometry.rad(this.point2,this.midpoint,checkPoint.usrCoords.slice(1));
187             if (angle>JXG.Math.Geometry.rad(this.point2,this.midpoint,this.point3)) { has = false; }
188         }
189         return has;    
190     };
191 
192     /**
193      * Checks whether (x,y) is within the sector defined by the arc.
194      * @param {Number} x Coordinate in x direction, screen coordinates.
195      * @param {Number} y Coordinate in y direction, screen coordinates.
196      * @returns {Boolean} True if (x,y) is within the sector defined by the arc, False otherwise.
197      */
198     el.hasPointSector = function (x, y) { 
199         var checkPoint = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x,y], this.board),
200             r = this.Radius(),
201             dist = this.midpoint.coords.distance(JXG.COORDS_BY_USER,checkPoint),
202             has = (dist<r),
203             angle;
204         
205         if(has) {
206             angle = JXG.Math.Geometry.rad(this.point2,this.midpoint,checkPoint.usrCoords.slice(1));
207             if (angle>JXG.Math.Geometry.rad(this.point2,this.midpoint,this.point3)) { has = false; }
208         }
209         return has;    
210     };
211 
212     /**
213      * @returns {JXG.Coords} Coordinates of the text's anchor.
214      */
215     el.getTextAnchor = function() {
216         return this.midpoint.coords;
217     };
218 
219     /**
220      * @returns {JXG.Coords} Coordinates of the label's anchor
221      */
222     el.getLabelAnchor = function() {
223         var angle = JXG.Math.Geometry.rad(this.point2, this.midpoint, this.point3),
224             dx = 10/(this.board.stretchX),
225             dy = 10/(this.board.stretchY),
226             p2c = this.point2.coords.usrCoords,
227             pmc = this.midpoint.coords.usrCoords,
228             bxminusax = p2c[1] - pmc[1],
229             byminusay = p2c[2] - pmc[2],
230             coords, vecx, vecy, len;
231 
232         if(this.label.content != null) {                          
233             this.label.content.relativeCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [0,0],this.board);                      
234         }  
235 
236         coords = new JXG.Coords(JXG.COORDS_BY_USER, 
237                         [pmc[1]+ Math.cos(angle*0.5)*bxminusax - Math.sin(angle*0.5)*byminusay, 
238                         pmc[2]+ Math.sin(angle*0.5)*bxminusax + Math.cos(angle*0.5)*byminusay], 
239                         this.board);
240 
241         vecx = coords.usrCoords[1] - pmc[1];
242         vecy = coords.usrCoords[2] - pmc[2];
243     
244         len = Math.sqrt(vecx*vecx+vecy*vecy);
245         vecx = vecx*(len+dx)/len;
246         vecy = vecy*(len+dy)/len;
247 
248         return new JXG.Coords(JXG.COORDS_BY_USER, [pmc[1]+vecx,pmc[2]+vecy],this.board);
249     };
250 
251     el.prepareUpdate().update();
252     return el;
253 };
254 
255 JXG.JSXGraph.registerElement('arc', JXG.createArc);
256 
257 /** TODO: Documentation of those two elements below are to be written like the one above for ARC.
258 /**
259  * Creates a new semicircle. The semicircle is drawn clock-wise between the first and the second defining point.
260  * @param {JXG.Board} board The board the semicircle is put on.
261  * @param {Array} parents Array of two opposite points defining the semicircle.
262  * @param {Object} attributes Object containing properties for the element such as stroke-color and visibility. See @see JXG.GeometryElement#setProperty
263  * @returns {JXG.Arc} Reference to the created object.
264  */
265 JXG.createSemicircle = function(board, parents, attributes) {
266     var el, mp, idmp = '';
267     
268     attributes = JXG.checkAttributes(attributes,{});
269     if(attributes['id'] != null) {
270         idmp = attributes['id']+'_mp';
271     }
272     // Alles 2 Punkte?
273     if ( (JXG.isPoint(parents[0])) && (JXG.isPoint(parents[1])) ) {
274         mp = board.create('midpoint', [parents[0], parents[1]], {id:idmp, withLabel:false, visible:false});
275         el = board.create('arc',[mp, parents[1], parents[0]],attributes);
276     } // Ansonsten eine fette Exception um die Ohren hauen
277     else
278         throw new Error("JSXGraph: Can't create Semicircle with parent types '" + 
279                         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
280                         "\nPossible parent types: [point,point]");
281 
282     return el;
283 };
284 
285 JXG.JSXGraph.registerElement('semicircle', JXG.createSemicircle);
286 
287 /**
288  * Creates a new circumcircle arc through three defining points.
289  * @param {JXG.Board} board The board the arc is put on.
290  * @param {Array} parents Array of three points defining the circumcircle arc.
291  * @param {Object} attributes Object containing properties for the element such as stroke-color and visibility. See @see JXG.GeometryElement#setProperty
292  * @returns {JXG.Arc} Reference to the created object.
293  */
294 JXG.createCircumcircleArc = function(board, parents, attributes) {
295     var el, mp, idmp;
296     
297     attributes = JXG.checkAttributes(attributes,{withLabel:JXG.readOption(board.options,'arc','withLabel'), layer:null});
298     if(attributes['id'] != null) {
299         idmp = attributes['id']+'_mp';
300     }
301     
302     // Alles 3 Punkte?
303     if ( (JXG.isPoint(parents[0])) && (JXG.isPoint(parents[1])) && (JXG.isPoint(parents[2]))) {
304         mp = board.create('circumcirclemidpoint',[parents[0], parents[1], parents[2]], {id:idmp, withLabel:false, visible:false});
305         attributes.useDirection = true;
306         el = board.create('arc', [mp,parents[0],parents[2],parents[1]], attributes);
307     } // Ansonsten eine fette Exception um die Ohren hauen
308     else
309         throw new Error("JSXGraph: create Circumcircle Arc with parent types '" + 
310                         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'." +
311                         "\nPossible parent types: [point,point,point]");
312 
313 
314     return el;
315 };
316 
317 JXG.JSXGraph.registerElement('circumcirclearc', JXG.createCircumcircleArc);
318