From 53b65271522dbf22fcd724706072eb4d2aff0fa0 Mon Sep 17 00:00:00 2001 From: Leon van Kammen Date: Tue, 14 May 2024 17:14:01 +0200 Subject: [PATCH] feat/godot: work in progress [might break] --- example/godot/main.gd | 47 +++++++++++++++-------- example/godot/main.tscn | 7 ++++ example/godot/xrfragment.gd | 75 ++++++++++++++++++++++++++----------- 3 files changed, 92 insertions(+), 37 deletions(-) diff --git a/example/godot/main.gd b/example/godot/main.gd index 70793e8..78c6a88 100644 --- a/example/godot/main.gd +++ b/example/godot/main.gd @@ -16,31 +16,46 @@ func _ready(): func _onXRF(event:String,data ): if event == "scene_loaded": scene = data - if event == 'href': + if event == 'href': # optional: hook into href events print(data) + if event == 'src': # optional: hook into src metadata + print(data) if event == 'teleport': print("teleport!") + #player.teleport( data ) # optional: use PlayerBody find_child("XROrigin3D").position = data.origin - #player.teleport( data ) + + # update URL bar + # spec 4 @ https://xrfragment.org/doc/RFC_XR_Fragments.html#navigating-content-href-portals + var URLbar:RichTextLabel = find_child("URLbar") + URLbar.text = xrf.URI.string func _input(event): var cam = find_child("XRCamera3D") if event is InputEventMouseMotion: - var mouse_sens = 0.2 + var mouse_sens = 0.3 cam.rotate_y(deg_to_rad(-event.relative.x*mouse_sens)) - if event is InputEventMouseButton: - var mouse_pos = get_viewport().get_mouse_position() - var from = cam.project_ray_origin(mouse_pos) - var to = from + cam.project_ray_normal(mouse_pos) * 20000 #200 - var space_state = get_world_3d().direct_space_state - var handle_query = PhysicsRayQueryParameters3D.create(from, to) - handle_query.collide_with_areas = true - var mesh_query = PhysicsRayQueryParameters3D.create(from, to) - mesh_query.collide_with_areas = true - var intersectMesh = space_state.intersect_ray(mesh_query) - var intersectHandle = space_state.intersect_ray(handle_query) - if intersectMesh.has('collider'): - xrf.traverse( intersectMesh.collider, xrf.href_click ) + if event is InputEventMouseButton and event.pressed: + if event.button_index == 2: + xrf.back() + + if event.button_index == 3: + xrf.forward() + + if event.button_index == 1: + # raycast to detect clicked item + var mouse_pos = get_viewport().get_mouse_position() + var from = cam.project_ray_origin(mouse_pos) + var to = from + cam.project_ray_normal(mouse_pos) * 20000 #200 + var space_state = get_world_3d().direct_space_state + var handle_query = PhysicsRayQueryParameters3D.create(from, to) + handle_query.collide_with_areas = true + var mesh_query = PhysicsRayQueryParameters3D.create(from, to) + mesh_query.collide_with_areas = true + var intersectMesh = space_state.intersect_ray(mesh_query) + var intersectHandle = space_state.intersect_ray(handle_query) + if intersectMesh.has('collider'): + xrf.traverse( intersectMesh.collider, xrf.href_click ) diff --git a/example/godot/main.tscn b/example/godot/main.tscn index 513160b..93de079 100644 --- a/example/godot/main.tscn +++ b/example/godot/main.tscn @@ -20,6 +20,13 @@ shadow_enabled = true [node name="XRCamera3D" type="XRCamera3D" parent="XROrigin3D"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.7, 0) +[node name="URLbar" type="RichTextLabel" parent="XROrigin3D/XRCamera3D"] +offset_left = 20.0 +offset_top = 20.0 +offset_right = 1171.0 +offset_bottom = 136.0 +scale = Vector2(1.2, 1.2) + [node name="LeftHand" type="XRController3D" parent="XROrigin3D"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 0, 0) tracker = &"left_hand" diff --git a/example/godot/xrfragment.gd b/example/godot/xrfragment.gd index d920564..ef53bb0 100644 --- a/example/godot/xrfragment.gd +++ b/example/godot/xrfragment.gd @@ -6,7 +6,9 @@ extends Node class_name XRF var scene: Node3D -var URI: Dictionary +var URI: Dictionary = {} +var history: Array +var animplayer: AnimationPlayer var isModelLoading = false var metadata var callback: Callable; @@ -31,7 +33,7 @@ func _process(delta): #################################################################################################### func parseURL(url: String) -> Dictionary: - var URI = {"string":url} + var URI = {"string":url, "next": null} # Split URL by '://' to get protocol and the rest of the URL var parts = url.split("://") @@ -45,14 +47,18 @@ func parseURL(url: String) -> Dictionary: parts = url.split("/") URI["domain"] = parts[0] if parts.size() > 1: - var path_and_file = parts[1] + parts.remove_at(0) + var path_and_file = "/".join(parts) + path_and_file = path_and_file.split("?")[0] + path_and_file = path_and_file.split("#")[0] var path_and_file_parts = path_and_file.split("/") if path_and_file_parts.size() > 1: - URI["path"] = path_and_file_parts - var file = path_and_file_parts.pop_back() - URI["path"] = path_and_file_parts.join("/") + URI["path"] = "/".join(path_and_file_parts) else: URI["path"] = path_and_file + else: + URI["path"] = self.URI.path # copy from current URI + URI["domain"] = self.URI.domain # Check if there's a query string if url.find("?") != -1: @@ -117,12 +123,25 @@ func guess_type(str: String) -> Dictionary: # Model Related functions #################################################################################################### +func back(): + var prev = self.history.pop_back() + if prev != null: + prev.next = self.URI + self.to( prev.string, callback ) + +func forward(): + if self.URI.next != null: + self.to( self.URI.next.string, callback ) + # Download model by HTTP and run `downloadModelSuccess` if OK func to(url, f:Callable ): print("navigating to "+url) var URI = self.parseURL(url) callback = f + if self.URI.has('domain') && URI.domain == self.URI.domain && URI.path == self.URI.path: + URI.isLocal = true + if !URI.isLocal: var http_request = HTTPRequest.new() add_child(http_request) @@ -130,10 +149,11 @@ func to(url, f:Callable ): var error = http_request.request(url) if error != OK: push_error("An error occurred in the HTTP request.") - + if self.URI: + self.URI.next = null + self.history.push_back(self.URI ) self.URI = URI if URI.isLocal && URI.fragment.has('pos'): - print(URI.fragment.pos) callback.call("teleport", self.posToTransform3D( URI.fragment.pos ) ) @@ -153,18 +173,17 @@ func downloadModelSuccess(result, response_code, headers, body): callback.call("scene_loaded", scene) func loadModelFromBufferByGLTFDocument(body): - print("loadModelFromBuffer") var doc = GLTFDocument.new() var state = GLTFState.new() #state.set_handle_binary_image(GLTFState.HANDLE_BINARY_EMBED_AS_BASISU) # Fixed in new Godot version (4.3 as I see) https://github.com/godotengine/godot/blob/17e7f85c06366b427e5068c5b3e2940e27ff5f1d/scene/resources/portable_compressed_texture.cpp#L116 - var error = doc.append_from_buffer(body, "", state) + var error = doc.append_from_buffer(body, "", state, 8) # 8 = force ENABLE_TANGENTS since it is required for mesh compression since 4.2 if error == OK: scene = doc.generate_scene(state) scene.name = "XRFscene" - _addAnimations(state, scene) metadata = _parseMetadata(state,scene) add_child(scene) print("model added") + _addAnimations(state, scene) else: print("Couldn't load glTF scene (error code: %s). Are you connected to internet?" % error_string(error)) @@ -177,10 +196,19 @@ func _parseXRFMetadata(node:Node): XRF[ i ] = parseURL( extras[i] ) node.set_meta("XRF", XRF) -func _addAnimations( state:GLTFState, scene): - for i in state.get_animation_players_count(0): - print(i) #add_child( state.get_animation_player(i) ) - +func _addAnimations( state:GLTFState, scene:Node): + self.animplayer == null + for i in scene.get_child_count(): + var animplayer : AnimationPlayer = scene.get_child(i) as AnimationPlayer; + if animplayer == null: + continue; + self.animplayer = animplayer + print("playing animations") + print(animplayer.get_animation_library_list()) + var anims = animplayer.get_animation_library_list() + for j in anims: + animplayer.play( j ) + func traverse(node, f:Callable ): for N in node.get_children(): if N.get_child_count() > 0: @@ -249,6 +277,9 @@ func posToTransform3D(v:Dictionary): func setPredefinedSceneView(): var XRF = scene.get_meta("XRF") if XRF && XRF.has("#") && XRF["#"]["fragment"]["pos"]: + self.URI.fragment = XRF["#"]["fragment"] + if !self.URI.string.match("#"): + self.URI.string += XRF["#"]["string"] callback.call("teleport", posToTransform3D(XRF["#"]["fragment"]["pos"]) ) func href_init(node:Node): @@ -275,11 +306,13 @@ func href_click(node:Node): func src_init(node:Node): if node.has_meta("XRF"): var XRF = node.get_meta("XRF") - if (XRF.has('src') || (XRF.has('href') && XRF.has('src'))) && node is MeshInstance3D: + if XRF.has('src'): var mesh = node as MeshInstance3D - var mat = mesh.get_active_material(0) as BaseMaterial3D - mat = mat.duplicate() - mat.transparency = mat.TRANSPARENCY_ALPHA - mat.albedo = Color(1.0,1.0,1.0, 0.3) # 0.5 sets 50% opacity - mesh.set_surface_override_material(0,mat) + if mesh != null: + var mat = mesh.get_active_material(0) as BaseMaterial3D + mat = mat.duplicate() + mat.transparency = mat.TRANSPARENCY_ALPHA + mat.albedo = Color(1.0,1.0,1.0, 0.3) # 0.5 sets 50% opacity + mesh.set_surface_override_material(0,mat) + callback.call("src", {"node":node,"XRF":XRF} )