janusweb 1.5.29 → 1.5.33

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.
Files changed (60) hide show
  1. package/LICENSE +1 -1
  2. package/media/assets/janus-avatar-animations.glb +0 -0
  3. package/media/assets/janus-avatar-base.glb +0 -0
  4. package/media/assets/webui/apps/avatar/avatar.css +6 -2
  5. package/media/assets/webui/apps/avatar/avatar.js +97 -10
  6. package/media/assets/webui/apps/buttons/buttons.html +2 -2
  7. package/media/assets/webui/apps/buttons/buttons.js +3 -3
  8. package/media/assets/webui/apps/comms/comms.js +7 -4
  9. package/media/assets/webui/apps/comms/voip.js +138 -55
  10. package/media/assets/webui/apps/editor/editor.js +2 -0
  11. package/media/assets/webui/apps/inventory/inventory.html +7 -4
  12. package/media/assets/webui/apps/inventory/inventory.js +1 -1
  13. package/media/assets/webui/apps/locomotion/teleporter.js +9 -8
  14. package/media/assets/webui/apps/settings/settings.html +1 -1
  15. package/media/assets/webui/apps/virtualgamepad/virtualgamepad.js +9 -7
  16. package/media/assets/webui/apps/xrmenu/xrmenu.js +13 -4
  17. package/media/assets/webui/none.json +1 -0
  18. package/media/assets.json +2 -1
  19. package/media/index.html +1 -1
  20. package/media/lib/draco/README.md +32 -0
  21. package/media/lib/draco/draco_decoder.js +52 -0
  22. package/media/lib/draco/draco_decoder.wasm +0 -0
  23. package/media/lib/draco/draco_encoder.js +33 -0
  24. package/media/lib/draco/draco_wasm_wrapper.js +104 -0
  25. package/media/lib/draco/gltf/draco_decoder.js +48 -0
  26. package/media/lib/draco/gltf/draco_decoder.wasm +0 -0
  27. package/media/lib/draco/gltf/draco_encoder.js +33 -0
  28. package/media/lib/draco/gltf/draco_wasm_wrapper.js +104 -0
  29. package/media/lib/physx/physx.release.js +21 -0
  30. package/media/lib/physx/physx.release.wasm +0 -0
  31. package/media/manifest.json +5 -5
  32. package/media/service-worker.js +7 -0
  33. package/package.json +1 -1
  34. package/scripts/client.js +4 -3
  35. package/scripts/config.js +2 -0
  36. package/scripts/elements/linesegments.js +5 -1
  37. package/scripts/external/JanusFireboxParser.js +1 -0
  38. package/scripts/image.js +7 -1
  39. package/scripts/janusbase.js +83 -34
  40. package/scripts/janusghost.js +314 -15
  41. package/scripts/januslight.js +11 -2
  42. package/scripts/janusparagraph.js +3 -3
  43. package/scripts/janusparticle.js +16 -5
  44. package/scripts/janusplayer.js +124 -35
  45. package/scripts/janusweb.js +5 -0
  46. package/scripts/janusxrplayer.js +2 -3
  47. package/scripts/multiplayermanager.js +3 -1
  48. package/scripts/object.js +106 -10
  49. package/scripts/portal.js +28 -12
  50. package/scripts/remoteplayer.js +1 -25
  51. package/scripts/room.js +116 -36
  52. package/scripts/sound.js +8 -5
  53. package/scripts/text.js +3 -3
  54. package/scripts/websurface.js +6 -1
  55. package/utils/build.sh +3 -0
  56. package/utils/init.sh +2 -2
  57. package/scripts/chat.js +0 -80
  58. package/scripts/ui.js +0 -265
  59. package/scripts/urlbar.js +0 -93
  60. package/templates/janusweb.tpl +0 -3
@@ -54,6 +54,7 @@ elation.require(['engine.things.generic', 'utils.template', 'janusweb.parts'], f
54
54
  ongazeleave: { type: 'callback' },
55
55
  ongazeprogress: { type: 'callback' },
56
56
  ongazeactivate: { type: 'callback' },
57
+ oncollision: { type: 'callback' },
57
58
  });
58
59
  this.lastframevalues = {
59
60
  position: new THREE.Vector3(0,0,0),
@@ -129,7 +130,7 @@ elation.require(['engine.things.generic', 'utils.template', 'janusweb.parts'], f
129
130
  this.removeCollider();
130
131
  if (!(this.collidable || this.pickable) || !this.objects['dynamics']) return;
131
132
  var collision_id = this.collision_id || this.collider_id;
132
- var collision_scale = this.scale.clone();
133
+ var collision_scale = V().copy(this.scale);
133
134
  if (this.collision_scale) {
134
135
  collision_scale.multiply(this.collision_scale);
135
136
  }
@@ -154,7 +155,7 @@ elation.require(['engine.things.generic', 'utils.template', 'janusweb.parts'], f
154
155
  } else if (collision_id == 'cylinder') {
155
156
  this.setCollider('cylinder', {height: 1, radius: .5, offset: new THREE.Vector3(0, 0.5, 0), trigger: this.collision_trigger});
156
157
  } else if (collision_id == 'capsule') {
157
- this.setCollider('capsule', {height: 1, radius: .5, offset: new THREE.Vector3(0, 0.5, 0), trigger: this.collision_trigger});
158
+ this.setCollider('capsule', {length: 1, radius: .5, offset: new THREE.Vector3(0, 0, 0), trigger: this.collision_trigger});
158
159
  } else {
159
160
  var colliderasset = this.getAsset('model', collision_id);
160
161
  if (colliderasset) {
@@ -174,24 +175,25 @@ elation.require(['engine.things.generic', 'utils.template', 'janusweb.parts'], f
174
175
  let remove = [];
175
176
  collider.traverse(n => {
176
177
  // Ignore collider if it's too high-poly
177
- if (n instanceof THREE.Mesh && !((n.geometry instanceof THREE.BufferGeometry && n.geometry.attributes.position.count <= 65536) ||
178
- (n.geometry instanceof THREE.Geometry && n.geometry.vertices.length <= 65536))) {
179
- console.warn('Collider mesh rejected, too many polys!', collision_id, this, n, collider);
180
- elation.events.fire({type: 'thing_collider_rejected', element: this, data: {root: collider, mesh: n}});
181
- remove.push(n);
182
- } else {
183
- if (n.material) {
184
- n.material = new THREE.MeshPhongMaterial({
185
- color: collidercolor,
186
- opacity: .2,
187
- transparent: true,
188
- emissive: 0x444400,
189
- alphaTest: .01,
190
- depthTest: false,
191
- depthWrite: false
192
- });
178
+ if (n instanceof THREE.Mesh && n.geometry instanceof THREE.BufferGeometry) {
179
+ if (n.geometry.attributes.position.count > 65536) {
180
+ console.warn('Collider mesh rejected, too many polys!', collision_id, this, n, collider);
181
+ elation.events.fire({type: 'thing_collider_rejected', element: this, data: {root: collider, mesh: n}});
182
+ remove.push(n);
183
+ } else {
184
+ if (n.material) {
185
+ n.material = new THREE.MeshPhongMaterial({
186
+ color: collidercolor,
187
+ opacity: .2,
188
+ transparent: true,
189
+ emissive: 0x444400,
190
+ alphaTest: .01,
191
+ depthTest: false,
192
+ depthWrite: false
193
+ });
194
+ }
195
+ n.userData.thing = this;
193
196
  }
194
- n.userData.thing = this;
195
197
  }
196
198
  });
197
199
  if (remove.length > 0) {
@@ -205,8 +207,12 @@ elation.require(['engine.things.generic', 'utils.template', 'janusweb.parts'], f
205
207
  //this.colliders.add(collider);
206
208
  processMeshCollider(collider);
207
209
  } else {
210
+ let meshColliderLoaded = false;
208
211
  elation.events.add(collider, 'asset_load', elation.bind(this, function(ev) {
209
- processMeshCollider(collider);
212
+ if (!meshColliderLoaded) {
213
+ processMeshCollider(collider);
214
+ }
215
+ meshColliderLoaded = true;
210
216
  }) );
211
217
  }
212
218
  }
@@ -286,7 +292,7 @@ elation.require(['engine.things.generic', 'utils.template', 'janusweb.parts'], f
286
292
  }
287
293
  } else if (val instanceof THREE.Color) {
288
294
  if (defaultval instanceof THREE.Color) defaultval = defaultval.toArray();
289
- if (!('default' in propdef) || ('default' in propdef && !(val.r == defaultval[0] && val.g == defaultval[1] && val.b == defaultval[2]))) {
295
+ if (!('default' in propdef) || defaultval === null || ('default' in propdef && !(val.r == defaultval[0] && val.g == defaultval[1] && val.b == defaultval[2]))) {
290
296
  attrs[k] = val.toArray().map(n => Math.round(n * 10000) / 10000).join(' ');
291
297
  }
292
298
  } else if (val instanceof THREE.Euler) {
@@ -372,6 +378,9 @@ elation.require(['engine.things.generic', 'utils.template', 'janusweb.parts'], f
372
378
  rotate_deg_per_sec: [ 'property', 'rotate_deg_per_sec'],
373
379
  rotate_axis: [ 'property', 'rotate_axis'],
374
380
 
381
+ anim_id: ['property', 'anim_id'],
382
+ anim_transition_time: ['property', 'anim_transition_time'],
383
+
375
384
  fwd: ['property', 'zdir'],
376
385
  xdir: ['property', 'xdir'],
377
386
  ydir: ['property', 'ydir'],
@@ -739,7 +748,7 @@ elation.require(['engine.things.generic', 'utils.template', 'janusweb.parts'], f
739
748
  dir = new THREE.Vector3(),
740
749
  up = new THREE.Vector3();
741
750
  return function(ev) {
742
- let parent = this.parent;
751
+ let parent = this.properties.parent;
743
752
  let billboard = this.properties.billboard;
744
753
  if (billboard && parent) {
745
754
  //player.camera.localToWorld(playerpos.set(0,0,0));
@@ -896,7 +905,7 @@ elation.require(['engine.things.generic', 'utils.template', 'janusweb.parts'], f
896
905
  }
897
906
 
898
907
  this.createObject = function(type, args, skipstart) {
899
- return room.createObject(type, args, this, !this.started);
908
+ return this.room.createObject(type, args, this, !this.started);
900
909
  }
901
910
  this.appendChild = function(obj) {
902
911
  var proxyobj = obj
@@ -934,8 +943,8 @@ elation.require(['engine.things.generic', 'utils.template', 'janusweb.parts'], f
934
943
  realobj.stop();
935
944
  this.remove(realobj);
936
945
  this.updateScriptChildren();
937
- if (room.objects[obj.js_id]) {
938
- delete room.objects[obj.js_id];
946
+ if (this.room.objects[obj.js_id]) {
947
+ delete this.room.objects[obj.js_id];
939
948
  }
940
949
  }
941
950
  }
@@ -1009,25 +1018,58 @@ elation.require(['engine.things.generic', 'utils.template', 'janusweb.parts'], f
1009
1018
  }
1010
1019
  this.updateAnimation = function() {
1011
1020
  // Triggered whenever this.anim_id changes
1012
- this.setAnimation(this.anim_id);
1021
+ if (this.anim_id) {
1022
+ this.setAnimation(this.anim_id);
1023
+ } else if (this.activeanimation) {
1024
+ this.stopAnimation();
1025
+ }
1013
1026
  }
1014
1027
  this.setAnimation = function(anim_id) {
1015
- if (!this.activeanimation || anim_id != this.anim_id) {
1016
- if (!this.animationmixer) return;
1028
+ if (!this.started) return;
1029
+ if (!this.activeanimation || anim_id != this.activeanimation._clip.name) {
1030
+ if (!this.animationmixer) this.extractAnimations(this.objects['3d']);
1017
1031
  if (this.activeanimation) {
1018
1032
  //console.log('pause active animation', this.activeanimation);
1019
1033
  // TODO - interpolating between actions would make transitions smoother
1020
- this.activeanimation.stop();
1034
+ //this.activeanimation.stop();
1035
+ //this.activeanimation.fadeOut(.5);
1036
+ let oldaction = this.activeanimation;
1037
+ if (!this.fadetimers) this.fadetimers = {};
1038
+ let clipname = oldaction._clip.name;
1039
+ if (!this.fadetimers[clipname]) {
1040
+ // FIXME - for some reason, THREE.AnimationAction.fadeIn() / fadeOut() / etc are just causing the animations to stop, so we'll handle fading ourselves
1041
+ this.fadetimers[clipname] = setInterval(() => {
1042
+ oldaction.weight *= .95;
1043
+ if (oldaction.weight <= .001) {
1044
+ oldaction.weight = 0;
1045
+ oldaction.stop();
1046
+ clearTimeout(this.fadetimers[clipname]);
1047
+ this.fadetimers[clipname] = false;
1048
+ }
1049
+ }, 20);
1050
+ }
1021
1051
  }
1022
- if (this.animationactions && this.animationactions[anim_id]) {
1023
- var action = this.animationactions[anim_id];
1024
- //console.log('found action!', anim_id, action);
1052
+ if (this.animations && this.animations[anim_id]) {
1053
+ var action = this.animations[anim_id];
1054
+ let clipname = action._clip.name;
1055
+ if (this.fadetimers && this.fadetimers[clipname]) {
1056
+ clearTimeout(this.fadetimers[clipname]);
1057
+ this.fadetimers[clipname] = false;
1058
+ }
1059
+ action.weight = 1;
1060
+ action.reset();
1025
1061
  action.play();
1026
1062
  this.activeanimation = action;
1027
1063
  }
1028
1064
  this.anim_id = anim_id;
1029
1065
  }
1030
1066
  }
1067
+ this.stopAnimation = function() {
1068
+ if (this.activeanimation) {
1069
+ this.activeanimation.stop();
1070
+ this.activeanimation = false;
1071
+ }
1072
+ }
1031
1073
  this.dispatchEvent = function(event, target) {
1032
1074
  if (!event.element) event.element = target || this;
1033
1075
  if (!event.target) {
@@ -1315,9 +1357,10 @@ console.log('clone', props);
1315
1357
  }
1316
1358
  }
1317
1359
  this.setRoom = function(newroom, ischild) {
1318
- if (newroom._target) newroom = newroom._target; // If the proxy object is passed in, use its target instead
1360
+ if (newroom && newroom._target) newroom = newroom._target; // If the proxy object is passed in, use its target instead
1319
1361
 
1320
1362
  if (this.room !== newroom) {
1363
+ let oldroom = this.room;
1321
1364
  if (!ischild) {
1322
1365
  this.stop();
1323
1366
  }
@@ -1327,7 +1370,13 @@ console.log('clone', props);
1327
1370
  this.children[k].setRoom(newroom, true);
1328
1371
  }
1329
1372
  }
1330
- if (!ischild) {
1373
+ if (newroom && !ischild) {
1374
+ let roomproxy = (newroom._target ? newroom : newroom.getProxyObject()),
1375
+ objproxy = (this._target ? this : this.getProxyObject());
1376
+ if (!roomproxy.contains(objproxy)) {
1377
+ //newroom.add(this);
1378
+ roomproxy.appendChild(objproxy);
1379
+ }
1331
1380
  this.start();
1332
1381
  }
1333
1382
  }
@@ -1,4 +1,5 @@
1
1
  elation.require(['janusweb.janusbase', 'engine.things.leapmotion'], function() {
2
+
2
3
  elation.component.add('engine.things.janusghost', function() {
3
4
  this.postinit = function() {
4
5
  elation.engine.things.janusghost.extendclass.postinit.call(this);
@@ -16,9 +17,29 @@ elation.require(['janusweb.janusbase', 'engine.things.leapmotion'], function() {
16
17
  ghostassets: { type: 'object' },
17
18
  auto_play: { type: 'boolean', default: true },
18
19
  screen_name: { type: 'string' },
20
+ animation_extras: { type: 'string' },
21
+ bone_head: { type: 'string' },
22
+ morphtarget_mouth: { type: 'string' },
23
+ morphtarget_eyes: { type: 'string' },
19
24
  });
20
25
 
21
26
  this.frames = false;
27
+
28
+ if (!('project_vertex_discard_close' in THREE.ShaderChunk)) {
29
+ THREE.ShaderChunk['color_fragment_discard_close'] = `
30
+ #if defined( USE_COLOR_ALPHA )
31
+ diffuseColor *= vColor;
32
+ #elif defined( USE_COLOR )
33
+ diffuseColor.rgb *= vColor;
34
+ #endif
35
+ float dist = length(vViewPosition);
36
+ float mindist = .4;
37
+ float maxdist = .6;
38
+ if (dist < maxdist) {
39
+ diffuseColor.a = clamp(((dist - mindist) / (maxdist - mindist)), 0., 1.);
40
+ }
41
+ `;
42
+ }
22
43
  }
23
44
  this.createObject3D = function() {
24
45
  if (this.ghost_src) {
@@ -114,6 +135,45 @@ elation.require(['janusweb.janusbase', 'engine.things.leapmotion'], function() {
114
135
  console.log(this.ghostassets);
115
136
  this.assetpack.loadJSON(assets.assetlist);
116
137
  }
138
+
139
+ let animnames = []; //'idle', 'walk', 'walk_left', 'walk_right', 'walk_back', 'run', 'jump', 'fly', 'speak', 'type', 'portal'];
140
+ let animassets = assets.assetlist.filter(asset => animnames.indexOf(asset.name) != -1);
141
+ console.log('some ghost animations', animassets, assets);
142
+
143
+ if (!this.animationmixer) {
144
+ // Set up our animation mixer with a simple bone mapper for our head. We'll add more animations to this ass other assets load
145
+ // TODO - this is probably also where we'd map any other tracked objects (hands, hips, etc). and set up IK
146
+
147
+ /*
148
+ let headtrack = new THREE.QuaternionKeyframeTrack('Neck.quaternion', [0], [0, 0, 0, 1]),
149
+ headclip = new THREE.AnimationClip('head_rotation', -1, [headtrack]);
150
+ this.headtrack = headtrack;
151
+
152
+ this.initAnimations([ headclip ]);
153
+ //this.animations['head_rotation'].play();
154
+ */
155
+ this.initAnimations([]);
156
+ }
157
+
158
+ animassets.forEach(anim => {
159
+ let asset = this.getAsset('model', anim.name);
160
+ console.log('try to load the animation', asset, anim);
161
+ if (asset) {
162
+ if (!asset.loaded) {
163
+ let model = asset.getInstance();
164
+ elation.events.add(asset, 'asset_load', ev => {
165
+ let clip = false;
166
+ model.traverse(n => { if (n.animations && n.animations.length > 0) clip = n.animations[0]; });
167
+ if (clip) {
168
+ if (this.animationmixer && !this.animations[anim.name]) {
169
+ let action = this.animationmixer.clipAction(clip);
170
+ this.animations[anim.name] = action;
171
+ }
172
+ }
173
+ });
174
+ }
175
+ }
176
+ });
117
177
  }
118
178
  this.getGhostObjects = function() {
119
179
  var objects = {};
@@ -186,6 +246,8 @@ elation.require(['janusweb.janusbase', 'engine.things.leapmotion'], function() {
186
246
  this.head.appendChild(headid);
187
247
  this.face.start();
188
248
  }
249
+ this.head.pos = headpos.clone().multiplyScalar(this.scale.y);
250
+ this.face.applyPosition(headpos.negate());
189
251
  if (this.remotevideo) {
190
252
  this.updateVideoScreen();
191
253
  this.face.addEventListener('load', (ev) => {
@@ -217,6 +279,11 @@ elation.require(['janusweb.janusbase', 'engine.things.leapmotion'], function() {
217
279
  //rotation: V(0, 180, 0),
218
280
  lighting: this.lighting,
219
281
  //cull_face: 'none'
282
+ opacity: 0.9999,
283
+ renderorder: this.renderorder || 100,
284
+ shader_chunk_replace: (this.lighting ? {
285
+ 'color_fragment': 'color_fragment_discard_close',
286
+ } : {}),
220
287
  });
221
288
  } else {
222
289
  this.body = bodyid;
@@ -225,11 +292,169 @@ elation.require(['janusweb.janusbase', 'engine.things.leapmotion'], function() {
225
292
  if (pos) this.body.pos = pos;
226
293
  this.body.start();
227
294
  if (scale && this.body) this.body.scale.fromArray(scale);
295
+
296
+ elation.events.add(this, 'load', ev => {
297
+ if (this.body.modelasset) {
298
+ if (this.body.modelasset.loaded) {
299
+ this.loadAnimations();
300
+ if (this.animation_extras) {
301
+ this.loadAnimationExtras();
302
+ }
303
+ } else {
304
+ elation.events.add(this.body.modelasset, 'asset_load_complete', () => {
305
+ this.loadAnimations();
306
+ if (this.animation_extras) {
307
+ this.loadAnimationExtras();
308
+ }
309
+ });
310
+ }
311
+ }
312
+ // FIXME - this prevents avatars from being culled, which prevents our default mesh from disappearing but also means we lose out on possible performance optimizations in rooms with lots of avatars
313
+ this.objects['3d'].traverse(n => {
314
+ if (n instanceof THREE.Mesh) {
315
+ n.frustumCulled = false;
316
+ }
317
+ });
318
+ });
319
+ }
320
+ }
321
+ this.loadAnimations = function(assetid) {
322
+ if (!assetid) assetid = 'avatar_animations';
323
+ let animasset = this.getAsset('model', assetid);
324
+ if (!animasset.loaded) {
325
+ let animationsLoaded = false;
326
+ elation.events.add(animasset, 'asset_load_complete', ev => {
327
+ if (!animationsLoaded) {
328
+ animationsLoaded = true;
329
+ if (this.body.animationmixer) {
330
+ this.cloneAnimations(animasset);
331
+ }
332
+ this.body.setAnimation('idle');
333
+ }
334
+ });
335
+ animasset.load();
336
+ } else {
337
+ if (this.body.animationmixer) {
338
+ this.cloneAnimations(animasset);
339
+ }
340
+ this.body.setAnimation('idle');
228
341
  }
229
342
  }
343
+ this.cloneAnimations = function(animasset) {
344
+ let animations = animasset.animations;
345
+ //console.log('clone all the animations', animations, animasset._model);
346
+ if (this.body) {
347
+ if (this.bone_head) {
348
+ this.initHeadAnimation(animasset);
349
+ }
350
+
351
+ animations.forEach(clip => {
352
+ this.body.animations[clip.name] = this.body.animationmixer.clipAction(this.retargetAnimation(clip, animasset._model));
353
+ //console.log('new clip', clip.name, clip, this.body.animations[clip.name]);
354
+ });
355
+ //console.log('head rot', this.body.animations['head_rotation'], headclip, headaction);
356
+ //console.log(this.body.modelasset, this.body.modelasset.vrm);
357
+ if (this.body.modelasset && this.body.modelasset.vrm) {
358
+ let rename = {};
359
+ let bonemap = this.body.modelasset.vrm.humanoid.humanBones;
360
+ for (let k in bonemap) {
361
+ console.log(k, bonemap[k]);
362
+ if (bonemap[k].length > 0) {
363
+ rename[bonemap[k][0].node.name] = k;
364
+ }
365
+ }
366
+ let bones = [];
367
+ let meshes = [];
368
+ this.body.objects['3d'].traverse(n => { if (n instanceof THREE.Bone) bones.push(n); else if (n instanceof THREE.SkinnedMesh) meshes.push(n); });
369
+ /*
370
+ console.log('rename the bones!', rename);
371
+ THREE.SkeletonUtils.renameBones(bones, rename);
372
+ console.log('bones now', bones);
373
+ */
374
+
375
+ }
376
+ }
377
+ }
378
+ this.retargetAnimation = function(clip, sourcecontainer) {
379
+ let newclip = clip.clone();
380
+ let sourcemesh = null,
381
+ destmesh = null;
382
+ if (sourcecontainer) {
383
+ sourcecontainer.traverse(n => { /*console.log(' - ', n); */ if (n instanceof THREE.SkinnedMesh && n.skeleton) sourcemesh = n; });
384
+ }
385
+ this.objects['3d'].traverse(n => { if (n instanceof THREE.SkinnedMesh && n.skeleton) destmesh = n; });
386
+ //console.log('RETARGET ANI*MATION', clip, sourcemesh, destmesh, sourcecontainer);
387
+ let remove = [];
388
+ if (this.body && this.body.modelasset && this.body.modelasset.vrm) {
389
+ let vrm = this.body.modelasset.vrm;
390
+ //console.log('RETARGET ANI*MATION', clip, this.body.modelasset.vrm, sourcemesh, destmesh);
391
+ newclip.tracks.forEach(track => {
392
+ let parts = track.name.split('.');
393
+ console.log(parts, track.name);
394
+ try {
395
+ let bone = vrm.humanoid.getBone(parts[0]);
396
+ if (bone) {
397
+ track.name = bone.node.name + '.' + parts[1];
398
+ let sourcebone = sourcemesh.skeleton.getBone(bone.node.name);
399
+ //console.log(track, bone);
400
+ } else {
401
+ //console.log('no bone!', parts, track);
402
+ }
403
+ } catch (e) {
404
+ console.log('omgwtf', e.message);
405
+ remove.push(track);
406
+ }
407
+ });
408
+ remove.forEach(track => {
409
+ let idx = newclip.tracks.indexOf(track);
410
+ if (idx != -1) {
411
+ newclip.tracks.splice(idx, 1);
412
+ //console.log('removed track', track);
413
+ }
414
+ });
415
+ } else if (destmesh && sourcemesh) {
416
+ // FIXME - silly hack to store skeleton, we should just be extracting it at load time
417
+ this.body.skeleton = destmesh.skeleton;
418
+ this.body.objects['3d'].skeleton = destmesh.skeleton;
419
+ //newclip = THREE.SkeletonUtils.retargetClip(destmesh, sourcemesh, newclip, {useFirstFramePosition: true});
420
+ //console.log('retarget it!', newclip, destmesh, sourcemesh);
421
+ newclip.tracks.forEach(track => {
422
+ let [name, property] = track.name.split('.');
423
+ let srcbone = sourcemesh.skeleton.getBoneByName(name);
424
+ let dstbone = destmesh.skeleton.getBoneByName(name);
425
+ //console.log(' - fix track', name, property, track, srcbone, dstbone);
426
+ if (dstbone) {
427
+ let scale = srcbone.position.length() / dstbone.position.length();
428
+ if (property == 'position') {
429
+ for (let i = 0; i < track.values.length; i++) {
430
+ track.values[i] /= scale;
431
+ }
432
+ //remove.push(track);
433
+ } else if (property == 'quaternion') {
434
+ }
435
+ } else {
436
+ //console.log('missing bone!', srcbone, track);
437
+ remove.push(track);
438
+ }
439
+ });
440
+ }
441
+ remove.forEach(track => {
442
+ let idx = newclip.tracks.indexOf(track);
443
+ if (idx != -1) {
444
+ newclip.tracks.splice(idx, 1);
445
+ //console.log('removed track', track);
446
+ }
447
+ });
448
+ //console.log('finished clip', newclip);
449
+ return newclip;
450
+ }
230
451
  this.rebindAnimations = function() {
231
452
  this.body.rebindAnimations();
232
453
  }
454
+ this.loadAnimationExtras = function() {
455
+ let animids = this.animation_extras.split(' ');
456
+ animids.forEach(animid => this.loadAnimations(animid));
457
+ }
233
458
 
234
459
  this.start = function() {
235
460
  elation.engine.things.janusghost.extendclass.start.call(this);
@@ -289,13 +514,16 @@ elation.require(['janusweb.janusbase', 'engine.things.leapmotion'], function() {
289
514
 
290
515
  matrix.makeBasis(xdir, ydir, zdir);
291
516
  q1.setFromRotationMatrix(matrix);
292
- this.head.properties.orientation.copy(this.orientation).inverse().multiply(q1);
517
+ this.head.properties.orientation.copy(this.orientation).invert().multiply(q1);
518
+ if (this.body && this.headaction) {
519
+ this.setHeadOrientation(this.head.orientation);
520
+ }
293
521
  if (movedata.head_pos && this.face) {
294
522
  var headpos = this.head.properties.position;
295
523
  var facepos = this.face.properties.position;
296
524
  var newpos = parser.getVectorValue(movedata.head_pos);
297
- headpos.copy(this.head_pos);
298
- facepos.fromArray(newpos).sub(headpos);
525
+ //headpos.copy(this.head_pos);
526
+ //facepos.fromArray(newpos).sub(headpos);
299
527
  if (this.body) {
300
528
  if (this.head.parent != this.body) {
301
529
  this.body.appendChild(this.head);
@@ -357,6 +585,20 @@ elation.require(['janusweb.janusbase', 'engine.things.leapmotion'], function() {
357
585
  if (movedata.userid_pos) {
358
586
  this.userid_pos = movedata.userid_pos;
359
587
  }
588
+
589
+ // FIXME - workaround for null values
590
+ if (isNaN(this.position.x)) this.position.x = 0;
591
+ if (isNaN(this.position.y)) this.position.y = 0;
592
+ if (isNaN(this.position.z)) this.position.z = 0;
593
+ if (isNaN(this.orientation.x) || isNaN(this.orientation.y) || isNaN(this.orientation.z) || isNaN(this.orientation.w)) this.orientation.set(0,0,0,1);
594
+
595
+ if (this.head) {
596
+ if (isNaN(this.head.position.x)) this.head.position.x = 0;
597
+ if (isNaN(this.head.position.y)) this.head.position.y = 0;
598
+ if (isNaN(this.head.position.z)) this.head.position.z = 0;
599
+ if (isNaN(this.head.orientation.x) || isNaN(this.head.orientation.y) || isNaN(this.head.orientation.z) || isNaN(this.head.orientation.w)) this.head.orientation.set(0,0,0,1);
600
+ }
601
+
360
602
  this.objects.dynamics.updateState();
361
603
  this.refresh();
362
604
  }
@@ -391,6 +633,14 @@ elation.require(['janusweb.janusbase', 'engine.things.leapmotion'], function() {
391
633
  this.screen_name = ghostdef.screen_name;
392
634
  }
393
635
  this.setGhostAssets(things.assets);
636
+ this.animation_extras = ghostdef.animation_extras;
637
+ this.bone_head = ghostdef.bone_head;
638
+ this.morphtarget_mouth = ghostdef.morphtarget_mouth;
639
+ this.morphtarget_eyes = ghostdef.morphtarget_eyes;
640
+ this.lighting = elation.utils.any(ghostdef.lighting, true);
641
+ if (ghostdef.morphtarget_eyes) {
642
+ this.blink();
643
+ }
394
644
  if (ghostdef.head_id) {
395
645
  this.setHead(ghostdef.head_id, headpos, ghostdef.scale);
396
646
  }
@@ -431,7 +681,7 @@ elation.require(['janusweb.janusbase', 'engine.things.leapmotion'], function() {
431
681
  }
432
682
 
433
683
  var inverse = new THREE.Matrix4();
434
- inverse.getInverse(this.objects['3d'].matrixWorld);
684
+ inverse.copy(this.objects['3d'].matrixWorld).invert();
435
685
  if (hand0 && hand0.state) {
436
686
  this.hands.left.show();
437
687
  this.hands.left.setState(hand0.state, inverse);
@@ -460,6 +710,7 @@ elation.require(['janusweb.janusbase', 'engine.things.leapmotion'], function() {
460
710
  }
461
711
  }
462
712
  this.updateTransparency = function() {
713
+ return;
463
714
  var player = this.engine.client.player;
464
715
 
465
716
  var dist = player.distanceTo(this);
@@ -478,17 +729,6 @@ elation.require(['janusweb.janusbase', 'engine.things.leapmotion'], function() {
478
729
  m.visible = (opacity > 0);
479
730
  });
480
731
  }
481
- this.setRoom = function(room) {
482
- if (room !== this.room) {
483
- if (this.room) {
484
- this.room.remove(this);
485
- }
486
- this.room = room;
487
- if (room && this.parent != this.room) {
488
- room.add(this);
489
- }
490
- }
491
- }
492
732
  this.setRemoteVideo = function(video) {
493
733
  if (video) {
494
734
  this.remotevideo = video;
@@ -540,5 +780,64 @@ elation.require(['janusweb.janusbase', 'engine.things.leapmotion'], function() {
540
780
  });
541
781
  }
542
782
  }
783
+ this.setAnimation = function(anim) {
784
+ if (this.body) this.body.anim_id = anim;
785
+ }
786
+ this.initHeadAnimation = function(animasset) {
787
+ let body = this.body || this._target.body,
788
+ head = this.head || this._target.head;
789
+ if (body && this.bone_head && !this.headaction) {
790
+ if (!body.animationmixer) {
791
+ body.initAnimations([]);
792
+ }
793
+ // Set up our animation mixer with a simple bone mapper for our head. We'll add more animations to this as other assets load
794
+ // TODO - this is probably also where we'd map any other tracked objects (hands, hips, etc). and set up IK
795
+
796
+ let headtrack = new THREE.QuaternionKeyframeTrack(this.bone_head + '.quaternion', [0], [0, 0, 0, 1]),
797
+ headclip = new THREE.AnimationClip('head_rotation', -1, [headtrack]);
798
+ let headaction = body.animationmixer.clipAction(this.retargetAnimation(headclip, animasset._model));
799
+ headaction.weight = 5;
800
+ this.headaction = headaction;
801
+ headaction.play();
802
+ }
803
+ }
804
+ this.setHeadOrientation = function(orientation, invert) {
805
+ let headaction = this.headaction || this._target.headaction;
806
+ if (headaction && headaction._clip.tracks[0]) {
807
+ let track = headaction._clip.tracks[0];
808
+ track.values[0] = orientation.x * (invert ? -1 : 1);
809
+ track.values[1] = orientation.y * (invert ? -1 : 1);
810
+ track.values[2] = orientation.z * (invert ? -1 : 1);
811
+ track.values[3] = orientation.w; // * (invert ? -1 : 1);
812
+ }
813
+ }
814
+ this.blink = function() {
815
+ if (!this.blinktimer) {
816
+ if (this.body && this.morphtarget_eyes) {
817
+ this.body.setMorphTargetInfluence(this.morphtarget_eyes, 1);
818
+ setTimeout(() => {
819
+ this.body.setMorphTargetInfluence(this.morphtarget_eyes, 0);
820
+ }, 100);
821
+ }
822
+ this.blinktimer = setTimeout(() => {
823
+ this.blinktimer = false;
824
+ this.blink();
825
+ }, 2500 + Math.random() * 8500);
826
+ }
827
+ }
828
+ this.setSpeakingVolume = function(volume) {
829
+ if (this.body && this.morphtarget_mouth) {
830
+ this.body.setMorphTargetInfluence(this.morphtarget_mouth, volume);
831
+ }
832
+ }
833
+ this.getProxyObject = function(classdef) {
834
+ if (!this._proxyobject) {
835
+ this._proxyobject = elation.engine.things.janusghost.extendclass.getProxyObject.call(this, classdef);
836
+ this._proxyobject._proxydefs = {
837
+ body: [ 'property', 'body'],
838
+ };
839
+ }
840
+ return this._proxyobject;
841
+ }
543
842
  }, elation.engine.things.janusbase);
544
843
  });
@@ -18,6 +18,8 @@ elation.require(['janusweb.janusbase'], function() {
18
18
  light_shadow_bias: { type: 'float', default: .0001, set: this.updateLight },
19
19
  light_shadow_radius: { type: 'float', default: 2.5, set: this.updateLight },
20
20
  light_helper: { type: 'boolean', default: false, set: this.updateLightHelper },
21
+ light_style: { type: 'string', default: '' },
22
+ light_style_fps: { type: 'float', default: 10 },
21
23
  collision_id: { type: 'string', default: 'sphere', set: this.updateCollider },
22
24
  collision_trigger: { type: 'boolean', default: true, set: this.updateCollider },
23
25
  });
@@ -98,8 +100,14 @@ elation.require(['janusweb.janusbase'], function() {
98
100
  this.updateLightTarget();
99
101
  }
100
102
  if (this.light_directional || this.light_cone_angle == 1) {
101
- this.light.position.subVectors(this.light.position, this.light.target.position).add(player.pos);
102
- this.light.target.position.copy(player.pos);
103
+ //this.light.position.subVectors(this.light.position, this.light.target.position).add(player.pos).sub(this.pos);
104
+ this.light.position.subVectors(this.position, this.light.target.position).normalize().multiplyScalar(this.light_range / 2).add(player.pos).sub(this.pos);
105
+ this.light.target.position.copy(player.pos).sub(this.pos);
106
+ }
107
+ if (this.light_style != '') {
108
+ let idx = Math.floor(Date.now() / (1000 / this.light_style_fps)) % this.light_style.length;
109
+ let brightness = (this.light_style.charCodeAt(idx) - 97) / 13;
110
+ this.light.intensity = .1 * brightness;
103
111
  }
104
112
  }
105
113
  this.createLight = function() {
@@ -127,6 +135,7 @@ elation.require(['janusweb.janusbase'], function() {
127
135
  //this.light.intensity = this.light_intensity / 100;
128
136
  var avgscale = (this.scale.x + this.scale.y + this.scale.z) / 3;
129
137
  //this.light.intensity = this.light_intensity / 100;
138
+ let brightness = 1;
130
139
  this.light.intensity = .1;
131
140
  this.light.penumbra = this.light_penumbra;
132
141
  this.light.decay = this.light_decay;