Add per-pod orientation gizmo to the API demo.
Shows live accel attitude with a front-view cylinder (flat / facing / steer) and a TOP marker that rotates with the mesh. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+1
-1
@@ -12,7 +12,7 @@ config_version=5
|
|||||||
|
|
||||||
config/name="alox_test_game"
|
config/name="alox_test_game"
|
||||||
run/main_scene="res://scenes/menu.tscn"
|
run/main_scene="res://scenes/menu.tscn"
|
||||||
config/features=PackedStringArray("4.6", "GL Compatibility")
|
config/features=PackedStringArray("4.7", "GL Compatibility")
|
||||||
config/icon="res://icon.svg"
|
config/icon="res://icon.svg"
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
[gd_scene load_steps=6 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/orientation_gizmo.gd" id="1_gizmo"]
|
||||||
|
|
||||||
|
[sub_resource type="CylinderMesh" id="CylinderMesh_body"]
|
||||||
|
top_radius = 0.35
|
||||||
|
bottom_radius = 0.35
|
||||||
|
height = 1.1
|
||||||
|
radial_segments = 24
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="Mat_body"]
|
||||||
|
albedo_color = Color(0.42, 0.48, 0.58, 1)
|
||||||
|
roughness = 0.55
|
||||||
|
|
||||||
|
[sub_resource type="CylinderMesh" id="CylinderMesh_cap"]
|
||||||
|
top_radius = 0.38
|
||||||
|
bottom_radius = 0.38
|
||||||
|
height = 0.08
|
||||||
|
radial_segments = 24
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="Mat_top"]
|
||||||
|
albedo_color = Color(1, 0.55, 0.12, 1)
|
||||||
|
roughness = 0.4
|
||||||
|
emission_enabled = true
|
||||||
|
emission = Color(0.85, 0.4, 0.05, 1)
|
||||||
|
emission_energy_multiplier = 0.35
|
||||||
|
|
||||||
|
[node name="OrientationGizmo" type="SubViewportContainer"]
|
||||||
|
custom_minimum_size = Vector2(220, 220)
|
||||||
|
stretch = true
|
||||||
|
script = ExtResource("1_gizmo")
|
||||||
|
|
||||||
|
[node name="SubViewport" type="SubViewport" parent="."]
|
||||||
|
own_world_3d = true
|
||||||
|
transparent_bg = true
|
||||||
|
handle_input_locally = false
|
||||||
|
size = Vector2i(220, 220)
|
||||||
|
render_target_update_mode = 4
|
||||||
|
|
||||||
|
[node name="Camera3D" type="Camera3D" parent="SubViewport"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3.2)
|
||||||
|
projection = 1
|
||||||
|
size = 2.6
|
||||||
|
near = 0.05
|
||||||
|
far = 50.0
|
||||||
|
|
||||||
|
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="SubViewport"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 4)
|
||||||
|
light_energy = 1.2
|
||||||
|
shadow_enabled = false
|
||||||
|
|
||||||
|
[node name="FillLight" type="DirectionalLight3D" parent="SubViewport"]
|
||||||
|
transform = Transform3D(-0.866025, -0.353553, 0.353553, 0, 0.707107, 0.707107, -0.5, 0.612372, -0.612372, 0, 0, 0)
|
||||||
|
light_energy = 0.4
|
||||||
|
shadow_enabled = false
|
||||||
|
|
||||||
|
[node name="Pod" type="Node3D" parent="SubViewport"]
|
||||||
|
|
||||||
|
[node name="Body" type="MeshInstance3D" parent="SubViewport/Pod"]
|
||||||
|
mesh = SubResource("CylinderMesh_body")
|
||||||
|
surface_material_override/0 = SubResource("Mat_body")
|
||||||
|
|
||||||
|
[node name="TopCap" type="MeshInstance3D" parent="SubViewport/Pod"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.59, 0)
|
||||||
|
mesh = SubResource("CylinderMesh_cap")
|
||||||
|
surface_material_override/0 = SubResource("Mat_top")
|
||||||
|
|
||||||
|
[node name="TopLabel" type="Label3D" parent="SubViewport/Pod"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 0, -1, 0, 1, 0, 0, -0.66, 0)
|
||||||
|
pixel_size = 0.01
|
||||||
|
billboard = 0
|
||||||
|
text = "TOP"
|
||||||
|
font_size = 42
|
||||||
|
outline_size = 8
|
||||||
|
modulate = Color(1, 0.95, 0.85, 1)
|
||||||
|
outline_modulate = Color(0.05, 0.05, 0.08, 1)
|
||||||
+23
-4
@@ -12,6 +12,7 @@ const PANEL_BORDER_FLASH := Color(1.0, 0.82, 0.15, 1.0)
|
|||||||
|
|
||||||
const LED_MODES: PackedStringArray = ["color", "clear", "progress", "digit", "blink", "find-me"]
|
const LED_MODES: PackedStringArray = ["color", "clear", "progress", "digit", "blink", "find-me"]
|
||||||
const LED_MODES_COLOR_INTENSITY: PackedStringArray = ["color", "progress", "digit", "blink"]
|
const LED_MODES_COLOR_INTENSITY: PackedStringArray = ["color", "progress", "digit", "blink"]
|
||||||
|
const ORIENTATION_GIZMO_SCENE := preload("res://scenes/orientation_gizmo.tscn")
|
||||||
|
|
||||||
@onready var status_label: Label = $UiLayer/TopBar/StatusLabel
|
@onready var status_label: Label = $UiLayer/TopBar/StatusLabel
|
||||||
@onready var log_output: TextEdit = $UiLayer/MainSplit/LogPanel/LogOutput
|
@onready var log_output: TextEdit = $UiLayer/MainSplit/LogPanel/LogOutput
|
||||||
@@ -122,6 +123,7 @@ func _stop_stream() -> void:
|
|||||||
"triple": false,
|
"triple": false,
|
||||||
})
|
})
|
||||||
ui["values_label"].text = "—"
|
ui["values_label"].text = "—"
|
||||||
|
ui["orient_gizmo"].clear()
|
||||||
|
|
||||||
_streaming = false
|
_streaming = false
|
||||||
start_stream_btn.disabled = _client_ui.is_empty()
|
start_stream_btn.disabled = _client_ui.is_empty()
|
||||||
@@ -328,14 +330,26 @@ func _populate_clients(clients: Array) -> void:
|
|||||||
_add_led_preset_btn(led_presets, "Blau", cid, available, func() -> void: _led_apply_preset(cid, "color", Color(0.2, 0.4, 1), 128))
|
_add_led_preset_btn(led_presets, "Blau", cid, available, func() -> void: _led_apply_preset(cid, "color", Color(0.2, 0.4, 1), 128))
|
||||||
_add_led_preset_btn(led_presets, "Find", cid, available, func() -> void: _led_apply_preset(cid, "find-me"))
|
_add_led_preset_btn(led_presets, "Find", cid, available, func() -> void: _led_apply_preset(cid, "find-me"))
|
||||||
|
|
||||||
|
var accel_row := HBoxContainer.new()
|
||||||
|
accel_row.add_theme_constant_override("separation", 12)
|
||||||
|
accel_row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
vbox.add_child(accel_row)
|
||||||
|
|
||||||
var values_label := Label.new()
|
var values_label := Label.new()
|
||||||
values_label.text = "—"
|
values_label.text = "—"
|
||||||
values_label.add_theme_font_size_override("font_size", 12)
|
values_label.add_theme_font_size_override("font_size", 12)
|
||||||
values_label.add_theme_color_override("font_color", Color(0.65, 0.85, 0.75))
|
values_label.add_theme_color_override("font_color", Color(0.65, 0.85, 0.75))
|
||||||
values_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
values_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
vbox.add_child(values_label)
|
values_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
values_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||||
|
accel_row.add_child(values_label)
|
||||||
|
|
||||||
|
var orient_gizmo: OrientationGizmo = ORIENTATION_GIZMO_SCENE.instantiate()
|
||||||
|
accel_row.add_child(orient_gizmo)
|
||||||
|
|
||||||
clients_list.add_child(panel)
|
clients_list.add_child(panel)
|
||||||
|
# clear() after enter_tree so @onready nodes exist; _ready also clears.
|
||||||
|
orient_gizmo.clear()
|
||||||
|
|
||||||
_client_ui[cid] = {
|
_client_ui[cid] = {
|
||||||
"available": available,
|
"available": available,
|
||||||
@@ -360,6 +374,7 @@ func _populate_clients(clients: Array) -> void:
|
|||||||
"led_digit_row": led_digit_row,
|
"led_digit_row": led_digit_row,
|
||||||
"led_blink_row": led_blink_row,
|
"led_blink_row": led_blink_row,
|
||||||
"values_label": values_label,
|
"values_label": values_label,
|
||||||
|
"orient_gizmo": orient_gizmo,
|
||||||
}
|
}
|
||||||
_update_led_fields(cid)
|
_update_led_fields(cid)
|
||||||
|
|
||||||
@@ -443,14 +458,18 @@ func _on_input(data: Dictionary) -> void:
|
|||||||
var ui: Dictionary = _client_ui[cid]
|
var ui: Dictionary = _client_ui[cid]
|
||||||
var lines: PackedStringArray = []
|
var lines: PackedStringArray = []
|
||||||
if client.get("valid", false):
|
if client.get("valid", false):
|
||||||
|
var ax := int(client.get("x", 0))
|
||||||
|
var ay := int(client.get("y", 0))
|
||||||
|
var az := int(client.get("z", 0))
|
||||||
lines.append(
|
lines.append(
|
||||||
"x=%d y=%d z=%d (age %sms)" % [
|
"x=%d y=%d z=%d (age %sms)" % [
|
||||||
int(client.get("x", 0)),
|
ax, ay, az,
|
||||||
int(client.get("y", 0)),
|
|
||||||
int(client.get("z", 0)),
|
|
||||||
str(client.get("accel_age_ms", "?")),
|
str(client.get("accel_age_ms", "?")),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
ui["orient_gizmo"].set_accel(Vector3(float(ax), float(ay), float(az)))
|
||||||
|
else:
|
||||||
|
ui["orient_gizmo"].clear()
|
||||||
|
|
||||||
var tap_kind := str(client.get("tap_kind", "none"))
|
var tap_kind := str(client.get("tap_kind", "none"))
|
||||||
if tap_kind != "none":
|
if tap_kind != "none":
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
extends SubViewportContainer
|
||||||
|
class_name OrientationGizmo
|
||||||
|
|
||||||
|
const MIN_ACCEL_LEN := 1000.0
|
||||||
|
const SMOOTH := 0.25
|
||||||
|
const VIEW_SIZE := Vector2(220, 220)
|
||||||
|
const ACTIVE_MODULATE := Color(1, 1, 1, 1)
|
||||||
|
const IDLE_MODULATE := Color(0.55, 0.58, 0.65, 0.85)
|
||||||
|
|
||||||
|
## Mesh TOP = local −Y.
|
||||||
|
## Flat accel ≈ +Z → side view, TOP at top of screen
|
||||||
|
## Facing accel ≈ −Y → TOP face toward camera
|
||||||
|
## Steer accel ≈ −X → facing + roll left (steering wheel)
|
||||||
|
var _basis_flat: Basis = Basis(Quaternion(Vector3.RIGHT, PI))
|
||||||
|
var _basis_facing: Basis = Basis(Quaternion(Vector3.RIGHT, -PI * 0.5))
|
||||||
|
|
||||||
|
@onready var _viewport: SubViewport = $SubViewport
|
||||||
|
@onready var _camera: Camera3D = $SubViewport/Camera3D
|
||||||
|
@onready var _pod: Node3D = $SubViewport/Pod
|
||||||
|
|
||||||
|
var _has_sample := false
|
||||||
|
var _target_basis := Basis.IDENTITY
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
custom_minimum_size = VIEW_SIZE
|
||||||
|
stretch = true
|
||||||
|
_viewport.own_world_3d = true
|
||||||
|
_viewport.transparent_bg = true
|
||||||
|
_viewport.size = Vector2i(VIEW_SIZE)
|
||||||
|
_viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
|
||||||
|
_camera.projection = Camera3D.PROJECTION_ORTHOGONAL
|
||||||
|
_camera.size = 2.6
|
||||||
|
_camera.near = 0.05
|
||||||
|
_camera.far = 50.0
|
||||||
|
_camera.position = Vector3(0.0, 0.0, 3.2)
|
||||||
|
_camera.look_at(Vector3.ZERO, Vector3.UP)
|
||||||
|
clear()
|
||||||
|
|
||||||
|
|
||||||
|
func set_accel(v: Vector3) -> void:
|
||||||
|
if v.length() < MIN_ACCEL_LEN:
|
||||||
|
return
|
||||||
|
_has_sample = true
|
||||||
|
modulate = ACTIVE_MODULATE
|
||||||
|
_target_basis = _basis_from_accel(v)
|
||||||
|
|
||||||
|
|
||||||
|
func clear() -> void:
|
||||||
|
_has_sample = false
|
||||||
|
_target_basis = _basis_flat
|
||||||
|
if _pod != null:
|
||||||
|
_pod.basis = _basis_flat
|
||||||
|
modulate = IDLE_MODULATE
|
||||||
|
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
if not _has_sample:
|
||||||
|
return
|
||||||
|
_pod.basis = _pod.basis.slerp(_target_basis, SMOOTH)
|
||||||
|
|
||||||
|
|
||||||
|
func _basis_from_accel(accel: Vector3) -> Basis:
|
||||||
|
var n := accel.normalized()
|
||||||
|
# 0 = liegend (+Z), 1 = aufgerichtet / gedreht (wenig +Z)
|
||||||
|
var face_t := clampf(1.0 - maxf(n.z, 0.0), 0.0, 1.0)
|
||||||
|
var b := _basis_flat.slerp(_basis_facing, face_t)
|
||||||
|
# Lenkrad-Roll in der Sensor-XY-Ebene:
|
||||||
|
# facing (0,−1) → 0; links (−1,0) → +π/2; rechts (+1,0) → −π/2
|
||||||
|
var roll := atan2(-n.x, -n.y)
|
||||||
|
return Basis(Quaternion(Vector3.BACK, roll * face_t)) * b
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://djoqip111hnbd
|
||||||
Reference in New Issue
Block a user