Compare commits
11 commits
Author | SHA1 | Date | |
---|---|---|---|
|
0f925bb1df | ||
|
7987fa3c8a | ||
af27e657f6 | |||
b90b94922e | |||
5b203c9531 | |||
46491f8e2f | |||
|
3f823111d1 | ||
ea953e92a6 | |||
b54c78da19 | |||
|
4da8618746 | ||
|
ec4728ed2a |
18 changed files with 339 additions and 61 deletions
24
README.md
24
README.md
|
@ -2,13 +2,13 @@
|
|||
|
||||
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.
|
||||
VR hardware and software provide commonly an interface for tracking data by implementing a VPRN server. Binding the actual VRPN client library into an extension is proven and working poses a burden for maintenance and deployability. To solve this gdvrpn allows for a native 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.
|
||||
This project does not implement any server components or forwarding and is only tested with a limited number of devices in our lab. Currently, UDP mode is under development.
|
||||
|
||||

|
||||
|
||||
# Install
|
||||
|
||||
|
@ -16,7 +16,21 @@ Install the project from the AssetLib in Godot or directly from the repository.
|
|||
|
||||
# Usage
|
||||
|
||||
TBW
|
||||
There are two components, a `VRPN_Client` and needed for using this addon:
|
||||
|
||||
`VPRPN_Client` is mandatory and implements the network component of this addon that parses the data received and broadcasts them to the respective receivers within the scene tree.
|
||||
|
||||
`VRPN_Tracker` is a receiver taking pose information (i.e. position and orientation) of a rigid body and applies it to the nodes global transform.
|
||||
|
||||
`VRPN_Button` is a receiver of button events.
|
||||
|
||||
## Example
|
||||
|
||||
For some inspiration try the example `spin_tracker.tscn`. To create a test input use the shipped VRPN config file `test/vrpn.test.cfg` as follows (from the root of this repository)
|
||||
|
||||
```bash
|
||||
$> vrpn_server -f addons/vrpn/tests/vrpn.test.cfg
|
||||
```
|
||||
|
||||
# License
|
||||
|
||||
|
|
11
addons/vrpn/docs/Examples.md
Normal file
11
addons/vrpn/docs/Examples.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
---
|
||||
|
||||
```sh
|
||||
[you@yourpc uvrpn]$ vrpn_server -f addons/vrpn/tests/vrpn.test.cfg
|
||||
Reading from config file addons/vrpn/tests/vrpn.test.cfg
|
||||
Opening vrpn_Tracker_Spin: Tracker0 with 1 sensors, rate 200.000000
|
||||
Opening vrpn_Tracker_Spin: Tracker1 with 1 sensors, rate 200.000000
|
||||
Opening vrpn_Tracker_Spin: Tracker2 with 1 sensors, rate 200.000000
|
||||
Opening vrpn_Button_Example: Button0 with 2 sensors, toggle rate 2.000000
|
||||
```
|
|
@ -12,12 +12,14 @@ 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
|
||||
- [ ] make global/local a choice
|
||||
- [ ] add velocity and acceleration
|
||||
- [ ] add buttons
|
||||
|
||||
# Internal Notes
|
||||
|
||||
|
|
0
addons/vrpn/docs/img/.gdignore
Normal file
0
addons/vrpn/docs/img/.gdignore
Normal file
BIN
addons/vrpn/docs/img/gdvrpn-optitrack.jpg
Normal file
BIN
addons/vrpn/docs/img/gdvrpn-optitrack.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 MiB |
|
@ -1,7 +1,7 @@
|
|||
[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="Script" uid="uid://dmq3i7qmo1qe0" path="res://addons/vrpn/scripts/VRPN_Client.gd" id="1_jrm7s"]
|
||||
[ext_resource type="Script" uid="uid://dpj1wrvfsiq4v" path="res://addons/vrpn/scripts/VRPN_Tracker.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"]
|
||||
|
@ -19,20 +19,21 @@ shadow_enabled = true
|
|||
|
||||
[node name="Root" type="Node3D" parent="."]
|
||||
|
||||
[node name="VRPN" type="Node3D" parent="Root" node_paths=PackedStringArray("tracker_receivers")]
|
||||
[node name="VRPN" type="Node3D" parent="Root"]
|
||||
script = ExtResource("1_jrm7s")
|
||||
tracker_receivers = [NodePath("RB1"), NodePath("RB2")]
|
||||
vrpn_server = "212.201.64.122"
|
||||
|
||||
[node name="RB1" type="Node3D" parent="Root/VRPN"]
|
||||
[node name="RB1" type="Node3D" parent="Root/VRPN" node_paths=PackedStringArray("vrpn_client")]
|
||||
script = ExtResource("2_fp2uy")
|
||||
vrpn_client = NodePath("..")
|
||||
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"]
|
||||
[node name="RB2" type="Node3D" parent="Root/VRPN" node_paths=PackedStringArray("vrpn_client")]
|
||||
script = ExtResource("2_fp2uy")
|
||||
vrpn_client = NodePath("..")
|
||||
tracker_name = "RB2"
|
||||
|
||||
[node name="axis" parent="Root/VRPN/RB2" instance=ExtResource("3_73ywu")]
|
||||
|
|
|
@ -1,12 +1,29 @@
|
|||
[gd_scene load_steps=5 format=3 uid="uid://bj5ykdjle10tt"]
|
||||
[gd_scene load_steps=8 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="Script" uid="uid://dmq3i7qmo1qe0" path="res://addons/vrpn/scripts/VRPN_Client.gd" id="2_24d08"]
|
||||
[ext_resource type="Script" uid="uid://dpj1wrvfsiq4v" path="res://addons/vrpn/scripts/VRPN_Tracker.gd" id="2_170dk"]
|
||||
[ext_resource type="PackedScene" uid="uid://b426fy7d6jw2d" path="res://addons/vrpn/assets/axis.blend" id="3_170dk"]
|
||||
[ext_resource type="Script" uid="uid://bmlyip5xa5df4" path="res://addons/vrpn/scripts/VRPN_Button.gd" id="4_j4l28"]
|
||||
|
||||
[sub_resource type="PlaneMesh" id="PlaneMesh_24d08"]
|
||||
size = Vector2(6, 2)
|
||||
|
||||
[sub_resource type="SphereMesh" id="SphereMesh_j4l28"]
|
||||
radius = 0.25
|
||||
height = 0.5
|
||||
radial_segments = 32
|
||||
rings = 16
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_j4l28"]
|
||||
resource_name = "ButtonReact"
|
||||
script/source = "extends Node3D
|
||||
|
||||
|
||||
func _on_button_0_on_vrpn_button(data):
|
||||
if 0 in data['changes']:
|
||||
self.visible = (data['changes'][0] == 1)
|
||||
"
|
||||
|
||||
[node name="Node3D" type="Node3D"]
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="."]
|
||||
|
@ -18,9 +35,8 @@ shadow_enabled = true
|
|||
|
||||
[node name="Root" type="Node3D" parent="."]
|
||||
|
||||
[node name="VRPN" type="Node3D" parent="Root" node_paths=PackedStringArray("tracker_receivers")]
|
||||
[node name="VRPN" type="Node3D" parent="Root"]
|
||||
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")
|
||||
|
@ -28,8 +44,9 @@ 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"]
|
||||
[node name="Tracker1" type="Node3D" parent="Root/SpinTracker" node_paths=PackedStringArray("vrpn_client")]
|
||||
script = ExtResource("2_170dk")
|
||||
vrpn_client = NodePath("../../VRPN")
|
||||
tracker_name = "Tracker1"
|
||||
tracker_use_position = false
|
||||
|
||||
|
@ -43,8 +60,9 @@ 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"]
|
||||
[node name="Tracker2" type="Node3D" parent="Root/SpinTracker/Offset2" node_paths=PackedStringArray("vrpn_client")]
|
||||
script = ExtResource("2_170dk")
|
||||
vrpn_client = NodePath("../../../VRPN")
|
||||
tracker_name = "Tracker2"
|
||||
tracker_use_position = false
|
||||
|
||||
|
@ -58,8 +76,9 @@ 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"]
|
||||
[node name="Tracker0" type="Node3D" parent="Root/SpinTracker/Offset0" node_paths=PackedStringArray("vrpn_client")]
|
||||
script = ExtResource("2_170dk")
|
||||
vrpn_client = NodePath("../../../VRPN")
|
||||
tracker_use_position = false
|
||||
|
||||
[node name="axis" parent="Root/SpinTracker/Offset0/Tracker0" instance=ExtResource("3_170dk")]
|
||||
|
@ -68,3 +87,18 @@ 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"
|
||||
|
||||
[node name="Button0" type="Node3D" parent="Root/SpinTracker" node_paths=PackedStringArray("vrpn_client")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.21693, 0)
|
||||
script = ExtResource("4_j4l28")
|
||||
vrpn_client = NodePath("../../VRPN")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="Root/SpinTracker/Button0"]
|
||||
mesh = SubResource("SphereMesh_j4l28")
|
||||
script = SubResource("GDScript_j4l28")
|
||||
|
||||
[node name="Label3D" type="Label3D" parent="Root/SpinTracker/Button0/MeshInstance3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.431905, 0)
|
||||
text = "Button"
|
||||
|
||||
[connection signal="on_vrpn_button" from="Root/SpinTracker/Button0" to="Root/SpinTracker/Button0/MeshInstance3D" method="_on_button_0_on_vrpn_button"]
|
||||
|
|
BIN
addons/vrpn/icons/icon.png
Normal file
BIN
addons/vrpn/icons/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
34
addons/vrpn/icons/icon.png.import
Normal file
34
addons/vrpn/icons/icon.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b6ryn3e6ymnyj"
|
||||
path="res://.godot/imported/icon.png-57fdb6924c90abbcc87b543a14f2061e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/vrpn/icons/icon.png"
|
||||
dest_files=["res://.godot/imported/icon.png-57fdb6924c90abbcc87b543a14f2061e.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
|
@ -1 +1,89 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="128"
|
||||
height="128"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="icon.svg"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
inkscape:export-filename="icon.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
style="font-weight:bold;font-size:16px;line-height:1.25;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans, Normal';text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;stroke-width:2.60642"
|
||||
d="M 36.466327,83.172862 H 29.46027 L 24.289133,106.7349 18.784374,83.172862 h -7.214571 l 8.340544,28.899988 h 8.507355 z M 54.02316,112.07285 h 7.715003 L 54.398484,99.770545 c 3.753245,-1.709812 5.546462,-4.003461 5.546462,-7.798409 0,-6.005192 -4.045164,-8.799274 -11.885276,-8.799274 h -9.466517 v 28.899988 h 6.839246 v -11.05122 h 2.668974 z M 45.432399,87.968675 h 2.46046 c 3.377921,0 5.004327,1.209379 5.004327,4.003461 0,3.044299 -1.584703,4.337083 -4.5873,4.337083 h -2.877487 z m 28.232766,-4.795813 h -9.341409 v 28.899988 h 6.839246 v -9.63333 h 3.127704 c 6.672436,0 11.426546,-3.127705 11.426546,-9.841843 0,-6.338814 -4.5873,-9.424815 -12.052087,-9.424815 z m 0.0417,14.345736 H 71.162999 V 88.05208 h 2.418758 c 3.37792,0 5.087732,1.459596 5.087732,4.545597 0,3.628137 -1.876623,4.920921 -4.962624,4.920921 z M 111.61466,83.172862 h -6.1303 v 11.676762 c 0,4.629002 0.54213,8.423946 0.91746,10.759306 L 97.852759,83.172862 h -8.507355 v 28.899988 h 6.088597 v -10.59249 c 0,-5.337952 -0.542135,-9.174602 -0.875757,-11.801874 l 8.340546,22.394364 h 8.71587 z"
|
||||
id="text8"
|
||||
aria-label="VRPN" />
|
||||
<defs
|
||||
id="defs6">
|
||||
<marker
|
||||
style="overflow:visible"
|
||||
id="Triangle"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto-start-reverse"
|
||||
inkscape:stockid="Triangle arrow"
|
||||
markerWidth="1"
|
||||
markerHeight="1"
|
||||
viewBox="0 0 1 1"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"
|
||||
preserveAspectRatio="xMidYMid">
|
||||
<path
|
||||
transform="scale(0.5)"
|
||||
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
|
||||
d="M 5.77,0 -2.88,5 V -5 Z"
|
||||
id="path135" />
|
||||
</marker>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showguides="false"
|
||||
inkscape:zoom="5.1983397"
|
||||
inkscape:cx="15.774267"
|
||||
inkscape:cy="37.896715"
|
||||
inkscape:window-width="2914"
|
||||
inkscape:window-height="1477"
|
||||
inkscape:window-x="26"
|
||||
inkscape:window-y="23"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg6" />
|
||||
<rect
|
||||
width="124"
|
||||
height="124"
|
||||
x="2"
|
||||
y="2"
|
||||
fill="#363d52"
|
||||
stroke="#212532"
|
||||
stroke-width="4"
|
||||
rx="14"
|
||||
id="rect1"
|
||||
style="fill:none" />
|
||||
<g
|
||||
id="g8"
|
||||
transform="matrix(0.55851618,0,0,0.55851618,28.571682,9.9203741)">
|
||||
<path
|
||||
style="fill:none;stroke:#26a269;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Triangle)"
|
||||
d="M 63.5,70.214727 V 23.084294"
|
||||
id="path6" />
|
||||
<path
|
||||
style="fill:none;stroke:#e01b24;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Triangle)"
|
||||
d="m 65,73.460171 40.81615,23.565217"
|
||||
id="path7" />
|
||||
<path
|
||||
style="fill:none;stroke:#1a5fb4;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Triangle)"
|
||||
d="M 61.865858,73.460175 21.04971,97.025388"
|
||||
id="path8" />
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 994 B After Width: | Height: | Size: 3.9 KiB |
20
addons/vrpn/scripts/VRPN_Button.gd
Normal file
20
addons/vrpn/scripts/VRPN_Button.gd
Normal file
|
@ -0,0 +1,20 @@
|
|||
class_name VRPN_Button
|
||||
extends Node
|
||||
|
||||
@export var vrpn_client : VRPN_Client = null
|
||||
@export var button_sensor : String = "Button0"
|
||||
@export var register_on_ready : bool = true
|
||||
|
||||
var state : int = 0
|
||||
|
||||
signal on_vrpn_button(data:Dictionary)
|
||||
|
||||
func _ready():
|
||||
if not vrpn_client:
|
||||
push_warning("No VRPN client for button on '%s' given." % [self.name])
|
||||
elif register_on_ready:
|
||||
vrpn_client.buttons.append(self)
|
||||
|
||||
func _on_vrpn(vrpn_data : Dictionary) -> void:
|
||||
if vrpn_data['sensor'] == button_sensor:
|
||||
on_vrpn_button.emit(vrpn_data)
|
1
addons/vrpn/scripts/VRPN_Button.gd.uid
Normal file
1
addons/vrpn/scripts/VRPN_Button.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://bmlyip5xa5df4
|
|
@ -1,8 +1,9 @@
|
|||
class_name VRPN_Client
|
||||
extends Node
|
||||
class_name VRPN
|
||||
|
||||
# tracking associated data
|
||||
enum TrackingData { POS_QUAT, VELOCITY, ACCELERATION }
|
||||
enum TrackingDataType { POS_QUAT, VELOCITY, ACCELERATION }
|
||||
enum ButtonDataType { BUTTON_STATE, BUTTON_CHANGE }
|
||||
|
||||
# magic cookie
|
||||
const magic_cookie_start : String = "vrpn: ver."
|
||||
|
@ -18,14 +19,14 @@ 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
|
||||
|
||||
var tracker : Array[VRPN_Tracker] = []
|
||||
var buttons : Array[VRPN_Button] = []
|
||||
|
||||
@onready var _stream: StreamPeerTCP = StreamPeerTCP.new()
|
||||
|
||||
func _ready() -> void:
|
||||
|
||||
self.connect_to_host(vrpn_server,vrpn_port)
|
||||
|
@ -87,11 +88,11 @@ func _on_data(data : Array):
|
|||
var as_cookie = bytes.get_string_from_ascii()
|
||||
|
||||
# Cookie Hack!
|
||||
if as_cookie.begins_with(VRPN.magic_cookie_start): #
|
||||
if as_cookie.begins_with(VRPN_Client.magic_cookie_start): #
|
||||
# kaboom we just send back the same cookie :)
|
||||
self.send(bytes)
|
||||
else:
|
||||
VRPN.marshall_block(bytes,self)
|
||||
VRPN_Client.marshall_block(bytes,self)
|
||||
|
||||
|
||||
func _on_connected(s : StreamPeerTCP):
|
||||
|
@ -104,7 +105,7 @@ func _on_error(msg:String):
|
|||
push_warning(msg) # Replace with function body.
|
||||
|
||||
|
||||
static func marshall_block(data : PackedByteArray,session : VRPN) -> void:
|
||||
static func marshall_block(data : PackedByteArray,client : VRPN_Client) -> void:
|
||||
|
||||
# need to fix that
|
||||
var block_offset : int = 0
|
||||
|
@ -133,7 +134,7 @@ static func marshall_block(data : PackedByteArray,session : VRPN) -> void:
|
|||
#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)
|
||||
marshall_body(data.slice(block_offset+header_size,block_offset+length),message_type,sender_id,client)
|
||||
|
||||
# next datablock
|
||||
block_offset += aligned_size(length)
|
||||
|
@ -143,7 +144,7 @@ static func decode_string(stream : StreamPeerBuffer) -> String:
|
|||
return stream.get_string(len)
|
||||
|
||||
|
||||
static func marshall_body(data : PackedByteArray,message_type : int, sender_id: int, session : VRPN):
|
||||
static func marshall_body(data : PackedByteArray,message_type : int, sender_id: int, client : VRPN_Client):
|
||||
var body := StreamPeerBuffer.new()
|
||||
body.data_array = data
|
||||
body.big_endian = true
|
||||
|
@ -156,16 +157,16 @@ static func marshall_body(data : PackedByteArray,message_type : int, sender_id:
|
|||
-1: # sensor names
|
||||
var name = decode_string(body)
|
||||
print("sensor name is '%s' with '%d" % [name,sender_id])
|
||||
session.sensors[sender_id] = name
|
||||
client.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
|
||||
client.messages[sender_id] = name
|
||||
return
|
||||
|
||||
# now we use the string identifiers
|
||||
# because they are supposedly dynamically assigned
|
||||
match session.messages[message_type]:
|
||||
match client.messages[message_type]:
|
||||
'vrpn_Tracker Pos_Quat': # quat pos
|
||||
# get id
|
||||
var sensor_id = body.get_32()
|
||||
|
@ -175,19 +176,73 @@ static func marshall_body(data : PackedByteArray,message_type : int, sender_id:
|
|||
# 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],
|
||||
for r in client.tracker:
|
||||
r._on_vrpn({
|
||||
"type" : TrackingDataType.POS_QUAT,
|
||||
"tracker" : client.sensors[sender_id],
|
||||
"sensor" : sensor_id,
|
||||
"position" : pos,
|
||||
"rotation" : quat
|
||||
})
|
||||
'vrpn_Tracker Velocity':
|
||||
pass
|
||||
# get id
|
||||
var sensor_id = body.get_32()
|
||||
var padding = body.get_32() # padding
|
||||
# position
|
||||
var vel = 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 vel_rot = Quaternion(body.get_double(),body.get_double(),body.get_double(),body.get_double()).normalized()
|
||||
# submit to listener
|
||||
for r in client.tracker:
|
||||
r._on_vrpn({
|
||||
"type" : TrackingDataType.VELOCITY,
|
||||
"tracker" : client.sensors[sender_id],
|
||||
"sensor" : sensor_id,
|
||||
"velocity_linear" : vel,
|
||||
"velocity_rotation" : vel_rot
|
||||
})
|
||||
'vrpn_Tracker Acceleration':
|
||||
# get id
|
||||
var sensor_id = body.get_32()
|
||||
var padding = body.get_32() # padding
|
||||
# position
|
||||
var acc = 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 acc_rot = Quaternion(body.get_double(),body.get_double(),body.get_double(),body.get_double()).normalized()
|
||||
|
||||
var acc_dt = body.get_double() # padding
|
||||
# submit to listener
|
||||
for r in client.tracker:
|
||||
r._on_vrpn({
|
||||
"type" : TrackingDataType.ACCELERATION,
|
||||
"tracker" : client.sensors[sender_id],
|
||||
"sensor" : sensor_id,
|
||||
"acceleration_linear" : acc,
|
||||
"acceleration_rotation" : acc_rot,
|
||||
"acceleration_dt" : acc_dt
|
||||
})
|
||||
'vrpn_Button Change':
|
||||
var num_buttons : int = body.get_32()
|
||||
var button_changes : Dictionary = {}
|
||||
for i in range(num_buttons):
|
||||
button_changes[i] = body.get_32()
|
||||
for button in client.buttons:
|
||||
button._on_vrpn(
|
||||
{
|
||||
"type" : ButtonDataType.BUTTON_CHANGE,
|
||||
"sensor" : client.sensors[sender_id],
|
||||
"changes" : button_changes
|
||||
}
|
||||
)
|
||||
'vrpn_Button States':
|
||||
var num_buttons : int = body.get_32()
|
||||
for i in range(num_buttons):
|
||||
var button_state = body.get_32()
|
||||
#print("button state {0} : {1}".format([i,button_state]))
|
||||
|
||||
_:
|
||||
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)
|
|
@ -1,18 +0,0 @@
|
|||
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)
|
32
addons/vrpn/scripts/VRPN_Tracker.gd
Normal file
32
addons/vrpn/scripts/VRPN_Tracker.gd
Normal file
|
@ -0,0 +1,32 @@
|
|||
class_name VRPN_Tracker
|
||||
extends Node3D
|
||||
|
||||
@export var vrpn_client : VRPN_Client = null
|
||||
@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
|
||||
@export var register_on_ready : bool = true
|
||||
|
||||
func _ready() -> void:
|
||||
if vrpn_client and register_on_ready:
|
||||
vrpn_client.tracker.append(self)
|
||||
|
||||
func _on_vrpn(vrpn_data : Dictionary):
|
||||
match vrpn_data['type'] as VRPN_Client.TrackingDataType:
|
||||
VRPN_Client.TrackingDataType.POS_QUAT:
|
||||
if vrpn_data['tracker'] == tracker_name and vrpn_data['sensor'] == tracker_sensor:
|
||||
if tracker_use_position:
|
||||
self.global_position = vrpn_data['position']
|
||||
if tracker_use_rotation:
|
||||
var rotation := vrpn_data['rotation'] as Quaternion
|
||||
self.global_basis = Basis(rotation)
|
||||
VRPN_Client.TrackingDataType.ACCELERATION:
|
||||
pass
|
||||
VRPN_Client.TrackingDataType.VELOCITY:
|
||||
pass
|
||||
_:
|
||||
push_warning("unknown tracker datatype")
|
||||
|
||||
func _on_vrpn_connected(s):
|
||||
pass # Replace with function body.
|
|
@ -10,7 +10,11 @@ config_version=5
|
|||
|
||||
[application]
|
||||
|
||||
config/name="uvrpn"
|
||||
config/name="gdvrpn"
|
||||
run/main_scene="uid://bj5ykdjle10tt"
|
||||
config/features=PackedStringArray("4.4", "Forward Plus")
|
||||
config/icon="uid://cdprcmtx102rp"
|
||||
|
||||
[rendering]
|
||||
|
||||
anti_aliasing/quality/screen_space_aa=1
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue