progress on single shared light

This commit is contained in:
Daniel Bulant 2025-12-02 17:37:11 +01:00
parent 10a1b6d9d8
commit 7e5bfe891c
8 changed files with 151 additions and 25 deletions

25
main.gd
View file

@ -2,18 +2,21 @@ extends Node3D
@onready var lights: Lights = %Lights
@onready var slider: HSlider = %HSlider
@onready var networked_lights: Lights = %NetworkedLights
@export var light_rotation: float = 0.0
@export var light_delta: float = 0.0
@export var color_gradient: Gradient
var remote_light_rotation: float = 0.0
var remote_light_delta: float = 0.0
var peer: PacketPeerUDP
func _ready():
slider.value_changed.connect(set_local_light_rotation)
peer = PacketPeerUDP.new()
peer.bind(4433)
peer.set_dest_address("rpi", 4444)
peer.set_dest_address("rpi1", 4444)
func send_data(data: Dictionary) -> void:
@ -46,11 +49,21 @@ func set_local_light_rotation(value: float) -> void:
send_data({"value": value})
return
error_correction = 0.
lights.expected_rotation_delta = diff
light_delta = diff
light_rotation = value
send_data({"value": value, "delta": diff})
func _process(_delta):
var joystick_x = Input.get_joy_axis(0, JOY_AXIS_LEFT_X)
var joystick_y = Input.get_joy_axis(0, JOY_AXIS_LEFT_Y)
var joystick = Vector2(joystick_x, joystick_y)
if joystick.length() > 0.2:
var angle = (atan2(joystick_y, joystick_x) / TAU) + 0.25
angle = wrapf(angle, 0, 1)
set_local_light_rotation(angle)
if peer.get_available_packet_count() > 0:
var array_bytes = peer.get_packet()
var packet_string = array_bytes.get_string_from_ascii()
@ -58,8 +71,8 @@ func _process(_delta):
if json != null:
var value = json.value
var delta = json.delta
networked_lights.expected_rotation_delta = delta
# networked_lights.expected_rotation_delta = delta
var polarized_value := pingpong(value * 4, 1)
networked_lights.light_intensity = 1.0 - polarized_value
networked_lights.light_color = color_gradient.sample(value)
# var polarized_value := pingpong(value * 4, 1)
# networked_lights.light_intensity = 1.0 - polarized_value
# networked_lights.light_color = color_gradient.sample(value)

View file

@ -32,7 +32,6 @@ color_gradient = SubResource("Gradient_h2yge")
[node name="Plane" type="MeshInstance3D" parent="."]
unique_name_in_owner = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 0)
mesh = SubResource("CylinderMesh_0xm2m")
surface_material_override/0 = SubResource("StandardMaterial3D_7dm0k")
@ -45,26 +44,11 @@ light_count = 20
light_radius = 0.7
light_template = ExtResource("2_0xm2m")
[node name="NetworkedPlane" type="MeshInstance3D" parent="."]
unique_name_in_owner = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0)
mesh = SubResource("CylinderMesh_0xm2m")
surface_material_override/0 = SubResource("StandardMaterial3D_7dm0k")
[node name="NetworkedLights" type="Marker3D" parent="NetworkedPlane"]
unique_name_in_owner = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
gizmo_extents = 0.7
script = ExtResource("1_ig7tw")
light_count = 20
light_radius = 0.7
light_template = ExtResource("2_0xm2m")
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_0xm2m")
[node name="Camera3D" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0, 2, 0)
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0, 1.4683123, 0)
[node name="HSlider" type="HSlider" parent="."]
unique_name_in_owner = true

View file

@ -15,6 +15,19 @@ run/main_scene="uid://bfysabo18nycq"
config/features=PackedStringArray("4.5", "Forward Plus")
config/icon="res://icon.svg"
[input]
forward={
"deadzone": 0.2,
"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null)
]
}
right={
"deadzone": 0.2,
"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null)
]
}
[rendering]
anti_aliasing/quality/msaa_2d=2

View file

@ -31,3 +31,12 @@ def sample_color_gradient(t):
color_b = colors_rgb[i + 1]
return tuple(int(lerp(color_a[j], color_b[j], local_t)) for j in range(3))
return colors_rgb[-1]
def shortest_diff(old, new):
diff = old - new
wrapped_diff = (new + 1) - old
if abs(wrapped_diff) < abs(diff):
diff = -wrapped_diff
wrapped_diff = (old + 1) - new
if abs(wrapped_diff) < abs(diff):
diff = wrapped_diff
return diff

View file

@ -23,3 +23,5 @@ class Lights:
self.pixels[i] = new_color
self.pixels.show()
self.expected_rotation_delta = lerp(self.expected_rotation_delta, 0, delta * LIGHT_SLOWDOWN_SPEED)
lights = Lights(pixels)

73
rpi/main.py Normal file
View file

@ -0,0 +1,73 @@
import socket
import time
import json
from lights import lights
from stepper import stepper
import gdmath
from readangle import read_angle_f
import threading
UDP_HOST = "steamdeck"
UDP_PORT_LOCAL = 4444
UDP_PORT_NETWORKED = 4433
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("", UDP_PORT_LOCAL))
sock.setblocking(0)
def send_json(data):
message = json.dumps(data).encode('utf-8')
sock.sendto(message, (UDP_HOST, UDP_PORT_NETWORKED))
def receive_json():
try:
data, _ = sock.recvfrom(1024)
return json.loads(data.decode('utf-8'))
except BlockingIOError:
return None
local_rotation = 0
local_delta = 0
remote_rotation = 0
remote_delta = 0
def read_thread_fn():
global local_rotation, local_delta
while True:
old_rotation = local_rotation
local_rotation = read_angle_f()
local_delta = gdmath.shortest_diff(old_rotation, local_rotation)
send_json({"value": local_rotation, "delta": local_delta})
def lights_fn():
global local_rotation, local_delta, remote_rotation, remote_delta
last_time = time.time()
delta = 1/60
while True:
lights.color = gdmath.sample_color_gradient(local_rotation + remote_rotation)
lights.expected_rotation_delta = local_delta
lights.process(delta)
current_time = time.time()
time.sleep(1/60)
delta = current_time - last_time
last_time = current_time
def network_recv_fn():
global remote_rotation, remote_delta
while True:
data = receive_json()
if data:
remote_rotation = data["value"]
remote_delta = data["delta"]
time.sleep(0.01)
read_thread = threading.Thread(target=read_thread_fn)
read_thread.start()
lights_thread = threading.Thread(target=lights_fn)
lights_thread.start()
network_thread = threading.Thread(target=network_recv_fn)
network_thread.start()
read_thread.join()
lights_thread.join()
network_thread.join()

14
rpi/readangle.py Normal file
View file

@ -0,0 +1,14 @@
import smbus2
# Define I2C address and bus
AS5600_ADDR = 0x36
ANGLE_REG = 0x0E
bus = smbus2.SMBus(1)
def read_angle_f():
# Read two bytes from the angle register
raw_data = bus.read_i2c_block_data(AS5600_ADDR, ANGLE_REG, 2)
angle = (raw_data[0] << 8) | raw_data[1] # Combine MSB and LSB
angle = angle & 0x0FFF # Mask to 12 bits
return (angle / 4096.0) # convert to 0-1

View file

@ -48,3 +48,21 @@ class StepperMotor:
self.single_step_back()
def pos(self):
return self.current_step % self.step_count
def fpos(self):
return (self.current_step % self.step_count) / self.step_count
def single_step_towards(self, target_pos):
current_pos = self.pos()
# Determine shortest direction
# includes wrap-around (it's a circular motion motor)
diff = (target_pos - current_pos + self.step_count) % self.step_count
if diff > self.step_count / 2:
self.single_step_back()
else:
self.single_step()
def angle_to_pos(self, angle):
return int((angle % 360) / 360 * self.step_count)
def fpos_to_pos(self, fpos):
return int((fpos % 1) * self.step_count)
stepper = StepperMotor()