Two-player mode with client assignment, 3D vector calibration for cylindrical pods, lives, tap/double-tap boost with low-intensity LED cooldown feedback, and Kenney sprite assets. Co-authored-by: Cursor <cursoragent@cursor.com>
160 lines
3.5 KiB
GDScript
160 lines
3.5 KiB
GDScript
class_name AccelCalibration
|
|
extends RefCounted
|
|
|
|
enum Axis { X, Y, Z }
|
|
|
|
const MIN_SAMPLES := 25
|
|
const TAIL_FRACTION := 0.55
|
|
|
|
var axis: Axis = Axis.X
|
|
var sign: float = 1.0
|
|
var center: float = 0.0
|
|
var suggested_threshold: float = 3000.0
|
|
var direction: Vector3 = Vector3.RIGHT
|
|
var neutral: Vector3 = Vector3.ZERO
|
|
var uses_vector: bool = false
|
|
|
|
var _samples_neutral: Array[Vector3] = []
|
|
var _samples_right: Array[Vector3] = []
|
|
var _samples_left: Array[Vector3] = []
|
|
|
|
|
|
func reset() -> void:
|
|
_samples_neutral.clear()
|
|
_samples_right.clear()
|
|
_samples_left.clear()
|
|
uses_vector = false
|
|
direction = Vector3.RIGHT
|
|
neutral = Vector3.ZERO
|
|
|
|
|
|
func record_neutral(accel: Vector3i) -> void:
|
|
_samples_neutral.append(Vector3(accel))
|
|
|
|
|
|
func record_right(accel: Vector3i) -> void:
|
|
_samples_right.append(Vector3(accel))
|
|
|
|
|
|
func record_left(accel: Vector3i) -> void:
|
|
_samples_left.append(Vector3(accel))
|
|
|
|
|
|
func is_ready() -> bool:
|
|
return (
|
|
_samples_neutral.size() >= MIN_SAMPLES
|
|
and _samples_right.size() >= MIN_SAMPLES
|
|
and _samples_left.size() >= MIN_SAMPLES
|
|
)
|
|
|
|
|
|
func analyze() -> bool:
|
|
if not is_ready():
|
|
return false
|
|
|
|
var mean_n := _tail_mean(_samples_neutral)
|
|
var mean_r := _tail_mean(_samples_right)
|
|
var mean_l := _tail_mean(_samples_left)
|
|
var delta := mean_r - mean_l
|
|
|
|
if delta.length_squared() < 150.0 * 150.0:
|
|
return false
|
|
|
|
direction = delta.normalized()
|
|
neutral = mean_n
|
|
uses_vector = true
|
|
sign = 1.0
|
|
_set_axis_from_direction()
|
|
|
|
var neutral_proj: Array[float] = []
|
|
for sample in _samples_neutral:
|
|
neutral_proj.append(_project_vec(sample))
|
|
neutral_proj.sort()
|
|
center = neutral_proj[neutral_proj.size() / 2]
|
|
|
|
var deviations: Array[float] = []
|
|
for value in neutral_proj:
|
|
deviations.append(absf(value - center))
|
|
deviations.sort()
|
|
var spread := deviations[deviations.size() / 2] if not deviations.is_empty() else 500.0
|
|
suggested_threshold = clampf(spread * 2.2 + 180.0, 250.0, 8000.0)
|
|
|
|
var right_proj := _project_vec(mean_r) - center
|
|
var left_proj := _project_vec(mean_l) - center
|
|
var separation := absf(right_proj - left_proj)
|
|
if separation < suggested_threshold * 2.0:
|
|
suggested_threshold = clampf(separation * 0.18, 250.0, 8000.0)
|
|
|
|
return true
|
|
|
|
|
|
func project(accel: Vector3i) -> float:
|
|
if uses_vector:
|
|
return _project_vec(Vector3(accel))
|
|
return _component(Vector3(accel), axis) * sign
|
|
|
|
|
|
func duplicate() -> AccelCalibration:
|
|
var copy := AccelCalibration.new()
|
|
copy.axis = axis
|
|
copy.sign = sign
|
|
copy.center = center
|
|
copy.suggested_threshold = suggested_threshold
|
|
copy.direction = direction
|
|
copy.neutral = neutral
|
|
copy.uses_vector = uses_vector
|
|
return copy
|
|
|
|
|
|
func axis_name() -> String:
|
|
if uses_vector:
|
|
return "3D-Schwank (%+.1f,%+.1f,%+.1f)" % [direction.x, direction.y, direction.z]
|
|
match axis:
|
|
Axis.X:
|
|
return "x"
|
|
Axis.Y:
|
|
return "y"
|
|
Axis.Z:
|
|
return "z"
|
|
return "?"
|
|
|
|
|
|
func _project_vec(v: Vector3) -> float:
|
|
return (v - neutral).dot(direction)
|
|
|
|
|
|
func _tail_mean(samples: Array[Vector3]) -> Vector3:
|
|
var start_idx := int(samples.size() * TAIL_FRACTION)
|
|
return _mean(samples.slice(start_idx))
|
|
|
|
|
|
func _set_axis_from_direction() -> void:
|
|
var abs_dir := direction.abs()
|
|
axis = Axis.X
|
|
var best := abs_dir.x
|
|
if abs_dir.y > best:
|
|
best = abs_dir.y
|
|
axis = Axis.Y
|
|
if abs_dir.z > best:
|
|
axis = Axis.Z
|
|
|
|
|
|
func _mean(samples: Array[Vector3]) -> Vector3:
|
|
if samples.is_empty():
|
|
return Vector3.ZERO
|
|
var sum := Vector3.ZERO
|
|
for s in samples:
|
|
sum += s
|
|
return sum / float(samples.size())
|
|
|
|
|
|
func _component(v: Vector3, ax: Axis) -> float:
|
|
match ax:
|
|
Axis.X:
|
|
return v.x
|
|
Axis.Y:
|
|
return v.y
|
|
Axis.Z:
|
|
return v.z
|
|
return 0.0
|