merge refactor branch

This commit is contained in:
Hartmut Seichter 2025-07-02 23:34:02 +02:00
commit 4da8618746
23 changed files with 455 additions and 256 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
# Godot 4+ specific ignores
.godot/
/android/
*.blend?

9
LICENSE.md Normal file
View file

@ -0,0 +1,9 @@
Copyright 2025 Hartmut Seichter
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,18 +0,0 @@
# Resources
- [vrpn-rs description of VRPN protocol](https://github.com/vrpn/vrpn-rs/blob/main/Protocol.md)
# Design
Allow for a session based design - data needs to be collected per block
and immediatly made available. Minimal or no internal state keeping!
- first collect names and ids of message_types and senders (stored in session)
- register listeners in session
- listeners inject captured data with signals
- keep naming "classic" ??? ... above session is a tracker with a sender with sensors

23
README.md Normal file
View file

@ -0,0 +1,23 @@
# gdvrpn
A native GDScript implementation of the [VRPN](https://vrpn.github.io/) client protocol.
# TOC
# Background
Various VR tools even today in use are providing an interface for tracking data by implementing a VPRN server. Binding a the actual VRPN library into an extension is proven and working but is quite a burden if the main aim of usage is receiving tracking data. Hence, this project was born to allow for easy integration of VRPN tracking in Godot.
This project does not implement any server components and is only tested with a limited number of devices in our lab.
# Install
Install the project from the AssetLib in Godot or directly from the repository.
# Usage
TBW
# License
This project is licensed under the terms of the [MIT License](https://opensource.org/license/mit)

View file

@ -1,12 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://bj5ykdjle10tt"]
[ext_resource type="Script" uid="uid://vnywsf0rn1ax" path="res://SocketClient.gd" id="1_gxo8o"]
[node name="Node3D" type="Node3D"]
script = ExtResource("1_gxo8o")
vrpn_server = "212.201.64.122"
[connection signal="connected" from="." to="." method="_on_connected"]
[connection signal="data" from="." to="." method="_on_data"]
[connection signal="disconnected" from="." to="." method="_on_disconnected"]
[connection signal="error" from="." to="." method="_on_error"]

View file

@ -1,82 +0,0 @@
extends Node
signal connected(s:StreamPeerTCP)
signal data(data:Array)
signal disconnected
signal error
@onready var _stream: StreamPeerTCP = StreamPeerTCP.new()
@export var vrpn_server : String = "localhost"
@export var vrpn_port : int = 3883
func _ready() -> void:
self.connect_to_host(vrpn_server,vrpn_port)
func _process(delta: float) -> void:
var old_status = _stream.get_status()
_stream.poll()
var new_status = _stream.get_status()
if old_status != new_status:
match new_status:
_stream.STATUS_NONE:
emit_signal("disconnected")
_stream.STATUS_CONNECTING:
print("Connecting.")
_stream.STATUS_CONNECTED:
print("Connected.")
emit_signal("connected",_stream.poll()
)
_stream.STATUS_ERROR:
print("Error with socket stream.")
emit_signal("error")
if new_status == _stream.STATUS_CONNECTED:
var available_bytes: int = _stream.get_available_bytes()
if available_bytes > 0:
var res = _stream.get_partial_data(available_bytes)
if res[0] != OK:
emit_signal("error")
else:
emit_signal("data", res[1])
func connect_to_host(host: String, port: int) -> void:
print("Connecting to %s:%d" % [host, port])
if _stream.connect_to_host(host, port) != OK:
print("Error connecting to host.")
emit_signal("error")
func send(data: PackedByteArray) -> bool:
if _stream.get_status() != _stream.STATUS_CONNECTED:
print("Error: Stream is not currently connected.")
return false
var error: int = _stream.put_data(data)
if error != OK:
print("Error writing to stream: ", error)
return false
return true
func _on_data(data : Array):
var bytes = PackedByteArray(data)
var as_cookie = bytes.get_string_from_ascii()
# Cookie Hack!
if as_cookie.begins_with(VRPN.magic_cookie_start): #
# kaboom we just send back the same cookie :)
self.send(bytes)
else:
VRPN.marshall_block(bytes)
func _on_connected(s : StreamPeerTCP):
print("Connected to",s.get_connected_host()) # Replace with function body.
func _on_disconnected():
print("Disconnected") # Replace with function body.
func _on_error():
print("Error") # Replace with function body.

View file

@ -1 +0,0 @@
uid://vnywsf0rn1ax

87
VRPN.gd
View file

@ -1,87 +0,0 @@
extends Node
class_name VRPN
const magic_cookie_start : String = "vrpn: ver."
static func marshall_block(data : PackedByteArray) -> void:
# need to fix that
var block_offset : int = 0
var header_size = aligned_size(20) # kinda redundant as we take the seq number as well
while data.size() > block_offset:
# reader for stream
var header := StreamPeerBuffer.new()
# get block addresses
header.data_array = data.slice(block_offset,block_offset+header_size)
# make sure we read as big endian
header.big_endian = true
# read header
var length := header.get_32() as int # length of message
var time_sec := header.get_32() as int # datetime sec
var time_msec := header.get_32() as int # datetime micro sec
var sender_id := header.get_32() as int # sender id
var message_type := header.get_32() as int # type of message (payload)
var sequence_num := header.get_32() as int # inofficial sequence number (padding)
if false:
print("length '%d'" % length)
print("time_sec '%d'" % time_sec)
print("time_msec '%d'" % time_msec)
print("sender_id '%d'" % sender_id)
print("message_type '%d'" % message_type)
print("sequence_num '%d'" % sequence_num)
# print
print("sender_id:{0} message_type:{1} block_offset:{2} header_size:{3} length:{4}".format([sender_id, message_type, block_offset,header_size,length]))
# directly hand over marshalling for body
marshall_body(data.slice(block_offset+header_size,block_offset+length),message_type)
# next datablock
block_offset += aligned_size(length)
static func marshall_body(data : PackedByteArray,message_type : int):
var body := StreamPeerBuffer.new()
body.data_array = data
body.big_endian = true
# sender description
match message_type: # message_type_ids are supposedly dynamic
-1,-2:
# get length of string
var body_length = body.get_32()
# get string
var sender_name = body.get_string(body_length)
print("sender name is '%s'" % sender_name)
#print(body.data_array)
4: # quat pos
# get id
var sensor_id = body.get_32()
var padding = body.get_32()
var pos = Vector3(body.get_double(),body.get_double(),body.get_double())
# VRPN quaternions are w,xyz
var quat_w = body.get_double()
var quat_x = body.get_double()
var quat_y = body.get_double()
var quat_z = body.get_double()
var quat = Quaternion(quat_x,quat_y,quat_z,quat_w)
#print("sensor id {0} {1} {2}".format([sensor_id,pos,quat]))
18:
var num_buttons = body.get_32()
var buttons = {}
for i in num_buttons:
var button_id = body.get_32()
var button_state = body.get_32()
buttons[button_id] = button_state
#print("sensor with {0} buttons".format([buttons]))
_:
print("unhandled message type {0}".format([message_type]))
static func aligned_size(actual_size : int, alignment : int = 8) -> int:
return (actual_size + alignment - 1) & ~(alignment - 1)

Binary file not shown.

View file

@ -0,0 +1,62 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://b426fy7d6jw2d"
path="res://.godot/imported/axis.blend-64f5b0cb53b3b69c5a0ce687708e9ae2.scn"
[deps]
source_file="res://addons/vrpn/assets/axis.blend"
dest_files=["res://.godot/imported/axis.blend-64f5b0cb53b3b69c5a0ce687708e9ae2.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={
"nodes": {
"PATH:Camera": {
"import/skip_import": true
},
"PATH:Light": {
"import/skip_import": true
}
}
}
blender/nodes/visible=0
blender/nodes/active_collection_only=false
blender/nodes/punctual_lights=true
blender/nodes/cameras=true
blender/nodes/custom_properties=true
blender/nodes/modifiers=1
blender/meshes/colors=false
blender/meshes/uvs=true
blender/meshes/normals=true
blender/meshes/export_geometry_nodes_instances=false
blender/meshes/tangents=true
blender/meshes/skins=2
blender/meshes/export_bones_deforming_mesh_only=false
blender/materials/unpack_enabled=true
blender/materials/export_materials=1
blender/animation/limit_playback=true
blender/animation/always_sample=true
blender/animation/group_tracks=true

24
addons/vrpn/docs/Notes.md Normal file
View file

@ -0,0 +1,24 @@
# Resources
Reference for implementing this library are from here [vrpn-rs description of VRPN protocol](https://github.com/vrpn/vrpn-rs/blob/main/Protocol.md)
Unfortunately, there are some mistakes in the document, most notably it presumes that
Quaternion in VRPN (quat library) are w,x,y,z - which is incorrect.
# Design
Allow for a session based design - data needs to be collected per block
and immediatly made available. Minimal or no internal state keeping!
- first collect names and ids of message_types and senders (stored in session)
- register listeners in session
- listeners inject captured data with signals
- keep naming "classic" ??? ... above session is a tracker with a sender with sensors
# Todo
- [x] Testset for Quaternion rotations
- [x] Axis as subscene
# Internal Notes
- Graphics Interaction Lab OptiTrack system is at 212.201.64.122

View file

@ -0,0 +1,47 @@
[gd_scene load_steps=5 format=3 uid="uid://en7tpf1d6yak"]
[ext_resource type="Script" uid="uid://dmq3i7qmo1qe0" path="res://addons/vrpn/scripts/VRPN.gd" id="1_jrm7s"]
[ext_resource type="Script" uid="uid://dpj1wrvfsiq4v" path="res://addons/vrpn/scripts/VRPN_Receiver.gd" id="2_fp2uy"]
[ext_resource type="PackedScene" uid="uid://b426fy7d6jw2d" path="res://addons/vrpn/assets/axis.blend" id="3_73ywu"]
[sub_resource type="PlaneMesh" id="PlaneMesh_24d08"]
size = Vector2(6, 2)
[node name="Node3D" type="Node3D"]
[node name="Camera3D" type="Camera3D" parent="."]
transform = Transform3D(-1, 0, 8.74228e-08, 0, 1, 0, -8.74228e-08, 0, -1, 0, 0.355791, -1.59348)
current = true
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(-0.878275, 0.266876, -0.396749, -7.71365e-11, 0.829749, 0.558137, 0.478155, 0.490197, -0.728748, 0, 0.631436, 0)
shadow_enabled = true
[node name="Root" type="Node3D" parent="."]
[node name="VRPN" type="Node3D" parent="Root" node_paths=PackedStringArray("tracker_receivers")]
script = ExtResource("1_jrm7s")
tracker_receivers = [NodePath("RB1"), NodePath("RB2")]
vrpn_server = "212.201.64.122"
[node name="RB1" type="Node3D" parent="Root/VRPN"]
script = ExtResource("2_fp2uy")
tracker_name = "RB1"
[node name="axis" parent="Root/VRPN/RB1" instance=ExtResource("3_73ywu")]
transform = Transform3D(0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.1, 0, 0, 0)
[node name="RB2" type="Node3D" parent="Root/VRPN"]
script = ExtResource("2_fp2uy")
tracker_name = "RB2"
[node name="axis" parent="Root/VRPN/RB2" instance=ExtResource("3_73ywu")]
transform = Transform3D(0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.1, 0, 0, 0)
[node name="Floor" type="MeshInstance3D" parent="Root"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.704947, 0)
mesh = SubResource("PlaneMesh_24d08")
[node name="Wall" type="MeshInstance3D" parent="Root"]
transform = Transform3D(-1, 8.74228e-08, -3.82137e-15, 0, -4.37114e-08, -1, -8.74228e-08, -1, 4.37114e-08, 0, 0.268571, 1.06259)
mesh = SubResource("PlaneMesh_24d08")

View file

@ -0,0 +1,70 @@
[gd_scene load_steps=5 format=3 uid="uid://bj5ykdjle10tt"]
[ext_resource type="Script" uid="uid://dmq3i7qmo1qe0" path="res://addons/vrpn/scripts/VRPN.gd" id="2_24d08"]
[ext_resource type="Script" uid="uid://dpj1wrvfsiq4v" path="res://addons/vrpn/scripts/VRPN_Receiver.gd" id="2_170dk"]
[ext_resource type="PackedScene" uid="uid://b426fy7d6jw2d" path="res://addons/vrpn/assets/axis.blend" id="3_170dk"]
[sub_resource type="PlaneMesh" id="PlaneMesh_24d08"]
size = Vector2(6, 2)
[node name="Node3D" type="Node3D"]
[node name="Camera3D" type="Camera3D" parent="."]
transform = Transform3D(1, 0, -1.74846e-07, 0, 1, 0, 1.74846e-07, 0, 1, 0, 0.933082, 2.80616)
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(-0.999686, -0.0139775, 0.0207795, 0.0125216, 0.439603, 0.898105, -0.021688, 0.898083, -0.43929, 0, 1.59521, 0)
shadow_enabled = true
[node name="Root" type="Node3D" parent="."]
[node name="VRPN" type="Node3D" parent="Root" node_paths=PackedStringArray("tracker_receivers")]
script = ExtResource("2_24d08")
tracker_receivers = [NodePath("../SpinTracker/Offset0/Tracker0"), NodePath("../SpinTracker/Tracker1"), NodePath("../SpinTracker/Offset2/Tracker2")]
[node name="Floor" type="MeshInstance3D" parent="Root"]
mesh = SubResource("PlaneMesh_24d08")
[node name="SpinTracker" type="Node3D" parent="Root"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.764802, 0)
[node name="Tracker1" type="Node3D" parent="Root/SpinTracker"]
script = ExtResource("2_170dk")
tracker_name = "Tracker1"
tracker_use_position = false
[node name="Axis" parent="Root/SpinTracker/Tracker1" instance=ExtResource("3_170dk")]
transform = Transform3D(0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.1, 0, 0, 0)
[node name="Label-Y-Axis" type="Label3D" parent="Root/SpinTracker/Tracker1"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.759683, 0)
text = "Y-Axis"
[node name="Offset2" type="Node3D" parent="Root/SpinTracker"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0, 0)
[node name="Tracker2" type="Node3D" parent="Root/SpinTracker/Offset2"]
script = ExtResource("2_170dk")
tracker_name = "Tracker2"
tracker_use_position = false
[node name="Axis" parent="Root/SpinTracker/Offset2/Tracker2" instance=ExtResource("3_170dk")]
transform = Transform3D(0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.1, 0, 0, 0)
[node name="Label-Z-Axis" type="Label3D" parent="Root/SpinTracker/Offset2/Tracker2"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.764205, 0)
text = "Z-Axis"
[node name="Offset0" type="Node3D" parent="Root/SpinTracker"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 0, 0)
[node name="Tracker0" type="Node3D" parent="Root/SpinTracker/Offset0"]
script = ExtResource("2_170dk")
tracker_use_position = false
[node name="axis" parent="Root/SpinTracker/Offset0/Tracker0" instance=ExtResource("3_170dk")]
transform = Transform3D(0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.1, 0, 0, 0)
[node name="Label3D" type="Label3D" parent="Root/SpinTracker/Offset0/Tracker0"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.769847, 0)
text = "X-Axis"

View file

Before

Width:  |  Height:  |  Size: 994 B

After

Width:  |  Height:  |  Size: 994 B

Before After
Before After

View file

@ -3,15 +3,15 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://cdprcmtx102rp"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
path="res://.godot/imported/icon.svg-bbc889e9147eb1676401b9ec3d05066e.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
source_file="res://addons/vrpn/icons/icon.svg"
dest_files=["res://.godot/imported/icon.svg-bbc889e9147eb1676401b9ec3d05066e.ctex"]
[params]

193
addons/vrpn/scripts/VRPN.gd Normal file
View file

@ -0,0 +1,193 @@
extends Node
class_name VRPN
# tracking associated data
enum TrackingData { POS_QUAT, VELOCITY, ACCELERATION }
# magic cookie
const magic_cookie_start : String = "vrpn: ver."
# kinda redundant as we take the seq number as well
static var header_size : int = aligned_size(20)
var sensors : Dictionary[int,String] = {}
var messages : Dictionary[int,String] = {}
signal connected(s:StreamPeerTCP)
signal data(data:Array)
signal disconnected
signal error(msg:String)
@onready var _stream: StreamPeerTCP = StreamPeerTCP.new()
@export var tracker_receivers : Array[VRPN_Receiver] = []
@export var vrpn_server : String = "127.0.0.1"
@export var vrpn_port : int = 3883
func _ready() -> void:
self.connect_to_host(vrpn_server,vrpn_port)
if not connected.has_connections():
connected.connect(self._on_connected)
if not data.has_connections():
data.connect(self._on_data)
if not disconnected.has_connections():
disconnected.connect(self._on_disconnected)
if not error.has_connections():
error.connect(self._on_error)
func _process(delta: float) -> void:
# store old state
var old_status = _stream.get_status()
# poll
_stream.poll()
# receive all errors
var new_status = _stream.get_status()
# process if something changed
if old_status != new_status:
match new_status:
_stream.STATUS_NONE:
emit_signal("disconnected")
_stream.STATUS_CONNECTING:
pass
_stream.STATUS_CONNECTED:
emit_signal("connected",_stream)
_stream.STATUS_ERROR:
emit_signal("error","error with socket stream from {0}:{1}".format([_stream.get_connected_host(),_stream.get_connected_port()]))
if new_status == _stream.STATUS_CONNECTED:
var available_bytes: int = _stream.get_available_bytes()
if available_bytes > 0:
var res = _stream.get_partial_data(available_bytes)
if res[0] != OK:
emit_signal("error","error receiving data {0}".format([res[0]]))
else:
emit_signal("data", res[1])
func connect_to_host(host: String, port: int) -> void:
if _stream.connect_to_host(host, port) != OK:
emit_signal("error","error connecting to host '{0}:{1}'".format([host,port]))
func send(data: PackedByteArray) -> bool:
if _stream.get_status() != _stream.STATUS_CONNECTED:
emit_signal("error","stream not connected!")
return false
var res: int = _stream.put_data(data)
if res != OK:
emit_signal("error","error writing to stream {0}!".format([res]))
return false
return true
func _on_data(data : Array):
var bytes = PackedByteArray(data)
var as_cookie = bytes.get_string_from_ascii()
# Cookie Hack!
if as_cookie.begins_with(VRPN.magic_cookie_start): #
# kaboom we just send back the same cookie :)
self.send(bytes)
else:
VRPN.marshall_block(bytes,self)
func _on_connected(s : StreamPeerTCP):
print("Connected to",s.get_connected_host()) # Replace with function body.
func _on_disconnected():
push_warning("Disconnected") # Replace with function body.
func _on_error(msg:String):
push_warning(msg) # Replace with function body.
static func marshall_block(data : PackedByteArray,session : VRPN) -> void:
# need to fix that
var block_offset : int = 0
while data.size() > block_offset:
# reader for stream
var header := StreamPeerBuffer.new()
# get block addresses
header.data_array = data.slice(block_offset,block_offset+header_size)
# make sure we read as big endian
header.big_endian = true
# read header
var length := header.get_32() as int # length of message
var time_sec := header.get_32() as int # datetime sec
var time_msec := header.get_32() as int # datetime micro sec
var sender_id := header.get_32() as int # sender id (tracker or interfaces)
var message_type := header.get_32() as int # type of message (payload)
var sequence_num := header.get_32() as int # inofficial sequence number (padding)
# for debugging
#print("length '%d'" % length)
#print("time_sec '%d'" % time_sec)
#print("time_msec '%d'" % time_msec)
#print("sender_id '%d'" % sender_id)
#print("message_type '%d'" % message_type)
#print("sequence_num '%d'" % sequence_num)
marshall_body(data.slice(block_offset+header_size,block_offset+length),message_type,sender_id,session)
# next datablock
block_offset += aligned_size(length)
static func decode_string(stream : StreamPeerBuffer) -> String:
var len = stream.get_32()
return stream.get_string(len)
static func marshall_body(data : PackedByteArray,message_type : int, sender_id: int, session : VRPN):
var body := StreamPeerBuffer.new()
body.data_array = data
body.big_endian = true
# only take message_type directly for negative (-1,-2)
# messages that provide dynamic descriptors
if message_type < 0:
# message and sender descriptions
match message_type:
-1: # sensor names
var name = decode_string(body)
print("sensor name is '%s' with '%d" % [name,sender_id])
session.sensors[sender_id] = name
-2: # message names
var name = decode_string(body)
print("message name is '%s' for message_type '%d'" % [name,sender_id])
session.messages[sender_id] = name
return
# now we use the string identifiers
# because they are supposedly dynamically assigned
match session.messages[message_type]:
'vrpn_Tracker Pos_Quat': # quat pos
# get id
var sensor_id = body.get_32()
var padding = body.get_32() # padding
# position
var pos = Vector3(body.get_double(),body.get_double(),body.get_double())
# VRPN quat layout and Godot Quaternion c'tor identical with x,y,z,w
var quat = Quaternion(body.get_double(),body.get_double(),body.get_double(),body.get_double()).normalized()
# submit to listener
for r in session.tracker_receivers:
r._on_pos_quat({
"tracker" : session.sensors[sender_id],
"sensor" : sensor_id,
"position" : pos,
"rotation" : quat
})
'vrpn_Tracker Velocity':
pass
_:
pass
#print("unhandled message type {0}".format([message_type]))
static func aligned_size(actual_size : int, alignment : int = 8) -> int:
return (actual_size + alignment - 1) & ~(alignment - 1)

View file

@ -0,0 +1,18 @@
extends Node3D
class_name VRPN_Receiver
@export var tracker_name : String = "Tracker0"
@export var tracker_sensor : int = 0
@export var tracker_use_position : bool = true
@export var tracker_use_rotation : bool = true
func _on_pos_quat(tracker_data : Dictionary):
if tracker_data['tracker'] == tracker_name and tracker_data['sensor'] == tracker_sensor:
if tracker_use_position:
self.global_position = tracker_data['position']
if tracker_use_rotation:
var rotation := tracker_data['rotation'] as Quaternion
self.global_basis = Basis(rotation)

View file

@ -0,0 +1 @@
uid://dpj1wrvfsiq4v

View file

@ -40,7 +40,9 @@
# float z_of_axis_to_spin_around
# float rotation_rate_around_axis_in_Hz
vrpn_Tracker_Spin Tracker0 1 200.0 0.0 1.0 0.0 0.1
vrpn_Tracker_Spin Tracker0 1 200.0 1.0 0.0 0.0 0.1
vrpn_Tracker_Spin Tracker1 1 200.0 0.0 1.0 0.0 0.2
vrpn_Tracker_Spin Tracker2 1 200.0 0.0 0.0 1.0 0.3
################################################################################
# WintrackerIII from VR SPace

View file

@ -13,4 +13,4 @@ config_version=5
config/name="uvrpn"
run/main_scene="uid://bj5ykdjle10tt"
config/features=PackedStringArray("4.4", "Forward Plus")
config/icon="res://icon.svg"
config/icon="uid://cdprcmtx102rp"

View file

@ -1,50 +0,0 @@
extends Node
@export var url : String = "localhost"
@export var port : int = 3883
var vrpn_cookie : String = "vrpn: ver. 07.35"
#var server : UDPServer = null
@onready var socket : StreamPeerTCP = StreamPeerTCP.new()
func _ready() -> void:
#if StreamPeerTCP.STATUS_NONE == socket.get_status():
if socket.connect_to_host(url,port) == OK:
print("Socket connected ...")
socket.set_no_delay(true)
send_data(socket,vrpn_cookie.to_utf8_buffer())
#server = UDPServer.new()
#server.listen(3883)
else:
print("Error connecting to server")
#else:
#print("Stream not ready")
func _process(delta: float) -> void:
if socket:
socket.poll()
##return
##if server:
##server.poll()
##if server.is_connection_available():
##var peer = server.take_connection()
##var packet = peer.get_packet()
##print("Accepted peer: %s:%s" % [peer.get_packet_ip(), peer.get_packet_port()])
##print("Received data: %s" % [packet.get_string_from_utf8()])
### Reply so it knows we received the message.
##peer.put_packet(packet)
static func send_data(socket : StreamPeerTCP, data: PackedByteArray) -> bool:
print(socket.get_status())
if socket.get_status() == StreamPeerTCP.STATUS_CONNECTED:
if socket.put_data(data) == OK:
return true
else:
print("Error writing data ...")
return false
else:
print("Error connecting")
return false

View file

@ -1 +0,0 @@
uid://ca5psnjx63ua8