jsxgraph 1.9.1 → 1.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/distrib/docs.zip +0 -0
- package/distrib/geogebra.min.js +2 -2
- package/distrib/geonext.min.js +2 -2
- package/distrib/intergeo.min.js +2 -2
- package/distrib/jsxgraph.css +1 -1
- package/distrib/jsxgraph.zip +0 -0
- package/distrib/jsxgraphcore.js +3 -3
- package/distrib/jsxgraphcore.mjs +3 -3
- package/distrib/jsxgraphcore.mjs.map +1 -1
- package/distrib/jsxgraphsrc.js +2328 -1508
- package/distrib/jsxgraphsrc.mjs +2328 -1508
- package/distrib/jsxgraphsrc.mjs.map +1 -1
- package/distrib/sketch.min.js +3 -3
- package/package.json +4 -4
- package/src/3d/box3d.js +4 -1
- package/src/3d/circle3d.js +10 -9
- package/src/3d/curve3d.js +2 -0
- package/src/3d/linspace3d.js +18 -55
- package/src/3d/point3d.js +6 -0
- package/src/3d/polygon3d.js +1 -0
- package/src/3d/sphere3d.js +47 -43
- package/src/3d/surface3d.js +1 -0
- package/src/3d/view3d.js +1504 -1067
- package/src/base/board.js +176 -55
- package/src/base/circle.js +3 -1
- package/src/base/constants.js +1 -1
- package/src/base/curve.js +1 -0
- package/src/base/element.js +38 -30
- package/src/base/line.js +93 -105
- package/src/base/polygon.js +1 -1
- package/src/base/text.js +5 -1
- package/src/element/button.js +62 -12
- package/src/element/checkbox.js +6 -2
- package/src/element/grid.js +6 -6
- package/src/element/input.js +9 -6
- package/src/element/slider.js +42 -39
- package/src/jsxgraph.js +5 -3
- package/src/math/geometry.js +86 -64
- package/src/math/math.js +47 -5
- package/src/options.js +45 -0
- package/src/options3d.js +82 -5
- package/src/parser/jessiecode.js +2 -1
- package/src/parser/prefix.js +8 -1
- package/src/reader/sketch.js +106 -0
- package/src/renderer/svg.js +16 -6
- package/src/utils/env.js +2 -2
- package/src/utils/type.js +27 -15
package/src/3d/view3d.js
CHANGED
|
@@ -71,6 +71,13 @@ JXG.View3D = function (board, parents, attributes) {
|
|
|
71
71
|
*/
|
|
72
72
|
this.objects = {};
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* An array containing all the points in the view.
|
|
76
|
+
* @Type Array
|
|
77
|
+
* @private
|
|
78
|
+
*/
|
|
79
|
+
this.points = this.visProp.depthorderpoints ? [] : null;
|
|
80
|
+
|
|
74
81
|
/**
|
|
75
82
|
* An array containing all geometric objects in this view in the order of construction.
|
|
76
83
|
* @type Array
|
|
@@ -93,6 +100,26 @@ JXG.View3D = function (board, parents, attributes) {
|
|
|
93
100
|
*/
|
|
94
101
|
this.defaultAxes = null;
|
|
95
102
|
|
|
103
|
+
/**
|
|
104
|
+
* The Tait-Bryan angles specifying the view box orientation
|
|
105
|
+
*/
|
|
106
|
+
this.angles = {
|
|
107
|
+
az: null,
|
|
108
|
+
el: null,
|
|
109
|
+
bank: null
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @type {Array}
|
|
114
|
+
* The view box orientation matrix
|
|
115
|
+
*/
|
|
116
|
+
this.matrix3DRot = [
|
|
117
|
+
[1, 0, 0, 0],
|
|
118
|
+
[0, 1, 0, 0],
|
|
119
|
+
[0, 0, 1, 0],
|
|
120
|
+
[0, 0, 0, 1]
|
|
121
|
+
];
|
|
122
|
+
|
|
96
123
|
/**
|
|
97
124
|
* @type {Array}
|
|
98
125
|
* @private
|
|
@@ -105,17 +132,28 @@ JXG.View3D = function (board, parents, attributes) {
|
|
|
105
132
|
];
|
|
106
133
|
|
|
107
134
|
/**
|
|
108
|
-
* The
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
135
|
+
* The 4×4 matrix that maps box coordinates to camera coordinates. These
|
|
136
|
+
* coordinate systems fit into the View3D coordinate atlas as follows.
|
|
137
|
+
* <ul>
|
|
138
|
+
* <li><b>World coordinates.</b> The coordinates used to specify object
|
|
139
|
+
* positions in a JSXGraph scene.</li>
|
|
140
|
+
* <li><b>Box coordinates.</b> The world coordinates translated to put the
|
|
141
|
+
* center of the view box at the origin.
|
|
142
|
+
* <li><b>Camera coordinates.</b> The coordinate system where the
|
|
143
|
+
* <code>x</code>, <code>y</code> plane is the screen, the origin is the
|
|
144
|
+
* center of the screen, and the <code>z</code> axis points out of the
|
|
145
|
+
* screen, toward the viewer.
|
|
146
|
+
* <li><b>Focal coordinates.</b> The camera coordinates translated to put
|
|
147
|
+
* the origin at the focal point, which is set back from the screen by the
|
|
148
|
+
* focal distance.</li>
|
|
149
|
+
* </ul>
|
|
150
|
+
* The <code>boxToCam</code> transformation is exposed to help 3D elements
|
|
151
|
+
* manage their 2D representations in central projection mode. To map world
|
|
152
|
+
* coordinates to focal coordinates, use the
|
|
153
|
+
* {@link JXG.View3D#worldToFocal} method.
|
|
116
154
|
* @type {Array}
|
|
117
155
|
*/
|
|
118
|
-
this.
|
|
156
|
+
this.boxToCam = [];
|
|
119
157
|
|
|
120
158
|
/**
|
|
121
159
|
* @type array
|
|
@@ -158,6 +196,12 @@ JXG.View3D = function (board, parents, attributes) {
|
|
|
158
196
|
// Will be set in update().
|
|
159
197
|
this.projectionType = 'parallel';
|
|
160
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Whether trackball navigation is currently enabled.
|
|
201
|
+
* @type String
|
|
202
|
+
*/
|
|
203
|
+
this.trackballEnabled = false;
|
|
204
|
+
|
|
161
205
|
this.timeoutAzimuth = null;
|
|
162
206
|
|
|
163
207
|
this.id = this.board.setId(this, 'V');
|
|
@@ -173,1188 +217,1539 @@ JXG.View3D.prototype = new GeometryElement();
|
|
|
173
217
|
JXG.extend(
|
|
174
218
|
JXG.View3D.prototype, /** @lends JXG.View3D.prototype */ {
|
|
175
219
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
220
|
+
/**
|
|
221
|
+
* Creates a new 3D element of type elementType.
|
|
222
|
+
* @param {String} elementType Type of the element to be constructed given as a string e.g. 'point3d' or 'surface3d'.
|
|
223
|
+
* @param {Array} parents Array of parent elements needed to construct the element e.g. coordinates for a 3D point or two
|
|
224
|
+
* 3D points to construct a line. This highly depends on the elementType that is constructed. See the corresponding JXG.create*
|
|
225
|
+
* methods for a list of possible parameters.
|
|
226
|
+
* @param {Object} [attributes] An object containing the attributes to be set. This also depends on the elementType.
|
|
227
|
+
* Common attributes are name, visible, strokeColor.
|
|
228
|
+
* @returns {Object} Reference to the created element. This is usually a GeometryElement3D, but can be an array containing
|
|
229
|
+
* two or more elements.
|
|
230
|
+
*/
|
|
231
|
+
create: function (elementType, parents, attributes) {
|
|
232
|
+
var prefix = [],
|
|
233
|
+
el;
|
|
234
|
+
|
|
235
|
+
if (elementType.indexOf('3d') > 0) {
|
|
236
|
+
// is3D = true;
|
|
237
|
+
prefix.push(this);
|
|
238
|
+
}
|
|
239
|
+
el = this.board.create(elementType, prefix.concat(parents), attributes);
|
|
190
240
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
241
|
+
return el;
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Select a single or multiple elements at once.
|
|
246
|
+
* @param {String|Object|function} str The name, id or a reference to a JSXGraph 3D element in the 3D view. An object will
|
|
247
|
+
* be used as a filter to return multiple elements at once filtered by the properties of the object.
|
|
248
|
+
* @param {Boolean} onlyByIdOrName If true (default:false) elements are only filtered by their id, name or groupId.
|
|
249
|
+
* The advanced filters consisting of objects or functions are ignored.
|
|
250
|
+
* @returns {JXG.GeometryElement3D|JXG.Composition}
|
|
251
|
+
* @example
|
|
252
|
+
* // select the element with name A
|
|
253
|
+
* view.select('A');
|
|
254
|
+
*
|
|
255
|
+
* // select all elements with strokecolor set to 'red' (but not '#ff0000')
|
|
256
|
+
* view.select({
|
|
257
|
+
* strokeColor: 'red'
|
|
258
|
+
* });
|
|
259
|
+
*
|
|
260
|
+
* // select all points on or below the x/y plane and make them black.
|
|
261
|
+
* view.select({
|
|
262
|
+
* elType: 'point3d',
|
|
263
|
+
* Z: function (v) {
|
|
264
|
+
* return v <= 0;
|
|
265
|
+
* }
|
|
266
|
+
* }).setAttribute({color: 'black'});
|
|
267
|
+
*
|
|
268
|
+
* // select all elements
|
|
269
|
+
* view.select(function (el) {
|
|
270
|
+
* return true;
|
|
271
|
+
* });
|
|
272
|
+
*/
|
|
273
|
+
select: function (str, onlyByIdOrName) {
|
|
274
|
+
var flist,
|
|
275
|
+
olist,
|
|
276
|
+
i,
|
|
277
|
+
l,
|
|
278
|
+
s = str;
|
|
279
|
+
|
|
280
|
+
if (s === null) {
|
|
281
|
+
return s;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// It's a string, most likely an id or a name.
|
|
285
|
+
if (Type.isString(s) && s !== '') {
|
|
286
|
+
// Search by ID
|
|
287
|
+
if (Type.exists(this.objects[s])) {
|
|
288
|
+
s = this.objects[s];
|
|
289
|
+
// Search by name
|
|
290
|
+
} else if (Type.exists(this.elementsByName[s])) {
|
|
291
|
+
s = this.elementsByName[s];
|
|
292
|
+
// // Search by group ID
|
|
293
|
+
// } else if (Type.exists(this.groups[s])) {
|
|
294
|
+
// s = this.groups[s];
|
|
194
295
|
}
|
|
195
|
-
el = this.board.create(elementType, prefix.concat(parents), attributes);
|
|
196
296
|
|
|
197
|
-
|
|
198
|
-
}
|
|
297
|
+
// It's a function or an object, but not an element
|
|
298
|
+
} else if (
|
|
299
|
+
!onlyByIdOrName &&
|
|
300
|
+
(Type.isFunction(s) || (Type.isObject(s) && !Type.isFunction(s.setAttribute)))
|
|
301
|
+
) {
|
|
302
|
+
flist = Type.filterElements(this.objectsList, s);
|
|
199
303
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
* @param {Boolean} onlyByIdOrName If true (default:false) elements are only filtered by their id, name or groupId.
|
|
205
|
-
* The advanced filters consisting of objects or functions are ignored.
|
|
206
|
-
* @returns {JXG.GeometryElement3D|JXG.Composition}
|
|
207
|
-
* @example
|
|
208
|
-
* // select the element with name A
|
|
209
|
-
* view.select('A');
|
|
210
|
-
*
|
|
211
|
-
* // select all elements with strokecolor set to 'red' (but not '#ff0000')
|
|
212
|
-
* view.select({
|
|
213
|
-
* strokeColor: 'red'
|
|
214
|
-
* });
|
|
215
|
-
*
|
|
216
|
-
* // select all points on or below the x/y plane and make them black.
|
|
217
|
-
* view.select({
|
|
218
|
-
* elType: 'point3d',
|
|
219
|
-
* Z: function (v) {
|
|
220
|
-
* return v <= 0;
|
|
221
|
-
* }
|
|
222
|
-
* }).setAttribute({color: 'black'});
|
|
223
|
-
*
|
|
224
|
-
* // select all elements
|
|
225
|
-
* view.select(function (el) {
|
|
226
|
-
* return true;
|
|
227
|
-
* });
|
|
228
|
-
*/
|
|
229
|
-
select: function (str, onlyByIdOrName) {
|
|
230
|
-
var flist,
|
|
231
|
-
olist,
|
|
232
|
-
i,
|
|
233
|
-
l,
|
|
234
|
-
s = str;
|
|
235
|
-
|
|
236
|
-
if (s === null) {
|
|
237
|
-
return s;
|
|
304
|
+
olist = {};
|
|
305
|
+
l = flist.length;
|
|
306
|
+
for (i = 0; i < l; i++) {
|
|
307
|
+
olist[flist[i].id] = flist[i];
|
|
238
308
|
}
|
|
309
|
+
s = new Composition(olist);
|
|
239
310
|
|
|
240
|
-
// It's
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
// // Search by group ID
|
|
249
|
-
// } else if (Type.exists(this.groups[s])) {
|
|
250
|
-
// s = this.groups[s];
|
|
251
|
-
}
|
|
311
|
+
// It's an element which has been deleted (and still hangs around, e.g. in an attractor list
|
|
312
|
+
} else if (
|
|
313
|
+
Type.isObject(s) &&
|
|
314
|
+
Type.exists(s.id) &&
|
|
315
|
+
!Type.exists(this.objects[s.id])
|
|
316
|
+
) {
|
|
317
|
+
s = null;
|
|
318
|
+
}
|
|
252
319
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
320
|
+
return s;
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
// set the Tait-Bryan angles to specify the current view rotation matrix
|
|
324
|
+
setAnglesFromRotation: function () {
|
|
325
|
+
var rem = this.matrix3DRot, // rotation remaining after angle extraction
|
|
326
|
+
rBank, cosBank, sinBank,
|
|
327
|
+
cosEl, sinEl,
|
|
328
|
+
cosAz, sinAz;
|
|
329
|
+
|
|
330
|
+
// extract bank by rotating the view box z axis onto the camera yz plane
|
|
331
|
+
rBank = Math.sqrt(rem[1][3] * rem[1][3] + rem[2][3] * rem[2][3]);
|
|
332
|
+
if (rBank > Mat.eps) {
|
|
333
|
+
cosBank = rem[2][3] / rBank;
|
|
334
|
+
sinBank = rem[1][3] / rBank;
|
|
335
|
+
} else {
|
|
336
|
+
// if the z axis is pointed almost exactly at the screen, we
|
|
337
|
+
// keep the current bank value
|
|
338
|
+
cosBank = Math.cos(this.angles.bank);
|
|
339
|
+
sinBank = Math.sin(this.angles.bank);
|
|
340
|
+
}
|
|
341
|
+
rem = Mat.matMatMult([
|
|
342
|
+
[1, 0, 0, 0],
|
|
343
|
+
[0, cosBank, -sinBank, 0],
|
|
344
|
+
[0, sinBank, cosBank, 0],
|
|
345
|
+
[0, 0, 0, 1]
|
|
346
|
+
], rem);
|
|
347
|
+
this.angles.bank = Math.atan2(sinBank, cosBank);
|
|
348
|
+
|
|
349
|
+
// extract elevation by rotating the view box z axis onto the camera
|
|
350
|
+
// y axis
|
|
351
|
+
cosEl = rem[2][3];
|
|
352
|
+
sinEl = rem[3][3];
|
|
353
|
+
rem = Mat.matMatMult([
|
|
354
|
+
[1, 0, 0, 0],
|
|
355
|
+
[0, 1, 0, 0],
|
|
356
|
+
[0, 0, cosEl, sinEl],
|
|
357
|
+
[0, 0, -sinEl, cosEl]
|
|
358
|
+
], rem);
|
|
359
|
+
this.angles.el = Math.atan2(sinEl, cosEl);
|
|
360
|
+
|
|
361
|
+
// extract azimuth
|
|
362
|
+
cosAz = -rem[1][1];
|
|
363
|
+
sinAz = rem[3][1];
|
|
364
|
+
this.angles.az = Math.atan2(sinAz, cosAz);
|
|
365
|
+
if (this.angles.az < 0) this.angles.az += 2 * Math.PI;
|
|
366
|
+
|
|
367
|
+
this.setSlidersFromAngles();
|
|
368
|
+
},
|
|
369
|
+
|
|
370
|
+
anglesHaveMoved: function () {
|
|
371
|
+
return (
|
|
372
|
+
this._hasMoveAz || this._hasMoveEl ||
|
|
373
|
+
Math.abs(this.angles.az - this.az_slide.Value()) > Mat.eps ||
|
|
374
|
+
Math.abs(this.angles.el - this.el_slide.Value()) > Mat.eps ||
|
|
375
|
+
Math.abs(this.angles.bank - this.bank_slide.Value()) > Mat.eps
|
|
376
|
+
);
|
|
377
|
+
},
|
|
378
|
+
|
|
379
|
+
getAnglesFromSliders: function () {
|
|
380
|
+
this.angles.az = this.az_slide.Value();
|
|
381
|
+
this.angles.el = this.el_slide.Value();
|
|
382
|
+
this.angles.bank = this.bank_slide.Value();
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
setSlidersFromAngles: function () {
|
|
386
|
+
this.az_slide.setValue(this.angles.az);
|
|
387
|
+
this.el_slide.setValue(this.angles.el);
|
|
388
|
+
this.bank_slide.setValue(this.angles.bank);
|
|
389
|
+
},
|
|
390
|
+
|
|
391
|
+
// return the rotation matrix specified by the current Tait-Bryan angles
|
|
392
|
+
getRotationFromAngles: function () {
|
|
393
|
+
var a, e, b, f,
|
|
394
|
+
cosBank, sinBank,
|
|
395
|
+
mat = [
|
|
396
|
+
[1, 0, 0, 0],
|
|
397
|
+
[0, 1, 0, 0],
|
|
398
|
+
[0, 0, 1, 0],
|
|
399
|
+
[0, 0, 0, 1]
|
|
400
|
+
];
|
|
259
401
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
402
|
+
// mat projects homogeneous 3D coords in View3D
|
|
403
|
+
// to homogeneous 2D coordinates in the board
|
|
404
|
+
a = this.angles.az;
|
|
405
|
+
e = this.angles.el;
|
|
406
|
+
b = this.angles.bank;
|
|
407
|
+
f = -Math.sin(e);
|
|
408
|
+
|
|
409
|
+
mat[1][1] = -Math.cos(a);
|
|
410
|
+
mat[1][2] = Math.sin(a);
|
|
411
|
+
mat[1][3] = 0;
|
|
412
|
+
|
|
413
|
+
mat[2][1] = f * Math.sin(a);
|
|
414
|
+
mat[2][2] = f * Math.cos(a);
|
|
415
|
+
mat[2][3] = Math.cos(e);
|
|
416
|
+
|
|
417
|
+
mat[3][1] = Math.cos(e) * Math.sin(a);
|
|
418
|
+
mat[3][2] = Math.cos(e) * Math.cos(a);
|
|
419
|
+
mat[3][3] = Math.sin(e);
|
|
420
|
+
|
|
421
|
+
cosBank = Math.cos(b);
|
|
422
|
+
sinBank = Math.sin(b);
|
|
423
|
+
mat = Mat.matMatMult([
|
|
424
|
+
[1, 0, 0, 0],
|
|
425
|
+
[0, cosBank, sinBank, 0],
|
|
426
|
+
[0, -sinBank, cosBank, 0],
|
|
427
|
+
[0, 0, 0, 1]
|
|
428
|
+
], mat);
|
|
429
|
+
|
|
430
|
+
return mat;
|
|
431
|
+
|
|
432
|
+
/* this code, originally from `_updateCentralProjection`, is an
|
|
433
|
+
* alternate implementation of the azimuth-elevation matrix
|
|
434
|
+
* computation above. using this implementation instead of the
|
|
435
|
+
* current one might lead to simpler code in a future refactoring
|
|
436
|
+
var a, e, up,
|
|
437
|
+
ax, ay, az, v, nrm,
|
|
438
|
+
eye, d,
|
|
439
|
+
func_sphere;
|
|
440
|
+
|
|
441
|
+
// finds the point on the unit sphere with the given azimuth and
|
|
442
|
+
// elevation, and returns its affine coordinates
|
|
443
|
+
func_sphere = function (az, el) {
|
|
444
|
+
return [
|
|
445
|
+
Math.cos(az) * Math.cos(el),
|
|
446
|
+
-Math.sin(az) * Math.cos(el),
|
|
447
|
+
Math.sin(el)
|
|
448
|
+
];
|
|
449
|
+
};
|
|
266
450
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
Type.isObject(s) &&
|
|
270
|
-
Type.exists(s.id) &&
|
|
271
|
-
!Type.exists(this.objects[s.id])
|
|
272
|
-
) {
|
|
273
|
-
s = null;
|
|
274
|
-
}
|
|
451
|
+
a = this.az_slide.Value() + (3 * Math.PI * 0.5); // Sphere
|
|
452
|
+
e = this.el_slide.Value();
|
|
275
453
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
var r, a, e, f,
|
|
281
|
-
mat = [
|
|
282
|
-
[1, 0, 0, 0],
|
|
283
|
-
[0, 1, 0, 0],
|
|
284
|
-
[0, 0, 1, 0],
|
|
285
|
-
[0, 0, 0, 1]
|
|
286
|
-
];
|
|
287
|
-
|
|
288
|
-
// mat projects homogeneous 3D coords in View3D
|
|
289
|
-
// to homogeneous 2D coordinates in the board
|
|
290
|
-
e = this.el_slide.Value();
|
|
291
|
-
r = this.r;
|
|
292
|
-
a = this.az_slide.Value();
|
|
293
|
-
f = r * Math.sin(e);
|
|
294
|
-
|
|
295
|
-
mat[1][1] = r * Math.cos(a);
|
|
296
|
-
mat[1][2] = -r * Math.sin(a);
|
|
297
|
-
mat[1][3] = 0;
|
|
298
|
-
|
|
299
|
-
mat[2][1] = f * Math.sin(a);
|
|
300
|
-
mat[2][2] = f * Math.cos(a);
|
|
301
|
-
mat[2][3] = Math.cos(e);
|
|
302
|
-
|
|
303
|
-
mat[3][1] = r * Math.cos(e) * Math.sin(a);
|
|
304
|
-
mat[3][2] = r * Math.cos(e) * Math.cos(a);
|
|
305
|
-
mat[3][3] = -Math.sin(e);
|
|
306
|
-
|
|
307
|
-
return mat;
|
|
308
|
-
},
|
|
454
|
+
// create an up vector and an eye vector which are 90 degrees out of phase
|
|
455
|
+
up = func_sphere(a, e + Math.PI / 2);
|
|
456
|
+
eye = func_sphere(a, e);
|
|
457
|
+
d = [eye[0], eye[1], eye[2]];
|
|
309
458
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
* see Bell's virtual trackpad, and return z-component of the
|
|
313
|
-
* number.
|
|
314
|
-
*
|
|
315
|
-
* @param {Number} r
|
|
316
|
-
* @param {Number} x
|
|
317
|
-
* @param {Number} y
|
|
318
|
-
* @returns Number
|
|
319
|
-
* @private
|
|
320
|
-
*/
|
|
321
|
-
_projectToSphere: function(r, x, y) {
|
|
322
|
-
var d = Mat.hypot(x, y),
|
|
323
|
-
t, z;
|
|
324
|
-
|
|
325
|
-
if (d < r * 0.7071067811865475) { // Inside sphere
|
|
326
|
-
z = Math.sqrt(r * r - d * d);
|
|
327
|
-
} else { // On hyperbola
|
|
328
|
-
t = r / 1.414213562373095;
|
|
329
|
-
z = t * t / d;
|
|
330
|
-
}
|
|
331
|
-
return z;
|
|
332
|
-
},
|
|
459
|
+
nrm = Mat.norm(d, 3);
|
|
460
|
+
az = [d[0] / nrm, d[1] / nrm, d[2] / nrm];
|
|
333
461
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
462
|
+
nrm = Mat.norm(up, 3);
|
|
463
|
+
v = [up[0] / nrm, up[1] / nrm, up[2] / nrm];
|
|
464
|
+
|
|
465
|
+
ax = Mat.crossProduct(v, az);
|
|
466
|
+
ay = Mat.crossProduct(az, ax);
|
|
467
|
+
|
|
468
|
+
this.matrix3DRot[1] = [0, ax[0], ax[1], ax[2]];
|
|
469
|
+
this.matrix3DRot[2] = [0, ay[0], ay[1], ay[2]];
|
|
470
|
+
this.matrix3DRot[3] = [0, az[0], az[1], az[2]];
|
|
339
471
|
*/
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
472
|
+
},
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Project 2D point (x,y) to the virtual trackpad sphere,
|
|
476
|
+
* see Bell's virtual trackpad, and return z-component of the
|
|
477
|
+
* number.
|
|
478
|
+
*
|
|
479
|
+
* @param {Number} r
|
|
480
|
+
* @param {Number} x
|
|
481
|
+
* @param {Number} y
|
|
482
|
+
* @returns Number
|
|
483
|
+
* @private
|
|
484
|
+
*/
|
|
485
|
+
_projectToSphere: function (r, x, y) {
|
|
486
|
+
var d = Mat.hypot(x, y),
|
|
487
|
+
t, z;
|
|
488
|
+
|
|
489
|
+
if (d < r * 0.7071067811865475) { // Inside sphere
|
|
490
|
+
z = Math.sqrt(r * r - d * d);
|
|
491
|
+
} else { // On hyperbola
|
|
492
|
+
t = r / 1.414213562373095;
|
|
493
|
+
z = t * t / d;
|
|
494
|
+
}
|
|
495
|
+
return z;
|
|
496
|
+
},
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Determine 4x4 rotation matrix with Bell's virtual trackball.
|
|
500
|
+
*
|
|
501
|
+
* @returns {Array} 4x4 rotation matrix
|
|
502
|
+
* @private
|
|
503
|
+
*/
|
|
504
|
+
updateProjectionTrackball: function (Pref) {
|
|
505
|
+
var R = 100,
|
|
506
|
+
dx, dy, dr2,
|
|
507
|
+
p1, p2, x, y, theta, t, d,
|
|
508
|
+
c, s, n,
|
|
509
|
+
mat = [
|
|
510
|
+
[1, 0, 0, 0],
|
|
511
|
+
[0, 1, 0, 0],
|
|
512
|
+
[0, 0, 1, 0],
|
|
513
|
+
[0, 0, 0, 1]
|
|
514
|
+
];
|
|
515
|
+
|
|
516
|
+
if (!Type.exists(this._trackball)) {
|
|
517
|
+
return this.matrix3DRot;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
dx = this._trackball.dx;
|
|
521
|
+
dy = this._trackball.dy;
|
|
522
|
+
dr2 = dx * dx + dy * dy;
|
|
523
|
+
if (dr2 > Mat.eps) {
|
|
524
|
+
// // Method by Hanson, "The rolling ball", Graphics Gems III, p.51
|
|
525
|
+
// // Rotation axis:
|
|
526
|
+
// // n = (-dy/dr, dx/dr, 0)
|
|
527
|
+
// // Rotation angle around n:
|
|
528
|
+
// // theta = atan(dr / R) approx dr / R
|
|
529
|
+
// dr = Math.sqrt(dr2);
|
|
530
|
+
// c = R / Math.hypot(R, dr); // cos(theta)
|
|
531
|
+
// t = 1 - c; // 1 - cos(theta)
|
|
532
|
+
// s = dr / Math.hypot(R, dr); // sin(theta)
|
|
533
|
+
// n = [-dy / dr, dx / dr, 0];
|
|
534
|
+
|
|
535
|
+
// Bell virtual trackpad, see
|
|
536
|
+
// https://opensource.apple.com/source/X11libs/X11libs-60/mesa/Mesa-7.8.2/progs/util/trackball.c.auto.html
|
|
537
|
+
// http://scv.bu.edu/documentation/presentations/visualizationworkshop08/materials/opengl/trackball.c.
|
|
538
|
+
// See also Henriksen, Sporring, Hornaek, "Virtual Trackballs revisited".
|
|
539
|
+
//
|
|
540
|
+
R = (this.size[0] * this.board.unitX + this.size[1] * this.board.unitY) * 0.25;
|
|
541
|
+
x = this._trackball.x;
|
|
542
|
+
y = this._trackball.y;
|
|
543
|
+
|
|
544
|
+
p2 = [x, y, this._projectToSphere(R, x, y)];
|
|
545
|
+
x -= dx;
|
|
546
|
+
y -= dy;
|
|
547
|
+
p1 = [x, y, this._projectToSphere(R, x, y)];
|
|
548
|
+
|
|
549
|
+
n = Mat.crossProduct(p1, p2);
|
|
550
|
+
d = Mat.hypot(n[0], n[1], n[2]);
|
|
551
|
+
n[0] /= d;
|
|
552
|
+
n[1] /= d;
|
|
553
|
+
n[2] /= d;
|
|
554
|
+
|
|
555
|
+
t = Geometry.distance(p2, p1, 3) / (2 * R);
|
|
556
|
+
t = (t > 1.0) ? 1.0 : t;
|
|
557
|
+
t = (t < -1.0) ? -1.0 : t;
|
|
558
|
+
theta = 2.0 * Math.asin(t);
|
|
559
|
+
c = Math.cos(theta);
|
|
560
|
+
t = 1 - c;
|
|
561
|
+
s = Math.sin(theta);
|
|
562
|
+
|
|
563
|
+
// Rotation by theta about the axis n. See equation 9.63 of
|
|
564
|
+
//
|
|
565
|
+
// Ian Richard Cole. "Modeling CPV" (thesis). Loughborough
|
|
566
|
+
// University. https://hdl.handle.net/2134/18050
|
|
567
|
+
//
|
|
568
|
+
mat[1][1] = c + n[0] * n[0] * t;
|
|
569
|
+
mat[2][1] = n[1] * n[0] * t + n[2] * s;
|
|
570
|
+
mat[3][1] = n[2] * n[0] * t - n[1] * s;
|
|
571
|
+
|
|
572
|
+
mat[1][2] = n[0] * n[1] * t - n[2] * s;
|
|
573
|
+
mat[2][2] = c + n[1] * n[1] * t;
|
|
574
|
+
mat[3][2] = n[2] * n[1] * t + n[0] * s;
|
|
575
|
+
|
|
576
|
+
mat[1][3] = n[0] * n[2] * t + n[1] * s;
|
|
577
|
+
mat[2][3] = n[1] * n[2] * t - n[0] * s;
|
|
578
|
+
mat[3][3] = c + n[2] * n[2] * t;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
mat = Mat.matMatMult(mat, this.matrix3DRot);
|
|
582
|
+
return mat;
|
|
583
|
+
},
|
|
584
|
+
|
|
585
|
+
updateAngleSliderBounds: function () {
|
|
586
|
+
var az_smax, az_smin,
|
|
587
|
+
el_smax, el_smin, el_cover,
|
|
588
|
+
el_smid, el_equiv, el_flip_equiv,
|
|
589
|
+
el_equiv_loss, el_flip_equiv_loss, el_interval_loss,
|
|
590
|
+
bank_smax, bank_smin;
|
|
591
|
+
|
|
592
|
+
// update stored trackball toggle
|
|
593
|
+
this.trackballEnabled = Type.evaluate(this.visProp.trackball.enabled);
|
|
594
|
+
|
|
595
|
+
// set slider bounds
|
|
596
|
+
if (this.trackballEnabled) {
|
|
597
|
+
this.az_slide.setMin(0);
|
|
598
|
+
this.az_slide.setMax(2 * Math.PI);
|
|
599
|
+
this.el_slide.setMin(-0.5 * Math.PI);
|
|
600
|
+
this.el_slide.setMax(0.5 * Math.PI);
|
|
601
|
+
this.bank_slide.setMin(-Math.PI);
|
|
602
|
+
this.bank_slide.setMax(Math.PI);
|
|
603
|
+
} else {
|
|
604
|
+
this.az_slide.setMin(this.visProp.az.slider.min);
|
|
605
|
+
this.az_slide.setMax(this.visProp.az.slider.max);
|
|
606
|
+
this.el_slide.setMin(this.visProp.el.slider.min);
|
|
607
|
+
this.el_slide.setMax(this.visProp.el.slider.max);
|
|
608
|
+
this.bank_slide.setMin(this.visProp.bank.slider.min);
|
|
609
|
+
this.bank_slide.setMax(this.visProp.bank.slider.max);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// get new slider bounds
|
|
613
|
+
az_smax = this.az_slide._smax;
|
|
614
|
+
az_smin = this.az_slide._smin;
|
|
615
|
+
el_smax = this.el_slide._smax;
|
|
616
|
+
el_smin = this.el_slide._smin;
|
|
617
|
+
bank_smax = this.bank_slide._smax;
|
|
618
|
+
bank_smin = this.bank_slide._smin;
|
|
619
|
+
|
|
620
|
+
// wrap and restore angle values
|
|
621
|
+
if (this.trackballEnabled) {
|
|
622
|
+
// if we're upside-down, flip the bank angle to reach the same
|
|
623
|
+
// orientation with an elevation between -pi/2 and pi/2
|
|
624
|
+
el_cover = Mat.mod(this.angles.el, 2 * Math.PI);
|
|
625
|
+
if (0.5 * Math.PI < el_cover && el_cover < 1.5 * Math.PI) {
|
|
626
|
+
this.angles.el = Math.PI - el_cover;
|
|
627
|
+
this.angles.az = Mat.wrap(this.angles.az + Math.PI, az_smin, az_smax);
|
|
628
|
+
this.angles.bank = Mat.wrap(this.angles.bank + Math.PI, bank_smin, bank_smax);
|
|
354
629
|
}
|
|
355
630
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
// Bell virtual trackpad, see
|
|
372
|
-
// https://opensource.apple.com/source/X11libs/X11libs-60/mesa/Mesa-7.8.2/progs/util/trackball.c.auto.html
|
|
373
|
-
// http://scv.bu.edu/documentation/presentations/visualizationworkshop08/materials/opengl/trackball.c.
|
|
374
|
-
// See also Henriksen, Sporring, Hornaek, "Virtual Trackballs revisited".
|
|
375
|
-
//
|
|
376
|
-
R = (this.size[0] * this.board.unitX + this.size[1] * this.board.unitY) * 0.25;
|
|
377
|
-
x = this._trackball.x;
|
|
378
|
-
y = this._trackball.y;
|
|
379
|
-
|
|
380
|
-
// p2 = [x, y, this._projectToSphere(R, x, y)];
|
|
381
|
-
p2 = [this._projectToSphere(R, x, y), x, y];
|
|
382
|
-
x -= dx;
|
|
383
|
-
y -= dy;
|
|
384
|
-
// p1 = [x, y, this._projectToSphere(R, x, y)];
|
|
385
|
-
p1 = [this._projectToSphere(R, x, y), x, y];
|
|
386
|
-
|
|
387
|
-
n = Mat.crossProduct(p2, p1);
|
|
388
|
-
d = Mat.hypot(n[0], n[1], n[2]);
|
|
389
|
-
n[0] /= -d;
|
|
390
|
-
n[1] /= -d;
|
|
391
|
-
n[2] /= d;
|
|
392
|
-
|
|
393
|
-
t = Geometry.distance(p2, p1, 3) / (2 * R);
|
|
394
|
-
t = (t > 1.0) ? 1.0 : t;
|
|
395
|
-
t = (t < -1.0) ? -1.0 : t;
|
|
396
|
-
theta = 2.0 * Math.asin(t);
|
|
397
|
-
c = Math.cos(theta);
|
|
398
|
-
t = 1 - c;
|
|
399
|
-
s = Math.sin(theta);
|
|
400
|
-
|
|
401
|
-
// General rotation around axis n with angle theta
|
|
402
|
-
// See Pique, Graphics Gems I, pp. 466
|
|
403
|
-
// Attention: he multiplies (row vector x matrix),
|
|
404
|
-
// we multiply (matrix x column vector)
|
|
405
|
-
mat[1][1] = c + n[0] * n[0] * t;
|
|
406
|
-
mat[2][1] = n[0] * n[1] * t - n[2] * s;
|
|
407
|
-
mat[3][1] = n[0] * n[2] * t + n[1] * s;
|
|
408
|
-
|
|
409
|
-
mat[1][2] = n[1] * n[0] * t + n[2] * s;
|
|
410
|
-
mat[2][2] = c + n[1] * n[1] * t;
|
|
411
|
-
mat[3][2] = n[1] * n[2] * t - n[0] * s;
|
|
412
|
-
|
|
413
|
-
mat[1][3] = n[2] * n[0] * t - n[1] * s;
|
|
414
|
-
mat[2][3] = n[2] * n[1] * t + n[0] * s;
|
|
415
|
-
mat[3][3] = c + n[2] * n[2] * t;
|
|
416
|
-
|
|
417
|
-
if (Pref !== null) {
|
|
418
|
-
// For central projection we have to rotate around Pref.
|
|
419
|
-
// Parallel projection: Pref === null
|
|
420
|
-
mat[1][0] = Pref[0] - mat[1][1] * Pref[0] - mat[1][2] * Pref[0] - mat[1][3] * Pref[0];
|
|
421
|
-
mat[2][0] = Pref[1] - mat[2][1] * Pref[1] - mat[2][2] * Pref[1] - mat[2][3] * Pref[1];
|
|
422
|
-
mat[3][0] = Pref[2] - mat[3][1] * Pref[2] - mat[3][2] * Pref[2] - mat[3][3] * Pref[2];
|
|
631
|
+
// wrap the azimuth and bank angle
|
|
632
|
+
this.angles.az = Mat.wrap(this.angles.az, az_smin, az_smax);
|
|
633
|
+
this.angles.el = Mat.wrap(this.angles.el, el_smin, el_smax);
|
|
634
|
+
this.angles.bank = Mat.wrap(this.angles.bank, bank_smin, bank_smax);
|
|
635
|
+
} else {
|
|
636
|
+
// wrap and clamp the elevation into the slider range. if
|
|
637
|
+
// flipping the elevation gets us closer to the slider interval,
|
|
638
|
+
// do that, inverting the azimuth and bank angle to compensate
|
|
639
|
+
el_interval_loss = function (t) {
|
|
640
|
+
if (t < el_smin) {
|
|
641
|
+
return el_smin - t;
|
|
642
|
+
} else if (el_smax < t) {
|
|
643
|
+
return t - el_smax;
|
|
644
|
+
} else {
|
|
645
|
+
return 0;
|
|
423
646
|
}
|
|
647
|
+
};
|
|
648
|
+
el_smid = 0.5 * (el_smin + el_smax);
|
|
649
|
+
el_equiv = Mat.wrap(
|
|
650
|
+
this.angles.el,
|
|
651
|
+
el_smid - Math.PI,
|
|
652
|
+
el_smid + Math.PI
|
|
653
|
+
);
|
|
654
|
+
el_flip_equiv = Mat.wrap(
|
|
655
|
+
Math.PI - this.angles.el,
|
|
656
|
+
el_smid - Math.PI,
|
|
657
|
+
el_smid + Math.PI
|
|
658
|
+
);
|
|
659
|
+
el_equiv_loss = el_interval_loss(el_equiv);
|
|
660
|
+
el_flip_equiv_loss = el_interval_loss(el_flip_equiv);
|
|
661
|
+
if (el_equiv_loss <= el_flip_equiv_loss) {
|
|
662
|
+
this.angles.el = Mat.clamp(el_equiv, el_smin, el_smax);
|
|
663
|
+
} else {
|
|
664
|
+
this.angles.el = Mat.clamp(el_flip_equiv, el_smin, el_smax);
|
|
665
|
+
this.angles.az = Mat.wrap(this.angles.az + Math.PI, az_smin, az_smax);
|
|
666
|
+
this.angles.bank = Mat.wrap(this.angles.bank + Math.PI, bank_smin, bank_smax);
|
|
424
667
|
}
|
|
425
668
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
/**
|
|
431
|
-
* @private
|
|
432
|
-
* @returns {Array}
|
|
433
|
-
*/
|
|
434
|
-
_updateCentralProjection: function () {
|
|
435
|
-
var r, e, a, up,
|
|
436
|
-
az, ax, ay, v, nrm,
|
|
437
|
-
// See https://www.mathematik.uni-marburg.de/~thormae/lectures/graphics1/graphics_6_1_eng_web.html
|
|
438
|
-
// bbox3D is always at the world origin, i.e. T_obj is the unit matrix.
|
|
439
|
-
// All vectors contain affine coordinates and have length 3
|
|
440
|
-
// The matrices are of size 4x4.
|
|
441
|
-
eye, d,
|
|
442
|
-
zf = 20, // near clip plane
|
|
443
|
-
zn = 8, // far clip plane
|
|
444
|
-
Pref = [
|
|
445
|
-
0.5 * (this.bbox3D[0][0] + this.bbox3D[0][1]),
|
|
446
|
-
0.5 * (this.bbox3D[1][0] + this.bbox3D[1][1]),
|
|
447
|
-
0.5 * (this.bbox3D[2][0] + this.bbox3D[2][1])
|
|
448
|
-
],
|
|
449
|
-
A,
|
|
450
|
-
func_sphere;
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* Calculates a spherical parametric surface, which depends on az, el and r.
|
|
454
|
-
* @param {Number} a
|
|
455
|
-
* @param {Number} e
|
|
456
|
-
* @param {Number} r
|
|
457
|
-
* @returns {Array} 3-dimensional vector in cartesian coordinates
|
|
458
|
-
*/
|
|
459
|
-
func_sphere = function (az, el, r) {
|
|
460
|
-
return [
|
|
461
|
-
r * Math.cos(az) * Math.cos(el),
|
|
462
|
-
-r * Math.sin(az) * Math.cos(el),
|
|
463
|
-
r * Math.sin(el)
|
|
464
|
-
];
|
|
465
|
-
};
|
|
669
|
+
// wrap and clamp the azimuth and bank angle into the slider range
|
|
670
|
+
this.angles.az = Mat.wrapAndClamp(this.angles.az, az_smin, az_smax, 2 * Math.PI);
|
|
671
|
+
this.angles.bank = Mat.wrapAndClamp(this.angles.bank, bank_smin, bank_smax, 2 * Math.PI);
|
|
466
672
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
r = Type.evaluate(this.visProp.r);
|
|
471
|
-
if (r === 'auto') {
|
|
472
|
-
r = Mat.hypot(
|
|
473
|
-
this.bbox3D[0][0] - this.bbox3D[0][1],
|
|
474
|
-
this.bbox3D[1][0] - this.bbox3D[1][1],
|
|
475
|
-
this.bbox3D[2][0] - this.bbox3D[2][1]
|
|
476
|
-
) * 1.01;
|
|
477
|
-
// console.log(r);
|
|
478
|
-
}
|
|
673
|
+
// since we're using `clamp`, angles may have changed
|
|
674
|
+
this.matrix3DRot = this.getRotationFromAngles();
|
|
675
|
+
}
|
|
479
676
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
d = [eye[0], eye[1], eye[2]];
|
|
677
|
+
// restore slider positions
|
|
678
|
+
this.setSlidersFromAngles();
|
|
679
|
+
},
|
|
484
680
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
681
|
+
/**
|
|
682
|
+
* @private
|
|
683
|
+
* @returns {Array}
|
|
684
|
+
*/
|
|
685
|
+
_updateCentralProjection: function () {
|
|
686
|
+
var zf = 20, // near clip plane
|
|
687
|
+
zn = 8, // far clip plane
|
|
688
|
+
|
|
689
|
+
// See https://www.mathematik.uni-marburg.de/~thormae/lectures/graphics1/graphics_6_1_eng_web.html
|
|
690
|
+
// bbox3D is always at the world origin, i.e. T_obj is the unit matrix.
|
|
691
|
+
// All vectors contain affine coordinates and have length 3
|
|
692
|
+
// The matrices are of size 4x4.
|
|
693
|
+
r, A;
|
|
694
|
+
|
|
695
|
+
// set distance from view box center to camera
|
|
696
|
+
r = Type.evaluate(this.visProp.r);
|
|
697
|
+
if (r === 'auto') {
|
|
698
|
+
r = Mat.hypot(
|
|
699
|
+
this.bbox3D[0][0] - this.bbox3D[0][1],
|
|
700
|
+
this.bbox3D[1][0] - this.bbox3D[1][1],
|
|
701
|
+
this.bbox3D[2][0] - this.bbox3D[2][1]
|
|
702
|
+
) * 1.01;
|
|
703
|
+
}
|
|
488
704
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
705
|
+
// compute camera transformation
|
|
706
|
+
this.boxToCam = this.matrix3DRot.map((row) => row.slice());
|
|
707
|
+
this.boxToCam[3][0] = -r;
|
|
492
708
|
|
|
493
|
-
|
|
494
|
-
|
|
709
|
+
// compute focal distance and clip space transformation
|
|
710
|
+
this.focalDist = 1 / Math.tan(0.5 * Type.evaluate(this.visProp.fov));
|
|
711
|
+
A = [
|
|
712
|
+
[0, 0, 0, -1],
|
|
713
|
+
[0, this.focalDist, 0, 0],
|
|
714
|
+
[0, 0, this.focalDist, 0],
|
|
715
|
+
[2 * zf * zn / (zn - zf), 0, 0, (zf + zn) / (zn - zf)]
|
|
716
|
+
];
|
|
495
717
|
|
|
496
|
-
|
|
497
|
-
|
|
718
|
+
return Mat.matMatMult(A, this.boxToCam);
|
|
719
|
+
},
|
|
498
720
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
721
|
+
/**
|
|
722
|
+
* Comparison function for 3D points. It is used to sort points according to their z-index.
|
|
723
|
+
* @param {Point3D} a
|
|
724
|
+
* @param {Point3D} b
|
|
725
|
+
* @returns Integer
|
|
726
|
+
*/
|
|
727
|
+
compareDepth: function (a, b) {
|
|
728
|
+
var worldDiff = [0,
|
|
729
|
+
a.coords[1] - b.coords[1],
|
|
730
|
+
a.coords[2] - b.coords[2],
|
|
731
|
+
a.coords[3] - b.coords[3]],
|
|
732
|
+
oriBoxDiff = Mat.matVecMult(this.matrix3DRot, Mat.matVecMult(this.shift, worldDiff));
|
|
733
|
+
return oriBoxDiff[3];
|
|
734
|
+
},
|
|
735
|
+
|
|
736
|
+
// Update 3D-to-2D transformation matrix with the actual azimuth and elevation angles.
|
|
737
|
+
update: function () {
|
|
738
|
+
var r = this.r,
|
|
739
|
+
stretch = [
|
|
502
740
|
[1, 0, 0, 0],
|
|
503
|
-
[
|
|
504
|
-
[
|
|
505
|
-
[
|
|
506
|
-
]
|
|
741
|
+
[0, -r, 0, 0],
|
|
742
|
+
[0, 0, -r, 0],
|
|
743
|
+
[0, 0, 0, 1]
|
|
744
|
+
],
|
|
745
|
+
mat2D, objectToClip, size,
|
|
746
|
+
dx, dy,
|
|
747
|
+
objectsList;
|
|
748
|
+
|
|
749
|
+
if (
|
|
750
|
+
!Type.exists(this.el_slide) ||
|
|
751
|
+
!Type.exists(this.az_slide) ||
|
|
752
|
+
!Type.exists(this.bank_slide) ||
|
|
753
|
+
!this.needsUpdate
|
|
754
|
+
) {
|
|
755
|
+
return this;
|
|
756
|
+
}
|
|
507
757
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
[0, 0, this.focalDist, 0],
|
|
514
|
-
[2 * zf * zn / (zn - zf), 0, 0, (zf + zn) / (zn - zf)]
|
|
515
|
-
];
|
|
758
|
+
mat2D = [
|
|
759
|
+
[1, 0, 0],
|
|
760
|
+
[0, 1, 0],
|
|
761
|
+
[0, 0, 1]
|
|
762
|
+
];
|
|
516
763
|
|
|
517
|
-
|
|
518
|
-
},
|
|
764
|
+
this.projectionType = Type.evaluate(this.visProp.projection).toLowerCase();
|
|
519
765
|
|
|
520
|
-
//
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
Pref = null,
|
|
525
|
-
useTrackball = false;
|
|
766
|
+
// override angle slider bounds when trackball navigation is enabled
|
|
767
|
+
if (this.trackballEnabled !== Type.evaluate(this.visProp.trackball.enabled)) {
|
|
768
|
+
this.updateAngleSliderBounds();
|
|
769
|
+
}
|
|
526
770
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
771
|
+
if (this._hasMoveTrackball) {
|
|
772
|
+
// The trackball has been moved since the last update, so we do
|
|
773
|
+
// trackball navigation. When the trackball is enabled, a drag
|
|
774
|
+
// event is interpreted as a trackball movement unless it's
|
|
775
|
+
// caught by something else, like point dragging. When the
|
|
776
|
+
// trackball is disabled, the trackball movement flag should
|
|
777
|
+
// never be set
|
|
778
|
+
this.matrix3DRot = this.updateProjectionTrackball();
|
|
779
|
+
this.setAnglesFromRotation();
|
|
780
|
+
} else if (this.anglesHaveMoved()) {
|
|
781
|
+
// The trackball hasn't been moved since the last up date, but
|
|
782
|
+
// the Tait-Bryan angles have been, so we do angle navigation
|
|
783
|
+
this.getAnglesFromSliders();
|
|
784
|
+
this.matrix3DRot = this.getRotationFromAngles();
|
|
785
|
+
}
|
|
534
786
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
]
|
|
787
|
+
/**
|
|
788
|
+
* The translation that moves the center of the view box to the origin.
|
|
789
|
+
*/
|
|
790
|
+
this.shift = [
|
|
791
|
+
[1, 0, 0, 0],
|
|
792
|
+
[-0.5 * (this.bbox3D[0][0] + this.bbox3D[0][1]), 1, 0, 0],
|
|
793
|
+
[-0.5 * (this.bbox3D[1][0] + this.bbox3D[1][1]), 0, 1, 0],
|
|
794
|
+
[-0.5 * (this.bbox3D[2][0] + this.bbox3D[2][1]), 0, 0, 1]
|
|
795
|
+
];
|
|
796
|
+
|
|
797
|
+
switch (this.projectionType) {
|
|
798
|
+
case 'central': // Central projection
|
|
799
|
+
|
|
800
|
+
// Add a final transformation to scale and shift the projection
|
|
801
|
+
// on the board, usually called viewport.
|
|
802
|
+
size = 2 * 0.4;
|
|
803
|
+
mat2D[1][1] = this.size[0] / size; // w / d_x
|
|
804
|
+
mat2D[2][2] = this.size[1] / size; // h / d_y
|
|
805
|
+
mat2D[1][0] = this.llftCorner[0] + mat2D[1][1] * 0.5 * size; // llft_x
|
|
806
|
+
mat2D[2][0] = this.llftCorner[1] + mat2D[2][2] * 0.5 * size; // llft_y
|
|
807
|
+
// The transformations this.matrix3D and mat2D can not be combined at this point,
|
|
808
|
+
// since the projected vectors have to be normalized in between in project3DTo2D
|
|
809
|
+
this.viewPortTransform = mat2D;
|
|
810
|
+
|
|
811
|
+
objectToClip = this._updateCentralProjection();
|
|
812
|
+
// this.matrix3D is a 4x4 matrix
|
|
813
|
+
this.matrix3D = Mat.matMatMult(objectToClip, this.shift);
|
|
814
|
+
break;
|
|
540
815
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
//
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
];
|
|
559
|
-
}
|
|
560
|
-
this.matrix3DRot = this.updateProjectionTrackball(Pref);
|
|
561
|
-
}
|
|
562
|
-
useTrackball = true;
|
|
563
|
-
}
|
|
816
|
+
case 'parallel': // Parallel projection
|
|
817
|
+
default:
|
|
818
|
+
// Add a final transformation to scale and shift the projection
|
|
819
|
+
// on the board, usually called viewport.
|
|
820
|
+
dx = this.bbox3D[0][1] - this.bbox3D[0][0];
|
|
821
|
+
dy = this.bbox3D[1][1] - this.bbox3D[1][0];
|
|
822
|
+
mat2D[1][1] = this.size[0] / dx; // w / d_x
|
|
823
|
+
mat2D[2][2] = this.size[1] / dy; // h / d_y
|
|
824
|
+
mat2D[1][0] = this.llftCorner[0] + mat2D[1][1] * 0.5 * dx; // llft_x
|
|
825
|
+
mat2D[2][0] = this.llftCorner[1] + mat2D[2][2] * 0.5 * dy; // llft_y
|
|
826
|
+
|
|
827
|
+
// Combine all transformations, this.matrix3D is a 3x4 matrix
|
|
828
|
+
this.matrix3D = Mat.matMatMult(
|
|
829
|
+
mat2D,
|
|
830
|
+
Mat.matMatMult(Mat.matMatMult(this.matrix3DRot, stretch), this.shift).slice(0, 3)
|
|
831
|
+
);
|
|
832
|
+
}
|
|
564
833
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
mat2D[1][0] = this.llftCorner[0] + mat2D[1][1] * 0.5 * size; // llft_x
|
|
574
|
-
mat2D[2][0] = this.llftCorner[1] + mat2D[2][2] * 0.5 * size; // llft_y
|
|
575
|
-
// The transformations this.matrix3D and mat2D can not be combined at this point,
|
|
576
|
-
// since the projected vectors have to be normalized in between in project3DTo2D
|
|
577
|
-
this.viewPortTransform = mat2D;
|
|
578
|
-
|
|
579
|
-
if (!useTrackball) {
|
|
580
|
-
// Do elevation / azimuth navigation or at least initialize matrix
|
|
581
|
-
// this.matrix3DRot
|
|
582
|
-
this.matrix3DRot = this._updateCentralProjection();
|
|
583
|
-
}
|
|
584
|
-
// this.matrix3D is a 4x4 matrix
|
|
585
|
-
this.matrix3D = this.matrix3DRot.slice();
|
|
586
|
-
break;
|
|
587
|
-
|
|
588
|
-
case 'parallel': // Parallel projection
|
|
589
|
-
default:
|
|
590
|
-
// Rotate the scenery around the center of the box, not around the origin
|
|
591
|
-
shift = [
|
|
592
|
-
[1, 0, 0, 0],
|
|
593
|
-
[-0.5 * (this.bbox3D[0][0] + this.bbox3D[0][1]), 1, 0, 0],
|
|
594
|
-
[-0.5 * (this.bbox3D[1][0] + this.bbox3D[1][1]), 0, 1, 0],
|
|
595
|
-
[-0.5 * (this.bbox3D[2][0] + this.bbox3D[2][1]), 0, 0, 1]
|
|
596
|
-
];
|
|
597
|
-
|
|
598
|
-
// Add a final transformation to scale and shift the projection
|
|
599
|
-
// on the board, usually called viewport.
|
|
600
|
-
dx = this.bbox3D[0][1] - this.bbox3D[0][0];
|
|
601
|
-
dy = this.bbox3D[1][1] - this.bbox3D[1][0];
|
|
602
|
-
mat2D[1][1] = this.size[0] / dx; // w / d_x
|
|
603
|
-
mat2D[2][2] = this.size[1] / dy; // h / d_y
|
|
604
|
-
mat2D[1][0] = this.llftCorner[0] + mat2D[1][1] * 0.5 * dx; // llft_x
|
|
605
|
-
mat2D[2][0] = this.llftCorner[1] + mat2D[2][2] * 0.5 * dy; // llft_y
|
|
606
|
-
|
|
607
|
-
if (!useTrackball) {
|
|
608
|
-
// Do elevation / azimuth navigation or at least initialize matrix this.matrix3DRot
|
|
609
|
-
this.matrix3DRot = this.updateParallelProjection();
|
|
610
|
-
}
|
|
611
|
-
// Combine all transformations, this.matrix3D is a 3x4 matrix
|
|
612
|
-
this.matrix3D = Mat.matMatMult(mat2D, Mat.matMatMult(this.matrix3DRot, shift).slice(0, 3));
|
|
613
|
-
}
|
|
834
|
+
// if depth-ordering for points was just switched on, initialize the
|
|
835
|
+
// list of points
|
|
836
|
+
if (this.visProp.depthorderpoints && this.points === null) {
|
|
837
|
+
objectsList = Object.values(this.objects);
|
|
838
|
+
this.points = objectsList.filter(
|
|
839
|
+
el => el.type === Const.OBJECT_TYPE_POINT3D
|
|
840
|
+
);
|
|
841
|
+
}
|
|
614
842
|
|
|
615
|
-
|
|
616
|
-
|
|
843
|
+
// if depth-ordering for points was just switched off, throw away the
|
|
844
|
+
// list of points
|
|
845
|
+
if (!this.visProp.depthorderpoints && this.points !== null) {
|
|
846
|
+
this.points = null;
|
|
847
|
+
}
|
|
617
848
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
849
|
+
// depth-order visible points. the `setLayer` method is used here to
|
|
850
|
+
// re-order the points within each layer: it has the side effect of
|
|
851
|
+
// moving the target element to the end of the layer's child list
|
|
852
|
+
if (this.visProp.depthorderpoints && this.board.renderer && this.board.renderer.type === 'svg') {
|
|
853
|
+
this.points
|
|
854
|
+
.filter((pt) => Type.evaluate(pt.element2D.visProp.visible))
|
|
855
|
+
.sort(this.compareDepth.bind(this))
|
|
856
|
+
.forEach((pt) => this.board.renderer.setLayer(pt.element2D, pt.element2D.visProp.layer));
|
|
857
|
+
|
|
858
|
+
/* [DEBUG] list oriented box coordinates in depth order */
|
|
859
|
+
// console.log('depth-ordered points in oriented box coordinates');
|
|
860
|
+
// this.points
|
|
861
|
+
// .filter((pt) => pt.element2D.visProp.visible)
|
|
862
|
+
// .sort(compareDepth)
|
|
863
|
+
// .forEach(function (pt) {
|
|
864
|
+
// console.log(Mat.matVecMult(that.matrix3DRot, Mat.matVecMult(that.shift, pt.coords)));
|
|
865
|
+
// });
|
|
866
|
+
}
|
|
622
867
|
|
|
623
|
-
|
|
624
|
-
|
|
868
|
+
return this;
|
|
869
|
+
},
|
|
625
870
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
}
|
|
631
|
-
return this;
|
|
632
|
-
}
|
|
871
|
+
updateRenderer: function () {
|
|
872
|
+
this.needsUpdate = false;
|
|
873
|
+
return this;
|
|
874
|
+
},
|
|
633
875
|
|
|
634
|
-
|
|
876
|
+
removeObject: function (object, saveMethod) {
|
|
877
|
+
var i;
|
|
635
878
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
879
|
+
// this.board.removeObject(object, saveMethod);
|
|
880
|
+
if (Type.isArray(object)) {
|
|
881
|
+
for (i = 0; i < object.length; i++) {
|
|
882
|
+
this.removeObject(object[i]);
|
|
640
883
|
}
|
|
884
|
+
return this;
|
|
885
|
+
}
|
|
641
886
|
|
|
642
|
-
|
|
643
|
-
// // remove all children.
|
|
644
|
-
// for (el in object.childElements) {
|
|
645
|
-
// if (object.childElements.hasOwnProperty(el)) {
|
|
646
|
-
// object.childElements[el].board.removeObject(object.childElements[el]);
|
|
647
|
-
// }
|
|
648
|
-
// }
|
|
649
|
-
|
|
650
|
-
delete this.objects[object.id];
|
|
651
|
-
} catch (e) {
|
|
652
|
-
JXG.debug('View3D ' + object.id + ': Could not be removed: ' + e);
|
|
653
|
-
}
|
|
887
|
+
object = this.select(object);
|
|
654
888
|
|
|
655
|
-
|
|
889
|
+
// // If the object which is about to be removed unknown or a string, do nothing.
|
|
890
|
+
// // it is a string if a string was given and could not be resolved to an element.
|
|
891
|
+
if (!Type.exists(object) || Type.isString(object)) {
|
|
892
|
+
return this;
|
|
893
|
+
}
|
|
656
894
|
|
|
657
|
-
|
|
895
|
+
try {
|
|
896
|
+
// // remove all children.
|
|
897
|
+
// for (el in object.childElements) {
|
|
898
|
+
// if (object.childElements.hasOwnProperty(el)) {
|
|
899
|
+
// object.childElements[el].board.removeObject(object.childElements[el]);
|
|
900
|
+
// }
|
|
901
|
+
// }
|
|
902
|
+
|
|
903
|
+
delete this.objects[object.id];
|
|
904
|
+
} catch (e) {
|
|
905
|
+
JXG.debug('View3D ' + object.id + ': Could not be removed: ' + e);
|
|
906
|
+
}
|
|
658
907
|
|
|
659
|
-
|
|
660
|
-
},
|
|
908
|
+
// this.update();
|
|
661
909
|
|
|
662
|
-
|
|
663
|
-
* Map world coordinates to view coordinates.
|
|
664
|
-
*
|
|
665
|
-
* @param {Array} pWorld A world space point, in homogeneous coordinates.
|
|
666
|
-
* @param {Boolean} [homog=true] Whether to return homogeneous coordinates.
|
|
667
|
-
* If false, projects down to ordinary coordinates.
|
|
668
|
-
*/
|
|
669
|
-
worldToView: function (pWorld, homog=true) {
|
|
670
|
-
var k,
|
|
671
|
-
pView = Mat.matVecMult(this.cameraTransform, pWorld);
|
|
910
|
+
this.board.removeObject(object, saveMethod);
|
|
672
911
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
912
|
+
return this;
|
|
913
|
+
},
|
|
914
|
+
|
|
915
|
+
/**
|
|
916
|
+
* Map world coordinates to focal coordinates. These coordinate systems
|
|
917
|
+
* are explained in the {@link JXG.View3D#boxToCam} matrix
|
|
918
|
+
* documentation.
|
|
919
|
+
*
|
|
920
|
+
* @param {Array} pWorld A world space point, in homogeneous coordinates.
|
|
921
|
+
* @param {Boolean} [homog=true] Whether to return homogeneous coordinates.
|
|
922
|
+
* If false, projects down to ordinary coordinates.
|
|
923
|
+
*/
|
|
924
|
+
worldToFocal: function (pWorld, homog = true) {
|
|
925
|
+
var k,
|
|
926
|
+
pView = Mat.matVecMult(this.boxToCam, Mat.matVecMult(this.shift, pWorld));
|
|
927
|
+
pView[3] -= pView[0] * this.focalDist;
|
|
928
|
+
if (homog) {
|
|
929
|
+
return pView;
|
|
930
|
+
} else {
|
|
931
|
+
for (k = 1; k < 4; k++) {
|
|
932
|
+
pView[k] /= pView[0];
|
|
681
933
|
}
|
|
682
|
-
|
|
934
|
+
return pView.slice(1, 4);
|
|
935
|
+
}
|
|
936
|
+
},
|
|
683
937
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
938
|
+
/**
|
|
939
|
+
* Project 3D coordinates to 2D board coordinates
|
|
940
|
+
* The 3D coordinates are provides as three numbers x, y, z or one array of length 3.
|
|
941
|
+
*
|
|
942
|
+
* @param {Number|Array} x
|
|
943
|
+
* @param {Number[]} y
|
|
944
|
+
* @param {Number[]} z
|
|
945
|
+
* @returns {Array} Array of length 3 containing the projection on to the board
|
|
946
|
+
* in homogeneous user coordinates.
|
|
947
|
+
*/
|
|
948
|
+
project3DTo2D: function (x, y, z) {
|
|
949
|
+
var vec, w;
|
|
950
|
+
if (arguments.length === 3) {
|
|
951
|
+
vec = [1, x, y, z];
|
|
952
|
+
} else {
|
|
953
|
+
// Argument is an array
|
|
954
|
+
if (x.length === 3) {
|
|
955
|
+
// vec = [1].concat(x);
|
|
956
|
+
vec = x.slice();
|
|
957
|
+
vec.unshift(1);
|
|
698
958
|
} else {
|
|
699
|
-
|
|
700
|
-
if (x.length === 3) {
|
|
701
|
-
// vec = [1].concat(x);
|
|
702
|
-
vec = x.slice();
|
|
703
|
-
vec.unshift(1);
|
|
704
|
-
} else {
|
|
705
|
-
vec = x;
|
|
706
|
-
}
|
|
959
|
+
vec = x;
|
|
707
960
|
}
|
|
961
|
+
}
|
|
708
962
|
|
|
709
|
-
|
|
963
|
+
w = Mat.matVecMult(this.matrix3D, vec);
|
|
710
964
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
965
|
+
switch (this.projectionType) {
|
|
966
|
+
case 'central':
|
|
967
|
+
w[1] /= w[0];
|
|
968
|
+
w[2] /= w[0];
|
|
969
|
+
w[3] /= w[0];
|
|
970
|
+
w[0] /= w[0];
|
|
971
|
+
return Mat.matVecMult(this.viewPortTransform, w.slice(0, 3));
|
|
718
972
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
973
|
+
case 'parallel':
|
|
974
|
+
default:
|
|
975
|
+
return w;
|
|
976
|
+
}
|
|
977
|
+
},
|
|
724
978
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
979
|
+
/**
|
|
980
|
+
* We know that v2d * w0 = mat * (1, x, y, d)^T where v2d = (1, b, c, h)^T with unknowns w0, h, x, y.
|
|
981
|
+
* Setting R = mat^(-1) gives
|
|
982
|
+
* 1/ w0 * (1, x, y, d)^T = R * v2d.
|
|
983
|
+
* The first and the last row of this equation allows to determine 1/w0 and h.
|
|
984
|
+
*
|
|
985
|
+
* @param {Array} mat
|
|
986
|
+
* @param {Array} v2d
|
|
987
|
+
* @param {Number} d
|
|
988
|
+
* @returns Array
|
|
989
|
+
* @private
|
|
990
|
+
*/
|
|
991
|
+
_getW0: function (mat, v2d, d) {
|
|
992
|
+
var R = Mat.inverse(mat),
|
|
993
|
+
R1 = R[0][0] + v2d[1] * R[0][1] + v2d[2] * R[0][2],
|
|
994
|
+
R2 = R[3][0] + v2d[1] * R[3][1] + v2d[2] * R[3][2],
|
|
995
|
+
w, h, det;
|
|
996
|
+
|
|
997
|
+
det = d * R[0][3] - R[3][3];
|
|
998
|
+
w = (R2 * R[0][3] - R1 * R[3][3]) / det;
|
|
999
|
+
h = (R2 - R1 * d) / det;
|
|
1000
|
+
return [1 / w, h];
|
|
1001
|
+
},
|
|
748
1002
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
}
|
|
780
|
-
sol = Mat.Numerics.Gauss(mat, rhs);
|
|
781
|
-
} catch (e) {
|
|
782
|
-
sol = [0, NaN, NaN, NaN];
|
|
1003
|
+
/**
|
|
1004
|
+
* Project a 2D coordinate to the plane defined by point "foot"
|
|
1005
|
+
* and the normal vector `normal`.
|
|
1006
|
+
*
|
|
1007
|
+
* @param {JXG.Point} point2d
|
|
1008
|
+
* @param {Array} normal
|
|
1009
|
+
* @param {Array} foot
|
|
1010
|
+
* @returns {Array} of length 4 containing the projected
|
|
1011
|
+
* point in homogeneous coordinates.
|
|
1012
|
+
*/
|
|
1013
|
+
project2DTo3DPlane: function (point2d, normal, foot) {
|
|
1014
|
+
var mat, rhs, d, le, sol,
|
|
1015
|
+
n = normal.slice(1),
|
|
1016
|
+
v2d, w0, res;
|
|
1017
|
+
|
|
1018
|
+
foot = foot || [1, 0, 0, 0];
|
|
1019
|
+
le = Mat.norm(n, 3);
|
|
1020
|
+
d = Mat.innerProduct(foot.slice(1), n, 3) / le;
|
|
1021
|
+
|
|
1022
|
+
if (this.projectionType === 'parallel') {
|
|
1023
|
+
mat = this.matrix3D.slice(0, 3); // Copy each row by reference
|
|
1024
|
+
mat.push([0, n[0], n[1], n[2]]);
|
|
1025
|
+
|
|
1026
|
+
// 2D coordinates of point:
|
|
1027
|
+
rhs = point2d.coords.usrCoords.slice();
|
|
1028
|
+
rhs.push(d);
|
|
1029
|
+
try {
|
|
1030
|
+
// Prevent singularity in case elevation angle is zero
|
|
1031
|
+
if (mat[2][3] === 1.0) {
|
|
1032
|
+
mat[2][1] = mat[2][2] = Mat.eps * 0.001;
|
|
783
1033
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
]
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
sol[2] /= sol[0];
|
|
808
|
-
sol[3] /= sol[0];
|
|
809
|
-
// sol[3] = d;
|
|
810
|
-
sol[0] /= sol[0];
|
|
811
|
-
} catch (err) {
|
|
812
|
-
sol = [0, NaN, NaN, NaN];
|
|
1034
|
+
sol = Mat.Numerics.Gauss(mat, rhs);
|
|
1035
|
+
} catch (e) {
|
|
1036
|
+
sol = [0, NaN, NaN, NaN];
|
|
1037
|
+
}
|
|
1038
|
+
} else {
|
|
1039
|
+
mat = this.matrix3D;
|
|
1040
|
+
|
|
1041
|
+
// 2D coordinates of point:
|
|
1042
|
+
rhs = point2d.coords.usrCoords.slice();
|
|
1043
|
+
|
|
1044
|
+
v2d = Mat.Numerics.Gauss(this.viewPortTransform, rhs);
|
|
1045
|
+
res = this._getW0(mat, v2d, d);
|
|
1046
|
+
w0 = res[0];
|
|
1047
|
+
rhs = [
|
|
1048
|
+
v2d[0] * w0,
|
|
1049
|
+
v2d[1] * w0,
|
|
1050
|
+
v2d[2] * w0,
|
|
1051
|
+
res[1] * w0
|
|
1052
|
+
];
|
|
1053
|
+
try {
|
|
1054
|
+
// Prevent singularity in case elevation angle is zero
|
|
1055
|
+
if (mat[2][3] === 1.0) {
|
|
1056
|
+
mat[2][1] = mat[2][2] = Mat.eps * 0.001;
|
|
813
1057
|
}
|
|
1058
|
+
|
|
1059
|
+
sol = Mat.Numerics.Gauss(mat, rhs);
|
|
1060
|
+
sol[1] /= sol[0];
|
|
1061
|
+
sol[2] /= sol[0];
|
|
1062
|
+
sol[3] /= sol[0];
|
|
1063
|
+
// sol[3] = d;
|
|
1064
|
+
sol[0] /= sol[0];
|
|
1065
|
+
} catch (err) {
|
|
1066
|
+
sol = [0, NaN, NaN, NaN];
|
|
814
1067
|
}
|
|
1068
|
+
}
|
|
815
1069
|
|
|
816
|
-
|
|
817
|
-
|
|
1070
|
+
return sol;
|
|
1071
|
+
},
|
|
818
1072
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1073
|
+
/**
|
|
1074
|
+
* Project a point on the screen to the nearest point, in screen
|
|
1075
|
+
* distance, on a line segment in 3d space. The inputs must be in
|
|
1076
|
+
* ordinary coordinates, but the output is in homogeneous coordinates.
|
|
1077
|
+
*
|
|
1078
|
+
* @param {Array} pScr The screen coordinates of the point to project.
|
|
1079
|
+
* @param {Array} end0 The world space coordinates of one end of the
|
|
1080
|
+
* line segment.
|
|
1081
|
+
* @param {Array} end1 The world space coordinates of the other end of
|
|
1082
|
+
* the line segment.
|
|
1083
|
+
*/
|
|
1084
|
+
projectScreenToSegment: function (pScr, end0, end1) {
|
|
1085
|
+
var end0_2d = this.project3DTo2D(end0).slice(1, 3),
|
|
1086
|
+
end1_2d = this.project3DTo2D(end1).slice(1, 3),
|
|
1087
|
+
dir_2d = [
|
|
1088
|
+
end1_2d[0] - end0_2d[0],
|
|
1089
|
+
end1_2d[1] - end0_2d[1]
|
|
1090
|
+
],
|
|
1091
|
+
dir_2d_norm_sq = Mat.innerProduct(dir_2d, dir_2d),
|
|
1092
|
+
diff = [
|
|
1093
|
+
pScr[0] - end0_2d[0],
|
|
1094
|
+
pScr[1] - end0_2d[1]
|
|
1095
|
+
],
|
|
1096
|
+
s = Mat.innerProduct(diff, dir_2d) / dir_2d_norm_sq, // screen-space affine parameter
|
|
1097
|
+
mid, mid_2d, mid_diff, m,
|
|
1098
|
+
|
|
1099
|
+
t, // view-space affine parameter
|
|
1100
|
+
t_clamped, // affine parameter clamped to range
|
|
1101
|
+
t_clamped_co;
|
|
1102
|
+
|
|
1103
|
+
if (this.projectionType === 'central') {
|
|
1104
|
+
mid = [
|
|
1105
|
+
0.5 * (end0[0] + end1[0]),
|
|
1106
|
+
0.5 * (end0[1] + end1[1]),
|
|
1107
|
+
0.5 * (end0[2] + end1[2])
|
|
1108
|
+
];
|
|
1109
|
+
mid_2d = this.project3DTo2D(mid).slice(1, 3);
|
|
1110
|
+
mid_diff = [
|
|
1111
|
+
mid_2d[0] - end0_2d[0],
|
|
1112
|
+
mid_2d[1] - end0_2d[1]
|
|
1113
|
+
];
|
|
1114
|
+
m = Mat.innerProduct(mid_diff, dir_2d) / dir_2d_norm_sq;
|
|
1115
|
+
|
|
1116
|
+
// the view-space affine parameter s is related to the
|
|
1117
|
+
// screen-space affine parameter t by a Möbius transformation,
|
|
1118
|
+
// which is determined by the following relations:
|
|
1119
|
+
//
|
|
1120
|
+
// s | t
|
|
1121
|
+
// -----
|
|
1122
|
+
// 0 | 0
|
|
1123
|
+
// m | 1/2
|
|
1124
|
+
// 1 | 1
|
|
1125
|
+
//
|
|
1126
|
+
t = (1 - m) * s / ((1 - 2 * m) * s + m);
|
|
1127
|
+
} else {
|
|
1128
|
+
t = s;
|
|
1129
|
+
}
|
|
841
1130
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
c3d[1] = cube[0][0];
|
|
852
|
-
}
|
|
853
|
-
if (c3d[1] > cube[0][1]) {
|
|
854
|
-
c3d[1] = cube[0][1];
|
|
855
|
-
}
|
|
856
|
-
if (c3d[2] < cube[1][0]) {
|
|
857
|
-
c3d[2] = cube[1][0];
|
|
858
|
-
}
|
|
859
|
-
if (c3d[2] > cube[1][1]) {
|
|
860
|
-
c3d[2] = cube[1][1];
|
|
861
|
-
}
|
|
862
|
-
if (c3d[3] < cube[2][0]) {
|
|
863
|
-
c3d[3] = cube[2][0];
|
|
864
|
-
}
|
|
865
|
-
if (c3d[3] > cube[2][1]) {
|
|
866
|
-
c3d[3] = cube[2][1];
|
|
867
|
-
}
|
|
1131
|
+
t_clamped = Math.min(Math.max(t, 0), 1);
|
|
1132
|
+
t_clamped_co = 1 - t_clamped;
|
|
1133
|
+
return [
|
|
1134
|
+
1,
|
|
1135
|
+
t_clamped_co * end0[0] + t_clamped * end1[0],
|
|
1136
|
+
t_clamped_co * end0[1] + t_clamped * end1[1],
|
|
1137
|
+
t_clamped_co * end0[2] + t_clamped * end1[2]
|
|
1138
|
+
];
|
|
1139
|
+
},
|
|
868
1140
|
|
|
869
|
-
|
|
870
|
-
|
|
1141
|
+
/**
|
|
1142
|
+
* Project a 2D coordinate to a new 3D position by keeping
|
|
1143
|
+
* the 3D x, y coordinates and changing only the z coordinate.
|
|
1144
|
+
* All horizontal moves of the 2D point are ignored.
|
|
1145
|
+
*
|
|
1146
|
+
* @param {JXG.Point} point2d
|
|
1147
|
+
* @param {Array} base_c3d
|
|
1148
|
+
* @returns {Array} of length 4 containing the projected
|
|
1149
|
+
* point in homogeneous coordinates.
|
|
1150
|
+
*/
|
|
1151
|
+
project2DTo3DVertical: function (point2d, base_c3d) {
|
|
1152
|
+
var pScr = point2d.coords.usrCoords.slice(1, 3),
|
|
1153
|
+
end0 = [base_c3d[1], base_c3d[2], this.bbox3D[2][0]],
|
|
1154
|
+
end1 = [base_c3d[1], base_c3d[2], this.bbox3D[2][1]];
|
|
871
1155
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1156
|
+
return this.projectScreenToSegment(pScr, end0, end1);
|
|
1157
|
+
},
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* Limit 3D coordinates to the bounding cube.
|
|
1161
|
+
*
|
|
1162
|
+
* @param {Array} c3d 3D coordinates [x,y,z]
|
|
1163
|
+
* @returns Array with updated 3D coordinates.
|
|
1164
|
+
*/
|
|
1165
|
+
project3DToCube: function (c3d) {
|
|
1166
|
+
var cube = this.bbox3D;
|
|
1167
|
+
if (c3d[1] < cube[0][0]) {
|
|
1168
|
+
c3d[1] = cube[0][0];
|
|
1169
|
+
}
|
|
1170
|
+
if (c3d[1] > cube[0][1]) {
|
|
1171
|
+
c3d[1] = cube[0][1];
|
|
1172
|
+
}
|
|
1173
|
+
if (c3d[2] < cube[1][0]) {
|
|
1174
|
+
c3d[2] = cube[1][0];
|
|
1175
|
+
}
|
|
1176
|
+
if (c3d[2] > cube[1][1]) {
|
|
1177
|
+
c3d[2] = cube[1][1];
|
|
1178
|
+
}
|
|
1179
|
+
if (c3d[3] < cube[2][0]) {
|
|
1180
|
+
c3d[3] = cube[2][0];
|
|
1181
|
+
}
|
|
1182
|
+
if (c3d[3] > cube[2][1]) {
|
|
1183
|
+
c3d[3] = cube[2][1];
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
return c3d;
|
|
1187
|
+
},
|
|
1188
|
+
|
|
1189
|
+
/**
|
|
1190
|
+
* Intersect a ray with the bounding cube of the 3D view.
|
|
1191
|
+
* @param {Array} p 3D coordinates [x,y,z]
|
|
1192
|
+
* @param {Array} d 3D direction vector of the line (array of length 3)
|
|
1193
|
+
* @param {Number} r direction of the ray (positive if r > 0, negative if r < 0).
|
|
1194
|
+
* @returns Affine ratio of the intersection of the line with the cube.
|
|
1195
|
+
*/
|
|
1196
|
+
intersectionLineCube: function (p, d, r) {
|
|
1197
|
+
var r_n, i, r0, r1;
|
|
1198
|
+
|
|
1199
|
+
r_n = r;
|
|
1200
|
+
for (i = 0; i < 3; i++) {
|
|
1201
|
+
if (d[i] !== 0) {
|
|
1202
|
+
r0 = (this.bbox3D[i][0] - p[i]) / d[i];
|
|
1203
|
+
r1 = (this.bbox3D[i][1] - p[i]) / d[i];
|
|
1204
|
+
if (r < 0) {
|
|
1205
|
+
r_n = Math.max(r_n, Math.min(r0, r1));
|
|
1206
|
+
} else {
|
|
1207
|
+
r_n = Math.min(r_n, Math.max(r0, r1));
|
|
892
1208
|
}
|
|
893
1209
|
}
|
|
894
|
-
|
|
895
|
-
|
|
1210
|
+
}
|
|
1211
|
+
return r_n;
|
|
1212
|
+
},
|
|
896
1213
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1214
|
+
/**
|
|
1215
|
+
* Test if coordinates are inside of the bounding cube.
|
|
1216
|
+
* @param {array} q 3D coordinates [x,y,z] of a point.
|
|
1217
|
+
* @returns Boolean
|
|
1218
|
+
*/
|
|
1219
|
+
isInCube: function (q) {
|
|
1220
|
+
return (
|
|
1221
|
+
q[0] > this.bbox3D[0][0] - Mat.eps &&
|
|
1222
|
+
q[0] < this.bbox3D[0][1] + Mat.eps &&
|
|
1223
|
+
q[1] > this.bbox3D[1][0] - Mat.eps &&
|
|
1224
|
+
q[1] < this.bbox3D[1][1] + Mat.eps &&
|
|
1225
|
+
q[2] > this.bbox3D[2][0] - Mat.eps &&
|
|
1226
|
+
q[2] < this.bbox3D[2][1] + Mat.eps
|
|
1227
|
+
);
|
|
1228
|
+
},
|
|
912
1229
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
1230
|
+
/**
|
|
1231
|
+
*
|
|
1232
|
+
* @param {JXG.Plane3D} plane1
|
|
1233
|
+
* @param {JXG.Plane3D} plane2
|
|
1234
|
+
* @param {JXG.Plane3D} d
|
|
1235
|
+
* @returns {Array} of length 2 containing the coordinates of the defining points of
|
|
1236
|
+
* of the intersection segment.
|
|
1237
|
+
*/
|
|
1238
|
+
intersectionPlanePlane: function (plane1, plane2, d) {
|
|
1239
|
+
var ret = [[], []],
|
|
1240
|
+
p,
|
|
1241
|
+
dir,
|
|
1242
|
+
r,
|
|
1243
|
+
q;
|
|
1244
|
+
|
|
1245
|
+
d = d || plane2.d;
|
|
1246
|
+
|
|
1247
|
+
p = Mat.Geometry.meet3Planes(
|
|
1248
|
+
plane1.normal,
|
|
1249
|
+
plane1.d,
|
|
1250
|
+
plane2.normal,
|
|
1251
|
+
d,
|
|
1252
|
+
Mat.crossProduct(plane1.normal, plane2.normal),
|
|
1253
|
+
0
|
|
1254
|
+
);
|
|
1255
|
+
dir = Mat.Geometry.meetPlanePlane(
|
|
1256
|
+
plane1.vec1,
|
|
1257
|
+
plane1.vec2,
|
|
1258
|
+
plane2.vec1,
|
|
1259
|
+
plane2.vec2
|
|
1260
|
+
);
|
|
1261
|
+
r = this.intersectionLineCube(p, dir, Infinity);
|
|
1262
|
+
q = Mat.axpy(r, dir, p);
|
|
1263
|
+
if (this.isInCube(q)) {
|
|
1264
|
+
ret[0] = q;
|
|
1265
|
+
}
|
|
1266
|
+
r = this.intersectionLineCube(p, dir, -Infinity);
|
|
1267
|
+
q = Mat.axpy(r, dir, p);
|
|
1268
|
+
if (this.isInCube(q)) {
|
|
1269
|
+
ret[1] = q;
|
|
1270
|
+
}
|
|
1271
|
+
return ret;
|
|
1272
|
+
},
|
|
956
1273
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1274
|
+
/**
|
|
1275
|
+
* Generate mesh for a surface / plane.
|
|
1276
|
+
* Returns array [dataX, dataY] for a JSXGraph curve's updateDataArray function.
|
|
1277
|
+
* @param {Array|Function} func
|
|
1278
|
+
* @param {Array} interval_u
|
|
1279
|
+
* @param {Array} interval_v
|
|
1280
|
+
* @returns Array
|
|
1281
|
+
* @private
|
|
1282
|
+
*
|
|
1283
|
+
* @example
|
|
1284
|
+
* var el = view.create('curve', [[], []]);
|
|
1285
|
+
* el.updateDataArray = function () {
|
|
1286
|
+
* var steps_u = Type.evaluate(this.visProp.stepsu),
|
|
1287
|
+
* steps_v = Type.evaluate(this.visProp.stepsv),
|
|
1288
|
+
* r_u = Type.evaluate(this.range_u),
|
|
1289
|
+
* r_v = Type.evaluate(this.range_v),
|
|
1290
|
+
* func, ret;
|
|
1291
|
+
*
|
|
1292
|
+
* if (this.F !== null) {
|
|
1293
|
+
* func = this.F;
|
|
1294
|
+
* } else {
|
|
1295
|
+
* func = [this.X, this.Y, this.Z];
|
|
1296
|
+
* }
|
|
1297
|
+
* ret = this.view.getMesh(func,
|
|
1298
|
+
* r_u.concat([steps_u]),
|
|
1299
|
+
* r_v.concat([steps_v]));
|
|
1300
|
+
*
|
|
1301
|
+
* this.dataX = ret[0];
|
|
1302
|
+
* this.dataY = ret[1];
|
|
1303
|
+
* };
|
|
1304
|
+
*
|
|
1305
|
+
*/
|
|
1306
|
+
getMesh: function (func, interval_u, interval_v) {
|
|
1307
|
+
var i_u, i_v, u, v,
|
|
1308
|
+
c2d, delta_u, delta_v,
|
|
1309
|
+
p = [0, 0, 0],
|
|
1310
|
+
steps_u = interval_u[2],
|
|
1311
|
+
steps_v = interval_v[2],
|
|
1312
|
+
dataX = [],
|
|
1313
|
+
dataY = [];
|
|
1314
|
+
|
|
1315
|
+
delta_u = (Type.evaluate(interval_u[1]) - Type.evaluate(interval_u[0])) / steps_u;
|
|
1316
|
+
delta_v = (Type.evaluate(interval_v[1]) - Type.evaluate(interval_v[0])) / steps_v;
|
|
1317
|
+
|
|
1318
|
+
for (i_u = 0; i_u <= steps_u; i_u++) {
|
|
1319
|
+
u = interval_u[0] + delta_u * i_u;
|
|
1320
|
+
for (i_v = 0; i_v <= steps_v; i_v++) {
|
|
1321
|
+
v = interval_v[0] + delta_v * i_v;
|
|
1322
|
+
if (Type.isFunction(func)) {
|
|
1323
|
+
p = func(u, v);
|
|
1324
|
+
} else {
|
|
1325
|
+
p = [func[0](u, v), func[1](u, v), func[2](u, v)];
|
|
1326
|
+
}
|
|
1327
|
+
c2d = this.project3DTo2D(p);
|
|
1328
|
+
dataX.push(c2d[1]);
|
|
1329
|
+
dataY.push(c2d[2]);
|
|
1330
|
+
}
|
|
1331
|
+
dataX.push(NaN);
|
|
1332
|
+
dataY.push(NaN);
|
|
1333
|
+
}
|
|
1000
1334
|
|
|
1335
|
+
for (i_v = 0; i_v <= steps_v; i_v++) {
|
|
1336
|
+
v = interval_v[0] + delta_v * i_v;
|
|
1001
1337
|
for (i_u = 0; i_u <= steps_u; i_u++) {
|
|
1002
1338
|
u = interval_u[0] + delta_u * i_u;
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
} else {
|
|
1008
|
-
p = [func[0](u, v), func[1](u, v), func[2](u, v)];
|
|
1009
|
-
}
|
|
1010
|
-
c2d = this.project3DTo2D(p);
|
|
1011
|
-
dataX.push(c2d[1]);
|
|
1012
|
-
dataY.push(c2d[2]);
|
|
1339
|
+
if (Type.isFunction(func)) {
|
|
1340
|
+
p = func(u, v);
|
|
1341
|
+
} else {
|
|
1342
|
+
p = [func[0](u, v), func[1](u, v), func[2](u, v)];
|
|
1013
1343
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1344
|
+
c2d = this.project3DTo2D(p);
|
|
1345
|
+
dataX.push(c2d[1]);
|
|
1346
|
+
dataY.push(c2d[2]);
|
|
1016
1347
|
}
|
|
1348
|
+
dataX.push(NaN);
|
|
1349
|
+
dataY.push(NaN);
|
|
1350
|
+
}
|
|
1017
1351
|
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
for (i_u = 0; i_u <= steps_u; i_u++) {
|
|
1021
|
-
u = interval_u[0] + delta_u * i_u;
|
|
1022
|
-
if (Type.isFunction(func)) {
|
|
1023
|
-
p = func(u, v);
|
|
1024
|
-
} else {
|
|
1025
|
-
p = [func[0](u, v), func[1](u, v), func[2](u, v)];
|
|
1026
|
-
}
|
|
1027
|
-
c2d = this.project3DTo2D(p);
|
|
1028
|
-
dataX.push(c2d[1]);
|
|
1029
|
-
dataY.push(c2d[2]);
|
|
1030
|
-
}
|
|
1031
|
-
dataX.push(NaN);
|
|
1032
|
-
dataY.push(NaN);
|
|
1033
|
-
}
|
|
1352
|
+
return [dataX, dataY];
|
|
1353
|
+
},
|
|
1034
1354
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1355
|
+
/**
|
|
1356
|
+
*
|
|
1357
|
+
*/
|
|
1358
|
+
animateAzimuth: function () {
|
|
1359
|
+
var s = this.az_slide._smin,
|
|
1360
|
+
e = this.az_slide._smax,
|
|
1361
|
+
sdiff = e - s,
|
|
1362
|
+
newVal = this.az_slide.Value() + 0.1;
|
|
1363
|
+
|
|
1364
|
+
this.az_slide.position = (newVal - s) / sdiff;
|
|
1365
|
+
if (this.az_slide.position > 1) {
|
|
1366
|
+
this.az_slide.position = 0.0;
|
|
1367
|
+
}
|
|
1368
|
+
this.board.update();
|
|
1037
1369
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
var s = this.az_slide._smin,
|
|
1043
|
-
e = this.az_slide._smax,
|
|
1044
|
-
sdiff = e - s,
|
|
1045
|
-
newVal = this.az_slide.Value() + 0.1;
|
|
1046
|
-
|
|
1047
|
-
this.az_slide.position = (newVal - s) / sdiff;
|
|
1048
|
-
if (this.az_slide.position > 1) {
|
|
1049
|
-
this.az_slide.position = 0.0;
|
|
1050
|
-
}
|
|
1051
|
-
this.board.update();
|
|
1370
|
+
this.timeoutAzimuth = setTimeout(function () {
|
|
1371
|
+
this.animateAzimuth();
|
|
1372
|
+
}.bind(this), 200);
|
|
1373
|
+
},
|
|
1052
1374
|
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1375
|
+
/**
|
|
1376
|
+
*
|
|
1377
|
+
*/
|
|
1378
|
+
stopAzimuth: function () {
|
|
1379
|
+
clearTimeout(this.timeoutAzimuth);
|
|
1380
|
+
this.timeoutAzimuth = null;
|
|
1381
|
+
},
|
|
1057
1382
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1383
|
+
/**
|
|
1384
|
+
* Check if vertical dragging is enabled and which action is needed.
|
|
1385
|
+
* Default is shiftKey.
|
|
1386
|
+
*
|
|
1387
|
+
* @returns Boolean
|
|
1388
|
+
* @private
|
|
1389
|
+
*/
|
|
1390
|
+
isVerticalDrag: function () {
|
|
1391
|
+
var b = this.board,
|
|
1392
|
+
key;
|
|
1393
|
+
if (!Type.evaluate(this.visProp.verticaldrag.enabled)) {
|
|
1394
|
+
return false;
|
|
1395
|
+
}
|
|
1396
|
+
key = '_' + Type.evaluate(this.visProp.verticaldrag.key) + 'Key';
|
|
1397
|
+
return b[key];
|
|
1398
|
+
},
|
|
1065
1399
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
return false;
|
|
1078
|
-
}
|
|
1079
|
-
key = '_' + Type.evaluate(this.visProp.verticaldrag.key) + 'Key';
|
|
1080
|
-
return b[key];
|
|
1081
|
-
},
|
|
1400
|
+
/**
|
|
1401
|
+
* Sets camera view to the given values.
|
|
1402
|
+
*
|
|
1403
|
+
* @param {Number} az Value of azimuth.
|
|
1404
|
+
* @param {Number} el Value of elevation.
|
|
1405
|
+
* @param {Number} [r] Value of radius.
|
|
1406
|
+
*
|
|
1407
|
+
* @returns {Object} Reference to the view.
|
|
1408
|
+
*/
|
|
1409
|
+
setView: function (az, el, r) {
|
|
1410
|
+
r = r || this.r;
|
|
1082
1411
|
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
* @param {Number} el Value of elevation.
|
|
1088
|
-
* @param {Number} [r] Value of radius.
|
|
1089
|
-
*
|
|
1090
|
-
* @returns {Object} Reference to the view.
|
|
1091
|
-
*/
|
|
1092
|
-
setView: function (az, el, r) {
|
|
1093
|
-
r = r || this.r;
|
|
1412
|
+
this.az_slide.setValue(az);
|
|
1413
|
+
this.el_slide.setValue(el);
|
|
1414
|
+
this.r = r;
|
|
1415
|
+
this.board.update();
|
|
1094
1416
|
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
this.r = r;
|
|
1098
|
-
this.board.update();
|
|
1417
|
+
return this;
|
|
1418
|
+
},
|
|
1099
1419
|
|
|
1100
|
-
|
|
1101
|
-
|
|
1420
|
+
/**
|
|
1421
|
+
* Changes view to the next view stored in the attribute `values`.
|
|
1422
|
+
*
|
|
1423
|
+
* @see View3D#values
|
|
1424
|
+
*
|
|
1425
|
+
* @returns {Object} Reference to the view.
|
|
1426
|
+
*/
|
|
1427
|
+
nextView: function () {
|
|
1428
|
+
var views = Type.evaluate(this.visProp.values),
|
|
1429
|
+
n = this.visProp._currentview;
|
|
1102
1430
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
*
|
|
1106
|
-
* @see View3D#values
|
|
1107
|
-
*
|
|
1108
|
-
* @returns {Object} Reference to the view.
|
|
1109
|
-
*/
|
|
1110
|
-
nextView: function () {
|
|
1111
|
-
var views = Type.evaluate(this.visProp.values),
|
|
1112
|
-
n = this.visProp._currentview;
|
|
1431
|
+
n = (n + 1) % views.length;
|
|
1432
|
+
this.setCurrentView(n);
|
|
1113
1433
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1434
|
+
return this;
|
|
1435
|
+
},
|
|
1116
1436
|
|
|
1117
|
-
|
|
1118
|
-
|
|
1437
|
+
/**
|
|
1438
|
+
* Changes view to the previous view stored in the attribute `values`.
|
|
1439
|
+
*
|
|
1440
|
+
* @see View3D#values
|
|
1441
|
+
*
|
|
1442
|
+
* @returns {Object} Reference to the view.
|
|
1443
|
+
*/
|
|
1444
|
+
previousView: function () {
|
|
1445
|
+
var views = Type.evaluate(this.visProp.values),
|
|
1446
|
+
n = this.visProp._currentview;
|
|
1119
1447
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
*
|
|
1123
|
-
* @see View3D#values
|
|
1124
|
-
*
|
|
1125
|
-
* @returns {Object} Reference to the view.
|
|
1126
|
-
*/
|
|
1127
|
-
previousView: function () {
|
|
1128
|
-
var views = Type.evaluate(this.visProp.values),
|
|
1129
|
-
n = this.visProp._currentview;
|
|
1448
|
+
n = (n + views.length - 1) % views.length;
|
|
1449
|
+
this.setCurrentView(n);
|
|
1130
1450
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1451
|
+
return this;
|
|
1452
|
+
},
|
|
1133
1453
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1454
|
+
/**
|
|
1455
|
+
* Changes view to the determined view stored in the attribute `values`.
|
|
1456
|
+
*
|
|
1457
|
+
* @see View3D#values
|
|
1458
|
+
*
|
|
1459
|
+
* @param {Number} n Index of view in attribute `values`.
|
|
1460
|
+
* @returns {Object} Reference to the view.
|
|
1461
|
+
*/
|
|
1462
|
+
setCurrentView: function (n) {
|
|
1463
|
+
var views = Type.evaluate(this.visProp.values);
|
|
1136
1464
|
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
* @see View3D#values
|
|
1141
|
-
*
|
|
1142
|
-
* @param {Number} n Index of view in attribute `values`.
|
|
1143
|
-
* @returns {Object} Reference to the view.
|
|
1144
|
-
*/
|
|
1145
|
-
setCurrentView: function (n) {
|
|
1146
|
-
var views = Type.evaluate(this.visProp.values);
|
|
1465
|
+
if (n < 0 || n >= views.length) {
|
|
1466
|
+
n = ((n % views.length) + views.length) % views.length;
|
|
1467
|
+
}
|
|
1147
1468
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
}
|
|
1469
|
+
this.setView(views[n][0], views[n][1], views[n][2]);
|
|
1470
|
+
this.visProp._currentview = n;
|
|
1151
1471
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1472
|
+
return this;
|
|
1473
|
+
},
|
|
1154
1474
|
|
|
1475
|
+
/**
|
|
1476
|
+
* Controls the navigation in az direction using either the keyboard or a pointer.
|
|
1477
|
+
*
|
|
1478
|
+
* @private
|
|
1479
|
+
*
|
|
1480
|
+
* @param {event} evt either the keydown or the pointer event
|
|
1481
|
+
* @returns view
|
|
1482
|
+
*/
|
|
1483
|
+
_azEventHandler: function (evt) {
|
|
1484
|
+
var smax = this.az_slide._smax,
|
|
1485
|
+
smin = this.az_slide._smin,
|
|
1486
|
+
speed = (smax - smin) / this.board.canvasWidth * (Type.evaluate(this.visProp.az.pointer.speed)),
|
|
1487
|
+
delta = evt.movementX,
|
|
1488
|
+
az = this.az_slide.Value(),
|
|
1489
|
+
el = this.el_slide.Value();
|
|
1490
|
+
|
|
1491
|
+
// Doesn't allow navigation if another moving event is triggered
|
|
1492
|
+
if (this.board.mode === this.board.BOARD_MODE_DRAG) {
|
|
1155
1493
|
return this;
|
|
1156
|
-
}
|
|
1494
|
+
}
|
|
1157
1495
|
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
*/
|
|
1166
|
-
_azEventHandler: function (evt) {
|
|
1167
|
-
var smax = this.az_slide._smax,
|
|
1168
|
-
smin = this.az_slide._smin,
|
|
1169
|
-
speed = (smax - smin) / this.board.canvasWidth * (Type.evaluate(this.visProp.az.pointer.speed)),
|
|
1170
|
-
delta = evt.movementX,
|
|
1171
|
-
az = this.az_slide.Value(),
|
|
1172
|
-
el = this.el_slide.Value();
|
|
1173
|
-
|
|
1174
|
-
// Doesn't allow navigation if another moving event is triggered
|
|
1175
|
-
if (this.board.mode === this.board.BOARD_MODE_DRAG) {
|
|
1176
|
-
return this;
|
|
1496
|
+
// Calculate new az value if keyboard events are triggered
|
|
1497
|
+
// Plus if right-button, minus if left-button
|
|
1498
|
+
if (Type.evaluate(this.visProp.az.keyboard.enabled)) {
|
|
1499
|
+
if (evt.key === 'ArrowRight') {
|
|
1500
|
+
az = az + Type.evaluate(this.visProp.az.keyboard.step) * Math.PI / 180;
|
|
1501
|
+
} else if (evt.key === 'ArrowLeft') {
|
|
1502
|
+
az = az - Type.evaluate(this.visProp.az.keyboard.step) * Math.PI / 180;
|
|
1177
1503
|
}
|
|
1504
|
+
}
|
|
1178
1505
|
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
if (evt.key === 'ArrowRight') {
|
|
1183
|
-
az = az + Type.evaluate(this.visProp.az.keyboard.step) * Math.PI / 180;
|
|
1184
|
-
} else if (evt.key === 'ArrowLeft') {
|
|
1185
|
-
az = az - Type.evaluate(this.visProp.az.keyboard.step) * Math.PI / 180;
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1506
|
+
if (Type.evaluate(this.visProp.az.pointer.enabled) && (delta !== 0) && evt.key == null) {
|
|
1507
|
+
az += delta * speed;
|
|
1508
|
+
}
|
|
1188
1509
|
|
|
1189
|
-
|
|
1190
|
-
|
|
1510
|
+
// Project the calculated az value to a usable value in the interval [smin,smax]
|
|
1511
|
+
// Use modulo if continuous is true
|
|
1512
|
+
if (Type.evaluate(this.visProp.az.continuous)) {
|
|
1513
|
+
az = Mat.wrap(az, smin, smax);
|
|
1514
|
+
} else {
|
|
1515
|
+
if (az > 0) {
|
|
1516
|
+
az = Math.min(smax, az);
|
|
1517
|
+
} else if (az < 0) {
|
|
1518
|
+
az = Math.max(smin, az);
|
|
1191
1519
|
}
|
|
1520
|
+
}
|
|
1192
1521
|
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
az = (az + smax) % smax;
|
|
1197
|
-
} else {
|
|
1198
|
-
if (az > 0) {
|
|
1199
|
-
az = Math.min(smax, az);
|
|
1200
|
-
} else if (az < 0) {
|
|
1201
|
-
az = Math.max(smin, az);
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1522
|
+
this.setView(az, el);
|
|
1523
|
+
return this;
|
|
1524
|
+
},
|
|
1204
1525
|
|
|
1205
|
-
|
|
1526
|
+
/**
|
|
1527
|
+
* Controls the navigation in el direction using either the keyboard or a pointer.
|
|
1528
|
+
*
|
|
1529
|
+
* @private
|
|
1530
|
+
*
|
|
1531
|
+
* @param {event} evt either the keydown or the pointer event
|
|
1532
|
+
* @returns view
|
|
1533
|
+
*/
|
|
1534
|
+
_elEventHandler: function (evt) {
|
|
1535
|
+
var smax = this.el_slide._smax,
|
|
1536
|
+
smin = this.el_slide._smin,
|
|
1537
|
+
speed = (smax - smin) / this.board.canvasHeight * Type.evaluate(this.visProp.el.pointer.speed),
|
|
1538
|
+
delta = evt.movementY,
|
|
1539
|
+
az = this.az_slide.Value(),
|
|
1540
|
+
el = this.el_slide.Value();
|
|
1541
|
+
|
|
1542
|
+
// Doesn't allow navigation if another moving event is triggered
|
|
1543
|
+
if (this.board.mode === this.board.BOARD_MODE_DRAG) {
|
|
1206
1544
|
return this;
|
|
1207
|
-
}
|
|
1545
|
+
}
|
|
1208
1546
|
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
*/
|
|
1217
|
-
_elEventHandler: function (evt) {
|
|
1218
|
-
var smax = this.el_slide._smax,
|
|
1219
|
-
smin = this.el_slide._smin,
|
|
1220
|
-
speed = (smax - smin) / this.board.canvasHeight * Type.evaluate(this.visProp.el.pointer.speed),
|
|
1221
|
-
delta = evt.movementY,
|
|
1222
|
-
az = this.az_slide.Value(),
|
|
1223
|
-
el = this.el_slide.Value();
|
|
1224
|
-
|
|
1225
|
-
// Doesn't allow navigation if another moving event is triggered
|
|
1226
|
-
if (this.board.mode === this.board.BOARD_MODE_DRAG) {
|
|
1227
|
-
return this;
|
|
1547
|
+
// Calculate new az value if keyboard events are triggered
|
|
1548
|
+
// Plus if down-button, minus if up-button
|
|
1549
|
+
if (Type.evaluate(this.visProp.el.keyboard.enabled)) {
|
|
1550
|
+
if (evt.key === 'ArrowUp') {
|
|
1551
|
+
el = el - Type.evaluate(this.visProp.el.keyboard.step) * Math.PI / 180;
|
|
1552
|
+
} else if (evt.key === 'ArrowDown') {
|
|
1553
|
+
el = el + Type.evaluate(this.visProp.el.keyboard.step) * Math.PI / 180;
|
|
1228
1554
|
}
|
|
1555
|
+
}
|
|
1229
1556
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
if (evt.key === 'ArrowUp') {
|
|
1234
|
-
el = el - Type.evaluate(this.visProp.el.keyboard.step) * Math.PI / 180;
|
|
1235
|
-
} else if (evt.key === 'ArrowDown') {
|
|
1236
|
-
el = el + Type.evaluate(this.visProp.el.keyboard.step) * Math.PI / 180;
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1557
|
+
if (Type.evaluate(this.visProp.el.pointer.enabled) && (delta !== 0) && evt.key == null) {
|
|
1558
|
+
el += delta * speed;
|
|
1559
|
+
}
|
|
1239
1560
|
|
|
1240
|
-
|
|
1241
|
-
|
|
1561
|
+
// Project the calculated el value to a usable value in the interval [smin,smax]
|
|
1562
|
+
// Use modulo if continuous is true and the trackball is disabled
|
|
1563
|
+
if (Type.evaluate(this.visProp.el.continuous) && !this.trackballEnabled) {
|
|
1564
|
+
el = Mat.wrap(el, smin, smax);
|
|
1565
|
+
} else {
|
|
1566
|
+
if (el > 0) {
|
|
1567
|
+
el = Math.min(smax, el);
|
|
1568
|
+
} else if (el < 0) {
|
|
1569
|
+
el = Math.max(smin, el);
|
|
1242
1570
|
}
|
|
1571
|
+
}
|
|
1243
1572
|
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
el = (el + smax) % smax;
|
|
1248
|
-
} else {
|
|
1249
|
-
if (el > 0) {
|
|
1250
|
-
el = Math.min(smax, el);
|
|
1251
|
-
} else if (el < 0) {
|
|
1252
|
-
el = Math.max(smin, el);
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1573
|
+
this.setView(az, el);
|
|
1574
|
+
return this;
|
|
1575
|
+
},
|
|
1255
1576
|
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
this.board.update();
|
|
1577
|
+
/**
|
|
1578
|
+
* Controls the navigation in bank direction using either the keyboard or a pointer.
|
|
1579
|
+
*
|
|
1580
|
+
* @private
|
|
1581
|
+
*
|
|
1582
|
+
* @param {event} evt either the keydown or the pointer event
|
|
1583
|
+
* @returns view
|
|
1584
|
+
*/
|
|
1585
|
+
_bankEventHandler: function (evt) {
|
|
1586
|
+
var smax = this.bank_slide._smax,
|
|
1587
|
+
smin = this.bank_slide._smin,
|
|
1588
|
+
step, speed,
|
|
1589
|
+
delta = evt.deltaY,
|
|
1590
|
+
bank = this.bank_slide.Value();
|
|
1591
|
+
|
|
1592
|
+
// Doesn't allow navigation if another moving event is triggered
|
|
1593
|
+
if (this.board.mode === this.board.BOARD_MODE_DRAG) {
|
|
1274
1594
|
return this;
|
|
1275
|
-
}
|
|
1595
|
+
}
|
|
1276
1596
|
|
|
1277
|
-
|
|
1278
|
-
|
|
1597
|
+
// Calculate new bank value if keyboard events are triggered
|
|
1598
|
+
// Plus if down-button, minus if up-button
|
|
1599
|
+
if (Type.evaluate(this.visProp.bank.keyboard.enabled)) {
|
|
1600
|
+
step = Type.evaluate(this.visProp.bank.keyboard.step) * Math.PI / 180;
|
|
1601
|
+
if (evt.key === '.' || evt.key === '<') {
|
|
1602
|
+
bank -= step;
|
|
1603
|
+
} else if (evt.key === ',' || evt.key === '>') {
|
|
1604
|
+
bank += step;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1279
1607
|
|
|
1280
|
-
|
|
1281
|
-
this.
|
|
1282
|
-
|
|
1608
|
+
if (Type.evaluate(this.visProp.bank.pointer.enabled) && (delta !== 0) && evt.key == null) {
|
|
1609
|
+
speed = (smax - smin) / this.board.canvasHeight * Type.evaluate(this.visProp.bank.pointer.speed);
|
|
1610
|
+
bank += delta * speed;
|
|
1283
1611
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1612
|
+
// prevent the pointer wheel from scrolling the page
|
|
1613
|
+
evt.preventDefault();
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
// Project the calculated bank value to a usable value in the interval [smin,smax]
|
|
1617
|
+
if (Type.evaluate(this.visProp.bank.continuous)) {
|
|
1618
|
+
// in continuous mode, wrap value around slider range
|
|
1619
|
+
bank = Mat.wrap(bank, smin, smax);
|
|
1620
|
+
} else {
|
|
1621
|
+
// in non-continuous mode, clamp value to slider range
|
|
1622
|
+
bank = Mat.clamp(bank, smin, smax);
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
this.bank_slide.setValue(bank);
|
|
1626
|
+
this.board.update();
|
|
1627
|
+
return this;
|
|
1628
|
+
},
|
|
1629
|
+
|
|
1630
|
+
_trackballHandler: function (evt) {
|
|
1631
|
+
var pos = this.board.getMousePosition(evt),
|
|
1632
|
+
x, y, center;
|
|
1633
|
+
|
|
1634
|
+
center = new Coords(Const.COORDS_BY_USER, [this.llftCorner[0] + this.size[0] * 0.5, this.llftCorner[1] + this.size[1] * 0.5], this.board);
|
|
1635
|
+
x = pos[0] - center.scrCoords[1];
|
|
1636
|
+
y = pos[1] - center.scrCoords[2];
|
|
1637
|
+
this._trackball = {
|
|
1638
|
+
dx: evt.movementX,
|
|
1639
|
+
dy: -evt.movementY,
|
|
1640
|
+
x: x,
|
|
1641
|
+
y: -y
|
|
1642
|
+
};
|
|
1643
|
+
this.board.update();
|
|
1644
|
+
return this;
|
|
1645
|
+
},
|
|
1646
|
+
|
|
1647
|
+
pointerDownHandler: function (evt) {
|
|
1648
|
+
var neededButton, neededKey, target;
|
|
1649
|
+
|
|
1650
|
+
this._hasMoveAz = false;
|
|
1651
|
+
this._hasMoveEl = false;
|
|
1652
|
+
this._hasMoveBank = false;
|
|
1653
|
+
this._hasMoveTrackball = false;
|
|
1654
|
+
|
|
1655
|
+
if (this.board.mode !== this.board.BOARD_MODE_NONE) {
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1287
1658
|
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1659
|
+
if (Type.evaluate(this.visProp.trackball.enabled)) {
|
|
1660
|
+
neededButton = Type.evaluate(this.visProp.trackball.button);
|
|
1661
|
+
neededKey = Type.evaluate(this.visProp.trackball.key);
|
|
1291
1662
|
|
|
1292
|
-
|
|
1663
|
+
// Move events for virtual trackball
|
|
1664
|
+
if (
|
|
1665
|
+
(neededButton === -1 || neededButton === evt.button) &&
|
|
1666
|
+
(neededKey === 'none' || (neededKey.indexOf('shift') > -1 && evt.shiftKey) || (neededKey.indexOf('ctrl') > -1 && evt.ctrlKey))
|
|
1667
|
+
) {
|
|
1668
|
+
// If outside is true then the event listener is bound to the document, otherwise to the div
|
|
1669
|
+
target = (Type.evaluate(this.visProp.trackball.outside)) ? document : this.board.containerObj;
|
|
1670
|
+
Env.addEvent(target, 'pointermove', this._trackballHandler, this);
|
|
1671
|
+
this._hasMoveTrackball = true;
|
|
1672
|
+
}
|
|
1673
|
+
} else {
|
|
1674
|
+
if (Type.evaluate(this.visProp.az.pointer.enabled)) {
|
|
1675
|
+
neededButton = Type.evaluate(this.visProp.az.pointer.button);
|
|
1676
|
+
neededKey = Type.evaluate(this.visProp.az.pointer.key);
|
|
1677
|
+
|
|
1678
|
+
// Move events for azimuth
|
|
1293
1679
|
if (
|
|
1294
1680
|
(neededButton === -1 || neededButton === evt.button) &&
|
|
1295
1681
|
(neededKey === 'none' || (neededKey.indexOf('shift') > -1 && evt.shiftKey) || (neededKey.indexOf('ctrl') > -1 && evt.ctrlKey))
|
|
1296
1682
|
) {
|
|
1297
1683
|
// If outside is true then the event listener is bound to the document, otherwise to the div
|
|
1298
|
-
target = (Type.evaluate(this.visProp.
|
|
1299
|
-
Env.addEvent(target, 'pointermove', this.
|
|
1300
|
-
this.
|
|
1301
|
-
}
|
|
1302
|
-
} else {
|
|
1303
|
-
if (Type.evaluate(this.visProp.az.pointer.enabled)) {
|
|
1304
|
-
neededButton = Type.evaluate(this.visProp.az.pointer.button);
|
|
1305
|
-
neededKey = Type.evaluate(this.visProp.az.pointer.key);
|
|
1306
|
-
|
|
1307
|
-
// Move events for azimuth
|
|
1308
|
-
if (
|
|
1309
|
-
(neededButton === -1 || neededButton === evt.button) &&
|
|
1310
|
-
(neededKey === 'none' || (neededKey.indexOf('shift') > -1 && evt.shiftKey) || (neededKey.indexOf('ctrl') > -1 && evt.ctrlKey))
|
|
1311
|
-
) {
|
|
1312
|
-
// If outside is true then the event listener is bound to the document, otherwise to the div
|
|
1313
|
-
target = (Type.evaluate(this.visProp.az.pointer.outside)) ? document : this.board.containerObj;
|
|
1314
|
-
Env.addEvent(target, 'pointermove', this._azEventHandler, this);
|
|
1315
|
-
this._hasMoveAz = true;
|
|
1316
|
-
}
|
|
1684
|
+
target = (Type.evaluate(this.visProp.az.pointer.outside)) ? document : this.board.containerObj;
|
|
1685
|
+
Env.addEvent(target, 'pointermove', this._azEventHandler, this);
|
|
1686
|
+
this._hasMoveAz = true;
|
|
1317
1687
|
}
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
if (Type.evaluate(this.visProp.el.pointer.enabled)) {
|
|
1691
|
+
neededButton = Type.evaluate(this.visProp.el.pointer.button);
|
|
1692
|
+
neededKey = Type.evaluate(this.visProp.el.pointer.key);
|
|
1318
1693
|
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
// If outside is true then the event listener is bound to the document, otherwise to the div
|
|
1329
|
-
target = (Type.evaluate(this.visProp.el.pointer.outside)) ? document : this.board.containerObj;
|
|
1330
|
-
Env.addEvent(target, 'pointermove', this._elEventHandler, this);
|
|
1331
|
-
this._hasMoveEl = true;
|
|
1332
|
-
}
|
|
1694
|
+
// Events for elevation
|
|
1695
|
+
if (
|
|
1696
|
+
(neededButton === -1 || neededButton === evt.button) &&
|
|
1697
|
+
(neededKey === 'none' || (neededKey.indexOf('shift') > -1 && evt.shiftKey) || (neededKey.indexOf('ctrl') > -1 && evt.ctrlKey))
|
|
1698
|
+
) {
|
|
1699
|
+
// If outside is true then the event listener is bound to the document, otherwise to the div
|
|
1700
|
+
target = (Type.evaluate(this.visProp.el.pointer.outside)) ? document : this.board.containerObj;
|
|
1701
|
+
Env.addEvent(target, 'pointermove', this._elEventHandler, this);
|
|
1702
|
+
this._hasMoveEl = true;
|
|
1333
1703
|
}
|
|
1334
1704
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
this._hasMoveTrackball = false;
|
|
1705
|
+
|
|
1706
|
+
if (Type.evaluate(this.visProp.bank.pointer.enabled)) {
|
|
1707
|
+
neededButton = Type.evaluate(this.visProp.bank.pointer.button);
|
|
1708
|
+
neededKey = Type.evaluate(this.visProp.bank.pointer.key);
|
|
1709
|
+
|
|
1710
|
+
// Events for bank
|
|
1711
|
+
if (
|
|
1712
|
+
(neededButton === -1 || neededButton === evt.button) &&
|
|
1713
|
+
(neededKey === 'none' || (neededKey.indexOf('shift') > -1 && evt.shiftKey) || (neededKey.indexOf('ctrl') > -1 && evt.ctrlKey))
|
|
1714
|
+
) {
|
|
1715
|
+
// If `outside` is true, we bind the event listener to
|
|
1716
|
+
// the document. otherwise, we bind it to the div. we
|
|
1717
|
+
// register the event listener as active so it can
|
|
1718
|
+
// prevent the pointer wheel from scrolling the page
|
|
1719
|
+
target = (Type.evaluate(this.visProp.bank.pointer.outside)) ? document : this.board.containerObj;
|
|
1720
|
+
Env.addEvent(target, 'wheel', this._bankEventHandler, this, { passive: false });
|
|
1721
|
+
this._hasMoveBank = true;
|
|
1722
|
+
}
|
|
1354
1723
|
}
|
|
1355
|
-
Env.removeEvent(document, 'pointerup', this.pointerUpHandler, this);
|
|
1356
1724
|
}
|
|
1357
|
-
|
|
1725
|
+
Env.addEvent(document, 'pointerup', this.pointerUpHandler, this);
|
|
1726
|
+
},
|
|
1727
|
+
|
|
1728
|
+
pointerUpHandler: function (evt) {
|
|
1729
|
+
var target;
|
|
1730
|
+
if (this._hasMoveAz) {
|
|
1731
|
+
target = (Type.evaluate(this.visProp.az.pointer.outside)) ? document : this.board.containerObj;
|
|
1732
|
+
Env.removeEvent(target, 'pointermove', this._azEventHandler, this);
|
|
1733
|
+
this._hasMoveAz = false;
|
|
1734
|
+
}
|
|
1735
|
+
if (this._hasMoveEl) {
|
|
1736
|
+
target = (Type.evaluate(this.visProp.el.pointer.outside)) ? document : this.board.containerObj;
|
|
1737
|
+
Env.removeEvent(target, 'pointermove', this._elEventHandler, this);
|
|
1738
|
+
this._hasMoveEl = false;
|
|
1739
|
+
}
|
|
1740
|
+
if (this._hasMoveBank) {
|
|
1741
|
+
target = (Type.evaluate(this.visProp.bank.pointer.outside)) ? document : this.board.containerObj;
|
|
1742
|
+
Env.removeEvent(target, 'wheel', this._bankEventHandler, this);
|
|
1743
|
+
this._hasMoveBank = false;
|
|
1744
|
+
}
|
|
1745
|
+
if (this._hasMoveTrackball) {
|
|
1746
|
+
target = (Type.evaluate(this.visProp.az.pointer.outside)) ? document : this.board.containerObj;
|
|
1747
|
+
Env.removeEvent(target, 'pointermove', this._trackballHandler, this);
|
|
1748
|
+
this._hasMoveTrackball = false;
|
|
1749
|
+
}
|
|
1750
|
+
Env.removeEvent(document, 'pointerup', this.pointerUpHandler, this);
|
|
1751
|
+
}
|
|
1752
|
+
});
|
|
1358
1753
|
|
|
1359
1754
|
/**
|
|
1360
1755
|
* @class This element creates a 3D view.
|
|
@@ -1430,7 +1825,7 @@ JXG.extend(
|
|
|
1430
1825
|
*
|
|
1431
1826
|
*/
|
|
1432
1827
|
JXG.createView3D = function (board, parents, attributes) {
|
|
1433
|
-
var view, attr, attr_az, attr_el,
|
|
1828
|
+
var view, attr, attr_az, attr_el, attr_bank,
|
|
1434
1829
|
x, y, w, h,
|
|
1435
1830
|
coords = parents[0], // llft corner
|
|
1436
1831
|
size = parents[1]; // [w, h]
|
|
@@ -1450,6 +1845,9 @@ JXG.createView3D = function (board, parents, attributes) {
|
|
|
1450
1845
|
attr_el = Type.copyAttributes(attributes, board.options, 'view3d', 'el', 'slider');
|
|
1451
1846
|
attr_el.name = 'el';
|
|
1452
1847
|
|
|
1848
|
+
attr_bank = Type.copyAttributes(attributes, board.options, 'view3d', 'bank', 'slider');
|
|
1849
|
+
attr_bank.name = 'bank';
|
|
1850
|
+
|
|
1453
1851
|
/**
|
|
1454
1852
|
* Slider to adapt azimuth angle
|
|
1455
1853
|
* @name JXG.View3D#az_slide
|
|
@@ -1468,6 +1866,8 @@ JXG.createView3D = function (board, parents, attributes) {
|
|
|
1468
1866
|
],
|
|
1469
1867
|
attr_az
|
|
1470
1868
|
);
|
|
1869
|
+
// view.az_slide.inherits.push(view);
|
|
1870
|
+
view.inherits.push(view.az_slide);
|
|
1471
1871
|
|
|
1472
1872
|
/**
|
|
1473
1873
|
* Slider to adapt elevation angle
|
|
@@ -1487,6 +1887,28 @@ JXG.createView3D = function (board, parents, attributes) {
|
|
|
1487
1887
|
],
|
|
1488
1888
|
attr_el
|
|
1489
1889
|
);
|
|
1890
|
+
view.inherits.push(view.el_slide);
|
|
1891
|
+
|
|
1892
|
+
/**
|
|
1893
|
+
* Slider to adjust bank angle
|
|
1894
|
+
*
|
|
1895
|
+
* @name JXG.View3D#bank_slide
|
|
1896
|
+
* @type {Slider}
|
|
1897
|
+
*/
|
|
1898
|
+
view.bank_slide = board.create(
|
|
1899
|
+
'slider',
|
|
1900
|
+
[
|
|
1901
|
+
[x - 1, y + h + 2],
|
|
1902
|
+
[x + w + 1, y + h + 2],
|
|
1903
|
+
[
|
|
1904
|
+
Type.evaluate(attr_bank.min),
|
|
1905
|
+
Type.evaluate(attr_bank.start),
|
|
1906
|
+
Type.evaluate(attr_bank.max)
|
|
1907
|
+
]
|
|
1908
|
+
],
|
|
1909
|
+
attr_bank
|
|
1910
|
+
);
|
|
1911
|
+
view.inherits.push(view.bank_slide);
|
|
1490
1912
|
|
|
1491
1913
|
view.board.highlightInfobox = function (x, y, el) {
|
|
1492
1914
|
var d, i, c3d, foot,
|
|
@@ -1536,6 +1958,7 @@ JXG.createView3D = function (board, parents, attributes) {
|
|
|
1536
1958
|
}
|
|
1537
1959
|
};
|
|
1538
1960
|
|
|
1961
|
+
|
|
1539
1962
|
// Hack needed to enable addEvent for view3D:
|
|
1540
1963
|
view.BOARD_MODE_NONE = 0x0000;
|
|
1541
1964
|
|
|
@@ -1568,6 +1991,13 @@ JXG.createView3D = function (board, parents, attributes) {
|
|
|
1568
1991
|
catchEvt = true;
|
|
1569
1992
|
}
|
|
1570
1993
|
}
|
|
1994
|
+
if (Type.evaluate(view.visProp.bank.keyboard.enabled) && (event.key === ',' || event.key === '<' || event.key === '.' || event.key === '>')) {
|
|
1995
|
+
neededKey = Type.evaluate(view.visProp.bank.keyboard.key);
|
|
1996
|
+
if (neededKey === 'none' || (neededKey.indexOf('shift') > -1 && event.shiftKey) || (neededKey.indexOf('ctrl') > -1 && event.ctrlKey)) {
|
|
1997
|
+
view._bankEventHandler(event);
|
|
1998
|
+
catchEvt = true;
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
1571
2001
|
if (event.key === 'PageUp') {
|
|
1572
2002
|
view.nextView();
|
|
1573
2003
|
catchEvt = true;
|
|
@@ -1587,6 +2017,13 @@ JXG.createView3D = function (board, parents, attributes) {
|
|
|
1587
2017
|
// Add events for the pointer navigation
|
|
1588
2018
|
Env.addEvent(board.containerObj, 'pointerdown', view.pointerDownHandler, view);
|
|
1589
2019
|
|
|
2020
|
+
// Initialize view rotation matrix
|
|
2021
|
+
view.getAnglesFromSliders();
|
|
2022
|
+
view.matrix3DRot = view.getRotationFromAngles();
|
|
2023
|
+
|
|
2024
|
+
// override angle slider bounds when trackball navigation is enabled
|
|
2025
|
+
view.updateAngleSliderBounds();
|
|
2026
|
+
|
|
1590
2027
|
view.board.update();
|
|
1591
2028
|
|
|
1592
2029
|
return view;
|