Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tweening arrays and objects? #78

Closed
earthling4211573 opened this issue Jun 27, 2013 · 5 comments
Closed

Tweening arrays and objects? #78

earthling4211573 opened this issue Jun 27, 2013 · 5 comments

Comments

@earthling4211573
Copy link

So I have these "from" and "to" objects that I need to tween:

var from = {x: 0, y: {a: 0, b: 0}, z:[0, 0], color: "#000000"};
var to = {x: 10, y: {a: 20, b: 30}, z:[40, 50], color: "#ffffff"};

Tween.js doesn't handle the "y", "z" or "color" values out of the box, but would it make sense to do so? Maybe not colors, but what about objects and arrays? And perhaps even nested objects and arrays like so:

{x: 0, y: {a: {t: 0, k:0}, b: 0}, z:[[5,2], [10]], color: "#000000"};

So far to deal with this I came up with the following in a hurry, and I still need to implement support for nested arrays and objects (also if you know of a better way I'd be interested):

http://jsfiddle.net/gautamadude/YRbQ3/

var from = {x: 0, y: {a: 0, b: 0}, z:[0, 0], color: "#000000"};
var to = {x: 10, y: {a: 20, b: 30}, z:[40, 50], color: "#ffffff"};
var speed = 2000;
var easing = TWEEN.Easing.Linear.None;
var delay = 0;
tweenWithObjectArrayAndColorSupport(from, to, speed, easing, delay);

function tweenWithObjectArrayAndColorSupport(from, to, speed, easing, delay) {
    var outFromTo = tweenSeperate(from, to); //Seperate the colors, array objects, and objects
                                             //from the "from" and "to" objects.

    //Tween the remainder
    new TWEEN.Tween( from )
        .to( to, speed )
        .easing( easing )
        .delay(delay)
        .start();

    //Create Tweens for the separated values
    for (type in outFromTo.from) {
        for (attr in outFromTo.from[type]) {
            var vals = {"attr": attr, "type": type};
            createTween(vals, outFromTo.from[type][attr], outFromTo.to[type][attr], speed, easing, delay);
        }
    }
}

function tweenSeperate(from, to) {
    var vals = {};
    var objs = [from, to];
    for (o in objs) {
        var outColors = {};
        var outObjects = {};
        var outArrays = {};
        for (a in objs[o]) {
            if (typeof objs[o][a] == "string") {
                if (/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(objs[o][a])) {
                    var t = tinycolor(objs[o][a])
                    outColors[a] = t.toHsv();
                    delete objs[o][a];
                }
            }
            if (Object.prototype.toString.call(objs[o][a]) == "[object Object]") {
                outObjects[a] = objs[o][a];
                delete objs[o][a];
            } else if (Object.prototype.toString.call(objs[o][a]) == "[object Array]") {
                outArrays[a] = {};
                var array = objs[o][a];
                delete objs[o][a];
                for (val in array) {
                    outArrays[a][val] = array[val];
                }
            }
        }
        if (o == "0") vals.from = {"colors": outColors, "objects": outObjects, "arrays": outArrays};
        else vals.to = {"colors": outColors, "objects": outObjects, "arrays": outArrays};
    }
    return vals;
}

function createTween(vals, f, t, speed, easing, delay) {
    new TWEEN.Tween( f )
        .to( t, speed )
        .easing( easing )
        .onUpdate( function() {
            if (vals.type == "objects" && from[vals.attr] == undefined) {
                from[vals.attr] = {};
            } else if (vals.type == "arrays" && from[vals.attr] == undefined) {
                from[vals.attr] = [];
            }

            if (vals.type == "colors") {
                var t = tinycolor(this)
                from[vals.attr] = t.toHexString();
            } else {
                for (a in this) {
                    if (a != "attr" & a != "type") {
                        from[vals.attr][a] = this[a];
                    }
                }
            }
        })
        .onComplete( function () {
            console.log(from); //Everything Tweened correctly!
        })
        .delay(delay)
        .start();
}

function r(frame) {
    requestAnimationFrame( r );
    TWEEN.update();
}
r();

The reason I need this is to tween KineticJS, Two.js and Pixi.js shape attributes.

So basically, would it make sense to implement support for tweening array and object values (nested or not) in Tween.js?

@sole
Copy link
Member

sole commented Jun 27, 2013

WOW. Now that is massive! 😱

The reason tween.js is fast is because it's simple at its core. If we add all these checks to be executed per frame, it will slow down. I think a solution for tweening complicated objects could be to "flatten" the properties you want to tween, and create a similarly flat object for the "to" values. The onUpdate function should take care of updating the original object with the values from the tween, and that way you'd have only ONE tween which means only ONE easing function would be calculated, not one per nested / complicated property.

I know this is something that needs to be done somehow often when you try to animate complex objects, so I wouldn't be against it per se, but definitely would try to go the external function that preprocesses and creates an onUpdate function route rather than modifying the tween.js core.

@sole
Copy link
Member

sole commented Jun 27, 2013

Also, there is support for tweening array values already: http://sole.github.io/tween.js/examples/06_array_interpolation.html

@mrdoob
Copy link
Contributor

mrdoob commented Jun 27, 2013

I agree. If anything something like TweenGroup could be something to consider.

@earthling4211573
Copy link
Author

I've rewritten the code using the "flatten" method and a single tween instead of multiple, and it's got support for nesting! Aswell as colors (depends on tinycolors), which is a bit convoluted though to make sure it doesn't change hsv to hex, just hex to hsv and back to hex:

http://jsfiddle.net/gautamadude/engDj/

var from = {x: 0, y: {a: 0, b: [{a: 0},0]}, z:[[0,0], 0], color: ["#000000", "#ffffff"], color2: "#dddddd"};
var to = {x: 10, y: {a: 20, b: [{a: 30},40]}, z:[[50,60], 70], color: ["#ffffff", "#000000"], color2: "#cccccc"};

//Flatten/Deflate an object
var flatten = function(source,pathArray,result){
    pathArray = (typeof pathArray === 'undefined') ? [] : pathArray;
    result = (typeof result === 'undefined') ? {} : result;
    var key, value, newKey;
    for (var i in source) { 
        if (source.hasOwnProperty(i)) {
            key                 = i;
            value               = source[i];
            pathArray.push(key);

            if (typeof value === 'object' && value !== null){
                result          = flatten(value,pathArray,result);
            } else {
                newKey          = pathArray.join('.');
                result[newKey]  = value;
            }
            pathArray.pop();
        }
    }
    return result;
};

function ref(obj, str) {
    return str.split(".").reduce(function(o, x) { return o[x] }, obj);
}

//Move values from a flatten object to its original object
function returnValue(obj, flattened, key) {
    parts = key.split(/\.(?=[^.]+$)/)  // Split "foo.bar.baz" into ["foo.bar", "baz"]
    if (parts.length == 1) {
        obj[parts[0]] = flattened[key];
    } else {
        ref(obj, parts[0])[parts[1]] = flattened[key];
    }
}

//Convert hex to ahsv and save the keys or places
//where the original hex was
function toAhsv(obj, key, color_keys) {
    if (typeof obj[key] == "string") {
        if (/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(obj[key])) {
            var t = tinycolor(obj[key])
            var hsv = t.toHsv();
            delete obj[key];
            obj[key+".a"] = hsv.a;
            obj[key+".h"] = hsv.h;
            obj[key+".s"] = hsv.s;
            obj[key+".v"] = hsv.v;
            if (color_keys != undefined) color_keys[key] = {};
        }                         
    }
}

var flattened_from = flatten(from);
var flattened_to = flatten(to);
var color_keys = {};
for (key in flattened_from) {
    toAhsv(flattened_from, key, color_keys);
}
for (key in flattened_to) {
    toAhsv(flattened_to, key);
}

new TWEEN.Tween( flattened_from )
    .to( flattened_to, 2000 )
    .easing( TWEEN.Easing.Linear.None )
    .onUpdate( function() {
        //Convert ahsv to hex and set it in the this
        //object under a key that refers to the colors
        //place in the original object.
        for (key in color_keys) {
            var ahsv = {};
            ahsv.a = this[key+".a"];
            ahsv.h = this[key+".h"];
            ahsv.s = this[key+".s"];
            ahsv.v = this[key+".v"];
            this[key] = tinycolor(ahsv).toHexString();
        }
        //Move the values from the flattened and tweening object
        //to the original object
        for (key in this) {
            returnValue(from, this, key);
        }
    })
    .onComplete( function () {
        console.log(from) // Everything tweened correctly!
    }).start();

function r(frame) {
    requestAnimationFrame( r );
    TWEEN.update();
}
r();

Uses code snippets from:
http://stackoverflow.com/questions/10934664/convert-string-in-dot-notation-to-get-the-object-reference
https://gist.github.com/wmbenedetto/5080102

@thednp
Copy link
Contributor

thednp commented Oct 8, 2014

@gautamadude do you have a working example of tweening colors? Would you please share it with us?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants