Need help understanding Bezier Curves

So currently I have a working Bezier Curve script but im a little confused on how to get the points to spread out evenly. I’ve read the article on DevHub but im a little confused. Can anyone help me understand?

Current error: https://gyazo.com/0a7c6d3e862e68004705b2c2a888e8bb

My script:

function lerp(p0, p1, t)
	return (1 - t) * p0 + t * p1
end

local function quadratic(p0, p1, p2, t)
	local L1 = lerp(p0, p1, t)
	local L2 = lerp(p1, p2, t)
	return lerp(L1, L2, t)
end

local function cubic(p0, p1, p2, p3, t)
	local Q1 = quadratic(p0, p1, p2, t)
	local Q2 = quadratic(p1, p2, p3, t)
	return lerp(Q1, Q2, t)
end

function travelCurve(p0, p1, p2, p3, cart)
    for t = 0, 1, 0.01 do 
		cart.Position = cubic(p0.Position, p1.Position, p2.Position, p3.Position, t)
		cart.Orientation = cubic(p0.Orientation, p1.Orientation, p2.Orientation, p3.Orientation, t)
		local newpart = Instance.new("Part")
		newpart.Anchored = true
		newpart.CanCollide = false
		newpart.Material = "Neon"
		newpart.BrickColor = BrickColor.White()
		newpart.Position = cart.Position
		newpart.Orientation = cart.Orientation
		newpart.Size = Vector3.new(0.25, 0.25, 0.25)
		newpart.Parent = workspace
	wait()
	end
end

travelCurve(workspace.p0, workspace.p1, workspace.p2, workspace.p3, workspace.cart)
3 Likes

What part of the arc length parametrization section of that article do you need clarification on?

Mostly all of it, im just having trouble understanding it and how to implement it into my code. I just wanna know how it works so I can maybe create my own because I don’t understand most of the code in the article.

think about what it takes to make the curve evenly spaced. equal distance between each point. that means every l / n studs should have a point where n is the number of points and l is the length of the curve, let’s call that d.

of course, it’s not that easy to just readjust every point, since a completely smooth curve would take an infinite number of points and we only have n to work with. the way it works is:

  • generate every point and get the total distance traveled at that specific point (by adding the distance between old and new each time) in a list.
  • then, go through the list the you made and find the actual distance it should be at. that’s simple, just multiply the point number by d. let’s call this target distance t.
  • then, from the old list, find the closest point that has a distance less than t. this is the one you’re lerping.
  • how much to lerp? well, lerp the target point (d0) to the next point (d1) in such a way that it reaches t which is (t - d0) / (d1 - d0) percent.
  • add the point to your new, actual curve.

this works because it uses the closest point to the actual distance, and brings it to the next closest point, which makes it pretty accurate. however, the path between each point is a straight line while it should actually be curved. that’s why the article says it isn’t perfect.

here is the explanation I learned from, personally I think it does a better job than the devforum

1 Like

what do you mean “and get the total distance traveled at that specific point” im having trouble understanding

by that I mean the distance travelled on the curve. for example, imagine a line going from A to B to C. the distance between A to C is the distance between A to B plus the distance between B to C. you need to get the distance of each point from the beginning (A) up until that point.

lua example:

-- function that gets bezier points here

local points = {}
points[#points] = {
    ["point"] = Vector3.new(),
    ["distance"] = 0
}

for i = 0, 1, 0.1 do
    local point = bezierPoint(i)
    local previous = points[#points]

    local distance = previous.distance + (point - previous.point).Magnitude

    points[#points+1] = {
        ["point"] = point,
        ["distance"] = distance
    }
end

local total = points[#points].distance

Are you sure you got your variables right? you made distance a number but then tried to use it as a table.

is it erroring? haven’t tested, might be a name conflict. just change the variable names in that case, but you need to store an object containing a point and a distance (or separate lists if you want, I think the devforum does it in separate lists).

or you need quotes around dict keys, don’t remember.

Okay I did that, could you review mine just to be sure im doing this correctly?

Script:

function travelCurve(p0, p1, p2, p3, cart)
	local points = {
		
	}
	for t = 0, 1, 0.01 do 
		local v3 = cubic(p0.Position, p1.Position, p2.Position, p3.Position, t)
		local target = v3 * #points -- multiply it by the number of points like you said in 2 step
		points[v3] = target 
	wait(0.01)
	end

end

you don’t need the target yet, just points and distances. that’s for a second loop.

okay then what do i do in the second loop? get the target and closest point? Also wouldn’t it be easier to do it all in one loop like so:

for t = 0, 1, 0.01 do 
		local v3 = cubic(p0.Position, p1.Position, p2.Position, p3.Position, t)
		local target = v3 * #points -- multiply it by the number of points like you said in step 2
		local nearest = points[#points]
		points[v3] = target 
		-- then lerp
	end

no. the nearest point is the point that has a distance closest to the target. you also need the total distance to calculate the target distance, which is impossible without looping once.

also, your target needs to be the total distance divided by the total number of points, times whichever point you are on (ie the 5th point, etc).

okay but what exactly is the total distance? sorry im a bit confused.

length of the entire curve

distance between all points added together

to get the target distances you can also multiply that by t, probably faster

Alright, that makes everything make a lot more sense thanks, ill try adding the other loop

okay so in order to get the total distance couldn’t i just subract the last point from the first?

for example, imagine a line going from A to B to C. the distance between A to C is the distance between A to B plus the distance between B to C.

Got it, so it should be like this?

function travelCurve(p0, p1, p2, p3, cart)
	local points = {
		
	}
	for t = 0, 1, 0.01 do 
		local v3 = cubic(p0.Position, p1.Position, p2.Position, p3.Position, t)
	
		points[v3] = t 
		-- then lerp
	end
	
	local totaldistance = points[#points] - points[1]
	
	for i,v in ipairs(points) do 
		v = totaldistance / #points * i
	end
	
end

sorry for the late response

refer to my original example, I’ve edited it, which covers the first loop. in that first loop you are calculating the points as normal, like an unweighted bezier, and putting them in a list.

along with that you store the length of every line segment up until that point. I did it by storing the points and the distances together in a table, but you can also use separate lists.

to get the distance you add the distance up until the previous point, plus the distance between the previous point and the current one.

you need to store them in an array, not key value pairs. otherwise it’s not ordered at all, and it needs to be in order.

the total distance is the same as the distance of the final point.

Im confused on what you mean by “function that gets bezier points here” and “bezierPoint(i)” isnt the thing we are making the function that makes the bezier points?