Bezier Curve in Transition Function

Hello,

I am looking to implement a seizer curve into a transition.to function. I have two objects, A & B for simplicity’s sake, and am looking for them to swap places, one curving under and one curving over. As of now the two objects simply cross over into each others position, as so:

[lua]

transition.to(h1, {time = moveSpeed, rotation = rotation, x = h2.x, y = h2.y, transition = easing.inQuad}

transition.to(h2, {time = moveSpeed, rotation = rotation_negative, x = h1.x, y = h1.y, transition = easing.inQuad, onComplete= checkMovesLeft})

[/lua]

How can I get them to curve into each others positions? Thanks!

Off-hand I’d probably say go for a quadratic Bézier curve and just interpolate t. Then update the positions themselves in a timer or enterFrame listener. You don’t say how A and B are related (opposite corners, left-to-right, etc.), but maybe a reasonably general way to find the control points, i.e. the P1’s (the “over” one and the “under” one) would be to get the midpoint, then displace perpendicularly in each direction the same distance from the midpoint.

In code, something like (untested):

function checkMovesLeft (h) -- Normal stuff... timer.cancel(h.update\_curves) -- Kill the timer end h1.t = 0 -- Give this to one of the objects transition.to(h1, { time = moveSpeed, rotation = rotation, t = 1, transition = easing.inQuad }) -- Interpolate the time transition.to(h2, { time = moveSpeed, rotation = rotation\_negative, transition = easing.inQuad, onComplete = checkMovesLeft }) -- Cache the state as upvalues. local x1, y1 = h1.x, h1.y local x2, y2 = h2.x, h2.y local midx, midy = .5 \* (x1 + x2), .5 \* (y1 + y2) local to\_midx, to\_midy = midx - x1, midy - y1 -- Perpendiculars: Depends how the points are laid out, assuming leftish to rightish local overx, overy = midx - to\_midy, midy + to\_midx local underx, undery = midx + to\_midy, midy - to\_midx -- Give this to the one with the "onComplete" logic h2.update\_curves = timer.performWithDelay(5, function() -- Coefficients local t = h1.t local s = 1 - t local a = s \* s local b = 2 \* s \* t local c = t \* t h1.x, h1.y = x1 \* a + overx \* b + x2 \* c, y1 \* a + overy \* b + y2 \* c h2.x, h2.y = x2 \* a + underx \* b + x1 \* c, y2 \* a + undery \* b + y1 \* c end, 0)

I probably screwed up somewhere, but that’s the general idea. :slight_smile:

@StarCrunch

Plugging this in and messing around with it, the hats not move, but start to get out of line, before eventually glitching out. Any ideas?

@StarCrunch

Here is a little more context

[lua]

function randomHatMove()

    local randm1 = math.floor(math.random(1,5))

    local randm2 = math.floor(math.random(1,5))

    if (randm1 == randm2) then

        if (randm1 == 1 ) then

            randm1 = randm1 + 1

        elseif (randm1 > 1) then

            randm1 = randm1 - 1

        end

    end

    local h1 = hats[randm1]

    local h2 = hats[randm2]

    totalMoves = totalMoves - 1

    

    h1.t = 0 – Give this to one of the objects

    transition.to(h1, { time = moveSpeed, rotation = rotation, t = 1, transition = easing.inQuad }) – Interpolate the time

    transition.to(h2, { time = moveSpeed, rotation = rotation_negative, transition = easing.inQuad, onComplete = checkMovesLeft })

    – Cache the state as upvalues.

    local x1, y1 = h1.x, h1.y

    local x2, y2 = h2.x, h2.y

    local midx, midy = .5 * (x1 + x2), .5 * (y1 + y2)

    local to_midx, to_midy = midx - x1, midy - y1

    – Perpendiculars: Depends how the points are laid out, assuming leftish to rightish

    local overx, overy = midx - to_midy, midy + to_midx

    local underx, undery = midx + to_midy, midy - to_midx

    – Give this to the one with the “onComplete” logic

    h2.update_curves = timer.performWithDelay(5, function()

    

      – Coefficients

      local t = h1.t

      local s = 1 - t

      local a = s * s

      local b = 2 * s * t

      local c = t * t

      h1.x, h1.y = x1 * a + overx * b + x2 * c, y1 * a + overy * b + y2 * c

      h2.x, h2.y = x2 * a + underx * b + x1 * c, y2 * a + undery * b + y1 * c

    end, 0)

    ball.isVisible = false

end

–playBtn:addEventListener(“tap”, randomHatMove)

function checkMovesLeft(h)

    if(totalMoves > 0) then

        randomHatMove()

        timer.cancel(h.update_curves) – Kill the timer

    else

        hat1:addEventListener(‘tap’, revealBall)

        hat2:addEventListener(‘tap’, revealBall)

        hat3:addEventListener(‘tap’, revealBall)

        hat4:addEventListener(‘tap’, revealBall)

        hat5:addEventListener(‘tap’, revealBall)

        timer.cancel(h.update_curves) – Kill the timer

        totalMoves = 15

    end

end

[/lua]

Also thank you so much. This has been killing me for a while now!

I’m not sure about the glitching, but it looks like the not moving is because the timer gets canceled right after randomHatMove() gets called in checkMovesLeft(). Does the first move work?

Actually, since you’re keeping track of the move count, an enterFrame listener might be a more natural place to update the positions anyhow; timers are just a personal habit. :slight_smile:

Where would you place the Event Listener at in the checkMovesLeft() function?

@StarCrunch

How would you write the code out?

Im sorry I just haven’t been able to figure it out and its getting to me  :frowning:

Hi.

I’d amend the above code as

-- Current hats -- local Hat1, Hat2 -- Bezier polygons -- local x1, y1, x2, y2, overx, overy, underx, undery function randomHatMove() local randm1 = math.floor(math.random(1,5)) local randm2 = math.floor(math.random(1,5)) if (randm1 == randm2) then if (randm1 == 1 ) then randm1 = randm1 + 1 elseif (randm1 \> 1) then randm1 = randm1 - 1 end end local h1 = hats[randm1] local h2 = hats[randm2] totalMoves = totalMoves - 1 h1.t = 0 -- Give this to one of the objects transition.to(h1, { time = moveSpeed, rotation = rotation, t = 1, transition = easing.inQuad }) -- Interpolate the time transition.to(h2, { time = moveSpeed, rotation = rotation\_negative, transition = easing.inQuad, onComplete = checkMovesLeft }) -- Cache some position state (since the positions themselves change as the curve updates). x1, y1 = h1.x, h1.y x2, y2 = h2.x, h2.y local midx, midy = .5 \* (x1 + x2), .5 \* (y1 + y2) local to\_midx, to\_midy = midx - x1, midy - y1 -- Perpendiculars: Depends how the points are laid out, assuming leftish to rightish overx, overy = midx - to\_midy, midy + to\_midx underx, undery = midx + to\_midy, midy - to\_midx -- Tell the enterFrame listener which hats we're watching. Hat1, Hat2 = h1, h2 ball.isVisible = false end --playBtn:addEventListener("tap", randomHatMove) function checkMovesLeft(h) if(totalMoves \> 0) then randomHatMove() else hat1:addEventListener('tap', revealBall) hat2:addEventListener('tap', revealBall) hat3:addEventListener('tap', revealBall) hat4:addEventListener('tap', revealBall) hat5:addEventListener('tap', revealBall) totalMoves = 15 end end Runtime:addEventListener("enterFrame", function() if totalMoves \> 0 then -- Coefficients local t = Hat1.t local s = 1 - t local a = s \* s local b = 2 \* s \* t local c = t \* t Hat1.x, Hat1.y = x1 \* a + overx \* b + x2 \* c, y1 \* a + overy \* b + y2 \* c Hat2.x, Hat2.y = x2 \* a + underx \* b + x1 \* c, y2 \* a + undery \* b + y1 \* c end end)

For reference, is this roughly the behavior you would like to see?

local h1 = display.newRect(50, 50, 100, 100) local h2 = display.newRect(400, 200, 100, 100) h1.t = 0 -- Give this to one of the objects local moveSpeed = 5000 local rotation = 180 local rotation\_negative = -180 transition.to(h1, { time = moveSpeed, rotation = rotation, t = 1, transition = easing.inQuad }) -- Interpolate the time transition.to(h2, { time = moveSpeed, rotation = rotation\_negative, transition = easing.inQuad }) -- Cache the state as upvalues. local x1, y1 = h1.x, h1.y local x2, y2 = h2.x, h2.y local midx, midy = .5 \* (x1 + x2), .5 \* (y1 + y2) local to\_midx, to\_midy = midx - x1, midy - y1 -- Perpendiculars: Depends how the points are laid out, assuming leftish to rightish local overx, overy = midx - to\_midy, midy + to\_midx local underx, undery = midx + to\_midy, midy - to\_midx Hat1, Hat2 = h1, h2 Runtime:addEventListener("enterFrame", function() if Hat1.t \>= 1 then return end -- Coefficients local t = Hat1.t local s = 1 - t local a = s \* s local b = 2 \* s \* t local c = t \* t Hat1.x, Hat1.y = x1 \* a + overx \* b + x2 \* c, y1 \* a + overy \* b + y2 \* c Hat2.x, Hat2.y = x2 \* a + underx \* b + x1 \* c, y2 \* a + undery \* b + y1 \* c end)

Its giving me the error " attempting to index upvalue Hat1 ( a nil value)"

Any ideas? Also thank you so much

My guess is that totalMoves starts as 15? In that case, “if totalMoves > 0 then” is not the right test to be doing in the enterFrame listener (since it would already “succeed” before we’ve moved any hats). Try it with “if Hat1 then” instead, and clear the hats when you set totalMoves back to 15 (“Hat1, Hat2 = nil”).

The hats now move properly, but the ball pops up under the wrong hat! Im about to go crazy on this one. 

[lua]

–Start Movement---------------------------------------------------------------------------------------

function upHatAnimation()

    transition.to( hat3, { time = 500, y = hat3.y - 150})

    local function downHatAnimation ()

        transition.to( hat3, { time = 500, y = hat3.y + 150, onComplete = randomHatMove})

    end

    timer.performWithDelay( 3000, downHatAnimation, 1)

    playBtn.alpha = 0.5

    playBtn:removeEventListener(“tap”, upHatAnimation)

    textBoxStart.isVisible = false

    textBoxBlank.isVisible = true

    textBoxRight.isVisible = false

    textBoxWrong.isVisible = false

    textBoxPick.isVisible = false

    playBtn:removeEventListener(“tap”, playBtn)

    --backArrow:removeEventListener(“tap”, gotoGame)

end

playBtn:addEventListener(“tap”, upHatAnimation)

–Movement---------------------------------------------------------------------------------------

local currentHat1, currentHat2

function randomHatMove()

    local randm1 = math.floor(math.random(1,5))

    local randm2 = math.floor(math.random(1,5))

    if (randm1 == randm2) then

        if (randm1 == 1 ) then

            randm1 = randm1 + 1

        elseif (randm1 > 1) then

            randm1 = randm1 - 1

        end

    end

    local h1 = hats[randm1]

    local h2 = hats[randm2]

    totalMoves = totalMoves - 1

    

    h1.t = 0 – Give this to one of the objects

    transition.to(h1, { time = moveSpeed, rotation = rotation, t = 1, transition = easing.inQuad }) – Interpolate the time

    transition.to(h2, { time = moveSpeed, rotation = rotation_negative, transition = easing.inQuad, onComplete = checkMovesLeft })

    – Cache some position state (since the positions themselves change as the curve updates).

    x1, y1 = h1.x, h1.y

    x2, y2 = h2.x, h2.y

    local midx, midy = .5 * (x1 + x2), .5 * (y1 + y2)

    local to_midx, to_midy = midx - x1, midy - y1

    – Perpendiculars: Depends how the points are laid out, assuming leftish to rightish

    overx, overy = midx - to_midy, midy + to_midx

    underx, undery = midx + to_midy, midy - to_midx

    – Tell the enterFrame listener which hats we’re watching.

    currentHat1, currentHat2 = h1, h2

    ball.isVisible = false

end

–playBtn:addEventListener(“tap”, randomHatMove)

function checkMovesLeft(h)

     

     function checkHatMovement ()

      if totalMoves > 0 then

          – Coefficients

          local t = currentHat1.t

          local s = 1 - t

          local a = s * s

          local b = 2 * s * t

          local c = t * t

    

          currentHat1.x, currentHat1.y = x1 * a + overx * b + x2 * c, y1 * a + overy * b + y2 * c

          currentHat2.x, currentHat2.y = x2 * a + underx * b + x1 * c, y2 * a + undery * b + y1 * c

      end

    end

    Runtime:addEventListener(“enterFrame”, checkHatMovement)

   

    if(totalMoves > 0) then

        timer.performWithDelay(100, randomHatMove, 1)

    else

        hat1:addEventListener(‘tap’, revealBall)

        hat2:addEventListener(‘tap’, revealBall)

        hat3:addEventListener(‘tap’, revealBall)

        hat4:addEventListener(‘tap’, revealBall)

        hat5:addEventListener(‘tap’, revealBall)

        Runtime:removeEventListener(“enterFrame”, checkHatMovement)

        totalMoves = 15

    end

end

–Reveal Ball---------------------------------------------------------------------------------------

function revealBall(e)

    textBoxBlank.isVisible = false    

    textBoxPick.isVisible = true

    hat1:removeEventListener(‘tap’, revealBall)

    hat2:removeEventListener(‘tap’, revealBall)

    hat3:removeEventListener(‘tap’, revealBall)

    hat4:removeEventListener(‘tap’, revealBall)

    hat5:removeEventListener(‘tap’, revealBall)

    ball.x = hat3.x + 130

    ball.y = hat3.y + 250

    ball.isVisible = true

    if ( e.target.name == ‘finalHat’ ) then

        textBoxPick.isVisible = false

        textBoxRight.isVisible = true

        transition.to( hat3, { time = 500, y = hat3.y - 150})

        local function downHatAnimation_end ()

            transition.to( hat3, { time = 500, y = hat3.y + 150})

            playBtn:addEventListener(“tap”, upHatAnimation)

            playBtn.alpha = 1

        end

        timer.performWithDelay( 1500, downHatAnimation_end, 1)

    else

        textBoxPick.isVisible = false

        textBoxWrong.isVisible = true

        transition.to( hat3, { time = 500, y = hat3.y - 150})

        local function downHatAnimation_end ()

            transition.to( hat3, { time = 500, y = hat3.y + 150})

            playBtn.alpha = 1

            playBtn:addEventListener(“tap”, upHatAnimation)

            --backArrow:addEventListener(“tap”, gotoGame)

        end

        timer.performWithDelay( 1500, downHatAnimation_end, 1)

    end

end

[/lua]

Any idea whatsoever?

I don’t know if this is related, but you want to pull your checkHatMovement() function outside of checkMovesLeft()… otherwise it’s getting redefined every time you enter that function and you’ll pile up 15 unique listeners.  :slight_smile: It’s “harmless” because it only does the same assignment over and over, but the only event listener that will get removed is the one you just added, so it’s possible that’s causing some subtle error, e.g. immediately interpolating to the next hat.

When I pull out checkHatMovement(), it tells me that " Attempt to index ‘currentHat’ (a nil value)" Any idea?

If that wasn’t a typo, then you just forgot the “1” on currentHat1, so you’re accessing a non-existent variable.

Otherwise… did you move the function / end part outside, too? If you just moved the code out, it would be in file scope and get called while the script is loading, which is a bit early.  :slight_smile:

Basically, is it like this?

 function checkHatMovement () if totalMoves \> 0 then -- Coefficients local t = currentHat1.t local s = 1 - t local a = s \* s local b = 2 \* s \* t local c = t \* t currentHat1.x, currentHat1.y = x1 \* a + overx \* b + x2 \* c, y1 \* a + overy \* b + y2 \* c currentHat2.x, currentHat2.y = x2 \* a + underx \* b + x1 \* c, y2 \* a + undery \* b + y1 \* c end end function checkMovesLeft(h) Runtime:addEventListener("enterFrame", checkHatMovement) -- ...and so on

That works not, but is there a way to make it only check the function once every move? RIght now it is infinitely checking the movements, causing memory errors.

Ah, it’s probably still doing that pile-up, just a little slower. It looks like upHatAnimation is where you kick the whole thing off? If so, try adding the enterFrame listener there, instead. (It looks like then it should only get added once, and removed when it’s all done.)

Its still giving me the " Attempt to index ‘currentHat1’ (a nil value)"

Ah, right, there’s the timer.

Well, if you do want to add the listener in checkMovesLeft(), maybe it would suffice to guard it with “if totalMoves == 15 then” (it starts at 15, right?).

An alternative would be, instead of passing randomHatMove directly as the onComplete parameter in upHatAnimation(), instead pass a function that adds the listener first, then calls randomHatMove().

Off-hand I’d probably say go for a quadratic Bézier curve and just interpolate t. Then update the positions themselves in a timer or enterFrame listener. You don’t say how A and B are related (opposite corners, left-to-right, etc.), but maybe a reasonably general way to find the control points, i.e. the P1’s (the “over” one and the “under” one) would be to get the midpoint, then displace perpendicularly in each direction the same distance from the midpoint.

In code, something like (untested):

function checkMovesLeft (h) -- Normal stuff... timer.cancel(h.update\_curves) -- Kill the timer end h1.t = 0 -- Give this to one of the objects transition.to(h1, { time = moveSpeed, rotation = rotation, t = 1, transition = easing.inQuad }) -- Interpolate the time transition.to(h2, { time = moveSpeed, rotation = rotation\_negative, transition = easing.inQuad, onComplete = checkMovesLeft }) -- Cache the state as upvalues. local x1, y1 = h1.x, h1.y local x2, y2 = h2.x, h2.y local midx, midy = .5 \* (x1 + x2), .5 \* (y1 + y2) local to\_midx, to\_midy = midx - x1, midy - y1 -- Perpendiculars: Depends how the points are laid out, assuming leftish to rightish local overx, overy = midx - to\_midy, midy + to\_midx local underx, undery = midx + to\_midy, midy - to\_midx -- Give this to the one with the "onComplete" logic h2.update\_curves = timer.performWithDelay(5, function() -- Coefficients local t = h1.t local s = 1 - t local a = s \* s local b = 2 \* s \* t local c = t \* t h1.x, h1.y = x1 \* a + overx \* b + x2 \* c, y1 \* a + overy \* b + y2 \* c h2.x, h2.y = x2 \* a + underx \* b + x1 \* c, y2 \* a + undery \* b + y1 \* c end, 0)

I probably screwed up somewhere, but that’s the general idea. :slight_smile:

@StarCrunch

Plugging this in and messing around with it, the hats not move, but start to get out of line, before eventually glitching out. Any ideas?