mirror of
https://github.com/ElHombreAlex/FTL-Faster-Than-Light-VR.git
synced 2026-10-06 01:00:06 +02:00
124 lines
5.6 KiB
GDScript
124 lines
5.6 KiB
GDScript
extends RefCounted
|
|
|
|
# Follow the headset first. Only the panel's path toward the ship engages the
|
|
# world-height stop, so a distant or side-on table never drags the HUD around.
|
|
const CLEARANCE := 0.05
|
|
const HEAD_OFFSET := Vector3(0.0, -0.12, -1.30)
|
|
const POSITION_TIME := 0.12
|
|
const PANEL_THICKNESS := 0.008
|
|
var initialized := false
|
|
var blocked := false
|
|
var lift := 0.0
|
|
var pose := Transform3D.IDENTITY
|
|
|
|
|
|
func reset() -> void:
|
|
initialized = false
|
|
blocked = false
|
|
lift = 0.0
|
|
|
|
|
|
static func transformed_bounds(local: AABB, transform: Transform3D) -> AABB:
|
|
var result := AABB(transform * local.position, Vector3.ZERO)
|
|
for x in [0.0, 1.0]:
|
|
for y in [0.0, 1.0]:
|
|
for z in [0.0, 1.0]:
|
|
result = result.expand(transform * (local.position + local.size * Vector3(x, y, z)))
|
|
return result
|
|
|
|
|
|
static func panel_bounds(transform: Transform3D, dimensions: Vector2) -> AABB:
|
|
var half := transform.basis.x.abs() * dimensions.x * 0.5 + transform.basis.y.abs() * dimensions.y * 0.5
|
|
return AABB(transform.origin - half, half * 2.0)
|
|
|
|
|
|
static func _boxes_intersect(first: Transform3D, first_box: AABB, second: Transform3D, second_box: AABB, margin := 0.0) -> bool:
|
|
# Separate the oriented boxes on face normals and edge cross products. A
|
|
# world AABB alone can stop the HUD beside a rotated ship with empty space.
|
|
var first_axes: Array[Vector3] = [first.basis.x.normalized(), first.basis.y.normalized(), first.basis.z.normalized()]
|
|
var second_axes: Array[Vector3] = [second.basis.x.normalized(), second.basis.y.normalized(), second.basis.z.normalized()]
|
|
var first_half := first_box.size * Vector3(first.basis.x.length(), first.basis.y.length(), first.basis.z.length()) * 0.5
|
|
var second_half := second_box.size * Vector3(second.basis.x.length(), second.basis.y.length(), second.basis.z.length()) * 0.5 + Vector3.ONE * margin
|
|
var separation := second * second_box.get_center() - first * first_box.get_center()
|
|
var axes: Array[Vector3] = []
|
|
axes.append_array(first_axes)
|
|
axes.append_array(second_axes)
|
|
for first_axis in first_axes:
|
|
for second_axis in second_axes:
|
|
var cross := first_axis.cross(second_axis)
|
|
if cross.length_squared() > 0.000001:
|
|
axes.append(cross.normalized())
|
|
for axis in axes:
|
|
var first_radius := 0.0
|
|
var second_radius := 0.0
|
|
for index in range(3):
|
|
first_radius += absf(axis.dot(first_axes[index])) * first_half[index]
|
|
second_radius += absf(axis.dot(second_axes[index])) * second_half[index]
|
|
if absf(axis.dot(separation)) > first_radius + second_radius:
|
|
return false
|
|
return true
|
|
|
|
|
|
static func overlaps_ship(panel: Transform3D, dimensions: Vector2, ship_transform: Transform3D, local_bounds: AABB, margin := 0.0) -> bool:
|
|
var half := Vector3(dimensions.x * 0.5, dimensions.y * 0.5, PANEL_THICKNESS)
|
|
return _boxes_intersect(panel, AABB(-half, half * 2.0), ship_transform, local_bounds, margin)
|
|
|
|
|
|
static func _facing(point: Vector3, viewer: Transform3D) -> Basis:
|
|
var toward := viewer.origin - point
|
|
if toward.length_squared() < 0.0001:
|
|
toward = viewer.basis.z
|
|
var up := viewer.basis.y.normalized()
|
|
if absf(toward.normalized().dot(up)) > 0.98:
|
|
up = viewer.basis.z.normalized()
|
|
return Basis.looking_at(toward, up, true)
|
|
|
|
|
|
static func _clear_position(point: Vector3, basis: Basis, dimensions: Vector2, ship: AABB) -> Vector3:
|
|
var half := basis.x.abs() * dimensions.x * 0.5 + basis.y.abs() * dimensions.y * 0.5
|
|
point.y = maxf(point.y, ship.end.y + CLEARANCE + half.y + PANEL_THICKNESS)
|
|
return point
|
|
|
|
|
|
func update(ship_transform: Transform3D, local_bounds: AABB, viewer: Transform3D, dimensions: Vector2, delta: float, snap := false) -> Transform3D:
|
|
var desired := viewer * Transform3D(Basis.IDENTITY, HEAD_OFFSET)
|
|
var path_length := viewer.origin.distance_to(desired.origin)
|
|
var path := Transform3D(Basis.looking_at(desired.origin - viewer.origin, viewer.basis.y), (viewer.origin + desired.origin) * 0.5)
|
|
var path_box := AABB(Vector3(0, 0, -path_length * 0.5), Vector3(0, 0, path_length))
|
|
# The full panel handles edge contact. Its center's viewing path also stops
|
|
# a large downward head motion tunnelling the panel entirely below the hull.
|
|
# Sweeping its full width all the way to the face would unnecessarily clamp
|
|
# it when looking away from a table close behind the viewer.
|
|
blocked = overlaps_ship(desired, dimensions, ship_transform, local_bounds, CLEARANCE) or _boxes_intersect(path, path_box, ship_transform, local_bounds, CLEARANCE)
|
|
var target_lift := 0.0
|
|
var ship := transformed_bounds(local_bounds, ship_transform)
|
|
if blocked:
|
|
var target := desired.origin
|
|
var target_basis := desired.basis
|
|
for iteration in range(6):
|
|
target = _clear_position(target, target_basis, dimensions, ship)
|
|
target_basis = _facing(target, viewer)
|
|
target_lift = maxf(0.0, target.y - desired.origin.y)
|
|
# Head motion itself is immediate. Smooth only release of the clearance
|
|
# offset; raising it immediately prevents a fast grab crossing the panel.
|
|
if not initialized or snap or target_lift >= lift:
|
|
lift = target_lift
|
|
else:
|
|
var amount := 1.0 - exp(-maxf(delta, 0.0) / POSITION_TIME)
|
|
lift = lerpf(lift, target_lift, amount)
|
|
if lift < 0.0001:
|
|
lift = 0.0
|
|
pose = desired
|
|
pose.origin.y += lift
|
|
if lift > 0.0:
|
|
pose.basis = _facing(pose.origin, viewer)
|
|
# A raised ship can meet the releasing panel even after the desired head
|
|
# pose has left its path. Enforce clearance for that actual pose as well.
|
|
if blocked or overlaps_ship(pose, dimensions, ship_transform, local_bounds, CLEARANCE):
|
|
for iteration in range(6):
|
|
pose.origin = _clear_position(pose.origin, pose.basis, dimensions, ship)
|
|
pose.basis = _facing(pose.origin, viewer)
|
|
lift = maxf(0.0, pose.origin.y - desired.origin.y)
|
|
initialized = true
|
|
return pose
|