Calculate index of and progress along Curve3D segment

Godot Version: 4.3

Question

How do you calculate the index of and progress along a Curve3D segment given an offset or a position?

The reason I need this is because I want to store a size value of each segment in the Bezier curve, exported from the radius values of each path segment in Blender, and be able to sample a size value for an arbitrary point on the curve. The calculated value will be interpolated using the index of the relevant segment (the same index used in get_point_position) to obtain the size value for that segment from a separate array, as well as a number representing the distance on the curve from the segment origin to the given offset/point.

(I’m using Bezier curves for the track to aid AI navigation in a racing game.)

Well, I couldn’t find a way to calculate that using only pre-existing Curve3D methods and data. Actually, I think it’s impossible. I instead came up with a method that involved creating my own cache of baked values.

I created a class that wrapped around Curve3D (since I want to know when the cache is invalidated) and added a procedure that creates a baked cache of radii. It loops through each vertex in the curve in small increments of the value t, which is a number between 0 and 1 describing the progress from one vertex to the next. It also keeps track of its current offset by adding the distances between the points that it iterates through. Once it passes the offset of the first baked position, it adds the radius value to the radius cache, lerped using t. Then, once it passes the offset of the second baked position, it adds another value to cache. Then, once it passes the offset of the third baked position, it adds another value, and so on. This will bake radius values in such a way that it lines up with the values baked by Curve3D.

Here’s what I wrote for it:

const MAX_ITERATIONS = 300

# this should be filled in with radius data per vertex
var _radii: PackedFloat32Array = PackedFloat32Array()
var _curve: Curve3D = Curve3D.new()

var _cache_invalidated = true
var _baked_radii: PackedFloat32Array = PackedFloat32Array()
var _offset_cache: PackedFloat32Array = PackedFloat32Array()

func _bake() -> void:
	if not _cache_invalidated:
		return
	_cache_invalidated = false

	_baked_radii.clear()
	_offset_cache.clear()

	var baked_points = _curve.get_baked_points()

	_baked_radii.resize(baked_points.size())
	_offset_cache.resize(baked_points.size())

	var dist_accum = 0.0
	var last_pos = _curve.get_point_position(0)
	var cache_index = 0
	var cache_offset = _curve.get_closest_offset(last_pos)

	var j = 0
	for si in _curve.point_count - 1:
		for i in MAX_ITERATIONS:
			var t = float(i) / MAX_ITERATIONS
			var pos = _curve.sample(si, t)
			var dist = last_pos.distance_to(pos)

			if dist_accum >= cache_offset:
				_baked_radii[j] = lerpf(_radii[si], _radii[si+1], t)
				_offset_cache[j] = cache_offset
				j += 1

				if cache_index + 1 >= baked_points.size():
					break
				
				cache_index += 1
				cache_offset = _curve.get_closest_offset(baked_points[cache_index])

			dist_accum += dist
			last_pos = pos
	
	_baked_radii[j] = _radii[-1]
	_offset_cache[j] = cache_offset
	j += 1

	assert(j == baked_points.size())

I don’t think this implementation would work as intended if, on at least one iteration of t, it passes through more than one baked point value. But I set my bake_interval to a relatively high value since I didn’t need the precision anyway, so it works perfectly fine for my purposes.

For my sample_baked_radius, it uses a binary search on the offset cache generated during the radius baking process (that I hadn’t mentioned until now). It’s obviously inefficient in terms of memory, considering that I’m pretty sure Curve3D also keeps an internal cache describing the exact same data. But it’s inaccessible, so be it.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.