Browse Source

GLTF: Prerequisite cleanups before KHR_animation_pointer

Add get_scene_node_path and has_additional_data to GLTFNode, remove center of mass ignore warning in physics (it's supported now), rename `d` to `mesh_dict` in mesh import code.
Aaron Franke 1 year ago
parent
commit
cd367b3da3

+ 9 - 0
modules/gltf/doc_classes/GLTFNode.xml

@@ -27,6 +27,15 @@
 				The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is null.
 				The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is null.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="get_scene_node_path">
+			<return type="NodePath" />
+			<param index="0" name="gltf_state" type="GLTFState" />
+			<param index="1" name="handle_skeletons" type="bool" default="true" />
+			<description>
+				Returns the [NodePath] that this GLTF node will have in the Godot scene tree after being imported.
+				If [param handle_skeletons] is true, paths to skeleton bone glTF nodes will be resolved properly. For example, a path that would be [code]^"A/B/C/Bone1/Bone2/Bone3"[/code] if false will become [code]^"A/B/C/Skeleton3D:Bone3"[/code].
+			</description>
+		</method>
 		<method name="set_additional_data">
 		<method name="set_additional_data">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="extension_name" type="StringName" />
 			<param index="0" name="extension_name" type="StringName" />

+ 0 - 3
modules/gltf/extensions/physics/gltf_physics_body.cpp

@@ -193,9 +193,6 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_node(const CollisionObject3D *p_body_
 		physics_body->angular_velocity = body->get_angular_velocity();
 		physics_body->angular_velocity = body->get_angular_velocity();
 		physics_body->center_of_mass = body->get_center_of_mass();
 		physics_body->center_of_mass = body->get_center_of_mass();
 		physics_body->inertia_diagonal = body->get_inertia();
 		physics_body->inertia_diagonal = body->get_inertia();
-		if (body->get_center_of_mass() != Vector3()) {
-			WARN_PRINT("GLTFPhysicsBody: This rigid body has a center of mass offset from the origin, which will be ignored when exporting to glTF.");
-		}
 		if (cast_to<VehicleBody3D>(p_body_node)) {
 		if (cast_to<VehicleBody3D>(p_body_node)) {
 			physics_body->body_type = PhysicsBodyType::VEHICLE;
 			physics_body->body_type = PhysicsBodyType::VEHICLE;
 		} else {
 		} else {

+ 43 - 40
modules/gltf/gltf_document.cpp

@@ -2894,41 +2894,42 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
 	Array meshes = p_state->json["meshes"];
 	Array meshes = p_state->json["meshes"];
 	for (GLTFMeshIndex i = 0; i < meshes.size(); i++) {
 	for (GLTFMeshIndex i = 0; i < meshes.size(); i++) {
 		print_verbose("glTF: Parsing mesh: " + itos(i));
 		print_verbose("glTF: Parsing mesh: " + itos(i));
-		Dictionary d = meshes[i];
+		Dictionary mesh_dict = meshes[i];
 
 
 		Ref<GLTFMesh> mesh;
 		Ref<GLTFMesh> mesh;
 		mesh.instantiate();
 		mesh.instantiate();
 		bool has_vertex_color = false;
 		bool has_vertex_color = false;
 
 
-		ERR_FAIL_COND_V(!d.has("primitives"), ERR_PARSE_ERROR);
+		ERR_FAIL_COND_V(!mesh_dict.has("primitives"), ERR_PARSE_ERROR);
 
 
-		Array primitives = d["primitives"];
-		const Dictionary &extras = d.has("extras") ? (Dictionary)d["extras"] : Dictionary();
+		Array primitives = mesh_dict["primitives"];
+		const Dictionary &extras = mesh_dict.has("extras") ? (Dictionary)mesh_dict["extras"] : Dictionary();
 		_attach_extras_to_meta(extras, mesh);
 		_attach_extras_to_meta(extras, mesh);
 		Ref<ImporterMesh> import_mesh;
 		Ref<ImporterMesh> import_mesh;
 		import_mesh.instantiate();
 		import_mesh.instantiate();
 		String mesh_name = "mesh";
 		String mesh_name = "mesh";
-		if (d.has("name") && !String(d["name"]).is_empty()) {
-			mesh_name = d["name"];
+		if (mesh_dict.has("name") && !String(mesh_dict["name"]).is_empty()) {
+			mesh_name = mesh_dict["name"];
 			mesh->set_original_name(mesh_name);
 			mesh->set_original_name(mesh_name);
 		}
 		}
 		import_mesh->set_name(_gen_unique_name(p_state, vformat("%s_%s", p_state->scene_name, mesh_name)));
 		import_mesh->set_name(_gen_unique_name(p_state, vformat("%s_%s", p_state->scene_name, mesh_name)));
 		mesh->set_name(import_mesh->get_name());
 		mesh->set_name(import_mesh->get_name());
+		TypedArray<Material> instance_materials;
 
 
 		for (int j = 0; j < primitives.size(); j++) {
 		for (int j = 0; j < primitives.size(); j++) {
 			uint64_t flags = RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES;
 			uint64_t flags = RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES;
-			Dictionary p = primitives[j];
+			Dictionary mesh_prim = primitives[j];
 
 
 			Array array;
 			Array array;
 			array.resize(Mesh::ARRAY_MAX);
 			array.resize(Mesh::ARRAY_MAX);
 
 
-			ERR_FAIL_COND_V(!p.has("attributes"), ERR_PARSE_ERROR);
+			ERR_FAIL_COND_V(!mesh_prim.has("attributes"), ERR_PARSE_ERROR);
 
 
-			Dictionary a = p["attributes"];
+			Dictionary a = mesh_prim["attributes"];
 
 
 			Mesh::PrimitiveType primitive = Mesh::PRIMITIVE_TRIANGLES;
 			Mesh::PrimitiveType primitive = Mesh::PRIMITIVE_TRIANGLES;
-			if (p.has("mode")) {
-				const int mode = p["mode"];
+			if (mesh_prim.has("mode")) {
+				const int mode = mesh_prim["mode"];
 				ERR_FAIL_INDEX_V(mode, 7, ERR_FILE_CORRUPT);
 				ERR_FAIL_INDEX_V(mode, 7, ERR_FILE_CORRUPT);
 				// Convert mesh.primitive.mode to Godot Mesh enum. See:
 				// Convert mesh.primitive.mode to Godot Mesh enum. See:
 				// https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#_mesh_primitive_mode
 				// https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#_mesh_primitive_mode
@@ -2959,8 +2960,8 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
 			Vector<int> indices_mapping;
 			Vector<int> indices_mapping;
 			Vector<int> indices_rev_mapping;
 			Vector<int> indices_rev_mapping;
 			Vector<int> indices_vec4_mapping;
 			Vector<int> indices_vec4_mapping;
-			if (p.has("indices")) {
-				indices = _decode_accessor_as_ints(p_state, p["indices"], false);
+			if (mesh_prim.has("indices")) {
+				indices = _decode_accessor_as_ints(p_state, mesh_prim["indices"], false);
 				const int is = indices.size();
 				const int is = indices.size();
 
 
 				if (primitive == Mesh::PRIMITIVE_TRIANGLES) {
 				if (primitive == Mesh::PRIMITIVE_TRIANGLES) {
@@ -3218,7 +3219,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
 				}
 				}
 			}
 			}
 
 
-			if (p_state->force_disable_compression || is_mesh_2d || !a.has("POSITION") || !a.has("NORMAL") || p.has("targets") || (a.has("JOINTS_0") || a.has("JOINTS_1"))) {
+			if (p_state->force_disable_compression || is_mesh_2d || !a.has("POSITION") || !a.has("NORMAL") || mesh_prim.has("targets") || (a.has("JOINTS_0") || a.has("JOINTS_1"))) {
 				flags &= ~RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES;
 				flags &= ~RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES;
 			}
 			}
 
 
@@ -3250,9 +3251,9 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
 
 
 			Array morphs;
 			Array morphs;
 			// Blend shapes
 			// Blend shapes
-			if (p.has("targets")) {
+			if (mesh_prim.has("targets")) {
 				print_verbose("glTF: Mesh has targets");
 				print_verbose("glTF: Mesh has targets");
-				const Array &targets = p["targets"];
+				const Array &targets = mesh_prim["targets"];
 
 
 				import_mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED);
 				import_mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED);
 
 
@@ -3383,8 +3384,8 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
 			Ref<Material> mat;
 			Ref<Material> mat;
 			String mat_name;
 			String mat_name;
 			if (!p_state->discard_meshes_and_materials) {
 			if (!p_state->discard_meshes_and_materials) {
-				if (p.has("material")) {
-					const int material = p["material"];
+				if (mesh_prim.has("material")) {
+					const int material = mesh_prim["material"];
 					ERR_FAIL_INDEX_V(material, p_state->materials.size(), ERR_FILE_CORRUPT);
 					ERR_FAIL_INDEX_V(material, p_state->materials.size(), ERR_FILE_CORRUPT);
 					Ref<Material> mat3d = p_state->materials[material];
 					Ref<Material> mat3d = p_state->materials[material];
 					ERR_FAIL_COND_V(mat3d.is_null(), ERR_FILE_CORRUPT);
 					ERR_FAIL_COND_V(mat3d.is_null(), ERR_FILE_CORRUPT);
@@ -3404,6 +3405,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
 					mat = mat3d;
 					mat = mat3d;
 				}
 				}
 				ERR_FAIL_COND_V(mat.is_null(), ERR_FILE_CORRUPT);
 				ERR_FAIL_COND_V(mat.is_null(), ERR_FILE_CORRUPT);
+				instance_materials.append(mat);
 				mat_name = mat->get_name();
 				mat_name = mat->get_name();
 			}
 			}
 			import_mesh->add_surface(primitive, array, morphs,
 			import_mesh->add_surface(primitive, array, morphs,
@@ -3416,8 +3418,8 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
 			blend_weights.write[weight_i] = 0.0f;
 			blend_weights.write[weight_i] = 0.0f;
 		}
 		}
 
 
-		if (d.has("weights")) {
-			const Array &weights = d["weights"];
+		if (mesh_dict.has("weights")) {
+			const Array &weights = mesh_dict["weights"];
 			for (int j = 0; j < weights.size(); j++) {
 			for (int j = 0; j < weights.size(); j++) {
 				if (j >= blend_weights.size()) {
 				if (j >= blend_weights.size()) {
 					break;
 					break;
@@ -3426,6 +3428,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
 			}
 			}
 		}
 		}
 		mesh->set_blend_weights(blend_weights);
 		mesh->set_blend_weights(blend_weights);
+		mesh->set_instance_materials(instance_materials);
 		mesh->set_mesh(import_mesh);
 		mesh->set_mesh(import_mesh);
 
 
 		p_state->meshes.push_back(mesh);
 		p_state->meshes.push_back(mesh);
@@ -5366,44 +5369,44 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current,
 	gltf_node->set_original_name(p_current->get_name());
 	gltf_node->set_original_name(p_current->get_name());
 	gltf_node->set_name(_gen_unique_name(p_state, p_current->get_name()));
 	gltf_node->set_name(_gen_unique_name(p_state, p_current->get_name()));
 	gltf_node->merge_meta_from(p_current);
 	gltf_node->merge_meta_from(p_current);
-	if (cast_to<Node3D>(p_current)) {
-		Node3D *spatial = cast_to<Node3D>(p_current);
+	if (Object::cast_to<Node3D>(p_current)) {
+		Node3D *spatial = Object::cast_to<Node3D>(p_current);
 		_convert_spatial(p_state, spatial, gltf_node);
 		_convert_spatial(p_state, spatial, gltf_node);
 	}
 	}
-	if (cast_to<MeshInstance3D>(p_current)) {
-		MeshInstance3D *mi = cast_to<MeshInstance3D>(p_current);
+	if (Object::cast_to<MeshInstance3D>(p_current)) {
+		MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(p_current);
 		_convert_mesh_instance_to_gltf(mi, p_state, gltf_node);
 		_convert_mesh_instance_to_gltf(mi, p_state, gltf_node);
-	} else if (cast_to<BoneAttachment3D>(p_current)) {
-		BoneAttachment3D *bone = cast_to<BoneAttachment3D>(p_current);
+	} else if (Object::cast_to<BoneAttachment3D>(p_current)) {
+		BoneAttachment3D *bone = Object::cast_to<BoneAttachment3D>(p_current);
 		_convert_bone_attachment_to_gltf(bone, p_state, p_gltf_parent, p_gltf_root, gltf_node);
 		_convert_bone_attachment_to_gltf(bone, p_state, p_gltf_parent, p_gltf_root, gltf_node);
 		return;
 		return;
-	} else if (cast_to<Skeleton3D>(p_current)) {
-		Skeleton3D *skel = cast_to<Skeleton3D>(p_current);
+	} else if (Object::cast_to<Skeleton3D>(p_current)) {
+		Skeleton3D *skel = Object::cast_to<Skeleton3D>(p_current);
 		_convert_skeleton_to_gltf(skel, p_state, p_gltf_parent, p_gltf_root, gltf_node);
 		_convert_skeleton_to_gltf(skel, p_state, p_gltf_parent, p_gltf_root, gltf_node);
 		// We ignore the Godot Engine node that is the skeleton.
 		// We ignore the Godot Engine node that is the skeleton.
 		return;
 		return;
-	} else if (cast_to<MultiMeshInstance3D>(p_current)) {
-		MultiMeshInstance3D *multi = cast_to<MultiMeshInstance3D>(p_current);
+	} else if (Object::cast_to<MultiMeshInstance3D>(p_current)) {
+		MultiMeshInstance3D *multi = Object::cast_to<MultiMeshInstance3D>(p_current);
 		_convert_multi_mesh_instance_to_gltf(multi, p_gltf_parent, p_gltf_root, gltf_node, p_state);
 		_convert_multi_mesh_instance_to_gltf(multi, p_gltf_parent, p_gltf_root, gltf_node, p_state);
 #ifdef MODULE_CSG_ENABLED
 #ifdef MODULE_CSG_ENABLED
-	} else if (cast_to<CSGShape3D>(p_current)) {
-		CSGShape3D *shape = cast_to<CSGShape3D>(p_current);
+	} else if (Object::cast_to<CSGShape3D>(p_current)) {
+		CSGShape3D *shape = Object::cast_to<CSGShape3D>(p_current);
 		if (shape->get_parent() && shape->is_root_shape()) {
 		if (shape->get_parent() && shape->is_root_shape()) {
 			_convert_csg_shape_to_gltf(shape, p_gltf_parent, gltf_node, p_state);
 			_convert_csg_shape_to_gltf(shape, p_gltf_parent, gltf_node, p_state);
 		}
 		}
 #endif // MODULE_CSG_ENABLED
 #endif // MODULE_CSG_ENABLED
 #ifdef MODULE_GRIDMAP_ENABLED
 #ifdef MODULE_GRIDMAP_ENABLED
-	} else if (cast_to<GridMap>(p_current)) {
+	} else if (Object::cast_to<GridMap>(p_current)) {
 		GridMap *gridmap = Object::cast_to<GridMap>(p_current);
 		GridMap *gridmap = Object::cast_to<GridMap>(p_current);
 		_convert_grid_map_to_gltf(gridmap, p_gltf_parent, p_gltf_root, gltf_node, p_state);
 		_convert_grid_map_to_gltf(gridmap, p_gltf_parent, p_gltf_root, gltf_node, p_state);
 #endif // MODULE_GRIDMAP_ENABLED
 #endif // MODULE_GRIDMAP_ENABLED
-	} else if (cast_to<Camera3D>(p_current)) {
+	} else if (Object::cast_to<Camera3D>(p_current)) {
 		Camera3D *camera = Object::cast_to<Camera3D>(p_current);
 		Camera3D *camera = Object::cast_to<Camera3D>(p_current);
 		_convert_camera_to_gltf(camera, p_state, gltf_node);
 		_convert_camera_to_gltf(camera, p_state, gltf_node);
-	} else if (cast_to<Light3D>(p_current)) {
+	} else if (Object::cast_to<Light3D>(p_current)) {
 		Light3D *light = Object::cast_to<Light3D>(p_current);
 		Light3D *light = Object::cast_to<Light3D>(p_current);
 		_convert_light_to_gltf(light, p_state, gltf_node);
 		_convert_light_to_gltf(light, p_state, gltf_node);
-	} else if (cast_to<AnimationPlayer>(p_current)) {
+	} else if (Object::cast_to<AnimationPlayer>(p_current)) {
 		AnimationPlayer *animation_player = Object::cast_to<AnimationPlayer>(p_current);
 		AnimationPlayer *animation_player = Object::cast_to<AnimationPlayer>(p_current);
 		p_state->animation_players.push_back(animation_player);
 		p_state->animation_players.push_back(animation_player);
 	}
 	}
@@ -5675,9 +5678,9 @@ void GLTFDocument::_convert_bone_attachment_to_gltf(BoneAttachment3D *p_bone_att
 	Skeleton3D *skeleton;
 	Skeleton3D *skeleton;
 	// Note that relative transforms to external skeletons and pose overrides are not supported.
 	// Note that relative transforms to external skeletons and pose overrides are not supported.
 	if (p_bone_attachment->get_use_external_skeleton()) {
 	if (p_bone_attachment->get_use_external_skeleton()) {
-		skeleton = cast_to<Skeleton3D>(p_bone_attachment->get_node_or_null(p_bone_attachment->get_external_skeleton()));
+		skeleton = Object::cast_to<Skeleton3D>(p_bone_attachment->get_node_or_null(p_bone_attachment->get_external_skeleton()));
 	} else {
 	} else {
-		skeleton = cast_to<Skeleton3D>(p_bone_attachment->get_parent());
+		skeleton = Object::cast_to<Skeleton3D>(p_bone_attachment->get_parent());
 	}
 	}
 	GLTFSkeletonIndex skel_gltf_i = -1;
 	GLTFSkeletonIndex skel_gltf_i = -1;
 	if (skeleton != nullptr && p_state->skeleton3d_to_gltf_skeleton.has(skeleton->get_instance_id())) {
 	if (skeleton != nullptr && p_state->skeleton3d_to_gltf_skeleton.has(skeleton->get_instance_id())) {
@@ -6478,7 +6481,7 @@ void GLTFDocument::_process_mesh_instances(Ref<GLTFState> p_state, Node *p_scene
 }
 }
 
 
 GLTFNodeIndex GLTFDocument::_node_and_or_bone_to_gltf_node_index(Ref<GLTFState> p_state, const Vector<StringName> &p_node_subpath, const Node *p_godot_node) {
 GLTFNodeIndex GLTFDocument::_node_and_or_bone_to_gltf_node_index(Ref<GLTFState> p_state, const Vector<StringName> &p_node_subpath, const Node *p_godot_node) {
-	const Skeleton3D *skeleton = cast_to<Skeleton3D>(p_godot_node);
+	const Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_godot_node);
 	if (skeleton && p_node_subpath.size() == 1) {
 	if (skeleton && p_node_subpath.size() == 1) {
 		// Special case: Handle skeleton bone TRS tracks. They use the format `A/B/C/Skeleton3D:bone_name`.
 		// Special case: Handle skeleton bone TRS tracks. They use the format `A/B/C/Skeleton3D:bone_name`.
 		// We have a Skeleton3D, check if it has a bone with the same name as this subpath.
 		// We have a Skeleton3D, check if it has a bone with the same name as this subpath.
@@ -6909,7 +6912,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
 		const GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::godot_to_gltf_interpolation(animation, track_index);
 		const GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::godot_to_gltf_interpolation(animation, track_index);
 		// First, check if it's a Blend Shape track.
 		// First, check if it's a Blend Shape track.
 		if (animation->track_get_type(track_index) == Animation::TYPE_BLEND_SHAPE) {
 		if (animation->track_get_type(track_index) == Animation::TYPE_BLEND_SHAPE) {
-			const MeshInstance3D *mesh_instance = cast_to<MeshInstance3D>(animated_node);
+			const MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(animated_node);
 			ERR_CONTINUE_MSG(!mesh_instance, "glTF: Animation had a Blend Shape track, but the node wasn't a MeshInstance3D. Ignoring this track.");
 			ERR_CONTINUE_MSG(!mesh_instance, "glTF: Animation had a Blend Shape track, but the node wasn't a MeshInstance3D. Ignoring this track.");
 			Ref<Mesh> mesh = mesh_instance->get_mesh();
 			Ref<Mesh> mesh = mesh_instance->get_mesh();
 			ERR_CONTINUE(mesh.is_null());
 			ERR_CONTINUE(mesh.is_null());

+ 1 - 0
modules/gltf/gltf_state.h

@@ -48,6 +48,7 @@
 class GLTFState : public Resource {
 class GLTFState : public Resource {
 	GDCLASS(GLTFState, Resource);
 	GDCLASS(GLTFState, Resource);
 	friend class GLTFDocument;
 	friend class GLTFDocument;
+	friend class GLTFNode;
 
 
 protected:
 protected:
 	String base_path;
 	String base_path;

+ 45 - 0
modules/gltf/structures/gltf_node.cpp

@@ -30,6 +30,8 @@
 
 
 #include "gltf_node.h"
 #include "gltf_node.h"
 
 
+#include "../gltf_state.h"
+
 void GLTFNode::_bind_methods() {
 void GLTFNode::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_original_name"), &GLTFNode::get_original_name);
 	ClassDB::bind_method(D_METHOD("get_original_name"), &GLTFNode::get_original_name);
 	ClassDB::bind_method(D_METHOD("set_original_name", "original_name"), &GLTFNode::set_original_name);
 	ClassDB::bind_method(D_METHOD("set_original_name", "original_name"), &GLTFNode::set_original_name);
@@ -60,6 +62,7 @@ void GLTFNode::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_light", "light"), &GLTFNode::set_light);
 	ClassDB::bind_method(D_METHOD("set_light", "light"), &GLTFNode::set_light);
 	ClassDB::bind_method(D_METHOD("get_additional_data", "extension_name"), &GLTFNode::get_additional_data);
 	ClassDB::bind_method(D_METHOD("get_additional_data", "extension_name"), &GLTFNode::get_additional_data);
 	ClassDB::bind_method(D_METHOD("set_additional_data", "extension_name", "additional_data"), &GLTFNode::set_additional_data);
 	ClassDB::bind_method(D_METHOD("set_additional_data", "extension_name", "additional_data"), &GLTFNode::set_additional_data);
+	ClassDB::bind_method(D_METHOD("get_scene_node_path", "gltf_state", "handle_skeletons"), &GLTFNode::get_scene_node_path, DEFVAL(true));
 
 
 	ADD_PROPERTY(PropertyInfo(Variant::STRING, "original_name"), "set_original_name", "get_original_name"); // String
 	ADD_PROPERTY(PropertyInfo(Variant::STRING, "original_name"), "set_original_name", "get_original_name"); // String
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "parent"), "set_parent", "get_parent"); // GLTFNodeIndex
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "parent"), "set_parent", "get_parent"); // GLTFNodeIndex
@@ -187,6 +190,48 @@ Variant GLTFNode::get_additional_data(const StringName &p_extension_name) {
 	return additional_data[p_extension_name];
 	return additional_data[p_extension_name];
 }
 }
 
 
+bool GLTFNode::has_additional_data(const StringName &p_extension_name) {
+	return additional_data.has(p_extension_name);
+}
+
 void GLTFNode::set_additional_data(const StringName &p_extension_name, Variant p_additional_data) {
 void GLTFNode::set_additional_data(const StringName &p_extension_name, Variant p_additional_data) {
 	additional_data[p_extension_name] = p_additional_data;
 	additional_data[p_extension_name] = p_additional_data;
 }
 }
+
+NodePath GLTFNode::get_scene_node_path(Ref<GLTFState> p_state, bool p_handle_skeletons) {
+	Vector<StringName> path;
+	Vector<StringName> subpath;
+	Ref<GLTFNode> current_gltf_node = this;
+	const int gltf_node_count = p_state->nodes.size();
+	if (p_handle_skeletons && skeleton != -1) {
+		// Special case for skeleton nodes, skip all bones so that the path is to the Skeleton3D node.
+		// A path that would otherwise be `A/B/C/Bone1/Bone2/Bone3` becomes `A/B/C/Skeleton3D:Bone3`.
+		subpath.append(get_name());
+		// The generated Skeleton3D node will be named Skeleton3D, so add it to the path.
+		path.append("Skeleton3D");
+		do {
+			const int parent_index = current_gltf_node->get_parent();
+			ERR_FAIL_INDEX_V(parent_index, gltf_node_count, NodePath());
+			current_gltf_node = p_state->nodes[parent_index];
+		} while (current_gltf_node->skeleton != -1);
+	}
+	const bool is_godot_single_root = p_state->extensions_used.has("GODOT_single_root");
+	while (true) {
+		const int parent_index = current_gltf_node->get_parent();
+		if (is_godot_single_root && parent_index == -1) {
+			// For GODOT_single_root scenes, the root glTF node becomes the Godot scene root, so it
+			// should not be included in the path. Ex: A/B/C, A is single root, we want B/C only.
+			break;
+		}
+		path.insert(0, current_gltf_node->get_name());
+		if (!is_godot_single_root && parent_index == -1) {
+			break;
+		}
+		ERR_FAIL_INDEX_V(parent_index, gltf_node_count, NodePath());
+		current_gltf_node = p_state->nodes[parent_index];
+	}
+	if (unlikely(path.is_empty())) {
+		path.append(".");
+	}
+	return NodePath(path, subpath, false);
+}

+ 3 - 0
modules/gltf/structures/gltf_node.h

@@ -103,7 +103,10 @@ public:
 	void set_light(GLTFLightIndex p_light);
 	void set_light(GLTFLightIndex p_light);
 
 
 	Variant get_additional_data(const StringName &p_extension_name);
 	Variant get_additional_data(const StringName &p_extension_name);
+	bool has_additional_data(const StringName &p_extension_name);
 	void set_additional_data(const StringName &p_extension_name, Variant p_additional_data);
 	void set_additional_data(const StringName &p_extension_name, Variant p_additional_data);
+
+	NodePath get_scene_node_path(Ref<GLTFState> p_state, bool p_handle_skeletons = true);
 };
 };
 
 
 #endif // GLTF_NODE_H
 #endif // GLTF_NODE_H