You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

87 lines
2.6 KiB

extends MeshInstance3D
## Displays JPEG webcam frames received from the robot on a quad mesh.
## The quad is positioned in front of the user's view (child of XRCamera3D).
##
## Receives JPEG bytes via the webcam_frame_received signal from TeleopClient.
## Display settings
@export var default_color := Color(0.1, 0.1, 0.1, 0.8)
## State
var _texture: ImageTexture
var _material: StandardMaterial3D
var _frame_count: int = 0
var _last_frame_time: int = 0
var _fps: float = 0.0
var _fps_update_timer: float = 0.0
var _has_received_frame: bool = false
func _ready() -> void:
# Get or create the material
_material = material_override as StandardMaterial3D
if _material == null:
_material = StandardMaterial3D.new()
material_override = _material
# Configure material for unlit display (no scene lighting affects the webcam feed)
_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
_material.albedo_color = default_color
_material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
# Create initial texture (will be replaced on first frame)
_texture = ImageTexture.new()
print("[WebcamDisplay] Ready, waiting for frames...")
func _process(delta: float) -> void:
# Update FPS counter
_fps_update_timer += delta
if _fps_update_timer >= 1.0:
_fps = _frame_count / _fps_update_timer
_frame_count = 0
_fps_update_timer = 0.0
## Called when a JPEG webcam frame is received from the server.
## Connected via signal from TeleopClient in Main.gd.
func _on_webcam_frame(jpeg_bytes: PackedByteArray) -> void:
if jpeg_bytes.size() < 2:
return
var image := Image.new()
var err := image.load_jpg_from_buffer(jpeg_bytes)
if err != OK:
if _frame_count == 0:
printerr("[WebcamDisplay] Failed to decode JPEG frame (size=%d, err=%d)" % [jpeg_bytes.size(), err])
return
# Update texture from decoded image
if _texture.get_image() == null or _texture.get_width() != image.get_width() or _texture.get_height() != image.get_height():
_texture = ImageTexture.create_from_image(image)
_material.albedo_texture = _texture
_material.albedo_color = Color.WHITE # Full brightness once we have a real image
print("[WebcamDisplay] Texture created: %dx%d" % [image.get_width(), image.get_height()])
else:
_texture.update(image)
_frame_count += 1
_last_frame_time = Time.get_ticks_msec()
if not _has_received_frame:
_has_received_frame = true
print("[WebcamDisplay] First frame received!")
## Get display FPS
func get_fps() -> float:
return _fps
## Check if frames are being received
func is_receiving() -> bool:
if not _has_received_frame:
return false
# Consider stale if no frame for 2 seconds
return (Time.get_ticks_msec() - _last_frame_time) < 2000