Mixing Easing Calls and Bezier Paths

I’m trying to mix using this bezier.lua file I found:

local bezier = {} function bezier:curve(xv, yv) local reductor = {\_\_index = function(self, ind) return setmetatable({tree = self, level = ind}, {\_\_index = function(curves, ind) return function(t) local x1, y1 = curves.tree[curves.level-1][ind](t) local x2, y2 = curves.tree[curves.level-1][ind+1](t) return x1 + (x2 - x1) \* t, y1 + (y2 - y1) \* t end end}) end } local points = {} for i = 1, #xv do points[i] = function(t) return xv[i], yv[i] end end return setmetatable({points}, reductor)[#points][1] end return bezier

with the easing transition functions found here: https://docs.coronalabs.com/daily/api/library/easing/index.html.

Is there a smart way to do this? I’m finding that the bezier movement is often don’e with a timer or listener in many iterations of a loop of some kind. But the problem is that the transition.to() call (something like:

transition.to(enemyGroup[1], { x = 300, y = 300, time = 2000, transition=easing.inCubic })

is just called once and I can’t see a way to fit a bezier path in there. Any ideas?

https://coronalabs.com/blog/2014/09/09/tutorial-working-with-curved-paths/

I don’t think this will work. The transition ends up happening in the code block:

 transition.to( obj, { tag = "moveObject", time = transTime, x = pathPoints[obj.nextPoint].x, y = pathPoints[obj.nextPoint].y, onComplete = nextTransition })

which gets repeated several times. I don’t think throwing an easing call in there would do what I want it to since it would just perform the easing motion on the very small segments that is being traveled in each of the many transition.to() calls. By all means correct me if I am wrong, but I don’t think this will work.

Super-simple way would be to count the points in the curve and only perform the transition  with easing at specific points.

Should I assume that you only want to the easing to be applied to the first/last points in the curve, or do you have other arbitrary points you’d like it applied to as well?

Well my thought is that the easing effect won’t be recognizable when you apply it over and over to, say, 100 tiny line segments. I guess I am confused as to what you are asking.

I agree with your last point, which is why it would be more aesthetically pleasing to only apply the easing at specific points, rather than along the entire path. 

If you go verbatim by what the above tutorial is describing, you are generating a table  pathPoints  which would contain all of the relevant points that the object would move along. The important part here is the  obj.nextPoint portion. Above, you left out the “obj.nextPoint = obj.nextPoint+1”, which is really where the progression takes place. If you set some logic to say:

if obj.nextPoint == 2 then -- set easing here end

That would allow you to set easings as you see fit.

EDIT: Obviously the above is a ham-fisted example, but it illustrates the technique.

I think there is still a disconnect somewhere between us. I don’t feel like applying easing effects to any number (no matter how large or small) of these tiny segments will create a noticeable effect that I am looking for. I want the easing to appear as though the bezier path was one contiguous line (meaning the easing motion is noticed from the very first to the very last point).

I think you might me assuming that this code produces curved segments, or that it has some segments that a a few 10’s of pixels apart from one another. The points for each transition.to() call are pixels away from each other, so applying an easing effect to any amount of them causes a weird shaky behavior.

Ah, I see what you are saying, now. However, I’m a bit confused, as well. If you want to apply a special easing to a transition as a whole, per your example “inCubic”, would you not want the path to incorporate the properties of the easing you have in mind? 

 If I were to attempt to mimic the easing “inCubic” (start slow, speed up over time) with the tutorial’s function, I’d do something like:

if obj.nextPoint % 9 then transTime = 1000 else transTime = transTime-100 end

Again, another short-hand example, but the theory should still apply. I’m not going to pretend to be an expert on transitions, so perhaps someone else can provide something more intelligent/tested. 

If I’ve again misunderstood your question and/or intent, I apologize.

I heard TweenTrain is also a good option. 

I see what you are going for, and I know this indeed will produce the effect I am going for. However, I know if I go this route I won’t be able to use all the nice predefined easing functions. I will effectively be writing my own which isn’t really ideal but it may have to suffice until someone comes up with something better :confused:

And to answer your question of “would you not want the path to incorporate the properties of the easing you have in mind?”: Applying the “starting slow and speeding up” effect to line segments which are 1-5 pixels long makes the movement look jittery and not at all like the behavior of performing the ease effect on the line as a whole.

you (probably) need the parametric bezier equation, because it sounds like you’re trying to alter the t parameter *a priori* to determining the xy’s.  (and “easing” is simply modifying a linear “t” in a parametric equation to something non-linear, like t^2 fe)

the code you’re using seems to do its own quanitizing of the curve (based on a two-second read), so it’s “too late” to ease those quantized values (they were created into linear array indices by a linear t).  yes, you could ease between pairs of those linear-interpolated positions, but as you seem to be trying to point out – that WON’T do much.

on the other hand, a parametric bezier typically looks like f(a,b,c,d,t) – if you ease on THAT “t”, THEN quantize it into discrete array of x/y’s, THEN I think you’d get something you could use transition.to() as a linear interpolation as per that article.

This does sound very promising, is there some documentation on this kind of thing that you could point me to?

Does anyone know where I could find an example of an implementation of a transition with “parametric bezier equations”?

Well, using the stuff above, you might try a proxy table and something like this (untested):

local function Interpolate (object, vx, vy, time, transition) local t = 0 -- captured by metamethods local curve = bezier:curve(vx, vy) -- ditto local proxy = setmetatable({}, { -- table is just a dummy; metamethods do all the heavy lifting \_\_index = function() return t -- transition will do something like proxy.t = proxy.t + delta end, \_\_newindex = function(\_, \_, v) t = v -- update object.x, object.y = curve(t) -- sneak in some work end }) return transition.to(proxy, { t = 1, time = time, transition = transition }) end

and then call it, say, as

Interpolate(enemyGroup[1], xv, yv, 2000, easing.inCubic)

You’d be better off baking your x and y into the curve, I’d say.

THIS DID IT. Thank you so much! I’m definitely going to take the time to go through this and make sure I know what is happening with this code block!

Thank you to everyone else who contributed to this answer as well! This community is so helpful!

-- building from StarCrunch's voodoo, replacing "curve"/etal.. function CubicBezierTransition(obj,x1,y1,x2,y2,x3,y3,x4,y4,time,tran) local t = 0 local function cubicBezier(a,b,c,d,t) local s = 1-t local ss = s\*s local tt = t\*t return ss\*s\*a + 3\*ss\*t\*b + 3\*s\*tt\*c + tt\*t\*d end local proxy = setmetatable({},{ \_\_index = function() return t end, \_\_newindex = function(\_,\_,v) t = v; obj.x, obj.y = cubicBezier(x1,x2,x3,x4,t), cubicBezier(y1,y2,y3,y4,t) end }) return transition.to(proxy, {t=1, time=time, transition=tran}) end local obj1 = display.newRect(0,0,10,10) obj1:setFillColor(1,0,0) local obj2 = display.newRect(0,0,10,10) obj2:setFillColor(0,1,0) local obj3 = display.newRect(0,0,10,10) obj3:setFillColor(0,0,1) local obj4 = display.newRect(0,0,10,10) obj4:setFillColor(1,0,1) -- nb: bezier coords assume 320x480 screen, a lazy/loose "Z" -- linear CubicBezierTransition(obj1, 10,100, 300,200, 10,300, 300,400, 5000, easing.linear) -- eased CubicBezierTransition(obj2, 10,100, 300,200, 10,300, 300,400, 5000, easing.inCubic) -- double-speed there and back again linearly -- (just for contrast against custom ease below) CubicBezierTransition(obj3, 10,100, 300,200, 10,300, 300,400, 5000, easing.continuousLoop) function easingSinLoop(t,tmax,start,delta) return start+math.sin(t/tmax\*math.pi)\*delta end -- custom-eased, double-speed there and back again smoothed CubicBezierTransition(obj4, 10,100, 300,200, 10,300, 300,400, 5000, easingSinLoop)

This was excellent help thanks!

I’ve modified it slightly to include rotation while following the curve:

local function angleBetween( srcX, srcY, dstX, dstY ) local angle = ( math.deg( math.atan2( dstY-srcY, dstX-srcX ) )+90 ) return angle % 360 end function CubicBezierTransition(obj,x1,y1,x2,y2,x3,y3,x4,y4,time,tran) local t = 0 local function cubicBezier(a,b,c,d,t) local s = 1-t local ss = s\*s local tt = t\*t return ss\*s\*a + 3\*ss\*t\*b + 3\*s\*tt\*c + tt\*t\*d end local proxy = setmetatable({},{ \_\_index = function() return t end, \_\_newindex = function(\_,\_,v) t = v; local oldX = obj.x; local oldY = obj.y; obj.x, obj.y = cubicBezier(x1,x2,x3,x4,t), cubicBezier(y1,y2,y3,y4,t) obj.rotation = angleBetween(oldX, oldY, obj.x, obj.y) end }) return transition.to(proxy, {t=1, time=time, transition=tran}) end

https://coronalabs.com/blog/2014/09/09/tutorial-working-with-curved-paths/