Path2D calculate progress (px or ratio) of a given point

Godot Version

v4.2.stable.mono.official [46dc27791]

Question

Hey, so I’m trying to make an enemy, that will “jump” between points I specify in a Curve2D using an AnimationPlayer/PathFollow2D.
I pre-calculate all jump animations as I have to couple them with the sprite animation happening in the other part of the code. I have 90% of the logic for the animations done. The only thing is, the enemy “jumps” to the wrong points because I just blindly assumed that ratio always divides evenly between the points (so if you have 10 points 0 is first point, 0.1 is second point etc.) but it doesn’t work like that…

# This works only if the points are equal distances apart
var nextPointProgressRatio = (1.0 / (curve.point_count - 1.0)) * (point_index + 1.0)
var nextPointProgress = nextPointPositionRatio * curve.get_baked_length()

Is there any way I can get a single point’s “progress” (or progress_ratio) value in any way that doesn’t involve myself writing a bunch of complicated math stuff? (Not saying I couldn’t, but I’m wondering if there is already a wheel made I can use insted)

I haven’t tried that myself, but maybe you need to do something like:

var point_pos: Vector2 = curve.get_point_position(index)
var point_progress_ratio: float = curve.get_closest_offset(point_pos)

If that does not work, I would query the array of baked points directly

If neither of the above helps, maybe it is worth to examine the sources: godot/scene/resources/curve.cpp at 6b281c0c07b07f2142b1fc8a6b3158091a9b124c · godotengine/godot · GitHub


UP

Btw, there’s root motion feature for 2D. Not sure whether this is applicable in your case, but maybe some enemy movements can also be implemented with it instead

1 Like

It unfortunately doesn’t work for the case when the path is crossing itself or goes through the same point twice because get_closest_offset returns the first point’s offset not the specified one’s.
But I can duplicate the curve, and after checking the get_closest_offset just delete the point from the curve. So that works either way! Thanks!

Cann you elaborate? I have been looking for the same thing, I need the progress of individual points in a curve2d

@saitmaras , the logic is as in my reply above, but AFAIU the corner case is that when you have a path going through the same point multiple times (e.g. in shape of 8), you’d better this path into 2 separate paths to not have these overlaps, because get_closest_offset may return the wrong point and you’ll get the wrong offset.

So if you don’t have self-crossing / perfectly looped paths, you should be safe

@skilllgg , please correct my understanding if something is off (I didn’t quite get the trick with duplicating curves)

So because get_closest_offset() picks the first point in the curve closest to the vector you specified (at least it did fro my testing) I could write a function that would look something like this in pseudo-code

1. Get the offset to the "1st" (indexed 1) of the curve ("0th" one is on the 0.0 offset)
2. Store the offset (in pixels) to `cumulative_length`.
   We just measured the distance between "0th" and "1st" point
3. Remove the "0th" point so that next point now becomes the "0th" one
4. Get offset to the new "1st" point (formerly "2nd") that will be the distance between "1st" and "2nd" points.
...
And so on until you've ran out of points

I have written these functions that calculate it

func get_point_offsets(in_curve: Curve2D) -> Array[float]:
	if(in_curve == null):
		return []
	# Offset from previous points
	var cumul_length:float = 0.0 
	# full length to change it back to ratio instead of pixels
	var full_length = in_curve.get_baked_length() 
	# duplicate the curve to not destroy the old one
	var curve = in_curve.duplicate(true) as Curve2D
	var ret: Array[float] = [0] # this is the array of all the "offsets"
	# 0 is the 0th point
	var curve_size = in_curve.point_count
	for point_index in range(1, curve_size):
		var offset = get_point_offset(curve, 1) + cumul_length
 		# remove the point
		curve.remove_point(0)
		cumul_length = offset
		# you can omit this step and just append the offset directly
		# if you want to have values in pixels rather than in ratios
		var offset_ratio = cumul_length / full_length
		ret.append(offset_ratio)
	return ret
	
func get_point_offset(curve: Curve2D, index: int):
	var point_pos: Vector2 = curve.get_point_position(index)
	var point_progress: float = curve.get_closest_offset(point_pos)
	return point_progress

You could change this code to do it for just one point, but I guess it would eat a lot of computation power to calculate the offset of later points in a long curve, so I just pre-calculate the value and store it next to the curve whenever it changes once, and then reference the table when I want to get the offset of the point I want

1 Like

Thanks for the code,
It works like a charm.

1 Like