elation-engine 0.9.113 → 0.9.115

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 (91) hide show
  1. package/css/systems/render.css +5 -1
  2. package/package.json +1 -1
  3. package/scripts/assets.js +103 -20
  4. package/scripts/assetworker.js +18 -1
  5. package/scripts/external/holoplay.js +1494 -0
  6. package/scripts/external/octree.js +0 -0
  7. package/scripts/external/three/CSS3DRenderer.js +46 -43
  8. package/scripts/external/three/CubemapToEquirectangular.js +1 -1
  9. package/scripts/external/three/three-extras.js +1553 -392
  10. package/scripts/external/three/three-icosa.js +2575 -0
  11. package/scripts/external/three/three-loaders.js +925 -133
  12. package/scripts/external/three/three-postprocessing.js +3 -3
  13. package/scripts/external/three/three-r116dev.js +50930 -0
  14. package/scripts/external/three/three-spotlighttextures.js +50953 -0
  15. package/scripts/external/three/three-vrm.js +2 -2
  16. package/scripts/external/three/three-working.js +35968 -0
  17. package/scripts/external/three/three.js +38532 -24087
  18. package/scripts/external/three-mesh-bvh.js +5370 -0
  19. package/scripts/external/three-old/BVHLoader.js +406 -0
  20. package/scripts/external/three-old/ColladaLoader.js +5519 -0
  21. package/scripts/external/three-old/ColladaLoader2.js +1694 -0
  22. package/scripts/external/three-old/CubemapToEquirectangular.js +188 -0
  23. package/scripts/external/three-old/DDSLoader.js +269 -0
  24. package/scripts/external/three-old/FBXLoader-mine.js +5063 -0
  25. package/scripts/external/three-old/FBXLoader.js +5112 -0
  26. package/scripts/external/three-old/FlyControls.js +295 -0
  27. package/scripts/external/three-old/GLTF2Loader.js +2950 -0
  28. package/scripts/external/three-old/GLTFLoader.js +2213 -0
  29. package/scripts/external/three-old/JSONLoader.js +435 -0
  30. package/scripts/external/three-old/MTLLoader.js +533 -0
  31. package/scripts/external/three-old/OBJLoader-experimental.js +874 -0
  32. package/scripts/external/three-old/OBJLoader-working.js +727 -0
  33. package/scripts/external/three-old/OBJLoader.js +723 -0
  34. package/scripts/external/three-old/OBJMTLLoader.js +440 -0
  35. package/scripts/external/three-old/OrbitControls.js +592 -0
  36. package/scripts/external/three-old/PLYLoader.js +517 -0
  37. package/scripts/external/three-old/TransformControls.js +1100 -0
  38. package/scripts/external/three-old/VRMLLoader.js +1021 -0
  39. package/scripts/external/three-old/glTFLoader-combined.js +2513 -0
  40. package/scripts/external/three-old/nodethree.js +44018 -0
  41. package/scripts/external/three-old/render/BleachBypassShader.js +64 -0
  42. package/scripts/external/three-old/render/BloomPass.js +116 -0
  43. package/scripts/external/three-old/render/CSS3DRenderer.js +310 -0
  44. package/scripts/external/three-old/render/ClearPass.js +44 -0
  45. package/scripts/external/three-old/render/ConvolutionShader.js +101 -0
  46. package/scripts/external/three-old/render/CopyShader.js +46 -0
  47. package/scripts/external/three-old/render/EffectComposer.js +211 -0
  48. package/scripts/external/three-old/render/FXAAShader.js +88 -0
  49. package/scripts/external/three-old/render/FilmPass.js +60 -0
  50. package/scripts/external/three-old/render/FilmShader.js +104 -0
  51. package/scripts/external/three-old/render/ManualMSAARenderPass.js +168 -0
  52. package/scripts/external/three-old/render/MaskPass.js +97 -0
  53. package/scripts/external/three-old/render/OculusRenderPass.js +84 -0
  54. package/scripts/external/three-old/render/OculusRiftEffect.js +240 -0
  55. package/scripts/external/three-old/render/PortalRenderPass.js +166 -0
  56. package/scripts/external/three-old/render/RecordingPass.js +208 -0
  57. package/scripts/external/three-old/render/RenderPass.js +57 -0
  58. package/scripts/external/three-old/render/SSAOShader.js +259 -0
  59. package/scripts/external/three-old/render/SepiaShader.js +54 -0
  60. package/scripts/external/three-old/render/ShaderPass.js +66 -0
  61. package/scripts/external/three-old/render/VREffect.js +482 -0
  62. package/scripts/external/three-old/shimthree.js +23 -0
  63. package/scripts/external/three-old/stats.js +6 -0
  64. package/scripts/external/three-old/three-88dev.js +45004 -0
  65. package/scripts/external/three-old/three-backgroundoptimization.js +44432 -0
  66. package/scripts/external/three-old/three-updates.js +44735 -0
  67. package/scripts/external/three-old/three-working.js +44719 -0
  68. package/scripts/external/three-old/three.js +44431 -0
  69. package/scripts/external/three-old/threex.rendererstats.js +66 -0
  70. package/scripts/external/three-old/tween.js +13 -0
  71. package/scripts/external/webvr-polyfill-new.js +3497 -0
  72. package/scripts/external/webvr-polyfill-newest.js +3491 -0
  73. package/scripts/external/webvr-polyfill-old.js +6337 -0
  74. package/scripts/geometries.js +2 -2
  75. package/scripts/math.js +6 -6
  76. package/scripts/systems/admin.js +1 -1
  77. package/scripts/systems/controls.js +6 -4
  78. package/scripts/systems/physics.js +10 -10
  79. package/scripts/systems/render.js +58 -20
  80. package/scripts/systems/render2.js +38 -0
  81. package/scripts/things/camera.js +6 -1
  82. package/scripts/things/generic-trackedvectors.js +1875 -0
  83. package/scripts/things/generic.js +3 -4
  84. package/scripts/things/label2d.js +1 -1
  85. package/scripts/things/leapmotion.js +6 -6
  86. package/scripts/things/menu.js +1 -1
  87. package/scripts/things/player-bak.js +638 -0
  88. package/scripts/things/player.js +28 -10
  89. package/scripts/things/skysphere.js +1 -1
  90. package/scripts/things/terrain.js +1 -1
  91. package/scripts/things/text.js +1 -1
@@ -0,0 +1,1875 @@
1
+ elation.require([
2
+ //"engine.things.trigger"
3
+ "utils.proxy",
4
+ "engine.math"
5
+ ], function() {
6
+
7
+ elation.component.add("engine.things.generic", function() {
8
+ this.init = function() {
9
+ this._proxies = {};
10
+ this._thingdef = {
11
+ properties: {},
12
+ events: {},
13
+ actions: {}
14
+ };
15
+ this.parentname = this.args.parentname || '';
16
+ this.name = this.args.name || '';
17
+ this.type = this.args.type || 'generic';
18
+ this.engine = this.args.engine;
19
+ this.client = this.args.client;
20
+ this.properties = {};
21
+ this.objects = {};
22
+ this.parts = {};
23
+ this.triggers = {};
24
+ this.parttypes = {};
25
+ this.children = {};
26
+ this.tags = [];
27
+ this.sounds = {};
28
+ this.animations = false;
29
+ this.skeleton = false;
30
+
31
+ this.tmpvec = new THREE.Vector3();
32
+
33
+ this.interp = {
34
+ rate: 20,
35
+ lastTime: 0,
36
+ time: 0,
37
+ endpoint: new THREE.Vector3(),
38
+ spline: [],
39
+ active: false,
40
+ fn: this.applyInterp
41
+ };
42
+
43
+ //elation.events.add(this, 'thing_create', this);
44
+ elation.events.add(this, 'thing_use_activate', this);
45
+ this.defineActions({
46
+ 'spawn': this.spawn,
47
+ 'move': this.move
48
+ });
49
+ this.defineProperties({
50
+ 'position': { type: 'vector3', default: [0, 0, 0], comment: 'Object position, relative to parent' },
51
+ 'orientation': { type: 'quaternion', default: [0, 0, 0, 1], comment: 'Object orientation, relative to parent' },
52
+ 'scale': { type: 'vector3', default: [1, 1, 1], comment: 'Object scale, relative to parent' },
53
+ 'velocity': { type: 'vector3', default: [0, 0, 0], comment: 'Object velocity (m/s)' },
54
+ 'acceleration': { type: 'vector3', default: [0, 0, 0], comment: 'Object acceleration (m/s^2)' },
55
+ 'angular': { type: 'vector3', default: [0, 0, 0], comment: 'Object angular velocity (radians/sec)' },
56
+ 'angularacceleration': { type: 'vector3', default: [0, 0, 0], comment: 'Object angular acceleration (radians/sec^2)' },
57
+ 'mass': { type: 'float', default: 0.0, comment: 'Object mass (kg)' },
58
+ 'exists': { type: 'bool', default: true, comment: 'Exists' },
59
+ 'visible': { type: 'bool', default: true, comment: 'Is visible' },
60
+ 'physical': { type: 'bool', default: true, comment: 'Simulate physically' },
61
+ 'collidable': { type: 'bool', default: true, comment: 'Can crash into other things' },
62
+ 'restitution': { type: 'float', default: 0.5, comment: 'Amount of energy preserved after each bounce', set: this.updatePhysics },
63
+ 'dynamicfriction':{ type: 'float', default: 0.0, comment: 'Dynamic friction inherent to this object', set: this.updatePhysics },
64
+ 'staticfriction': { type: 'float', default: 0.0, comment: 'Static friction inherent to this object', set: this.updatePhysics },
65
+ //'fog': { type: 'bool', default: true, comment: 'Affected by fog' },
66
+ 'shadow': { type: 'bool', default: true, refreshMaterial: true, comment: 'Casts and receives shadows' },
67
+ 'wireframe': { type: 'bool', default: false, refreshMaterial: true, comment: 'Render this object as a wireframe' },
68
+ 'forcereload': { type: 'bool', default: false, refreshGeometry: true, refreshMaterial: true, comment: 'Force a full reload of all files' },
69
+ 'mouseevents': { type: 'bool', default: true, comment: 'Respond to mouse/touch events' },
70
+ 'persist': { type: 'bool', default: false, comment: 'Continues existing across world saves' },
71
+ 'pickable': { type: 'bool', default: true, comment: 'Selectable via mouse/touch events' },
72
+ 'render.mesh': { type: 'string', refreshGeometry: true, comment: 'URL for JSON model file' },
73
+ 'render.meshname':{ type: 'string', refreshGeometry: true },
74
+ 'render.scene': { type: 'string', refreshGeometry: true, comment: 'URL for JSON scene file' },
75
+ 'render.collada': { type: 'string', refreshGeometry: true, comment: 'URL for Collada scene file' },
76
+ 'render.model': { type: 'string', refreshGeometry: true, comment: 'Name of model asset' },
77
+ 'render.gltf': { type: 'string', refreshGeometry: true, comment: 'URL for glTF file' },
78
+ 'render.materialname': { type: 'string', refreshMaterial: true, comment: 'Material library name' },
79
+ 'render.texturepath': { type: 'string', refreshMaterial: true, comment: 'Texture location' },
80
+ 'player_id': { type: 'float', default: null, comment: 'Network id of the creator' },
81
+ 'tags': { type: 'string', comment: 'Default tags to add to this object' }
82
+ });
83
+ this.defineEvents({
84
+ 'thing_create': [],
85
+ 'thing_add': ['child'],
86
+ 'thing_load': ['mesh'],
87
+ 'thing_remove': [],
88
+ 'thing_destroy': [],
89
+ 'thing_tick': ['delta'],
90
+ 'thing_think': ['delta'],
91
+ 'thing_move': [],
92
+ 'mouseover': ['clientX', 'clientY', 'position'],
93
+ 'mouseout': [],
94
+ 'mousedown': [],
95
+ 'mouseup': [],
96
+ 'click': []
97
+ });
98
+
99
+ if (typeof this.preinit == 'function') {
100
+ this.preinit();
101
+ }
102
+
103
+ if (typeof this.postinit == 'function') {
104
+ this.postinit();
105
+ }
106
+ this.init3D();
107
+ this.initDOM();
108
+ this.initPhysics();
109
+
110
+ setTimeout(elation.bind(this, function() {
111
+ // Fire create event next frame
112
+ this.createChildren();
113
+ this.refresh();
114
+ elation.events.fire({type: 'thing_create', element: this});
115
+ }), 0);
116
+ }
117
+ this.preinit = function() {
118
+ }
119
+ this.postinit = function() {
120
+ }
121
+ this.initProperties = function() {
122
+ if (!this.properties) {
123
+ this.properties = {};
124
+ }
125
+
126
+ for (var propname in this._thingdef.properties) {
127
+ var prop = this._thingdef.properties[propname];
128
+ if (!this.hasOwnProperty(propname)) {
129
+ this.defineProperty(propname, prop);
130
+ }
131
+ }
132
+ }
133
+ this.getPropertyValue = function(type, value) {
134
+ if (value === null) {
135
+ return;
136
+ }
137
+ switch (type) {
138
+ case 'vector2':
139
+ if (elation.utils.isArray(value)) {
140
+ value = new THREE.Vector2(+value[0], +value[1]);
141
+ } else if (elation.utils.isString(value)) {
142
+ var split = value.split((value.indexOf(' ') != -1 ? ' ' : ','));
143
+ value = new THREE.Vector2(+split[0], +split[1]);
144
+ }
145
+ break;
146
+ case 'vector3':
147
+ if (elation.utils.isArray(value)) {
148
+ value = new elation.physics.vector3(+value[0], +value[1], +value[2]);
149
+ } else if (elation.utils.isString(value)) {
150
+ var split = value.split((value.indexOf(' ') != -1 ? ' ' : ','));
151
+ value = new elation.physics.vector3(+split[0], +split[1], +split[2]);
152
+ }
153
+ break;
154
+ case 'euler':
155
+ if (elation.utils.isArray(value)) {
156
+ value = new EulerDegrees().set(+value[0], +value[1], +value[2]);
157
+ } else if (elation.utils.isString(value)) {
158
+ var split = value.split((value.indexOf(' ') != -1 ? ' ' : ','));
159
+ value = new EulerDegrees().set(+split[0], +split[1], +split[2]);
160
+ } else if (value instanceof THREE.Vector3) {
161
+ value = new EulerDegrees().set(value.x, value.y, value.z);
162
+ }
163
+ break;
164
+ case 'quaternion':
165
+ if (elation.utils.isArray(value)) {
166
+ value = new elation.physics.quaternion(+value[0], +value[1], +value[2], +value[3]);
167
+ } else if (elation.utils.isString(value)) {
168
+ var split = value.split((value.indexOf(' ') != -1 ? ' ' : ','));
169
+ value = new elation.physics.quaternion(+split[0], +split[1], +split[2], +split[3]);
170
+ } else if (value instanceof THREE.Quaternion && !(value instanceof elation.physics.quaternion)) {
171
+ value = new elation.physics.quaternion().copy(value);
172
+ }
173
+ break;
174
+ case 'color':
175
+ var clamp = elation.utils.math.clamp;
176
+ if (value instanceof THREE.Vector3) {
177
+ value = new THREE.Color(clamp(value.x, 0, 1), clamp(value.y, 0, 1), clamp(value.z, 0, 1));
178
+ } else if (value instanceof THREE.Vector4) {
179
+ this.opacity = clamp(value.w, 0, 1);
180
+ value = new THREE.Color(clamp(value.x, 0, 1), clamp(value.y, 0, 1), clamp(value.z, 0, 1));
181
+ } else if (elation.utils.isString(value)) {
182
+ var splitpos = value.indexOf(' ');
183
+ if (splitpos == -1) splitpos = value.indexOf(',');
184
+ if (splitpos == -1) {
185
+ value = new THREE.Color(value);
186
+ } else {
187
+ var split = value.split((value.indexOf(' ') != -1 ? ' ' : ','));
188
+ value = new THREE.Color(clamp(split[0], 0, 1), clamp(split[1], 0, 1), clamp(split[2], 0, 1));
189
+ if (split.length > 3) {
190
+ this.opacity = clamp(split[3], 0, 1);
191
+ }
192
+ }
193
+ } else if (elation.utils.isArray(value)) {
194
+ value = new THREE.Color(+value[0], +value[1], +value[2]);
195
+ } else if (!(value instanceof THREE.Color)) {
196
+ value = new THREE.Color(value);
197
+ }
198
+ break;
199
+ case 'bool':
200
+ case 'boolean':
201
+ value = !(value === false || (elation.utils.isString(value) && value.toLowerCase() === 'false') || value === 0 || value === '0' || value === '' || value === null || typeof value == 'undefined');
202
+ break;
203
+ case 'float':
204
+ value = +value;
205
+ break;
206
+ case 'int':
207
+ case 'integer':
208
+ value = value | 0;
209
+ break;
210
+ case 'texture':
211
+ if (value !== false) {
212
+ value = (value instanceof THREE.Texture ? value : elation.engine.materials.getTexture(value));
213
+ }
214
+ break;
215
+ case 'json':
216
+ if (value !== false) {
217
+ value = (elation.utils.isString(value) ? JSON.parse(value) : value);
218
+ }
219
+ break;
220
+ case 'component':
221
+ if (value) {
222
+ var component = elation.component.fetch(value[0], value[1]);
223
+ if (component) {
224
+ value = component;
225
+ }
226
+ }
227
+ break;
228
+ }
229
+ return value;
230
+ }
231
+ this.defineProperties = function(properties) {
232
+ elation.utils.merge(properties, this._thingdef.properties);
233
+ this.initProperties();
234
+ }
235
+ this.defineProperty = function(propname, prop) {
236
+ var propval = elation.utils.arrayget(this.properties, propname, null);
237
+ Object.defineProperty(this, propname, {
238
+ configurable: true,
239
+ enumerable: true,
240
+ get: function() {
241
+ var proxy = this._proxies[propname];
242
+ if (proxy) {
243
+ return proxy;
244
+ }
245
+ return elation.utils.arrayget(this.properties, propname);
246
+ },
247
+ set: function(v) {
248
+ this.set(propname, v, prop.refreshGeometry);
249
+ //this.refresh();
250
+ //console.log('set ' + propname + ' to ', v);
251
+ }
252
+ });
253
+ if (propval === null) {
254
+ if (!elation.utils.isNull(this.args.properties[propname])) {
255
+ propval = this.args.properties[propname]
256
+ } else if (!elation.utils.isNull(prop.default)) {
257
+ propval = prop.default;
258
+ }
259
+ this.set(propname, propval);
260
+ }
261
+ if (prop.type == 'vector2' || prop.type == 'vector3' || prop.type == 'quaternion' || prop.type == 'color') {
262
+ if (propval && !this._proxies[propname]) {
263
+ // Create proxy objects for these special types
264
+ var proxydef = {
265
+ x: ['property', 'x'],
266
+ y: ['property', 'y'],
267
+ z: ['property', 'z'],
268
+ changed: ['property', 'changed'],
269
+ add: ['function', 'add'],
270
+ addScalar: ['function', 'addScalar'],
271
+ addScaledVector: ['function', 'addScaledVector'],
272
+ addVectors: ['function', 'addVectors'],
273
+ applyAxisAngle: ['function', 'applyAxisAngle'],
274
+ angleTo: ['function', 'angleTo'],
275
+ ceil: ['function', 'ceil'],
276
+ clamp: ['function', 'clamp'],
277
+ clampLength: ['function', 'clampLength'],
278
+ clampScalar: ['function', 'clampScalar'],
279
+ clone: ['function', 'clone'],
280
+ copy: ['function', 'copy'],
281
+ cross: ['function', 'cross'],
282
+ crossVectors: ['function', 'crossVectors'],
283
+ distanceTo: ['function', 'distanceTo'],
284
+ manhattanDistanceTo: ['function', 'manhattanDistanceTo'],
285
+ distanceToSquared: ['function', 'distanceToSquared'],
286
+ divide: ['function', 'divide'],
287
+ divideScalar: ['function', 'divideScalar'],
288
+ dot: ['function', 'dot'],
289
+ equals: ['function', 'equals'],
290
+ floor: ['function', 'floor'],
291
+ length: ['function', 'length'],
292
+ manhattanLength: ['function', 'manhattanLength'],
293
+ lengthSq: ['function', 'lengthSq'],
294
+ lerp: ['function', 'lerp'],
295
+ lerpVectors: ['function', 'lerpVectors'],
296
+ max: ['function', 'max'],
297
+ min: ['function', 'min'],
298
+ multiply: ['function', 'multiply'],
299
+ multiplyScalar: ['function', 'multiplyScalar'],
300
+ multiplyVectors: ['function', 'multiplyVectors'],
301
+ negate: ['function', 'negate'],
302
+ normalize: ['function', 'normalize'],
303
+ projectOnPlane: ['function', 'projectOnPlane'],
304
+ projectOnVector: ['function', 'projectOnVector'],
305
+ reflect: ['function', 'reflect'],
306
+ round: ['function', 'round'],
307
+ roundToZero: ['function', 'roundToZero'],
308
+ set: ['function', 'set'],
309
+ setLength: ['function', 'setLength'],
310
+ setScalar: ['function', 'setScalar'],
311
+ sub: ['function', 'sub'],
312
+ subScalar: ['function', 'subScalar'],
313
+ subVectors: ['function', 'subVectors'],
314
+ toArray: ['function', 'toArray'],
315
+ reset: ['function', 'reset'],
316
+ };
317
+ if (prop.type == 'quaternion') {
318
+ proxydef.w = ['property', 'w'];
319
+ } else if (prop.type == 'color') {
320
+ // We want to support color.xyz as well as color.rgb
321
+ proxydef.r = ['property', 'r'];
322
+ proxydef.g = ['property', 'g'];
323
+ proxydef.b = ['property', 'b'];
324
+ proxydef.x = ['property', 'r'];
325
+ proxydef.y = ['property', 'g'];
326
+ proxydef.z = ['property', 'b'];
327
+ }
328
+ var propval = elation.utils.arrayget(this.properties, propname, null);
329
+ if (propval) {
330
+ this._proxies[propname] = new elation.proxy(
331
+ propval, proxydef, true
332
+ );
333
+ // FIXME - listening for proxy_change events would let us respond to changes for individual vector elements, but it gets expensive, and can lead to weird infinite loops
334
+ /*
335
+ elation.events.add(propval, 'proxy_change', elation.bind(this, function(ev) {
336
+ //this.refresh();
337
+ //this.set('exists', this.properties.exists, prop.refreshGeometry);
338
+ //this[propname] = this[propname];
339
+ var propdef = this._thingdef.properties[propname];
340
+ if (propdef && propdef.set) {
341
+ propdef.set.apply(this, [propname, propval]);
342
+ }
343
+ }));
344
+ */
345
+ }
346
+ }
347
+ } else if (prop.type == 'euler') {
348
+ if (propval && !this._proxies[propname]) {
349
+ // Create proxy objects for these special types
350
+ var propval = elation.utils.arrayget(this.properties, propname, null);
351
+ let degrees = new EulerDegrees(propval);
352
+ var proxydef = {
353
+ x: ['property', 'x'],
354
+ y: ['property', 'y'],
355
+ z: ['property', 'z'],
356
+ order: ['property', 'order'],
357
+ set: ['function', 'set'],
358
+ copy: ['function', 'copy'],
359
+ clone: ['function', 'clone'],
360
+ toArray: ['function', 'toArray'],
361
+ };
362
+ if (propval) {
363
+ /*
364
+ this._proxies[propname] = new elation.proxy(
365
+ degrees, proxydef, true
366
+ );
367
+ */
368
+ }
369
+ }
370
+ }
371
+ }
372
+ this.defineActions = function(actions) {
373
+ elation.utils.merge(actions, this._thingdef.actions);
374
+ }
375
+ this.defineEvents = function(events) {
376
+ elation.utils.merge(events, this._thingdef.events);
377
+ }
378
+
379
+ this.set = function(property, value, forcerefresh) {
380
+ var propdef = this._thingdef.properties[property];
381
+ if (!propdef) {
382
+ console.warn('Tried to set unknown property', property, value, this);
383
+ return;
384
+ }
385
+ var changed = false
386
+ var propval = this.getPropertyValue(propdef.type, value);
387
+ var currval = this.get(property);
388
+ //if (currval !== null) {
389
+ switch (propdef.type) {
390
+ case 'vector2':
391
+ case 'vector3':
392
+ case 'vector4':
393
+ case 'quaternion':
394
+ case 'color':
395
+ if (currval === null) {
396
+ elation.utils.arrayset(this.properties, property, propval);
397
+ changed = true;
398
+ } else {
399
+ if (!currval.equals(propval)) {
400
+ currval.copy(propval);
401
+ changed = true
402
+ }
403
+ }
404
+ break;
405
+ case 'euler':
406
+ if (currval === null) {
407
+ elation.utils.arrayset(this.properties, property, propval);
408
+ changed = true;
409
+ } else {
410
+ if (!currval.equals(propval)) {
411
+ currval.copy(propval);
412
+ changed = true
413
+ }
414
+ }
415
+ break;
416
+ case 'texture':
417
+ //console.log('TRY TO SET NEW TEX', property, value, forcerefresh);
418
+ default:
419
+ if (currval !== propval) {
420
+ elation.utils.arrayset(this.properties, property, propval);
421
+ changed = true;
422
+ }
423
+ }
424
+ //} else {
425
+ // elation.utils.arrayset(this.properties, property, propval);
426
+ //}
427
+ if (changed) {
428
+ if (propdef.set) {
429
+ propdef.set.apply(this, [property, propval]);
430
+ }
431
+
432
+ if (forcerefresh && this.objects['3d']) {
433
+ var oldobj = this.objects['3d'],
434
+ parent = oldobj.parent,
435
+ newobj = this.createObject3D();
436
+
437
+ this.objects['3d'] = newobj;
438
+ this.bindObjectProperties(this.objects['3d']);
439
+
440
+ this.objects['3d'].userData.thing = this;
441
+
442
+ elation.events.fire({type: 'thing_recreate', element: this});
443
+ if (parent) {
444
+ parent.remove(oldobj);
445
+ parent.add(newobj);
446
+ }
447
+ }
448
+ if (this.objects.dynamics) {
449
+ if (false && forcerefresh) {
450
+ this.removeDynamics();
451
+ this.initPhysics();
452
+ } else {
453
+ this.objects.dynamics.mass = this.properties.mass;
454
+ this.objects.dynamics.updateState();
455
+ if (this.objects.dynamics.collider) {
456
+ this.objects.dynamics.collider.getInertialMoment();
457
+ }
458
+ }
459
+ this.objects.dynamics.position = this.properties.position;
460
+ this.objects.dynamics.orientation = this.properties.orientation;
461
+ }
462
+
463
+ this.refresh();
464
+ }
465
+ }
466
+ this.setProperties = function(properties, interpolate) {
467
+ for (var prop in properties) {
468
+ if (prop == 'position' && interpolate == true )
469
+ {
470
+ if ( this.tmpvec.fromArray(properties[prop]).distanceToSquared(this.get('position')) > 1 )
471
+ {
472
+ // call interpolate function
473
+ // TODO: fix magic number 0.001
474
+ this.interpolateTo(properties[prop]);
475
+ }
476
+ }
477
+ else {
478
+ this.set(prop, properties[prop], false);
479
+ }
480
+ }
481
+ this.refresh();
482
+ }
483
+
484
+ this.interpolateTo = function(newPos) {
485
+ this.interp.time = 0;
486
+ this.interp.endpoint.fromArray(newPos);
487
+ this.interp.spline = new THREE.SplineCurve3([this.get('position'), this.interp.endpoint]).getPoints(10);
488
+ // console.log(this.interp.spline);
489
+ elation.events.add(this.engine, 'engine_frame', elation.bind(this, this.applyInterp));
490
+ }
491
+
492
+ this.applyInterp = function(ev) {
493
+ this.interp.time += ev.data.delta * this.engine.systems.physics.timescale;
494
+ if (this.interp.time >= this.interp.rate) {
495
+ elation.events.remove(this, 'engine_frame', elation.bind(this, this.applyInterp));
496
+ return;
497
+ }
498
+ console.log("DEBUG: interpolating, time:", this.interp.time);
499
+ if (this.interp.time - this.interp.lastTime >= 2)
500
+ {
501
+ this.set('position', this.interp.spline[Math.floor((this.interp.time * 10) / this.interp.rate)], false);
502
+ this.refresh();
503
+ }
504
+ };
505
+
506
+ this.get = function(property, defval) {
507
+ if (typeof defval == 'undefined') defval = null;
508
+ return elation.utils.arrayget(this.properties, property, defval);
509
+ }
510
+ this.init3D = function() {
511
+ if (this.objects['3d']) {
512
+ if (this.objects['3d'].parent) { this.objects['3d'].parent.remove(this.objects['3d']); }
513
+ }
514
+ if (this.properties.tags) {
515
+ var tags = this.properties.tags.split(',');
516
+ for (var i = 0; i < tags.length; i++) {
517
+ this.addTag(tags[i].trim());
518
+ }
519
+ }
520
+ this.objects['3d'] = this.createObject3D();
521
+ if (this.objects['3d']) {
522
+ this.bindObjectProperties(this.objects['3d']);
523
+ //this.objects['3d'].useQuaternion = true;
524
+ this.objects['3d'].userData.thing = this;
525
+ }
526
+ if (!this.colliders) {
527
+ this.colliders = new THREE.Object3D();
528
+ this.bindObjectProperties(this.colliders);
529
+ //this.colliders.scale.set(1/this.properties.scale.x, 1/this.properties.scale.y, 1/this.properties.scale.z);
530
+ this.colliders.userData.thing = this;
531
+ }
532
+
533
+ var childkeys = Object.keys(this.children);
534
+ if (childkeys.length > 0) {
535
+ // Things were added during initialization, so make sure they're added to the scene
536
+ for (var i = 0; i < childkeys.length; i++) {
537
+ var k = childkeys[i];
538
+ if (this.children[k].objects['3d']) {
539
+ this.objects['3d'].add(this.children[k].objects['3d']);
540
+ }
541
+ }
542
+ }
543
+ if (!this.properties.visible) {
544
+ this.hide();
545
+ }
546
+ elation.events.fire({type: 'thing_init3d', element: this});
547
+ this.refresh();
548
+ }
549
+ this.initDOM = function() {
550
+ if (ENV_IS_BROWSER) {
551
+ this.objects['dom'] = this.createObjectDOM();
552
+ elation.html.addclass(this.container, "space.thing");
553
+ elation.html.addclass(this.container, "space.things." + this.type);
554
+ //this.updateDOM();
555
+ }
556
+ }
557
+ this.initPhysics = function() {
558
+ if (this.properties.physical) {
559
+ this.createDynamics();
560
+ this.createForces();
561
+ }
562
+ }
563
+ this.createObject3D = function() {
564
+ // if (this.properties.exists === false || !ENV_IS_BROWSER) return;
565
+ if (this.properties.exists === false) return;
566
+
567
+ var object = null, geometry = null, material = null;
568
+ var cachebust = '';
569
+ if (this.properties.forcereload) cachebust = '?n=' + (Math.floor(Math.random() * 10000));
570
+ if (this.properties.render) {
571
+ if (this.properties.render.scene) {
572
+ this.loadJSONScene(this.properties.render.scene, this.properties.render.texturepath + cachebust);
573
+ } else if (this.properties.render.mesh) {
574
+ this.loadJSON(this.properties.render.mesh, this.properties.render.texturepath + cachebust);
575
+ } else if (this.properties.render.collada) {
576
+ this.loadCollada(this.properties.render.collada + cachebust);
577
+ } else if (this.properties.render.model) {
578
+ object = elation.engine.assets.find('model', this.properties.render.model);
579
+ } else if (this.properties.render.gltf) {
580
+ this.loadglTF(this.properties.render.gltf + cachebust);
581
+ } else if (this.properties.render.meshname) {
582
+ object = new THREE.Object3D();
583
+ setTimeout(elation.bind(this, this.loadMeshName, this.properties.render.meshname), 0);
584
+ }
585
+ }
586
+
587
+ var geomparams = elation.utils.arrayget(this.properties, "generic.geometry") || {};
588
+ switch (geomparams.type) {
589
+ case 'sphere':
590
+ geometry = new THREE.SphereGeometry(geomparams.radius || geomparams.size, geomparams.segmentsWidth, geomparams.segmentsHeight);
591
+ break;
592
+ case 'cube':
593
+ geometry = new THREE.BoxGeometry(
594
+ geomparams.width || geomparams.size,
595
+ geomparams.height || geomparams.size,
596
+ geomparams.depth || geomparams.size,
597
+ geomparams.segmentsWidth || 1,
598
+ geomparams.segmentsHeight || 1,
599
+ geomparams.segmentsDepth || 1);
600
+ break;
601
+ case 'cylinder':
602
+ geometry = new THREE.CylinderGeometry(
603
+ geomparams.radiusTop || geomparams.radius,
604
+ geomparams.radiusBottom || geomparams.radius,
605
+ geomparams.height,
606
+ geomparams.segmentsRadius,
607
+ geomparams.segmentsHeight,
608
+ geomparams.openEnded);
609
+ break;
610
+ case 'torus':
611
+ geometry = new THREE.TorusGeometry(geomparams.radius, geomparams.tube, geomparams.segmentsR, geomparams.segmentsT, geomparams.arc);
612
+ break;
613
+ }
614
+ if (geometry) {
615
+ var materialparams = elation.utils.arrayget(this.properties, "generic.material") || {};
616
+ if (materialparams instanceof THREE.Material) {
617
+ material = materialparams;
618
+ } else {
619
+ switch (materialparams.type) {
620
+ case 'phong':
621
+ material = new THREE.MeshPhongMaterial(materialparams);
622
+ break;
623
+ case 'lambert':
624
+ material = new THREE.MeshLambertMaterial(materialparams);
625
+ break;
626
+ case 'depth':
627
+ material = new THREE.MeshDepthMaterial();
628
+ break;
629
+ case 'normal':
630
+ material = new THREE.MeshNormalMaterial();
631
+ break;
632
+ case 'basic':
633
+ default:
634
+ material = new THREE.MeshBasicMaterial(materialparams);
635
+ }
636
+ }
637
+ }
638
+
639
+ if (!geometry && !material) {
640
+ //geometry = new THREE.BoxGeometry(1, 1, 1);
641
+ //material = new THREE.MeshPhongMaterial({color: 0xcccccc, opacity: .2, emissive: 0x333333, transparent: true});
642
+ //console.log('made placeholder thing', geometry, material);
643
+ }
644
+
645
+ if (!object) {
646
+ object = (geometry && material ? new THREE.Mesh(geometry, material) : new THREE.Object3D());
647
+ }
648
+ if (geometry && material) {
649
+ if (geomparams.flipSided) material.side = THREE.BackSide;
650
+ if (geomparams.doubleSided) material.side = THREE.DoubleSide;
651
+ }
652
+ this.objects['3d'] = object;
653
+ //this.spawn('gridhelper', {persist: false});
654
+ return object;
655
+ }
656
+ this.createObjectDOM = function() {
657
+ return;
658
+ }
659
+ this.createChildren = function() {
660
+ return;
661
+ }
662
+ this.add = function(thing) {
663
+ if (!this.children[thing.id]) {
664
+ this.children[thing.id] = thing;
665
+ thing.parent = this;
666
+ if (this.objects && thing.objects && this.objects['3d'] && thing.objects['3d']) {
667
+ this.objects['3d'].add(thing.objects['3d']);
668
+ } else if (thing instanceof THREE.Object3D) {
669
+ this.objects['3d'].add(thing);
670
+ }
671
+ if (this.objects && thing.objects && this.objects['dynamics'] && thing.objects['dynamics']) {
672
+ this.objects['dynamics'].add(thing.objects['dynamics']);
673
+ }
674
+ if (this.container && thing.container) {
675
+ this.container.appendChild(thing.container);
676
+ }
677
+ if (this.colliders && thing.colliders) {
678
+ this.colliders.add(thing.colliders);
679
+ }
680
+ elation.events.fire({type: 'thing_add', element: this, data: {thing: thing}});
681
+ return true;
682
+ } else {
683
+ console.log("Couldn't add ", thing.name, " already exists in ", this.name);
684
+ }
685
+ return false;
686
+ }
687
+ this.remove = function(thing) {
688
+ if (thing && this.children[thing.id]) {
689
+ if (this.objects['3d'] && thing.objects['3d']) {
690
+ this.objects['3d'].remove(thing.objects['3d']);
691
+ }
692
+ if (thing.container && thing.container.parentNode) {
693
+ thing.container.parentNode.removeChild(thing.container);
694
+ }
695
+ if (thing.objects['dynamics'] && thing.objects['dynamics'].parent) {
696
+ thing.objects['dynamics'].parent.remove(thing.objects['dynamics']);
697
+ }
698
+ if (this.colliders && thing.colliders) {
699
+ this.colliders.remove(thing.colliders);
700
+ }
701
+ if (thing.colliderhelper) {
702
+ //this.engine.systems.world.scene['colliders'].remove(thing.colliderhelper);
703
+ }
704
+ elation.events.fire({type: 'thing_remove', element: this, data: {thing: thing}});
705
+ delete this.children[thing.id];
706
+ thing.parent = false;
707
+ } else {
708
+ console.log("Couldn't remove ", thing.name, " doesn't exist in ", this.name);
709
+ }
710
+ }
711
+ this.reparent = function(newparent) {
712
+ if (newparent) {
713
+ if (this.parent) {
714
+ newparent.worldToLocal(this.parent.localToWorld(this.properties.position));
715
+ this.properties.orientation.copy(newparent.worldToLocalOrientation(this.parent.localToWorldOrientation()));
716
+ this.parent.remove(this);
717
+ //newparent.worldToLocalDir(this.parent.localToWorldDir(this.properties.orientation));
718
+ }
719
+ var success = newparent.add(this);
720
+ this.refresh();
721
+ return success;
722
+ }
723
+ return false;
724
+ }
725
+ this.show = function() {
726
+ this.objects['3d'].visible = true;
727
+ if (this.colliderhelper) this.colliderhelper.visible = true;
728
+ }
729
+ this.hide = function() {
730
+ this.objects['3d'].visible = false;
731
+ if (this.colliderhelper) this.colliderhelper.visible = false;
732
+ }
733
+ this.createDynamics = function() {
734
+ if (!this.objects['dynamics'] && this.engine.systems.physics) {
735
+ this.objects['dynamics'] = new elation.physics.rigidbody({
736
+ position: this.properties.position,
737
+ orientation: this.properties.orientation,
738
+ mass: this.properties.mass,
739
+ scale: this.properties.scale,
740
+ velocity: this.properties.velocity,
741
+ acceleration: this.properties.acceleration,
742
+ angular: this.properties.angular,
743
+ angularacceleration: this.properties.angularacceleration,
744
+ restitution: this.properties.restitution,
745
+ material: {
746
+ dynamicfriction: this.properties.dynamicfriction,
747
+ staticfriction: this.properties.staticfriction,
748
+ },
749
+ object: this
750
+ });
751
+ //this.engine.systems.physics.add(this.objects['dynamics']);
752
+
753
+ if ((this.properties.collidable || this.properties.pickable) && this.objects['3d'] && this.objects['3d'].geometry) {
754
+ setTimeout(elation.bind(this, this.updateColliderFromGeometry), 0);
755
+ }
756
+
757
+ elation.events.add(this.objects['dynamics'], "physics_update,physics_collide", this);
758
+ elation.events.add(this.objects['dynamics'], "physics_update", elation.bind(this, this.refresh));
759
+ }
760
+ }
761
+ this.removeDynamics = function() {
762
+ if (this.objects.dynamics) {
763
+ if (this.objects.dynamics.parent) {
764
+ this.objects.dynamics.parent.remove(this.objects.dynamics);
765
+ } else {
766
+ this.engine.systems.physics.remove(this.objects.dynamics);
767
+ }
768
+ delete this.objects.dynamics;
769
+ }
770
+ }
771
+ this.createForces = function() {
772
+ }
773
+ this.addForce = function(type, args) {
774
+ return this.objects.dynamics.addForce(type, args);
775
+ }
776
+ this.removeForce = function(force) {
777
+ return this.objects.dynamics.removeForce(force);
778
+ }
779
+ this.updateColliderFromGeometry = function(geom) {
780
+ if (!geom) geom = this.objects['3d'].geometry;
781
+ var collidergeom = false;
782
+ // Determine appropriate collider for the geometry associated with this thing
783
+ var dyn = this.objects['dynamics'];
784
+ if (geom && dyn) {
785
+ if (geom instanceof THREE.SphereGeometry ||
786
+ geom instanceof THREE.SphereBufferGeometry) {
787
+ if (!geom.boundingSphere) geom.computeBoundingSphere();
788
+ this.setCollider('sphere', {radius: geom.boundingSphere.radius});
789
+ } else if (geom instanceof THREE.PlaneGeometry || geom instanceof THREE.PlaneBufferGeometry) {
790
+ if (!geom.boundingBox) geom.computeBoundingBox();
791
+ var size = new THREE.Vector3().subVectors(geom.boundingBox.max, geom.boundingBox.min);
792
+
793
+ // Ensure minimum size
794
+ if (size.x < 1e-6) size.x = .25;
795
+ if (size.y < 1e-6) size.y = .25;
796
+ if (size.z < 1e-6) size.z = .25;
797
+
798
+ this.setCollider('box', geom.boundingBox);
799
+ } else if (geom instanceof THREE.CylinderGeometry) {
800
+ if (geom.radiusTop == geom.radiusBottom) {
801
+ this.setCollider('cylinder', {height: geom.height, radius: geom.radiusTop});
802
+ } else {
803
+ console.log('FIXME - cylinder collider only supports uniform cylinders for now');
804
+ }
805
+ } else if (!dyn.collider) {
806
+ if (!geom.boundingBox) geom.computeBoundingBox();
807
+ this.setCollider('box', geom.boundingBox);
808
+ }
809
+ }
810
+ }
811
+ this.setCollider = function(type, args, rigidbody, reuseMesh) {
812
+ //console.log('set collider', type, args, rigidbody, this.collidable);
813
+ if (!rigidbody) rigidbody = this.objects['dynamics'];
814
+ if (this.properties.collidable) {
815
+ rigidbody.setCollider(type, args);
816
+ }
817
+ if (this.properties.collidable || this.properties.pickable) {
818
+ var collidergeom = false;
819
+ if (type == 'sphere') {
820
+ collidergeom = elation.engine.geometries.generate('sphere', {
821
+ radius: args.radius / this.properties.scale.x
822
+ });
823
+ } else if (type == 'box') {
824
+ var size = new THREE.Vector3().subVectors(args.max, args.min);
825
+ size.x /= this.scale.x;
826
+ size.y /= this.scale.y;
827
+ size.z /= this.scale.z;
828
+ var offset = new THREE.Vector3().addVectors(args.max, args.min).multiplyScalar(.5);
829
+ collidergeom = elation.engine.geometries.generate('box', {
830
+ size: size,
831
+ offset: offset
832
+ });
833
+ } else if (type == 'cylinder') {
834
+ collidergeom = elation.engine.geometries.generate('cylinder', {
835
+ radius: args.radius,
836
+ height: args.height,
837
+ radialSegments: 12
838
+ });
839
+ if (args.offset) {
840
+ collidergeom.applyMatrix4(new THREE.Matrix4().makeTranslation(args.offset.x, args.offset.y, args.offset.z));
841
+ }
842
+ } else if (type == 'capsule') {
843
+ collidergeom = elation.engine.geometries.generate('capsule', {
844
+ radius: args.radius,
845
+ length: args.length,
846
+ radialSegments: 8,
847
+ offset: args.offset,
848
+ });
849
+ } else if (type == 'triangle') {
850
+ collidergeom = elation.engine.geometries.generate('triangle', {
851
+ p1: args.p1,
852
+ p2: args.p2,
853
+ p3: args.p3
854
+ });
855
+ } else if (type == 'mesh') {
856
+ // FIXME - not sure if this is the best way to do it, but this forces mesh colliders into the picking scene
857
+ collidermesh = args.mesh;
858
+ this.colliders.add(args.mesh);
859
+ }
860
+ /*
861
+ if (this.collidermesh) {
862
+ this.colliders.remove(this.collidermesh);
863
+ this.engine.systems.world.scene['colliders'].remove(this.colliderhelper);
864
+ this.collidermesh = false;
865
+ }
866
+ */
867
+ // If we have children, re-add their colliders as children of our own collider, but only if we're working with the root object
868
+ if (rigidbody === this.objects['dynamics']) {
869
+ for (var k in this.children) {
870
+ if (this.children[k].colliders) {
871
+ this.colliders.add(this.children[k].colliders);
872
+ }
873
+ }
874
+ }
875
+ if (collidergeom) {
876
+ let collidercolor = 0x009900;
877
+ if (rigidbody.mass === 0) {
878
+ collidercolor = 0x990000;
879
+ }
880
+ var collidermat = new THREE.MeshPhongMaterial({color: collidercolor, opacity: .2, transparent: true, emissive: 0x333300, alphaTest: .1, depthTest: false, depthWrite: false, side: THREE.DoubleSide});
881
+
882
+ if (reuseMesh && this.collidermesh) {
883
+ var collidermesh = this.collidermesh;
884
+ collidermesh.geometry = collidergeom;
885
+ } else {
886
+ var collidermesh = this.collidermesh = new THREE.Mesh(collidergeom, collidermat);
887
+ if (rigidbody.position !== this.properties.position) {
888
+ // Bind the mesh's position to the rigidbody that represents this part
889
+ Object.defineProperties( collidermesh, {
890
+ position: {
891
+ enumerable: true,
892
+ configurable: true,
893
+ value: rigidbody.position
894
+ },
895
+ quaternion: {
896
+ enumerable: true,
897
+ configurable: true,
898
+ value: rigidbody.orientation
899
+ },
900
+ scale: {
901
+ enumerable: true,
902
+ configurable: true,
903
+ value: rigidbody.scale
904
+ },
905
+ });
906
+ }
907
+ collidermesh.userData.thing = this;
908
+ this.colliders.add(collidermesh);
909
+ }
910
+ collidermesh.updateMatrix();
911
+ collidermesh.updateMatrixWorld();
912
+ var colliderhelper = this.colliderhelper;
913
+ if (!colliderhelper) {
914
+ //colliderhelper = this.colliderhelper = new THREE.EdgesHelper(collidermesh, 0x999900);
915
+ //colliderhelper.matrix = collidermesh.matrix;
916
+ //this.colliders.add(colliderhelper);
917
+ } else {
918
+ //THREE.EdgesHelper.call(colliderhelper, collidermesh, 0x990099);
919
+ }
920
+ //this.engine.systems.world.scene['colliders'].add(colliderhelper);
921
+
922
+ // TODO - integrate this with the physics debug system
923
+ /*
924
+ elation.events.add(rigidbody, 'physics_collide', function() {
925
+ collidermat.color.setHex(0x990000);
926
+ colliderhelper.material.color.setHex(0x990000);
927
+ setTimeout(function() {
928
+ collidermat.color.setHex(0x999900);
929
+ colliderhelper.material.color.setHex(0x999900);
930
+ }, 100);
931
+ });
932
+ elation.events.add(this, 'mouseover,mouseout', elation.bind(this, function(ev) {
933
+ var color = 0xffff00;
934
+ if (ev.type == 'mouseover' && ev.data.object === collidermesh) {
935
+ color = 0x00ff00;
936
+ }
937
+ collidermat.color.setHex(0xffff00);
938
+ colliderhelper.material.color.setHex(color);
939
+ this.refresh();
940
+ }));
941
+ */
942
+ }
943
+ }
944
+ }
945
+ this.resetColliders = function() {
946
+ while (this.colliders.children.length > 0) {
947
+ this.colliders.remove(this.colliders.children[0]);
948
+ }
949
+ }
950
+ this.physics_collide = function(ev) {
951
+ var obj1 = ev.data.bodies[0].object, obj2 = ev.data.bodies[1].object;
952
+
953
+ let events = elation.events.fire({type: 'collide', element: this, data: {
954
+ other: (obj1 == this ? obj2 : obj1),
955
+ collision: ev.data
956
+ } });
957
+ if (elation.events.wasDefaultPrevented(events)) {
958
+ ev.preventDefault();
959
+ }
960
+ }
961
+ this.loadJSON = function(url, texturepath) {
962
+ if (typeof texturepath == 'undefined') {
963
+ texturepath = '/media/space/textures/';
964
+ }
965
+ var loader = new THREE.JSONLoader();
966
+ loader.load(url, elation.bind(this, this.processJSON), texturepath);
967
+ }
968
+ this.processJSON = function(geometry, materials) {
969
+ geometry.computeFaceNormals();
970
+ geometry.computeVertexNormals();
971
+ var mesh = new THREE.Mesh(geometry, materials);
972
+ mesh.doubleSided = false;
973
+ mesh.castShadow = false;
974
+ mesh.receiveShadow = false;
975
+ //this.objects['3d'].updateCollisionSize();
976
+ elation.events.fire({type: "thing_load", element: this, data: mesh});
977
+ this.objects['3d'].add(mesh);
978
+ this.refresh();
979
+ }
980
+ this.loadJSONScene = function(url, texturepath) {
981
+ if (typeof texturepath == 'undefined') {
982
+ texturepath = '/media/space/textures';
983
+ }
984
+ var loader = new THREE.ObjectLoader();
985
+ loader.load(url, elation.bind(this, this.processJSONScene, url));
986
+ }
987
+ this.processJSONScene = function(url, scene) {
988
+ this.extractEntities(scene);
989
+ this.objects['3d'].add(scene);
990
+
991
+ this.extractColliders(scene);
992
+ var textures = this.extractTextures(scene, true);
993
+ this.loadTextures(textures);
994
+ elation.events.fire({ type: 'resource_load_finish', element: this, data: { type: 'model', url: url } });
995
+
996
+ this.extractAnimations(scene);
997
+
998
+ this.refresh();
999
+ }
1000
+ this.loadCollada = function(url) {
1001
+ if (!THREE.ColladaLoader) {
1002
+ // If the loader hasn't been initialized yet, fetch it!
1003
+ elation.require('engine.external.three.ColladaLoader', elation.bind(this, this.loadCollada, url));
1004
+ } else {
1005
+ var loader = new THREE.ColladaLoader();
1006
+ loader.options.convertUpAxis = true;
1007
+ var xhr = loader.load(url, elation.bind(this, this.processCollada, url));
1008
+ elation.events.fire({ type: 'resource_load_start', element: this, data: { type: 'model', url: url } });
1009
+ }
1010
+ }
1011
+ this.processCollada = function(url, collada) {
1012
+ //collada.scene.rotation.x = -Math.PI / 2;
1013
+ //collada.scene.rotation.z = Math.PI;
1014
+ this.extractEntities(collada.scene);
1015
+ /*
1016
+ collada.scene.computeBoundingSphere();
1017
+ collada.scene.computeBoundingBox();
1018
+ //this.updateCollisionSize();
1019
+ */
1020
+ this.objects['3d'].add(collada.scene);
1021
+
1022
+ this.extractColliders(collada.scene, true);
1023
+ var textures = this.extractTextures(collada.scene, true);
1024
+ this.loadTextures(textures);
1025
+ elation.events.fire({ type: 'resource_load_finish', element: this, data: { type: 'model', url: url } });
1026
+
1027
+ this.refresh();
1028
+ }
1029
+ this.loadglTF = function(url) {
1030
+ if (!THREE.glTFLoader) {
1031
+ // If the loader hasn't been initialized yet, fetch it!
1032
+ elation.require('engine.external.three.glTFLoader-combined', elation.bind(this, this.loadglTF, url));
1033
+ } else {
1034
+ var loader = new THREE.glTFLoader();
1035
+ loader.useBufferGeometry = true;
1036
+ loader.load(url, elation.bind(this, this.processglTF, url));
1037
+ elation.events.fire({ type: 'resource_load_start', data: { type: 'model', url: url } });
1038
+ }
1039
+ }
1040
+ this.processglTF = function(url, scenedata) {
1041
+ this.extractEntities(scenedata.scene);
1042
+ //this.updateCollisionSize();
1043
+ //this.objects['3d'].add(scenedata.scene);
1044
+ var parent = this.objects['3d'].parent;
1045
+ parent.remove(this.objects['3d']);
1046
+ this.objects['3d'] = new THREE.Object3D();
1047
+ this.bindObjectProperties(this.objects['3d']);
1048
+ this.objects['3d'].userData.thing = this;
1049
+
1050
+ // Correct coordinate space from various modelling programs
1051
+ // FIXME - hardcoded for blender's settings for now, this should come from a property
1052
+ var scene = scenedata.scene;
1053
+ scene.rotation.x = -Math.PI/2;
1054
+
1055
+ // FIXME - enable shadows for all non-transparent materials. This should be coming from the model file...
1056
+ scene.traverse(function(n) {
1057
+ if (n instanceof THREE.Mesh) {
1058
+ if (n.material && !(n.material.transparent || n.material.opacity < 1.0)) {
1059
+ n.castShadow = true;
1060
+ n.receiveShadow = true;
1061
+ } else {
1062
+ n.castShadow = false;
1063
+ n.receiveShadow = false;
1064
+ }
1065
+ }
1066
+ });
1067
+
1068
+ /*
1069
+ while (scenedata.scene.children.length > 0) {
1070
+ var obj = scenedata.scene.children[0];
1071
+ scenedata.scene.remove(obj);
1072
+ coordspace.add(obj);
1073
+ }
1074
+ this.objects['3d'].add(coordspace);
1075
+ */
1076
+ this.objects['3d'].add(scene);
1077
+
1078
+ //this.objects['3d'].quaternion.setFromEuler(scenedata.scene.rotation);
1079
+
1080
+ var textures = this.extractTextures(scene, true);
1081
+ this.loadTextures(textures);
1082
+
1083
+ parent.add(this.objects['3d']);
1084
+
1085
+ // If no pending textures, we're already loaded, so fire the event
1086
+ if (this.pendingtextures == 0) {
1087
+ elation.events.fire({type: "thing_load", element: this, data: scenedata.scene});
1088
+ }
1089
+
1090
+ elation.events.fire({ type: 'resource_load_finish', data: { type: 'model', url: url } });
1091
+
1092
+ this.refresh();
1093
+ }
1094
+ this.loadMeshName = function(meshname) {
1095
+ var subobj = elation.engine.geometries.getMesh(meshname);
1096
+ subobj.rotation.x = -Math.PI/2;
1097
+ subobj.rotation.y = 0;
1098
+ subobj.rotation.z = Math.PI;
1099
+ this.extractEntities(subobj);
1100
+ this.objects['3d'].add(subobj);
1101
+ this.extractColliders(subobj);
1102
+
1103
+ elation.events.add(null, 'resource_load_complete', elation.bind(this, this.extractColliders, subobj));
1104
+
1105
+ if (ENV_IS_BROWSER){
1106
+ var textures = this.extractTextures(subobj, true);
1107
+ this.loadTextures(textures);
1108
+ }
1109
+ }
1110
+ this.extractEntities = function(scene) {
1111
+ this.cameras = [];
1112
+ this.parts = {};
1113
+ if (!scene) scene = this.objects['3d'];
1114
+ scene.traverse(elation.bind(this, function ( node ) {
1115
+ if ( node instanceof THREE.Camera ) {
1116
+ this.cameras.push(node);
1117
+ //} else if (node instanceof THREE.Mesh) {
1118
+ } else if (node.name !== '') {
1119
+ this.parts[node.name] = node;
1120
+ node.castShadow = this.properties.shadow;
1121
+ node.receiveShadow = this.properties.shadow;
1122
+ }
1123
+ if (node.material) {
1124
+ node.material.fog = this.properties.fog;
1125
+ node.material.wireframe = this.properties.wireframe;
1126
+ }
1127
+ }));
1128
+ //console.log('Collada loaded: ', this.parts, this.cameras, this);
1129
+ if (this.cameras.length > 0) {
1130
+ this.camera = this.cameras[0];
1131
+ }
1132
+ //this.updateCollisionSize();
1133
+ }
1134
+ this.extractColliders = function(obj, useParentPosition) {
1135
+ if (!(this.properties.collidable || this.properties.pickable)) return;
1136
+ var meshes = [];
1137
+ if (!obj) obj = this.objects['3d'];
1138
+ var re = new RegExp(/^[_\*](collider|trigger)-(.*)$/);
1139
+
1140
+ obj.traverse(function(n) {
1141
+ if (n instanceof THREE.Mesh && n.material) {
1142
+ var materials = n.material;
1143
+ if (!elation.utils.isArray(n.material)) {
1144
+ materials = [n.material];
1145
+ }
1146
+ for (var i = 0; i < materials.length; i++) {
1147
+ var m = materials[i];
1148
+ if (m.name && m.name.match(re)) {
1149
+ n.geometry.computeBoundingBox();
1150
+ n.geometry.computeBoundingSphere();
1151
+ meshes.push(n);
1152
+ break;
1153
+ }
1154
+ }
1155
+ }
1156
+ });
1157
+
1158
+ // FIXME - hack to make demo work
1159
+ //this.colliders.bindPosition(this.localToWorld(new THREE.Vector3()));
1160
+
1161
+ //var flip = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, Math.PI, 0));
1162
+ var flip = new elation.physics.quaternion();
1163
+ var root = new elation.physics.rigidbody({orientation: flip, object: this});// orientation: obj.quaternion.clone() });
1164
+ //root.orientation.multiply(flip);
1165
+
1166
+ for (var i = 0; i < meshes.length; i++) {
1167
+ var m = meshes[i].material.name.match(re),
1168
+ type = m[1],
1169
+ shape = m[2];
1170
+
1171
+ var rigid = new elation.physics.rigidbody({object: this});
1172
+ var min = meshes[i].geometry.boundingBox.min.clone().multiply(meshes[i].scale),
1173
+ max = meshes[i].geometry.boundingBox.max.clone().multiply(meshes[i].scale);
1174
+ //console.log('type is', type, shape, min, max);
1175
+
1176
+ var position = meshes[i].position,
1177
+ orientation = meshes[i].quaternion;
1178
+ if (useParentPosition) {
1179
+ position = meshes[i].parent.position;
1180
+ orientation = meshes[i].parent.quaternion;
1181
+ }
1182
+
1183
+ rigid.position.copy(position);
1184
+ rigid.orientation.copy(orientation);
1185
+
1186
+ min.x *= this.properties.scale.x;
1187
+ min.y *= this.properties.scale.y;
1188
+ min.z *= this.properties.scale.z;
1189
+ max.x *= this.properties.scale.x;
1190
+ max.y *= this.properties.scale.y;
1191
+ max.z *= this.properties.scale.z;
1192
+
1193
+ rigid.position.x *= this.properties.scale.x;
1194
+ rigid.position.y *= this.properties.scale.y;
1195
+ rigid.position.z *= this.properties.scale.z;
1196
+
1197
+ if (shape == 'box') {
1198
+ this.setCollider('box', {min: min, max: max}, rigid);
1199
+ } else if (shape == 'sphere') {
1200
+ this.setCollider('sphere', {radius: Math.max(max.x, max.y, max.z)}, rigid);
1201
+ } else if (shape == 'cylinder') {
1202
+ var radius = Math.max(max.x - min.x, max.z - min.z) / 2,
1203
+ height = max.y - min.y,
1204
+ pos = max.clone().add(min).multiplyScalar(.5);
1205
+ this.setCollider('cylinder', {radius: radius, height: height, offset: pos}, rigid);
1206
+
1207
+ rigid.position.add(pos);
1208
+ // FIXME - rotate everything by 90 degrees on x axis to match default orientation
1209
+ var rot = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, -Math.PI/2, 0));
1210
+ rigid.orientation.multiply(rot);
1211
+ }
1212
+
1213
+ if (type == 'collider') {
1214
+ //console.log('add collider:', rigid, rigid.position.toArray(), rigid.orientation.toArray());
1215
+ root.add(rigid);
1216
+ } else if (type == 'trigger') {
1217
+ var triggername = meshes[i].parent.name;
1218
+
1219
+ var size = new THREE.Vector3().subVectors(max, min);
1220
+ /*
1221
+ size.x /= this.properties.scale.x;
1222
+ size.y /= this.properties.scale.y;
1223
+ size.z /= this.properties.scale.z;
1224
+ */
1225
+
1226
+ var quat = new THREE.Quaternion().multiplyQuaternions(obj.quaternion, rigid.orientation);
1227
+ var pos = rigid.position.clone().applyQuaternion(quat);
1228
+ /*
1229
+ pos.x /= this.properties.scale.x;
1230
+ pos.y /= this.properties.scale.y;
1231
+ pos.z /= this.properties.scale.z;
1232
+ */
1233
+
1234
+ this.triggers[triggername] = this.spawn('trigger', 'trigger_' + this.name + '_' + triggername, {
1235
+ position: pos,
1236
+ orientation: quat,
1237
+ shape: shape,
1238
+ size: size,
1239
+ scale: new THREE.Vector3(1 / this.properties.scale.x, 1 / this.properties.scale.y, 1 / this.properties.scale.z)
1240
+ });
1241
+ }
1242
+ meshes[i].parent.remove(meshes[i]);
1243
+ meshes[i].position.copy(rigid.position);
1244
+ meshes[i].quaternion.copy(rigid.orientation);
1245
+ /*
1246
+ meshes[i].bindPosition(rigid.position);
1247
+ meshes[i].bindQuaternion(rigid.orientation);
1248
+ */
1249
+ //meshes[i].bindScale(this.properties.scale);
1250
+ meshes[i].userData.thing = this;
1251
+ meshes[i].updateMatrix();
1252
+ meshes[i].updateMatrixWorld();
1253
+ //meshes[i].material = new THREE.MeshPhongMaterial({color: 0x999900, emissive: 0x666666, opacity: .5, transparent: true});
1254
+ //this.colliders.add(meshes[i]);
1255
+ let collidercolor = 0x999999;
1256
+ if (rigid.mass === 0) {
1257
+ collidercolor = 0x990000;
1258
+ }
1259
+ meshes[i].material = new THREE.MeshPhongMaterial({color: 0x0000ff, opacity: .2, transparent: true, emissive: 0x552200, alphaTest: .1, depthTest: false, depthWrite: false});
1260
+ /*
1261
+ this.colliderhelper = new THREE.EdgesHelper(meshes[i], 0x00ff00);
1262
+ this.colliders.add(this.colliderhelper);
1263
+ this.engine.systems.world.scene['colliders'].add(this.colliderhelper);
1264
+ meshes[i].updateMatrix();
1265
+ meshes[i].updateMatrixWorld();
1266
+ */
1267
+ }
1268
+ if (this.objects.dynamics) {
1269
+ this.objects.dynamics.add(root);
1270
+ }
1271
+
1272
+ /*
1273
+ new3d.scale.copy(obj.scale);
1274
+ new3d.position.copy(obj.position);
1275
+ new3d.quaternion.copy(obj.quaternion);
1276
+ this.objects['3d'].add(new3d);
1277
+ */
1278
+ //this.colliders.bindScale(this.properties.scale);
1279
+ //this.colliders.updateMatrixWorld();
1280
+ return this.colliders;
1281
+ }
1282
+ this.extractTextures = function(object, useloadhandler) {
1283
+ if (!object) object = this.objects['3d'];
1284
+
1285
+ // Extract the unique texture images out of the specified object
1286
+ var unique = {};
1287
+ var ret = [];
1288
+ var mapnames = ['map', 'lightMap', 'bumpMap', 'normalMap', 'specularMap', 'envMap'];
1289
+ object.traverse(function(n) {
1290
+ if (n instanceof THREE.Mesh) {
1291
+ var materials = n.material;
1292
+ if (!elation.utils.isArray(n.material)) {
1293
+ materials = [n.material];
1294
+ }
1295
+
1296
+ for (var materialidx = 0; materialidx < materials.length; materialidx++) {
1297
+ var m = materials[materialidx];
1298
+ for (var mapidx = 0; mapidx < mapnames.length; mapidx++) {
1299
+ var tex = m[mapnames[mapidx]];
1300
+ if (tex) {
1301
+ if (tex.image && !unique[tex.image.src]) {
1302
+ unique[tex.image.src] = true;
1303
+ ret.push(tex);
1304
+ } else if (!tex.image && tex.sourceFile != '') {
1305
+ if (!unique[tex.sourceFile]) {
1306
+ unique[tex.sourceFile] = true;
1307
+ ret.push(tex);
1308
+ }
1309
+ } else if (!tex.image) {
1310
+ ret.push(tex);
1311
+ }
1312
+ }
1313
+ }
1314
+ }
1315
+ }
1316
+ });
1317
+ return ret;
1318
+ }
1319
+ this.extractAnimations = function(scene) {
1320
+ var animations = [],
1321
+ actions = {},
1322
+ skeleton = false,
1323
+ meshes = [],
1324
+ num = 0;
1325
+
1326
+ scene.traverse(function(n) {
1327
+ if (n.animations) {
1328
+ //animations[n.name] = n;
1329
+ animations.push.apply(animations, n.animations);
1330
+ num += n.animations.length;
1331
+ }
1332
+ if (n.skeleton) {
1333
+ skeleton = n.skeleton;
1334
+ }
1335
+ if (n instanceof THREE.SkinnedMesh) {
1336
+ meshes.push(n);
1337
+ }
1338
+ });
1339
+
1340
+ var mixer = this.animationmixer = new THREE.AnimationMixer(scene);
1341
+ //console.log('animation mixer', mixer, meshes[0]);
1342
+ if (skeleton) {
1343
+ this.skeleton = skeleton;
1344
+
1345
+ /*
1346
+ scene.traverse(function(n) {
1347
+ n.skeleton = skeleton;
1348
+ });
1349
+ */
1350
+ if (true) { //meshes.length > 0) {
1351
+ var skeletons = [];
1352
+ for (var i = 0; i < meshes.length; i++) {
1353
+ //meshes[i].bind(this.skeleton);
1354
+ skeletons.push(meshes[i].skeleton);
1355
+ }
1356
+ // FIXME - shouldn't be hardcoded!
1357
+ var then = performance.now();
1358
+ setInterval(elation.bind(this, function() {
1359
+ var now = performance.now(),
1360
+ diff = now - then;
1361
+ then = now;
1362
+ this.animationmixer.update(diff / 1000);
1363
+ for (var i = 0; i < skeletons.length; i++) {
1364
+ skeletons[i].update();
1365
+ }
1366
+ this.refresh();
1367
+ }), 16);
1368
+ }
1369
+ }
1370
+ if (num > 0) {
1371
+ this.animations = animations;
1372
+ for (var i = 0; i < animations.length; i++) {
1373
+ var anim = animations[i];
1374
+ actions[anim.name] = mixer.clipAction(anim);
1375
+ }
1376
+ this.animationactions = actions;
1377
+ }
1378
+
1379
+ if (this.skeletonhelper && this.skeletonhelper.parent) {
1380
+ this.skeletonhelper.parent.remove(this.skeletonhelper);
1381
+ }
1382
+ this.skeletonhelper = new THREE.SkeletonHelper(this.objects['3d']);
1383
+
1384
+ //this.engine.systems.world.scene['world-3d'].add(this.skeletonhelper);
1385
+
1386
+ }
1387
+ this.rebindAnimations = function() {
1388
+ var rootobject = this.objects['3d'];
1389
+
1390
+ if (this.animationmixer) {
1391
+ // Reset to t-pose before rebinding skeleton
1392
+ this.animationmixer.stopAllAction();
1393
+ this.animationmixer.update(0);
1394
+ }
1395
+
1396
+ rootobject.traverse(function(n) {
1397
+ if (n instanceof THREE.SkinnedMesh) {
1398
+ n.rebindByName(rootobject);
1399
+ }
1400
+ });
1401
+ }
1402
+ this.loadTextures = function(textures) {
1403
+ this.pendingtextures = 0;
1404
+ for (var i = 0; i < textures.length; i++) {
1405
+ if (textures[i].image) {
1406
+ if (!textures[i].image.complete) {
1407
+ elation.events.fire({ type: 'resource_load_start', data: { type: 'image', image: textures[i].image } });
1408
+ this.pendingtextures++;
1409
+ elation.events.add(textures[i].image, 'load', elation.bind(this, this.textureLoadComplete));
1410
+ }
1411
+ }
1412
+ }
1413
+
1414
+ // Everything was already loaded, so fire the event immediately
1415
+ if (this.pendingtextures === 0) {
1416
+ this.refresh();
1417
+ elation.events.fire({type: "thing_load", element: this, data: this.objects['3d']});
1418
+ }
1419
+ }
1420
+ this.textureLoadComplete = function(ev) {
1421
+ this.refresh();
1422
+ if (--this.pendingtextures == 0) {
1423
+ // Fire the thing_load event once all textures are loaded
1424
+ elation.events.fire({type: "thing_load", element: this, data: this.objects['3d']});
1425
+ }
1426
+ }
1427
+ this.spawn = function(type, name, spawnargs, orphan) {
1428
+ var newspawn = this.engine.systems.world.spawn(type, name, spawnargs, (orphan ? null : this));
1429
+ return newspawn;
1430
+ }
1431
+ this.die = function() {
1432
+ this.removeDynamics();
1433
+ if (this.parent) {
1434
+ this.parent.remove(this);
1435
+ }
1436
+ if (this.children) {
1437
+ var keys = Object.keys(this.children);
1438
+ for (var i = 0; i < keys.length; i++) {
1439
+ this.children[keys[i]].die();
1440
+ }
1441
+ }
1442
+ elation.events.fire({element: this, type: 'thing_destroy'});
1443
+ this.destroy();
1444
+ }
1445
+ this.refresh = function() {
1446
+ elation.events.fire({type: 'thing_change_queued', element: this});
1447
+ }
1448
+ this.applyChanges = function() {
1449
+ var s = this.scale;
1450
+ if (s && this.objects['3d']) {
1451
+ this.objects['3d'].visible = this.visible && !(s.x == 0 || s.y == 0 || s.z == 0);
1452
+ }
1453
+ if (this.colliders) {
1454
+ this.colliders.position.copy(this.properties.position);
1455
+ this.colliders.quaternion.copy(this.properties.orientation);
1456
+ this.colliders.scale.copy(this.properties.scale);
1457
+ }
1458
+ elation.events.fire({type: 'thing_change', element: this});
1459
+ }
1460
+ this.reload = function() {
1461
+ this.set('forcereload', true, true);
1462
+ }
1463
+ this.worldToLocal = function(worldpos, clone) {
1464
+ if (this.objects['3d'].matrixWorldNeedsUpdate) this.objects['3d'].updateMatrixWorld();
1465
+ if (clone) worldpos = worldpos.clone();
1466
+ return this.objects['3d'].worldToLocal(worldpos);
1467
+ }
1468
+ this.localToWorld = function(localpos, clone) {
1469
+ if (this.objects['3d'].matrixWorldNeedsUpdate) this.objects['3d'].updateMatrixWorld();
1470
+ if (clone) localpos = localpos.clone();
1471
+ return this.objects['3d'].localToWorld(localpos);
1472
+ }
1473
+ this.worldToParent = function(worldpos, clone) {
1474
+ if (this.objects['3d'].matrixWorldNeedsUpdate) this.objects['3d'].updateMatrixWorld();
1475
+ if (clone) worldpos = worldpos.clone();
1476
+ return this.objects['3d'].parent.worldToLocal(worldpos);
1477
+ }
1478
+ this.localToParent = function(localpos, clone) {
1479
+ if (this.objects['3d'].matrixWorldNeedsUpdate) this.objects['3d'].updateMatrixWorld();
1480
+ if (clone) localpos = localpos.clone();
1481
+ return localpos.applyMatrix4(this.objects['3d'].matrix);
1482
+ }
1483
+ this.localToWorldOrientation = function(orient) {
1484
+ if (!orient) orient = new THREE.Quaternion();
1485
+ var n = this;
1486
+ while (n && n.properties) {
1487
+ orient.multiply(n.properties.orientation);
1488
+ n = n.parent;
1489
+ }
1490
+ return orient;
1491
+ }
1492
+ this.worldToLocalOrientation = function(orient) {
1493
+ if (!orient) orient = new THREE.Quaternion();
1494
+ /*
1495
+ var n = this.parent;
1496
+ var worldorient = new THREE.Quaternion();
1497
+ while (n && n.properties) {
1498
+ worldorient.multiply(inverse.copy(n.properties.orientation).inverse());
1499
+ n = n.parent;
1500
+ }
1501
+ return orient.multiply(worldorient);
1502
+ */
1503
+ // FIXME - this is cheating!
1504
+ var tmpquat = new THREE.Quaternion();
1505
+ return orient.multiply(tmpquat.copy(this.objects.dynamics.orientationWorld).inverse());
1506
+
1507
+ }
1508
+ this.getWorldPosition = function(target) {
1509
+ if (!target) target = new THREE.Vector3();
1510
+ if (this.objects['3d'].matrixWorldNeedsUpdate) {
1511
+ this.objects['3d'].updateMatrixWorld();
1512
+ }
1513
+ target.setFromMatrixPosition(this.objects['3d'].matrixWorld);
1514
+ return target;
1515
+ }
1516
+ this.lookAt = function(other, up) {
1517
+ if (!up) up = new THREE.Vector3(0,1,0);
1518
+ var otherpos = false;
1519
+ if (other.properties && other.properties.position) {
1520
+ otherpos = other.localToWorld(new THREE.Vector3());
1521
+ } else if (other instanceof THREE.Vector3) {
1522
+ otherpos = other.clone();
1523
+ }
1524
+ var thispos = this.localToWorld(new THREE.Vector3());
1525
+
1526
+ if (otherpos) {
1527
+ var dir = thispos.clone().sub(otherpos).normalize();
1528
+ var axis = new THREE.Vector3().crossVectors(up, dir);
1529
+ var angle = dir.dot(up);
1530
+ this.properties.orientation.setFromAxisAngle(axis, -angle);
1531
+ //console.log(thispos.toArray(), otherpos.toArray(), dir.toArray(), axis.toArray(), angle, this.properties.orientation.toArray());
1532
+ }
1533
+ }
1534
+ this.serialize = function(serializeAll) {
1535
+ var ret = {
1536
+ name: this.name,
1537
+ parentname: this.parentname,
1538
+ type: this.type,
1539
+ properties: {},
1540
+ things: {}
1541
+ };
1542
+ var numprops = 0,
1543
+ numthings = 0;
1544
+
1545
+ for (var k in this._thingdef.properties) {
1546
+ var propdef = this._thingdef.properties[k];
1547
+ var propval = this.get(k);
1548
+ if (propval !== null) {
1549
+ switch (propdef.type) {
1550
+ case 'vector2':
1551
+ case 'vector3':
1552
+ case 'vector4':
1553
+ propval = propval.toArray();
1554
+ for (var i = 0; i < propval.length; i++) propval[i] = +propval[i]; // FIXME - force to float
1555
+ break;
1556
+ case 'quaternion':
1557
+ propval = [propval.x, propval.y, propval.z, propval.w];
1558
+ break;
1559
+ case 'texture':
1560
+ propval = propval.sourceFile;
1561
+ break;
1562
+ /*
1563
+ case 'color':
1564
+ propval = propval.getHexString();
1565
+ break;
1566
+ */
1567
+ case 'component':
1568
+ var ref = propval;
1569
+ propval = [ ref.type, ref.id ];
1570
+ break;
1571
+
1572
+ }
1573
+ try {
1574
+ if (propval !== null && !elation.utils.isIdentical(propval, propdef.default)) {
1575
+ //elation.utils.arrayset(ret.properties, k, propval);
1576
+ ret.properties[k] = propval;
1577
+ numprops++;
1578
+ }
1579
+ } catch (e) {
1580
+ console.log("Error serializing property: " + k, this, e);
1581
+ }
1582
+ }
1583
+ }
1584
+ if (numprops == 0) delete ret.properties;
1585
+
1586
+ for (var k in this.children) {
1587
+ if (this.children[k].properties) {
1588
+ if (!serializeAll) {
1589
+ if (this.children[k].properties.persist) {
1590
+ ret.things[k] = this.children[k].serialize();
1591
+ numthings++;
1592
+ }
1593
+ }
1594
+ else {
1595
+ ret.things[k] = this.children[k].serialize();
1596
+ numthings++;
1597
+ }
1598
+ } else {
1599
+ console.log('huh what', k, this.children[k]);
1600
+ }
1601
+ }
1602
+ if (numthings == 0) delete ret.things;
1603
+
1604
+ return ret;
1605
+ }
1606
+ //this.thing_add = function(ev) {
1607
+ // elation.events.fire({type: 'thing_add', element: this, data: ev.data});
1608
+ //}
1609
+
1610
+ /*
1611
+ this.createCamera = function(offset, rotation) {
1612
+ //var viewsize = this.engine.systems.render.views['main'].size;
1613
+ var viewsize = [640, 480]; // FIXME - hardcoded hack!
1614
+ this.cameras.push(new THREE.PerspectiveCamera(50, viewsize[0] / viewsize[1], .01, 1e15));
1615
+ this.camera = this.cameras[this.cameras.length-1];
1616
+ if (offset) {
1617
+ this.camera.position.copy(offset)
1618
+ }
1619
+ if (rotation) {
1620
+ //this.camera.eulerOrder = "YZX";
1621
+ this.camera.rotation.copy(rotation);
1622
+ }
1623
+ this.objects['3d'].add(this.camera);
1624
+ }
1625
+ */
1626
+
1627
+ // Sound functions
1628
+ this.playSound = function(sound, volume, position, velocity) {
1629
+ if (this.sounds[sound] && this.engine.systems.sound.enabled) {
1630
+ this.updateSound(sound, volume, position, velocity);
1631
+ this.sounds[sound].play();
1632
+ }
1633
+ }
1634
+ this.stopSound = function(sound) {
1635
+ if (this.sounds[sound] && this.sounds[sound].playing) {
1636
+ this.sounds[sound].stop();
1637
+ }
1638
+ }
1639
+ this.updateSound = function(sound, volume, position, velocity) {
1640
+ if (this.sounds[sound]) {
1641
+ if (!volume) volume = 100;
1642
+ this.sounds[sound].setVolume(volume);
1643
+ if (position) {
1644
+ this.sounds[sound].setPan([position.x, position.y, position.z], (velocity ? [velocity.x, velocity.y, velocity.z] : [0,0,0]));
1645
+ }
1646
+ }
1647
+ }
1648
+ this.addTag = function(tag) {
1649
+ if (!this.hasTag(tag)) {
1650
+ this.tags.push(tag);
1651
+ return true;
1652
+ }
1653
+ return false;
1654
+ }
1655
+ this.hasTag = function(tag) {
1656
+ return (this.tags.indexOf(tag) !== -1);
1657
+ }
1658
+ this.removeTag = function(tag) {
1659
+ var idx = this.tags.indexOf(tag);
1660
+ if (idx !== -1) {
1661
+ this.tags.splice(idx, 1);
1662
+ return true;
1663
+ }
1664
+ return false;
1665
+ }
1666
+ this.getPlayer = function() {
1667
+ console.log('player id:', this.get('player_id'));
1668
+ return this.get('player_id');
1669
+ }
1670
+ this.addPart = function(name, part) {
1671
+ if (this.parts[name] === undefined) {
1672
+ this.parts[name] = part;
1673
+ var type = part.componentname;
1674
+ if (this.parttypes[type] === undefined) {
1675
+ this.parttypes[type] = [];
1676
+ }
1677
+ this.parttypes[type].push(part);
1678
+ return true;
1679
+ }
1680
+ return false;
1681
+ }
1682
+ this.hasPart = function(name) {
1683
+ return (this.parts[name] !== undefined);
1684
+ }
1685
+ this.hasPartOfType = function(type) {
1686
+ return (this.parttypes[type] !== undefined && this.parttypes[type].length > 0);
1687
+ }
1688
+ this.getPart = function(name) {
1689
+ return this.parts[name];
1690
+ }
1691
+ this.getPartsByType = function(type) {
1692
+ return this.parttypes[type] || [];
1693
+ }
1694
+ this.getThingByObject = function(obj) {
1695
+ while (obj) {
1696
+ if (obj.userData.thing) return obj.userData.thing;
1697
+ obj = obj.parent;
1698
+ }
1699
+ return null;
1700
+ }
1701
+ this.getObjectsByTag = function(tag) {
1702
+ }
1703
+ this.getChildren = function(collection) {
1704
+ if (typeof collection == 'undefined') collection = [];
1705
+ for (var k in this.children) {
1706
+ collection.push(this.children[k]);
1707
+ this.children[k].getChildren(collection);
1708
+ }
1709
+ return collection;
1710
+ }
1711
+ this.getChildrenByProperty = function(key, value, collection) {
1712
+ if (typeof collection == 'undefined') collection = [];
1713
+ for (var k in this.children) {
1714
+ if (this.children[k][key] === value) {
1715
+ collection.push(this.children[k]);
1716
+ }
1717
+ this.children[k].getChildrenByProperty(key, value, collection);
1718
+ }
1719
+ return collection;
1720
+ }
1721
+ this.getChildrenByPlayer = function(player, collection) {
1722
+ if (typeof collection == 'undefined') collection = [];
1723
+ for (var k in this.children) {
1724
+ if (this.children[k].getPlayer() == player) {
1725
+ collection.push(this.children[k]);
1726
+ }
1727
+ this.children[k].getChildrenByPlayer(player, collection);
1728
+ }
1729
+ return collection;
1730
+ }
1731
+ this.getChildrenByTag = function(tag, collection) {
1732
+ if (typeof collection == 'undefined') collection = [];
1733
+ for (var k in this.children) {
1734
+ if (this.children[k].hasTag(tag)) {
1735
+ collection.push(this.children[k]);
1736
+ }
1737
+ this.children[k].getChildrenByTag(tag, collection);
1738
+ }
1739
+ return collection;
1740
+ }
1741
+ this.getChildrenByType = function(type, collection) {
1742
+ if (typeof collection == 'undefined') collection = [];
1743
+ for (var k in this.children) {
1744
+ if (this.children[k].type == type) {
1745
+ collection.push(this.children[k]);
1746
+ }
1747
+ this.children[k].getChildrenByType(type, collection);
1748
+ }
1749
+ return collection;
1750
+ }
1751
+ this.distanceTo = function(obj) {
1752
+ return Math.sqrt(this.distanceToSquared(obj));
1753
+ }
1754
+ this.distanceToSquared = (function() {
1755
+ // closure scratch variables
1756
+ var _v1 = new THREE.Vector3(),
1757
+ _v2 = new THREE.Vector3();
1758
+ return function(obj) {
1759
+ var mypos = this.localToWorld(_v1.set(0,0,0));
1760
+ if (obj && obj.localToWorld) {
1761
+ return mypos.distanceToSquared(obj.localToWorld(_v2.set(0,0,0)));
1762
+ } else if (obj instanceof THREE.Vector3) {
1763
+ return mypos.distanceToSquared(obj);
1764
+ }
1765
+ return Infinity;
1766
+ }
1767
+ })();
1768
+ this.canUse = function(object) {
1769
+ return false;
1770
+ }
1771
+ this.thing_use_activate = function(ev) {
1772
+ var player = ev.data;
1773
+ var canuse = this.canUse(player);
1774
+ if (canuse && canuse.action) {
1775
+ canuse.action(player);
1776
+ }
1777
+ }
1778
+ this.getBoundingSphere = function() {
1779
+ // Iterate over all children and expand our bounding sphere to encompass them.
1780
+ // This gives us the total size of the whole thing
1781
+
1782
+ var bounds = new THREE.Sphere();
1783
+ var worldpos = this.localToWorld(new THREE.Vector3());
1784
+ var childworldpos = new THREE.Vector3();
1785
+ this.objects['3d'].traverse((n) => {
1786
+ childworldpos.set(0,0,0).applyMatrix4(n.matrixWorld);
1787
+ if (n instanceof THREE.Mesh) {
1788
+ if (!n.geometry.boundingSphere) {
1789
+ n.geometry.computeBoundingSphere();
1790
+ }
1791
+ var newradius = worldpos.distanceTo(childworldpos) + n.geometry.boundingSphere.radius * Math.max(n.scale.x, n.scale.y, n.scale.z);
1792
+ if (newradius > bounds.radius) {
1793
+ bounds.radius = newradius;
1794
+ }
1795
+ }
1796
+ });
1797
+ return bounds;
1798
+ }
1799
+ this.getBoundingBox = function(local) {
1800
+ // Iterate over all children and expand our bounding box to encompass them.
1801
+ // This gives us the total size of the whole thing
1802
+
1803
+ var bounds = new THREE.Box3();
1804
+ bounds.setFromObject(this.objects['3d']);
1805
+
1806
+ if (bounds.min.lengthSq() === Infinity || bounds.max.lengthSq === Infinity) {
1807
+ bounds.min.set(0,0,0);
1808
+ bounds.max.set(0,0,0);
1809
+ }
1810
+
1811
+ if (local) {
1812
+ this.worldToLocal(bounds.min);
1813
+ this.worldToLocal(bounds.max);
1814
+ }
1815
+
1816
+ return bounds;
1817
+ }
1818
+ this.bindObjectProperties = function(obj) {
1819
+ Object.defineProperties( obj, {
1820
+ position: {
1821
+ enumerable: true,
1822
+ configurable: true,
1823
+ value: this.properties.position
1824
+ },
1825
+ quaternion: {
1826
+ enumerable: true,
1827
+ configurable: true,
1828
+ value: this.properties.orientation
1829
+ },
1830
+ scale: {
1831
+ enumerable: true,
1832
+ configurable: true,
1833
+ value: this.properties.scale
1834
+ },
1835
+ });
1836
+ }
1837
+ this.playAnimation = function(name) {
1838
+ let anim = false;
1839
+ if (this.animations) {
1840
+ this.animations.forEach(n => {
1841
+ if (n.name == name) {
1842
+ anim = n;
1843
+ }
1844
+ });
1845
+ }
1846
+ console.log('play anim', anim);
1847
+ if (anim) {
1848
+ this.animationmixer.clipAction(anim).play();
1849
+ }
1850
+ }
1851
+ this.enableDebug = function() {
1852
+ if (this.objects.dynamics) {
1853
+ this.engine.systems.physics.debug(this);
1854
+ }
1855
+ for (let k in this.children) {
1856
+ this.children[k].enableDebug();
1857
+ }
1858
+ }
1859
+ this.disableDebug = function() {
1860
+ for (let k in this.children) {
1861
+ this.children[k].disableDebug();
1862
+ }
1863
+ if (this.objects.dynamics) {
1864
+ this.engine.systems.physics.disableDebug(this);
1865
+ }
1866
+ }
1867
+ this.updatePhysics = function() {
1868
+ if (this.objects.dynamics) {
1869
+ this.objects.dynamics.restitution = this.restitution;
1870
+ this.objects.dynamics.material.dynamicfriction = this.dynamicfriction;
1871
+ this.objects.dynamics.material.staticfriction = this.staticfriction;
1872
+ }
1873
+ }
1874
+ });
1875
+ });