@mcolabs/threebox-plugin 4.0.0

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.
@@ -0,0 +1,1201 @@
1
+ /**
2
+ * @author peterqliu / https://github.com/peterqliu
3
+ * @author jscastro / https://github.com/jscastro76
4
+ */
5
+
6
+ import * as THREE from 'three';
7
+ import CameraSync from './camera/CameraSync.js';
8
+ import utils from './utils/utils.js';
9
+ import SunCalc from './utils/suncalc.js';
10
+ import ThreeboxConstants from './utils/constants.js';
11
+ import Objects from './objects/objects.js';
12
+ import material from './utils/material.js';
13
+ import sphere from './objects/sphere.js';
14
+ import extrusion from './objects/extrusion.js';
15
+ import label from './objects/label.js';
16
+ import tooltip from './objects/tooltip.js';
17
+ import loader from './objects/loadObj.js';
18
+ import Object3D from './objects/Object3D.js';
19
+ import line from './objects/line.js';
20
+ import tube from './objects/tube.js';
21
+ import LabelRenderer from './objects/LabelRenderer.js';
22
+ import BuildingShadows from './objects/effects/BuildingShadows.js';
23
+
24
+ function Threebox(map, glContext, options){
25
+
26
+ this.init(map, glContext, options);
27
+
28
+ };
29
+
30
+ Threebox.prototype = {
31
+
32
+ repaint: function () {
33
+ this.map.repaint = true;
34
+ },
35
+
36
+ /**
37
+ * Threebox constructor init method
38
+ * @param {mapboxgl.map} map
39
+ * @param {WebGLRenderingContext} glContext
40
+ * @param {defaultOptions} options
41
+ */
42
+ init: function (map, glContext, options) {
43
+
44
+ // apply starter options
45
+ this.options = utils._validate(options || {}, defaultOptions);
46
+
47
+ this.map = map;
48
+ this.map.tb = this; //[jscastro] needed if we want to queryRenderedFeatures from map.onload
49
+
50
+ this.objects = new Objects();
51
+
52
+ this.mapboxVersion = parseFloat(this.map.version);
53
+
54
+ // Set up a THREE.js scene
55
+ this.renderer = new THREE.WebGLRenderer({
56
+ alpha: true,
57
+ antialias: true,
58
+ preserveDrawingBuffer: options.preserveDrawingBuffer,
59
+ canvas: map.getCanvas(),
60
+ context: glContext
61
+ });
62
+
63
+ this.renderer.setPixelRatio(window.devicePixelRatio);
64
+ this.renderer.setSize(this.map.getCanvas().clientWidth, this.map.getCanvas().clientHeight);
65
+ this.renderer.outputColorSpace = THREE.SRGBColorSpace;
66
+ this.renderer.autoClear = false;
67
+
68
+ // [jscastro] set labelRendered
69
+ this.labelRenderer = new LabelRenderer(this.map);
70
+
71
+ this.scene = new THREE.Scene();
72
+ this.world = new THREE.Group();
73
+ this.world.name = "world";
74
+ this.scene.add(this.world);
75
+
76
+ this.objectsCache = new Map();
77
+ this.zoomLayers = [];
78
+
79
+ this.fov = this.options.fov;
80
+ this.orthographic = this.options.orthographic || false;
81
+
82
+ //raycaster for mouse events
83
+ this.raycaster = new THREE.Raycaster();
84
+ this.raycaster.layers.set(0);
85
+ //this.raycaster.params.Points.threshold = 100;
86
+
87
+ this.mapCenter = this.map.getCenter();
88
+ this.mapCenterUnits = utils.projectToWorld([this.mapCenter.lng, this.mapCenter.lat]);
89
+ this.lightDateTime = new Date();
90
+ this.lightLng = this.mapCenter.lng;
91
+ this.lightLat = this.mapCenter.lat;
92
+ this.sunPosition;
93
+ this.rotationStep = 5;// degrees step size for rotation
94
+ this.gridStep = 6;// decimals to adjust the lnglat grid step, 6 = 11.1cm
95
+ this.altitudeStep = 0.1; // 1px = 0.1m = 10cm
96
+ this.defaultCursor = 'default';
97
+
98
+ this.lights = this.initLights;
99
+ if (this.options.defaultLights) this.defaultLights();
100
+ if (this.options.realSunlight) this.realSunlight(this.options.realSunlightHelper);
101
+ this.skyLayerName = 'sky-layer';
102
+ this.terrainSourceName = 'mapbox-dem';
103
+ this.terrainExaggeration = 1.0;
104
+ this.terrainLayerName = '';
105
+ this.enableSelectingFeatures = this.options.enableSelectingFeatures || false;
106
+ this.enableSelectingObjects = this.options.enableSelectingObjects || false;
107
+ this.enableDraggingObjects = this.options.enableDraggingObjects || false;
108
+ this.enableRotatingObjects = this.options.enableRotatingObjects || false;
109
+ this.enableTooltips = this.options.enableTooltips || false;
110
+ this.multiLayer = this.options.multiLayer || false;
111
+ this.enableHelpTooltips = this.options.enableHelpTooltips || false;
112
+
113
+ this.map.on('style.load', function () {
114
+ this.tb.zoomLayers = [];
115
+ //[jscastro] if multiLayer, create a by default layer in the map, so tb.update won't be needed in client side to avoid duplicating calls to render
116
+ if (this.tb.options.multiLayer) this.addLayer({ id: "threebox_layer", type: 'custom', renderingMode: '3d', map: this, onAdd: function (map, gl) { }, render: function (gl, matrix) { this.map.tb.update(); } })
117
+
118
+ this.once('idle', () => {
119
+ this.tb.setObjectsScale();
120
+ });
121
+
122
+ if (this.tb.options.sky) {
123
+ this.tb.sky = true;
124
+ }
125
+ if (this.tb.options.terrain) {
126
+ this.tb.terrain = true;
127
+ }
128
+ let rasterLayers = ['satellite', 'mapbox-mapbox-satellite', 'satelliteLayer'];
129
+ rasterLayers.forEach((l) => {
130
+ if (this.getLayer(l)) this.tb.terrainLayerName = l;
131
+ })
132
+ });
133
+
134
+ //[jscastro] new event map on load
135
+ this.map.on('load', function () {
136
+
137
+ //[jscastro] new fields to manage events on map
138
+ this.selectedObject; //selected object through click
139
+ this.selectedFeature;//selected state id for extrusion layer features
140
+ this.draggedObject; //dragged object through mousedown + mousemove
141
+ let draggedAction; //dragged action to notify frontend
142
+ this.overedObject; //overed object through mouseover
143
+ this.overedFeature; //overed state for extrusion layer features
144
+
145
+ let canvas = this.getCanvasContainer();
146
+ this.getCanvasContainer().style.cursor = this.tb.defaultCursor;
147
+ // Variable to hold the starting xy coordinates
148
+ // when 'mousedown' occured.
149
+ let start;
150
+
151
+ //when object selected
152
+ let startCoords = [];
153
+
154
+ let lngDiff; // difference between cursor and model left corner
155
+ let latDiff; // difference between cursor and model bottom corner
156
+ let altDiff; // difference between cursor and model height
157
+ let rotationDiff;
158
+
159
+ // Return the xy coordinates of the mouse position
160
+ function mousePos(e) {
161
+ var rect = canvas.getBoundingClientRect();
162
+ return {
163
+ x: e.originalEvent.clientX - rect.left - canvas.clientLeft,
164
+ y: e.originalEvent.clientY - rect.top - canvas.clientTop
165
+ };
166
+ }
167
+
168
+ this.unselectObject = function () {
169
+ //deselect, reset and return
170
+ this.selectedObject.selected = false;
171
+ this.selectedObject = null;
172
+ }
173
+
174
+ this.outObject = function () {
175
+ this.overedObject.over = false;
176
+ this.overedObject = null;
177
+ }
178
+
179
+ this.unselectFeature = function (f) {
180
+ if (typeof f.id == 'undefined') return;
181
+ this.setFeatureState(
182
+ { source: f.source, sourceLayer: f.sourceLayer, id: f.id },
183
+ { select: false }
184
+ );
185
+
186
+ this.removeTooltip(f);
187
+ f = this.queryRenderedFeatures({ layers: [f.layer.id], filter: ["==", ['id'], f.id] })[0];
188
+ // Dispatch new event f for unselected
189
+ if (f) this.fire('SelectedFeatureChange', { detail: f });
190
+ this.selectedFeature = null;
191
+
192
+ }
193
+
194
+ this.selectFeature = function(f) {
195
+ this.selectedFeature = f;
196
+ this.setFeatureState(
197
+ { source: this.selectedFeature.source, sourceLayer: this.selectedFeature.sourceLayer, id: this.selectedFeature.id },
198
+ { select: true }
199
+ );
200
+ this.selectedFeature = this.queryRenderedFeatures({ layers: [this.selectedFeature.layer.id], filter: ["==", ['id'], this.selectedFeature.id] })[0];
201
+ this.addTooltip(this.selectedFeature);
202
+ // Dispatch new event SelectedFeature for selected
203
+ this.fire('SelectedFeatureChange', { detail: this.selectedFeature });
204
+
205
+ }
206
+
207
+ this.outFeature = function(f) {
208
+ if (this.overedFeature && typeof this.overedFeature != 'undefined' && this.overedFeature.id != f) {
209
+ map.setFeatureState(
210
+ { source: this.overedFeature.source, sourceLayer: this.overedFeature.sourceLayer, id: this.overedFeature.id },
211
+ { hover: false }
212
+ );
213
+ this.removeTooltip(this.overedFeature);
214
+ this.overedFeature = null;
215
+ }
216
+ }
217
+
218
+ this.addTooltip = function(f) {
219
+ if (!this.tb.enableTooltips) return;
220
+ let coordinates = this.tb.getFeatureCenter(f);
221
+ let t = this.tb.tooltip({
222
+ text: f.properties.name || f.id || f.type,
223
+ mapboxStyle: true,
224
+ feature: f
225
+ });
226
+ t.setCoords(coordinates);
227
+ this.tb.add(t, f.layer.id);
228
+ f.tooltip = t;
229
+ f.tooltip.tooltip.visible = true;
230
+ }
231
+
232
+ this.removeTooltip = function(f) {
233
+ if (f.tooltip) {
234
+ f.tooltip.visibility = false;
235
+ this.tb.remove(f.tooltip);
236
+ f.tooltip = null;
237
+ }
238
+ }
239
+
240
+ map.onContextMenu = function (e) {
241
+ alert('contextMenu'); //TODO: implement a callback
242
+ }
243
+
244
+ // onclick function
245
+ this.onClick = function (e) {
246
+ let intersectionExists
247
+ let intersects = [];
248
+ if (map.tb.enableSelectingObjects) {
249
+ //raycast only if we are in a custom layer, for other layers go to the else, this avoids duplicated calls to raycaster
250
+ intersects = this.tb.queryRenderedFeatures(e.point);
251
+ }
252
+ intersectionExists = typeof intersects[0] == 'object';
253
+ // if intersect exists, highlight it
254
+ if (intersectionExists) {
255
+
256
+ let nearestObject = Threebox.prototype.findParent3DObject(intersects[0]);
257
+
258
+ if (nearestObject) {
259
+ //if extrusion object selected, unselect
260
+ if (this.selectedFeature) {
261
+ this.unselectFeature(this.selectedFeature);
262
+ }
263
+ //if not selected yet, select it
264
+ if (!this.selectedObject) {
265
+ this.selectedObject = nearestObject;
266
+ this.selectedObject.selected = true;
267
+ }
268
+ else if (this.selectedObject.uuid != nearestObject.uuid) {
269
+ //it's a different object, restore the previous and select the new one
270
+ this.selectedObject.selected = false;
271
+ nearestObject.selected = true;
272
+ this.selectedObject = nearestObject;
273
+
274
+ } else if (this.selectedObject.uuid == nearestObject.uuid) {
275
+ //deselect, reset and return
276
+ this.unselectObject();
277
+ return;
278
+ }
279
+
280
+ // fire the Wireframed event to notify UI status change
281
+ this.selectedObject.dispatchEvent({ type: 'Wireframed', detail: this.selectedObject });
282
+ this.selectedObject.dispatchEvent({ type: 'IsPlayingChanged', detail: this.selectedObject });
283
+
284
+ this.repaint = true;
285
+ e.preventDefault();
286
+ }
287
+ }
288
+ else {
289
+ let features = [];
290
+ if (map.tb.enableSelectingFeatures) {
291
+ features = this.queryRenderedFeatures(e.point);
292
+ }
293
+ //now let's check the extrusion layer objects
294
+ if (features.length > 0) {
295
+
296
+ if (features[0].layer.type == 'fill-extrusion' && typeof features[0].id != 'undefined') {
297
+
298
+ //if 3D object selected, unselect
299
+ if (this.selectedObject) {
300
+ this.unselectObject();
301
+ }
302
+
303
+ //if not selected yet, select it
304
+ if (!this.selectedFeature) {
305
+ this.selectFeature(features[0])
306
+ }
307
+ else if (this.selectedFeature.id != features[0].id) {
308
+ //it's a different feature, restore the previous and select the new one
309
+ this.unselectFeature(this.selectedFeature);
310
+ this.selectFeature(features[0])
311
+
312
+ } else if (this.selectedFeature.id == features[0].id) {
313
+ //deselect, reset and return
314
+ this.unselectFeature(this.selectedFeature);
315
+ return;
316
+ }
317
+
318
+ }
319
+ }
320
+ }
321
+ }
322
+
323
+ this.onMouseMove = function (e) {
324
+
325
+ // Capture the ongoing xy coordinates
326
+ let current = mousePos(e);
327
+
328
+ this.getCanvasContainer().style.cursor = this.tb.defaultCursor;
329
+ //check if being rotated
330
+ if (e.originalEvent.altKey && this.draggedObject) {
331
+
332
+ if (!map.tb.enableRotatingObjects) return;
333
+ draggedAction = 'rotate';
334
+ // Set a UI indicator for dragging.
335
+ this.getCanvasContainer().style.cursor = 'move';
336
+ var minX = Math.min(start.x, current.x),
337
+ maxX = Math.max(start.x, current.x),
338
+ minY = Math.min(start.y, current.y),
339
+ maxY = Math.max(start.y, current.y);
340
+ //set the movement fluid we rotate only every 10px moved, in steps of 10 degrees up to 360
341
+ let rotation = { x: 0, y: 0, z: (Math.round(rotationDiff[2] + (~~((current.x - start.x) / this.tb.rotationStep) % 360 * this.tb.rotationStep) % 360)) };
342
+ //now rotate the model depending the axis
343
+ this.draggedObject.setRotation(rotation);
344
+ if (map.tb.enableHelpTooltips) this.draggedObject.addHelp("rot: " + rotation.z + "°");
345
+ //this.draggedObject.setRotationAxis(rotation);
346
+ return;
347
+ }
348
+
349
+ //check if being moved
350
+ if (e.originalEvent.shiftKey && this.draggedObject) {
351
+ if (!map.tb.enableDraggingObjects) return;
352
+
353
+ draggedAction = 'translate';
354
+ // Set a UI indicator for dragging.
355
+ this.getCanvasContainer().style.cursor = 'move';
356
+ // Capture the first xy coordinates, height must be the same to move on the same plane
357
+ let coords = e.lngLat;
358
+ let options = [Number((coords.lng + lngDiff).toFixed(this.tb.gridStep)), Number((coords.lat + latDiff).toFixed(this.tb.gridStep)), this.draggedObject.modelHeight];
359
+ this.draggedObject.setCoords(options);
360
+ if (map.tb.enableHelpTooltips) this.draggedObject.addHelp("lng: " + options[0] + "°, lat: " + options[1] + "°");
361
+ return;
362
+ }
363
+
364
+ //check if being moved on altitude
365
+ if (e.originalEvent.ctrlKey && this.draggedObject) {
366
+ if (!map.tb.enableDraggingObjects) return;
367
+ draggedAction = 'altitude';
368
+ // Set a UI indicator for dragging.
369
+ this.getCanvasContainer().style.cursor = 'move';
370
+ // Capture the first xy coordinates, height must be the same to move on the same plane
371
+ let now = (e.point.y * this.tb.altitudeStep);
372
+ let options = [this.draggedObject.coordinates[0], this.draggedObject.coordinates[1], Number((- now - altDiff).toFixed(this.tb.gridStep))];
373
+ this.draggedObject.setCoords(options);
374
+ if (map.tb.enableHelpTooltips) this.draggedObject.addHelp("alt: " + options[2] + "m");
375
+ return;
376
+ }
377
+
378
+ let intersectionExists
379
+ let intersects = [];
380
+
381
+ if (map.tb.enableSelectingObjects) {
382
+ // calculate objects intersecting the picking ray
383
+ intersects = this.tb.queryRenderedFeatures(e.point);
384
+ }
385
+ intersectionExists = typeof intersects[0] == 'object';
386
+
387
+ // if intersect exists, highlight it, if not check the extrusion layer
388
+ if (intersectionExists) {
389
+ let nearestObject = Threebox.prototype.findParent3DObject(intersects[0]);
390
+ if (nearestObject) {
391
+ this.outFeature(this.overedFeature);
392
+ this.getCanvasContainer().style.cursor = 'pointer';
393
+ if (!this.selectedObject || nearestObject.uuid != this.selectedObject.uuid) {
394
+ if (this.overedObject && this.overedObject.uuid != nearestObject.uuid) {
395
+ this.outObject();
396
+ }
397
+ nearestObject.over = true;
398
+ this.overedObject = nearestObject;
399
+ } else if (this.selectedObject && nearestObject.uuid == this.selectedObject.uuid) {
400
+ nearestObject.over = true;
401
+ this.overedObject = nearestObject;
402
+ }
403
+ this.repaint = true;
404
+ e.preventDefault();
405
+ }
406
+ }
407
+ else {
408
+ //clean the object overed
409
+ if (this.overedObject) { this.outObject(); }
410
+ //now let's check the extrusion layer objects
411
+ let features = [];
412
+ if (map.tb.enableSelectingFeatures) {
413
+ features = this.queryRenderedFeatures(e.point);
414
+ }
415
+ if (features.length > 0) {
416
+ this.outFeature(features[0]);
417
+
418
+ if (features[0].layer.type == 'fill-extrusion' && typeof features[0].id != 'undefined') {
419
+ if ((!this.selectedFeature || this.selectedFeature.id != features[0].id)) {
420
+ this.getCanvasContainer().style.cursor = 'pointer';
421
+ this.overedFeature = features[0];
422
+ this.setFeatureState(
423
+ { source: this.overedFeature.source, sourceLayer: this.overedFeature.sourceLayer, id: this.overedFeature.id },
424
+ { hover: true }
425
+ );
426
+ this.overedFeature = map.queryRenderedFeatures({ layers: [this.overedFeature.layer.id], filter: ["==", ['id'], this.overedFeature.id] })[0];
427
+ this.addTooltip(this.overedFeature);
428
+
429
+ }
430
+ }
431
+ }
432
+ }
433
+
434
+ }
435
+
436
+ this.onMouseDown = function (e) {
437
+
438
+ // Continue the rest of the function shiftkey or altkey are pressed, and if object is selected
439
+ if (!((e.originalEvent.shiftKey || e.originalEvent.altKey || e.originalEvent.ctrlKey) && e.originalEvent.button === 0 && this.selectedObject)) return;
440
+ if (!map.tb.enableDraggingObjects && !map.tb.enableRotatingObjects) return;
441
+
442
+ e.preventDefault();
443
+
444
+ map.getCanvasContainer().style.cursor = 'move';
445
+
446
+ // Disable default drag zooming when the shift key is held down.
447
+ //map.dragPan.disable();
448
+
449
+ // Call functions for the following events
450
+ map.once('mouseup', this.onMouseUp);
451
+ //map.once('mouseout', this.onMouseUp);
452
+
453
+ // move the selected object
454
+ this.draggedObject = this.selectedObject;
455
+
456
+ // Capture the first xy coordinates
457
+ start = mousePos(e);
458
+ startCoords = this.draggedObject.coordinates;
459
+
460
+ rotationDiff = utils.degreeify(this.draggedObject.rotation);
461
+ lngDiff = startCoords[0] - e.lngLat.lng;
462
+ latDiff = startCoords[1] - e.lngLat.lat;
463
+ altDiff = -this.draggedObject.modelHeight - (e.point.y * this.tb.altitudeStep);
464
+ }
465
+
466
+ this.onMouseUp = function (e) {
467
+
468
+ // Set a UI indicator for dragging.
469
+ this.getCanvasContainer().style.cursor = this.tb.defaultCursor;
470
+
471
+ // Remove these events now that finish has been called.
472
+ //map.off('mousemove', onMouseMove);
473
+ this.off('mouseup', this.onMouseUp);
474
+ this.off('mouseout', this.onMouseUp);
475
+ this.dragPan.enable();
476
+
477
+ if (this.draggedObject) {
478
+ this.draggedObject.dispatchEvent({ type: 'ObjectDragged', detail: { draggedObject: this.draggedObject, draggedAction: draggedAction } });
479
+ this.draggedObject.removeHelp();
480
+ this.draggedObject = null;
481
+ draggedAction = null;
482
+ };
483
+ }
484
+
485
+ this.onMouseOut = function (e) {
486
+ if (this.overedFeature) {
487
+ let features = this.queryRenderedFeatures(e.point);
488
+ if (features.length > 0 && this.overedFeature.id != features[0].id) {
489
+ this.getCanvasContainer().style.cursor = this.tb.defaultCursor;
490
+ //only unover when new feature is another
491
+ this.outFeature(features[0]);
492
+ }
493
+ }
494
+ }
495
+
496
+ this.onZoom = function (e) {
497
+ this.tb.zoomLayers.forEach((l) => { this.tb.toggleLayer(l); });
498
+ this.tb.setObjectsScale();
499
+ }
500
+
501
+ let ctrlDown = false;
502
+ let shiftDown = false;
503
+ let ctrlKey = 17, cmdKey = 91, shiftKey = 16, sK = 83, dK = 68;
504
+
505
+ function onKeyDown(e) {
506
+
507
+ if (e.which === ctrlKey || e.which === cmdKey) ctrlDown = true;
508
+ if (e.which === shiftKey) shiftDown = true;
509
+ let obj = this.selectedObject;
510
+ if (shiftDown && e.which === sK && obj) {
511
+ //shift + sS
512
+ let dc = utils.toDecimal;
513
+ if (!obj.help) {
514
+ let s = obj.modelSize;
515
+ let sf = 1;
516
+ if (obj.userData.units !== 'meters') {
517
+ //if not meters, calculate scale to the current lat
518
+ sf = utils.projectedUnitsPerMeter(obj.coordinates[1]);
519
+ if (!sf) { sf = 1; };
520
+ sf = dc(sf, 7);
521
+ }
522
+
523
+ if (map.tb.enableHelpTooltips) obj.addHelp("size(m): " + dc((s.x / sf), 3) + " W, " + dc((s.y / sf), 3) + " L, " + dc((s.z / sf), 3) + " H");
524
+ this.repaint = true;
525
+ }
526
+ else {
527
+ obj.removeHelp();
528
+ }
529
+ return false;
530
+ }
531
+
532
+ };
533
+
534
+ function onKeyUp (e) {
535
+ if (e.which == ctrlKey || e.which == cmdKey) ctrlDown = false;
536
+ if (e.which === shiftKey) shiftDown = false;
537
+ }
538
+
539
+ //listener to the events
540
+ //this.on('contextmenu', map.onContextMenu);
541
+ this.on('click', this.onClick);
542
+ this.on('mousemove', this.onMouseMove);
543
+ this.on('mouseout', this.onMouseOut)
544
+ this.on('mousedown', this.onMouseDown);
545
+ this.on('zoom', this.onZoom);
546
+ this.on('zoomend', this.onZoom);
547
+
548
+ document.addEventListener('keydown', onKeyDown.bind(this), true);
549
+ document.addEventListener('keyup', onKeyUp.bind(this));
550
+
551
+ });
552
+
553
+ },
554
+
555
+ //[jscastro] added property to manage an athmospheric sky layer
556
+ get sky() { return this.options.sky; },
557
+ set sky(value) {
558
+ if (value) {
559
+ this.createSkyLayer();
560
+ }
561
+ else {
562
+ this.removeLayer(this.skyLayerName);
563
+ }
564
+ this.options.sky = value;
565
+ },
566
+
567
+ //[jscastro] added property to manage an athmospheric sky layer
568
+ get terrain() { return this.options.terrain; },
569
+ set terrain(value) {
570
+ this.terrainLayerName = '';
571
+ if (value) {
572
+ this.createTerrainLayer();
573
+ }
574
+ else {
575
+ if (this.mapboxVersion < 2.0) { console.warn("Terrain layer are only supported by Mapbox-gl-js > v2.0"); return };
576
+
577
+ if (this.map.getTerrain()) {
578
+ this.map.setTerrain(null); //
579
+ this.map.removeSource(this.terrainSourceName);
580
+ }
581
+ }
582
+ this.options.terrain = value;
583
+ },
584
+
585
+ //[jscastro] added property to manage FOV for perspective camera
586
+ get fov() { return this.options.fov;},
587
+ set fov(value) {
588
+ if (this.camera instanceof THREE.PerspectiveCamera && this.options.fov !== value) {
589
+ this.map.transform.fov = value;
590
+ this.camera.fov = this.map.transform.fov;
591
+ this.cameraSync.setupCamera();
592
+ this.map.repaint = true;
593
+ this.options.fov = value;
594
+ }
595
+
596
+ },
597
+
598
+ //[jscastro] added property to manage camera type
599
+ get orthographic() { return this.options.orthographic; },
600
+ set orthographic(value) {
601
+ const h = this.map.getCanvas().clientHeight;
602
+ const w = this.map.getCanvas().clientWidth;
603
+ if (value) {
604
+ this.map.transform.fov = 0;
605
+ this.camera = new THREE.OrthographicCamera(w / - 2, w / 2, h / 2, h / - 2, 0.1, 1e21);
606
+ } else {
607
+ this.map.transform.fov = this.fov;
608
+ this.camera = new THREE.PerspectiveCamera(this.map.transform.fov, w / h, 0.1, 1e21);
609
+ }
610
+ this.camera.layers.enable(0);
611
+ this.camera.layers.enable(1);
612
+ // The CameraSync object will keep the Mapbox and THREE.js camera movements in sync.
613
+ // It requires a world group to scale as we zoom in. Rotation is handled in the camera's
614
+ // projection matrix itself (as is field of view and near/far clipping)
615
+ // It automatically registers to listen for move events on the map so we don't need to do that here
616
+ this.cameraSync = new CameraSync(this.map, this.camera, this.world);
617
+ this.map.repaint = true; // repaint the map
618
+ this.options.orthographic = value;
619
+
620
+ },
621
+
622
+ //[jscastro] method to create an athmospheric sky layer
623
+ createSkyLayer: function () {
624
+ if (this.mapboxVersion < 2.0) { console.warn("Sky layer are only supported by Mapbox-gl-js > v2.0"); this.options.sky = false; return };
625
+
626
+ let layer = this.map.getLayer(this.skyLayerName);
627
+ if (!layer) {
628
+ this.map.addLayer({
629
+ 'id': this.skyLayerName,
630
+ 'type': 'sky',
631
+ 'paint': {
632
+ 'sky-opacity': [
633
+ 'interpolate',
634
+ ['linear'],
635
+ ['zoom'],
636
+ 0,
637
+ 0,
638
+ 5,
639
+ 0.3,
640
+ 8,
641
+ 1
642
+ ],
643
+ // set up the sky layer for atmospheric scattering
644
+ 'sky-type': 'atmosphere',
645
+ // explicitly set the position of the sun rather than allowing the sun to be attached to the main light source
646
+ 'sky-atmosphere-sun': this.getSunSky(this.lightDateTime),
647
+ // set the intensity of the sun as a light source (0-100 with higher values corresponding to brighter skies)
648
+ 'sky-atmosphere-sun-intensity': 10
649
+ }
650
+ });
651
+
652
+ this.map.once('idle', () => {
653
+ this.setSunlight();
654
+ this.repaint();
655
+ });
656
+ }
657
+ },
658
+
659
+ //[jscastro] method to create a terrain layer
660
+ createTerrainLayer: function () {
661
+ if (this.mapboxVersion < 2.0) { console.warn("Terrain layer are only supported by Mapbox-gl-js > v2.0"); this.options.terrain = false; return };
662
+ let layer = this.map.getTerrain();
663
+ if (!layer) {
664
+ // add the DEM source as a terrain layer with exaggerated height
665
+ this.map.addSource(this.terrainSourceName, {
666
+ 'type': 'raster-dem',
667
+ 'url': 'mapbox://mapbox.mapbox-terrain-dem-v1',
668
+ 'tileSize': 512,
669
+ 'maxzoom': 14
670
+ });
671
+ this.map.setTerrain({ 'source': this.terrainSourceName, 'exaggeration': this.terrainExaggeration });
672
+ this.map.once('idle', () => {
673
+ //alert("idle");
674
+ this.cameraSync.updateCamera();
675
+ this.repaint();
676
+ });
677
+
678
+ }
679
+ },
680
+
681
+ // Objects
682
+ sphere: function (options) {
683
+ this.setDefaultView(options, this.options);
684
+ return sphere(options, this.world)
685
+ },
686
+
687
+ line: line,
688
+
689
+ label: label,
690
+
691
+ tooltip: tooltip,
692
+
693
+ tube: function (options) {
694
+ this.setDefaultView(options, this.options);
695
+ return tube(options, this.world)
696
+ },
697
+
698
+ extrusion: function (options) {
699
+ this.setDefaultView(options, this.options);
700
+ return extrusion(options);
701
+ },
702
+
703
+ Object3D: function (options) {
704
+ this.setDefaultView(options, this.options);
705
+ return Object3D(options)
706
+ },
707
+
708
+ loadObj: async function loadObj(options, cb) {
709
+ this.setDefaultView(options, this.options);
710
+ if (options.clone === false) {
711
+ return new Promise(
712
+ async (resolve) => {
713
+ loader(options, cb, async (obj) => {
714
+ resolve(obj);
715
+ });
716
+ });
717
+ }
718
+ else {
719
+ //[jscastro] new added cache for 3D Objects
720
+ let cache = this.objectsCache.get(options.obj);
721
+ if (cache) {
722
+ cache.promise
723
+ .then(obj => {
724
+ cb(obj.duplicate(options));
725
+ })
726
+ .catch(err => {
727
+ this.objectsCache.delete(options.obj);
728
+ console.error("Could not load model file: " + options.obj);
729
+ });
730
+ } else {
731
+ this.objectsCache.set(options.obj, {
732
+ promise: new Promise(
733
+ async (resolve, reject) => {
734
+ loader(options, cb, async (obj) => {
735
+ if (obj.duplicate) {
736
+ resolve(obj.duplicate());
737
+ } else {
738
+ reject(obj);
739
+ }
740
+ });
741
+ })
742
+ });
743
+
744
+ }
745
+ }
746
+ },
747
+
748
+ // Material
749
+
750
+ material: function (o) {
751
+ return material(o)
752
+ },
753
+
754
+ initLights : {
755
+ ambientLight: null,
756
+ dirLight: null,
757
+ dirLightBack: null,
758
+ dirLightHelper: null,
759
+ hemiLight: null,
760
+ pointLight: null
761
+ },
762
+
763
+ utils: utils,
764
+
765
+ SunCalc: SunCalc,
766
+
767
+ Constants: ThreeboxConstants,
768
+
769
+ projectToWorld: function (coords) {
770
+ return this.utils.projectToWorld(coords)
771
+ },
772
+
773
+ unprojectFromWorld: function (v3) {
774
+ return this.utils.unprojectFromWorld(v3)
775
+ },
776
+
777
+ projectedUnitsPerMeter: function (lat) {
778
+ return this.utils.projectedUnitsPerMeter(lat)
779
+ },
780
+
781
+ //get the center point of a feature
782
+ getFeatureCenter: function getFeatureCenter(feature, obj, level) {
783
+ return utils.getFeatureCenter(feature, obj, level);
784
+ },
785
+
786
+ getObjectHeightOnFloor: function (feature, obj, level) {
787
+ return utils.getObjectHeightOnFloor(feature, obj, level);
788
+ },
789
+
790
+ queryRenderedFeatures: function (point) {
791
+
792
+ let mouse = new THREE.Vector2();
793
+
794
+ // // scale mouse pixel position to a percentage of the screen's width and height
795
+ mouse.x = (point.x / this.map.transform.width) * 2 - 1;
796
+ mouse.y = 1 - (point.y / this.map.transform.height) * 2;
797
+
798
+ this.raycaster.setFromCamera(mouse, this.camera);
799
+
800
+ // calculate objects intersecting the picking ray
801
+ let intersects = this.raycaster.intersectObjects(this.world.children, true);
802
+
803
+ return intersects
804
+ },
805
+
806
+ //[jscastro] find 3D object of a mesh. this method is needed to know the object of a raycasted mesh
807
+ findParent3DObject: function (mesh) {
808
+ //find the Parent Object3D of the mesh captured by Raytracer
809
+ var result;
810
+ mesh.object.traverseAncestors(function (m) {
811
+ if (m.parent)
812
+ if (m.parent.type == "Group" && m.userData.obj) {
813
+ result = m;
814
+ }
815
+ });
816
+ return result;
817
+ },
818
+
819
+ //[jscastro] method to replicate behaviour of map.setLayoutProperty when Threebox are affected
820
+ setLayoutProperty: function (layerId, name, value) {
821
+ //first set layout property at the map
822
+ this.map.setLayoutProperty(layerId, name, value);
823
+ if (value !== null && value !== undefined) {
824
+ if (name === 'visibility') {
825
+ this.world.children.filter(o => (o.layer === layerId)).forEach((o) => { o.visibility = value });
826
+ }
827
+ }
828
+ },
829
+
830
+ //[jscastro] Custom Layers doesn't work on minzoom and maxzoom attributes, and if the layer is including labels they don't hide either on minzoom
831
+ setLayerZoomRange: function (layerId, minZoomLayer, maxZoomLayer) {
832
+ if (this.map.getLayer(layerId)) {
833
+ this.map.setLayerZoomRange(layerId, minZoomLayer, maxZoomLayer);
834
+ if (!this.zoomLayers.includes(layerId)) this.zoomLayers.push(layerId);
835
+ this.toggleLayer(layerId);
836
+ }
837
+ },
838
+
839
+ //[jscastro] method to set the height of all the objects in a level. this only works if the objects have a geojson feature
840
+ setLayerHeigthProperty: function (layerId, level) {
841
+ let layer = this.map.getLayer(layerId);
842
+ if (!layer) return;
843
+ if (layer.type == "fill-extrusion") {
844
+ let data = this.map.getStyle().sources[layer.source].data;
845
+ let features = data.features;
846
+ features.forEach(function (f) {
847
+ f.properties.level = level;
848
+ });
849
+ //we change the level on the source
850
+ this.map.getSource(layer.source).setData(data);
851
+ } else if (layer.type == "custom") {
852
+ this.world.children.forEach(function (obj) {
853
+ let feature = obj.userData.feature;
854
+ if (feature && feature.layer === layerId) {
855
+ //TODO: this could be a multidimensional array
856
+ let location = this.tb.getFeatureCenter(feature, obj, level);
857
+ obj.setCoords(location);
858
+ }
859
+ });
860
+ }
861
+ },
862
+
863
+ //[jscastro] method to set globally all the objects that are fixedScale
864
+ setObjectsScale: function () {
865
+ this.world.children.filter(o => (o.fixedZoom != null)).forEach((o) => { o.setObjectScale(this.map.transform.scale); });
866
+ },
867
+
868
+ //[jscastro] mapbox setStyle removes all the layers, including custom layers, so tb.world must be cleaned up too
869
+ setStyle: function (styleId, options) {
870
+ this.clear().then(() => {
871
+ this.map.setStyle(styleId, options);
872
+ });
873
+ },
874
+
875
+ //[jscastro] method to toggle Layer visibility checking zoom range
876
+ toggleLayer: function (layerId, visible = true) {
877
+ let l = this.map.getLayer(layerId);
878
+ if (l) {
879
+ if (!visible) {
880
+ this.toggle(l.id, false);
881
+ return;
882
+ }
883
+ let z = this.map.getZoom();
884
+ if (l.minzoom && z < l.minzoom) { this.toggle(l.id, false); return; };
885
+ if (l.maxzoom && z >= l.maxzoom) { this.toggle(l.id, false); return; };
886
+ this.toggle(l.id, true);
887
+ };
888
+ },
889
+
890
+ //[jscastro] method to toggle Layer visibility
891
+ toggle: function (layerId, visible) {
892
+ //call
893
+ this.setLayoutProperty(layerId, 'visibility', (visible ? 'visible' : 'none'))
894
+ this.labelRenderer.toggleLabels(layerId, visible);
895
+ },
896
+
897
+ update: function () {
898
+
899
+ if (this.map.repaint) this.map.repaint = false
900
+
901
+ var timestamp = Date.now();
902
+
903
+ // Update any animations
904
+ this.objects.animationManager.update(timestamp);
905
+
906
+ this.updateLightHelper();
907
+
908
+ // Render the scene and repaint the map
909
+ this.renderer.resetState(); //update threejs r126
910
+ this.renderer.render(this.scene, this.camera);
911
+
912
+ // [jscastro] Render any label
913
+ this.labelRenderer.render(this.scene, this.camera);
914
+ if (this.options.passiveRendering === false) this.map.triggerRepaint();
915
+ },
916
+
917
+ add: function (obj, layerId, sourceId) {
918
+ //[jscastro] remove the tooltip if not enabled
919
+ if (!this.enableTooltips && obj.tooltip) { obj.tooltip.visibility = false };
920
+ this.world.add(obj);
921
+ if (layerId) {
922
+ obj.layer = layerId;
923
+ obj.source = sourceId;
924
+ let l = this.map.getLayer(layerId);
925
+ if (l) {
926
+ let v = l.visibility;
927
+ let u = typeof v === 'undefined';
928
+ obj.visibility = (u || v === 'visible' ? true : false);
929
+ }
930
+ }
931
+ },
932
+
933
+ removeByName: function (name) {
934
+ let obj = this.world.getObjectByName(name);
935
+ if (obj) this.remove(obj);
936
+ },
937
+
938
+ remove: function (obj) {
939
+ if (this.map.selectedObject && obj.uuid == this.map.selectedObject.uuid) this.map.unselectObject();
940
+ if (this.map.draggedObject && obj.uuid == this.map.draggedObject.uuid) this.map.draggedObject = null;
941
+ if (obj.dispose) obj.dispose();
942
+ this.world.remove(obj);
943
+ obj = null;
944
+ },
945
+
946
+ //[jscastro] this clears tb.world in order to dispose properly the resources
947
+ clear: async function (layerId = null, dispose = false) {
948
+ return new Promise((resolve, reject) => {
949
+ let objects = [];
950
+ this.world.children.forEach(function (object) {
951
+ objects.push(object);
952
+ });
953
+ for (let i = 0; i < objects.length; i++) {
954
+ let obj = objects[i];
955
+ //if layerId, check the layer to remove, otherwise always remove
956
+ if (obj.layer === layerId || !layerId) {
957
+ this.remove(obj);
958
+ }
959
+ }
960
+ if (dispose) {
961
+ this.objectsCache.forEach((value) => {
962
+ value.promise.then(obj => {
963
+ obj.dispose();
964
+ obj = null;
965
+ })
966
+ })
967
+ }
968
+
969
+ resolve("clear");
970
+ });
971
+ },
972
+
973
+ //[jscastro] remove a layer clearing first the 3D objects from this layer in tb.world
974
+ removeLayer: function (layerId) {
975
+ this.clear(layerId, true).then( () => {
976
+ this.map.removeLayer(layerId);
977
+ });
978
+ },
979
+
980
+ //[jscastro] get the sun position (azimuth, altitude) from a given datetime, lng, lat
981
+ getSunPosition: function (date, coords) {
982
+ return SunCalc.getPosition(date || Date.now(), coords[1], coords[0]);
983
+ },
984
+
985
+ //[jscastro] get the sun times for sunrise, sunset, etc.. from a given datetime, lng, lat and alt
986
+ getSunTimes: function (date, coords) {
987
+ return SunCalc.getTimes(date, coords[1], coords[0], (coords[2] ? coords[2] : 0));
988
+ },
989
+
990
+ //[jscastro] set shadows for fill-extrusion layers
991
+ setBuildingShadows: function (options) {
992
+ if (this.map.getLayer(options.buildingsLayerId)) {
993
+ let layer = new BuildingShadows(options, this);
994
+ this.map.addLayer(layer, options.buildingsLayerId);
995
+ }
996
+ else {
997
+ console.warn("The layer '" + options.buildingsLayerId + "' does not exist in the map.");
998
+ }
999
+ },
1000
+
1001
+ //[jscastro] This method set the sun light for a given datetime and lnglat
1002
+ setSunlight: function (newDate = new Date(), coords) {
1003
+ if (!this.lights.dirLight || !this.options.realSunlight) {
1004
+ console.warn("To use setSunlight it's required to set realSunlight : true in Threebox initial options.");
1005
+ return;
1006
+ }
1007
+
1008
+ var date = new Date(newDate.getTime());
1009
+
1010
+ if (coords) {
1011
+ if (coords.lng && coords.lat) this.mapCenter = coords
1012
+ else this.mapCenter = { lng: coords[0], lat: coords[1] };
1013
+ }
1014
+ else {
1015
+ this.mapCenter = this.map.getCenter();
1016
+ }
1017
+
1018
+ if (this.lightDateTime && this.lightDateTime.getTime() === date.getTime() && this.lightLng === this.mapCenter.lng && this.lightLat === this.mapCenter.lat) {
1019
+ return; //setSunLight could be called on render, so due to performance, avoid duplicated calls
1020
+ }
1021
+
1022
+ this.lightDateTime = date;
1023
+ this.lightLng = this.mapCenter.lng;
1024
+ this.lightLat = this.mapCenter.lat
1025
+ this.sunPosition = this.getSunPosition(date, [this.mapCenter.lng, this.mapCenter.lat]);
1026
+ let altitude = this.sunPosition.altitude;
1027
+ let azimuth = Math.PI + this.sunPosition.azimuth;
1028
+ //console.log("Altitude: " + utils.degreeify(altitude) + ", Azimuth: " + (utils.degreeify(azimuth)));
1029
+
1030
+ let radius = ThreeboxConstants.WORLD_SIZE / 2;
1031
+ let alt = Math.sin(altitude);
1032
+ let altRadius = Math.cos(altitude);
1033
+ let azCos = Math.cos(azimuth) * altRadius;
1034
+ let azSin = Math.sin(azimuth) * altRadius;
1035
+
1036
+ this.lights.dirLight.position.set(azSin, azCos, alt);
1037
+ this.lights.dirLight.position.multiplyScalar(radius);
1038
+ this.lights.dirLight.intensity = Math.max(alt, 0);
1039
+ this.lights.hemiLight.intensity = Math.max(alt * 1, 0.1);
1040
+ //console.log("Intensity:" + this.lights.dirLight.intensity);
1041
+ this.lights.dirLight.updateMatrixWorld();
1042
+ this.updateLightHelper();
1043
+ if (this.map.loaded()) {
1044
+ this.updateSunGround(this.sunPosition);
1045
+ this.map.setLight({
1046
+ anchor: 'map',
1047
+ position: [3, 180 + this.sunPosition.azimuth * 180 / Math.PI, 90 - this.sunPosition.altitude * 180 / Math.PI],
1048
+ intensity: Math.cos(this.sunPosition.altitude), //0.4,
1049
+ color: `hsl(40, ${50 * Math.cos(this.sunPosition.altitude)}%, ${Math.max(20, 20 + (96 * Math.sin(this.sunPosition.altitude)))}%)`
1050
+
1051
+ }, { duration: 0 });
1052
+ if (this.sky) { this.updateSunSky(this.getSunSky(date, this.sunPosition));}
1053
+ }
1054
+ },
1055
+
1056
+ getSunSky: function (date, sunPos) {
1057
+ if (!sunPos) {
1058
+ var center = this.map.getCenter();
1059
+ sunPos = this.getSunPosition(
1060
+ date || Date.now(), [center.lng, center.lat]
1061
+ );
1062
+ }
1063
+ var sunAzimuth = 180 + (sunPos.azimuth * 180) / Math.PI;
1064
+ var sunAltitude = 90 - (sunPos.altitude * 180) / Math.PI;
1065
+ return [sunAzimuth, sunAltitude];
1066
+ },
1067
+
1068
+ updateSunSky: function (sunPos) {
1069
+ if (this.sky) {
1070
+ // update the `sky-atmosphere-sun` paint property with the position of the sun based on the selected time
1071
+ this.map.setPaintProperty(this.skyLayerName, 'sky-atmosphere-sun', sunPos);
1072
+ }
1073
+ },
1074
+
1075
+ updateSunGround: function (sunPos) {
1076
+ if (this.terrainLayerName != '') {
1077
+ // update the raster layer paint property with the position of the sun based on the selected time
1078
+ this.map.setPaintProperty(this.terrainLayerName, 'raster-opacity', Math.max(Math.min(1, sunPos.altitude * 4), 0.25));
1079
+ }
1080
+ },
1081
+
1082
+ //[jscastro] this updates the directional light helper
1083
+ updateLightHelper: function () {
1084
+ if (this.lights.dirLightHelper) {
1085
+ this.lights.dirLightHelper.position.setFromMatrixPosition(this.lights.dirLight.matrixWorld);
1086
+ this.lights.dirLightHelper.updateMatrix();
1087
+ this.lights.dirLightHelper.update();
1088
+ }
1089
+ },
1090
+
1091
+ //[jscastro] method to fully dispose the resources, watch out is you call this without navigating to other page
1092
+ dispose: async function () {
1093
+
1094
+ console.log(this.memory());
1095
+ //console.log(window.performance.memory);
1096
+
1097
+ return new Promise((resolve) => {
1098
+ resolve(
1099
+ this.clear(null, true).then((resolve) => {
1100
+ this.map.remove();
1101
+ this.map = {};
1102
+ this.scene.remove(this.world);
1103
+ this.world.children = [];
1104
+ this.world = null;
1105
+ this.objectsCache.clear();
1106
+ this.labelRenderer.dispose();
1107
+ console.log(this.memory());
1108
+ this.renderer.dispose();
1109
+ return resolve;
1110
+ })
1111
+ );
1112
+ //console.log(window.performance.memory);
1113
+ });
1114
+
1115
+ },
1116
+
1117
+ defaultLights: function () {
1118
+
1119
+ this.lights.ambientLight = new THREE.AmbientLight(new THREE.Color('hsl(0, 0%, 100%)'), 0.75);
1120
+ this.scene.add(this.lights.ambientLight);
1121
+
1122
+ this.lights.dirLightBack = new THREE.DirectionalLight(new THREE.Color('hsl(0, 0%, 100%)'), 0.25);
1123
+ this.lights.dirLightBack.position.set(30, 100, 100);
1124
+ this.scene.add(this.lights.dirLightBack);
1125
+
1126
+ this.lights.dirLight = new THREE.DirectionalLight(new THREE.Color('hsl(0, 0%, 100%)'), 0.25);
1127
+ this.lights.dirLight.position.set(-30, 100, -100);
1128
+ this.scene.add(this.lights.dirLight);
1129
+
1130
+ },
1131
+
1132
+ realSunlight: function (helper = false) {
1133
+
1134
+ this.renderer.shadowMap.enabled = true;
1135
+ //this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
1136
+ this.lights.dirLight = new THREE.DirectionalLight(0xffffff, 1);
1137
+ this.scene.add(this.lights.dirLight);
1138
+ if (helper) {
1139
+ this.lights.dirLightHelper = new THREE.DirectionalLightHelper(this.lights.dirLight, 5);
1140
+ this.scene.add(this.lights.dirLightHelper);
1141
+ }
1142
+ let d2 = 1000; let r2 = 2; let mapSize2 = 8192;
1143
+ this.lights.dirLight.castShadow = true;
1144
+ this.lights.dirLight.shadow.radius = r2;
1145
+ this.lights.dirLight.shadow.mapSize.width = mapSize2;
1146
+ this.lights.dirLight.shadow.mapSize.height = mapSize2;
1147
+ this.lights.dirLight.shadow.camera.top = this.lights.dirLight.shadow.camera.right = d2;
1148
+ this.lights.dirLight.shadow.camera.bottom = this.lights.dirLight.shadow.camera.left = -d2;
1149
+ this.lights.dirLight.shadow.camera.near = 1;
1150
+ this.lights.dirLight.shadow.camera.visible = true;
1151
+ this.lights.dirLight.shadow.camera.far = 400000000;
1152
+
1153
+ this.lights.hemiLight = new THREE.HemisphereLight(new THREE.Color(0xffffff), new THREE.Color(0xffffff), 0.6);
1154
+ this.lights.hemiLight.color.setHSL(0.661, 0.96, 0.12);
1155
+ this.lights.hemiLight.groundColor.setHSL(0.11, 0.96, 0.14);
1156
+ this.lights.hemiLight.position.set(0, 0, 50);
1157
+ this.scene.add(this.lights.hemiLight);
1158
+ this.setSunlight();
1159
+
1160
+ this.map.once('idle', () => {
1161
+ this.setSunlight();
1162
+ this.repaint();
1163
+ });
1164
+
1165
+ },
1166
+
1167
+ setDefaultView: function (options, defOptions) {
1168
+ options.bbox = (options.bbox || options.bbox == null) && defOptions.enableSelectingObjects;
1169
+ options.tooltip = (options.tooltip || options.tooltip == null) && defOptions.enableTooltips;
1170
+ options.mapScale = this.map.transform.scale;
1171
+ },
1172
+
1173
+ memory: function () { return this.renderer.info.memory },
1174
+
1175
+ programs: function () { return this.renderer.info.programs.length },
1176
+
1177
+ version: '2.2.7',
1178
+
1179
+ }
1180
+
1181
+ var defaultOptions = {
1182
+ defaultLights: false,
1183
+ realSunlight: false,
1184
+ realSunlightHelper: false,
1185
+ passiveRendering: true,
1186
+ preserveDrawingBuffer: false,
1187
+ enableSelectingFeatures: false,
1188
+ enableSelectingObjects: false,
1189
+ enableDraggingObjects: false,
1190
+ enableRotatingObjects: false,
1191
+ enableTooltips: false,
1192
+ enableHelpTooltips: false,
1193
+ multiLayer: false,
1194
+ orthographic: false,
1195
+ fov: ThreeboxConstants.FOV_DEGREES,
1196
+ sky: false,
1197
+ terrain: false
1198
+ }
1199
+
1200
+ export default Threebox;
1201
+