OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-creatures / scenes / utils / sphere_sweep.gd

67 lines · gd ref:
class_name SphereSweep extends RefCounted
## Shared sphere sweep utility. Wraps cast_motion + get_rest_info with
## Overgrowth-style sliding sphere refinement for accurate surface normals.
## Caches SphereShape3D by radius to avoid per-call allocations.

static var _shape_cache: Dictionary = {}  ## radius → SphereShape3D
static var _params: PhysicsShapeQueryParameters3D = null

## Sweep a sphere from `from` to `to`. Returns { "point", "normal", "fraction" } or empty.
static func sweep(space: PhysicsDirectSpaceState3D, from: Vector3, to: Vector3,
		radius: float, exclude: Array[RID] = [], collision_mask: int = 0xFFFFFFFF) -> Dictionary:
	var motion: Vector3 = to - from

	# Cache shapes by radius (snapped to 3 decimals to avoid unbounded growth)
	var key: float = snappedf(radius, 0.001)
	var shape: SphereShape3D
	if _shape_cache.has(key):
		shape = _shape_cache[key]
	else:
		shape = SphereShape3D.new()
		shape.radius = key
		_shape_cache[key] = shape

	# Reuse params object
	if _params == null:
		_params = PhysicsShapeQueryParameters3D.new()
	_params.shape = shape
	_params.transform = Transform3D(Basis.IDENTITY, from)
	_params.motion = motion
	_params.collision_mask = collision_mask
	if not exclude.is_empty():
		_params.exclude = exclude
	else:
		_params.exclude = []

	var fractions: PackedFloat32Array = space.cast_motion(_params)
	if fractions.is_empty() or fractions[1] >= 1.0:
		return {}

	var contact_t: float = fractions[1]
	var contact_pos: Vector3 = from + motion * contact_t
	_params.transform = Transform3D(Basis.IDENTITY, contact_pos)

	var rest: Dictionary = space.get_rest_info(_params)
	if rest.is_empty():
		return {
			"point": contact_pos - motion.normalized() * radius,
			"normal": -motion.normalized(),
			"fraction": contact_t
		}

	var hit_point: Vector3 = rest["point"]
	var hit_normal: Vector3 = rest["normal"]

	# Sliding sphere refinement for accurate surface normal
	var slide_pos: Vector3 = hit_point + hit_normal * radius
	_params.transform = Transform3D(Basis.IDENTITY, slide_pos)
	var slide_rest: Dictionary = space.get_rest_info(_params)
	if not slide_rest.is_empty():
		hit_normal = slide_rest["normal"]

	return {
		"point": hit_point,
		"normal": hit_normal,
		"fraction": contact_t
	}