start work on rpi version

This commit is contained in:
Daniel Bulant 2025-11-26 20:05:32 +01:00
parent ddc7aa1045
commit 0ce0445426
6 changed files with 160 additions and 24 deletions

View file

@ -3,6 +3,7 @@ class_name Lights
@export var light_count: int = 1 @export var light_count: int = 1
@export var light_radius: float = 5.0 @export var light_radius: float = 5.0
@export var light_slowdown_speed: float = 1.0
@export var light_template: PackedScene @export var light_template: PackedScene
@export var pattern_repetition: int = 5 @export var pattern_repetition: int = 5
@ -19,21 +20,19 @@ func _ready() -> void:
light_instance.position = Vector3(x, 0, z) light_instance.position = Vector3(x, 0, z)
add_child(light_instance) add_child(light_instance)
const BASE_DELTA: float = 16
var virtual_rotation: float = 0.0 var virtual_rotation: float = 0.0
const FULL_DARK_ROTATION_DELTA := .1 const FULL_DARK_ROTATION_DELTA := .1
func _process(delta: float) -> void: func _process(delta: float) -> void:
var frames_elapsed = delta / BASE_DELTA virtual_rotation += expected_rotation_delta * (delta * 60)
virtual_rotation += expected_rotation_delta var max_darkness := clampf(abs(expected_rotation_delta / FULL_DARK_ROTATION_DELTA), 0, 1)
var max_darkness = expected_rotation_delta / FULL_DARK_ROTATION_DELTA
var i := 0 var i := 0
for light in get_children(): for light in get_children():
if light is SpotLight3D: if light is SpotLight3D:
var pattern_offset := ((i + int(virtual_rotation * pattern_repetition)) % pattern_repetition) / float(pattern_repetition) var pattern_offset := wrapf((i + (virtual_rotation * pattern_repetition)) / pattern_repetition, 0, 1)
light.light_color = light_color light.light_color = light_color
light.light_energy = 1 - (1 - pattern_offset) * max_darkness light.light_energy = (1 - (1 - pattern_offset) * max_darkness) * light_intensity
i += 1 i += 1
expected_rotation_delta = lerp(expected_rotation_delta, 0.0, frames_elapsed * 5) expected_rotation_delta = lerp(expected_rotation_delta, 0.0, delta * light_slowdown_speed)

47
main.gd
View file

@ -2,18 +2,33 @@ extends Node3D
@onready var lights: Lights = %Lights @onready var lights: Lights = %Lights
@onready var slider: HSlider = %HSlider @onready var slider: HSlider = %HSlider
@onready var plane: MeshInstance3D = %Plane @onready var networked_lights: Lights = %NetworkedLights
@export var light_rotation: float = 0.0 @export var light_rotation: float = 0.0
@export var color_gradient: Gradient @export var color_gradient: Gradient
var peer: PacketPeerUDP
func _ready(): func _ready():
slider.value_changed.connect(set_light_rotation) slider.value_changed.connect(set_local_light_rotation)
peer = PacketPeerUDP.new()
peer.bind(4433)
peer.set_dest_address("rpi", 4444)
func send_data(data: Dictionary) -> void:
var json_data := JSON.stringify(data)
var byte_array := json_data.to_utf8_buffer()
peer.put_packet(byte_array)
var error_correction := 0. var error_correction := 0.
func set_light_rotation(value: float) -> void: func set_local_light_rotation(value: float) -> void:
value = wrapf(value, 0, 1) value = wrapf(value, 0, 1)
var polarized_value := pingpong(value * 4, 1)
lights.light_intensity = 1.0 - polarized_value
lights.light_color = color_gradient.sample(value)
slider.value = value
# get shortest diff including wrapping # get shortest diff including wrapping
var diff = light_rotation - value var diff = light_rotation - value
@ -25,12 +40,26 @@ func set_light_rotation(value: float) -> void:
diff = wrapped_diff diff = wrapped_diff
# ignore small movements until a bigger one occurs or until they accumulate enough, to avoid reseting the light movement # ignore small movements until a bigger one occurs or until they accumulate enough, to avoid reseting the light movement
# diff += error_correction diff += error_correction
# if diff < .05: if abs(diff) < .05:
# error_correction = diff error_correction = diff
# return send_data({"value": value})
return
lights.expected_rotation_delta = diff lights.expected_rotation_delta = diff
light_rotation = value light_rotation = value
lights.light_color = color_gradient.sample(value) send_data({"value": value, "delta": diff})
slider.value = value
func _process(_delta):
if peer.get_available_packet_count() > 0:
var array_bytes = peer.get_packet()
var packet_string = array_bytes.get_string_from_ascii()
var json = JSON.parse_string(packet_string)
if json != null:
var value = json.value
var delta = json.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)

View file

@ -24,6 +24,7 @@ sky_material = SubResource("ProceduralSkyMaterial_7dm0k")
[sub_resource type="Environment" id="Environment_0xm2m"] [sub_resource type="Environment" id="Environment_0xm2m"]
background_mode = 2 background_mode = 2
sky = SubResource("Sky_ig7tw") sky = SubResource("Sky_ig7tw")
glow_enabled = true
[node name="Node3D" type="Node3D"] [node name="Node3D" type="Node3D"]
script = ExtResource("1_h2yge") script = ExtResource("1_h2yge")
@ -31,16 +32,11 @@ color_gradient = SubResource("Gradient_h2yge")
[node name="Plane" type="MeshInstance3D" parent="."] [node name="Plane" type="MeshInstance3D" parent="."]
unique_name_in_owner = true unique_name_in_owner = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 0)
mesh = SubResource("CylinderMesh_0xm2m") mesh = SubResource("CylinderMesh_0xm2m")
surface_material_override/0 = SubResource("StandardMaterial3D_7dm0k") surface_material_override/0 = SubResource("StandardMaterial3D_7dm0k")
[node name="WorldEnvironment" type="WorldEnvironment" parent="."] [node name="Lights" type="Marker3D" parent="Plane"]
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)
[node name="Lights" type="Marker3D" parent="."]
unique_name_in_owner = true unique_name_in_owner = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
gizmo_extents = 0.7 gizmo_extents = 0.7
@ -49,6 +45,27 @@ light_count = 20
light_radius = 0.7 light_radius = 0.7
light_template = ExtResource("2_0xm2m") 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)
[node name="HSlider" type="HSlider" parent="."] [node name="HSlider" type="HSlider" parent="."]
unique_name_in_owner = true unique_name_in_owner = true
anchors_preset = 7 anchors_preset = 7

View file

@ -14,3 +14,8 @@ config/name="ambientlightdemo"
run/main_scene="uid://bfysabo18nycq" run/main_scene="uid://bfysabo18nycq"
config/features=PackedStringArray("4.5", "Forward Plus") config/features=PackedStringArray("4.5", "Forward Plus")
config/icon="res://icon.svg" config/icon="res://icon.svg"
[rendering]
anti_aliasing/quality/msaa_2d=2
anti_aliasing/quality/msaa_3d=2

1
rpi/README.md Normal file
View file

@ -0,0 +1 @@
https://learn.adafruit.com/neopixels-on-raspberry-pi/python-usage

85
rpi/lights.py Normal file
View file

@ -0,0 +1,85 @@
import socket
import time
import board
import neopixel
import json
num_pixels = 30
local_pixels = neopixel.NeoPixel(
board.D18, num_pixels, brightness=0.2, auto_write=False, pixel_order=neopixel.GRB
)
networked_pixels = neopixel.NeoPixel(
board.D21, num_pixels, brightness=0.2, auto_write=False, pixel_order=neopixel.GRB
)
colors = ["d062ff", "00b097", "50cc00", "8dcaff", "d062ff"]
colors_rgb = [tuple(int(colors[i][j:j+2], 16) for j in (0, 2, 4)) for i in range(len(colors))]
color_offsets = [0, .25, .5, .75, 1]
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)
FULL_DARK_ROTATION_DELTA = .1
PATTERN_REPETITION = 5
LIGHT_SLOWDOWN_SPEED = 1
def clamp(value, min_value, max_value):
return max(min_value, min(value, max_value))
def wrap(value, min_value, max_value):
range_size = max_value - min_value
while value < min_value:
value += range_size
while value >= max_value:
value -= range_size
return value
def lerp(a, b, t):
return a + (b - a) * t
def pingpong(value, max):
value = wrap(value, 0, max * 2)
if value > max:
value = max * 2 - value
return value
def sample_color_gradient(t):
t = wrap(t, 0, 1)
for i in range(len(color_offsets) - 1):
if t >= color_offsets[i] and t <= color_offsets[i + 1]:
local_t = (t - color_offsets[i]) / (color_offsets[i + 1] - color_offsets[i])
color_a = colors_rgb[i]
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 send_data(value, delta):
message = json.dumps({"value": value, "delta": delta}).encode('utf-8')
sock.sendto(message, (UDP_HOST, UDP_PORT_NETWORKED))
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
class Lights:
virtual_rotation = 0
color = (255, 255, 255)
expected_rotation_delta = 0
def __init__(self, pixels):
self.pixels = pixels
def process(self, delta):
self.virtual_rotation += self.expected_rotation_delta * (delta * 60)
max_darkness = clamp(abs(self.expected_rotation_delta / FULL_DARK_ROTATION_DELTA), 0, 1)
for i in range(num_pixels):
pattern_offset = wrap((i + (self.virtual_rotation * PATTERN_REPETITION)) / PATTERN_REPETITION, 0, 1)
energy = (1 - (1 - pattern_offset) * max_darkness)
new_color = tuple(x * energy for x in self.color)
self.pixels[i] = new_color
self.pixels.show()
self.expected_rotation_delta = lerp(self.expected_rotation_delta, 0, delta * LIGHT_SLOWDOWN_SPEED)