@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,482 @@
|
|
|
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
|
+
|
|
8
|
+
function AnimationManager(map) {
|
|
9
|
+
|
|
10
|
+
this.map = map
|
|
11
|
+
this.enrolledObjects = [];
|
|
12
|
+
this.previousFrameTime;
|
|
13
|
+
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
AnimationManager.prototype = {
|
|
17
|
+
|
|
18
|
+
unenroll: function (obj) {
|
|
19
|
+
this.enrolledObjects.splice(this.enrolledObjects.indexOf(obj), 1);
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
enroll: function (obj) {
|
|
23
|
+
|
|
24
|
+
//[jscastro] add the object default animations
|
|
25
|
+
obj.clock = new THREE.Clock();
|
|
26
|
+
obj.hasDefaultAnimation = false;
|
|
27
|
+
obj.defaultAction;
|
|
28
|
+
obj.actions = [];
|
|
29
|
+
obj.mixer;
|
|
30
|
+
|
|
31
|
+
//[jscastro] if the object includes animations
|
|
32
|
+
if (obj.animations && obj.animations.length > 0) {
|
|
33
|
+
|
|
34
|
+
obj.hasDefaultAnimation = true;
|
|
35
|
+
|
|
36
|
+
//check first if a defaultAnimation is defined by options
|
|
37
|
+
let daIndex = (obj.userData.defaultAnimation ? obj.userData.defaultAnimation : 0);
|
|
38
|
+
obj.mixer = new THREE.AnimationMixer(obj);
|
|
39
|
+
|
|
40
|
+
setAction(daIndex);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//[jscastro] set the action to play
|
|
44
|
+
function setAction(animationIndex) {
|
|
45
|
+
for (let i = 0; i < obj.animations.length; i++) {
|
|
46
|
+
|
|
47
|
+
if (animationIndex > obj.animations.length)
|
|
48
|
+
console.log("The animation index " + animationIndex + " doesn't exist for this object");
|
|
49
|
+
let animation = obj.animations[i];
|
|
50
|
+
let action = obj.mixer.clipAction(animation);
|
|
51
|
+
obj.actions.push(action);
|
|
52
|
+
|
|
53
|
+
//select the default animation and set the weight to 1
|
|
54
|
+
if (animationIndex === i) {
|
|
55
|
+
obj.defaultAction = action;
|
|
56
|
+
action.setEffectiveWeight(1);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
action.setEffectiveWeight(0);
|
|
60
|
+
}
|
|
61
|
+
action.play();
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let _isPlaying = false;
|
|
67
|
+
//[jscastro] added property for isPlaying state
|
|
68
|
+
Object.defineProperty(obj, 'isPlaying', {
|
|
69
|
+
get() { return _isPlaying; },
|
|
70
|
+
set(value) {
|
|
71
|
+
if (_isPlaying != value) {
|
|
72
|
+
_isPlaying = value;
|
|
73
|
+
// Dispatch new event IsPlayingChanged
|
|
74
|
+
obj.dispatchEvent({ type: 'IsPlayingChanged', detail: obj});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
/* Extend the provided object with animation-specific properties and track in the animation manager */
|
|
80
|
+
this.enrolledObjects.push(obj);
|
|
81
|
+
|
|
82
|
+
// Give this object its own internal animation queue
|
|
83
|
+
obj.animationQueue = [];
|
|
84
|
+
|
|
85
|
+
obj.set = function (options) {
|
|
86
|
+
|
|
87
|
+
//if duration is set, animate to the new state
|
|
88
|
+
if (options.duration > 0) {
|
|
89
|
+
|
|
90
|
+
let newParams = {
|
|
91
|
+
start: Date.now(),
|
|
92
|
+
expiration: Date.now() + options.duration,
|
|
93
|
+
endState: {}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
utils.extend(options, newParams);
|
|
97
|
+
|
|
98
|
+
let translating = options.coords;
|
|
99
|
+
let rotating = options.rotation;
|
|
100
|
+
let scaling = options.scale || options.scaleX || options.scaleY || options.scaleZ;
|
|
101
|
+
|
|
102
|
+
if (rotating) {
|
|
103
|
+
|
|
104
|
+
let r = obj.rotation;
|
|
105
|
+
options.startRotation = [r.x, r.y, r.z];
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
options.endState.rotation = utils.types.rotation(options.rotation, options.startRotation);
|
|
109
|
+
options.rotationPerMs = options.endState.rotation
|
|
110
|
+
.map(function (angle, index) {
|
|
111
|
+
return (angle - options.startRotation[index]) / options.duration;
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (scaling) {
|
|
116
|
+
let s = obj.scale;
|
|
117
|
+
options.startScale = [s.x, s.y, s.z];
|
|
118
|
+
options.endState.scale = utils.types.scale(options.scale, options.startScale);
|
|
119
|
+
|
|
120
|
+
options.scalePerMs = options.endState.scale
|
|
121
|
+
.map(function (scale, index) {
|
|
122
|
+
return (scale - options.startScale[index]) / options.duration;
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (translating) options.pathCurve = new THREE.CatmullRomCurve3(utils.lnglatsToWorld([obj.coordinates, options.coords]));
|
|
127
|
+
|
|
128
|
+
let entry = {
|
|
129
|
+
type: 'set',
|
|
130
|
+
parameters: options
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this.animationQueue
|
|
134
|
+
.push(entry);
|
|
135
|
+
|
|
136
|
+
tb.map.repaint = true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
//if no duration set, stop object's existing animations and go to that state immediately
|
|
140
|
+
else {
|
|
141
|
+
this.stop();
|
|
142
|
+
options.rotation = utils.radify(options.rotation);
|
|
143
|
+
this._setObject(options);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return this
|
|
147
|
+
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
//[jscastro] animation method, is set by update method
|
|
151
|
+
obj.animationMethod = null;
|
|
152
|
+
|
|
153
|
+
//[jscastro] stop animation and the queue
|
|
154
|
+
obj.stop = function (index) {
|
|
155
|
+
if (obj.mixer) {
|
|
156
|
+
obj.isPlaying = false;
|
|
157
|
+
cancelAnimationFrame(obj.animationMethod);
|
|
158
|
+
}
|
|
159
|
+
//TODO: if this is removed, it produces an error in
|
|
160
|
+
this.animationQueue = [];
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
obj.followPath = function (options, cb) {
|
|
165
|
+
|
|
166
|
+
let entry = {
|
|
167
|
+
type: 'followPath',
|
|
168
|
+
parameters: utils._validate(options, defaults.followPath)
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
utils.extend(
|
|
172
|
+
entry.parameters,
|
|
173
|
+
{
|
|
174
|
+
pathCurve: new THREE.CatmullRomCurve3(
|
|
175
|
+
utils.lnglatsToWorld(options.path)
|
|
176
|
+
),
|
|
177
|
+
start: Date.now(),
|
|
178
|
+
expiration: Date.now() + entry.parameters.duration,
|
|
179
|
+
cb: cb
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
this.animationQueue
|
|
184
|
+
.push(entry);
|
|
185
|
+
|
|
186
|
+
tb.map.repaint = true;
|
|
187
|
+
|
|
188
|
+
return this;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
obj._setObject = function (options) {
|
|
192
|
+
|
|
193
|
+
//default scale always
|
|
194
|
+
obj.setScale();
|
|
195
|
+
|
|
196
|
+
let p = options.position; // lnglat
|
|
197
|
+
let r = options.rotation; // radians
|
|
198
|
+
let s = options.scale; // custom scale
|
|
199
|
+
let w = options.worldCoordinates; //Vector3
|
|
200
|
+
let q = options.quaternion; // [axis, angle in rads]
|
|
201
|
+
let t = options.translate; // [jscastro] lnglat + height for 3D objects
|
|
202
|
+
let wt = options.worldTranslate; // [jscastro] Vector3 translation
|
|
203
|
+
|
|
204
|
+
if (p) {
|
|
205
|
+
this.coordinates = p;
|
|
206
|
+
let c = utils.projectToWorld(p);
|
|
207
|
+
this.position.copy(c)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (t) {
|
|
211
|
+
this.coordinates = [this.coordinates[0] + t[0], this.coordinates[1] + t[1], this.coordinates[2] + t[2]];
|
|
212
|
+
let c = utils.projectToWorld(t);
|
|
213
|
+
this.position.copy(c)
|
|
214
|
+
//this.translateX(c.x);
|
|
215
|
+
//this.translateY(c.y);
|
|
216
|
+
//this.translateZ(c.z);
|
|
217
|
+
options.position = this.coordinates;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (wt) {
|
|
221
|
+
this.translateX(wt.x);
|
|
222
|
+
this.translateY(wt.y);
|
|
223
|
+
this.translateZ(wt.z);
|
|
224
|
+
let p = utils.unprojectFromWorld(this.position);
|
|
225
|
+
this.coordinates = options.position = p;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (r) {
|
|
229
|
+
this.rotation.set(r[0], r[1], r[2]);
|
|
230
|
+
options.rotation = new THREE.Vector3(r[0], r[1], r[2]);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (s) {
|
|
234
|
+
this.scale.set(s[0], s[1], s[2]);
|
|
235
|
+
options.scale = this.scale;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (q) {
|
|
239
|
+
this.quaternion.setFromAxisAngle(q[0], q[1]);
|
|
240
|
+
options.rotation = q[0].multiplyScalar(q[1]);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (w) {
|
|
244
|
+
this.position.copy(w);
|
|
245
|
+
let p = utils.unprojectFromWorld(w);
|
|
246
|
+
this.coordinates = options.position = p;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
//Each time the object is positioned, project the floor and correct shadow plane
|
|
250
|
+
this.setBoundingBoxShadowFloor();
|
|
251
|
+
this.setReceiveShadowFloor();
|
|
252
|
+
|
|
253
|
+
this.updateMatrixWorld();
|
|
254
|
+
tb.map.repaint = true;
|
|
255
|
+
|
|
256
|
+
//const threeTarget = new THREE.EventDispatcher();
|
|
257
|
+
//threeTarget.dispatchEvent({ type: 'event', detail: { object: this, action: { position: options.position, rotation: options.rotation, scale: options.scale } } });
|
|
258
|
+
// fire the ObjectChanged event to notify UI object change
|
|
259
|
+
let e = { type: 'ObjectChanged', detail: { object: this, action: { position: options.position, rotation: options.rotation, scale: options.scale } } };
|
|
260
|
+
this.dispatchEvent(e);
|
|
261
|
+
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
//[jscastro] play default animation
|
|
265
|
+
obj.playDefault = function (options) {
|
|
266
|
+
if (obj.mixer && obj.hasDefaultAnimation) {
|
|
267
|
+
|
|
268
|
+
let newParams = {
|
|
269
|
+
start: Date.now(),
|
|
270
|
+
expiration: Date.now() + options.duration,
|
|
271
|
+
endState: {}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
utils.extend(options, newParams);
|
|
275
|
+
|
|
276
|
+
obj.mixer.timeScale = options.speed || 1;
|
|
277
|
+
|
|
278
|
+
let entry = {
|
|
279
|
+
type: 'playDefault',
|
|
280
|
+
parameters: options
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
this.animationQueue
|
|
284
|
+
.push(entry);
|
|
285
|
+
|
|
286
|
+
tb.map.repaint = true
|
|
287
|
+
return this;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
//[jscastro] play an animation, requires options.animation as an index, if not it will play the default one
|
|
292
|
+
obj.playAnimation = function (options) {
|
|
293
|
+
if (obj.mixer) {
|
|
294
|
+
|
|
295
|
+
if (options.animation) {
|
|
296
|
+
setAction(options.animation)
|
|
297
|
+
}
|
|
298
|
+
obj.playDefault(options);
|
|
299
|
+
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
//[jscastro] pause all actions animation
|
|
304
|
+
obj.pauseAllActions = function () {
|
|
305
|
+
if (obj.mixer) {
|
|
306
|
+
obj.actions.forEach(function (action) {
|
|
307
|
+
action.paused = true;
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
//[jscastro] unpause all actions
|
|
313
|
+
obj.unPauseAllActions = function () {
|
|
314
|
+
if (obj.mixer) {
|
|
315
|
+
obj.actions.forEach(function (action) {
|
|
316
|
+
action.paused = false;
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
//[jscastro] stop all actions
|
|
323
|
+
obj.deactivateAllActions = function () {
|
|
324
|
+
if (obj.mixer) {
|
|
325
|
+
obj.actions.forEach(function (action) {
|
|
326
|
+
action.stop();
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
//[jscastro] play all actions
|
|
332
|
+
obj.activateAllActions = function () {
|
|
333
|
+
if (obj.mixer) {
|
|
334
|
+
obj.actions.forEach(function (action) {
|
|
335
|
+
action.play();
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
//[jscastro] move the model action one tick just to avoid issues with initial position
|
|
341
|
+
obj.idle = function () {
|
|
342
|
+
if (obj.mixer) {
|
|
343
|
+
// Update the animation mixer and render this frame
|
|
344
|
+
obj.mixer.update(0.01);
|
|
345
|
+
}
|
|
346
|
+
tb.map.repaint = true;
|
|
347
|
+
return this;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
update: function (now) {
|
|
353
|
+
|
|
354
|
+
if (this.previousFrameTime === undefined) this.previousFrameTime = now;
|
|
355
|
+
|
|
356
|
+
let dimensions = ['X', 'Y', 'Z'];
|
|
357
|
+
|
|
358
|
+
//[jscastro] when function expires this produces an error
|
|
359
|
+
if (!this.enrolledObjects) return false;
|
|
360
|
+
|
|
361
|
+
//iterate through objects in queue. count in reverse so we can cull objects without frame shifting
|
|
362
|
+
for (let a = this.enrolledObjects.length - 1; a >= 0; a--) {
|
|
363
|
+
|
|
364
|
+
let object = this.enrolledObjects[a];
|
|
365
|
+
|
|
366
|
+
if (!object.animationQueue || object.animationQueue.length === 0) continue;
|
|
367
|
+
|
|
368
|
+
//[jscastro] now multiple animations on a single object is possible
|
|
369
|
+
for (let i = object.animationQueue.length - 1; i >= 0; i--) {
|
|
370
|
+
|
|
371
|
+
//focus on first item in queue
|
|
372
|
+
let item = object.animationQueue[i];
|
|
373
|
+
if (!item) continue;
|
|
374
|
+
let options = item.parameters;
|
|
375
|
+
|
|
376
|
+
// if an animation is past its expiration date, cull it
|
|
377
|
+
if (!options.expiration) {
|
|
378
|
+
// console.log('culled')
|
|
379
|
+
|
|
380
|
+
object.animationQueue.splice(i, 1);
|
|
381
|
+
|
|
382
|
+
// set the start time of the next animation
|
|
383
|
+
if (object.animationQueue[i]) object.animationQueue[i].parameters.start = now;
|
|
384
|
+
|
|
385
|
+
return
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
//if finished, jump to end state and flag animation entry for removal next time around. Execute callback if there is one
|
|
389
|
+
let expiring = now >= options.expiration;
|
|
390
|
+
|
|
391
|
+
if (expiring) {
|
|
392
|
+
options.expiration = false;
|
|
393
|
+
if (item.type === 'playDefault') {
|
|
394
|
+
object.stop();
|
|
395
|
+
} else {
|
|
396
|
+
if (options.endState) object._setObject(options.endState);
|
|
397
|
+
if (typeof (options.cb) != 'undefined') options.cb();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
else {
|
|
402
|
+
|
|
403
|
+
let timeProgress = (now - options.start) / options.duration;
|
|
404
|
+
|
|
405
|
+
if (item.type === 'set') {
|
|
406
|
+
|
|
407
|
+
let objectState = {};
|
|
408
|
+
|
|
409
|
+
if (options.pathCurve) objectState.worldCoordinates = options.pathCurve.getPoint(timeProgress);
|
|
410
|
+
|
|
411
|
+
if (options.rotationPerMs) {
|
|
412
|
+
objectState.rotation = options.startRotation.map(function (rad, index) {
|
|
413
|
+
return rad + options.rotationPerMs[index] * timeProgress * options.duration
|
|
414
|
+
})
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (options.scalePerMs) {
|
|
418
|
+
objectState.scale = options.startScale.map(function (scale, index) {
|
|
419
|
+
return scale + options.scalePerMs[index] * timeProgress * options.duration
|
|
420
|
+
})
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
object._setObject(objectState);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (item.type === 'followPath') {
|
|
427
|
+
|
|
428
|
+
let position = options.pathCurve.getPointAt(timeProgress);
|
|
429
|
+
let objectState = { worldCoordinates: position };
|
|
430
|
+
|
|
431
|
+
// if we need to track heading
|
|
432
|
+
if (options.trackHeading) {
|
|
433
|
+
|
|
434
|
+
let tangent = options.pathCurve
|
|
435
|
+
.getTangentAt(timeProgress)
|
|
436
|
+
.normalize();
|
|
437
|
+
|
|
438
|
+
let axis = new THREE.Vector3(0, 0, 0);
|
|
439
|
+
let up = new THREE.Vector3(0, 1, 0);
|
|
440
|
+
|
|
441
|
+
axis
|
|
442
|
+
.crossVectors(up, tangent)
|
|
443
|
+
.normalize();
|
|
444
|
+
|
|
445
|
+
let radians = Math.acos(up.dot(tangent));
|
|
446
|
+
|
|
447
|
+
objectState.quaternion = [axis, radians];
|
|
448
|
+
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
object._setObject(objectState);
|
|
452
|
+
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
//[jscastro] play default animation
|
|
456
|
+
if (item.type === 'playDefault') {
|
|
457
|
+
object.activateAllActions();
|
|
458
|
+
object.isPlaying = true;
|
|
459
|
+
object.animationMethod = requestAnimationFrame(this.update);
|
|
460
|
+
object.mixer.update(object.clock.getDelta());
|
|
461
|
+
tb.map.repaint = true;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
this.previousFrameTime = now;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const defaults = {
|
|
475
|
+
followPath: {
|
|
476
|
+
path: null,
|
|
477
|
+
duration: 1000,
|
|
478
|
+
trackHeading: true
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
export default AnimationManager;
|