Files
Zos/Skills/@be/node_modules/jibo-keyframes/lib/jibo-keyframes.js

2866 lines
107 KiB
JavaScript
Raw Normal View History

(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<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* All meta data related to a jibo .keys file. This is passed to the keyframes.runtime functions
* to make it aware of specific layers and their properties/info.
*/
const semver = require("semver");
const BodyLayer_1 = require("./layers/BodyLayer");
const EyeLayer_1 = require("./layers/EyeLayer");
const OverlayLayer_1 = require("./layers/OverlayLayer");
const EyeTextureLayer_1 = require("./layers/EyeTextureLayer");
const OverlayTextureLayer_1 = require("./layers/OverlayTextureLayer");
const BackgroundTextureLayer_1 = require("./layers/BackgroundTextureLayer");
const LEDLayer_1 = require("./layers/LEDLayer");
const PixiLayer_1 = require("./layers/PixiLayer");
const EventLayer_1 = require("./layers/EventLayer");
const AudioLayer_1 = require("./layers/AudioLayer");
const ReferenceLayer_1 = require("./layers/ReferenceLayer");
const IDUtils_1 = require("./utils/IDUtils");
const Conversion_1 = require("./utils/Conversion");
const Colors_1 = require("./utils/Colors");
class JiboKeyframeInfo {
/**
* Implement this to do any backward compatibility related touchups to old files
*/
static onLoad(keyframes) {
// TODO: Figure out why the version isn't always in files
keyframes.version = keyframes.version || "0.3.0";
// Layers didn't have an id at 0.2.0, add some for old files
if (keyframes.version === "0.2.0") {
keyframes.layers.forEach((layer) => {
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<Object>} 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