Impact

This forum is read only and just serves as an archive. If you have any questions, please post them on github.com/phoboslab/impact

6 years ago by Joncom

From Wikipedia:
interpolation is a method of constructing new data points within the range of a discrete set of known data points
So this plugin let's you specify two points (start and end values) and get all the points in between for any time period you choose.

I use it to quickly (but not instantly) switch the camera between different entities.

https://github.com/Joncom/impact-interpolation

6 years ago by vincentpiel

I think that the idea is good, but the signature of the constructor + the implementation can be improved :

- Have the constructor to require the 3 parameters : start, end, duration.
- The value should be defined as a property (Object.defineProperty) so that you don't need to inject (and slow down / risk issues with) ig.game.Update.

You only deal with linear interpolations here : you might want to handle easing functions ( [0,1] --> [0,1]) as an additionnal optionnal parameter to have more interesting effects.

6 years ago by Joncom

Have the constructor to require the 3 parameters : start, end, duration.
That's a good idea. Done.
The value should be defined as a property (Object.defineProperty) so that you don't need to inject (and slow down / risk issues with) ig.game.Update.
That's an even better idea. Done.

6 years ago by vincentpiel

Ok some more feedbacks :

* Maybe add a fourth parameter, easingFunction, that defaults to the identity function ( x => x ).

* you might implement all this using a pure javascript class.

* In fact, if you handle only numbers, there's even a nicer way to handle the value : overload the valueOf() object method.
Just add ValueOf in the methods and have it return the same as value property.

But what is ValueOf() ??
--> When Javascript expects a number and encounter an object, it will try to get its value using the valueOf() method :
expl : if (object == 2) ...
expl2 : drawImage(image, xCoordObject, yCoordObject);

The default value returned is -expectedly- NaN.
For strings, the value returned is parseFloat(this).

So if you do this with your object, the way to use it will become :

   // in the init or after a given event...
   this.xCoord = new ig.Interpolation(100, 200, 5);

  // in the draw...
  ctx.drawImage ( someImage, this.xCoord, 20);

the example above will have the image move on an horizontal line from 100 to 200 in 5 seconds.

the only issue with this way of dealing with the value is that you (or the framework)
might easily 'break' the object by assigning a value to it :
say xCoord's value is 150 now. if you do :
this.xCoord++;
then you have this.xCoord === 151 (as expected), but it is === to 151, not ==, meaning it won't move a step further : you replaced the interpolation object with the number 151.

You might, for instance, set, in an entity, this.pos.x to an interpoler, but then do not call this.parent() in the update, or the move (this.pos.x += delta * this.vel.x ), will 'kill' the interpoler.

Still i think it is an interesting way to provide access to the value.

6 years ago by Joncom

you (or the framework) might easily 'break' the object by assigning a value to it
Maybe not if you defined valueOf using Object.defineProperty and wrote a setter to handle that situation gracefully?
you might implement all this using a pure javascript class
I'm currently under the impression that external JavaScript libraries can be a hassle.

6 years ago by vincentpiel

For your first point : yes but no :-) valueOf is a method, defining it as a property has no meaning (you cannot change the value of the object by changing its valueOf method).
But yes, if you declare the tweened property as the property of an object, you can handle the assignation gracefully. But the signature of your constructor would become :

 Interpolate ( baseObject, propertyName, start, end, duration, _easeFunction);

why not, it has some pros and cons. Most often i think we indeed tween properties of object instead of wanting a tween object. However, to change the value after an interpolation ended, one would need to redefine the property... Maybe a little messy...

For the pure javascript class thing : you can put a pure js class declaration within an impact module - and also simple object declaration also -. Just as long as you make it available globally as you like (namespacing seems recommanded though : ig.myClass = function() { this. ...}; ig.myClass.prototype = { ...}; ).

6 years ago by Joncom

valueOf is a method, defining it as a property has no meaning
Whoops, my bad! :)
you can put a pure js class declaration within an impact module
Yes, I suppose I did not need to extend ig.Class, habit now. On second thought however, it seems like a good choice because I do make use of the init method and it might look messier than presently if I tried to come up with a pure JavaScript version of the class.
Maybe add a fourth parameter, easingFunction
Yes, I was thinking of perhaps using these...

6 years ago by Krisjet

Not to be a spoilsport, but pretty much the first thing I include in all my Impact projects is TweenMax.js. There is a thread on the forum on how to implement it so it's ticks are in synch with the game (you just add one line in one of the impact classes) and it tweens and interpolates stuff beautifully with all the different kinds of easing functions and onComplete callbacks you could ever wish for.

Link: http://impactjs.com/forums/impact-engine/hybridizing-js-greensock-tweenlite-and-impact

6 years ago by vincentpiel

@Krisjet : interesting, how would you compare with TweenJs from CreateJS ? This one seems interesting also...

A pure javascript function messier ??? Oh my god !!!!!
:-)

Just for fun i re-wrote it, with quite some changes, now it is garbage free and very lightweight (no closure).

ig.module('plugins.joncom.interpolation.interpolation')
.requires('impact.impact', 'impact.game')
.defines(function(){

  /* Example snippet :
     // range from 100 to 200 in 4 sec
      var myNumb = new ig.Interpolation(100, 200, 4 ); 

      myNumb.value <-- current value

      myNumb.done <-- has interpolation ended ?

      you can use the object as a Number: ( !not a number)
      var someValue = 100 + myNumb ;

     launch another interpolation with set. The ease function is kept if unchanged.
     myNumb.set(500, 800, 10);
  */

  // Interpolation : object that will take values from start to end in
  // a duration time (in seconds). 
  // Default to linear interpolation. 
  // define _easeFn argument to set an easing function ([0,1] -> [0,1])
  ig.Interpolation = function(start, end, duration, _easeFn) {
            // private properties
            this._start         = start || 0 ;
            this._end          = end || 0;
            this._duration   = duration || 0 ;
            this._oneOnDuration   = this._duration && 1/duration ;
            this._easeFn    = _easeFn || this._easeFn ; 
            this._done        = (end - start !== 0) && (duration>0);       
            this._startTime = this._done ? -1 : ig.Timer.time;
            return this;
  };
  
  var interpProto = ig.Interpolation.prototype;

  // have the object return its value when used as a Number
  interpProto.valueOf = function() {
    if (this._done) return this._end; 
    var v = ig.Timer.time - this._startTime ;
    if ( v>= this._duration ) { this._done = true;   return this._end; }
    v *=  this._oneOnDuration;
    if  (this._easeFn)  { v = this._easeFn(v); }
    return (this._start ) + ((this._end-this._start) * v);    // just 1 mul
  };

   // public properties : value and done
  defGetter(interpProto, 'value', ig.Interpolation.prototype.valueOf );           
 
  defGetter(interpProto, 'done', function() {
         if (this._done) return true;
         var v = ig.Timer.time - this._startTime ;
         if ( v>= this._duration ) { this._done = true;   return this._end; }
  });

 // public method : set
  interpProto.prototype.set = ig.Interpolation;

  //----------------------------------
  // helper function
  function defGetter(Obj, prop, getterFunc)        {
    propDef.get = getterFunc;
    Object.defineProperty(Obj, prop, propDef );    }
  var propDef = { get : null, configurable : true } ;

});

6 years ago by Joncom

That's cool @vincentpiel, but it doesn't really make sense to store the _done boolean anywhere since the only way it gets updated in your code is via the .done getter. This is at the very least confusing because the .value getter will use it whether _done is accurate or not.

Edit 1: Oh nevermind, I see you run another check which updates _done in the valueOf function. Still, I think there is room for improvement. Give me a moment...

Edit 2: Cleaned things up a bit. Some of the math was a bit off, so corrected that too. This class now works 100% with existing projects that used the class before.

ig.module('plugins.joncom.interpolation.interpolation')
.defines(function(){

    ig.Interpolation = function(start, end, duration, _easeFn) {
        this.start = start || 0;
        this.end = end || 0;
        this.duration = duration || 0;
        this._easeFn = _easeFn || function(v) { return v; };
        this._startTime = ig.Timer.time;
        return this;
    };

    ig.Interpolation.prototype.valueOf = function() {
        if (this.done) {
            return this.end;
        }
        var elapsed = ig.Timer.time - this._startTime
        var v = (this.duration - elapsed) / this.duration;
        if (this._easeFn) {
            v = this._easeFn(v);
        }
        return (this.start * v) + (this.end * (1-v));
    };

    Object.defineProperty(ig.Interpolation.prototype, 'value', {
        get: this.valueOf
    });

    Object.defineProperty(ig.Interpolation.prototype, 'done', {
        get: function() {
            var elapsed = ig.Timer.time - this._startTime;
            return (elapsed >= this.duration);
        }
    });

});

Edit 3: In case you're curious why start, end, and duration are public.. I like them exposed, particularly end because when I'm tweening to an entity position, it is possible that the end can change before the interpolation is done.

Edit 4: What is that public set method in your code for?

Edit 5: I added a couple of easing functions IN and OUT. Example:

var start = 0;
var end = 10;
var duration = 3;
var ease = ig.Interpolation.EASE.OUT;
var interpolation = new ig.Interpolation(start, end, duration, ease);

6 years ago by vincentpiel

Ok, i corrected the math, so now it should be ok, and faster since it is only one multiplication (20 times slower than - or +).
I traded also a / for a * ( / is 35 to 50 times slower than + or - ).

The code i suggested is faster with linear interpolation (one boolean check vs one function call ) and faster when tweet is done since end is 'cached' once true, so it's just a boolean check for .done or .value as long as tween is 'dead'.

set(...) is used to allow setting the tween to some other values, while still keeping the same interpolation function if you wish.
Since i think one would change the 3 values at once, it is more convenient just to call myInterp.set(10,50,3) rather than do 3 assignations.

Keeping the variable '''private''' will allow you to compute everything as you want, in this case, since now i use 1/duration also, just changing by hand duration would lead to strange behaviour.
Another advantage of letting some things hidden : you might for instance perform only integer operations ( *, +, -, and >>) to perform the interpolation using a fixed set of bits (4) to represent the float part. So you need 'your' properties x, y, duration, 1/duration.
Surely faster, and, yes, perfectly useless over-optimization, but that's just for the example.

Better example : In a tween that would rotate, you might use internally polar coordinates. Then changing the x and y property would have no effect.

In your example, changing the tween's start or end directly will in fact change the speed of the tween. If you have the user to use your set() function, you can detect that a tween is ongoing and re-compute start, end, duration to keep the same speed as previously (duration will be changed).

( :-) )

6 years ago by vincentpiel

Just for a laugh : about your use of interpolation to follow an entity : you can have it done once and for all by just writing :

###
// inside Missile Class's init
var target = ???;
var myXFollower = new ig.Interpolation ( this.pos.x,
{ valueOf : function() { return target.x } },
5 );
###

and since, if you don't call this.parent() in the update, you can set pos.x just as you like, you can do :

  this.pos.x = myXFollower;

do the same for y, and bam ! your object is alive ! :-)

(rq : if you change pos.x by yourself, either 'by hand' or with a valueOf Object, think about updating velocity (vel.x, vel.y) if you want accurate collisions, especially for fast objects. )

6 years ago by vincentpiel

a last (?) word : in your code :
    Object.defineProperty(ig.Interpolation.prototype, 'value', {
        get: this.valueOf
    });


'this' is either undefined or the window object at this point, i don't see how this could work.

6 years ago by Joncom

var myXFollower = new ig.Interpolation ( this.pos.x, 
{ valueOf : function() { return target.x } },
5 );
This is genius!! Much simpler and cleaner than the bulkier way I was doing it before (your way get's all of this into a single line):
var entity= this;
Object.defineProperty(this.fireAngleInterpolation, 'end', {
    get: function() { return entity.fireAngle; }
});

Quote from vincentpiel
Object.defineProperty(ig.Interpolation.prototype, 'value', {
    get: this.valueOf
});

'this' is either undefined or the window object at this point, i don't see how this could work.
Somehow it's working though, lol. Strange.

Edit 1

OK, so it ended up working for a strange reason. this.valueOf was returning the object that I meant to reference using the word this. And so I was calling .value which returned the class instance, and since the class has an overloaded .valueOf function now, it worked out OK. Haha. Crazy...

Thanks for pointing that out. I have no idea how to reference the instance properly though. Normally I'd use this, but since the instance does not exist, what can I pass into the getter?

Edit 2

Nevermind, figured out out. Replaced this.valueOf with ig.Interpolation.prototype.valueOf.

6 years ago by DaveVoyles

Can you guys give an example of when / how I would use this?

In particular, with enemies or bullets.

Thank you.

6 years ago by Joncom

Quote from DaveVoyles
Can you guys give an example of when / how I would use this?

In particular, with enemies or bullets.

Thank you.
That totally depends on what you want to accomplish. I can't think of a reason I'd need to use this plugin for bullets off the top of my head. I like to use it to smoothly transition the camera between focus on different entities.

6 years ago by Rungo73

I'd like to use this to reset my camera - is the code above ok to use / complete?

6 years ago by Joncom

@Rungo73: Yeah. I would recommend using the code in the repo.
Page 1 of 1
« first « previous next › last »