diff --git a/examples/animations/assets/skeleton_animation.fbx b/examples/animations/assets/skeleton_animation.fbx index 9ff8c54c..7f67309a 100644 Binary files a/examples/animations/assets/skeleton_animation.fbx and b/examples/animations/assets/skeleton_animation.fbx differ diff --git a/examples/animations/index.js b/examples/animations/index.js index 4d7730c4..0165a247 100644 --- a/examples/animations/index.js +++ b/examples/animations/index.js @@ -58,7 +58,7 @@ export default class Intro extends Level { // const animated = Models.getModel('animated'); const skeleton = Models.get('skeleton'); - const duck = Models.get('duck'); + // const duck = Models.get('duck'); const eyeballs = [ Particles.addParticleEmitter(PARTICLES.FIRE, { texture: 'fire', @@ -88,18 +88,21 @@ export default class Intro extends Level { leftEyeball.emit(Infinity); leftEyeball.setPosition({x: 0.2, y: 0.35, z: 0.3}); - skeleton.playAnimation(skeleton.getAvailableAnimations()[5]) + skeleton.playAnimation('Root|Interact'); + console.log(skeleton.getAvailableAnimations()); skeleton.setMaterialFromName(constants.MATERIALS.STANDARD, { roughness: .5, metalness: 0 }); - duck.setMaterialFromName(constants.MATERIALS.STANDARD, { roughness: .5, metalness: 0 }); + // duck.setMaterialFromName(constants.MATERIALS.STANDARD, { roughness: .5, metalness: 0 }); + + // setTimeout(() => { + // skeleton.dispose(); + // }, 2000); } } const assets = { '/': { models: { - 'animated': 'assets/animated.fbx', - 'duck': 'assets/duck_animation.fbx', 'skeleton': 'assets/skeleton_animation.fbx', }, textures: { diff --git a/examples/particles_fire/index.js b/examples/particles_fire/index.js index b0375c90..5bd19a96 100644 --- a/examples/particles_fire/index.js +++ b/examples/particles_fire/index.js @@ -47,6 +47,7 @@ export default class Intro extends Level { fire.setPosition({ y: 1 }); window.cube = cube; + window.fire = fire; } onKeyDown({ event }) { diff --git a/src/core/universe.js b/src/core/universe.js index bbafc3a7..6900ba63 100755 --- a/src/core/universe.js +++ b/src/core/universe.js @@ -58,9 +58,9 @@ export class Universe { } forEach = (callback) => { - const keys = Object.keys(this.reality); - - keys.forEach(k => callback(this.reality[k])); + Object + .keys(this.reality) + .forEach(k => callback(this.reality[k], k)); }; forEachAsync = (callback) => { @@ -68,7 +68,7 @@ export class Universe { return new Promise(resolve => { Promise - .all(keys.map(k => callback(this.reality[k]))) + .all(keys.map(k => callback(this.reality[k], k))) .then(resolve); }); }; @@ -86,7 +86,9 @@ export class Universe { } bigfreeze = () => { - this.forEach(o => o.dispose()); + this.forEach((o) => { + o && o.dispose && o.dispose() + }); this.reset(); } diff --git a/src/entities/Element.js b/src/entities/Element.js index bd775860..5d536629 100644 --- a/src/entities/Element.js +++ b/src/entities/Element.js @@ -574,7 +574,7 @@ export default class Element extends Entity { return new Promise((resolve) => new Between(this.opacity, opacity) .time(time) - .on('update', value => this.setOpacity(value)) + .on('update', value => !this.isDisposed() && this.setOpacity(value)) .on('complete', resolve) ); } @@ -626,19 +626,21 @@ export default class Element extends Entity { } disposeBody() { - if (hasMaterial(this.body)) { + super.disposeBody(); + + if (hasMaterial(this.getBody())) { disposeTextures(this.getBody()); disposeMaterial(this.getBody()); disposeGeometry(this.getBody()); - } else { - this.body.traverse(child => { - if (hasMaterial(child)) { - disposeTextures(child); - disposeMaterial(child); - disposeGeometry(child); - } - }); } + + this.getBody().traverse(child => { + if (hasMaterial(child)) { + disposeTextures(child); + disposeMaterial(child); + disposeGeometry(child); + } + }); } update(dt) { @@ -657,10 +659,6 @@ export default class Element extends Entity { dispose() { super.dispose(); - if (this.hasBody()) { - Scene.remove(this.getBody()); - this.disposeBody(); - } if (this.hasAnimationHandler()) { this.removeAnimationHandlerListeners(); } @@ -673,7 +671,6 @@ export default class Element extends Entity { return { ...super.toJSON(), body: this.body.toJSON(), - scripts: this.mapScriptsToJSON(), textures: this.textures, ...this.options } diff --git a/src/entities/Entity.js b/src/entities/Entity.js index 4c26ee57..8251b3ca 100644 --- a/src/entities/Entity.js +++ b/src/entities/Entity.js @@ -12,10 +12,14 @@ import { KEY_IS_MISSING, KEY_VALUE_IS_MISSING, ENTITY_CANT_ADD_NOT_ENTITY, - ENTITY_CHILD_IS_NOT_ENTITY, ENTITY_NOT_SET } from '../lib/messages'; import Scripts from '../scripts/Scripts'; +import Scene from '../core/Scene'; + +import { + isScene +} from '../lib/meshUtils'; import { DEFAULT_TAG, @@ -33,6 +37,7 @@ export default class Entity extends EventDispatcher { this.children = []; this.isMage = true; this.parent = false; + this.disposed = false; this.addTags([ DEFAULT_TAG, tag, ...tags ]); this.serializable = serializable; @@ -46,6 +51,10 @@ export default class Entity extends EventDispatcher { return !!this.serializable; } + isDisposed() { + return this.disposed; + } + reset() { this.scripts = []; this.children = []; @@ -149,6 +158,10 @@ export default class Entity extends EventDispatcher { } } + hasChildren() { + return this.children.length > 0; + } + getHierarchy() { return { element: this, @@ -197,14 +210,18 @@ export default class Entity extends EventDispatcher { return this.tags; } - stopScripts() { + disposeScripts() { if (this.hasScripts()) { - this.scripts.forEach(({ script, enabled }) => { + const length = this.scripts.length; + for (let i = 0; i < length; i++) { + const { script, enabled } = this.scripts[i]; if (enabled) { script.onDispose(); - script.__hasStarted(false); + script.__setStartedFlag(false); } - }); + + delete this.scripts[i]; + } } } @@ -213,7 +230,7 @@ export default class Entity extends EventDispatcher { this.scripts.forEach(({ script, enabled, options }) => { if (enabled) { script.start(this, options); - script.__hasStarted(true); + script.__setStartedFlag(true); } }); } @@ -239,16 +256,34 @@ export default class Entity extends EventDispatcher { } } + disposeBody() { + this.getBody().clear(); + if (this.getBody().dispose && !isScene(this.getBody())) { + this.getBody().dispose(); + } + } + dispose() { + if (this.hasChildren()) { + this.children.forEach(child => { + child.dispose(); + }); + } + if (this.hasBody()) { this.stopStateMachine(); - this.stopScripts(); - this.reset(); + this.disposeScripts(); + + Scene.remove(this.getBody()); + this.disposeBody(); } + this.dispatchEvent({ type: ENTITY_EVENTS.DISPOSE - }) + }); + + this.reset(); } hasStateMachine = () => !!this.stateMachine; @@ -527,7 +562,7 @@ export default class Entity extends EventDispatcher { return new Promise((resolve) => new Between({ x, y, z}, rotation) .time(time) - .on('update', value => this.setRotation(value)) + .on('update', value => !this.isDisposed() && this.setRotation(value)) .on('complete', resolve) ); } @@ -538,7 +573,7 @@ export default class Entity extends EventDispatcher { return new Promise((resolve) => new Between({ x, y, z}, position) .time(time) - .on('update', value => this.setPosition(value)) + .on('update', value => !this.isDisposed() && this.setPosition(value)) .on('complete', resolve) ); } @@ -621,6 +656,7 @@ export default class Entity extends EventDispatcher { rotation: this.getRotation(), scale: this.getScale(), entityType: this.getEntityType(), + scripts: this.mapScriptsToJSON(), tags: this.getTags() } } diff --git a/src/fx/particles/ParticleEmitter.js b/src/fx/particles/ParticleEmitter.js index d06df8f4..7f0989af 100644 --- a/src/fx/particles/ParticleEmitter.js +++ b/src/fx/particles/ParticleEmitter.js @@ -104,10 +104,14 @@ export default class ParticleEmitter extends Entity { } } + dispose() { + super.dispose(); + } + update(dt) { super.update(dt); - if (this.hasSystem()) { + if (this.hasSystem() && !this.isSystemDead()) { this.system.update(dt); } diff --git a/src/fx/particles/Particles.js b/src/fx/particles/Particles.js index 37862ee5..33a75916 100644 --- a/src/fx/particles/Particles.js +++ b/src/fx/particles/Particles.js @@ -7,7 +7,7 @@ import Trail from './Trail'; import Scene from '../../core/Scene'; import Proton from 'three.proton.js'; -import { INVALID_EMITTER_ID } from '../../lib/messages'; +import { DEPRECATIONS, INVALID_EMITTER_ID } from '../../lib/messages'; import { PARTICLE_EMITTER_TYPES } from './constants'; import ParticleEmitter from './ParticleEmitter'; import ParticleEmitterGroup from './ParticleEmitterGroup'; @@ -62,8 +62,12 @@ export class Particles { emitter instanceof ProtonParticleEmitter; } - addParticleEmitter(_emitter, options = {}) { + addParticleEmitter(emitter, options = {}) { + console.warn(DEPRECATIONS.PARTICLES_ADD_PARTICLE_EMITTER); + return this.add(emitter, options); + } + add(_emitter, options = {}) { let emitter; if (this.isRegisteredEmitter(_emitter)) { const Emitter = this.get(_emitter); @@ -122,7 +126,7 @@ export class Particles { } if (emitter.isSystemDead()) { - this.toDispose.push(emitter.uuid); + this.toDispose.push(emitter.uuid()); } } diff --git a/src/fx/particles/ProtonParticleEmitter.js b/src/fx/particles/ProtonParticleEmitter.js index bbcaa505..680cc940 100644 --- a/src/fx/particles/ProtonParticleEmitter.js +++ b/src/fx/particles/ProtonParticleEmitter.js @@ -9,6 +9,7 @@ import Images from '../../images/Images'; import PALETTES from '../../lib/palettes'; const DEFAULT_PARTICLE_COLOR = PALETTES.BASE.BLACK; +const SYSTEM_DISPOSE_TIMEOUT = 700; export default class ProtonParticleEmitter extends ParticleEmitter { @@ -95,10 +96,16 @@ export default class ProtonParticleEmitter extends ParticleEmitter { this.system.p.z = position.z; } + disposeSystem = () => { + if (this.hasSystem()) { + this.system.removeAllParticles(); + this.system.destroy(); + } + } + dispose() { super.dispose(); - - this.system.stopEmit(); - this.system.destroy(); + this.stop(); + setTimeout(this.disposeSystem, SYSTEM_DISPOSE_TIMEOUT); } } \ No newline at end of file diff --git a/src/lib/math.js b/src/lib/math.js index d525609f..1922de51 100644 --- a/src/lib/math.js +++ b/src/lib/math.js @@ -3,7 +3,9 @@ import { Vector3, MathUtils } from 'three'; export const PI = Math.PI; export const PI_2 = PI/2; -export const pickRandom = list => list.length && list[Math.floor(Math.random() * list.length)]; +export const identity = a => a; + +export const pickRandom = (list = []) => list[Math.floor(Math.random() * list.length)]; export const degToRad = (angle) => { return angle * (PI / 180); diff --git a/src/lib/meshUtils.js b/src/lib/meshUtils.js index 4ee1b40f..c265cc42 100644 --- a/src/lib/meshUtils.js +++ b/src/lib/meshUtils.js @@ -10,7 +10,7 @@ import { import Config from '../core/config'; import Lights from '../lights/Lights'; import ToonMaterial from '../materials/Toon'; -import { MATERIALS } from './constants'; +import { MATERIALS, TEXTURES } from './constants'; export const setUpLightsAndShadows = (mesh) => { const { @@ -96,7 +96,12 @@ const cloneMaterial = (MeshMaterial, mesh, options = {}) => { export const disposeTextures = mesh => { if (hasMaterial(mesh)) { const _disposeTexture = (material) => { - material.map && material.dispose(); + Object.values(TEXTURES) + .forEach(key => { + if (material[key]) { + material[key].dispose(); + } + }) } processMaterial(mesh.material, _disposeTexture); } @@ -104,13 +109,13 @@ export const disposeTextures = mesh => { export const disposeMaterial = mesh => { if (hasMaterial(mesh)) { - mesh.material.dispose(); + mesh.material.dispose && mesh.material.dispose(); } }; export const disposeGeometry = mesh => { if (hasGeometry(mesh)) { - mesh.geometry.dispose(); + mesh.geometry.dispose && mesh.geometry.dispose(); } }; diff --git a/src/lib/messages.js b/src/lib/messages.js index 5f525ba2..8b147612 100644 --- a/src/lib/messages.js +++ b/src/lib/messages.js @@ -2,7 +2,8 @@ export const PREFIX = '[Mage]'; export const DEPRECATED = '[DEPRECATED]'; export const DEPRECATIONS = { - MODELS_GETMODEL: `${PREFIX} ${DEPRECATED} Models.getModel is deprecated, use Models.get instead. WIll be removed in next major release`, + PARTICLES_ADD_PARTICLE_EMITTER: `${PREFIX} ${DEPRECATED} Particles.addParticleEmitter is deprecated, use Particles.add instead. Will be removed in the next major release`, + MODELS_GETMODEL: `${PREFIX} ${DEPRECATED} Models.getModel is deprecated, use Models.get instead. Will be removed in next major release`, SCRIPTS_CREATE: `${PREFIX} ${DEPRECATED} Scripts.create is deprecated, use Scripts.register instead. Will be removed in next major release.`, ELEMENT_SET_TEXTURE_MAP: `${PREFIX} ${DEPRECATED} Element.setTextureMap is deprecated, use Element.setTexture() instead. Will be removed in next major release.`, }; diff --git a/src/lights/light.js b/src/lights/light.js index 7a55c2ed..c0308fae 100644 --- a/src/lights/light.js +++ b/src/lights/light.js @@ -116,7 +116,7 @@ export default class Light extends Entity { return new Promise((resolve) => new Between(intensity, value) .time(time) - .on('update', value => this.setIntensity(value)) + .on('update', value => !this.isDisposed() && this.setIntensity(value)) .on('complete', resolve) ); } diff --git a/src/physics/index.js b/src/physics/index.js index 0f0692d7..a0b5132d 100644 --- a/src/physics/index.js +++ b/src/physics/index.js @@ -337,17 +337,6 @@ export class Physics extends EventDispatcher { } } - disposeElement = element => { - if (Config.physics().enabled) { - const uuid = element.uuid(); - - this.worker.postMessage({ - event: PHYSICS_EVENTS.ELEMENT.DISPOSE, - uuid - }); - } - }; - explosion = (element, strength, radius) => { if (Config.physics().enabled) { const uuid = element.uuid(); diff --git a/src/scripts/BaseScript.js b/src/scripts/BaseScript.js index 8013d630..ecfe61f4 100644 --- a/src/scripts/BaseScript.js +++ b/src/scripts/BaseScript.js @@ -2,7 +2,10 @@ export default class BaseScript { __check() { return true; } - __hasStarted(flag) { this.hasStarted = flag; } + __hasStarted() { return this.hasStarted; } + __isDisposed() { return this.isDisposed; } + __setStartedFlag(flag) { this.hasStarted = flag; } + __setDisposedFlag(flag) { this.isDisposed = flag; } constructor(name) { this.__name = name || this.contructor.name; diff --git a/src/scripts/builtin/Trails.js b/src/scripts/builtin/Trails.js index 65910561..27bbb814 100644 --- a/src/scripts/builtin/Trails.js +++ b/src/scripts/builtin/Trails.js @@ -10,7 +10,7 @@ export default class Trails extends BaseScript { start(element, { texture = false, size }) { this.element = element; - this.emitter = Particles.addParticleEmitter(PARTICLES.TRAIL, { texture, size }); + this.emitter = Particles.add(PARTICLES.TRAIL, { texture, size }); this.emitter.start(Infinity); }