@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,1111 @@
1
+ /**
2
+ * @author peterqliu / https://github.com/peterqliu
3
+ * @author jscastro / https://github.com/jscastro76
4
+ */
5
+ import * as THREE from 'three';
6
+ import utils from '../utils/utils.js';
7
+ import material from '../utils/material.js';
8
+ import AnimationManager from '../animation/AnimationManager.js';
9
+ import { CSS2DObject } from './CSS2DRenderer.js';
10
+
11
+ function Objects(){
12
+
13
+ }
14
+
15
+ Objects.prototype = {
16
+
17
+ // standard 1px line with gl
18
+ line: function (obj) {
19
+
20
+ obj = utils._validate(obj, this._defaults.line);
21
+
22
+ //project to world and normalize
23
+ var straightProject = utils.lnglatsToWorld(obj.geometry);
24
+ var normalized = utils.normalizeVertices(straightProject);
25
+
26
+ //flatten array for buffergeometry
27
+ var flattenedArray = utils.flattenVectors(normalized.vertices);
28
+
29
+ var positions = new Float32Array(flattenedArray); // 3 vertices per point
30
+ var geometry = new THREE.BufferGeometry();
31
+ geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
32
+
33
+ // material
34
+ var material = new THREE.LineBasicMaterial({ color: 0xff0000, linewidth: 21 });
35
+ var line = new THREE.Line(geometry, material);
36
+
37
+ line.options = options || {};
38
+ line.position.copy(normalized.position)
39
+
40
+ return line
41
+ },
42
+
43
+ extrusion: function (options) {
44
+
45
+ },
46
+
47
+ unenroll: function (obj, isStatic) {
48
+ var root = this;
49
+
50
+ if (isStatic) {
51
+
52
+ }
53
+
54
+ else {
55
+ // Bestow this mesh with animation superpowers and keeps track of its movements in the global animation queue
56
+ root.animationManager.unenroll(obj);
57
+
58
+ }
59
+
60
+ },
61
+
62
+ _addMethods: function (obj, isStatic) {
63
+
64
+ var root = this;
65
+ const labelName = "label";
66
+ const tooltipName = "tooltip";
67
+ const helpName = "help";
68
+ const shadowPlane = "shadowPlane";
69
+
70
+ if (isStatic) {
71
+
72
+ }
73
+
74
+ else {
75
+
76
+ if (!obj.coordinates) obj.coordinates = [0, 0, 0];
77
+
78
+ //[jscastro] added property for the internal 3D model
79
+ Object.defineProperty(obj, 'model', {
80
+ get() {
81
+ return obj.getObjectByName("model");
82
+ }
83
+ });
84
+
85
+ let _animations;
86
+ //[jscastro] added property for the internal 3D model
87
+ Object.defineProperty(obj, 'animations', {
88
+ get() {
89
+ const model = obj.model;
90
+ if (model) {
91
+ return model.animations
92
+ } else return null;
93
+ },
94
+ //set(value) { _animations = value}
95
+ });
96
+
97
+ // Bestow this mesh with animation superpowers and keeps track of its movements in the global animation queue
98
+ root.animationManager.enroll(obj);
99
+
100
+ // Place an object on the map at the given lnglat
101
+ obj.setCoords = function (lnglat) {
102
+
103
+ // CSS2DObjects could bring an specific vertical positioning to correct in units
104
+ if (obj.userData.topMargin && obj.userData.feature) {
105
+ lnglat[2] += ((obj.userData.feature.properties.height || 0) - (obj.userData.feature.properties.base_height || obj.userData.feature.properties.min_height || 0)) * (obj.userData.topMargin || 0);
106
+ }
107
+
108
+ obj.coordinates = lnglat;
109
+ obj.set({ position: lnglat });
110
+ return obj;
111
+
112
+ }
113
+
114
+ obj.setTranslate = function (lnglat) {
115
+
116
+ obj.set({ translate: lnglat });
117
+ return obj;
118
+
119
+ }
120
+
121
+ obj.setRotation = function (xyz) {
122
+
123
+ if (typeof xyz === 'number') xyz = { z: xyz }
124
+
125
+ var r = {
126
+ x: utils.radify(xyz.x) || obj.rotation.x,
127
+ y: utils.radify(xyz.y) || obj.rotation.y,
128
+ z: utils.radify(xyz.z) || obj.rotation.z
129
+ }
130
+
131
+ obj._setObject({ rotation: [r.x, r.y, r.z] })
132
+ }
133
+
134
+ //[jscastro] added method to adjust 3D models to their issues with center position for rotation
135
+ obj.calculateAdjustedPosition = function (lnglat, xyz, inverse) {
136
+
137
+ let location = lnglat.slice();
138
+
139
+ //we convert the units to Long/Lat/Height
140
+ let newCoords = utils.unprojectFromWorld(obj.modelSize);
141
+
142
+ if (inverse) {
143
+ //each model will have different adjustment attributes, we add them for x, y, z
144
+ location[0] -= (xyz.x != 0 ? (newCoords[0] / xyz.x) : 0);
145
+ location[1] -= (xyz.y != 0 ? (newCoords[1] / xyz.y) : 0);
146
+ location[2] -= (xyz.z != 0 ? (newCoords[2] / xyz.z) : 0);
147
+ } else {
148
+ //each model will have different adjustment attributes, we add them for x, y, z
149
+ location[0] += (xyz.x != 0 ? (newCoords[0] / xyz.x) : 0);
150
+ location[1] += (xyz.y != 0 ? (newCoords[1] / xyz.y) : 0);
151
+ location[2] += (xyz.z != 0 ? (newCoords[2] / xyz.z) : 0);
152
+
153
+ }
154
+ return location;
155
+ }
156
+
157
+ //[jscastro] added method to rotate on objects on an axis instead of centers
158
+ obj.setRotationAxis = function (xyz) {
159
+ if (typeof xyz === 'number') xyz = { z: xyz }
160
+
161
+ let bb = obj.modelBox();
162
+
163
+ let point = new THREE.Vector3(bb.max.x, bb.max.y, bb.min.z);
164
+ //apply Axis rotation on angle
165
+ if (xyz.x != 0) _applyAxisAngle(obj, point, new THREE.Vector3(0, 0, 1), xyz.x);
166
+ if (xyz.y != 0) _applyAxisAngle(obj, point, new THREE.Vector3(0, 0, 1), xyz.y);
167
+ if (xyz.z != 0) _applyAxisAngle(obj, point, new THREE.Vector3(0, 0, 1), xyz.z);
168
+ }
169
+
170
+ //[jscastro] Auxiliar method to rotate an object on an axis
171
+ function _applyAxisAngle(model, point, axis, degrees) {
172
+ let theta = utils.radify(degrees);
173
+ model.position.sub(point); // remove the offset
174
+ model.position.applyAxisAngle(axis, theta); // rotate the POSITION
175
+ model.position.add(point); // re-add the offset
176
+ model.rotateOnAxis(axis, theta)
177
+
178
+ tb.map.repaint = true;
179
+ }
180
+
181
+
182
+ //[jscastro] added property for scaled group inside threeboxObject
183
+ Object.defineProperty(obj, 'scaleGroup', {
184
+ get() {
185
+ return obj.getObjectByName("scaleGroup");
186
+ }
187
+ })
188
+
189
+ //[jscastro] added property for boundingBox group helper
190
+ Object.defineProperty(obj, 'boxGroup', {
191
+ get() {
192
+ return obj.getObjectByName("boxGroup");
193
+ }
194
+ })
195
+
196
+ //[jscastro] added property for boundingBox helper
197
+ Object.defineProperty(obj, 'boundingBox', {
198
+ get() {
199
+ return obj.getObjectByName("boxModel");
200
+ }
201
+ })
202
+
203
+ let _boundingBoxShadow;
204
+ //[jscastro] added property for boundingBox shadow helper
205
+ Object.defineProperty(obj, 'boundingBoxShadow', {
206
+ get() {
207
+ return obj.getObjectByName("boxShadow");
208
+ }
209
+ })
210
+
211
+ //[jscastro] added method to create a bounding box and a shadow box
212
+ obj.drawBoundingBox = function () {
213
+ //let's create 2 wireframes, one for the object and one to project on the floor position
214
+ let bb = obj.box3();
215
+ //create the group to return
216
+ let boxGroup = new THREE.Group();
217
+ boxGroup.name = "boxGroup";
218
+ boxGroup.updateMatrixWorld(true);
219
+ let boxModel = new THREE.Box3Helper(bb, Objects.prototype._defaults.colors.yellow);
220
+ boxModel.name = "boxModel";
221
+ boxGroup.add(boxModel);
222
+ boxModel.layers.disable(0); // it makes the object invisible for the raycaster
223
+ //obj.boundingBox = boxModel;
224
+
225
+ //it needs to clone, to avoid changing the object by reference
226
+ let bb2 = bb.clone();
227
+ //we make the second box flat and at the floor height level
228
+ bb2.max.z = bb2.min.z;
229
+ let boxShadow = new THREE.Box3Helper(bb2, Objects.prototype._defaults.colors.black);
230
+ boxShadow.name = "boxShadow";
231
+
232
+ boxGroup.add(boxShadow);
233
+ boxShadow.layers.disable(0); // it makes the object invisible for the raycaster
234
+ //obj.boundingBoxShadow = boxShadow;
235
+
236
+ boxGroup.visible = false; // visibility is managed from the parent
237
+ obj.scaleGroup.add(boxGroup);
238
+ obj.setBoundingBoxShadowFloor();
239
+ }
240
+
241
+ //[jscastro] added method to position the shadow box on the floor depending the object height
242
+ obj.setBoundingBoxShadowFloor = function () {
243
+ if (obj.boundingBoxShadow) {
244
+ let h = -obj.modelHeight, r = obj.rotation, o = obj.boundingBoxShadow;
245
+ o.box.max.z = o.box.min.z = h;
246
+ o.rotation.y = r.y;
247
+ o.rotation.x = -r.x;
248
+ }
249
+ }
250
+
251
+ //[jscastro] Set the positional and pivotal anchor automatically from string param
252
+ obj.setAnchor = function (anchor) {
253
+ const b = obj.box3();
254
+ //const size = b.getSize(new THREE.Vector3());
255
+ const c = b.getCenter(new THREE.Vector3());
256
+ obj.none = { x: 0, y: 0, z: 0 };
257
+ obj.center = { x: c.x, y: c.y, z: b.min.z };
258
+ obj.bottom = { x: c.x, y: b.max.y, z: b.min.z };
259
+ obj.bottomLeft = { x: b.max.x, y: b.max.y, z: b.min.z };
260
+ obj.bottomRight = { x: b.min.x, y: b.max.y, z: b.min.z };
261
+ obj.top = { x: c.x, y: b.min.y, z: b.min.z };
262
+ obj.topLeft = { x: b.max.x, y: b.min.y, z: b.min.z };
263
+ obj.topRight = { x: b.min.x, y: b.min.y, z: b.min.z };
264
+ obj.left = { x: b.max.x, y: c.y, z: b.min.z };
265
+ obj.right = { x: b.min.x, y: c.y, z: b.min.z };
266
+
267
+ switch (anchor) {
268
+ case 'center':
269
+ obj.anchor = obj.center;
270
+ break;
271
+ case 'top':
272
+ obj.anchor = obj.top;
273
+ break;
274
+ case 'top-left':
275
+ obj.anchor = obj.topLeft;
276
+ break;
277
+ case 'top-right':
278
+ obj.anchor = obj.topRight;
279
+ break;
280
+ case 'left':
281
+ obj.anchor = obj.left;
282
+ break;
283
+ case 'right':
284
+ obj.anchor = obj.right;
285
+ break;
286
+ case 'bottom':
287
+ obj.anchor = obj.bottom;
288
+ break;
289
+ case 'bottom-left':
290
+ default:
291
+ obj.anchor = obj.bottomLeft;
292
+ break;
293
+ case 'bottom-right':
294
+ obj.anchor = obj.bottomRight;
295
+ break;
296
+ case 'auto':
297
+ case 'none':
298
+ obj.anchor = obj.none;
299
+ }
300
+
301
+ obj.model.position.set(-obj.anchor.x, -obj.anchor.y, -obj.anchor.z);
302
+
303
+ }
304
+
305
+ //[jscastro] Set the positional and pivotal anchor based on (x, y, z) size units
306
+ obj.setCenter = function (center) {
307
+ //[jscastro] if the object options have an adjustment to center the 3D Object different to 0
308
+ if (center && (center.x != 0 || center.y != 0 || center.z != 0)) {
309
+ let size = obj.getSize();
310
+ obj.anchor = { x: obj.anchor.x - (size.x * center.x), y: obj.anchor.y - (size.y * center.y), z: obj.anchor.z - (size.z * center.z) };
311
+ obj.model.position.set(-obj.anchor.x, -obj.anchor.y, -obj.anchor.z)
312
+ }
313
+ }
314
+
315
+ //[jscastro] added property for simulated label
316
+ Object.defineProperty(obj, 'label', {
317
+ get() { return obj.getObjectByName(labelName); }
318
+ });
319
+
320
+ //[jscastro] added property for simulated tooltip
321
+ Object.defineProperty(obj, 'tooltip', {
322
+ get() { return obj.getObjectByName(tooltipName); }
323
+ });
324
+
325
+ //[jscastro] added property for help
326
+ Object.defineProperty(obj, 'help', {
327
+ get() { return obj.getObjectByName(helpName); }
328
+ });
329
+
330
+ let _hidden = false;
331
+ //[jscastro] added property for explicitely hidden object to avoid zoom layer behavior
332
+ Object.defineProperty(obj, 'hidden', {
333
+ get() { return _hidden; },
334
+ set(value) {
335
+ if (_hidden != value) {
336
+ _hidden = value;
337
+ obj.visibility = !_hidden;
338
+ }
339
+ }
340
+ });
341
+
342
+ //[jscastro] added property to redefine visible, including the label and tooltip
343
+ Object.defineProperty(obj, 'visibility', {
344
+ get() { return obj.visible; },
345
+ set(value) {
346
+ let _value = value;
347
+ if (value == 'visible' || value == true) {
348
+ _value = true;
349
+ if (obj.label) obj.label.visible = _value;
350
+ }
351
+ else if (value == 'none' || value == false) {
352
+ _value = false;
353
+ if (obj.label && obj.label.alwaysVisible) obj.label.visible = _value;
354
+ if (obj.tooltip) obj.tooltip.visible = _value;
355
+ }
356
+ else return;
357
+ if (obj.visible != _value) {
358
+ if (obj.hidden && _value) return;
359
+
360
+ obj.visible = _value;
361
+
362
+ if (obj.model) {
363
+ obj.model.traverse(function (c) {
364
+ if (c.type == "Mesh" || c.type == "SkinnedMesh") {
365
+ if (_value && obj.raycasted) {
366
+ c.layers.enable(0); //this makes the meshes visible for raycast
367
+ } else {
368
+ c.layers.disable(0); //this makes the meshes invisible for raycast
369
+ }
370
+ }
371
+ if (c.type == "LineSegments") {
372
+ c.layers.disableAll();
373
+ }
374
+ });
375
+ }
376
+ }
377
+ }
378
+ });
379
+
380
+ //[jscastro] add CSS2 label method
381
+ obj.addLabel = function (HTMLElement, visible, center, height) {
382
+ if (HTMLElement) {
383
+ //we add it to the first children to get same boxing and position
384
+ //obj.children[0].add(obj.drawLabel(text, height));
385
+ obj.drawLabelHTML(HTMLElement, visible, center, height);
386
+ }
387
+ }
388
+
389
+ //[jscastro] remove CSS2 label method
390
+ obj.removeLabel = function () {
391
+ obj.removeCSS2D(labelName);
392
+ }
393
+
394
+ //[jscastro] draw label method can be invoked separately
395
+ obj.drawLabelHTML = function (HTMLElement, visible = false, center = obj.anchor, height = 0.5) {
396
+ let divLabel = root.drawLabelHTML(HTMLElement, Objects.prototype._defaults.label.cssClass);
397
+ let label = obj.addCSS2D(divLabel, labelName, center, height) //label.position.set(((-size.x * 0.5) - obj.model.position.x - center.x + bottomLeft.x), ((-size.y * 0.5) - obj.model.position.y - center.y + bottomLeft.y), size.z * 0.5); //middle-centered
398
+ label.alwaysVisible = visible;
399
+ label.visible = visible;
400
+ return label;
401
+ }
402
+
403
+ //[jscastro] add tooltip method
404
+ obj.addTooltip = function (tooltipText, mapboxStyle, center, custom = true, height = 1) {
405
+ let t = obj.addHelp(tooltipText, tooltipName, mapboxStyle, center, height);
406
+ t.visible = false;
407
+ t.custom = custom;
408
+ }
409
+
410
+ //[jscastro] remove CSS2 tooltip method
411
+ obj.removeTooltip = function () {
412
+ obj.removeCSS2D(tooltipName);
413
+ }
414
+
415
+ //[jscastro] add tooltip method
416
+ obj.addHelp = function (helpText, objName = helpName, mapboxStyle = false, center = obj.anchor, height = 0) {
417
+ let divHelp = root.drawTooltip(helpText, mapboxStyle);
418
+ let h = obj.addCSS2D(divHelp, objName, center, height);
419
+ h.visible = true;
420
+ return h;
421
+ }
422
+
423
+ //[jscastro] remove CSS2 tooltip method
424
+ obj.removeHelp = function () {
425
+ obj.removeCSS2D(helpName);
426
+ }
427
+
428
+ //[jscastro] add CSS2D help method
429
+ obj.addCSS2D = function (element, objName, center = obj.anchor, height = 1) {
430
+ if (element) {
431
+ const box = obj.box3();
432
+ const size = box.getSize(new THREE.Vector3());
433
+ let bottomLeft = { x: box.max.x, y: box.max.y, z: box.min.z };
434
+ obj.removeCSS2D(objName);
435
+ let c = new CSS2DObject(element);
436
+ c.name = objName;
437
+ c.position.set(((-size.x * 0.5) - obj.model.position.x - center.x + bottomLeft.x), ((-size.y * 0.5) - obj.model.position.y - center.y + bottomLeft.y), size.z * height);
438
+ c.visible = false; //only visible on mouseover or selected
439
+ obj.scaleGroup.add(c);
440
+ return c;
441
+ }
442
+ }
443
+
444
+ //[jscastro] remove CSS2 help method
445
+ obj.removeCSS2D = function (objName) {
446
+ let css2D = obj.getObjectByName(objName);
447
+ if (css2D) {
448
+ css2D.dispose();
449
+ let g = obj.scaleGroup.children;
450
+ g.splice(g.indexOf(css2D), 1);
451
+ }
452
+ }
453
+
454
+ //[jscastro] added property for help
455
+ Object.defineProperty(obj, 'shadowPlane', {
456
+ get() { return obj.getObjectByName(shadowPlane); }
457
+ });
458
+
459
+ let _castShadow = false;
460
+ //[jscastro] added property for traverse an object to cast a shadow
461
+ Object.defineProperty(obj, 'castShadow', {
462
+ get() { return _castShadow; },
463
+ set(value) {
464
+ if (!obj.model || _castShadow === value) return;
465
+
466
+ obj.model.traverse(function (c) {
467
+ if (c.isMesh) c.castShadow = true;
468
+ });
469
+ if (value) {
470
+ // we add the shadow plane automatically
471
+ const s = obj.modelSize;
472
+ const sz = [s.x, s.y, s.z, obj.modelHeight];
473
+ const pSize = Math.max(...sz) * 10;
474
+ const pGeo = new THREE.PlaneGeometry(pSize, pSize);
475
+ const pMat = new THREE.ShadowMaterial();
476
+ //const pMat = new THREE.MeshStandardMaterial({ color: 0x660000 });
477
+ pMat.opacity = 0.5;
478
+ let p = new THREE.Mesh(pGeo, pMat);
479
+ p.name = shadowPlane;
480
+ p.layers.enable(1); p.layers.disable(0); // it makes the object invisible for the raycaster
481
+ p.receiveShadow = value;
482
+ obj.add(p);
483
+ } else {
484
+ // or we remove it
485
+ obj.traverse(function (c) {
486
+ if (c.isMesh && c.material instanceof THREE.ShadowMaterial)
487
+ obj.remove(c);
488
+ });
489
+
490
+ }
491
+ _castShadow = value;
492
+
493
+ }
494
+ })
495
+
496
+ //[jscastro] added method to position the shadow box on the floor depending the object height
497
+ obj.setReceiveShadowFloor = function () {
498
+ if (obj.castShadow) {
499
+ let sp = obj.shadowPlane, p = sp.position, r = sp.rotation;
500
+ p.z = -obj.modelHeight;
501
+ r.y = obj.rotation.y;
502
+ r.x = -obj.rotation.x;
503
+ if (obj.userData.units === 'meters') {
504
+ const s = obj.modelSize;
505
+ const sz = [s.x, s.y, s.z, -p.z];
506
+ const ps = Math.max(...sz) * 10;
507
+ const sc = ps / sp.geometry.parameters.width;
508
+ sp.scale.set(sc, sc, sc);
509
+ }
510
+ }
511
+ }
512
+
513
+ let _receiveShadow = false;
514
+ //[jscastro] added property for traverse an object to receive a shadow
515
+ Object.defineProperty(obj, 'receiveShadow', {
516
+ get() { return _receiveShadow; },
517
+ set(value) {
518
+ if (!obj.model || _receiveShadow === value) return;
519
+ obj.model.traverse(function (c) {
520
+ if (c.isMesh) c.receiveShadow = true;
521
+ });
522
+ _receiveShadow = value;
523
+ }
524
+ })
525
+
526
+ let _wireframe = false;
527
+ //[jscastro] added property for wireframes state
528
+ Object.defineProperty(obj, 'wireframe', {
529
+ get() { return _wireframe; },
530
+ set(value) {
531
+ if (!obj.model || _wireframe === value) return;
532
+ obj.model.traverse(function (c) {
533
+ if (c.type == "Mesh" || c.type == "SkinnedMesh") {
534
+ let materials = [];
535
+ if (!Array.isArray(c.material)) {
536
+ materials.push(c.material);
537
+ } else {
538
+ materials = c.material;
539
+ }
540
+ let m = materials[0];
541
+ if (value) {
542
+ c.userData.materials = m;
543
+ c.material = m.clone();
544
+ c.material.wireframe = c.material.transparent = value;
545
+ c.material.opacity = 0.3;
546
+ } else {
547
+ c.material.dispose();
548
+ c.material = c.userData.materials;
549
+ c.userData.materials.dispose();
550
+ c.userData.materials = null;
551
+ }
552
+
553
+ if (value) { c.layers.disable(0); c.layers.enable(1); } else { c.layers.disable(1); c.layers.enable(0); }
554
+ }
555
+ if (c.type == "LineSegments") {
556
+ c.layers.disableAll();
557
+ }
558
+ });
559
+ _wireframe = value;
560
+ // Dispatch new event WireFramed
561
+ obj.dispatchEvent({ type: 'Wireframed', detail: obj });
562
+ }
563
+ })
564
+
565
+ let _color = null;
566
+ //[jscastro] added property for wireframes state
567
+ Object.defineProperty(obj, 'color', {
568
+ get() { return _color; },
569
+ set(value) {
570
+ if (!obj.model || _color === value) return;
571
+ obj.model.traverse(function (c) {
572
+ if (c.type == "Mesh" || c.type == "SkinnedMesh") {
573
+ let materials = [];
574
+ if (!Array.isArray(c.material)) {
575
+ materials.push(c.material);
576
+ } else {
577
+ materials = c.material;
578
+ }
579
+ let m = materials[0];
580
+ if (value) {
581
+ c.userData.materials = m;
582
+ c.material = new THREE.MeshStandardMaterial();
583
+ c.material.color.setHex(value);
584
+ } else {
585
+ c.material.dispose();
586
+ c.material = c.userData.materials;
587
+ c.userData.materials.dispose();
588
+ c.userData.materials = null;
589
+ }
590
+
591
+ }
592
+ });
593
+ _color = value;
594
+ }
595
+ })
596
+
597
+
598
+ let _selected = false;
599
+ //[jscastro] added property for selected state
600
+ Object.defineProperty(obj, 'selected', {
601
+ get() { return _selected; },
602
+ set(value) {
603
+ if (value) {
604
+ if (obj.userData.bbox && !obj.boundingBox) obj.drawBoundingBox();
605
+ if (obj.boxGroup) {
606
+ obj.boundingBox.material = Objects.prototype._defaults.materials.boxSelectedMaterial;
607
+ obj.boundingBox.parent.visible = true;
608
+ obj.boundingBox.layers.enable(1);
609
+ obj.boundingBoxShadow.layers.enable(1);
610
+ }
611
+ if (obj.label && !obj.label.alwaysVisible) obj.label.visible = true;
612
+ }
613
+ else {
614
+ if (obj.boxGroup) {
615
+ obj.remove(obj.boxGroup); //remove the box group
616
+ }
617
+ if (obj.label && !obj.label.alwaysVisible) obj.label.visible = false;
618
+ obj.removeHelp();
619
+ }
620
+ if (obj.tooltip) obj.tooltip.visible = value;
621
+ //only fire the event if value is different
622
+ if (_selected != value) {
623
+ _selected = value;
624
+ // Dispatch new event SelectedChange
625
+ obj.dispatchEvent({ type: 'SelectedChange', detail: obj });
626
+ }
627
+ }
628
+ })
629
+
630
+ let _raycasted = true;
631
+ //[jscastro] added property for including/excluding an object from raycast
632
+ Object.defineProperty(obj, 'raycasted', {
633
+ get() { return _raycasted; },
634
+ set(value) {
635
+ if (!obj.model || _raycasted === value) return;
636
+ obj.model.traverse(function (c) {
637
+ if (c.type == "Mesh" || c.type == "SkinnedMesh") {
638
+ if (!value) { c.layers.disable(0); c.layers.enable(1); } else { c.layers.disable(1); c.layers.enable(0); }
639
+ }
640
+ });
641
+ _raycasted = value;
642
+ }
643
+ });
644
+
645
+ let _over = false;
646
+ //[jscastro] added property for over state
647
+ Object.defineProperty(obj, 'over', {
648
+ get() { return _over; },
649
+ set(value) {
650
+ if (value) {
651
+ if (!obj.selected) {
652
+ if (obj.userData.bbox && !obj.boundingBox) obj.drawBoundingBox();
653
+ if (obj.userData.tooltip && !obj.tooltip) obj.addTooltip(obj.uuid, true, obj.anchor, false);
654
+ if (obj.boxGroup) {
655
+ obj.boundingBox.material = Objects.prototype._defaults.materials.boxOverMaterial;
656
+ obj.boundingBox.parent.visible = true;
657
+ obj.boundingBox.layers.enable(1);
658
+ obj.boundingBoxShadow.layers.enable(1);
659
+ }
660
+ }
661
+ if (obj.label && !obj.label.alwaysVisible) { obj.label.visible = true; }
662
+ // Dispatch new event ObjectOver
663
+ obj.dispatchEvent({ type: 'ObjectMouseOver', detail: obj });
664
+
665
+ }
666
+ else {
667
+ if (!obj.selected) {
668
+ if (obj.boxGroup) {
669
+ obj.remove(obj.boxGroup); //remove the box group
670
+ if (obj.tooltip && !obj.tooltip.custom) obj.removeTooltip();
671
+ }
672
+ if (obj.label && !obj.label.alwaysVisible) { obj.label.visible = false; }
673
+ }
674
+ // Dispatch new event ObjectOver
675
+ obj.dispatchEvent({ type: 'ObjectMouseOut', detail: obj });
676
+ }
677
+ if (obj.tooltip) obj.tooltip.visible = value || obj.selected;
678
+ _over = value;
679
+ }
680
+ })
681
+
682
+ //[jscastro] get the object model Box3 in runtime
683
+ obj.box3 = function () {
684
+ //update Matrix and MatrixWorld to avoid issues with transformations not full applied
685
+ obj.updateMatrix();
686
+ obj.updateMatrixWorld(true, true);
687
+ let bounds;
688
+ //clone also the model inside it's the one who keeps the real size
689
+ if (obj.model) {
690
+ //let's clone the object before manipulate it
691
+ let dup = obj.clone(true);
692
+ let model = obj.model.clone();
693
+ //get the size of the model because the object is translated and has boundingBoxShadow
694
+ bounds = new THREE.Box3().setFromObject(model);
695
+ //if the object has parent it's already in the added to world so it's scaled and it could be rotated
696
+ if (obj.parent) {
697
+ //first, we return the object to it's original position of rotation, extract rotation and apply inversed
698
+ let rm = new THREE.Matrix4();
699
+ let rmi = new THREE.Matrix4();
700
+ obj.matrix.extractRotation(rm);
701
+ rmi.copy(rm).invert();
702
+ dup.setRotationFromMatrix(rmi);
703
+ //now the object inside will give us a NAABB Non-Axes Aligned Bounding Box
704
+ bounds = new THREE.Box3().setFromObject(model);
705
+ }
706
+ }
707
+ return bounds;
708
+ };
709
+
710
+ //[jscastro] modelBox
711
+ obj.modelBox = function () {
712
+ return obj.box3();
713
+ }
714
+
715
+ obj.getSize = function () {
716
+ return obj.box3().getSize(new THREE.Vector3(0, 0, 0));
717
+ }
718
+
719
+ //[jscastro]
720
+ let _modelSize = false;
721
+ //[jscastro] added property for wireframes state
722
+ Object.defineProperty(obj, 'modelSize', {
723
+ get() {
724
+ _modelSize = obj.getSize();
725
+ return _modelSize;
726
+ },
727
+ set(value) {
728
+ if (_modelSize != value) {
729
+ _modelSize = value;
730
+ }
731
+ }
732
+ })
733
+
734
+
735
+ //[jscastro] added property to get modelHeight
736
+ Object.defineProperty(obj, 'modelHeight', {
737
+ get() {
738
+ let h = obj.coordinates[2] || 0;
739
+ if (obj.userData.units === 'scene') h *= (obj.unitsPerMeter / obj.scale.x);
740
+ return h;
741
+ }
742
+ });
743
+
744
+ //[jscastro] added property to calculate the units per meter in a given latitude
745
+ //reduced to 7 decimals to avoid deviations on the size of the same object
746
+ Object.defineProperty(obj, 'unitsPerMeter', {
747
+ get() { return Number(utils.projectedUnitsPerMeter(obj.coordinates[1]).toFixed(7)); }
748
+ });
749
+
750
+ let _fixedZoom = null;
751
+ //[jscastro] added property to have a fixed scale for some objects
752
+ Object.defineProperty(obj, 'fixedZoom', {
753
+ get() { return obj.userData.fixedZoom; },
754
+ set(value) {
755
+ if (obj.userData.fixedZoom === value) return;
756
+ obj.userData.fixedZoom = value;
757
+ obj.userData.units = (value ? 'scene' : 'meters');
758
+ }
759
+ });
760
+
761
+ //[jscastro] sets the scale of an object based fixedZoom
762
+ obj.setFixedZoom = function (scale) {
763
+ if (obj.fixedZoom != null && obj.fixedZoom != 0) {
764
+ if (!scale) scale = obj.userData.mapScale;
765
+ let s = zoomScale(obj.fixedZoom);
766
+ if (s > scale) {
767
+ let calc = s / scale;
768
+ obj.scale.set(calc, calc, calc);
769
+ } else {
770
+ obj.scale.set(1, 1, 1);
771
+ }
772
+ }
773
+ }
774
+
775
+ //[jscastro] sets the scale of an object based in the scale and fixedZoom
776
+ obj.setScale = function (scale) {
777
+ // scale the model so that its units are interpreted as meters at the given latitude
778
+ if (obj.userData.units != 'scene') {
779
+ let s = obj.unitsPerMeter;
780
+ obj.scale.set(s, s, s);
781
+ } else if (obj.fixedZoom) {
782
+ if (scale) obj.userData.mapScale = scale;
783
+ obj.setFixedZoom(obj.userData.mapScale); //apply fixed zoom
784
+ } else obj.scale.set(1, 1, 1);
785
+ }
786
+
787
+ function zoomScale(zoom) { return Math.pow(2, zoom); }
788
+
789
+ //[jscastro] sets the scale and shadows position of an object based in the scale
790
+ obj.setObjectScale = function (scale) {
791
+ obj.setScale(scale);
792
+ obj.setBoundingBoxShadowFloor();
793
+ obj.setReceiveShadowFloor();
794
+ }
795
+
796
+ }
797
+
798
+ obj.add = function (o) {
799
+ obj.scaleGroup.add(o);
800
+ o.position.z = (obj.coordinates[2] ? -obj.coordinates[2] : 0);
801
+ return o;
802
+ }
803
+
804
+ obj.remove = function (o) {
805
+ if (!o) return;
806
+ o.traverse(m => {
807
+ //console.log('dispose geometry!')
808
+ if (m.geometry) m.geometry.dispose();
809
+ if (m.material) {
810
+ if (m.material.isMaterial) {
811
+ cleanMaterial(m.material)
812
+ } else {
813
+ // an array of materials
814
+ for (const material of m.material) cleanMaterial(material)
815
+ }
816
+ }
817
+ if (m.dispose) m.dispose();
818
+ })
819
+
820
+ obj.scaleGroup.remove(o);
821
+ tb.map.repaint = true;
822
+ }
823
+
824
+ //[jscastro] clone + assigning all the attributes
825
+ obj.duplicate = function (options) {
826
+
827
+ let dupe = obj.clone(true); //clone the whole threebox object
828
+ dupe.getObjectByName("model").animations = obj.animations; //we must set this explicitly before addMethods
829
+ if (dupe.userData.feature) {
830
+ if (options && options.feature) dupe.userData.feature = options.feature;
831
+ dupe.userData.feature.properties.uuid = dupe.uuid;
832
+ }
833
+ root._addMethods(dupe); // add methods
834
+
835
+ if (!options || utils.equal(options.scale, obj.userData.scale)) {
836
+ //no options, no changes, just return the same object
837
+ dupe.copyAnchor(obj); // copy anchors
838
+ //[jscastro] we add by default a tooltip that can be overriden later or hide it with threebox `enableTooltips`
839
+ return dupe;
840
+ } else {
841
+ dupe.userData = options;
842
+ dupe.userData.isGeoGroup = true;
843
+ dupe.remove(dupe.boxGroup);
844
+ // [jscastro] rotate and scale the model
845
+ const r = utils.types.rotation(options.rotation, [0, 0, 0]);
846
+ const s = utils.types.scale(options.scale, [1, 1, 1]);
847
+ // [jscastro] reposition to 0,0,0
848
+ dupe.model.position.set(0, 0, 0);
849
+ // rotate and scale
850
+ dupe.model.rotation.set(r[0], r[1], r[2]);
851
+ dupe.model.scale.set(s[0], s[1], s[2]);
852
+ //[jscastro] calculate automatically the pivotal center of the object
853
+ dupe.setAnchor(options.anchor);
854
+ //[jscastro] override the center calculated if the object has adjustments
855
+ dupe.setCenter(options.adjustment);
856
+ return dupe;
857
+
858
+ }
859
+
860
+ }
861
+
862
+ //[jscastro] copy anchor values
863
+ obj.copyAnchor = function (o) {
864
+
865
+ obj.anchor = o.anchor;
866
+ obj.none = { x: 0, y: 0, z: 0 };
867
+ obj.center = o.center;
868
+ obj.bottom = o.bottom;
869
+ obj.bottomLeft = o.bottomLeft;
870
+ obj.bottomRight = o.bottomRight;
871
+ obj.top = o.top;
872
+ obj.topLeft = o.topLeft;
873
+ obj.topRight = o.topRight;
874
+ obj.left = o.left;
875
+ obj.right = o.right;
876
+
877
+ }
878
+
879
+ obj.dispose = function () {
880
+
881
+ Objects.prototype.unenroll(obj);
882
+
883
+ obj.traverse(o => {
884
+ //don't dispose th object itself as it will be recursive
885
+ if (o.parent && o.parent.name == "world") return;
886
+ if (o.name === "threeboxObject") return;
887
+
888
+ //console.log('dispose geometry!')
889
+ if (o.geometry) o.geometry.dispose();
890
+
891
+ if (o.material) {
892
+ if (o.material.isMaterial) {
893
+ cleanMaterial(o.material)
894
+ } else {
895
+ // an array of materials
896
+ for (const material of o.material) cleanMaterial(material)
897
+ }
898
+ }
899
+ if (o.dispose) o.dispose();
900
+
901
+ })
902
+
903
+ obj.children = [];
904
+
905
+ }
906
+
907
+ const cleanMaterial = material => {
908
+ //console.log('dispose material!')
909
+ material.dispose()
910
+
911
+ // dispose textures
912
+ for (const key of Object.keys(material)) {
913
+ const value = material[key]
914
+ if (value && typeof value === 'object' && 'minFilter' in value) {
915
+ //console.log('dispose texture!')
916
+ value.dispose()
917
+ }
918
+ }
919
+ let m = material;
920
+ let md = (m.map || m.alphaMap || m.aoMap || m.bumpMap || m.displacementMap || m.emissiveMap || m.envMap || m.lightMap || m.metalnessMap || m.normalMap || m.roughnessMap)
921
+ if (md) {
922
+ if (m.map) m.map.dispose();
923
+ if (m.alphaMap) m.alphaMap.dispose();
924
+ if (m.aoMap) m.aoMap.dispose();
925
+ if (m.bumpMap) m.bumpMap.dispose();
926
+ if (m.displacementMap) m.displacementMap.dispose();
927
+ if (m.emissiveMap) m.emissiveMap.dispose();
928
+ if (m.envMap) m.envMap.dispose();
929
+ if (m.lightMap) m.lightMap.dispose();
930
+ if (m.metalnessMap) m.metalnessMap.dispose();
931
+ if (m.normalMap) m.normalMap.dispose();
932
+ if (m.roughnessMap) m.roughnessMap.dispose();
933
+ }
934
+ }
935
+
936
+ return obj
937
+ },
938
+
939
+ _makeGroup: function (obj, options) {
940
+ let projScaleGroup = new THREE.Group();
941
+ projScaleGroup.name = "scaleGroup";
942
+ projScaleGroup.add(obj)
943
+
944
+ var geoGroup = new THREE.Group();
945
+ geoGroup.userData = options || {};
946
+ geoGroup.userData.isGeoGroup = true;
947
+ if (geoGroup.userData.feature) {
948
+ geoGroup.userData.feature.properties.uuid = geoGroup.uuid;
949
+ }
950
+ var isArrayOfObjects = projScaleGroup.length;
951
+ if (isArrayOfObjects) for (o of projScaleGroup) geoGroup.add(o)
952
+ else geoGroup.add(projScaleGroup);
953
+
954
+ //utils._flipMaterialSides(projScaleGroup);
955
+ geoGroup.name = "threeboxObject";
956
+
957
+ return geoGroup
958
+ },
959
+
960
+ animationManager: new AnimationManager,
961
+
962
+ //[jscastro] add tooltip method
963
+ drawTooltip : function (tooltipText, mapboxStyle = false) {
964
+ if (tooltipText) {
965
+ let divToolTip;
966
+ if (mapboxStyle) {
967
+ let divContent = document.createElement('div');
968
+ divContent.className = 'mapboxgl-popup-content';
969
+ let strong = document.createElement('strong');
970
+ strong.innerHTML = tooltipText;
971
+ divContent.appendChild(strong);
972
+ let tip = document.createElement('div');
973
+ tip.className = 'mapboxgl-popup-tip';
974
+ let div = document.createElement('div');
975
+ div.className = 'marker mapboxgl-popup-anchor-bottom';
976
+ div.appendChild(tip);
977
+ div.appendChild(divContent);
978
+ divToolTip = document.createElement('div');
979
+ divToolTip.className += 'label3D';
980
+ divToolTip.appendChild(div);
981
+ }
982
+ else {
983
+ divToolTip = document.createElement('span');
984
+ divToolTip.className = this._defaults.tooltip.cssClass;
985
+ divToolTip.innerHTML = tooltipText;
986
+ }
987
+ return divToolTip;
988
+ }
989
+ },
990
+
991
+ //[jscastro] draw label method can be invoked separately
992
+ drawLabelHTML: function (HTMLElement, cssClass) {
993
+ let div = document.createElement('div');
994
+ div.className += cssClass;
995
+ // [jscastro] create a div [TODO] analize if must be moved
996
+ if (typeof (HTMLElement) == 'string') {
997
+ div.innerHTML = HTMLElement;
998
+ } else {
999
+ div.innerHTML = HTMLElement.outerHTML;
1000
+ }
1001
+ return div;
1002
+ },
1003
+
1004
+ _defaults: {
1005
+ colors: {
1006
+ red: new THREE.Color(0xff0000),
1007
+ yellow: new THREE.Color(0xffff00),
1008
+ green: new THREE.Color(0x00ff00),
1009
+ black: new THREE.Color(0x000000)
1010
+ },
1011
+
1012
+ materials: {
1013
+ boxNormalMaterial: new THREE.LineBasicMaterial({ color: new THREE.Color(0xff0000) }),
1014
+ boxOverMaterial: new THREE.LineBasicMaterial({ color: new THREE.Color(0xffff00) }),
1015
+ boxSelectedMaterial: new THREE.LineBasicMaterial({ color: new THREE.Color(0x00ff00) })
1016
+ },
1017
+
1018
+ line: {
1019
+ geometry: null,
1020
+ color: 'black',
1021
+ width: 1,
1022
+ opacity: 1
1023
+ },
1024
+
1025
+ label: {
1026
+ htmlElement: null,
1027
+ cssClass: " label3D",
1028
+ alwaysVisible: false,
1029
+ topMargin: -0.5
1030
+ },
1031
+
1032
+ tooltip: {
1033
+ text: '',
1034
+ cssClass: 'toolTip text-xs',
1035
+ mapboxStyle: false,
1036
+ topMargin: 0
1037
+ },
1038
+
1039
+ sphere: {
1040
+ position: [0, 0, 0],
1041
+ radius: 1,
1042
+ sides: 20,
1043
+ units: 'scene',
1044
+ material: 'MeshBasicMaterial',
1045
+ anchor: 'bottom-left',
1046
+ bbox: true,
1047
+ tooltip: true,
1048
+ raycasted: true
1049
+
1050
+ },
1051
+
1052
+ tube: {
1053
+ geometry: null,
1054
+ radius: 1,
1055
+ sides: 6,
1056
+ units: 'scene',
1057
+ material: 'MeshBasicMaterial',
1058
+ anchor: 'center',
1059
+ bbox: true,
1060
+ tooltip: true,
1061
+ raycasted: true
1062
+ },
1063
+
1064
+ loadObj: {
1065
+ type: null,
1066
+ obj: null,
1067
+ units: 'scene',
1068
+ scale: 1,
1069
+ rotation: 0,
1070
+ defaultAnimation: 0,
1071
+ anchor: 'bottom-left',
1072
+ bbox: true,
1073
+ tooltip: true,
1074
+ raycasted: true,
1075
+ clone: true,
1076
+ withCredentials: false
1077
+ },
1078
+
1079
+ Object3D: {
1080
+ obj: null,
1081
+ units: 'scene',
1082
+ anchor: 'bottom-left',
1083
+ bbox: true,
1084
+ tooltip: true,
1085
+ raycasted: true
1086
+ },
1087
+
1088
+ extrusion: {
1089
+ coordinates: [[[]]],
1090
+ geometryOptions: {},
1091
+ height: 100,
1092
+ materials: new THREE.MeshPhongMaterial({ color: 0x660000, side: THREE.DoubleSide }),
1093
+ scale: 1,
1094
+ rotation: 0,
1095
+ units: 'scene',
1096
+ anchor: 'center',
1097
+ bbox: true,
1098
+ tooltip: true,
1099
+ raycasted: true
1100
+
1101
+ }
1102
+ },
1103
+
1104
+ geometries: {
1105
+ line: ['LineString'],
1106
+ tube: ['LineString'],
1107
+ sphere: ['Point'],
1108
+ }
1109
+ }
1110
+
1111
+ export default Objects;