1 /* 2 Copyright 2008-2024 3 Matthias Ehmann, 4 Aaron Fenyes, 5 Carsten Miller, 6 Andreas Walter, 7 Alfred Wassermann 8 9 This file is part of JSXGraph. 10 11 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 12 13 You can redistribute it and/or modify it under the terms of the 14 15 * GNU Lesser General Public License as published by 16 the Free Software Foundation, either version 3 of the License, or 17 (at your option) any later version 18 OR 19 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 20 21 JSXGraph is distributed in the hope that it will be useful, 22 but WITHOUT ANY WARRANTY; without even the implied warranty of 23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 GNU Lesser General Public License for more details. 25 26 You should have received a copy of the GNU Lesser General Public License and 27 the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/> 28 and <https://opensource.org/licenses/MIT/>. 29 */ 30 /*global JXG:true, define: true*/ 31 32 import JXG from "../jxg.js"; 33 import Const from "../base/constants.js"; 34 import Type from "../utils/type.js"; 35 import Mat from '../math/math.js'; 36 import Geometry from '../math/geometry.js'; 37 38 /** 39 * In 3D space, a circle consists of all points on a given plane with a given distance from a given point. The given point is called the center, and the given distance is called the radius. 40 * A circle can be constructed by providing a center, a normal vector, and a radius (given as a number or function). 41 * @class Creates a new 3D circle object. Do not use this constructor to create a 3D circle. Use {@link JXG.View3D#create} with 42 * type {@link Circle3D} instead. 43 * @constructor 44 * @augments JXG.GeometryElement3D 45 * @augments JXG.GeometryElement 46 * @param {JXG.View3D} view The 3D view the circle is drawn on. 47 * @param {JXG.Point} center The center of the circle. 48 * @param {Array} normal A normal vector of the plane the circle lies in. Must be either an array of three numbers or an array of three functions returning numbers. 49 * @param {Number|Function} radius The radius of the circle. 50 * @param {Object} attributes 51 * @see JXG.Board#generateName 52 */ 53 JXG.Circle3D = function (view, center, normal, radius, attributes) { 54 var altFrame1; 55 56 this.constructor(view.board, attributes, Const.OBJECT_TYPE_CIRCLE3D, Const.OBJECT_CLASS_3D); 57 this.constructor3D(view, "circle3d"); 58 59 /** 60 * The circle's center. Do not set this parameter directly, as that will break JSXGraph's update system. 61 * @type JXG.Point3D 62 */ 63 this.center = this.board.select(center); 64 65 /** 66 * A normal vector of the plane the circle lies in. Do not set this parameter directly, as that will break JSXGraph's update system. 67 * @type Array 68 * @private 69 * 70 * @see updateNormal 71 */ 72 this.normal = [0, 0, 0]; 73 74 /** 75 * The circle's underlying Curve3D. 76 */ 77 this.curve; 78 79 /** 80 * The first vector in an orthonormal frame for the plane the circle lies in. 81 * Do not set this parameter directly, as that will break JSXGraph's update system. 82 * @type Array 83 * @private 84 * 85 * @see updateFrame 86 */ 87 this.frame1; 88 89 /** 90 * The second vector in an orthonormal frame for the plane the circle lies in. 91 * Do not set this parameter directly, as that will break JSXGraph's update system. 92 * @type Array 93 * @private 94 * 95 * @see updateFrame 96 */ 97 this.frame2; 98 99 this.updateNormal = function () { 100 // evaluate normal direction 101 var i, len; 102 for (i = 0; i < 3; i++) { 103 this.normal[i] = Type.evaluate(normal[i]); 104 } 105 106 // scale normal to unit length 107 len = Mat.norm(this.normal); 108 if (Math.abs(len) > Mat.eps) { 109 for (i = 0; i < 3; i++) { 110 this.normal[i] /= len; 111 } 112 } 113 }; 114 115 // place the circle or its center---whichever is newer---in the scene tree 116 if (Type.exists(this.center._is_new)) { 117 this.addChild(this.center); 118 delete this.center._is_new; 119 } else { 120 this.center.addChild(this); 121 } 122 123 // Converts JessieCode syntax into JavaScript syntax and generally ensures that the radius is a function 124 this.updateRadius = Type.createFunction(radius, this.board); 125 this.addParentsFromJCFunctions([this.updateRadius]); 126 127 // initialize normal 128 this.updateNormal(); 129 130 // initialize the first frame vector by taking the cross product with 131 // [1, 0, 0] or [-0.5, sqrt(3)/2, 0]---whichever is further away on the unit 132 // sphere. every vector is at least 60 degrees from one of these, which 133 // should be good enough to make the frame vector numerically accurate 134 this.frame1 = Mat.crossProduct(this.normal, [1, 0, 0]); 135 altFrame1 = Mat.crossProduct(this.normal, [-0.5, 0.8660254037844386, 0]); // [1/2, sqrt(3)/2, 0] 136 if (Mat.norm(altFrame1) > Mat.norm(this.frame1)) { 137 this.frame1 = altFrame1; 138 } 139 140 // initialize the second frame vector 141 this.frame2 = Mat.crossProduct(this.normal, this.frame1); 142 143 // scale both frame vectors to unit length 144 this.normalizeFrame(); 145 146 // create the underlying curve 147 this.curve = view.create( 148 'curve3d', 149 [ 150 (t) => this.center.X() + this.Radius() * (Math.cos(t) * this.frame1[0] + Math.sin(t) * this.frame2[0]), 151 (t) => this.center.Y() + this.Radius() * (Math.cos(t) * this.frame1[1] + Math.sin(t) * this.frame2[1]), 152 (t) => this.center.Z() + this.Radius() * (Math.cos(t) * this.frame1[2] + Math.sin(t) * this.frame2[2]), 153 [0, 2 * Math.PI] // parameter range 154 ], 155 attributes 156 ); 157 }; 158 JXG.Circle3D.prototype = new JXG.GeometryElement(); 159 Type.copyPrototypeMethods(JXG.Circle3D, JXG.GeometryElement3D, "constructor3D"); 160 161 JXG.extend( 162 JXG.Circle3D.prototype, 163 /** @lends JXG.Circle3D.prototype */ { 164 update: function () { 165 this.updateNormal(); 166 this.updateFrame(); 167 this.curve.visProp.visible = !isNaN(this.Radius()); 168 return this; 169 }, 170 171 updateRenderer: function () { 172 this.needsUpdate = false; 173 return this; 174 }, 175 176 /** 177 * Set a new radius, then update the board. 178 * @param {String|Number|function} r A string, function or number describing the new radius 179 * @returns {JXG.Circle3D} Reference to this sphere 180 */ 181 setRadius: function (r) { 182 this.updateRadius = Type.createFunction(r, this.board); 183 this.addParentsFromJCFunctions([this.updateRadius]); 184 this.board.update(); 185 186 return this; 187 }, 188 189 /** 190 * Calculates the radius of the circle. 191 * @param {String|Number|function} [value] Set new radius 192 * @returns {Number} The radius of the circle 193 */ 194 Radius: function (value) { 195 if (Type.exists(value)) { 196 this.setRadius(value); 197 return this.Radius(); 198 } 199 200 return Math.abs(this.updateRadius()); 201 }, 202 203 normalizeFrame: function () { 204 // normalize frame 205 var len1 = Mat.norm(this.frame1), 206 len2 = Mat.norm(this.frame2), 207 i; 208 for (i = 0; i < 3; i++) { 209 this.frame1[i] /= len1; 210 this.frame2[i] /= len2; 211 } 212 }, 213 214 updateFrame: function () { 215 this.frame1 = Mat.crossProduct(this.frame2, this.normal); 216 this.frame2 = Mat.crossProduct(this.normal, this.frame1); 217 this.normalizeFrame(); 218 }, 219 220 projectCoords: function (p, params) { 221 // we have to call `this.curve.projectCoords` from the curve, rather 222 // than the circle, to make `this` refer to the curve within the 223 // call 224 return this.curve.projectCoords(p, params); 225 }, 226 227 projectScreenCoords: function (pScr, params) { 228 // we have to call `this.curve.projectScreenCoords` from the curve, 229 // rather than the circle, to make `this` refer to the curve within 230 // the call 231 return this.curve.projectScreenCoords(pScr, params); 232 } 233 } 234 ); 235 236 /** 237 * @class This element is used to provide a constructor for a circle. 238 * @pseudo 239 * @description In 3D space, a circle consists of all points on a given plane with a given distance from a given point. The given point is called the center, and the given distance is called the radius. 240 * A circle can be constructed by providing a center, a normal vector, and a radius (given as a number or function). 241 * <p> 242 * If the radius has a negative value, its absolute value is taken. If the radius evaluates to NaN, 243 * the circle is not displayed. This is convenient for constructing an intersection circle, which is empty when its parents do not intersect. 244 * @name Circle3D 245 * @augments JXG.Circle3D 246 * @constructor 247 * @type JXG.Circle3D 248 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 249 * @param {JXG.Point_Array_number} center,normal,radius The center must be given as a {@link JXG.Point} (see {@link JXG.providePoints}). 250 * The normal vector can be given as an array of three numbers or an array of three functions returning numbers, 251 * and the radius can be given as a number (which will create a circle with a fixed radius) or a function. 252 * <p> 253 * If the radius is supplied as a number or the output of a function, its absolute value is taken. When the radius evaluates to NaN, the circle does not display. 254 */ 255 JXG.createCircle3D = function (board, parents, attributes) { 256 var view = parents[0], 257 attr = Type.copyAttributes(attributes, board.options, 'circle3d'), 258 center = Type.providePoints3D(view, [parents[1]], attributes, 'line3d', ['point'])[0], 259 normal = parents[2], 260 radius = parents[3], 261 el; 262 263 // create element 264 el = new JXG.Circle3D(view, center, normal, radius, attr); 265 266 // update scene tree 267 el.center.addChild(el); 268 el.addChild(el.curve); 269 270 el.update(); 271 return el; 272 }; 273 274 JXG.registerElement("circle3d", JXG.createCircle3D); 275 276 /** 277 * @class An intersection circle is a circle which lives on two JSXGraph elements. 278 * The following element types can be (mutually) intersected: sphere, plane. 279 * 280 * @pseudo 281 * @name IntersectionCircle3D 282 * @augments JXG.Circle3D 283 * @constructor 284 * @type JXG.Circle3D 285 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 286 * @param {JXG.Sphere3D_JXG.Sphere3D|JXG.Plane3D} el1,el2 The result will be the intersection of el1 and el2. 287 * @example 288 * // Create the intersection circle of two spheres 289 * var view = board.create( 290 * 'view3d', 291 * [[-6, -3], [8, 8], 292 * [[0, 3], [0, 3], [0, 3]]], 293 * { 294 * xPlaneRear: {fillOpacity: 0.2, gradient: null}, 295 * yPlaneRear: {fillOpacity: 0.2, gradient: null}, 296 * zPlaneRear: {fillOpacity: 0.2, gradient: null} 297 * } 298 * ); 299 * var a1 = view.create('point3d', [-1, 0, 0]); 300 * var a2 = view.create('point3d', [1, 0, 0]); 301 * 302 * var s1 = view.create( 303 * 'sphere3d', 304 * [a1, 2], 305 * {fillColor: '#00ff80'} 306 * ); 307 * var s2 = view.create( 308 * 'sphere3d', 309 * [a2, 2], 310 * {fillColor: '#ff0000'} 311 * ); 312 * 313 * var i = view.create('intersectioncircle3d', [s1, s2]); 314 * 315 * </pre><div id="JXGdb931076-b29a-4eff-b97e-4251aaf24943" class="jxgbox" style="width: 300px; height: 300px;"></div> 316 * <script type="text/javascript"> 317 * (function() { 318 * var view = board.create( 319 * 'view3d', 320 * [[-6, -3], [8, 8], 321 * [[0, 3], [0, 3], [0, 3]]], 322 * { 323 * xPlaneRear: {fillOpacity: 0.2, gradient: null}, 324 * yPlaneRear: {fillOpacity: 0.2, gradient: null}, 325 * zPlaneRear: {fillOpacity: 0.2, gradient: null} 326 * } 327 * ); 328 * var a1 = view.create('point3d', [-1, 0, 0]); 329 * var a2 = view.create('point3d', [1, 0, 0]); 330 * 331 * var s1 = view.create( 332 * 'sphere3d', 333 * [a1, 2], 334 * {fillColor: '#00ff80'} 335 * ); 336 * var p2 = view.create( 337 * 'sphere3d', 338 * [a2, 2], 339 * {fillColor: '#ff0000'} 340 * ); 341 * 342 * })(); 343 * 344 * </script><pre> 345 * 346 */ 347 JXG.createIntersectionCircle3D = function (board, parents, attributes) { 348 var view = parents[0], 349 el1 = parents[1], 350 el2 = parents[2], 351 ixnCircle, center, func, 352 attr = Type.copyAttributes(attributes, board.options, "intersectioncircle3d"); 353 354 func = Geometry.intersectionFunction3D(view, el1, el2); 355 center = view.create('point3d', func[0], { visible: false }); 356 ixnCircle = view.create('circle3d', [center, func[1], func[2]], attr); 357 358 try { 359 el1.addChild(ixnCircle); 360 el2.addChild(ixnCircle); 361 } catch (e) { 362 throw new Error( 363 "JSXGraph: Can't create 'intersection' with parent types '" + 364 typeof parents[0] + 365 "' and '" + 366 typeof parents[1] + 367 "'." 368 ); 369 } 370 371 ixnCircle.type = Const.OBJECT_TYPE_INTERSECTION_CIRCLE3D; 372 ixnCircle.elType = 'intersectioncircle3d'; 373 ixnCircle.setParents([el1.id, el2.id]); 374 375 return ixnCircle; 376 }; 377 378 JXG.registerElement('intersectioncircle3d', JXG.createIntersectionCircle3D); 379