Open flobotics opened 2 years ago
cc @fire
I don't have a detailed answer, but here's a few minutes paragraph.
The feature is using GLTFDocument and GLTFExtensions to load glb etc. at runtime like from a buffer.
You can do it, but it's not fully optimized for runtime usage.
Will see if I can get some examples, but I'm a bit slow.
ok, i will do some tests
thx, i am new to gdscript.
I use the Fox.glb to have a good base ( https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Fox/glTF-Binary )
supply a glb in a byte buffer var myfox = load("res://Fox.glb") #loading should be a normal filesystem path var myArr = PackedByteArray() ??? how put the myfox in myArr ???
GLTFDocument has an append_from_buffer, which will give you a GLTFState. What is the second parameter "base_path" of append_from_buffer() ??
need to learn more GDScript
i can load the fox with
var gltf := GLTFDocument.new() var gltf_state := GLTFState.new()
var path = "C:/Users/SuperUserName/Documents/godot-workspace/godot-data/Fox.glb"
var snd_file = FileAccess.open(path, FileAccess.READ)
var fileBytes = PackedByteArray()
fileBytes = snd_file.get_buffer(snd_file.get_length())
gltf.append_from_buffer(fileBytes, "base_path?", gltf_state)
var node = gltf.generate_scene(gltf_state)
add_child(node)
I use "base_path?" as parameter and it seemed to work ?
Now testing with multiplayer networking
For a byte buffer setting base path to "" (empty) string is fine. It's a feature to not require access to the filesystem.
Working on the docs https://github.com/godotengine/godot/pull/68933
thanks for the base_path info.
still trying/working on a multiplayer network test for glTF runtime loader cross platform (i am new to godot :) )
hi, i try to spawn the Fox.glb file from a networking server and it should be displayed also on the clients.
I use https://github.com/TheGodojo/Godot-4-Networking-Demonstration-Complete as a simple test project.
In the player_character.gd script i added:
@rpc(any_peer, call_local, reliable, 1)
func spawnGlTF():
# if we are the server (could be multiplayer.is_server() ? )
if multiplayer.get_unique_id() == 1:
print("spawnGlTF")
var gltf := GLTFDocument.new()
var gltf_state := GLTFState.new()
var path = "C:/Users/SuperUserName/Documents/godot-workspace/godot-data/Fox.glb"
if FileAccess.file_exists(path):
print("File to spawn exists")
else:
print("File to spawn does not exist")
return
var snd_file = FileAccess.open(path, FileAccess.READ)
var fileBytes = PackedByteArray()
fileBytes = snd_file.get_buffer(snd_file.get_length())
gltf.append_from_buffer(fileBytes, "base_path?", gltf_state)
var node = gltf.generate_scene(gltf_state)
add_child(node)
else:
print("notSpawnGlTF")
and call it simply when someone clicks on the player_character
func _on_mouse_click_area_input_event(camera, event, position, normal, shape_idx):
if event is InputEventMouseButton:
rpc("clicked_by_player")
rpc("spawnGlTF")
The fox is loaded as a child of the player-1, but only on the server side ? What do i wrong/miss ?
thx
I think this is a networking problem @Faless probably knows immediately. I think it's because the scene paths don't match.
I've been also playing with a runtime loader.
https://github.com/V-Sekai-fire/VSK_model_explorer
hi @fire i tried your model exporter, but its not working. I made issue https://github.com/V-Sekai-fire/VSK_model_explorer/issues/1
I may be using a newer version of the engine than is released.
Trying to get a release_template built but having trouble.
i followed your hint @fire , tried setting some path on GLDocument and GLState, but nothing changed. I also had a look at multiplayersynchronizer but that seemed to update properties only.
Perhaps @Faless could you give me some hints, where to look at ?
thx
When i set the multiplayer_authority and then request if i set it, it returns false ? But i think i use multiplayerSpawner wrong, perhaps someone got a hint ?
@rpc(any_peer, call_local, reliable, 1)
func spawnGlTF():
# if we are the server (could be multiplayer.is_server() ? )
if multiplayer.get_unique_id() == 1:
print("spawnGlTF")
var gltf := GLTFDocument.new()
var gltf_state := GLTFState.new()
var path = "C:/Users/SuperUserName/Documents/godot-workspace/godot-data/Fox.glb"
if FileAccess.file_exists(path):
print("File to spawn exists")
else:
print("File to spawn does not exist")
return
var snd_file = FileAccess.open(path, FileAccess.READ)
var fileBytes = PackedByteArray()
fileBytes = snd_file.get_buffer(snd_file.get_length())
gltf.append_from_buffer(fileBytes, "base_path?", gltf_state)
var node = gltf.generate_scene(gltf_state)
#this adds fox the the player as child, so it moves with the player
# E 0:00:13:0789 spawnGlTF: Cannot get path of node as it is not in a scene tree.
# <C++-Fehler> Condition "!is_inside_tree()" is true. Returning: NodePath()
# <C++-Quellcode>scene/main/node.cpp:1715 @ get_path()
# <Stacktrace> player_character.gd:66 @ spawnGlTF()
add_child(node)
var sceneTree = get_tree().root
# sceneTree.add_child(node)
#multiplayersyncronizer test
var gltf_spawner := MultiplayerSpawner.new()
print("scene count:" +str(gltf_spawner.get_spawnable_scene_count()) )
gltf_spawner.add_spawnable_scene(node.get_path())
print("scene count2:" + str(gltf_spawner.get_spawnable_scene_count()) )
# gltf_spawner.spawn_path = "."
gltf_spawner.set_multiplayer_authority(get_multiplayer_authority())
if gltf_spawner.is_multiplayer_authority():
print("is multiplayer authority")
else:
print("is not multiplayer authority")
if gltf_spawner.is_inside_tree():
print("is inside tree")
else:
print("is not inside tree")
if multiplayer.has_multiplayer_peer():
print("has multiplayer peer")
else:
print("has not multiplayer peer")
gltf_spawner.spawn()
#some tree tests
# gltf_state.scene_name = "Main"
# gltf_state.resource_path = "res://Fox.glb"
# gltf_state.resource_name = "Fox.glb"
# print("rsc name " + gltf_state.resource_name )
# print("rsc path " + gltf_state.resource_path )
# print("nooode " + gltf_state.scene_name )
# var sceneTree = get_tree().root
# sceneTree.add_child(node)
else:
print("notSpawnGlTF")
i tried packing the Fox.glb into a scene, save it with res:// path and then load it again, so that it has a res:// path for add_spawnable_scene() function, but it wont work ( !is_inside_tree ) . The function Node::_propagate_enter_tree() is setting is_inside_tree to true, but dont know yet how to get there?
void Node::_propagate_enter_tree() {
// this needs to happen to all children before any enter_tree
if (data.parent) {
data.tree = data.parent->data.tree;
data.depth = data.parent->data.depth + 1;
} else {
data.depth = 1;
}
data.viewport = Object::cast_to<Viewport>(this);
if (!data.viewport && data.parent) {
data.viewport = data.parent->data.viewport;
}
data.inside_tree = true;
My try with saving and loading glb as scene :)
@rpc(any_peer, call_local, reliable, 1)
func spawnGlTF():
# if we are the server (could be multiplayer.is_server() ? )
if multiplayer.get_unique_id() == 1:
print("spawnGlTF")
var gltf := GLTFDocument.new()
var gltf_state := GLTFState.new()
var path = "C:/Users/SuperUserName/Documents/godot-workspace/godot-data/Fox.glb"
if FileAccess.file_exists(path):
print("File to spawn exists")
else:
print("File to spawn does not exist")
return
var snd_file = FileAccess.open(path, FileAccess.READ)
var fileBytes = PackedByteArray()
fileBytes = snd_file.get_buffer(snd_file.get_length())
gltf.append_from_buffer(fileBytes, "base_path?", gltf_state)
var node = gltf.generate_scene(gltf_state)
# save gltf as a scene with res:// path, to later add the scene to MultiplayerSpawner.add_spawnable_scene ??
var scene = PackedScene.new()
var result = scene.pack(node)
if result == OK:
var error = ResourceSaver.save(scene, "res://myscene.scn") # Or "user://..."
if error != OK:
print("An error occurred while saving the scene to disk.")
# load the scene from res:// path
var loadscene = ResourceLoader.load("res://myscene.scn")
# add node to tree for is_inside_tree ??
add_child(loadscene.get_local_scene())
# E 0:00:14:0633 spawnGlTF: Parameter "p_child" is null.
# <C++-Quellcode>scene/main/node.cpp:1127 @ add_child()
# <Stacktrace> player_character.gd:70 @ spawnGlTF()
#multiplayerspawner test
var gltf_spawner := MultiplayerSpawner.new()
print("gltf_spawner.spawn_path:" + str(gltf_spawner.spawn_path) )
gltf_spawner.spawn_path = "." # "/root/" # node.get_path()
print("gltf_spawner.spawn_path:" + str(gltf_spawner.spawn_path) )
print("scene count:" +str(gltf_spawner.get_spawnable_scene_count()) )
# print("node-get_path:" + str(node.get_path()) )
gltf_spawner.add_spawnable_scene(loadscene.resource_path)
print("scene count2:" + str(gltf_spawner.get_spawnable_scene_count()) )
# gltf_spawner.spawn_path = "."
gltf_spawner.set_multiplayer_authority(multiplayer.get_unique_id() ) # get_multiplayer_authority())
if gltf_spawner.is_multiplayer_authority():
print("is multiplayer authority")
else:
print("is not multiplayer authority")
if gltf_spawner.is_inside_tree():
print("is inside tree")
else:
print("is not inside tree")
if multiplayer.has_multiplayer_peer():
print("has multiplayer peer")
else:
print("has not multiplayer peer")
# gltf_spawner.spawn()
I dont need to copy the scene to all multiplayer clients with RPC , or? that copying is the spawner doing, or ?
thx
seemed i got it running with godot 4 beta 8.
If interrested , my first working code, to reproduce i use https://github.com/TheGodojo/Godot-4-Networking-Demonstration-Complete and change
func _on_host_pressed():
$NetworkInfo/NetworkSideDisplay.text = "Server"
$Menu.visible = false
multiplayer_peer.create_server(PORT)
multiplayer.multiplayer_peer = multiplayer_peer
$NetworkInfo/UniquePeerID.text = str(multiplayer.get_unique_id())
add_player_character(1)
multiplayer_peer.peer_connected.connect(
func(new_peer_id):
await get_tree().create_timer(1).timeout
rpc("add_newly_connected_player_character", new_peer_id)
rpc_id(new_peer_id, "add_previously_connected_player_characters", connected_peer_ids)
add_player_character(new_peer_id)
spawnGlTF()
)
@rpc(any_peer, call_local, reliable, 1)
func spawnGlTF():
# if we are the server (could be multiplayer.is_server() ? )
if multiplayer.get_unique_id() == 1:
print("spawnGlTF")
var gltf := GLTFDocument.new()
var gltf_state := GLTFState.new()
var path = "C:/Users/SuperUserName/Documents/godot-workspace/godot-data/Fox.glb"
if FileAccess.file_exists(path):
print("File to spawn exists")
else:
print("File to spawn does not exist")
return
var snd_file = FileAccess.open(path, FileAccess.READ)
var fileBytes = PackedByteArray()
fileBytes = snd_file.get_buffer(snd_file.get_length())
gltf.append_from_buffer(fileBytes, "base_path?", gltf_state)
var node = gltf.generate_scene(gltf_state)
rpc("send_gltf_scene_to_peers", fileBytes)
add_child(node)
else:
print("notSpawnGlTF")
@rpc(any_peer, call_local, reliable, 1)
func send_gltf_scene_to_peers(fileBytes):
print("send-gltf-scene-to-peers")
if multiplayer.get_unique_id() != 1:
var gltf := GLTFDocument.new()
var gltf_state := GLTFState.new()
gltf.append_from_buffer(fileBytes, "base_path?", gltf_state)
var node = gltf.generate_scene(gltf_state)
add_child(node)
print("We are NOT server, add_child()")
else:
print("We are server, doing nothing")
pass
wrote a "distributor" , where you can request a not-at-compile-time-known-glb, the glb gets distributed across the clients and added to add_spawnable_scene(). Then you can use it with "normal" MultiplayerSpawner capabilities, I mean, you can add_child() it, and all clients also automatically load it too. add_child() only from server at present time possible.
first minimal-only-windows try https://github.com/flobotics/godot/tree/glb_distributor
I think i handle many things wrong , so any help or hint is welcome
thx
Hi everyone, We have the same problem in my company, and this is the way we are doing it here: It might be a good reference on how to do it using c#. However, we are still trying to figure it out. If it's possible to do remapping as well at runtime, it seems impossible.
To load a typical 3d scene:
private GltfDocument GltfImporter;
GltfState gltfState = new();
GltfImporter.AppendFromFile(filepath, gltfState);
AgentModels[index] = GltfImporter.GenerateScene(gltfState) as Node3D;
To load an animations library:
GltfState gltfState = new();
GltfImporter.AppendFromFile(filepath, gltfState);
Node3D scene = GltfImporter.GenerateScene(gltfState) as Node3D;
AnimationLibrary animations = null;
for (int i = 0; i < scene.GetChildCount(); i++)
{
AnimationPlayer? animationPlayer = scene.GetChild(i) as AnimationPlayer;
if (animation player == null)
continue;
Array<StringName> libs = animationPlayer.GetAnimationLibraryList();
if (libs. Count >= 0)
{
animations = animationPlayer.GetAnimationLibrary(libs[0]);
// Set animation loop to true -> Because of the way our animation system works its better to have all of the animations imported as a loop.
foreach (string animationName in animations.GetAnimationList())
{
animations.GetAnimation(animationName).LoopMode = Animation.LoopModeEnum.Linear;
}
break;
}
is there any way to background load glb resources at runtime while resource file is not in project directory?
hi, i searched alot, but can only find old github post about it. Is there a doc about the new glTF runtime loader in godot 4 ?
Is it possible to e.g. start a multiplayer-networking-game and while playing, another persons sends per e.g. email a new glTF (e.g. mesh+texture) and you want to load it into the running multiplayer-networking-game without rebuilding/reloading/redistribution ?
thx