From 0516568d830c4db1da82a67b44c636d27c01a8bb Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Thu, 26 Mar 2020 15:58:26 +0000 Subject: [PATCH 001/144] Create the anim component --- build/dependencies.txt | 8 + src/anim/anim.js | 68 ++-- src/framework/application.js | 3 + src/framework/components/anim/binder.js | 351 ++++++++++++++++++ src/framework/components/anim/component.js | 90 +++++ src/framework/components/anim/controller.js | 208 +++++++++++ src/framework/components/anim/data.js | 18 + .../components/anim/property-locator.js | 60 +++ src/framework/components/anim/system.js | 105 ++++++ .../components/animation/component.js | 46 +-- src/framework/components/animation/data.js | 2 +- src/framework/components/animation/system.js | 12 +- src/resources/animation-clip.js | 71 ++++ src/resources/animation-state-graph.js | 49 +++ 14 files changed, 1027 insertions(+), 64 deletions(-) create mode 100644 src/framework/components/anim/binder.js create mode 100644 src/framework/components/anim/component.js create mode 100644 src/framework/components/anim/controller.js create mode 100644 src/framework/components/anim/data.js create mode 100644 src/framework/components/anim/property-locator.js create mode 100644 src/framework/components/anim/system.js create mode 100644 src/resources/animation-clip.js create mode 100644 src/resources/animation-state-graph.js diff --git a/build/dependencies.txt b/build/dependencies.txt index 2d20b7c2b5e..73eca8b5d85 100644 --- a/build/dependencies.txt +++ b/build/dependencies.txt @@ -139,6 +139,12 @@ ../src/framework/components/animation/component.js ../src/framework/components/animation/system.js ../src/framework/components/animation/data.js +../src/framework/components/anim/binder.js +../src/framework/components/anim/controller.js +../src/framework/components/anim/component.js +../src/framework/components/anim/data.js +../src/framework/components/anim/property-locator.js +../src/framework/components/anim/system.js ../src/framework/components/model/component.js ../src/framework/components/model/system.js ../src/framework/components/model/data.js @@ -224,6 +230,8 @@ ../src/resources/bundle.js ../src/resources/untar.js ../src/resources/animation.js +../src/resources/animation-clip.js +../src/resources/animation-state-graph.js ../src/resources/audio.js ../src/resources/cubemap.js ../src/resources/json.js diff --git a/src/anim/anim.js b/src/anim/anim.js index 5fa53293aaf..c892820739d 100644 --- a/src/anim/anim.js +++ b/src/anim/anim.js @@ -513,7 +513,7 @@ Object.assign(pc, function () { /** * @private * @callback pc.AnimSetter - * @description Callback function that the {@link pc.AnimController} uses to set final animation values. + * @description Callback function that the {@link pc.AnimEvaluator} uses to set final animation values. * These callbacks are stored in {@link pc.AnimTarget} instances which are constructed by an * {@link pc.AnimBinder}. * @param {number[]} value - updated animation value. @@ -523,9 +523,9 @@ Object.assign(pc, function () { * @private * @class * @name pc.AnimTarget - * @classdesc Stores the information required by {@link pc.AnimController} for updating a target value. + * @classdesc Stores the information required by {@link pc.AnimEvaluator} for updating a target value. * @param {pc.AnimSetter} func - this function will be called when a new animation value is output by - * the {@link pc.AnimController}. + * the {@link pc.AnimEvaluator}. * @param {'vector'|'quaternion'} type - the type of animation data this target expects. * @param {number} components - the number of components on this target (this should ideally match the number * of components found on all attached animation curves). @@ -558,7 +558,7 @@ Object.assign(pc, function () { * @private * @class * @name pc.AnimBinder - * @classdesc This interface is used by {@link pc.AnimController} to resolve unique animation target path strings + * @classdesc This interface is used by {@link pc.AnimEvaluator} to resolve unique animation target path strings * into instances of {@link pc.AnimTarget}. */ var AnimBinder = function () { }; @@ -617,7 +617,7 @@ Object.assign(pc, function () { * @private * @function * @name pc.AnimBinder#unresolve - * @description Called when the {@link AnimController} no longer has a curve driving the given key. + * @description Called when the {@link AnimEvaluator} no longer has a curve driving the given key. * @param {string} path - the animation curve path which is no longer driven. */ unresolve: function (path) { @@ -628,7 +628,7 @@ Object.assign(pc, function () { * @private * @function * @name pc.AnimBinder#update - * @description Called by {@link pc.AnimController} once a frame after animation updates are done. + * @description Called by {@link pc.AnimEvaluator} once a frame after animation updates are done. * @param {number} deltaTime - amount of time that passed in the current update. */ update: function (deltaTime) { @@ -747,13 +747,13 @@ Object.assign(pc, function () { /** * @private * @class - * @name pc.AnimController + * @name pc.AnimEvaluator * @classdesc AnimContoller blends multiple sets of animation clips together. * @description Create a new animation controller. * @param {pc.AnimBinder} binder - interface resolves curve paths to instances of {@link pc.AnimTarget}. * @property {pc.AnimClip[]} clips - the list of animation clips */ - var AnimController = function (binder) { + var AnimEvaluator = function (binder) { this._binder = binder; this._clips = []; this._inputs = []; @@ -763,11 +763,11 @@ Object.assign(pc, function () { /** * @private - * @name pc.AnimController + * @name pc.AnimEvaluator * @type {number} * @description The number of clips. */ - Object.defineProperties(AnimController.prototype, { + Object.defineProperties(AnimEvaluator.prototype, { 'clips': { get: function () { return this._clips; @@ -775,7 +775,7 @@ Object.assign(pc, function () { } }); - AnimController._dot = function (a, b) { + AnimEvaluator._dot = function (a, b) { var len = a.length; var result = 0; for (var i = 0; i < len; ++i) { @@ -784,8 +784,8 @@ Object.assign(pc, function () { return result; }; - AnimController._normalize = function (a) { - var l = AnimController._dot(a, a); + AnimEvaluator._normalize = function (a) { + var l = AnimEvaluator._dot(a, a); if (l > 0) { l = 1.0 / Math.sqrt(l); var len = a.length; @@ -795,12 +795,12 @@ Object.assign(pc, function () { } }; - AnimController._set = function (a, b, type) { + AnimEvaluator._set = function (a, b, type) { var len = a.length; var i; if (type === 'quaternion') { - var l = AnimController._dot(b, b); + var l = AnimEvaluator._dot(b, b); if (l > 0) { l = 1.0 / Math.sqrt(l); } @@ -814,7 +814,7 @@ Object.assign(pc, function () { } }; - AnimController._blendVec = function (a, b, t) { + AnimEvaluator._blendVec = function (a, b, t) { var it = 1.0 - t; var len = a.length; for (var i = 0; i < len; ++i) { @@ -822,14 +822,14 @@ Object.assign(pc, function () { } }; - AnimController._blendQuat = function (a, b, t) { + AnimEvaluator._blendQuat = function (a, b, t) { var len = a.length; var it = 1.0 - t; // negate b if a and b don't lie in the same winding (due to // double cover). if we don't do this then often rotations from // one orientation to another go the long way around. - if (AnimController._dot(a, b) < 0) { + if (AnimEvaluator._dot(a, b) < 0) { t = -t; } @@ -837,18 +837,18 @@ Object.assign(pc, function () { a[i] = a[i] * it + b[i] * t; } - AnimController._normalize(a); + AnimEvaluator._normalize(a); }; - AnimController._blend = function (a, b, t, type) { + AnimEvaluator._blend = function (a, b, t, type) { if (type === 'quaternion') { - AnimController._blendQuat(a, b, t); + AnimEvaluator._blendQuat(a, b, t); } else { - AnimController._blendVec(a, b, t); + AnimEvaluator._blendVec(a, b, t); } }; - AnimController._stableSort = function (a, lessFunc) { + AnimEvaluator._stableSort = function (a, lessFunc) { var len = a.length; for (var i = 0; i < len - 1; ++i) { for (var j = i + 1; j < len; ++j) { @@ -861,11 +861,11 @@ Object.assign(pc, function () { } }; - Object.assign(AnimController.prototype, { + Object.assign(AnimEvaluator.prototype, { /** * @private * @function - * @name pc.AnimController#addClip + * @name pc.AnimEvaluator#addClip * @description Add a clip to the controller. * @param {pc.AnimClip} clip - the clip to add to the controller. */ @@ -923,7 +923,7 @@ Object.assign(pc, function () { /** * @private * @function - * @name pc.AnimController#removeClip + * @name pc.AnimEvaluator#removeClip * @description Remove a clip from the controller. * @param {number} index - index of the clip to remove. */ @@ -960,7 +960,7 @@ Object.assign(pc, function () { /** * @private * @function - * @name pc.AnimController#removeClips + * @name pc.AnimEvaluator#removeClips * @description Remove all clips from the controller. */ removeClips: function () { @@ -972,7 +972,7 @@ Object.assign(pc, function () { /** * @private * @function - * @name pc.AnimController#findClip + * @name pc.AnimEvaluator#findClip * @description Returns the first clip which matches the given name, or null if no such clip was found. * @param {string} name - name of the clip to find. * @returns {pc.AnimClip|null} - the clip with the given name or null if no such clip was found. @@ -991,7 +991,7 @@ Object.assign(pc, function () { /** * @private * @function - * @name pc.AnimController#update + * @name pc.AnimEvaluator#update * @description Contoller frame update function. All the attached {@link pc.AnimClip}s are evaluated, * blended and the results set on the {@link pc.AnimTarget}. * @param {number} deltaTime - the amount of time that has passed since the last update, in seconds. @@ -1004,7 +1004,7 @@ Object.assign(pc, function () { var order = clips.map(function (c, i) { return i; }); - AnimController._stableSort(order, function (a, b) { + AnimEvaluator._stableSort(order, function (a, b) { return clips[a].blendOrder < clips[b].blendOrder; }); @@ -1032,7 +1032,7 @@ Object.assign(pc, function () { output = outputs[j]; value = output.value; - AnimController._set(value, input, output.target.type); + AnimEvaluator._set(value, input, output.target.type); output.blendCounter++; } @@ -1043,9 +1043,9 @@ Object.assign(pc, function () { value = output.value; if (output.blendCounter === 0) { - AnimController._set(value, input, output.target.type); + AnimEvaluator._set(value, input, output.target.type); } else { - AnimController._blend(value, input, blendWeight, output.target.type); + AnimEvaluator._blend(value, input, blendWeight, output.target.type); } output.blendCounter++; @@ -1082,6 +1082,6 @@ Object.assign(pc, function () { AnimClip: AnimClip, AnimBinder: AnimBinder, DefaultAnimBinder: DefaultAnimBinder, - AnimController: AnimController + AnimEvaluator: AnimEvaluator }; }()); diff --git a/src/framework/application.js b/src/framework/application.js index 6d990638db8..c2c58e243b7 100644 --- a/src/framework/application.js +++ b/src/framework/application.js @@ -580,6 +580,8 @@ Object.assign(pc, function () { } this.loader.addHandler("animation", new pc.AnimationHandler()); + this.loader.addHandler("animationclip", new pc.AnimationClipHandler()); + this.loader.addHandler("animationstategraph", new pc.AnimationStateGraphHandler()); this.loader.addHandler("model", new pc.ModelHandler(this.graphicsDevice, this.scene.defaultMaterial)); this.loader.addHandler("material", new pc.MaterialHandler(this)); this.loader.addHandler("texture", new pc.TextureHandler(this.graphicsDevice, this.assets, this.loader)); @@ -606,6 +608,7 @@ Object.assign(pc, function () { this.systems.add(new pc.RigidBodyComponentSystem(this)); this.systems.add(new pc.CollisionComponentSystem(this)); this.systems.add(new pc.AnimationComponentSystem(this)); + this.systems.add(new pc.AnimComponentSystem(this)); this.systems.add(new pc.ModelComponentSystem(this)); this.systems.add(new pc.CameraComponentSystem(this)); this.systems.add(new pc.LightComponentSystem(this)); diff --git a/src/framework/components/anim/binder.js b/src/framework/components/anim/binder.js new file mode 100644 index 00000000000..bf60ac3a6d9 --- /dev/null +++ b/src/framework/components/anim/binder.js @@ -0,0 +1,351 @@ +Object.assign(pc, function () { + + var AnimComponentBinder = function (animComponent, graph) { + this.animComponent = animComponent; + + if (graph) { + var nodes = { }; + + // cache node names so we can quickly resolve animation paths + var flatten = function (node) { + nodes[node.name] = { + node: node, + count: 0 + }; + for (var i = 0; i < node.children.length; ++i) { + flatten(node.children[i]); + } + }; + flatten(graph); + + this.nodes = nodes; // map of node name -> { node, count } + this.activeNodes = []; // list of active nodes + this.schema = { + 'translation': { + components: 3, + target: 'localPosition', + type: 'vector' + }, + 'rotation': { + components: 4, + target: 'localRotation', + type: 'quaternion' + }, + 'scale': { + components: 3, + target: 'localScale', + type: 'vector' + } + }; + } + }; + + Object.assign(AnimComponentBinder.prototype, { + resolve: function(path) { + if (path.split('/').length === 3) { + return this._resolveGeneralPath(path); + } + return this._resolveGraphPath(path); + }, + _resolveGeneralPath: function (path) { + var pathSections = new pc.PropertyLocator().decode(path); + + var entityHeirarchy = pathSections[0]; + var component = pathSections[1]; + var propertyHeirarchy = pathSections[2]; + + var entity = this._getEntityFromHeirarchy(entityHeirarchy); + + if (!entity) + return null; + + var propertyComponent = component === 'entity' ? entity : entity.findComponent(component); + + if (!propertyComponent) { + return null; + } + + return this._createAnimTargetForProperty(propertyComponent, propertyHeirarchy); + }, + + _resolveGraphPath: function (path) { + var parts = this._getParts(path); + if (!parts) { + return null; + } + + var node = this.nodes[parts[0]]; + var prop = this.schema[parts[1]]; + + if (node.count === 0) { + this.activeNodes.push(node.node); + } + node.count++; + + return new pc.AnimTarget(this._createSetter(node.node[prop.target]), prop.type, prop.components); + }, + + unresolve: function (path) { + if (path.split('/').length === 3) { + return; + } + // get the path parts. we expect parts to have structure nodeName.[translation|rotation|scale] + var parts = this._getParts(path); + if (parts) { + var node = this.nodes[parts[0]]; + + node.count--; + if (node.count === 0) { + var activeNodes = this.activeNodes; + var i = activeNodes.indexOf(node.node); // :( + var len = activeNodes.length; + if (i < len - 1) { + activeNodes[i] = activeNodes[len - 1]; + } + activeNodes.pop(); + } + } + }, + + update: function (deltaTime) { + // flag active nodes as dirty + var activeNodes = this.activeNodes; + if (activeNodes) { + for (var i = 0; i < activeNodes.length; ++i) { + activeNodes[i]._dirtifyLocal(); + } + } + }, + + joinPath: function (pathSegments) { + var escape = function (string) { + return string.replace(/\\/g, '\\\\').replace(/\./g, '\\.'); + }; + return pathSegments.map(escape).join('.'); + }, + + // split a path string into its segments and resolve character escaping + splitPath: function (path) { + var result = []; + var curr = ""; + var i = 0; + while (i < path.length) { + var c = path[i++]; + + if (c === '\\' && i < path.length) { + c = path[i++]; + if (c === '\\' || c === '.') { + curr += c; + } else { + curr += '\\' + c; + } + } else if (c === '.') { + result.push(curr); + curr = ''; + } else { + curr += c; + } + } + if (curr.length > 0) { + result.push(curr); + } + return result; + }, + + // get the path parts. we expect parts to have structure nodeName.[translation|rotation|scale] + _getParts: function (path) { + var parts = this.splitPath(path); + if (parts.length !== 2 || + !this.nodes.hasOwnProperty(parts[0]) || + !this.schema.hasOwnProperty(parts[1])) { + return null; + } + return parts; + }, + + // create a setter function (works for pc.Vec* and pc.Quaternion) which have a 'set' function. + _createSetter: function (target) { + return function (value) { + target.set.apply(target, value); + }; + }, + + _getEntityFromHeirarchy: function(entityHeirarchy) { + if (!this.animComponent.entity.name === entityHeirarchy[0]) + return null; + + var currEntity = this.animComponent.entity; + for (var i = 0; i < entityHeirarchy.length - 1; i++) { + var entityChildren = currEntity.getChildren(); + var child; + for (var j = 0; j < entityChildren.length; j++) { + if (entityChildren[j].name === entityHeirarchy[i+1]) + child = entityChildren[j]; + } + if (child) + currEntity = child; + else + return null; + } + return currEntity; + }, + + _floatSetter: function(propertyComponent, propertyHeirarchy) { + var setter = function(values) { + this._setProperty(propertyComponent, propertyHeirarchy, values[0]); + }; + return setter.bind(this); + }, + _booleanSetter: function(propertyComponent, propertyHeirarchy) { + var setter = function(values) { + this._setProperty(propertyComponent, propertyHeirarchy, !!values[0]); + }; + return setter.bind(this); + }, + _colorSetter: function(propertyComponent, propertyHeirarchy) { + var colorKeys = ['r', 'g', 'b', 'a']; + var setter = function(values) { + for (var i = 0; i < values.length; i++) { + this._setProperty(propertyComponent, propertyHeirarchy.concat(colorKeys[i]), values[i]); + } + }; + return setter.bind(this); + }, + _vecSetter: function(propertyComponent, propertyHeirarchy) { + var vectorKeys = ['x', 'y', 'z', 'w']; + var setter = function(values) { + for (var i = 0; i < values.length; i++) { + this._setProperty(propertyComponent, propertyHeirarchy.concat(vectorKeys[i]), values[i]); + } + }; + return setter.bind(this); + }, + + _getProperty: function(propertyComponent, propertyHeirarchy) { + if (propertyHeirarchy.length === 1) { + return propertyComponent[propertyHeirarchy[0]]; + } else { + var propertyObject = propertyComponent[propertyHeirarchy[0]]; + return propertyObject[propertyHeirarchy[1]]; + } + }, + + _setProperty: function(propertyComponent, propertyHeirarchy, value) { + if (propertyHeirarchy.length === 1) { + propertyComponent[propertyHeirarchy[0]] = value; + } else { + var propertyObject = propertyComponent[propertyHeirarchy[0]]; + propertyObject[propertyHeirarchy[1]] = value; + propertyComponent[propertyHeirarchy[0]] = propertyObject; + } + }, + + _getObjectPropertyType: function(property) { + if (!property.constructor) + return undefined; + + return property.constructor.name; + }, + + _getEntityProperty: function(propertyHeirarchy) { + var entityProperties = [ + 'localScale', + 'localPosition', + 'localRotation', + 'localEulerAngles', + 'position', + 'rotation', + 'eulerAngles' + ]; + var entityProperty; + for (var i = 0; i < entityProperties.length; i++) { + if (propertyHeirarchy.indexOf(entityProperties[i]) !== -1) { + entityProperty = entityProperties[i]; + } + } + return entityProperty; + }, + + _createAnimTargetForProperty: function(propertyComponent, propertyHeirarchy) { + + var property = this._getProperty(propertyComponent, propertyHeirarchy); + + if (typeof property === 'undefined') + return null; + + var setter; + var animDataType; + var animDataComponents; + + if (typeof property === 'number') { + setter = this._floatSetter(propertyComponent, propertyHeirarchy); + animDataType = 'vector'; + animDataComponents = 1; + } + else if (typeof property === 'boolean') { + setter = this._booleanSetter(propertyComponent, propertyHeirarchy); + animDataType = 'vector'; + animDataComponents = 1; + } + else if (typeof property === 'object') { + switch (this._getObjectPropertyType(property)) { + case 'Vec2': + setter = this._vecSetter(propertyComponent, propertyHeirarchy); + animDataType = 'vector'; + animDataComponents = 2; + break; + case 'Vec3': + setter = this._vecSetter(propertyComponent, propertyHeirarchy); + animDataType = 'vector'; + animDataComponents = 3; + break; + case 'Vec4': + setter = this._vecSetter(propertyComponent, propertyHeirarchy); + animDataType = 'vector'; + animDataComponents = 4; + break; + case 'Color': + setter = this._colorSetter(propertyComponent, propertyHeirarchy); + animDataType = 'vector'; + animDataComponents = 4; + break; + case 'Quat': + setter = this._quatSetter(propertyComponent, propertyHeirarchy); + animDataType = 'quaternion'; + animDataComponents = 4; + break; + default: + break; + } + } + + if (setter) { + var entityProperty = this._getEntityProperty(propertyHeirarchy); + if (entityProperty) { + var entityPropertySetter = function(values) { + // set new values on the property as before + setter(values); + + // create the function name of the properties setter + var entityPropertySetterFunctionName = pc.string.format( + 'set{0}{1}', + entityProperty.substring(0,1).toUpperCase(), + entityProperty.substring(1) + ); + // call the setter function for entities updated property using the newly set property value + propertyComponent[entityPropertySetterFunctionName](this._getProperty(propertyComponent, [entityProperty])); + }; + return new pc.AnimTarget(entityPropertySetter.bind(this), animDataType, animDataComponents); + + } else { + return new pc.AnimTarget(setter, animDataType, animDataComponents); + } + } + return null; + } + }); + + return { + AnimComponentBinder: AnimComponentBinder + }; +}()); diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js new file mode 100644 index 00000000000..111b76ceeb7 --- /dev/null +++ b/src/framework/components/anim/component.js @@ -0,0 +1,90 @@ +Object.assign(pc, function () { + + /** + * @component Anim + * @class + * @name pc.AnimComponent + * @augments pc.Component + * @classdesc The Anim Component allows an Entity to playback animations on models. + * @description Create a new AnimComponent. + * @param {pc.AnimComponentSystem} system - The {@link pc.ComponentSystem} that created this Component. + * @param {pc.Entity} entity - The Entity that this Component is attached to. + * @property {number} speed Speed multiplier for animation play back speed. 1.0 is playback at normal speed, 0.0 pauses the animation. + * @property {boolean} loop If true the animation will restart from the beginning when it reaches the end. + * @property {boolean} activate If true the first animation will begin playing when the scene is loaded. + */ + var AnimComponent = function (system, entity) { + pc.Component.call(this, system, entity); + + // Handle changes to the 'loop' value + this.on('set_loop', this.onSetLoop, this); + }; + AnimComponent.prototype = Object.create(pc.Component.prototype); + AnimComponent.prototype.constructor = AnimComponent; + + Object.assign(AnimComponent.prototype, { + + loadStateGraph: function(asset) { + var data = this.data; + + var graph; + var modelComponent = this.entity.model; + if (modelComponent) { + var m = modelComponent.model; + if (m) { + graph = m.getGraph(); + } + } + + var animBinder = new pc.AnimComponentBinder(this, graph); + var animEvaluator = new pc.AnimEvaluator(animBinder); + + data.animController = new pc.AnimController( + animEvaluator, + asset.states, + asset.transitions, + this.data.activate + ); + }, + + linkAnimAssetToState: function(stateName, asset) { + if (!this.data.animController) + console.error('linkAnimAssetToState: Trying to link an anim asset to non existing state graph. Have you called loadStateMachineAsset?'); + + var animTrack = asset.resource; + + if(!animTrack) + console.error('linkAnimAssetToState: No animation found for given assetName'); + + this.data.animController.linkAnimTrackToState(stateName, animTrack); + }, + + /** + * @function + * @name pc.AnimComponent#play + * @description Start playing an animation. + * @param {string} name - The name of the animation asset to begin playing. + */ + play: function (name) { + + if (!this.enabled || !this.entity.enabled) { + return; + } + + if (!this.data.animController) { + console.error('Trying to play an animation when no animation state machine has been loaded. Have you called loadStateMachineAsset?'); + return; + } + + this.data.animController.play(name); + }, + + onSetLoop: function (name, oldValue, newValue) { + }, + + }); + + return { + AnimComponent: AnimComponent + }; +}()); diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js new file mode 100644 index 00000000000..c4b8c6da93c --- /dev/null +++ b/src/framework/components/anim/controller.js @@ -0,0 +1,208 @@ +Object.assign(pc, function () { + + var AnimState = function (name, speed) { + this.name = name; + this.animTrack = null; + this.speed = speed || 1.0; + }; + + Object.assign(AnimState.prototype, { + isPlayable: function() { + return (this.animTrack || this.name === 'Start' || this.name === 'End'); + } + }); + + var AnimTransition = function (from, to, time, priority) { + this.from = from; + this.to = to; + this.time = time; + this.priority = priority; + }; + + var AnimController = function (animEvaluator, states, transitions, activate) { + this.animEvaluator = animEvaluator; + this.states = states.map(function(state) { + return new AnimState(state.name, state.speed); + }); + this.transitions = transitions.map(function(transition) { + return new AnimTransition(transition.from, transition.to, transition.time, transition.priority); + }); + this.previousStateName = null; + this.activeStateName = 'Start'; + this.playing = false; + this.activate = activate; + + this.currTransitionTime = 1.0; + this.totalTransitionTime = 1.0; + this.isTransitioning = false; + }; + + Object.assign(AnimController.prototype, { + linkAnimTrackToState: function(stateName, animTrack) { + for (var i = 0; i < this.states.length; i++) { + if (this.states[i].name === stateName) { + this.states[i].animTrack = animTrack; + if (this.isPlayable() && this.activate) { + this.play(); + } + return; + } + } + console.error('Linking animation asset to animation state that does not exist'); + }, + isPlayable: function() { + var playable = true; + for (var i = 0; i < this.states.length; i++) { + if (!this.states[i].isPlayable()) { + playable = false; + } + } + return playable; + }, + _getState: function(stateName) { + for (var i = 0; i < this.states.length; i++) { + if (this.states[i].name === stateName) { + return this.states[i]; + } + } + return null; + }, + _getActiveState: function() { + return this._getState(this.activeStateName); + }, + _setActiveState: function(stateName) { + return this.activeStateName = stateName; + }, + _getPreviousState: function() { + return this._getState(this.previousStateName); + }, + _setPreviousState: function(stateName) { + return this.previousStateName = stateName; + }, + play: function(stateName) { + if (stateName) { + this._transitionState(stateName); + } + this.playing = true; + }, + _getActiveStateProgress: function() { + if (this.activeStateName === 'Start' || this.activeStateName === 'End') + return 1.0; + else { + var activeClip = this.animEvaluator.findClip(this.activeStateName); + if (activeClip) { + return activeClip.time / activeClip.track.duration; + } + } + return null; + }, + _findTransition: function(from, to) { + var transitions = this.transitions.filter((function(transition) { + if (to && from) { + return transition.from === from && transition.to === to; + } else { + return transition.from === this.activeStateName; + } + }).bind(this)); + if (transitions.length === 0) + return null; + else if (transitions.length === 1) + return transitions[0]; + else { + transitions.sort(function(a, b) { + return a.priority < b.priority; + }); + return transitions[0]; + } + }, + + _transitionState: function(newStateName) { + var transition; + if (newStateName) { + + if (newStateName === this.activeStateName) { + return; + } + + if (!this._getState(newStateName)) { + return; + } + + transition = this._findTransition(this.activeStateName, newStateName); + if (!transition) { + this._setPreviousState(null); + this._setActiveState(newStateName); + this.animEvaluator.removeClips(); + var activeState = this._getActiveState(); + clip = new pc.AnimClip(activeState.animTrack, 0, activeState.speed, true, true); + clip.name = this.activeStateName; + clip.blendWeight = 1.0; + clip.reset(); + this.animEvaluator.addClip(clip); + clip.play(); + return; + } + } else { + transition = this._findTransition(); + if (!transition) + return; + } + + if (transition.to === 'End') { + this._setActiveState('Start'); + this._transitionState(); + } else { + this._setPreviousState(this.activeStateName); + this._setActiveState(transition.to); + if (transition.time > 0) { + this.isTransitioning = true; + this.totalTransitionTime = transition.time; + this.currTransitionTime = 0; + } + var clip = this.animEvaluator.findClip(this.activeStateName); + if (!clip) { + var activeState = this._getActiveState(); + clip = new pc.AnimClip(activeState.animTrack, 0, activeState.speed, true, true); + clip.name = this.activeStateName; + if (transition.time > 0) { + clip.blendWeight = 0.0; + } else { + clip.blendWeight = 1.0; + } + clip.reset(); + this.animEvaluator.addClip(clip); + } + clip.play(); + } + }, + + update: function(dt) { + if (this.playing) { + this.animEvaluator.update(dt); + + var progress = this._getActiveStateProgress(); + + if (progress >= 1.0) { + this._transitionState(); + } + + if (this.isTransitioning) { + if (this.currTransitionTime > this.totalTransitionTime) { + this.isTransitioning = false; + this.animEvaluator.findClip(this.previousStateName).pause(); + this.animEvaluator.findClip(this.activeStateName).blendWeight = 1.0; + } else { + var interpolatedTime = this.currTransitionTime / this.totalTransitionTime; + this.animEvaluator.findClip(this.previousStateName).blendWeight = 1.0 - interpolatedTime; + this.animEvaluator.findClip(this.activeStateName).blendWeight = interpolatedTime; + } + this.currTransitionTime = this.currTransitionTime + dt; + } + } + }, + }); + + return { + AnimController: AnimController + } +}()); \ No newline at end of file diff --git a/src/framework/components/anim/data.js b/src/framework/components/anim/data.js new file mode 100644 index 00000000000..5f8126a3190 --- /dev/null +++ b/src/framework/components/anim/data.js @@ -0,0 +1,18 @@ +Object.assign(pc, function () { + var AnimComponentData = function () { + // Serialized + this.speed = 1.0; + this.loop = true; + this.activate = true; + this.enabled = true; + + // Non-serialized + this.animController = null; + this.model = null; + this.playing = false; + }; + + return { + AnimComponentData: AnimComponentData + }; +}()); diff --git a/src/framework/components/anim/property-locator.js b/src/framework/components/anim/property-locator.js new file mode 100644 index 00000000000..819e567e4bc --- /dev/null +++ b/src/framework/components/anim/property-locator.js @@ -0,0 +1,60 @@ +Object.assign(pc, function () { + var PropertyLocator = function () { + }; + Object.assign(PropertyLocator.prototype, { + _splitPath: function(path) { + var result = []; + var curr = ""; + var i = 0; + while (i < path.length) { + var c = path[i++]; + + if (c === '\\' && i < path.length) { + c = path[i++]; + if (c === '\\' || c === '.') { + curr += c; + } else { + curr += '\\' + c; + } + } else if (c === '.') { + result.push(curr); + curr = ''; + } else { + curr += c; + } + } + if (curr.length > 0) { + result.push(curr); + } + return result; + }, + encode: function(decodedLocator) { + var entityHeirarchy = decodedLocator[0]; + for (var i = 0; i < entityHeirarchy.length; i++) { + entityHeirarchy[i] = entityHeirarchy[i].split('.').join('\\.'); + } + var component = decodedLocator[1]; + var propertyHeirarchy = decodedLocator[2]; + return pc.string.format( + '{0}/{1}/{2}', + entityHeirarchy.join('.'), + component, + propertyHeirarchy.join('.') + ); + }, + decode: function(encodedLocator) { + var locatorSections = encodedLocator.split('/'); + var entityHeirarchy = this._splitPath(locatorSections[0]); + for (var i = 0; i < entityHeirarchy.length; i++) { + entityHeirarchy[i] = entityHeirarchy[i].split('\\.').join('.'); + } + var component = locatorSections[1]; + var propertyHeirarchy = locatorSections[2].split('.'); + + return [entityHeirarchy, component, propertyHeirarchy]; + } + }); + return { + PropertyLocator: PropertyLocator + } +}()); \ No newline at end of file diff --git a/src/framework/components/anim/system.js b/src/framework/components/anim/system.js new file mode 100644 index 00000000000..a7888b544f0 --- /dev/null +++ b/src/framework/components/anim/system.js @@ -0,0 +1,105 @@ +Object.assign(pc, function () { + var _schema = [ + 'enabled', + 'assets', + 'speed', + 'loop', + 'activate', + 'animations', + 'model', + 'prevAnim', + 'currAnim', + 'blending', + 'blendTimeRemaining', + 'playing' + ]; + + /** + * @class + * @name pc.AnimComponentSystem + * @augments pc.ComponentSystem + * @classdesc The AnimComponentSystem manages creating and deleting AnimComponents. + * @description Create an AnimComponentSystem. + * @param {pc.Application} app - The application managing this system. + */ + var AnimComponentSystem = function AnimComponentSystem(app) { + pc.ComponentSystem.call(this, app); + + this.id = 'anim'; + this.description = "Specifies the animation assets that can run on the model specified by the Entity's model Component."; + + this.ComponentType = pc.AnimComponent; + this.DataType = pc.AnimComponentData; + + this.schema = _schema; + + this.on('beforeremove', this.onBeforeRemove, this); + this.on('update', this.onUpdate, this); + + pc.ComponentSystem.bind('update', this.onUpdate, this); + }; + AnimComponentSystem.prototype = Object.create(pc.ComponentSystem.prototype); + AnimComponentSystem.prototype.constructor = AnimComponentSystem; + + pc.Component._buildAccessors(pc.AnimComponent.prototype, _schema); + + Object.assign(AnimComponentSystem.prototype, { + initializeComponentData: function (component, data, properties) { + properties = ['activate', 'enabled', 'loop', 'speed']; + pc.ComponentSystem.prototype.initializeComponentData.call(this, component, data, properties); + }, + + cloneComponent: function (entity, clone) { + var key; + this.addComponent(clone, {}); + + clone.animation.data.speed = entity.animation.speed; + clone.animation.data.loop = entity.animation.loop; + clone.animation.data.activate = entity.animation.activate; + clone.animation.data.enabled = entity.animation.enabled; + + var clonedAnims = { }; + var animations = entity.animation.animations; + for (key in animations) { + if (animations.hasOwnProperty(key)) { + clonedAnims[key] = animations[key]; + } + } + clone.animation.animations = clonedAnims; + + var clonedAnimsIndex = { }; + var animationsIndex = entity.animation.animationsIndex; + for (key in animationsIndex) { + if (animationsIndex.hasOwnProperty(key)) { + clonedAnimsIndex[key] = animationsIndex[key]; + } + } + clone.animation.animationsIndex = clonedAnimsIndex; + }, + + onBeforeRemove: function (entity, component) { + component.onBeforeRemove(); + }, + + onUpdate: function (dt) { + var components = this.store; + + for (var id in components) { + if (components.hasOwnProperty(id)) { + var component = components[id]; + var componentData = component.data; + + if (componentData.enabled && component.entity.enabled) { + if (componentData.animController) { + componentData.animController.update(dt * componentData.speed); + } + } + } + } + } + }); + + return { + AnimComponentSystem: AnimComponentSystem + }; +}()); diff --git a/src/framework/components/animation/component.js b/src/framework/components/animation/component.js index e0c7de36085..ac5d3910136 100644 --- a/src/framework/components/animation/component.js +++ b/src/framework/components/animation/component.js @@ -63,7 +63,7 @@ Object.assign(pc, function () { if (data.model) { - if (!data.skeleton && !data.animController) { + if (!data.skeleton && !data.animEvaluator) { this._createAnimationController(); } @@ -88,23 +88,23 @@ Object.assign(pc, function () { } } - if (data.animController) { - var animController = data.animController; + if (data.animEvaluator) { + var animEvaluator = data.animEvaluator; if (data.blending) { // remove all but the last clip - while (animController.clips.length > 1) { - animController.removeClip(0); + while (animEvaluator.clips.length > 1) { + animEvaluator.removeClip(0); } } else { - data.animController.removeClips(); + data.animEvaluator.removeClips(); } var clip = new pc.AnimClip(data.animations[data.currAnim], 0, 1.0, true, data.loop); clip.name = data.currAnim; clip.blendWeight = data.blending ? 0 : 1; clip.reset(); - data.animController.addClip(clip); + data.animEvaluator.addClip(clip); } } @@ -144,7 +144,7 @@ Object.assign(pc, function () { data.skeleton = null; data.fromSkel = null; data.toSkel = null; - data.animController = null; + data.animEvaluator = null; }, _createAnimationController: function () { @@ -174,7 +174,7 @@ Object.assign(pc, function () { data.skeleton.looping = data.loop; data.skeleton.setGraph(graph); } else if (hasGlb) { - data.animController = new pc.AnimController(new pc.DefaultAnimBinder(graph)); + data.animEvaluator = new pc.AnimEvaluator(new pc.DefaultAnimBinder(graph)); } }, @@ -318,8 +318,8 @@ Object.assign(pc, function () { data.skeleton.currentTime = 0; data.skeleton.animation = null; } - if (data.animController) { - data.animController.removeClips(); + if (data.animEvaluator) { + data.animEvaluator.removeClips(); } }, @@ -380,9 +380,9 @@ Object.assign(pc, function () { data.skeleton.looping = data.loop; } - if (data.animController) { - for (var i = 0; i < data.animController.clips.length; ++i) { - data.animController.clips[i].loop = data.loop; + if (data.animEvaluator) { + for (var i = 0; i < data.animEvaluator.clips.length; ++i) { + data.animEvaluator.clips[i].loop = data.loop; } } }, @@ -397,10 +397,10 @@ Object.assign(pc, function () { skeleton.updateGraph(); } - if (data.animController) { - var animController = data.animController; - for (var i = 0; i < animController.clips.length; ++i) { - animController.clips[i].time = newValue; + if (data.animEvaluator) { + var animEvaluator = data.animEvaluator; + for (var i = 0; i < animEvaluator.clips.length; ++i) { + animEvaluator.clips[i].time = newValue; } } }, @@ -448,7 +448,7 @@ Object.assign(pc, function () { delete data.fromSkel; delete data.toSkel; - delete data.animController; + delete data.animEvaluator; } }); @@ -466,10 +466,10 @@ Object.assign(pc, function () { skeleton.updateGraph(); } - if (data.animController) { - var animController = data.animController; - for (var i = 0; i < animController.clips.length; ++i) { - animController.clips[i].time = currentTime; + if (data.animEvaluator) { + var animEvaluator = data.animEvaluator; + for (var i = 0; i < animEvaluator.clips.length; ++i) { + animEvaluator.clips[i].time = currentTime; } } } diff --git a/src/framework/components/animation/data.js b/src/framework/components/animation/data.js index c60e5f37482..01a23d738b9 100644 --- a/src/framework/components/animation/data.js +++ b/src/framework/components/animation/data.js @@ -23,7 +23,7 @@ Object.assign(pc, function () { this.toSkel = null; // glb animation controller - this.animController = null; + this.animEvaluator = null; }; return { diff --git a/src/framework/components/animation/system.js b/src/framework/components/animation/system.js index 3674d5dc8ad..08bb7d67cdc 100644 --- a/src/framework/components/animation/system.js +++ b/src/framework/components/animation/system.js @@ -130,12 +130,12 @@ Object.assign(pc, function () { } // update anim controller - var animController = componentData.animController; - if (animController) { + var animEvaluator = componentData.animEvaluator; + if (animEvaluator) { // force all clip's speed and playing state from the component - for (var i = 0; i < animController.clips.length; ++i) { - var clip = animController.clips[i]; + for (var i = 0; i < animEvaluator.clips.length; ++i) { + var clip = animEvaluator.clips[i]; clip.speed = componentData.speed; if (!componentData.playing) { clip.pause(); @@ -146,10 +146,10 @@ Object.assign(pc, function () { // update blend weight if (componentData.blending) { - animController.clips[1].blendWeight = componentData.blend; + animEvaluator.clips[1].blendWeight = componentData.blend; } - animController.update(dt); + animEvaluator.update(dt); } // clear blending flag diff --git a/src/resources/animation-clip.js b/src/resources/animation-clip.js new file mode 100644 index 00000000000..0f2c27c7983 --- /dev/null +++ b/src/resources/animation-clip.js @@ -0,0 +1,71 @@ +Object.assign(pc, function () { + 'use strict'; + + /** + * @class + * @name pc.AnimationClipHandler + * @implements {pc.ResourceHandler} + * @classdesc Resource handler used for loading {@link pc.AnimationClip} resources. + */ + var AnimationClipHandler = function () { + this.retryRequests = false; + }; + + Object.assign(AnimationClipHandler.prototype, { + load: function (url, callback) { + if (typeof url === 'string') { + url = { + load: url, + original: url + }; + } + + // we need to specify JSON for blob URLs + var options = { + retry: this.retryRequests + }; + + if (url.load.startsWith('blob:')) { + options.responseType = pc.Http.ResponseType.JSON; + } + + pc.http.get(url.load, options, function (err, response) { + if (err) { + callback(pc.string.format("Error loading animation clip resource: {0} [{1}]", url.original, err)); + } else { + callback(null, response); + } + }); + }, + + open: function (url, data) { + var name = data.name; + var duration = data.duration; + var inputs = data.inputs.map(function(input) { + return new pc.AnimData(1, input); + }); + var outputs = data.outputs.map(function(output) { + return new pc.AnimData(1, output); + }); + var curves = data.curves.map(function(curve) { + return new pc.AnimCurve( + [ curve.path ], + curve.inputIndex, + curve.outputIndex, + curve.interpolation + ); + }); + return new pc.AnimTrack( + name, + duration, + inputs, + outputs, + curves + ); + }, + }); + + return { + AnimationClipHandler: AnimationClipHandler + }; +}()); diff --git a/src/resources/animation-state-graph.js b/src/resources/animation-state-graph.js new file mode 100644 index 00000000000..81eab381ea8 --- /dev/null +++ b/src/resources/animation-state-graph.js @@ -0,0 +1,49 @@ +Object.assign(pc, function () { + 'use strict'; + + /** + * @class + * @name pc.AnimationStateGraphHandler + * @implements {pc.ResourceHandler} + * @classdesc Resource handler used for loading {@link pc.AnimationStateGraph} resources. + */ + var AnimationStateGraphHandler = function () { + this.retryRequests = false; + }; + + Object.assign(AnimationStateGraphHandler.prototype, { + load: function (url, callback) { + if (typeof url === 'string') { + url = { + load: url, + original: url + }; + } + + // we need to specify JSON for blob URLs + var options = { + retry: this.retryRequests + }; + + if (url.load.startsWith('blob:')) { + options.responseType = pc.Http.ResponseType.JSON; + } + + pc.http.get(url.load, options, function (err, response) { + if (err) { + callback(pc.string.format("Error loading animation state graph resource: {0} [{1}]", url.original, err)); + } else { + callback(null, response); + } + }); + }, + + open: function (url, data) { + return data; + }, + }); + + return { + AnimationStateGraphHandler: AnimationStateGraphHandler + }; +}()); From 8791178adc47518708ded435edff16ccc0578fdb Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Tue, 21 Apr 2020 09:37:13 +0100 Subject: [PATCH 002/144] Unified the glb and anim clip paths in the binder --- src/anim/anim.js | 36 ++-- src/framework/components/anim/binder.js | 184 +++++------------- src/framework/components/anim/component.js | 8 - src/framework/components/anim/controller.js | 18 +- .../components/anim/property-locator.js | 21 +- src/resources/parser/glb-parser.js | 9 +- 6 files changed, 97 insertions(+), 179 deletions(-) diff --git a/src/anim/anim.js b/src/anim/anim.js index c892820739d..2a81d8bd36d 100644 --- a/src/anim/anim.js +++ b/src/anim/anim.js @@ -678,17 +678,16 @@ Object.assign(pc, function () { type: 'vector' } }; + + this.propertyLocator = new pc.AnimPropertyLocator(); }; Object.assign(DefaultAnimBinder.prototype, { resolve: function (path) { - var parts = this._getParts(path); - if (!parts) { - return null; - } + var pathSections = this.propertyLocator.decode(path); - var node = this.nodes[parts[0]]; - var prop = this.schema[parts[1]]; + var node = this.nodes[pathSections[0][0]]; + var prop = this.schema[pathSections[2][0]]; if (node.count === 0) { this.activeNodes.push(node.node); @@ -699,21 +698,18 @@ Object.assign(pc, function () { }, unresolve: function (path) { - // get the path parts. we expect parts to have structure nodeName.[translation|rotation|scale] - var parts = this._getParts(path); - if (parts) { - var node = this.nodes[parts[0]]; - - node.count--; - if (node.count === 0) { - var activeNodes = this.activeNodes; - var i = activeNodes.indexOf(node.node); // :( - var len = activeNodes.length; - if (i < len - 1) { - activeNodes[i] = activeNodes[len - 1]; - } - activeNodes.pop(); + var pathSections = this.propertyLocator.decode(path); + var node = this.nodes[pathSections[0][0]]; + + node.count--; + if (node.count === 0) { + var activeNodes = this.activeNodes; + var i = activeNodes.indexOf(node.node); // :( + var len = activeNodes.length; + if (i < len - 1) { + activeNodes[i] = activeNodes[len - 1]; } + activeNodes.pop(); } }, diff --git a/src/framework/components/anim/binder.js b/src/framework/components/anim/binder.js index bf60ac3a6d9..3fa3eed2409 100644 --- a/src/framework/components/anim/binder.js +++ b/src/framework/components/anim/binder.js @@ -2,6 +2,7 @@ Object.assign(pc, function () { var AnimComponentBinder = function (animComponent, graph) { this.animComponent = animComponent; + this.propertyLocator = new pc.AnimPropertyLocator(); if (graph) { var nodes = { }; @@ -20,35 +21,12 @@ Object.assign(pc, function () { this.nodes = nodes; // map of node name -> { node, count } this.activeNodes = []; // list of active nodes - this.schema = { - 'translation': { - components: 3, - target: 'localPosition', - type: 'vector' - }, - 'rotation': { - components: 4, - target: 'localRotation', - type: 'quaternion' - }, - 'scale': { - components: 3, - target: 'localScale', - type: 'vector' - } - }; } }; Object.assign(AnimComponentBinder.prototype, { resolve: function(path) { - if (path.split('/').length === 3) { - return this._resolveGeneralPath(path); - } - return this._resolveGraphPath(path); - }, - _resolveGeneralPath: function (path) { - var pathSections = new pc.PropertyLocator().decode(path); + var pathSections = this.propertyLocator.decode(path); var entityHeirarchy = pathSections[0]; var component = pathSections[1]; @@ -59,51 +37,41 @@ Object.assign(pc, function () { if (!entity) return null; - var propertyComponent = component === 'entity' ? entity : entity.findComponent(component); - - if (!propertyComponent) { - return null; + var propertyComponent; + + switch(component) { + case 'entity': + propertyComponent = entity; + break; + case 'graph': + propertyComponent = this.nodes[entityHeirarchy[0]].node; + break; + default: + entity.findComponent(component); + if (!propertyComponent) + return null; } return this._createAnimTargetForProperty(propertyComponent, propertyHeirarchy); }, - - _resolveGraphPath: function (path) { - var parts = this._getParts(path); - if (!parts) { - return null; - } - - var node = this.nodes[parts[0]]; - var prop = this.schema[parts[1]]; - - if (node.count === 0) { - this.activeNodes.push(node.node); - } - node.count++; - - return new pc.AnimTarget(this._createSetter(node.node[prop.target]), prop.type, prop.components); - }, unresolve: function (path) { - if (path.split('/').length === 3) { + var pathSections = this.propertyLocator.decode(path); + if (pathSections[1] !== 'graph') return; - } + // get the path parts. we expect parts to have structure nodeName.[translation|rotation|scale] - var parts = this._getParts(path); - if (parts) { - var node = this.nodes[parts[0]]; + var node = pathSections[0][0]; - node.count--; - if (node.count === 0) { - var activeNodes = this.activeNodes; - var i = activeNodes.indexOf(node.node); // :( - var len = activeNodes.length; - if (i < len - 1) { - activeNodes[i] = activeNodes[len - 1]; - } - activeNodes.pop(); + node.count--; + if (node.count === 0) { + var activeNodes = this.activeNodes; + var i = activeNodes.indexOf(node.node); // :( + var len = activeNodes.length; + if (i < len - 1) { + activeNodes[i] = activeNodes[len - 1]; } + activeNodes.pop(); } }, @@ -117,59 +85,6 @@ Object.assign(pc, function () { } }, - joinPath: function (pathSegments) { - var escape = function (string) { - return string.replace(/\\/g, '\\\\').replace(/\./g, '\\.'); - }; - return pathSegments.map(escape).join('.'); - }, - - // split a path string into its segments and resolve character escaping - splitPath: function (path) { - var result = []; - var curr = ""; - var i = 0; - while (i < path.length) { - var c = path[i++]; - - if (c === '\\' && i < path.length) { - c = path[i++]; - if (c === '\\' || c === '.') { - curr += c; - } else { - curr += '\\' + c; - } - } else if (c === '.') { - result.push(curr); - curr = ''; - } else { - curr += c; - } - } - if (curr.length > 0) { - result.push(curr); - } - return result; - }, - - // get the path parts. we expect parts to have structure nodeName.[translation|rotation|scale] - _getParts: function (path) { - var parts = this.splitPath(path); - if (parts.length !== 2 || - !this.nodes.hasOwnProperty(parts[0]) || - !this.schema.hasOwnProperty(parts[1])) { - return null; - } - return parts; - }, - - // create a setter function (works for pc.Vec* and pc.Quaternion) which have a 'set' function. - _createSetter: function (target) { - return function (value) { - target.set.apply(target, value); - }; - }, - _getEntityFromHeirarchy: function(entityHeirarchy) { if (!this.animComponent.entity.name === entityHeirarchy[0]) return null; @@ -310,38 +225,35 @@ Object.assign(pc, function () { animDataComponents = 4; break; case 'Quat': - setter = this._quatSetter(propertyComponent, propertyHeirarchy); + setter = this._vecSetter(propertyComponent, propertyHeirarchy); animDataType = 'quaternion'; animDataComponents = 4; break; default: - break; + return null; } } - if (setter) { - var entityProperty = this._getEntityProperty(propertyHeirarchy); - if (entityProperty) { - var entityPropertySetter = function(values) { - // set new values on the property as before - setter(values); - - // create the function name of the properties setter - var entityPropertySetterFunctionName = pc.string.format( - 'set{0}{1}', - entityProperty.substring(0,1).toUpperCase(), - entityProperty.substring(1) - ); - // call the setter function for entities updated property using the newly set property value - propertyComponent[entityPropertySetterFunctionName](this._getProperty(propertyComponent, [entityProperty])); - }; - return new pc.AnimTarget(entityPropertySetter.bind(this), animDataType, animDataComponents); - - } else { - return new pc.AnimTarget(setter, animDataType, animDataComponents); - } + // for entity properties we cannot just set their values, we must also call the values setter function. + var entityProperty = this._getEntityProperty(propertyHeirarchy); + if (entityProperty) { + var entityPropertySetter = function(values) { + // first set new values on the property as before + setter(values); + + // create the function name of the entity properties setter + var entityPropertySetterFunctionName = pc.string.format( + 'set{0}{1}', + entityProperty.substring(0,1).toUpperCase(), + entityProperty.substring(1) + ); + // call the setter function for entities updated property using the newly set property value + propertyComponent[entityPropertySetterFunctionName](this._getProperty(propertyComponent, [entityProperty])); + }; + return new pc.AnimTarget(entityPropertySetter.bind(this), animDataType, animDataComponents); + } else { + return new pc.AnimTarget(setter, animDataType, animDataComponents); } - return null; } }); diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 111b76ceeb7..d9ce06468e6 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -10,14 +10,10 @@ Object.assign(pc, function () { * @param {pc.AnimComponentSystem} system - The {@link pc.ComponentSystem} that created this Component. * @param {pc.Entity} entity - The Entity that this Component is attached to. * @property {number} speed Speed multiplier for animation play back speed. 1.0 is playback at normal speed, 0.0 pauses the animation. - * @property {boolean} loop If true the animation will restart from the beginning when it reaches the end. * @property {boolean} activate If true the first animation will begin playing when the scene is loaded. */ var AnimComponent = function (system, entity) { pc.Component.call(this, system, entity); - - // Handle changes to the 'loop' value - this.on('set_loop', this.onSetLoop, this); }; AnimComponent.prototype = Object.create(pc.Component.prototype); AnimComponent.prototype.constructor = AnimComponent; @@ -78,10 +74,6 @@ Object.assign(pc, function () { this.data.animController.play(name); }, - - onSetLoop: function (name, oldValue, newValue) { - }, - }); return { diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index c4b8c6da93c..ed960ac3375 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -141,12 +141,18 @@ Object.assign(pc, function () { this.animEvaluator.addClip(clip); clip.play(); return; - } - } else { - transition = this._findTransition(); - if (!transition) - return; - } + } + } else { + transition = this._findTransition(); + if (!transition) + return; + } + + switch(transition.to) { + case 'Start': + case 'End': + this._transitionState(transition); + } if (transition.to === 'End') { this._setActiveState('Start'); diff --git a/src/framework/components/anim/property-locator.js b/src/framework/components/anim/property-locator.js index 819e567e4bc..2fbcbe15af9 100644 --- a/src/framework/components/anim/property-locator.js +++ b/src/framework/components/anim/property-locator.js @@ -1,7 +1,8 @@ Object.assign(pc, function () { - var PropertyLocator = function () { + var AnimPropertyLocator = function () { }; - Object.assign(PropertyLocator.prototype, { + Object.assign(AnimPropertyLocator.prototype, { + // split a path string into its segments and resolve character escaping _splitPath: function(path) { var result = []; var curr = ""; @@ -28,18 +29,22 @@ Object.assign(pc, function () { } return result; }, + // join a list of path segments into a path string + _joinPath: function (pathSegments) { + var escape = function (string) { + return string.replace(/\\/g, '\\\\').replace(/\./g, '\\.'); + }; + return pathSegments.map(escape).join('.'); + }, encode: function(decodedLocator) { var entityHeirarchy = decodedLocator[0]; - for (var i = 0; i < entityHeirarchy.length; i++) { - entityHeirarchy[i] = entityHeirarchy[i].split('.').join('\\.'); - } var component = decodedLocator[1]; var propertyHeirarchy = decodedLocator[2]; return pc.string.format( '{0}/{1}/{2}', - entityHeirarchy.join('.'), + this._joinPath(entityHeirarchy), component, - propertyHeirarchy.join('.') + this._joinPath(propertyHeirarchy) ); }, decode: function(encodedLocator) { @@ -55,6 +60,6 @@ Object.assign(pc, function () { } }); return { - PropertyLocator: PropertyLocator + AnimPropertyLocator: AnimPropertyLocator } }()); \ No newline at end of file diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 5acdecdf1e0..b61d73339bb 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -827,12 +827,19 @@ Object.assign(pc, function () { var quatArrays = []; + var propertyLocator = new pc.AnimPropertyLocator(); + var transformSchema = { + 'translation': 'localPosition', + 'rotation': 'localRotation', + 'scale': 'localScale', + }; + // convert anim channels for (i = 0; i < animationData.channels.length; ++i) { var channel = animationData.channels[i]; var target = channel.target; var curve = curves[channel.sampler]; - curve._paths.push(pc.AnimBinder.joinPath([nodes[target.node].name, target.path])); + curve._paths.push(propertyLocator.encode([[nodes[target.node].name], 'graph', [transformSchema[target.path]]])); // if this target is a set of quaternion keys, make note of its index so we can perform // quaternion-specific processing on it. From 1331455a603a513ac6b2e8b8530b95a28954b760 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Tue, 21 Apr 2020 11:48:49 +0100 Subject: [PATCH 003/144] cleanup anim controller state transition code --- src/framework/components/anim/controller.js | 113 +++++++++----------- 1 file changed, 52 insertions(+), 61 deletions(-) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index ed960ac3375..7bb90a7a9e6 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -79,12 +79,6 @@ Object.assign(pc, function () { _setPreviousState: function(stateName) { return this.previousStateName = stateName; }, - play: function(stateName) { - if (stateName) { - this._transitionState(stateName); - } - this.playing = true; - }, _getActiveStateProgress: function() { if (this.activeStateName === 'Start' || this.activeStateName === 'End') return 1.0; @@ -116,72 +110,69 @@ Object.assign(pc, function () { } }, - _transitionState: function(newStateName) { - var transition; - if (newStateName) { + _updateStateFromTransition: function(transition) { + this._setPreviousState(this.activeStateName); + this._setActiveState(transition.to); - if (newStateName === this.activeStateName) { - return; - } - - if (!this._getState(newStateName)) { - return; - } + if (transition.time > 0) { + this.isTransitioning = true; + this.totalTransitionTime = transition.time; + this.currTransitionTime = 0; + } - transition = this._findTransition(this.activeStateName, newStateName); - if (!transition) { - this._setPreviousState(null); - this._setActiveState(newStateName); - this.animEvaluator.removeClips(); - var activeState = this._getActiveState(); - clip = new pc.AnimClip(activeState.animTrack, 0, activeState.speed, true, true); - clip.name = this.activeStateName; + var clip = this.animEvaluator.findClip(this.activeStateName); + if (!clip) { + var activeState = this._getActiveState(); + clip = new pc.AnimClip(activeState.animTrack, 0, activeState.speed, true, true); + clip.name = this.activeStateName; + if (transition.time > 0) { + clip.blendWeight = 0.0; + } else { clip.blendWeight = 1.0; - clip.reset(); - this.animEvaluator.addClip(clip); - clip.play(); - return; } - } else { - transition = this._findTransition(); - if (!transition) - return; + clip.reset(); + this.animEvaluator.addClip(clip); } + clip.play(); + }, - switch(transition.to) { - case 'Start': - case 'End': - this._transitionState(transition); + _transitionToState: function(newStateName) { + if (newStateName === this.activeStateName) { + return; } - if (transition.to === 'End') { + if (!this._getState(newStateName)) { + return; + } + + var transition = this._findTransition(this.activeStateName, newStateName); + if (!transition) { + this.animEvaluator.removeClips(); + transition = new AnimTransition(this.activeStateName, newStateName, 0, 0); + } + this._updateStateFromTransition(transition); + }, + + _transitionToNextState: function() { + var transition = this._findTransition(); + if (!transition) { + return; + } + if (transition.to === 'End') + { this._setActiveState('Start'); - this._transitionState(); - } else { - this._setPreviousState(this.activeStateName); - this._setActiveState(transition.to); - if (transition.time > 0) { - this.isTransitioning = true; - this.totalTransitionTime = transition.time; - this.currTransitionTime = 0; - } - var clip = this.animEvaluator.findClip(this.activeStateName); - if (!clip) { - var activeState = this._getActiveState(); - clip = new pc.AnimClip(activeState.animTrack, 0, activeState.speed, true, true); - clip.name = this.activeStateName; - if (transition.time > 0) { - clip.blendWeight = 0.0; - } else { - clip.blendWeight = 1.0; - } - clip.reset(); - this.animEvaluator.addClip(clip); - } - clip.play(); + transition = this._findTransition(); } + this._updateStateFromTransition(transition); }, + play: function(stateName) { + if (stateName) { + this._transitionToState(stateName); + } + this.playing = true; + }, + update: function(dt) { if (this.playing) { this.animEvaluator.update(dt); @@ -189,7 +180,7 @@ Object.assign(pc, function () { var progress = this._getActiveStateProgress(); if (progress >= 1.0) { - this._transitionState(); + this._transitionToNextState(); } if (this.isTransitioning) { From 5705a4b264257d0645cb49340c98c891b97f4620 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Tue, 21 Apr 2020 11:56:04 +0100 Subject: [PATCH 004/144] anim controller cleanup --- src/framework/components/anim/controller.js | 50 ++++++++++++--------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 7bb90a7a9e6..3f8d4298c4e 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -38,27 +38,6 @@ Object.assign(pc, function () { }; Object.assign(AnimController.prototype, { - linkAnimTrackToState: function(stateName, animTrack) { - for (var i = 0; i < this.states.length; i++) { - if (this.states[i].name === stateName) { - this.states[i].animTrack = animTrack; - if (this.isPlayable() && this.activate) { - this.play(); - } - return; - } - } - console.error('Linking animation asset to animation state that does not exist'); - }, - isPlayable: function() { - var playable = true; - for (var i = 0; i < this.states.length; i++) { - if (!this.states[i].isPlayable()) { - playable = false; - } - } - return playable; - }, _getState: function(stateName) { for (var i = 0; i < this.states.length; i++) { if (this.states[i].name === stateName) { @@ -67,18 +46,23 @@ Object.assign(pc, function () { } return null; }, + _getActiveState: function() { return this._getState(this.activeStateName); }, + _setActiveState: function(stateName) { return this.activeStateName = stateName; }, + _getPreviousState: function() { return this._getState(this.previousStateName); }, + _setPreviousState: function(stateName) { return this.previousStateName = stateName; }, + _getActiveStateProgress: function() { if (this.activeStateName === 'Start' || this.activeStateName === 'End') return 1.0; @@ -90,6 +74,7 @@ Object.assign(pc, function () { } return null; }, + _findTransition: function(from, to) { var transitions = this.transitions.filter((function(transition) { if (to && from) { @@ -166,6 +151,29 @@ Object.assign(pc, function () { this._updateStateFromTransition(transition); }, + linkAnimTrackToState: function(stateName, animTrack) { + for (var i = 0; i < this.states.length; i++) { + if (this.states[i].name === stateName) { + this.states[i].animTrack = animTrack; + if (this.isPlayable() && this.activate) { + this.play(); + } + return; + } + } + console.error('Linking animation asset to animation state that does not exist'); + }, + + isPlayable: function() { + var playable = true; + for (var i = 0; i < this.states.length; i++) { + if (!this.states[i].isPlayable()) { + playable = false; + } + } + return playable; + }, + play: function(stateName) { if (stateName) { this._transitionToState(stateName); From 21ae4b536fe167ba73612fc04facc50de9206701 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Tue, 21 Apr 2020 16:47:22 +0100 Subject: [PATCH 005/144] support multiple animations in a single AnimState --- src/framework/components/anim/component.js | 2 +- src/framework/components/anim/controller.js | 94 +++++++++++++++------ 2 files changed, 70 insertions(+), 26 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index d9ce06468e6..c60a1e2d40c 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -52,7 +52,7 @@ Object.assign(pc, function () { if(!animTrack) console.error('linkAnimAssetToState: No animation found for given assetName'); - this.data.animController.linkAnimTrackToState(stateName, animTrack); + this.data.animController.linkAnimationToState(stateName, animTrack); }, /** diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 3f8d4298c4e..4b454ae1b29 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -2,13 +2,20 @@ Object.assign(pc, function () { var AnimState = function (name, speed) { this.name = name; - this.animTrack = null; + this.animations = []; this.speed = speed || 1.0; }; Object.assign(AnimState.prototype, { isPlayable: function() { - return (this.animTrack || this.name === 'Start' || this.name === 'End'); + return (this.animations.length > 0 || this.name === 'Start' || this.name === 'End'); + }, + getTotalWeight: function() { + var sum = 0; + for (var i = 0; i < this.animations.length; i++) { + sum = sum + this.animations[i].weight; + } + return sum; } }); @@ -47,6 +54,14 @@ Object.assign(pc, function () { return null; }, + _setState: function(stateName, state) { + for (var i = 0; i < this.states.length; i++) { + if (this.states[i].name === stateName) { + this.states[i] = state; + } + } + }, + _getActiveState: function() { return this._getState(this.activeStateName); }, @@ -105,20 +120,22 @@ Object.assign(pc, function () { this.currTransitionTime = 0; } - var clip = this.animEvaluator.findClip(this.activeStateName); - if (!clip) { - var activeState = this._getActiveState(); - clip = new pc.AnimClip(activeState.animTrack, 0, activeState.speed, true, true); - clip.name = this.activeStateName; + var activeState = this._getActiveState(); + for (var i = 0; i < activeState.animations.length; i++) { + var clip = this.animEvaluator.findClip(activeState.animations[i].name); + if (!clip) { + clip = new pc.AnimClip(activeState.animations[i].animTrack, 0, activeState.speed, true, true); + clip.name = activeState.animations[i].name; + this.animEvaluator.addClip(clip); + } if (transition.time > 0) { - clip.blendWeight = 0.0; + clip.blendWeight = 0.0 / activeState.getTotalWeight(); } else { - clip.blendWeight = 1.0; + clip.blendWeight = 1.0 / activeState.getTotalWeight(); } clip.reset(); - this.animEvaluator.addClip(clip); + clip.play(); } - clip.play(); }, _transitionToState: function(newStateName) { @@ -151,17 +168,23 @@ Object.assign(pc, function () { this._updateStateFromTransition(transition); }, - linkAnimTrackToState: function(stateName, animTrack) { - for (var i = 0; i < this.states.length; i++) { - if (this.states[i].name === stateName) { - this.states[i].animTrack = animTrack; - if (this.isPlayable() && this.activate) { - this.play(); - } - return; - } + linkAnimationToState: function(stateName, animTrack) { + var state = this._getState(stateName); + if (!state) { + console.error('Linking animation asset to animation state that does not exist'); + return; + } + + var animation = { + name: animTrack.name, + animTrack: animTrack, + weight: 1.0 + }; + state.animations.push(animation); + + if (!this.playing && this.activate && this.isPlayable()) { + this.play(); } - console.error('Linking animation asset to animation state that does not exist'); }, isPlayable: function() { @@ -194,12 +217,33 @@ Object.assign(pc, function () { if (this.isTransitioning) { if (this.currTransitionTime > this.totalTransitionTime) { this.isTransitioning = false; - this.animEvaluator.findClip(this.previousStateName).pause(); - this.animEvaluator.findClip(this.activeStateName).blendWeight = 1.0; + + var previousState = this._getPreviousState(); + for (var i = 0; i < previousState.animations.length; i++) { + var animation = previousState.animations[i]; + this.animEvaluator.findClip(animation.name).pause(); + this.animEvaluator.findClip(animation.name).blendWeight = 0; + } + + var activeState = this._getActiveState(); + for (var i = 0; i < activeState.animations.length; i++) { + var animation = activeState.animations[i]; + this.animEvaluator.findClip(animation.name).blendWeight = animation.weight / activeState.getTotalWeight(); + } } else { var interpolatedTime = this.currTransitionTime / this.totalTransitionTime; - this.animEvaluator.findClip(this.previousStateName).blendWeight = 1.0 - interpolatedTime; - this.animEvaluator.findClip(this.activeStateName).blendWeight = interpolatedTime; + + var previousState = this._getPreviousState(); + for (var i = 0; i < previousState.animations.length; i++) { + var animation = previousState.animations[i]; + this.animEvaluator.findClip(animation.name).blendWeight = (1.0 - interpolatedTime) * animation.weight / previousState.getTotalWeight(); + } + var activeState = this._getActiveState(); + for (var i = 0; i < activeState.animations.length; i++) { + var animation = activeState.animations[i]; + this.animEvaluator.findClip(animation.name).blendWeight = interpolatedTime * animation.weight / activeState.getTotalWeight(); + } + } this.currTransitionTime = this.currTransitionTime + dt; } From fcaa9c05fde3ae92f2138fb136120755c7d28f45 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Wed, 22 Apr 2020 19:05:46 +0100 Subject: [PATCH 006/144] spelling fix --- src/framework/components/anim/binder.js | 80 +++++++++---------- .../components/anim/property-locator.js | 18 ++--- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/framework/components/anim/binder.js b/src/framework/components/anim/binder.js index 3fa3eed2409..b2cefe36b82 100644 --- a/src/framework/components/anim/binder.js +++ b/src/framework/components/anim/binder.js @@ -28,11 +28,11 @@ Object.assign(pc, function () { resolve: function(path) { var pathSections = this.propertyLocator.decode(path); - var entityHeirarchy = pathSections[0]; + var entityHierarchy = pathSections[0]; var component = pathSections[1]; - var propertyHeirarchy = pathSections[2]; + var propertyHierarchy = pathSections[2]; - var entity = this._getEntityFromHeirarchy(entityHeirarchy); + var entity = this._getEntityFromHierarchy(entityHierarchy); if (!entity) return null; @@ -44,7 +44,7 @@ Object.assign(pc, function () { propertyComponent = entity; break; case 'graph': - propertyComponent = this.nodes[entityHeirarchy[0]].node; + propertyComponent = this.nodes[entityHierarchy[0]].node; break; default: entity.findComponent(component); @@ -52,7 +52,7 @@ Object.assign(pc, function () { return null; } - return this._createAnimTargetForProperty(propertyComponent, propertyHeirarchy); + return this._createAnimTargetForProperty(propertyComponent, propertyHierarchy); }, unresolve: function (path) { @@ -85,16 +85,16 @@ Object.assign(pc, function () { } }, - _getEntityFromHeirarchy: function(entityHeirarchy) { - if (!this.animComponent.entity.name === entityHeirarchy[0]) + _getEntityFromHierarchy: function(entityHierarchy) { + if (!this.animComponent.entity.name === entityHierarchy[0]) return null; var currEntity = this.animComponent.entity; - for (var i = 0; i < entityHeirarchy.length - 1; i++) { + for (var i = 0; i < entityHierarchy.length - 1; i++) { var entityChildren = currEntity.getChildren(); var child; for (var j = 0; j < entityChildren.length; j++) { - if (entityChildren[j].name === entityHeirarchy[i+1]) + if (entityChildren[j].name === entityHierarchy[i+1]) child = entityChildren[j]; } if (child) @@ -105,53 +105,53 @@ Object.assign(pc, function () { return currEntity; }, - _floatSetter: function(propertyComponent, propertyHeirarchy) { + _floatSetter: function(propertyComponent, propertyHierarchy) { var setter = function(values) { - this._setProperty(propertyComponent, propertyHeirarchy, values[0]); + this._setProperty(propertyComponent, propertyHierarchy, values[0]); }; return setter.bind(this); }, - _booleanSetter: function(propertyComponent, propertyHeirarchy) { + _booleanSetter: function(propertyComponent, propertyHierarchy) { var setter = function(values) { - this._setProperty(propertyComponent, propertyHeirarchy, !!values[0]); + this._setProperty(propertyComponent, propertyHierarchy, !!values[0]); }; return setter.bind(this); }, - _colorSetter: function(propertyComponent, propertyHeirarchy) { + _colorSetter: function(propertyComponent, propertyHierarchy) { var colorKeys = ['r', 'g', 'b', 'a']; var setter = function(values) { for (var i = 0; i < values.length; i++) { - this._setProperty(propertyComponent, propertyHeirarchy.concat(colorKeys[i]), values[i]); + this._setProperty(propertyComponent, propertyHierarchy.concat(colorKeys[i]), values[i]); } }; return setter.bind(this); }, - _vecSetter: function(propertyComponent, propertyHeirarchy) { + _vecSetter: function(propertyComponent, propertyHierarchy) { var vectorKeys = ['x', 'y', 'z', 'w']; var setter = function(values) { for (var i = 0; i < values.length; i++) { - this._setProperty(propertyComponent, propertyHeirarchy.concat(vectorKeys[i]), values[i]); + this._setProperty(propertyComponent, propertyHierarchy.concat(vectorKeys[i]), values[i]); } }; return setter.bind(this); }, - _getProperty: function(propertyComponent, propertyHeirarchy) { - if (propertyHeirarchy.length === 1) { - return propertyComponent[propertyHeirarchy[0]]; + _getProperty: function(propertyComponent, propertyHierarchy) { + if (propertyHierarchy.length === 1) { + return propertyComponent[propertyHierarchy[0]]; } else { - var propertyObject = propertyComponent[propertyHeirarchy[0]]; - return propertyObject[propertyHeirarchy[1]]; + var propertyObject = propertyComponent[propertyHierarchy[0]]; + return propertyObject[propertyHierarchy[1]]; } }, - _setProperty: function(propertyComponent, propertyHeirarchy, value) { - if (propertyHeirarchy.length === 1) { - propertyComponent[propertyHeirarchy[0]] = value; + _setProperty: function(propertyComponent, propertyHierarchy, value) { + if (propertyHierarchy.length === 1) { + propertyComponent[propertyHierarchy[0]] = value; } else { - var propertyObject = propertyComponent[propertyHeirarchy[0]]; - propertyObject[propertyHeirarchy[1]] = value; - propertyComponent[propertyHeirarchy[0]] = propertyObject; + var propertyObject = propertyComponent[propertyHierarchy[0]]; + propertyObject[propertyHierarchy[1]] = value; + propertyComponent[propertyHierarchy[0]] = propertyObject; } }, @@ -162,7 +162,7 @@ Object.assign(pc, function () { return property.constructor.name; }, - _getEntityProperty: function(propertyHeirarchy) { + _getEntityProperty: function(propertyHierarchy) { var entityProperties = [ 'localScale', 'localPosition', @@ -174,16 +174,16 @@ Object.assign(pc, function () { ]; var entityProperty; for (var i = 0; i < entityProperties.length; i++) { - if (propertyHeirarchy.indexOf(entityProperties[i]) !== -1) { + if (propertyHierarchy.indexOf(entityProperties[i]) !== -1) { entityProperty = entityProperties[i]; } } return entityProperty; }, - _createAnimTargetForProperty: function(propertyComponent, propertyHeirarchy) { + _createAnimTargetForProperty: function(propertyComponent, propertyHierarchy) { - var property = this._getProperty(propertyComponent, propertyHeirarchy); + var property = this._getProperty(propertyComponent, propertyHierarchy); if (typeof property === 'undefined') return null; @@ -193,39 +193,39 @@ Object.assign(pc, function () { var animDataComponents; if (typeof property === 'number') { - setter = this._floatSetter(propertyComponent, propertyHeirarchy); + setter = this._floatSetter(propertyComponent, propertyHierarchy); animDataType = 'vector'; animDataComponents = 1; } else if (typeof property === 'boolean') { - setter = this._booleanSetter(propertyComponent, propertyHeirarchy); + setter = this._booleanSetter(propertyComponent, propertyHierarchy); animDataType = 'vector'; animDataComponents = 1; } else if (typeof property === 'object') { switch (this._getObjectPropertyType(property)) { case 'Vec2': - setter = this._vecSetter(propertyComponent, propertyHeirarchy); + setter = this._vecSetter(propertyComponent, propertyHierarchy); animDataType = 'vector'; animDataComponents = 2; break; case 'Vec3': - setter = this._vecSetter(propertyComponent, propertyHeirarchy); + setter = this._vecSetter(propertyComponent, propertyHierarchy); animDataType = 'vector'; animDataComponents = 3; break; case 'Vec4': - setter = this._vecSetter(propertyComponent, propertyHeirarchy); + setter = this._vecSetter(propertyComponent, propertyHierarchy); animDataType = 'vector'; animDataComponents = 4; break; case 'Color': - setter = this._colorSetter(propertyComponent, propertyHeirarchy); + setter = this._colorSetter(propertyComponent, propertyHierarchy); animDataType = 'vector'; animDataComponents = 4; break; case 'Quat': - setter = this._vecSetter(propertyComponent, propertyHeirarchy); + setter = this._vecSetter(propertyComponent, propertyHierarchy); animDataType = 'quaternion'; animDataComponents = 4; break; @@ -235,7 +235,7 @@ Object.assign(pc, function () { } // for entity properties we cannot just set their values, we must also call the values setter function. - var entityProperty = this._getEntityProperty(propertyHeirarchy); + var entityProperty = this._getEntityProperty(propertyHierarchy); if (entityProperty) { var entityPropertySetter = function(values) { // first set new values on the property as before diff --git a/src/framework/components/anim/property-locator.js b/src/framework/components/anim/property-locator.js index 2fbcbe15af9..4750be4df36 100644 --- a/src/framework/components/anim/property-locator.js +++ b/src/framework/components/anim/property-locator.js @@ -37,26 +37,26 @@ Object.assign(pc, function () { return pathSegments.map(escape).join('.'); }, encode: function(decodedLocator) { - var entityHeirarchy = decodedLocator[0]; + var entityHierarchy = decodedLocator[0]; var component = decodedLocator[1]; - var propertyHeirarchy = decodedLocator[2]; + var propertyHierarchy = decodedLocator[2]; return pc.string.format( '{0}/{1}/{2}', - this._joinPath(entityHeirarchy), + this._joinPath(entityHierarchy), component, - this._joinPath(propertyHeirarchy) + this._joinPath(propertyHierarchy) ); }, decode: function(encodedLocator) { var locatorSections = encodedLocator.split('/'); - var entityHeirarchy = this._splitPath(locatorSections[0]); - for (var i = 0; i < entityHeirarchy.length; i++) { - entityHeirarchy[i] = entityHeirarchy[i].split('\\.').join('.'); + var entityHierarchy = this._splitPath(locatorSections[0]); + for (var i = 0; i < entityHierarchy.length; i++) { + entityHierarchy[i] = entityHierarchy[i].split('\\.').join('.'); } var component = locatorSections[1]; - var propertyHeirarchy = locatorSections[2].split('.'); + var propertyHierarchy = locatorSections[2].split('.'); - return [entityHeirarchy, component, propertyHeirarchy]; + return [entityHierarchy, component, propertyHierarchy]; } }); return { From b97f8ad364ea6c36b1c040b9c70d34c25752d942 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Fri, 24 Apr 2020 13:04:51 +0100 Subject: [PATCH 007/144] make the anim controller parameter driven and introduce transition exit times --- src/framework/components/anim/component.js | 56 +++++++ src/framework/components/anim/controller.js | 175 +++++++++++++++++--- 2 files changed, 208 insertions(+), 23 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index c60a1e2d40c..5a66aa84d97 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -39,8 +39,10 @@ Object.assign(pc, function () { animEvaluator, asset.states, asset.transitions, + asset.parameters, this.data.activate ); + this.animController = data.animController; }, linkAnimAssetToState: function(stateName, asset) { @@ -55,6 +57,60 @@ Object.assign(pc, function () { this.data.animController.linkAnimationToState(stateName, animTrack); }, + getFloat: function(name) { + if (this.data.animController) { + return this.data.animController.getFloat(name); + } + }, + + setFloat: function(name, value) { + if (this.data.animController) { + this.data.animController.setFloat(name, value); + } + }, + + getInteger: function(name) { + if (this.data.animController) { + return this.data.animController.getInteger(name); + } + }, + + setInteger: function(name, value) { + if (this.data.animController) { + this.data.animController.setInteger(name, value); + } + }, + + getBoolean: function(name) { + if (this.data.animController) { + return this.data.animController.getBoolean(name); + } + }, + + setBoolean: function(name, value) { + if (this.data.animController) { + this.data.animController.setBoolean(name, value); + } + }, + + getTrigger: function(name) { + if (this.data.animController) { + return this.data.animController.getTrigger(name); + } + }, + + setTrigger: function(name) { + if (this.data.animController) { + this.data.animController.setTrigger(name); + } + }, + + resetTrigger: function(name) { + if (this.data.animController) { + this.data.animController.resetTrigger(name); + } + }, + /** * @function * @name pc.AnimComponent#play diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 4b454ae1b29..1bc4136d6fb 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -10,6 +10,9 @@ Object.assign(pc, function () { isPlayable: function() { return (this.animations.length > 0 || this.name === 'Start' || this.name === 'End'); }, + isLooping: function() { + return true; + }, getTotalWeight: function() { var sum = 0; for (var i = 0; i < this.animations.length; i++) { @@ -19,21 +22,61 @@ Object.assign(pc, function () { } }); - var AnimTransition = function (from, to, time, priority) { + var AnimTransition = function (controller, from, to, time, priority, conditions, exitTime) { + this.controller = controller; this.from = from; this.to = to; this.time = time; this.priority = priority; + this.conditions = conditions || []; + this.exitTime = exitTime || null; }; - var AnimController = function (animEvaluator, states, transitions, activate) { + Object.assign(AnimTransition.prototype, { + hasConditionsMet: function() { + var conditionsMet = true; + for (var i = 0; i < this.conditions.length; i++) { + var condition = this.conditions[i]; + var parameter = this.controller.getParameter(condition.parameterName); + switch(condition.predicate) { + case 'GREATER_THAN': + conditionsMet = conditionsMet && parameter.value > condition.value; + break; + case 'LESS_THAN': + conditionsMet = conditionsMet && parameter.value < condition.value; + break; + case 'GREATER_THAN_EQUAL_TO': + conditionsMet = conditionsMet && parameter.value >= condition.value; + break; + case 'LESS_THAN_EQUAL_TO': + conditionsMet = conditionsMet && parameter.value <= condition.value; + break; + case 'EQUAL_TO': + conditionsMet = conditionsMet && parameter.value === condition.value; + break; + case 'NOT_EQUAL_TO': + conditionsMet = conditionsMet && parameter.value !== condition.value; + break; + } + if (!conditionsMet) + return conditionsMet; + } + return conditionsMet; + }, + hasExitTime: function() { + return !!this.exitTime; + } + }); + + var AnimController = function (animEvaluator, states, transitions, parameters, activate) { this.animEvaluator = animEvaluator; this.states = states.map(function(state) { return new AnimState(state.name, state.speed); }); - this.transitions = transitions.map(function(transition) { - return new AnimTransition(transition.from, transition.to, transition.time, transition.priority); - }); + this.transitions = transitions.map((function(transition) { + return new AnimTransition(this, transition.from, transition.to, transition.time, transition.priority, transition.conditions, transition.exitTime); + }).bind(this)); + this.parameters = parameters; this.previousStateName = null; this.activeStateName = 'Start'; this.playing = false; @@ -42,6 +85,9 @@ Object.assign(pc, function () { this.currTransitionTime = 1.0; this.totalTransitionTime = 1.0; this.isTransitioning = false; + + this.timeInState = 0; + this.timeInStateBefore = 0; }; Object.assign(AnimController.prototype, { @@ -78,19 +124,22 @@ Object.assign(pc, function () { return this.previousStateName = stateName; }, - _getActiveStateProgress: function() { + _getActiveStateProgress: function(checkBeforeUpdate) { if (this.activeStateName === 'Start' || this.activeStateName === 'End') return 1.0; else { - var activeClip = this.animEvaluator.findClip(this.activeStateName); + var activeClip = this.animEvaluator.findClip(this._getActiveState().animations[0].name); if (activeClip) { - return activeClip.time / activeClip.track.duration; + return (checkBeforeUpdate ? this.timeInStateBefore : this.timeInState) / activeClip.track.duration; } } return null; }, _findTransition: function(from, to) { + if (this.isTransitioning) { + return false; + } var transitions = this.transitions.filter((function(transition) { if (to && from) { return transition.from === from && transition.to === to; @@ -98,15 +147,30 @@ Object.assign(pc, function () { return transition.from === this.activeStateName; } }).bind(this)); - if (transitions.length === 0) - return null; - else if (transitions.length === 1) - return transitions[0]; - else { - transitions.sort(function(a, b) { - return a.priority < b.priority; - }); + transitions = transitions.filter((function(transition) { + // when an exit time is present, we should only exit if it falls within the current frame delta time + if (transition.hasExitTime()) { + var progressBefore = this._getActiveStateProgress(true); + var progress = this._getActiveStateProgress(); + // when the exit time is smaller than 1 and the state is looping, we should check for an exit each loop + if (transition.exitTime < 1.0 && this._getActiveState().isLooping()) { + progressBefore = progressBefore - Math.floor(progressBefore); + progress = progress - Math.floor(progress); + } + // return false if exit time isn't within the frames delta time + if (!(transition.exitTime > progressBefore && transition.exitTime <= progress)) { + return false; + } + } + return transition.hasConditionsMet(); + }).bind(this)); + transitions.sort(function(a, b) { + return a.priority < b.priority; + }); + if (transitions.length > 0) { return transitions[0]; + } else { + return null; } }, @@ -114,11 +178,21 @@ Object.assign(pc, function () { this._setPreviousState(this.activeStateName); this._setActiveState(transition.to); + var triggers = transition.conditions.filter((function(condition) { + var parameter = this.getParameter(condition.parameterName); + return parameter.type === 'trigger'; + }).bind(this)); + for (var i = 0; i < triggers.length; i++) { + this.resetTrigger(triggers[i].parameterName); + } + if (transition.time > 0) { this.isTransitioning = true; this.totalTransitionTime = transition.time; this.currTransitionTime = 0; } + this.timeInState = 0; + this.timeInStateBefore = 0; var activeState = this._getActiveState(); for (var i = 0; i < activeState.animations.length; i++) { @@ -150,7 +224,7 @@ Object.assign(pc, function () { var transition = this._findTransition(this.activeStateName, newStateName); if (!transition) { this.animEvaluator.removeClips(); - transition = new AnimTransition(this.activeStateName, newStateName, 0, 0); + transition = new AnimTransition(this, this.activeStateName, newStateName, 0, 0); } this._updateStateFromTransition(transition); }, @@ -207,15 +281,15 @@ Object.assign(pc, function () { update: function(dt) { if (this.playing) { this.animEvaluator.update(dt); + this.timeInStateBefore = this.timeInState; + this.timeInState = this.timeInState + dt; - var progress = this._getActiveStateProgress(); - - if (progress >= 1.0) { - this._transitionToNextState(); - } + var transition = this._findTransition(this.activeStateName); + if (transition) + this._updateStateFromTransition(transition); if (this.isTransitioning) { - if (this.currTransitionTime > this.totalTransitionTime) { + if (this.currTransitionTime >= this.totalTransitionTime) { this.isTransitioning = false; var previousState = this._getPreviousState(); @@ -249,6 +323,61 @@ Object.assign(pc, function () { } } }, + + getFloat: function(name) { + return this.parameters[name].value; + }, + + setFloat: function(name, value) { + //TODO typechecking + var float = this.parameters[name]; + if (float && float.type === 'float') + float.value = value; + }, + + getInteger: function(name) { + return this.parameters[name].value; + }, + + setInteger: function(name, value) { + //TODO typechecking + var integer = this.parameters[name]; + if (integer && integer.type === 'integer') + integer.value = value; + }, + + getBoolean: function(name) { + return this.parameters[name].value; + }, + + setBoolean: function(name, value) { + //TODO typechecking + var boolean = this.parameters[name]; + if (boolean && boolean.type === 'boolean') + boolean.value = value; + }, + + getTrigger: function(name) { + return this.parameters[name].value; + }, + + setTrigger: function(name) { + //TODO typechecking + var trigger = this.parameters[name]; + if (trigger && trigger.type === 'trigger') + trigger.value = true; + }, + + resetTrigger: function(name) { + //TODO typechecking + var trigger = this.parameters[name]; + if (trigger && trigger.type === 'trigger') + trigger.value = false; + }, + + getParameter: function(name) { + return this.parameters[name]; + } }); return { From 46cc873030d811c0c6c6bb755200645b96f0d48d Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Fri, 24 Apr 2020 16:50:37 +0100 Subject: [PATCH 008/144] add transition offsets which allow for the entry time of destination state animations to be set --- src/framework/components/anim/component.js | 9 ++- src/framework/components/anim/controller.js | 72 ++++++++++++++------- 2 files changed, 57 insertions(+), 24 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 5a66aa84d97..88b99cfc7fa 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -42,7 +42,6 @@ Object.assign(pc, function () { asset.parameters, this.data.activate ); - this.animController = data.animController; }, linkAnimAssetToState: function(stateName, asset) { @@ -130,6 +129,14 @@ Object.assign(pc, function () { this.data.animController.play(name); }, + + getActiveStateName: function () { + return this.data.animController ? this.data.animController.getActiveStateName() : null; + }, + + getActiveStateProgress: function () { + return this.data.animController ? this.data.animController.getActiveStateProgress() : null; + } }); return { diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 1bc4136d6fb..52aea66d79a 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -10,19 +10,32 @@ Object.assign(pc, function () { isPlayable: function() { return (this.animations.length > 0 || this.name === 'Start' || this.name === 'End'); }, + isLooping: function() { return true; }, + getTotalWeight: function() { var sum = 0; for (var i = 0; i < this.animations.length; i++) { sum = sum + this.animations[i].weight; } return sum; + }, + + getTimelineDuration: function() { + var duration = 0; + for (var i = 0; i < this.animations.length; i++) { + var animation = this.animations[i]; + if (animation.animTrack.duration > duration) { + duration = animation.animTrack.duration > duration; + } + } + return duration; } }); - var AnimTransition = function (controller, from, to, time, priority, conditions, exitTime) { + var AnimTransition = function (controller, from, to, time, priority, conditions, exitTime, transitionOffset) { this.controller = controller; this.from = from; this.to = to; @@ -30,6 +43,7 @@ Object.assign(pc, function () { this.priority = priority; this.conditions = conditions || []; this.exitTime = exitTime || null; + this.transitionOffset = transitionOffset || null; }; Object.assign(AnimTransition.prototype, { @@ -74,7 +88,7 @@ Object.assign(pc, function () { return new AnimState(state.name, state.speed); }); this.transitions = transitions.map((function(transition) { - return new AnimTransition(this, transition.from, transition.to, transition.time, transition.priority, transition.conditions, transition.exitTime); + return new AnimTransition(this, transition.from, transition.to, transition.time, transition.priority, transition.conditions, transition.exitTime, transition.transitionOffset); }).bind(this)); this.parameters = parameters; this.previousStateName = null; @@ -124,18 +138,6 @@ Object.assign(pc, function () { return this.previousStateName = stateName; }, - _getActiveStateProgress: function(checkBeforeUpdate) { - if (this.activeStateName === 'Start' || this.activeStateName === 'End') - return 1.0; - else { - var activeClip = this.animEvaluator.findClip(this._getActiveState().animations[0].name); - if (activeClip) { - return (checkBeforeUpdate ? this.timeInStateBefore : this.timeInState) / activeClip.track.duration; - } - } - return null; - }, - _findTransition: function(from, to) { if (this.isTransitioning) { return false; @@ -150,8 +152,8 @@ Object.assign(pc, function () { transitions = transitions.filter((function(transition) { // when an exit time is present, we should only exit if it falls within the current frame delta time if (transition.hasExitTime()) { - var progressBefore = this._getActiveStateProgress(true); - var progress = this._getActiveStateProgress(); + var progressBefore = this.getActiveStateProgress(true); + var progress = this.getActiveStateProgress(); // when the exit time is smaller than 1 and the state is looping, we should check for an exit each loop if (transition.exitTime < 1.0 && this._getActiveState().isLooping()) { progressBefore = progressBefore - Math.floor(progressBefore); @@ -191,8 +193,8 @@ Object.assign(pc, function () { this.totalTransitionTime = transition.time; this.currTransitionTime = 0; } - this.timeInState = 0; - this.timeInStateBefore = 0; + + var hasTransitionOffset = transition.transitionOffset && transition.transitionOffset > 0.0 && transition.transitionOffset < 1.0; var activeState = this._getActiveState(); for (var i = 0; i < activeState.animations.length; i++) { @@ -208,8 +210,21 @@ Object.assign(pc, function () { clip.blendWeight = 1.0 / activeState.getTotalWeight(); } clip.reset(); + if (hasTransitionOffset) { + clip.time = activeState.getTimelineDuration() * transition.transitionOffset; + } clip.play(); } + + var timeInState = 0; + var timeInStateBefore = 0; + if (hasTransitionOffset) { + var offsetTime = activeState.getTimelineDuration() * transition.transitionOffset; + timeInState = offsetTime; + timeInStateBefore = offsetTime; + } + this.timeInState = timeInState; + this.timeInStateBefore = timeInStateBefore; }, _transitionToState: function(newStateName) { @@ -324,12 +339,27 @@ Object.assign(pc, function () { } }, + getActiveStateProgress: function(checkBeforeUpdate) { + if (this.activeStateName === 'Start' || this.activeStateName === 'End') + return 1.0; + else { + var activeClip = this.animEvaluator.findClip(this._getActiveState().animations[0].name); + if (activeClip) { + return (checkBeforeUpdate ? this.timeInStateBefore : this.timeInState) / activeClip.track.duration; + } + } + return null; + }, + + getActiveStateName: function() { + return this._getState(this.activeStateName).name; + }, + getFloat: function(name) { return this.parameters[name].value; }, setFloat: function(name, value) { - //TODO typechecking var float = this.parameters[name]; if (float && float.type === 'float') float.value = value; @@ -340,7 +370,6 @@ Object.assign(pc, function () { }, setInteger: function(name, value) { - //TODO typechecking var integer = this.parameters[name]; if (integer && integer.type === 'integer') integer.value = value; @@ -351,7 +380,6 @@ Object.assign(pc, function () { }, setBoolean: function(name, value) { - //TODO typechecking var boolean = this.parameters[name]; if (boolean && boolean.type === 'boolean') boolean.value = value; @@ -362,14 +390,12 @@ Object.assign(pc, function () { }, setTrigger: function(name) { - //TODO typechecking var trigger = this.parameters[name]; if (trigger && trigger.type === 'trigger') trigger.value = true; }, resetTrigger: function(name) { - //TODO typechecking var trigger = this.parameters[name]; if (trigger && trigger.type === 'trigger') trigger.value = false; From 76280c7d7c3107310b31aaf278f9da65c7332437 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Fri, 24 Apr 2020 17:11:59 +0100 Subject: [PATCH 009/144] add a reset function to the anim controller and expose it in the anim component --- src/framework/components/anim/component.js | 6 +++++ src/framework/components/anim/controller.js | 26 +++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 88b99cfc7fa..c89dbb0269d 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -130,6 +130,12 @@ Object.assign(pc, function () { this.data.animController.play(name); }, + reset: function() { + if (this.data.animController) { + this.data.animController.reset(); + } + }, + getActiveStateName: function () { return this.data.animController ? this.data.animController.getActiveStateName() : null; }, diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 52aea66d79a..b6de9636eec 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -91,6 +91,7 @@ Object.assign(pc, function () { return new AnimTransition(this, transition.from, transition.to, transition.time, transition.priority, transition.conditions, transition.exitTime, transition.transitionOffset); }).bind(this)); this.parameters = parameters; + this.initialParameters = JSON.parse(JSON.stringify(parameters)); this.previousStateName = null; this.activeStateName = 'Start'; this.playing = false; @@ -269,10 +270,17 @@ Object.assign(pc, function () { animTrack: animTrack, weight: 1.0 }; + + // Currently the anim controller only supports single animations in a state + if (state.animations.length > 0) { + state.animations = []; + this.reset(); + } state.animations.push(animation); if (!this.playing && this.activate && this.isPlayable()) { this.play(); + return; } }, @@ -292,6 +300,24 @@ Object.assign(pc, function () { } this.playing = true; }, + + reset: function() { + this.previousStateName = null; + this.activeStateName = 'Start'; + this.playing = false; + + this.currTransitionTime = 1.0; + this.totalTransitionTime = 1.0; + this.isTransitioning = false; + + this.timeInState = 0; + this.timeInStateBefore = 0; + + this.animEvaluator.removeClips(); + + this.parameters = JSON.parse(JSON.stringify(this.initialParameters)); + + }, update: function(dt) { if (this.playing) { From bfb66d90ac8111203114f75d6ace5c80d00acb51 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Fri, 24 Apr 2020 17:20:46 +0100 Subject: [PATCH 010/144] update anim clips after transitions and blending --- src/framework/components/anim/controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index b6de9636eec..a10207c9367 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -321,7 +321,6 @@ Object.assign(pc, function () { update: function(dt) { if (this.playing) { - this.animEvaluator.update(dt); this.timeInStateBefore = this.timeInState; this.timeInState = this.timeInState + dt; @@ -362,6 +361,7 @@ Object.assign(pc, function () { } this.currTransitionTime = this.currTransitionTime + dt; } + this.animEvaluator.update(dt); } }, From 314ee1195e53a0c89c127a89736ec80e243fc936 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Mon, 27 Apr 2020 11:40:11 +0100 Subject: [PATCH 011/144] Update anim clip names to include state name --- src/framework/components/anim/controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index a10207c9367..ac88ae90168 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -266,7 +266,7 @@ Object.assign(pc, function () { } var animation = { - name: animTrack.name, + name: pc.string.format('{0}.{1}', stateName, animTrack.name), animTrack: animTrack, weight: 1.0 }; From 7d0028690408825ccd115e51e5c6154fd5b10559 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Mon, 27 Apr 2020 12:29:15 +0100 Subject: [PATCH 012/144] syntax update --- src/framework/components/anim/controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index ac88ae90168..b207cc7cee5 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -266,7 +266,7 @@ Object.assign(pc, function () { } var animation = { - name: pc.string.format('{0}.{1}', stateName, animTrack.name), + name: stateName + '.' + animTrack.name, animTrack: animTrack, weight: 1.0 }; From 8a26728de8251935604942bb89c5366460d1f155 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Mon, 27 Apr 2020 14:44:07 +0100 Subject: [PATCH 013/144] use enums throughout the anim controller --- src/framework/components/anim/controller.js | 60 ++++++++++++++------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index b207cc7cee5..43057369657 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -1,5 +1,26 @@ Object.assign(pc, function () { + var ANIM_INTERRUPTION_SOURCE_NONE = 0; + var ANIM_INTERRUPTION_SOURCE_CURRENT_STATE = 1; + var ANIM_INTERRUPTION_SOURCE_NEXT_STATE = 2; + var ANIM_INTERRUPTION_SOURCE_CURRENT_STATE_NEXT_STATE = 3; + var ANIM_INTERRUPTION_SOURCE_NEXT_STATE_CURRENT_STATE = 4; + + var ANIM_TRANSITION_PREDICATE_GREATER_THAN = 0; + var ANIM_TRANSITION_PREDICATE_LESS_THAN = 1; + var ANIM_TRANSITION_PREDICATE_GREATER_THAN_EQUAL_TO = 2; + var ANIM_TRANSITION_PREDICATE_LESS_THAN_EQUAL_TO = 3; + var ANIM_TRANSITION_PREDICATE_EQUAL_TO = 4; + var ANIM_TRANSITION_PREDICATE_NOT_EQUAL_TO = 5; + + var ANIM_PARAMETER_INTEGER = 0; + var ANIM_PARAMETER_FLOAT = 1; + var ANIM_PARAMETER_BOOLEAN = 2; + var ANIM_PARAMETER_TRIGGER = 3; + + var ANIM_STATE_START = 'ANIM_STATE_START'; + var ANIM_STATE_END = 'ANIM_STATE_END'; + var AnimState = function (name, speed) { this.name = name; this.animations = []; @@ -8,7 +29,7 @@ Object.assign(pc, function () { Object.assign(AnimState.prototype, { isPlayable: function() { - return (this.animations.length > 0 || this.name === 'Start' || this.name === 'End'); + return (this.animations.length > 0 || this.name === ANIM_STATE_START || this.name === ANIM_STATE_END); }, isLooping: function() { @@ -35,7 +56,7 @@ Object.assign(pc, function () { } }); - var AnimTransition = function (controller, from, to, time, priority, conditions, exitTime, transitionOffset) { + var AnimTransition = function (controller, from, to, time, priority, conditions, exitTime, transitionOffset, interruptionSource) { this.controller = controller; this.from = from; this.to = to; @@ -44,6 +65,7 @@ Object.assign(pc, function () { this.conditions = conditions || []; this.exitTime = exitTime || null; this.transitionOffset = transitionOffset || null; + this.interruptionSource = interruptionSource || ANIM_INTERRUPTION_SOURCE_NONE; }; Object.assign(AnimTransition.prototype, { @@ -53,22 +75,22 @@ Object.assign(pc, function () { var condition = this.conditions[i]; var parameter = this.controller.getParameter(condition.parameterName); switch(condition.predicate) { - case 'GREATER_THAN': + case ANIM_TRANSITION_PREDICATE_GREATER_THAN: conditionsMet = conditionsMet && parameter.value > condition.value; break; - case 'LESS_THAN': + case ANIM_TRANSITION_PREDICATE_LESS_THAN: conditionsMet = conditionsMet && parameter.value < condition.value; break; - case 'GREATER_THAN_EQUAL_TO': + case ANIM_TRANSITION_PREDICATE_GREATER_THAN_EQUAL_TO: conditionsMet = conditionsMet && parameter.value >= condition.value; break; - case 'LESS_THAN_EQUAL_TO': + case ANIM_TRANSITION_PREDICATE_LESS_THAN_EQUAL_TO: conditionsMet = conditionsMet && parameter.value <= condition.value; break; - case 'EQUAL_TO': + case ANIM_TRANSITION_PREDICATE_EQUAL_TO: conditionsMet = conditionsMet && parameter.value === condition.value; break; - case 'NOT_EQUAL_TO': + case ANIM_TRANSITION_PREDICATE_NOT_EQUAL_TO: conditionsMet = conditionsMet && parameter.value !== condition.value; break; } @@ -93,7 +115,7 @@ Object.assign(pc, function () { this.parameters = parameters; this.initialParameters = JSON.parse(JSON.stringify(parameters)); this.previousStateName = null; - this.activeStateName = 'Start'; + this.activeStateName = ANIM_STATE_START; this.playing = false; this.activate = activate; @@ -183,7 +205,7 @@ Object.assign(pc, function () { var triggers = transition.conditions.filter((function(condition) { var parameter = this.getParameter(condition.parameterName); - return parameter.type === 'trigger'; + return parameter.type === ANIM_PARAMETER_TRIGGER; }).bind(this)); for (var i = 0; i < triggers.length; i++) { this.resetTrigger(triggers[i].parameterName); @@ -250,9 +272,9 @@ Object.assign(pc, function () { if (!transition) { return; } - if (transition.to === 'End') + if (transition.to === ANIM_STATE_END) { - this._setActiveState('Start'); + this._setActiveState(ANIM_STATE_START); transition = this._findTransition(); } this._updateStateFromTransition(transition); @@ -303,7 +325,7 @@ Object.assign(pc, function () { reset: function() { this.previousStateName = null; - this.activeStateName = 'Start'; + this.activeStateName = ANIM_STATE_START; this.playing = false; this.currTransitionTime = 1.0; @@ -366,7 +388,7 @@ Object.assign(pc, function () { }, getActiveStateProgress: function(checkBeforeUpdate) { - if (this.activeStateName === 'Start' || this.activeStateName === 'End') + if (this.activeStateName === ANIM_STATE_START || this.activeStateName === ANIM_STATE_END) return 1.0; else { var activeClip = this.animEvaluator.findClip(this._getActiveState().animations[0].name); @@ -387,7 +409,7 @@ Object.assign(pc, function () { setFloat: function(name, value) { var float = this.parameters[name]; - if (float && float.type === 'float') + if (float && float.type === ANIM_PARAMETER_FLOAT) float.value = value; }, @@ -397,7 +419,7 @@ Object.assign(pc, function () { setInteger: function(name, value) { var integer = this.parameters[name]; - if (integer && integer.type === 'integer') + if (integer && integer.type === ANIM_PARAMETER_INTEGER) integer.value = value; }, @@ -407,7 +429,7 @@ Object.assign(pc, function () { setBoolean: function(name, value) { var boolean = this.parameters[name]; - if (boolean && boolean.type === 'boolean') + if (boolean && boolean.type === ANIM_PARAMETER_BOOLEAN) boolean.value = value; }, @@ -417,13 +439,13 @@ Object.assign(pc, function () { setTrigger: function(name) { var trigger = this.parameters[name]; - if (trigger && trigger.type === 'trigger') + if (trigger && trigger.type === ANIM_PARAMETER_TRIGGER) trigger.value = true; }, resetTrigger: function(name) { var trigger = this.parameters[name]; - if (trigger && trigger.type === 'trigger') + if (trigger && trigger.type === ANIM_PARAMETER_TRIGGER) trigger.value = false; }, From c68bde505ccf5916c7ca0fac7bafeede9395dd57 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Mon, 27 Apr 2020 17:11:43 +0100 Subject: [PATCH 014/144] Add transition interruption sources to the anim controller --- src/framework/components/anim/controller.js | 151 +++++++++++++++----- 1 file changed, 112 insertions(+), 39 deletions(-) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 43057369657..ce098acd442 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -1,10 +1,10 @@ Object.assign(pc, function () { var ANIM_INTERRUPTION_SOURCE_NONE = 0; - var ANIM_INTERRUPTION_SOURCE_CURRENT_STATE = 1; + var ANIM_INTERRUPTION_SOURCE_PREV_STATE = 1; var ANIM_INTERRUPTION_SOURCE_NEXT_STATE = 2; - var ANIM_INTERRUPTION_SOURCE_CURRENT_STATE_NEXT_STATE = 3; - var ANIM_INTERRUPTION_SOURCE_NEXT_STATE_CURRENT_STATE = 4; + var ANIM_INTERRUPTION_SOURCE_PREV_STATE_NEXT_STATE = 3; + var ANIM_INTERRUPTION_SOURCE_NEXT_STATE_PREV_STATE = 4; var ANIM_TRANSITION_PREDICATE_GREATER_THAN = 0; var ANIM_TRANSITION_PREDICATE_LESS_THAN = 1; @@ -107,10 +107,23 @@ Object.assign(pc, function () { var AnimController = function (animEvaluator, states, transitions, parameters, activate) { this.animEvaluator = animEvaluator; this.states = states.map(function(state) { - return new AnimState(state.name, state.speed); + return new AnimState( + state.name, + state.speed + ); }); this.transitions = transitions.map((function(transition) { - return new AnimTransition(this, transition.from, transition.to, transition.time, transition.priority, transition.conditions, transition.exitTime, transition.transitionOffset); + return new AnimTransition( + this, + transition.from, + transition.to, + transition.time, + transition.priority, + transition.conditions, + transition.exitTime, + transition.transitionOffset, + transition.interruptionSource + ); }).bind(this)); this.parameters = parameters; this.initialParameters = JSON.parse(JSON.stringify(parameters)); @@ -122,6 +135,8 @@ Object.assign(pc, function () { this.currTransitionTime = 1.0; this.totalTransitionTime = 1.0; this.isTransitioning = false; + this.transitionInterruptionSource = ANIM_INTERRUPTION_SOURCE_NONE; + this.transitionPreviousStates = []; this.timeInState = 0; this.timeInStateBefore = 0; @@ -161,17 +176,60 @@ Object.assign(pc, function () { return this.previousStateName = stateName; }, - _findTransition: function(from, to) { - if (this.isTransitioning) { - return false; - } + _findTransitionsFromState: function(stateName) { + var transitions = this.transitions.filter((function(transition) { + return transition.from === stateName && transition.to !== this.activeStateName; + }).bind(this)); + + // sort transitions in priority order + transitions.sort(function(a, b) { + return a.priority < b.priority; + }); + + return transitions; + }, + + _findTransitionsBetweenStates: function(sourceStateName, destinationStateName) { var transitions = this.transitions.filter((function(transition) { - if (to && from) { - return transition.from === from && transition.to === to; + return transition.from === sourceStateName && transition.to === destinationStateName; + }).bind(this)); + + // sort transitions in priority order + transitions.sort(function(a, b) { + return a.priority < b.priority; + }); + + return transitions; + }, + + _findTransition: function(from, to) { + var transitions = []; + + // find transitions that include the required source and destination states + if (from && to) { + transitions.concat(this._findTransitionsBetweenStates(this.activeStateName)); + } else { + if (!this.isTransitioning) { + transitions = transitions.concat(this._findTransitionsFromState(this.activeStateName)); } else { - return transition.from === this.activeStateName; + switch(this.transitionInterruptionSource) { + case ANIM_INTERRUPTION_SOURCE_PREV_STATE: + transitions = transitions.concat(this._findTransitionsFromState(this.previousStateName)); + case ANIM_INTERRUPTION_SOURCE_NEXT_STATE: + transitions = transitions.concat(this._findTransitionsFromState(this.activeStateName)); + case ANIM_INTERRUPTION_SOURCE_PREV_STATE_NEXT_STATE: + transitions = transitions.concat(this._findTransitionsFromState(this.previousStateName)); + transitions = transitions.concat(this._findTransitionsFromState(this.activeStateName)); + case ANIM_INTERRUPTION_SOURCE_NEXT_STATE_PREV_STATE: + transitions = transitions.concat(this._findTransitionsFromState(this.activeStateName)); + transitions = transitions.concat(this._findTransitionsFromState(this.previousStateName)); + case ANIM_INTERRUPTION_SOURCE_NONE: + default: + } } - }).bind(this)); + } + + // filter out transitions that don't have their conditions met transitions = transitions.filter((function(transition) { // when an exit time is present, we should only exit if it falls within the current frame delta time if (transition.hasExitTime()) { @@ -184,14 +242,12 @@ Object.assign(pc, function () { } // return false if exit time isn't within the frames delta time if (!(transition.exitTime > progressBefore && transition.exitTime <= progress)) { - return false; + return null; } } return transition.hasConditionsMet(); }).bind(this)); - transitions.sort(function(a, b) { - return a.priority < b.priority; - }); + if (transitions.length > 0) { return transitions[0]; } else { @@ -211,10 +267,32 @@ Object.assign(pc, function () { this.resetTrigger(triggers[i].parameterName); } + if (this.isTransitioning) { + var interpolatedTime = this.currTransitionTime / this.totalTransitionTime; + for (var i = 0; i < this.transitionPreviousStates.length; i++) { + this.transitionPreviousStates[i].weight = this.transitionPreviousStates[i].weight * (1.0 - interpolatedTime); + var state = this._getState(this.transitionPreviousStates[i].name); + for (var j = 0; j < state.animations.length; j++) { + var animation = state.animations[j]; + this.animEvaluator.findClip(animation.name).pause(); + } + } + this.transitionPreviousStates.push({ + name: this.previousStateName, + weight: interpolatedTime + }); + } else { + this.transitionPreviousStates.push({ + name: this.previousStateName, + weight: 1.0 + }); + } + if (transition.time > 0) { this.isTransitioning = true; this.totalTransitionTime = transition.time; this.currTransitionTime = 0; + this.transitionInterruptionSource = transition.interruptionSource; } var hasTransitionOffset = transition.transitionOffset && transition.transitionOffset > 0.0 && transition.transitionOffset < 1.0; @@ -267,19 +345,6 @@ Object.assign(pc, function () { this._updateStateFromTransition(transition); }, - _transitionToNextState: function() { - var transition = this._findTransition(); - if (!transition) { - return; - } - if (transition.to === ANIM_STATE_END) - { - this._setActiveState(ANIM_STATE_START); - transition = this._findTransition(); - } - this._updateStateFromTransition(transition); - }, - linkAnimationToState: function(stateName, animTrack) { var state = this._getState(stateName); if (!state) { @@ -354,13 +419,18 @@ Object.assign(pc, function () { if (this.currTransitionTime >= this.totalTransitionTime) { this.isTransitioning = false; - var previousState = this._getPreviousState(); - for (var i = 0; i < previousState.animations.length; i++) { - var animation = previousState.animations[i]; - this.animEvaluator.findClip(animation.name).pause(); - this.animEvaluator.findClip(animation.name).blendWeight = 0; + for (var i = 0; i < this.transitionPreviousStates.length; i++) { + var state = this._getState(this.transitionPreviousStates[i].name); + for (var j = 0; j < state.animations.length; j++) { + var animation = state.animations[j]; + var clip = this.animEvaluator.findClip(animation.name); + clip.pause(); + clip.blendWeight = 0; + } } + this.transitionPreviousStates = []; + var activeState = this._getActiveState(); for (var i = 0; i < activeState.animations.length; i++) { var animation = activeState.animations[i]; @@ -369,10 +439,13 @@ Object.assign(pc, function () { } else { var interpolatedTime = this.currTransitionTime / this.totalTransitionTime; - var previousState = this._getPreviousState(); - for (var i = 0; i < previousState.animations.length; i++) { - var animation = previousState.animations[i]; - this.animEvaluator.findClip(animation.name).blendWeight = (1.0 - interpolatedTime) * animation.weight / previousState.getTotalWeight(); + for (var i = 0; i < this.transitionPreviousStates.length; i++) { + var state = this._getState(this.transitionPreviousStates[i].name); + var stateWeight = this.transitionPreviousStates[i].weight; + for (var j = 0; j < state.animations.length; j++) { + var animation = state.animations[j]; + this.animEvaluator.findClip(animation.name).blendWeight = (1.0 - interpolatedTime) * animation.weight / state.getTotalWeight() * stateWeight; + } } var activeState = this._getActiveState(); for (var i = 0; i < activeState.animations.length; i++) { From 17cd652512c5953a96480f57b2bf67736236f916 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Tue, 28 Apr 2020 09:54:34 +0100 Subject: [PATCH 015/144] enclose logging in DEBUG conditionals --- src/framework/components/anim/component.js | 6 ++++++ src/framework/components/anim/controller.js | 19 ++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index c89dbb0269d..fe3899fdeb1 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -46,12 +46,16 @@ Object.assign(pc, function () { linkAnimAssetToState: function(stateName, asset) { if (!this.data.animController) + // #ifdef DEBUG console.error('linkAnimAssetToState: Trying to link an anim asset to non existing state graph. Have you called loadStateMachineAsset?'); + // #endif var animTrack = asset.resource; if(!animTrack) + // #ifdef DEBUG console.error('linkAnimAssetToState: No animation found for given assetName'); + // #endif this.data.animController.linkAnimationToState(stateName, animTrack); }, @@ -123,7 +127,9 @@ Object.assign(pc, function () { } if (!this.data.animController) { + // #ifdef DEBUG console.error('Trying to play an animation when no animation state machine has been loaded. Have you called loadStateMachineAsset?'); + // #endif return; } diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index ce098acd442..62c836506a0 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -348,7 +348,9 @@ Object.assign(pc, function () { linkAnimationToState: function(stateName, animTrack) { var state = this._getState(stateName); if (!state) { + // #ifdef DEBUG console.error('Linking animation asset to animation state that does not exist'); + // #endif return; } @@ -481,9 +483,20 @@ Object.assign(pc, function () { }, setFloat: function(name, value) { - var float = this.parameters[name]; - if (float && float.type === ANIM_PARAMETER_FLOAT) - float.value = value; + if (Number(value) === value && value % 1 === 0) { + var param = this.parameters[name]; + if (param && param.type === ANIM_PARAMETER_FLOAT) { + param.value = value; + } else { + // #ifdef DEBUG + console.error('No float parameter named "' + name + '" found in anim controller'); + // #endif + } + } else { + // #ifdef DEBUG + console.error('Cannot assign non float value to float anim controller parameter'); + // #endif + } }, getInteger: function(name) { From 56e3d0d8b1660797a97fa9848e7d24f98ccae699 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Tue, 28 Apr 2020 09:59:41 +0100 Subject: [PATCH 016/144] Switch object copying to use object assign --- src/framework/components/anim/controller.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 62c836506a0..dde086b778c 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -126,7 +126,7 @@ Object.assign(pc, function () { ); }).bind(this)); this.parameters = parameters; - this.initialParameters = JSON.parse(JSON.stringify(parameters)); + this.initialParameters = Object.assign({}, parameters); this.previousStateName = null; this.activeStateName = ANIM_STATE_START; this.playing = false; @@ -404,8 +404,7 @@ Object.assign(pc, function () { this.animEvaluator.removeClips(); - this.parameters = JSON.parse(JSON.stringify(this.initialParameters)); - + this.parameters = Object.assign({}, this.initialParameters); }, update: function(dt) { From 86cad694664a1fd9a56020ded00becea63c6266a Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Tue, 28 Apr 2020 12:27:30 +0100 Subject: [PATCH 017/144] expose anim enums in the pc namespace --- src/framework/components/anim/controller.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index dde086b778c..057c1b5d8c2 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -540,6 +540,27 @@ Object.assign(pc, function () { }); return { + ANIM_INTERRUPTION_SOURCE_NONE: ANIM_INTERRUPTION_SOURCE_NONE, + ANIM_INTERRUPTION_SOURCE_PREV_STATE: ANIM_INTERRUPTION_SOURCE_PREV_STATE, + ANIM_INTERRUPTION_SOURCE_NEXT_STATE: ANIM_INTERRUPTION_SOURCE_NEXT_STATE, + ANIM_INTERRUPTION_SOURCE_PREV_STATE_NEXT_STATE: ANIM_INTERRUPTION_SOURCE_PREV_STATE_NEXT_STATE, + ANIM_INTERRUPTION_SOURCE_NEXT_STATE_PREV_STATE: ANIM_INTERRUPTION_SOURCE_NEXT_STATE_PREV_STATE, + + ANIM_TRANSITION_PREDICATE_GREATER_THAN: ANIM_TRANSITION_PREDICATE_GREATER_THAN, + ANIM_TRANSITION_PREDICATE_LESS_THAN: ANIM_TRANSITION_PREDICATE_LESS_THAN, + ANIM_TRANSITION_PREDICATE_GREATER_THAN_EQUAL_TO: ANIM_TRANSITION_PREDICATE_GREATER_THAN_EQUAL_TO, + ANIM_TRANSITION_PREDICATE_LESS_THAN_EQUAL_TO: ANIM_TRANSITION_PREDICATE_LESS_THAN_EQUAL_TO, + ANIM_TRANSITION_PREDICATE_EQUAL_TO: ANIM_TRANSITION_PREDICATE_EQUAL_TO, + ANIM_TRANSITION_PREDICATE_NOT_EQUAL_TO: ANIM_TRANSITION_PREDICATE_NOT_EQUAL_TO, + + ANIM_PARAMETER_INTEGER: ANIM_PARAMETER_INTEGER, + ANIM_PARAMETER_FLOAT: ANIM_PARAMETER_FLOAT, + ANIM_PARAMETER_BOOLEAN: ANIM_PARAMETER_BOOLEAN, + ANIM_PARAMETER_TRIGGER: ANIM_PARAMETER_TRIGGER, + + ANIM_STATE_START: ANIM_STATE_START, + ANIM_STATE_END: ANIM_STATE_END, + AnimController: AnimController } }()); \ No newline at end of file From 7b245f77ce02de23859bb0839bfbc451bb60f215 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Tue, 28 Apr 2020 14:14:13 +0100 Subject: [PATCH 018/144] Update the anim property locator to escape forward slashes and cleanup it's code --- src/anim/anim.js | 17 +++-- .../components/anim/property-locator.js | 67 ++++--------------- 2 files changed, 23 insertions(+), 61 deletions(-) diff --git a/src/anim/anim.js b/src/anim/anim.js index 2a81d8bd36d..ee16b90c2cd 100644 --- a/src/anim/anim.js +++ b/src/anim/anim.js @@ -563,16 +563,19 @@ Object.assign(pc, function () { */ var AnimBinder = function () { }; - // join a list of path segments into a path string - AnimBinder.joinPath = function (pathSegments) { + // join a list of path segments into a path string using the full stop character. If another character is supplied, + // it will join using that character instead + AnimBinder.joinPath = function (pathSegments, character) { + character = character || '.'; var escape = function (string) { - return string.replace(/\\/g, '\\\\').replace(/\./g, '\\.'); + return string.replace(/\\/g, '\\\\').replace(new RegExp('\\' + character, 'g'), '\\' + character) }; - return pathSegments.map(escape).join('.'); + return pathSegments.map(escape).join(character); }; // split a path string into its segments and resolve character escaping - AnimBinder.splitPath = function (path) { + AnimBinder.splitPath = function (path, character) { + character = character || '.'; var result = []; var curr = ""; var i = 0; @@ -581,12 +584,12 @@ Object.assign(pc, function () { if (c === '\\' && i < path.length) { c = path[i++]; - if (c === '\\' || c === '.') { + if (c === '\\' || c === character) { curr += c; } else { curr += '\\' + c; } - } else if (c === '.') { + } else if (c === character) { result.push(curr); curr = ''; } else { diff --git a/src/framework/components/anim/property-locator.js b/src/framework/components/anim/property-locator.js index 4750be4df36..d6cf816d7ab 100644 --- a/src/framework/components/anim/property-locator.js +++ b/src/framework/components/anim/property-locator.js @@ -2,61 +2,20 @@ Object.assign(pc, function () { var AnimPropertyLocator = function () { }; Object.assign(AnimPropertyLocator.prototype, { - // split a path string into its segments and resolve character escaping - _splitPath: function(path) { - var result = []; - var curr = ""; - var i = 0; - while (i < path.length) { - var c = path[i++]; - - if (c === '\\' && i < path.length) { - c = path[i++]; - if (c === '\\' || c === '.') { - curr += c; - } else { - curr += '\\' + c; - } - } else if (c === '.') { - result.push(curr); - curr = ''; - } else { - curr += c; - } - } - if (curr.length > 0) { - result.push(curr); - } - return result; + encode: function(locator) { + return pc.AnimBinder.joinPath([ + pc.AnimBinder.joinPath(locator[0]), + locator[1], + pc.AnimBinder.joinPath(locator[2]) + ], '/'); }, - // join a list of path segments into a path string - _joinPath: function (pathSegments) { - var escape = function (string) { - return string.replace(/\\/g, '\\\\').replace(/\./g, '\\.'); - }; - return pathSegments.map(escape).join('.'); - }, - encode: function(decodedLocator) { - var entityHierarchy = decodedLocator[0]; - var component = decodedLocator[1]; - var propertyHierarchy = decodedLocator[2]; - return pc.string.format( - '{0}/{1}/{2}', - this._joinPath(entityHierarchy), - component, - this._joinPath(propertyHierarchy) - ); - }, - decode: function(encodedLocator) { - var locatorSections = encodedLocator.split('/'); - var entityHierarchy = this._splitPath(locatorSections[0]); - for (var i = 0; i < entityHierarchy.length; i++) { - entityHierarchy[i] = entityHierarchy[i].split('\\.').join('.'); - } - var component = locatorSections[1]; - var propertyHierarchy = locatorSections[2].split('.'); - - return [entityHierarchy, component, propertyHierarchy]; + decode: function(locator) { + var locatorSections = pc.AnimBinder.splitPath(locator, '/'); + return [ + pc.AnimBinder.splitPath(locatorSections[0]), + locatorSections[1], + pc.AnimBinder.splitPath(locatorSections[2]) + ]; } }); return { From 6f5e31a4c6468434e67d32990b581e3cfe0b64e9 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Tue, 28 Apr 2020 17:23:58 +0100 Subject: [PATCH 019/144] cache transitions when searching from and between states --- src/framework/components/anim/controller.js | 67 +++++++++++---------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 057c1b5d8c2..db03010d1ee 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -106,12 +106,13 @@ Object.assign(pc, function () { var AnimController = function (animEvaluator, states, transitions, parameters, activate) { this.animEvaluator = animEvaluator; - this.states = states.map(function(state) { - return new AnimState( - state.name, - state.speed + this.states = {}; + for (var i = 0; i < states.length; i++) { + this.states[states[i].name] = new AnimState( + states[i].name, + states[i].speed ); - }); + } this.transitions = transitions.map((function(transition) { return new AnimTransition( this, @@ -125,6 +126,8 @@ Object.assign(pc, function () { transition.interruptionSource ); }).bind(this)); + this.findTransitionsFromStateCache = {}; + this.findTransitionsBetweenStatesCache = {}; this.parameters = parameters; this.initialParameters = Object.assign({}, parameters); this.previousStateName = null; @@ -144,20 +147,11 @@ Object.assign(pc, function () { Object.assign(AnimController.prototype, { _getState: function(stateName) { - for (var i = 0; i < this.states.length; i++) { - if (this.states[i].name === stateName) { - return this.states[i]; - } - } - return null; + return this.states[stateName]; }, _setState: function(stateName, state) { - for (var i = 0; i < this.states.length; i++) { - if (this.states[i].name === stateName) { - this.states[i] = state; - } - } + this.states[stateName] = state; }, _getActiveState: function() { @@ -177,27 +171,36 @@ Object.assign(pc, function () { }, _findTransitionsFromState: function(stateName) { - var transitions = this.transitions.filter((function(transition) { - return transition.from === stateName && transition.to !== this.activeStateName; - }).bind(this)); - - // sort transitions in priority order - transitions.sort(function(a, b) { - return a.priority < b.priority; - }); - + var transitions = this.findTransitionsFromStateCache[stateName]; + if (!transitions) { + transitions = this.transitions.filter((function(transition) { + return transition.from === stateName; + }).bind(this)); + + // sort transitions in priority order + transitions.sort(function(a, b) { + return a.priority < b.priority; + }); + + this.findTransitionsFromStateCache[stateName] = transitions; + } return transitions; }, _findTransitionsBetweenStates: function(sourceStateName, destinationStateName) { - var transitions = this.transitions.filter((function(transition) { - return transition.from === sourceStateName && transition.to === destinationStateName; - }).bind(this)); + var transitions = this.findTransitionsBetweenStatesCache[sourceStateName + '->' + destinationStateName]; + if (!transitions) { + transitions = this.transitions.filter((function(transition) { + return transition.from === sourceStateName && transition.to === destinationStateName; + }).bind(this)); + + // sort transitions in priority order + transitions.sort(function(a, b) { + return a.priority < b.priority; + }); - // sort transitions in priority order - transitions.sort(function(a, b) { - return a.priority < b.priority; - }); + this.findTransitionsBetweenStatesCache[sourceStateName + '->' + destinationStateName] = transitions; + } return transitions; }, From f10f6c1b81b3099e8fb9542a26f9ae7cf6e39024 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Tue, 28 Apr 2020 18:52:09 +0100 Subject: [PATCH 020/144] update anim controller parameters get/set methods --- src/framework/components/anim/component.js | 18 ++--- src/framework/components/anim/controller.js | 86 +++++++-------------- 2 files changed, 38 insertions(+), 66 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index fe3899fdeb1..570782eeb94 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -62,55 +62,55 @@ Object.assign(pc, function () { getFloat: function(name) { if (this.data.animController) { - return this.data.animController.getFloat(name); + return this.data.animController.getParameterValue(name, pc.ANIM_PARAMETER_FLOAT); } }, setFloat: function(name, value) { if (this.data.animController) { - this.data.animController.setFloat(name, value); + return this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_FLOAT, value); } }, getInteger: function(name) { if (this.data.animController) { - return this.data.animController.getInteger(name); + return this.data.animController.getParameterValue(name, pc.ANIM_PARAMETER_INTEGER); } }, setInteger: function(name, value) { if (this.data.animController) { - this.data.animController.setInteger(name, value); + return this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_INTEGER, value); } }, getBoolean: function(name) { if (this.data.animController) { - return this.data.animController.getBoolean(name); + return this.data.animController.getParameterValue(name, pc.ANIM_PARAMETER_BOOLEAN); } }, setBoolean: function(name, value) { if (this.data.animController) { - this.data.animController.setBoolean(name, value); + return this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_BOOLEAN, value); } }, getTrigger: function(name) { if (this.data.animController) { - return this.data.animController.getTrigger(name); + return this.data.animController.getParameterValue(name, pc.ANIM_PARAMETER_TRIGGER); } }, setTrigger: function(name) { if (this.data.animController) { - this.data.animController.setTrigger(name); + return this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_TRIGGER, true); } }, resetTrigger: function(name) { if (this.data.animController) { - this.data.animController.resetTrigger(name); + return this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_TRIGGER, false); } }, diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index db03010d1ee..f0374694439 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -18,6 +18,13 @@ Object.assign(pc, function () { var ANIM_PARAMETER_BOOLEAN = 2; var ANIM_PARAMETER_TRIGGER = 3; + var ANIM_PARAMETER_TYPE_NAMES = { + ANIM_PARAMETER_INTEGER: 'Integer', + ANIM_PARAMETER_FLOAT: 'Float', + ANIM_PARAMETER_BOOLEAN: 'Boolean', + ANIM_PARAMETER_TRIGGER: 'Trigger', + }; + var ANIM_STATE_START = 'ANIM_STATE_START'; var ANIM_STATE_END = 'ANIM_STATE_END'; @@ -73,7 +80,7 @@ Object.assign(pc, function () { var conditionsMet = true; for (var i = 0; i < this.conditions.length; i++) { var condition = this.conditions[i]; - var parameter = this.controller.getParameter(condition.parameterName); + var parameter = this.controller._getParameter(condition.parameterName); switch(condition.predicate) { case ANIM_TRANSITION_PREDICATE_GREATER_THAN: conditionsMet = conditionsMet && parameter.value > condition.value; @@ -263,11 +270,11 @@ Object.assign(pc, function () { this._setActiveState(transition.to); var triggers = transition.conditions.filter((function(condition) { - var parameter = this.getParameter(condition.parameterName); + var parameter = this._getParameter(condition.parameterName); return parameter.type === ANIM_PARAMETER_TRIGGER; }).bind(this)); for (var i = 0; i < triggers.length; i++) { - this.resetTrigger(triggers[i].parameterName); + this.setParameterValue(triggers[i].parameterName, ANIM_PARAMETER_TRIGGER, false); } if (this.isTransitioning) { @@ -480,66 +487,30 @@ Object.assign(pc, function () { return this._getState(this.activeStateName).name; }, - getFloat: function(name) { - return this.parameters[name].value; + _getParameter: function(name) { + return this.parameters[name]; }, - setFloat: function(name, value) { - if (Number(value) === value && value % 1 === 0) { - var param = this.parameters[name]; - if (param && param.type === ANIM_PARAMETER_FLOAT) { - param.value = value; - } else { - // #ifdef DEBUG - console.error('No float parameter named "' + name + '" found in anim controller'); - // #endif - } - } else { - // #ifdef DEBUG - console.error('Cannot assign non float value to float anim controller parameter'); - // #endif + getParameterValue: function(name, type) { + var param = this._getParameter(name); + if (param && param.type === type) { + return param.value; } + // #ifdef DEBUG + console.log('Cannot get parameter value. No parameter found in anim controller named "' + name + '" of type "' + ANIM_PARAMETER_TYPE_NAMES[type] + '"'); + // #endif }, - getInteger: function(name) { - return this.parameters[name].value; - }, - - setInteger: function(name, value) { - var integer = this.parameters[name]; - if (integer && integer.type === ANIM_PARAMETER_INTEGER) - integer.value = value; - }, - - getBoolean: function(name) { - return this.parameters[name].value; - }, - - setBoolean: function(name, value) { - var boolean = this.parameters[name]; - if (boolean && boolean.type === ANIM_PARAMETER_BOOLEAN) - boolean.value = value; - }, - - getTrigger: function(name) { - return this.parameters[name].value; - }, - - setTrigger: function(name) { - var trigger = this.parameters[name]; - if (trigger && trigger.type === ANIM_PARAMETER_TRIGGER) - trigger.value = true; - }, - - resetTrigger: function(name) { - var trigger = this.parameters[name]; - if (trigger && trigger.type === ANIM_PARAMETER_TRIGGER) - trigger.value = false; + setParameterValue: function(name, type, value) { + var param = this._getParameter(name); + if (param && param.type === type) { + param.value = value; + return; + } + // #ifdef DEBUG + console.log('Cannot set parameter value. No parameter found in anim controller named "' + name + '" of type "' + ANIM_PARAMETER_TYPE_NAMES[type] + '"'); + // #endif }, - - getParameter: function(name) { - return this.parameters[name]; - } }); return { @@ -556,6 +527,7 @@ Object.assign(pc, function () { ANIM_TRANSITION_PREDICATE_EQUAL_TO: ANIM_TRANSITION_PREDICATE_EQUAL_TO, ANIM_TRANSITION_PREDICATE_NOT_EQUAL_TO: ANIM_TRANSITION_PREDICATE_NOT_EQUAL_TO, + ANIM_PARAMETER_TYPE_NAMES: ANIM_PARAMETER_TYPE_NAMES, ANIM_PARAMETER_INTEGER: ANIM_PARAMETER_INTEGER, ANIM_PARAMETER_FLOAT: ANIM_PARAMETER_FLOAT, ANIM_PARAMETER_BOOLEAN: ANIM_PARAMETER_BOOLEAN, From 9d834cc1fa80ef3daf5fd4ef499b7ce86dbfe17f Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Wed, 29 Apr 2020 15:50:54 +0100 Subject: [PATCH 021/144] add jsdocs --- src/framework/components/anim/component.js | 196 +++++++++++++----- .../components/anim/property-locator.js | 26 +++ src/framework/components/anim/system.js | 2 +- 3 files changed, 176 insertions(+), 48 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 570782eeb94..c7140ee3f97 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -19,8 +19,13 @@ Object.assign(pc, function () { AnimComponent.prototype.constructor = AnimComponent; Object.assign(AnimComponent.prototype, { - - loadStateGraph: function(asset) { + /** + * @function + * @name pc.AnimComponent#loadStateGraph + * @description Loads a state graph asset resource into the component. Then initialises the components animation controller. + * @param {object} stateGraph - The state graph asset to load into the component. Contains the states, transitions and parameters used to define a complete animation controller. + */ + loadStateGraph: function(stateGraph) { var data = this.data; var graph; @@ -37,118 +42,215 @@ Object.assign(pc, function () { data.animController = new pc.AnimController( animEvaluator, - asset.states, - asset.transitions, - asset.parameters, + stateGraph.states, + stateGraph.transitions, + stateGraph.parameters, this.data.activate ); }, - linkAnimAssetToState: function(stateName, asset) { - if (!this.data.animController) + /** + * @function + * @name pc.AnimComponent#linkAnimationToState + * @description Associates an animation with a state in the loaded state graph. If all states are linked and the pc.AnimComponent.activate value was set to true then the component will begin playing. + * @param {string} stateName - The name of the state that this animation should be associated with. + * @param {pc.AnimTrack} animTrack - The animation that will linked to this state and played whenever this state is active. + */ + linkAnimationToState: function(stateName, animTrack) { + if (!this.data.animController) { // #ifdef DEBUG - console.error('linkAnimAssetToState: Trying to link an anim asset to non existing state graph. Have you called loadStateMachineAsset?'); + console.error('linkAnimationToState: Trying to link an anim track before the state graph has been loaded. Have you called loadStateGraph?'); // #endif + return; + } + this.data.animController.linkAnimationToState(stateName, animTrack); + }, - var animTrack = asset.resource; + /** + * @function + * @name pc.AnimComponent#play + * @description Start playing the animation in the current state. + * @param {string} name - The name of the animation asset to begin playing. + */ + play: function (name) { + if (!this.enabled || !this.entity.enabled) { + return; + } - if(!animTrack) + if (!this.data.animController) { // #ifdef DEBUG - console.error('linkAnimAssetToState: No animation found for given assetName'); + console.error('Trying to play an animation when no animation state machine has been loaded. Have you called loadStateMachineAsset?'); // #endif + return; + } - this.data.animController.linkAnimationToState(stateName, animTrack); + this.data.animController.play(name); }, + /** + * @function + * @name pc.AnimComponent#reset + * @description Reset the animation component to it's initial state, including all parameters. The system will be paused. + */ + reset: function() { + if (this.data.animController) { + this.data.animController.reset(); + } + }, + + /** + * @function + * @name pc.AnimComponent#getActiveStateName + * @description Returns the currently active state name. + */ + getActiveStateName: function () { + return this.data.animController ? this.data.animController.getActiveStateName() : null; + }, + + /** + * @function + * @name pc.AnimComponent#getActiveStateProgress + * @description Returns the currently active states progress as a value normalised by the states animation duration. + */ + getActiveStateProgress: function () { + return this.data.animController ? this.data.animController.getActiveStateProgress() : null; + }, + + /** + * @function + * @name pc.AnimComponent#getSpeed + * @description Returns the current speed of the animation component system. + */ + getSpeed() { + return this.data.speed; + }, + + /** + * @function + * @name pc.AnimComponent#getSpeed + * @description Sets the current speed of the animation component system. All animations indivudual speeds will be multiplied by this value. + * @param {number} value - The speed value to multiply all animation playbacks by. + */ + setSpeed(value) { + if (typeof value === "number") { + this.data.speed = value; + return; + } + // #ifdef DEBUG + console.error('Anim component speed attribute must be a numeric value'); + // #endif + }, + + /** + * @function + * @name pc.AnimComponent#getFloat + * @description Returns a float parameter value by name. + * @param {string} name - The name of the float to return the value of. + */ getFloat: function(name) { if (this.data.animController) { return this.data.animController.getParameterValue(name, pc.ANIM_PARAMETER_FLOAT); } }, + /** + * @function + * @name pc.AnimComponent#setFloat + * @description Sets the value of a float parameter that was defined in the animation components state graph. + * @param {string} name - The name of the parameter to set. + * @param {number} value - The new float value to set this parameter to. + */ setFloat: function(name, value) { if (this.data.animController) { return this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_FLOAT, value); } }, + /** + * @function + * @name pc.AnimComponent#getInteger + * @description Returns an integer parameter value by name. + * @param {string} name - The name of the integer to return the value of. + */ getInteger: function(name) { if (this.data.animController) { return this.data.animController.getParameterValue(name, pc.ANIM_PARAMETER_INTEGER); } }, + /** + * @function + * @name pc.AnimComponent#setInteger + * @description Sets the value of an integer parameter that was defined in the animation components state graph. + * @param {string} name - The name of the parameter to set. + * @param {number} value - The new integer value to set this parameter to. + */ setInteger: function(name, value) { if (this.data.animController) { return this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_INTEGER, value); } }, + /** + * @function + * @name pc.AnimComponent#getBoolean + * @description Returns a boolean parameter value by name. + * @param {string} name - The name of the boolean to return the value of. + */ getBoolean: function(name) { if (this.data.animController) { return this.data.animController.getParameterValue(name, pc.ANIM_PARAMETER_BOOLEAN); } }, + /** + * @function + * @name pc.AnimComponent#setBoolean + * @description Sets the value of a boolean parameter that was defined in the animation components state graph. + * @param {string} name - The name of the parameter to set. + * @param {boolean} value - The new boolean value to set this parameter to. + */ setBoolean: function(name, value) { if (this.data.animController) { return this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_BOOLEAN, value); } }, + /** + * @function + * @name pc.AnimComponent#getTrigger + * @description Returns a trigger parameter value by name. + * @param {string} name - The name of the trigger to return the value of. + */ getTrigger: function(name) { if (this.data.animController) { return this.data.animController.getParameterValue(name, pc.ANIM_PARAMETER_TRIGGER); } }, + /** + * @function + * @name pc.AnimComponent#setTrigger + * @description Sets the value of a trigger parameter that was defined in the animation components state graph to true. + * @param {string} name - The name of the parameter to set. + */ setTrigger: function(name) { if (this.data.animController) { return this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_TRIGGER, true); } }, - resetTrigger: function(name) { - if (this.data.animController) { - return this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_TRIGGER, false); - } - }, - /** * @function - * @name pc.AnimComponent#play - * @description Start playing an animation. - * @param {string} name - The name of the animation asset to begin playing. + * @name pc.AnimComponent#setTrigger + * @description Resets the value of a trigger parameter that was defined in the animation components state graph to false. + * @param {string} name - The name of the parameter to set. */ - play: function (name) { - - if (!this.enabled || !this.entity.enabled) { - return; - } - - if (!this.data.animController) { - // #ifdef DEBUG - console.error('Trying to play an animation when no animation state machine has been loaded. Have you called loadStateMachineAsset?'); - // #endif - return; - } - - this.data.animController.play(name); - }, - - reset: function() { + resetTrigger: function(name) { if (this.data.animController) { - this.data.animController.reset(); + return this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_TRIGGER, false); } }, - - getActiveStateName: function () { - return this.data.animController ? this.data.animController.getActiveStateName() : null; - }, - - getActiveStateProgress: function () { - return this.data.animController ? this.data.animController.getActiveStateProgress() : null; - } }); return { diff --git a/src/framework/components/anim/property-locator.js b/src/framework/components/anim/property-locator.js index d6cf816d7ab..193168bec50 100644 --- a/src/framework/components/anim/property-locator.js +++ b/src/framework/components/anim/property-locator.js @@ -1,7 +1,23 @@ Object.assign(pc, function () { + /** + * @class + * @name pc.AnimPropertyLocator + * @classdesc The AnimProperyLocator encodes and decodes paths to properties in the scene hierarchy. + * @description Create a new AnimPropertyLocator. + */ var AnimPropertyLocator = function () { }; Object.assign(AnimPropertyLocator.prototype, { + /** + * @function + * @name pc.AnimPropertyLocator#encode + * @description Converts a locator array into its string version + * @param {array} locator - The property location in the scene defined as an array + * @returns {string} + * @example + * // returns 'spotLight/light/color.r' + * encode([['spotLight'], 'light', ['color','r']]) + */ encode: function(locator) { return pc.AnimBinder.joinPath([ pc.AnimBinder.joinPath(locator[0]), @@ -9,6 +25,16 @@ Object.assign(pc, function () { pc.AnimBinder.joinPath(locator[2]) ], '/'); }, + /** + * @function + * @name pc.AnimPropertyLocator#decode + * @description Converts a locator string into its array version + * @param {array} locator - The property location in the scene defined as a string + * @returns {array} + * @example + * // returns [['spotLight'], 'light', ['color','r']] + * encode('spotLight/light/color.r') + */ decode: function(locator) { var locatorSections = pc.AnimBinder.splitPath(locator, '/'); return [ diff --git a/src/framework/components/anim/system.js b/src/framework/components/anim/system.js index a7888b544f0..a758c072ba1 100644 --- a/src/framework/components/anim/system.js +++ b/src/framework/components/anim/system.js @@ -45,7 +45,7 @@ Object.assign(pc, function () { Object.assign(AnimComponentSystem.prototype, { initializeComponentData: function (component, data, properties) { - properties = ['activate', 'enabled', 'loop', 'speed']; + properties = ['activate', 'enabled', 'speed']; pc.ComponentSystem.prototype.initializeComponentData.call(this, component, data, properties); }, From 2433108d23f8a452fbf3f632c784e191b4da8321 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Wed, 29 Apr 2020 15:56:36 +0100 Subject: [PATCH 022/144] anim component system cleanup --- src/framework/components/anim/system.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/framework/components/anim/system.js b/src/framework/components/anim/system.js index a758c072ba1..31aabd7bc41 100644 --- a/src/framework/components/anim/system.js +++ b/src/framework/components/anim/system.js @@ -1,17 +1,8 @@ Object.assign(pc, function () { var _schema = [ 'enabled', - 'assets', 'speed', - 'loop', 'activate', - 'animations', - 'model', - 'prevAnim', - 'currAnim', - 'blending', - 'blendTimeRemaining', - 'playing' ]; /** @@ -26,7 +17,7 @@ Object.assign(pc, function () { pc.ComponentSystem.call(this, app); this.id = 'anim'; - this.description = "Specifies the animation assets that can run on the model specified by the Entity's model Component."; + this.description = "State based animation system that can animate the models and component properties of this entity and its children"; this.ComponentType = pc.AnimComponent; this.DataType = pc.AnimComponentData; @@ -54,7 +45,6 @@ Object.assign(pc, function () { this.addComponent(clone, {}); clone.animation.data.speed = entity.animation.speed; - clone.animation.data.loop = entity.animation.loop; clone.animation.data.activate = entity.animation.activate; clone.animation.data.enabled = entity.animation.enabled; From 92f8e5b8bfec5edf0943b691f7e6a13012d8a097 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Wed, 29 Apr 2020 17:13:57 +0100 Subject: [PATCH 023/144] anim component adjustments --- src/framework/components/anim/component.js | 4 ++-- src/framework/components/anim/data.js | 1 - src/framework/components/anim/system.js | 25 +++------------------- 3 files changed, 5 insertions(+), 25 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index c7140ee3f97..cfc033832c1 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -121,7 +121,7 @@ Object.assign(pc, function () { * @name pc.AnimComponent#getSpeed * @description Returns the current speed of the animation component system. */ - getSpeed() { + getSpeed: function() { return this.data.speed; }, @@ -131,7 +131,7 @@ Object.assign(pc, function () { * @description Sets the current speed of the animation component system. All animations indivudual speeds will be multiplied by this value. * @param {number} value - The speed value to multiply all animation playbacks by. */ - setSpeed(value) { + setSpeed: function(value) { if (typeof value === "number") { this.data.speed = value; return; diff --git a/src/framework/components/anim/data.js b/src/framework/components/anim/data.js index 5f8126a3190..74a50348924 100644 --- a/src/framework/components/anim/data.js +++ b/src/framework/components/anim/data.js @@ -2,7 +2,6 @@ Object.assign(pc, function () { var AnimComponentData = function () { // Serialized this.speed = 1.0; - this.loop = true; this.activate = true; this.enabled = true; diff --git a/src/framework/components/anim/system.js b/src/framework/components/anim/system.js index 31aabd7bc41..6c6d8b2db80 100644 --- a/src/framework/components/anim/system.js +++ b/src/framework/components/anim/system.js @@ -41,30 +41,11 @@ Object.assign(pc, function () { }, cloneComponent: function (entity, clone) { - var key; this.addComponent(clone, {}); - clone.animation.data.speed = entity.animation.speed; - clone.animation.data.activate = entity.animation.activate; - clone.animation.data.enabled = entity.animation.enabled; - - var clonedAnims = { }; - var animations = entity.animation.animations; - for (key in animations) { - if (animations.hasOwnProperty(key)) { - clonedAnims[key] = animations[key]; - } - } - clone.animation.animations = clonedAnims; - - var clonedAnimsIndex = { }; - var animationsIndex = entity.animation.animationsIndex; - for (key in animationsIndex) { - if (animationsIndex.hasOwnProperty(key)) { - clonedAnimsIndex[key] = animationsIndex[key]; - } - } - clone.animation.animationsIndex = clonedAnimsIndex; + clone.anim.data.speed = entity.anim.speed; + clone.anim.data.activate = entity.anim.activate; + clone.anim.data.enabled = entity.anim.enabled; }, onBeforeRemove: function (entity, component) { From 726b98b06b7de5cd35ad4a7c9cfdbc068cf29ab7 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Thu, 30 Apr 2020 12:58:07 +0100 Subject: [PATCH 024/144] update anim component properties --- src/framework/components/anim/component.js | 68 ++++++++++++++-------- 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index cfc033832c1..89ede4ca2ba 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -116,31 +116,6 @@ Object.assign(pc, function () { return this.data.animController ? this.data.animController.getActiveStateProgress() : null; }, - /** - * @function - * @name pc.AnimComponent#getSpeed - * @description Returns the current speed of the animation component system. - */ - getSpeed: function() { - return this.data.speed; - }, - - /** - * @function - * @name pc.AnimComponent#getSpeed - * @description Sets the current speed of the animation component system. All animations indivudual speeds will be multiplied by this value. - * @param {number} value - The speed value to multiply all animation playbacks by. - */ - setSpeed: function(value) { - if (typeof value === "number") { - this.data.speed = value; - return; - } - // #ifdef DEBUG - console.error('Anim component speed attribute must be a numeric value'); - // #endif - }, - /** * @function * @name pc.AnimComponent#getFloat @@ -253,6 +228,49 @@ Object.assign(pc, function () { }, }); + Object.defineProperties(AnimComponent.prototype, { + /** + * @property + * @name pc.AnimComponent#activeState + * @description Returns the currently active state name. + */ + activeState: { + get: function() { + if (this.data.animController) { + return this.data.animController.getActiveStateName(); + } + } + }, + /** + * @property + * @name pc.AnimComponent#activeStateProgress + * @description Returns the currently active states progress as a value normalised by the states animation duration. Looped animations will return values greater than 1. + */ + activeStateProgress: { + get: function() { + if (this.data.animController) { + return this.data.animController.getActiveStateProgress(); + } + } + } + + // speed: { + // get: function () { + // return this.data.speed; + // }, + // set: function (value) { + // console.log('hello'); + // if (typeof value === "number") { + // this.data.speed = value; + // return; + // } + // // #ifdef DEBUG + // console.error('Anim component speed attribute must be a numeric value'); + // // #endif + // } + // } + }); + return { AnimComponent: AnimComponent }; From d3cf7ca424ab8ca4da375b84e6f5260a1e5a3ec7 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Thu, 30 Apr 2020 12:58:53 +0100 Subject: [PATCH 025/144] remove unused code --- src/framework/components/anim/component.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 89ede4ca2ba..b7a8ca9084c 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -253,22 +253,6 @@ Object.assign(pc, function () { } } } - - // speed: { - // get: function () { - // return this.data.speed; - // }, - // set: function (value) { - // console.log('hello'); - // if (typeof value === "number") { - // this.data.speed = value; - // return; - // } - // // #ifdef DEBUG - // console.error('Anim component speed attribute must be a numeric value'); - // // #endif - // } - // } }); return { From 94cabe9fe1b330c62d8d5df583a705a9fe417f3f Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Thu, 30 Apr 2020 12:59:56 +0100 Subject: [PATCH 026/144] remove functions to get active state name and progress --- src/framework/components/anim/component.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index b7a8ca9084c..085edc5d1d3 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -98,24 +98,6 @@ Object.assign(pc, function () { } }, - /** - * @function - * @name pc.AnimComponent#getActiveStateName - * @description Returns the currently active state name. - */ - getActiveStateName: function () { - return this.data.animController ? this.data.animController.getActiveStateName() : null; - }, - - /** - * @function - * @name pc.AnimComponent#getActiveStateProgress - * @description Returns the currently active states progress as a value normalised by the states animation duration. - */ - getActiveStateProgress: function () { - return this.data.animController ? this.data.animController.getActiveStateProgress() : null; - }, - /** * @function * @name pc.AnimComponent#getFloat From 2f8545c2a0c249726fb24ba2f37308540c7cdecb Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Fri, 1 May 2020 16:15:57 +0100 Subject: [PATCH 027/144] update anim component to support morph targets --- src/anim/anim.js | 3 ++ src/framework/components/anim/binder.js | 49 ++++++------------------- src/resources/parser/glb-parser.js | 1 + 3 files changed, 15 insertions(+), 38 deletions(-) diff --git a/src/anim/anim.js b/src/anim/anim.js index 5ac8e382f6a..d5e7d60bbc2 100644 --- a/src/anim/anim.js +++ b/src/anim/anim.js @@ -743,6 +743,9 @@ Object.assign(pc, function () { unresolve: function (path) { var pathSections = this.propertyLocator.decode(path); + if (pathSections[1] !== 'graph') + return; + var node = this.nodes[pathSections[0][0]]; node.count--; diff --git a/src/framework/components/anim/binder.js b/src/framework/components/anim/binder.js index b2cefe36b82..26c03fdabce 100644 --- a/src/framework/components/anim/binder.js +++ b/src/framework/components/anim/binder.js @@ -2,28 +2,17 @@ Object.assign(pc, function () { var AnimComponentBinder = function (animComponent, graph) { this.animComponent = animComponent; - this.propertyLocator = new pc.AnimPropertyLocator(); if (graph) { - var nodes = { }; - - // cache node names so we can quickly resolve animation paths - var flatten = function (node) { - nodes[node.name] = { - node: node, - count: 0 - }; - for (var i = 0; i < node.children.length; ++i) { - flatten(node.children[i]); - } - }; - flatten(graph); - - this.nodes = nodes; // map of node name -> { node, count } - this.activeNodes = []; // list of active nodes + pc.DefaultAnimBinder.call(this, graph); + } else { + this.propertyLocator = new pc.AnimPropertyLocator(); } }; + AnimComponentBinder.prototype = Object.create(pc.DefaultAnimBinder.prototype); + AnimComponentBinder.prototype.constructor = AnimComponentBinder; + Object.assign(AnimComponentBinder.prototype, { resolve: function(path) { var pathSections = this.propertyLocator.decode(path); @@ -47,7 +36,7 @@ Object.assign(pc, function () { propertyComponent = this.nodes[entityHierarchy[0]].node; break; default: - entity.findComponent(component); + propertyComponent = entity.findComponent(component); if (!propertyComponent) return null; } @@ -55,26 +44,6 @@ Object.assign(pc, function () { return this._createAnimTargetForProperty(propertyComponent, propertyHierarchy); }, - unresolve: function (path) { - var pathSections = this.propertyLocator.decode(path); - if (pathSections[1] !== 'graph') - return; - - // get the path parts. we expect parts to have structure nodeName.[translation|rotation|scale] - var node = pathSections[0][0]; - - node.count--; - if (node.count === 0) { - var activeNodes = this.activeNodes; - var i = activeNodes.indexOf(node.node); // :( - var len = activeNodes.length; - if (i < len - 1) { - activeNodes[i] = activeNodes[len - 1]; - } - activeNodes.pop(); - } - }, - update: function (deltaTime) { // flag active nodes as dirty var activeNodes = this.activeNodes; @@ -183,6 +152,10 @@ Object.assign(pc, function () { _createAnimTargetForProperty: function(propertyComponent, propertyHierarchy) { + if (this.handlers && this.handlers[propertyHierarchy[0]]) { + return this.handlers[propertyHierarchy[0]](propertyComponent); + } + var property = this._getProperty(propertyComponent, propertyHierarchy); if (typeof property === 'undefined') diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 85070d9e078..7b050d730b9 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1066,6 +1066,7 @@ Object.assign(pc, function () { 'translation': 'localPosition', 'rotation': 'localRotation', 'scale': 'localScale', + 'weights': 'weights' }; // convert anim channels From 7f256a32c261ac4f8dc1bc2bd608ff5bee57063e Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Fri, 1 May 2020 16:23:32 +0100 Subject: [PATCH 028/144] fix anim test --- tests/anim/test_anim.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/anim/test_anim.js b/tests/anim/test_anim.js index f440814e73c..caa5197c0ad 100644 --- a/tests/anim/test_anim.js +++ b/tests/anim/test_anim.js @@ -1,10 +1,10 @@ -describe("pc.AnimController", function () { +describe("pc.AnimEvaluator", function () { - it("AnimController: looping", function () { + it("AnimEvaluator: looping", function () { // create curve var keys = new pc.AnimData(1, [0, 1, 2]); var translations = new pc.AnimData(3, [0, 0, 0, 1, 0, 0, 1, 0, 1]); - var curve = new pc.AnimCurve(["child1.translation"], 0, 0, pc.INTERPOLATION_LINEAR); + var curve = new pc.AnimCurve(["child1/graph/translation"], 0, 0, pc.INTERPOLATION_LINEAR); // construct the animation track var track = new pc.AnimTrack("test track", 2, [keys], [translations], [curve]); @@ -19,31 +19,31 @@ describe("pc.AnimController", function () { parent.addChild(child1); child1.addChild(child2); - // construct the animation controller - var animController = new pc.AnimController(new pc.DefaultAnimBinder(parent)); - animController.addClip(clip); + // construct the animation evaluator + var animEvaluator = new pc.AnimEvaluator(new pc.DefaultAnimBinder(parent)); + animEvaluator.addClip(clip); // check initial state - animController.update(0); + animEvaluator.update(0); equal(clip.time, 0); equal(child1.localPosition.x, 0); equal(child1.localPosition.y, 0); equal(child1.localPosition.z, 0); - animController.update(0.5); + animEvaluator.update(0.5); equal(clip.time, 0.5); equal(child1.localPosition.x, 0.5); equal(child1.localPosition.y, 0); equal(child1.localPosition.z, 0); - animController.update(1.0); + animEvaluator.update(1.0); equal(clip.time, 1.5); equal(child1.localPosition.x, 1.0); equal(child1.localPosition.y, 0); equal(child1.localPosition.z, 0.5); // checked looped state (current time 0.5) - animController.update(1.0); + animEvaluator.update(1.0); equal(clip.time, 0.5); equal(child1.localPosition.x, 0.5); equal(child1.localPosition.y, 0); From 4257e1cb8c74cbb39eac28205995fb3149b51717 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Fri, 1 May 2020 16:36:00 +0100 Subject: [PATCH 029/144] fix jsdocs --- src/framework/components/anim/component.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 085edc5d1d3..8bfcd1a9848 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -54,7 +54,7 @@ Object.assign(pc, function () { * @name pc.AnimComponent#linkAnimationToState * @description Associates an animation with a state in the loaded state graph. If all states are linked and the pc.AnimComponent.activate value was set to true then the component will begin playing. * @param {string} stateName - The name of the state that this animation should be associated with. - * @param {pc.AnimTrack} animTrack - The animation that will linked to this state and played whenever this state is active. + * @param {object} animTrack - The animation that will linked to this state and played whenever this state is active. */ linkAnimationToState: function(stateName, animTrack) { if (!this.data.animController) { @@ -212,7 +212,7 @@ Object.assign(pc, function () { Object.defineProperties(AnimComponent.prototype, { /** - * @property + * @property {string} activeState * @name pc.AnimComponent#activeState * @description Returns the currently active state name. */ @@ -224,7 +224,7 @@ Object.assign(pc, function () { } }, /** - * @property + * @property {number} activeStateProgress * @name pc.AnimComponent#activeStateProgress * @description Returns the currently active states progress as a value normalised by the states animation duration. Looped animations will return values greater than 1. */ From d0543a1136d85b35a727fa96562bcc6b95394f96 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Fri, 1 May 2020 17:28:36 +0100 Subject: [PATCH 030/144] lint fix --- src/anim/anim.js | 3 +- src/framework/components/anim/binder.js | 58 +++--- src/framework/components/anim/component.js | 52 ++--- src/framework/components/anim/controller.js | 197 ++++++++++-------- .../components/anim/property-locator.js | 20 +- src/framework/components/anim/system.js | 2 +- src/resources/animation-clip.js | 10 +- src/resources/animation-state-graph.js | 2 +- 8 files changed, 180 insertions(+), 164 deletions(-) diff --git a/src/anim/anim.js b/src/anim/anim.js index d5e7d60bbc2..41cd69b2b2e 100644 --- a/src/anim/anim.js +++ b/src/anim/anim.js @@ -568,7 +568,7 @@ Object.assign(pc, function () { AnimBinder.joinPath = function (pathSegments, character) { character = character || '.'; var escape = function (string) { - return string.replace(/\\/g, '\\\\').replace(new RegExp('\\' + character, 'g'), '\\' + character) + return string.replace(/\\/g, '\\\\').replace(new RegExp('\\' + character, 'g'), '\\' + character); }; return pathSegments.map(escape).join(character); }; @@ -758,7 +758,6 @@ Object.assign(pc, function () { } activeNodes.pop(); } - activeNodes.pop(); }, // flag animating nodes as dirty diff --git a/src/framework/components/anim/binder.js b/src/framework/components/anim/binder.js index 26c03fdabce..dc7f89e931d 100644 --- a/src/framework/components/anim/binder.js +++ b/src/framework/components/anim/binder.js @@ -14,7 +14,7 @@ Object.assign(pc, function () { AnimComponentBinder.prototype.constructor = AnimComponentBinder; Object.assign(AnimComponentBinder.prototype, { - resolve: function(path) { + resolve: function (path) { var pathSections = this.propertyLocator.decode(path); var entityHierarchy = pathSections[0]; @@ -22,13 +22,13 @@ Object.assign(pc, function () { var propertyHierarchy = pathSections[2]; var entity = this._getEntityFromHierarchy(entityHierarchy); - + if (!entity) return null; var propertyComponent; - switch(component) { + switch (component) { case 'entity': propertyComponent = entity; break; @@ -54,7 +54,7 @@ Object.assign(pc, function () { } }, - _getEntityFromHierarchy: function(entityHierarchy) { + _getEntityFromHierarchy: function (entityHierarchy) { if (!this.animComponent.entity.name === entityHierarchy[0]) return null; @@ -63,7 +63,7 @@ Object.assign(pc, function () { var entityChildren = currEntity.getChildren(); var child; for (var j = 0; j < entityChildren.length; j++) { - if (entityChildren[j].name === entityHierarchy[i+1]) + if (entityChildren[j].name === entityHierarchy[i + 1]) child = entityChildren[j]; } if (child) @@ -74,30 +74,30 @@ Object.assign(pc, function () { return currEntity; }, - _floatSetter: function(propertyComponent, propertyHierarchy) { - var setter = function(values) { + _floatSetter: function (propertyComponent, propertyHierarchy) { + var setter = function (values) { this._setProperty(propertyComponent, propertyHierarchy, values[0]); }; return setter.bind(this); }, - _booleanSetter: function(propertyComponent, propertyHierarchy) { - var setter = function(values) { + _booleanSetter: function (propertyComponent, propertyHierarchy) { + var setter = function (values) { this._setProperty(propertyComponent, propertyHierarchy, !!values[0]); }; return setter.bind(this); }, - _colorSetter: function(propertyComponent, propertyHierarchy) { + _colorSetter: function (propertyComponent, propertyHierarchy) { var colorKeys = ['r', 'g', 'b', 'a']; - var setter = function(values) { + var setter = function (values) { for (var i = 0; i < values.length; i++) { this._setProperty(propertyComponent, propertyHierarchy.concat(colorKeys[i]), values[i]); } }; return setter.bind(this); }, - _vecSetter: function(propertyComponent, propertyHierarchy) { + _vecSetter: function (propertyComponent, propertyHierarchy) { var vectorKeys = ['x', 'y', 'z', 'w']; - var setter = function(values) { + var setter = function (values) { for (var i = 0; i < values.length; i++) { this._setProperty(propertyComponent, propertyHierarchy.concat(vectorKeys[i]), values[i]); } @@ -105,16 +105,16 @@ Object.assign(pc, function () { return setter.bind(this); }, - _getProperty: function(propertyComponent, propertyHierarchy) { + _getProperty: function (propertyComponent, propertyHierarchy) { if (propertyHierarchy.length === 1) { return propertyComponent[propertyHierarchy[0]]; - } else { - var propertyObject = propertyComponent[propertyHierarchy[0]]; - return propertyObject[propertyHierarchy[1]]; } + var propertyObject = propertyComponent[propertyHierarchy[0]]; + return propertyObject[propertyHierarchy[1]]; + }, - _setProperty: function(propertyComponent, propertyHierarchy, value) { + _setProperty: function (propertyComponent, propertyHierarchy, value) { if (propertyHierarchy.length === 1) { propertyComponent[propertyHierarchy[0]] = value; } else { @@ -124,14 +124,14 @@ Object.assign(pc, function () { } }, - _getObjectPropertyType: function(property) { + _getObjectPropertyType: function (property) { if (!property.constructor) return undefined; - + return property.constructor.name; }, - _getEntityProperty: function(propertyHierarchy) { + _getEntityProperty: function (propertyHierarchy) { var entityProperties = [ 'localScale', 'localPosition', @@ -150,7 +150,7 @@ Object.assign(pc, function () { return entityProperty; }, - _createAnimTargetForProperty: function(propertyComponent, propertyHierarchy) { + _createAnimTargetForProperty: function (propertyComponent, propertyHierarchy) { if (this.handlers && this.handlers[propertyHierarchy[0]]) { return this.handlers[propertyHierarchy[0]](propertyComponent); @@ -169,13 +169,11 @@ Object.assign(pc, function () { setter = this._floatSetter(propertyComponent, propertyHierarchy); animDataType = 'vector'; animDataComponents = 1; - } - else if (typeof property === 'boolean') { + } else if (typeof property === 'boolean') { setter = this._booleanSetter(propertyComponent, propertyHierarchy); animDataType = 'vector'; animDataComponents = 1; - } - else if (typeof property === 'object') { + } else if (typeof property === 'object') { switch (this._getObjectPropertyType(property)) { case 'Vec2': setter = this._vecSetter(propertyComponent, propertyHierarchy); @@ -210,23 +208,23 @@ Object.assign(pc, function () { // for entity properties we cannot just set their values, we must also call the values setter function. var entityProperty = this._getEntityProperty(propertyHierarchy); if (entityProperty) { - var entityPropertySetter = function(values) { + var entityPropertySetter = function (values) { // first set new values on the property as before setter(values); // create the function name of the entity properties setter var entityPropertySetterFunctionName = pc.string.format( 'set{0}{1}', - entityProperty.substring(0,1).toUpperCase(), + entityProperty.substring(0, 1).toUpperCase(), entityProperty.substring(1) ); // call the setter function for entities updated property using the newly set property value propertyComponent[entityPropertySetterFunctionName](this._getProperty(propertyComponent, [entityProperty])); }; return new pc.AnimTarget(entityPropertySetter.bind(this), animDataType, animDataComponents); - } else { - return new pc.AnimTarget(setter, animDataType, animDataComponents); } + return new pc.AnimTarget(setter, animDataType, animDataComponents); + } }); diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 8bfcd1a9848..af75d585343 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -25,7 +25,7 @@ Object.assign(pc, function () { * @description Loads a state graph asset resource into the component. Then initialises the components animation controller. * @param {object} stateGraph - The state graph asset to load into the component. Contains the states, transitions and parameters used to define a complete animation controller. */ - loadStateGraph: function(stateGraph) { + loadStateGraph: function (stateGraph) { var data = this.data; var graph; @@ -56,7 +56,7 @@ Object.assign(pc, function () { * @param {string} stateName - The name of the state that this animation should be associated with. * @param {object} animTrack - The animation that will linked to this state and played whenever this state is active. */ - linkAnimationToState: function(stateName, animTrack) { + linkAnimationToState: function (stateName, animTrack) { if (!this.data.animController) { // #ifdef DEBUG console.error('linkAnimationToState: Trying to link an anim track before the state graph has been loaded. Have you called loadStateGraph?'); @@ -92,7 +92,7 @@ Object.assign(pc, function () { * @name pc.AnimComponent#reset * @description Reset the animation component to it's initial state, including all parameters. The system will be paused. */ - reset: function() { + reset: function () { if (this.data.animController) { this.data.animController.reset(); } @@ -103,8 +103,9 @@ Object.assign(pc, function () { * @name pc.AnimComponent#getFloat * @description Returns a float parameter value by name. * @param {string} name - The name of the float to return the value of. + * @returns {number} A float */ - getFloat: function(name) { + getFloat: function (name) { if (this.data.animController) { return this.data.animController.getParameterValue(name, pc.ANIM_PARAMETER_FLOAT); } @@ -117,9 +118,9 @@ Object.assign(pc, function () { * @param {string} name - The name of the parameter to set. * @param {number} value - The new float value to set this parameter to. */ - setFloat: function(name, value) { + setFloat: function (name, value) { if (this.data.animController) { - return this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_FLOAT, value); + this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_FLOAT, value); } }, @@ -128,8 +129,9 @@ Object.assign(pc, function () { * @name pc.AnimComponent#getInteger * @description Returns an integer parameter value by name. * @param {string} name - The name of the integer to return the value of. + * @returns {number} An integer */ - getInteger: function(name) { + getInteger: function (name) { if (this.data.animController) { return this.data.animController.getParameterValue(name, pc.ANIM_PARAMETER_INTEGER); } @@ -142,9 +144,9 @@ Object.assign(pc, function () { * @param {string} name - The name of the parameter to set. * @param {number} value - The new integer value to set this parameter to. */ - setInteger: function(name, value) { + setInteger: function (name, value) { if (this.data.animController) { - return this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_INTEGER, value); + this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_INTEGER, value); } }, @@ -153,8 +155,9 @@ Object.assign(pc, function () { * @name pc.AnimComponent#getBoolean * @description Returns a boolean parameter value by name. * @param {string} name - The name of the boolean to return the value of. + * @returns {boolean} A boolean */ - getBoolean: function(name) { + getBoolean: function (name) { if (this.data.animController) { return this.data.animController.getParameterValue(name, pc.ANIM_PARAMETER_BOOLEAN); } @@ -167,9 +170,9 @@ Object.assign(pc, function () { * @param {string} name - The name of the parameter to set. * @param {boolean} value - The new boolean value to set this parameter to. */ - setBoolean: function(name, value) { + setBoolean: function (name, value) { if (this.data.animController) { - return this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_BOOLEAN, value); + this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_BOOLEAN, value); } }, @@ -178,8 +181,9 @@ Object.assign(pc, function () { * @name pc.AnimComponent#getTrigger * @description Returns a trigger parameter value by name. * @param {string} name - The name of the trigger to return the value of. + * @returns {boolean} A boolean */ - getTrigger: function(name) { + getTrigger: function (name) { if (this.data.animController) { return this.data.animController.getParameterValue(name, pc.ANIM_PARAMETER_TRIGGER); } @@ -191,9 +195,9 @@ Object.assign(pc, function () { * @description Sets the value of a trigger parameter that was defined in the animation components state graph to true. * @param {string} name - The name of the parameter to set. */ - setTrigger: function(name) { + setTrigger: function (name) { if (this.data.animController) { - return this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_TRIGGER, true); + this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_TRIGGER, true); } }, @@ -203,36 +207,36 @@ Object.assign(pc, function () { * @description Resets the value of a trigger parameter that was defined in the animation components state graph to false. * @param {string} name - The name of the parameter to set. */ - resetTrigger: function(name) { + resetTrigger: function (name) { if (this.data.animController) { - return this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_TRIGGER, false); + this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_TRIGGER, false); } - }, + } }); Object.defineProperties(AnimComponent.prototype, { /** - * @property {string} activeState * @name pc.AnimComponent#activeState - * @description Returns the currently active state name. + * @property {string} activeState - Returns the currently active state name. */ activeState: { - get: function() { + get: function () { if (this.data.animController) { return this.data.animController.getActiveStateName(); } + return null; } }, /** - * @property {number} activeStateProgress * @name pc.AnimComponent#activeStateProgress - * @description Returns the currently active states progress as a value normalised by the states animation duration. Looped animations will return values greater than 1. + * @property {number} activeStateProgress - Returns the currently active states progress as a value normalised by the states animation duration. Looped animations will return values greater than 1. */ activeStateProgress: { - get: function() { + get: function () { if (this.data.animController) { return this.data.animController.getActiveStateProgress(); } + return null; } } }); diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index f0374694439..19fdc42514b 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -22,7 +22,7 @@ Object.assign(pc, function () { ANIM_PARAMETER_INTEGER: 'Integer', ANIM_PARAMETER_FLOAT: 'Float', ANIM_PARAMETER_BOOLEAN: 'Boolean', - ANIM_PARAMETER_TRIGGER: 'Trigger', + ANIM_PARAMETER_TRIGGER: 'Trigger' }; var ANIM_STATE_START = 'ANIM_STATE_START'; @@ -35,25 +35,27 @@ Object.assign(pc, function () { }; Object.assign(AnimState.prototype, { - isPlayable: function() { + isPlayable: function () { return (this.animations.length > 0 || this.name === ANIM_STATE_START || this.name === ANIM_STATE_END); }, - isLooping: function() { + isLooping: function () { return true; }, - getTotalWeight: function() { + getTotalWeight: function () { var sum = 0; - for (var i = 0; i < this.animations.length; i++) { - sum = sum + this.animations[i].weight; + var i; + for (i = 0; i < this.animations.length; i++) { + sum += this.animations[i].weight; } return sum; }, - getTimelineDuration: function() { + getTimelineDuration: function () { var duration = 0; - for (var i = 0; i < this.animations.length; i++) { + var i; + for (i = 0; i < this.animations.length; i++) { var animation = this.animations[i]; if (animation.animTrack.duration > duration) { duration = animation.animTrack.duration > duration; @@ -76,12 +78,13 @@ Object.assign(pc, function () { }; Object.assign(AnimTransition.prototype, { - hasConditionsMet: function() { + hasConditionsMet: function () { var conditionsMet = true; - for (var i = 0; i < this.conditions.length; i++) { + var i; + for (i = 0; i < this.conditions.length; i++) { var condition = this.conditions[i]; var parameter = this.controller._getParameter(condition.parameterName); - switch(condition.predicate) { + switch (condition.predicate) { case ANIM_TRANSITION_PREDICATE_GREATER_THAN: conditionsMet = conditionsMet && parameter.value > condition.value; break; @@ -106,7 +109,7 @@ Object.assign(pc, function () { } return conditionsMet; }, - hasExitTime: function() { + hasExitTime: function () { return !!this.exitTime; } }); @@ -114,13 +117,14 @@ Object.assign(pc, function () { var AnimController = function (animEvaluator, states, transitions, parameters, activate) { this.animEvaluator = animEvaluator; this.states = {}; - for (var i = 0; i < states.length; i++) { + var i; + for (i = 0; i < states.length; i++) { this.states[states[i].name] = new AnimState( states[i].name, states[i].speed ); } - this.transitions = transitions.map((function(transition) { + this.transitions = transitions.map(function (transition) { return new AnimTransition( this, transition.from, @@ -132,7 +136,7 @@ Object.assign(pc, function () { transition.transitionOffset, transition.interruptionSource ); - }).bind(this)); + }.bind(this)); this.findTransitionsFromStateCache = {}; this.findTransitionsBetweenStatesCache = {}; this.parameters = parameters; @@ -141,7 +145,7 @@ Object.assign(pc, function () { this.activeStateName = ANIM_STATE_START; this.playing = false; this.activate = activate; - + this.currTransitionTime = 1.0; this.totalTransitionTime = 1.0; this.isTransitioning = false; @@ -153,56 +157,56 @@ Object.assign(pc, function () { }; Object.assign(AnimController.prototype, { - _getState: function(stateName) { + _getState: function (stateName) { return this.states[stateName]; }, - _setState: function(stateName, state) { + _setState: function (stateName, state) { this.states[stateName] = state; }, - _getActiveState: function() { + _getActiveState: function () { return this._getState(this.activeStateName); }, - _setActiveState: function(stateName) { - return this.activeStateName = stateName; + _setActiveState: function (stateName) { + this.activeStateName = stateName; }, - _getPreviousState: function() { + _getPreviousState: function () { return this._getState(this.previousStateName); }, - _setPreviousState: function(stateName) { - return this.previousStateName = stateName; + _setPreviousState: function (stateName) { + this.previousStateName = stateName; }, - _findTransitionsFromState: function(stateName) { + _findTransitionsFromState: function (stateName) { var transitions = this.findTransitionsFromStateCache[stateName]; if (!transitions) { - transitions = this.transitions.filter((function(transition) { + transitions = this.transitions.filter(function (transition) { return transition.from === stateName; - }).bind(this)); + }); // sort transitions in priority order - transitions.sort(function(a, b) { + transitions.sort(function (a, b) { return a.priority < b.priority; }); - + this.findTransitionsFromStateCache[stateName] = transitions; } return transitions; }, - _findTransitionsBetweenStates: function(sourceStateName, destinationStateName) { + _findTransitionsBetweenStates: function (sourceStateName, destinationStateName) { var transitions = this.findTransitionsBetweenStatesCache[sourceStateName + '->' + destinationStateName]; if (!transitions) { - transitions = this.transitions.filter((function(transition) { + transitions = this.transitions.filter(function (transition) { return transition.from === sourceStateName && transition.to === destinationStateName; - }).bind(this)); + }); // sort transitions in priority order - transitions.sort(function(a, b) { + transitions.sort(function (a, b) { return a.priority < b.priority; }); @@ -212,7 +216,7 @@ Object.assign(pc, function () { return transitions; }, - _findTransition: function(from, to) { + _findTransition: function (from, to) { var transitions = []; // find transitions that include the required source and destination states @@ -222,17 +226,21 @@ Object.assign(pc, function () { if (!this.isTransitioning) { transitions = transitions.concat(this._findTransitionsFromState(this.activeStateName)); } else { - switch(this.transitionInterruptionSource) { + switch (this.transitionInterruptionSource) { case ANIM_INTERRUPTION_SOURCE_PREV_STATE: transitions = transitions.concat(this._findTransitionsFromState(this.previousStateName)); + break; case ANIM_INTERRUPTION_SOURCE_NEXT_STATE: transitions = transitions.concat(this._findTransitionsFromState(this.activeStateName)); + break; case ANIM_INTERRUPTION_SOURCE_PREV_STATE_NEXT_STATE: transitions = transitions.concat(this._findTransitionsFromState(this.previousStateName)); transitions = transitions.concat(this._findTransitionsFromState(this.activeStateName)); + break; case ANIM_INTERRUPTION_SOURCE_NEXT_STATE_PREV_STATE: transitions = transitions.concat(this._findTransitionsFromState(this.activeStateName)); transitions = transitions.concat(this._findTransitionsFromState(this.previousStateName)); + break; case ANIM_INTERRUPTION_SOURCE_NONE: default: } @@ -240,15 +248,15 @@ Object.assign(pc, function () { } // filter out transitions that don't have their conditions met - transitions = transitions.filter((function(transition) { + transitions = transitions.filter(function (transition) { // when an exit time is present, we should only exit if it falls within the current frame delta time if (transition.hasExitTime()) { var progressBefore = this.getActiveStateProgress(true); var progress = this.getActiveStateProgress(); // when the exit time is smaller than 1 and the state is looping, we should check for an exit each loop if (transition.exitTime < 1.0 && this._getActiveState().isLooping()) { - progressBefore = progressBefore - Math.floor(progressBefore); - progress = progress - Math.floor(progress); + progressBefore -= Math.floor(progressBefore); + progress -= Math.floor(progress); } // return false if exit time isn't within the frames delta time if (!(transition.exitTime > progressBefore && transition.exitTime <= progress)) { @@ -256,40 +264,42 @@ Object.assign(pc, function () { } } return transition.hasConditionsMet(); - }).bind(this)); + }.bind(this)); if (transitions.length > 0) { return transitions[0]; - } else { - return null; } + return null; + }, - _updateStateFromTransition: function(transition) { + _updateStateFromTransition: function (transition) { + var i; + var j; this._setPreviousState(this.activeStateName); this._setActiveState(transition.to); - var triggers = transition.conditions.filter((function(condition) { + var triggers = transition.conditions.filter(function (condition) { var parameter = this._getParameter(condition.parameterName); return parameter.type === ANIM_PARAMETER_TRIGGER; - }).bind(this)); - for (var i = 0; i < triggers.length; i++) { + }.bind(this)); + for (i = 0; i < triggers.length; i++) { this.setParameterValue(triggers[i].parameterName, ANIM_PARAMETER_TRIGGER, false); } if (this.isTransitioning) { var interpolatedTime = this.currTransitionTime / this.totalTransitionTime; - for (var i = 0; i < this.transitionPreviousStates.length; i++) { - this.transitionPreviousStates[i].weight = this.transitionPreviousStates[i].weight * (1.0 - interpolatedTime); + for (i = 0; i < this.transitionPreviousStates.length; i++) { + this.transitionPreviousStates[i].weight *= (1.0 - interpolatedTime); var state = this._getState(this.transitionPreviousStates[i].name); - for (var j = 0; j < state.animations.length; j++) { + for (j = 0; j < state.animations.length; j++) { var animation = state.animations[j]; this.animEvaluator.findClip(animation.name).pause(); } } this.transitionPreviousStates.push({ name: this.previousStateName, - weight: interpolatedTime + weight: interpolatedTime }); } else { this.transitionPreviousStates.push({ @@ -308,7 +318,7 @@ Object.assign(pc, function () { var hasTransitionOffset = transition.transitionOffset && transition.transitionOffset > 0.0 && transition.transitionOffset < 1.0; var activeState = this._getActiveState(); - for (var i = 0; i < activeState.animations.length; i++) { + for (i = 0; i < activeState.animations.length; i++) { var clip = this.animEvaluator.findClip(activeState.animations[i].name); if (!clip) { clip = new pc.AnimClip(activeState.animations[i].animTrack, 0, activeState.speed, true, true); @@ -338,7 +348,7 @@ Object.assign(pc, function () { this.timeInStateBefore = timeInStateBefore; }, - _transitionToState: function(newStateName) { + _transitionToState: function (newStateName) { if (newStateName === this.activeStateName) { return; } @@ -355,7 +365,7 @@ Object.assign(pc, function () { this._updateStateFromTransition(transition); }, - linkAnimationToState: function(stateName, animTrack) { + linkAnimationToState: function (stateName, animTrack) { var state = this._getState(stateName); if (!state) { // #ifdef DEBUG @@ -379,13 +389,14 @@ Object.assign(pc, function () { if (!this.playing && this.activate && this.isPlayable()) { this.play(); - return; + } }, - isPlayable: function() { + isPlayable: function () { var playable = true; - for (var i = 0; i < this.states.length; i++) { + var i; + for (i = 0; i < this.states.length; i++) { if (!this.states[i].isPlayable()) { playable = false; } @@ -393,18 +404,18 @@ Object.assign(pc, function () { return playable; }, - play: function(stateName) { + play: function (stateName) { if (stateName) { this._transitionToState(stateName); } this.playing = true; }, - reset: function() { + reset: function () { this.previousStateName = null; this.activeStateName = ANIM_STATE_START; this.playing = false; - + this.currTransitionTime = 1.0; this.totalTransitionTime = 1.0; this.isTransitioning = false; @@ -416,11 +427,15 @@ Object.assign(pc, function () { this.parameters = Object.assign({}, this.initialParameters); }, - - update: function(dt) { + + update: function (dt) { if (this.playing) { + var i; + var j; + var state; + var animation; this.timeInStateBefore = this.timeInState; - this.timeInState = this.timeInState + dt; + this.timeInState += dt; var transition = this._findTransition(this.activeStateName); if (transition) @@ -430,10 +445,10 @@ Object.assign(pc, function () { if (this.currTransitionTime >= this.totalTransitionTime) { this.isTransitioning = false; - for (var i = 0; i < this.transitionPreviousStates.length; i++) { - var state = this._getState(this.transitionPreviousStates[i].name); - for (var j = 0; j < state.animations.length; j++) { - var animation = state.animations[j]; + for (i = 0; i < this.transitionPreviousStates.length; i++) { + state = this._getState(this.transitionPreviousStates[i].name); + for (j = 0; j < state.animations.length; j++) { + animation = state.animations[j]; var clip = this.animEvaluator.findClip(animation.name); clip.pause(); clip.blendWeight = 0; @@ -442,56 +457,56 @@ Object.assign(pc, function () { this.transitionPreviousStates = []; - var activeState = this._getActiveState(); - for (var i = 0; i < activeState.animations.length; i++) { - var animation = activeState.animations[i]; - this.animEvaluator.findClip(animation.name).blendWeight = animation.weight / activeState.getTotalWeight(); + state = this._getActiveState(); + for (i = 0; i < state.animations.length; i++) { + animation = state.animations[i]; + this.animEvaluator.findClip(animation.name).blendWeight = animation.weight / state.getTotalWeight(); } } else { var interpolatedTime = this.currTransitionTime / this.totalTransitionTime; - for (var i = 0; i < this.transitionPreviousStates.length; i++) { - var state = this._getState(this.transitionPreviousStates[i].name); + for (i = 0; i < this.transitionPreviousStates.length; i++) { + state = this._getState(this.transitionPreviousStates[i].name); var stateWeight = this.transitionPreviousStates[i].weight; - for (var j = 0; j < state.animations.length; j++) { - var animation = state.animations[j]; + for (j = 0; j < state.animations.length; j++) { + animation = state.animations[j]; this.animEvaluator.findClip(animation.name).blendWeight = (1.0 - interpolatedTime) * animation.weight / state.getTotalWeight() * stateWeight; } } - var activeState = this._getActiveState(); - for (var i = 0; i < activeState.animations.length; i++) { - var animation = activeState.animations[i]; - this.animEvaluator.findClip(animation.name).blendWeight = interpolatedTime * animation.weight / activeState.getTotalWeight(); + state = this._getActiveState(); + for (i = 0; i < state.animations.length; i++) { + animation = state.animations[i]; + this.animEvaluator.findClip(animation.name).blendWeight = interpolatedTime * animation.weight / state.getTotalWeight(); } } - this.currTransitionTime = this.currTransitionTime + dt; + this.currTransitionTime += dt; } this.animEvaluator.update(dt); } }, - getActiveStateProgress: function(checkBeforeUpdate) { + getActiveStateProgress: function (checkBeforeUpdate) { if (this.activeStateName === ANIM_STATE_START || this.activeStateName === ANIM_STATE_END) return 1.0; - else { - var activeClip = this.animEvaluator.findClip(this._getActiveState().animations[0].name); - if (activeClip) { - return (checkBeforeUpdate ? this.timeInStateBefore : this.timeInState) / activeClip.track.duration; - } + + var activeClip = this.animEvaluator.findClip(this._getActiveState().animations[0].name); + if (activeClip) { + return (checkBeforeUpdate ? this.timeInStateBefore : this.timeInState) / activeClip.track.duration; } + return null; }, - getActiveStateName: function() { + getActiveStateName: function () { return this._getState(this.activeStateName).name; }, - _getParameter: function(name) { + _getParameter: function (name) { return this.parameters[name]; }, - getParameterValue: function(name, type) { + getParameterValue: function (name, type) { var param = this._getParameter(name); if (param && param.type === type) { return param.value; @@ -501,7 +516,7 @@ Object.assign(pc, function () { // #endif }, - setParameterValue: function(name, type, value) { + setParameterValue: function (name, type, value) { var param = this._getParameter(name); if (param && param.type === type) { param.value = value; @@ -510,7 +525,7 @@ Object.assign(pc, function () { // #ifdef DEBUG console.log('Cannot set parameter value. No parameter found in anim controller named "' + name + '" of type "' + ANIM_PARAMETER_TYPE_NAMES[type] + '"'); // #endif - }, + } }); return { @@ -537,5 +552,5 @@ Object.assign(pc, function () { ANIM_STATE_END: ANIM_STATE_END, AnimController: AnimController - } -}()); \ No newline at end of file + }; +}()); diff --git a/src/framework/components/anim/property-locator.js b/src/framework/components/anim/property-locator.js index 193168bec50..eaaff9b9fff 100644 --- a/src/framework/components/anim/property-locator.js +++ b/src/framework/components/anim/property-locator.js @@ -12,13 +12,13 @@ Object.assign(pc, function () { * @function * @name pc.AnimPropertyLocator#encode * @description Converts a locator array into its string version - * @param {array} locator - The property location in the scene defined as an array - * @returns {string} + * @param {Array} locator - The property location in the scene defined as an array + * @returns {string} The locator encoded as a string * @example * // returns 'spotLight/light/color.r' - * encode([['spotLight'], 'light', ['color','r']]) + * encode([['spotLight'], 'light', ['color', 'r']]); */ - encode: function(locator) { + encode: function (locator) { return pc.AnimBinder.joinPath([ pc.AnimBinder.joinPath(locator[0]), locator[1], @@ -29,13 +29,13 @@ Object.assign(pc, function () { * @function * @name pc.AnimPropertyLocator#decode * @description Converts a locator string into its array version - * @param {array} locator - The property location in the scene defined as a string - * @returns {array} + * @param {Array} locator - The property location in the scene defined as a string + * @returns {Array} - The locator decoded into an array * @example * // returns [['spotLight'], 'light', ['color','r']] - * encode('spotLight/light/color.r') + * encode('spotLight/light/color.r'); */ - decode: function(locator) { + decode: function (locator) { var locatorSections = pc.AnimBinder.splitPath(locator, '/'); return [ pc.AnimBinder.splitPath(locatorSections[0]), @@ -46,5 +46,5 @@ Object.assign(pc, function () { }); return { AnimPropertyLocator: AnimPropertyLocator - } -}()); \ No newline at end of file + }; +}()); diff --git a/src/framework/components/anim/system.js b/src/framework/components/anim/system.js index 6c6d8b2db80..61bc16c6b7e 100644 --- a/src/framework/components/anim/system.js +++ b/src/framework/components/anim/system.js @@ -2,7 +2,7 @@ Object.assign(pc, function () { var _schema = [ 'enabled', 'speed', - 'activate', + 'activate' ]; /** diff --git a/src/resources/animation-clip.js b/src/resources/animation-clip.js index 0f2c27c7983..39b23b5747f 100644 --- a/src/resources/animation-clip.js +++ b/src/resources/animation-clip.js @@ -41,15 +41,15 @@ Object.assign(pc, function () { open: function (url, data) { var name = data.name; var duration = data.duration; - var inputs = data.inputs.map(function(input) { + var inputs = data.inputs.map(function (input) { return new pc.AnimData(1, input); }); - var outputs = data.outputs.map(function(output) { + var outputs = data.outputs.map(function (output) { return new pc.AnimData(1, output); }); - var curves = data.curves.map(function(curve) { + var curves = data.curves.map(function (curve) { return new pc.AnimCurve( - [ curve.path ], + [curve.path], curve.inputIndex, curve.outputIndex, curve.interpolation @@ -62,7 +62,7 @@ Object.assign(pc, function () { outputs, curves ); - }, + } }); return { diff --git a/src/resources/animation-state-graph.js b/src/resources/animation-state-graph.js index 81eab381ea8..b3fcd0ccf33 100644 --- a/src/resources/animation-state-graph.js +++ b/src/resources/animation-state-graph.js @@ -40,7 +40,7 @@ Object.assign(pc, function () { open: function (url, data) { return data; - }, + } }); return { From a59c839893c58ef824c6872d93129947c7349255 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Tue, 5 May 2020 14:43:30 +0100 Subject: [PATCH 031/144] add a pause function & update enum types --- src/framework/components/anim/component.js | 32 +++++++++++++++++---- src/framework/components/anim/controller.js | 30 ++++++++++--------- 2 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index af75d585343..bd9113fe6f0 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -51,26 +51,26 @@ Object.assign(pc, function () { /** * @function - * @name pc.AnimComponent#linkAnimationToState + * @name pc.AnimComponent#assignAnimation * @description Associates an animation with a state in the loaded state graph. If all states are linked and the pc.AnimComponent.activate value was set to true then the component will begin playing. * @param {string} stateName - The name of the state that this animation should be associated with. * @param {object} animTrack - The animation that will linked to this state and played whenever this state is active. */ - linkAnimationToState: function (stateName, animTrack) { + assignAnimation: function (stateName, animTrack) { if (!this.data.animController) { // #ifdef DEBUG - console.error('linkAnimationToState: Trying to link an anim track before the state graph has been loaded. Have you called loadStateGraph?'); + console.error('assignAnimation: Trying to assign an anim track before the state graph has been loaded. Have you called loadStateGraph?'); // #endif return; } - this.data.animController.linkAnimationToState(stateName, animTrack); + this.data.animController.assignAnimation(stateName, animTrack); }, /** * @function * @name pc.AnimComponent#play * @description Start playing the animation in the current state. - * @param {string} name - The name of the animation asset to begin playing. + * @param {string} name - If provided, will begin playing from the start of the state with this name. */ play: function (name) { if (!this.enabled || !this.entity.enabled) { @@ -79,7 +79,7 @@ Object.assign(pc, function () { if (!this.data.animController) { // #ifdef DEBUG - console.error('Trying to play an animation when no animation state machine has been loaded. Have you called loadStateMachineAsset?'); + console.error('Trying to play an animation when no animation state machine has been loaded. Have you called loadStateGraph?'); // #endif return; } @@ -87,6 +87,26 @@ Object.assign(pc, function () { this.data.animController.play(name); }, + /** + * @function + * @name pc.AnimComponent#pause + * @description Start playing the animation in the current state. + */ + pause: function () { + if (!this.enabled || !this.entity.enabled) { + return; + } + + if (!this.data.animController) { + // #ifdef DEBUG + console.error('Trying to pause the anim component when no animation graph has been loaded. Have you called loadStateGraph?'); + // #endif + return; + } + + this.data.animController.pause(); + }, + /** * @function * @name pc.AnimComponent#reset diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 19fdc42514b..dfc3f66c824 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -1,17 +1,17 @@ Object.assign(pc, function () { - var ANIM_INTERRUPTION_SOURCE_NONE = 0; - var ANIM_INTERRUPTION_SOURCE_PREV_STATE = 1; - var ANIM_INTERRUPTION_SOURCE_NEXT_STATE = 2; - var ANIM_INTERRUPTION_SOURCE_PREV_STATE_NEXT_STATE = 3; - var ANIM_INTERRUPTION_SOURCE_NEXT_STATE_PREV_STATE = 4; - - var ANIM_TRANSITION_PREDICATE_GREATER_THAN = 0; - var ANIM_TRANSITION_PREDICATE_LESS_THAN = 1; - var ANIM_TRANSITION_PREDICATE_GREATER_THAN_EQUAL_TO = 2; - var ANIM_TRANSITION_PREDICATE_LESS_THAN_EQUAL_TO = 3; - var ANIM_TRANSITION_PREDICATE_EQUAL_TO = 4; - var ANIM_TRANSITION_PREDICATE_NOT_EQUAL_TO = 5; + var ANIM_INTERRUPTION_SOURCE_NONE = 'NONE'; + var ANIM_INTERRUPTION_SOURCE_PREV_STATE = 'PREV_STATE'; + var ANIM_INTERRUPTION_SOURCE_NEXT_STATE = 'NEXT_STATE'; + var ANIM_INTERRUPTION_SOURCE_PREV_STATE_NEXT_STATE = 'PREV_STATE_NEXT_STATE'; + var ANIM_INTERRUPTION_SOURCE_NEXT_STATE_PREV_STATE = 'NEXT_STATE_PREV_STATE'; + + var ANIM_TRANSITION_PREDICATE_GREATER_THAN = 'GREATER_THAN'; + var ANIM_TRANSITION_PREDICATE_LESS_THAN = 'LESS_THAN'; + var ANIM_TRANSITION_PREDICATE_GREATER_THAN_EQUAL_TO = 'GREATER_THAN_EQUAL_TO'; + var ANIM_TRANSITION_PREDICATE_LESS_THAN_EQUAL_TO = 'LESS_THAN_EQUAL_TO'; + var ANIM_TRANSITION_PREDICATE_EQUAL_TO = 'EQUAL_TO'; + var ANIM_TRANSITION_PREDICATE_NOT_EQUAL_TO = 'NOT_EQUAL_TO'; var ANIM_PARAMETER_INTEGER = 0; var ANIM_PARAMETER_FLOAT = 1; @@ -365,7 +365,7 @@ Object.assign(pc, function () { this._updateStateFromTransition(transition); }, - linkAnimationToState: function (stateName, animTrack) { + assignAnimation: function (stateName, animTrack) { var state = this._getState(stateName); if (!state) { // #ifdef DEBUG @@ -411,6 +411,10 @@ Object.assign(pc, function () { this.playing = true; }, + pause: function () { + this.playing = false; + }, + reset: function () { this.previousStateName = null; this.activeStateName = ANIM_STATE_START; From ca75945a18d243dbc820c824e408882a9d56bf9c Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Wed, 6 May 2020 13:06:16 +0100 Subject: [PATCH 032/144] Update to anim component and controller code style --- src/framework/components/anim/component.js | 4 +- src/framework/components/anim/controller.js | 460 ++++++++++++-------- src/framework/components/anim/system.js | 10 +- 3 files changed, 272 insertions(+), 202 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index bd9113fe6f0..952bd7db3ad 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -242,7 +242,7 @@ Object.assign(pc, function () { activeState: { get: function () { if (this.data.animController) { - return this.data.animController.getActiveStateName(); + return this.data.animController.activeStateName; } return null; } @@ -254,7 +254,7 @@ Object.assign(pc, function () { activeStateProgress: { get: function () { if (this.data.animController) { - return this.data.animController.getActiveStateProgress(); + return this.data.animController.activeStateProgress; } return null; } diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index dfc3f66c824..d0e9fa0c57b 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -13,10 +13,10 @@ Object.assign(pc, function () { var ANIM_TRANSITION_PREDICATE_EQUAL_TO = 'EQUAL_TO'; var ANIM_TRANSITION_PREDICATE_NOT_EQUAL_TO = 'NOT_EQUAL_TO'; - var ANIM_PARAMETER_INTEGER = 0; - var ANIM_PARAMETER_FLOAT = 1; - var ANIM_PARAMETER_BOOLEAN = 2; - var ANIM_PARAMETER_TRIGGER = 3; + var ANIM_PARAMETER_INTEGER = 1; + var ANIM_PARAMETER_FLOAT = 2; + var ANIM_PARAMETER_BOOLEAN = 3; + var ANIM_PARAMETER_TRIGGER = 4; var ANIM_PARAMETER_TYPE_NAMES = { ANIM_PARAMETER_INTEGER: 'Integer', @@ -29,30 +29,59 @@ Object.assign(pc, function () { var ANIM_STATE_END = 'ANIM_STATE_END'; var AnimState = function (name, speed) { - this.name = name; - this.animations = []; - this.speed = speed || 1.0; + this._name = name; + this._animations = []; + this._speed = speed || 1.0; }; - Object.assign(AnimState.prototype, { - isPlayable: function () { + Object.defineProperty(AnimState.prototype, 'name', { + get: function () { + return this._name; + } + }); + + Object.defineProperty(AnimState.prototype, 'animations', { + get: function () { + return this._animations; + } + }); + + Object.defineProperty(AnimState.prototype, 'speed', { + get: function () { + return this._speed; + } + }); + + Object.defineProperty(AnimState.prototype, 'playable', { + get: function () { return (this.animations.length > 0 || this.name === ANIM_STATE_START || this.name === ANIM_STATE_END); - }, + } + }); - isLooping: function () { - return true; - }, + Object.defineProperty(AnimState.prototype, 'looping', { + get: function () { + var trackClipName = this.name + '.' + this.animatons[0].animTrack.name; + var trackClip = this._controller.animEvaluator.getClip(trackClipName); + if (trackClip) { + return trackClip.loop; + } + return false; + } + }); - getTotalWeight: function () { + Object.defineProperty(AnimState.prototype, 'totalWeight', { + get: function () { var sum = 0; var i; for (i = 0; i < this.animations.length; i++) { sum += this.animations[i].weight; } return sum; - }, + } + }); - getTimelineDuration: function () { + Object.defineProperty(AnimState.prototype, 'timelineDuration', { + get: function () { var duration = 0; var i; for (i = 0; i < this.animations.length; i++) { @@ -66,24 +95,78 @@ Object.assign(pc, function () { }); var AnimTransition = function (controller, from, to, time, priority, conditions, exitTime, transitionOffset, interruptionSource) { - this.controller = controller; - this.from = from; - this.to = to; - this.time = time; - this.priority = priority; - this.conditions = conditions || []; - this.exitTime = exitTime || null; - this.transitionOffset = transitionOffset || null; - this.interruptionSource = interruptionSource || ANIM_INTERRUPTION_SOURCE_NONE; + this._controller = controller; + this._from = from; + this._to = to; + this._time = time; + this._priority = priority; + this._conditions = conditions || []; + this._exitTime = exitTime || null; + this._transitionOffset = transitionOffset || null; + this._interruptionSource = interruptionSource || ANIM_INTERRUPTION_SOURCE_NONE; }; - Object.assign(AnimTransition.prototype, { - hasConditionsMet: function () { + Object.defineProperty(AnimTransition.prototype, 'from', { + get: function () { + return this._from; + } + }); + + Object.defineProperty(AnimTransition.prototype, 'to', { + get: function () { + return this._to; + } + }); + + Object.defineProperty(AnimTransition.prototype, 'time', { + get: function () { + return this._time; + } + }); + + Object.defineProperty(AnimTransition.prototype, 'priority', { + get: function () { + return this._priority; + } + }); + + Object.defineProperty(AnimTransition.prototype, 'conditions', { + get: function () { + return this._conditions; + } + }); + + Object.defineProperty(AnimTransition.prototype, 'exitTime', { + get: function () { + return this._exitTime; + } + }); + + Object.defineProperty(AnimTransition.prototype, 'transitionOffset', { + get: function () { + return this._transitionOffset; + } + }); + + Object.defineProperty(AnimTransition.prototype, 'interruptionSource', { + get: function () { + return this._interruptionSource; + } + }); + + Object.defineProperty(AnimTransition.prototype, 'hasExitTime', { + get: function () { + return !!this.exitTime; + } + }); + + Object.defineProperty(AnimTransition.prototype, 'hasConditionsMet', { + get: function () { var conditionsMet = true; var i; for (i = 0; i < this.conditions.length; i++) { var condition = this.conditions[i]; - var parameter = this.controller._getParameter(condition.parameterName); + var parameter = this._controller.findParameter(condition.parameterName); switch (condition.predicate) { case ANIM_TRANSITION_PREDICATE_GREATER_THAN: conditionsMet = conditionsMet && parameter.value > condition.value; @@ -108,23 +191,20 @@ Object.assign(pc, function () { return conditionsMet; } return conditionsMet; - }, - hasExitTime: function () { - return !!this.exitTime; } }); - var AnimController = function (animEvaluator, states, transitions, parameters, activate) { - this.animEvaluator = animEvaluator; - this.states = {}; + var AnimController = function (animEvaluator, states, transitions, parameters, activate, speed) { + this._animEvaluator = animEvaluator; + this._states = {}; var i; for (i = 0; i < states.length; i++) { - this.states[states[i].name] = new AnimState( + this._states[states[i].name] = new AnimState( states[i].name, states[i].speed ); } - this.transitions = transitions.map(function (transition) { + this._transitions = transitions.map(function (transition) { return new AnimTransition( this, transition.from, @@ -137,54 +217,85 @@ Object.assign(pc, function () { transition.interruptionSource ); }.bind(this)); - this.findTransitionsFromStateCache = {}; - this.findTransitionsBetweenStatesCache = {}; - this.parameters = parameters; - this.initialParameters = Object.assign({}, parameters); - this.previousStateName = null; - this.activeStateName = ANIM_STATE_START; - this.playing = false; - this.activate = activate; - - this.currTransitionTime = 1.0; - this.totalTransitionTime = 1.0; - this.isTransitioning = false; - this.transitionInterruptionSource = ANIM_INTERRUPTION_SOURCE_NONE; - this.transitionPreviousStates = []; - - this.timeInState = 0; - this.timeInStateBefore = 0; + this._findTransitionsFromStateCache = {}; + this._findTransitionsBetweenStatesCache = {}; + this._parameters = parameters; + this._initialParameters = Object.assign({}, parameters); + this._previousStateName = null; + this._activeStateName = ANIM_STATE_START; + this._playing = false; + this._activate = activate; + this._speed = speed; + + this._currTransitionTime = 1.0; + this._totalTransitionTime = 1.0; + this._isTransitioning = false; + this._transitionInterruptionSource = ANIM_INTERRUPTION_SOURCE_NONE; + this._transitionPreviousStates = []; + + this._timeInState = 0; + this._timeInStateBefore = 0; }; - Object.assign(AnimController.prototype, { - _getState: function (stateName) { - return this.states[stateName]; + Object.defineProperty(AnimController.prototype, 'activeState', { + get: function () { + return this._findState(this._activeStateName); }, + set: function (stateName) { + this._activeStateName = stateName; + } + }); - _setState: function (stateName, state) { - this.states[stateName] = state; + Object.defineProperty(AnimController.prototype, 'previousState', { + get: function () { + return this._findState(this._previousStateName); }, + set: function (stateName) { + this._previousStateName = stateName; + } + }); - _getActiveState: function () { - return this._getState(this.activeStateName); - }, + Object.defineProperty(AnimController.prototype, 'playable', { + get: function () { + var playable = true; + var i; + for (i = 0; i < this._states.length; i++) { + if (!this._states[i].playable) { + playable = false; + } + } + return playable; + } + }); - _setActiveState: function (stateName) { - this.activeStateName = stateName; - }, + Object.defineProperty(AnimController.prototype, 'activeStateProgress', { + get: function () { + return this._getActiveStateProgressForTime(this._timeInState); + } + }); - _getPreviousState: function () { - return this._getState(this.previousStateName); + Object.assign(AnimController.prototype, { + + _findState: function (stateName) { + return this._states[stateName]; }, - _setPreviousState: function (stateName) { - this.previousStateName = stateName; + _getActiveStateProgressForTime: function (time) { + if (this.activeStateName === ANIM_STATE_START || this.activeStateName === ANIM_STATE_END) + return 1.0; + + var activeClip = this._animEvaluator.findClip(this.activeState.animations[0].name); + if (activeClip) { + return time / activeClip.track.duration; + } + + return null; }, _findTransitionsFromState: function (stateName) { - var transitions = this.findTransitionsFromStateCache[stateName]; + var transitions = this._findTransitionsFromStateCache[stateName]; if (!transitions) { - transitions = this.transitions.filter(function (transition) { + transitions = this._transitions.filter(function (transition) { return transition.from === stateName; }); @@ -193,15 +304,15 @@ Object.assign(pc, function () { return a.priority < b.priority; }); - this.findTransitionsFromStateCache[stateName] = transitions; + this._findTransitionsFromStateCache[stateName] = transitions; } return transitions; }, _findTransitionsBetweenStates: function (sourceStateName, destinationStateName) { - var transitions = this.findTransitionsBetweenStatesCache[sourceStateName + '->' + destinationStateName]; + var transitions = this._findTransitionsBetweenStatesCache[sourceStateName + '->' + destinationStateName]; if (!transitions) { - transitions = this.transitions.filter(function (transition) { + transitions = this._transitions.filter(function (transition) { return transition.from === sourceStateName && transition.to === destinationStateName; }); @@ -210,9 +321,8 @@ Object.assign(pc, function () { return a.priority < b.priority; }); - this.findTransitionsBetweenStatesCache[sourceStateName + '->' + destinationStateName] = transitions; + this._findTransitionsBetweenStatesCache[sourceStateName + '->' + destinationStateName] = transitions; } - return transitions; }, @@ -221,25 +331,25 @@ Object.assign(pc, function () { // find transitions that include the required source and destination states if (from && to) { - transitions.concat(this._findTransitionsBetweenStates(this.activeStateName)); + transitions.concat(this._findTransitionsBetweenStates(this._activeStateName)); } else { - if (!this.isTransitioning) { - transitions = transitions.concat(this._findTransitionsFromState(this.activeStateName)); + if (!this._isTransitioning) { + transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName)); } else { - switch (this.transitionInterruptionSource) { + switch (this._transitionInterruptionSource) { case ANIM_INTERRUPTION_SOURCE_PREV_STATE: - transitions = transitions.concat(this._findTransitionsFromState(this.previousStateName)); + transitions = transitions.concat(this._findTransitionsFromState(this._previousStateName)); break; case ANIM_INTERRUPTION_SOURCE_NEXT_STATE: - transitions = transitions.concat(this._findTransitionsFromState(this.activeStateName)); + transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName)); break; case ANIM_INTERRUPTION_SOURCE_PREV_STATE_NEXT_STATE: - transitions = transitions.concat(this._findTransitionsFromState(this.previousStateName)); - transitions = transitions.concat(this._findTransitionsFromState(this.activeStateName)); + transitions = transitions.concat(this._findTransitionsFromState(this._previousStateName)); + transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName)); break; case ANIM_INTERRUPTION_SOURCE_NEXT_STATE_PREV_STATE: - transitions = transitions.concat(this._findTransitionsFromState(this.activeStateName)); - transitions = transitions.concat(this._findTransitionsFromState(this.previousStateName)); + transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName)); + transitions = transitions.concat(this._findTransitionsFromState(this._previousStateName)); break; case ANIM_INTERRUPTION_SOURCE_NONE: default: @@ -251,10 +361,10 @@ Object.assign(pc, function () { transitions = transitions.filter(function (transition) { // when an exit time is present, we should only exit if it falls within the current frame delta time if (transition.hasExitTime()) { - var progressBefore = this.getActiveStateProgress(true); - var progress = this.getActiveStateProgress(); + var progressBefore = this._getActiveStateProgressForTime(this._timeInStateBefore); + var progress = this.getActiveStateProgressForTime(this._timeInState); // when the exit time is smaller than 1 and the state is looping, we should check for an exit each loop - if (transition.exitTime < 1.0 && this._getActiveState().isLooping()) { + if (transition.exitTime < 1.0 && this.activeState.looping) { progressBefore -= Math.floor(progressBefore); progress -= Math.floor(progress); } @@ -276,63 +386,63 @@ Object.assign(pc, function () { _updateStateFromTransition: function (transition) { var i; var j; - this._setPreviousState(this.activeStateName); - this._setActiveState(transition.to); + this.previousState = this._activeStateName; + this.activeState = transition.to; var triggers = transition.conditions.filter(function (condition) { - var parameter = this._getParameter(condition.parameterName); + var parameter = this.findParameter(condition.parameterName); return parameter.type === ANIM_PARAMETER_TRIGGER; }.bind(this)); for (i = 0; i < triggers.length; i++) { this.setParameterValue(triggers[i].parameterName, ANIM_PARAMETER_TRIGGER, false); } - if (this.isTransitioning) { - var interpolatedTime = this.currTransitionTime / this.totalTransitionTime; - for (i = 0; i < this.transitionPreviousStates.length; i++) { - this.transitionPreviousStates[i].weight *= (1.0 - interpolatedTime); - var state = this._getState(this.transitionPreviousStates[i].name); + if (this._isTransitioning) { + var interpolatedTime = this._currTransitionTime / this._totalTransitionTime; + for (i = 0; i < this._transitionPreviousStates.length; i++) { + this._transitionPreviousStates[i].weight *= (1.0 - interpolatedTime); + var state = this._findState(this._transitionPreviousStates[i].name); for (j = 0; j < state.animations.length; j++) { var animation = state.animations[j]; - this.animEvaluator.findClip(animation.name).pause(); + this._animEvaluator.findClip(animation.name).pause(); } } - this.transitionPreviousStates.push({ - name: this.previousStateName, + this._transitionPreviousStates.push({ + name: this._previousStateName, weight: interpolatedTime }); } else { - this.transitionPreviousStates.push({ - name: this.previousStateName, + this._transitionPreviousStates.push({ + name: this._previousStateName, weight: 1.0 }); } if (transition.time > 0) { - this.isTransitioning = true; - this.totalTransitionTime = transition.time; - this.currTransitionTime = 0; - this.transitionInterruptionSource = transition.interruptionSource; + this._isTransitioning = true; + this._totalTransitionTime = transition.time; + this._currTransitionTime = 0; + this._transitionInterruptionSource = transition.interruptionSource; } var hasTransitionOffset = transition.transitionOffset && transition.transitionOffset > 0.0 && transition.transitionOffset < 1.0; - var activeState = this._getActiveState(); + var activeState = this.activeState; for (i = 0; i < activeState.animations.length; i++) { - var clip = this.animEvaluator.findClip(activeState.animations[i].name); + var clip = this._animEvaluator.findClip(activeState.animations[i].name); if (!clip) { clip = new pc.AnimClip(activeState.animations[i].animTrack, 0, activeState.speed, true, true); clip.name = activeState.animations[i].name; - this.animEvaluator.addClip(clip); + this._animEvaluator.addClip(clip); } if (transition.time > 0) { - clip.blendWeight = 0.0 / activeState.getTotalWeight(); + clip.blendWeight = 0.0 / activeState.totalWeight; } else { - clip.blendWeight = 1.0 / activeState.getTotalWeight(); + clip.blendWeight = 1.0 / activeState.totalWeight; } clip.reset(); if (hasTransitionOffset) { - clip.time = activeState.getTimelineDuration() * transition.transitionOffset; + clip.time = activeState.timelineDuration * transition.transitionOffset; } clip.play(); } @@ -340,33 +450,33 @@ Object.assign(pc, function () { var timeInState = 0; var timeInStateBefore = 0; if (hasTransitionOffset) { - var offsetTime = activeState.getTimelineDuration() * transition.transitionOffset; + var offsetTime = activeState.timelineDuration * transition.transitionOffset; timeInState = offsetTime; timeInStateBefore = offsetTime; } - this.timeInState = timeInState; - this.timeInStateBefore = timeInStateBefore; + this._timeInState = timeInState; + this._timeInStateBefore = timeInStateBefore; }, _transitionToState: function (newStateName) { - if (newStateName === this.activeStateName) { + if (newStateName === this._activeStateName) { return; } - if (!this._getState(newStateName)) { + if (!this._findState(newStateName)) { return; } - var transition = this._findTransition(this.activeStateName, newStateName); + var transition = this._findTransition(this._activeStateName, newStateName); if (!transition) { - this.animEvaluator.removeClips(); - transition = new AnimTransition(this, this.activeStateName, newStateName, 0, 0); + this._animEvaluator.removeClips(); + transition = new AnimTransition(this, this._activeStateName, newStateName, 0, 0); } this._updateStateFromTransition(transition); }, assignAnimation: function (stateName, animTrack) { - var state = this._getState(stateName); + var state = this._findState(stateName); if (!state) { // #ifdef DEBUG console.error('Linking animation asset to animation state that does not exist'); @@ -387,131 +497,99 @@ Object.assign(pc, function () { } state.animations.push(animation); - if (!this.playing && this.activate && this.isPlayable()) { + if (!this._playing && this._activate && this.playable) { this.play(); - } }, - isPlayable: function () { - var playable = true; - var i; - for (i = 0; i < this.states.length; i++) { - if (!this.states[i].isPlayable()) { - playable = false; - } - } - return playable; - }, - play: function (stateName) { if (stateName) { this._transitionToState(stateName); } - this.playing = true; + this._playing = true; }, pause: function () { - this.playing = false; + this._playing = false; }, reset: function () { - this.previousStateName = null; - this.activeStateName = ANIM_STATE_START; - this.playing = false; - - this.currTransitionTime = 1.0; - this.totalTransitionTime = 1.0; - this.isTransitioning = false; - - this.timeInState = 0; - this.timeInStateBefore = 0; - - this.animEvaluator.removeClips(); - - this.parameters = Object.assign({}, this.initialParameters); + this._previousStateName = null; + this._activeStateName = ANIM_STATE_START; + this._playing = false; + this._currTransitionTime = 1.0; + this._totalTransitionTime = 1.0; + this._isTransitioning = false; + this._timeInState = 0; + this._timeInStateBefore = 0; + this._parameters = Object.assign({}, this._initialParameters); + this._animEvaluator.removeClips(); }, update: function (dt) { - if (this.playing) { + if (this._playing) { var i; var j; var state; var animation; - this.timeInStateBefore = this.timeInState; - this.timeInState += dt; + this._timeInStateBefore = this._timeInState; + this._timeInState += dt; - var transition = this._findTransition(this.activeStateName); + var transition = this._findTransition(this._activeStateName); if (transition) this._updateStateFromTransition(transition); - if (this.isTransitioning) { - if (this.currTransitionTime >= this.totalTransitionTime) { - this.isTransitioning = false; + if (this._isTransitioning) { + if (this._currTransitionTime >= this._totalTransitionTime) { + this._isTransitioning = false; - for (i = 0; i < this.transitionPreviousStates.length; i++) { - state = this._getState(this.transitionPreviousStates[i].name); + for (i = 0; i < this._transitionPreviousStates.length; i++) { + state = this._findState(this._transitionPreviousStates[i].name); for (j = 0; j < state.animations.length; j++) { animation = state.animations[j]; - var clip = this.animEvaluator.findClip(animation.name); + var clip = this._animEvaluator.findClip(animation.name); clip.pause(); clip.blendWeight = 0; } } - this.transitionPreviousStates = []; + this._transitionPreviousStates = []; - state = this._getActiveState(); + state = this.activeState; for (i = 0; i < state.animations.length; i++) { animation = state.animations[i]; - this.animEvaluator.findClip(animation.name).blendWeight = animation.weight / state.getTotalWeight(); + this._animEvaluator.findClip(animation.name).blendWeight = animation.weight / state.totalWeight; } } else { - var interpolatedTime = this.currTransitionTime / this.totalTransitionTime; + var interpolatedTime = this._currTransitionTime / this._totalTransitionTime; - for (i = 0; i < this.transitionPreviousStates.length; i++) { - state = this._getState(this.transitionPreviousStates[i].name); - var stateWeight = this.transitionPreviousStates[i].weight; + for (i = 0; i < this._transitionPreviousStates.length; i++) { + state = this._findState(this._transitionPreviousStates[i].name); + var stateWeight = this._transitionPreviousStates[i].weight; for (j = 0; j < state.animations.length; j++) { animation = state.animations[j]; - this.animEvaluator.findClip(animation.name).blendWeight = (1.0 - interpolatedTime) * animation.weight / state.getTotalWeight() * stateWeight; + this._animEvaluator.findClip(animation.name).blendWeight = (1.0 - interpolatedTime) * animation.weight / state.totalWeight * stateWeight; } } - state = this._getActiveState(); + state = this.activeState; for (i = 0; i < state.animations.length; i++) { animation = state.animations[i]; - this.animEvaluator.findClip(animation.name).blendWeight = interpolatedTime * animation.weight / state.getTotalWeight(); + this._animEvaluator.findClip(animation.name).blendWeight = interpolatedTime * animation.weight / state.totalWeight; } } - this.currTransitionTime += dt; + this._currTransitionTime += dt; } - this.animEvaluator.update(dt); - } - }, - - getActiveStateProgress: function (checkBeforeUpdate) { - if (this.activeStateName === ANIM_STATE_START || this.activeStateName === ANIM_STATE_END) - return 1.0; - - var activeClip = this.animEvaluator.findClip(this._getActiveState().animations[0].name); - if (activeClip) { - return (checkBeforeUpdate ? this.timeInStateBefore : this.timeInState) / activeClip.track.duration; + this._animEvaluator.update(dt); } - - return null; - }, - - getActiveStateName: function () { - return this._getState(this.activeStateName).name; }, - _getParameter: function (name) { - return this.parameters[name]; + findParameter: function (name) { + return this._parameters[name]; }, getParameterValue: function (name, type) { - var param = this._getParameter(name); + var param = this.findParameter(name); if (param && param.type === type) { return param.value; } @@ -521,7 +599,7 @@ Object.assign(pc, function () { }, setParameterValue: function (name, type, value) { - var param = this._getParameter(name); + var param = this.findParameter(name); if (param && param.type === type) { param.value = value; return; diff --git a/src/framework/components/anim/system.js b/src/framework/components/anim/system.js index 61bc16c6b7e..ca9dc979ae9 100644 --- a/src/framework/components/anim/system.js +++ b/src/framework/components/anim/system.js @@ -40,14 +40,6 @@ Object.assign(pc, function () { pc.ComponentSystem.prototype.initializeComponentData.call(this, component, data, properties); }, - cloneComponent: function (entity, clone) { - this.addComponent(clone, {}); - - clone.anim.data.speed = entity.anim.speed; - clone.anim.data.activate = entity.anim.activate; - clone.anim.data.enabled = entity.anim.enabled; - }, - onBeforeRemove: function (entity, component) { component.onBeforeRemove(); }, @@ -62,7 +54,7 @@ Object.assign(pc, function () { if (componentData.enabled && component.entity.enabled) { if (componentData.animController) { - componentData.animController.update(dt * componentData.speed); + componentData.animController.update(dt); } } } From 72478760b01406994e775ccd2ef9e3c8a0ecc942 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Wed, 6 May 2020 14:32:57 +0100 Subject: [PATCH 033/144] styling updates --- src/framework/components/anim/controller.js | 100 ++++++++++---------- src/framework/components/anim/system.js | 2 +- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index d0e9fa0c57b..128eabe02b8 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -194,7 +194,7 @@ Object.assign(pc, function () { } }); - var AnimController = function (animEvaluator, states, transitions, parameters, activate, speed) { + var AnimController = function (animEvaluator, states, transitions, parameters, activate) { this._animEvaluator = animEvaluator; this._states = {}; var i; @@ -225,7 +225,6 @@ Object.assign(pc, function () { this._activeStateName = ANIM_STATE_START; this._playing = false; this._activate = activate; - this._speed = speed; this._currTransitionTime = 1.0; this._totalTransitionTime = 1.0; @@ -527,61 +526,62 @@ Object.assign(pc, function () { }, update: function (dt) { - if (this._playing) { - var i; - var j; - var state; - var animation; - this._timeInStateBefore = this._timeInState; - this._timeInState += dt; - - var transition = this._findTransition(this._activeStateName); - if (transition) - this._updateStateFromTransition(transition); - - if (this._isTransitioning) { - if (this._currTransitionTime >= this._totalTransitionTime) { - this._isTransitioning = false; - - for (i = 0; i < this._transitionPreviousStates.length; i++) { - state = this._findState(this._transitionPreviousStates[i].name); - for (j = 0; j < state.animations.length; j++) { - animation = state.animations[j]; - var clip = this._animEvaluator.findClip(animation.name); - clip.pause(); - clip.blendWeight = 0; - } - } + if (!this._playing) { + return; + } + var i; + var j; + var state; + var animation; + this._timeInStateBefore = this._timeInState; + this._timeInState += dt; - this._transitionPreviousStates = []; + var transition = this._findTransition(this._activeStateName); + if (transition) + this._updateStateFromTransition(transition); - state = this.activeState; - for (i = 0; i < state.animations.length; i++) { - animation = state.animations[i]; - this._animEvaluator.findClip(animation.name).blendWeight = animation.weight / state.totalWeight; - } - } else { - var interpolatedTime = this._currTransitionTime / this._totalTransitionTime; - - for (i = 0; i < this._transitionPreviousStates.length; i++) { - state = this._findState(this._transitionPreviousStates[i].name); - var stateWeight = this._transitionPreviousStates[i].weight; - for (j = 0; j < state.animations.length; j++) { - animation = state.animations[j]; - this._animEvaluator.findClip(animation.name).blendWeight = (1.0 - interpolatedTime) * animation.weight / state.totalWeight * stateWeight; - } - } - state = this.activeState; - for (i = 0; i < state.animations.length; i++) { - animation = state.animations[i]; - this._animEvaluator.findClip(animation.name).blendWeight = interpolatedTime * animation.weight / state.totalWeight; + if (this._isTransitioning) { + if (this._currTransitionTime >= this._totalTransitionTime) { + this._isTransitioning = false; + + for (i = 0; i < this._transitionPreviousStates.length; i++) { + state = this._findState(this._transitionPreviousStates[i].name); + for (j = 0; j < state.animations.length; j++) { + animation = state.animations[j]; + var clip = this._animEvaluator.findClip(animation.name); + clip.pause(); + clip.blendWeight = 0; } + } + + this._transitionPreviousStates = []; + state = this.activeState; + for (i = 0; i < state.animations.length; i++) { + animation = state.animations[i]; + this._animEvaluator.findClip(animation.name).blendWeight = animation.weight / state.totalWeight; } - this._currTransitionTime += dt; + } else { + var interpolatedTime = this._currTransitionTime / this._totalTransitionTime; + + for (i = 0; i < this._transitionPreviousStates.length; i++) { + state = this._findState(this._transitionPreviousStates[i].name); + var stateWeight = this._transitionPreviousStates[i].weight; + for (j = 0; j < state.animations.length; j++) { + animation = state.animations[j]; + this._animEvaluator.findClip(animation.name).blendWeight = (1.0 - interpolatedTime) * animation.weight / state.totalWeight * stateWeight; + } + } + state = this.activeState; + for (i = 0; i < state.animations.length; i++) { + animation = state.animations[i]; + this._animEvaluator.findClip(animation.name).blendWeight = interpolatedTime * animation.weight / state.totalWeight; + } + } - this._animEvaluator.update(dt); + this._currTransitionTime += dt; } + this._animEvaluator.update(dt); }, findParameter: function (name) { diff --git a/src/framework/components/anim/system.js b/src/framework/components/anim/system.js index ca9dc979ae9..1be83e5cec8 100644 --- a/src/framework/components/anim/system.js +++ b/src/framework/components/anim/system.js @@ -54,7 +54,7 @@ Object.assign(pc, function () { if (componentData.enabled && component.entity.enabled) { if (componentData.animController) { - componentData.animController.update(dt); + componentData.animController.update(dt * componentData.speed); } } } From 0c9e3c496e2d2ea8165243ab464ac4d7454febf2 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Wed, 6 May 2020 14:48:32 +0100 Subject: [PATCH 034/144] fix breaking changes --- src/framework/components/anim/controller.js | 40 ++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 128eabe02b8..7a2535bda56 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -13,22 +13,16 @@ Object.assign(pc, function () { var ANIM_TRANSITION_PREDICATE_EQUAL_TO = 'EQUAL_TO'; var ANIM_TRANSITION_PREDICATE_NOT_EQUAL_TO = 'NOT_EQUAL_TO'; - var ANIM_PARAMETER_INTEGER = 1; - var ANIM_PARAMETER_FLOAT = 2; - var ANIM_PARAMETER_BOOLEAN = 3; - var ANIM_PARAMETER_TRIGGER = 4; - - var ANIM_PARAMETER_TYPE_NAMES = { - ANIM_PARAMETER_INTEGER: 'Integer', - ANIM_PARAMETER_FLOAT: 'Float', - ANIM_PARAMETER_BOOLEAN: 'Boolean', - ANIM_PARAMETER_TRIGGER: 'Trigger' - }; + var ANIM_PARAMETER_INTEGER = 'INTEGER'; + var ANIM_PARAMETER_FLOAT = 'FLOAT'; + var ANIM_PARAMETER_BOOLEAN = 'BOOLEAN'; + var ANIM_PARAMETER_TRIGGER = 'TRIGGER'; var ANIM_STATE_START = 'ANIM_STATE_START'; var ANIM_STATE_END = 'ANIM_STATE_END'; - var AnimState = function (name, speed) { + var AnimState = function (controller, name, speed) { + this._controller = controller; this._name = name; this._animations = []; this._speed = speed || 1.0; @@ -60,8 +54,8 @@ Object.assign(pc, function () { Object.defineProperty(AnimState.prototype, 'looping', { get: function () { - var trackClipName = this.name + '.' + this.animatons[0].animTrack.name; - var trackClip = this._controller.animEvaluator.getClip(trackClipName); + var trackClipName = this.name + '.' + this.animations[0].animTrack.name; + var trackClip = this._controller.animEvaluator.findClip(trackClipName); if (trackClip) { return trackClip.loop; } @@ -200,6 +194,7 @@ Object.assign(pc, function () { var i; for (i = 0; i < states.length; i++) { this._states[states[i].name] = new AnimState( + this, states[i].name, states[i].speed ); @@ -236,6 +231,12 @@ Object.assign(pc, function () { this._timeInStateBefore = 0; }; + Object.defineProperty(AnimController.prototype, 'animEvaluator', { + get: function () { + return this._animEvaluator; + } + }); + Object.defineProperty(AnimController.prototype, 'activeState', { get: function () { return this._findState(this._activeStateName); @@ -359,9 +360,9 @@ Object.assign(pc, function () { // filter out transitions that don't have their conditions met transitions = transitions.filter(function (transition) { // when an exit time is present, we should only exit if it falls within the current frame delta time - if (transition.hasExitTime()) { + if (transition.hasExitTime) { var progressBefore = this._getActiveStateProgressForTime(this._timeInStateBefore); - var progress = this.getActiveStateProgressForTime(this._timeInState); + var progress = this._getActiveStateProgressForTime(this._timeInState); // when the exit time is smaller than 1 and the state is looping, we should check for an exit each loop if (transition.exitTime < 1.0 && this.activeState.looping) { progressBefore -= Math.floor(progressBefore); @@ -372,7 +373,7 @@ Object.assign(pc, function () { return null; } } - return transition.hasConditionsMet(); + return transition.hasConditionsMet; }.bind(this)); if (transitions.length > 0) { @@ -594,7 +595,7 @@ Object.assign(pc, function () { return param.value; } // #ifdef DEBUG - console.log('Cannot get parameter value. No parameter found in anim controller named "' + name + '" of type "' + ANIM_PARAMETER_TYPE_NAMES[type] + '"'); + console.log('Cannot get parameter value. No parameter found in anim controller named "' + name + '" of type "' + type + '"'); // #endif }, @@ -605,7 +606,7 @@ Object.assign(pc, function () { return; } // #ifdef DEBUG - console.log('Cannot set parameter value. No parameter found in anim controller named "' + name + '" of type "' + ANIM_PARAMETER_TYPE_NAMES[type] + '"'); + console.log('Cannot set parameter value. No parameter found in anim controller named "' + name + '" of type "' + type + '"'); // #endif } }); @@ -624,7 +625,6 @@ Object.assign(pc, function () { ANIM_TRANSITION_PREDICATE_EQUAL_TO: ANIM_TRANSITION_PREDICATE_EQUAL_TO, ANIM_TRANSITION_PREDICATE_NOT_EQUAL_TO: ANIM_TRANSITION_PREDICATE_NOT_EQUAL_TO, - ANIM_PARAMETER_TYPE_NAMES: ANIM_PARAMETER_TYPE_NAMES, ANIM_PARAMETER_INTEGER: ANIM_PARAMETER_INTEGER, ANIM_PARAMETER_FLOAT: ANIM_PARAMETER_FLOAT, ANIM_PARAMETER_BOOLEAN: ANIM_PARAMETER_BOOLEAN, From 89566a809a2bec0f0694e57d41343ba63e1d16b9 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Wed, 6 May 2020 15:22:07 +0100 Subject: [PATCH 035/144] update typechecking --- src/framework/components/anim/component.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 952bd7db3ad..2227c41bef5 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -166,7 +166,13 @@ Object.assign(pc, function () { */ setInteger: function (name, value) { if (this.data.animController) { - this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_INTEGER, value); + if (typeof value === 'number' && value % 1 === 0) { + this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_INTEGER, Math.floor(value)); + } else { + // #ifdef DEBUG + console.error('Attempting to assign non integer value to integer parameter'); + // #endif + } } }, @@ -192,7 +198,7 @@ Object.assign(pc, function () { */ setBoolean: function (name, value) { if (this.data.animController) { - this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_BOOLEAN, value); + this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_BOOLEAN, !!value); } }, From 32646122caf5d3917f8511d862e53c6a4d657811 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Thu, 7 May 2020 12:15:35 +0100 Subject: [PATCH 036/144] code style changes --- src/framework/components/anim/controller.js | 397 ++++++++++---------- 1 file changed, 192 insertions(+), 205 deletions(-) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 7a2535bda56..4ecdaeb9b72 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -1,25 +1,25 @@ Object.assign(pc, function () { - var ANIM_INTERRUPTION_SOURCE_NONE = 'NONE'; - var ANIM_INTERRUPTION_SOURCE_PREV_STATE = 'PREV_STATE'; - var ANIM_INTERRUPTION_SOURCE_NEXT_STATE = 'NEXT_STATE'; - var ANIM_INTERRUPTION_SOURCE_PREV_STATE_NEXT_STATE = 'PREV_STATE_NEXT_STATE'; - var ANIM_INTERRUPTION_SOURCE_NEXT_STATE_PREV_STATE = 'NEXT_STATE_PREV_STATE'; - - var ANIM_TRANSITION_PREDICATE_GREATER_THAN = 'GREATER_THAN'; - var ANIM_TRANSITION_PREDICATE_LESS_THAN = 'LESS_THAN'; - var ANIM_TRANSITION_PREDICATE_GREATER_THAN_EQUAL_TO = 'GREATER_THAN_EQUAL_TO'; - var ANIM_TRANSITION_PREDICATE_LESS_THAN_EQUAL_TO = 'LESS_THAN_EQUAL_TO'; - var ANIM_TRANSITION_PREDICATE_EQUAL_TO = 'EQUAL_TO'; - var ANIM_TRANSITION_PREDICATE_NOT_EQUAL_TO = 'NOT_EQUAL_TO'; + var ANIM_INTERRUPTION_NONE = 'NONE'; + var ANIM_INTERRUPTION_PREV = 'PREV_STATE'; + var ANIM_INTERRUPTION_NEXT = 'NEXT_STATE'; + var ANIM_INTERRUPTION_PREV_NEXT = 'PREV_STATE_NEXT_STATE'; + var ANIM_INTERRUPTION_NEXT_PREV = 'NEXT_STATE_PREV_STATE'; + + var ANIM_GREATER_THAN = 'GREATER_THAN'; + var ANIM_LESS_THAN = 'LESS_THAN'; + var ANIM_GREATER_THAN_EQUAL_TO = 'GREATER_THAN_EQUAL_TO'; + var ANIM_LESS_THAN_EQUAL_TO = 'LESS_THAN_EQUAL_TO'; + var ANIM_EQUAL_TO = 'EQUAL_TO'; + var ANIM_NOT_EQUAL_TO = 'NOT_EQUAL_TO'; var ANIM_PARAMETER_INTEGER = 'INTEGER'; var ANIM_PARAMETER_FLOAT = 'FLOAT'; var ANIM_PARAMETER_BOOLEAN = 'BOOLEAN'; var ANIM_PARAMETER_TRIGGER = 'TRIGGER'; - var ANIM_STATE_START = 'ANIM_STATE_START'; - var ANIM_STATE_END = 'ANIM_STATE_END'; + var ANIM_STATE_START = 'START'; + var ANIM_STATE_END = 'END'; var AnimState = function (controller, name, speed) { this._controller = controller; @@ -28,63 +28,59 @@ Object.assign(pc, function () { this._speed = speed || 1.0; }; - Object.defineProperty(AnimState.prototype, 'name', { - get: function () { - return this._name; - } - }); - - Object.defineProperty(AnimState.prototype, 'animations', { - get: function () { - return this._animations; - } - }); - - Object.defineProperty(AnimState.prototype, 'speed', { - get: function () { - return this._speed; - } - }); - - Object.defineProperty(AnimState.prototype, 'playable', { - get: function () { - return (this.animations.length > 0 || this.name === ANIM_STATE_START || this.name === ANIM_STATE_END); - } - }); - - Object.defineProperty(AnimState.prototype, 'looping', { - get: function () { - var trackClipName = this.name + '.' + this.animations[0].animTrack.name; - var trackClip = this._controller.animEvaluator.findClip(trackClipName); - if (trackClip) { - return trackClip.loop; + Object.defineProperties(AnimState.prototype, { + name: { + get: function () { + return this._name; } - return false; - } - }); - - Object.defineProperty(AnimState.prototype, 'totalWeight', { - get: function () { - var sum = 0; - var i; - for (i = 0; i < this.animations.length; i++) { - sum += this.animations[i].weight; + }, + animations: { + get: function () { + return this._animations; } - return sum; - } - }); - - Object.defineProperty(AnimState.prototype, 'timelineDuration', { - get: function () { - var duration = 0; - var i; - for (i = 0; i < this.animations.length; i++) { - var animation = this.animations[i]; - if (animation.animTrack.duration > duration) { - duration = animation.animTrack.duration > duration; + }, + speed: { + get: function () { + return this._speed; + } + }, + playable: { + get: function () { + return (this.animations.length > 0 || this.name === ANIM_STATE_START || this.name === ANIM_STATE_END); + } + }, + looping: { + get: function () { + var trackClipName = this.name + '.' + this.animations[0].animTrack.name; + var trackClip = this._controller.animEvaluator.findClip(trackClipName); + if (trackClip) { + return trackClip.loop; } + return false; + } + }, + totalWeight: { + get: function () { + var sum = 0; + var i; + for (i = 0; i < this.animations.length; i++) { + sum += this.animations[i].weight; + } + return sum; + } + }, + timelineDuration: { + get: function () { + var duration = 0; + var i; + for (i = 0; i < this.animations.length; i++) { + var animation = this.animations[i]; + if (animation.animTrack.duration > duration) { + duration = animation.animTrack.duration > duration; + } + } + return duration; } - return duration; } }); @@ -100,91 +96,84 @@ Object.assign(pc, function () { this._interruptionSource = interruptionSource || ANIM_INTERRUPTION_SOURCE_NONE; }; - Object.defineProperty(AnimTransition.prototype, 'from', { - get: function () { - return this._from; - } - }); - - Object.defineProperty(AnimTransition.prototype, 'to', { - get: function () { - return this._to; - } - }); - - Object.defineProperty(AnimTransition.prototype, 'time', { - get: function () { - return this._time; - } - }); - - Object.defineProperty(AnimTransition.prototype, 'priority', { - get: function () { - return this._priority; - } - }); - - Object.defineProperty(AnimTransition.prototype, 'conditions', { - get: function () { - return this._conditions; - } - }); - - Object.defineProperty(AnimTransition.prototype, 'exitTime', { - get: function () { - return this._exitTime; - } - }); - - Object.defineProperty(AnimTransition.prototype, 'transitionOffset', { - get: function () { - return this._transitionOffset; - } - }); - - Object.defineProperty(AnimTransition.prototype, 'interruptionSource', { - get: function () { - return this._interruptionSource; - } - }); - - Object.defineProperty(AnimTransition.prototype, 'hasExitTime', { - get: function () { - return !!this.exitTime; - } - }); - - Object.defineProperty(AnimTransition.prototype, 'hasConditionsMet', { - get: function () { - var conditionsMet = true; - var i; - for (i = 0; i < this.conditions.length; i++) { - var condition = this.conditions[i]; - var parameter = this._controller.findParameter(condition.parameterName); - switch (condition.predicate) { - case ANIM_TRANSITION_PREDICATE_GREATER_THAN: - conditionsMet = conditionsMet && parameter.value > condition.value; - break; - case ANIM_TRANSITION_PREDICATE_LESS_THAN: - conditionsMet = conditionsMet && parameter.value < condition.value; - break; - case ANIM_TRANSITION_PREDICATE_GREATER_THAN_EQUAL_TO: - conditionsMet = conditionsMet && parameter.value >= condition.value; - break; - case ANIM_TRANSITION_PREDICATE_LESS_THAN_EQUAL_TO: - conditionsMet = conditionsMet && parameter.value <= condition.value; - break; - case ANIM_TRANSITION_PREDICATE_EQUAL_TO: - conditionsMet = conditionsMet && parameter.value === condition.value; - break; - case ANIM_TRANSITION_PREDICATE_NOT_EQUAL_TO: - conditionsMet = conditionsMet && parameter.value !== condition.value; - break; + Object.defineProperties(AnimTransition.prototype, { + from: { + get: function () { + return this._from; + } + }, + to: { + get: function () { + return this._to; + } + }, + time: { + get: function () { + return this._time; + } + }, + priority: { + get: function () { + return this._priority; + } + }, + conditions: { + get: function () { + return this._conditions; + } + }, + exitTime: { + get: function () { + return this._exitTime; + } + }, + transitionOffset: { + get: function () { + return this._transitionOffset; + } + }, + interruptionSource: { + get: function () { + return this._interruptionSource; + } + }, + hasExitTime: { + get: function () { + return !!this.exitTime; + } + }, + hasConditionsMet: { + get: function () { + var conditionsMet = true; + var i; + for (i = 0; i < this.conditions.length; i++) { + var condition = this.conditions[i]; + var parameter = this._controller.findParameter(condition.parameterName); + switch (condition.predicate) { + case ANIM_GREATER_THAN: + conditionsMet = conditionsMet && parameter.value > condition.value; + break; + case ANIM_LESS_THAN: + conditionsMet = conditionsMet && parameter.value < condition.value; + break; + case ANIM_GREATER_THAN_EQUAL_TO: + conditionsMet = conditionsMet && parameter.value >= condition.value; + break; + case ANIM_LESS_THAN_EQUAL_TO: + conditionsMet = conditionsMet && parameter.value <= condition.value; + break; + case ANIM_EQUAL_TO: + conditionsMet = conditionsMet && parameter.value === condition.value; + break; + case ANIM_NOT_EQUAL_TO: + conditionsMet = conditionsMet && parameter.value !== condition.value; + break; + } + if (!conditionsMet) + return conditionsMet; } - if (!conditionsMet) - return conditionsMet; + return conditionsMet; } - return conditionsMet; } }); @@ -224,53 +213,51 @@ Object.assign(pc, function () { this._currTransitionTime = 1.0; this._totalTransitionTime = 1.0; this._isTransitioning = false; - this._transitionInterruptionSource = ANIM_INTERRUPTION_SOURCE_NONE; + this._transitionInterruptionSource = ANIM_INTERRUPTION_NONE; this._transitionPreviousStates = []; this._timeInState = 0; this._timeInStateBefore = 0; }; - Object.defineProperty(AnimController.prototype, 'animEvaluator', { - get: function () { - return this._animEvaluator; - } - }); - - Object.defineProperty(AnimController.prototype, 'activeState', { - get: function () { - return this._findState(this._activeStateName); + Object.defineProperties(AnimState.prototype, { + animEvaluator: { + get: function () { + return this._animEvaluator; + } }, - set: function (stateName) { - this._activeStateName = stateName; - } - }); - - Object.defineProperty(AnimController.prototype, 'previousState', { - get: function () { - return this._findState(this._previousStateName); + activeState: { + get: function () { + return this._findState(this._activeStateName); + }, + set: function (stateName) { + this._activeStateName = stateName; + } }, - set: function (stateName) { - this._previousStateName = stateName; - } - }); - - Object.defineProperty(AnimController.prototype, 'playable', { - get: function () { - var playable = true; - var i; - for (i = 0; i < this._states.length; i++) { - if (!this._states[i].playable) { - playable = false; + previousState: { + get: function () { + return this._findState(this._previousStateName); + }, + set: function (stateName) { + this._previousStateName = stateName; + } + }, + playable: { + get: function () { + var playable = true; + var i; + for (i = 0; i < this._states.length; i++) { + if (!this._states[i].playable) { + playable = false; + } } + return playable; + } + }, + activeStateProgress: { + get: function () { + return this._getActiveStateProgressForTime(this._timeInState); } - return playable; - } - }); - - Object.defineProperty(AnimController.prototype, 'activeStateProgress', { - get: function () { - return this._getActiveStateProgressForTime(this._timeInState); } }); @@ -337,21 +324,21 @@ Object.assign(pc, function () { transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName)); } else { switch (this._transitionInterruptionSource) { - case ANIM_INTERRUPTION_SOURCE_PREV_STATE: + case ANIM_INTERRUPTION_PREV: transitions = transitions.concat(this._findTransitionsFromState(this._previousStateName)); break; - case ANIM_INTERRUPTION_SOURCE_NEXT_STATE: + case ANIM_INTERRUPTION_NEXT: transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName)); break; - case ANIM_INTERRUPTION_SOURCE_PREV_STATE_NEXT_STATE: + case ANIM_INTERRUPTION_PREV_NEXT: transitions = transitions.concat(this._findTransitionsFromState(this._previousStateName)); transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName)); break; - case ANIM_INTERRUPTION_SOURCE_NEXT_STATE_PREV_STATE: + case ANIM_INTERRUPTION_NEXT_PREV: transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName)); transitions = transitions.concat(this._findTransitionsFromState(this._previousStateName)); break; - case ANIM_INTERRUPTION_SOURCE_NONE: + case ANIM_INTERRUPTION_NONE: default: } } @@ -436,7 +423,7 @@ Object.assign(pc, function () { this._animEvaluator.addClip(clip); } if (transition.time > 0) { - clip.blendWeight = 0.0 / activeState.totalWeight; + clip.blendWeight = 0.0; } else { clip.blendWeight = 1.0 / activeState.totalWeight; } @@ -612,18 +599,18 @@ Object.assign(pc, function () { }); return { - ANIM_INTERRUPTION_SOURCE_NONE: ANIM_INTERRUPTION_SOURCE_NONE, - ANIM_INTERRUPTION_SOURCE_PREV_STATE: ANIM_INTERRUPTION_SOURCE_PREV_STATE, - ANIM_INTERRUPTION_SOURCE_NEXT_STATE: ANIM_INTERRUPTION_SOURCE_NEXT_STATE, - ANIM_INTERRUPTION_SOURCE_PREV_STATE_NEXT_STATE: ANIM_INTERRUPTION_SOURCE_PREV_STATE_NEXT_STATE, - ANIM_INTERRUPTION_SOURCE_NEXT_STATE_PREV_STATE: ANIM_INTERRUPTION_SOURCE_NEXT_STATE_PREV_STATE, - - ANIM_TRANSITION_PREDICATE_GREATER_THAN: ANIM_TRANSITION_PREDICATE_GREATER_THAN, - ANIM_TRANSITION_PREDICATE_LESS_THAN: ANIM_TRANSITION_PREDICATE_LESS_THAN, - ANIM_TRANSITION_PREDICATE_GREATER_THAN_EQUAL_TO: ANIM_TRANSITION_PREDICATE_GREATER_THAN_EQUAL_TO, - ANIM_TRANSITION_PREDICATE_LESS_THAN_EQUAL_TO: ANIM_TRANSITION_PREDICATE_LESS_THAN_EQUAL_TO, - ANIM_TRANSITION_PREDICATE_EQUAL_TO: ANIM_TRANSITION_PREDICATE_EQUAL_TO, - ANIM_TRANSITION_PREDICATE_NOT_EQUAL_TO: ANIM_TRANSITION_PREDICATE_NOT_EQUAL_TO, + ANIM_INTERRUPTION_NONE: ANIM_INTERRUPTION_NONE, + ANIM_INTERRUPTION_PREV: ANIM_INTERRUPTION_PREV, + ANIM_INTERRUPTION_NEXT: ANIM_INTERRUPTION_NEXT, + ANIM_INTERRUPTION_PREV_NEXT: ANIM_INTERRUPTION_PREV_NEXT, + ANIM_INTERRUPTION_NEXT_PREV: ANIM_INTERRUPTION_NEXT_PREV, + + ANIM_GREATER_THAN: ANIM_GREATER_THAN, + ANIM_LESS_THAN: ANIM_LESS_THAN, + ANIM_GREATER_THAN_EQUAL_TO: ANIM_GREATER_THAN_EQUAL_TO, + ANIM_LESS_THAN_EQUAL_TO: ANIM_LESS_THAN_EQUAL_TO, + ANIM_EQUAL_TO: ANIM_EQUAL_TO, + ANIM_NOT_EQUAL_TO: ANIM_NOT_EQUAL_TO, ANIM_PARAMETER_INTEGER: ANIM_PARAMETER_INTEGER, ANIM_PARAMETER_FLOAT: ANIM_PARAMETER_FLOAT, From 5304e5423e71337a2031f83df6a64edacd7059d8 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Mon, 11 May 2020 09:54:12 +0100 Subject: [PATCH 037/144] animation clips can store multi component keyframe outputs --- src/framework/components/anim/controller.js | 4 ++-- src/resources/animation-clip.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 4ecdaeb9b72..5d044aa7c79 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -93,7 +93,7 @@ Object.assign(pc, function () { this._conditions = conditions || []; this._exitTime = exitTime || null; this._transitionOffset = transitionOffset || null; - this._interruptionSource = interruptionSource || ANIM_INTERRUPTION_SOURCE_NONE; + this._interruptionSource = interruptionSource || ANIM_INTERRUPTION_NONE; }; Object.defineProperties(AnimTransition.prototype, { @@ -220,7 +220,7 @@ Object.assign(pc, function () { this._timeInStateBefore = 0; }; - Object.defineProperties(AnimState.prototype, { + Object.defineProperties(AnimController.prototype, { animEvaluator: { get: function () { return this._animEvaluator; diff --git a/src/resources/animation-clip.js b/src/resources/animation-clip.js index 39b23b5747f..c6d323dfa2a 100644 --- a/src/resources/animation-clip.js +++ b/src/resources/animation-clip.js @@ -45,7 +45,7 @@ Object.assign(pc, function () { return new pc.AnimData(1, input); }); var outputs = data.outputs.map(function (output) { - return new pc.AnimData(1, output); + return new pc.AnimData(output.components, output.data); }); var curves = data.curves.map(function (curve) { return new pc.AnimCurve( From d94c1a3041ff62242fe0c9698c269ba570935531 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Mon, 11 May 2020 14:44:54 +0100 Subject: [PATCH 038/144] Add an animatable properties engine example --- examples/animation/animatable-properties.html | 182 ++++++++++++++++++ .../animation-clips/alarm-off-clip.json | 49 +++++ .../animation-clips/alarm-on-clip.json | 66 +++++++ .../state-graphs/alarm-state-graph.json | 54 ++++++ 4 files changed, 351 insertions(+) create mode 100644 examples/animation/animatable-properties.html create mode 100644 examples/assets/animations/animation-clips/alarm-off-clip.json create mode 100644 examples/assets/animations/animation-clips/alarm-on-clip.json create mode 100644 examples/assets/animations/state-graphs/alarm-state-graph.json diff --git a/examples/animation/animatable-properties.html b/examples/animation/animatable-properties.html new file mode 100644 index 00000000000..8bb8259ffb0 --- /dev/null +++ b/examples/animation/animatable-properties.html @@ -0,0 +1,182 @@ + + + + Playcanvas Animation + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/assets/animations/animation-clips/alarm-off-clip.json b/examples/assets/animations/animation-clips/alarm-off-clip.json new file mode 100644 index 00000000000..3596e3f03ef --- /dev/null +++ b/examples/assets/animations/animation-clips/alarm-off-clip.json @@ -0,0 +1,49 @@ +{ + "name": "normalClip", + "duration": 1.0, + "inputs": [ + [ + 0.0 + ] + ], + "outputs": [ + { + "components": 4, + "data": [ + 1.0, 1.0, 1.0, 1.0 + ] + }, + { + "components": 4, + "data": [ + 0.0, 0.0, 0.0, 0.0 + ] + } + ], + "curves": [ + { + "path": "lights.spotLight1/light/color", + "inputIndex": 0, + "outputIndex": 0, + "interpolation": 1 + }, + { + "path": "lights.spotLight2/light/color", + "inputIndex": 0, + "outputIndex": 0, + "interpolation": 1 + }, + { + "path": "lights.spotLight2/entity/localEulerAngles", + "inputIndex": 0, + "outputIndex": 1, + "interpolation": 1 + }, + { + "path": "lights.spotLight1/entity/localEulerAngles", + "inputIndex": 0, + "outputIndex": 1, + "interpolation": 1 + } + ] +} \ No newline at end of file diff --git a/examples/assets/animations/animation-clips/alarm-on-clip.json b/examples/assets/animations/animation-clips/alarm-on-clip.json new file mode 100644 index 00000000000..752d412799c --- /dev/null +++ b/examples/assets/animations/animation-clips/alarm-on-clip.json @@ -0,0 +1,66 @@ +{ + "name": "alarmClip", + "duration": 2.0, + "inputs": [ + [ + 0.0, 0.5, 1.0, 1.5, 2.0 + ], + [ + 0, 1, 2 + ] + ], + "outputs": [ + { + "components": 4, + "data": [ + 1.0, 0.0, 0.0, 1.0, + 0.4, 0.0, 0.0, 1.0, + 1.0, 0.0, 0.0, 1.0, + 0.4, 0.0, 0.0, 1.0, + 1.0, 0.0, 0.0, 1.0 + ] + }, + { + "components": 4, + "data": [ + 4.0, 0.0, 0.0, 0.0, + 4.0, 180.0, 0.0, 0.0, + 4.0, 0.0, 0.0, 0.0 + ] + }, + { + "components": 4, + "data": [ + -4.0, 0.0, 0.0, 0.0, + -4.0, 180.0, 0.0, 0.0, + -4.0, 0.0, 0.0, 0.0 + ] + } + ], + "curves": [ + { + "path": "lights.spotLight1/light/color", + "inputIndex": 0, + "outputIndex": 0, + "interpolation": 1 + }, + { + "path": "lights.spotLight2/light/color", + "inputIndex": 0, + "outputIndex": 0, + "interpolation": 1 + }, + { + "path": "lights.spotLight1/entity/localEulerAngles", + "inputIndex": 1, + "outputIndex": 1, + "interpolation": 1 + }, + { + "path": "lights.spotLight2/entity/localEulerAngles", + "inputIndex": 1, + "outputIndex": 2, + "interpolation": 1 + } + ] +} \ No newline at end of file diff --git a/examples/assets/animations/state-graphs/alarm-state-graph.json b/examples/assets/animations/state-graphs/alarm-state-graph.json new file mode 100644 index 00000000000..d43880f8116 --- /dev/null +++ b/examples/assets/animations/state-graphs/alarm-state-graph.json @@ -0,0 +1,54 @@ +{ + "states": [ + { + "name": "START" + }, + { + "name": "Normal", + "speed": 1.0 + }, + { + "name": "Alarm", + "speed": 1.0 + }, + { + "name": "END" + } + ], + "transitions": [ + { + "from": "START", + "to": "Normal" + }, + { + "from": "Normal", + "to": "Alarm", + "time": 0.1, + "conditions": [ + { + "parameterName": "alarm", + "predicate": "EQUAL_TO", + "value": true + } + ] + }, + { + "from": "Alarm", + "to": "Normal", + "time": 1.5, + "conditions": [ + { + "parameterName": "alarm", + "predicate": "EQUAL_TO", + "value": false + } + ] + } + ], + "parameters": { + "alarm": { + "type": "BOOLEAN", + "value": false + } + } +} \ No newline at end of file From a0569788a727ddc73d946091935eed38b14811cb Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Mon, 11 May 2020 19:04:30 +0100 Subject: [PATCH 039/144] update the animatable properties example --- examples/animation/animatable-properties.html | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/examples/animation/animatable-properties.html b/examples/animation/animatable-properties.html index 8bb8259ffb0..aeb4af9282d 100644 --- a/examples/animation/animatable-properties.html +++ b/examples/animation/animatable-properties.html @@ -101,6 +101,17 @@ modelEntity.setPosition(0, 0.25, 0); modelEntity.setLocalScale(0.5, 0.5, 0.5); + app.assets.loadFromUrl( + "../assets/playcanvas-grey.png", + "texture", + function (err, asset) { + var material = new pc.StandardMaterial(); + material.diffuseMap = asset.resource; + material.update(); + modelEntity.model.meshInstances[0].material = material; + } + ); + var planeEntity = new pc.Entity(); planeEntity.addComponent("model", { type: "plane" From bf73691c7c0174c9ef3d94e6a7737e4d2b3ea54b Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Mon, 11 May 2020 19:04:48 +0100 Subject: [PATCH 040/144] update the anim component api --- src/framework/components/anim/component.js | 36 +++++++++++++++++++++ src/framework/components/anim/controller.js | 20 ++++++++++++ 2 files changed, 56 insertions(+) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 2227c41bef5..2431d2eedf2 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -253,6 +253,18 @@ Object.assign(pc, function () { return null; } }, + /** + * @name pc.AnimComponent#previousState + * @property {string} previousState - Returns the previously active state name. + */ + previousState: { + get: function () { + if (this.data.animController) { + return this.data.animController.previousStateName; + } + return null; + } + }, /** * @name pc.AnimComponent#activeStateProgress * @property {number} activeStateProgress - Returns the currently active states progress as a value normalised by the states animation duration. Looped animations will return values greater than 1. @@ -264,6 +276,30 @@ Object.assign(pc, function () { } return null; } + }, + /** + * @name pc.AnimComponent#transitioning + * @property {number} transitioning - Returns whether the anim component is currently transitioning between states. + */ + transitioning: { + get: function () { + if (this.data.animController) { + return this.data.animController.transitioning; + } + return null; + } + }, + /** + * @name pc.AnimComponent#transitionProgress + * @property {number} transitionProgress - If the anim component is currently transitioning between states, returns the progress. Otherwise returns null. + */ + transitionProgress: { + get: function () { + if (this.data.animController && this.transitioning) { + return this.data.animController.transitionProgress; + } + return null; + } } }); diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 5d044aa7c79..827d5a8e9de 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -234,6 +234,11 @@ Object.assign(pc, function () { this._activeStateName = stateName; } }, + activeStateName: { + get: function () { + return this._activeStateName; + } + }, previousState: { get: function () { return this._findState(this._previousStateName); @@ -242,6 +247,11 @@ Object.assign(pc, function () { this._previousStateName = stateName; } }, + previousStateName: { + get: function () { + return this._previousStateName; + } + }, playable: { get: function () { var playable = true; @@ -258,6 +268,16 @@ Object.assign(pc, function () { get: function () { return this._getActiveStateProgressForTime(this._timeInState); } + }, + transitioning: { + get: function () { + return this._isTransitioning; + } + }, + transitionProgress: { + get: function () { + return this._currTransitionTime / this._totalTransitionTime; + } } }); From 20162526c5334027c7d65fe0d155cf01b7e0a11c Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Mon, 11 May 2020 19:09:27 +0100 Subject: [PATCH 041/144] jsdocs fix --- src/framework/components/anim/component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 2431d2eedf2..815cd3a9b0a 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -279,7 +279,7 @@ Object.assign(pc, function () { }, /** * @name pc.AnimComponent#transitioning - * @property {number} transitioning - Returns whether the anim component is currently transitioning between states. + * @property {boolean} transitioning - Returns whether the anim component is currently transitioning between states. */ transitioning: { get: function () { From 278f2c9839375ce441e4bc5b3190a46547db6ce1 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Tue, 12 May 2020 16:52:35 +0100 Subject: [PATCH 042/144] correctly handle transition iterruptions which return the controller back to the source state --- .../state-graphs/alarm-state-graph.json | 2 + src/anim/anim.js | 16 ++++++ src/framework/components/anim/controller.js | 56 ++++++++++++------- 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/examples/assets/animations/state-graphs/alarm-state-graph.json b/examples/assets/animations/state-graphs/alarm-state-graph.json index d43880f8116..830342f1e02 100644 --- a/examples/assets/animations/state-graphs/alarm-state-graph.json +++ b/examples/assets/animations/state-graphs/alarm-state-graph.json @@ -24,6 +24,7 @@ "from": "Normal", "to": "Alarm", "time": 0.1, + "interruptionSource": "NEXT_STATE", "conditions": [ { "parameterName": "alarm", @@ -36,6 +37,7 @@ "from": "Alarm", "to": "Normal", "time": 1.5, + "interruptionSource": "NEXT_STATE", "conditions": [ { "parameterName": "alarm", diff --git a/src/anim/anim.js b/src/anim/anim.js index 41cd69b2b2e..4fd3955e8c4 100644 --- a/src/anim/anim.js +++ b/src/anim/anim.js @@ -993,6 +993,22 @@ Object.assign(pc, function () { this._outputs.splice(index, 1); }, + /** + * @private + * @function + * @name pc.AnimEvaluator#removeClipByName + * @description Remove a clip from the controller by name. + * @param {number} name - name of the clip to remove. + */ + removeClipByName: function (name) { + for (var i = 0; i < this._clips.length; i++) { + if (this.clips[i].name === name) { + this.removeClip(i); + return; + } + } + }, + /** * @private * @function diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 827d5a8e9de..5220f1a7be3 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -366,6 +366,10 @@ Object.assign(pc, function () { // filter out transitions that don't have their conditions met transitions = transitions.filter(function (transition) { + // if the transition is moving to the already active state, ignore it + if (transition.to === this.activeStateName) { + return false; + } // when an exit time is present, we should only exit if it falls within the current frame delta time if (transition.hasExitTime) { var progressBefore = this._getActiveStateProgressForTime(this._timeInStateBefore); @@ -393,6 +397,9 @@ Object.assign(pc, function () { _updateStateFromTransition: function (transition) { var i; var j; + var state; + var animation; + var clip; this.previousState = this._activeStateName; this.activeState = transition.to; @@ -404,25 +411,34 @@ Object.assign(pc, function () { this.setParameterValue(triggers[i].parameterName, ANIM_PARAMETER_TRIGGER, false); } - if (this._isTransitioning) { - var interpolatedTime = this._currTransitionTime / this._totalTransitionTime; - for (i = 0; i < this._transitionPreviousStates.length; i++) { + if (!this._isTransitioning) { + this._transitionPreviousStates = []; + } + + this._transitionPreviousStates.push({ + name: this._previousStateName, + weight: 1 + }); + + var interpolatedTime = this._currTransitionTime / this._totalTransitionTime; + for (i = 0; i < this._transitionPreviousStates.length; i++) { + if (i !== this._transitionPreviousStates.length - 1) { this._transitionPreviousStates[i].weight *= (1.0 - interpolatedTime); - var state = this._findState(this._transitionPreviousStates[i].name); - for (j = 0; j < state.animations.length; j++) { - var animation = state.animations[j]; - this._animEvaluator.findClip(animation.name).pause(); + } else { + this._transitionPreviousStates[i].weight = interpolatedTime; + } + state = this._findState(this._transitionPreviousStates[i].name); + for (j = 0; j < state.animations.length; j++) { + animation = state.animations[j]; + clip = this._animEvaluator.findClip(animation.name + '.previous'); + if (!clip) { + clip = this._animEvaluator.findClip(animation.name); + clip.name = animation.name + '.previous'; + } + if (i !== this._transitionPreviousStates.length - 1) { + clip.pause(); } } - this._transitionPreviousStates.push({ - name: this._previousStateName, - weight: interpolatedTime - }); - } else { - this._transitionPreviousStates.push({ - name: this._previousStateName, - weight: 1.0 - }); } if (transition.time > 0) { @@ -436,7 +452,7 @@ Object.assign(pc, function () { var activeState = this.activeState; for (i = 0; i < activeState.animations.length; i++) { - var clip = this._animEvaluator.findClip(activeState.animations[i].name); + clip = this._animEvaluator.findClip(activeState.animations[i].name); if (!clip) { clip = new pc.AnimClip(activeState.animations[i].animTrack, 0, activeState.speed, true, true); clip.name = activeState.animations[i].name; @@ -556,9 +572,7 @@ Object.assign(pc, function () { state = this._findState(this._transitionPreviousStates[i].name); for (j = 0; j < state.animations.length; j++) { animation = state.animations[j]; - var clip = this._animEvaluator.findClip(animation.name); - clip.pause(); - clip.blendWeight = 0; + this._animEvaluator.removeClipByName(animation.name + '.previous'); } } @@ -577,7 +591,7 @@ Object.assign(pc, function () { var stateWeight = this._transitionPreviousStates[i].weight; for (j = 0; j < state.animations.length; j++) { animation = state.animations[j]; - this._animEvaluator.findClip(animation.name).blendWeight = (1.0 - interpolatedTime) * animation.weight / state.totalWeight * stateWeight; + this._animEvaluator.findClip(animation.name + '.previous').blendWeight = (1.0 - interpolatedTime) * animation.weight / state.totalWeight * stateWeight; } } state = this.activeState; From e06b939e8d7419c77efb235984c42824ad47a401 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Tue, 12 May 2020 17:07:17 +0100 Subject: [PATCH 043/144] add a character animation example --- examples/animation/animatable-properties.html | 2 +- examples/animation/animated-character.html | 254 ++++++++++++++++++ .../state-graphs/character-state-graph.json | 188 +++++++++++++ 3 files changed, 443 insertions(+), 1 deletion(-) create mode 100644 examples/animation/animated-character.html create mode 100644 examples/assets/animations/state-graphs/character-state-graph.json diff --git a/examples/animation/animatable-properties.html b/examples/animation/animatable-properties.html index aeb4af9282d..66f2314f59f 100644 --- a/examples/animation/animatable-properties.html +++ b/examples/animation/animatable-properties.html @@ -102,7 +102,7 @@ modelEntity.setLocalScale(0.5, 0.5, 0.5); app.assets.loadFromUrl( - "../assets/playcanvas-grey.png", + "../assets/textures/playcanvas-grey.png", "texture", function (err, asset) { var material = new pc.StandardMaterial(); diff --git a/examples/animation/animated-character.html b/examples/animation/animated-character.html new file mode 100644 index 00000000000..37a95f9a3d6 --- /dev/null +++ b/examples/animation/animated-character.html @@ -0,0 +1,254 @@ + + + + + Playcanvas Animation + + + + + + + + +
+ +
+ Speed: 0 +
+
+ + + + + + \ No newline at end of file diff --git a/examples/assets/animations/state-graphs/character-state-graph.json b/examples/assets/animations/state-graphs/character-state-graph.json new file mode 100644 index 00000000000..81c66fae6fa --- /dev/null +++ b/examples/assets/animations/state-graphs/character-state-graph.json @@ -0,0 +1,188 @@ +{ + "states": [ + { + "name": "START" + }, + { + "name": "Idle", + "speed": 1.0 + }, + { + "name": "Walk", + "speed": 1.0 + }, + { + "name": "WalkBackwards", + "speed": 1.0 + }, + { + "name": "Run", + "speed": 1.0 + }, + { + "name": "Jump", + "speed": 1.0 + }, + { + "name": "END" + } + ], + "transitions": [ + { + "from": "START", + "to": "Idle", + "time": 0, + "priority": 0 + }, + { + "from": "Idle", + "to": "Walk", + "time": 0.4, + "priority": 0, + "conditions": [ + { + "parameterName": "speed", + "predicate": "GREATER_THAN", + "value": 0 + } + ] + }, + { + "from": "Idle", + "to": "WalkBackwards", + "time": 0.4, + "priority": 0, + "conditions": [ + { + "parameterName": "speed", + "predicate": "LESS_THAN", + "value": 0 + } + ] + }, + { + "from": "WalkBackwards", + "to": "Idle", + "time": 0.4, + "priority": 0, + "conditions": [ + { + "parameterName": "speed", + "predicate": "GREATER_THAN_EQUAL_TO", + "value": 0 + } + ] + }, + { + "from": "Idle", + "to": "Jump", + "time": 0.2, + "priority": 0, + "transitionOffset": 0.0, + "conditions": [ + { + "parameterName": "jump", + "predicate": "EQUAL_TO", + "value": true + } + ] + }, + { + "from": "WalkBackwards", + "to": "Jump", + "time": 0.2, + "priority": 0, + "transitionOffset": 0.0, + "conditions": [ + { + "parameterName": "jump", + "predicate": "EQUAL_TO", + "value": true + } + ] + }, + { + "from": "Walk", + "to": "Jump", + "time": 0.2, + "priority": 0, + "conditions": [ + { + "parameterName": "jump", + "predicate": "EQUAL_TO", + "value": true + } + ] + }, + { + "from": "Run", + "to": "Jump", + "time": 0.2, + "priority": 0, + "conditions": [ + { + "parameterName": "jump", + "predicate": "EQUAL_TO", + "value": true + } + ] + }, + { + "from": "Jump", + "to": "Idle", + "time": 0.5, + "priority": 0, + "exitTime": 0.75 + }, + { + "from": "Walk", + "to": "Run", + "time": 0.2, + "priority": 0, + "conditions": [ + { + "parameterName": "speed", + "predicate": "GREATER_THAN", + "value": 1 + } + ] + }, + { + "from": "Walk", + "to": "Idle", + "time": 0.5, + "priority": 0, + "conditions": [ + { + "parameterName": "speed", + "predicate": "LESS_THAN_EQUAL_TO", + "value": 0 + } + ] + }, + { + "from": "Run", + "to": "Walk", + "time": 0.2, + "exitTime": 0.8, + "priority": 0, + "conditions": [ + { + "parameterName": "speed", + "predicate": "LESS_THAN_EQUAL_TO", + "value": 1 + } + ] + } + ], + "parameters": { + "speed": { + "type": "INTEGER", + "value": 0 + }, + "jump": { + "type": "TRIGGER", + "value": false + } + } +} \ No newline at end of file From 1aa88280379b6c114dad5a813519206011037ee2 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Wed, 13 May 2020 11:46:17 +0100 Subject: [PATCH 044/144] record the previous transition states index in its name to uniquely identify it --- src/framework/components/anim/controller.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 5220f1a7be3..e1af5fa4f45 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -430,10 +430,10 @@ Object.assign(pc, function () { state = this._findState(this._transitionPreviousStates[i].name); for (j = 0; j < state.animations.length; j++) { animation = state.animations[j]; - clip = this._animEvaluator.findClip(animation.name + '.previous'); + clip = this._animEvaluator.findClip(animation.name + '.previous.' + i); if (!clip) { clip = this._animEvaluator.findClip(animation.name); - clip.name = animation.name + '.previous'; + clip.name = animation.name + '.previous.' + i; } if (i !== this._transitionPreviousStates.length - 1) { clip.pause(); @@ -572,7 +572,7 @@ Object.assign(pc, function () { state = this._findState(this._transitionPreviousStates[i].name); for (j = 0; j < state.animations.length; j++) { animation = state.animations[j]; - this._animEvaluator.removeClipByName(animation.name + '.previous'); + this._animEvaluator.removeClipByName(animation.name + '.previous.' + i); } } @@ -591,7 +591,7 @@ Object.assign(pc, function () { var stateWeight = this._transitionPreviousStates[i].weight; for (j = 0; j < state.animations.length; j++) { animation = state.animations[j]; - this._animEvaluator.findClip(animation.name + '.previous').blendWeight = (1.0 - interpolatedTime) * animation.weight / state.totalWeight * stateWeight; + this._animEvaluator.findClip(animation.name + '.previous.' + i).blendWeight = (1.0 - interpolatedTime) * animation.weight / state.totalWeight * stateWeight; } } state = this.activeState; From e55b9d40be74f69d7d5ffe95ceb4d06b68baafe1 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Wed, 13 May 2020 12:51:55 +0100 Subject: [PATCH 045/144] performance update and comments for the anim controller --- src/anim/anim.js | 16 ------ src/framework/components/anim/controller.js | 56 +++++++++++++-------- 2 files changed, 35 insertions(+), 37 deletions(-) diff --git a/src/anim/anim.js b/src/anim/anim.js index 4fd3955e8c4..41cd69b2b2e 100644 --- a/src/anim/anim.js +++ b/src/anim/anim.js @@ -993,22 +993,6 @@ Object.assign(pc, function () { this._outputs.splice(index, 1); }, - /** - * @private - * @function - * @name pc.AnimEvaluator#removeClipByName - * @description Remove a clip from the controller by name. - * @param {number} name - name of the clip to remove. - */ - removeClipByName: function (name) { - for (var i = 0; i < this._clips.length; i++) { - if (this.clips[i].name === name) { - this.removeClip(i); - return; - } - } - }, - /** * @private * @function diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index e1af5fa4f45..d5c82755956 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -299,6 +299,7 @@ Object.assign(pc, function () { return null; }, + // return all the transitions that have the given stateName as their source state _findTransitionsFromState: function (stateName) { var transitions = this._findTransitionsFromStateCache[stateName]; if (!transitions) { @@ -316,6 +317,7 @@ Object.assign(pc, function () { return transitions; }, + // return all the transitions that contain the given source and destination states _findTransitionsBetweenStates: function (sourceStateName, destinationStateName) { var transitions = this._findTransitionsBetweenStatesCache[sourceStateName + '->' + destinationStateName]; if (!transitions) { @@ -336,10 +338,11 @@ Object.assign(pc, function () { _findTransition: function (from, to) { var transitions = []; - // find transitions that include the required source and destination states if (from && to) { + // find transitions that include the required source and destination states if from and to is supplied transitions.concat(this._findTransitionsBetweenStates(this._activeStateName)); } else { + // otherwise look for transitions from the previous and active states based on the current interruption source if (!this._isTransitioning) { transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName)); } else { @@ -384,9 +387,11 @@ Object.assign(pc, function () { return null; } } + // if the exitTime condition has been met or is not present, check condition parameters return transition.hasConditionsMet; }.bind(this)); + // return the highest priority transition to use if (transitions.length > 0) { return transitions[0]; } @@ -403,6 +408,7 @@ Object.assign(pc, function () { this.previousState = this._activeStateName; this.activeState = transition.to; + // turn off any triggers which were required to activate this transition var triggers = transition.conditions.filter(function (condition) { var parameter = this.findParameter(condition.parameterName); return parameter.type === ANIM_PARAMETER_TRIGGER; @@ -415,19 +421,25 @@ Object.assign(pc, function () { this._transitionPreviousStates = []; } + // record the transition source state in the previous states array this._transitionPreviousStates.push({ name: this._previousStateName, weight: 1 }); + // if this new transition was activated during another transition, update the previous transition state weights based + // on the progress through the previous transition. var interpolatedTime = this._currTransitionTime / this._totalTransitionTime; for (i = 0; i < this._transitionPreviousStates.length; i++) { + // interpolate the weights of the most recent previous state and all other previous states based on the progress through the previous transition if (i !== this._transitionPreviousStates.length - 1) { this._transitionPreviousStates[i].weight *= (1.0 - interpolatedTime); } else { this._transitionPreviousStates[i].weight = interpolatedTime; } state = this._findState(this._transitionPreviousStates[i].name); + // update the animations of previous states, set their name to include their position in the previous state array + // to uniquely identify animations from the same state that were added during different transitions for (j = 0; j < state.animations.length; j++) { animation = state.animations[j]; clip = this._animEvaluator.findClip(animation.name + '.previous.' + i); @@ -435,12 +447,14 @@ Object.assign(pc, function () { clip = this._animEvaluator.findClip(animation.name); clip.name = animation.name + '.previous.' + i; } + // pause previous animation clips to reduce their impact on performance if (i !== this._transitionPreviousStates.length - 1) { clip.pause(); } } } + // start a new transition based on the current transitions information if (transition.time > 0) { this._isTransitioning = true; this._totalTransitionTime = transition.time; @@ -449,8 +463,8 @@ Object.assign(pc, function () { } var hasTransitionOffset = transition.transitionOffset && transition.transitionOffset > 0.0 && transition.transitionOffset < 1.0; - var activeState = this.activeState; + // Add clips to the evaluator for each animation in the new state. for (i = 0; i < activeState.animations.length; i++) { clip = this._animEvaluator.findClip(activeState.animations[i].name); if (!clip) { @@ -470,6 +484,7 @@ Object.assign(pc, function () { clip.play(); } + // set the time in the new state to 0 or to a value based on transitionOffset if one was given var timeInState = 0; var timeInStateBefore = 0; if (hasTransitionOffset) { @@ -490,6 +505,7 @@ Object.assign(pc, function () { return; } + // move to the given state, if a transition is present in the state graph use it. Otherwise move instantly to it. var transition = this._findTransition(this._activeStateName, newStateName); if (!transition) { this._animEvaluator.removeClips(); @@ -560,46 +576,44 @@ Object.assign(pc, function () { this._timeInStateBefore = this._timeInState; this._timeInState += dt; + // transition between states if a transition is available from the active state var transition = this._findTransition(this._activeStateName); if (transition) this._updateStateFromTransition(transition); if (this._isTransitioning) { - if (this._currTransitionTime >= this._totalTransitionTime) { - this._isTransitioning = false; - + if (this._currTransitionTime < this._totalTransitionTime) { + var interpolatedTime = this._currTransitionTime / this._totalTransitionTime; + // while transitioning, set all previous state animations to be weighted by (1.0 - interpolationTime). for (i = 0; i < this._transitionPreviousStates.length; i++) { state = this._findState(this._transitionPreviousStates[i].name); + var stateWeight = this._transitionPreviousStates[i].weight; for (j = 0; j < state.animations.length; j++) { animation = state.animations[j]; - this._animEvaluator.removeClipByName(animation.name + '.previous.' + i); + this._animEvaluator.findClip(animation.name + '.previous.' + i).blendWeight = (1.0 - interpolatedTime) * animation.weight / state.totalWeight * stateWeight; } } - - this._transitionPreviousStates = []; - + // while transitioning, set active state animations to be weighted by (interpolationTime). state = this.activeState; for (i = 0; i < state.animations.length; i++) { animation = state.animations[i]; - this._animEvaluator.findClip(animation.name).blendWeight = animation.weight / state.totalWeight; + this._animEvaluator.findClip(animation.name).blendWeight = interpolatedTime * animation.weight / state.totalWeight; } } else { - var interpolatedTime = this._currTransitionTime / this._totalTransitionTime; - - for (i = 0; i < this._transitionPreviousStates.length; i++) { - state = this._findState(this._transitionPreviousStates[i].name); - var stateWeight = this._transitionPreviousStates[i].weight; - for (j = 0; j < state.animations.length; j++) { - animation = state.animations[j]; - this._animEvaluator.findClip(animation.name + '.previous.' + i).blendWeight = (1.0 - interpolatedTime) * animation.weight / state.totalWeight * stateWeight; - } + this._isTransitioning = false; + // when a transition ends, remove all previous state clips from the evaluator + var activeClips = this.activeState.animations.length; + var totalClips = this._animEvaluator.clips.length; + for (i = 0; i < totalClips - activeClips; i++) { + this._animEvaluator.removeClip(0); } + this._transitionPreviousStates = []; + // when a transition ends, set the active state clip weights so they sum to 1 state = this.activeState; for (i = 0; i < state.animations.length; i++) { animation = state.animations[i]; - this._animEvaluator.findClip(animation.name).blendWeight = interpolatedTime * animation.weight / state.totalWeight; + this._animEvaluator.findClip(animation.name).blendWeight = animation.weight / state.totalWeight; } - } this._currTransitionTime += dt; } From 1afbd4ab729fe4534c26ebc6cc9cc3eec7dbe48a Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Mon, 18 May 2020 15:20:38 +0100 Subject: [PATCH 046/144] add anim component layers to the anim component --- src/framework/components/anim/component.js | 417 +++++++++++++------- src/framework/components/anim/controller.js | 39 +- src/framework/components/anim/data.js | 6 +- src/framework/components/anim/system.js | 11 +- 4 files changed, 294 insertions(+), 179 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 815cd3a9b0a..3916bcfe6ff 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -1,5 +1,165 @@ Object.assign(pc, function () { + var AnimComponentLayer = function (name, controller, order, component) { + this._name = name; + this._controller = controller; + this._order = order; + this._component = component; + }; + + Object.assign(AnimComponentLayer.prototype, { + /** + * @function + * @name pc.AnimComponentLayer#play + * @description Start playing the animation in the current state. + * @param {string} name - If provided, will begin playing from the start of the state with this name. + */ + play: function (name) { + this._controller.play(name); + }, + + /** + * @function + * @name pc.AnimComponentLayer#pause + * @description Start playing the animation in the current state. + */ + pause: function () { + this._controller.pause(); + }, + + /** + * @function + * @name pc.AnimComponentLayer#reset + * @description Reset the animation component to it's initial state, including all parameters. The system will be paused. + */ + reset: function () { + this._controller.reset(); + }, + + update: function (dt) { + this._controller.update(dt); + }, + + /** + * @function + * @name pc.AnimComponentLayer#assignAnimation + * @description Associates an animation with a state in the loaded state graph. If all states are linked and the pc.AnimComponent.activate value was set to true then the component will begin playing. + * @param {string} stateName - The name of the state that this animation should be associated with. + * @param {object} animTrack - The animation track that will be assigned to this state and played whenever this state is active. + */ + assignAnimation: function (stateName, animTrack) { + this._controller.assignAnimation(stateName, animTrack); + + if (this._component.activate) { + for (var i = 0; i < this._component.data.layers.length; i++) { + if (!this._component.data.layers[i].playable) { + return; + } + this._component.playing = true; + } + } + }, + + /** + * @function + * @name pc.AnimComponentLayer#removeStateAnimations + * @description Removes animations from a state in the loaded state graph. + * @param {string} stateName - The name of the state that should have its animation tracks removed. + */ + removeStateAnimations: function (stateName) { + this._controller.removeStateAnimations(stateName); + }, + + getParameterValue: function (name, type) { + this._controller.getParameterValue(name, type); + }, + + setParameterValue: function (name, type, value) { + this._controller.setParameterValue(name, type, value); + } + }); + + Object.defineProperties(AnimComponentLayer.prototype, { + /** + * @name pc.AnimComponentLayer#name + * @property {string} name - Returns the name of the layer + */ + name: { + get: function () { + return this._name; + } + }, + /** + * @name pc.AnimComponentLayer#playing + * @property {string} playing - Whether this layer is currently playing + */ + playing: { + get: function () { + return this._controller.playing; + }, + set: function (value) { + this._controller.playing = value; + } + }, + /** + * @name pc.AnimComponentLayer#playable + * @property {string} playable - Returns true if a state graph has been loaded and all states in the graph have been assigned animation tracks. + */ + playable: { + get: function () { + return this._controller.playable; + } + }, + /** + * @name pc.AnimComponentLayer#activeState + * @property {string} activeState - Returns the currently active state name. + */ + activeState: { + get: function () { + return this._controller.activeStateName; + } + }, + /** + * @name pc.AnimComponentLayer#previousState + * @property {string} previousState - Returns the previously active state name. + */ + previousState: { + get: function () { + return this._controller.previousStateName; + } + }, + /** + * @name pc.AnimComponentLayer#activeStateProgress + * @property {number} activeStateProgress - Returns the currently active states progress as a value normalised by the states animation duration. Looped animations will return values greater than 1. + */ + activeStateProgress: { + get: function () { + return this._controller.activeStateProgress; + } + }, + /** + * @name pc.AnimComponentLayer#transitioning + * @property {boolean} transitioning - Returns whether the anim component layer is currently transitioning between states. + */ + transitioning: { + get: function () { + return this._controller.transitioning; + } + }, + /** + * @name pc.AnimComponentLayer#transitionProgress + * @property {number} transitionProgress - If the anim component layer is currently transitioning between states, returns the progress. Otherwise returns null. + */ + transitionProgress: { + get: function () { + if (this.transitioning) { + return this._controller.transitionProgress; + } + return null; + } + } + }); + /** * @component Anim * @class @@ -22,12 +182,10 @@ Object.assign(pc, function () { /** * @function * @name pc.AnimComponent#loadStateGraph - * @description Loads a state graph asset resource into the component. Then initialises the components animation controller. + * @description Initialises component animation controllers using the provided state graph. * @param {object} stateGraph - The state graph asset to load into the component. Contains the states, transitions and parameters used to define a complete animation controller. */ loadStateGraph: function (stateGraph) { - var data = this.data; - var graph; var modelComponent = this.entity.model; if (modelComponent) { @@ -37,85 +195,131 @@ Object.assign(pc, function () { } } - var animBinder = new pc.AnimComponentBinder(this, graph); - var animEvaluator = new pc.AnimEvaluator(animBinder); + var data = this.data; + + data.parameters = stateGraph.parameters; + data.initialParameters = Object.assign({}, stateGraph.parameters); + + function addLayer(name, states, transitions, order) { + var animBinder = new pc.AnimComponentBinder(this, graph); + var animEvaluator = new pc.AnimEvaluator(animBinder); + var controller = new pc.AnimController( + animEvaluator, + states, + transitions, + data.parameters, + data.activate + ); + data.layers.push(new AnimComponentLayer(name, controller, order, this)); + data.layerIndicies[name] = order; + } + + if (stateGraph.layers) { + for (var i = 0; i < stateGraph.layers; i++) { + var layer = stateGraph.layers[i]; + addLayer.bind(this)(layer.name, layer.states, layer.transitions, i); + } + } else { + addLayer.bind(this)('DEFAULT_LAYER', stateGraph.states, stateGraph.transitions, 0); + } + }, - data.animController = new pc.AnimController( - animEvaluator, - stateGraph.states, - stateGraph.transitions, - stateGraph.parameters, - this.data.activate - ); + /** + * @function + * @name pc.AnimComponent#removeStateGraph + * @description Removes all layers from the anim component. + */ + removeStateGraph: function () { + this.data.layers = []; + this.data.layerIndicies = {}; + this.data.parameters = {}; + this.data.initialParameters = {}; }, /** * @function - * @name pc.AnimComponent#assignAnimation - * @description Associates an animation with a state in the loaded state graph. If all states are linked and the pc.AnimComponent.activate value was set to true then the component will begin playing. - * @param {string} stateName - The name of the state that this animation should be associated with. - * @param {object} animTrack - The animation that will linked to this state and played whenever this state is active. + * @name pc.AnimComponent#reset + * @description Reset all of the components layers and parameters to their initial states. If a layer was playing before it will continue playing */ - assignAnimation: function (stateName, animTrack) { - if (!this.data.animController) { - // #ifdef DEBUG - console.error('assignAnimation: Trying to assign an anim track before the state graph has been loaded. Have you called loadStateGraph?'); - // #endif - return; + reset: function () { + this.data.parameters = Object.assign({}, this.data.initialParameters); + for (var i = 0; i < this.data.layers.length; i++) { + var layerPlaying = this.data.layers[i].playing; + this.data.layers[i].reset(); + this.data.layers[i].playing = layerPlaying; } - this.data.animController.assignAnimation(stateName, animTrack); }, /** * @function - * @name pc.AnimComponent#play - * @description Start playing the animation in the current state. - * @param {string} name - If provided, will begin playing from the start of the state with this name. + * @name pc.AnimComponent#findAnimationLayer + * @description Finds a pc.AnimComponentLayer in this component. + * @param {string} layerName - The name of the anim component layer to find + * @returns {pc.AnimComponentLayer} layer */ - play: function (name) { - if (!this.enabled || !this.entity.enabled) { - return; - } + findAnimationLayer: function (layerName) { + var layerIndex = this.data.layerIndicies[layerName]; + return this.data.layers[layerIndex]; + }, - if (!this.data.animController) { + /** + * @function + * @name pc.AnimComponent#assignAnimation + * @description Associates an animation with a state in the loaded state graph. If all states are linked and the pc.AnimComponent.activate value was set to true then the component will begin playing. + * @param {string} stateName - The name of the state that this animation should be associated with. + * @param {object} animTrack - The animation track that will be assigned to this state and played whenever this state is active. + * @param {string?} layerName - The name of the anim component layer to update. If omitted the default layer is used. + */ + assignAnimation: function (stateName, animTrack, layerName) { + layerName = layerName || 'DEFAULT_LAYER'; + var layer = this.findAnimationLayer(layerName); + if (!layer) { // #ifdef DEBUG - console.error('Trying to play an animation when no animation state machine has been loaded. Have you called loadStateGraph?'); + console.error('assignAnimation: Trying to assign an anim track before the state graph has been loaded. Have you called loadStateGraph?'); // #endif return; } - - this.data.animController.play(name); + layer.assignAnimation(stateName, animTrack); }, /** * @function - * @name pc.AnimComponent#pause - * @description Start playing the animation in the current state. + * @name pc.AnimComponent#removeStateAnimations + * @description Removes animations from a state in the loaded state graph. + * @param {string} stateName - The name of the state that should have its animation tracks removed. + * @param {string?} layerName - The name of the anim component layer to update. If omitted the default layer is used. */ - pause: function () { - if (!this.enabled || !this.entity.enabled) { - return; - } - - if (!this.data.animController) { + removeStateAnimations: function (stateName, layerName) { + layerName = layerName || 'DEFAULT_LAYER'; + var layer = this.findAnimationLayer(layerName); + if (!layer) { // #ifdef DEBUG - console.error('Trying to pause the anim component when no animation graph has been loaded. Have you called loadStateGraph?'); + console.error('removeStateAnimations: Trying to remove animation tracks from a state before the state graph has been loaded. Have you called loadStateGraph?'); // #endif return; } + layer.removeStateAnimations(stateName); + }, - this.data.animController.pause(); + getParameterValue: function (name, type) { + var param = this.data.parameters[name]; + if (param && param.type === type) { + return param.value; + } + // #ifdef DEBUG + console.log('Cannot get parameter value. No parameter found in anim controller named "' + name + '" of type "' + type + '"'); + // #endif }, - /** - * @function - * @name pc.AnimComponent#reset - * @description Reset the animation component to it's initial state, including all parameters. The system will be paused. - */ - reset: function () { - if (this.data.animController) { - this.data.animController.reset(); + setParameterValue: function (name, type, value) { + var param = this.data.parameters[name]; + if (param && param.type === type) { + param.value = value; + return; } + // #ifdef DEBUG + console.log('Cannot set parameter value. No parameter found in anim controller named "' + name + '" of type "' + type + '"'); + // #endif }, /** @@ -126,9 +330,7 @@ Object.assign(pc, function () { * @returns {number} A float */ getFloat: function (name) { - if (this.data.animController) { - return this.data.animController.getParameterValue(name, pc.ANIM_PARAMETER_FLOAT); - } + return this.getParameterValue(name, pc.ANIM_PARAMETER_FLOAT); }, /** @@ -139,9 +341,7 @@ Object.assign(pc, function () { * @param {number} value - The new float value to set this parameter to. */ setFloat: function (name, value) { - if (this.data.animController) { - this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_FLOAT, value); - } + this.setParameterValue(name, pc.ANIM_PARAMETER_FLOAT, value); }, /** @@ -152,9 +352,7 @@ Object.assign(pc, function () { * @returns {number} An integer */ getInteger: function (name) { - if (this.data.animController) { - return this.data.animController.getParameterValue(name, pc.ANIM_PARAMETER_INTEGER); - } + return this.getParameterValue(name, pc.ANIM_PARAMETER_INTEGER); }, /** @@ -165,14 +363,12 @@ Object.assign(pc, function () { * @param {number} value - The new integer value to set this parameter to. */ setInteger: function (name, value) { - if (this.data.animController) { - if (typeof value === 'number' && value % 1 === 0) { - this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_INTEGER, Math.floor(value)); - } else { - // #ifdef DEBUG - console.error('Attempting to assign non integer value to integer parameter'); - // #endif - } + if (typeof value === 'number' && value % 1 === 0) { + this.setParameterValue(name, pc.ANIM_PARAMETER_INTEGER, Math.floor(value)); + } else { + // #ifdef DEBUG + console.error('Attempting to assign non integer value to integer parameter'); + // #endif } }, @@ -184,9 +380,7 @@ Object.assign(pc, function () { * @returns {boolean} A boolean */ getBoolean: function (name) { - if (this.data.animController) { - return this.data.animController.getParameterValue(name, pc.ANIM_PARAMETER_BOOLEAN); - } + return this.getParameterValue(name, pc.ANIM_PARAMETER_BOOLEAN); }, /** @@ -197,9 +391,7 @@ Object.assign(pc, function () { * @param {boolean} value - The new boolean value to set this parameter to. */ setBoolean: function (name, value) { - if (this.data.animController) { - this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_BOOLEAN, !!value); - } + this.setParameterValue(name, pc.ANIM_PARAMETER_BOOLEAN, !!value); }, /** @@ -210,9 +402,7 @@ Object.assign(pc, function () { * @returns {boolean} A boolean */ getTrigger: function (name) { - if (this.data.animController) { - return this.data.animController.getParameterValue(name, pc.ANIM_PARAMETER_TRIGGER); - } + return this.getParameterValue(name, pc.ANIM_PARAMETER_TRIGGER); }, /** @@ -222,9 +412,7 @@ Object.assign(pc, function () { * @param {string} name - The name of the parameter to set. */ setTrigger: function (name) { - if (this.data.animController) { - this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_TRIGGER, true); - } + this.setParameterValue(name, pc.ANIM_PARAMETER_TRIGGER, true); }, /** @@ -234,72 +422,7 @@ Object.assign(pc, function () { * @param {string} name - The name of the parameter to set. */ resetTrigger: function (name) { - if (this.data.animController) { - this.data.animController.setParameterValue(name, pc.ANIM_PARAMETER_TRIGGER, false); - } - } - }); - - Object.defineProperties(AnimComponent.prototype, { - /** - * @name pc.AnimComponent#activeState - * @property {string} activeState - Returns the currently active state name. - */ - activeState: { - get: function () { - if (this.data.animController) { - return this.data.animController.activeStateName; - } - return null; - } - }, - /** - * @name pc.AnimComponent#previousState - * @property {string} previousState - Returns the previously active state name. - */ - previousState: { - get: function () { - if (this.data.animController) { - return this.data.animController.previousStateName; - } - return null; - } - }, - /** - * @name pc.AnimComponent#activeStateProgress - * @property {number} activeStateProgress - Returns the currently active states progress as a value normalised by the states animation duration. Looped animations will return values greater than 1. - */ - activeStateProgress: { - get: function () { - if (this.data.animController) { - return this.data.animController.activeStateProgress; - } - return null; - } - }, - /** - * @name pc.AnimComponent#transitioning - * @property {boolean} transitioning - Returns whether the anim component is currently transitioning between states. - */ - transitioning: { - get: function () { - if (this.data.animController) { - return this.data.animController.transitioning; - } - return null; - } - }, - /** - * @name pc.AnimComponent#transitionProgress - * @property {number} transitionProgress - If the anim component is currently transitioning between states, returns the progress. Otherwise returns null. - */ - transitionProgress: { - get: function () { - if (this.data.animController && this.transitioning) { - return this.data.animController.transitionProgress; - } - return null; - } + this.setParameterValue(name, pc.ANIM_PARAMETER_TRIGGER, false); } }); diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index d5c82755956..ef001fb78d5 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -204,7 +204,6 @@ Object.assign(pc, function () { this._findTransitionsFromStateCache = {}; this._findTransitionsBetweenStatesCache = {}; this._parameters = parameters; - this._initialParameters = Object.assign({}, parameters); this._previousStateName = null; this._activeStateName = ANIM_STATE_START; this._playing = false; @@ -414,7 +413,7 @@ Object.assign(pc, function () { return parameter.type === ANIM_PARAMETER_TRIGGER; }.bind(this)); for (i = 0; i < triggers.length; i++) { - this.setParameterValue(triggers[i].parameterName, ANIM_PARAMETER_TRIGGER, false); + this.findParameter(triggers[i].parameterName).value = false; } if (!this._isTransitioning) { @@ -518,7 +517,7 @@ Object.assign(pc, function () { var state = this._findState(stateName); if (!state) { // #ifdef DEBUG - console.error('Linking animation asset to animation state that does not exist'); + console.error('Attempting to assign an animation track to an animation state that does not exist.'); // #endif return; } @@ -541,6 +540,18 @@ Object.assign(pc, function () { } }, + removeStateAnimations: function (stateName) { + var state = this._findState(stateName); + if (!state) { + // #ifdef DEBUG + console.error('Attempting to unassign animation tracks from a state that does not exist.'); + // #endif + return; + } + + state.animations = []; + }, + play: function (stateName) { if (stateName) { this._transitionToState(stateName); @@ -561,7 +572,6 @@ Object.assign(pc, function () { this._isTransitioning = false; this._timeInState = 0; this._timeInStateBefore = 0; - this._parameters = Object.assign({}, this._initialParameters); this._animEvaluator.removeClips(); }, @@ -622,27 +632,6 @@ Object.assign(pc, function () { findParameter: function (name) { return this._parameters[name]; - }, - - getParameterValue: function (name, type) { - var param = this.findParameter(name); - if (param && param.type === type) { - return param.value; - } - // #ifdef DEBUG - console.log('Cannot get parameter value. No parameter found in anim controller named "' + name + '" of type "' + type + '"'); - // #endif - }, - - setParameterValue: function (name, type, value) { - var param = this.findParameter(name); - if (param && param.type === type) { - param.value = value; - return; - } - // #ifdef DEBUG - console.log('Cannot set parameter value. No parameter found in anim controller named "' + name + '" of type "' + type + '"'); - // #endif } }); diff --git a/src/framework/components/anim/data.js b/src/framework/components/anim/data.js index 74a50348924..0c8c12ab6a4 100644 --- a/src/framework/components/anim/data.js +++ b/src/framework/components/anim/data.js @@ -4,11 +4,13 @@ Object.assign(pc, function () { this.speed = 1.0; this.activate = true; this.enabled = true; + this.playing = false; // Non-serialized - this.animController = null; + this.layers = []; + this.layerIndicies = {}; + this.parameters = {}; this.model = null; - this.playing = false; }; return { diff --git a/src/framework/components/anim/system.js b/src/framework/components/anim/system.js index 1be83e5cec8..2e8ae5925c4 100644 --- a/src/framework/components/anim/system.js +++ b/src/framework/components/anim/system.js @@ -2,7 +2,8 @@ Object.assign(pc, function () { var _schema = [ 'enabled', 'speed', - 'activate' + 'activate', + 'playing' ]; /** @@ -36,7 +37,7 @@ Object.assign(pc, function () { Object.assign(AnimComponentSystem.prototype, { initializeComponentData: function (component, data, properties) { - properties = ['activate', 'enabled', 'speed']; + properties = ['activate', 'enabled', 'speed', 'playing']; pc.ComponentSystem.prototype.initializeComponentData.call(this, component, data, properties); }, @@ -52,9 +53,9 @@ Object.assign(pc, function () { var component = components[id]; var componentData = component.data; - if (componentData.enabled && component.entity.enabled) { - if (componentData.animController) { - componentData.animController.update(dt * componentData.speed); + if (componentData.enabled && component.entity.enabled && componentData.playing) { + for (var i = 0; i < componentData.layers.length; i++) { + componentData.layers[i].update(dt * componentData.speed); } } } From ce25474e697a8b17abec34da1a25e752e88b1427 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Mon, 18 May 2020 15:24:18 +0100 Subject: [PATCH 047/144] update examples --- examples/animation/animated-character.html | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/examples/animation/animated-character.html b/examples/animation/animated-character.html index 37a95f9a3d6..695647fa0da 100644 --- a/examples/animation/animated-character.html +++ b/examples/animation/animated-character.html @@ -168,10 +168,11 @@ // add jump interactivity to the character document.querySelector('#jump').addEventListener('click', () => { + var animLayer = modelEntity.anim.findAnimationLayer('DEFAULT_LAYER'); var shouldTriggerJump = false; - var isJumping = modelEntity.anim.activeStateName === 'Jump'; + var isJumping = animLayer.activeState === 'Jump'; if (isJumping) { - var stateProgress = modelEntity.anim.activeStateProgress; + var stateProgress = animLayer.activeStateProgress; if (stateProgress > 0.6) { shouldTriggerJump = true; } @@ -220,10 +221,11 @@ return speed; } MoveBox.prototype.update = function (dt) { - var speed = getSpeedFromState(modelEntity.anim.activeState); - if (modelEntity.anim.transitioning) { - var prevStateSpeed = getSpeedFromState(modelEntity.anim.previousState); - speed = ((prevStateSpeed * (1.0 - modelEntity.anim.transitionProgress)) + (speed * modelEntity.anim.transitionProgress)); + var animLayer = modelEntity.anim.findAnimationLayer('DEFAULT_LAYER'); + var speed = getSpeedFromState(animLayer.activeState); + if (animLayer.transitioning) { + var prevStateSpeed = getSpeedFromState(animLayer.previousState); + speed = ((prevStateSpeed * (1.0 - animLayer.transitionProgress)) + (speed * animLayer.transitionProgress)); } var zPos = this.entity.localPosition.z - speed * 0.66 * dt; From 8898c797fbdef07b05fa513c1b659a3d2682c51d Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Mon, 18 May 2020 15:36:11 +0100 Subject: [PATCH 048/144] jsdocs fix --- src/framework/components/anim/component.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 3916bcfe6ff..0a822f47e41 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -1,9 +1,17 @@ Object.assign(pc, function () { - var AnimComponentLayer = function (name, controller, order, component) { + /** + * @class + * @name pc.AnimComponentLayer + * @classdesc The Anim Component Layer allows managers a single layer of the animation state graph. + * @description Create a new AnimComponentLayer. + * @param {string} name - The name of the layer. + * @param {pc.AnimController} controller - The controller to manage this layers animations. + * @param {pc.AnimComponent} component - The component that this layer is a member of. + */ + var AnimComponentLayer = function (name, controller, component) { this._name = name; this._controller = controller; - this._order = order; this._component = component; }; @@ -165,7 +173,7 @@ Object.assign(pc, function () { * @class * @name pc.AnimComponent * @augments pc.Component - * @classdesc The Anim Component allows an Entity to playback animations on models. + * @classdesc The Anim Component allows an Entity to playback animations on models and entity properties. * @description Create a new AnimComponent. * @param {pc.AnimComponentSystem} system - The {@link pc.ComponentSystem} that created this Component. * @param {pc.Entity} entity - The Entity that this Component is attached to. @@ -210,7 +218,7 @@ Object.assign(pc, function () { data.parameters, data.activate ); - data.layers.push(new AnimComponentLayer(name, controller, order, this)); + data.layers.push(new AnimComponentLayer(name, controller, this)); data.layerIndicies[name] = order; } @@ -427,6 +435,7 @@ Object.assign(pc, function () { }); return { + AnimComponentLayer: AnimComponentLayer, AnimComponent: AnimComponent }; }()); From ada6279f99b89c8f5d6c5b92523b21165ed03bd2 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Mon, 18 May 2020 15:42:16 +0100 Subject: [PATCH 049/144] jsdocs fix --- src/framework/components/anim/component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 0a822f47e41..0ec7e6753fd 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -6,7 +6,7 @@ Object.assign(pc, function () { * @classdesc The Anim Component Layer allows managers a single layer of the animation state graph. * @description Create a new AnimComponentLayer. * @param {string} name - The name of the layer. - * @param {pc.AnimController} controller - The controller to manage this layers animations. + * @param {object} controller - The controller to manage this layers animations. * @param {pc.AnimComponent} component - The component that this layer is a member of. */ var AnimComponentLayer = function (name, controller, component) { From 78838263c15e133e1e5e1b042d5dfedba7cec28d Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Mon, 18 May 2020 17:16:03 +0100 Subject: [PATCH 050/144] state graph multiple layers fix --- src/framework/components/anim/component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 0ec7e6753fd..1610f18420e 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -223,7 +223,7 @@ Object.assign(pc, function () { } if (stateGraph.layers) { - for (var i = 0; i < stateGraph.layers; i++) { + for (var i = 0; i < stateGraph.layers.length; i++) { var layer = stateGraph.layers[i]; addLayer.bind(this)(layer.name, layer.states, layer.transitions, i); } From 655f08df729f46e0c5c525f139046527ec6f2168 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 22 May 2020 17:41:54 +0200 Subject: [PATCH 051/144] wip: test returning entity instead of model from parser --- src/resources/container.js | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/resources/container.js b/src/resources/container.js index a43c464cf44..4bc3e9bf7d1 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -127,9 +127,34 @@ Object.assign(pc, function () { var data = container.data; var i; + /** + * TODO: + * - data.nodes should have all nodes as pc.Entity + * - data.models should have all models as pc.Model + metadata to know which node they are assigned to + * - same as above for animations + * - create assets for all models, animations etc + * - assign model/animation components to root entity + * + * nodes: pc.Entity[] + * models: {model: pc.Model, node: number}[] + * animations: {animation: pc.AnimComponent, node: number}[] + * scene: {node: number} + * scenes: {node: number}[] + */ + + console.log(data.nodes); + // create model asset var model = createAsset('model', pc.GlbParser.createModel(data, this._defaultMaterial), 0); + var entity = new pc.Entity(); + entity.addComponent('model', { + type: "asset", + asset: model + }); + + var entityAsset = createAsset('entity', entity, 0); + // create material assets var materials = []; for (i = 0; i < data.materials.length; ++i) { @@ -149,7 +174,7 @@ Object.assign(pc, function () { } container.data = null; // since assets are created, release GLB data - container.model = model; + container.model = entityAsset; container.materials = materials; container.textures = textures; container.animations = animations; From 1476e77447e38b87c16f33224ee9e2fd3bf46671 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Mon, 25 May 2020 14:42:24 +0200 Subject: [PATCH 052/144] feat(GlbParser): return pc.Entity instead of pc.Model as scene --- src/resources/container.js | 80 +++++++++++++++++------------- src/resources/parser/glb-parser.js | 73 ++++++++++++++++++++++++--- 2 files changed, 112 insertions(+), 41 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index 4bc3e9bf7d1..38d4f08a2ad 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -129,55 +129,67 @@ Object.assign(pc, function () { /** * TODO: - * - data.nodes should have all nodes as pc.Entity - * - data.models should have all models as pc.Model + metadata to know which node they are assigned to - * - same as above for animations - * - create assets for all models, animations etc - * - assign model/animation components to root entity - * - * nodes: pc.Entity[] - * models: {model: pc.Model, node: number}[] - * animations: {animation: pc.AnimComponent, node: number}[] - * scene: {node: number} - * scenes: {node: number}[] + * - use data.gltf.nodes to add model components to entities */ - console.log(data.nodes); - - // create model asset - var model = createAsset('model', pc.GlbParser.createModel(data, this._defaultMaterial), 0); - - var entity = new pc.Entity(); - entity.addComponent('model', { - type: "asset", - asset: model - }); - - var entityAsset = createAsset('entity', entity, 0); + console.log("data", data); + + // create model assets + var modelAssets = []; + for (i = 0; i < data.models.length; ++i) { + var model = data.models[i]; + if (model !== null) { + var modelAsset = createAsset('model', data.models[i], i); + data.nodes[i].addComponent('model', { + type: 'asset', + asset: modelAsset + }); + modelAssets.push(modelAsset); + } + } // create material assets - var materials = []; + var materialAssets = []; for (i = 0; i < data.materials.length; ++i) { - materials.push(createAsset('material', data.materials[i], i)); + materialAssets.push(createAsset('material', data.materials[i], i)); } // create texture assets - var textures = []; + var textureAssets = []; for (i = 0; i < data.textures.length; ++i) { - textures.push(createAsset('texture', data.textures[i], i)); + textureAssets.push(createAsset('texture', data.textures[i], i)); } // create animation assets - var animations = []; + var animationAssets = []; for (i = 0; i < data.animations.length; ++i) { - animations.push(createAsset('animation', data.animations[i], i)); + animationAssets.push(createAsset('animation', data.animations[i], i)); + } + + var rootNodes = []; + for (i = 0; i < data.nodes.length; i++) { + var node = data.nodes[i]; + if (node.parent === null) { + rootNodes.push(node); + } + } + + var rootNode; + if (rootNodes.length === 1) { + rootNode = rootNodes[0]; + } else { + rootNode = new pc.GraphNode('Root'); + for (i = 0; i < rootNodes.length; ++i) { + rootNode.addChild(rootNodes[i]); + } } - container.data = null; // since assets are created, release GLB data - container.model = entityAsset; - container.materials = materials; - container.textures = textures; - container.animations = animations; + container.data = null; // since assets are created, release GLB data + container.scene = rootNode; + container.materials = materialAssets; + container.textures = textureAssets; + container.animations = animationAssets; + container.models = modelAssets; container.registry = assets; } }); diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 608a35a20a4..fe1497b7356 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -430,7 +430,7 @@ Object.assign(pc, function () { var tempMat = new pc.Mat4(); var tempVec = new pc.Vec3(); - var createMesh = function (device, meshData, accessors, bufferViews, buffers, callback) { + var createMeshGroup = function (device, meshData, accessors, bufferViews, buffers, callback) { var meshes = []; var semanticMap = { @@ -1172,7 +1172,7 @@ Object.assign(pc, function () { }; var createNode = function (nodeData, nodeIndex) { - var entity = new pc.GraphNode(); + var entity = new pc.Entity(); if (nodeData.hasOwnProperty('name')) { entity.name = nodeData.name; @@ -1209,6 +1209,49 @@ Object.assign(pc, function () { return entity; }; + var createModel = function (meshGroup, skin, materials) { + var model = new pc.Model(); + model.graph = new pc.GraphNode(); + + // TODO: Get defaultMaterial from scene + var defaultMaterial = new pc.StandardMaterial(); + defaultMaterial.name = "Default Material"; + defaultMaterial.shadingModel = pc.SPECULAR_BLINN; + + meshGroup.forEach(function (mesh) { + var material = (mesh.materialIndex === undefined) ? defaultMaterial : materials[mesh.materialIndex]; + var meshNode = new pc.GraphNode(); + var meshInstance = new pc.MeshInstance(meshNode, mesh, material); + + if (mesh.morph) { + var morphInstance = new pc.MorphInstance(mesh.morph); + if (mesh.weights) { + for (var wi = 0; wi < mesh.weights.length; wi++) { + morphInstance.setWeight(wi, mesh.weights[wi]); + } + } + + meshInstance.morphInstance = morphInstance; + model.morphInstances.push(morphInstance); + } + + if (skin !== null) { + mesh.skin = skin; + + var skinInstance = new pc.SkinInstance(skin); + skinInstance.bones = skin.bones; + + meshInstance.skinInstance = skinInstance; + model.skinInstances.push(skinInstance); + } + + model.graph.addChild(meshNode); + model.meshInstances.push(meshInstance); + }); + + return model; + }; + var createSkins = function (device, gltf, nodes, buffers) { if (!gltf.hasOwnProperty('skins') || gltf.skins.length === 0) { return []; @@ -1219,14 +1262,14 @@ Object.assign(pc, function () { }; - var createMeshes = function (device, gltf, buffers, callback) { + var createMeshGroups = function (device, gltf, buffers, callback) { if (!gltf.hasOwnProperty('meshes') || gltf.meshes.length === 0 || !gltf.hasOwnProperty('accessors') || gltf.accessors.length === 0 || !gltf.hasOwnProperty('bufferViews') || gltf.bufferViews.length === 0) { return []; } return gltf.meshes.map(function (meshData) { - return createMesh(device, meshData, gltf.accessors, gltf.bufferViews, buffers, callback); + return createMeshGroup(device, meshData, gltf.accessors, gltf.bufferViews, buffers, callback); }); }; @@ -1283,14 +1326,29 @@ Object.assign(pc, function () { return nodes; }; + var createModels = function (gltf, meshGroups, skins, materials) { + if (!gltf.hasOwnProperty('nodes') || gltf.nodes.length === 0) { + return []; + } + return gltf.nodes.map(function (nodeData) { + if (!nodeData.hasOwnProperty('mesh')) { + return null; + } + var meshGroup = meshGroups[nodeData.mesh]; + var skin = nodeData.hasOwnProperty('skin') ? skins[nodeData.skin] : null; + return createModel(meshGroup, skin, materials); + }); + }; + // create engine resources from the downloaded GLB data var createResources = function (device, gltf, buffers, images, callback) { var nodes = createNodes(gltf); var animations = createAnimations(gltf, nodes, buffers); var textures = createTextures(device, gltf, images); var materials = createMaterials(gltf, textures); - var meshes = createMeshes(device, gltf, buffers, callback); + var meshGroups = createMeshGroups(device, gltf, buffers, callback); var skins = createSkins(device, gltf, nodes, buffers); + var models = createModels(gltf, meshGroups, skins, materials); callback(null, { 'gltf': gltf, @@ -1298,8 +1356,9 @@ Object.assign(pc, function () { 'animations': animations, 'textures': textures, 'materials': materials, - 'meshes': meshes, - 'skins': skins + 'meshes': meshGroups, + 'skins': skins, + 'models': models }); }; From 5d6c7879fa088563026ce5a8b7739d45ef3ed175 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Mon, 25 May 2020 15:07:10 +0200 Subject: [PATCH 053/144] refactor(GlbParser): remove now unused createModel method --- src/resources/parser/glb-parser.js | 65 ------------------------------ 1 file changed, 65 deletions(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index fe1497b7356..a35e9603713 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1692,71 +1692,6 @@ Object.assign(pc, function () { return result; }; - // create a pc.Model from the parsed GLB data structures - GlbParser.createModel = function (glb, defaultMaterial) { - var createMeshInstance = function (model, mesh, skins, materials, node, nodeData) { - var material = (mesh.materialIndex === undefined) ? defaultMaterial : materials[mesh.materialIndex]; - - var meshInstance = new pc.MeshInstance(node, mesh, material); - - if (mesh.morph) { - var morphInstance = new pc.MorphInstance(mesh.morph); - if (mesh.weights) { - for (var wi = 0; wi < mesh.weights.length; wi++) { - morphInstance.setWeight(wi, mesh.weights[wi]); - } - } - - meshInstance.morphInstance = morphInstance; - model.morphInstances.push(morphInstance); - } - - if (nodeData.hasOwnProperty('skin')) { - var skin = skins[nodeData.skin]; - mesh.skin = skin; - - var skinInstance = new pc.SkinInstance(skin); - skinInstance.bones = skin.bones; - - meshInstance.skinInstance = skinInstance; - model.skinInstances.push(skinInstance); - } - - model.meshInstances.push(meshInstance); - }; - - var model = new pc.Model(); - var i; - - var rootNodes = []; - for (i = 0; i < glb.nodes.length; i++) { - var node = glb.nodes[i]; - if (node.parent === null) { - rootNodes.push(node); - } - - var nodeData = glb.gltf.nodes[i]; - if (nodeData.hasOwnProperty('mesh')) { - var meshGroup = glb.meshes[nodeData.mesh]; - for (var mi = 0; mi < meshGroup.length; mi++) { - createMeshInstance(model, meshGroup[mi], glb.skins, glb.materials, node, nodeData); - } - } - } - - // set model root (create a group if there is more than one) - if (rootNodes.length === 1) { - model.graph = rootNodes[0]; - } else { - model.graph = new pc.GraphNode('SceneGroup'); - for (i = 0; i < rootNodes.length; ++i) { - model.graph.addChild(rootNodes[i]); - } - } - - return model; - }; - return { GlbParser: GlbParser }; From 3803bbd25b201d5bd0e2ff2c001f35b1595726ca Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Mon, 25 May 2020 15:07:54 +0200 Subject: [PATCH 054/144] feat(GlbParser): add support for glTF properties scene and scenes --- src/resources/container.js | 27 ++-------------- src/resources/parser/glb-parser.js | 49 ++++++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 26 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index 38d4f08a2ad..3402ac96744 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -125,13 +125,9 @@ Object.assign(pc, function () { var container = asset.resource; var data = container.data; + var gltf = data.gltf; var i; - /** - * TODO: - * - use data.gltf.nodes to add model components to entities - */ - console.log("data", data); // create model assets @@ -166,26 +162,9 @@ Object.assign(pc, function () { animationAssets.push(createAsset('animation', data.animations[i], i)); } - var rootNodes = []; - for (i = 0; i < data.nodes.length; i++) { - var node = data.nodes[i]; - if (node.parent === null) { - rootNodes.push(node); - } - } - - var rootNode; - if (rootNodes.length === 1) { - rootNode = rootNodes[0]; - } else { - rootNode = new pc.GraphNode('Root'); - for (i = 0; i < rootNodes.length; ++i) { - rootNode.addChild(rootNodes[i]); - } - } - container.data = null; // since assets are created, release GLB data - container.scene = rootNode; + container.scene = data.scene; + container.scenes = data.scenes; container.materials = materialAssets; container.textures = textureAssets; container.animations = animationAssets; diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index a35e9603713..c2fa9bac712 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1209,6 +1209,25 @@ Object.assign(pc, function () { return entity; }; + var createScene = function (sceneData, sceneIndex, nodes) { + var sceneRoot = new pc.Entity(); + + if (sceneData.hasOwnProperty('name')) { + sceneRoot.name = sceneData.name; + } else { + sceneRoot.name = "scene_" + sceneIndex; + } + + sceneData.nodes.forEach(function (nodeIndex) { + var node = nodes[nodeIndex]; + if (node !== undefined) { + sceneRoot.addChild(node); + } + }); + + return sceneRoot; + }; + var createModel = function (meshGroup, skin, materials) { var model = new pc.Model(); model.graph = new pc.GraphNode(); @@ -1326,6 +1345,27 @@ Object.assign(pc, function () { return nodes; }; + var createScenes = function (gltf, nodes) { + if (!gltf.hasOwnProperty('scenes') || gltf.scenes.length === 0) { + return []; + } + + return gltf.scenes.map(function (sceneData, sceneIndex) { + return createScene(sceneData, sceneIndex, nodes); + }); + }; + + var getDefaultScene = function (gltf, scenes) { + if (!gltf.hasOwnProperty('scene')) { + if (scenes.length === 0) { + return undefined; + } + return scenes[0]; + } + + return scenes[gltf.scene]; + }; + var createModels = function (gltf, meshGroups, skins, materials) { if (!gltf.hasOwnProperty('nodes') || gltf.nodes.length === 0) { return []; @@ -1343,6 +1383,8 @@ Object.assign(pc, function () { // create engine resources from the downloaded GLB data var createResources = function (device, gltf, buffers, images, callback) { var nodes = createNodes(gltf); + var scenes = createScenes(gltf, nodes); + var scene = getDefaultScene(gltf, scenes); var animations = createAnimations(gltf, nodes, buffers); var textures = createTextures(device, gltf, images); var materials = createMaterials(gltf, textures); @@ -1350,15 +1392,18 @@ Object.assign(pc, function () { var skins = createSkins(device, gltf, nodes, buffers); var models = createModels(gltf, meshGroups, skins, materials); + callback(null, { 'gltf': gltf, 'nodes': nodes, + 'models': models, + 'scenes': scenes, + 'scene': scene, 'animations': animations, 'textures': textures, 'materials': materials, 'meshes': meshGroups, - 'skins': skins, - 'models': models + 'skins': skins }); }; From df12ec6055032e6e4957946f123ef7ddc14ee43f Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Mon, 25 May 2020 15:22:03 +0200 Subject: [PATCH 055/144] refactor(GlbParser): remove unused code --- src/resources/container.js | 1 - src/resources/parser/glb-parser.js | 1 - 2 files changed, 2 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index 3402ac96744..60b3087efe1 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -125,7 +125,6 @@ Object.assign(pc, function () { var container = asset.resource; var data = container.data; - var gltf = data.gltf; var i; console.log("data", data); diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index c2fa9bac712..db38bec597f 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1392,7 +1392,6 @@ Object.assign(pc, function () { var skins = createSkins(device, gltf, nodes, buffers); var models = createModels(gltf, meshGroups, skins, materials); - callback(null, { 'gltf': gltf, 'nodes': nodes, From e5f2cc2a1109c255c67dfefd856827a5bf056a34 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Mon, 25 May 2020 17:22:50 +0200 Subject: [PATCH 056/144] fix(GlbParser): fix animation curve paths by using node names in pc.Model graphs --- src/resources/parser/glb-parser.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index db38bec597f..41cbbb87688 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1111,7 +1111,7 @@ Object.assign(pc, function () { var channel = animationData.channels[i]; var target = channel.target; var curve = curves[channel.sampler]; - curve._paths.push(pc.AnimBinder.joinPath([nodes[target.node].name, target.path])); + curve._paths.push(pc.AnimBinder.joinPath(["model_" + nodes[target.node].name, target.path])); // if this target is a set of quaternion keys, make note of its index so we can perform // quaternion-specific processing on it. @@ -1228,9 +1228,11 @@ Object.assign(pc, function () { return sceneRoot; }; - var createModel = function (meshGroup, skin, materials) { + var createModel = function (node, meshGroup, skin, materials) { var model = new pc.Model(); - model.graph = new pc.GraphNode(); + // TODO: Node name is used as path for animations. Is this sufficient? + // This prefix is added in createAnimation as well. + model.graph = new pc.GraphNode("model_" + node.name); // TODO: Get defaultMaterial from scene var defaultMaterial = new pc.StandardMaterial(); @@ -1366,17 +1368,18 @@ Object.assign(pc, function () { return scenes[gltf.scene]; }; - var createModels = function (gltf, meshGroups, skins, materials) { + var createModels = function (gltf, nodes, meshGroups, skins, materials) { if (!gltf.hasOwnProperty('nodes') || gltf.nodes.length === 0) { return []; } - return gltf.nodes.map(function (nodeData) { + return gltf.nodes.map(function (nodeData, nodeIndex) { if (!nodeData.hasOwnProperty('mesh')) { return null; } + var node = nodes[nodeIndex]; var meshGroup = meshGroups[nodeData.mesh]; var skin = nodeData.hasOwnProperty('skin') ? skins[nodeData.skin] : null; - return createModel(meshGroup, skin, materials); + return createModel(node, meshGroup, skin, materials); }); }; @@ -1390,7 +1393,7 @@ Object.assign(pc, function () { var materials = createMaterials(gltf, textures); var meshGroups = createMeshGroups(device, gltf, buffers, callback); var skins = createSkins(device, gltf, nodes, buffers); - var models = createModels(gltf, meshGroups, skins, materials); + var models = createModels(gltf, nodes, meshGroups, skins, materials); callback(null, { 'gltf': gltf, From 36192ab680d55dff0abf79fa5defc1f41c0bb7e0 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Tue, 26 May 2020 11:32:17 +0200 Subject: [PATCH 057/144] refactor(GlbParser): remove "model_" prefix from pc.Model root nodes --- src/resources/parser/glb-parser.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 41cbbb87688..5241b5ab55d 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1111,7 +1111,7 @@ Object.assign(pc, function () { var channel = animationData.channels[i]; var target = channel.target; var curve = curves[channel.sampler]; - curve._paths.push(pc.AnimBinder.joinPath(["model_" + nodes[target.node].name, target.path])); + curve._paths.push(pc.AnimBinder.joinPath([nodes[target.node].name, target.path])); // if this target is a set of quaternion keys, make note of its index so we can perform // quaternion-specific processing on it. @@ -1231,8 +1231,7 @@ Object.assign(pc, function () { var createModel = function (node, meshGroup, skin, materials) { var model = new pc.Model(); // TODO: Node name is used as path for animations. Is this sufficient? - // This prefix is added in createAnimation as well. - model.graph = new pc.GraphNode("model_" + node.name); + model.graph = new pc.GraphNode(node.name); // TODO: Get defaultMaterial from scene var defaultMaterial = new pc.StandardMaterial(); From b387449de64ae56dd37575568355d5b9dfcfdcfa Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Tue, 26 May 2020 16:37:37 +0200 Subject: [PATCH 058/144] wip(GlbParser): automatically add animation components to entities in scene --- src/resources/container.js | 50 ++++++++++++++++++++++-------- src/resources/parser/glb-parser.js | 49 ++++++++++++++++++++--------- 2 files changed, 72 insertions(+), 27 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index 60b3087efe1..40be1905b78 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -127,20 +127,10 @@ Object.assign(pc, function () { var data = container.data; var i; - console.log("data", data); - // create model assets var modelAssets = []; for (i = 0; i < data.models.length; ++i) { - var model = data.models[i]; - if (model !== null) { - var modelAsset = createAsset('model', data.models[i], i); - data.nodes[i].addComponent('model', { - type: 'asset', - asset: modelAsset - }); - modelAssets.push(modelAsset); - } + modelAssets.push(createAsset('model', data.models[i], i)); } // create material assets @@ -161,13 +151,47 @@ Object.assign(pc, function () { animationAssets.push(createAsset('animation', data.animations[i], i)); } - container.data = null; // since assets are created, release GLB data + var animationComponents = data.animations.map(function () { + return []; + }); + + // add components to nodes + data.nodes.forEach(function (node, nodeIndex) { + var components = data.nodeComponents[nodeIndex]; + + if (components.model !== null) { + node.addComponent('model', { + type: 'asset', + asset: modelAssets[components.model] + }); + } + + if (components.animations.length > 0) { + var animations = components.animations; + var animationIds = animations.map(function (animationIndex) { + return animationAssets[animationIndex].id; + }); + + var animationComponent = node.addComponent('animation', { + assets: animationIds, + enabled: false + }); + + animations.forEach(function (animationIndex) { + animationComponents[animationIndex].push(animationComponent); + }); + } + }); + + // since assets are created, release GLB data + container.data = null; + container.scene = data.scene; container.scenes = data.scenes; container.materials = materialAssets; container.textures = textureAssets; container.animations = animationAssets; - container.models = modelAssets; + container.animationComponents = animationComponents; container.registry = assets; } }); diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 5241b5ab55d..5c495c440a5 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1313,14 +1313,23 @@ Object.assign(pc, function () { }); }; - var createAnimations = function (gltf, nodes, buffers) { + var createAnimations = function (gltf, nodes, nodeComponents, buffers) { if (!gltf.hasOwnProperty('animations') || gltf.animations.length === 0) { return []; } - return gltf.animations.map(function (animationData, animationIndex) { - return createAnimation(animationData, animationIndex, gltf.accessors, gltf.bufferViews, nodes, buffers); + + var animations = []; + + gltf.animations.forEach(function (animationData, animationIndex) { + var animation = createAnimation(animationData, animationIndex, gltf.accessors, gltf.bufferViews, nodes, buffers); + animations.push(animation); + + animationData.channels.forEach(function (channel) { + nodeComponents[channel.target.node].animations.push(animationIndex); + }); }); + return animations; }; var createNodes = function (gltf) { @@ -1367,44 +1376,56 @@ Object.assign(pc, function () { return scenes[gltf.scene]; }; - var createModels = function (gltf, nodes, meshGroups, skins, materials) { + var createModels = function (gltf, nodes, nodeComponents, meshGroups, skins, materials) { if (!gltf.hasOwnProperty('nodes') || gltf.nodes.length === 0) { return []; } - return gltf.nodes.map(function (nodeData, nodeIndex) { + + var models = []; + + gltf.nodes.forEach(function (nodeData, nodeIndex) { if (!nodeData.hasOwnProperty('mesh')) { - return null; + return; } var node = nodes[nodeIndex]; var meshGroup = meshGroups[nodeData.mesh]; var skin = nodeData.hasOwnProperty('skin') ? skins[nodeData.skin] : null; - return createModel(node, meshGroup, skin, materials); + var model = createModel(node, meshGroup, skin, materials); + var modelIndex = models.push(model) - 1; + nodeComponents[nodeIndex].model = modelIndex; }); + + return models; }; // create engine resources from the downloaded GLB data var createResources = function (device, gltf, buffers, images, callback) { var nodes = createNodes(gltf); + var nodeComponents = nodes.map(function () { + return { + animations: [], + model: null + }; + }); + var scenes = createScenes(gltf, nodes); var scene = getDefaultScene(gltf, scenes); - var animations = createAnimations(gltf, nodes, buffers); var textures = createTextures(device, gltf, images); var materials = createMaterials(gltf, textures); var meshGroups = createMeshGroups(device, gltf, buffers, callback); var skins = createSkins(device, gltf, nodes, buffers); - var models = createModels(gltf, nodes, meshGroups, skins, materials); + var models = createModels(gltf, nodes, nodeComponents, meshGroups, skins, materials); + var animations = createAnimations(gltf, nodes, nodeComponents, buffers); callback(null, { - 'gltf': gltf, 'nodes': nodes, + 'nodeComponents': nodeComponents, 'models': models, + 'animations': animations, 'scenes': scenes, 'scene': scene, - 'animations': animations, 'textures': textures, - 'materials': materials, - 'meshes': meshGroups, - 'skins': skins + 'materials': materials }); }; From 9a8e40d5000c57fc9f6bd9106dd143fd5fa67613 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Tue, 26 May 2020 16:39:31 +0200 Subject: [PATCH 059/144] fix: allow animation of entities without model components, e.g. grouping entities (temporary fix) --- src/framework/components/animation/component.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/framework/components/animation/component.js b/src/framework/components/animation/component.js index e0c7de36085..9d32b8ec6ea 100644 --- a/src/framework/components/animation/component.js +++ b/src/framework/components/animation/component.js @@ -61,7 +61,7 @@ Object.assign(pc, function () { data.prevAnim = data.currAnim; data.currAnim = name; - if (data.model) { + if (true) { if (!data.skeleton && !data.animController) { this._createAnimationController(); @@ -166,7 +166,7 @@ Object.assign(pc, function () { } } - var graph = model.getGraph(); + var graph = model ? model.getGraph() : this.entity; if (hasJson) { data.fromSkel = new pc.Skeleton(graph); data.toSkel = new pc.Skeleton(graph); From d82ebc8d1062f59dce61f66aa73626c0064dbf04 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Wed, 27 May 2020 17:30:13 +0200 Subject: [PATCH 060/144] fix(pc.Model): keep references to bone nodes outside the model when cloning a model --- src/scene/model.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/scene/model.js b/src/scene/model.js index ca4b644dcfd..a7ab55befeb 100644 --- a/src/scene/model.js +++ b/src/scene/model.js @@ -110,7 +110,9 @@ Object.assign(pc, function () { var bones = []; for (j = 0; j < skin.boneNames.length; j++) { var boneName = skin.boneNames[j]; - var bone = cloneGraph.findByName(boneName); + var boneFromCloneGraph = cloneGraph.findByName(boneName); + // Keep reference to bones that exist outside the models internal graph + var bone = boneFromCloneGraph === null ? this.skinInstances[i].bones[j] : boneFromCloneGraph; bones.push(bone); } cloneSkinInstance.bones = bones; From 8d98403a0d5f45d485fc6086708e7de7012cc9cb Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Thu, 28 May 2020 12:03:14 +0200 Subject: [PATCH 061/144] refactor(GlbParser): handle absence of scene node with null instead of undefined --- src/resources/parser/glb-parser.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 5c495c440a5..1a339caeae3 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1368,12 +1368,12 @@ Object.assign(pc, function () { var getDefaultScene = function (gltf, scenes) { if (!gltf.hasOwnProperty('scene')) { if (scenes.length === 0) { - return undefined; + return null; } return scenes[0]; } - return scenes[gltf.scene]; + return scenes[gltf.scene] || null; }; var createModels = function (gltf, nodes, nodeComponents, meshGroups, skins, materials) { From 69372f4428db79e93c6065f07712c960af880f9d Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Thu, 28 May 2020 12:04:04 +0200 Subject: [PATCH 062/144] refactor(GlbParser): remove animationComponents from container data --- src/resources/container.js | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index 40be1905b78..01021e2499f 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -151,10 +151,6 @@ Object.assign(pc, function () { animationAssets.push(createAsset('animation', data.animations[i], i)); } - var animationComponents = data.animations.map(function () { - return []; - }); - // add components to nodes data.nodes.forEach(function (node, nodeIndex) { var components = data.nodeComponents[nodeIndex]; @@ -167,31 +163,25 @@ Object.assign(pc, function () { } if (components.animations.length > 0) { - var animations = components.animations; - var animationIds = animations.map(function (animationIndex) { - return animationAssets[animationIndex].id; - }); - - var animationComponent = node.addComponent('animation', { - assets: animationIds, + node.addComponent('animation', { + assets: components.animations.map(function (animationIndex) { + return animationAssets[animationIndex].id; + }), enabled: false }); - - animations.forEach(function (animationIndex) { - animationComponents[animationIndex].push(animationComponent); - }); } }); // since assets are created, release GLB data container.data = null; + delete container.data; container.scene = data.scene; container.scenes = data.scenes; container.materials = materialAssets; container.textures = textureAssets; container.animations = animationAssets; - container.animationComponents = animationComponents; + container.models = modelAssets; container.registry = assets; } }); From 288e2efd6ba5ecdfa5adbdb4f0d3402fbca9ea50 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Thu, 28 May 2020 12:06:53 +0200 Subject: [PATCH 063/144] fix(GlbParser): fix cleanup process for ContainerResource --- src/resources/container.js | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index 01021e2499f..9a27bc0835f 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -8,10 +8,12 @@ Object.assign(pc, function () { */ var ContainerResource = function (data) { this.data = data; - this.model = null; + this.scene = null; + this.scenes = []; this.materials = []; this.textures = []; this.animations = []; + this.models = []; this.registry = null; }; @@ -26,17 +28,31 @@ Object.assign(pc, function () { }; var destroyAssets = function (assets) { - assets.forEach(function (asset) { - destroyAsset(asset); - }); + assets.forEach(destroyAsset); }; // unload and destroy assets + if (this.scene) { + this.scene.destroy(); + this.scene = null; + } + + if (this.scenes) { + this.scenes.forEach(function (scene) { + scene.destroy(); + }); + } + if (this.animations) { destroyAssets(this.animations); this.animations = null; } + if (this.models) { + destroyAssets(this.models); + this.models = null; + } + if (this.textures) { destroyAssets(this.textures); this.textures = null; @@ -47,11 +63,6 @@ Object.assign(pc, function () { this.materials = null; } - if (this.model) { - destroyAsset(this.model); - this.model = null; - } - this.data = null; this.assets = null; } From 3b3faad5a179f9a0785e722efe11a828d7699da4 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Thu, 28 May 2020 12:28:00 +0200 Subject: [PATCH 064/144] refactor(ContainerResource): use Array.map instead of for loop to create assets --- src/resources/container.js | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index 9a27bc0835f..2812bfc5163 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -136,31 +136,26 @@ Object.assign(pc, function () { var container = asset.resource; var data = container.data; - var i; // create model assets - var modelAssets = []; - for (i = 0; i < data.models.length; ++i) { - modelAssets.push(createAsset('model', data.models[i], i)); - } + var modelAssets = data.models.map(function (model, index) { + return createAsset('model', model, index); + }); // create material assets - var materialAssets = []; - for (i = 0; i < data.materials.length; ++i) { - materialAssets.push(createAsset('material', data.materials[i], i)); - } + var materialAssets = data.materials.map(function (material, index) { + return createAsset('material', material, index); + }); // create texture assets - var textureAssets = []; - for (i = 0; i < data.textures.length; ++i) { - textureAssets.push(createAsset('texture', data.textures[i], i)); - } + var textureAssets = data.textures.map(function (texture, index) { + return createAsset('texture', texture, index); + }); // create animation assets - var animationAssets = []; - for (i = 0; i < data.animations.length; ++i) { - animationAssets.push(createAsset('animation', data.animations[i], i)); - } + var animationAssets = data.animations.map(function (animation, index) { + return createAsset('animation', animation, index); + }); // add components to nodes data.nodes.forEach(function (node, nodeIndex) { From bdee64546c355986a9521c7792b27d08ea249581 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Thu, 28 May 2020 13:01:11 +0200 Subject: [PATCH 065/144] refactor(GlbParser): propagate default material from ContainerHandler down to createModel --- src/resources/container.js | 2 +- src/resources/parser/glb-parser.js | 23 +++++++++-------------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index 2812bfc5163..6aab5ae8270 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -104,7 +104,7 @@ Object.assign(pc, function () { if (!err) { var filename = (asset.file && asset.file.filename) ? asset.file.filename : asset.name; - pc.GlbParser.parseAsync(filename, pc.path.extractPath(url.original), response, self._device, function (err, result) { + pc.GlbParser.parseAsync(filename, pc.path.extractPath(url.original), response, self._device, self._defaultMaterial, function (err, result) { if (err) { callback(err); } else { diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 1a339caeae3..3e4082b73cd 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1228,16 +1228,11 @@ Object.assign(pc, function () { return sceneRoot; }; - var createModel = function (node, meshGroup, skin, materials) { + var createModel = function (node, meshGroup, skin, materials, defaultMaterial) { var model = new pc.Model(); // TODO: Node name is used as path for animations. Is this sufficient? model.graph = new pc.GraphNode(node.name); - // TODO: Get defaultMaterial from scene - var defaultMaterial = new pc.StandardMaterial(); - defaultMaterial.name = "Default Material"; - defaultMaterial.shadingModel = pc.SPECULAR_BLINN; - meshGroup.forEach(function (mesh) { var material = (mesh.materialIndex === undefined) ? defaultMaterial : materials[mesh.materialIndex]; var meshNode = new pc.GraphNode(); @@ -1376,7 +1371,7 @@ Object.assign(pc, function () { return scenes[gltf.scene] || null; }; - var createModels = function (gltf, nodes, nodeComponents, meshGroups, skins, materials) { + var createModels = function (gltf, nodes, nodeComponents, meshGroups, skins, materials, defaultMaterial) { if (!gltf.hasOwnProperty('nodes') || gltf.nodes.length === 0) { return []; } @@ -1390,7 +1385,7 @@ Object.assign(pc, function () { var node = nodes[nodeIndex]; var meshGroup = meshGroups[nodeData.mesh]; var skin = nodeData.hasOwnProperty('skin') ? skins[nodeData.skin] : null; - var model = createModel(node, meshGroup, skin, materials); + var model = createModel(node, meshGroup, skin, materials, defaultMaterial); var modelIndex = models.push(model) - 1; nodeComponents[nodeIndex].model = modelIndex; }); @@ -1399,7 +1394,7 @@ Object.assign(pc, function () { }; // create engine resources from the downloaded GLB data - var createResources = function (device, gltf, buffers, images, callback) { + var createResources = function (device, gltf, buffers, images, defaultMaterial, callback) { var nodes = createNodes(gltf); var nodeComponents = nodes.map(function () { return { @@ -1414,7 +1409,7 @@ Object.assign(pc, function () { var materials = createMaterials(gltf, textures); var meshGroups = createMeshGroups(device, gltf, buffers, callback); var skins = createSkins(device, gltf, nodes, buffers); - var models = createModels(gltf, nodes, nodeComponents, meshGroups, skins, materials); + var models = createModels(gltf, nodes, nodeComponents, meshGroups, skins, materials, defaultMaterial); var animations = createAnimations(gltf, nodes, nodeComponents, buffers); callback(null, { @@ -1690,7 +1685,7 @@ Object.assign(pc, function () { var GlbParser = function () { }; // parse the gltf or glb data asynchronously, loading external resources - GlbParser.parseAsync = function (filename, urlBase, data, device, callback) { + GlbParser.parseAsync = function (filename, urlBase, data, device, defaultMaterial, callback) { // parse the data parseChunk(filename, data, function (err, chunks) { if (err) { @@ -1719,7 +1714,7 @@ Object.assign(pc, function () { return; } - createResources(device, gltf, buffers, images, callback); + createResources(device, gltf, buffers, images, defaultMaterial, callback); }); }); }); @@ -1727,7 +1722,7 @@ Object.assign(pc, function () { }; // parse the gltf or glb data synchronously. external resources (buffers and images) are ignored. - GlbParser.parse = function (filename, data, device) { + GlbParser.parse = function (filename, data, device, defaultMaterial) { var result = null; // parse the data @@ -1744,7 +1739,7 @@ Object.assign(pc, function () { var images = []; // create resources - createResources(device, gltf, buffers, images, function (err, result_) { + createResources(device, gltf, buffers, images, defaultMaterial, function (err, result_) { if (err) { console.error(err); } else { From b039aff37b1541dc9fbecda2e9cf197c2b842175 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Thu, 28 May 2020 13:09:38 +0200 Subject: [PATCH 066/144] chore(GlbParser): remove todo comment --- src/resources/parser/glb-parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 3e4082b73cd..5c3ef80e156 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1230,7 +1230,7 @@ Object.assign(pc, function () { var createModel = function (node, meshGroup, skin, materials, defaultMaterial) { var model = new pc.Model(); - // TODO: Node name is used as path for animations. Is this sufficient? + // Node name is used as path in animation curves model.graph = new pc.GraphNode(node.name); meshGroup.forEach(function (mesh) { From bd5eb3fff6e2ba63d66cfcae0ebdabca30ec598f Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Thu, 28 May 2020 13:23:13 +0200 Subject: [PATCH 067/144] fix(AnimationComponent): make it possible to use AnimationComponent on an Entity without a ModelComponent --- .../components/animation/component.js | 69 +++++++++---------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/src/framework/components/animation/component.js b/src/framework/components/animation/component.js index 9d32b8ec6ea..05e493b9239 100644 --- a/src/framework/components/animation/component.js +++ b/src/framework/components/animation/component.js @@ -61,51 +61,48 @@ Object.assign(pc, function () { data.prevAnim = data.currAnim; data.currAnim = name; - if (true) { + if (!data.skeleton && !data.animController) { + this._createAnimationController(); + } - if (!data.skeleton && !data.animController) { - this._createAnimationController(); - } + var prevAnim = data.animations[data.prevAnim]; + var currAnim = data.animations[data.currAnim]; - var prevAnim = data.animations[data.prevAnim]; - var currAnim = data.animations[data.currAnim]; + data.blending = blendTime > 0 && data.prevAnim; + if (data.blending) { + data.blend = 0; + data.blendSpeed = 1.0 / blendTime; + } - data.blending = blendTime > 0 && data.prevAnim; + if (data.skeleton) { if (data.blending) { - data.blend = 0; - data.blendSpeed = 1.0 / blendTime; - } - - if (data.skeleton) { - if (data.blending) { - // Blend from the current time of the current animation to the start of - // the newly specified animation over the specified blend time period. - data.fromSkel.animation = prevAnim; - data.fromSkel.addTime(data.skeleton._time); - data.toSkel.animation = currAnim; - } else { - data.skeleton.animation = currAnim; - } + // Blend from the current time of the current animation to the start of + // the newly specified animation over the specified blend time period. + data.fromSkel.animation = prevAnim; + data.fromSkel.addTime(data.skeleton._time); + data.toSkel.animation = currAnim; + } else { + data.skeleton.animation = currAnim; } + } - if (data.animController) { - var animController = data.animController; + if (data.animController) { + var animController = data.animController; - if (data.blending) { - // remove all but the last clip - while (animController.clips.length > 1) { - animController.removeClip(0); - } - } else { - data.animController.removeClips(); + if (data.blending) { + // remove all but the last clip + while (animController.clips.length > 1) { + animController.removeClip(0); } - - var clip = new pc.AnimClip(data.animations[data.currAnim], 0, 1.0, true, data.loop); - clip.name = data.currAnim; - clip.blendWeight = data.blending ? 0 : 1; - clip.reset(); - data.animController.addClip(clip); + } else { + data.animController.removeClips(); } + + var clip = new pc.AnimClip(data.animations[data.currAnim], 0, 1.0, true, data.loop); + clip.name = data.currAnim; + clip.blendWeight = data.blending ? 0 : 1; + clip.reset(); + data.animController.addClip(clip); } data.playing = true; From 4a87dd58ce2711e23ee5e5de7a6710e5a1ca565c Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Thu, 28 May 2020 13:43:11 +0200 Subject: [PATCH 068/144] chore(ContainerResource): update jsdoc documentation --- src/resources/container.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index 6aab5ae8270..f39688355cf 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -3,8 +3,17 @@ Object.assign(pc, function () { /** * @class * @name pc.ContainerResource - * @classdesc Container for a list of animations, textures, materials and a model. + * @classdesc Container for a list of animations, textures, materials, models, scenes (as entities) + * and a default scene (as entity). Entities in scene hierarchies will have model and animation components + * attached to them. * @param {object} data - The loaded GLB data. + * @property {pc.Entity|null} scene The root entity of the default scene. + * @property {pc.Entity[]} scenes The root entities of all scenes. + * @property {pc.Asset[]} materials Material assets. + * @property {pc.Asset[]} textures Texture assets. + * @property {pc.Asset[]} animations Animation assets. + * @property {pc.Asset[]} models Model assets. + * @property {pc.AssetRegistry} registry The asset registry. */ var ContainerResource = function (data) { this.data = data; @@ -73,7 +82,7 @@ Object.assign(pc, function () { * @name pc.ContainerHandler * @implements {pc.ResourceHandler} * @classdesc Loads files that contain in them multiple resources. For example GLB files which can contain - * textures, models and animations. + * textures, scenes and animations. * @param {pc.GraphicsDevice} device - The graphics device that will be rendering. * @param {pc.StandardMaterial} defaultMaterial - The shared default material that is used in any place that a material is not specified. */ @@ -122,7 +131,7 @@ Object.assign(pc, function () { return data; }, - // Create assets to wrap the loaded engine resources - model, materials, textures and animations. + // Create assets to wrap the loaded engine resources - models, materials, textures and animations. patch: function (asset, assets) { var createAsset = function (type, resource, index) { var subAsset = new pc.Asset(asset.name + '/' + type + '/' + index, type, { From 354028304dff477b4cee952277be90397880664a Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Thu, 28 May 2020 17:58:50 +0100 Subject: [PATCH 069/144] addressed PR comments --- build/dependencies.txt | 2 + examples/animation/animatable-properties.html | 193 ------------- examples/animation/animated-character.html | 256 ------------------ .../animation-clips/alarm-off-clip.json | 49 ---- .../animation-clips/alarm-on-clip.json | 66 ----- .../state-graphs/alarm-state-graph.json | 56 ---- .../state-graphs/character-state-graph.json | 188 ------------- src/framework/components/anim/component.js | 243 +++++------------ src/framework/components/anim/data.js | 3 +- src/framework/components/anim/layer.js | 188 +++++++++++++ .../components/anim/property-locator.js | 3 + src/framework/components/anim/state-graph.js | 37 +++ src/framework/components/anim/system.js | 1 + src/resources/animation-clip.js | 1 + src/resources/animation-state-graph.js | 3 +- 15 files changed, 299 insertions(+), 990 deletions(-) delete mode 100644 examples/animation/animatable-properties.html delete mode 100644 examples/animation/animated-character.html delete mode 100644 examples/assets/animations/animation-clips/alarm-off-clip.json delete mode 100644 examples/assets/animations/animation-clips/alarm-on-clip.json delete mode 100644 examples/assets/animations/state-graphs/alarm-state-graph.json delete mode 100644 examples/assets/animations/state-graphs/character-state-graph.json create mode 100644 src/framework/components/anim/layer.js create mode 100644 src/framework/components/anim/state-graph.js diff --git a/build/dependencies.txt b/build/dependencies.txt index 83c60c1901b..5c28c38ace2 100644 --- a/build/dependencies.txt +++ b/build/dependencies.txt @@ -146,6 +146,8 @@ ../src/framework/components/anim/data.js ../src/framework/components/anim/property-locator.js ../src/framework/components/anim/system.js +../src/framework/components/anim/state-graph.js +../src/framework/components/anim/layer.js ../src/framework/components/model/component.js ../src/framework/components/model/system.js ../src/framework/components/model/data.js diff --git a/examples/animation/animatable-properties.html b/examples/animation/animatable-properties.html deleted file mode 100644 index 66f2314f59f..00000000000 --- a/examples/animation/animatable-properties.html +++ /dev/null @@ -1,193 +0,0 @@ - - - - Playcanvas Animation - - - - - - - - - - - - - - \ No newline at end of file diff --git a/examples/animation/animated-character.html b/examples/animation/animated-character.html deleted file mode 100644 index 695647fa0da..00000000000 --- a/examples/animation/animated-character.html +++ /dev/null @@ -1,256 +0,0 @@ - - - - - Playcanvas Animation - - - - - - - - -
- -
- Speed: 0 -
-
- - - - - - \ No newline at end of file diff --git a/examples/assets/animations/animation-clips/alarm-off-clip.json b/examples/assets/animations/animation-clips/alarm-off-clip.json deleted file mode 100644 index 3596e3f03ef..00000000000 --- a/examples/assets/animations/animation-clips/alarm-off-clip.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "normalClip", - "duration": 1.0, - "inputs": [ - [ - 0.0 - ] - ], - "outputs": [ - { - "components": 4, - "data": [ - 1.0, 1.0, 1.0, 1.0 - ] - }, - { - "components": 4, - "data": [ - 0.0, 0.0, 0.0, 0.0 - ] - } - ], - "curves": [ - { - "path": "lights.spotLight1/light/color", - "inputIndex": 0, - "outputIndex": 0, - "interpolation": 1 - }, - { - "path": "lights.spotLight2/light/color", - "inputIndex": 0, - "outputIndex": 0, - "interpolation": 1 - }, - { - "path": "lights.spotLight2/entity/localEulerAngles", - "inputIndex": 0, - "outputIndex": 1, - "interpolation": 1 - }, - { - "path": "lights.spotLight1/entity/localEulerAngles", - "inputIndex": 0, - "outputIndex": 1, - "interpolation": 1 - } - ] -} \ No newline at end of file diff --git a/examples/assets/animations/animation-clips/alarm-on-clip.json b/examples/assets/animations/animation-clips/alarm-on-clip.json deleted file mode 100644 index 752d412799c..00000000000 --- a/examples/assets/animations/animation-clips/alarm-on-clip.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "name": "alarmClip", - "duration": 2.0, - "inputs": [ - [ - 0.0, 0.5, 1.0, 1.5, 2.0 - ], - [ - 0, 1, 2 - ] - ], - "outputs": [ - { - "components": 4, - "data": [ - 1.0, 0.0, 0.0, 1.0, - 0.4, 0.0, 0.0, 1.0, - 1.0, 0.0, 0.0, 1.0, - 0.4, 0.0, 0.0, 1.0, - 1.0, 0.0, 0.0, 1.0 - ] - }, - { - "components": 4, - "data": [ - 4.0, 0.0, 0.0, 0.0, - 4.0, 180.0, 0.0, 0.0, - 4.0, 0.0, 0.0, 0.0 - ] - }, - { - "components": 4, - "data": [ - -4.0, 0.0, 0.0, 0.0, - -4.0, 180.0, 0.0, 0.0, - -4.0, 0.0, 0.0, 0.0 - ] - } - ], - "curves": [ - { - "path": "lights.spotLight1/light/color", - "inputIndex": 0, - "outputIndex": 0, - "interpolation": 1 - }, - { - "path": "lights.spotLight2/light/color", - "inputIndex": 0, - "outputIndex": 0, - "interpolation": 1 - }, - { - "path": "lights.spotLight1/entity/localEulerAngles", - "inputIndex": 1, - "outputIndex": 1, - "interpolation": 1 - }, - { - "path": "lights.spotLight2/entity/localEulerAngles", - "inputIndex": 1, - "outputIndex": 2, - "interpolation": 1 - } - ] -} \ No newline at end of file diff --git a/examples/assets/animations/state-graphs/alarm-state-graph.json b/examples/assets/animations/state-graphs/alarm-state-graph.json deleted file mode 100644 index 830342f1e02..00000000000 --- a/examples/assets/animations/state-graphs/alarm-state-graph.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "states": [ - { - "name": "START" - }, - { - "name": "Normal", - "speed": 1.0 - }, - { - "name": "Alarm", - "speed": 1.0 - }, - { - "name": "END" - } - ], - "transitions": [ - { - "from": "START", - "to": "Normal" - }, - { - "from": "Normal", - "to": "Alarm", - "time": 0.1, - "interruptionSource": "NEXT_STATE", - "conditions": [ - { - "parameterName": "alarm", - "predicate": "EQUAL_TO", - "value": true - } - ] - }, - { - "from": "Alarm", - "to": "Normal", - "time": 1.5, - "interruptionSource": "NEXT_STATE", - "conditions": [ - { - "parameterName": "alarm", - "predicate": "EQUAL_TO", - "value": false - } - ] - } - ], - "parameters": { - "alarm": { - "type": "BOOLEAN", - "value": false - } - } -} \ No newline at end of file diff --git a/examples/assets/animations/state-graphs/character-state-graph.json b/examples/assets/animations/state-graphs/character-state-graph.json deleted file mode 100644 index 81c66fae6fa..00000000000 --- a/examples/assets/animations/state-graphs/character-state-graph.json +++ /dev/null @@ -1,188 +0,0 @@ -{ - "states": [ - { - "name": "START" - }, - { - "name": "Idle", - "speed": 1.0 - }, - { - "name": "Walk", - "speed": 1.0 - }, - { - "name": "WalkBackwards", - "speed": 1.0 - }, - { - "name": "Run", - "speed": 1.0 - }, - { - "name": "Jump", - "speed": 1.0 - }, - { - "name": "END" - } - ], - "transitions": [ - { - "from": "START", - "to": "Idle", - "time": 0, - "priority": 0 - }, - { - "from": "Idle", - "to": "Walk", - "time": 0.4, - "priority": 0, - "conditions": [ - { - "parameterName": "speed", - "predicate": "GREATER_THAN", - "value": 0 - } - ] - }, - { - "from": "Idle", - "to": "WalkBackwards", - "time": 0.4, - "priority": 0, - "conditions": [ - { - "parameterName": "speed", - "predicate": "LESS_THAN", - "value": 0 - } - ] - }, - { - "from": "WalkBackwards", - "to": "Idle", - "time": 0.4, - "priority": 0, - "conditions": [ - { - "parameterName": "speed", - "predicate": "GREATER_THAN_EQUAL_TO", - "value": 0 - } - ] - }, - { - "from": "Idle", - "to": "Jump", - "time": 0.2, - "priority": 0, - "transitionOffset": 0.0, - "conditions": [ - { - "parameterName": "jump", - "predicate": "EQUAL_TO", - "value": true - } - ] - }, - { - "from": "WalkBackwards", - "to": "Jump", - "time": 0.2, - "priority": 0, - "transitionOffset": 0.0, - "conditions": [ - { - "parameterName": "jump", - "predicate": "EQUAL_TO", - "value": true - } - ] - }, - { - "from": "Walk", - "to": "Jump", - "time": 0.2, - "priority": 0, - "conditions": [ - { - "parameterName": "jump", - "predicate": "EQUAL_TO", - "value": true - } - ] - }, - { - "from": "Run", - "to": "Jump", - "time": 0.2, - "priority": 0, - "conditions": [ - { - "parameterName": "jump", - "predicate": "EQUAL_TO", - "value": true - } - ] - }, - { - "from": "Jump", - "to": "Idle", - "time": 0.5, - "priority": 0, - "exitTime": 0.75 - }, - { - "from": "Walk", - "to": "Run", - "time": 0.2, - "priority": 0, - "conditions": [ - { - "parameterName": "speed", - "predicate": "GREATER_THAN", - "value": 1 - } - ] - }, - { - "from": "Walk", - "to": "Idle", - "time": 0.5, - "priority": 0, - "conditions": [ - { - "parameterName": "speed", - "predicate": "LESS_THAN_EQUAL_TO", - "value": 0 - } - ] - }, - { - "from": "Run", - "to": "Walk", - "time": 0.2, - "exitTime": 0.8, - "priority": 0, - "conditions": [ - { - "parameterName": "speed", - "predicate": "LESS_THAN_EQUAL_TO", - "value": 1 - } - ] - } - ], - "parameters": { - "speed": { - "type": "INTEGER", - "value": 0 - }, - "jump": { - "type": "TRIGGER", - "value": false - } - } -} \ No newline at end of file diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 1610f18420e..76a5fa88058 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -1,174 +1,7 @@ Object.assign(pc, function () { /** - * @class - * @name pc.AnimComponentLayer - * @classdesc The Anim Component Layer allows managers a single layer of the animation state graph. - * @description Create a new AnimComponentLayer. - * @param {string} name - The name of the layer. - * @param {object} controller - The controller to manage this layers animations. - * @param {pc.AnimComponent} component - The component that this layer is a member of. - */ - var AnimComponentLayer = function (name, controller, component) { - this._name = name; - this._controller = controller; - this._component = component; - }; - - Object.assign(AnimComponentLayer.prototype, { - /** - * @function - * @name pc.AnimComponentLayer#play - * @description Start playing the animation in the current state. - * @param {string} name - If provided, will begin playing from the start of the state with this name. - */ - play: function (name) { - this._controller.play(name); - }, - - /** - * @function - * @name pc.AnimComponentLayer#pause - * @description Start playing the animation in the current state. - */ - pause: function () { - this._controller.pause(); - }, - - /** - * @function - * @name pc.AnimComponentLayer#reset - * @description Reset the animation component to it's initial state, including all parameters. The system will be paused. - */ - reset: function () { - this._controller.reset(); - }, - - update: function (dt) { - this._controller.update(dt); - }, - - /** - * @function - * @name pc.AnimComponentLayer#assignAnimation - * @description Associates an animation with a state in the loaded state graph. If all states are linked and the pc.AnimComponent.activate value was set to true then the component will begin playing. - * @param {string} stateName - The name of the state that this animation should be associated with. - * @param {object} animTrack - The animation track that will be assigned to this state and played whenever this state is active. - */ - assignAnimation: function (stateName, animTrack) { - this._controller.assignAnimation(stateName, animTrack); - - if (this._component.activate) { - for (var i = 0; i < this._component.data.layers.length; i++) { - if (!this._component.data.layers[i].playable) { - return; - } - this._component.playing = true; - } - } - }, - - /** - * @function - * @name pc.AnimComponentLayer#removeStateAnimations - * @description Removes animations from a state in the loaded state graph. - * @param {string} stateName - The name of the state that should have its animation tracks removed. - */ - removeStateAnimations: function (stateName) { - this._controller.removeStateAnimations(stateName); - }, - - getParameterValue: function (name, type) { - this._controller.getParameterValue(name, type); - }, - - setParameterValue: function (name, type, value) { - this._controller.setParameterValue(name, type, value); - } - }); - - Object.defineProperties(AnimComponentLayer.prototype, { - /** - * @name pc.AnimComponentLayer#name - * @property {string} name - Returns the name of the layer - */ - name: { - get: function () { - return this._name; - } - }, - /** - * @name pc.AnimComponentLayer#playing - * @property {string} playing - Whether this layer is currently playing - */ - playing: { - get: function () { - return this._controller.playing; - }, - set: function (value) { - this._controller.playing = value; - } - }, - /** - * @name pc.AnimComponentLayer#playable - * @property {string} playable - Returns true if a state graph has been loaded and all states in the graph have been assigned animation tracks. - */ - playable: { - get: function () { - return this._controller.playable; - } - }, - /** - * @name pc.AnimComponentLayer#activeState - * @property {string} activeState - Returns the currently active state name. - */ - activeState: { - get: function () { - return this._controller.activeStateName; - } - }, - /** - * @name pc.AnimComponentLayer#previousState - * @property {string} previousState - Returns the previously active state name. - */ - previousState: { - get: function () { - return this._controller.previousStateName; - } - }, - /** - * @name pc.AnimComponentLayer#activeStateProgress - * @property {number} activeStateProgress - Returns the currently active states progress as a value normalised by the states animation duration. Looped animations will return values greater than 1. - */ - activeStateProgress: { - get: function () { - return this._controller.activeStateProgress; - } - }, - /** - * @name pc.AnimComponentLayer#transitioning - * @property {boolean} transitioning - Returns whether the anim component layer is currently transitioning between states. - */ - transitioning: { - get: function () { - return this._controller.transitioning; - } - }, - /** - * @name pc.AnimComponentLayer#transitionProgress - * @property {number} transitionProgress - If the anim component layer is currently transitioning between states, returns the progress. Otherwise returns null. - */ - transitionProgress: { - get: function () { - if (this.transitioning) { - return this._controller.transitionProgress; - } - return null; - } - } - }); - - /** + * @private * @component Anim * @class * @name pc.AnimComponent @@ -188,12 +21,15 @@ Object.assign(pc, function () { Object.assign(AnimComponent.prototype, { /** + * @private * @function * @name pc.AnimComponent#loadStateGraph * @description Initialises component animation controllers using the provided state graph. * @param {object} stateGraph - The state graph asset to load into the component. Contains the states, transitions and parameters used to define a complete animation controller. */ loadStateGraph: function (stateGraph) { + this.data.stateGraph = stateGraph; + var graph; var modelComponent = this.entity.model; if (modelComponent) { @@ -206,7 +42,6 @@ Object.assign(pc, function () { var data = this.data; data.parameters = stateGraph.parameters; - data.initialParameters = Object.assign({}, stateGraph.parameters); function addLayer(name, states, transitions, order) { var animBinder = new pc.AnimComponentBinder(this, graph); @@ -218,39 +53,38 @@ Object.assign(pc, function () { data.parameters, data.activate ); - data.layers.push(new AnimComponentLayer(name, controller, this)); + data.layers.push(new pc.AnimComponentLayer(name, controller, this)); data.layerIndicies[name] = order; } - if (stateGraph.layers) { - for (var i = 0; i < stateGraph.layers.length; i++) { - var layer = stateGraph.layers[i]; - addLayer.bind(this)(layer.name, layer.states, layer.transitions, i); - } - } else { - addLayer.bind(this)('DEFAULT_LAYER', stateGraph.states, stateGraph.transitions, 0); + for (var i = 0; i < stateGraph.layers.length; i++) { + var layer = stateGraph.layers[i]; + addLayer.bind(this)(layer.name, layer.states, layer.transitions, i); } }, /** + * @private * @function * @name pc.AnimComponent#removeStateGraph * @description Removes all layers from the anim component. */ removeStateGraph: function () { + this.data.stateGraph = null; this.data.layers = []; this.data.layerIndicies = {}; this.data.parameters = {}; - this.data.initialParameters = {}; + this.data.playing = false; }, /** + * @private * @function * @name pc.AnimComponent#reset * @description Reset all of the components layers and parameters to their initial states. If a layer was playing before it will continue playing */ reset: function () { - this.data.parameters = Object.assign({}, this.data.initialParameters); + this.data.parameters = Object.assign({}, this.data.stateGraph.parameters); for (var i = 0; i < this.data.layers.length; i++) { var layerPlaying = this.data.layers[i].playing; this.data.layers[i].reset(); @@ -259,6 +93,7 @@ Object.assign(pc, function () { }, /** + * @private * @function * @name pc.AnimComponent#findAnimationLayer * @description Finds a pc.AnimComponentLayer in this component. @@ -271,6 +106,7 @@ Object.assign(pc, function () { }, /** + * @private * @function * @name pc.AnimComponent#assignAnimation * @description Associates an animation with a state in the loaded state graph. If all states are linked and the pc.AnimComponent.activate value was set to true then the component will begin playing. @@ -291,6 +127,7 @@ Object.assign(pc, function () { }, /** + * @private * @function * @name pc.AnimComponent#removeStateAnimations * @description Removes animations from a state in the loaded state graph. @@ -331,6 +168,7 @@ Object.assign(pc, function () { }, /** + * @private * @function * @name pc.AnimComponent#getFloat * @description Returns a float parameter value by name. @@ -342,6 +180,7 @@ Object.assign(pc, function () { }, /** + * @private * @function * @name pc.AnimComponent#setFloat * @description Sets the value of a float parameter that was defined in the animation components state graph. @@ -353,6 +192,7 @@ Object.assign(pc, function () { }, /** + * @private * @function * @name pc.AnimComponent#getInteger * @description Returns an integer parameter value by name. @@ -364,6 +204,7 @@ Object.assign(pc, function () { }, /** + * @private * @function * @name pc.AnimComponent#setInteger * @description Sets the value of an integer parameter that was defined in the animation components state graph. @@ -381,6 +222,7 @@ Object.assign(pc, function () { }, /** + * @private * @function * @name pc.AnimComponent#getBoolean * @description Returns a boolean parameter value by name. @@ -392,6 +234,7 @@ Object.assign(pc, function () { }, /** + * @private * @function * @name pc.AnimComponent#setBoolean * @description Sets the value of a boolean parameter that was defined in the animation components state graph. @@ -403,6 +246,7 @@ Object.assign(pc, function () { }, /** + * @private * @function * @name pc.AnimComponent#getTrigger * @description Returns a trigger parameter value by name. @@ -414,6 +258,7 @@ Object.assign(pc, function () { }, /** + * @private * @function * @name pc.AnimComponent#setTrigger * @description Sets the value of a trigger parameter that was defined in the animation components state graph to true. @@ -424,6 +269,7 @@ Object.assign(pc, function () { }, /** + * @private * @function * @name pc.AnimComponent#setTrigger * @description Resets the value of a trigger parameter that was defined in the animation components state graph to false. @@ -434,8 +280,45 @@ Object.assign(pc, function () { } }); + Object.defineProperty(AnimComponent.prototype, "stateGraphAsset", { + get: function () { + return this.data.stateGraphAsset; + }, + + set: function (value) { + var _id; + var _asset; + + if (value instanceof pc.Asset) { + _id = value.id; + _asset = this.system.app.assets.get(_id); + if (!_asset) { + this.system.app.assets.add(value); + _asset = this.system.app.assets.get(_id); + } + } else { + _id = value; + _asset = this.system.app.assets.get(_id); + } + if (!_asset || this.data.stateGraphAsset === _id) { + return; + } + + if (_asset.resource) { + this.data.stateGraph = _asset.resource; + } else { + asset.on('load', function (asset) { + this.data.stateGraph = asset.resource; + this.loadStateGraph(this.data.stateGraph); + }.bind(this)); + this.system.app.assets.load(asset); + } + this.data.stateGraphAsset = _id; + } + }); + + return { - AnimComponentLayer: AnimComponentLayer, AnimComponent: AnimComponent }; }()); diff --git a/src/framework/components/anim/data.js b/src/framework/components/anim/data.js index 0c8c12ab6a4..16b4351a8b0 100644 --- a/src/framework/components/anim/data.js +++ b/src/framework/components/anim/data.js @@ -1,16 +1,17 @@ Object.assign(pc, function () { var AnimComponentData = function () { // Serialized + this.stateGraphAsset = null; this.speed = 1.0; this.activate = true; this.enabled = true; this.playing = false; // Non-serialized + this.stateGraph = null; this.layers = []; this.layerIndicies = {}; this.parameters = {}; - this.model = null; }; return { diff --git a/src/framework/components/anim/layer.js b/src/framework/components/anim/layer.js new file mode 100644 index 00000000000..002d8d951ad --- /dev/null +++ b/src/framework/components/anim/layer.js @@ -0,0 +1,188 @@ +Object.assign(pc, function () { + + /** + * @private + * @class + * @name pc.AnimComponentLayer + * @classdesc The Anim Component Layer allows managers a single layer of the animation state graph. + * @description Create a new AnimComponentLayer. + * @param {string} name - The name of the layer. + * @param {object} controller - The controller to manage this layers animations. + * @param {pc.AnimComponent} component - The component that this layer is a member of. + */ + var AnimComponentLayer = function (name, controller, component) { + this._name = name; + this._controller = controller; + this._component = component; + }; + + Object.assign(AnimComponentLayer.prototype, { + /** + * @private + * @function + * @name pc.AnimComponentLayer#play + * @description Start playing the animation in the current state. + * @param {string} name - If provided, will begin playing from the start of the state with this name. + */ + play: function (name) { + this._controller.play(name); + }, + + /** + * @private + * @function + * @name pc.AnimComponentLayer#pause + * @description Start playing the animation in the current state. + */ + pause: function () { + this._controller.pause(); + }, + + /** + * @private + * @function + * @name pc.AnimComponentLayer#reset + * @description Reset the animation component to it's initial state, including all parameters. The system will be paused. + */ + reset: function () { + this._controller.reset(); + }, + + update: function (dt) { + this._controller.update(dt); + }, + + /** + * @private + * @function + * @name pc.AnimComponentLayer#assignAnimation + * @description Associates an animation with a state in the loaded state graph. If all states are linked and the pc.AnimComponent.activate value was set to true then the component will begin playing. + * @param {string} stateName - The name of the state that this animation should be associated with. + * @param {object} animTrack - The animation track that will be assigned to this state and played whenever this state is active. + */ + assignAnimation: function (stateName, animTrack) { + this._controller.assignAnimation(stateName, animTrack); + + if (this._component.activate) { + for (var i = 0; i < this._component.data.layers.length; i++) { + if (!this._component.data.layers[i].playable) { + return; + } + this._component.playing = true; + } + } + }, + + /** + * @private + * @function + * @name pc.AnimComponentLayer#removeStateAnimations + * @description Removes animations from a state in the loaded state graph. + * @param {string} stateName - The name of the state that should have its animation tracks removed. + */ + removeStateAnimations: function (stateName) { + this._controller.removeStateAnimations(stateName); + }, + + getParameterValue: function (name, type) { + this._controller.getParameterValue(name, type); + }, + + setParameterValue: function (name, type, value) { + this._controller.setParameterValue(name, type, value); + } + }); + + Object.defineProperties(AnimComponentLayer.prototype, { + /** + * @private + * @name pc.AnimComponentLayer#name + * @property {string} name - Returns the name of the layer + */ + name: { + get: function () { + return this._name; + } + }, + /** + * @private + * @name pc.AnimComponentLayer#playing + * @property {string} playing - Whether this layer is currently playing + */ + playing: { + get: function () { + return this._controller.playing; + }, + set: function (value) { + this._controller.playing = value; + } + }, + /** + * @private + * @name pc.AnimComponentLayer#playable + * @property {string} playable - Returns true if a state graph has been loaded and all states in the graph have been assigned animation tracks. + */ + playable: { + get: function () { + return this._controller.playable; + } + }, + /** + * @private + * @name pc.AnimComponentLayer#activeState + * @property {string} activeState - Returns the currently active state name. + */ + activeState: { + get: function () { + return this._controller.activeStateName; + } + }, + /** + * @private + * @name pc.AnimComponentLayer#previousState + * @property {string} previousState - Returns the previously active state name. + */ + previousState: { + get: function () { + return this._controller.previousStateName; + } + }, + /** + * @private + * @name pc.AnimComponentLayer#activeStateProgress + * @property {number} activeStateProgress - Returns the currently active states progress as a value normalised by the states animation duration. Looped animations will return values greater than 1. + */ + activeStateProgress: { + get: function () { + return this._controller.activeStateProgress; + } + }, + /** + * @private + * @name pc.AnimComponentLayer#transitioning + * @property {boolean} transitioning - Returns whether the anim component layer is currently transitioning between states. + */ + transitioning: { + get: function () { + return this._controller.transitioning; + } + }, + /** + * @private + * @name pc.AnimComponentLayer#transitionProgress + * @property {number} transitionProgress - If the anim component layer is currently transitioning between states, returns the progress. Otherwise returns null. + */ + transitionProgress: { + get: function () { + if (this.transitioning) { + return this._controller.transitionProgress; + } + return null; + } + } + }); + + return { + AnimComponentLayer: AnimComponentLayer + }; +}()); diff --git a/src/framework/components/anim/property-locator.js b/src/framework/components/anim/property-locator.js index eaaff9b9fff..368e24073ca 100644 --- a/src/framework/components/anim/property-locator.js +++ b/src/framework/components/anim/property-locator.js @@ -1,5 +1,6 @@ Object.assign(pc, function () { /** + * @private * @class * @name pc.AnimPropertyLocator * @classdesc The AnimProperyLocator encodes and decodes paths to properties in the scene hierarchy. @@ -9,6 +10,7 @@ Object.assign(pc, function () { }; Object.assign(AnimPropertyLocator.prototype, { /** + * @private * @function * @name pc.AnimPropertyLocator#encode * @description Converts a locator array into its string version @@ -26,6 +28,7 @@ Object.assign(pc, function () { ], '/'); }, /** + * @private * @function * @name pc.AnimPropertyLocator#decode * @description Converts a locator string into its array version diff --git a/src/framework/components/anim/state-graph.js b/src/framework/components/anim/state-graph.js new file mode 100644 index 00000000000..bceba5c7297 --- /dev/null +++ b/src/framework/components/anim/state-graph.js @@ -0,0 +1,37 @@ +Object.assign(pc, function () { + + var AnimStateGraph = function (data) { + this._layers = []; + this._parameters = {}; + if (data) { + if (data.states) { + this._layers = []; + this._layers.push({ + name: 'DEFAULT_LAYER', + states: data.states, + transitions: data.transitions + }); + } else { + this._layers = data.layers; + } + this._parameters = Object.assign({}, data.parameters); + } + }; + + Object.defineProperties(AnimStateGraph.prototype, { + parameters: { + get: function () { + return Object.assign({}, this._parameters); + } + }, + layers: { + get: function () { + return this._layers; + } + } + }); + + return { + AnimStateGraph: AnimStateGraph + }; +}()); diff --git a/src/framework/components/anim/system.js b/src/framework/components/anim/system.js index 2e8ae5925c4..4a32ef259cc 100644 --- a/src/framework/components/anim/system.js +++ b/src/framework/components/anim/system.js @@ -7,6 +7,7 @@ Object.assign(pc, function () { ]; /** + * @private * @class * @name pc.AnimComponentSystem * @augments pc.ComponentSystem diff --git a/src/resources/animation-clip.js b/src/resources/animation-clip.js index c6d323dfa2a..1ef874b15e8 100644 --- a/src/resources/animation-clip.js +++ b/src/resources/animation-clip.js @@ -2,6 +2,7 @@ Object.assign(pc, function () { 'use strict'; /** + * @private * @class * @name pc.AnimationClipHandler * @implements {pc.ResourceHandler} diff --git a/src/resources/animation-state-graph.js b/src/resources/animation-state-graph.js index b3fcd0ccf33..dc7ff4fa724 100644 --- a/src/resources/animation-state-graph.js +++ b/src/resources/animation-state-graph.js @@ -2,6 +2,7 @@ Object.assign(pc, function () { 'use strict'; /** + * @private * @class * @name pc.AnimationStateGraphHandler * @implements {pc.ResourceHandler} @@ -39,7 +40,7 @@ Object.assign(pc, function () { }, open: function (url, data) { - return data; + return new pc.AnimStateGraph(data); } }); From e9769030ef8f938fc83fd970bb01e926ecb7eecf Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 29 May 2020 14:56:42 +0200 Subject: [PATCH 070/144] feat(GlbParser): use new pc.AnimComponent --- src/resources/container.js | 32 ++++++++++++++++--- src/resources/parser/glb-parser.js | 49 +++++++++++++++++++++--------- 2 files changed, 62 insertions(+), 19 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index f39688355cf..140bbf2862a 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -178,12 +178,36 @@ Object.assign(pc, function () { } if (components.animations.length > 0) { - node.addComponent('animation', { - assets: components.animations.map(function (animationIndex) { - return animationAssets[animationIndex].id; - }), + var anim = node.addComponent('anim', { enabled: false }); + + var ANIM_STATE_ACTIVE = 'ACTIVE'; + + var animLayers = components.animations.map(function (animationIndex) { + return { + name: animationAssets[animationIndex].resource.name, + states: [ + { name: pc.ANIM_STATE_START }, + { name: ANIM_STATE_ACTIVE } + ], + transitions: [], + parameters: {} + }; + }); + + anim.loadStateGraph({ layers: animLayers }); + + components.animations.forEach(function (animationIndex) { + var layer = anim.findAnimationLayer(animationAssets[animationIndex].resource.name); + if (!layer) { + return; + } + layer.assignAnimation(ANIM_STATE_ACTIVE, animationAssets[animationIndex].resource); + layer.play(ANIM_STATE_ACTIVE); + }); + + console.log(anim); } }); diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index df8829e8b34..2514c954218 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -17,6 +17,13 @@ Object.assign(pc, function () { return Math.pow(2, Math.round(Math.log(n) / Math.log(2))); }; + function getNodeLocatorPath(graphNode) { + if (graphNode.parent) { + return getNodeLocatorPath(graphNode.parent).concat([graphNode.name]); + } + return [graphNode.name]; + } + var getNumComponents = function (accessorType) { switch (accessorType) { case 'SCALAR': return 1; @@ -1072,6 +1079,7 @@ Object.assign(pc, function () { var outputs = []; var curves = []; + var targetRootNodes = []; var i; @@ -1119,7 +1127,9 @@ Object.assign(pc, function () { var channel = animationData.channels[i]; var target = channel.target; var curve = curves[channel.sampler]; - curve._paths.push(propertyLocator.encode([[nodes[target.node].name], 'graph', [transformSchema[target.path]]])); + var targetNodePath = getNodeLocatorPath(nodes[target.node]); + curve._paths.push(propertyLocator.encode([targetNodePath, 'entity', [transformSchema[target.path]]])); + targetRootNodes.push(targetNodePath[0]); // if this target is a set of quaternion keys, make note of its index so we can perform // quaternion-specific processing on it. @@ -1171,12 +1181,19 @@ Object.assign(pc, function () { return Math.max(value, data.length === 0 ? 0 : data[data.length - 1]); }, 0); - return new pc.AnimTrack( - animationData.hasOwnProperty('name') ? animationData.name : ("animation_" + animationIndex), - duration, - inputs, - outputs, - curves); + return { + animation: new pc.AnimTrack( + animationData.hasOwnProperty('name') ? animationData.name : ("animation_" + animationIndex), + duration, + inputs, + outputs, + curves + ), + // Return root nodes without duplicates + targetRootNodes: targetRootNodes.filter(function (rootNode, index, rootNodes) { + return rootNodes.indexOf(rootNode) === index; + }) + }; }; var createNode = function (nodeData, nodeIndex) { @@ -1238,8 +1255,7 @@ Object.assign(pc, function () { var createModel = function (node, meshGroup, skin, materials, defaultMaterial) { var model = new pc.Model(); - // Node name is used as path in animation curves - model.graph = new pc.GraphNode(node.name); + model.graph = new pc.GraphNode("model_" + node.name); meshGroup.forEach(function (mesh) { var material = (mesh.materialIndex === undefined) ? defaultMaterial : materials[mesh.materialIndex]; @@ -1325,10 +1341,13 @@ Object.assign(pc, function () { gltf.animations.forEach(function (animationData, animationIndex) { var animation = createAnimation(animationData, animationIndex, gltf.accessors, gltf.bufferViews, nodes, buffers); - animations.push(animation); + animations.push(animation.animation); - animationData.channels.forEach(function (channel) { - nodeComponents[channel.target.node].animations.push(animationIndex); + animation.targetRootNodes.forEach(function (rootNode) { + var rootNodeIndex = nodes.findIndex(function (node) { + return node.name === rootNode; + }); + nodeComponents[rootNodeIndex].animations.push(animationIndex); }); }); @@ -1406,11 +1425,12 @@ Object.assign(pc, function () { var nodes = createNodes(gltf); var nodeComponents = nodes.map(function () { return { - animations: [], - model: null + model: null, + animations: [] }; }); + var animations = createAnimations(gltf, nodes, nodeComponents, buffers); var scenes = createScenes(gltf, nodes); var scene = getDefaultScene(gltf, scenes); var textures = createTextures(device, gltf, images); @@ -1418,7 +1438,6 @@ Object.assign(pc, function () { var meshGroups = createMeshGroups(device, gltf, buffers, callback); var skins = createSkins(device, gltf, nodes, buffers); var models = createModels(gltf, nodes, nodeComponents, meshGroups, skins, materials, defaultMaterial); - var animations = createAnimations(gltf, nodes, nodeComponents, buffers); callback(null, { 'nodes': nodes, From 9835700128e557fc3fe59ec9a56a736bb5ae6b47 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 29 May 2020 15:23:05 +0200 Subject: [PATCH 071/144] refactor(GlbParser): refactor the way node locator paths are calculated to make it less error prone (e.g. in case of duplicate node names or scenes being created before animations) --- src/resources/parser/glb-parser.js | 44 ++++++++++++++++++------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 2514c954218..b7769d98b33 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -17,11 +17,14 @@ Object.assign(pc, function () { return Math.pow(2, Math.round(Math.log(n) / Math.log(2))); }; - function getNodeLocatorPath(graphNode) { - if (graphNode.parent) { - return getNodeLocatorPath(graphNode.parent).concat([graphNode.name]); + function getNodePath(targetNode, nodes) { + var parent = nodes.findIndex(function (node) { + return node.hasOwnProperty('children') ? node.children.includes(targetNode) : false; + }); + if (parent !== -1) { + return getNodePath(parent, nodes).concat([targetNode]); } - return [graphNode.name]; + return [targetNode]; } var getNumComponents = function (accessorType) { @@ -1057,7 +1060,7 @@ Object.assign(pc, function () { }; // create the anim structure - var createAnimation = function (animationData, animationIndex, accessors, bufferViews, nodes, buffers) { + var createAnimation = function (animationData, animationIndex, accessors, bufferViews, nodes, nodeEntities, buffers) { // create animation data block for the accessor var createAnimData = function (accessor) { @@ -1127,9 +1130,14 @@ Object.assign(pc, function () { var channel = animationData.channels[i]; var target = channel.target; var curve = curves[channel.sampler]; - var targetNodePath = getNodeLocatorPath(nodes[target.node]); - curve._paths.push(propertyLocator.encode([targetNodePath, 'entity', [transformSchema[target.path]]])); + // get node locator path relative to root node + var targetNodePath = getNodePath(target.node, nodes); + var targetNodePathNamed = targetNodePath.map(function (node) { + return nodeEntities[node].name; + }); + targetRootNodes.push(targetNodePath[0]); + curve._paths.push(propertyLocator.encode([targetNodePathNamed, 'entity', [transformSchema[target.path]]])); // if this target is a set of quaternion keys, make note of its index so we can perform // quaternion-specific processing on it. @@ -1182,14 +1190,14 @@ Object.assign(pc, function () { }, 0); return { - animation: new pc.AnimTrack( + track: new pc.AnimTrack( animationData.hasOwnProperty('name') ? animationData.name : ("animation_" + animationIndex), duration, inputs, outputs, curves ), - // Return root nodes without duplicates + // return root nodes without duplicates targetRootNodes: targetRootNodes.filter(function (rootNode, index, rootNodes) { return rootNodes.indexOf(rootNode) === index; }) @@ -1337,21 +1345,21 @@ Object.assign(pc, function () { return []; } - var animations = []; + var animTracks = []; gltf.animations.forEach(function (animationData, animationIndex) { - var animation = createAnimation(animationData, animationIndex, gltf.accessors, gltf.bufferViews, nodes, buffers); - animations.push(animation.animation); + var animation = createAnimation(animationData, animationIndex, gltf.accessors, gltf.bufferViews, gltf.nodes, nodes, buffers); + animTracks.push(animation.track); + // animation components should be added to all root nodes targeted by an + // animation track since the locator path in animation curves is relative + // to its targets root node animation.targetRootNodes.forEach(function (rootNode) { - var rootNodeIndex = nodes.findIndex(function (node) { - return node.name === rootNode; - }); - nodeComponents[rootNodeIndex].animations.push(animationIndex); + nodeComponents[rootNode].animations.push(animationIndex); }); }); - return animations; + return animTracks; }; var createNodes = function (gltf) { @@ -1430,7 +1438,6 @@ Object.assign(pc, function () { }; }); - var animations = createAnimations(gltf, nodes, nodeComponents, buffers); var scenes = createScenes(gltf, nodes); var scene = getDefaultScene(gltf, scenes); var textures = createTextures(device, gltf, images); @@ -1438,6 +1445,7 @@ Object.assign(pc, function () { var meshGroups = createMeshGroups(device, gltf, buffers, callback); var skins = createSkins(device, gltf, nodes, buffers); var models = createModels(gltf, nodes, nodeComponents, meshGroups, skins, materials, defaultMaterial); + var animations = createAnimations(gltf, nodes, nodeComponents, buffers); callback(null, { 'nodes': nodes, From a975095421389571f75f9b9ae76be676c617a95f Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 29 May 2020 15:23:31 +0200 Subject: [PATCH 072/144] fix(AnimComponentSystem): remove call to undefined function --- src/framework/components/anim/system.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/framework/components/anim/system.js b/src/framework/components/anim/system.js index 2e8ae5925c4..8efde486aae 100644 --- a/src/framework/components/anim/system.js +++ b/src/framework/components/anim/system.js @@ -42,7 +42,6 @@ Object.assign(pc, function () { }, onBeforeRemove: function (entity, component) { - component.onBeforeRemove(); }, onUpdate: function (dt) { From ae76a074ee58e6646a67dd79596f2a4948e0c6d1 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 29 May 2020 15:30:38 +0200 Subject: [PATCH 073/144] refactor(ContainerResource): remove console.log --- src/resources/container.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index 140bbf2862a..ecfec50892c 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -1,5 +1,7 @@ Object.assign(pc, function () { + var ANIM_STATE_ACTIVE = 'ACTIVE'; + /** * @class * @name pc.ContainerResource @@ -182,8 +184,6 @@ Object.assign(pc, function () { enabled: false }); - var ANIM_STATE_ACTIVE = 'ACTIVE'; - var animLayers = components.animations.map(function (animationIndex) { return { name: animationAssets[animationIndex].resource.name, @@ -206,8 +206,6 @@ Object.assign(pc, function () { layer.assignAnimation(ANIM_STATE_ACTIVE, animationAssets[animationIndex].resource); layer.play(ANIM_STATE_ACTIVE); }); - - console.log(anim); } }); From 85de1af3fef89671ecddafd55af6201c7552b647 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 29 May 2020 15:36:13 +0200 Subject: [PATCH 074/144] refactor(GlbParser): add sanity check for nodeComponents in createModels and createAnimations --- src/resources/parser/glb-parser.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index b7769d98b33..36479423201 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1355,6 +1355,12 @@ Object.assign(pc, function () { // animation track since the locator path in animation curves is relative // to its targets root node animation.targetRootNodes.forEach(function (rootNode) { + if (!nodeComponents[rootNode]) { + nodeComponents[rootNode] = {}; + } + if (!nodeComponents[rootNode].animations) { + nodeComponents[rootNode].animations = []; + } nodeComponents[rootNode].animations.push(animationIndex); }); }); @@ -1417,11 +1423,16 @@ Object.assign(pc, function () { if (!nodeData.hasOwnProperty('mesh')) { return; } + var node = nodes[nodeIndex]; var meshGroup = meshGroups[nodeData.mesh]; var skin = nodeData.hasOwnProperty('skin') ? skins[nodeData.skin] : null; var model = createModel(node, meshGroup, skin, materials, defaultMaterial); var modelIndex = models.push(model) - 1; + + if (!nodeComponents[nodeIndex]) { + nodeComponents[nodeIndex] = {}; + } nodeComponents[nodeIndex].model = modelIndex; }); From 58222542d0e312a05f97f0890e06c3f5d634f362 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 29 May 2020 16:45:51 +0200 Subject: [PATCH 075/144] fix(ContainerResource): make sure anim components are enabled by default and pause all layers to prevent animations from starting automatically --- src/resources/container.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index ecfec50892c..534a04aeb0e 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -180,9 +180,7 @@ Object.assign(pc, function () { } if (components.animations.length > 0) { - var anim = node.addComponent('anim', { - enabled: false - }); + var anim = node.addComponent('anim'); var animLayers = components.animations.map(function (animationIndex) { return { @@ -204,7 +202,11 @@ Object.assign(pc, function () { return; } layer.assignAnimation(ANIM_STATE_ACTIVE, animationAssets[animationIndex].resource); + // This is currently the only way to set the current state of the layer. + // By doing this the animation can be played by simply running layer.play() + // in an application, since a GLB anim layer will never have more than one state. layer.play(ANIM_STATE_ACTIVE); + layer.pause(); }); } }); From 98e1318ac2bf270011c18e80a34f90b29764d29f Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 29 May 2020 16:46:22 +0200 Subject: [PATCH 076/144] chore(AnimComponent): fix jsdoc/typings --- src/framework/components/anim/component.js | 34 ++++++++++++++++------ src/framework/components/anim/data.js | 11 +++++++ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 1610f18420e..9d342088a5e 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -20,7 +20,7 @@ Object.assign(pc, function () { * @function * @name pc.AnimComponentLayer#play * @description Start playing the animation in the current state. - * @param {string} name - If provided, will begin playing from the start of the state with this name. + * @param {string} [name] - If provided, will begin playing from the start of the state with this name. */ play: function (name) { this._controller.play(name); @@ -89,8 +89,10 @@ Object.assign(pc, function () { Object.defineProperties(AnimComponentLayer.prototype, { /** + * @readonly * @name pc.AnimComponentLayer#name - * @property {string} name - Returns the name of the layer + * @type {string} + * @description Returns the name of the layer */ name: { get: function () { @@ -99,7 +101,8 @@ Object.assign(pc, function () { }, /** * @name pc.AnimComponentLayer#playing - * @property {string} playing - Whether this layer is currently playing + * @type {boolean} + * @description Whether this layer is currently playing */ playing: { get: function () { @@ -110,8 +113,10 @@ Object.assign(pc, function () { } }, /** + * @readonly * @name pc.AnimComponentLayer#playable - * @property {string} playable - Returns true if a state graph has been loaded and all states in the graph have been assigned animation tracks. + * @type {boolean} + * @description Returns true if a state graph has been loaded and all states in the graph have been assigned animation tracks. */ playable: { get: function () { @@ -119,8 +124,10 @@ Object.assign(pc, function () { } }, /** + * @readonly * @name pc.AnimComponentLayer#activeState - * @property {string} activeState - Returns the currently active state name. + * @type {string} + * @description Returns the currently active state name. */ activeState: { get: function () { @@ -128,8 +135,10 @@ Object.assign(pc, function () { } }, /** + * @readonly * @name pc.AnimComponentLayer#previousState - * @property {string} previousState - Returns the previously active state name. + * @type {string} + * @description Returns the previously active state name. */ previousState: { get: function () { @@ -137,8 +146,10 @@ Object.assign(pc, function () { } }, /** + * @readonly * @name pc.AnimComponentLayer#activeStateProgress - * @property {number} activeStateProgress - Returns the currently active states progress as a value normalised by the states animation duration. Looped animations will return values greater than 1. + * @type {number} + * @description Returns the currently active states progress as a value normalised by the states animation duration. Looped animations will return values greater than 1. */ activeStateProgress: { get: function () { @@ -146,8 +157,10 @@ Object.assign(pc, function () { } }, /** + * @readonly * @name pc.AnimComponentLayer#transitioning - * @property {boolean} transitioning - Returns whether the anim component layer is currently transitioning between states. + * @type {boolean} + * @description Returns whether the anim component layer is currently transitioning between states. */ transitioning: { get: function () { @@ -155,8 +168,10 @@ Object.assign(pc, function () { } }, /** + * @readonly * @name pc.AnimComponentLayer#transitionProgress - * @property {number} transitionProgress - If the anim component layer is currently transitioning between states, returns the progress. Otherwise returns null. + * @type {number} + * @description If the anim component layer is currently transitioning between states, returns the progress. Otherwise returns null. */ transitionProgress: { get: function () { @@ -179,6 +194,7 @@ Object.assign(pc, function () { * @param {pc.Entity} entity - The Entity that this Component is attached to. * @property {number} speed Speed multiplier for animation play back speed. 1.0 is playback at normal speed, 0.0 pauses the animation. * @property {boolean} activate If true the first animation will begin playing when the scene is loaded. + * @property {pc.AnimComponentData} data */ var AnimComponent = function (system, entity) { pc.Component.call(this, system, entity); diff --git a/src/framework/components/anim/data.js b/src/framework/components/anim/data.js index 0c8c12ab6a4..36c20544c34 100644 --- a/src/framework/components/anim/data.js +++ b/src/framework/components/anim/data.js @@ -1,4 +1,15 @@ Object.assign(pc, function () { + /** + * @class + * @name pc.AnimComponentData + * @property {number} speed + * @property {boolean} active + * @property {boolean} enabled + * @property {boolean} playing + * @property {pc.AnimComponentLayer[]} layers + * @property {object|undefined} parameters + * @property {pc.Model|null} model + */ var AnimComponentData = function () { // Serialized this.speed = 1.0; From 9b60ba244f6695f02f945cfbf225e6720b46bfc5 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Mon, 1 Jun 2020 11:26:58 +0200 Subject: [PATCH 077/144] 1.28.0-rc.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index f29d122c577..194ad5d257e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@animech-public/playcanvas", - "version": "1.28.0-dev", + "version": "1.28.0-rc.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b0cc8b9a5a2..bf4ad2e1022 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@animech-public/playcanvas", - "version": "1.28.0-dev", + "version": "1.28.0-rc.0", "author": "PlayCanvas ", "homepage": "https://playcanvas.com", "description": "PlayCanvas WebGL game engine", From 2e6ad0b99f78aa1d80d5cdaeb68425a15ed1d76e Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Mon, 1 Jun 2020 11:33:42 +0100 Subject: [PATCH 078/144] create a new animation update step in the component system, expose layer states & update jsdocs --- src/framework/application.js | 1 + src/framework/components/anim/component.js | 6 ++ src/framework/components/anim/controller.js | 11 +++- src/framework/components/anim/layer.js | 63 +++++++++++++++------ src/framework/components/anim/system.js | 10 +--- src/framework/components/system.js | 12 ++++ 6 files changed, 76 insertions(+), 27 deletions(-) diff --git a/src/framework/application.js b/src/framework/application.js index c2c58e243b7..ff186d0267b 100644 --- a/src/framework/application.js +++ b/src/framework/application.js @@ -1259,6 +1259,7 @@ Object.assign(pc, function () { pc.ComponentSystem.fixedUpdate(1.0 / 60.0, this._inTools); pc.ComponentSystem.update(dt, this._inTools); + pc.ComponentSystem.animationUpdate(dt, this._inTools); pc.ComponentSystem.postUpdate(dt, this._inTools); // fire update event diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 76a5fa88058..3c3bd7afe91 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -280,6 +280,12 @@ Object.assign(pc, function () { } }); + /** + * @private + * @name pc.AnimComponent#stateGraphAsset + * @type {string} + * @description The state graph asset this component should use to generate it's animation state graph + */ Object.defineProperty(AnimComponent.prototype, "stateGraphAsset", { get: function () { return this.data.stateGraphAsset; diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index ef001fb78d5..dcc8dfaeb7c 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -180,6 +180,7 @@ Object.assign(pc, function () { var AnimController = function (animEvaluator, states, transitions, parameters, activate) { this._animEvaluator = animEvaluator; this._states = {}; + this._stateNames = []; var i; for (i = 0; i < states.length; i++) { this._states[states[i].name] = new AnimState( @@ -187,6 +188,7 @@ Object.assign(pc, function () { states[i].name, states[i].speed ); + this._stateNames.push(states[i].name); } this._transitions = transitions.map(function (transition) { return new AnimTransition( @@ -277,6 +279,11 @@ Object.assign(pc, function () { get: function () { return this._currTransitionTime / this._totalTransitionTime; } + }, + states: { + get: function () { + return this._stateNames; + } } }); @@ -540,8 +547,8 @@ Object.assign(pc, function () { } }, - removeStateAnimations: function (stateName) { - var state = this._findState(stateName); + removeNodeAnimations: function (nodeName) { + var state = this._findState(nodeName); if (!state) { // #ifdef DEBUG console.error('Attempting to unassign animation tracks from a state that does not exist.'); diff --git a/src/framework/components/anim/layer.js b/src/framework/components/anim/layer.js index 002d8d951ad..67ae9610e1e 100644 --- a/src/framework/components/anim/layer.js +++ b/src/framework/components/anim/layer.js @@ -22,7 +22,7 @@ Object.assign(pc, function () { * @function * @name pc.AnimComponentLayer#play * @description Start playing the animation in the current state. - * @param {string} name - If provided, will begin playing from the start of the state with this name. + * @param {string} [name] - If provided, will begin playing from the start of the state with this name. */ play: function (name) { this._controller.play(name); @@ -56,12 +56,12 @@ Object.assign(pc, function () { * @private * @function * @name pc.AnimComponentLayer#assignAnimation - * @description Associates an animation with a state in the loaded state graph. If all states are linked and the pc.AnimComponent.activate value was set to true then the component will begin playing. - * @param {string} stateName - The name of the state that this animation should be associated with. + * @description Associates an animation with a state node in the loaded state graph. If all states nodes are linked and the pc.AnimComponent.activate value was set to true then the component will begin playing. + * @param {string} nodeName - The name of the node that this animation should be associated with. * @param {object} animTrack - The animation track that will be assigned to this state and played whenever this state is active. */ - assignAnimation: function (stateName, animTrack) { - this._controller.assignAnimation(stateName, animTrack); + assignAnimation: function (nodeName, animTrack) { + this._controller.assignAnimation(nodeName, animTrack); if (this._component.activate) { for (var i = 0; i < this._component.data.layers.length; i++) { @@ -76,12 +76,12 @@ Object.assign(pc, function () { /** * @private * @function - * @name pc.AnimComponentLayer#removeStateAnimations - * @description Removes animations from a state in the loaded state graph. - * @param {string} stateName - The name of the state that should have its animation tracks removed. + * @name pc.AnimComponentLayer#removeNodeAnimations + * @description Removes animations from a node in the loaded state graph. + * @param {string} nodeName - The name of the node that should have its animation tracks removed. */ - removeStateAnimations: function (stateName) { - this._controller.removeStateAnimations(stateName); + removeNodeAnimations: function (nodeName) { + this._controller.removeNodeAnimations(nodeName); }, getParameterValue: function (name, type) { @@ -96,8 +96,10 @@ Object.assign(pc, function () { Object.defineProperties(AnimComponentLayer.prototype, { /** * @private + * @readonly * @name pc.AnimComponentLayer#name - * @property {string} name - Returns the name of the layer + * @type {string} + * @description Returns the name of the layer */ name: { get: function () { @@ -107,7 +109,8 @@ Object.assign(pc, function () { /** * @private * @name pc.AnimComponentLayer#playing - * @property {string} playing - Whether this layer is currently playing + * @type {string} + * @description Whether this layer is currently playing */ playing: { get: function () { @@ -119,8 +122,10 @@ Object.assign(pc, function () { }, /** * @private + * @readonly * @name pc.AnimComponentLayer#playable - * @property {string} playable - Returns true if a state graph has been loaded and all states in the graph have been assigned animation tracks. + * @type {string} + * @description Returns true if a state graph has been loaded and all states in the graph have been assigned animation tracks. */ playable: { get: function () { @@ -129,8 +134,10 @@ Object.assign(pc, function () { }, /** * @private + * @readonly * @name pc.AnimComponentLayer#activeState - * @property {string} activeState - Returns the currently active state name. + * @type {string} + * @description Returns the currently active state name. */ activeState: { get: function () { @@ -139,8 +146,10 @@ Object.assign(pc, function () { }, /** * @private + * @readonly * @name pc.AnimComponentLayer#previousState - * @property {string} previousState - Returns the previously active state name. + * @type {string} + * @description Returns the previously active state name. */ previousState: { get: function () { @@ -149,8 +158,10 @@ Object.assign(pc, function () { }, /** * @private + * @readonly * @name pc.AnimComponentLayer#activeStateProgress - * @property {number} activeStateProgress - Returns the currently active states progress as a value normalised by the states animation duration. Looped animations will return values greater than 1. + * @type {number} + * @description Returns the currently active states progress as a value normalised by the states animation duration. Looped animations will return values greater than 1. */ activeStateProgress: { get: function () { @@ -159,8 +170,10 @@ Object.assign(pc, function () { }, /** * @private + * @readonly * @name pc.AnimComponentLayer#transitioning - * @property {boolean} transitioning - Returns whether the anim component layer is currently transitioning between states. + * @type {boolean} + * @description Returns whether the anim component layer is currently transitioning between states. */ transitioning: { get: function () { @@ -169,8 +182,10 @@ Object.assign(pc, function () { }, /** * @private + * @readonly * @name pc.AnimComponentLayer#transitionProgress - * @property {number} transitionProgress - If the anim component layer is currently transitioning between states, returns the progress. Otherwise returns null. + * @type {number} + * @description If the anim component layer is currently transitioning between states, returns the progress. Otherwise returns null. */ transitionProgress: { get: function () { @@ -179,6 +194,18 @@ Object.assign(pc, function () { } return null; } + }, + /** + * @private + * @readonly + * @name pc.AnimComponentLayer#states + * @type {string[]} + * @description Lists all available states in this layers state graph + */ + states: { + get: function () { + return this._controller.states; + } } }); diff --git a/src/framework/components/anim/system.js b/src/framework/components/anim/system.js index 4a32ef259cc..9f5e6fd17ad 100644 --- a/src/framework/components/anim/system.js +++ b/src/framework/components/anim/system.js @@ -27,9 +27,9 @@ Object.assign(pc, function () { this.schema = _schema; this.on('beforeremove', this.onBeforeRemove, this); - this.on('update', this.onUpdate, this); + this.on('animationUpdate', this.onAnimationUpdate, this); - pc.ComponentSystem.bind('update', this.onUpdate, this); + pc.ComponentSystem.bind('animationUpdate', this.onAnimationUpdate, this); }; AnimComponentSystem.prototype = Object.create(pc.ComponentSystem.prototype); AnimComponentSystem.prototype.constructor = AnimComponentSystem; @@ -42,11 +42,7 @@ Object.assign(pc, function () { pc.ComponentSystem.prototype.initializeComponentData.call(this, component, data, properties); }, - onBeforeRemove: function (entity, component) { - component.onBeforeRemove(); - }, - - onUpdate: function (dt) { + onAnimationUpdate: function (dt) { var components = this.store; for (var id in components) { diff --git a/src/framework/components/system.js b/src/framework/components/system.js index f5b6e5acfb7..0805039ad68 100644 --- a/src/framework/components/system.js +++ b/src/framework/components/system.js @@ -42,6 +42,10 @@ Object.assign(pc, function () { this._helper(inTools ? this._toolsUpdate : this._update, dt); }, + animationUpdate: function (dt, inTools) { + this._helper(this._animationUpdate, dt); + }, + // Update all ComponentSystems fixedUpdate: function (dt, inTools) { this._helper(this._fixedUpdate, dt); @@ -56,6 +60,7 @@ Object.assign(pc, function () { _postInit: [], _toolsUpdate: [], _update: [], + _animationUpdate: [], _fixedUpdate: [], _postUpdate: [], @@ -70,6 +75,9 @@ Object.assign(pc, function () { case 'update': this._update.push({ f: func, s: scope }); break; + case 'animationUpdate': + this._animationUpdate.push({ f: func, s: scope }); + break; case 'postUpdate': this._postUpdate.push({ f: func, s: scope }); break; @@ -103,6 +111,9 @@ Object.assign(pc, function () { case 'update': this._erase(this._update, func, scope); break; + case 'animationUpdate': + this._erase(this._animationUpdate, func, scope); + break; case 'postUpdate': this._erase(this._postUpdate, func, scope); break; @@ -316,6 +327,7 @@ Object.assign(pc, function () { ComponentSystem.off('postInitialize'); ComponentSystem.off('toolsUpdate'); ComponentSystem.off('update'); + ComponentSystem.off('animationUpdate'); ComponentSystem.off('fixedUpdate'); ComponentSystem.off('postUpdate'); }; From 456d9a88bd573be91459398a3b68f0e021707d2f Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Mon, 1 Jun 2020 14:27:49 +0200 Subject: [PATCH 079/144] fix(AnimComponent): make methods and properties public --- src/framework/components/anim/component.js | 1 - src/framework/components/anim/layer.js | 5 ----- 2 files changed, 6 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index dc543d71c62..c6323ed21d9 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -1,7 +1,6 @@ Object.assign(pc, function () { /** - * @private * @component Anim * @class * @name pc.AnimComponent diff --git a/src/framework/components/anim/layer.js b/src/framework/components/anim/layer.js index 67ae9610e1e..7b8f6cb0718 100644 --- a/src/framework/components/anim/layer.js +++ b/src/framework/components/anim/layer.js @@ -1,7 +1,6 @@ Object.assign(pc, function () { /** - * @private * @class * @name pc.AnimComponentLayer * @classdesc The Anim Component Layer allows managers a single layer of the animation state graph. @@ -18,7 +17,6 @@ Object.assign(pc, function () { Object.assign(AnimComponentLayer.prototype, { /** - * @private * @function * @name pc.AnimComponentLayer#play * @description Start playing the animation in the current state. @@ -29,7 +27,6 @@ Object.assign(pc, function () { }, /** - * @private * @function * @name pc.AnimComponentLayer#pause * @description Start playing the animation in the current state. @@ -95,7 +92,6 @@ Object.assign(pc, function () { Object.defineProperties(AnimComponentLayer.prototype, { /** - * @private * @readonly * @name pc.AnimComponentLayer#name * @type {string} @@ -121,7 +117,6 @@ Object.assign(pc, function () { } }, /** - * @private * @readonly * @name pc.AnimComponentLayer#playable * @type {string} From da3dfafc25f55f7b266b7f3abf9da469767679fb Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Mon, 1 Jun 2020 14:44:29 +0200 Subject: [PATCH 080/144] refactor(ContainerResource): use prop pc.AnimComponentLayer.states to set current state --- src/resources/container.js | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index 534a04aeb0e..d5b6c493092 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -1,7 +1,5 @@ Object.assign(pc, function () { - var ANIM_STATE_ACTIVE = 'ACTIVE'; - /** * @class * @name pc.ContainerResource @@ -182,30 +180,31 @@ Object.assign(pc, function () { if (components.animations.length > 0) { var anim = node.addComponent('anim'); - var animLayers = components.animations.map(function (animationIndex) { - return { - name: animationAssets[animationIndex].resource.name, - states: [ - { name: pc.ANIM_STATE_START }, - { name: ANIM_STATE_ACTIVE } - ], - transitions: [], - parameters: {} - }; + // Create one layer per animation asset so that the animations can be played simultaneously + anim.loadStateGraph({ + layers: components.animations.map(function (animationIndex) { + return { + name: animationAssets[animationIndex].resource.name, + states: [ + { name: pc.ANIM_STATE_START }, // Needed for pc.AnimComponentLayer not to crash + { name: "ACTIVE" } + ], + transitions: [], + parameters: {} + }; + }) }); - anim.loadStateGraph({ layers: animLayers }); - components.animations.forEach(function (animationIndex) { var layer = anim.findAnimationLayer(animationAssets[animationIndex].resource.name); if (!layer) { return; } - layer.assignAnimation(ANIM_STATE_ACTIVE, animationAssets[animationIndex].resource); - // This is currently the only way to set the current state of the layer. - // By doing this the animation can be played by simply running layer.play() - // in an application, since a GLB anim layer will never have more than one state. - layer.play(ANIM_STATE_ACTIVE); + layer.assignAnimation(layer.states[1], animationAssets[animationIndex].resource); + // This is currently the only public method to set the current state of a layer. + // By doing this the animation of a layer can be played by simply running layer.play() + // in an application. + layer.play(layer.states[1]); layer.pause(); }); } From 4bdc4e55fd7be9fec522e0b3cda75d95200cc1de Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Tue, 2 Jun 2020 13:47:44 +0100 Subject: [PATCH 081/144] anim component play state fix --- src/framework/components/anim/controller.js | 74 +++++++++++---------- src/framework/components/anim/layer.js | 8 --- 2 files changed, 39 insertions(+), 43 deletions(-) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index dcc8dfaeb7c..13928192a72 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -51,10 +51,12 @@ Object.assign(pc, function () { }, looping: { get: function () { - var trackClipName = this.name + '.' + this.animations[0].animTrack.name; - var trackClip = this._controller.animEvaluator.findClip(trackClipName); - if (trackClip) { - return trackClip.loop; + if (this.animations.length > 0) { + var trackClipName = this.name + '.' + this.animations[0].animTrack.name; + var trackClip = this._controller.animEvaluator.findClip(trackClipName); + if (trackClip) { + return trackClip.loop; + } } return false; } @@ -411,7 +413,7 @@ Object.assign(pc, function () { var state; var animation; var clip; - this.previousState = this._activeStateName; + this.previousState = transition.from; this.activeState = transition.to; // turn off any triggers which were required to activate this transition @@ -423,39 +425,41 @@ Object.assign(pc, function () { this.findParameter(triggers[i].parameterName).value = false; } - if (!this._isTransitioning) { - this._transitionPreviousStates = []; - } + if (this.previousState) { + if (!this._isTransitioning) { + this._transitionPreviousStates = []; + } - // record the transition source state in the previous states array - this._transitionPreviousStates.push({ - name: this._previousStateName, - weight: 1 - }); + // record the transition source state in the previous states array + this._transitionPreviousStates.push({ + name: this._previousStateName, + weight: 1 + }); - // if this new transition was activated during another transition, update the previous transition state weights based - // on the progress through the previous transition. - var interpolatedTime = this._currTransitionTime / this._totalTransitionTime; - for (i = 0; i < this._transitionPreviousStates.length; i++) { - // interpolate the weights of the most recent previous state and all other previous states based on the progress through the previous transition - if (i !== this._transitionPreviousStates.length - 1) { - this._transitionPreviousStates[i].weight *= (1.0 - interpolatedTime); - } else { - this._transitionPreviousStates[i].weight = interpolatedTime; - } - state = this._findState(this._transitionPreviousStates[i].name); - // update the animations of previous states, set their name to include their position in the previous state array - // to uniquely identify animations from the same state that were added during different transitions - for (j = 0; j < state.animations.length; j++) { - animation = state.animations[j]; - clip = this._animEvaluator.findClip(animation.name + '.previous.' + i); - if (!clip) { - clip = this._animEvaluator.findClip(animation.name); - clip.name = animation.name + '.previous.' + i; - } - // pause previous animation clips to reduce their impact on performance + // if this new transition was activated during another transition, update the previous transition state weights based + // on the progress through the previous transition. + var interpolatedTime = this._currTransitionTime / this._totalTransitionTime; + for (i = 0; i < this._transitionPreviousStates.length; i++) { + // interpolate the weights of the most recent previous state and all other previous states based on the progress through the previous transition if (i !== this._transitionPreviousStates.length - 1) { + this._transitionPreviousStates[i].weight *= (1.0 - interpolatedTime); + } else { + this._transitionPreviousStates[i].weight = interpolatedTime; + } + state = this._findState(this._transitionPreviousStates[i].name); + // update the animations of previous states, set their name to include their position in the previous state array + // to uniquely identify animations from the same state that were added during different transitions + for (j = 0; j < state.animations.length; j++) { + animation = state.animations[j]; + clip = this._animEvaluator.findClip(animation.name + '.previous.' + i); + if (!clip) { + clip = this._animEvaluator.findClip(animation.name); + clip.name = animation.name + '.previous.' + i; + } + // // pause previous animation clips to reduce their impact on performance + // if (i !== this._transitionPreviousStates.length - 1) { clip.pause(); + // } } } } @@ -515,7 +519,7 @@ Object.assign(pc, function () { var transition = this._findTransition(this._activeStateName, newStateName); if (!transition) { this._animEvaluator.removeClips(); - transition = new AnimTransition(this, this._activeStateName, newStateName, 0, 0); + transition = new AnimTransition(this, null, newStateName, 0, 0); } this._updateStateFromTransition(transition); }, diff --git a/src/framework/components/anim/layer.js b/src/framework/components/anim/layer.js index 67ae9610e1e..ba4c32c9042 100644 --- a/src/framework/components/anim/layer.js +++ b/src/framework/components/anim/layer.js @@ -82,14 +82,6 @@ Object.assign(pc, function () { */ removeNodeAnimations: function (nodeName) { this._controller.removeNodeAnimations(nodeName); - }, - - getParameterValue: function (name, type) { - this._controller.getParameterValue(name, type); - }, - - setParameterValue: function (name, type, value) { - this._controller.setParameterValue(name, type, value); } }); From 8cddf5235a3e3fb24cdc2fbe8b3cc51c65e5111c Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Tue, 2 Jun 2020 17:18:16 +0100 Subject: [PATCH 082/144] PR comments --- build/dependencies.txt | 1 + src/anim/anim.js | 16 ++-- src/framework/components/anim/binder.js | 8 +- src/framework/components/anim/component.js | 22 ++++-- src/framework/components/anim/constants.js | 22 ++++++ src/framework/components/anim/controller.js | 86 ++++++--------------- 6 files changed, 72 insertions(+), 83 deletions(-) create mode 100644 src/framework/components/anim/constants.js diff --git a/build/dependencies.txt b/build/dependencies.txt index 8f2ae6f8433..e72c7d50256 100644 --- a/build/dependencies.txt +++ b/build/dependencies.txt @@ -143,6 +143,7 @@ ../src/framework/components/animation/component.js ../src/framework/components/animation/system.js ../src/framework/components/animation/data.js +../src/framework/components/anim/constants.js ../src/framework/components/anim/binder.js ../src/framework/components/anim/controller.js ../src/framework/components/anim/component.js diff --git a/src/anim/anim.js b/src/anim/anim.js index 41cd69b2b2e..80a7c7e83bb 100644 --- a/src/anim/anim.js +++ b/src/anim/anim.js @@ -665,7 +665,7 @@ Object.assign(pc, function () { this.nodes = nodes; // map of node name -> { node, count } this.activeNodes = []; // list of active nodes this.handlers = { - 'translation': function (node) { + 'localPosition': function (node) { var object = node.localPosition; var func = function (value) { object.set.apply(object, value); @@ -673,7 +673,7 @@ Object.assign(pc, function () { return new pc.AnimTarget(func, 'vector', 3); }, - 'rotation': function (node) { + 'localRotation': function (node) { var object = node.localRotation; var func = function (value) { object.set.apply(object, value); @@ -681,7 +681,7 @@ Object.assign(pc, function () { return new pc.AnimTarget(func, 'quaternion', 4); }, - 'scale': function (node) { + 'localScale': function (node) { var object = node.localScale; var func = function (value) { object.set.apply(object, value); @@ -785,7 +785,7 @@ Object.assign(pc, function () { * @class * @name pc.AnimEvaluator * @classdesc AnimContoller blends multiple sets of animation clips together. - * @description Create a new animation controller. + * @description Create a new animation evaluator. * @param {pc.AnimBinder} binder - interface resolves curve paths to instances of {@link pc.AnimTarget}. * @property {pc.AnimClip[]} clips - the list of animation clips */ @@ -902,8 +902,8 @@ Object.assign(pc, function () { * @private * @function * @name pc.AnimEvaluator#addClip - * @description Add a clip to the controller. - * @param {pc.AnimClip} clip - the clip to add to the controller. + * @description Add a clip to the evaluator. + * @param {pc.AnimClip} clip - the clip to add to the evaluator. */ addClip: function (clip) { var targets = this._targets; @@ -960,7 +960,7 @@ Object.assign(pc, function () { * @private * @function * @name pc.AnimEvaluator#removeClip - * @description Remove a clip from the controller. + * @description Remove a clip from the evaluator. * @param {number} index - index of the clip to remove. */ removeClip: function (index) { @@ -997,7 +997,7 @@ Object.assign(pc, function () { * @private * @function * @name pc.AnimEvaluator#removeClips - * @description Remove all clips from the controller. + * @description Remove all clips from the evaluator. */ removeClips: function () { while (this._clips.length > 0) { diff --git a/src/framework/components/anim/binder.js b/src/framework/components/anim/binder.js index dc7f89e931d..8886c0e969d 100644 --- a/src/framework/components/anim/binder.js +++ b/src/framework/components/anim/binder.js @@ -63,8 +63,10 @@ Object.assign(pc, function () { var entityChildren = currEntity.getChildren(); var child; for (var j = 0; j < entityChildren.length; j++) { - if (entityChildren[j].name === entityHierarchy[i + 1]) + if (entityChildren[j].name === entityHierarchy[i + 1]) { child = entityChildren[j]; + break; + } } if (child) currEntity = child; @@ -152,8 +154,8 @@ Object.assign(pc, function () { _createAnimTargetForProperty: function (propertyComponent, propertyHierarchy) { - if (this.handlers && this.handlers[propertyHierarchy[0]]) { - return this.handlers[propertyHierarchy[0]](propertyComponent); + if (this.handlers && propertyHierarchy[0] === 'weights') { + return this.handlers.weights(propertyComponent); } var property = this._getProperty(propertyComponent, propertyHierarchy); diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 3c3bd7afe91..f93494042a9 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -110,20 +110,26 @@ Object.assign(pc, function () { * @function * @name pc.AnimComponent#assignAnimation * @description Associates an animation with a state in the loaded state graph. If all states are linked and the pc.AnimComponent.activate value was set to true then the component will begin playing. - * @param {string} stateName - The name of the state that this animation should be associated with. + * @param {string} nodeName - The name of the state node that this animation should be associated with. * @param {object} animTrack - The animation track that will be assigned to this state and played whenever this state is active. * @param {string?} layerName - The name of the anim component layer to update. If omitted the default layer is used. */ - assignAnimation: function (stateName, animTrack, layerName) { + assignAnimation: function (nodeName, animTrack, layerName) { + if (!this.data.stateGraph) { + // #ifdef DEBUG + console.error('assignAnimation: Trying to assign an anim track before the state graph has been loaded. Have you called loadStateGraph?'); + // #endif + return; + } layerName = layerName || 'DEFAULT_LAYER'; var layer = this.findAnimationLayer(layerName); if (!layer) { // #ifdef DEBUG - console.error('assignAnimation: Trying to assign an anim track before the state graph has been loaded. Have you called loadStateGraph?'); + console.error('assignAnimation: Trying to assign an anim track to a layer that doesn\'t exist'); // #endif return; } - layer.assignAnimation(stateName, animTrack); + layer.assignAnimation(nodeName, animTrack); }, /** @@ -131,10 +137,10 @@ Object.assign(pc, function () { * @function * @name pc.AnimComponent#removeStateAnimations * @description Removes animations from a state in the loaded state graph. - * @param {string} stateName - The name of the state that should have its animation tracks removed. + * @param {string} nodeName - The name of the state node that should have its animation tracks removed. * @param {string?} layerName - The name of the anim component layer to update. If omitted the default layer is used. */ - removeStateAnimations: function (stateName, layerName) { + removeNodeAnimations: function (nodeName, layerName) { layerName = layerName || 'DEFAULT_LAYER'; var layer = this.findAnimationLayer(layerName); if (!layer) { @@ -143,7 +149,7 @@ Object.assign(pc, function () { // #endif return; } - layer.removeStateAnimations(stateName); + layer.removeNodeAnimations(nodeName); }, getParameterValue: function (name, type) { @@ -213,7 +219,7 @@ Object.assign(pc, function () { */ setInteger: function (name, value) { if (typeof value === 'number' && value % 1 === 0) { - this.setParameterValue(name, pc.ANIM_PARAMETER_INTEGER, Math.floor(value)); + this.setParameterValue(name, pc.ANIM_PARAMETER_INTEGER, value); } else { // #ifdef DEBUG console.error('Attempting to assign non integer value to integer parameter'); diff --git a/src/framework/components/anim/constants.js b/src/framework/components/anim/constants.js new file mode 100644 index 00000000000..1bc7aec5bae --- /dev/null +++ b/src/framework/components/anim/constants.js @@ -0,0 +1,22 @@ +Object.assign(pc, { + ANIM_INTERRUPTION_NONE: 'NONE', + ANIM_INTERRUPTION_PREV: 'PREV_STATE', + ANIM_INTERRUPTION_NEXT: 'NEXT_STATE', + ANIM_INTERRUPTION_PREV_NEXT: 'PREV_STATE_NEXT_STATE', + ANIM_INTERRUPTION_NEXT_PREV: 'NEXT_STATE_PREV_STATE', + + ANIM_GREATER_THAN: 'GREATER_THAN', + ANIM_LESS_THAN: 'LESS_THAN', + ANIM_GREATER_THAN_EQUAL_TO: 'GREATER_THAN_EQUAL_TO', + ANIM_LESS_THAN_EQUAL_TO: 'LESS_THAN_EQUAL_TO', + ANIM_EQUAL_TO: 'EQUAL_TO', + ANIM_NOT_EQUAL_TO: 'NOT_EQUAL_TO', + + ANIM_PARAMETER_INTEGER: 'INTEGER', + ANIM_PARAMETER_FLOAT: 'FLOAT', + ANIM_PARAMETER_BOOLEAN: 'BOOLEAN', + ANIM_PARAMETER_TRIGGER: 'TRIGGER', + + ANIM_STATE_START: 'START', + ANIM_STATE_END: 'END' +}); diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 13928192a72..e44676f54b1 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -1,26 +1,5 @@ Object.assign(pc, function () { - var ANIM_INTERRUPTION_NONE = 'NONE'; - var ANIM_INTERRUPTION_PREV = 'PREV_STATE'; - var ANIM_INTERRUPTION_NEXT = 'NEXT_STATE'; - var ANIM_INTERRUPTION_PREV_NEXT = 'PREV_STATE_NEXT_STATE'; - var ANIM_INTERRUPTION_NEXT_PREV = 'NEXT_STATE_PREV_STATE'; - - var ANIM_GREATER_THAN = 'GREATER_THAN'; - var ANIM_LESS_THAN = 'LESS_THAN'; - var ANIM_GREATER_THAN_EQUAL_TO = 'GREATER_THAN_EQUAL_TO'; - var ANIM_LESS_THAN_EQUAL_TO = 'LESS_THAN_EQUAL_TO'; - var ANIM_EQUAL_TO = 'EQUAL_TO'; - var ANIM_NOT_EQUAL_TO = 'NOT_EQUAL_TO'; - - var ANIM_PARAMETER_INTEGER = 'INTEGER'; - var ANIM_PARAMETER_FLOAT = 'FLOAT'; - var ANIM_PARAMETER_BOOLEAN = 'BOOLEAN'; - var ANIM_PARAMETER_TRIGGER = 'TRIGGER'; - - var ANIM_STATE_START = 'START'; - var ANIM_STATE_END = 'END'; - var AnimState = function (controller, name, speed) { this._controller = controller; this._name = name; @@ -46,7 +25,7 @@ Object.assign(pc, function () { }, playable: { get: function () { - return (this.animations.length > 0 || this.name === ANIM_STATE_START || this.name === ANIM_STATE_END); + return (this.animations.length > 0 || this.name === pc.ANIM_STATE_START || this.name === pc.ANIM_STATE_END); } }, looping: { @@ -95,7 +74,7 @@ Object.assign(pc, function () { this._conditions = conditions || []; this._exitTime = exitTime || null; this._transitionOffset = transitionOffset || null; - this._interruptionSource = interruptionSource || ANIM_INTERRUPTION_NONE; + this._interruptionSource = interruptionSource || pc.ANIM_INTERRUPTION_NONE; }; Object.defineProperties(AnimTransition.prototype, { @@ -152,22 +131,22 @@ Object.assign(pc, function () { var condition = this.conditions[i]; var parameter = this._controller.findParameter(condition.parameterName); switch (condition.predicate) { - case ANIM_GREATER_THAN: + case pc.ANIM_GREATER_THAN: conditionsMet = conditionsMet && parameter.value > condition.value; break; - case ANIM_LESS_THAN: + case pc.ANIM_LESS_THAN: conditionsMet = conditionsMet && parameter.value < condition.value; break; - case ANIM_GREATER_THAN_EQUAL_TO: + case pc.ANIM_GREATER_THAN_EQUAL_TO: conditionsMet = conditionsMet && parameter.value >= condition.value; break; - case ANIM_LESS_THAN_EQUAL_TO: + case pc.ANIM_LESS_THAN_EQUAL_TO: conditionsMet = conditionsMet && parameter.value <= condition.value; break; - case ANIM_EQUAL_TO: + case pc.ANIM_EQUAL_TO: conditionsMet = conditionsMet && parameter.value === condition.value; break; - case ANIM_NOT_EQUAL_TO: + case pc.ANIM_NOT_EQUAL_TO: conditionsMet = conditionsMet && parameter.value !== condition.value; break; } @@ -209,14 +188,14 @@ Object.assign(pc, function () { this._findTransitionsBetweenStatesCache = {}; this._parameters = parameters; this._previousStateName = null; - this._activeStateName = ANIM_STATE_START; + this._activeStateName = pc.ANIM_STATE_START; this._playing = false; this._activate = activate; this._currTransitionTime = 1.0; this._totalTransitionTime = 1.0; this._isTransitioning = false; - this._transitionInterruptionSource = ANIM_INTERRUPTION_NONE; + this._transitionInterruptionSource = pc.ANIM_INTERRUPTION_NONE; this._transitionPreviousStates = []; this._timeInState = 0; @@ -296,7 +275,7 @@ Object.assign(pc, function () { }, _getActiveStateProgressForTime: function (time) { - if (this.activeStateName === ANIM_STATE_START || this.activeStateName === ANIM_STATE_END) + if (this.activeStateName === pc.ANIM_STATE_START || this.activeStateName === pc.ANIM_STATE_END) return 1.0; var activeClip = this._animEvaluator.findClip(this.activeState.animations[0].name); @@ -355,21 +334,21 @@ Object.assign(pc, function () { transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName)); } else { switch (this._transitionInterruptionSource) { - case ANIM_INTERRUPTION_PREV: + case pc.ANIM_INTERRUPTION_PREV: transitions = transitions.concat(this._findTransitionsFromState(this._previousStateName)); break; - case ANIM_INTERRUPTION_NEXT: + case pc.ANIM_INTERRUPTION_NEXT: transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName)); break; - case ANIM_INTERRUPTION_PREV_NEXT: + case pc.ANIM_INTERRUPTION_PREV_NEXT: transitions = transitions.concat(this._findTransitionsFromState(this._previousStateName)); transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName)); break; - case ANIM_INTERRUPTION_NEXT_PREV: + case pc.ANIM_INTERRUPTION_NEXT_PREV: transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName)); transitions = transitions.concat(this._findTransitionsFromState(this._previousStateName)); break; - case ANIM_INTERRUPTION_NONE: + case pc.ANIM_INTERRUPTION_NONE: default: } } @@ -417,12 +396,12 @@ Object.assign(pc, function () { this.activeState = transition.to; // turn off any triggers which were required to activate this transition - var triggers = transition.conditions.filter(function (condition) { + for (i = 0; i < transition.conditions.length; i++) { + var condition = transition.conditions[i]; var parameter = this.findParameter(condition.parameterName); - return parameter.type === ANIM_PARAMETER_TRIGGER; - }.bind(this)); - for (i = 0; i < triggers.length; i++) { - this.findParameter(triggers[i].parameterName).value = false; + if (parameter.type === pc.ANIM_PARAMETER_TRIGGER) { + parameter.value = false; + } } if (this.previousState) { @@ -576,7 +555,7 @@ Object.assign(pc, function () { reset: function () { this._previousStateName = null; - this._activeStateName = ANIM_STATE_START; + this._activeStateName = pc.ANIM_STATE_START; this._playing = false; this._currTransitionTime = 1.0; this._totalTransitionTime = 1.0; @@ -647,27 +626,6 @@ Object.assign(pc, function () { }); return { - ANIM_INTERRUPTION_NONE: ANIM_INTERRUPTION_NONE, - ANIM_INTERRUPTION_PREV: ANIM_INTERRUPTION_PREV, - ANIM_INTERRUPTION_NEXT: ANIM_INTERRUPTION_NEXT, - ANIM_INTERRUPTION_PREV_NEXT: ANIM_INTERRUPTION_PREV_NEXT, - ANIM_INTERRUPTION_NEXT_PREV: ANIM_INTERRUPTION_NEXT_PREV, - - ANIM_GREATER_THAN: ANIM_GREATER_THAN, - ANIM_LESS_THAN: ANIM_LESS_THAN, - ANIM_GREATER_THAN_EQUAL_TO: ANIM_GREATER_THAN_EQUAL_TO, - ANIM_LESS_THAN_EQUAL_TO: ANIM_LESS_THAN_EQUAL_TO, - ANIM_EQUAL_TO: ANIM_EQUAL_TO, - ANIM_NOT_EQUAL_TO: ANIM_NOT_EQUAL_TO, - - ANIM_PARAMETER_INTEGER: ANIM_PARAMETER_INTEGER, - ANIM_PARAMETER_FLOAT: ANIM_PARAMETER_FLOAT, - ANIM_PARAMETER_BOOLEAN: ANIM_PARAMETER_BOOLEAN, - ANIM_PARAMETER_TRIGGER: ANIM_PARAMETER_TRIGGER, - - ANIM_STATE_START: ANIM_STATE_START, - ANIM_STATE_END: ANIM_STATE_END, - AnimController: AnimController }; }()); From 855ba304bc7dd31bb69ffc48c6d894a96332f234 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Tue, 2 Jun 2020 17:27:29 +0100 Subject: [PATCH 083/144] test fix --- src/anim/anim.js | 4 ++-- tests/anim/test_anim.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/anim/anim.js b/src/anim/anim.js index 80a7c7e83bb..4ec34662743 100644 --- a/src/anim/anim.js +++ b/src/anim/anim.js @@ -784,7 +784,7 @@ Object.assign(pc, function () { * @private * @class * @name pc.AnimEvaluator - * @classdesc AnimContoller blends multiple sets of animation clips together. + * @classdesc AnimEvaluator blends multiple sets of animation clips together. * @description Create a new animation evaluator. * @param {pc.AnimBinder} binder - interface resolves curve paths to instances of {@link pc.AnimTarget}. * @property {pc.AnimClip[]} clips - the list of animation clips @@ -1028,7 +1028,7 @@ Object.assign(pc, function () { * @private * @function * @name pc.AnimEvaluator#update - * @description Contoller frame update function. All the attached {@link pc.AnimClip}s are evaluated, + * @description Evaluator frame update function. All the attached {@link pc.AnimClip}s are evaluated, * blended and the results set on the {@link pc.AnimTarget}. * @param {number} deltaTime - the amount of time that has passed since the last update, in seconds. */ diff --git a/tests/anim/test_anim.js b/tests/anim/test_anim.js index caa5197c0ad..dd289af92ff 100644 --- a/tests/anim/test_anim.js +++ b/tests/anim/test_anim.js @@ -4,7 +4,7 @@ describe("pc.AnimEvaluator", function () { // create curve var keys = new pc.AnimData(1, [0, 1, 2]); var translations = new pc.AnimData(3, [0, 0, 0, 1, 0, 0, 1, 0, 1]); - var curve = new pc.AnimCurve(["child1/graph/translation"], 0, 0, pc.INTERPOLATION_LINEAR); + var curve = new pc.AnimCurve(["child1/graph/localPosition"], 0, 0, pc.INTERPOLATION_LINEAR); // construct the animation track var track = new pc.AnimTrack("test track", 2, [keys], [translations], [curve]); From 3d16a3a980473789036ce7c213342887c6db636e Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Tue, 2 Jun 2020 17:53:06 +0100 Subject: [PATCH 084/144] find enities using the findByPath function in the anim binder --- src/framework/components/anim/binder.js | 43 ++++++++++++++++--------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/src/framework/components/anim/binder.js b/src/framework/components/anim/binder.js index 8886c0e969d..7a281fe5aff 100644 --- a/src/framework/components/anim/binder.js +++ b/src/framework/components/anim/binder.js @@ -55,25 +55,38 @@ Object.assign(pc, function () { }, _getEntityFromHierarchy: function (entityHierarchy) { - if (!this.animComponent.entity.name === entityHierarchy[0]) + if (!this.animComponent.entity.name === entityHierarchy[0]) { return null; + } var currEntity = this.animComponent.entity; - for (var i = 0; i < entityHierarchy.length - 1; i++) { - var entityChildren = currEntity.getChildren(); - var child; - for (var j = 0; j < entityChildren.length; j++) { - if (entityChildren[j].name === entityHierarchy[i + 1]) { - child = entityChildren[j]; - break; - } - } - if (child) - currEntity = child; - else - return null; + + if (entityHierarchy.length === 1) { + return currEntity; } - return currEntity; + return currEntity._parent.findByPath(entityHierarchy.join('/')); + + // if (entity) + // var nodeEntity = currEntity.findByPath(entityHierarchy.join('/')); + // if (!nodeEntity) { + // nodeEntity = currEntity.findByName(entityHierarchy.join('/')); + // } + // // return nodeEntity; + // for (var i = 0; i < entityHierarchy.length - 1; i++) { + // var entityChildren = currEntity.getChildren(); + // var child; + // for (var j = 0; j < entityChildren.length; j++) { + // if (entityChildren[j].name === entityHierarchy[i + 1]) { + // child = entityChildren[j]; + // break; + // } + // } + // if (child) + // currEntity = child; + // else + // return null; + // } + // return currEntity; }, _floatSetter: function (propertyComponent, propertyHierarchy) { From becf669db05a20a6300947bead4f5b2a8337c105 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Tue, 2 Jun 2020 17:54:49 +0100 Subject: [PATCH 085/144] handle null case for anim findAnimationLayer --- src/framework/components/anim/component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index f93494042a9..492ff7dc059 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -102,7 +102,7 @@ Object.assign(pc, function () { */ findAnimationLayer: function (layerName) { var layerIndex = this.data.layerIndicies[layerName]; - return this.data.layers[layerIndex]; + return this.data.layers[layerIndex] || null; }, /** From 5e832d5c55aa018864ea276d778b534f46461ef9 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Tue, 2 Jun 2020 18:05:18 +0100 Subject: [PATCH 086/144] updated the assignAnimation function --- src/framework/components/anim/component.js | 88 +++++++++++++--------- src/framework/components/anim/layer.js | 9 +-- 2 files changed, 55 insertions(+), 42 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 492ff7dc059..7ee356b58c3 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -286,46 +286,64 @@ Object.assign(pc, function () { } }); - /** - * @private - * @name pc.AnimComponent#stateGraphAsset - * @type {string} - * @description The state graph asset this component should use to generate it's animation state graph - */ - Object.defineProperty(AnimComponent.prototype, "stateGraphAsset", { - get: function () { - return this.data.stateGraphAsset; - }, - - set: function (value) { - var _id; - var _asset; + Object.defineProperties(AnimComponent.prototype, { + /** + * @private + * @name pc.AnimComponent#stateGraphAsset + * @type {number} + * @description The state graph asset this component should use to generate it's animation state graph + */ + stateGraphAsset: { + get: function () { + return this.data.stateGraphAsset; + }, + set: function (value) { + var _id; + var _asset; - if (value instanceof pc.Asset) { - _id = value.id; - _asset = this.system.app.assets.get(_id); - if (!_asset) { - this.system.app.assets.add(value); + if (value instanceof pc.Asset) { + _id = value.id; + _asset = this.system.app.assets.get(_id); + if (!_asset) { + this.system.app.assets.add(value); + _asset = this.system.app.assets.get(_id); + } + } else { + _id = value; _asset = this.system.app.assets.get(_id); } - } else { - _id = value; - _asset = this.system.app.assets.get(_id); - } - if (!_asset || this.data.stateGraphAsset === _id) { - return; - } + if (!_asset || this.data.stateGraphAsset === _id) { + return; + } - if (_asset.resource) { - this.data.stateGraph = _asset.resource; - } else { - asset.on('load', function (asset) { - this.data.stateGraph = asset.resource; - this.loadStateGraph(this.data.stateGraph); - }.bind(this)); - this.system.app.assets.load(asset); + if (_asset.resource) { + this.data.stateGraph = _asset.resource; + } else { + asset.on('load', function (asset) { + this.data.stateGraph = asset.resource; + this.loadStateGraph(this.data.stateGraph); + }.bind(this)); + this.system.app.assets.load(asset); + } + this.data.stateGraphAsset = _id; + } + }, + /** + * @private + * @name pc.AnimComponent#playable + * @type {boolean} + * @readonly + * @description Returns whether all component layers are currently playable + */ + playable: { + get: function () { + for (var i = 0; i < this.data.layers.length; i++) { + if (!this.data.layers[i].playable) { + return false; + } + } + return true; } - this.data.stateGraphAsset = _id; } }); diff --git a/src/framework/components/anim/layer.js b/src/framework/components/anim/layer.js index ba4c32c9042..be7678ecf86 100644 --- a/src/framework/components/anim/layer.js +++ b/src/framework/components/anim/layer.js @@ -63,13 +63,8 @@ Object.assign(pc, function () { assignAnimation: function (nodeName, animTrack) { this._controller.assignAnimation(nodeName, animTrack); - if (this._component.activate) { - for (var i = 0; i < this._component.data.layers.length; i++) { - if (!this._component.data.layers[i].playable) { - return; - } - this._component.playing = true; - } + if (this._component.activate && this._component.playable) { + this._component.playing = true; } }, From 915b70d716912527f33448d57704876f36499e8f Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Wed, 3 Jun 2020 09:55:42 +0100 Subject: [PATCH 087/144] pro comments --- src/framework/components/anim/binder.js | 22 ---------------------- src/framework/components/anim/component.js | 6 +++--- src/framework/components/anim/data.js | 2 +- 3 files changed, 4 insertions(+), 26 deletions(-) diff --git a/src/framework/components/anim/binder.js b/src/framework/components/anim/binder.js index 7a281fe5aff..55126a900dd 100644 --- a/src/framework/components/anim/binder.js +++ b/src/framework/components/anim/binder.js @@ -65,28 +65,6 @@ Object.assign(pc, function () { return currEntity; } return currEntity._parent.findByPath(entityHierarchy.join('/')); - - // if (entity) - // var nodeEntity = currEntity.findByPath(entityHierarchy.join('/')); - // if (!nodeEntity) { - // nodeEntity = currEntity.findByName(entityHierarchy.join('/')); - // } - // // return nodeEntity; - // for (var i = 0; i < entityHierarchy.length - 1; i++) { - // var entityChildren = currEntity.getChildren(); - // var child; - // for (var j = 0; j < entityChildren.length; j++) { - // if (entityChildren[j].name === entityHierarchy[i + 1]) { - // child = entityChildren[j]; - // break; - // } - // } - // if (child) - // currEntity = child; - // else - // return null; - // } - // return currEntity; }, _floatSetter: function (propertyComponent, propertyHierarchy) { diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 7ee356b58c3..07290b75cfa 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -54,7 +54,7 @@ Object.assign(pc, function () { data.activate ); data.layers.push(new pc.AnimComponentLayer(name, controller, this)); - data.layerIndicies[name] = order; + data.layerIndices[name] = order; } for (var i = 0; i < stateGraph.layers.length; i++) { @@ -72,7 +72,7 @@ Object.assign(pc, function () { removeStateGraph: function () { this.data.stateGraph = null; this.data.layers = []; - this.data.layerIndicies = {}; + this.data.layerIndices = {}; this.data.parameters = {}; this.data.playing = false; }, @@ -101,7 +101,7 @@ Object.assign(pc, function () { * @returns {pc.AnimComponentLayer} layer */ findAnimationLayer: function (layerName) { - var layerIndex = this.data.layerIndicies[layerName]; + var layerIndex = this.data.layerIndices[layerName]; return this.data.layers[layerIndex] || null; }, diff --git a/src/framework/components/anim/data.js b/src/framework/components/anim/data.js index 16b4351a8b0..f8a369cc7c9 100644 --- a/src/framework/components/anim/data.js +++ b/src/framework/components/anim/data.js @@ -10,7 +10,7 @@ Object.assign(pc, function () { // Non-serialized this.stateGraph = null; this.layers = []; - this.layerIndicies = {}; + this.layerIndices = {}; this.parameters = {}; }; From adcf1da65f4e712b4fe7d76810d6e8eadc66c8d8 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Wed, 3 Jun 2020 10:46:09 +0100 Subject: [PATCH 088/144] update anim resource names --- build/dependencies.txt | 4 ++-- src/framework/application.js | 4 ++-- src/resources/{animation-clip.js => anim-clip.js} | 10 +++++----- .../{animation-state-graph.js => anim-state-graph.js} | 10 +++++----- 4 files changed, 14 insertions(+), 14 deletions(-) rename src/resources/{animation-clip.js => anim-clip.js} (90%) rename src/resources/{animation-state-graph.js => anim-state-graph.js} (82%) diff --git a/build/dependencies.txt b/build/dependencies.txt index e72c7d50256..d0a9f6491d0 100644 --- a/build/dependencies.txt +++ b/build/dependencies.txt @@ -237,8 +237,8 @@ ../src/resources/bundle.js ../src/resources/untar.js ../src/resources/animation.js -../src/resources/animation-clip.js -../src/resources/animation-state-graph.js +../src/resources/anim-clip.js +../src/resources/anim-state-graph.js ../src/resources/audio.js ../src/resources/cubemap.js ../src/resources/json.js diff --git a/src/framework/application.js b/src/framework/application.js index 415ea019de0..a997ae99fab 100644 --- a/src/framework/application.js +++ b/src/framework/application.js @@ -581,8 +581,8 @@ Object.assign(pc, function () { } this.loader.addHandler("animation", new pc.AnimationHandler()); - this.loader.addHandler("animationclip", new pc.AnimationClipHandler()); - this.loader.addHandler("animationstategraph", new pc.AnimationStateGraphHandler()); + this.loader.addHandler("animclip", new pc.AnimClipHandler()); + this.loader.addHandler("animstategraph", new pc.AnimStateGraphHandler()); this.loader.addHandler("model", new pc.ModelHandler(this.graphicsDevice, this.scene.defaultMaterial)); this.loader.addHandler("material", new pc.MaterialHandler(this)); this.loader.addHandler("texture", new pc.TextureHandler(this.graphicsDevice, this.assets, this.loader)); diff --git a/src/resources/animation-clip.js b/src/resources/anim-clip.js similarity index 90% rename from src/resources/animation-clip.js rename to src/resources/anim-clip.js index 1ef874b15e8..3dc1bd8073f 100644 --- a/src/resources/animation-clip.js +++ b/src/resources/anim-clip.js @@ -4,15 +4,15 @@ Object.assign(pc, function () { /** * @private * @class - * @name pc.AnimationClipHandler + * @name pc.AnimClipHandler * @implements {pc.ResourceHandler} - * @classdesc Resource handler used for loading {@link pc.AnimationClip} resources. + * @classdesc Resource handler used for loading {@link pc.AnimClip} resources. */ - var AnimationClipHandler = function () { + var AnimClipHandler = function () { this.retryRequests = false; }; - Object.assign(AnimationClipHandler.prototype, { + Object.assign(AnimClipHandler.prototype, { load: function (url, callback) { if (typeof url === 'string') { url = { @@ -67,6 +67,6 @@ Object.assign(pc, function () { }); return { - AnimationClipHandler: AnimationClipHandler + AnimClipHandler: AnimClipHandler }; }()); diff --git a/src/resources/animation-state-graph.js b/src/resources/anim-state-graph.js similarity index 82% rename from src/resources/animation-state-graph.js rename to src/resources/anim-state-graph.js index dc7ff4fa724..12e938c51d6 100644 --- a/src/resources/animation-state-graph.js +++ b/src/resources/anim-state-graph.js @@ -4,15 +4,15 @@ Object.assign(pc, function () { /** * @private * @class - * @name pc.AnimationStateGraphHandler + * @name pc.AnimStateGraphHandler * @implements {pc.ResourceHandler} - * @classdesc Resource handler used for loading {@link pc.AnimationStateGraph} resources. + * @classdesc Resource handler used for loading {@link pc.AnimStateGraph} resources. */ - var AnimationStateGraphHandler = function () { + var AnimStateGraphHandler = function () { this.retryRequests = false; }; - Object.assign(AnimationStateGraphHandler.prototype, { + Object.assign(AnimStateGraphHandler.prototype, { load: function (url, callback) { if (typeof url === 'string') { url = { @@ -45,6 +45,6 @@ Object.assign(pc, function () { }); return { - AnimationStateGraphHandler: AnimationStateGraphHandler + AnimStateGraphHandler: AnimStateGraphHandler }; }()); From 451dcb91c4ee0c322e1bf020cec887312e90602e Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Thu, 4 Jun 2020 17:38:48 +0100 Subject: [PATCH 089/144] flag animation udpates to any materials --- src/framework/components/anim/binder.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/framework/components/anim/binder.js b/src/framework/components/anim/binder.js index 55126a900dd..61df2839a0d 100644 --- a/src/framework/components/anim/binder.js +++ b/src/framework/components/anim/binder.js @@ -215,7 +215,13 @@ Object.assign(pc, function () { propertyComponent[entityPropertySetterFunctionName](this._getProperty(propertyComponent, [entityProperty])); }; return new pc.AnimTarget(entityPropertySetter.bind(this), animDataType, animDataComponents); + } else if (propertyHierarchy.indexOf('material') !== -1) { + return new pc.AnimTarget(function (values) { + setter(values); + propertyComponent.material.dirty = true; + }, animDataType, animDataComponents); } + return new pc.AnimTarget(setter, animDataType, animDataComponents); } From ec5b99cceb6df0fa301c158201d0d6308265f2eb Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Thu, 4 Jun 2020 17:39:42 +0100 Subject: [PATCH 090/144] fix the anim controllers playable variable --- src/framework/components/anim/controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index e44676f54b1..80ca98cab6c 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -238,8 +238,8 @@ Object.assign(pc, function () { get: function () { var playable = true; var i; - for (i = 0; i < this._states.length; i++) { - if (!this._states[i].playable) { + for (i = 0; i < this.states.length; i++) { + if (!this._states[this.states[i]].playable) { playable = false; } } From e879b7388712700ac04315f7ea8d344709d524f1 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Thu, 4 Jun 2020 17:42:39 +0100 Subject: [PATCH 091/144] add a new baseLayer variable to the anim component and fix the stateGraphAsset variable --- src/framework/components/anim/component.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 07290b75cfa..511ee3b223e 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -318,8 +318,9 @@ Object.assign(pc, function () { if (_asset.resource) { this.data.stateGraph = _asset.resource; + this.loadStateGraph(this.data.stateGraph); } else { - asset.on('load', function (asset) { + _asset.on('load', function (asset) { this.data.stateGraph = asset.resource; this.loadStateGraph(this.data.stateGraph); }.bind(this)); @@ -344,6 +345,21 @@ Object.assign(pc, function () { } return true; } + }, + /** + * @private + * @name pc.AnimComponent#baseLayer + * @type {pc.AnimComponentLayer} + * @readonly + * @description Returns the base layer of the state graph + */ + baseLayer: { + get: function () { + if (this.data.layers.length > 0) { + return this.data.layers[0]; + } + return null; + } } }); From 60cd93c224c26acad877d4b1c632e3b73f35c621 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Thu, 4 Jun 2020 17:54:45 +0100 Subject: [PATCH 092/144] fix merge issue --- src/framework/components/anim/component.js | 8 -------- src/framework/components/anim/controller.js | 5 ----- 2 files changed, 13 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 4d0675653cf..511ee3b223e 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -318,14 +318,9 @@ Object.assign(pc, function () { if (_asset.resource) { this.data.stateGraph = _asset.resource; -<<<<<<< HEAD this.loadStateGraph(this.data.stateGraph); } else { _asset.on('load', function (asset) { -======= - } else { - asset.on('load', function (asset) { ->>>>>>> master this.data.stateGraph = asset.resource; this.loadStateGraph(this.data.stateGraph); }.bind(this)); @@ -350,7 +345,6 @@ Object.assign(pc, function () { } return true; } -<<<<<<< HEAD }, /** * @private @@ -366,8 +360,6 @@ Object.assign(pc, function () { } return null; } -======= ->>>>>>> master } }); diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 683685b3d9d..80ca98cab6c 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -238,13 +238,8 @@ Object.assign(pc, function () { get: function () { var playable = true; var i; -<<<<<<< HEAD for (i = 0; i < this.states.length; i++) { if (!this._states[this.states[i]].playable) { -======= - for (i = 0; i < this._states.length; i++) { - if (!this._states[i].playable) { ->>>>>>> master playable = false; } } From 99f9c8b59405c32857e725b875bd2a13188b40e3 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Fri, 5 Jun 2020 09:50:39 +0100 Subject: [PATCH 093/144] change how the anim binder flags materials --- src/framework/components/anim/binder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/framework/components/anim/binder.js b/src/framework/components/anim/binder.js index 61df2839a0d..281753d4f38 100644 --- a/src/framework/components/anim/binder.js +++ b/src/framework/components/anim/binder.js @@ -218,7 +218,7 @@ Object.assign(pc, function () { } else if (propertyHierarchy.indexOf('material') !== -1) { return new pc.AnimTarget(function (values) { setter(values); - propertyComponent.material.dirty = true; + propertyComponent.material.update(); }, animDataType, animDataComponents); } From 0b35179edbfd217fe23a19ee48b5897c0d6976e4 Mon Sep 17 00:00:00 2001 From: Elliott Thompson Date: Fri, 5 Jun 2020 11:50:31 +0100 Subject: [PATCH 094/144] anim controller syntax change --- src/framework/components/anim/controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 80ca98cab6c..98540205e8b 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -238,8 +238,8 @@ Object.assign(pc, function () { get: function () { var playable = true; var i; - for (i = 0; i < this.states.length; i++) { - if (!this._states[this.states[i]].playable) { + for (i = 0; i < this._stateNames.length; i++) { + if (!this._states[this._stateNames[i]].playable) { playable = false; } } From 0c19f103fc182a606a1fb54387d679ed146fb963 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 5 Jun 2020 15:22:52 +0200 Subject: [PATCH 095/144] fix(ContainerHandler): fix issue with baseUrl introduced in merge url.original now only contains file name which means we can't extract the base path from it. Use url.load instead. --- src/resources/container.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/container.js b/src/resources/container.js index 399353e0a78..9ea80139f61 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -117,7 +117,7 @@ Object.assign(pc, function () { if (!err) { pc.GlbParser.parseAsync(self._getUrlWithoutParams(url.original), - pc.path.extractPath(url.original), + pc.path.extractPath(url.load), response, self._device, asset.registry, From 13b534061203964dd68785a8fdfa0a7262c5b90a Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 5 Jun 2020 16:42:14 +0200 Subject: [PATCH 096/144] fix(GlbParser): prevent double asset prefixes in texture urls Remove asset prefix from urlBase when loading textures since it is automatically prepended when using pc.Asset --- src/resources/parser/glb-parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 4d947c153c6..adf8b1e62fe 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1423,7 +1423,7 @@ Object.assign(pc, function () { url = imgData.uri; mimeType = getDataURIMimeType(imgData.uri); } else { - url = pc.path.join(urlBase, imgData.uri); + url = pc.path.join(urlBase, imgData.uri).replace(registry.prefix, ""); options.crossOrigin = "anonymous"; } } else if (imgData.hasOwnProperty('bufferView') && imgData.hasOwnProperty('mimeType')) { From 506f436a7a232bb54abb2b4308d5de7171bcd52c Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 5 Jun 2020 16:42:54 +0200 Subject: [PATCH 097/144] fix(ContainerHandler): fix scenes unloading --- src/resources/container.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/resources/container.js b/src/resources/container.js index 9ea80139f61..e0c17867d68 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -50,6 +50,7 @@ Object.assign(pc, function () { this.scenes.forEach(function (scene) { scene.destroy(); }); + this.scenes = null; } if (this.animations) { From c5c852417fbe0073195d78afceb1adcec161510f Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 5 Jun 2020 17:25:48 +0200 Subject: [PATCH 098/144] fix(GlbParser): fix so that slashes in node names don't interfere with animation curve paths --- src/resources/parser/glb-parser.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index adf8b1e62fe..ba0bb11e91d 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1097,7 +1097,8 @@ Object.assign(pc, function () { var entity = new pc.Entity(); if (nodeData.hasOwnProperty('name')) { - entity.name = nodeData.name; + // Remove slashes since they interfere with animation curve paths + entity.name = nodeData.name.replace(/\//g, '_'); } else { entity.name = "node_" + nodeIndex; } From 3a964244b595fe1f18806ac0be9f024fd6129528 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 5 Jun 2020 17:32:45 +0200 Subject: [PATCH 099/144] ci: fix ci error --- src/framework/components/anim/system.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/framework/components/anim/system.js b/src/framework/components/anim/system.js index 9f5e6fd17ad..03560668ff9 100644 --- a/src/framework/components/anim/system.js +++ b/src/framework/components/anim/system.js @@ -7,7 +7,6 @@ Object.assign(pc, function () { ]; /** - * @private * @class * @name pc.AnimComponentSystem * @augments pc.ComponentSystem From eac2d5673bdf1a4e8814b2dd3f5ab6874bf578cb Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 5 Jun 2020 17:47:07 +0200 Subject: [PATCH 100/144] fix: fix lint errors --- src/framework/components/anim/component.js | 2 +- src/framework/components/anim/data.js | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 1e1bf3d0515..6c57eb8d2c2 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -11,7 +11,7 @@ Object.assign(pc, function () { * @param {pc.Entity} entity - The Entity that this Component is attached to. * @property {number} speed Speed multiplier for animation play back speed. 1.0 is playback at normal speed, 0.0 pauses the animation. * @property {boolean} activate If true the first animation will begin playing when the scene is loaded. - * @property {pc.AnimComponentData} data + * @property {pc.AnimComponentData} data Animation component data */ var AnimComponent = function (system, entity) { pc.Component.call(this, system, entity); diff --git a/src/framework/components/anim/data.js b/src/framework/components/anim/data.js index a1d5fc2bce2..e782b3df697 100644 --- a/src/framework/components/anim/data.js +++ b/src/framework/components/anim/data.js @@ -2,13 +2,13 @@ Object.assign(pc, function () { /** * @class * @name pc.AnimComponentData - * @property {number} speed - * @property {boolean} active - * @property {boolean} enabled - * @property {boolean} playing - * @property {pc.AnimComponentLayer[]} layers - * @property {object|undefined} parameters - * @property {pc.Model|null} model + * @property {number} speed Speed + * @property {boolean} active Active + * @property {boolean} enabled Enabled + * @property {boolean} playing Playing + * @property {pc.AnimComponentLayer[]} layers Layers + * @property {object|undefined} parameters Parameters + * @property {pc.Model|null} model Model */ var AnimComponentData = function () { // Serialized From ccca2ec91e38c6314291a09167faf65ba2c330ea Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Mon, 8 Jun 2020 14:39:00 +0200 Subject: [PATCH 101/144] refactor(GlbParser): change argument order of parseAsync --- src/resources/container.js | 2 +- src/resources/parser/glb-parser.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index e0c17867d68..e10ee77d4ea 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -121,8 +121,8 @@ Object.assign(pc, function () { pc.path.extractPath(url.load), response, self._device, - asset.registry, self._defaultMaterial, + asset.registry, function (err, result) { if (err) { callback(err); diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index ba0bb11e91d..67432d79f4d 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1629,7 +1629,7 @@ Object.assign(pc, function () { var GlbParser = function () { }; // parse the gltf or glb data asynchronously, loading external resources - GlbParser.parseAsync = function (filename, urlBase, data, device, registry, defaultMaterial, callback) { + GlbParser.parseAsync = function (filename, urlBase, data, device, defaultMaterial, registry, callback) { // parse the data parseChunk(filename, data, function (err, chunks) { if (err) { From d0a8988541feb4b72265fdd1088596b919226d26 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Wed, 17 Jun 2020 13:07:31 +0200 Subject: [PATCH 102/144] fix: make AnimTrack public --- src/anim/anim.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/anim/anim.js b/src/anim/anim.js index c41cf323bfd..6bb41e6c08d 100644 --- a/src/anim/anim.js +++ b/src/anim/anim.js @@ -28,7 +28,6 @@ Object.assign(pc, function () { var INTERPOLATION_CUBIC = 2; /** - * @private * @class * @name pc.AnimData * @classdesc Wraps a set of data used in animation. @@ -197,7 +196,6 @@ Object.assign(pc, function () { }); /** - * @private * @class * @name pc.AnimCurve * @classdesc Animation curve links an input data set to an output data set @@ -243,7 +241,6 @@ Object.assign(pc, function () { }); /** - * @private * @class * @name pc.AnimTrack * @classdesc AnimTrack contains a set of curve data which can be used to animate a set of target nodes. From d1678ca9f1ffadf120a299008f3de92b705e205a Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Thu, 18 Jun 2020 10:27:05 +0200 Subject: [PATCH 103/144] fix: typings for AnimTrack.name and AnimComponentLayer.playing --- src/anim/anim.js | 1 + src/framework/components/anim/layer.js | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/anim/anim.js b/src/anim/anim.js index 6bb41e6c08d..53623e8fb01 100644 --- a/src/anim/anim.js +++ b/src/anim/anim.js @@ -250,6 +250,7 @@ Object.assign(pc, function () { * @param {pc.AnimData[]} inputs - list of curve key data. * @param {pc.AnimData[]} outputs - list of curve value data. * @param {pc.AnimCurve[]} curves - the list of curves. + * @property {string} name - the track name */ var AnimTrack = function (name, duration, inputs, outputs, curves) { this._name = name; diff --git a/src/framework/components/anim/layer.js b/src/framework/components/anim/layer.js index c477626fd53..8543d7e945d 100644 --- a/src/framework/components/anim/layer.js +++ b/src/framework/components/anim/layer.js @@ -90,9 +90,8 @@ Object.assign(pc, function () { } }, /** - * @private * @name pc.AnimComponentLayer#playing - * @type {string} + * @type {boolean} * @description Whether this layer is currently playing */ playing: { From deaf615524846fc6ba2212f403f8008cba382973 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Thu, 18 Jun 2020 10:27:34 +0200 Subject: [PATCH 104/144] fix: add implementation for AnimController.playing --- src/framework/components/anim/controller.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 98540205e8b..2ba25b5ec30 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -234,6 +234,11 @@ Object.assign(pc, function () { return this._previousStateName; } }, + playing: { + get: function () { + return this._playing; + } + }, playable: { get: function () { var playable = true; From 32ef6fd7ecb745f8888407e08b29eb3a6efddd78 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Thu, 18 Jun 2020 17:32:23 +0200 Subject: [PATCH 105/144] feat(AnimComponent): make it possible to not loop animations by providing "loop: false" in the state of a state graph --- src/framework/components/anim/controller.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 2ba25b5ec30..a0c0b0fd323 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -1,10 +1,11 @@ Object.assign(pc, function () { - var AnimState = function (controller, name, speed) { + var AnimState = function (controller, name, speed, loop) { this._controller = controller; this._name = name; this._animations = []; this._speed = speed || 1.0; + this._loop = loop === undefined ? true : loop; }; Object.defineProperties(AnimState.prototype, { @@ -28,6 +29,11 @@ Object.assign(pc, function () { return (this.animations.length > 0 || this.name === pc.ANIM_STATE_START || this.name === pc.ANIM_STATE_END); } }, + loop: { + get: function () { + return this._loop; + } + }, looping: { get: function () { if (this.animations.length > 0) { @@ -167,7 +173,8 @@ Object.assign(pc, function () { this._states[states[i].name] = new AnimState( this, states[i].name, - states[i].speed + states[i].speed, + states[i].loop ); this._stateNames.push(states[i].name); } @@ -461,8 +468,9 @@ Object.assign(pc, function () { // Add clips to the evaluator for each animation in the new state. for (i = 0; i < activeState.animations.length; i++) { clip = this._animEvaluator.findClip(activeState.animations[i].name); + var startTime = activeState.speed >= 0 ? 0 : activeState.animations[i].animTrack.duration; if (!clip) { - clip = new pc.AnimClip(activeState.animations[i].animTrack, 0, activeState.speed, true, true); + clip = new pc.AnimClip(activeState.animations[i].animTrack, startTime, activeState.speed, true, activeState.loop); clip.name = activeState.animations[i].name; this._animEvaluator.addClip(clip); } @@ -476,6 +484,7 @@ Object.assign(pc, function () { clip.time = activeState.timelineDuration * transition.transitionOffset; } clip.play(); + clip.time = startTime; } // set the time in the new state to 0 or to a value based on transitionOffset if one was given From 1105243df72eb21c19e7553d23b3a8a9517ea4c1 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Thu, 18 Jun 2020 17:33:20 +0200 Subject: [PATCH 106/144] feat(GlbParser): make it possible to not loop animations (WIP) --- src/resources/container.js | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index e10ee77d4ea..7d461fe4639 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -194,26 +194,31 @@ Object.assign(pc, function () { return { name: animationAssets[animationIndex].resource.name, states: [ - { name: pc.ANIM_STATE_START }, // Needed for pc.AnimComponentLayer not to crash - { name: "ACTIVE" } + { name: pc.ANIM_STATE_START }, + { name: "LOOP", speed: 1, loop: true }, + { name: "LOOP_REVERSE", speed: -1, loop: true }, + { name: "ONCE", speed: 1, loop: false }, + { name: "ONCE_REVERSE", speed: -1, loop: false } ], - transitions: [], - parameters: {} + transitions: [] }; - }) + }), + parameters: {} }); components.animations.forEach(function (animationIndex) { var layer = anim.findAnimationLayer(animationAssets[animationIndex].resource.name); - if (!layer) { - return; + if (layer) { + layer.states.slice(1, layer.states.length).forEach(function (state) { + layer.assignAnimation(state, animationAssets[animationIndex].resource); + }); + + // This is currently the only public method to set the current state of a layer. + // By doing this the animation of a layer can be played by simply running layer.play() + // in an application. + layer.play("LOOP"); + layer.pause(); } - layer.assignAnimation(layer.states[1], animationAssets[animationIndex].resource); - // This is currently the only public method to set the current state of a layer. - // By doing this the animation of a layer can be played by simply running layer.play() - // in an application. - layer.play(layer.states[1]); - layer.pause(); }); } }); From 4a0813780514cacd4dc907fe4d13e67981e65913 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Tue, 23 Jun 2020 13:37:22 +0200 Subject: [PATCH 107/144] feat: make AnimComponent methods and props public --- src/framework/components/anim/component.js | 4 +--- src/framework/components/anim/constants.js | 13 +++++++++++++ src/framework/components/anim/layer.js | 2 -- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 6c57eb8d2c2..8b1ae1f4bed 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -21,7 +21,6 @@ Object.assign(pc, function () { Object.assign(AnimComponent.prototype, { /** - * @private * @function * @name pc.AnimComponent#loadStateGraph * @description Initialises component animation controllers using the provided state graph. @@ -93,12 +92,11 @@ Object.assign(pc, function () { }, /** - * @private * @function * @name pc.AnimComponent#findAnimationLayer * @description Finds a pc.AnimComponentLayer in this component. * @param {string} layerName - The name of the anim component layer to find - * @returns {pc.AnimComponentLayer} layer + * @returns {pc.AnimComponentLayer|null} layer */ findAnimationLayer: function (layerName) { var layerIndex = this.data.layerIndices[layerName]; diff --git a/src/framework/components/anim/constants.js b/src/framework/components/anim/constants.js index 1bc7aec5bae..117dc667ecb 100644 --- a/src/framework/components/anim/constants.js +++ b/src/framework/components/anim/constants.js @@ -17,6 +17,19 @@ Object.assign(pc, { ANIM_PARAMETER_BOOLEAN: 'BOOLEAN', ANIM_PARAMETER_TRIGGER: 'TRIGGER', + /** + * @constant + * @name pc.ANIM_STATE_START + * @type {string} + * @description AnimComponent start state. + */ ANIM_STATE_START: 'START', + + /** + * @constant + * @name pc.ANIM_STATE_START + * @type {string} + * @description AnimComponent end state. + */ ANIM_STATE_END: 'END' }); diff --git a/src/framework/components/anim/layer.js b/src/framework/components/anim/layer.js index 8543d7e945d..4537ef6da37 100644 --- a/src/framework/components/anim/layer.js +++ b/src/framework/components/anim/layer.js @@ -50,7 +50,6 @@ Object.assign(pc, function () { }, /** - * @private * @function * @name pc.AnimComponentLayer#assignAnimation * @description Associates an animation with a state node in the loaded state graph. If all states nodes are linked and the pc.AnimComponent.activate value was set to true then the component will begin playing. @@ -177,7 +176,6 @@ Object.assign(pc, function () { } }, /** - * @private * @readonly * @name pc.AnimComponentLayer#states * @type {string[]} From 86e8d40e4138912aa0349337d730a66989331e92 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Tue, 23 Jun 2020 13:39:28 +0200 Subject: [PATCH 108/144] feat(ContainerResource): return node-animation mapping instead of assigning AnimComponents to nodes in ContainerHandler # Conflicts: # src/resources/container.js --- src/resources/container.js | 70 ++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index 7d461fe4639..88ca337ee82 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -1,5 +1,14 @@ Object.assign(pc, function () { + /** + * Maps an entity to a number of animation assets. Animations can be added to the node with either + * pc.AnimationComponent or pc.AnimComponent. + * + * @typedef {object} pc.ContainerResourceAnimationMapping + * @property {pc.Entity} node Entity that should be animated. + * @property {pc.Asset[]} animations Animations assets to be assigned to node. + */ + /** * @class * @name pc.ContainerResource @@ -12,7 +21,7 @@ Object.assign(pc, function () { * @property {pc.Asset[]} materials Material assets. * @property {pc.Asset[]} textures Texture assets. * @property {pc.Asset[]} animations Animation assets. - * @property {pc.Asset[]} models Model assets. + * @property {pc.ContainerResourceAnimationMapping[]} nodeAnimations Mapping of animations to node entities. * @property {pc.AssetRegistry} registry The asset registry. */ var ContainerResource = function (data) { @@ -22,6 +31,7 @@ Object.assign(pc, function () { this.materials = []; this.textures = []; this.animations = []; + this.nodeAnimations = []; this.models = []; this.registry = null; }; @@ -58,6 +68,10 @@ Object.assign(pc, function () { this.animations = null; } + if (this.nodeAnimations) { + this.nodeAnimations = null; + } + if (this.models) { destroyAssets(this.models); this.models = null; @@ -174,53 +188,28 @@ Object.assign(pc, function () { // texture assets are created in the parser var textureAssets = data.textures; - // add components to nodes + // create mapping from nodes to animations + var nodeAnimations = data.nodes + .map(function (node, nodeIndex) { + return { + node: node, + animations: data.nodeComponents[nodeIndex].animations.map(function (animationIndex) { + return animationAssets[animationIndex]; + }) + }; + }).filter(function (mapping) { + return mapping.animations.length > 0; + }); + + // add model components to nodes data.nodes.forEach(function (node, nodeIndex) { var components = data.nodeComponents[nodeIndex]; - if (components.model !== null) { node.addComponent('model', { type: 'asset', asset: modelAssets[components.model] }); } - - if (components.animations.length > 0) { - var anim = node.addComponent('anim'); - - // Create one layer per animation asset so that the animations can be played simultaneously - anim.loadStateGraph({ - layers: components.animations.map(function (animationIndex) { - return { - name: animationAssets[animationIndex].resource.name, - states: [ - { name: pc.ANIM_STATE_START }, - { name: "LOOP", speed: 1, loop: true }, - { name: "LOOP_REVERSE", speed: -1, loop: true }, - { name: "ONCE", speed: 1, loop: false }, - { name: "ONCE_REVERSE", speed: -1, loop: false } - ], - transitions: [] - }; - }), - parameters: {} - }); - - components.animations.forEach(function (animationIndex) { - var layer = anim.findAnimationLayer(animationAssets[animationIndex].resource.name); - if (layer) { - layer.states.slice(1, layer.states.length).forEach(function (state) { - layer.assignAnimation(state, animationAssets[animationIndex].resource); - }); - - // This is currently the only public method to set the current state of a layer. - // By doing this the animation of a layer can be played by simply running layer.play() - // in an application. - layer.play("LOOP"); - layer.pause(); - } - }); - } }); // since assets are created, release GLB data @@ -232,6 +221,7 @@ Object.assign(pc, function () { container.materials = materialAssets; container.textures = textureAssets; container.animations = animationAssets; + container.nodeAnimations = nodeAnimations; container.models = modelAssets; container.registry = assets; } From 9340980b6948ce9c81ecf44edc21ab2b12ec432e Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Tue, 23 Jun 2020 14:27:22 +0200 Subject: [PATCH 109/144] refactor(GlbParser): remove unnecessary graph node per mesh instance in createModel --- src/resources/parser/glb-parser.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 67432d79f4d..bbd6739397d 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1157,8 +1157,7 @@ Object.assign(pc, function () { meshGroup.forEach(function (mesh) { var material = (mesh.materialIndex === undefined) ? defaultMaterial : materials[mesh.materialIndex]; - var meshNode = new pc.GraphNode(); - var meshInstance = new pc.MeshInstance(meshNode, mesh, material); + var meshInstance = new pc.MeshInstance(model.graph, mesh, material); if (mesh.morph) { var morphInstance = new pc.MorphInstance(mesh.morph); @@ -1182,7 +1181,6 @@ Object.assign(pc, function () { model.skinInstances.push(skinInstance); } - model.graph.addChild(meshNode); model.meshInstances.push(meshInstance); }); From 88d2791b23f928eadd8c9dee3e2621d97386a492 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Wed, 24 Jun 2020 14:56:51 +0200 Subject: [PATCH 110/144] feat(ContainerResource): return animation index instead of asset in nodeAnimations --- src/resources/container.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index 88ca337ee82..4ae2fc8b919 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -6,7 +6,7 @@ Object.assign(pc, function () { * * @typedef {object} pc.ContainerResourceAnimationMapping * @property {pc.Entity} node Entity that should be animated. - * @property {pc.Asset[]} animations Animations assets to be assigned to node. + * @property {number[]} animations Indexes of animations assets to be assigned to node. */ /** @@ -193,9 +193,7 @@ Object.assign(pc, function () { .map(function (node, nodeIndex) { return { node: node, - animations: data.nodeComponents[nodeIndex].animations.map(function (animationIndex) { - return animationAssets[animationIndex]; - }) + animations: data.nodeComponents[nodeIndex].animations }; }).filter(function (mapping) { return mapping.animations.length > 0; From 41fb0bd80e218899ae871de52658d26a33e10292 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Thu, 25 Jun 2020 14:49:41 +0200 Subject: [PATCH 111/144] chore: revert AnimComponent jsdoc This reverts commit 4a0813780514cacd4dc907fe4d13e67981e65913. Revert "fix: add implementation for AnimController.playing" This reverts commit deaf615524846fc6ba2212f403f8008cba382973. Revert "fix: typings for AnimTrack.name and AnimComponentLayer.playing" This reverts commit d1678ca9f1ffadf120a299008f3de92b705e205a. Revert "fix: make AnimTrack public" This reverts commit d0a8988541feb4b72265fdd1088596b919226d26. Revert "fix(AnimComponent): make methods and properties public" This reverts commit 456d9a88bd573be91459398a3b68f0e021707d2f. chore: revert AnimComponent jsdoc --- src/anim/anim.js | 4 +++- src/framework/components/anim/component.js | 6 ++++-- src/framework/components/anim/constants.js | 13 ------------- src/framework/components/anim/controller.js | 5 ----- src/framework/components/anim/data.js | 11 ----------- src/framework/components/anim/layer.js | 10 +++++++++- src/framework/components/anim/system.js | 1 + 7 files changed, 17 insertions(+), 33 deletions(-) diff --git a/src/anim/anim.js b/src/anim/anim.js index 53623e8fb01..c41cf323bfd 100644 --- a/src/anim/anim.js +++ b/src/anim/anim.js @@ -28,6 +28,7 @@ Object.assign(pc, function () { var INTERPOLATION_CUBIC = 2; /** + * @private * @class * @name pc.AnimData * @classdesc Wraps a set of data used in animation. @@ -196,6 +197,7 @@ Object.assign(pc, function () { }); /** + * @private * @class * @name pc.AnimCurve * @classdesc Animation curve links an input data set to an output data set @@ -241,6 +243,7 @@ Object.assign(pc, function () { }); /** + * @private * @class * @name pc.AnimTrack * @classdesc AnimTrack contains a set of curve data which can be used to animate a set of target nodes. @@ -250,7 +253,6 @@ Object.assign(pc, function () { * @param {pc.AnimData[]} inputs - list of curve key data. * @param {pc.AnimData[]} outputs - list of curve value data. * @param {pc.AnimCurve[]} curves - the list of curves. - * @property {string} name - the track name */ var AnimTrack = function (name, duration, inputs, outputs, curves) { this._name = name; diff --git a/src/framework/components/anim/component.js b/src/framework/components/anim/component.js index 8b1ae1f4bed..040d93b0afa 100644 --- a/src/framework/components/anim/component.js +++ b/src/framework/components/anim/component.js @@ -1,6 +1,7 @@ Object.assign(pc, function () { /** + * @private * @component Anim * @class * @name pc.AnimComponent @@ -11,7 +12,6 @@ Object.assign(pc, function () { * @param {pc.Entity} entity - The Entity that this Component is attached to. * @property {number} speed Speed multiplier for animation play back speed. 1.0 is playback at normal speed, 0.0 pauses the animation. * @property {boolean} activate If true the first animation will begin playing when the scene is loaded. - * @property {pc.AnimComponentData} data Animation component data */ var AnimComponent = function (system, entity) { pc.Component.call(this, system, entity); @@ -21,6 +21,7 @@ Object.assign(pc, function () { Object.assign(AnimComponent.prototype, { /** + * @private * @function * @name pc.AnimComponent#loadStateGraph * @description Initialises component animation controllers using the provided state graph. @@ -92,11 +93,12 @@ Object.assign(pc, function () { }, /** + * @private * @function * @name pc.AnimComponent#findAnimationLayer * @description Finds a pc.AnimComponentLayer in this component. * @param {string} layerName - The name of the anim component layer to find - * @returns {pc.AnimComponentLayer|null} layer + * @returns {pc.AnimComponentLayer} layer */ findAnimationLayer: function (layerName) { var layerIndex = this.data.layerIndices[layerName]; diff --git a/src/framework/components/anim/constants.js b/src/framework/components/anim/constants.js index 117dc667ecb..1bc7aec5bae 100644 --- a/src/framework/components/anim/constants.js +++ b/src/framework/components/anim/constants.js @@ -17,19 +17,6 @@ Object.assign(pc, { ANIM_PARAMETER_BOOLEAN: 'BOOLEAN', ANIM_PARAMETER_TRIGGER: 'TRIGGER', - /** - * @constant - * @name pc.ANIM_STATE_START - * @type {string} - * @description AnimComponent start state. - */ ANIM_STATE_START: 'START', - - /** - * @constant - * @name pc.ANIM_STATE_START - * @type {string} - * @description AnimComponent end state. - */ ANIM_STATE_END: 'END' }); diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index a0c0b0fd323..ebff2129f14 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -241,11 +241,6 @@ Object.assign(pc, function () { return this._previousStateName; } }, - playing: { - get: function () { - return this._playing; - } - }, playable: { get: function () { var playable = true; diff --git a/src/framework/components/anim/data.js b/src/framework/components/anim/data.js index e782b3df697..f8a369cc7c9 100644 --- a/src/framework/components/anim/data.js +++ b/src/framework/components/anim/data.js @@ -1,15 +1,4 @@ Object.assign(pc, function () { - /** - * @class - * @name pc.AnimComponentData - * @property {number} speed Speed - * @property {boolean} active Active - * @property {boolean} enabled Enabled - * @property {boolean} playing Playing - * @property {pc.AnimComponentLayer[]} layers Layers - * @property {object|undefined} parameters Parameters - * @property {pc.Model|null} model Model - */ var AnimComponentData = function () { // Serialized this.stateGraphAsset = null; diff --git a/src/framework/components/anim/layer.js b/src/framework/components/anim/layer.js index 4537ef6da37..be7678ecf86 100644 --- a/src/framework/components/anim/layer.js +++ b/src/framework/components/anim/layer.js @@ -1,6 +1,7 @@ Object.assign(pc, function () { /** + * @private * @class * @name pc.AnimComponentLayer * @classdesc The Anim Component Layer allows managers a single layer of the animation state graph. @@ -17,6 +18,7 @@ Object.assign(pc, function () { Object.assign(AnimComponentLayer.prototype, { /** + * @private * @function * @name pc.AnimComponentLayer#play * @description Start playing the animation in the current state. @@ -27,6 +29,7 @@ Object.assign(pc, function () { }, /** + * @private * @function * @name pc.AnimComponentLayer#pause * @description Start playing the animation in the current state. @@ -50,6 +53,7 @@ Object.assign(pc, function () { }, /** + * @private * @function * @name pc.AnimComponentLayer#assignAnimation * @description Associates an animation with a state node in the loaded state graph. If all states nodes are linked and the pc.AnimComponent.activate value was set to true then the component will begin playing. @@ -78,6 +82,7 @@ Object.assign(pc, function () { Object.defineProperties(AnimComponentLayer.prototype, { /** + * @private * @readonly * @name pc.AnimComponentLayer#name * @type {string} @@ -89,8 +94,9 @@ Object.assign(pc, function () { } }, /** + * @private * @name pc.AnimComponentLayer#playing - * @type {boolean} + * @type {string} * @description Whether this layer is currently playing */ playing: { @@ -102,6 +108,7 @@ Object.assign(pc, function () { } }, /** + * @private * @readonly * @name pc.AnimComponentLayer#playable * @type {string} @@ -176,6 +183,7 @@ Object.assign(pc, function () { } }, /** + * @private * @readonly * @name pc.AnimComponentLayer#states * @type {string[]} diff --git a/src/framework/components/anim/system.js b/src/framework/components/anim/system.js index 03560668ff9..9f5e6fd17ad 100644 --- a/src/framework/components/anim/system.js +++ b/src/framework/components/anim/system.js @@ -7,6 +7,7 @@ Object.assign(pc, function () { ]; /** + * @private * @class * @name pc.AnimComponentSystem * @augments pc.ComponentSystem From f238026a3d89f0f4c03cefb4cce9529e613907b4 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 26 Jun 2020 11:11:06 +0200 Subject: [PATCH 112/144] feat: re-apply changes to container resource that were lost in upstream merge --- src/resources/container.js | 111 ++++++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 26 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index cc436775b3e..754dbfe889a 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -6,18 +6,39 @@ import { Asset } from '../asset/asset.js'; import { GlbParser } from './parser/glb-parser.js'; +/** + * Maps an entity to a number of animation assets. Animations can be added to the node with either + * pc.AnimationComponent or pc.AnimComponent. + * + * @typedef {object} pc.ContainerResourceAnimationMapping + * @property {pc.Entity} node Entity that should be animated. + * @property {number[]} animations Indexes of animations assets to be assigned to node. + */ + /** * @class * @name pc.ContainerResource - * @classdesc Container for a list of animations, textures, materials and a model. + * @classdesc Container for a list of animations, textures, materials, models, scenes (as entities) + * and a default scene (as entity). Entities in scene hierarchies will have model and animation components + * attached to them. * @param {object} data - The loaded GLB data. + * @property {pc.Entity|null} scene The root entity of the default scene. + * @property {pc.Entity[]} scenes The root entities of all scenes. + * @property {pc.Asset[]} materials Material assets. + * @property {pc.Asset[]} textures Texture assets. + * @property {pc.Asset[]} animations Animation assets. + * @property {pc.ContainerResourceAnimationMapping[]} nodeAnimations Mapping of animations to node entities. + * @property {pc.AssetRegistry} registry The asset registry. */ function ContainerResource(data) { this.data = data; - this.model = null; + this.scene = null; + this.scenes = []; this.materials = []; this.textures = []; this.animations = []; + this.nodeAnimations = []; + this.models = []; this.registry = null; } @@ -32,17 +53,36 @@ Object.assign(ContainerResource.prototype, { }; var destroyAssets = function (assets) { - assets.forEach(function (asset) { - destroyAsset(asset); - }); + assets.forEach(destroyAsset); }; + if (this.scene) { + this.scene.destroy(); + this.scene = null; + } + + if (this.scenes) { + this.scenes.forEach(function (scene) { + scene.destroy(); + }); + this.scenes = null; + } + // unload and destroy assets if (this.animations) { destroyAssets(this.animations); this.animations = null; } + if (this.nodeAnimations) { + this.nodeAnimations = null; + } + + if (this.models) { + destroyAssets(this.models); + this.models = null; + } + if (this.textures) { destroyAssets(this.textures); this.textures = null; @@ -53,11 +93,6 @@ Object.assign(ContainerResource.prototype, { this.materials = null; } - if (this.model) { - destroyAsset(this.model); - this.model = null; - } - this.data = null; this.assets = null; } @@ -68,7 +103,7 @@ Object.assign(ContainerResource.prototype, { * @name pc.ContainerHandler * @implements {pc.ResourceHandler} * @classdesc Loads files that contain multiple resources. For example glTF files can contain - * textures, models and animations. + * textures, scenes and animations. * The asset options object can be used for passing in load time callbacks to handle the various resources * at different stages of loading as follows: * ``` @@ -148,7 +183,7 @@ Object.assign(ContainerHandler.prototype, { return data; }, - // Create assets to wrap the loaded engine resources - model, materials, textures and animations. + // Create assets to wrap the loaded engine resources - models, materials, textures and animations. patch: function (asset, assets) { var createAsset = function (type, resource, index) { var subAsset = new Asset(asset.name + '/' + type + '/' + index, type, { @@ -162,28 +197,52 @@ Object.assign(ContainerHandler.prototype, { var container = asset.resource; var data = container.data; - var i; - // create model asset - var model = (data.meshes.length === 0) ? null : createAsset('model', GlbParser.createModel(data, this._defaultMaterial), 0); + // create model assets + var modelAssets = data.models.map(function (model, index) { + return createAsset('model', model, index); + }); // create material assets - var materials = []; - for (i = 0; i < data.materials.length; ++i) { - materials.push(createAsset('material', data.materials[i], i)); - } + var materialAssets = data.materials.map(function (material, index) { + return createAsset('material', material, index); + }); // create animation assets - var animations = []; - for (i = 0; i < data.animations.length; ++i) { - animations.push(createAsset('animation', data.animations[i], i)); - } + var animationAssets = data.animations.map(function (animation, index) { + return createAsset('animation', animation, index); + }); + + // create mapping from nodes to animations + var nodeAnimations = data.nodes + .map(function (node, nodeIndex) { + return { + node: node, + animations: data.nodeComponents[nodeIndex].animations + }; + }).filter(function (mapping) { + return mapping.animations.length > 0; + }); + + // add model components to nodes + data.nodes.forEach(function (node, nodeIndex) { + var components = data.nodeComponents[nodeIndex]; + if (components.model !== null) { + node.addComponent('model', { + type: 'asset', + asset: modelAssets[components.model] + }); + } + }); container.data = null; // since assets are created, release GLB data - container.model = model; - container.materials = materials; + container.scene = data.scene; // scenes are not wrapped in an Asset + container.scenes = data.scenes; // scenes are not wrapped in an Asset + container.materials = materialAssets; container.textures = data.textures; // texture assets are created directly - container.animations = animations; + container.animations = animationAssets; + container.nodeAnimations = nodeAnimations; + container.models = modelAssets; container.registry = assets; } }); From bae31887971adaf34175cb4b48b34717fca748b3 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 26 Jun 2020 11:46:36 +0200 Subject: [PATCH 113/144] feat: re-apply changes to GLB parser that were lost in upstream merge --- src/resources/container.js | 2 + src/resources/parser/glb-parser.js | 294 +++++++++++++++++++---------- 2 files changed, 201 insertions(+), 95 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index 754dbfe889a..86964d46759 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -112,6 +112,7 @@ Object.assign(ContainerResource.prototype, { * |-------------+-------------+-------------+-------------+-------------| * | global | x | | | x | * | node | x | x | | x | + * | scene | x | x | | x | * | animation | x | | | x | * | material | x | x | | x | * | texture | x | | x | x | @@ -163,6 +164,7 @@ Object.assign(ContainerHandler.prototype, { path.extractPath(url.load), response, self._device, + self._defaultMaterial, asset.registry, asset.options, function (err, result) { diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index ef7bab89fad..d91e95a416e 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -163,6 +163,16 @@ var getPrimitiveType = function (primitive) { } }; +function getNodePath(targetNode, nodes) { + var parent = nodes.findIndex(function (node) { + return node.hasOwnProperty('children') ? node.children.includes(targetNode) : false; + }); + if (parent !== -1) { + return getNodePath(parent, nodes).concat([targetNode]); + } + return [targetNode]; +} + var generateIndices = function (numVertices) { var dummyIndices = new Uint16Array(numVertices); for (var i = 0; i < numVertices; i++) { @@ -465,7 +475,7 @@ var createSkin = function (device, gltfSkin, accessors, bufferViews, nodes, buff var tempMat = new Mat4(); var tempVec = new Vec3(); -var createMesh = function (device, gltfMesh, accessors, bufferViews, buffers, callback) { +var createMeshGroup = function (device, gltfMesh, accessors, bufferViews, buffers, callback) { var meshes = []; var semanticMap = { @@ -981,7 +991,7 @@ var createMaterial = function (gltfMaterial, textures) { }; // create the anim structure -var createAnimation = function (gltfAnimation, animationIndex, accessors, bufferViews, nodes, buffers) { +var createAnimation = function (gltfAnimation, animationIndex, accessors, bufferViews, nodes, nodeEntities, buffers) { // create animation data block for the accessor var createAnimData = function (accessor) { @@ -1003,6 +1013,7 @@ var createAnimation = function (gltfAnimation, animationIndex, accessors, buffer var outputs = []; var curves = []; + var targetRootNodes = []; var i; @@ -1051,7 +1062,14 @@ var createAnimation = function (gltfAnimation, animationIndex, accessors, buffer var target = channel.target; var curve = curves[channel.sampler]; - curve._paths.push(propertyLocator.encode([[nodes[target.node].name], 'graph', [transformSchema[target.path]]])); + // get node locator path relative to root node + var targetNodePath = getNodePath(target.node, nodes); + var targetNodePathNamed = targetNodePath.map(function (node) { + return nodeEntities[node].name; + }); + + targetRootNodes.push(targetNodePath[0]); + curve._paths.push(propertyLocator.encode([targetNodePathNamed, 'entity', [transformSchema[target.path]]])); // if this target is a set of quaternion keys, make note of its index so we can perform // quaternion-specific processing on it. @@ -1103,21 +1121,29 @@ var createAnimation = function (gltfAnimation, animationIndex, accessors, buffer return Math.max(value, data.length === 0 ? 0 : data[data.length - 1]); }, 0); - return new AnimTrack( - gltfAnimation.hasOwnProperty('name') ? gltfAnimation.name : ("animation_" + animationIndex), - duration, - inputs, - outputs, - curves); + return { + track: new AnimTrack( + gltfAnimation.hasOwnProperty('name') ? gltfAnimation.name : ('animation_' + animationIndex), + duration, + inputs, + outputs, + curves + ), + // return root nodes without duplicates + targetRootNodes: targetRootNodes.filter(function (rootNode, index, rootNodes) { + return rootNodes.indexOf(rootNode) === index; + }) + }; }; var createNode = function (gltfNode, nodeIndex) { - var entity = new GraphNode(); + var entity = new Entity(); if (gltfNode.hasOwnProperty('name')) { - entity.name = gltfNode.name; + // Remove slashes since they interfere with animation curve paths + entity.name = nodeData.name.replace(/\//g, '_'); } else { - entity.name = "node_" + nodeIndex; + entity.name = 'node_' + nodeIndex; } // Parse transformation properties @@ -1149,6 +1175,62 @@ var createNode = function (gltfNode, nodeIndex) { return entity; }; + +var createScene = function (sceneData, sceneIndex, nodes) { + var sceneRoot = new Entity(); + + if (sceneData.hasOwnProperty('name')) { + sceneRoot.name = sceneData.name; + } else { + sceneRoot.name = 'scene_' + sceneIndex; + } + + sceneData.nodes.forEach(function (nodeIndex) { + var node = nodes[nodeIndex]; + if (node !== undefined) { + sceneRoot.addChild(node); + } + }); + + return sceneRoot; +}; + +var createModel = function (node, meshGroup, skin, materials, defaultMaterial) { + var model = new Model(); + model.graph = new GraphNode('model_' + node.name); + + meshGroup.forEach(function (mesh) { + var material = (mesh.materialIndex === undefined) ? defaultMaterial : materials[mesh.materialIndex]; + var meshInstance = new MeshInstance(model.graph, mesh, material); + + if (mesh.morph) { + var morphInstance = new MorphInstance(mesh.morph); + if (mesh.weights) { + for (var wi = 0; wi < mesh.weights.length; wi++) { + morphInstance.setWeight(wi, mesh.weights[wi]); + } + } + + meshInstance.morphInstance = morphInstance; + model.morphInstances.push(morphInstance); + } + + if (skin !== null) { + mesh.skin = skin; + + var skinInstance = new SkinInstance(skin); + skinInstance.bones = skin.bones; + + meshInstance.skinInstance = skinInstance; + model.skinInstances.push(skinInstance); + } + + model.meshInstances.push(meshInstance); + }); + + return model; +}; + var createSkins = function (device, gltf, nodes, buffers) { if (!gltf.hasOwnProperty('skins') || gltf.skins.length === 0) { return []; @@ -1158,14 +1240,14 @@ var createSkins = function (device, gltf, nodes, buffers) { }); }; -var createMeshes = function (device, gltf, buffers, callback) { +var createMeshGroups = function (device, gltf, buffers, callback) { if (!gltf.hasOwnProperty('meshes') || gltf.meshes.length === 0 || !gltf.hasOwnProperty('accessors') || gltf.accessors.length === 0 || !gltf.hasOwnProperty('bufferViews') || gltf.bufferViews.length === 0) { return []; } return gltf.meshes.map(function (gltfMesh) { - return createMesh(device, gltfMesh, gltf.accessors, gltf.bufferViews, buffers, callback); + return createMeshGroup(device, gltfMesh, gltf.accessors, gltf.bufferViews, buffers, callback); }); }; @@ -1190,7 +1272,7 @@ var createMaterials = function (gltf, textures, options) { }); }; -var createAnimations = function (gltf, nodes, buffers, options) { +var createAnimations = function (gltf, nodes, nodeComponents, buffers, options) { if (!gltf.hasOwnProperty('animations') || gltf.animations.length === 0) { return []; } @@ -1202,11 +1284,25 @@ var createAnimations = function (gltf, nodes, buffers, options) { if (preprocess) { preprocess(gltfAnimation); } - var animation = createAnimation(gltfAnimation, index, gltf.accessors, gltf.bufferViews, nodes, buffers); + var animation = createAnimation(gltfAnimation, index, gltf.accessors, gltf.bufferViews, gltf.nodes, nodes, buffers); if (postprocess) { - postprocess(gltfAnimation, animation); + postprocess(gltfAnimation, animation.track); } - return animation; + + // Animation components should be added to all root nodes targeted by an + // animation track since the locator path in animation curves is relative + // to its targets root node + animation.targetRootNodes.forEach(function (rootNode) { + if (!nodeComponents[rootNode]) { + nodeComponents[rootNode] = {}; + } + if (!nodeComponents[rootNode].animations) { + nodeComponents[rootNode].animations = []; + } + nodeComponents[rootNode].animations.push(index); + }); + + return animation.track; }); }; @@ -1247,8 +1343,76 @@ var createNodes = function (gltf, options) { return nodes; }; +var createEmptyNodeComponents = function (nodes) { + return nodes.map(function () { + return { + model: null, + animations: [] + }; + }); +}; + +var createScenes = function (gltf, nodes, options) { + if (!gltf.hasOwnProperty('scenes') || gltf.scenes.length === 0) { + return []; + } + + var preprocess = options && options.scene && options.scene.preprocess; + var process = options && options.scene && options.scene.process || createScene; + var postprocess = options && options.scene && options.scene.postprocess; + + return gltf.scenes.map(function (gltfScene, index) { + if (preprocess) { + preprocess(gltfScene); + } + var scene = process(gltfScene, index, nodes); + if (postprocess) { + postprocess(gltfScene, scene); + } + return scene; + }); +}; + +var getDefaultScene = function (gltf, scenes) { + if (!gltf.hasOwnProperty('scene')) { + if (scenes.length === 0) { + return null; + } + return scenes[0]; + } + + return scenes[gltf.scene] || null; +}; + +var createModels = function (gltf, nodes, nodeComponents, meshGroups, skins, materials, defaultMaterial) { + if (!gltf.hasOwnProperty('nodes') || gltf.nodes.length === 0) { + return []; + } + + var models = []; + + gltf.nodes.forEach(function (gltfNode, nodeIndex) { + if (!gltfNode.hasOwnProperty('mesh')) { + return; + } + + var node = nodes[nodeIndex]; + var meshGroup = meshGroups[gltfNode.mesh]; + var skin = gltfNode.hasOwnProperty('skin') ? skins[gltfNode.skin] : null; + var model = createModel(node, meshGroup, skin, materials, defaultMaterial); + var modelIndex = models.push(model) - 1; + + if (!nodeComponents[nodeIndex]) { + nodeComponents[nodeIndex] = {}; + } + nodeComponents[nodeIndex].model = modelIndex; + }); + + return models; +}; + // create engine resources from the downloaded GLB data -var createResources = function (device, gltf, buffers, textures, options, callback) { +var createResources = function (device, gltf, buffers, textures, defaultMaterial, options, callback) { var preprocess = options && options.global && options.global.preprocess; var postprocess = options && options.global && options.global.postprocess; @@ -1258,21 +1422,26 @@ var createResources = function (device, gltf, buffers, textures, options, callba } var nodes = createNodes(gltf, options); - var animations = createAnimations(gltf, nodes, buffers, options); + var nodeComponents = createEmptyNodeComponents(nodes); + var scenes = createScenes(gltf, nodes, options); + var scene = getDefaultScene(gltf, scenes); + var animations = createAnimations(gltf, nodes, nodeComponents, buffers, options); var materials = createMaterials(gltf, gltf.textures ? gltf.textures.map(function (t) { return textures[t.source].resource; }) : [], options); - var meshes = createMeshes(device, gltf, buffers, callback); + var meshGroups = createMeshGroups(device, gltf, buffers, callback); var skins = createSkins(device, gltf, nodes, buffers); + var models = createModels(gltf, nodes, nodeComponents, meshGroups, skins, materials, defaultMaterial); var result = { - 'gltf': gltf, 'nodes': nodes, + 'nodeComponents': nodeComponents, + 'models': models, 'animations': animations, + 'scenes': scenes, + 'scene': scene, 'textures': textures, - 'materials': materials, - 'meshes': meshes, - 'skins': skins + 'materials': materials }; if (postprocess) { @@ -1397,7 +1566,7 @@ var loadTexturesAsync = function (gltf, buffers, urlBase, registry, options, cal if (isDataURI(gltfImage.uri)) { loadTexture(i, gltfImage.uri, getDataURIMimeType(gltfImage.uri)); } else { - loadTexture(i, path.join(urlBase, gltfImage.uri), "anonymous"); + loadTexture(i, path.join(urlBase, gltfImage.uri).replace(registry.prefix, ""), "anonymous"); } } else if (gltfImage.hasOwnProperty('bufferView') && gltfImage.hasOwnProperty('mimeType')) { // bufferview @@ -1598,7 +1767,7 @@ var parseChunk = function (filename, data, callback) { function GlbParser() {} // parse the gltf or glb data asynchronously, loading external resources -GlbParser.parseAsync = function (filename, urlBase, data, device, registry, options, callback) { +GlbParser.parseAsync = function (filename, urlBase, data, device, defaultMaterial, registry, options, callback) { // parse the data parseChunk(filename, data, function (err, chunks) { if (err) { @@ -1627,7 +1796,7 @@ GlbParser.parseAsync = function (filename, urlBase, data, device, registry, opti return; } - createResources(device, gltf, buffers, textures, options, callback); + createResources(device, gltf, buffers, textures, defaultMaterial, options, callback); }); }); }); @@ -1635,7 +1804,7 @@ GlbParser.parseAsync = function (filename, urlBase, data, device, registry, opti }; // parse the gltf or glb data synchronously. external resources (buffers and images) are ignored. -GlbParser.parse = function (filename, data, device, options) { +GlbParser.parse = function (filename, data, device, defaultMaterial, options) { var result = null; // parse the data @@ -1652,7 +1821,7 @@ GlbParser.parse = function (filename, data, device, options) { var textures = []; // create resources - createResources(device, gltf, buffers, textures, options || { }, function (err, result_) { + createResources(device, gltf, buffers, textures, defaultMaterial, options || { }, function (err, result_) { if (err) { console.error(err); } else { @@ -1667,69 +1836,4 @@ GlbParser.parse = function (filename, data, device, options) { return result; }; -// create a pc.Model from the parsed GLB data structures -GlbParser.createModel = function (glb, defaultMaterial) { - var createMeshInstance = function (model, mesh, skins, materials, node, gltfNode) { - var material = (mesh.materialIndex === undefined) ? defaultMaterial : materials[mesh.materialIndex]; - - var meshInstance = new MeshInstance(node, mesh, material); - - if (mesh.morph) { - var morphInstance = new MorphInstance(mesh.morph); - if (mesh.weights) { - for (var wi = 0; wi < mesh.weights.length; wi++) { - morphInstance.setWeight(wi, mesh.weights[wi]); - } - } - - meshInstance.morphInstance = morphInstance; - model.morphInstances.push(morphInstance); - } - - if (gltfNode.hasOwnProperty('skin')) { - var skin = skins[gltfNode.skin]; - mesh.skin = skin; - - var skinInstance = new SkinInstance(skin); - skinInstance.bones = skin.bones; - - meshInstance.skinInstance = skinInstance; - model.skinInstances.push(skinInstance); - } - - model.meshInstances.push(meshInstance); - }; - - var model = new Model(); - var i; - - var rootNodes = []; - for (i = 0; i < glb.nodes.length; i++) { - var node = glb.nodes[i]; - if (node.parent === null) { - rootNodes.push(node); - } - - var gltfNode = glb.gltf.nodes[i]; - if (gltfNode.hasOwnProperty('mesh')) { - var meshGroup = glb.meshes[gltfNode.mesh]; - for (var mi = 0; mi < meshGroup.length; mi++) { - createMeshInstance(model, meshGroup[mi], glb.skins, glb.materials, node, gltfNode); - } - } - } - - // set model root (create a group if there is more than one) - if (rootNodes.length === 1) { - model.graph = rootNodes[0]; - } else { - model.graph = new GraphNode('SceneGroup'); - for (i = 0; i < rootNodes.length; ++i) { - model.graph.addChild(rootNodes[i]); - } - } - - return model; -}; - export { GlbParser }; From 57e6f962cde4c80cdf610b95b14e86c8ea460b08 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 26 Jun 2020 12:01:08 +0200 Subject: [PATCH 114/144] 1.30.0-rc.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index d5f237b40ab..8d6e8890e98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@animech-public/playcanvas", - "version": "1.30.0-dev", + "version": "1.30.0-rc.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 52fcd8a8cc3..512a9e645d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@animech-public/playcanvas", - "version": "1.30.0-dev", + "version": "1.30.0-rc.0", "author": "PlayCanvas ", "homepage": "https://playcanvas.com", "description": "PlayCanvas WebGL game engine", From 558ecf5dc106f46730971f9edba439716c3f1cd3 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 26 Jun 2020 13:35:45 +0200 Subject: [PATCH 115/144] fix(GlbParser): fix broken reference to Entity --- src/resources/parser/glb-parser.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index d91e95a416e..a76f03adb30 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -28,6 +28,7 @@ import { } from '../../scene/constants.js'; import { calculateNormals } from '../../scene/procedural.js'; import { GraphNode } from '../../scene/graph-node.js'; +import { Entity } from '../../framework/entity.js'; import { Mesh } from '../../scene/mesh.js'; import { MeshInstance } from '../../scene/mesh-instance.js'; import { Model } from '../../scene/model.js'; @@ -1141,7 +1142,7 @@ var createNode = function (gltfNode, nodeIndex) { if (gltfNode.hasOwnProperty('name')) { // Remove slashes since they interfere with animation curve paths - entity.name = nodeData.name.replace(/\//g, '_'); + entity.name = gltfNode.name.replace(/\//g, '_'); } else { entity.name = 'node_' + nodeIndex; } From 6d9e9785f388cf96d7c9a8e3c1dab6c7fcb12176 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Mon, 29 Jun 2020 15:12:22 +0200 Subject: [PATCH 116/144] Revert "1.30.0-rc.0" This reverts commit 57e6f962cde4c80cdf610b95b14e86c8ea460b08. --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8d6e8890e98..d5f237b40ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@animech-public/playcanvas", - "version": "1.30.0-rc.0", + "version": "1.30.0-dev", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 512a9e645d0..52fcd8a8cc3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@animech-public/playcanvas", - "version": "1.30.0-rc.0", + "version": "1.30.0-dev", "author": "PlayCanvas ", "homepage": "https://playcanvas.com", "description": "PlayCanvas WebGL game engine", From 1bd478183a45ff85d2a869cb367e484371551ba5 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Thu, 2 Jul 2020 10:32:56 +0200 Subject: [PATCH 117/144] feat(GlbParser): return model per glTF mesh as well as model per node --- src/resources/container.js | 17 +++++++++++++++-- src/resources/parser/glb-parser.js | 26 ++++++++++++++++++-------- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index 86964d46759..b95af9d5ad1 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -25,9 +25,10 @@ import { GlbParser } from './parser/glb-parser.js'; * @property {pc.Entity|null} scene The root entity of the default scene. * @property {pc.Entity[]} scenes The root entities of all scenes. * @property {pc.Asset[]} materials Material assets. - * @property {pc.Asset[]} textures Texture assets. + * @property {pc.Asset[]} textures Texture assets per GLB image. * @property {pc.Asset[]} animations Animation assets. * @property {pc.ContainerResourceAnimationMapping[]} nodeAnimations Mapping of animations to node entities. + * @property {pc.Asset[]} models Model assets per GLB mesh. * @property {pc.AssetRegistry} registry The asset registry. */ function ContainerResource(data) { @@ -39,6 +40,7 @@ function ContainerResource(data) { this.animations = []; this.nodeAnimations = []; this.models = []; + this.nodeModels = []; this.registry = null; } @@ -83,6 +85,11 @@ Object.assign(ContainerResource.prototype, { this.models = null; } + if (this.nodeModels) { + destroyAssets(this.nodeModels); + this.nodeModels = null; + } + if (this.textures) { destroyAssets(this.textures); this.textures = null; @@ -205,6 +212,11 @@ Object.assign(ContainerHandler.prototype, { return createAsset('model', model, index); }); + // create node model assets + var nodeModelAssets = data.nodeModels.map(function (model, index) { + return createAsset('model', model, index); + }); + // create material assets var materialAssets = data.materials.map(function (material, index) { return createAsset('material', material, index); @@ -232,7 +244,7 @@ Object.assign(ContainerHandler.prototype, { if (components.model !== null) { node.addComponent('model', { type: 'asset', - asset: modelAssets[components.model] + asset: nodeModelAssets[components.model] }); } }); @@ -245,6 +257,7 @@ Object.assign(ContainerHandler.prototype, { container.animations = animationAssets; container.nodeAnimations = nodeAnimations; container.models = modelAssets; + container.nodeModels = nodeModelAssets; container.registry = assets; } }); diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index a76f03adb30..3cfa8842385 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1196,9 +1196,9 @@ var createScene = function (sceneData, sceneIndex, nodes) { return sceneRoot; }; -var createModel = function (node, meshGroup, skin, materials, defaultMaterial) { +var createModel = function (name, meshGroup, skin, materials, defaultMaterial) { var model = new Model(); - model.graph = new GraphNode('model_' + node.name); + model.graph = new GraphNode(name); meshGroup.forEach(function (mesh) { var material = (mesh.materialIndex === undefined) ? defaultMaterial : materials[mesh.materialIndex]; @@ -1385,12 +1385,20 @@ var getDefaultScene = function (gltf, scenes) { return scenes[gltf.scene] || null; }; -var createModels = function (gltf, nodes, nodeComponents, meshGroups, skins, materials, defaultMaterial) { +// Create standalone models per mesh group to include in parser response +var createModels = function (meshGroups, materials, defaultMaterial) { + return meshGroups.map(function (meshGroup, meshGroupIndex) { + return createModel('mesh_model_' + meshGroupIndex, meshGroup, null, materials, defaultMaterial); + }); +}; + +// Create model per node that uses the node skin +var createNodeModels = function (gltf, nodes, nodeComponents, meshGroups, skins, materials, defaultMaterial) { if (!gltf.hasOwnProperty('nodes') || gltf.nodes.length === 0) { return []; } - var models = []; + var nodeModels = []; gltf.nodes.forEach(function (gltfNode, nodeIndex) { if (!gltfNode.hasOwnProperty('mesh')) { @@ -1400,8 +1408,8 @@ var createModels = function (gltf, nodes, nodeComponents, meshGroups, skins, mat var node = nodes[nodeIndex]; var meshGroup = meshGroups[gltfNode.mesh]; var skin = gltfNode.hasOwnProperty('skin') ? skins[gltfNode.skin] : null; - var model = createModel(node, meshGroup, skin, materials, defaultMaterial); - var modelIndex = models.push(model) - 1; + var model = createModel('node_model_' + node.name, meshGroup, skin, materials, defaultMaterial); + var modelIndex = nodeModels.push(model) - 1; if (!nodeComponents[nodeIndex]) { nodeComponents[nodeIndex] = {}; @@ -1409,7 +1417,7 @@ var createModels = function (gltf, nodes, nodeComponents, meshGroups, skins, mat nodeComponents[nodeIndex].model = modelIndex; }); - return models; + return nodeModels; }; // create engine resources from the downloaded GLB data @@ -1432,12 +1440,14 @@ var createResources = function (device, gltf, buffers, textures, defaultMaterial }) : [], options); var meshGroups = createMeshGroups(device, gltf, buffers, callback); var skins = createSkins(device, gltf, nodes, buffers); - var models = createModels(gltf, nodes, nodeComponents, meshGroups, skins, materials, defaultMaterial); + var models = createModels(meshGroups, materials, defaultMaterial); + var nodeModels = createNodeModels(gltf, nodes, nodeComponents, meshGroups, skins, materials, defaultMaterial); var result = { 'nodes': nodes, 'nodeComponents': nodeComponents, 'models': models, + 'nodeModels': nodeModels, 'animations': animations, 'scenes': scenes, 'scene': scene, From 7b4742536cff590ef272c221a5410574da789c8d Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Thu, 2 Jul 2020 11:03:26 +0200 Subject: [PATCH 118/144] refactor(ContainerResource): add _ prefix to nodeModels to signal that it's a private prop --- src/resources/container.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index b95af9d5ad1..4257fa8a945 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -40,7 +40,7 @@ function ContainerResource(data) { this.animations = []; this.nodeAnimations = []; this.models = []; - this.nodeModels = []; + this._nodeModels = []; this.registry = null; } @@ -85,9 +85,9 @@ Object.assign(ContainerResource.prototype, { this.models = null; } - if (this.nodeModels) { - destroyAssets(this.nodeModels); - this.nodeModels = null; + if (this._nodeModels) { + destroyAssets(this._nodeModels); + this._nodeModels = null; } if (this.textures) { @@ -257,7 +257,7 @@ Object.assign(ContainerHandler.prototype, { container.animations = animationAssets; container.nodeAnimations = nodeAnimations; container.models = modelAssets; - container.nodeModels = nodeModelAssets; + container._nodeModels = nodeModelAssets; container.registry = assets; } }); From b9e35c2c886d96aff92a344b05e93a443dd56b39 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Thu, 2 Jul 2020 11:04:45 +0200 Subject: [PATCH 119/144] refactor(GlbParser): clone node models instead of running createModel multiple times --- src/resources/parser/glb-parser.js | 44 ++++++++++++++++-------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 3cfa8842385..2241e0a2a10 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1196,7 +1196,7 @@ var createScene = function (sceneData, sceneIndex, nodes) { return sceneRoot; }; -var createModel = function (name, meshGroup, skin, materials, defaultMaterial) { +var createModel = function (name, meshGroup, materials, defaultMaterial) { var model = new Model(); model.graph = new GraphNode(name); @@ -1216,22 +1216,24 @@ var createModel = function (name, meshGroup, skin, materials, defaultMaterial) { model.morphInstances.push(morphInstance); } - if (skin !== null) { - mesh.skin = skin; - - var skinInstance = new SkinInstance(skin); - skinInstance.bones = skin.bones; - - meshInstance.skinInstance = skinInstance; - model.skinInstances.push(skinInstance); - } - model.meshInstances.push(meshInstance); }); return model; }; +var createSkinInstances = function (model, skin) { + return model.meshInstances.map(function (meshInstance) { + meshInstance.mesh.skin = skin; + + var skinInstance = new SkinInstance(skin); + skinInstance.bones = skin.bones; + + meshInstance.skinInstance = skinInstance; + return skinInstance; + }); +}; + var createSkins = function (device, gltf, nodes, buffers) { if (!gltf.hasOwnProperty('skins') || gltf.skins.length === 0) { return []; @@ -1385,15 +1387,13 @@ var getDefaultScene = function (gltf, scenes) { return scenes[gltf.scene] || null; }; -// Create standalone models per mesh group to include in parser response var createModels = function (meshGroups, materials, defaultMaterial) { return meshGroups.map(function (meshGroup, meshGroupIndex) { - return createModel('mesh_model_' + meshGroupIndex, meshGroup, null, materials, defaultMaterial); + return createModel('model_' + meshGroupIndex, meshGroup, materials, defaultMaterial); }); }; -// Create model per node that uses the node skin -var createNodeModels = function (gltf, nodes, nodeComponents, meshGroups, skins, materials, defaultMaterial) { +var createNodeModels = function (gltf, nodeComponents, models, skins) { if (!gltf.hasOwnProperty('nodes') || gltf.nodes.length === 0) { return []; } @@ -1405,16 +1405,18 @@ var createNodeModels = function (gltf, nodes, nodeComponents, meshGroups, skins, return; } - var node = nodes[nodeIndex]; - var meshGroup = meshGroups[gltfNode.mesh]; + var model = models[gltfNode.mesh].clone(); var skin = gltfNode.hasOwnProperty('skin') ? skins[gltfNode.skin] : null; - var model = createModel('node_model_' + node.name, meshGroup, skin, materials, defaultMaterial); - var modelIndex = nodeModels.push(model) - 1; + if (skin !== null) { + model.skinInstances = createSkinInstances(model, skins); + } + + var nodeModelIndex = nodeModels.push(model) - 1; if (!nodeComponents[nodeIndex]) { nodeComponents[nodeIndex] = {}; } - nodeComponents[nodeIndex].model = modelIndex; + nodeComponents[nodeIndex].model = nodeModelIndex; }); return nodeModels; @@ -1441,7 +1443,7 @@ var createResources = function (device, gltf, buffers, textures, defaultMaterial var meshGroups = createMeshGroups(device, gltf, buffers, callback); var skins = createSkins(device, gltf, nodes, buffers); var models = createModels(meshGroups, materials, defaultMaterial); - var nodeModels = createNodeModels(gltf, nodes, nodeComponents, meshGroups, skins, materials, defaultMaterial); + var nodeModels = createNodeModels(gltf, nodeComponents, models, skins); var result = { 'nodes': nodes, From 2a183951b4adc00d9835a324b661f37a6e253317 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Thu, 2 Jul 2020 11:15:18 +0200 Subject: [PATCH 120/144] fix(GlbParser): fix typo --- src/resources/parser/glb-parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 2241e0a2a10..c20668e99ad 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1408,7 +1408,7 @@ var createNodeModels = function (gltf, nodeComponents, models, skins) { var model = models[gltfNode.mesh].clone(); var skin = gltfNode.hasOwnProperty('skin') ? skins[gltfNode.skin] : null; if (skin !== null) { - model.skinInstances = createSkinInstances(model, skins); + model.skinInstances = createSkinInstances(model, skin); } var nodeModelIndex = nodeModels.push(model) - 1; From 9330c09da58cbed588f5463d16a5e80b89a0fdbc Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Thu, 2 Jul 2020 16:17:08 +0200 Subject: [PATCH 121/144] feat(ContainerResource): include nodes in result since it may be relevant for GLB extensions --- src/resources/container.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/resources/container.js b/src/resources/container.js index 4257fa8a945..d98058cfac0 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -24,6 +24,7 @@ import { GlbParser } from './parser/glb-parser.js'; * @param {object} data - The loaded GLB data. * @property {pc.Entity|null} scene The root entity of the default scene. * @property {pc.Entity[]} scenes The root entities of all scenes. + * @property {pc.Entity[]} nodes Entity per GLB node. * @property {pc.Asset[]} materials Material assets. * @property {pc.Asset[]} textures Texture assets per GLB image. * @property {pc.Asset[]} animations Animation assets. @@ -35,6 +36,7 @@ function ContainerResource(data) { this.data = data; this.scene = null; this.scenes = []; + this.nodes = []; this.materials = []; this.textures = []; this.animations = []; @@ -58,6 +60,7 @@ Object.assign(ContainerResource.prototype, { assets.forEach(destroyAsset); }; + // destroy entities if (this.scene) { this.scene.destroy(); this.scene = null; @@ -70,6 +73,13 @@ Object.assign(ContainerResource.prototype, { this.scenes = null; } + if (this.nodes) { + this.nodes.forEach(function (node) { + node.destroy(); + }); + this.nodes = null; + } + // unload and destroy assets if (this.animations) { destroyAssets(this.animations); @@ -252,6 +262,7 @@ Object.assign(ContainerHandler.prototype, { container.data = null; // since assets are created, release GLB data container.scene = data.scene; // scenes are not wrapped in an Asset container.scenes = data.scenes; // scenes are not wrapped in an Asset + container.nodes = data.nodes; // nodes are not wrapped in an Asset container.materials = materialAssets; container.textures = data.textures; // texture assets are created directly container.animations = animationAssets; From d4a0e5af32b95125c07a4069e9acf12bfe7c4d99 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Tue, 21 Jul 2020 10:44:00 +0200 Subject: [PATCH 122/144] chore(ContainerResource): add note about _nodeModels --- src/resources/container.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index d98058cfac0..9085d2220c3 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -259,16 +259,16 @@ Object.assign(ContainerHandler.prototype, { } }); - container.data = null; // since assets are created, release GLB data - container.scene = data.scene; // scenes are not wrapped in an Asset - container.scenes = data.scenes; // scenes are not wrapped in an Asset - container.nodes = data.nodes; // nodes are not wrapped in an Asset + container.data = null; // since assets are created, release GLB data + container.scene = data.scene; // scenes are not wrapped in an Asset + container.scenes = data.scenes; // scenes are not wrapped in an Asset + container.nodes = data.nodes; // nodes are not wrapped in an Asset container.materials = materialAssets; - container.textures = data.textures; // texture assets are created directly + container.textures = data.textures; // texture assets are created directly container.animations = animationAssets; container.nodeAnimations = nodeAnimations; container.models = modelAssets; - container._nodeModels = nodeModelAssets; + container._nodeModels = nodeModelAssets; // keep model refs for when container is destroyed container.registry = assets; } }); From c0fddb2bf6235b58aee87d7f5605b7c7b651707d Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Wed, 22 Jul 2020 17:46:05 +0200 Subject: [PATCH 123/144] feat(GlbParser): add support for glTF cameras --- src/resources/container.js | 8 ++++ src/resources/parser/glb-parser.js | 75 +++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index 9085d2220c3..fb53a3a95ea 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -24,6 +24,7 @@ import { GlbParser } from './parser/glb-parser.js'; * @param {object} data - The loaded GLB data. * @property {pc.Entity|null} scene The root entity of the default scene. * @property {pc.Entity[]} scenes The root entities of all scenes. + * @property {pc.CameraComponent[]} cameras Camera components. * @property {pc.Entity[]} nodes Entity per GLB node. * @property {pc.Asset[]} materials Material assets. * @property {pc.Asset[]} textures Texture assets per GLB image. @@ -36,6 +37,7 @@ function ContainerResource(data) { this.data = data; this.scene = null; this.scenes = []; + this.cameras = []; this.nodes = []; this.materials = []; this.textures = []; @@ -73,6 +75,10 @@ Object.assign(ContainerResource.prototype, { this.scenes = null; } + if (this.cameras) { + this.cameras = null; + } + if (this.nodes) { this.nodes.forEach(function (node) { node.destroy(); @@ -130,6 +136,7 @@ Object.assign(ContainerResource.prototype, { * | global | x | | | x | * | node | x | x | | x | * | scene | x | x | | x | + * | camera | x | x | | x | * | animation | x | | | x | * | material | x | x | | x | * | texture | x | | x | x | @@ -262,6 +269,7 @@ Object.assign(ContainerHandler.prototype, { container.data = null; // since assets are created, release GLB data container.scene = data.scene; // scenes are not wrapped in an Asset container.scenes = data.scenes; // scenes are not wrapped in an Asset + container.cameras = data.cameras; // camera components are not wrapped in an Asset container.nodes = data.nodes; // nodes are not wrapped in an Asset container.materials = materialAssets; container.textures = data.textures; // texture assets are created directly diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index c20668e99ad..df269c5812a 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -2,6 +2,7 @@ import { path } from '../../core/path.js'; import { http } from '../../net/http.js'; +import { math } from '../../math/math.js'; import { Mat4 } from '../../math/mat4.js'; import { Vec2 } from '../../math/vec2.js'; import { Vec3 } from '../../math/vec3.js'; @@ -24,7 +25,7 @@ import { VertexBuffer } from '../../graphics/vertex-buffer.js'; import { VertexFormat } from '../../graphics/vertex-format.js'; import { - BLEND_NONE, BLEND_NORMAL + BLEND_NONE, BLEND_NORMAL, PROJECTION_PERSPECTIVE, PROJECTION_ORTHOGRAPHIC, ASPECT_MANUAL, ASPECT_AUTO } from '../../scene/constants.js'; import { calculateNormals } from '../../scene/procedural.js'; import { GraphNode } from '../../scene/graph-node.js'; @@ -1222,6 +1223,42 @@ var createModel = function (name, meshGroup, materials, defaultMaterial) { return model; }; +var createCamera = function (gltfCamera, node) { + var cameraProps = { + enabled: false + }; + + if (gltfCamera.type === "orthographic") { + const orthographic = gltfCamera.orthographic; + const xMag = orthographic.xmag; + const yMag = orthographic.ymag; + const aspectRatio = xMag !== undefined ? xMag / yMag : undefined; + + Object.assign(cameraProps, { + projection: PROJECTION_ORTHOGRAPHIC, + aspectRatioMode: aspectRatio !== undefined ? ASPECT_MANUAL : ASPECT_AUTO, + aspectRatio: aspectRatio, + orthoHeight: yMag, + farClip: orthographic.zfar, + nearClip: orthographic.znear + }); + } else { + const perspective = gltfCamera.perspective; + const aspectRatio = perspective.aspectRatio; + + Object.assign(cameraProps, { + projection: PROJECTION_PERSPECTIVE, + aspectRatioMode: aspectRatio !== undefined ? ASPECT_MANUAL : ASPECT_AUTO, + aspectRatio: aspectRatio, + fov: perspective.yfov * math.RAD_TO_DEG, + farClip: perspective.zfar, + nearClip: perspective.znear + }); + } + + return node.addComponent("camera", cameraProps); +}; + var createSkinInstances = function (model, skin) { return model.meshInstances.map(function (meshInstance) { meshInstance.mesh.skin = skin; @@ -1422,6 +1459,38 @@ var createNodeModels = function (gltf, nodeComponents, models, skins) { return nodeModels; }; +var createCameras = function (gltf, nodes, options) { + if (!gltf.hasOwnProperty('cameras') || gltf.cameras.length === 0) { + return []; + } + + var preprocess = options && options.cameras && options.cameras.preprocess; + var process = options && options.cameras && options.cameras.process || createCamera; + var postprocess = options && options.cameras && options.cameras.postprocess; + + var cameras = []; + + gltf.nodes.forEach(function (gltfNode, nodeIndex) { + if (!gltfNode.hasOwnProperty('camera')) { + return; + } + var gltfCamera = gltf.cameras[gltfNode.camera]; + if (!gltfCamera) { + return; + } + if (preprocess) { + preprocess(gltfCamera); + } + var camera = process(gltfCamera, nodes[nodeIndex]); + if (postprocess) { + postprocess(gltfCamera, camera); + } + cameras.push(camera); + }); + + return cameras; +}; + // create engine resources from the downloaded GLB data var createResources = function (device, gltf, buffers, textures, defaultMaterial, options, callback) { @@ -1436,6 +1505,7 @@ var createResources = function (device, gltf, buffers, textures, defaultMaterial var nodeComponents = createEmptyNodeComponents(nodes); var scenes = createScenes(gltf, nodes, options); var scene = getDefaultScene(gltf, scenes); + var cameras = createCameras(gltf, nodes, options); var animations = createAnimations(gltf, nodes, nodeComponents, buffers, options); var materials = createMaterials(gltf, gltf.textures ? gltf.textures.map(function (t) { return textures[t.source].resource; @@ -1454,7 +1524,8 @@ var createResources = function (device, gltf, buffers, textures, defaultMaterial 'scenes': scenes, 'scene': scene, 'textures': textures, - 'materials': materials + 'materials': materials, + 'cameras': cameras }; if (postprocess) { From 227596558779dd3cc4031a3c2339c53b4d3cf622 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Thu, 23 Jul 2020 17:43:32 +0200 Subject: [PATCH 124/144] feat(GlbParser): add support for KHR_lights_punctual --- src/resources/container.js | 7 +++ src/resources/parser/glb-parser.js | 86 ++++++++++++++++++++++++++++-- 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index fb53a3a95ea..b47d9607d42 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -25,6 +25,7 @@ import { GlbParser } from './parser/glb-parser.js'; * @property {pc.Entity|null} scene The root entity of the default scene. * @property {pc.Entity[]} scenes The root entities of all scenes. * @property {pc.CameraComponent[]} cameras Camera components. + * @property {pc.LightComponent[]} lights Light components. * @property {pc.Entity[]} nodes Entity per GLB node. * @property {pc.Asset[]} materials Material assets. * @property {pc.Asset[]} textures Texture assets per GLB image. @@ -38,6 +39,7 @@ function ContainerResource(data) { this.scene = null; this.scenes = []; this.cameras = []; + this.lights = []; this.nodes = []; this.materials = []; this.textures = []; @@ -79,6 +81,10 @@ Object.assign(ContainerResource.prototype, { this.cameras = null; } + if (this.lights) { + this.lights = null; + } + if (this.nodes) { this.nodes.forEach(function (node) { node.destroy(); @@ -270,6 +276,7 @@ Object.assign(ContainerHandler.prototype, { container.scene = data.scene; // scenes are not wrapped in an Asset container.scenes = data.scenes; // scenes are not wrapped in an Asset container.cameras = data.cameras; // camera components are not wrapped in an Asset + container.lights = data.lights; // light components are not wrapped in an Asset container.nodes = data.nodes; // nodes are not wrapped in an Asset container.materials = materialAssets; container.textures = data.textures; // texture assets are created directly diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index df269c5812a..86657fbbc36 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1,4 +1,5 @@ import { path } from '../../core/path.js'; +import { Color } from '../../core/color.js'; import { http } from '../../net/http.js'; @@ -25,7 +26,7 @@ import { VertexBuffer } from '../../graphics/vertex-buffer.js'; import { VertexFormat } from '../../graphics/vertex-format.js'; import { - BLEND_NONE, BLEND_NORMAL, PROJECTION_PERSPECTIVE, PROJECTION_ORTHOGRAPHIC, ASPECT_MANUAL, ASPECT_AUTO + BLEND_NONE, BLEND_NORMAL, PROJECTION_PERSPECTIVE, PROJECTION_ORTHOGRAPHIC, ASPECT_MANUAL, ASPECT_AUTO, LIGHTFALLOFF_INVERSESQUARED } from '../../scene/constants.js'; import { calculateNormals } from '../../scene/procedural.js'; import { GraphNode } from '../../scene/graph-node.js'; @@ -1259,6 +1260,49 @@ var createCamera = function (gltfCamera, node) { return node.addComponent("camera", cameraProps); }; +var createLight = function (gltfLight, node) { + var lightProps = { + type: gltfLight.type, + falloffMode: LIGHTFALLOFF_INVERSESQUARED + }; + + if (gltfLight.hasOwnProperty('color')) { + lightProps.color = new Color(...gltfLight.color); + } + + if (gltfLight.hasOwnProperty('intensity')) { + // TODO: Tweak intensity to match glTF specification. Point and spot lights use luminous + // intensity in candela (lm/sr) while directional lights use illuminance in lux (lm/m2). + lightProps.intensity = gltfLight.intensity; + } else { + lightProps.intensity = 1; + } + + if (gltfLight.hasOwnProperty('range')) { + lightProps.range = gltfLight.range; + } else { + // TODO: Better way of representing infinite range + lightProps.range = 99999999; + } + + if (gltfLight.hasOwnProperty('spot') && gltfLight.spot.hasOwnProperty('innerConeAngle')) { + lightProps.innerConeAngle = gltfLight.spot.innerConeAngle * math.RAD_TO_DEG; + } else { + lightProps.innerConeAngle = 0; + } + + if (gltfLight.hasOwnProperty('spot') && gltfLight.spot.hasOwnProperty('outerConeAngle')) { + lightProps.outerConeAngle = gltfLight.spot.outerConeAngle * math.RAD_TO_DEG; + } + + // Rotate to match light orientation in glTF specification + const cameraNode = new Entity(node.name); + cameraNode.rotateLocal(90, 0, 0); + node.addChild(cameraNode); + + return cameraNode.addComponent("light", lightProps); +}; + var createSkinInstances = function (model, skin) { return model.meshInstances.map(function (meshInstance) { meshInstance.mesh.skin = skin; @@ -1460,7 +1504,7 @@ var createNodeModels = function (gltf, nodeComponents, models, skins) { }; var createCameras = function (gltf, nodes, options) { - if (!gltf.hasOwnProperty('cameras') || gltf.cameras.length === 0) { + if (!gltf.hasOwnProperty('nodes') || !gltf.hasOwnProperty('cameras') || gltf.cameras.length === 0) { return []; } @@ -1491,6 +1535,40 @@ var createCameras = function (gltf, nodes, options) { return cameras; }; +var createLights = function (gltf, nodes) { + if (!gltf.hasOwnProperty('nodes') || + !gltf.hasOwnProperty('extensions') || + !gltf.extensions.hasOwnProperty('KHR_lights_punctual') || + !gltf.extensions.KHR_lights_punctual.hasOwnProperty('lights')) { + return []; + } + + var gltfLights = gltf.extensions.KHR_lights_punctual.lights; + if (gltfLights.length === 0) { + return []; + } + + var lights = []; + + gltf.nodes.forEach(function (gltfNode, nodeIndex) { + if (!gltfNode.hasOwnProperty('extensions') || + !gltfNode.extensions.hasOwnProperty('KHR_lights_punctual') || + !gltfNode.extensions.KHR_lights_punctual.hasOwnProperty('light')) { + return; + } + + var lightIndex = gltfNode.extensions.KHR_lights_punctual.light; + var gltfLight = gltfLights[lightIndex]; + if (!gltfLight) { + return; + } + + lights.push(createLight(gltfLight, nodes[nodeIndex])); + }); + + return lights; +}; + // create engine resources from the downloaded GLB data var createResources = function (device, gltf, buffers, textures, defaultMaterial, options, callback) { @@ -1506,6 +1584,7 @@ var createResources = function (device, gltf, buffers, textures, defaultMaterial var scenes = createScenes(gltf, nodes, options); var scene = getDefaultScene(gltf, scenes); var cameras = createCameras(gltf, nodes, options); + var lights = createLights(gltf, nodes); var animations = createAnimations(gltf, nodes, nodeComponents, buffers, options); var materials = createMaterials(gltf, gltf.textures ? gltf.textures.map(function (t) { return textures[t.source].resource; @@ -1525,7 +1604,8 @@ var createResources = function (device, gltf, buffers, textures, defaultMaterial 'scene': scene, 'textures': textures, 'materials': materials, - 'cameras': cameras + 'cameras': cameras, + 'lights': lights }; if (postprocess) { From de9a563cb5194e5389413cf5ac1b08edf8d71fe5 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 24 Jul 2020 11:52:27 +0200 Subject: [PATCH 125/144] feat(GlbParser): optimize reusing skin instances as implemented in https://github.com/playcanvas/engine/pull/2275 --- src/resources/parser/glb-parser.js | 35 ++++++++++++++++-------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index a0a67cf66ac..53a1c17f15e 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1369,18 +1369,6 @@ var createLight = function (gltfLight, node) { return cameraNode.addComponent("light", lightProps); }; -var createSkinInstances = function (model, skin) { - return model.meshInstances.map(function (meshInstance) { - meshInstance.mesh.skin = skin; - - var skinInstance = new SkinInstance(skin); - skinInstance.bones = skin.bones; - - meshInstance.skinInstance = skinInstance; - return skinInstance; - }); -}; - var createSkins = function (device, gltf, nodes, buffers) { if (!gltf.hasOwnProperty('skins') || gltf.skins.length === 0) { return []; @@ -1390,6 +1378,14 @@ var createSkins = function (device, gltf, nodes, buffers) { }); }; +var createSkinInstances = function (skins) { + return skins.map(function (skin) { + var skinInstance = new SkinInstance(skin); + skinInstance.bones = skin.bones; + return skinInstance; + }); +}; + var createMeshGroups = function (device, gltf, buffers, callback, disableFlipV) { if (!gltf.hasOwnProperty('meshes') || gltf.meshes.length === 0 || !gltf.hasOwnProperty('accessors') || gltf.accessors.length === 0 || @@ -1541,7 +1537,7 @@ var createModels = function (meshGroups, materials, defaultMaterial) { }); }; -var createNodeModels = function (gltf, nodeComponents, models, skins) { +var createNodeModels = function (gltf, nodeComponents, models, skins, skinInstances) { if (!gltf.hasOwnProperty('nodes') || gltf.nodes.length === 0) { return []; } @@ -1555,8 +1551,14 @@ var createNodeModels = function (gltf, nodeComponents, models, skins) { var model = models[gltfNode.mesh].clone(); var skin = gltfNode.hasOwnProperty('skin') ? skins[gltfNode.skin] : null; - if (skin !== null) { - model.skinInstances = createSkinInstances(model, skin); + var skinInstance = gltfNode.hasOwnProperty('skin') ? skinInstances[gltfNode.skin] : null; + + if (skin !== null && skinInstance !== null) { + model.skinInstances = model.meshInstances.map(function (meshInstance) { + meshInstance.mesh.skin = skin; + meshInstance.skinInstance = skinInstance; + return skinInstance; + }); } var nodeModelIndex = nodeModels.push(model) - 1; @@ -1663,8 +1665,9 @@ var createResources = function (device, gltf, buffers, textures, defaultMaterial }) : [], options, disableFlipV); var meshGroups = createMeshGroups(device, gltf, buffers, callback, disableFlipV); var skins = createSkins(device, gltf, nodes, buffers); + var skinInstances = createSkinInstances(skins); var models = createModels(meshGroups, materials, defaultMaterial); - var nodeModels = createNodeModels(gltf, nodeComponents, models, skins); + var nodeModels = createNodeModels(gltf, nodeComponents, models, skins, skinInstances); var result = { 'nodes': nodes, From 546d1384936f3d0672e663f2a217d67d17adc3ff Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 24 Jul 2020 12:33:13 +0200 Subject: [PATCH 126/144] refactor(GlbParser): simplify creation of nodeModels by removing need for nodeComponents --- src/resources/container.js | 16 ++++++++++------ src/resources/parser/glb-parser.js | 20 +++++--------------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index 73b1df6bdb9..0da9353f96a 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -61,7 +61,11 @@ Object.assign(ContainerResource.prototype, { }; var destroyAssets = function (assets) { - assets.forEach(destroyAsset); + assets.forEach(function (asset) { + if (asset) { + destroyAsset(asset); + } + }); }; // destroy entities @@ -248,7 +252,7 @@ Object.assign(ContainerHandler.prototype, { // create node model assets var nodeModelAssets = data.nodeModels.map(function (model, index) { - return createAsset('model', model, index); + return model !== null ? createAsset('model', model, index) : null; }); // create material assets @@ -274,11 +278,11 @@ Object.assign(ContainerHandler.prototype, { // add model components to nodes data.nodes.forEach(function (node, nodeIndex) { - var components = data.nodeComponents[nodeIndex]; - if (components.model !== null) { + var modelAsset = nodeModelAssets[nodeIndex]; + if (modelAsset !== null) { node.addComponent('model', { type: 'asset', - asset: nodeModelAssets[components.model] + asset: modelAsset }); } }); @@ -290,7 +294,7 @@ Object.assign(ContainerHandler.prototype, { container.lights = data.lights; // light components are not wrapped in an Asset container.nodes = data.nodes; // nodes are not wrapped in an Asset container.materials = materialAssets; - container.textures = data.textures; // texture assets are created directly + container.textures = data.textures; // texture assets are created in parser container.animations = animationAssets; container.nodeAnimations = nodeAnimations; container.models = modelAssets; diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 53a1c17f15e..e016674d587 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1493,7 +1493,6 @@ var createNodes = function (gltf, options) { var createEmptyNodeComponents = function (nodes) { return nodes.map(function () { return { - model: null, animations: [] }; }); @@ -1537,16 +1536,14 @@ var createModels = function (meshGroups, materials, defaultMaterial) { }); }; -var createNodeModels = function (gltf, nodeComponents, models, skins, skinInstances) { +var createNodeModels = function (gltf, models, skins, skinInstances) { if (!gltf.hasOwnProperty('nodes') || gltf.nodes.length === 0) { return []; } - var nodeModels = []; - - gltf.nodes.forEach(function (gltfNode, nodeIndex) { + return gltf.nodes.map(function (gltfNode) { if (!gltfNode.hasOwnProperty('mesh')) { - return; + return null; } var model = models[gltfNode.mesh].clone(); @@ -1561,15 +1558,8 @@ var createNodeModels = function (gltf, nodeComponents, models, skins, skinInstan }); } - var nodeModelIndex = nodeModels.push(model) - 1; - - if (!nodeComponents[nodeIndex]) { - nodeComponents[nodeIndex] = {}; - } - nodeComponents[nodeIndex].model = nodeModelIndex; + return model; }); - - return nodeModels; }; var createCameras = function (gltf, nodes, options) { @@ -1667,7 +1657,7 @@ var createResources = function (device, gltf, buffers, textures, defaultMaterial var skins = createSkins(device, gltf, nodes, buffers); var skinInstances = createSkinInstances(skins); var models = createModels(meshGroups, materials, defaultMaterial); - var nodeModels = createNodeModels(gltf, nodeComponents, models, skins, skinInstances); + var nodeModels = createNodeModels(gltf, models, skins, skinInstances); var result = { 'nodes': nodes, From 59510bb429af25804fec4a1368ae57b0426f4428 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 24 Jul 2020 12:34:07 +0200 Subject: [PATCH 127/144] refactor(GlbParser): replace nodeComponents with nodeAnimations --- src/resources/container.js | 24 ++--------------- src/resources/parser/glb-parser.js | 43 ++++++++++++++---------------- 2 files changed, 22 insertions(+), 45 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index 0da9353f96a..a4b6b46dc49 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -6,15 +6,6 @@ import { Asset } from '../asset/asset.js'; import { GlbParser } from './parser/glb-parser.js'; -/** - * Maps an entity to a number of animation assets. Animations can be added to the node with either - * pc.AnimationComponent or pc.AnimComponent. - * - * @typedef {object} pc.ContainerResourceAnimationMapping - * @property {pc.Entity} node Entity that should be animated. - * @property {number[]} animations Indexes of animations assets to be assigned to node. - */ - /** * @class * @name pc.ContainerResource @@ -30,7 +21,7 @@ import { GlbParser } from './parser/glb-parser.js'; * @property {pc.Asset[]} materials Material assets. * @property {pc.Asset[]} textures Texture assets per GLB image. * @property {pc.Asset[]} animations Animation assets. - * @property {pc.ContainerResourceAnimationMapping[]} nodeAnimations Mapping of animations to node entities. + * @property {number[][]} nodeAnimations Animation asset indices per node. * @property {pc.Asset[]} models Model assets per GLB mesh. * @property {pc.AssetRegistry} registry The asset registry. */ @@ -265,17 +256,6 @@ Object.assign(ContainerHandler.prototype, { return createAsset('animation', animation, index); }); - // create mapping from nodes to animations - var nodeAnimations = data.nodes - .map(function (node, nodeIndex) { - return { - node: node, - animations: data.nodeComponents[nodeIndex].animations - }; - }).filter(function (mapping) { - return mapping.animations.length > 0; - }); - // add model components to nodes data.nodes.forEach(function (node, nodeIndex) { var modelAsset = nodeModelAssets[nodeIndex]; @@ -296,7 +276,7 @@ Object.assign(ContainerHandler.prototype, { container.materials = materialAssets; container.textures = data.textures; // texture assets are created in parser container.animations = animationAssets; - container.nodeAnimations = nodeAnimations; + container.nodeAnimations = data.nodeAnimations; container.models = modelAssets; container._nodeModels = nodeModelAssets; // keep model refs for when container is destroyed container.registry = assets; diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index e016674d587..cfbfd2bff7e 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1419,19 +1419,26 @@ var createMaterials = function (gltf, textures, options, disableFlipV) { }); }; -var createAnimations = function (gltf, nodes, nodeComponents, buffers, options) { - if (!gltf.hasOwnProperty('animations') || gltf.animations.length === 0) { +var createAnimations = function (gltf, nodes, buffers, options) { + var nodeAnimations = nodes.map(function () { return []; + }); + + if (!gltf.hasOwnProperty('animations') || gltf.animations.length === 0) { + return { + animations: [], + nodeAnimations: nodeAnimations + }; } var preprocess = options && options.animation && options.animation.preprocess; var postprocess = options && options.animation && options.animation.postprocess; - return gltf.animations.map(function (gltfAnimation, index) { + var animations = gltf.animations.map(function (gltfAnimation, animationIndex) { if (preprocess) { preprocess(gltfAnimation); } - var animation = createAnimation(gltfAnimation, index, gltf.accessors, gltf.bufferViews, gltf.nodes, nodes, buffers); + var animation = createAnimation(gltfAnimation, animationIndex, gltf.accessors, gltf.bufferViews, gltf.nodes, nodes, buffers); if (postprocess) { postprocess(gltfAnimation, animation.track); } @@ -1440,17 +1447,16 @@ var createAnimations = function (gltf, nodes, nodeComponents, buffers, options) // animation track since the locator path in animation curves is relative // to its targets root node animation.targetRootNodes.forEach(function (rootNode) { - if (!nodeComponents[rootNode]) { - nodeComponents[rootNode] = {}; - } - if (!nodeComponents[rootNode].animations) { - nodeComponents[rootNode].animations = []; - } - nodeComponents[rootNode].animations.push(index); + nodeAnimations[rootNode].push(animationIndex); }); return animation.track; }); + + return { + animations: animations, + nodeAnimations: nodeAnimations + }; }; var createNodes = function (gltf, options) { @@ -1490,14 +1496,6 @@ var createNodes = function (gltf, options) { return nodes; }; -var createEmptyNodeComponents = function (nodes) { - return nodes.map(function () { - return { - animations: [] - }; - }); -}; - var createScenes = function (gltf, nodes, options) { if (!gltf.hasOwnProperty('scenes') || gltf.scenes.length === 0) { return []; @@ -1644,12 +1642,11 @@ var createResources = function (device, gltf, buffers, textures, defaultMaterial var disableFlipV = gltf.asset && gltf.asset.generator === 'PlayCanvas'; var nodes = createNodes(gltf, options); - var nodeComponents = createEmptyNodeComponents(nodes); var scenes = createScenes(gltf, nodes, options); var scene = getDefaultScene(gltf, scenes); var cameras = createCameras(gltf, nodes, options); var lights = createLights(gltf, nodes); - var animations = createAnimations(gltf, nodes, nodeComponents, buffers, options); + var animations = createAnimations(gltf, nodes, buffers, options); var materials = createMaterials(gltf, gltf.textures ? gltf.textures.map(function (t) { return textures[t.source].resource; }) : [], options, disableFlipV); @@ -1661,10 +1658,10 @@ var createResources = function (device, gltf, buffers, textures, defaultMaterial var result = { 'nodes': nodes, - 'nodeComponents': nodeComponents, 'models': models, 'nodeModels': nodeModels, - 'animations': animations, + 'animations': animations.animations, + 'nodeAnimations': animations.nodeAnimations, 'scenes': scenes, 'scene': scene, 'textures': textures, From a23b3be17bc214222c2147073bdc5a2d3c941b00 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 24 Jul 2020 12:37:09 +0200 Subject: [PATCH 128/144] chore(ContainerResource): remove unnecessary comments --- src/resources/container.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index a4b6b46dc49..1ba67824f68 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -268,11 +268,11 @@ Object.assign(ContainerHandler.prototype, { }); container.data = null; // since assets are created, release GLB data - container.scene = data.scene; // scenes are not wrapped in an Asset - container.scenes = data.scenes; // scenes are not wrapped in an Asset - container.cameras = data.cameras; // camera components are not wrapped in an Asset - container.lights = data.lights; // light components are not wrapped in an Asset - container.nodes = data.nodes; // nodes are not wrapped in an Asset + container.scene = data.scene; + container.scenes = data.scenes; + container.cameras = data.cameras; + container.lights = data.lights; + container.nodes = data.nodes; container.materials = materialAssets; container.textures = data.textures; // texture assets are created in parser container.animations = animationAssets; From 1c70dbf90c57c3b0062307a9205afd05db366f5b Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 24 Jul 2020 14:03:01 +0200 Subject: [PATCH 129/144] feat(ContainerResource): include both images and textures in container --- src/resources/container.js | 10 +++++++--- src/resources/parser/glb-parser.js | 21 +++++++++++++-------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index 1ba67824f68..a2a7a5bbaa2 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -34,6 +34,7 @@ function ContainerResource(data) { this.nodes = []; this.materials = []; this.textures = []; + this.images = []; this.animations = []; this.nodeAnimations = []; this.models = []; @@ -107,8 +108,10 @@ Object.assign(ContainerResource.prototype, { this._nodeModels = null; } - if (this.textures) { - destroyAssets(this.textures); + if (this.images) { + // This will destroy all textures as well, since they are a subset of images + destroyAssets(this.images); + this.images = null; this.textures = null; } @@ -127,7 +130,7 @@ Object.assign(ContainerResource.prototype, { * @name pc.ContainerHandler * @implements {pc.ResourceHandler} * @classdesc Loads files that contain multiple resources. For example glTF files can contain - * textures, scenes and animations. + * textures, scenes, animations and more. * The asset options object can be used for passing in load time callbacks to handle the various resources * at different stages of loading as follows: * ``` @@ -275,6 +278,7 @@ Object.assign(ContainerHandler.prototype, { container.nodes = data.nodes; container.materials = materialAssets; container.textures = data.textures; // texture assets are created in parser + container.images = data.images; // texture assets are created in parser container.animations = animationAssets; container.nodeAnimations = data.nodeAnimations; container.models = modelAssets; diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index cfbfd2bff7e..95589d3954d 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1627,7 +1627,7 @@ var createLights = function (gltf, nodes) { }; // create engine resources from the downloaded GLB data -var createResources = function (device, gltf, buffers, textures, defaultMaterial, options, callback) { +var createResources = function (device, gltf, buffers, imageTextures, defaultMaterial, options, callback) { var preprocess = options && options.global && options.global.preprocess; var postprocess = options && options.global && options.global.postprocess; @@ -1636,6 +1636,10 @@ var createResources = function (device, gltf, buffers, textures, defaultMaterial preprocess(gltf); } + var textures = gltf.textures ? gltf.textures.map(function (t) { + return imageTextures[t.source]; + }) : []; + // The original version of FACT generated incorrectly flipped V texture // coordinates. We must compensate by -not- flipping V in this case. Once // all models have been re-exported we can remove this flag. @@ -1647,9 +1651,9 @@ var createResources = function (device, gltf, buffers, textures, defaultMaterial var cameras = createCameras(gltf, nodes, options); var lights = createLights(gltf, nodes); var animations = createAnimations(gltf, nodes, buffers, options); - var materials = createMaterials(gltf, gltf.textures ? gltf.textures.map(function (t) { - return textures[t.source].resource; - }) : [], options, disableFlipV); + var materials = createMaterials(gltf, textures.map(function (textureAsset) { + return textureAsset.resource; + }), options, disableFlipV); var meshGroups = createMeshGroups(device, gltf, buffers, callback, disableFlipV); var skins = createSkins(device, gltf, nodes, buffers); var skinInstances = createSkinInstances(skins); @@ -1664,6 +1668,7 @@ var createResources = function (device, gltf, buffers, textures, defaultMaterial 'nodeAnimations': animations.nodeAnimations, 'scenes': scenes, 'scene': scene, + 'images': imageTextures, 'textures': textures, 'materials': materials, 'cameras': cameras, @@ -2025,13 +2030,13 @@ GlbParser.parseAsync = function (filename, urlBase, data, device, defaultMateria } // async load images - loadTexturesAsync(gltf, buffers, urlBase, registry, options, function (err, textures) { + loadTexturesAsync(gltf, buffers, urlBase, registry, options, function (err, imageTextures) { if (err) { callback(err); return; } - createResources(device, gltf, buffers, textures, defaultMaterial, options, callback); + createResources(device, gltf, buffers, imageTextures, defaultMaterial, options, callback); }); }); }); @@ -2053,10 +2058,10 @@ GlbParser.parse = function (filename, data, device, defaultMaterial, options) { console.error(err); } else { var buffers = [chunks.binaryChunk]; - var textures = []; + var imageTextures = []; // create resources - createResources(device, gltf, buffers, textures, defaultMaterial, options || { }, function (err, result_) { + createResources(device, gltf, buffers, imageTextures, defaultMaterial, options || { }, function (err, result_) { if (err) { console.error(err); } else { From b057e3a118ae3cbcb8d1de0df924baab2faa9062 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 24 Jul 2020 14:03:20 +0200 Subject: [PATCH 130/144] chore(ContainerResource): update jsdoc --- src/resources/container.js | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index a2a7a5bbaa2..141baf99e91 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -9,20 +9,21 @@ import { GlbParser } from './parser/glb-parser.js'; /** * @class * @name pc.ContainerResource - * @classdesc Container for a list of animations, textures, materials, models, scenes (as entities) - * and a default scene (as entity). Entities in scene hierarchies will have model and animation components - * attached to them. + * @classdesc Container for a list of animations, images, textures, materials, models, nodes, scenes, default scene + * cameras and lights. Entities in scene hierarchies will have model, camera and light components attached to them. + * Animation components have to be added manually (using nodeAnimations) as either pc.AnimComponent or pc.AnimationComponent. * @param {object} data - The loaded GLB data. - * @property {pc.Entity|null} scene The root entity of the default scene. - * @property {pc.Entity[]} scenes The root entities of all scenes. - * @property {pc.CameraComponent[]} cameras Camera components. - * @property {pc.LightComponent[]} lights Light components. - * @property {pc.Entity[]} nodes Entity per GLB node. - * @property {pc.Asset[]} materials Material assets. - * @property {pc.Asset[]} textures Texture assets per GLB image. - * @property {pc.Asset[]} animations Animation assets. - * @property {number[][]} nodeAnimations Animation asset indices per node. - * @property {pc.Asset[]} models Model assets per GLB mesh. + * @property {pc.Entity|null} scene Root entity of the default GLB scene. + * @property {pc.Entity[]} scenes Root entities of scenes indexed by GLB scenes. + * @property {pc.CameraComponent[]} cameras Instanced camera components, does not match index of GLB cameras. + * @property {pc.LightComponent[]} lights Instanced light components, does not match index of GLB lights. + * @property {pc.Entity[]} nodes Entities indexed by GLB nodes. + * @property {pc.Asset[]} materials Material assets indexed by GLB materials. + * @property {pc.Asset[]} textures Texture assets indexed by GLB textures. + * @property {pc.Asset[]} images Texture assets indexed by GLB images. + * @property {pc.Asset[]} animations Animation assets indexed by GLB animations. + * @property {number[][]} nodeAnimations Animation asset indices indexed by GLB nodes. + * @property {pc.Asset[]} models Model assets indexed by GLB meshes. * @property {pc.AssetRegistry} registry The asset registry. */ function ContainerResource(data) { From c944b6770afd6333797a3f5ff2892779744ed562 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Tue, 28 Jul 2020 14:22:41 +0200 Subject: [PATCH 131/144] refactor(AnimState): remove duplicate property "loop" --- src/framework/components/anim/controller.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/framework/components/anim/controller.js b/src/framework/components/anim/controller.js index 1e1aac16fab..ca50ea7fb07 100644 --- a/src/framework/components/anim/controller.js +++ b/src/framework/components/anim/controller.js @@ -44,11 +44,6 @@ Object.defineProperties(AnimState.prototype, { return (this.animations.length > 0 || this.name === ANIM_STATE_START || this.name === ANIM_STATE_END); } }, - loop: { - get: function () { - return this._loop; - } - }, looping: { get: function () { if (this.animations.length > 0) { From eb853e43d0c9366c1079b8c0b597990462a52f60 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Thu, 30 Jul 2020 16:19:30 +0200 Subject: [PATCH 132/144] fix(GlbParser): fix incorrect reference to camera callbacks --- src/resources/parser/glb-parser.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 931dff90cb5..8c79f028fd4 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1543,9 +1543,9 @@ var createCameras = function (gltf, nodes, options) { return []; } - var preprocess = options && options.cameras && options.cameras.preprocess; - var process = options && options.cameras && options.cameras.process || createCamera; - var postprocess = options && options.cameras && options.cameras.postprocess; + var preprocess = options && options.camera && options.camera.preprocess; + var process = options && options.camera && options.camera.process || createCamera; + var postprocess = options && options.camera && options.camera.postprocess; var cameras = []; From f5ae9b294421f0b755c2faaf842d3f471c57cacd Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 21 Aug 2020 17:06:12 +0200 Subject: [PATCH 133/144] refactor(GlbParser): use Number.POSITIVE_INFINITY for infinite light range --- src/resources/parser/glb-parser.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 8c79f028fd4..848935c4be0 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1325,8 +1325,7 @@ var createLight = function (gltfLight, node) { if (gltfLight.hasOwnProperty('range')) { lightProps.range = gltfLight.range; } else { - // TODO: Better way of representing infinite range - lightProps.range = 99999999; + lightProps.range = Number.POSITIVE_INFINITY; } if (gltfLight.hasOwnProperty('spot') && gltfLight.spot.hasOwnProperty('innerConeAngle')) { From 7a29662b03fcd1beb5261c49313036437dcec3c2 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Fri, 21 Aug 2020 17:10:34 +0200 Subject: [PATCH 134/144] refactor(GlbParser): remove use of Object.assign for camera props --- src/resources/parser/glb-parser.js | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 848935c4be0..6e483392e42 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1279,26 +1279,22 @@ var createCamera = function (gltfCamera, node) { const yMag = orthographic.ymag; const aspectRatio = xMag !== undefined ? xMag / yMag : undefined; - Object.assign(cameraProps, { - projection: PROJECTION_ORTHOGRAPHIC, - aspectRatioMode: aspectRatio !== undefined ? ASPECT_MANUAL : ASPECT_AUTO, - aspectRatio: aspectRatio, - orthoHeight: yMag, - farClip: orthographic.zfar, - nearClip: orthographic.znear - }); + cameraProps.projection = PROJECTION_ORTHOGRAPHIC; + cameraProps.aspectRatioMode = aspectRatio !== undefined ? ASPECT_MANUAL : ASPECT_AUTO; + cameraProps.aspectRatio = aspectRatio; + cameraProps.orthoHeight = yMag; + cameraProps.farClip = orthographic.zfar; + cameraProps.nearClip = orthographic.znear; } else { const perspective = gltfCamera.perspective; const aspectRatio = perspective.aspectRatio; - Object.assign(cameraProps, { - projection: PROJECTION_PERSPECTIVE, - aspectRatioMode: aspectRatio !== undefined ? ASPECT_MANUAL : ASPECT_AUTO, - aspectRatio: aspectRatio, - fov: perspective.yfov * math.RAD_TO_DEG, - farClip: perspective.zfar, - nearClip: perspective.znear - }); + cameraProps.projection = PROJECTION_PERSPECTIVE; + cameraProps.aspectRatioMode = aspectRatio !== undefined ? ASPECT_MANUAL : ASPECT_AUTO; + cameraProps.aspectRatio = aspectRatio; + cameraProps.fov = perspective.yfov * math.RAD_TO_DEG; + cameraProps.farClip = perspective.zfar; + cameraProps.nearClip = perspective.znear; } return node.addComponent("camera", cameraProps); From a792880a7ef3e032475e2f5d8c78a3847280b1d4 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Mon, 24 Aug 2020 12:46:32 +0200 Subject: [PATCH 135/144] refactor(GlbParser): rename variable --- src/resources/parser/glb-parser.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 6e483392e42..53920849067 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1335,11 +1335,11 @@ var createLight = function (gltfLight, node) { } // Rotate to match light orientation in glTF specification - const cameraNode = new Entity(node.name); - cameraNode.rotateLocal(90, 0, 0); - node.addChild(cameraNode); + const lightNode = new Entity(node.name); + lightNode.rotateLocal(90, 0, 0); + node.addChild(lightNode); - return cameraNode.addComponent("light", lightProps); + return lightNode.addComponent("light", lightProps); }; var createSkins = function (device, gltf, nodes, bufferViews) { From 29a8d99be65ade632e05479060cbce870e32fe1c Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Mon, 24 Aug 2020 12:47:08 +0200 Subject: [PATCH 136/144] feat(GlbParser): add support for light extensions --- src/resources/container.js | 1 + src/resources/parser/glb-parser.js | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index ef284b14b7f..b39c9fc8b77 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -139,6 +139,7 @@ Object.assign(ContainerResource.prototype, { * | node | x | x | | x | * | scene | x | x | | x | * | camera | x | x | | x | + * | light | x | x | | x | * | animation | x | | | x | * | material | x | x | | x | * | image | x | | x | x | diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 53920849067..2096a60d67f 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1565,7 +1565,7 @@ var createCameras = function (gltf, nodes, options) { return cameras; }; -var createLights = function (gltf, nodes) { +var createLights = function (gltf, nodes, options) { if (!gltf.hasOwnProperty('nodes') || !gltf.hasOwnProperty('extensions') || !gltf.extensions.hasOwnProperty('KHR_lights_punctual') || @@ -1578,6 +1578,10 @@ var createLights = function (gltf, nodes) { return []; } + var preprocess = options && options.light && options.light.preprocess; + var process = options && options.light && options.light.process || createLight; + var postprocess = options && options.light && options.light.postprocess; + var lights = []; gltf.nodes.forEach(function (gltfNode, nodeIndex) { @@ -1586,14 +1590,19 @@ var createLights = function (gltf, nodes) { !gltfNode.extensions.KHR_lights_punctual.hasOwnProperty('light')) { return; } - var lightIndex = gltfNode.extensions.KHR_lights_punctual.light; var gltfLight = gltfLights[lightIndex]; if (!gltfLight) { return; } - - lights.push(createLight(gltfLight, nodes[nodeIndex])); + if (preprocess) { + preprocess(gltfLight); + } + var light = process(gltfLight, nodes[nodeIndex]); + if (postprocess) { + postprocess(gltfLight, light); + } + lights.push(light); }); return lights; @@ -1617,7 +1626,7 @@ var createResources = function (device, gltf, bufferViews, textureAssets, defaul var scenes = createScenes(gltf, nodes, options); var scene = getDefaultScene(gltf, scenes); var cameras = createCameras(gltf, nodes, options); - var lights = createLights(gltf, nodes); + var lights = createLights(gltf, nodes, options); var animations = createAnimations(gltf, nodes, bufferViews, options); var materials = createMaterials(gltf, textureAssets.map(function (textureAsset) { return textureAsset.resource; From ecccb5f01051bd45bbb946aecb75b60a49fef70a Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Tue, 25 Aug 2020 11:47:13 +0200 Subject: [PATCH 137/144] refactor(ContainerResource): rename nodeAnimations to animationIndicesByNode and _nodeModels to modelByNode --- src/resources/container.js | 26 +++++++++++++------------- src/resources/parser/glb-parser.js | 16 ++++++++-------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/resources/container.js b/src/resources/container.js index b39c9fc8b77..63f52d26943 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -11,7 +11,7 @@ import { GlbParser } from './parser/glb-parser.js'; * @name pc.ContainerResource * @classdesc Container for a list of animations, textures, materials, models, nodes, scenes, default scene * cameras and lights. Entities in scene hierarchies will have model, camera and light components attached to them. - * Animation components have to be added manually (using nodeAnimations) as either pc.AnimComponent or pc.AnimationComponent. + * Animation components have to be added manually (using animationIndicesByNode) as either pc.AnimComponent or pc.AnimationComponent. * @param {object} data - The loaded GLB data. * @property {pc.Entity|null} scene Root entity of the default GLB scene. * @property {pc.Entity[]} scenes Root entities of scenes indexed by GLB scenes. @@ -21,7 +21,7 @@ import { GlbParser } from './parser/glb-parser.js'; * @property {pc.Asset[]} materials Material assets indexed by GLB materials. * @property {pc.Asset[]} textures Texture assets indexed by GLB textures. * @property {pc.Asset[]} animations Animation assets indexed by GLB animations. - * @property {number[][]} nodeAnimations Animation asset indices indexed by GLB nodes. + * @property {number[][]} animationIndicesByNode Animation asset indices indexed by GLB nodes. * @property {pc.Asset[]} models Model assets indexed by GLB meshes. * @property {pc.AssetRegistry} registry The asset registry. */ @@ -35,9 +35,9 @@ function ContainerResource(data) { this.materials = []; this.textures = []; this.animations = []; - this.nodeAnimations = []; + this.animationIndicesByNode = []; this.models = []; - this._nodeModels = []; + this._modelByNode = []; // not public since it is only used to keep model refs for when container is destroyed this.registry = null; } @@ -93,8 +93,8 @@ Object.assign(ContainerResource.prototype, { this.animations = null; } - if (this.nodeAnimations) { - this.nodeAnimations = null; + if (this.animationIndicesByNode) { + this.animationIndicesByNode = null; } if (this.models) { @@ -102,9 +102,9 @@ Object.assign(ContainerResource.prototype, { this.models = null; } - if (this._nodeModels) { - destroyAssets(this._nodeModels); - this._nodeModels = null; + if (this._modelByNode) { + destroyAssets(this._modelByNode); + this._modelByNode = null; } if (this.textures) { @@ -246,7 +246,7 @@ Object.assign(ContainerHandler.prototype, { }); // create node model assets - var nodeModelAssets = data.nodeModels.map(function (model, index) { + var modelAssetByNode = data.modelByNode.map(function (model, index) { return model !== null ? createAsset('model', model, index) : null; }); @@ -262,7 +262,7 @@ Object.assign(ContainerHandler.prototype, { // add model components to nodes data.nodes.forEach(function (node, nodeIndex) { - var modelAsset = nodeModelAssets[nodeIndex]; + var modelAsset = modelAssetByNode[nodeIndex]; if (modelAsset !== null) { node.addComponent('model', { type: 'asset', @@ -280,9 +280,9 @@ Object.assign(ContainerHandler.prototype, { container.materials = materialAssets; container.textures = data.textures; // texture assets are created in parser container.animations = animationAssets; - container.nodeAnimations = data.nodeAnimations; + container.animationIndicesByNode = data.animationIndicesByNode; container.models = modelAssets; - container._nodeModels = nodeModelAssets; // keep model refs for when container is destroyed + container._modelByNode = modelAssetByNode; // keep model refs for when container is destroyed container.registry = assets; } }); diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 2096a60d67f..5f21ee6646a 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1393,14 +1393,14 @@ var createMaterials = function (gltf, textures, options, disableFlipV) { }; var createAnimations = function (gltf, nodes, bufferViews, options) { - var nodeAnimations = nodes.map(function () { + var animationIndicesByNode = nodes.map(function () { return []; }); if (!gltf.hasOwnProperty('animations') || gltf.animations.length === 0) { return { animations: [], - nodeAnimations: nodeAnimations + animationIndicesByNode: animationIndicesByNode }; } @@ -1420,7 +1420,7 @@ var createAnimations = function (gltf, nodes, bufferViews, options) { // animation track since the locator path in animation curves is relative // to its targets root node animation.targetRootNodes.forEach(function (rootNode) { - nodeAnimations[rootNode].push(animationIndex); + animationIndicesByNode[rootNode].push(animationIndex); }); return animation.track; @@ -1428,7 +1428,7 @@ var createAnimations = function (gltf, nodes, bufferViews, options) { return { animations: animations, - nodeAnimations: nodeAnimations + animationIndicesByNode: animationIndicesByNode }; }; @@ -1507,7 +1507,7 @@ var createModels = function (meshGroups, materials, defaultMaterial) { }); }; -var createNodeModels = function (gltf, models, skins, skinInstances) { +var createModelByNode = function (gltf, models, skins, skinInstances) { if (!gltf.hasOwnProperty('nodes') || gltf.nodes.length === 0) { return []; } @@ -1635,14 +1635,14 @@ var createResources = function (device, gltf, bufferViews, textureAssets, defaul var skins = createSkins(device, gltf, nodes, bufferViews); var skinInstances = createSkinInstances(skins); var models = createModels(meshGroups, materials, defaultMaterial); - var nodeModels = createNodeModels(gltf, models, skins, skinInstances); + var modelByNode = createModelByNode(gltf, models, skins, skinInstances); var result = { 'nodes': nodes, 'models': models, - 'nodeModels': nodeModels, + 'modelByNode': modelByNode, 'animations': animations.animations, - 'nodeAnimations': animations.nodeAnimations, + 'animationIndicesByNode': animations.animationIndicesByNode, 'scenes': scenes, 'scene': scene, 'textures': textureAssets, From 4505a7236b7f9abf688e6bbea0febfa4980fbf57 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Tue, 25 Aug 2020 13:23:40 +0200 Subject: [PATCH 138/144] refactor(GlbParser): handle null AssetRegistry prefix when loading textures --- src/resources/parser/glb-parser.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 5f21ee6646a..9e70fe0b600 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1759,7 +1759,9 @@ var loadImageAsync = function (gltfImage, index, bufferViews, urlBase, registry, if (isDataURI(gltfImage.uri)) { loadTexture(gltfImage.uri, getDataURIMimeType(gltfImage.uri)); } else { - loadTexture(path.join(urlBase, gltfImage.uri).replace(registry.prefix, ""), null, "anonymous"); + // remove registry prefix from urlBase since it is added again via AssetRegistry.load + var urlBaseWithoutPrefix = registry.prefix ? urlBase.replace(registry.prefix, "") : urlBase; + loadTexture(path.join(urlBaseWithoutPrefix, gltfImage.uri), null, "anonymous"); } } else if (gltfImage.hasOwnProperty('bufferView') && gltfImage.hasOwnProperty('mimeType')) { // bufferview From ddf10695953291713438bb9aebfb7fee9c55ba4f Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Tue, 25 Aug 2020 13:50:19 +0200 Subject: [PATCH 139/144] fix(ContainerResource): fix merge issue --- src/resources/container.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/resources/container.js b/src/resources/container.js index 570994b9ead..3a47cfa693d 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -231,6 +231,16 @@ Object.assign(ContainerHandler.prototype, { var data = container.data; if (data) { + var createAsset = function (type, resource, index) { + var subAsset = new Asset(asset.name + '/' + type + '/' + index, type, { + url: '' + }); + subAsset.resource = resource; + subAsset.loaded = true; + assets.add(subAsset); + return subAsset; + }; + // create model assets var modelAssets = data.models.map(function (model, index) { return createAsset('model', model, index); From 4a513b82797e0217fcaafc5f9a51eb89545acb48 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Tue, 25 Aug 2020 15:34:42 +0200 Subject: [PATCH 140/144] fix(GlbParser): fix typo causing bugs with options.texture.postprocess --- src/resources/parser/glb-parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 0d002111407..04de209703f 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1870,7 +1870,7 @@ var loadTexturesAsync = function (gltf, bufferViews, urlBase, registry, options, applySampler(textureAsset.resource, (gltf.samplers || [])[gltf.textures[textureIndex].sampler]); result[textureIndex] = textureAsset; if (postprocess) { - postprocess(gltf.textures[index], textureAsset); + postprocess(gltf.textures[textureIndex], textureAsset); } }); }); From cd76697fc297ea2e85b696333479f1c74fa4be2c Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Tue, 25 Aug 2020 16:10:15 +0200 Subject: [PATCH 141/144] refactor(GlbParser): replace use of const with var --- src/resources/parser/glb-parser.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 04de209703f..8af0b0c9ee8 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1337,24 +1337,24 @@ var createCamera = function (gltfCamera, node) { }; if (gltfCamera.type === "orthographic") { - const orthographic = gltfCamera.orthographic; - const xMag = orthographic.xmag; - const yMag = orthographic.ymag; - const aspectRatio = xMag !== undefined ? xMag / yMag : undefined; + var orthographic = gltfCamera.orthographic; + var xMag = orthographic.xmag; + var yMag = orthographic.ymag; + var orthographicAR = xMag !== undefined ? xMag / yMag : undefined; cameraProps.projection = PROJECTION_ORTHOGRAPHIC; - cameraProps.aspectRatioMode = aspectRatio !== undefined ? ASPECT_MANUAL : ASPECT_AUTO; - cameraProps.aspectRatio = aspectRatio; + cameraProps.aspectRatioMode = orthographicAR !== undefined ? ASPECT_MANUAL : ASPECT_AUTO; + cameraProps.aspectRatio = orthographicAR; cameraProps.orthoHeight = yMag; cameraProps.farClip = orthographic.zfar; cameraProps.nearClip = orthographic.znear; } else { - const perspective = gltfCamera.perspective; - const aspectRatio = perspective.aspectRatio; + var perspective = gltfCamera.perspective; + var perspectiveAR = perspective.aspectRatio; cameraProps.projection = PROJECTION_PERSPECTIVE; - cameraProps.aspectRatioMode = aspectRatio !== undefined ? ASPECT_MANUAL : ASPECT_AUTO; - cameraProps.aspectRatio = aspectRatio; + cameraProps.aspectRatioMode = perspectiveAR !== undefined ? ASPECT_MANUAL : ASPECT_AUTO; + cameraProps.aspectRatio = perspectiveAR; cameraProps.fov = perspective.yfov * math.RAD_TO_DEG; cameraProps.farClip = perspective.zfar; cameraProps.nearClip = perspective.znear; From 1a81d8fb1a178bea90b991b84bd5b6f1e7c1c41b Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Tue, 25 Aug 2020 16:13:39 +0200 Subject: [PATCH 142/144] refactor(GlbParser): remove use of array spread syntax --- src/resources/parser/glb-parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 8af0b0c9ee8..31cb00be544 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1370,7 +1370,7 @@ var createLight = function (gltfLight, node) { }; if (gltfLight.hasOwnProperty('color')) { - lightProps.color = new Color(...gltfLight.color); + lightProps.color = new Color(gltfLight.color); } if (gltfLight.hasOwnProperty('intensity')) { From 334318b1acb0736ec0f1a98f8d2da64b031a6acd Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Tue, 25 Aug 2020 16:16:05 +0200 Subject: [PATCH 143/144] refactor(GlbParser): replace use of const with var --- src/resources/parser/glb-parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/parser/glb-parser.js b/src/resources/parser/glb-parser.js index 31cb00be544..10dedaece32 100644 --- a/src/resources/parser/glb-parser.js +++ b/src/resources/parser/glb-parser.js @@ -1398,7 +1398,7 @@ var createLight = function (gltfLight, node) { } // Rotate to match light orientation in glTF specification - const lightNode = new Entity(node.name); + var lightNode = new Entity(node.name); lightNode.rotateLocal(90, 0, 0); node.addChild(lightNode); From 455f9eb193fb4ece5d648e22fcd8e99779187c79 Mon Sep 17 00:00:00 2001 From: Samuel Johansson <@animech.com> Date: Wed, 26 Aug 2020 12:27:34 +0200 Subject: [PATCH 144/144] fix(ContainerHandler): add missing null check for container --- src/resources/container.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/container.js b/src/resources/container.js index 3a47cfa693d..4be1c719c00 100644 --- a/src/resources/container.js +++ b/src/resources/container.js @@ -228,7 +228,7 @@ Object.assign(ContainerHandler.prototype, { // Create assets to wrap the loaded engine resources - models, materials, textures and animations. patch: function (asset, assets) { var container = asset.resource; - var data = container.data; + var data = container && container.data; if (data) { var createAsset = function (type, resource, index) {