(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.jiboKeyframes = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o { layer.id = IDUtils_1.default.create(); }); } // 0.4.0 changed the coordinate system of the eye/overlay from // meters to pixel coordinates if (semver.lt(keyframes.version, "0.4.0")) { // Find each eye and overlay layer and then convert each property // that represents a translation from meters to pixel coordinates keyframes.layers.forEach((layer) => { if (layer.type === "Eye" || layer.type === "Overlay") { layer.keyframes.forEach((key) => { for (let prop in key.value) { // Everything but Scale and Rotate are x/y pairs // representing 2D translations if (prop !== "Scale" && prop !== "Rotate") { key.value[prop].x = Conversion_1.default.toPixelsX(key.value[prop].x); key.value[prop].y = Conversion_1.default.toPixelsY(key.value[prop].y); } } }); } }); } if (semver.lte(keyframes.version, '0.4.0')) { keyframes.layers.forEach((layer) => { layer.keyframes.forEach((key) => { for (let prop in key.value) { if (prop === "Tween" && key.value.Tween.value === 'body') { key.value.Tween = { display: 'cubic in out', label: 'Cubic Ease In Out', value: 'cubicInOut' }; } } }); }); } // 0.5.0 changed colors to use rgb instead of hsv for blending and internal representation if (semver.lt(keyframes.version, "0.5.0")) { keyframes.layers.forEach((layer) => { if (layer.type === "Background Texture" || layer.type === "Eye Texture" || layer.type === "Overlay Texture" || layer.type === "LED") { // Find each Color property and convert it from hsv space to rgb layer.keyframes.forEach((key) => { key.value.Color = Colors_1.default.hsvToRgb(key.value.Color.h, key.value.Color.s, key.value.Color.v); }); } }); } // 0.6.0 eye and overlay coordinates were off vertically causing squishing. This was // because the conversion between vertical meters and pixels was off. We fixed the conversion // and now we have to touch up coordinates from old files so that animations created that // compensated for the squish would be broken by this fix if (semver.lt(keyframes.version, "0.6.0")) { keyframes.layers.forEach((layer) => { if (layer.type === "Overlay" || layer.type === "Eye") { // Find each Color property and convert it from hsv space to rgb layer.keyframes.forEach((key) => { for (let field in key.value) { if (field !== 'Scale' && typeof key.value[field].y !== 'undefined') { key.value[field].y = key.value[field].y * 0.06922346 / 0.073142126; } } }); } }); } if (semver.lt(keyframes.version, "0.7.0")) { keyframes.layers.forEach((layer) => { if (layer.type === "Overlay" || layer.type === "Eye") { // Find each Color property and convert it from hsv space to rgb layer.keyframes.forEach((key) => { key.value.Alpha = 1.0; key.value.Visible = true; }); } else if (layer.type === "Pixi") { layer.keyframes.forEach((key) => { console.log("Upgrade to attach and effect"); key.value["Attach To Eye"] = false; key.value["Apply Eye Lighting"] = false; }); } }); } // Deprecate the Apply Eye Lighting and add a frame offset if (semver.lt(keyframes.version, "0.9.0")) { keyframes.layers.forEach((layer) => { if (layer.type === "Pixi") { layer.keyframes.forEach((key) => { console.log("Add frame offset, deprecated lighting"); key.value["Frame Offset"] = 0; delete key.value["Apply Eye Lighting"]; }); } }); } // 0.9 Added reference layers (No upgrade needed as it only adds a new layer type) // 0.10.0 Deprecate the Apply Eye Lighting and add a frame offset if (semver.lt(keyframes.version, "0.10.0")) { keyframes.layers.forEach((layer) => { if (layer.type === "Glow") { layer.keyframes.forEach((key) => { console.log("Deprecated halo speed on Glow"); delete key.value.HaloSpeed; }); } }); } // 0.11.0: Reference layer upgrade if (semver.lt(keyframes.version, "0.11.0")) { keyframes.layers.forEach((layer) => { // Upgrade reference layers to have "hide before" and "hide after" properties set to false if (layer.type === "Reference") { layer.keyframes.forEach((key) => { console.log("Add Hide-Before Hide-After to reference layer"); key.value["Hide Referenced Layers Before Use"] = true; key.value["Hide Referenced Layers After Use"] = true; }); } // Upgrade other layers to have their HideLayer properties set to false if (layer.type === 'Overlay Texture' || layer.type === 'Overlay' || layer.type === 'Eye Texture' || layer.type === 'Eye') { layer.keyframes.forEach((key) => { key.value["HideLayer"] = false; }); } }); } // 0.12.0: Remove 'Hide Layers Before Use' field in Reference layer. if (semver.lt(keyframes.version, "0.12.0")) { keyframes.layers.forEach((layer) => { // Upgrade reference layers to have "hide before" and "hide after" properties set to false if (layer.type === "Reference") { layer.keyframes.forEach((key) => { console.log("Remove Hide-Before and Hide-After from reference layer"); delete key.value["Hide Referenced Layers Before Use"]; key.value["HoldFinalPose"] = !key.value["Hide Referenced Layers After Use"]; delete key.value["Hide Referenced Layers After Use"]; }); } }); } // 0.13.0: Remove 'Hide Layer' field. if (semver.lt(keyframes.version, "0.13.0")) { keyframes.layers.forEach((layer) => { // Downgrade various layers to remove their HideLayer property if (layer.type === 'Overlay Texture' || layer.type === 'Overlay' || layer.type === 'Eye Texture' || layer.type === 'Eye') { layer.keyframes.forEach((key) => { delete key.value["HideLayer"]; }); } }); } // 0.14.0: Add ReferenceMode field (set to "file" mode) to reference layers. Upgrade KeysFileReference to have // an empty anim name. if (semver.lt(keyframes.version, "0.14.0")) { keyframes.layers.forEach((layer) => { if (layer.type === 'Reference') { layer.keyframes.forEach((key) => { key.value.ReferenceMode = { display: 'File Path', label: 'By File path', value: 'file' }; key.value.KeysFileReference.name = ""; }); } }); } var removeGlowAndLightingLayers = true; // 0.15.0: Remove glow and lighting layers from the file, they are deprecated from the editor. if (removeGlowAndLightingLayers && semver.lt(keyframes.version, "0.15.0")) { let newLayers = []; for (let layer of keyframes.layers) { if (layer.type === 'Glow' || layer.type === 'Lighting') { continue; } newLayers.push(layer); } keyframes.layers = newLayers; } // Make version is set to the latest keyframes.version = JiboKeyframeInfo.defaults.version; } } // Layer types that jibo supports. For efficient access, layerTypes indexes // each Layer Types constructor by it's name (ie. "Body": Body) JiboKeyframeInfo.layerTypes = { Body: BodyLayer_1.default, Eye: EyeLayer_1.default, "Eye Texture": EyeTextureLayer_1.default, Overlay: OverlayLayer_1.default, "Overlay Texture": OverlayTextureLayer_1.default, "Background Texture": BackgroundTextureLayer_1.default, LED: LEDLayer_1.default, Event: EventLayer_1.default, "Audio Event": AudioLayer_1.default, Reference: ReferenceLayer_1.default }; JiboKeyframeInfo.layerTypes.Pixi = PixiLayer_1.default; JiboKeyframeInfo.defaults = { version: "0.15.0", framerate: 30, duration: 30, scale: 1, // Layers that newly created Jibo Keyframe files have layers: [ { name: "Eye", type: "Eye" }, { name: "Body", type: "Body" }, { name: "Overlay", type: "Overlay", visible: true, locked: false, keyframes: [ { "value": { "Rotate": 0, "Scale": { "x": 1, "y": 1 }, "Translate": { "x": 0, "y": 0 }, "Visible": false, "Alpha": 1, "Tween": { "display": "sine in out", "label": "Sine Ease In Out", "value": "sineInOut" }, "Vertex 1": { "x": -EyeLayer_1.default.eyeRadiusX, "y": -EyeLayer_1.default.eyeRadiusY }, "Vertex 2": { "x": 0, "y": -EyeLayer_1.default.eyeRadiusY }, "Vertex 3": { "x": EyeLayer_1.default.eyeRadiusX, "y": -EyeLayer_1.default.eyeRadiusY }, "Vertex 4": { "x": -EyeLayer_1.default.eyeRadiusX, "y": 0 }, "Vertex 5": { "x": 0, "y": 0 }, "Vertex 6": { "x": EyeLayer_1.default.eyeRadiusX, "y": 0 }, "Vertex 7": { "x": -EyeLayer_1.default.eyeRadiusX, "y": EyeLayer_1.default.eyeRadiusY }, "Vertex 8": { "x": 0, "y": EyeLayer_1.default.eyeRadiusY }, "Vertex 9": { "x": EyeLayer_1.default.eyeRadiusX, "y": EyeLayer_1.default.eyeRadiusY } }, "time": 0 } ] } ] }; exports.default = JiboKeyframeInfo; },{"./layers/AudioLayer":6,"./layers/BackgroundTextureLayer":7,"./layers/BodyLayer":8,"./layers/EventLayer":9,"./layers/EyeLayer":11,"./layers/EyeTextureLayer":12,"./layers/LEDLayer":13,"./layers/OverlayLayer":14,"./layers/OverlayTextureLayer":15,"./layers/PixiLayer":16,"./layers/ReferenceLayer":17,"./utils/Colors":20,"./utils/Conversion":21,"./utils/IDUtils":22,"semver":undefined}],2:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); /** * Class responsible for * - saving and loading files * - manipulating the animation and notifying views of changes * - computing the values of animation at any given point in time */ const fs = require("fs"); const lodash_min_1 = require("lodash/lodash.min"); const Tweening_1 = require("./utils/Tweening"); const IDUtils_1 = require("./utils/IDUtils"); const Search_1 = require("./utils/Search"); const BlendOperations_1 = require("./utils/BlendOperations"); class Runtime { /** * Create a new keyframes object. * * @param keyframeInfo {Object} Meta data that includes supported layer types, * default values, and etc * @returns {Object} The newly created keyframes object with default layers created * */ static create(keyframeInfo) { let keyframes = { version: keyframeInfo.defaults.version, framerate: keyframeInfo.defaults.framerate, duration: keyframeInfo.defaults.duration, scale: keyframeInfo.defaults.scale, layers: [] }; keyframeInfo.defaults.layers.forEach((defaultLayer) => { keyframes.layers.push({ id: IDUtils_1.default.create(), name: defaultLayer.name, type: defaultLayer.type, visible: true, locked: false, keyframes: lodash_min_1.cloneDeep(defaultLayer.keyframes || []) }); }); return keyframes; } /** * Load an animation file into this instance * * @param uri {String} Filepath to load the keyframes file from * @param keyframeInfo {Object} Meta data that includes supported layer types, * default values, and etc * @returns {Object} Keyframes file content * * TODO: Write validation code at some point to catch issues like a file that merged incorrectly, * was corrupted, or is just the wrong file format/type. Ensure invariants such as: * - That keys are time ordered * - That key values are valid * - That all required fields exist and no extra ones do */ static load(uri, keyframeInfo) { let keyframes = JSON.parse(fs.readFileSync(uri, 'utf8')); if (keyframeInfo.onLoad) { keyframeInfo.onLoad(keyframes); } return keyframes; } /** * Save the keyframes to disk * * @param uri {String} Filepath to save to * @param keyframes {Object} Keyframe object to serialize to disk */ static save(uri, keyframes) { // Save the current animation state to disk with four spaces for indentation fs.writeFileSync(uri, JSON.stringify(keyframes, null, ' '), 'utf8'); } static _isEventLayer(layer, keyframeInfo) { if (layer) { let layerClass = keyframeInfo.layerTypes[layer.type]; return layerClass.isEvent(); } return false; } /** * Get the dof values generated from all the DOF layers at a point in time * @param keyframes {Object} Keyframes data we'll be evaluating the layers of * @param keyframeInfo {Object} Meta data that includes supported layer types, * default values, and etc * @param timeInSeconds {number} The time to compute the final dof values at * @param overrides {Object} If provided each field in this object includes a set of properties * to override the usually calculated interpolated keyframed properties. * This is useful in tools for interactively changing * the properties of a layer at a point in time without * committing a new keyframe as a user tries out different jibo poses * @return {Object} An object who's fields are each dof and it's value after all * interpolation and layer blending */ static evaluateAllDOFLayers(keyframes, keyframeInfo, timeInSeconds, overrides) { let accumulatedProps = Runtime.evaluateAllLayersFiltered(keyframes, keyframeInfo, timeInSeconds, (layer) => { return !Runtime._isEventLayer(layer, keyframeInfo); // only care about non-event layers (DOF) }, overrides); // Now that all props have been accumulated, pass them to // the layer types generateDofs() function to get our final dof values let dofs = {}; for (let layerType in accumulatedProps) { let layerClass = keyframeInfo.layerTypes[layerType]; let generatedDofs = layerClass.generateDofs(accumulatedProps[layerType]); // Copy These // TODO: Eventually multiple types of layers will be generating dof values // so blending operations will need to occur here as well. for (let dofName in generatedDofs) { dofs[dofName] = generatedDofs[dofName]; } } return dofs; } /** * Detect whether the layer was originally from a referenced layer. * (Every referenced layer's ID is prefixed with the parent (i.e. referencing) * layer's ID and delimited with "-" as part of the compositing operation). * @param {*} layer * @returns {boolean} */ static isReferencedLayer(layer) { return layer.id.indexOf('-') > 0; } /** * Detect whether there were any layers of the given type in the referenced layers. * @param {Keyframes} keyframes * @params {string} type the string name of the layer type we're looking for. * @returns {boolean} */ static hasReferencedLayersOfType(keyframes, type) { for (let i = 0; i < keyframes.layers.length; i++) { let layer = keyframes.layers[i]; if (this.isReferencedLayer(layer) && layer.type === type) { return true; } } return false; } /** * Evaluate and Blend the layers within keyframes (filtering some out). * Handles Blink Layer Mode: * Any eye layers found in referenced layers put us in so-called "blink layer" mode, which operates as follows: * The eye layers at top level are used to blend all the referenced eye and [any] referenced overlay layers. * If any referenced layers contain overlays, the top level overlay layers are ignored. * All other layers are blended normally. * @param {Keyframes} keyframes * @param keyframeInfo * @param {number} timeInSeconds * @param {Callback} includeFilter * @param overrides * @returns {*} */ static evaluateAllLayersFiltered(keyframes, keyframeInfo, timeInSeconds, includeFilter, overrides) { // If we are not in Blink Layer mode, perform the standard layer evaluation for all layers. if (!this.hasReferencedLayersOfType(keyframes, 'Eye')) { return this.evaluateLayersFiltered(keyframes, keyframeInfo, timeInSeconds, includeFilter, overrides); } // "Blink layer mode" combines the Eye and Overlay in the Top and Reference layers in a particular way. const hasNoReferencedOverlays = !this.hasReferencedLayersOfType(keyframes, 'Overlay'); const topFilter = (layer) => { return includeFilter(layer) && !this.isReferencedLayer(layer) && layer.type === 'Eye'; }; const refFilter = (layer) => { return includeFilter(layer) && this.isReferencedLayer(layer) && (layer.type === 'Eye' || layer.type === 'Overlay'); }; const otherFilter = (layer) => { return includeFilter(layer) && layer.type !== 'Eye' && (layer.type !== 'Overlay' || hasNoReferencedOverlays); }; // Gather up the Eye and Overlay properties from Ref layers. const ref = this.evaluateLayersFiltered(keyframes, keyframeInfo, timeInSeconds, refFilter, overrides); // If no layers in the reference are currently active, we are not in blink mode. if (Object.keys(ref).length === 0) { return this.evaluateLayersFiltered(keyframes, keyframeInfo, timeInSeconds, includeFilter, overrides); } // Perform the standard evaluation of all the non-Eye/Overlay layers. const accumulatedProps = this.evaluateLayersFiltered(keyframes, keyframeInfo, timeInSeconds, otherFilter, overrides); // Gather up the Eye properties from the top layer. const top = this.evaluateLayersFiltered(keyframes, keyframeInfo, timeInSeconds, topFilter, overrides); const eyeLayerInfo = keyframeInfo.layerTypes['Eye'].getInfo(); // Then form the final Eye and Overlay props by combining the referenced layers with the top Eye layer. for (let refLayerType of ['Eye', 'Overlay']) { if (ref[refLayerType]) { // Initialize this layer's properties with the referenced layer values. accumulatedProps[refLayerType] = ref[refLayerType]; // However, if we have a top level Eye layer, we instead make this layer's properties by // blending each referenced layer property with the top level Eye layer's properties. if (top['Eye']) { for (let prop in eyeLayerInfo.properties) { const propInfo = eyeLayerInfo.properties[prop]; const blendOp = BlendOperations_1.default.normal(propInfo.blendOperation, propInfo.type); accumulatedProps[refLayerType][prop] = blendOp(top['Eye'][prop], ref[refLayerType][prop], propInfo.defaultValue); } } } } return accumulatedProps; } // Evaluate all layers by tweening within each layer and then blending the tweened values together. static evaluateLayersFiltered(keyframes, keyframeInfo, timeInSeconds, includeFilter, overrides) { // This will hold each layers blended props as they accumulate const accumulatedProps = {}; // Blend layers from bottom to top. for (let i = keyframes.layers.length - 1; i >= 0; i--) { const layer = keyframes.layers[i]; // Locked, invisible, or filtered layers don't have any affect on the final props/dofs if (!includeFilter(layer) || !layer.visible) { continue; } const override = overrides === undefined ? undefined : overrides[layer.id], props = Runtime.evaluateLayer(layer, keyframes, keyframeInfo, timeInSeconds, override); // Ignore the entire layer if is currently marked hidden. if (!props) { continue; } const layerType = layer.type; // Is this the first instance of a particular layer type // If so initialize the accumulated props to this layer's computed values if (typeof accumulatedProps[layerType] === "undefined") { accumulatedProps[layerType] = props; } else { const layerProps = accumulatedProps[layerType], layerInfo = keyframeInfo.layerTypes[layerType].getInfo(); // Blend each property based on it's type/blendOperation for (let propName in props) { const propInfo = layerInfo.properties[propName]; if (!('blendOperation' in propInfo)) { console.log(`blendoperation not found in ${Object.keys(propInfo)} for layerType ${layerType}`); continue; } layerProps[propName] = BlendOperations_1.default.normal(propInfo.blendOperation, propInfo.type)(layerProps[propName], props[propName], propInfo.defaultValue); } } } return accumulatedProps; } /* * Used by the editor's animation model. * Turn our absoluteProps into ones relative to all the layers of our type that don't include the excluded one. * */ static computeRelativePropValues(excludedLayer, keyframes, keyframeInfo, timeInSeconds, absoluteProps, overrides) { const props = Runtime.evaluateAllLayersFiltered(keyframes, keyframeInfo, timeInSeconds, (layer) => layer.id !== excludedLayer.id, overrides); // Return absolute props as is if there were no other layers of it's type if (typeof props[excludedLayer.type] === "undefined") { return absoluteProps; } const layerInfo = keyframeInfo.layerTypes[excludedLayer.type].getInfo(), relativeProps = {}; for (let propName in absoluteProps) { const propInfo = layerInfo.properties[propName]; relativeProps[propName] = BlendOperations_1.default.inverse(propInfo.blendOperation, propInfo.type)(props[excludedLayer.type][propName], absoluteProps[propName], propInfo.defaultValue); } return relativeProps; } /** * Get the property values generated from a single layer at a point in time * * @param layer {Object} The layer that we need to evaluate * @param keyframes {Object} Keyframes data that this layer is a part of * @param keyframeInfo {Object} Meta data that includes supported layer types, * default values, and etc * @param timeInSeconds {number} The time to compute the properties values at * @param override {Object} If provided this object includes a set of properties * to override the usually calculated keyframed properties. * This is useful in tools for interactively changing * the properties of a layer at a point in time without * committing a new keyframe as a user tries out different jibo poses * @return {Object} An object who's fields are each dof and it's value after all * interpolation and layer blending. Alternatively, return undefined * if there is no valid layer at the given time. */ static evaluateLayer(layer, keyframes, keyframeInfo, timeInSeconds, override) { if (Runtime._isEventLayer(layer, keyframeInfo)) { // do not interpolate for event types return Runtime._evaluateEventLayer(layer, keyframes, keyframeInfo, timeInSeconds); } const props = {}; if (override) { for (let propName in override) { props[propName] = override[propName]; } return props; } const layerTypeInfo = keyframeInfo.layerTypes[layer.type].getInfo(); const defaults = {}; for (let propName in layerTypeInfo.properties) { defaults[propName] = lodash_min_1.cloneDeep(layerTypeInfo.properties[propName].defaultValue); } // If there are no keyframes, return the defaults if (layer.keyframes.length === 0) { if (layer.type === 'Overlay') { // Except in the case of Overlay -- if there are no keyframes in the // overlay, pretend there is no overlay layer. return; } return defaults; } // There are keyframes, calculate the properties at the supplied time // Calculate the keyframe index this time is equivalent to. // NOTE: We don't floor it, we just want to have a number in frame counts // instead of in seconds so that we can compare times and interpolate between // times if (keyframes.scale === undefined) { keyframes.scale = keyframeInfo.defaults.scale; } // Floating point division in Javascript might yield non-integer values // that are very close (millionths) to frame numbers, but not quite let currentTime = Math.round(timeInSeconds * keyframes.framerate * 1000000) / 1000000; // Ignore layers that are not valid before the current time. if (layer.validFrom !== undefined && currentTime < layer.validFrom) { return; } // Ignore layers that are not valid after the current time. if (layer.validUpto !== undefined && currentTime >= layer.validUpto) { return; } // Hold layers at a specific timepoint if specified. if (layer.holdAfter !== undefined && currentTime >= layer.holdAfter) { currentTime = layer.holdAfter; } // Return defaults if we're before the beginning. if (currentTime < layer.keyframes[0].time) { if (layer.type === 'Overlay') { // Except in the case of Overlay -- if there are no keyframes before the // overlay, pretend there is no overlay layer. return; } return defaults; } // Is the current time past the time of the last keyframe? If so grab that // keyframe's value. if (currentTime >= layer.keyframes[layer.keyframes.length - 1].time) { return lodash_min_1.cloneDeep(layer.keyframes[layer.keyframes.length - 1].value); } // The current time is between keyframes. // Scan from the last to first keyframe, stop on the first keyframe // further back or equal in time to us. let { start, end } = Search_1.default.keyframeSearch(layer.keyframes, currentTime); start = layer.keyframes[start]; end = layer.keyframes[end]; // Now that we have our two closest keyframes, interpolate between their values const timeStart = start.time, timeEnd = end.time, timeNormalized = (currentTime - timeStart) / (timeEnd - timeStart); let easingFunction = 'cubicInOut'; if (start.value.Tween) { easingFunction = start.value.Tween.value; } for (let propName in start.value) { let tweenType = layerTypeInfo.properties[propName].type; if (typeof tweenType !== 'string') { tweenType = tweenType.name; } props[propName] = Tweening_1.default[tweenType](start.value[propName], end.value[propName], timeNormalized, easingFunction); } return props; } static _evaluateEventLayer(layer, keyframes, keyframeInfo, timeInSeconds) { // Floating point division in Javascript might yield non-integer values // that are very close (millionths) to frame numbers, but not quite const keyframeTime = Math.round(timeInSeconds * keyframes.framerate * 1000000) / 1000000; // if we have a keyframe at the given keyframeTime, then return it; // otherwise, return the default values const frame = Search_1.default.keyframeSearchAbsolute(layer.keyframes, keyframeTime); if (frame !== undefined) { return lodash_min_1.cloneDeep(layer.keyframes[frame].value); } const layerTypeInfo = keyframeInfo.layerTypes[layer.type].getInfo(); const props = {}; for (let propName in layerTypeInfo.properties) { props[propName] = lodash_min_1.cloneDeep(layerTypeInfo.properties[propName].defaultValue); } return props; } /** * Get the event values (payloads) generated from all the event layers at a point in time * @param keyframes {Object} Keyframes data we'll be evaluating the layers of * @param keyframeInfo {Object} Meta data that includes supported layer types, * default values, and etc * @param timeInSeconds {number} The time to check * * @return {array} An array of all accumulated event layers and their payloads. */ static evaluateAllEventLayers(keyframes, keyframeInfo, timeInSeconds) { const events = []; // Find all our event layers const layersLen = keyframes.layers.length; for (let i = 0; i < layersLen; i++) { const layer = keyframes.layers[i]; // if not visible or not an event layer, move along if (!Runtime._isEventLayer(layer, keyframeInfo) || !layer.visible) { continue; } const props = Runtime.evaluateLayer(layer, keyframes, keyframeInfo, timeInSeconds); if (props) { const layerClass = keyframeInfo.layerTypes[layer.type]; const generatedEvent = layerClass.generateEvent(props, i); const eventType = (typeof generatedEvent); if ((eventType === 'object') && (layerClass.isValid(generatedEvent))) { events.push(generatedEvent); } } } return events; } } exports.default = Runtime; },{"./utils/BlendOperations":18,"./utils/IDUtils":22,"./utils/Search":25,"./utils/Tweening":26,"fs":undefined,"lodash/lodash.min":undefined}],3:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); class BaseLayer { /** * Return type information about the layer, such as: * - Properties it supports and their: * - type * - default values * - min/max where applicable * - the operation (for DOF) that the layering system uses to blend with other layers: * - additive for translations, rotations, and color type properties * - multiplicative for scaling * - last one wins for textures * - values (DOF, events, etc.) the layer generates * Each layer needs to implement this. */ static getInfo() { throw "Implement static Layer.getInfo() to supply property meta data"; } static isEvent() { throw "Implement static Layer.isEvent()"; } } exports.default = BaseLayer; },{}],4:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const BaseLayer_1 = require("./BaseLayer"); class DOFBaseLayer extends BaseLayer_1.default { /** * Given a set of properties that a layer supports return the dof values that * that this layer generates. The properties will have already been interpolated * from the keyframes of the layer and blended with other layers. Only generation * logic needs to be implemented. * * @param props {Object} An object with each field having the name of each layer * property and it's interpolated/blended value * * @returns {Object} An object with each field having the name of each generated * dof and it's value. These will then be handed to final blending * logic that blends all dofs generated from all layer types * according to their blend operation. */ static generateDofs() { throw "Implement static Layer.generateDofs()"; } static isEvent() { return false; } } exports.default = DOFBaseLayer; },{"./BaseLayer":3}],5:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const BaseLayer_1 = require("./BaseLayer"); class EventBaseLayer extends BaseLayer_1.default { /** * Given a set of properties that a layer supports return the event value (payload) that * that this layer generates. * * @param props {Object} An object with each field having the name of each layer * property and its event payloads if applicable * * @returns {Object} A JSON object with the payload information for that layer */ static generateEvent() { throw "Implement static Layer.generateEvent()"; } static isEvent() { return true; } static isValid() { throw "Implement static Layer.isValid(generedatedEvent)"; } } exports.default = EventBaseLayer; },{"./BaseLayer":3}],6:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const EventBaseLayer_1 = require("../bases/EventBaseLayer"); let AudioInfo = { layerType: "Audio Event", properties: { AudioEvent: { type: "audio", blendOperation: 'lastOneWins', defaultValue: { file: "" } } } }; /* * Audio layer with specific payload data. * */ class AudioLayer extends EventBaseLayer_1.default { static getInfo() { return AudioInfo; } constructor() { super(); } static generateEvent(props) { let event = { name: "play-audio", payload: { "file": props.AudioEvent.file } }; return event; } static isValid(generatedEvent) { return ((generatedEvent.name === "play-audio") && (generatedEvent.payload.file.length > 0)); } } exports.default = AudioLayer; },{"../bases/EventBaseLayer":5}],7:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const DOFBaseLayer_1 = require("../bases/DOFBaseLayer"); let BackgroundTextureInfo = { layerType: "Background Texture", properties: { Texture: { type: "texture", defaultValue: "", blendOperation: "lastOneWins" }, Color: { type: "rgb", defaultValue: { r: 255, g: 255, b: 255 }, blendOperation: "add" } }, dofs: { screenBGTextureInfixBn_r: true, screenBG_redChannelBn_r: true, screenBG_greenChannelBn_r: true, screenBG_blueChannelBn_r0: true } }; class BackgroundTextureLayer extends DOFBaseLayer_1.default { static getInfo() { return BackgroundTextureInfo; } constructor() { super(); } static generateDofs(props) { return { screenBGTextureInfixBn_r: props.Texture, screenBG_redChannelBn_r: props.Color.r / 255.0, screenBG_greenChannelBn_r: props.Color.g / 255.0, screenBG_blueChannelBn_r: props.Color.b / 255.0 }; } } exports.default = BackgroundTextureLayer; },{"../bases/DOFBaseLayer":4}],8:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const DOFBaseLayer_1 = require("../bases/DOFBaseLayer"); let values = [ { display: 'linear', label: 'Linear Tween', value: 'linear' }, { display: 'sine in', label: 'Sine Ease In', value: 'sineIn' }, { display: 'sine out', label: 'Sine Ease Out', value: 'sineOut' }, { display: 'sine in out', label: 'Sine Ease In Out', value: 'sineInOut' }, { display: 'cubic in', label: 'Cubic Ease In', value: 'cubicIn' }, { display: 'cubic out', label: 'Cubic Ease Out', value: 'cubicOut' }, { display: 'cubic in out', label: 'Cubic Ease In Out', value: 'cubicInOut' } ]; let Tween = { type: { name: 'enum', values }, defaultValue: values[3], blendOperation: 'none' }; let RotationType = { type: "float", defaultValue: 0.0, blendOperation: "add" }; let BodyInfo = { layerType: "Body", properties: { Head: RotationType, Torso: RotationType, Pelvis: RotationType, Tween }, dofs: { topSection_r: true, middleSection_r: true, bottomSection_r: true } }; class BodyLayer extends DOFBaseLayer_1.default { static getInfo() { return BodyInfo; } constructor() { super(); } static generateDofs(props) { return { topSection_r: props.Head / 180 * Math.PI, middleSection_r: props.Torso / 180 * Math.PI, bottomSection_r: props.Pelvis / 180 * Math.PI }; } } exports.default = BodyLayer; },{"../bases/DOFBaseLayer":4}],9:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const EventBaseLayer_1 = require("../bases/EventBaseLayer"); let EventInfo = { layerType: "Event", properties: { Event: { type: "event", blendOperation: 'lastOneWins', defaultValue: { name: "", payload: "" } } } }; /* * Generic event layer with arbitrary payload data. * */ class EventLayer extends EventBaseLayer_1.default { static getInfo() { return EventInfo; } constructor() { super(); } static generateEvent(props) { let event = { name: ((props.Event.name.length === 0) ? "" : props.Event.name), payload: {} }; // if it's a valid JSON, return the parsed object; otherwise, return // whatever the payload was if (props.Event.payload.length > 0) { try { event.payload = JSON.parse(props.Event.payload); } catch (error) { event.payload = props.Event.payload; } } return event; } static isValid(generatedEvent) { // only need valid name return (generatedEvent.name.length !== 0); } } exports.default = EventLayer; },{"../bases/EventBaseLayer":5}],10:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const lodash_min_1 = require("lodash/lodash.min"); const DOFBaseLayer_1 = require("../bases/DOFBaseLayer"); const Conversion_1 = require("../utils/Conversion"); // Eye/Overlays let centerX = 0; let centerY = 0; let eyeRadiusX = 300; let eyeRadiusY = 300; let values = [ { display: 'linear', label: 'Linear Tween', value: 'linear' }, { display: 'sine in', label: 'Sine Ease In', value: 'sineIn' }, { display: 'sine out', label: 'Sine Ease Out', value: 'sineOut' }, { display: 'sine in out', label: 'Sine Ease In Out', value: 'sineInOut' }, { display: 'cubic in', label: 'Cubic Ease In', value: 'cubicIn' }, { display: 'cubic out', label: 'Cubic Ease Out', value: 'cubicOut' }, { display: 'cubic in out', label: 'Cubic Ease In Out', value: 'cubicInOut' } ]; let Tween = { type: { name: 'enum', values }, defaultValue: values[3], blendOperation: 'none' }; let VertexType = (xRelativeToCenter, yRelativeToCenter) => { return { type: "vector2", defaultValue: { x: xRelativeToCenter, y: yRelativeToCenter }, blendOperation: "add" }; }; let Info = { properties: { Rotate: { type: "float", defaultValue: 0.0, blendOperation: "add" }, Scale: { type: "vector2", defaultValue: { x: 1, y: 1 }, blendOperation: "multiply" }, Translate: { type: "vector2", defaultValue: { x: centerX, y: centerY }, blendOperation: "add" }, Visible: { type: "bool", defaultValue: true, blendOperation: "lastOneWins" }, Alpha: { type: "float", defaultValue: 1.0, blendOperation: "multiply" }, Tween, "Vertex 1": VertexType(-eyeRadiusX, -eyeRadiusY), "Vertex 2": VertexType(0, -eyeRadiusY), "Vertex 3": VertexType(eyeRadiusX, -eyeRadiusY), "Vertex 4": VertexType(-eyeRadiusX, 0), "Vertex 5": VertexType(0, 0), "Vertex 6": VertexType(eyeRadiusX, 0), "Vertex 7": VertexType(-eyeRadiusX, eyeRadiusY), "Vertex 8": VertexType(0, eyeRadiusY), "Vertex 9": VertexType(eyeRadiusX, eyeRadiusY) } }; class EyeAndOverlayLayer extends DOFBaseLayer_1.default { static get eyeRadiusY() { return eyeRadiusY; } static get eyeRadiusX() { return eyeRadiusX; } static _generateDofs(props, nameStem) { return { [nameStem + "vertexJoint1_t"]: Conversion_1.default.toMetersX(props['Vertex 1'].x) * props.Scale.x, [nameStem + "vertexJoint1_t_2"]: Conversion_1.default.toMetersY(props['Vertex 1'].y) * props.Scale.y, [nameStem + "vertexJoint2_t"]: Conversion_1.default.toMetersX(props['Vertex 2'].x) * props.Scale.x, [nameStem + "vertexJoint2_t_2"]: Conversion_1.default.toMetersY(props['Vertex 2'].y) * props.Scale.y, [nameStem + "vertexJoint3_t"]: Conversion_1.default.toMetersX(props['Vertex 3'].x) * props.Scale.x, [nameStem + "vertexJoint3_t_2"]: Conversion_1.default.toMetersY(props['Vertex 3'].y) * props.Scale.y, // [nameStem + "vertexJoint4_t"]: Conversion_1.default.toMetersX(props['Vertex 4'].x) * props.Scale.x, [nameStem + "vertexJoint4_t_2"]: Conversion_1.default.toMetersY(props['Vertex 4'].y) * props.Scale.y, [nameStem + "vertexJoint5_t"]: Conversion_1.default.toMetersX(props['Vertex 5'].x) * props.Scale.x, [nameStem + "vertexJoint5_t_2"]: Conversion_1.default.toMetersY(props['Vertex 5'].y) * props.Scale.y, [nameStem + "vertexJoint6_t"]: Conversion_1.default.toMetersX(props['Vertex 6'].x) * props.Scale.x, [nameStem + "vertexJoint6_t_2"]: Conversion_1.default.toMetersY(props['Vertex 6'].y) * props.Scale.y, // [nameStem + "vertexJoint7_t"]: Conversion_1.default.toMetersX(props['Vertex 7'].x) * props.Scale.x, [nameStem + "vertexJoint7_t_2"]: Conversion_1.default.toMetersY(props['Vertex 7'].y) * props.Scale.y, [nameStem + "vertexJoint8_t"]: Conversion_1.default.toMetersX(props['Vertex 8'].x) * props.Scale.x, [nameStem + "vertexJoint8_t_2"]: Conversion_1.default.toMetersY(props['Vertex 8'].y) * props.Scale.y, [nameStem + "vertexJoint9_t"]: Conversion_1.default.toMetersX(props['Vertex 9'].x) * props.Scale.x, [nameStem + "vertexJoint9_t_2"]: Conversion_1.default.toMetersY(props['Vertex 9'].y) * props.Scale.y }; } static overrideProperties(newProps) { Object.assign(Info.properties, newProps); } static extend(input) { let result = lodash_min_1.merge({}, Info, input); return result; } } exports.default = EyeAndOverlayLayer; },{"../bases/DOFBaseLayer":4,"../utils/Conversion":21,"lodash/lodash.min":undefined}],11:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const EyeAndOverlayLayer_1 = require("./EyeAndOverlayLayer"); const Conversion_1 = require("../utils/Conversion"); let EyeInfo = EyeAndOverlayLayer_1.default.extend({ layerType: "Eye", dofs: { eyeSubRootBn_r: true, eyeSubRootBn_t: true, eyeSubRootBn_t_2: true, vertexJoint1_t: true, vertexJoint1_t_2: true, vertexJoint2_t: true, vertexJoint2_t_2: true, vertexJoint3_t: true, vertexJoint3_t_2: true, vertexJoint4_t: true, vertexJoint4_t_2: true, vertexJoint5_t: true, vertexJoint5_t_2: true, vertexJoint6_t: true, vertexJoint6_t_2: true, vertexJoint7_t: true, vertexJoint7_t_2: true, vertexJoint8_t: true, vertexJoint8_t_2: true, vertexJoint9_t: true, eyeVisibilityBn_r: true, eye_alphaChannelBn_r: true } }); class EyeLayer extends EyeAndOverlayLayer_1.default { static getInfo() { return EyeInfo; } static get eyeRadiusX() { return EyeAndOverlayLayer_1.default.eyeRadiusX; } static get eyeRadiusY() { return EyeAndOverlayLayer_1.default.eyeRadiusY; } static generateDofs(props) { let dofs = EyeAndOverlayLayer_1.default._generateDofs(props, ""); dofs.eyeSubRootBn_r = props.Rotate / 180 * Math.PI; dofs.eyeSubRootBn_t = Conversion_1.default.toMetersX(props.Translate.x); dofs.eyeSubRootBn_t_2 = Conversion_1.default.toMetersY(props.Translate.y); dofs.eyeVisibilityBn_r = props.Visible === undefined ? 1 : (props.Visible ? 1 : 0); dofs.eye_alphaChannelBn_r = props.Alpha === undefined ? 1 : props.Alpha; return dofs; } } exports.default = EyeLayer; },{"../utils/Conversion":21,"./EyeAndOverlayLayer":10}],12:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const DOFBaseLayer_1 = require("../bases/DOFBaseLayer"); let EyeTextureInfo = { layerType: "Eye Texture", properties: { Texture: { type: "texture", defaultValue: "", blendOperation: "lastOneWins" }, Color: { type: "rgb", defaultValue: { r: 255, g: 255, b: 255 }, blendOperation: "add" } }, dofs: { eyeTextureInfixBn_r: true, eye_redChannelBn_r: true, eye_greenChannelBn_r: true, eye_blueChannelBn_r: true } }; class EyeTextureLayer extends DOFBaseLayer_1.default { static getInfo() { return EyeTextureInfo; } constructor() { super(); } static generateDofs(props) { return { eyeTextureInfixBn_r: props.Texture, eye_redChannelBn_r: props.Color.r / 255.0, eye_greenChannelBn_r: props.Color.g / 255.0, eye_blueChannelBn_r: props.Color.b / 255.0 }; } } exports.default = EyeTextureLayer; },{"../bases/DOFBaseLayer":4}],13:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const DOFBaseLayer_1 = require("../bases/DOFBaseLayer"); let LEDInfo = { layerType: "LED", properties: { Color: { type: "rgb", defaultValue: { r: 0, g: 0, b: 0 }, blendOperation: "add" } }, dofs: { lightring_redChannelBn_r: true, lightring_greenChannelBn_r: true, lightring_blueChannelBn_r: true } }; class LEDLayer extends DOFBaseLayer_1.default { static getInfo() { return LEDInfo; } constructor() { super(); } static generateDofs(props) { return { lightring_redChannelBn_r: props.Color.r / 255.0, lightring_greenChannelBn_r: props.Color.g / 255.0, lightring_blueChannelBn_r: props.Color.b / 255.0 }; } } exports.default = LEDLayer; },{"../bases/DOFBaseLayer":4}],14:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const EyeAndOverlayLayer_1 = require("./EyeAndOverlayLayer"); const Conversion_1 = require("../utils/Conversion"); let OverlayInfo = EyeAndOverlayLayer_1.default.extend({ layerType: "Overlay", properties: { Visible: { defaultValue: false } }, dofs: { overlay_textureSubRootBn_r: true, overlay_textureSubRootBn_t: true, overlay_textureSubRootBn_t_2: true, overlay_vertexJoint1_t: true, overlay_vertexJoint1_t_2: true, overlay_vertexJoint2_t: true, overlay_vertexJoint2_t_2: true, overlay_vertexJoint3_t: true, overlay_vertexJoint3_t_2: true, overlay_vertexJoint4_t: true, overlay_vertexJoint4_t_2: true, overlay_vertexJoint5_t: true, overlay_vertexJoint5_t_2: true, overlay_vertexJoint6_t: true, overlay_vertexJoint6_t_2: true, overlay_vertexJoint7_t: true, overlay_vertexJoint7_t_2: true, overlay_vertexJoint8_t: true, overlay_vertexJoint8_t_2: true, overlay_vertexJoint9_t: true, overlay_alphaChannelBn_r: true, overlayVisibilityBn_r: true } }); class OverlayLayer extends EyeAndOverlayLayer_1.default { static getInfo() { return OverlayInfo; } static generateDofs(props) { let dofs = EyeAndOverlayLayer_1.default._generateDofs(props, "overlay_"); dofs.overlay_textureSubRootBn_r = props.Rotate / 180 * Math.PI; dofs.overlay_textureSubRootBn_t = Conversion_1.default.toMetersX(props.Translate.x); dofs.overlay_textureSubRootBn_t_2 = Conversion_1.default.toMetersY(props.Translate.y); dofs.overlayVisibilityBn_r = props.Visible === undefined ? 1 : (props.Visible ? 1 : 0); dofs.overlay_alphaChannelBn_r = props.Alpha === undefined ? 1 : props.Alpha; return dofs; } } exports.default = OverlayLayer; },{"../utils/Conversion":21,"./EyeAndOverlayLayer":10}],15:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const DOFBaseLayer_1 = require("../bases/DOFBaseLayer"); let OverlayTextureInfo = { layerType: "Overlay Texture", properties: { Texture: { type: "texture", defaultValue: "", blendOperation: "lastOneWins" }, Color: { type: "rgb", defaultValue: { r: 255, g: 255, b: 255 }, blendOperation: "add" } }, dofs: { overlayTextureInfixBn_r: true, overlay_redChannelBn_r: true, overlay_greenChannelBn_r: true, overlay_blueChannelBn_r: true } }; class OverlayTextureLayer extends DOFBaseLayer_1.default { static getInfo() { return OverlayTextureInfo; } constructor() { super(); } static generateDofs(props) { return { overlayTextureInfixBn_r: props.Texture, overlay_redChannelBn_r: props.Color.r / 255.0, overlay_greenChannelBn_r: props.Color.g / 255.0, overlay_blueChannelBn_r: props.Color.b / 255.0 }; } } exports.default = OverlayTextureLayer; },{"../bases/DOFBaseLayer":4}],16:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const EventBaseLayer_1 = require("../bases/EventBaseLayer"); let PixiInfo = { layerType: "Pixi", properties: { Pixi: { type: "pixi", defaultValue: "", blendOperation: "singleton" }, "Attach To Eye": { type: "bool", defaultValue: false, blendOperation: "lastOneWins" }, "Frame Offset": { type: "offset", defaultValue: "", blendOperation: "lastOneWins" } }, dofs: {} }; class PixiLayer extends EventBaseLayer_1.default { static getInfo() { return PixiInfo; } constructor() { super(); } static generateEvent(props, layerNum) { return { name: "play-pixi", payload: { file: props.Pixi, layerNum, attach: props["Attach To Eye"], offset: props["Frame Offset"], effect: false // deprecated } }; } static isValid(generatedEvent) { return ((generatedEvent.name === "play-pixi") && (generatedEvent.payload.file.length > 0)); } } exports.default = PixiLayer; },{"../bases/EventBaseLayer":5}],17:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const DOFBaseLayer_1 = require("../bases/DOFBaseLayer"); let RefModes = [ { display: 'Name', label: 'By Name', value: 'name' }, { display: 'File Path', label: 'By File path', value: 'file' } ]; let ReferenceInfo = { layerType: "Reference", properties: { "ReferenceMode": { label: 'Type of Reference', type: { name: 'enum', values: RefModes }, defaultValue: RefModes[0], blendOperation: 'none' }, "KeysFileReference": { label: ' ', enumProperty: "ReferenceMode", type: "keysref", defaultValue: { file: "", name: "", } }, "HoldFinalPose": { label: 'Hold Final Pose', type: "bool", defaultValue: true, blendOperation: "lastOneWins" } } }; class ReferenceLayer extends DOFBaseLayer_1.default { static generateDofs() { return {}; } static getInfo() { return ReferenceInfo; } } exports.default = ReferenceLayer; },{"../bases/DOFBaseLayer":4}],18:[function(require,module,exports){ "use strict"; /** * Operations per property type for blending layers of properties together */ Object.defineProperty(exports, "__esModule", { value: true }); let operations = { lastOneWins: { texture(accumulated, newValue) { return newValue; }, bool(accumulated, newValue) { return newValue; }, event(accumulated, newValue) { return newValue; }, audio(accumulated, newValue) { return newValue; } }, add: { float(accumulated, newValue, defaultValue) { return accumulated + (newValue - defaultValue); }, vector2(accumulated, newValue, defaultValue) { return { x: accumulated.x + (newValue.x - defaultValue.x), y: accumulated.y + (newValue.y - defaultValue.y) }; }, rgb(accumulated, newValue, defaultValue) { return { r: Math.max(0, Math.min(255, accumulated.r + (newValue.r - defaultValue.r))), g: Math.max(0, Math.min(255, accumulated.g + (newValue.g - defaultValue.g))), b: Math.max(0, Math.min(255, accumulated.b + (newValue.b - defaultValue.b))) }; } }, multiply: { float(accumulated, newValue) { return accumulated * newValue; }, vector2(accumulated, newValue) { return { x: accumulated.x * newValue.x, y: accumulated.y * newValue.y }; }, rgb(accumulated, newValue) { return { r: Math.max(0, Math.min(255, accumulated.r * newValue.r)), g: Math.max(0, Math.min(255, accumulated.g * newValue.g)), b: Math.max(0, Math.min(255, accumulated.b * newValue.b)) }; } }, singleton: { video(accumulated, newValue) { return newValue; } }, inverse: { lastOneWins: { texture(accumulated) { return accumulated; }, bool(accumulated) { return accumulated; } }, singleton: { video(accumulated) { return accumulated; } }, add: { float(accumulated, newValue, defaultValue) { return (newValue + defaultValue) - accumulated; }, vector2(accumulated, newValue, defaultValue) { return { x: (newValue.x + defaultValue.x) - accumulated.x, y: (newValue.y + defaultValue.y) - accumulated.y }; }, rgb(accumulated, newValue, defaultValue) { return { r: Math.max(0, Math.min(255, (newValue.r + defaultValue.r) - accumulated.r)), g: Math.max(0, Math.min(255, (newValue.g + defaultValue.g) - accumulated.g)), b: Math.max(0, Math.min(255, (newValue.b + defaultValue.b) - accumulated.b)) }; } }, multiply: { float(accumulated, newValue) { return accumulated / newValue; }, vector2(accumulated, newValue) { return { x: newValue.x / accumulated.x, y: newValue.y / accumulated.y }; }, rgb(accumulated, newValue) { return { r: Math.max(0, Math.min(255, newValue.r / accumulated.r)), g: Math.max(0, Math.min(255, newValue.g / accumulated.g)), b: Math.max(0, Math.min(255, newValue.b / accumulated.b)) }; } } } }; class BlendOperations { static inverse(operation, type) { // Operation none for any type is the same as lastOneWins for textures if (operation === 'none') { return operations.inverse.lastOneWins.texture; } else { return operations.inverse[operation][type]; } } static normal(operation, type) { // Operation none for any type is the same as lastOneWins for textures if (operation === 'none') { return operations.lastOneWins.texture; } else { if (!(operation in operations)) { console.log(`operation ${operation} not found in ${Object.keys(operations)}`); } else { return operations[operation][type]; } } } } exports.default = BlendOperations; },{}],19:[function(require,module,exports){ "use strict"; /** * This file implements meta data associated with .keys and .anim files and conversions between them * - mapping the internal animationutilities channel names to more user friendly * names for display in the jibo animation tool and for developers who * will be manipulating animation tool files themselves * - information about converting between values in each file format. For example * converting between radians and degrees for rotation angles */ Object.defineProperty(exports, "__esModule", { value: true }); // Animation robot DOFs and info (currently only includes layer type and display name; // keeping as object in case we want other data here. let dofInfo = { // BODY "topSection_r": { type: "Body", name: "Head" }, "middleSection_r": { type: "Body", name: "Torso" }, "bottomSection_r": { type: "Body", name: "Pelvis" }, // EYE "eyeSubRootBn_r": { type: "Eye", name: "Rotate" }, "eyeSubRootBn_t": { type: "Eye", name: "Translate" }, "eyeSubRootBn_t_2": { type: "Eye", name: "Translate" }, "vertexJoint1_t": { type: "Eye" }, "vertexJoint1_t_2": { type: "Eye" }, "vertexJoint2_t": { type: "Eye" }, "vertexJoint2_t_2": { type: "Eye" }, "vertexJoint3_t": { type: "Eye" }, "vertexJoint3_t_2": { type: "Eye" }, "vertexJoint4_t": { type: "Eye" }, "vertexJoint4_t_2`": { type: "Eye" }, "vertexJoint5_t": { type: "Eye" }, "vertexJoint5_t_2": { type: "Eye" }, "vertexJoint6_t": { type: "Eye" }, "vertexJoint6_t_2": { type: "Eye" }, "vertexJoint7_t": { type: "Eye" }, "vertexJoint7_t_2": { type: "Eye" }, "vertexJoint8_t": { type: "Eye" }, "vertexJoint8_t_2": { type: "Eye" }, "vertexJoint9_t": { type: "Eye" }, "vertexJoint9_t_2": { type: "Eye" }, // EYE TEXTURE "eyeTextureInfixBn_r": { type: "Eye Texture" }, "eye_redChannelBn_r": { type: "Eye Texture" }, "eye_greenChannelBn_r": { type: "Eye Texture" }, "eye_blueChannelBn_r": { type: "Eye Texture" }, // OVERLAY "overlay_textureSubRootBn_r": { type: "Overlay", name: "Rotate" }, "overlay_textureSubRootBn_t": { type: "Overlay", name: "Translate" }, "overlay_textureSubRootBn_t_2": { type: "Overlay", name: "Translate" }, "overlay_vertexJoint1_t": { type: "Overlay" }, "overlay_vertexJoint1_t_2": { type: "Overlay" }, "overlay_vertexJoint2_t": { type: "Overlay" }, "overlay_vertexJoint2_t_2": { type: "Overlay" }, "overlay_vertexJoint3_t": { type: "Overlay" }, "overlay_vertexJoint3_t_2": { type: "Overlay" }, "overlay_vertexJoint4_t": { type: "Overlay" }, "overlay_vertexJoint4_t_2": { type: "Overlay" }, "overlay_vertexJoint5_t": { type: "Overlay" }, "overlay_vertexJoint5_t_2": { type: "Overlay" }, "overlay_vertexJoint6_t": { type: "Overlay" }, "overlay_vertexJoint6_t_2": { type: "Overlay" }, "overlay_vertexJoint7_t": { type: "Overlay" }, "overlay_vertexJoint7_t_2": { type: "Overlay" }, "overlay_vertexJoint8_t": { type: "Overlay" }, "overlay_vertexJoint8_t_2": { type: "Overlay" }, "overlay_vertexJoint9_t": { type: "Overlay" }, "overlay_vertexJoint9_t_2": { type: "Overlay" }, // OVERLAY TEXURE "overlayTextureInfixBn_r": { type: "Overlay Texture" }, "overlay_redChannelBn_r": { type: "Overlay Texture" }, overlay_greenChannelBn_r: { type: "Overlay Texture" }, "overlay_blueChannelBn_r": { type: "Overlay Texture" }, // BACKGROUND TEXTURE "screenBGTextureInfixBn_r": { type: "Background Texture" }, "screenBG_redChannelBn_r": { type: "Background Texture" }, "screenBG_greenChannelBn_r": { type: "Background Texture" }, "screenBG_blueChannelBn_r": { type: "Background Texture" }, // LED "lightring_redChannelBn_r": { type: "LED" }, "lightring_greenChannelBn_r": { type: "LED" }, "lightring_blueChannelBn_r": { type: "LED" } }; class Channels { static dofNameToDisplayType(dofName) { return dofInfo[dofName].type; } static dofNameToDisplayName(dofName) { let displayName = dofInfo[dofName].name; return (displayName !== undefined) ? displayName : Channels.dofNameToDisplayType(dofName); } static displayNameToDof(displayName) { for (var dofName in dofInfo) { if (dofInfo[dofName].name === displayName) { return dofName; } } return; } static radiansToDegrees(radians) { return radians / Math.PI * 180; } static degreesToRadians(degree) { return degree / 180 * Math.PI; } static framesToSeconds(frame, frameRate) { return frame / frameRate; } static secondsToFrames(timeInSeconds, totalTimeInSeconds, totalFrames) { return Math.round(timeInSeconds / totalTimeInSeconds * (totalFrames)); } static toTextureURL(jiboConfig, displayValue) { return this.baseTextureURL + this.getPaddedValueString(displayValue) + ".png"; } static toBGTextureURL(jiboConfig, displayValue) { return this.baseBGURL + this.getPaddedValueString(displayValue) + ".png"; } } exports.default = Channels; },{}],20:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const hsv = require("color-space/hsv"); // function clampColorComponent(v){ // return Math.floor(Math.max(0, Math.min(255, v))); // } /** * hsv value range is: * h = [0,360], * s = [0,100], * v = [0,100] * * Uses range/values mapped out in this interface: * http://www.colorpicker.com/ */ class Colors { static hsvToRgb(h, s, v) { let rgb = hsv.rgb([h, s, v]); return { r: rgb[0], g: rgb[1], b: rgb[2] }; } } exports.default = Colors; },{"color-space/hsv":undefined}],21:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const // These numbers come from here: // https://github.jibo.com/sdk/geometry-config/blob/9206df71556c925876362abbd3c382a1e2923fea/P1.0/jibo.jscene#L10-L11 // They are computed based on the CAD model that we have in Maya and is used for exporting our 3D visualizers // meshes WIDTH_METERS = 0.13002148, HEIGHT_METERS = 0.073142126, WIDTH_PIXELS = 1280, HEIGHT_PIXELS = 720; class Conversion { static toMetersX(xInPixels) { return (xInPixels) / WIDTH_PIXELS * WIDTH_METERS; } static toMetersY(yInPixels) { return (yInPixels) / HEIGHT_PIXELS * HEIGHT_METERS * -1; } static toPixelsX(xInMeters) { return xInMeters / WIDTH_METERS * WIDTH_PIXELS; } static toPixelsY(yInMeters) { return yInMeters / HEIGHT_METERS * HEIGHT_PIXELS * -1; } } exports.default = Conversion; },{}],22:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const uuid = require("uuid"); class IDUtils { static create() { // I dislike the dashes, so I strip them out. This doesn't not affect // the chance of collisions over including them return uuid.v4().replace(/-/g, ""); } } exports.default = IDUtils; },{"uuid":undefined}],23:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = require("path"); const fs = require("fs"); const Runtime_1 = require("../Runtime"); const JiboKeyframeInfo_1 = require("../JiboKeyframeInfo"); const jibo_plugins_1 = require("jibo-plugins"); /** * Keyframes Compositer: deep-copies the given keyframes structure and recursively composites any reference layers into it. * This is essentially the synchronous version of Jibo/src/rendering/tasks/KeysTask.ts. */ class KeyframesCompositer { /** * Constructor * @param {string} basepath the project root for the animation files being composited. * @param {string} [animdbPath] filename of the animDB.json files as an alternate to looking for it in * the project's node modules. * @param {string} [fileDir] the directory of the file being composited (needed when compositing frames since * we have no file to indicate what directory we're in). * @param {string} [assetPack] the asset pack the file is from. */ constructor(basepath, animdbPath, fileDir, assetPack) { this.basepath = basepath; this.fileDir = fileDir; this.errors = []; this.hits = 0; this.misses = 0; this.noWarn = false; this.animDB = new miniAnimdb(basepath, animdbPath); this.cache = {}; this.watchedFiles = {}; this.assetPack = assetPack || ''; } setNoWarn() { this.noWarn = true; } /** * Find all references within the keyframes and incorporate them (copies the data before compositing in references) * @param {Keyframes} frames -- deserialized .keys file (JSON "keyframes") object. * @returns {Keyframes} composited keyframes object. */ getCompositedContentLayers(frames) { return this.getCompositedlayers(frames).contentLayers; } /** * Get the reference layers used during compositing. These are needed in order to find the * childPrefix associated with a particular reference. The childPrefix is used to find the audio layers * associated with a particular reference. All of this is done for the sake of displaying a waveform * within the reference layer in an animation editor timeline. * @param frames * @returns {Array} */ getCompositedReferenceLayers(frames) { return this.getCompositedlayers(frames).referenceLayers; } getCompositedlayers(frames) { const inputString = JSON.stringify(frames); if (!(inputString in this.cache)) { frames = JSON.parse(inputString); this.cache[inputString] = this.processReferencesInKeyframes(frames, this.fileDir); const cacheEntries = Object.keys(this.cache).length; console.log(`Compositing cache now has ${cacheEntries} entries, hits/misses= ${this.hits / this.misses}`); this.misses++; } else { this.hits++; } return this.cache[inputString]; } /** * Load the given keys file and all the references within it into a single object with all the layer references removed. * @param {string} url -- filename of file to load. * @returns {Keyframes} composited keyframes object. */ compositeKeysfile(url) { const frames = Runtime_1.default.load(url, JiboKeyframeInfo_1.default); const dir = path.dirname(url); return this.processReferencesInKeyframes(frames, dir).contentLayers; } /** * Convert a [relative] file path reference or anim name reference into a full url. * @param {any} keyframe the keyframe containing the reference. * @param {string} refBase the directory of the file whose keyframe we are evaluating. * @returns {string} full resolved path to referenced file. */ getReferencedUrl(keyframe, refBase) { let url; let assetPack; if (keyframe.value.ReferenceMode.value === 'name') { let name = keyframe.value.KeysFileReference.name; let anim = this.animDB.getAnimByName(name); if (!anim) { this.warn(`animdb name lookup failed on ${name}`); } else { // ResourceRoot is e.g. "/Users/eric/jibo/skills/template-6-is-best/node_modules/jibo-anim-db-animations" let parts = anim.resourceRoot.split('/node_modules/'); // For future expansion -- when there is more than one animdb if (parts.length > 1) { assetPack = parts[1]; } else { assetPack = 'project'; } url = path.join(anim.resourceRoot, anim.meta.path); } } else { let filename = keyframe.value.KeysFileReference.file; assetPack = jibo_plugins_1.PathUtils.getAssetPack(filename); url = jibo_plugins_1.PathUtils.getAssetUri(filename, '', refBase); } return { url, assetPack }; } /** * Process reference layers into normal keyframes. * @param {Keyframes} frames keyframes object to be composited. * @param {string} refBase the directory of the file whose keyframe we are evaluating. * @returns {Keyframes} contentLayers the composited keyframes object. * @returns {layer[]} referenceLayers the original reference layers from the keyframes object. */ processReferencesInKeyframes(frames, refBase) { let contentLayers = this.dereferenceKeyframes(frames, refBase); this.removeHoldSafesInReferences(contentLayers); let referenceLayers = this.removeReferenceLayers(contentLayers); return { contentLayers, referenceLayers }; } /** * Recursively load all referenced layers into the place(s) they were referenced in the referencing layers. * Layers pulled in by reference will be bounded by either the end of the overall file or the next reference * in the layer. * @param {Keyframes} frames keyframes object to be composited. * @param {string} refBase the directory of the file whose keyframe we are evaluating. * @returns {Keyframes} composited keyframes object. */ dereferenceKeyframes(frames, refBase) { const fileRefs = this.getKeyFileReferences(frames, refBase); for (let ref of fileRefs) { if (!fs.existsSync(ref.url) || !fs.statSync(ref.url).isFile()) { this.warn(`Could not find referenced keys file ${ref.url} -- ignored.`); continue; } ref.frames = this.nestedLoad(ref.url, ref.assetPack, ref.parentId); let validUpto = ref.holdFinalPose ? ref.nextRefStart : ref.timeOffset + ref.frames.duration; this.timeshift(ref.frames, ref.timeOffset, validUpto, ref.containerDuration - 1); this.composite(ref.layerNumber, frames, ref.frames); } return frames; } /** * Given a keys file url, load the file and then recursively * composite into it any keys files it points to (somewhat like macro expansion). * Returns the result of the compositing operation with all reference layers removed. * @param {string} url file to load. * @param {string} prefix layerID(s) of referencing layer. * @returns {Keyframes} composited keyframes object. */ nestedLoad(url, assetPack, prefix) { const frames = Runtime_1.default.load(url, JiboKeyframeInfo_1.default); const dir = path.dirname(url); this.resolveAssetRefs(frames, assetPack); this.watchForChanges(url); if (this.hasCircularReference(frames, prefix)) { this.warn(`Circular reference detected in file: ${url}`); return frames; } this.fixLayerIds(frames, prefix); return this.dereferenceKeyframes(frames, dir); } /** * Convert keyframes referencing texture, pixi and audio information into assetpack style references if necessary. * * The motivation: * Consider a reference layer in a skill's animation that references an animDB keys file. * Compositing will pull in the layers from the animDB keys file, but any asset references (textures, audio, pixi) * in those layers will be relative to the animDB root. If the paths to those assets are not converted to * asset-pack style references (while we know their provenance) we will not be able to distinguish later on * whether those references are relative to the animDB or to the local skill. * * @param {Keyframes} frames the keyframes object whose asset references will be modified. * @param {string} assetPack the assetPack that the containing file is in. */ resolveAssetRefs(frames, assetPack) { if (assetPack) { for (let layer of frames.layers) { if (layer.type === 'Pixi') { for (let keyframe of layer.keyframes) { keyframe.value.Pixi = this.convertToAssetReference(assetPack, keyframe.value.Pixi); } } else if (layer.type === 'Audio Event') { for (let keyframe of layer.keyframes) { keyframe.value.AudioEvent.file = this.convertToAssetReference(assetPack, keyframe.value.AudioEvent.file); } } else if (layer.type.indexOf('Texture') !== -1) { for (let keyframe of layer.keyframes) { keyframe.value.Texture = this.convertToAssetReference(assetPack, keyframe.value.Texture); } } } } } /** * Find all references in a keyfile, returning them in reverse-order. * @param {Keyframes} * @param {string} refBase the directory of the file that contained the keyframes. * @returns {Array} array of objects describing the reference layers found. */ getKeyFileReferences(frames, refBase) { const fileRefs = []; for (let i = 0; i < frames.layers.length; i++) { const layer = frames.layers[i]; if (layer.type === 'Reference' && layer.visible) { for (let keyi = 0; keyi < layer.keyframes.length; keyi++) { let keyframe = layer.keyframes[keyi]; let atEnd = layer.keyframes.length === keyi + 1; let nextRefStart = atEnd ? Infinity : layer.keyframes[keyi + 1].time; let { url, assetPack } = this.getReferencedUrl(keyframe, refBase); keyframe.childPrefix = `${layer.id}-${this.zeroPad(keyi, 2)}`; if (url) { fileRefs.push({ url, assetPack, parentId: keyframe.childPrefix, holdFinalPose: keyframe.value.HoldFinalPose, containerDuration: frames.duration, layerNumber: i, atEnd, nextRefStart, timeOffset: keyframe.time }); } } } } fileRefs.reverse(); return fileRefs; } /** * Place num in a leading-zero-padded field of the given size * @param {number} num Number to pad * @param {number} size Size of final field * @returns {string} */ zeroPad(num, size) { return (1e15 + num + "").slice(-size); } /** * Find any "HOLD_SAFE" events in referenced layers and remove them. * @param {Keyframes} frames keyframes to be cleaned */ removeHoldSafesInReferences(frames) { for (let layer of frames.layers) { if (layer.type === 'Event' && this.isReferencedLayer(layer)) { for (let keyframe of layer.keyframes) { if (keyframe.value.Event.name === 'HOLD_SAFE') { keyframe.value.Event.name = 'REFERENCED_HOLD_SAFE'; } } } } } /** * Removes all reference-type layers within the given keysfile * and return them. * @param {Keyframes} frames */ removeReferenceLayers(frames) { let referenceLayers = []; for (let i = 0; i < frames.layers.length; i++) { const layer = frames.layers[i]; if (layer.type === 'Reference') { referenceLayers.push(frames.layers.splice(i--, 1)[0]); } } return referenceLayers; } /** * Shift the timepoint of all the keyframes within each of the given layers by the given timeOffset. * validFrom and validUpto are in "referencing" layer coordinates. * @param {Keyframes} frames * @param {number} validFrom the beginning of the valid region. * @param {number} validUpto the ending of valid region, or Infinity for unlimited. The layer will not be included in renderings after this point. * @param {number} holdAfter the latest timepoint in the layer that will be rendered (regardless of the actual time requested). */ timeshift(frames, validFrom, validUpto, holdAfter) { for (let layer of frames.layers) { if (layer.validFrom === undefined) { // Initialize all not-yet-bounded layers. layer.validFrom = validFrom; layer.validUpto = validUpto; layer.holdAfter = holdAfter; } else { // Shift the valid region of previously bounded layers. layer.validFrom += validFrom; layer.validUpto += validFrom; layer.holdAfter += validFrom; } // Limit a layer's end time if it exceeds the requested end time. if (layer.validUpto > validUpto) { layer.validUpto = validUpto; } // Limit a layer's hold after point if it exceeds the requested one. if (layer.holdAfter > holdAfter) { layer.holdAfter = holdAfter; } // Shift the individual keyframes over by the new start point. for (let keyframe of layer.keyframes) { keyframe.time += validFrom; } } } /** * Insert all the layers of the toBeInsertedFrames into existingFrames at the given layer number. * Insert them last-first in order to preserve their original ordering at the designated layer. * @param {number} layerNumber * @param {Keyframes} existingFrames * @param {Keyframes} toBeInsertedFrames */ composite(layerNumber, existingFrames, toBeInsertedFrames) { for (let i = toBeInsertedFrames.layers.length - 1; i >= 0; i--) { existingFrames.layers.splice(layerNumber, 0, toBeInsertedFrames.layers[i]); } } /** * Modifies the layer ids to prefixed by the referencing layer's id (delimited by "-") * and suffixed with the layer number (delimited by '.'). * @param {Keyframes} frames * @param {string} prefix */ fixLayerIds(frames, prefix) { let i = 0; for (let layer of frames.layers) { layer.id = `${prefix}-${layer.id}.${i}`; i++; } } /** * Was this layer produced from a keyframes reference? * @param layer * @returns {boolean} */ isReferencedLayer(layer) { return layer.id.indexOf('-') >= 0; } /** * Return true if frame being referenced already exists in hierarchy. * @param frames * @param prefix */ hasCircularReference(frames, prefix) { for (let layer of frames.layers) { if (prefix.indexOf(layer.id) >= 0) { return true; } } return false; } /** * Convert a non-asset pack reference into an asset-pack one. * @param assetPack * @param filepath * @returns {string} */ convertToAssetReference(assetPack, filepath) { const ASSETPACK_DELIMITER = '://'; if (filepath && filepath.indexOf(ASSETPACK_DELIMITER) !== -1) { // Don't touch paths that already reference asset packs. return filepath; } return assetPack + ASSETPACK_DELIMITER + filepath; } watchForChanges(url) { if (url in this.watchedFiles) { return; } this.watchedFiles[url] = true; fs.watch(url, (event, filename) => { this.cache = {}; console.log(`File ${filename} had event: ${event} `); }); } warn(error) { if (this.errors.indexOf(error) === -1) { this.errors.push(error); } if (!this.noWarn) { console.warn(error); } } } exports.default = KeyframesCompositer; /** * Poor man's animDB -- only supports name lookup. */ class miniAnimdb { constructor(basepath, animdbPath) { if (!animdbPath) { animdbPath = path.join(basepath, 'animdb.json'); if (!fs.existsSync(animdbPath)) { animdbPath = path.join(basepath, 'node_modules/jibo-anim-db-animations/animdb.json'); } } if (!fs.existsSync(animdbPath)) { console.warn(`No animdb found at ${animdbPath}, reference layers by name will fail.`); } else { this.basepath = path.dirname(animdbPath); this.animdb = JSON.parse(fs.readFileSync(animdbPath, 'utf8')); // Make lower case versions of the keys for (let key in this.animdb) { this.animdb[key.toLowerCase()] = this.animdb[key]; } } } getAnimByName(name) { let key = name.toLowerCase(); if (this.animdb && key in this.animdb) { return { meta: { path: this.animdb[key].path }, resourceRoot: this.basepath }; } } } },{"../JiboKeyframeInfo":1,"../Runtime":2,"fs":undefined,"jibo-plugins":undefined,"path":undefined}],24:[function(require,module,exports){ "use strict"; // Initialize the data with a default set of values for the necessary channels to have jibo animating his top, middle, and bottom body rotations. // This includes some eye and overlay texture related default values in order to make sure Jibo's eye is showing centered on his screen Object.defineProperty(exports, "__esModule", { value: true }); const fs = require("fs"); const path = require("path"); const hash = require("object-hash"); const JiboKeyframeInfo_1 = require("../JiboKeyframeInfo"); const Channels_1 = require("./Channels"); const Runtime_1 = require("../Runtime"); class KeysUtils { /** * Load a keys file asynchronously and create an anim object from it. * @deprecated Use jibo.rendering.animation.KeysLoader -- this version does NOT composite reference layers. * @param keysPath * @param cb */ static keysToAnimObjectAsync(keysPath, cb) { fs.readFile(keysPath, 'utf8', (err, data) => { if (err) { console.log("\nError parsing \"" + keysPath + "\". " + err + "\n"); cb(null); } else { let keysData = JSON.parse(data); JiboKeyframeInfo_1.default.onLoad(keysData); // use the base name of the uri for the anim data "name" let baseName = path.basename(keysPath); let animObj = KeysUtils.keysObjToAnimObject(keysData, baseName); cb(animObj); } }); } /** * Load a keys file synchronously. * @deprecated Use jibo-keyframes.utils.KeyframesCompositer -- this version does NOT composite reference layers. * @returns {*} */ static keysToAnimObject(keysPath) { let keysData; try { // Load .keys file keysData = JSON.parse(fs.readFileSync(keysPath, 'utf8')); } catch (error) { console.log("\nError parsing \"" + keysPath + "\". " + error + "\n"); process.exit(1); } // use the base name of the uri for the anim data "name" let baseName = path.basename(keysPath); return KeysUtils.keysObjToAnimObject(keysData, baseName); } /** * Write out an .anim file created from a .keys file. * @deprecated this version does NOT composite reference layers. * @param keysPath * @param animPath */ static keysToAnim(keysPath, animPath) { console.log(keysPath); let animData = KeysUtils.keysToAnimObject(keysPath); try { fs.writeFileSync(animPath, JSON.stringify(animData, null, ' '), 'utf8'); } catch (error) { console.log("\nERROROROROROR\n"); process.exit(1); } } /** * Given a keysfile path, see if there is an .anim file that has been made from it and asynchronously return that. * @param keysFile * @param cb */ static getCachedAnimForKeysAsync(keysFile, cb) { let animFile = KeysUtils.keysPathToAnimPath(keysFile); if (fs.existsSync(animFile) && fs.statSync(animFile).mtime >= fs.statSync(keysFile).mtime) { fs.readFile(animFile, 'utf8', (err, data) => { if (err) { return cb(err); } let animData = JSON.parse(data); cb(null, animData); }); } else { cb(null, null); } } /** * Given a keysfile path, see if there is an .anim file that has been made from it and return that. * @param keysFile */ static getCachedAnimForKeys(keysFile) { let animFile = KeysUtils.keysPathToAnimPath(keysFile); if (fs.existsSync(animFile) && fs.statSync(animFile).mtime >= fs.statSync(keysFile).mtime) { let data = fs.readFileSync(animFile, 'utf8'); let animData = JSON.parse(data); return animData; } } /** * Given a keysfile in memory (and the filename from which it came) asynchronously return an anim object. * @param keysData * @param filename * @param cb */ static keysObjToAnimObjectAsync(keysData, filename, options, cb) { JiboKeyframeInfo_1.default.onLoad(keysData); KeysUtils.getCachedAnimForKeysAsync(filename, (err, cachedData) => { if (err) { return cb(err); } if (!cachedData) { cachedData = KeysUtils.computeAnimObject(keysData, filename); } if (options) { KeysUtils.transformAnimObject(options, cachedData); } cb(null, cachedData); }); } /** * Given a keysfile in memory (and the filename from which it came) return an anim object. * @param keysData * @param filename * @returns {null} */ static keysObjToAnimObject(keysData, filename) { JiboKeyframeInfo_1.default.onLoad(keysData); let cachedData = filename ? KeysUtils.getCachedAnimForKeys(filename) : null; if (!cachedData) { cachedData = KeysUtils.computeAnimObject(keysData, filename); } return cachedData; } static keysPathToAnimPath(keysfile) { return keysfile.replace(/\.keys$/, '.anim'); } static getCacheFileNameIfAvailable(keysfile, cacheExt, cb) { let cacheFile = keysfile.replace(/\.keys$/, cacheExt); fs.stat(cacheFile, (err, stats) => { if (err) { return cb(null, null); } let cacheMtime = stats.mtime; fs.stat(keysfile, (err, stats) => { if (err) { return cb(err); } let keysMtime = stats.mtime; cb(null, (cacheMtime >= keysMtime) ? cacheFile : null); }); }); } static getAnimFilePath(keysfile, cb) { KeysUtils.getCacheFileNameIfAvailable(keysfile, '.anim', cb); } static getAssetsFilePath(keysfile, cb) { KeysUtils.getCacheFileNameIfAvailable(keysfile, '.assets', cb); } /** * Convert keysData into an animData object * @param keysData * @param filename * @returns {{header: {fileType: string, version, creationTime: number}, content: {name, channels: Array, events: Array}}} */ static computeAnimObject(keysData, filename) { var animData = { header: { fileType: "Animation", version: keysData.version, // Add animdb object hash metadata so that the .anim file changes in the case that its // containing .keys file only changes in its animdb field (which isn't otherwise picked up) animdb: keysData.animdb ? hash(keysData.animdb) : hash({}) }, content: { name: filename ? path.parse(filename).base : '', channels: [], events: [] } }; let animChannels = {}; // For each frame calculate all dof and event values and save them to file for (let frame = 0; frame < keysData.duration; frame++) { let timeInSeconds = Channels_1.default.framesToSeconds(frame, keysData.framerate); let dofs = Runtime_1.default.evaluateAllDOFLayers(keysData, JiboKeyframeInfo_1.default, timeInSeconds); let events = Runtime_1.default.evaluateAllEventLayers(keysData, JiboKeyframeInfo_1.default, timeInSeconds); for (let dofName in dofs) { if (typeof animChannels[dofName] === "undefined") { animChannels[dofName] = { dofName: dofName, length: keysData.duration / keysData.framerate, times: [timeInSeconds], values: [dofs[dofName]] }; animData.content.channels.push(animChannels[dofName]); } else { let values = animChannels[dofName].values; let times = animChannels[dofName].times; let value = dofs[dofName]; // *** DOF KOMPRESSOR *** // Here we compress each channel's DOFs in a way that is transparent to the rendering system. // Do the prior two frames in this channel have the same value as the new one we're adding? // If so we don't add a further time/value frame but rather update the last frame's time to the current time. // This "hollows out" the inner frames from a run of frames that would otherwise all have the same value. let svalue = `${value}`; let length = values.length; if (length >= 2 && `${values[length - 1]}` === svalue && `${values[length - 2]}` === svalue) { times.pop(); values.pop(); } times.push(timeInSeconds); values.push(value); } } for (let i = 0; i < events.length; i++) { let animEvent = { time: timeInSeconds, eventName: events[i].name, payload: events[i].payload }; animData.content.events.push(animEvent); } } return animData; } } exports.default = KeysUtils; },{"../JiboKeyframeInfo":1,"../Runtime":2,"./Channels":19,"fs":undefined,"object-hash":undefined,"path":undefined}],25:[function(require,module,exports){ "use strict"; /** * keyframes entry point. */ Object.defineProperty(exports, "__esModule", { value: true }); /** * This assumes that some higher level logic has already determined that the currentTime is between the first * and last keyframe's times. * That is, do not call this with a currentTime < first-keyframe-time or currentTime >= last-keyframe-time. * Returns: start-keyframe-idx and end-keyframe-idx where: start-keyframe-time <= currentTime < end-keyframe-time */ class Search { static keyframeSearch(keyframes, currentTime) { let lo = 0, hi = keyframes.length - 2, mid /*, steps = 0*/; while (lo <= hi) { mid = Math.floor((lo + hi) / 2); //steps++; //console.log(`mid: ${mid}, lo: ${lo}, hi: ${hi} start: ${keyframes[mid].time}, end: ${keyframes[mid+1].time}`) if (currentTime >= keyframes[mid + 1].time) { //console.log('moving up'); lo = mid + 1; } else if (currentTime < keyframes[mid].time) { //console.log('moving down'); hi = mid - 1; } else { //console.log(`steps: ${steps}`); return { start: mid, end: mid + 1 }; } } } static keyframeSearchAbsolute(keyframes, keyframeTime) { if (keyframes.length === 0) { return undefined; } let lo = 0; let hi = keyframes.length - 1; let mid; while (lo <= hi) { mid = Math.floor((lo + hi) / 2); let midTime = keyframes[mid].time; if (keyframeTime === midTime) { return mid; // found it } else if (keyframeTime > midTime) { // if our time is greater, then move up lo = mid + 1; } else { // if our time is less, then move down hi = mid - 1; } } // not found return undefined; } } exports.default = Search; },{}],26:[function(require,module,exports){ "use strict"; /** * A set of functions for blending between a start and end keyframe for a * variety of value types. The name of the function matches the value type * it knows how to blend */ Object.defineProperty(exports, "__esModule", { value: true }); /** * These are the set of easing functions our animation system supports. They * are used by the animation system to create variations of speed, acceleration, * overshooting, and undershooting of blending values. * * The `t` variable passed to each easing function has the value range: [0,1]. The * functions each return a float that should be used to create the usual weighted * blend of two values to be tweened. Example * * tweenFloats(start, end, t) { * let f = easing.linear(t); * return start*(1-f) + end*f; * } */ const lodash_min_1 = require("lodash/lodash.min"); const easesFunctions = require("eases"); /** * For now we support all of the easing functions in 'eases' module. * There's some good stuff here: http://www.rich-harris.co.uk/ramjet/ */ let easing = lodash_min_1.clone(easesFunctions); /** * "snap" always returns a weight fully weighted to the last keyframe */ easing.snap = () => 0; class Tweening { /** * getEasingFunc - Resolve which easing function is desired * * @param {function|string} easingFuncOrString function or name of a built-in easing function that calculates the blending weights * @return {function} the resolved easing function */ static getEasingFunc(easingFuncOrString) { if (typeof easingFuncOrString === "function") { return easingFuncOrString; } else { return easing[easingFuncOrString]; } } /** * @param {number} start - Start value * @param {number} end - End Value * @param {number} time - time between [0,1] indicating where in a blend we are * @param {function|string} easing function or name of a built-in easing function that calculates the blending weights */ static float(start, end, time, easing) { let f = Tweening.getEasingFunc(easing)(time); return start * (1 - f) + end * f; } /** * @param {object} start - Start value * @param {object} end - End Value * @param {number} time - time between [0,1] indicating where in a blend we are * @param {function|string} easing function or name of a built-in easing function that calculates the blending weights */ static vector2(start, end, time, easing) { let f = Tweening.getEasingFunc(easing)(time); return { x: start.x * (1 - f) + end.x * f, y: start.y * (1 - f) + end.y * f }; } /** * @param {object} start - Start value * @param {object} end - End Value * @param {number} time - time between [0,1] indicating where in a blend we are * @param {function|string} easing function or name of a built-in easing function that calculates the blending weights */ static rgb(start, end, time, easing) { let f = Tweening.getEasingFunc(easing)(time); return { r: start.r * (1 - f) + end.r * f, g: start.g * (1 - f) + end.g * f, b: start.b * (1 - f) + end.b * f }; } /** * @param {number} start - Start value * @param {number} end - End Value * @param {number} time - time between [0,1] indicating where in a blend we are * @param {function|string} easing function or name of a built-in easing function that calculates the blending weights */ static color(start, end, time /*, easing*/) { let f = Tweening.getEasingFunc("linear")(time); return start * (1 - f) + end * f; } static texture(start /*, end, time, easing*/) { return start; } static video(start /*, end, time, easing*/) { return start; } static bool(start) { return start; } static enum(start /*, end, time, easing*/) { return start; } static pixi(start) { return start; } static keysref(start) { return start; } } exports.default = Tweening; },{"eases":undefined,"lodash/lodash.min":undefined}],27:[function(require,module,exports){ "use strict"; /** * keyframes entry point. */ const KeysUtils_1 = require("./utils/KeysUtils"); const Channels_1 = require("./utils/Channels"); const IDUtils_1 = require("./utils/IDUtils"); const Conversion_1 = require("./utils/Conversion"); const Search_1 = require("./utils/Search"); const Tweening_1 = require("./utils/Tweening"); const JiboKeyframeInfo_1 = require("./JiboKeyframeInfo"); const KeyframesCompositer_1 = require("./utils/KeyframesCompositer"); const Runtime_1 = require("./Runtime"); module.exports = { JiboKeyframeInfo: JiboKeyframeInfo_1.default, KeyframesCompositer: KeyframesCompositer_1.default, keysToAnimObject: KeysUtils_1.default.keysToAnimObject, keysToAnimObjectAsync: KeysUtils_1.default.keysToAnimObjectAsync, keysObjToAnimObjectAsync: KeysUtils_1.default.keysObjToAnimObjectAsync, keysObjToAnimObject: KeysUtils_1.default.keysObjToAnimObject, computeAnimObject: KeysUtils_1.default.computeAnimObject, getAnimFilePath: KeysUtils_1.default.getAnimFilePath, getAssetsFilePath: KeysUtils_1.default.getAssetsFilePath, keysToAnim: KeysUtils_1.default.keysToAnim, search: Search_1.default, tweening: Tweening_1.default, runtime: Runtime_1.default, channels: Channels_1.default, generateId: IDUtils_1.default.create, conversion: Conversion_1.default }; },{"./JiboKeyframeInfo":1,"./Runtime":2,"./utils/Channels":19,"./utils/Conversion":21,"./utils/IDUtils":22,"./utils/KeyframesCompositer":23,"./utils/KeysUtils":24,"./utils/Search":25,"./utils/Tweening":26}]},{},[27])(27) }); //# sourceMappingURL=jibo-keyframes.js.map