1 /* 2 Copyright 2008-2022 3 Matthias Ehmann, 4 Carsten Miller, 5 Andreas Walter, 6 Alfred Wassermann 7 8 This file is part of JSXGraph. 9 10 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 11 12 You can redistribute it and/or modify it under the terms of the 13 14 * GNU Lesser General Public License as published by 15 the Free Software Foundation, either version 3 of the License, or 16 (at your option) any later version 17 OR 18 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 19 20 JSXGraph is distributed in the hope that it will be useful, 21 but WITHOUT ANY WARRANTY; without even the implied warranty of 22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 GNU Lesser General Public License for more details. 24 25 You should have received a copy of the GNU Lesser General Public License and 26 the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/> 27 and <http://opensource.org/licenses/MIT/>. 28 */ 29 /*global JXG:true, define: true*/ 30 31 /** 32 * Create linear spaces of dimension at least one, 33 * i.e. lines and planes. 34 */ 35 define(['jxg', 'utils/type', 'math/math', 'math/geometry', '3d/view3d' 36 ], function (JXG, Type, Mat, Geometry, ThreeD) { 37 "use strict"; 38 39 /** 40 * @class This element is used to provide a constructor for a 3D line. 41 * @pseudo 42 * @description There are two possibilities to create a Line3D object. 43 * <p> 44 * First: the line in 3D is defined by two points in 3D (Point3D). 45 * The points can be either existing points or coordinate arrays of 46 * the form [x, y, z]. 47 * <p>Second: the line in 3D is defined by a point (or coordinate array [x, y, z]) 48 * a direction given as array [x, y, z] and an optional range 49 * given as array [s, e]. The default value for the range is [-Infinity, Infinity]. 50 * <p> 51 * All numbers can also be provided as functions returning a number. 52 * 53 * @name Line3D 54 * @augments JXG.Curve 55 * @constructor 56 * @type JXG.Curve 57 * @throws {Exception} If the element cannot be constructed with the given parent 58 * objects an exception is thrown. 59 * @param {JXG.Point_number,JXG.Point,JXG.Line,JXG.Circle} center,radius The center must be given as a {@link JXG.Point}, see {@link JXG.providePoints}, but the radius can be given 60 * as a number (which will create a circle with a fixed radius), another {@link JXG.Point}, a {@link JXG.Line} (the distance of start and end point of the 61 * line will determine the radius), or another {@link JXG.Circle}. 62 * 63 */ 64 ThreeD.createLine = function (board, parents, attributes) { 65 var view = parents[0], 66 attr, D3, point, point1, point2, 67 el; 68 69 // Range 70 D3 = { 71 elType: 'line3d', 72 range: parents[3] || [-Infinity, Infinity] 73 }; 74 75 // Point 76 if (Type.isPoint(parents[1])) { 77 point = parents[1]; 78 } else { 79 point = view.create('point3d', parents[1], { visible: false, name: '', withLabel: false }); 80 } 81 D3.point = point; 82 83 // Direction 84 if (Type.isPoint(parents[2]) && Type.exists(parents[2].D3)) { 85 // Line defined by two points 86 87 point1 = point; 88 point2 = parents[2]; 89 D3.direction = function () { 90 return [ 91 point2.D3.X() - point.D3.X(), 92 point2.D3.Y() - point.D3.Y(), 93 point2.D3.Z() - point.D3.Z() 94 ]; 95 }; 96 D3.range = [0, 1]; 97 } else { 98 // Line defined by point, direction and range 99 100 // Directions are handled as arrays of length 4, 101 // i.e. with homogeneous coordinates. 102 if (Type.isFunction(parents[2])) { 103 D3.direction = parents[2]; 104 } else if (parents[2].length === 3) { 105 D3.direction = [1].concat(parents[2]); 106 } else if (parents[2].length === 4) { 107 D3.direction = parents[2]; 108 } else { 109 // Throw error 110 } 111 112 // Direction given as array 113 D3.getPointCoords = function (r) { 114 var p = [], 115 d = [], 116 i; 117 118 p.push(point.D3.X()); 119 p.push(point.D3.Y()); 120 p.push(point.D3.Z()); 121 122 if (Type.isFunction(D3.direction)) { 123 d = D3.direction(); 124 } else { 125 for (i = 1; i < 4; i++) { 126 d.push(Type.evaluate(D3.direction[i])); 127 } 128 } 129 if (Math.abs(r) === Infinity) { 130 r = view.intersectionLineCube(p, d, r); 131 } 132 return [ 133 p[0] + d[0] * r, 134 p[1] + d[1] * r, 135 p[2] + d[2] * r 136 ]; 137 138 }; 139 140 attr = Type.copyAttributes(attributes, board.options, 'line3d', 'point1'); 141 point1 = view.create('point3d', [ 142 function () { 143 return D3.getPointCoords(Type.evaluate(D3.range[0])); 144 } 145 ], attr); 146 attr = Type.copyAttributes(attributes, board.options, 'line3d', 'point2'); 147 point2 = view.create('point3d', [ 148 function () { 149 return D3.getPointCoords(Type.evaluate(D3.range[1])); 150 } 151 ], attr); 152 } 153 154 attr = Type.copyAttributes(attributes, board.options, 'line3d'); 155 el = view.create('segment', [point1, point2], attr); 156 el.point1 = point1; 157 el.point2 = point2; 158 point1.addChild(el); 159 point2.addChild(el); 160 el.D3 = D3; 161 162 return el; 163 }; 164 JXG.registerElement('line3d', ThreeD.createLine); 165 166 ThreeD.createPlane = function (board, parents, attributes) { 167 var view = parents[0], 168 attr, D3, 169 point, 170 vec1 = parents[2], 171 vec2 = parents[3], 172 el, grid, update; 173 174 // D3: { 175 // point, 176 // vec1, 177 // vec2, 178 // poin1, 179 // point2, 180 // normal array of len 3 181 // d 182 // } 183 D3 = { 184 elType: 'plane3d', 185 dir1: [], 186 dir2: [], 187 range1: parents[4], 188 range2: parents[5], 189 vec1: vec1, 190 vec2: vec2 191 }; 192 193 if (Type.isPoint(parents[1])) { 194 point = parents[1]; 195 } else { 196 point = view.create('point3d', parents[1], { visible: false, name: '', withLabel: false }); 197 } 198 D3.point = point; 199 200 D3.updateNormal = function () { 201 var i; 202 for (i = 0; i < 3; i++) { 203 D3.dir1[i] = Type.evaluate(D3.vec1[i]); 204 D3.dir2[i] = Type.evaluate(D3.vec2[i]); 205 } 206 D3.normal = Mat.crossProduct(D3.dir1, D3.dir2); 207 D3.d = Mat.innerProduct(D3.point.D3.coords.slice(1), D3.normal, 3); 208 }; 209 D3.updateNormal(); 210 211 attr = Type.copyAttributes(attributes, board.options, 'plane3d'); 212 el = board.create('curve', [[], []], attr); 213 el.D3 = D3; 214 215 el.updateDataArray = function () { 216 var s1, e1, s2, e2, 217 c2d, l1, l2, 218 planes = ['xPlaneRear', 'yPlaneRear', 'zPlaneRear'], 219 points = [], 220 v1 = [0, 0, 0], 221 v2 = [0, 0, 0], 222 q = [0, 0, 0], 223 p = [0, 0, 0], d, i, j, a, b, first, pos, pos_akt; 224 225 this.dataX = []; 226 this.dataY = []; 227 228 this.D3.updateNormal(); 229 230 // Infinite plane 231 if (this.D3.elType !== 'axisplane3d' && view.defaultAxes && 232 (!D3.range1 || !D3.range2) 233 ) { 234 235 // Start with the rear plane. 236 // Determine the intersections with the view bbox3d 237 // For each face of the bbox3d we determine two points 238 // which are the ends of the intersection line. 239 // We start with the three rear planes. 240 for (j = 0; j < planes.length; j++) { 241 p = view.intersectionPlanePlane(this, view.defaultAxes[planes[j]]); 242 243 if (p[0].length === 3 && p[1].length === 3) { 244 // This test is necessary to filter out intersection lines which are 245 // identical to intersections of axis planes (they would occur twice). 246 for (i = 0; i < points.length; i++) { 247 if ((Geometry.distance(p[0], points[i][0], 3) < Mat.eps && Geometry.distance(p[1], points[i][1], 3) < Mat.eps) || 248 (Geometry.distance(p[0], points[i][1], 3) < Mat.eps && Geometry.distance(p[1], points[i][0], 3) < Mat.eps)) { 249 break; 250 } 251 } 252 if (i === points.length) { 253 points.push(p.slice()); 254 } 255 } 256 257 // Point on the front plane of the bbox3d 258 p = [0, 0, 0]; 259 p[j] = view.D3.bbox3d[j][1]; 260 261 // d is the rhs of the Hesse normal form of the front plane. 262 d = Mat.innerProduct(p, view.defaultAxes[planes[j]].D3.normal, 3); 263 p = view.intersectionPlanePlane(this, view.defaultAxes[planes[j]], d); 264 265 if (p[0].length === 3 && p[1].length === 3) { 266 // Do the same test as above 267 for (i = 0; i < points.length; i++) { 268 if ((Geometry.distance(p[0], points[i][0], 3) < Mat.eps && Geometry.distance(p[1], points[i][1], 3) < Mat.eps) || 269 (Geometry.distance(p[0], points[i][1], 3) < Mat.eps && Geometry.distance(p[1], points[i][0], 3) < Mat.eps)) { 270 break; 271 } 272 } 273 if (i === points.length) { 274 points.push(p.slice()); 275 } 276 } 277 } 278 279 // Concatenate the intersection points to a polygon. 280 // If all wents well, each intersection should appear 281 // twice in the list. 282 first = 0; 283 pos = first; 284 i = 0; 285 do { 286 p = points[pos][i]; 287 if (p.length === 3) { 288 c2d = view.project3DTo2D(p); 289 this.dataX.push(c2d[1]); 290 this.dataY.push(c2d[2]); 291 } 292 i = (i + 1) % 2; 293 p = points[pos][i]; 294 295 pos_akt = pos; 296 for (j = 0; j < points.length; j++) { 297 if (j !== pos && Geometry.distance(p, points[j][0]) < Mat.eps) { 298 pos = j; 299 i = 0; 300 break; 301 } 302 if (j !== pos && Geometry.distance(p, points[j][1]) < Mat.eps) { 303 pos = j; 304 i = 1; 305 break; 306 } 307 } 308 if (pos === pos_akt) { 309 console.log("Error: update plane3d: did not find next", pos); 310 break; 311 } 312 } while (pos !== first); 313 c2d = view.project3DTo2D(points[first][0]); 314 this.dataX.push(c2d[1]); 315 this.dataY.push(c2d[2]); 316 317 } else { 318 // 3D bounded flat 319 s1 = Type.evaluate(this.D3.range1[0]); 320 e1 = Type.evaluate(this.D3.range1[1]); 321 s2 = Type.evaluate(this.D3.range2[0]); 322 e2 = Type.evaluate(this.D3.range2[1]); 323 324 q = this.D3.point.D3.coords.slice(1); 325 326 v1 = this.D3.dir1.slice(); 327 v2 = this.D3.dir2.slice(); 328 l1 = Mat.norm(v1, 3); 329 l2 = Mat.norm(v2, 3); 330 for (i = 0; i < 3; i++) { 331 v1[i] /= l1; 332 v2[i] /= l2; 333 } 334 335 for (j = 0; j < 4; j++) { 336 switch (j) { 337 case 0: a = s1; b = s2; break; 338 case 1: a = e1; b = s2; break; 339 case 2: a = e1; b = e2; break; 340 case 3: a = s1; b = e2; 341 } 342 for (i = 0; i < 3; i++) { 343 p[i] = q[i] + a * v1[i] + b * v2[i]; 344 } 345 c2d = view.project3DTo2D(p); 346 this.dataX.push(c2d[1]); 347 this.dataY.push(c2d[2]); 348 } 349 // Close the curve 350 this.dataX.push(this.dataX[0]); 351 this.dataY.push(this.dataY[0]); 352 } 353 }; 354 355 attr = Type.copyAttributes(attributes.mesh3d, board.options, 'mesh3d'); 356 357 if (D3.range1 && D3.range2) { 358 grid = view.create('mesh3d', [point.D3.coords.slice(1), vec1, D3.range1, vec2, D3.range2], attr); 359 el.grid = grid; 360 el.inherits.push(grid); 361 } 362 363 // update = el.update; 364 // el.update = function () { 365 // if (el.needsUpdate) { 366 // update.apply(el); 367 // } 368 // return this; 369 // }; 370 371 return el; 372 }; 373 JXG.registerElement('plane3d', ThreeD.createPlane); 374 375 });