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