Moving objects along curved paths using Bezier

I have read the excellent tutorial for moving display objects along curved paths using Bezier curves (https://coronalabs.com/blog/2014/09/09/tutorial-working-with-curved-paths/). The problem is that the example code only uses two anchor points. I have been trying to make the code more generic so that any number of anchor points can be used but I simply cannot figure out how to do it…  

The biggest problem is the function where the curve itself is rendered and I am completely lost at the “Bezier formula” stuff. Anyone done this before?

local function renderCurve( segNum, r, g, b ) local inc = ( 1.0 / segNum ) for i = 1,#anchorPoints,4 do local t = 0 local t1 = 0 local i = 1 for j = 1,segNum do t1 = 1.0 - t local t1\_3 = t1 \* t1 \* t1 local t1\_3a = (3\*t) \* (t1\*t1) local t1\_3b = (3\*(t\*t)) \* t1 local t1\_3c = t \* t \* t local p1 = anchorPoints[i] local p2 = anchorPoints[i+1] local p3 = anchorPoints[i+2] local p4 = anchorPoints[i+3] local x = t1\_3 \* p1.x x = x + t1\_3a \* p2.x x = x + t1\_3b \* p3.x x = x + t1\_3c \* p4.x local y = t1\_3 \* p1.y y = y + t1\_3a \* p2.y y = y + t1\_3b \* p3.y y = y + t1\_3c \* p4.y pathPoints[j].x = x pathPoints[j].y = y t = t + inc end end --reset/clear core if curve then display.remove( curve ) end curve = display.newLine( pathPoints[1].x, pathPoints[1].y, pathPoints[2].x, pathPoints[2].y ) for i = 3,#pathPoints do curve:append( pathPoints[i].x, pathPoints[i].y ) end curve:append( anchorPoints[4].x, anchorPoints[4].y ) curve:setStrokeColor( r, g, b ) curve.strokeWidth = 4 curve:toBack() end 

For more than you probably ever wanted to know: A Primer on Bézier Curves

The code appears to follow this format: point on the curve, two control points, then another point on the curve.

To stitch curve segments together (which is probably what you want?), you can reuse the fourth point from a given segment as the first one in the next. So for two segments it would go P-C-C-P-C-C-P, where P represents a point on the curve and C is a control point. This change might be enough to effect that:

for i = 1,#anchorPoints - 1,3 do -- skip ahead by three to land on last point; subtract 1 so -- as not to do one last iteration with only the final point

Obviously you’ll still need to adjust some of the other code for laying down points.

Hi,

I found these videos very helpful:

Coding Math: Episode 19 - Bezier Curve: https://youtu.be/dXECQRlmIaE

Coding Math: Episode 20 - More on Bezier Curves: https://youtu.be/2hL1LGMVnVM

TL;DR - Basically, his approach is (NOTE: he uses 3-points per curve segment):

p1-----------p2-----a-----p3-----b-----p4

a & b = computed mid-points

Using the above points, you draw the curve as:

p1->p2->a,

a->p3->b,

b->p4->…

This joins the curves segments without any noticeable kinks or bends.

He explains it really well in the videos.

HTH

-David

Some of the examples with Level Director X use Bezier curves with an unlimited number of points, so you might want to check them out for reference.

Thanks for all the answers, guys!

@Star Crunch: I read the Bézier primer, which was extremely good. Yes, it contained more information than I ever wanted about Bézier curves but now I fully understand what they are and how they are calculated.

@DGuy: I watched the videos and they were also very pedagogical. 

@retrofitProductions: I actually came across your product when searching for an answer to my problem but I thought I might solve it the old fashioned way by coding it myself. However, if I can’t get my code working with a reasonable effort I might just purchase it…

@retrofitProductions: is it possible to draw the Bézier curve in the tool and then just export the path as lua table of curve points?

Yes, in LDX you can draw the Bezier and export the data.

The library will try and draw the Bezier, but you can override this by setting the line color alpha to 0, it will skip the drawing part, and you can then access the data points.

LDX gives me a list with xy coordinates for the control points, which is good, since a full list of all xy coordinates for the bezier curve would be very long. So, I have tried to merge the “cruved paths tutorial” (see my first post) with Rajendra Pondel’s bezier.lua (https://github.com/neostar20/Bezier-Curve-for-Corona-SDK/blob/master/Examples/Three%20Curves/bezier.lua), which seems to be used everywhere (but uses the deprecated “package.seeall”) including LDX. I still can’t get it to work.

Can anyone please tell me what I am doing wrong? I’ve been at this for a week now…

local function bezier(xValues, yValues) -- Set number of segments local segments = 100 -- Set place holder point values local points = {} for x = 1, #xValues do points[#points + 1] = { x = 0, y = 0 } end local t = 0 local t1 = 0 -- Iterate over all x and y values for i = 1, #xValues - 1, 2 do t1 = 1.0 - t -- Calculate bezier curve point points[i].x = xValues[i] + (xValues[i + 1] - xValues[i]) \* t1 points[i].y = yValues[i] + (yValues[i + 1] - yValues[i]) \* t1 t = t + (1 / segments) end return points end

That’s right, LDX still uses Rajendra’s library because it works so well (even though it does use the deprecated ‘package.seall’) and I question why you would want to reinvent the wheel, I tried to do something similar all those years ago but decided this little library was a much better solution.

You still need a generated Bezier curve (LDX exports the control vectors), so I think at minimum you’ll need the following;

-- create the Bezier curve based on the given points (generated from LDX) myCurve = bezier:curve(xValues, yValues) -- obtain x,y values of generated curve x, y = myCurve(0.01) for i=0.01, 1, 0.02 do x1,y1 = myCurve(i+0.01) -- decide what to do with points x,y,x1,y1 -- or store them and post process them afterwards x = x1 y = y1 end

If you look at LD_Helper.lua file, it does something similar, but actually draws lines, but from this concept you can decide if you want to store a subset of the generated points.

Hope this helps

I should have clarified, the example routine finds two points (start,end) as though you were drawing a line from x,y to x1,y1, so it could be simplified even further.

I have now found a solution that I want to share in case someone else finds it useful. The code is derived partly from information found in the amazing guide that StarCrunch linked to above (thanks!) and the Corona tutorial that I refer to in my original post. 

Apart from creating the actual path points for the curve I also create tables with information about the transition time that should be used for a specific segment if you want constant speed. A table with rotation data is also created and should be used if you want the object to rotate so that it always has the same side facing the direction it moves.

function M.renderCurve(params) -- Set up local drawCurve = {} local t = 0 local x = 0 local y = 0 local newpoints = {} local anchorPoints = params.anchorPoints -- The anchor points to calculate the path points from local curveSegments = params.curveSegments -- The number of segments, higher number gives smoother curve local timePerSegment = params.timePerSegment -- The time to transition each segment local doRotate = params.doRotate -- If the transition object should rotate with the curve or not local curveData = {} local curvePathPoints = {} local curveSegmentTimes = {} local curveRotations = {} local calculus = {} for j = 1, (curveSegments - 1) do function drawCurve(points, t) if (#points == 1) then curvePathPoints[#curvePathPoints + 1] = { x = points[1].x, y = points[1].y } else newpoints = {} for i = 1, (#points - 1) do x = (1 - t) \* points[i].x + t \* points[i + 1].x y = (1 - t) \* points[i].y + t \* points[i + 1].y newpoints[i] = { x = x, y = y } end drawCurve(newpoints, t) end end drawCurve(anchorPoints, t) -- Update counter t = (t + (1.0 / curveSegments)) end -- The last point in the bezier curve path is the point of the last control point curvePathPoints[#curvePathPoints + 1] = { x = anchorPoints[#anchorPoints].x, y = anchorPoints[#anchorPoints].y } -- Iterate over all segments and create tables with segment time and rotation data for i = 1, #curvePathPoints - 1 do if (timePerSegment ~= nil) then calculus.xFactor = (curvePathPoints[i + 1].x - curvePathPoints[i].x) calculus.yFactor = (curvePathPoints[i + 1].y - curvePathPoints[i].y) calculus.dist = properties.definedSqrt((calculus.xFactor \* calculus.xFactor) + (calculus.yFactor \* calculus.yFactor)) curveSegmentTimes[i] = (calculus.dist \* timePerSegment) end if (doRotate) then calculus.angle = (properties.definedDeg(properties.definedAtan((curvePathPoints[i + 1].y - curvePathPoints[i].y), (curvePathPoints[i + 1].x - curvePathPoints[i].x))) + 90) calculus.angleModulo = (calculus.angle % 360) curveRotations[i] = calculus.angleModulo end end -- Add all data to master table curveData.curvePathPoints = curvePathPoints if (timePerSegment ~= nil) then curveData.curveSegmentTimes = curveSegmentTimes end if (doRotate) then curveData.curveRotations = curveRotations end -- Return master table return curveData end

For more than you probably ever wanted to know: A Primer on Bézier Curves

The code appears to follow this format: point on the curve, two control points, then another point on the curve.

To stitch curve segments together (which is probably what you want?), you can reuse the fourth point from a given segment as the first one in the next. So for two segments it would go P-C-C-P-C-C-P, where P represents a point on the curve and C is a control point. This change might be enough to effect that:

for i = 1,#anchorPoints - 1,3 do -- skip ahead by three to land on last point; subtract 1 so -- as not to do one last iteration with only the final point

Obviously you’ll still need to adjust some of the other code for laying down points.

Hi,

I found these videos very helpful:

Coding Math: Episode 19 - Bezier Curve: https://youtu.be/dXECQRlmIaE

Coding Math: Episode 20 - More on Bezier Curves: https://youtu.be/2hL1LGMVnVM

TL;DR - Basically, his approach is (NOTE: he uses 3-points per curve segment):

p1-----------p2-----a-----p3-----b-----p4

a & b = computed mid-points

Using the above points, you draw the curve as:

p1->p2->a,

a->p3->b,

b->p4->…

This joins the curves segments without any noticeable kinks or bends.

He explains it really well in the videos.

HTH

-David

Some of the examples with Level Director X use Bezier curves with an unlimited number of points, so you might want to check them out for reference.

Thanks for all the answers, guys!

@Star Crunch: I read the Bézier primer, which was extremely good. Yes, it contained more information than I ever wanted about Bézier curves but now I fully understand what they are and how they are calculated.

@DGuy: I watched the videos and they were also very pedagogical. 

@retrofitProductions: I actually came across your product when searching for an answer to my problem but I thought I might solve it the old fashioned way by coding it myself. However, if I can’t get my code working with a reasonable effort I might just purchase it…

@retrofitProductions: is it possible to draw the Bézier curve in the tool and then just export the path as lua table of curve points?

Yes, in LDX you can draw the Bezier and export the data.

The library will try and draw the Bezier, but you can override this by setting the line color alpha to 0, it will skip the drawing part, and you can then access the data points.

LDX gives me a list with xy coordinates for the control points, which is good, since a full list of all xy coordinates for the bezier curve would be very long. So, I have tried to merge the “cruved paths tutorial” (see my first post) with Rajendra Pondel’s bezier.lua (https://github.com/neostar20/Bezier-Curve-for-Corona-SDK/blob/master/Examples/Three%20Curves/bezier.lua), which seems to be used everywhere (but uses the deprecated “package.seeall”) including LDX. I still can’t get it to work.

Can anyone please tell me what I am doing wrong? I’ve been at this for a week now…

local function bezier(xValues, yValues) -- Set number of segments local segments = 100 -- Set place holder point values local points = {} for x = 1, #xValues do points[#points + 1] = { x = 0, y = 0 } end local t = 0 local t1 = 0 -- Iterate over all x and y values for i = 1, #xValues - 1, 2 do t1 = 1.0 - t -- Calculate bezier curve point points[i].x = xValues[i] + (xValues[i + 1] - xValues[i]) \* t1 points[i].y = yValues[i] + (yValues[i + 1] - yValues[i]) \* t1 t = t + (1 / segments) end return points end

That’s right, LDX still uses Rajendra’s library because it works so well (even though it does use the deprecated ‘package.seall’) and I question why you would want to reinvent the wheel, I tried to do something similar all those years ago but decided this little library was a much better solution.

You still need a generated Bezier curve (LDX exports the control vectors), so I think at minimum you’ll need the following;

-- create the Bezier curve based on the given points (generated from LDX) myCurve = bezier:curve(xValues, yValues) -- obtain x,y values of generated curve x, y = myCurve(0.01) for i=0.01, 1, 0.02 do x1,y1 = myCurve(i+0.01) -- decide what to do with points x,y,x1,y1 -- or store them and post process them afterwards x = x1 y = y1 end

If you look at LD_Helper.lua file, it does something similar, but actually draws lines, but from this concept you can decide if you want to store a subset of the generated points.

Hope this helps

I should have clarified, the example routine finds two points (start,end) as though you were drawing a line from x,y to x1,y1, so it could be simplified even further.