Compare commits

...

6 commits

Author SHA1 Message Date
c83135a67b Track connection to manager and receive MANUAL_CONTROL 2025-02-18 12:47:35 +01:00
attila-vr
4ddccdaf62 Redo main scene
Much simpler XR setup based on Bastiaan Olij's Dev stream 58
Correctly applied bone deformation in Mi-2 model
2025-02-18 11:34:59 +01:00
9ebb025f8a Add cone model 2025-02-13 09:48:52 +01:00
b868c2bdbb Cleanup MarshConnector
Receive ATTITUDE and LOCAL_POSITION_NED
Remove unnecessary prints
2025-02-12 15:57:45 +01:00
046388a3eb Start project in Godot 4.4 beta3 2025-02-12 15:46:23 +01:00
8787bdd8f6 Add initial Mi-2 3D model 2025-02-11 15:22:30 +01:00
33 changed files with 564 additions and 123 deletions

4
.gitattributes vendored
View file

@ -1,2 +1,6 @@
# Normalize EOL for all files that Git considers text files. # Normalize EOL for all files that Git considers text files.
* text=auto eol=lf * text=auto eol=lf
*.glb filter=lfs diff=lfs merge=lfs -text
*.fbx filter=lfs diff=lfs merge=lfs -text
*.webp filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text

1
.godot-version Normal file
View file

@ -0,0 +1 @@
4.4-beta3

View file

@ -12,7 +12,7 @@ git clone --recurse-submodules <address of this repository>
git submodule update --init --recursive git submodule update --init --recursive
``` ```
Using Godot v4.4.beta1.official [d33da79d3](https://github.com/godotengine/godot/commit/d33da79d3f8fe84be2521d25b9ba8e440cf25a88). Using Godot v4.4 beta3 (expect it to change often until 4.4 is released)
Install SCons with `pipx install scons`. Install SCons with `pipx install scons`.
You will need a C++ compiler, you might have one already on Linux, see below for Windows, otherwise in [Godot documentation](https://docs.godotengine.org/en/stable/contributing/development/compiling/index.html). You will need a C++ compiler, you might have one already on Linux, see below for Windows, otherwise in [Godot documentation](https://docs.godotengine.org/en/stable/contributing/development/compiling/index.html).
@ -29,9 +29,9 @@ Some files are generated, run the following commands on first setup and when dep
```sh ```sh
godot --dump-extension-api # after updating Godot godot --dump-extension-api # after updating Godot
scons compile_commands # after modifying SConstruct
python update_mavlink.py # after updating MAVLink dialect python update_mavlink.py # after updating MAVLink dialect
python update_addons.py # after changing any addon submodules python update_addons.py # after changing any addon submodules
scons compile_commands # after modifying SConstruct
``` ```
### Windows setup ### Windows setup

6
project/CREDITS.txt Normal file
View file

@ -0,0 +1,6 @@
Programming:
Marek S. Łukasiewicz
3D models:
Igor Samek
Marek S. Łukasiewicz

View file

@ -1 +0,0 @@
uid://46i64f2dmonl

View file

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

View file

@ -1,6 +0,0 @@
extends Node3D
@export var connector: MarshConnector
func _process(_delta: float) -> void:
transform = connector.get_aircraft()

View file

@ -0,0 +1,19 @@
extends Node3D
@onready var connector = $MarshConnector
@onready var skeleton = $"Mi-2/Armature/Skeleton3D"
@onready var attitude_root = $AttitudeRoot
@onready var bone_cg: int = skeleton.find_bone("BodyCG")
func _process(_delta: float) -> void:
var target: Transform3D = connector.get_aircraft()
position = target.origin
# Add the rotation to the bone
var rest = skeleton.get_bone_rest(bone_cg).basis.get_rotation_quaternion()
var attitude = target.basis.get_rotation_quaternion()
skeleton.set_bone_pose_rotation(bone_cg, attitude * rest)
# Rotate other children (not using BoneAttachment3D due to jitter)
attitude_root.rotation = target.basis.get_euler()

View file

@ -0,0 +1,26 @@
[gd_scene load_steps=4 format=3 uid="uid://bj1s0g7ixjw71"]
[ext_resource type="Script" uid="uid://cx30pr7kn4c74" path="res://aircraft/aircraft.gd" id="1_l4uib"]
[ext_resource type="PackedScene" uid="uid://cux4tju0ovvly" path="res://assets/mi2/Mi-2.glb" id="1_mrxe8"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_mrxe8"]
[node name="Aircraft" type="Node3D"]
script = ExtResource("1_l4uib")
[node name="Mi-2" parent="." instance=ExtResource("1_mrxe8")]
[node name="Łopata" parent="Mi-2/Armature/Skeleton3D" index="16"]
material_override = SubResource("StandardMaterial3D_mrxe8")
[node name="AttitudeRoot" type="Node3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.4, 0)
[node name="XROrigin3D" type="XROrigin3D" parent="AttitudeRoot"]
transform = Transform3D(-1, 0, -8.74227e-08, 0, 0.999999, 0, 8.74228e-08, 0, -0.999999, 0.214, -0.721897, 1.53478)
[node name="XRCamera3D" type="XRCamera3D" parent="AttitudeRoot/XROrigin3D"]
[node name="MarshConnector" type="MarshConnector" parent="."]
[editable path="Mi-2"]

BIN
project/assets/cone/Cone80cm.fbx (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,38 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://b6gpwum12u52w"
path="res://.godot/imported/Cone80cm.fbx-d949dbbc55b52604489414c5f9df328e.scn"
[deps]
source_file="res://assets/cone/Cone80cm.fbx"
dest_files=["res://.godot/imported/Cone80cm.fbx-d949dbbc55b52604489414c5f9df328e.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=true
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1

BIN
project/assets/cone/Cone_DefaultMaterial_BaseColor.webp (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://8d1ovbs1uls5"
path="res://.godot/imported/Cone_DefaultMaterial_BaseColor.webp-bad00f3f7b1863582144eaca57549c7d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/cone/Cone_DefaultMaterial_BaseColor.webp"
dest_files=["res://.godot/imported/Cone_DefaultMaterial_BaseColor.webp-bad00f3f7b1863582144eaca57549c7d.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

BIN
project/assets/cone/Cone_DefaultMaterial_Normal.webp (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cq5sci0t8q0hy"
path="res://.godot/imported/Cone_DefaultMaterial_Normal.webp-6a67b945c81f799997551ce26f4f31c8.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/cone/Cone_DefaultMaterial_Normal.webp"
dest_files=["res://.godot/imported/Cone_DefaultMaterial_Normal.webp-6a67b945c81f799997551ce26f4f31c8.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

Binary file not shown.

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://0x0t6mks64wj"
path="res://.godot/imported/Cone_DefaultMaterial_OcclusionRoughnessMetallic.webp-1a5b8b1d7a0eb8a9d7d6e168dd7479db.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/cone/Cone_DefaultMaterial_OcclusionRoughnessMetallic.webp"
dest_files=["res://.godot/imported/Cone_DefaultMaterial_OcclusionRoughnessMetallic.webp-1a5b8b1d7a0eb8a9d7d6e168dd7479db.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

View file

@ -0,0 +1,9 @@
[gd_scene load_steps=3 format=3 uid="uid://7tddcx4u26w4"]
[ext_resource type="PackedScene" uid="uid://b6gpwum12u52w" path="res://assets/cone/Cone80cm.fbx" id="1_caoip"]
[ext_resource type="Material" uid="uid://8my138hapqp8" path="res://assets/cone/cone_material.tres" id="2_pk7h2"]
[node name="Cone80cm" instance=ExtResource("1_caoip")]
[node name="Cylinder" parent="." index="0"]
surface_material_override/0 = ExtResource("2_pk7h2")

View file

@ -0,0 +1,28 @@
[gd_resource type="StandardMaterial3D" load_steps=6 format=3 uid="uid://8my138hapqp8"]
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_2wwbo"]
load_path = "res://.godot/imported/Cone_DefaultMaterial_BaseColor.webp-bad00f3f7b1863582144eaca57549c7d.ctex"
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_86lm8"]
load_path = "res://.godot/imported/Cone_DefaultMaterial_OcclusionRoughnessMetallic.webp-1a5b8b1d7a0eb8a9d7d6e168dd7479db.ctex"
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_c2qq7"]
load_path = "res://.godot/imported/Cone_DefaultMaterial_OcclusionRoughnessMetallic.webp-1a5b8b1d7a0eb8a9d7d6e168dd7479db.ctex"
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_f28vr"]
load_path = "res://.godot/imported/Cone_DefaultMaterial_Normal.webp-6a67b945c81f799997551ce26f4f31c8.ctex"
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_tpcsw"]
load_path = "res://.godot/imported/Cone_DefaultMaterial_OcclusionRoughnessMetallic.webp-1a5b8b1d7a0eb8a9d7d6e168dd7479db.ctex"
[resource]
albedo_texture = SubResource("CompressedTexture2D_2wwbo")
metallic = 1.0
metallic_texture = SubResource("CompressedTexture2D_c2qq7")
metallic_texture_channel = 2
roughness_texture = SubResource("CompressedTexture2D_tpcsw")
roughness_texture_channel = 1
normal_enabled = true
normal_texture = SubResource("CompressedTexture2D_f28vr")
ao_enabled = true
ao_texture = SubResource("CompressedTexture2D_86lm8")

BIN
project/assets/mi2/Mi-2.glb (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,37 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://cux4tju0ovvly"
path="res://.godot/imported/Mi-2.glb-12197697bde557481ecf457851b9e618.scn"
[deps]
source_file="res://assets/mi2/Mi-2.glb"
dest_files=["res://.godot/imported/Mi-2.glb-12197697bde557481ecf457851b9e618.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={}
gltf/naming_version=1
gltf/embedded_image_handling=1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 130 B

Before After
Before After

View file

@ -1,42 +0,0 @@
[gd_scene load_steps=8 format=3 uid="uid://cx82op4eecbwr"]
[ext_resource type="Script" uid="uid://cx30pr7kn4c74" path="res://aircraft.gd" id="1_hips3"]
[ext_resource type="PackedScene" uid="uid://qbmx03iibuuu" path="res://addons/godot-xr-tools/staging/scene_base.tscn" id="1_k0lu6"]
[sub_resource type="BoxMesh" id="BoxMesh_2w36v"]
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_4tcqf"]
[sub_resource type="Sky" id="Sky_0xm2m"]
sky_material = SubResource("ProceduralSkyMaterial_4tcqf")
[sub_resource type="Environment" id="Environment_jdgxj"]
background_mode = 2
sky = SubResource("Sky_0xm2m")
[sub_resource type="CameraAttributesPractical" id="CameraAttributesPractical_fpvso"]
[node name="SceneBase" instance=ExtResource("1_k0lu6")]
[node name="XROrigin3D" parent="." index="0"]
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 0, -3)
[node name="MarshConnector" type="MarshConnector" parent="." index="1"]
process_mode = 0
[node name="Aircraft" type="Node3D" parent="." index="2" node_paths=PackedStringArray("connector")]
script = ExtResource("1_hips3")
connector = NodePath("../MarshConnector")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Aircraft" index="0"]
mesh = SubResource("BoxMesh_2w36v")
[node name="Camera3D" type="Camera3D" parent="." index="3"]
transform = Transform3D(-1, -2.26267e-08, 8.44439e-08, 0, 0.965926, 0.258819, -8.74228e-08, 0.258819, -0.965926, 0, 1, -3)
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." index="4"]
transform = Transform3D(-0.707107, -0.683013, 0.183013, 0, 0.258819, 0.965926, -0.707107, 0.683013, -0.183013, 3, 3, 3)
[node name="WorldEnvironment" type="WorldEnvironment" parent="." index="5"]
environment = SubResource("Environment_jdgxj")
camera_attributes = SubResource("CameraAttributesPractical_fpvso")

View file

@ -1,38 +1,27 @@
[gd_scene load_steps=7 format=3 uid="uid://cjrkxv8ix1h8s"] [gd_scene load_steps=6 format=3 uid="uid://crq3o0eu4y8ya"]
[ext_resource type="Script" uid="uid://cx30pr7kn4c74" path="res://aircraft.gd" id="1_ig7tw"] [ext_resource type="Script" path="res://start_vr.gd" id="1_ig7tw"]
[ext_resource type="PackedScene" uid="uid://bj1s0g7ixjw71" path="res://aircraft/aircraft.tscn" id="2_0xm2m"]
[sub_resource type="BoxMesh" id="BoxMesh_0xm2m"] [sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_7dm0k"]
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_ig7tw"] [sub_resource type="Sky" id="Sky_ig7tw"]
sky_material = SubResource("ProceduralSkyMaterial_7dm0k")
[sub_resource type="Sky" id="Sky_0xm2m"] [sub_resource type="Environment" id="Environment_0xm2m"]
sky_material = SubResource("ProceduralSkyMaterial_ig7tw")
[sub_resource type="Environment" id="Environment_ig7tw"]
background_mode = 2 background_mode = 2
sky = SubResource("Sky_0xm2m") sky = SubResource("Sky_ig7tw")
[sub_resource type="CameraAttributesPractical" id="CameraAttributesPractical_0xm2m"]
[node name="Main" type="Node3D"] [node name="Main" type="Node3D"]
[node name="MarshConnector" type="MarshConnector" parent="."] [node name="WorldEnvironment" type="WorldEnvironment" parent="."]
process_mode = 0 environment = SubResource("Environment_0xm2m")
[node name="Aircraft" type="Node3D" parent="." node_paths=PackedStringArray("connector")]
script = ExtResource("1_ig7tw")
connector = NodePath("../MarshConnector")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Aircraft"]
mesh = SubResource("BoxMesh_0xm2m")
[node name="Camera3D" type="Camera3D" parent="."]
transform = Transform3D(-1, -2.26267e-08, 8.44439e-08, 0, 0.965926, 0.258819, -8.74228e-08, 0.258819, -0.965926, 0, 1, -3)
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] [node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(-0.707107, -0.683013, 0.183013, 0, 0.258819, 0.965926, -0.707107, 0.683013, -0.183013, 3, 3, 3) transform = Transform3D(0.707107, 0.683013, -0.183013, 0, 0.258819, 0.965926, 0.707107, -0.683013, 0.183013, -3, 3, -3)
[node name="WorldEnvironment" type="WorldEnvironment" parent="."] [node name="StartVR" type="Node3D" parent="."]
environment = SubResource("Environment_ig7tw") script = ExtResource("1_ig7tw")
camera_attributes = SubResource("CameraAttributesPractical_0xm2m") maximum_refresh_rate = 144
[node name="Aircraft" parent="." instance=ExtResource("2_0xm2m")]

View file

@ -1,4 +1,4 @@
[gd_resource type="OpenXRActionMap" load_steps=417 format=3 uid="uid://c52lwapbt6wt2"] [gd_resource type="OpenXRActionMap" load_steps=417 format=3 uid="uid://5a62ftnefy7o"]
[sub_resource type="OpenXRAction" id="OpenXRAction_6ivru"] [sub_resource type="OpenXRAction" id="OpenXRAction_6ivru"]
resource_name = "trigger" resource_name = "trigger"

View file

@ -11,18 +11,13 @@ config_version=5
[application] [application]
config/name="Visualisation for MARSH" config/name="Visualisation for MARSH"
run/main_scene="uid://ba764fx7hx8ei" run/main_scene="uid://crq3o0eu4y8ya"
config/features=PackedStringArray("4.4", "GL Compatibility") config/features=PackedStringArray("4.4", "GL Compatibility")
config/icon="res://icon.svg" config/icon="res://icon.svg"
[autoload]
XRToolsUserSettings="*res://addons/godot-xr-tools/user_settings/user_settings.gd"
XRToolsRumbleManager="*res://addons/godot-xr-tools/rumble/rumble_manager.gd"
[editor_plugins] [editor_plugins]
enabled=PackedStringArray("res://addons/godot-xr-tools/plugin.cfg") enabled=PackedStringArray()
[physics] [physics]
@ -36,4 +31,6 @@ renderer/rendering_method.mobile="gl_compatibility"
[xr] [xr]
openxr/enabled=true openxr/enabled=true
openxr/reference_space=0
openxr/foveation_level=3
shaders/enabled=true shaders/enabled=true

View file

@ -1,11 +0,0 @@
[gd_scene load_steps=3 format=3 uid="uid://ba764fx7hx8ei"]
[ext_resource type="PackedScene" uid="uid://bnqnnnet4dw12" path="res://addons/godot-xr-tools/staging/staging.tscn" id="1_hips3"]
[ext_resource type="Texture2D" uid="uid://ftrrxm7sxndi" path="res://assets/splash/splash.png" id="2_k0lu6"]
[node name="Staging" instance=ExtResource("1_hips3")]
main_scene = "uid://cx82op4eecbwr"
prompt_for_continue = false
[node name="LoadingScreen" parent="." index="2"]
splash_screen = ExtResource("2_k0lu6")

112
project/start_vr.gd Normal file
View file

@ -0,0 +1,112 @@
extends Node3D
signal focus_lost
signal focus_gained
signal pose_recentered
@export var maximum_refresh_rate : int = 90
var xr_interface : OpenXRInterface
var xr_is_focused := false
func _ready() -> void:
xr_interface = XRServer.find_interface("OpenXR")
if xr_interface and xr_interface.is_initialized():
print("OpenXR instantiated successfully.")
var vp : Viewport = get_viewport()
# Enable XR on our viewport.
vp.use_xr = true
# Make sure V-Sync is off, as V-Sync is handled by OpenXR.
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
# Enable variable rate shading.
if RenderingServer.get_rendering_device():
vp.vrs_mode = Viewport.VRS_XR
elif int(ProjectSettings.get_setting("xr/openxr/foveation_level")) == 0:
push_warning("OpenXR: Recommend setting Foveation level to High in Project Settings")
# Connect the OpenXR events.
xr_interface.session_begun.connect(_on_openxr_session_begun)
xr_interface.session_visible.connect(_on_openxr_visible_state)
xr_interface.session_focussed.connect(_on_openxr_focused_state)
xr_interface.session_stopping.connect(_on_openxr_stopping)
xr_interface.pose_recentered.connect(_on_openxr_pose_recentered)
else:
# We couldn't start OpenXR.
print("OpenXR not instantiated!")
get_tree().quit()
# Handle OpenXR session ready.
func _on_openxr_session_begun() -> void:
# Get the reported refresh rate.
var current_refresh_rate := xr_interface.get_display_refresh_rate()
if current_refresh_rate > 0:
print("OpenXR: Refresh rate reported as ", str(current_refresh_rate))
else:
print("OpenXR: No refresh rate given by XR runtime")
# See if we have a better refresh rate available.
var new_rate := current_refresh_rate
var available_rates: Array = xr_interface.get_available_display_refresh_rates()
if available_rates.is_empty():
print("OpenXR: Target does not support refresh rate extension")
elif available_rates.size() == 1:
# Only one available, so use it.
new_rate = available_rates[0]
else:
for rate in available_rates:
if rate > new_rate and rate <= maximum_refresh_rate:
new_rate = rate
# Did we find a better rate?
if current_refresh_rate != new_rate:
print("OpenXR: Setting refresh rate to ", str(new_rate))
xr_interface.set_display_refresh_rate(new_rate)
current_refresh_rate = new_rate
# Now match our physics rate. This is currently needed to avoid jittering,
# due to physics interpolation not being used.
Engine.physics_ticks_per_second = roundi(current_refresh_rate)
# Handle OpenXR visible state.
func _on_openxr_visible_state() -> void:
# We always pass this state at startup,
# but the second time we get this, it means our player took off their headset.
if xr_is_focused:
print("OpenXR lost focus")
xr_is_focused = false
# Pause our game.
process_mode = Node.PROCESS_MODE_DISABLED
focus_lost.emit()
# Handle OpenXR focused state
func _on_openxr_focused_state() -> void:
print("OpenXR gained focus")
xr_is_focused = true
# Unpause our game.
process_mode = Node.PROCESS_MODE_INHERIT
focus_gained.emit()
# Handle OpenXR stopping state.
func _on_openxr_stopping() -> void:
# Our session is being stopped.
print("OpenXR is stopping")
# Handle OpenXR pose recentered signal.
func _on_openxr_pose_recentered() -> void:
# User recentered view, we have to react to this by recentering the view.
# This is game implementation dependent.
pose_recentered.emit()

1
project/start_vr.gd.uid Normal file
View file

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

View file

@ -2,6 +2,7 @@
#include "godot_cpp/classes/engine.hpp" #include "godot_cpp/classes/engine.hpp"
#include "godot_cpp/classes/font_file.hpp" #include "godot_cpp/classes/font_file.hpp"
#include "godot_cpp/classes/global_constants.hpp"
#include "godot_cpp/classes/packet_peer_udp.hpp" #include "godot_cpp/classes/packet_peer_udp.hpp"
#include "godot_cpp/core/math.hpp" #include "godot_cpp/core/math.hpp"
#include "godot_cpp/core/memory.hpp" #include "godot_cpp/core/memory.hpp"
@ -12,18 +13,29 @@
#include "godot_cpp/variant/transform3d.hpp" #include "godot_cpp/variant/transform3d.hpp"
#include "godot_cpp/variant/vector3.hpp" #include "godot_cpp/variant/vector3.hpp"
#include "mavlink/all/mavlink.h" // IWYU pragma: keep; always include the mavlink.h file for selected dialect #include "mavlink/all/mavlink.h" // IWYU pragma: keep; always include the mavlink.h file for selected dialect
#include "mavlink/common/mavlink_msg_command_long.h"
#include "mavlink/common/mavlink_msg_manual_control.h"
#include "mavlink/common/mavlink_msg_sim_state.h" #include "mavlink/common/mavlink_msg_sim_state.h"
#include "mavlink/mavlink_helpers.h" #include "mavlink/mavlink_helpers.h"
#include "mavlink/mavlink_types.h"
// How much extra bytes are needed on top of message payload length
#define MAVLINK_OVERHEAD 12
using namespace godot; using namespace godot;
void MarshConnector::_bind_methods() { void MarshConnector::_bind_methods() {
// Data access
ClassDB::bind_method(D_METHOD("get_aircraft"), &MarshConnector::get_aircraft);
ClassDB::bind_method(D_METHOD("get_cyclic"), &MarshConnector::get_cyclic);
ClassDB::bind_method(D_METHOD("get_collective"),
&MarshConnector::get_collective);
ClassDB::bind_method(D_METHOD("get_pedals"), &MarshConnector::get_pedals);
// Timer callbacks
ClassDB::bind_method(D_METHOD("send_heartbeat"), ClassDB::bind_method(D_METHOD("send_heartbeat"),
&MarshConnector::send_heartbeat); &MarshConnector::send_heartbeat);
ClassDB::bind_method(D_METHOD("get_aircraft"), &MarshConnector::get_aircraft); ClassDB::bind_method(D_METHOD("manager_timeout"),
&MarshConnector::manager_timeout);
// ClassDB::bind_method(D_METHOD("model_timeout"),
// &MarshConnector::model_timeout);
} }
MarshConnector::MarshConnector() { MarshConnector::MarshConnector() {
@ -39,10 +51,14 @@ MarshConnector::MarshConnector() {
socket = memnew(PacketPeerUDP); socket = memnew(PacketPeerUDP);
if (Engine::get_singleton()->is_editor_hint()) { manager_connected = false;
// Don't run _process() in the editor
set_process_mode(Node::ProcessMode::PROCESS_MODE_DISABLED); manager_timer = memnew(Timer);
} add_child(manager_timer);
manager_timer->set_wait_time(5.0);
manager_timer->set_one_shot(true);
manager_timer->set_autostart(false);
manager_timer->connect("timeout", Callable(this, "manager_timeout"));
} }
MarshConnector::~MarshConnector() { MarshConnector::~MarshConnector() {
@ -65,7 +81,7 @@ void MarshConnector::_process(double delta) {
} }
Error MarshConnector::send_heartbeat() { Error MarshConnector::send_heartbeat() {
print_line("Sending HEARTBEAT at ", time_passed, " seconds"); // print_line("Sending HEARTBEAT at ", time_passed, " seconds");
mavlink_heartbeat_t heartbeat; mavlink_heartbeat_t heartbeat;
heartbeat.type = MAV_TYPE_HELICOPTER; heartbeat.type = MAV_TYPE_HELICOPTER;
@ -77,17 +93,19 @@ Error MarshConnector::send_heartbeat() {
mavlink_message_t message; mavlink_message_t message;
mavlink_msg_heartbeat_encode_chan(1, MARSH_COMP_ID_VISUALISATION, mavlink_msg_heartbeat_encode_chan(1, MARSH_COMP_ID_VISUALISATION,
MAVLINK_COMM_0, &message, &heartbeat); MAVLINK_COMM_0, &message, &heartbeat);
uint8_t send_buffer[MAVLINK_OVERHEAD + MAVLINK_MSG_ID_HEARTBEAT_LEN]; return send_message(message);
}
Error MarshConnector::send_message(mavlink_message_t message) {
uint8_t send_buffer[MAVLINK_MAX_PACKET_LEN];
mavlink_msg_to_send_buffer(send_buffer, &message); mavlink_msg_to_send_buffer(send_buffer, &message);
PackedByteArray array; PackedByteArray array;
for (int i = 0; i < sizeof(send_buffer); i++) { for (int i = 0; i < sizeof(send_buffer); i++) {
array.append(send_buffer[i]); array.append(send_buffer[i]);
} }
print_line("Data array ", array);
const auto result = socket->put_packet(array); const auto result = socket->put_packet(array);
print_line("Send result ", result);
return result; return result;
} }
@ -114,6 +132,12 @@ void MarshConnector::receive_data(const PackedByteArray &data) {
case MAVLINK_MSG_ID_PARAM_SET: case MAVLINK_MSG_ID_PARAM_SET:
handle_param(message); handle_param(message);
break; break;
case MAVLINK_MSG_ID_MANUAL_CONTROL:
handle_manual_control(message);
break;
case MAVLINK_MSG_ID_HEARTBEAT:
handle_heartbeat(message);
break;
default: default:
break; break;
} }
@ -135,6 +159,13 @@ Transform3D MarshConnector::get_aircraft() {
return Transform3D{Basis{last_rotation}, last_location}; return Transform3D{Basis{last_rotation}, last_location};
} }
Vector2 MarshConnector::get_cyclic() {
return Vector2{last_controls.x, last_controls.y};
}
float MarshConnector::get_collective() { return last_controls.w; }
float MarshConnector::get_pedals() { return last_controls.z; }
Vector2 MarshConnector::local_meters_from_global_degrees(double latitude, Vector2 MarshConnector::local_meters_from_global_degrees(double latitude,
double longitude) { double longitude) {
@ -195,13 +226,86 @@ Quaternion MarshConnector::godot_rotation_from_mavlink(float roll_rad,
} }
void MarshConnector::handle_local_position(mavlink_message_t message) { void MarshConnector::handle_local_position(mavlink_message_t message) {
print_line("Implement handle_local_position"); if (message.msgid != MAVLINK_MSG_ID_LOCAL_POSITION_NED)
return;
mavlink_local_position_ned_t local_pos;
mavlink_msg_local_position_ned_decode(&message, &local_pos);
last_location =
godot_location_from_mavlink(local_pos.x, local_pos.y, -local_pos.z);
} }
void MarshConnector::handle_attitude(mavlink_message_t message) { void MarshConnector::handle_attitude(mavlink_message_t message) {
print_line("Implement handle_attitude"); if (message.msgid != MAVLINK_MSG_ID_ATTITUDE)
return;
mavlink_attitude_t attitude;
mavlink_msg_attitude_decode(&message, &attitude);
last_rotation =
godot_rotation_from_mavlink(attitude.roll, attitude.pitch, attitude.yaw);
} }
void MarshConnector::handle_param(mavlink_message_t message) { void MarshConnector::handle_param(mavlink_message_t message) {
print_line("Implement handle_param"); print_line("Implement handle_param");
} }
void MarshConnector::handle_manual_control(mavlink_message_t message) {
if (message.msgid != MAVLINK_MSG_ID_MANUAL_CONTROL)
return;
mavlink_manual_control_t manual_control;
mavlink_msg_manual_control_decode(&message, &manual_control);
const int16_t invalid = 0x7FFF;
if (manual_control.y != invalid)
last_controls.x = manual_control.y / 1000.0f;
if (manual_control.x != invalid)
last_controls.y = manual_control.x / -1000.0f;
if (manual_control.r != invalid)
last_controls.z = manual_control.r / 1000.0f;
if (manual_control.z != invalid)
last_controls.w = manual_control.z / 1000.0f;
}
void MarshConnector::handle_heartbeat(mavlink_message_t message) {
if (message.msgid != MAVLINK_MSG_ID_HEARTBEAT)
return;
// mavlink_heartbeat_t heartbeat;
// mavlink_msg_heartbeat_decode(&message, &heartbeat);
if (message.compid == MARSH_COMP_ID_MANAGER) {
if (!manager_connected) {
print_line("Connected to MARSH Manager");
// subscribe to messages not sent to visualisation node by default
mavlink_command_long_t command;
command.target_system = 1;
command.target_component = MARSH_COMP_ID_MANAGER;
command.command = MAV_CMD_SET_MESSAGE_INTERVAL;
command.confirmation = 0;
command.param1 = static_cast<float>(MAVLINK_MSG_ID_MANUAL_CONTROL);
command.param2 = 0; // Default rate
command.param3 = 0; // Not used
command.param4 = 0;
command.param5 = 0;
command.param6 = 0;
command.param7 = 1; // Address of requestor
mavlink_message_t message_sent;
mavlink_msg_command_long_encode_chan(1, MARSH_COMP_ID_VISUALISATION,
MAVLINK_COMM_0, &message_sent,
&command);
Error result = send_message(message_sent);
if (result != OK) {
print_line("Subscribe send result ", result);
}
}
manager_connected = true;
manager_timer->start();
}
}
void MarshConnector::manager_timeout() {
print_line("Lost connection to MARSH Manager");
manager_connected = false;
}

View file

@ -33,8 +33,25 @@ public:
Error set_parameter(const String &id, float value); Error set_parameter(const String &id, float value);
// Get current parameter value // Get current parameter value
float get_parameter(const String &id); float get_parameter(const String &id);
// Get current state of the aircraft // Get current state of the aircraft
Transform3D get_aircraft(); Transform3D get_aircraft();
// Get normalized (-1 to 1) cyclic position, where X is right, Y is pitch up
Vector2 get_cyclic();
// Get normalized (0 to 1) collective position, positive to climb
float get_collective();
// Get normalized (-1 to 1) pedals position, positive turn right
float get_pedals();
// Is there a connection to MARSH Manager
bool get_manager_connected();
// Is receiving data from flight model
bool get_model_connected();
// Called by timer
void manager_timeout();
// // Called by timer
// void model_timeout();
// Convert from global coordinates to local position x=north, y=east // Convert from global coordinates to local position x=north, y=east
// taking into account current configuration. // taking into account current configuration.
@ -55,6 +72,10 @@ private:
void handle_local_position(mavlink_message_t message); void handle_local_position(mavlink_message_t message);
void handle_attitude(mavlink_message_t message); void handle_attitude(mavlink_message_t message);
void handle_param(mavlink_message_t message); void handle_param(mavlink_message_t message);
void handle_manual_control(mavlink_message_t message);
void handle_heartbeat(mavlink_message_t message);
Error send_message(mavlink_message_t message);
// Do not specify specific values, to ensure PARAM_COUNT is correct // Do not specify specific values, to ensure PARAM_COUNT is correct
// Not an enum class on purpose, to make use more convenient, it's scoped // Not an enum class on purpose, to make use more convenient, it's scoped
@ -78,10 +99,14 @@ private:
// TODO: Interpolate with some delay // TODO: Interpolate with some delay
Vector3 last_location; Vector3 last_location;
Quaternion last_rotation; Quaternion last_rotation;
Vector4 last_controls;
double time_passed; double time_passed;
Timer *heartbeat_timer; Timer *heartbeat_timer;
PacketPeerUDP *socket; PacketPeerUDP *socket;
bool manager_connected;
Timer *manager_timer;
}; };
} // namespace godot } // namespace godot

View file

@ -1,11 +0,0 @@
from os import path
from shutil import copytree
# add repository root path
root_path = path.abspath(path.dirname(__file__))
copytree(
path.join(root_path, 'modules', 'godot-xr-tools', 'addons'),
path.join(root_path, 'project', 'addons'),
dirs_exist_ok=True,
)