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.
90 lines
2.8 KiB
90 lines
2.8 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 — duplicate to ensure each instance has its own
|
|
_material = material_override as StandardMaterial3D
|
|
if _material != null:
|
|
_material = _material.duplicate() as StandardMaterial3D
|
|
material_override = _material
|
|
else:
|
|
_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
|