Added motion planel for raw/pitch/roll and acceleration. Consolidated all colors into one stylesheet in themes/palette.gd.
This commit is contained in:
1 parent
948b177eee
commit
f05a6148fb
13 files changed
+672
No files matched your search
@@ -0,0 +1,18 @@
|
||||
extends VBoxContainer
|
||||
|
||||
var elapsed := 0.0
|
||||
var running := true
|
||||
@onready var instrument = $MotionPanel
|
||||
|
||||
func _ready() -> void:
|
||||
$Pause.toggled.connect(func(paused: bool) -> void: running = not paused)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not running:
|
||||
return
|
||||
elapsed += delta
|
||||
instrument.set_telemetry(
|
||||
Vector3(24 * sin(elapsed * 0.5), 18 * sin(elapsed * 0.3), fposmod(350 + elapsed * 4, 360)),
|
||||
Vector3(1.8 * cos(elapsed * 0.5), 0.9 * cos(elapsed * 0.3), 0.6),
|
||||
Vector3(2.8 * sin(elapsed * 0.7), 1.9 * cos(elapsed * 0.5), 1.2 * sin(elapsed * 0.4))
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://c5mncovlnarve
|
||||
@@ -0,0 +1,133 @@
|
||||
@tool
|
||||
extends Control
|
||||
@export_enum("Attitude", "Heading", "Acceleration", "Vertical acceleration") var instrument: int = 0
|
||||
## Shares the circle radius with another instrument.
|
||||
@export var radius_source: Control
|
||||
|
||||
var attitude := Vector3.ZERO
|
||||
var acceleration := Vector3.ZERO
|
||||
var available := false
|
||||
var full_scale := 5.0
|
||||
|
||||
func _ready() -> void:
|
||||
resized.connect(queue_redraw)
|
||||
theme_changed.connect(queue_redraw)
|
||||
if is_instance_valid(radius_source):
|
||||
radius_source.resized.connect(queue_redraw)
|
||||
|
||||
func _text(at: Vector2, value: String, font_size: int = 16, color: Variant = null) -> void:
|
||||
draw_string(get_theme_default_font(), at, value, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, _ink() if color == null else color)
|
||||
|
||||
func _line(a: Vector2, b: Vector2, color: Variant = null, width: float = 1.0) -> void:
|
||||
draw_line(a, b, _role("instrument_grid") if color == null else color, width, true)
|
||||
|
||||
func _draw() -> void:
|
||||
match instrument:
|
||||
0: _draw_attitude(available)
|
||||
1: _draw_heading()
|
||||
2: _draw_acceleration()
|
||||
3: _draw_vertical()
|
||||
|
||||
func _horizon_half(center: Vector2, radius: float, normal: Vector2, displacement: float) -> PackedVector2Array:
|
||||
var polygon := PackedVector2Array()
|
||||
var previous := Vector2(radius, 0)
|
||||
var previous_distance := previous.dot(normal) - displacement
|
||||
for step in range(1, 129):
|
||||
var point := Vector2.from_angle(step * TAU / 128.0) * radius
|
||||
var distance := point.dot(normal) - displacement
|
||||
if (distance >= 0) != (previous_distance >= 0):
|
||||
polygon.append(center + previous.lerp(point, previous_distance / (previous_distance - distance)))
|
||||
if distance >= 0:
|
||||
polygon.append(center + point)
|
||||
previous = point
|
||||
previous_distance = distance
|
||||
return polygon
|
||||
|
||||
func _draw_attitude(available: bool) -> void:
|
||||
var center := circle_center()
|
||||
var radius := circle_radius()
|
||||
draw_circle(center, radius, _role("horizon_sky") if available else _role("instrument_empty"))
|
||||
if available:
|
||||
var rotation := deg_to_rad(-attitude.x)
|
||||
var normal := Vector2.DOWN.rotated(rotation)
|
||||
var direction := Vector2.RIGHT.rotated(rotation)
|
||||
var displacement := attitude.y * radius / 60.0
|
||||
var ground := _horizon_half(center, radius, normal, displacement)
|
||||
if ground.size() >= 3:
|
||||
draw_colored_polygon(ground, _role("horizon_ground"))
|
||||
if absf(displacement) < radius:
|
||||
var half_chord := sqrt(radius * radius - displacement * displacement)
|
||||
_line(center + normal * displacement - direction * half_chord, center + normal * displacement + direction * half_chord, _ink(), 3)
|
||||
for pitch_mark in range(-90, 91, 10):
|
||||
var y: float = (attitude.y - pitch_mark) * radius / 60.0
|
||||
if pitch_mark != 0 and absf(y) < radius - 25:
|
||||
var p := center + normal * y
|
||||
var width := 26.0 if pitch_mark % 20 == 0 else 15.0
|
||||
_line(p - direction * width, p + direction * width, _ink(), 2)
|
||||
if pitch_mark % 20 == 0:
|
||||
_text(p + direction * (width + 7) + Vector2(0, 6), str(absi(pitch_mark)), 18)
|
||||
for bank in [-60, -30, 0, 30, 60]:
|
||||
var radial := Vector2.UP.rotated(deg_to_rad(bank))
|
||||
_line(center + radial * (radius - 10), center + radial * (radius - 1), _ink(), 2)
|
||||
var pointer := Vector2.UP.rotated(rotation)
|
||||
draw_colored_polygon(PackedVector2Array([center + pointer * (radius - 17), center + pointer.rotated(0.07) * (radius - 29), center + pointer.rotated(-0.07) * (radius - 29)]), _role("instrument_accent"))
|
||||
draw_arc(center, radius, 0, TAU, 128, _role("instrument_grid"), 2, true)
|
||||
_line(center + Vector2(-47, 0), center + Vector2(-14, 0), _role("instrument_accent"), 4)
|
||||
_line(center + Vector2(14, 0), center + Vector2(47, 0), _role("instrument_accent"), 4)
|
||||
_line(center + Vector2(-14, 0), center + Vector2(-14, 8), _role("instrument_accent"), 3)
|
||||
_line(center + Vector2(14, 0), center + Vector2(14, 8), _role("instrument_accent"), 3)
|
||||
draw_circle(center, 4, _role("instrument_accent"))
|
||||
func _heading_tick_x(degrees: float) -> float:
|
||||
return size.x / 2 + (degrees - attitude.z) * size.x / 100.0
|
||||
|
||||
func _draw_heading() -> void:
|
||||
if available:
|
||||
var first_tick := int(floor((attitude.z - 50.0) / 10.0)) * 10
|
||||
for degrees in range(first_tick, first_tick + 120, 10):
|
||||
var x := _heading_tick_x(degrees)
|
||||
if x < 18 or x > size.x - 18:
|
||||
continue
|
||||
_line(Vector2(x, 7), Vector2(x, 16), _ink(), 2)
|
||||
if degrees % 20 == 0:
|
||||
_text(Vector2(x - 17, 36), "%03d" % int(fposmod(degrees, 360)), 18, _ink())
|
||||
_line(Vector2(size.x / 2, 0), Vector2(size.x / 2, 20), _role("instrument_accent"), 3)
|
||||
|
||||
func _draw_acceleration() -> void:
|
||||
var radius := circle_radius()
|
||||
var center := circle_center()
|
||||
for fraction in [0.5, 1.0]:
|
||||
draw_arc(center, radius * fraction, 0, TAU, 96, _role("instrument_grid"), 2, true)
|
||||
_line(center - Vector2(radius, 0), center + Vector2(radius, 0), _role("instrument_axis"))
|
||||
_line(center - Vector2(0, radius), center + Vector2(0, radius), _role("instrument_axis"))
|
||||
_text(center + Vector2(-14, -radius - 8), "+X", 18)
|
||||
_text(center + Vector2(radius + 2, 6), "+Y", 18)
|
||||
if available:
|
||||
var planar := Vector2(acceleration.y, -acceleration.x) / maxf(full_scale, 0.1)
|
||||
var endpoint := center + planar.limit_length(1.0) * radius
|
||||
_line(center, endpoint, _role("instrument_accent"), 4)
|
||||
draw_circle(endpoint, 7, _role("instrument_accent"))
|
||||
|
||||
func _draw_vertical() -> void:
|
||||
var center := circle_center()
|
||||
var radius := circle_radius()
|
||||
_line(center - Vector2(0, radius), center + Vector2(0, radius), _role("instrument_axis"), 2)
|
||||
for fraction in [-1.0, -0.5, 0.0, 0.5, 1.0]:
|
||||
var y: float = center.y + fraction * radius
|
||||
_line(Vector2(center.x - 6, y), Vector2(center.x + 6, y), _ink(), 2)
|
||||
if available:
|
||||
var y := center.y + clampf(acceleration.z / maxf(full_scale, 0.1), -1, 1) * radius
|
||||
_line(Vector2(center.x - 10, y), Vector2(center.x + 10, y), _role("instrument_accent"), 5)
|
||||
|
||||
func circle_radius() -> float:
|
||||
if is_instance_valid(radius_source):
|
||||
return radius_source.circle_radius()
|
||||
return maxf(1.0, minf(size.x - 24, size.y - 44) / 2)
|
||||
|
||||
func circle_center() -> Vector2:
|
||||
return size / 2 + Vector2(0, 10)
|
||||
|
||||
func _ink() -> Color:
|
||||
return get_theme_color("font_color", "Label")
|
||||
|
||||
func _role(name: StringName) -> Color:
|
||||
return get_theme_color(name, "SurfaceSide")
|
||||
@@ -0,0 +1 @@
|
||||
uid://1dn362pyibkx
|
||||
@@ -0,0 +1,63 @@
|
||||
@tool
|
||||
extends Control
|
||||
## Telemetry conventions: docs/motion_panel.md.
|
||||
|
||||
@export var stale_after_seconds: float = 2.0
|
||||
@export_range(0.1, 100.0) var acceleration_full_scale: float = 5.0:
|
||||
set(value):
|
||||
acceleration_full_scale = maxf(value, 0.1)
|
||||
_refresh()
|
||||
|
||||
var attitude := Vector3.ZERO # roll, pitch, heading in degrees
|
||||
var momentum := Vector3.ZERO
|
||||
var acceleration := Vector3.ZERO
|
||||
var _received_ms: int = -1
|
||||
|
||||
var _last_stale := false
|
||||
|
||||
func _ready() -> void:
|
||||
_refresh()
|
||||
|
||||
func set_telemetry(orientation_degrees: Vector3, angular_momentum: Vector3, linear_acceleration: Vector3) -> void:
|
||||
if not orientation_degrees.is_finite() or not angular_momentum.is_finite() or not linear_acceleration.is_finite():
|
||||
return
|
||||
attitude = Vector3(wrapf(orientation_degrees.x, -180, 180), wrapf(orientation_degrees.y, -180, 180), fposmod(orientation_degrees.z, 360))
|
||||
momentum = angular_momentum
|
||||
acceleration = linear_acceleration
|
||||
_received_ms = Time.get_ticks_msec()
|
||||
_refresh()
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
var stale := _is_stale()
|
||||
if stale != _last_stale:
|
||||
_refresh()
|
||||
|
||||
func _is_stale() -> bool:
|
||||
return _received_ms >= 0 and (Time.get_ticks_msec() - _received_ms) / 1000.0 > stale_after_seconds
|
||||
|
||||
func _number(value: float, precision: int = 2) -> String:
|
||||
return "—" if _received_ms < 0 else ("%+.*f" % [precision, value])
|
||||
|
||||
func _refresh() -> void:
|
||||
if not is_node_ready():
|
||||
return
|
||||
_last_stale = _is_stale()
|
||||
%Status.text = "STALE" if _last_stale else "NO DATA"
|
||||
%StatusBanner.visible = _last_stale or _received_ms < 0
|
||||
%Status.visible = true
|
||||
%Roll.text = "R " + _number(attitude.x, 1) + "°"
|
||||
%Pitch.text = "P " + _number(attitude.y, 1) + "°"
|
||||
%Heading.text = "H " + ("%05.1f°" % attitude.z if _received_ms >= 0 else "—")
|
||||
var accel_labels := [%AccelX, %AccelY, %AccelZ]
|
||||
var momentum_labels := [%MomentumX, %MomentumY, %MomentumZ]
|
||||
for axis in range(3):
|
||||
accel_labels[axis].text = _number(acceleration[axis])
|
||||
momentum_labels[axis].text = _number(momentum[axis])
|
||||
%FullScale.text = "±%.1f" % acceleration_full_scale
|
||||
%Limit.visible = _received_ms >= 0 and (Vector2(acceleration.x, acceleration.y).length() > acceleration_full_scale or absf(acceleration.z) > acceleration_full_scale)
|
||||
for graphic in [%Horizon, %HeadingTape, %AccelerationPlot, %VerticalAcceleration]:
|
||||
graphic.attitude = attitude
|
||||
graphic.acceleration = acceleration
|
||||
graphic.available = _received_ms >= 0
|
||||
graphic.full_scale = acceleration_full_scale
|
||||
graphic.queue_redraw()
|
||||
@@ -0,0 +1 @@
|
||||
uid://dh2vnk35potq3
|
||||
Reference in new issue
Block a user