@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.
- package/LICENSE.txt +97 -0
- package/README.md +199 -0
- package/dist/threebox.cjs +2 -0
- package/dist/threebox.cjs.map +1 -0
- package/dist/threebox.iife.js +2 -0
- package/dist/threebox.iife.js.map +1 -0
- package/dist/threebox.js +3521 -0
- package/dist/threebox.js.map +1 -0
- package/package.json +57 -0
- package/src/Threebox.js +1201 -0
- package/src/animation/AnimationManager.js +482 -0
- package/src/camera/CameraSync.js +298 -0
- package/src/index.js +8 -0
- package/src/objects/CSS2DRenderer.js +236 -0
- package/src/objects/LabelRenderer.js +70 -0
- package/src/objects/Object3D.js +32 -0
- package/src/objects/effects/BuildingShadows.js +163 -0
- package/src/objects/extrusion.js +59 -0
- package/src/objects/fflate.min.js +6 -0
- package/src/objects/label.js +25 -0
- package/src/objects/line.js +45 -0
- package/src/objects/loadObj.js +139 -0
- package/src/objects/objects.js +1111 -0
- package/src/objects/sphere.js +22 -0
- package/src/objects/tooltip.js +26 -0
- package/src/objects/tube.js +28 -0
- package/src/utils/ValueGenerator.js +11 -0
- package/src/utils/constants.js +23 -0
- package/src/utils/material.js +52 -0
- package/src/utils/suncalc.js +311 -0
- package/src/utils/utils.js +420 -0
- package/src/utils/validate.js +114 -0
|
@@ -0,0 +1,298 @@
|
|
|
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 ThreeboxConstants from '../utils/constants.js';
|
|
8
|
+
|
|
9
|
+
function CameraSync(map, camera, world) {
|
|
10
|
+
// console.log("CameraSync constructor");
|
|
11
|
+
this.map = map;
|
|
12
|
+
this.camera = camera;
|
|
13
|
+
this.active = true;
|
|
14
|
+
|
|
15
|
+
this.camera.matrixAutoUpdate = false; // We're in charge of the camera now!
|
|
16
|
+
|
|
17
|
+
// Postion and configure the world group so we can scale it appropriately when the camera zooms
|
|
18
|
+
this.world = world || new THREE.Group();
|
|
19
|
+
this.world.position.x = this.world.position.y = ThreeboxConstants.WORLD_SIZE / 2
|
|
20
|
+
this.world.matrixAutoUpdate = false;
|
|
21
|
+
|
|
22
|
+
// set up basic camera state
|
|
23
|
+
this.state = {
|
|
24
|
+
translateCenter: new THREE.Matrix4().makeTranslation(ThreeboxConstants.WORLD_SIZE / 2, -ThreeboxConstants.WORLD_SIZE / 2, 0),
|
|
25
|
+
worldSizeRatio: ThreeboxConstants.TILE_SIZE / ThreeboxConstants.WORLD_SIZE,
|
|
26
|
+
worldSize: ThreeboxConstants.TILE_SIZE * this.map.transform.scale
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Listen for move events from the map and update the Three.js camera
|
|
30
|
+
let _this = this; // keep the function on _this
|
|
31
|
+
this.map
|
|
32
|
+
.on('move', function () {
|
|
33
|
+
_this.updateCamera();
|
|
34
|
+
})
|
|
35
|
+
.on('resize', function () {
|
|
36
|
+
_this.setupCamera();
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
this.setupCamera();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
CameraSync.prototype = {
|
|
43
|
+
setupCamera: function () {
|
|
44
|
+
const t = this.map.transform;
|
|
45
|
+
this.camera.aspect = t.width / t.height; //bug fixed, if aspect is not reset raycast will fail on map resize
|
|
46
|
+
this.halfFov = t._fov / 2;
|
|
47
|
+
this.cameraToCenterDistance = 0.5 / Math.tan(this.halfFov) * t.height;
|
|
48
|
+
const maxPitch = t._maxPitch * Math.PI / 180;
|
|
49
|
+
this.acuteAngle = Math.PI / 2 - maxPitch;
|
|
50
|
+
this.updateCamera();
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
updateCamera: function (ev) {
|
|
54
|
+
if (!this.camera) {
|
|
55
|
+
console.log('nocamera')
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const t = this.map.transform;
|
|
60
|
+
this.camera.aspect = t.width / t.height; //bug fixed, if aspect is not reset raycast will fail on map resize
|
|
61
|
+
const offset = t.centerOffset || new THREE.Vector3(); //{ x: t.width / 2, y: t.height / 2 };
|
|
62
|
+
let farZ = 0;
|
|
63
|
+
let furthestDistance = 0;
|
|
64
|
+
this.halfFov = t._fov / 2;
|
|
65
|
+
const groundAngle = Math.PI / 2 + t._pitch;
|
|
66
|
+
const pitchAngle = Math.cos((Math.PI / 2) - t._pitch); //pitch seems to influence heavily the depth calculation and cannot be more than 60 = PI/3 < v1 and 85 > v2
|
|
67
|
+
this.cameraToCenterDistance = 0.5 / Math.tan(this.halfFov) * t.height;
|
|
68
|
+
let pixelsPerMeter = 1;
|
|
69
|
+
const worldSize = this.worldSize();
|
|
70
|
+
|
|
71
|
+
if (this.map.tb.mapboxVersion >= 2.0) {
|
|
72
|
+
// mapbox version >= 2.0
|
|
73
|
+
pixelsPerMeter = this.mercatorZfromAltitude(1, t.center.lat) * worldSize;
|
|
74
|
+
const fovAboveCenter = t._fov * (0.5 + t.centerOffset.y / t.height);
|
|
75
|
+
|
|
76
|
+
// Adjust distance to MSL by the minimum possible elevation visible on screen,
|
|
77
|
+
// this way the far plane is pushed further in the case of negative elevation.
|
|
78
|
+
const minElevationInPixels = t.elevation ? t.elevation.getMinElevationBelowMSL() * pixelsPerMeter : 0;
|
|
79
|
+
const cameraToSeaLevelDistance = ((t._camera.position[2] * worldSize) - minElevationInPixels) / Math.cos(t._pitch);
|
|
80
|
+
const topHalfSurfaceDistance = Math.sin(fovAboveCenter) * cameraToSeaLevelDistance / Math.sin(utils.clamp(Math.PI - groundAngle - fovAboveCenter, 0.01, Math.PI - 0.01));
|
|
81
|
+
|
|
82
|
+
// Calculate z distance of the farthest fragment that should be rendered.
|
|
83
|
+
furthestDistance = pitchAngle * topHalfSurfaceDistance + cameraToSeaLevelDistance;
|
|
84
|
+
|
|
85
|
+
// Add a bit extra to avoid precision problems when a fragment's distance is exactly `furthestDistance`
|
|
86
|
+
const horizonDistance = cameraToSeaLevelDistance * (1 / t._horizonShift);
|
|
87
|
+
farZ = Math.min(furthestDistance * 1.01, horizonDistance);
|
|
88
|
+
} else {
|
|
89
|
+
// mapbox version < 2.0 or azure maps
|
|
90
|
+
// Furthest distance optimized by @jscastro76
|
|
91
|
+
const topHalfSurfaceDistance = Math.sin(this.halfFov) * this.cameraToCenterDistance / Math.sin(Math.PI - groundAngle - this.halfFov);
|
|
92
|
+
|
|
93
|
+
// Calculate z distance of the farthest fragment that should be rendered.
|
|
94
|
+
furthestDistance = pitchAngle * topHalfSurfaceDistance + this.cameraToCenterDistance;
|
|
95
|
+
|
|
96
|
+
// Add a bit extra to avoid precision problems when a fragment's distance is exactly `furthestDistance`
|
|
97
|
+
farZ = furthestDistance * 1.01;
|
|
98
|
+
}
|
|
99
|
+
this.cameraTranslateZ = new THREE.Matrix4().makeTranslation(0, 0, this.cameraToCenterDistance);
|
|
100
|
+
|
|
101
|
+
// someday @ansis set further near plane to fix precision for deckgl,so we should fix it to use mapbox-gl v1.3+ correctly
|
|
102
|
+
// https://github.com/mapbox/mapbox-gl-js/commit/5cf6e5f523611bea61dae155db19a7cb19eb825c#diff-5dddfe9d7b5b4413ee54284bc1f7966d
|
|
103
|
+
const nz = (t.height / 50); //min near z as coded by @ansis
|
|
104
|
+
const nearZ = Math.max(nz * pitchAngle, nz); //on changes in the pitch nz could be too low
|
|
105
|
+
|
|
106
|
+
const h = t.height;
|
|
107
|
+
const w = t.width;
|
|
108
|
+
if (this.camera instanceof THREE.OrthographicCamera) {
|
|
109
|
+
this.camera.projectionMatrix = utils.makeOrthographicMatrix(w / - 2, w / 2, h / 2, h / - 2, nearZ, farZ);
|
|
110
|
+
} else {
|
|
111
|
+
this.camera.projectionMatrix = utils.makePerspectiveMatrix(t._fov, w / h, nearZ, farZ);
|
|
112
|
+
}
|
|
113
|
+
this.camera.projectionMatrix.elements[8] = -offset.x * 2 / t.width;
|
|
114
|
+
this.camera.projectionMatrix.elements[9] = offset.y * 2 / t.height;
|
|
115
|
+
|
|
116
|
+
// Unlike the Mapbox GL JS camera, separate camera translation and rotation out into its world matrix
|
|
117
|
+
// If this is applied directly to the projection matrix, it will work OK but break raycasting
|
|
118
|
+
let cameraWorldMatrix = this.calcCameraMatrix(t._pitch, t.angle);
|
|
119
|
+
// When terrain layers are included, height of 3D layers must be modified from t_camera.z * worldSize
|
|
120
|
+
if (t.elevation) cameraWorldMatrix.elements[14] = t._camera.position[2] * worldSize;
|
|
121
|
+
//this.camera.matrixWorld.elements is equivalent to t._camera._transform
|
|
122
|
+
this.camera.matrixWorld.copy(cameraWorldMatrix);
|
|
123
|
+
|
|
124
|
+
let zoomPow = t.scale * this.state.worldSizeRatio;
|
|
125
|
+
// Handle scaling and translation of objects in the map in the world's matrix transform, not the camera
|
|
126
|
+
let scale = new THREE.Matrix4;
|
|
127
|
+
let translateMap = new THREE.Matrix4;
|
|
128
|
+
let rotateMap = new THREE.Matrix4;
|
|
129
|
+
|
|
130
|
+
scale.makeScale(zoomPow, zoomPow, zoomPow);
|
|
131
|
+
|
|
132
|
+
let x = t.x || t.point.x;
|
|
133
|
+
let y = t.y || t.point.y;
|
|
134
|
+
translateMap.makeTranslation(-x, y, 0);
|
|
135
|
+
rotateMap.makeRotationZ(Math.PI);
|
|
136
|
+
|
|
137
|
+
this.world.matrix = new THREE.Matrix4()
|
|
138
|
+
.premultiply(rotateMap)
|
|
139
|
+
.premultiply(this.state.translateCenter)
|
|
140
|
+
.premultiply(scale)
|
|
141
|
+
.premultiply(translateMap)
|
|
142
|
+
|
|
143
|
+
// utils.prettyPrintMatrix(this.camera.projectionMatrix.elements);
|
|
144
|
+
this.map.fire('CameraSynced', { detail: { nearZ: nearZ, farZ: farZ, pitch: t._pitch, angle: t.angle, furthestDistance: furthestDistance, cameraToCenterDistance: this.cameraToCenterDistance, t: this.map.transform, tbProjMatrix: this.camera.projectionMatrix.elements, tbWorldMatrix: this.world.matrix.elements, cameraSyn: CameraSync } });
|
|
145
|
+
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
worldSize() {
|
|
149
|
+
let t = this.map.transform;
|
|
150
|
+
return t.tileSize * t.scale;
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
worldSizeFromZoom() {
|
|
154
|
+
let t = this.map.transform;
|
|
155
|
+
return Math.pow(2.0, t.zoom) * t.tileSize;
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
mercatorZfromAltitude(altitude, lat) {
|
|
159
|
+
return altitude / this.circumferenceAtLatitude(lat);
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
mercatorZfromZoom() {
|
|
163
|
+
return this.cameraToCenterDistance / this.worldSizeFromZoom();
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
circumferenceAtLatitude(latitude) {
|
|
167
|
+
return ThreeboxConstants.EARTH_CIRCUMFERENCE * Math.cos(latitude * Math.PI / 180);
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
calcCameraMatrix(pitch, angle, trz) {
|
|
171
|
+
const t = this.map.transform;
|
|
172
|
+
const _pitch = (pitch === undefined) ? t._pitch : pitch;
|
|
173
|
+
const _angle = (angle === undefined) ? t.angle : angle;
|
|
174
|
+
const _trz = (trz === undefined) ? this.cameraTranslateZ : trz;
|
|
175
|
+
|
|
176
|
+
return new THREE.Matrix4()
|
|
177
|
+
.premultiply(_trz)
|
|
178
|
+
.premultiply(new THREE.Matrix4().makeRotationX(_pitch))
|
|
179
|
+
.premultiply(new THREE.Matrix4().makeRotationZ(_angle));
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
updateCameraState() {
|
|
183
|
+
let t = this.map.transform;
|
|
184
|
+
if (!t.height) return;
|
|
185
|
+
|
|
186
|
+
// Set camera orientation and move it to a proper distance from the map
|
|
187
|
+
//t._camera.setPitchBearing(t._pitch, t.angle);
|
|
188
|
+
|
|
189
|
+
const dir = t._camera.forward();
|
|
190
|
+
const distance = t.cameraToCenterDistance;
|
|
191
|
+
const center = t.point;
|
|
192
|
+
|
|
193
|
+
// Use camera zoom (if terrain is enabled) to maintain constant altitude to sea level
|
|
194
|
+
const zoom = t._cameraZoom ? t._cameraZoom : t._zoom;
|
|
195
|
+
const altitude = this.mercatorZfromZoom(t);
|
|
196
|
+
const height = altitude - this.mercatorZfromAltitude(t._centerAltitude, t.center.lat);
|
|
197
|
+
|
|
198
|
+
// simplified version of: this._worldSizeFromZoom(this._zoomFromMercatorZ(height))
|
|
199
|
+
const updatedWorldSize = t.cameraToCenterDistance / height;
|
|
200
|
+
return [
|
|
201
|
+
center.x / this.worldSize() - (dir[0] * distance) / updatedWorldSize,
|
|
202
|
+
center.y / this.worldSize() - (dir[1] * distance) / updatedWorldSize,
|
|
203
|
+
this.mercatorZfromAltitude(t._centerAltitude, t._center.lat) + (-dir[2] * distance) / updatedWorldSize
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
getWorldToCamera(worldSize, pixelsPerMeter) {
|
|
209
|
+
// transformation chain from world space to camera space:
|
|
210
|
+
// 1. Height value (z) of renderables is in meters. Scale z coordinate by pixelsPerMeter
|
|
211
|
+
// 2. Transform from pixel coordinates to camera space with cameraMatrix^-1
|
|
212
|
+
// 3. flip Y if required
|
|
213
|
+
|
|
214
|
+
// worldToCamera: flip * cam^-1 * zScale
|
|
215
|
+
// cameraToWorld: (flip * cam^-1 * zScale)^-1 => (zScale^-1 * cam * flip^-1)
|
|
216
|
+
let t = this.map.transform;
|
|
217
|
+
const matrix = new THREE.Matrix4();
|
|
218
|
+
const matrixT = new THREE.Matrix4();
|
|
219
|
+
|
|
220
|
+
// Compute inverse of camera matrix and post-multiply negated translation
|
|
221
|
+
const o = t._camera._orientation;
|
|
222
|
+
const p = t._camera.position;
|
|
223
|
+
const invPosition = new THREE.Vector3(p[0], p[1], p[2]);
|
|
224
|
+
|
|
225
|
+
const quat = new THREE.Quaternion();
|
|
226
|
+
quat.set(o[0], o[1], o[2], o[3]);
|
|
227
|
+
const invOrientation = quat.conjugate();
|
|
228
|
+
invPosition.multiplyScalar(-worldSize);
|
|
229
|
+
|
|
230
|
+
matrixT.makeTranslation(invPosition.x, invPosition.y, invPosition.z);
|
|
231
|
+
matrix
|
|
232
|
+
.makeRotationFromQuaternion(invOrientation)
|
|
233
|
+
.premultiply(matrixT);
|
|
234
|
+
//this would make the matrix exact to getWorldToCamera but breaks
|
|
235
|
+
//this.translate(matrix.elements, matrix.elements, invPosition);
|
|
236
|
+
|
|
237
|
+
// Pre-multiply y (2nd row)
|
|
238
|
+
matrix.elements[1] *= -1.0;
|
|
239
|
+
matrix.elements[5] *= -1.0;
|
|
240
|
+
matrix.elements[9] *= -1.0;
|
|
241
|
+
matrix.elements[13] *= -1.0;
|
|
242
|
+
|
|
243
|
+
// Post-multiply z (3rd column)
|
|
244
|
+
matrix.elements[8] *= pixelsPerMeter;
|
|
245
|
+
matrix.elements[9] *= pixelsPerMeter;
|
|
246
|
+
matrix.elements[10] *= pixelsPerMeter;
|
|
247
|
+
matrix.elements[11] *= pixelsPerMeter;
|
|
248
|
+
//console.log(matrix.elements);
|
|
249
|
+
return matrix;
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
translate(out, a, v) {
|
|
253
|
+
let x = v[0] || v.x,
|
|
254
|
+
y = v[1] || v.y,
|
|
255
|
+
z = v[2] || v.z;
|
|
256
|
+
let a00, a01, a02, a03;
|
|
257
|
+
let a10, a11, a12, a13;
|
|
258
|
+
let a20, a21, a22, a23;
|
|
259
|
+
if (a === out) {
|
|
260
|
+
out[12] = a[0] * x + a[4] * y + a[8] * z + a[12];
|
|
261
|
+
out[13] = a[1] * x + a[5] * y + a[9] * z + a[13];
|
|
262
|
+
out[14] = a[2] * x + a[6] * y + a[10] * z + a[14];
|
|
263
|
+
out[15] = a[3] * x + a[7] * y + a[11] * z + a[15];
|
|
264
|
+
} else {
|
|
265
|
+
a00 = a[0];
|
|
266
|
+
a01 = a[1];
|
|
267
|
+
a02 = a[2];
|
|
268
|
+
a03 = a[3];
|
|
269
|
+
a10 = a[4];
|
|
270
|
+
a11 = a[5];
|
|
271
|
+
a12 = a[6];
|
|
272
|
+
a13 = a[7];
|
|
273
|
+
a20 = a[8];
|
|
274
|
+
a21 = a[9];
|
|
275
|
+
a22 = a[10];
|
|
276
|
+
a23 = a[11];
|
|
277
|
+
out[0] = a00;
|
|
278
|
+
out[1] = a01;
|
|
279
|
+
out[2] = a02;
|
|
280
|
+
out[3] = a03;
|
|
281
|
+
out[4] = a10;
|
|
282
|
+
out[5] = a11;
|
|
283
|
+
out[6] = a12;
|
|
284
|
+
out[7] = a13;
|
|
285
|
+
out[8] = a20;
|
|
286
|
+
out[9] = a21;
|
|
287
|
+
out[10] = a22;
|
|
288
|
+
out[11] = a23;
|
|
289
|
+
out[12] = a00 * x + a10 * y + a20 * z + a[12];
|
|
290
|
+
out[13] = a01 * x + a11 * y + a21 * z + a[13];
|
|
291
|
+
out[14] = a02 * x + a12 * y + a22 * z + a[14];
|
|
292
|
+
out[15] = a03 * x + a13 * y + a23 * z + a[15];
|
|
293
|
+
}
|
|
294
|
+
return out;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export default CameraSync;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author mrdoob / http://mrdoob.com/
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as THREE from 'three';
|
|
6
|
+
|
|
7
|
+
class CSS2DObject extends THREE.Object3D {
|
|
8
|
+
|
|
9
|
+
constructor(element) {
|
|
10
|
+
|
|
11
|
+
super();
|
|
12
|
+
this.element = element || document.createElement('div');
|
|
13
|
+
this.element.style.position = 'absolute';
|
|
14
|
+
this.element.style.userSelect = 'none';
|
|
15
|
+
this.element.setAttribute('draggable', false);
|
|
16
|
+
|
|
17
|
+
//[jscastro] some labels must be always visible
|
|
18
|
+
this.alwaysVisible = false;
|
|
19
|
+
|
|
20
|
+
//[jscastro] layer is needed to be rendered/hidden based on layer visibility
|
|
21
|
+
Object.defineProperty(this, 'layer', {
|
|
22
|
+
get() { return (this.parent && this.parent.parent ? this.parent.parent.layer : null) }
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
//[jscastro] implement dispose
|
|
26
|
+
this.dispose = function () {
|
|
27
|
+
this.remove();
|
|
28
|
+
this.element = null;
|
|
29
|
+
}
|
|
30
|
+
//[jscastro] implement explicit method
|
|
31
|
+
this.remove = function () {
|
|
32
|
+
if (this.element instanceof Element && this.element.parentNode !== null) {
|
|
33
|
+
this.element.parentNode.removeChild(this.element);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.addEventListener('removed', function () {
|
|
38
|
+
|
|
39
|
+
this.remove();
|
|
40
|
+
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
copy(source, recursive) {
|
|
46
|
+
|
|
47
|
+
super.copy(source, recursive);
|
|
48
|
+
this.element = source.element.cloneNode(true);
|
|
49
|
+
return this;
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
CSS2DObject.prototype.isCSS2DObject = true;
|
|
56
|
+
|
|
57
|
+
const _vector = new THREE.Vector3();
|
|
58
|
+
|
|
59
|
+
const _viewMatrix = new THREE.Matrix4();
|
|
60
|
+
|
|
61
|
+
const _viewProjectionMatrix = new THREE.Matrix4();
|
|
62
|
+
|
|
63
|
+
const _a = new THREE.Vector3();
|
|
64
|
+
|
|
65
|
+
const _b = new THREE.Vector3();
|
|
66
|
+
|
|
67
|
+
class CSS2DRenderer {
|
|
68
|
+
|
|
69
|
+
constructor() {
|
|
70
|
+
|
|
71
|
+
const _this = this;
|
|
72
|
+
|
|
73
|
+
let _width, _height;
|
|
74
|
+
|
|
75
|
+
let _widthHalf, _heightHalf;
|
|
76
|
+
|
|
77
|
+
const cache = {
|
|
78
|
+
objects: new WeakMap(),
|
|
79
|
+
list: new Map()
|
|
80
|
+
};
|
|
81
|
+
this.cacheList = cache.list;
|
|
82
|
+
const domElement = document.createElement('div');
|
|
83
|
+
domElement.style.overflow = 'hidden';
|
|
84
|
+
this.domElement = domElement;
|
|
85
|
+
|
|
86
|
+
this.getSize = function () {
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
width: _width,
|
|
90
|
+
height: _height
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
this.render = function (scene, camera) {
|
|
96
|
+
|
|
97
|
+
if (scene.autoUpdate === true) scene.updateMatrixWorld();
|
|
98
|
+
if (camera.parent === null) camera.updateMatrixWorld();
|
|
99
|
+
|
|
100
|
+
_viewMatrix.copy(camera.matrixWorldInverse);
|
|
101
|
+
|
|
102
|
+
_viewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, _viewMatrix);
|
|
103
|
+
|
|
104
|
+
renderObject(scene, scene, camera);
|
|
105
|
+
zOrder(scene);
|
|
106
|
+
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
this.setSize = function (width, height) {
|
|
110
|
+
|
|
111
|
+
_width = width;
|
|
112
|
+
_height = height;
|
|
113
|
+
_widthHalf = _width / 2;
|
|
114
|
+
_heightHalf = _height / 2;
|
|
115
|
+
domElement.style.width = width + 'px';
|
|
116
|
+
domElement.style.height = height + 'px';
|
|
117
|
+
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
function renderObject(object, scene, camera) {
|
|
121
|
+
|
|
122
|
+
if (object.isCSS2DObject) {
|
|
123
|
+
|
|
124
|
+
//[jscastro] optimize performance and don't update and remove the labels that are not visible
|
|
125
|
+
if (!object.visible) {
|
|
126
|
+
cache.objects.delete({ key: object.uuid });
|
|
127
|
+
cache.list.delete(object.uuid);
|
|
128
|
+
object.remove();
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
|
|
132
|
+
object.onBeforeRender(_this, scene, camera);
|
|
133
|
+
|
|
134
|
+
_vector.setFromMatrixPosition(object.matrixWorld);
|
|
135
|
+
|
|
136
|
+
_vector.applyMatrix4(_viewProjectionMatrix);
|
|
137
|
+
|
|
138
|
+
const element = object.element;
|
|
139
|
+
var style;
|
|
140
|
+
if (/apple/i.test(navigator.vendor)) {
|
|
141
|
+
|
|
142
|
+
// https://github.com/mrdoob/three.js/issues/21415
|
|
143
|
+
style = 'translate(-50%,-50%) translate(' + Math.round(_vector.x * _widthHalf + _widthHalf) + 'px,' + Math.round(- _vector.y * _heightHalf + _heightHalf) + 'px)';
|
|
144
|
+
|
|
145
|
+
} else {
|
|
146
|
+
|
|
147
|
+
style = 'translate(-50%,-50%) translate(' + (_vector.x * _widthHalf + _widthHalf) + 'px,' + (- _vector.y * _heightHalf + _heightHalf) + 'px)';
|
|
148
|
+
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
element.style.WebkitTransform = style;
|
|
152
|
+
element.style.MozTransform = style;
|
|
153
|
+
element.style.oTransform = style;
|
|
154
|
+
element.style.transform = style;
|
|
155
|
+
|
|
156
|
+
element.style.display = object.visible && _vector.z >= - 1 && _vector.z <= 1 ? '' : 'none';
|
|
157
|
+
|
|
158
|
+
const objectData = {
|
|
159
|
+
distanceToCameraSquared: getDistanceToSquared(camera, object)
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
cache.objects.set({ key: object.uuid }, objectData);
|
|
163
|
+
cache.list.set(object.uuid, object);
|
|
164
|
+
|
|
165
|
+
if (element.parentNode !== domElement) {
|
|
166
|
+
|
|
167
|
+
domElement.appendChild(element);
|
|
168
|
+
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
object.onAfterRender(_this, scene, camera);
|
|
172
|
+
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
for (let i = 0, l = object.children.length; i < l; i++) {
|
|
177
|
+
|
|
178
|
+
renderObject(object.children[i], scene, camera);
|
|
179
|
+
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function getDistanceToSquared(object1, object2) {
|
|
186
|
+
|
|
187
|
+
_a.setFromMatrixPosition(object1.matrixWorld);
|
|
188
|
+
|
|
189
|
+
_b.setFromMatrixPosition(object2.matrixWorld);
|
|
190
|
+
|
|
191
|
+
return _a.distanceToSquared(_b);
|
|
192
|
+
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function filterAndFlatten(scene) {
|
|
196
|
+
|
|
197
|
+
const result = [];
|
|
198
|
+
scene.traverse(function (object) {
|
|
199
|
+
|
|
200
|
+
if (object.isCSS2DObject) result.push(object);
|
|
201
|
+
|
|
202
|
+
});
|
|
203
|
+
return result;
|
|
204
|
+
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function zOrder(scene) {
|
|
208
|
+
|
|
209
|
+
const sorted = filterAndFlatten(scene).sort(function (a, b) {
|
|
210
|
+
//[jscastro] check the objects already exist in the cache
|
|
211
|
+
let cacheA = cache.objects.get({ key: a.uuid });
|
|
212
|
+
let cacheB = cache.objects.get({ key: b.uuid });
|
|
213
|
+
|
|
214
|
+
if (cacheA && cacheB) {
|
|
215
|
+
const distanceA = cacheA.distanceToCameraSquared;
|
|
216
|
+
const distanceB = cacheB.distanceToCameraSquared;
|
|
217
|
+
return distanceA - distanceB;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const zMax = sorted.length;
|
|
223
|
+
|
|
224
|
+
for (let i = 0, l = sorted.length; i < l; i++) {
|
|
225
|
+
|
|
226
|
+
sorted[i].element.style.zIndex = zMax - i;
|
|
227
|
+
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export { CSS2DRenderer, CSS2DObject };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author jscastro / https://github.com/jscastro76
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { CSS2DRenderer } from './CSS2DRenderer.js';
|
|
6
|
+
|
|
7
|
+
function LabelRenderer(map) {
|
|
8
|
+
|
|
9
|
+
this.map = map;
|
|
10
|
+
|
|
11
|
+
this.renderer = new CSS2DRenderer();
|
|
12
|
+
|
|
13
|
+
this.renderer.setSize(this.map.getCanvas().clientWidth, this.map.getCanvas().clientHeight);
|
|
14
|
+
this.renderer.domElement.style.position = 'absolute';
|
|
15
|
+
this.renderer.domElement.id = 'labelCanvas'; //TODO: this value must come by parameter
|
|
16
|
+
this.renderer.domElement.style.top = 0;
|
|
17
|
+
this.renderer.domElement.style.zIndex = "0";
|
|
18
|
+
this.map.getCanvasContainer().appendChild(this.renderer.domElement);
|
|
19
|
+
|
|
20
|
+
this.scene, this.camera;
|
|
21
|
+
|
|
22
|
+
this.dispose = function () {
|
|
23
|
+
this.map.getCanvasContainer().removeChild(this.renderer.domElement)
|
|
24
|
+
this.renderer.domElement.remove();
|
|
25
|
+
this.renderer = {};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this.setSize = function (width, height) {
|
|
29
|
+
this.renderer.setSize(width, height);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.map.on('resize', function () {
|
|
33
|
+
this.renderer.setSize(this.map.getCanvas().clientWidth, this.map.getCanvas().clientHeight);
|
|
34
|
+
}.bind(this));
|
|
35
|
+
|
|
36
|
+
this.state = {
|
|
37
|
+
reset: function () {
|
|
38
|
+
//TODO: Implement a good state reset, check out what is made in WebGlRenderer
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.render = async function (scene, camera) {
|
|
43
|
+
this.scene = scene;
|
|
44
|
+
this.camera = camera;
|
|
45
|
+
return new Promise((resolve) => { resolve(this.renderer.render(scene, camera)) });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
//[jscastro] method to toggle Layer visibility
|
|
49
|
+
this.toggleLabels = async function (layerId, visible) {
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
resolve(this.setVisibility(layerId, visible, this.scene, this.camera, this.renderer));
|
|
52
|
+
})
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
//[jscastro] method to set visibility
|
|
56
|
+
this.setVisibility = function (layerId, visible, scene, camera, renderer) {
|
|
57
|
+
var cache = this.renderer.cacheList;
|
|
58
|
+
cache.forEach(function (l) {
|
|
59
|
+
if (l.visible != visible && l.layer === layerId) {
|
|
60
|
+
if ((visible && l.alwaysVisible) || !visible) {
|
|
61
|
+
l.visible = visible;
|
|
62
|
+
renderer.renderObject(l, scene, camera);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default LabelRenderer;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author peterqliu / https://github.com/peterqliu
|
|
3
|
+
* @author jscastro / https://github.com/jscastro76
|
|
4
|
+
*/
|
|
5
|
+
import Objects from './objects.js';
|
|
6
|
+
import utils from '../utils/utils.js';
|
|
7
|
+
|
|
8
|
+
function Object3D(opt) {
|
|
9
|
+
opt = utils._validate(opt, Objects.prototype._defaults.Object3D);
|
|
10
|
+
// [jscastro] full refactor of Object3D to behave exactly like 3D Models loadObj
|
|
11
|
+
let obj = opt.obj;
|
|
12
|
+
// [jscastro] options.rotation was wrongly used
|
|
13
|
+
const r = utils.types.rotation(opt.rotation, [0, 0, 0]);
|
|
14
|
+
const s = utils.types.scale(opt.scale, [1, 1, 1]);
|
|
15
|
+
obj.rotation.set(r[0], r[1], r[2]);
|
|
16
|
+
obj.scale.set(s[0], s[1], s[2]);
|
|
17
|
+
obj.name = "model";
|
|
18
|
+
let userScaleGroup = Objects.prototype._makeGroup(obj, opt);
|
|
19
|
+
opt.obj.name = "model";
|
|
20
|
+
Objects.prototype._addMethods(userScaleGroup);
|
|
21
|
+
//[jscastro] calculate automatically the pivotal center of the object
|
|
22
|
+
userScaleGroup.setAnchor(opt.anchor);
|
|
23
|
+
//[jscastro] override the center calculated if the object has adjustments
|
|
24
|
+
userScaleGroup.setCenter(opt.adjustment);
|
|
25
|
+
//[jscastro] if the object is excluded from raycasting
|
|
26
|
+
userScaleGroup.raycasted = opt.raycasted;
|
|
27
|
+
userScaleGroup.visibility = true;
|
|
28
|
+
|
|
29
|
+
return userScaleGroup
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default Object3D;
|