diff options
Diffstat (limited to 'modules')
93 files changed, 3163 insertions, 953 deletions
diff --git a/modules/betsy/image_compress_betsy.h b/modules/betsy/image_compress_betsy.h index 4e0bf0538f..ab7b785803 100644 --- a/modules/betsy/image_compress_betsy.h +++ b/modules/betsy/image_compress_betsy.h @@ -91,10 +91,10 @@ class BetsyCompressor : public Object { RenderingDevice *compress_rd = nullptr; RenderingContextDriver *compress_rcd = nullptr; HashMap<String, BetsyShader> cached_shaders; - RID src_sampler = RID(); + RID src_sampler; // Format-specific resources. - RID dxt1_encoding_table_buffer = RID(); + RID dxt1_encoding_table_buffer; void _init(); void _assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id); diff --git a/modules/camera/camera_feed_linux.cpp b/modules/camera/camera_feed_linux.cpp index 94bb2b6ad3..3ae1b70ac9 100644 --- a/modules/camera/camera_feed_linux.cpp +++ b/modules/camera/camera_feed_linux.cpp @@ -145,7 +145,7 @@ bool CameraFeedLinux::_request_buffers() { } buffers[i].length = buffer.length; - buffers[i].start = mmap(NULL, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, file_descriptor, buffer.m.offset); + buffers[i].start = mmap(nullptr, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, file_descriptor, buffer.m.offset); if (buffers[i].start == MAP_FAILED) { for (unsigned int b = 0; b < i; b++) { diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp index ce097092fb..d6c304d056 100644 --- a/modules/fbx/fbx_document.cpp +++ b/modules/fbx/fbx_document.cpp @@ -369,21 +369,25 @@ Error FBXDocument::_parse_nodes(Ref<FBXState> p_state) { // all skin clusters connected to the bone. for (const ufbx_connection &child_conn : fbx_node->element.connections_src) { ufbx_skin_cluster *child_cluster = ufbx_as_skin_cluster(child_conn.dst); - if (!child_cluster) + if (!child_cluster) { continue; + } ufbx_skin_deformer *child_deformer = _find_skin_deformer(child_cluster); - if (!child_deformer) + if (!child_deformer) { continue; + } // Found a skin cluster: Now iterate through all the skin clusters of the parent and // try to find one that used by the same deformer. for (const ufbx_connection &parent_conn : fbx_node->parent->element.connections_src) { ufbx_skin_cluster *parent_cluster = ufbx_as_skin_cluster(parent_conn.dst); - if (!parent_cluster) + if (!parent_cluster) { continue; + } ufbx_skin_deformer *parent_deformer = _find_skin_deformer(parent_cluster); - if (parent_deformer != child_deformer) + if (parent_deformer != child_deformer) { continue; + } // Success: Found two skin clusters from the same deformer, now we can resolve the // local bind pose from the difference between the two world-space bind poses. @@ -1389,7 +1393,7 @@ Error FBXDocument::_parse_animations(Ref<FBXState> p_state) { for (const ufbx_baked_node &fbx_baked_node : fbx_baked_anim->nodes) { const GLTFNodeIndex node = fbx_baked_node.typed_id; - GLTFAnimation::Track &track = animation->get_tracks()[node]; + GLTFAnimation::NodeTrack &track = animation->get_node_tracks()[node]; for (const ufbx_baked_vec3 &key : fbx_baked_node.translation_keys) { track.position_track.times.push_back(float(key.time)); @@ -1779,8 +1783,8 @@ void FBXDocument::_import_animation(Ref<FBXState> p_state, AnimationPlayer *p_an double anim_start_offset = p_trimming ? double(additional_animation_data["time_begin"]) : 0.0; - for (const KeyValue<int, GLTFAnimation::Track> &track_i : anim->get_tracks()) { - const GLTFAnimation::Track &track = track_i.value; + for (const KeyValue<int, GLTFAnimation::NodeTrack> &track_i : anim->get_node_tracks()) { + const GLTFAnimation::NodeTrack &track = track_i.value; //need to find the path: for skeletons, weight tracks will affect the mesh NodePath node_path; //for skeletons, transform tracks always affect bones diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 93d4a512a9..6241ada06a 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -5809,8 +5809,6 @@ void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p #ifdef DEBUG_ENABLED void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope) { const StringName &name = p_identifier->name; - GDScriptParser::DataType base = parser->current_class->get_datatype(); - GDScriptParser::ClassNode *base_class = base.class_type; { List<MethodInfo> gdscript_funcs; @@ -5838,37 +5836,53 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier } } + const GDScriptParser::DataType current_class_type = parser->current_class->get_datatype(); if (p_in_local_scope) { - while (base_class != nullptr) { + GDScriptParser::ClassNode *base_class = current_class_type.class_type; + + if (base_class != nullptr) { if (base_class->has_member(name)) { parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE, p_context, p_identifier->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line())); return; } base_class = base_class->base_type.class_type; } + + while (base_class != nullptr) { + if (base_class->has_member(name)) { + String base_class_name = base_class->get_global_name(); + if (base_class_name.is_empty()) { + base_class_name = base_class->fqcn; + } + + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line()), base_class_name); + return; + } + base_class = base_class->base_type.class_type; + } } - StringName parent = base.native_type; - while (parent != StringName()) { - ERR_FAIL_COND_MSG(!class_exists(parent), "Non-existent native base class."); + StringName native_base_class = current_class_type.native_type; + while (native_base_class != StringName()) { + ERR_FAIL_COND_MSG(!class_exists(native_base_class), "Non-existent native base class."); - if (ClassDB::has_method(parent, name, true)) { - parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "method", parent); + if (ClassDB::has_method(native_base_class, name, true)) { + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "method", native_base_class); return; - } else if (ClassDB::has_signal(parent, name, true)) { - parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "signal", parent); + } else if (ClassDB::has_signal(native_base_class, name, true)) { + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "signal", native_base_class); return; - } else if (ClassDB::has_property(parent, name, true)) { - parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "property", parent); + } else if (ClassDB::has_property(native_base_class, name, true)) { + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "property", native_base_class); return; - } else if (ClassDB::has_integer_constant(parent, name, true)) { - parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "constant", parent); + } else if (ClassDB::has_integer_constant(native_base_class, name, true)) { + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "constant", native_base_class); return; - } else if (ClassDB::has_enum(parent, name, true)) { - parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "enum", parent); + } else if (ClassDB::has_enum(native_base_class, name, true)) { + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "enum", native_base_class); return; } - parent = ClassDB::get_parent_class(parent); + native_base_class = ClassDB::get_parent_class(native_base_class); } } #endif // DEBUG_ENABLED diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index bc063693a3..d94a6dfda2 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -790,8 +790,9 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += method->get_name(); text += "("; for (int i = 0; i < argc; i++) { - if (i > 0) + if (i > 0) { text += ", "; + } text += DADDR(1 + i); } text += ")"; @@ -833,8 +834,9 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += method->get_name(); text += "("; for (int i = 0; i < argc; i++) { - if (i > 0) + if (i > 0) { text += ", "; + } text += DADDR(1 + i); } text += ")"; diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index 4ffb4bd9d1..a601cc4993 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -61,10 +61,13 @@ String GDScriptWarning::get_message() const { return vformat(R"(The signal "%s" is declared but never explicitly used in the class.)", symbols[0]); case SHADOWED_VARIABLE: CHECK_SYMBOLS(4); - return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s.)", symbols[0], symbols[1], symbols[2], symbols[3]); + return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s in the current class.)", symbols[0], symbols[1], symbols[2], symbols[3]); case SHADOWED_VARIABLE_BASE_CLASS: CHECK_SYMBOLS(4); - return vformat(R"(The local %s "%s" is shadowing an already-declared %s at the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3]); + if (symbols.size() > 4) { + return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s in the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3], symbols[4]); + } + return vformat(R"(The local %s "%s" is shadowing an already-declared %s in the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3]); case SHADOWED_GLOBAL_IDENTIFIER: CHECK_SYMBOLS(3); return vformat(R"(The %s "%s" has the same name as a %s.)", symbols[0], symbols[1], symbols[2]); diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index ffcf00a830..99e9b30af5 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -53,8 +53,8 @@ public: UNUSED_PRIVATE_CLASS_VARIABLE, // Class variable is declared private ("_" prefix) but never used in the class. UNUSED_PARAMETER, // Function parameter is never used. UNUSED_SIGNAL, // Signal is defined but never explicitly used in the class. - SHADOWED_VARIABLE, // Variable name shadowed by other variable in same class. - SHADOWED_VARIABLE_BASE_CLASS, // Variable name shadowed by other variable in some base class. + SHADOWED_VARIABLE, // A local variable/constant shadows a current class member. + SHADOWED_VARIABLE_BASE_CLASS, // A local variable/constant shadows a base class member. SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable. UNREACHABLE_CODE, // Code after a return statement. UNREACHABLE_PATTERN, // Pattern in a match statement after a catch all pattern (wildcard or bind). diff --git a/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out b/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out index 94e2ec2af8..fb616f1e94 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out +++ b/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out @@ -1,2 +1,2 @@ GDTEST_OK -0 +0.0 diff --git a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd index 5318d11f33..e91c7386fe 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd @@ -7,17 +7,17 @@ const const_packed_ints: PackedFloat64Array = [52] func test(): Utils.check(typeof(const_float_int) == TYPE_FLOAT) - Utils.check(str(const_float_int) == '19') + Utils.check(str(const_float_int) == '19.0') Utils.check(typeof(const_float_plus) == TYPE_FLOAT) - Utils.check(str(const_float_plus) == '34') + Utils.check(str(const_float_plus) == '34.0') Utils.check(typeof(const_float_cast) == TYPE_FLOAT) - Utils.check(str(const_float_cast) == '76') + Utils.check(str(const_float_cast) == '76.0') Utils.check(typeof(const_packed_empty) == TYPE_PACKED_FLOAT64_ARRAY) Utils.check(str(const_packed_empty) == '[]') Utils.check(typeof(const_packed_ints) == TYPE_PACKED_FLOAT64_ARRAY) - Utils.check(str(const_packed_ints) == '[52]') + Utils.check(str(const_packed_ints) == '[52.0]') Utils.check(typeof(const_packed_ints[0]) == TYPE_FLOAT) - Utils.check(str(const_packed_ints[0]) == '52') + Utils.check(str(const_packed_ints[0]) == '52.0') print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out b/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out index 15666c46ad..abf11548cb 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out +++ b/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out @@ -1,2 +1,2 @@ GDTEST_OK -4 +4.0 diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd index fe0274c27b..eb53d0a700 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd @@ -54,39 +54,39 @@ func test(): untyped_basic.push_back(430.0) inferred_basic.push_back(263.0) typed_basic.push_back(518.0) - Utils.check(str(empty_floats) == '[705, 430, 263, 518]') - Utils.check(str(untyped_basic) == '[705, 430, 263, 518]') - Utils.check(str(inferred_basic) == '[705, 430, 263, 518]') - Utils.check(str(typed_basic) == '[705, 430, 263, 518]') + Utils.check(str(empty_floats) == '[705.0, 430.0, 263.0, 518.0]') + Utils.check(str(untyped_basic) == '[705.0, 430.0, 263.0, 518.0]') + Utils.check(str(inferred_basic) == '[705.0, 430.0, 263.0, 518.0]') + Utils.check(str(typed_basic) == '[705.0, 430.0, 263.0, 518.0]') const constant_float := 950.0 const constant_int := 170 var typed_float := 954.0 var filled_floats: Array[float] = [constant_float, constant_int, typed_float, empty_floats[1] + empty_floats[2]] - Utils.check(str(filled_floats) == '[950, 170, 954, 693]') + Utils.check(str(filled_floats) == '[950.0, 170.0, 954.0, 693.0]') Utils.check(filled_floats.get_typed_builtin() == TYPE_FLOAT) var casted_floats := [empty_floats[2] * 2] as Array[float] - Utils.check(str(casted_floats) == '[526]') + Utils.check(str(casted_floats) == '[526.0]') Utils.check(casted_floats.get_typed_builtin() == TYPE_FLOAT) var returned_floats = (func () -> Array[float]: return [554]).call() - Utils.check(str(returned_floats) == '[554]') + Utils.check(str(returned_floats) == '[554.0]') Utils.check(returned_floats.get_typed_builtin() == TYPE_FLOAT) var passed_floats = floats_identity([663.0 if randf() > 0.5 else 663.0]) - Utils.check(str(passed_floats) == '[663]') + Utils.check(str(passed_floats) == '[663.0]') Utils.check(passed_floats.get_typed_builtin() == TYPE_FLOAT) var default_floats = (func (floats: Array[float] = [364.0]): return floats).call() - Utils.check(str(default_floats) == '[364]') + Utils.check(str(default_floats) == '[364.0]') Utils.check(default_floats.get_typed_builtin() == TYPE_FLOAT) var typed_int := 556 var converted_floats: Array[float] = [typed_int] converted_floats.push_back(498) - Utils.check(str(converted_floats) == '[556, 498]') + Utils.check(str(converted_floats) == '[556.0, 498.0]') Utils.check(converted_floats.get_typed_builtin() == TYPE_FLOAT) @@ -95,7 +95,7 @@ func test(): Utils.check(constant_basic.get_typed_builtin() == TYPE_NIL) const constant_floats: Array[float] = [constant_float - constant_basic[0] - constant_int] - Utils.check(str(constant_floats) == '[552]') + Utils.check(str(constant_floats) == '[552.0]') Utils.check(constant_floats.get_typed_builtin() == TYPE_FLOAT) @@ -103,15 +103,15 @@ func test(): untyped_basic = source_floats var destination_floats: Array[float] = untyped_basic destination_floats[0] -= 0.74 - Utils.check(str(source_floats) == '[999]') - Utils.check(str(untyped_basic) == '[999]') - Utils.check(str(destination_floats) == '[999]') + Utils.check(str(source_floats) == '[999.0]') + Utils.check(str(untyped_basic) == '[999.0]') + Utils.check(str(destination_floats) == '[999.0]') Utils.check(destination_floats.get_typed_builtin() == TYPE_FLOAT) var duplicated_floats := empty_floats.duplicate().slice(2, 3) duplicated_floats[0] *= 3 - Utils.check(str(duplicated_floats) == '[789]') + Utils.check(str(duplicated_floats) == '[789.0]') Utils.check(duplicated_floats.get_typed_builtin() == TYPE_FLOAT) diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd index 9d3fffd1de..c9ab368f45 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd @@ -62,44 +62,44 @@ func test(): untyped_basic[430.0] = 34.0 inferred_basic[263.0] = 362.0 typed_basic[518.0] = 815.0 - Utils.check(str(empty_floats) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }') - Utils.check(str(untyped_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }') - Utils.check(str(inferred_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }') - Utils.check(str(typed_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }') + Utils.check(str(empty_floats) == '{ 705.0: 507.0, 430.0: 34.0, 263.0: 362.0, 518.0: 815.0 }') + Utils.check(str(untyped_basic) == '{ 705.0: 507.0, 430.0: 34.0, 263.0: 362.0, 518.0: 815.0 }') + Utils.check(str(inferred_basic) == '{ 705.0: 507.0, 430.0: 34.0, 263.0: 362.0, 518.0: 815.0 }') + Utils.check(str(typed_basic) == '{ 705.0: 507.0, 430.0: 34.0, 263.0: 362.0, 518.0: 815.0 }') const constant_float := 950.0 const constant_int := 170 var typed_float := 954.0 var filled_floats: Dictionary[float, float] = { constant_float: constant_int, typed_float: empty_floats[430.0] + empty_floats[263.0] } - Utils.check(str(filled_floats) == '{ 950: 170, 954: 396 }') + Utils.check(str(filled_floats) == '{ 950.0: 170.0, 954.0: 396.0 }') Utils.check(filled_floats.get_typed_key_builtin() == TYPE_FLOAT) Utils.check(filled_floats.get_typed_value_builtin() == TYPE_FLOAT) var casted_floats := { empty_floats[263.0] * 2: empty_floats[263.0] / 2 } as Dictionary[float, float] - Utils.check(str(casted_floats) == '{ 724: 181 }') + Utils.check(str(casted_floats) == '{ 724.0: 181.0 }') Utils.check(casted_floats.get_typed_key_builtin() == TYPE_FLOAT) Utils.check(casted_floats.get_typed_value_builtin() == TYPE_FLOAT) var returned_floats = (func () -> Dictionary[float, float]: return { 554: 455 }).call() - Utils.check(str(returned_floats) == '{ 554: 455 }') + Utils.check(str(returned_floats) == '{ 554.0: 455.0 }') Utils.check(returned_floats.get_typed_key_builtin() == TYPE_FLOAT) Utils.check(returned_floats.get_typed_value_builtin() == TYPE_FLOAT) var passed_floats = floats_identity({ 663.0 if randf() > 0.5 else 663.0: 366.0 if randf() <= 0.5 else 366.0 }) - Utils.check(str(passed_floats) == '{ 663: 366 }') + Utils.check(str(passed_floats) == '{ 663.0: 366.0 }') Utils.check(passed_floats.get_typed_key_builtin() == TYPE_FLOAT) Utils.check(passed_floats.get_typed_value_builtin() == TYPE_FLOAT) var default_floats = (func (floats: Dictionary[float, float] = { 364.0: 463.0 }): return floats).call() - Utils.check(str(default_floats) == '{ 364: 463 }') + Utils.check(str(default_floats) == '{ 364.0: 463.0 }') Utils.check(default_floats.get_typed_key_builtin() == TYPE_FLOAT) Utils.check(default_floats.get_typed_value_builtin() == TYPE_FLOAT) var typed_int := 556 var converted_floats: Dictionary[float, float] = { typed_int: typed_int } converted_floats[498.0] = 894 - Utils.check(str(converted_floats) == '{ 556: 556, 498: 894 }') + Utils.check(str(converted_floats) == '{ 556.0: 556.0, 498.0: 894.0 }') Utils.check(converted_floats.get_typed_key_builtin() == TYPE_FLOAT) Utils.check(converted_floats.get_typed_value_builtin() == TYPE_FLOAT) @@ -110,7 +110,7 @@ func test(): Utils.check(constant_basic.get_typed_value_builtin() == TYPE_NIL) const constant_floats: Dictionary[float, float] = { constant_float - constant_basic[228] - constant_int: constant_float + constant_basic[228] + constant_int } - Utils.check(str(constant_floats) == '{ -42: 1942 }') + Utils.check(str(constant_floats) == '{ -42.0: 1942.0 }') Utils.check(constant_floats.get_typed_key_builtin() == TYPE_FLOAT) Utils.check(constant_floats.get_typed_value_builtin() == TYPE_FLOAT) @@ -119,9 +119,9 @@ func test(): untyped_basic = source_floats var destination_floats: Dictionary[float, float] = untyped_basic destination_floats[999.74] -= 0.999 - Utils.check(str(source_floats) == '{ 999.74: 47 }') - Utils.check(str(untyped_basic) == '{ 999.74: 47 }') - Utils.check(str(destination_floats) == '{ 999.74: 47 }') + Utils.check(str(source_floats) == '{ 999.74: 47.0 }') + Utils.check(str(untyped_basic) == '{ 999.74: 47.0 }') + Utils.check(str(destination_floats) == '{ 999.74: 47.0 }') Utils.check(destination_floats.get_typed_key_builtin() == TYPE_FLOAT) Utils.check(destination_floats.get_typed_value_builtin() == TYPE_FLOAT) @@ -131,7 +131,7 @@ func test(): duplicated_floats.erase(430.0) duplicated_floats.erase(518.0) duplicated_floats[263.0] *= 3 - Utils.check(str(duplicated_floats) == '{ 263: 1086 }') + Utils.check(str(duplicated_floats) == '{ 263.0: 1086.0 }') Utils.check(duplicated_floats.get_typed_key_builtin() == TYPE_FLOAT) Utils.check(duplicated_floats.get_typed_value_builtin() == TYPE_FLOAT) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out index 0e0d607831..cfe91e00bd 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out @@ -6,6 +6,6 @@ GDTEST_OK >> WARNING >> Line: 5 >> SHADOWED_VARIABLE ->> The local variable "a" is shadowing an already-declared variable at line 1. +>> The local variable "a" is shadowing an already-declared variable at line 1 in the current class. 1 2 diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out index 228a510490..ae0f2d8b8b 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out @@ -10,6 +10,6 @@ GDTEST_OK >> WARNING >> Line: 5 >> SHADOWED_VARIABLE ->> The local variable "a" is shadowing an already-declared variable at line 1. +>> The local variable "a" is shadowing an already-declared variable at line 1 in the current class. 1 2 diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out index 0d20e9f7a0..101d27df9d 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out @@ -6,7 +6,7 @@ GDTEST_OK >> WARNING >> Line: 6 >> SHADOWED_VARIABLE ->> The local variable "a" is shadowing an already-declared variable at line 1. +>> The local variable "a" is shadowing an already-declared variable at line 1 in the current class. 1 2 1 diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out index a98d80514c..5d059b9193 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out @@ -2,5 +2,5 @@ GDTEST_OK >> WARNING >> Line: 4 >> SHADOWED_VARIABLE ->> The local function parameter "shadow" is shadowing an already-declared variable at line 1. +>> The local function parameter "shadow" is shadowing an already-declared variable at line 1 in the current class. shadow diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowing_base.notest.gd b/modules/gdscript/tests/scripts/analyzer/warnings/shadowing_base.notest.gd new file mode 100644 index 0000000000..5819246ded --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowing_base.notest.gd @@ -0,0 +1,7 @@ +class_name ShadowingBase + +const base_const_member = 1 +var base_variable_member + +func base_function_member(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd index 939e787ea5..6a16ae6bcc 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd +++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd @@ -1,4 +1,5 @@ class_name ShadowedClass +extends ShadowingBase var member: int = 0 @@ -7,6 +8,7 @@ var print_debug := 'print_debug' var print := 'print' @warning_ignore("unused_variable") +@warning_ignore("unused_local_constant") func test(): var Array := 'Array' var Node := 'Node' @@ -15,5 +17,8 @@ func test(): var member := 'member' var reference := 'reference' var ShadowedClass := 'ShadowedClass' + var base_variable_member + const base_function_member = 1 + var base_const_member print('warn') diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out index 8297eed4b8..075f5d3225 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out @@ -1,34 +1,46 @@ GDTEST_OK >> WARNING ->> Line: 5 +>> Line: 6 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "print_debug" has the same name as a built-in function. >> WARNING ->> Line: 11 +>> Line: 13 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "Array" has the same name as a built-in type. >> WARNING ->> Line: 12 +>> Line: 14 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "Node" has the same name as a native class. >> WARNING ->> Line: 13 +>> Line: 15 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "is_same" has the same name as a built-in function. >> WARNING ->> Line: 14 +>> Line: 16 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "sqrt" has the same name as a built-in function. >> WARNING ->> Line: 15 +>> Line: 17 >> SHADOWED_VARIABLE ->> The local variable "member" is shadowing an already-declared variable at line 3. +>> The local variable "member" is shadowing an already-declared variable at line 4 in the current class. >> WARNING ->> Line: 16 +>> Line: 18 >> SHADOWED_VARIABLE_BASE_CLASS ->> The local variable "reference" is shadowing an already-declared method at the base class "RefCounted". +>> The local variable "reference" is shadowing an already-declared method in the base class "RefCounted". >> WARNING ->> Line: 17 +>> Line: 19 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "ShadowedClass" has the same name as a global class defined in "shadowning.gd". +>> WARNING +>> Line: 20 +>> SHADOWED_VARIABLE_BASE_CLASS +>> The local variable "base_variable_member" is shadowing an already-declared variable at line 4 in the base class "ShadowingBase". +>> WARNING +>> Line: 21 +>> SHADOWED_VARIABLE_BASE_CLASS +>> The local constant "base_function_member" is shadowing an already-declared function at line 6 in the base class "ShadowingBase". +>> WARNING +>> Line: 22 +>> SHADOWED_VARIABLE_BASE_CLASS +>> The local variable "base_const_member" is shadowing an already-declared constant at line 3 in the base class "ShadowingBase". warn diff --git a/modules/gdscript/tests/scripts/parser/features/export_arrays.out b/modules/gdscript/tests/scripts/parser/features/export_arrays.out index f1522d096f..7201d8082d 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_arrays.out +++ b/modules/gdscript/tests/scripts/parser/features/export_arrays.out @@ -80,21 +80,21 @@ var test_placeholder: Array var test_placeholder_packed: PackedStringArray hint=TYPE_STRING hint_string="<String>/<PLACEHOLDER_TEXT>:Placeholder" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_int: Array - hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" + hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_int_packed_byte: PackedByteArray - hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" + hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_int_packed32: PackedInt32Array - hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" + hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_int_packed64: PackedInt64Array - hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" + hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_int_float_step: Array - hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10,0.01" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" + hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0,0.01" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_float: Array - hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" + hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_float_packed32: PackedFloat32Array - hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" + hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_float_packed64: PackedFloat64Array - hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" + hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_exp_easing: Array hint=TYPE_STRING hint_string="<float>/<EXP_EASING>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_exp_easing_packed32: PackedFloat32Array @@ -126,14 +126,14 @@ var test_weak_packed_vector3_array: PackedVector3Array var test_weak_packed_vector4_array: PackedVector4Array hint=TYPE_STRING hint_string="<Vector4>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_weak_packed_byte_array: PackedByteArray - hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" + hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_weak_packed_int32_array: PackedInt32Array - hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" + hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_weak_packed_int64_array: PackedInt64Array - hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" + hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_weak_packed_float32_array: PackedFloat32Array - hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" + hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_weak_packed_float64_array: PackedFloat64Array - hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" + hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_noalpha_weak_packed_color_array: PackedColorArray hint=TYPE_STRING hint_string="<Color>/<COLOR_NO_ALPHA>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.out b/modules/gdscript/tests/scripts/parser/features/export_variable.out index 0d915e00e6..c0bf4d6e06 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_variable.out +++ b/modules/gdscript/tests/scripts/parser/features/export_variable.out @@ -4,11 +4,11 @@ var test_weak_int: int = 1 var test_hard_int: int = 2 hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range: int = 100 - hint=RANGE hint_string="0,100" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" + hint=RANGE hint_string="0.0,100.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_step: int = 101 - hint=RANGE hint_string="0,100,1" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" + hint=RANGE hint_string="0.0,100.0,1.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_range_step_or_greater: int = 102 - hint=RANGE hint_string="0,100,1,or_greater" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" + hint=RANGE hint_string="0.0,100.0,1.0,or_greater" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_color: Color = Color(0, 0, 0, 1) hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_color_no_alpha: Color = Color(0, 0, 0, 1) diff --git a/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out b/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out index c5958365ec..d94cbe5556 100644 --- a/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out +++ b/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out @@ -13,4 +13,4 @@ true 0 -255 256 -2 +2.0 diff --git a/modules/gdscript/tests/scripts/parser/features/number_separators.out b/modules/gdscript/tests/scripts/parser/features/number_separators.out index b0d2fd94fe..9407af9cd8 100644 --- a/modules/gdscript/tests/scripts/parser/features/number_separators.out +++ b/modules/gdscript/tests/scripts/parser/features/number_separators.out @@ -13,12 +13,12 @@ GDTEST_OK --- -1234.4567 -1234.4567 --1234 --1234 +-1234.0 +-1234.0 0.4567 0.4567 --- --1234500 --1234500 --1234500 --1234500 +-1234500.0 +-1234500.0 +-1234500.0 +-1234500.0 diff --git a/modules/gdscript/tests/scripts/parser/features/operator_assign.out b/modules/gdscript/tests/scripts/parser/features/operator_assign.out index b0cb63ef59..29910adf38 100644 --- a/modules/gdscript/tests/scripts/parser/features/operator_assign.out +++ b/modules/gdscript/tests/scripts/parser/features/operator_assign.out @@ -1,2 +1,2 @@ GDTEST_OK -8 +8.0 diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out index 75fa01f928..04df229f66 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out @@ -6,4 +6,4 @@ GDTEST_OK >> WARNING >> Line: 8 >> SHADOWED_VARIABLE ->> The local constant "TEST" is shadowing an already-declared constant at line 2. +>> The local constant "TEST" is shadowing an already-declared constant at line 2 in the current class. diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out index aab27e78e2..4a6964f503 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out @@ -6,4 +6,4 @@ GDTEST_OK >> WARNING >> Line: 8 >> SHADOWED_VARIABLE ->> The local variable "foo" is shadowing an already-declared variable at line 1. +>> The local variable "foo" is shadowing an already-declared variable at line 1 in the current class. diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out index e3cd358126..45fb771829 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out @@ -6,4 +6,4 @@ GDTEST_OK >> WARNING >> Line: 2 >> SHADOWED_VARIABLE ->> The local variable "test" is shadowing an already-declared function at line 1. +>> The local variable "test" is shadowing an already-declared function at line 1 in the current class. diff --git a/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out b/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out index 22929bf636..04b0773991 100644 --- a/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out +++ b/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out @@ -1,7 +1,7 @@ GDTEST_OK -{ 1: (2, 0) } -{ 3: (4, 0) } -[[(5, 0)]] -[[(6, 0)]] -[[(7, 0)]] -[X: (8, 9, 7), Y: (0, 1, 0), Z: (0, 0, 1), O: (0, 0, 0)] +{ 1: (2.0, 0.0) } +{ 3: (4.0, 0.0) } +[[(5.0, 0.0)]] +[[(6.0, 0.0)]] +[[(7.0, 0.0)]] +[X: (8.0, 9.0, 7.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0), O: (0.0, 0.0, 0.0)] diff --git a/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out b/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out index a9ef4919cf..78ea2a2d80 100644 --- a/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out +++ b/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out @@ -1,8 +1,8 @@ GDTEST_OK -x is 1 +x is 1.0 typeof x is 3 -x is 2 +x is 2.0 typeof x is 3 -x is 3 +x is 3.0 typeof x is 3 ok diff --git a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out index 5b981bc8bb..1650acadb5 100644 --- a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out +++ b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out @@ -2,16 +2,16 @@ GDTEST_OK >> WARNING >> Line: 5 >> SHADOWED_VARIABLE ->> The local function parameter "a" is shadowing an already-declared variable at line 3. +>> The local function parameter "a" is shadowing an already-declared variable at line 3 in the current class. >> WARNING >> Line: 15 >> SHADOWED_VARIABLE ->> The local function parameter "v" is shadowing an already-declared variable at line 13. +>> The local function parameter "v" is shadowing an already-declared variable at line 13 in the current class. a 1 b 1 -(1, 1) -(0, 0) -(6, 1) -(0, 0) +(1.0, 1.0) +(0.0, 0.0) +(6.0, 1.0) +(0.0, 0.0) diff --git a/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out index c51759f481..e82e31bbed 100644 --- a/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out +++ b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out @@ -1,26 +1,26 @@ GDTEST_OK === -prop1 setter (0, 0) -prop1 setter (1, 0) +prop1 setter (0.0, 0.0) +prop1 setter (1.0, 0.0) --- prop1 setter <Inner> subprop getter -subprop setter (1, 0) +subprop setter (1.0, 0.0) === prop2 setter <Inner> subprop getter -subprop setter (1, 0) +subprop setter (1.0, 0.0) === -prop3 setter (0, 0) +prop3 setter (0.0, 0.0) prop3 getter -prop3 setter (1, 0) +prop3 setter (1.0, 0.0) --- prop3 setter <Inner> prop3 getter subprop getter -subprop setter (1, 0) +subprop setter (1.0, 0.0) === prop4 setter <Inner> prop4 getter subprop getter -subprop setter (1, 0) +subprop setter (1.0, 0.0) diff --git a/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out index 31b3b3a3a8..8617a65c33 100644 --- a/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out +++ b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out @@ -1,4 +1,4 @@ GDTEST_OK -setting vec from (0, 0) to (2, 0) -setting vec from (0, 0) to (0, 2) -vec is (0, 0) +setting vec from (0.0, 0.0) to (2.0, 0.0) +setting vec from (0.0, 0.0) to (0.0, 2.0) +vec is (0.0, 0.0) diff --git a/modules/gdscript/tests/scripts/runtime/features/stringify.out b/modules/gdscript/tests/scripts/runtime/features/stringify.out index 7833b6e213..2463d70ef4 100644 --- a/modules/gdscript/tests/scripts/runtime/features/stringify.out +++ b/modules/gdscript/tests/scripts/runtime/features/stringify.out @@ -9,13 +9,13 @@ hello world [P: (0, 0), S: (0, 0)] (0.25, 0.25, 0.25) (0, 0, 0) -[X: (1, 0), Y: (0, 1), O: (0, 0)] -[N: (1, 2, 3), D: 4] -(1, 2, 3, 4) -[P: (0, 0, 0), S: (1, 1, 1)] -[X: (1, 0, 0), Y: (0, 1, 0), Z: (0, 0, 1)] -[X: (1, 0, 0), Y: (0, 1, 0), Z: (0, 0, 1), O: (0, 0, 0)] +[X: (1.0, 0.0), Y: (0.0, 1.0), O: (0.0, 0.0)] +[N: (1.0, 2.0, 3.0), D: 4] (1, 2, 3, 4) +[P: (0.0, 0.0, 0.0), S: (1.0, 1.0, 1.0)] +[X: (1.0, 0.0, 0.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0)] +[X: (1.0, 0.0, 0.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0), O: (0.0, 0.0, 0.0)] +(1.0, 2.0, 3.0, 4.0) hello hello/world RID(0) @@ -26,10 +26,10 @@ Node::[signal]property_list_changed [255, 0, 1] [-1, 0, 1] [-1, 0, 1] -[-1, 0, 1] -[-1, 0, 1] +[-1.0, 0.0, 1.0] +[-1.0, 0.0, 1.0] ["hello", "world"] -[(1, 1), (0, 0)] -[(1, 1, 1), (0, 0, 0)] -[(1, 0, 0, 1), (0, 0, 1, 1), (0, 1, 0, 1)] +[(1.0, 1.0), (0.0, 0.0)] +[(1.0, 1.0, 1.0), (0.0, 0.0, 0.0)] +[(1.0, 0.0, 0.0, 1.0), (0.0, 0.0, 1.0, 1.0), (0.0, 1.0, 0.0, 1.0)] [(1, 1, 1, 1), (0, 0, 0, 0)] diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd index fa289e442f..225bcb3008 100644 --- a/modules/gdscript/tests/scripts/utils.notest.gd +++ b/modules/gdscript/tests/scripts/utils.notest.gd @@ -1,6 +1,5 @@ class_name Utils - # `assert()` is not evaluated in non-debug builds. Do not use `assert()` # for anything other than testing the `assert()` itself. static func check(condition: Variant) -> void: diff --git a/modules/gltf/config.py b/modules/gltf/config.py index 67233db579..823b8dbec2 100644 --- a/modules/gltf/config.py +++ b/modules/gltf/config.py @@ -20,6 +20,7 @@ def get_doc_classes(): "GLTFLight", "GLTFMesh", "GLTFNode", + "GLTFObjectModelProperty", "GLTFPhysicsBody", "GLTFPhysicsShape", "GLTFSkeleton", diff --git a/modules/gltf/doc_classes/GLTFAccessor.xml b/modules/gltf/doc_classes/GLTFAccessor.xml index bc142797a3..04fa2a9835 100644 --- a/modules/gltf/doc_classes/GLTFAccessor.xml +++ b/modules/gltf/doc_classes/GLTFAccessor.xml @@ -22,7 +22,7 @@ The offset relative to the start of the buffer view in bytes. </member> <member name="component_type" type="int" setter="set_component_type" getter="get_component_type" default="0"> - The glTF component type as an enum. Possible values are 5120 for "BYTE", 5121 for "UNSIGNED_BYTE", 5122 for "SHORT", 5123 for "UNSIGNED_SHORT", 5125 for "UNSIGNED_INT", and 5126 for "FLOAT". A value of 5125 or "UNSIGNED_INT" must not be used for any accessor that is not referenced by mesh.primitive.indices. + The glTF component type as an enum. See [enum GLTFComponentType] for possible values. Within the core glTF specification, a value of 5125 or "UNSIGNED_INT" must not be used for any accessor that is not referenced by mesh.primitive.indices. </member> <member name="count" type="int" setter="set_count" getter="get_count" default="0"> The number of elements referenced by this accessor. @@ -80,5 +80,41 @@ <constant name="TYPE_MAT4" value="6" enum="GLTFAccessorType"> Accessor type "MAT4". For the glTF object model, this maps to "float4x4", represented in the glTF JSON as an array of sixteen floats. </constant> + <constant name="COMPONENT_TYPE_NONE" value="0" enum="GLTFComponentType"> + Component type "NONE". This is not a valid component type, and is used to indicate that the component type is not set. + </constant> + <constant name="COMPONENT_TYPE_SIGNED_BYTE" value="5120" enum="GLTFComponentType"> + Component type "BYTE". The value is [code]0x1400[/code] which comes from OpenGL. This indicates data is stored in 1-byte or 8-bit signed integers. This is a core part of the glTF specification. + </constant> + <constant name="COMPONENT_TYPE_UNSIGNED_BYTE" value="5121" enum="GLTFComponentType"> + Component type "UNSIGNED_BYTE". The value is [code]0x1401[/code] which comes from OpenGL. This indicates data is stored in 1-byte or 8-bit unsigned integers. This is a core part of the glTF specification. + </constant> + <constant name="COMPONENT_TYPE_SIGNED_SHORT" value="5122" enum="GLTFComponentType"> + Component type "SHORT". The value is [code]0x1402[/code] which comes from OpenGL. This indicates data is stored in 2-byte or 16-bit signed integers. This is a core part of the glTF specification. + </constant> + <constant name="COMPONENT_TYPE_UNSIGNED_SHORT" value="5123" enum="GLTFComponentType"> + Component type "UNSIGNED_SHORT". The value is [code]0x1403[/code] which comes from OpenGL. This indicates data is stored in 2-byte or 16-bit unsigned integers. This is a core part of the glTF specification. + </constant> + <constant name="COMPONENT_TYPE_SIGNED_INT" value="5124" enum="GLTFComponentType"> + Component type "INT". The value is [code]0x1404[/code] which comes from OpenGL. This indicates data is stored in 4-byte or 32-bit signed integers. This is NOT a core part of the glTF specification, and may not be supported by all glTF importers. May be used by some extensions including [code]KHR_interactivity[/code]. + </constant> + <constant name="COMPONENT_TYPE_UNSIGNED_INT" value="5125" enum="GLTFComponentType"> + Component type "UNSIGNED_INT". The value is [code]0x1405[/code] which comes from OpenGL. This indicates data is stored in 4-byte or 32-bit unsigned integers. This is a core part of the glTF specification. + </constant> + <constant name="COMPONENT_TYPE_SINGLE_FLOAT" value="5126" enum="GLTFComponentType"> + Component type "FLOAT". The value is [code]0x1406[/code] which comes from OpenGL. This indicates data is stored in 4-byte or 32-bit floating point numbers. This is a core part of the glTF specification. + </constant> + <constant name="COMPONENT_TYPE_DOUBLE_FLOAT" value="5130" enum="GLTFComponentType"> + Component type "DOUBLE". The value is [code]0x140A[/code] which comes from OpenGL. This indicates data is stored in 8-byte or 64-bit floating point numbers. This is NOT a core part of the glTF specification, and may not be supported by all glTF importers. May be used by some extensions including [code]KHR_interactivity[/code]. + </constant> + <constant name="COMPONENT_TYPE_HALF_FLOAT" value="5131" enum="GLTFComponentType"> + Component type "HALF_FLOAT". The value is [code]0x140B[/code] which comes from OpenGL. This indicates data is stored in 2-byte or 16-bit floating point numbers. This is NOT a core part of the glTF specification, and may not be supported by all glTF importers. May be used by some extensions including [code]KHR_interactivity[/code]. + </constant> + <constant name="COMPONENT_TYPE_SIGNED_LONG" value="5134" enum="GLTFComponentType"> + Component type "LONG". The value is [code]0x140E[/code] which comes from OpenGL. This indicates data is stored in 8-byte or 64-bit signed integers. This is NOT a core part of the glTF specification, and may not be supported by all glTF importers. May be used by some extensions including [code]KHR_interactivity[/code]. + </constant> + <constant name="COMPONENT_TYPE_UNSIGNED_LONG" value="5135" enum="GLTFComponentType"> + Component type "UNSIGNED_LONG". The value is [code]0x140F[/code] which comes from OpenGL. This indicates data is stored in 8-byte or 64-bit unsigned integers. This is NOT a core part of the glTF specification, and may not be supported by all glTF importers. May be used by some extensions including [code]KHR_interactivity[/code]. + </constant> </constants> </class> diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml index 10534594d3..ffc3ab926c 100644 --- a/modules/gltf/doc_classes/GLTFDocument.xml +++ b/modules/gltf/doc_classes/GLTFDocument.xml @@ -45,6 +45,16 @@ Takes a Godot Engine scene node and exports it and its descendants to the given [GLTFState] object through the [param state] parameter. </description> </method> + <method name="export_object_model_property" qualifiers="static"> + <return type="GLTFObjectModelProperty" /> + <param index="0" name="state" type="GLTFState" /> + <param index="1" name="node_path" type="NodePath" /> + <param index="2" name="godot_node" type="Node" /> + <param index="3" name="gltf_node_index" type="int" /> + <description> + Determines a mapping between the given Godot [param node_path] and the corresponding glTF Object Model JSON pointer(s) in the generated glTF file. The details of this mapping are returned in a [GLTFObjectModelProperty] object. Additional mappings can be supplied via the [method GLTFDocumentExtension._import_object_model_property] callback method. + </description> + </method> <method name="generate_buffer"> <return type="PackedByteArray" /> <param index="0" name="state" type="GLTFState" /> @@ -70,6 +80,14 @@ [b]Note:[/b] If this method is run before a GLTFDocumentExtension is registered, its extensions won't be included in the list. Be sure to only run this method after all extensions are registered. If you run this when the engine starts, consider waiting a frame before calling this method to ensure all extensions are registered. </description> </method> + <method name="import_object_model_property" qualifiers="static"> + <return type="GLTFObjectModelProperty" /> + <param index="0" name="state" type="GLTFState" /> + <param index="1" name="json_pointer" type="String" /> + <description> + Determines a mapping between the given glTF Object Model [param json_pointer] and the corresponding Godot node path(s) in the generated Godot scene. The details of this mapping are returned in a [GLTFObjectModelProperty] object. Additional mappings can be supplied via the [method GLTFDocumentExtension._export_object_model_property] callback method. + </description> + </method> <method name="register_gltf_document_extension" qualifiers="static"> <return type="void" /> <param index="0" name="extension" type="GLTFDocumentExtension" /> diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml index b33e296e1c..8fcb925a48 100644 --- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml +++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml @@ -33,6 +33,20 @@ This method can be used to modify the final JSON of each node. Data should be primarily stored in [param gltf_node] prior to serializing the JSON, but the original Godot [param node] is also provided if available. The node may be null if not available, such as when exporting glTF data not generated from a Godot scene. </description> </method> + <method name="_export_object_model_property" qualifiers="virtual"> + <return type="GLTFObjectModelProperty" /> + <param index="0" name="state" type="GLTFState" /> + <param index="1" name="node_path" type="NodePath" /> + <param index="2" name="godot_node" type="Node" /> + <param index="3" name="gltf_node_index" type="int" /> + <param index="4" name="target_object" type="Object" /> + <param index="5" name="target_depth" type="int" /> + <description> + Part of the export process. Allows GLTFDocumentExtension classes to provide mappings for properties of nodes in the Godot scene tree, to JSON pointers to glTF properties, as defined by the glTF object model. + Returns a [GLTFObjectModelProperty] instance that defines how the property should be mapped. If your extension can't handle the property, return null, or an instance without any JSON pointers (see [method GLTFObjectModelProperty.has_json_pointers]). You should use [method GLTFObjectModelProperty.set_types] to set the types, and set the JSON pointer(s) using the [member GLTFObjectModelProperty.json_pointers] property. + The parameters provide context for the property, including the NodePath, the Godot node, the GLTF node index, and the target object. The [param target_object] will be equal to [param godot_node] if no sub-object can be found, otherwise it will point to a sub-object. For example, if the path is [code]^"A/B/C/MeshInstance3D:mesh:surface_0/material:emission_intensity"[/code], it will get the node, then the mesh, and then the material, so [param target_object] will be the [Material] resource, and [param target_depth] will be 2 because 2 levels were traversed to get to the target. + </description> + </method> <method name="_export_post" qualifiers="virtual"> <return type="int" enum="Error" /> <param index="0" name="state" type="GLTFState" /> @@ -109,6 +123,17 @@ This method can be used to make modifications to each of the generated Godot scene nodes. </description> </method> + <method name="_import_object_model_property" qualifiers="virtual"> + <return type="GLTFObjectModelProperty" /> + <param index="0" name="state" type="GLTFState" /> + <param index="1" name="split_json_pointer" type="PackedStringArray" /> + <param index="2" name="partial_paths" type="NodePath[]" /> + <description> + Part of the import process. Allows GLTFDocumentExtension classes to provide mappings for JSON pointers to glTF properties, as defined by the glTF object model, to properties of nodes in the Godot scene tree. + Returns a [GLTFObjectModelProperty] instance that defines how the property should be mapped. If your extension can't handle the property, return null, or an instance without any NodePaths (see [method GLTFObjectModelProperty.has_node_paths]). You should use [method GLTFObjectModelProperty.set_types] to set the types, and [method GLTFObjectModelProperty.append_path_to_property] function is useful for most simple cases. + In many cases, [param partial_paths] will contain the start of a path, allowing the extension to complete the path. For example, for [code]/nodes/3/extensions/MY_ext/prop[/code], Godot will pass you a NodePath that leads to node 3, so the GLTFDocumentExtension class only needs to resolve the last [code]MY_ext/prop[/code] part of the path. In this example, the extension should check [code]split.size() > 4 and split[0] == "nodes" and split[2] == "extensions" and split[3] == "MY_ext"[/code] at the start of the function to check if this JSON pointer applies to it, then it can use [param partial_paths] and handle [code]split[4][/code]. + </description> + </method> <method name="_import_post" qualifiers="virtual"> <return type="int" enum="Error" /> <param index="0" name="state" type="GLTFState" /> diff --git a/modules/gltf/doc_classes/GLTFNode.xml b/modules/gltf/doc_classes/GLTFNode.xml index a242a0d1d8..eb92723a06 100644 --- a/modules/gltf/doc_classes/GLTFNode.xml +++ b/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. </description> </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. This is useful when importing glTF object model pointers with [GLTFObjectModelProperty], for handling extensions such as [code]KHR_animation_pointer[/code] or [code]KHR_interactivity[/code]. + 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"> <return type="void" /> <param index="0" name="extension_name" type="StringName" /> diff --git a/modules/gltf/doc_classes/GLTFObjectModelProperty.xml b/modules/gltf/doc_classes/GLTFObjectModelProperty.xml new file mode 100644 index 0000000000..e983269ccc --- /dev/null +++ b/modules/gltf/doc_classes/GLTFObjectModelProperty.xml @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="GLTFObjectModelProperty" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + Describes how to access a property as defined in the glTF object model. + </brief_description> + <description> + GLTFObjectModelProperty defines a mapping between a property in the glTF object model and a NodePath in the Godot scene tree. This can be used to animate properties in a glTF file using the [code]KHR_animation_pointer[/code] extension, or to access them through an engine-agnostic script such as a behavior graph as defined by the [code]KHR_interactivity[/code] extension. + The glTF property is identified by JSON pointer(s) stored in [member json_pointers], while the Godot property it maps to is defined by [member node_paths]. In most cases [member json_pointers] and [member node_paths] will each only have one item, but in some cases a single glTF JSON pointer will map to multiple Godot properties, or a single Godot property will be mapped to multiple glTF JSON pointers, or it might be a many-to-many relationship. + [Expression] objects can be used to define conversions between the data, such as when glTF defines an angle in radians and Godot uses degrees. The [member object_model_type] property defines the type of data stored in the glTF file as defined by the object model, see [enum GLTFObjectModelType] for possible values. + </description> + <tutorials> + <link title="GLTF Object Model">https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/ObjectModel.adoc</link> + <link title="KHR_animation_pointer GLTF extension">https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_animation_pointer</link> + </tutorials> + <methods> + <method name="append_node_path"> + <return type="void" /> + <param index="0" name="node_path" type="NodePath" /> + <description> + Appends a [NodePath] to [member node_paths]. This can be used by [GLTFDocumentExtension] classes to define how a glTF object model property maps to a Godot property, or multiple Godot properties. Prefer using [method append_path_to_property] for simple cases. Be sure to also call [method set_types] once (the order does not matter). + </description> + </method> + <method name="append_path_to_property"> + <return type="void" /> + <param index="0" name="node_path" type="NodePath" /> + <param index="1" name="prop_name" type="StringName" /> + <description> + High-level wrapper over [method append_node_path] that handles the most common cases. It constructs a new [NodePath] using [param node_path] as a base and appends [param prop_name] to the subpath. Be sure to also call [method set_types] once (the order does not matter). + </description> + </method> + <method name="get_accessor_type" qualifiers="const"> + <return type="int" enum="GLTFAccessor.GLTFAccessorType" /> + <description> + The GLTF accessor type associated with this property's [member object_model_type]. See [member GLTFAccessor.accessor_type] for possible values, and see [enum GLTFObjectModelType] for how the object model type maps to accessor types. + </description> + </method> + <method name="has_json_pointers" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if [member json_pointers] is not empty. This is used during export to determine if a [GLTFObjectModelProperty] can handle converting a Godot property to a glTF object model property. + </description> + </method> + <method name="has_node_paths" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if [member node_paths] is not empty. This is used during import to determine if a [GLTFObjectModelProperty] can handle converting a glTF object model property to a Godot property. + </description> + </method> + <method name="set_types"> + <return type="void" /> + <param index="0" name="variant_type" type="int" enum="Variant.Type" /> + <param index="1" name="obj_model_type" type="int" enum="GLTFObjectModelProperty.GLTFObjectModelType" /> + <description> + Sets the [member variant_type] and [member object_model_type] properties. This is a convenience method to set both properties at once, since they are almost always known at the same time. This method should be called once. Calling it again with the same values will have no effect. + </description> + </method> + </methods> + <members> + <member name="gltf_to_godot_expression" type="Expression" setter="set_gltf_to_godot_expression" getter="get_gltf_to_godot_expression"> + If set, this [Expression] will be used to convert the property value from the glTF object model to the value expected by the Godot property. This is useful when the glTF object model uses a different unit system, or when the data needs to be transformed in some way. If [code]null[/code], the value will be copied as-is. + </member> + <member name="godot_to_gltf_expression" type="Expression" setter="set_godot_to_gltf_expression" getter="get_godot_to_gltf_expression"> + If set, this [Expression] will be used to convert the property value from the Godot property to the value expected by the glTF object model. This is useful when the glTF object model uses a different unit system, or when the data needs to be transformed in some way. If [code]null[/code], the value will be copied as-is. + </member> + <member name="json_pointers" type="PackedStringArray[]" setter="set_json_pointers" getter="get_json_pointers" default="[]"> + The glTF object model JSON pointers used to identify the property in the glTF object model. In most cases, there will be only one item in this array, but niche cases may require multiple pointers. The items are themselves arrays which represent the JSON pointer split into its components. + </member> + <member name="node_paths" type="NodePath[]" setter="set_node_paths" getter="get_node_paths" default="[]"> + An array of [NodePath]s that point to a property, or multiple properties, in the Godot scene tree. On import, this will either be set by [GLTFDocument], or by a [GLTFDocumentExtension] class. For simple cases, use [method append_path_to_property] to add properties to this array. + In most cases [member node_paths] will only have one item, but in some cases a single glTF JSON pointer will map to multiple Godot properties. For example, a [GLTFCamera] or [GLTFLight] used on multiple glTF nodes will be represented by multiple Godot nodes. + </member> + <member name="object_model_type" type="int" setter="set_object_model_type" getter="get_object_model_type" enum="GLTFObjectModelProperty.GLTFObjectModelType" default="0"> + The type of data stored in the glTF file as defined by the object model. This is a superset of the available accessor types, and determines the accessor type. See [enum GLTFObjectModelType] for possible values. + </member> + <member name="variant_type" type="int" setter="set_variant_type" getter="get_variant_type" enum="Variant.Type" default="0"> + The type of data stored in the Godot property. This is the type of the property that the [member node_paths] point to. + </member> + </members> + <constants> + <constant name="GLTF_OBJECT_MODEL_TYPE_UNKNOWN" value="0" enum="GLTFObjectModelType"> + Unknown or not set object model type. If the object model type is set to this value, the real type still needs to be determined. + </constant> + <constant name="GLTF_OBJECT_MODEL_TYPE_BOOL" value="1" enum="GLTFObjectModelType"> + Object model type "bool". Represented in the glTF JSON as a boolean, and encoded in a [GLTFAccessor] as "SCALAR". When encoded in an accessor, a value of 0 is false, and any other value is true. + </constant> + <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT" value="2" enum="GLTFObjectModelType"> + Object model type "float". Represented in the glTF JSON as a number, and encoded in a [GLTFAccessor] as "SCALAR". + </constant> + <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT_ARRAY" value="3" enum="GLTFObjectModelType"> + Object model type "float[lb][rb]". Represented in the glTF JSON as an array of numbers, and encoded in a [GLTFAccessor] as "SCALAR". + </constant> + <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT2" value="4" enum="GLTFObjectModelType"> + Object model type "float2". Represented in the glTF JSON as an array of two numbers, and encoded in a [GLTFAccessor] as "VEC2". + </constant> + <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT3" value="5" enum="GLTFObjectModelType"> + Object model type "float3". Represented in the glTF JSON as an array of three numbers, and encoded in a [GLTFAccessor] as "VEC3". + </constant> + <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT4" value="6" enum="GLTFObjectModelType"> + Object model type "float4". Represented in the glTF JSON as an array of four numbers, and encoded in a [GLTFAccessor] as "VEC4". + </constant> + <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT2X2" value="7" enum="GLTFObjectModelType"> + Object model type "float2x2". Represented in the glTF JSON as an array of four numbers, and encoded in a [GLTFAccessor] as "MAT2". + </constant> + <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT3X3" value="8" enum="GLTFObjectModelType"> + Object model type "float3x3". Represented in the glTF JSON as an array of nine numbers, and encoded in a [GLTFAccessor] as "MAT3". + </constant> + <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT4X4" value="9" enum="GLTFObjectModelType"> + Object model type "float4x4". Represented in the glTF JSON as an array of sixteen numbers, and encoded in a [GLTFAccessor] as "MAT4". + </constant> + <constant name="GLTF_OBJECT_MODEL_TYPE_INT" value="10" enum="GLTFObjectModelType"> + Object model type "int". Represented in the glTF JSON as a number, and encoded in a [GLTFAccessor] as "SCALAR". The range of values is limited to signed integers. For [code]KHR_interactivity[/code], only 32-bit integers are supported. + </constant> + </constants> +</class> diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp index 022d2e4477..872054ec2e 100644 --- a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp +++ b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp @@ -88,7 +88,7 @@ void SceneExporterGLTFPlugin::_popup_gltf_export_dialog() { } _file_dialog->set_current_file(filename + String(".gltf")); // Generate and refresh the export settings. - _export_settings->generate_property_list(_gltf_document); + _export_settings->generate_property_list(_gltf_document, root); _settings_inspector->edit(nullptr); _settings_inspector->edit(_export_settings.ptr()); // Show the file dialog. diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp index 511da078d8..c14e92c3a0 100644 --- a/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp +++ b/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp @@ -129,7 +129,7 @@ String get_friendly_config_prefix(Ref<GLTFDocumentExtension> p_extension) { } // Run this before popping up the export settings, because the extensions may have changed. -void EditorSceneExporterGLTFSettings::generate_property_list(Ref<GLTFDocument> p_document) { +void EditorSceneExporterGLTFSettings::generate_property_list(Ref<GLTFDocument> p_document, Node *p_root) { _property_list.clear(); _document = p_document; String image_format_hint_string = "None,PNG,JPEG"; diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_settings.h b/modules/gltf/editor/editor_scene_exporter_gltf_settings.h index 898cddfd68..aa0e54078d 100644 --- a/modules/gltf/editor/editor_scene_exporter_gltf_settings.h +++ b/modules/gltf/editor/editor_scene_exporter_gltf_settings.h @@ -55,7 +55,7 @@ protected: bool _get_extension_setting(const String &p_name_str, Variant &r_ret) const; public: - void generate_property_list(Ref<GLTFDocument> p_document); + void generate_property_list(Ref<GLTFDocument> p_document, Node *p_root = nullptr); String get_copyright() const; void set_copyright(const String &p_copyright); diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index 542e00e560..2db46adef4 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -319,6 +319,8 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ state->set_import_as_skeleton_bones(true); } state->set_scene_name(blend_basename); + state->set_extract_path(p_path.get_base_dir()); + state->set_extract_prefix(blend_basename); err = gltf->append_from_file(sink.get_basename() + ".gltf", state, p_flags, base_dir); if (err != OK) { if (r_err) { diff --git a/modules/gltf/extensions/gltf_document_extension.cpp b/modules/gltf/extensions/gltf_document_extension.cpp index 6e611762b6..0806eee6bf 100644 --- a/modules/gltf/extensions/gltf_document_extension.cpp +++ b/modules/gltf/extensions/gltf_document_extension.cpp @@ -38,6 +38,7 @@ void GLTFDocumentExtension::_bind_methods() { GDVIRTUAL_BIND(_parse_image_data, "state", "image_data", "mime_type", "ret_image"); GDVIRTUAL_BIND(_get_image_file_extension); GDVIRTUAL_BIND(_parse_texture_json, "state", "texture_json", "ret_gltf_texture"); + GDVIRTUAL_BIND(_import_object_model_property, "state", "split_json_pointer", "partial_paths"); GDVIRTUAL_BIND(_import_post_parse, "state"); GDVIRTUAL_BIND(_import_pre_generate, "state"); GDVIRTUAL_BIND(_generate_scene_node, "state", "gltf_node", "scene_parent"); @@ -48,6 +49,7 @@ void GLTFDocumentExtension::_bind_methods() { GDVIRTUAL_BIND(_convert_scene_node, "state", "gltf_node", "scene_node"); GDVIRTUAL_BIND(_export_post_convert, "state", "root"); GDVIRTUAL_BIND(_export_preserialize, "state"); + GDVIRTUAL_BIND(_export_object_model_property, "state", "node_path", "godot_node", "gltf_node_index", "target_object", "target_depth"); GDVIRTUAL_BIND(_get_saveable_image_formats); GDVIRTUAL_BIND(_serialize_image_to_bytes, "state", "image", "image_dict", "image_format", "lossy_quality"); GDVIRTUAL_BIND(_save_image_at_path, "state", "image", "file_path", "image_format", "lossy_quality"); @@ -100,6 +102,13 @@ Error GLTFDocumentExtension::parse_texture_json(Ref<GLTFState> p_state, const Di return err; } +Ref<GLTFObjectModelProperty> GLTFDocumentExtension::import_object_model_property(Ref<GLTFState> p_state, const PackedStringArray &p_split_json_pointer, const TypedArray<NodePath> &p_partial_paths) { + Ref<GLTFObjectModelProperty> ret; + ERR_FAIL_COND_V(p_state.is_null(), ret); + GDVIRTUAL_CALL(_import_object_model_property, p_state, p_split_json_pointer, p_partial_paths, ret); + return ret; +} + Error GLTFDocumentExtension::import_post_parse(Ref<GLTFState> p_state) { ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); Error err = OK; @@ -169,6 +178,15 @@ Error GLTFDocumentExtension::export_preserialize(Ref<GLTFState> p_state) { return err; } +Ref<GLTFObjectModelProperty> GLTFDocumentExtension::export_object_model_property(Ref<GLTFState> p_state, const NodePath &p_node_path, const Node *p_godot_node, GLTFNodeIndex p_gltf_node_index, const Object *p_target_object, int p_target_depth) { + Ref<GLTFObjectModelProperty> ret; + ERR_FAIL_COND_V(p_state.is_null(), ret); + ERR_FAIL_NULL_V(p_godot_node, ret); + ERR_FAIL_NULL_V(p_target_object, ret); + GDVIRTUAL_CALL(_export_object_model_property, p_state, p_node_path, p_godot_node, p_gltf_node_index, p_target_object, p_target_depth, ret); + return ret; +} + Vector<String> GLTFDocumentExtension::get_saveable_image_formats() { Vector<String> ret; GDVIRTUAL_CALL(_get_saveable_image_formats, ret); diff --git a/modules/gltf/extensions/gltf_document_extension.h b/modules/gltf/extensions/gltf_document_extension.h index b70710e015..a6368ea780 100644 --- a/modules/gltf/extensions/gltf_document_extension.h +++ b/modules/gltf/extensions/gltf_document_extension.h @@ -49,6 +49,7 @@ public: virtual Error parse_image_data(Ref<GLTFState> p_state, const PackedByteArray &p_image_data, const String &p_mime_type, Ref<Image> r_image); virtual String get_image_file_extension(); virtual Error parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture); + virtual Ref<GLTFObjectModelProperty> import_object_model_property(Ref<GLTFState> p_state, const PackedStringArray &p_split_json_pointer, const TypedArray<NodePath> &p_partial_paths); virtual Error import_post_parse(Ref<GLTFState> p_state); virtual Error import_pre_generate(Ref<GLTFState> p_state); virtual Node3D *generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent); @@ -59,6 +60,7 @@ public: virtual void convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node); virtual Error export_post_convert(Ref<GLTFState> p_state, Node *p_root); virtual Error export_preserialize(Ref<GLTFState> p_state); + virtual Ref<GLTFObjectModelProperty> export_object_model_property(Ref<GLTFState> p_state, const NodePath &p_node_path, const Node *p_godot_node, GLTFNodeIndex p_gltf_node_index, const Object *p_target_object, int p_target_depth); virtual Vector<String> get_saveable_image_formats(); virtual PackedByteArray serialize_image_to_bytes(Ref<GLTFState> p_state, Ref<Image> p_image, Dictionary p_image_dict, const String &p_image_format, float p_lossy_quality); virtual Error save_image_at_path(Ref<GLTFState> p_state, Ref<Image> p_image, const String &p_file_path, const String &p_image_format, float p_lossy_quality); @@ -73,6 +75,7 @@ public: GDVIRTUAL4R(Error, _parse_image_data, Ref<GLTFState>, PackedByteArray, String, Ref<Image>); GDVIRTUAL0R(String, _get_image_file_extension); GDVIRTUAL3R(Error, _parse_texture_json, Ref<GLTFState>, Dictionary, Ref<GLTFTexture>); + GDVIRTUAL3R(Ref<GLTFObjectModelProperty>, _import_object_model_property, Ref<GLTFState>, PackedStringArray, TypedArray<NodePath>); GDVIRTUAL1R(Error, _import_post_parse, Ref<GLTFState>); GDVIRTUAL1R(Error, _import_pre_generate, Ref<GLTFState>); GDVIRTUAL3R(Node3D *, _generate_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *); @@ -83,6 +86,7 @@ public: GDVIRTUAL3(_convert_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *); GDVIRTUAL2R(Error, _export_post_convert, Ref<GLTFState>, Node *); GDVIRTUAL1R(Error, _export_preserialize, Ref<GLTFState>); + GDVIRTUAL6R(Ref<GLTFObjectModelProperty>, _export_object_model_property, Ref<GLTFState>, NodePath, const Node *, GLTFNodeIndex, const Object *, int); GDVIRTUAL0R(Vector<String>, _get_saveable_image_formats); GDVIRTUAL5R(PackedByteArray, _serialize_image_to_bytes, Ref<GLTFState>, Ref<Image>, Dictionary, String, float); GDVIRTUAL5R(Error, _save_image_at_path, Ref<GLTFState>, Ref<Image>, String, String, float); diff --git a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp index cde30bce18..b5edd35ad5 100644 --- a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp +++ b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp @@ -63,6 +63,7 @@ Error GLTFDocumentExtensionConvertImporterMesh::import_post(Ref<GLTFState> p_sta mesh_instance_node_3d->set_mesh(array_mesh); mesh_instance_node_3d->set_skin(importer_mesh_3d->get_skin()); mesh_instance_node_3d->set_skeleton_path(importer_mesh_3d->get_skeleton_path()); + mesh_instance_node_3d->set_visible(importer_mesh_3d->is_visible()); node->replace_by(mesh_instance_node_3d); _copy_meta(importer_mesh_3d, mesh_instance_node_3d); _copy_meta(mesh.ptr(), array_mesh.ptr()); diff --git a/modules/gltf/extensions/gltf_light.cpp b/modules/gltf/extensions/gltf_light.cpp index f6e91c1635..2bdcab2f0c 100644 --- a/modules/gltf/extensions/gltf_light.cpp +++ b/modules/gltf/extensions/gltf_light.cpp @@ -30,6 +30,7 @@ #include "gltf_light.h" +#include "../structures/gltf_object_model_property.h" #include "scene/3d/light_3d.h" void GLTFLight::_bind_methods() { @@ -62,6 +63,21 @@ void GLTFLight::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "outer_cone_angle"), "set_outer_cone_angle", "get_outer_cone_angle"); // float } +void GLTFLight::set_cone_inner_attenuation_conversion_expressions(Ref<GLTFObjectModelProperty> &r_obj_model_prop) { + // Expression to convert glTF innerConeAngle to Godot spot_angle_attenuation. + Ref<Expression> gltf_to_godot_expr; + gltf_to_godot_expr.instantiate(); + PackedStringArray gltf_to_godot_args = { "inner_cone_angle" }; + gltf_to_godot_expr->parse("0.2 / (1.0 - inner_cone_angle / spot_angle) - 0.1", gltf_to_godot_args); + r_obj_model_prop->set_gltf_to_godot_expression(gltf_to_godot_expr); + // Expression to convert Godot spot_angle_attenuation to glTF innerConeAngle. + Ref<Expression> godot_to_gltf_expr; + godot_to_gltf_expr.instantiate(); + PackedStringArray godot_to_gltf_args = { "godot_spot_angle_att" }; + godot_to_gltf_expr->parse("spot_angle * maxf(0.0, 1.0 - (0.2 / (0.1 + godot_spot_angle_att)))", godot_to_gltf_args); + r_obj_model_prop->set_godot_to_gltf_expression(godot_to_gltf_expr); +} + Color GLTFLight::get_color() { return color; } diff --git a/modules/gltf/extensions/gltf_light.h b/modules/gltf/extensions/gltf_light.h index e0894fc8c6..3d522bd174 100644 --- a/modules/gltf/extensions/gltf_light.h +++ b/modules/gltf/extensions/gltf_light.h @@ -33,6 +33,7 @@ #include "core/io/resource.h" +class GLTFObjectModelProperty; class Light3D; // https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual @@ -54,6 +55,8 @@ private: Dictionary additional_data; public: + static void set_cone_inner_attenuation_conversion_expressions(Ref<GLTFObjectModelProperty> &r_obj_model_prop); + Color get_color(); void set_color(Color p_color); diff --git a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp index 5c26a1686b..512f25a216 100644 --- a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp +++ b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp @@ -31,8 +31,11 @@ #include "gltf_document_extension_physics.h" #include "scene/3d/physics/area_3d.h" +#include "scene/3d/physics/rigid_body_3d.h" #include "scene/3d/physics/static_body_3d.h" +using GLTFShapeIndex = int64_t; + // Import process. Error GLTFDocumentExtensionPhysics::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) { if (!p_extensions.has("OMI_collider") && !p_extensions.has("OMI_physics_body") && !p_extensions.has("OMI_physics_shape")) { @@ -105,6 +108,7 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state Array state_shapes = p_state->get_additional_data(StringName("GLTFPhysicsShapes")); ERR_FAIL_INDEX_V_MSG(node_shape_index, state_shapes.size(), Error::ERR_FILE_CORRUPT, "glTF Physics: On node " + p_gltf_node->get_name() + ", the shape index " + itos(node_shape_index) + " is not in the state shapes (size: " + itos(state_shapes.size()) + ")."); p_gltf_node->set_additional_data(StringName("GLTFPhysicsColliderShape"), state_shapes[node_shape_index]); + p_gltf_node->set_additional_data(StringName("GLTFPhysicsColliderShapeIndex"), node_shape_index); } else { // If this node is a collider but does not have a collider // shape, then it only serves to combine together shapes. @@ -119,6 +123,7 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state Array state_shapes = p_state->get_additional_data(StringName("GLTFPhysicsShapes")); ERR_FAIL_INDEX_V_MSG(node_shape_index, state_shapes.size(), Error::ERR_FILE_CORRUPT, "glTF Physics: On node " + p_gltf_node->get_name() + ", the shape index " + itos(node_shape_index) + " is not in the state shapes (size: " + itos(state_shapes.size()) + ")."); p_gltf_node->set_additional_data(StringName("GLTFPhysicsTriggerShape"), state_shapes[node_shape_index]); + p_gltf_node->set_additional_data(StringName("GLTFPhysicsTriggerShapeIndex"), node_shape_index); } else { // If this node is a trigger but does not have a trigger shape, // then it's a trigger body, what Godot calls an Area3D node. @@ -129,8 +134,8 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state } // If this node defines explicit member shape nodes, save this information. if (node_trigger.has("nodes")) { - Array node_trigger_nodes = node_trigger["nodes"]; - p_gltf_node->set_additional_data(StringName("GLTFPhysicsCompoundTriggerNodes"), node_trigger_nodes); + Array compound_trigger_nodes = node_trigger["nodes"]; + p_gltf_node->set_additional_data(StringName("GLTFPhysicsCompoundTriggerNodes"), compound_trigger_nodes); } } if (physics_body_ext.has("motion") || physics_body_ext.has("type")) { @@ -140,6 +145,144 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state return OK; } +bool _will_gltf_shape_become_subnode(Ref<GLTFState> p_state, const Ref<GLTFNode> p_gltf_node, GLTFNodeIndex p_gltf_node_index) { + if (p_gltf_node->has_additional_data(StringName("GLTFPhysicsBody"))) { + return true; + } + const TypedArray<GLTFNode> state_gltf_nodes = p_state->get_nodes(); + const GLTFNodeIndex parent_index = p_gltf_node->get_parent(); + if (parent_index == -1 || parent_index >= state_gltf_nodes.size()) { + return true; + } + const Ref<GLTFNode> parent_gltf_node = state_gltf_nodes[parent_index]; + const Variant parent_body_maybe = parent_gltf_node->get_additional_data(StringName("GLTFPhysicsBody")); + if (parent_body_maybe.get_type() != Variant::NIL) { + Ref<GLTFPhysicsBody> parent_body = parent_body_maybe; + // If the parent matches the triggerness, then this node will be generated as a shape (CollisionShape3D). + // Otherwise, if there is a mismatch, a body will be generated for this node, and a subnode will also be generated for the shape. + if (parent_body->get_body_type() == "trigger") { + return p_gltf_node->has_additional_data(StringName("GLTFPhysicsColliderShape")); + } else { + return p_gltf_node->has_additional_data(StringName("GLTFPhysicsTriggerShape")); + } + } + if (parent_gltf_node->has_additional_data(StringName("GLTFPhysicsColliderShape"))) { + return false; + } + if (parent_gltf_node->has_additional_data(StringName("GLTFPhysicsTriggerShape"))) { + return false; + } + Variant compound_trigger_maybe = parent_gltf_node->has_additional_data(StringName("GLTFPhysicsCompoundTriggerNodes")); + if (compound_trigger_maybe.get_type() != Variant::NIL) { + Array compound_trigger_nodes = compound_trigger_maybe; + // Remember, JSON only has numbers, not integers, so must cast to double. + return !compound_trigger_nodes.has((double)p_gltf_node_index); + } + return true; +} + +NodePath _get_scene_node_path_for_shape_index(Ref<GLTFState> p_state, const GLTFNodeIndex p_shape_index) { + TypedArray<GLTFNode> state_gltf_nodes = p_state->get_nodes(); + for (GLTFNodeIndex node_index = 0; node_index < state_gltf_nodes.size(); node_index++) { + const Ref<GLTFNode> gltf_node = state_gltf_nodes[node_index]; + ERR_CONTINUE(gltf_node.is_null()); + // Check if this node has a shape index and if it matches the one we are looking for. + Variant shape_index_maybe = gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShapeIndex")); + if (shape_index_maybe.get_type() != Variant::INT) { + shape_index_maybe = gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShapeIndex")); + if (shape_index_maybe.get_type() != Variant::INT) { + continue; + } + } + const GLTFShapeIndex shape_index = shape_index_maybe; + if (shape_index != p_shape_index) { + continue; + } + NodePath node_path = gltf_node->get_scene_node_path(p_state); + // At this point, we have found a node with the shape index we were looking for. + if (_will_gltf_shape_become_subnode(p_state, gltf_node, node_index)) { + Vector<StringName> sname_path = node_path.get_names(); + sname_path.append(gltf_node->get_name() + "Shape"); + node_path = NodePath(sname_path, false); + } + return node_path; + } + return NodePath(); +} + +Ref<GLTFObjectModelProperty> GLTFDocumentExtensionPhysics::import_object_model_property(Ref<GLTFState> p_state, const PackedStringArray &p_split_json_pointer, const TypedArray<NodePath> &p_partial_paths) { + Ref<GLTFObjectModelProperty> ret; + if (p_split_json_pointer.size() != 6) { + // The only properties this class cares about are exactly 6 levels deep. + return ret; + } + ret.instantiate(); + const String &prop_name = p_split_json_pointer[5]; + if (p_split_json_pointer[0] == "extensions" && p_split_json_pointer[2] == "shapes") { + if (p_split_json_pointer[1] == "OMI_physics_shape" || p_split_json_pointer[1] == "KHR_collision_shapes") { + const GLTFNodeIndex shape_index = p_split_json_pointer[3].to_int(); + NodePath node_path = _get_scene_node_path_for_shape_index(p_state, shape_index); + if (node_path.is_empty()) { + return ret; + } + String godot_prop_name = prop_name; + if (prop_name == "size") { + ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3); + } else if (prop_name == "height" || prop_name == "radius") { + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + } else if (prop_name == "radiusBottom" || prop_name == "radiusTop") { + godot_prop_name = "radius"; + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + } else { + // Not something we handle, return without appending a NodePath. + return ret; + } + // Example: `A/B/C/CollisionShape3D:shape:radius`. + Vector<StringName> subnames; + subnames.append("shape"); + subnames.append(godot_prop_name); + node_path = NodePath(node_path.get_names(), subnames, false); + ret->append_node_path(node_path); + } + } else if (p_split_json_pointer[0] == "nodes" && p_split_json_pointer[2] == "extensions" && p_split_json_pointer[4] == "motion") { + if (p_split_json_pointer[3] == "OMI_physics_body" || p_split_json_pointer[3] == "KHR_physics_rigid_bodies") { + const GLTFNodeIndex node_index = p_split_json_pointer[1].to_int(); + const TypedArray<GLTFNode> all_gltf_nodes = p_state->get_nodes(); + ERR_FAIL_INDEX_V_MSG(node_index, all_gltf_nodes.size(), ret, "GLTF Physics: The node index " + itos(node_index) + " is not in the state nodes (size: " + itos(all_gltf_nodes.size()) + ")."); + const Ref<GLTFNode> gltf_node = all_gltf_nodes[node_index]; + NodePath node_path; + if (p_partial_paths.is_empty()) { + node_path = gltf_node->get_scene_node_path(p_state); + } else { + // The path is already computed for us, just grab it. + node_path = p_partial_paths[0]; + } + if (prop_name == "mass") { + ret->append_path_to_property(node_path, "mass"); + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + } else if (prop_name == "linearVelocity") { + ret->append_path_to_property(node_path, "linear_velocity"); + ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3); + } else if (prop_name == "angularVelocity") { + ret->append_path_to_property(node_path, "angular_velocity"); + ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3); + } else if (prop_name == "centerOfMass") { + ret->append_path_to_property(node_path, "center_of_mass"); + ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3); + } else if (prop_name == "inertiaDiagonal") { + ret->append_path_to_property(node_path, "inertia"); + ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3); + } else if (prop_name == "inertiaOrientation") { + WARN_PRINT("GLTF Physics: The 'inertiaOrientation' property is not supported by Godot."); + } else { + // Not something we handle, return without appending a NodePath. + return ret; + } + } + } + return ret; +} + void _setup_shape_mesh_resource_from_index_if_needed(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_gltf_shape) { GLTFMeshIndex shape_mesh_index = p_gltf_shape->get_mesh_index(); if (shape_mesh_index == -1) { @@ -434,24 +577,126 @@ Array _get_or_create_state_shapes_in_state(Ref<GLTFState> p_state) { return state_shapes; } -Dictionary _export_node_shape(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_physics_shape) { +GLTFShapeIndex _export_node_shape(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_physics_shape) { Array state_shapes = _get_or_create_state_shapes_in_state(p_state); - int size = state_shapes.size(); + GLTFShapeIndex size = state_shapes.size(); Dictionary shape_property; Dictionary shape_dict = p_physics_shape->to_dictionary(); - for (int i = 0; i < size; i++) { + for (GLTFShapeIndex i = 0; i < size; i++) { Dictionary other = state_shapes[i]; if (other == shape_dict) { // De-duplication: If we already have an identical shape, // set the shape index to the existing one and return. - shape_property["shape"] = i; - return shape_property; + return i; } } // If we don't have an identical shape, add it to the array. state_shapes.push_back(shape_dict); - shape_property["shape"] = size; - return shape_property; + return size; +} + +Error GLTFDocumentExtensionPhysics::export_preserialize(Ref<GLTFState> p_state) { + // Note: Need to do _export_node_shape before exporting animations, so export_node is too late. + TypedArray<GLTFNode> state_gltf_nodes = p_state->get_nodes(); + for (Ref<GLTFNode> gltf_node : state_gltf_nodes) { + Ref<GLTFPhysicsShape> collider_shape = gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShape")); + if (collider_shape.is_valid()) { + GLTFShapeIndex collider_shape_index = _export_node_shape(p_state, collider_shape); + gltf_node->set_additional_data(StringName("GLTFPhysicsColliderShapeIndex"), collider_shape_index); + } + Ref<GLTFPhysicsShape> trigger_shape = gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShape")); + if (trigger_shape.is_valid()) { + GLTFShapeIndex trigger_shape_index = _export_node_shape(p_state, trigger_shape); + gltf_node->set_additional_data(StringName("GLTFPhysicsTriggerShapeIndex"), trigger_shape_index); + } + } + return OK; +} + +Ref<GLTFObjectModelProperty> GLTFDocumentExtensionPhysics::export_object_model_property(Ref<GLTFState> p_state, const NodePath &p_node_path, const Node *p_godot_node, GLTFNodeIndex p_gltf_node_index, const Object *p_target_object, int p_target_depth) { + Ref<GLTFObjectModelProperty> ret; + const Vector<StringName> &path_subnames = p_node_path.get_subnames(); + if (path_subnames.is_empty()) { + return ret; + } + ret.instantiate(); + const StringName &node_prop = path_subnames[0]; + if (Object::cast_to<RigidBody3D>(p_target_object)) { + if (path_subnames.size() != 1) { + return ret; + } + // Example: `/nodes/0/extensions/OMI_physics_body/motion/mass` + PackedStringArray split_json_pointer; + split_json_pointer.append("nodes"); + split_json_pointer.append(itos(p_gltf_node_index)); + split_json_pointer.append("extensions"); + split_json_pointer.append("OMI_physics_body"); + split_json_pointer.append("motion"); + if (node_prop == StringName("mass")) { + split_json_pointer.append("mass"); + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + } else if (node_prop == StringName("linear_velocity")) { + split_json_pointer.append("linearVelocity"); + ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3); + } else if (node_prop == StringName("angular_velocity")) { + split_json_pointer.append("angularVelocity"); + ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3); + } else if (node_prop == StringName("center_of_mass")) { + split_json_pointer.append("centerOfMass"); + ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3); + } else if (node_prop == StringName("inertia")) { + split_json_pointer.append("inertiaDiagonal"); + ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3); + } else { + // Not something we handle, return without setting the JSON pointer. + return ret; + } + ret->set_json_pointers({ split_json_pointer }); + } else if (Object::cast_to<CollisionShape3D>(p_godot_node)) { + if (path_subnames.size() != 2) { + return ret; + } + // Example: `/extensions/OMI_physics_shape/shapes/0/box/size` + PackedStringArray split_json_pointer; + split_json_pointer.append("extensions"); + split_json_pointer.append("OMI_physics_shape"); + split_json_pointer.append("shapes"); + TypedArray<GLTFNode> state_gltf_nodes = p_state->get_nodes(); + ERR_FAIL_INDEX_V(p_gltf_node_index, state_gltf_nodes.size(), ret); + Ref<GLTFNode> gltf_node = state_gltf_nodes[p_gltf_node_index]; + Variant shape_index_maybe = gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShapeIndex")); + String shape_type; + if (shape_index_maybe.get_type() == Variant::INT) { + Ref<GLTFPhysicsShape> collider_shape = gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShape")); + shape_type = collider_shape->get_shape_type(); + } else { + shape_index_maybe = gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShapeIndex")); + if (shape_index_maybe.get_type() == Variant::INT) { + Ref<GLTFPhysicsShape> trigger_shape = gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShape")); + shape_type = trigger_shape->get_shape_type(); + } + } + ERR_FAIL_COND_V(shape_index_maybe.get_type() != Variant::INT, ret); + GLTFShapeIndex shape_index = shape_index_maybe; + split_json_pointer.append(itos(shape_index)); + split_json_pointer.append(shape_type); + const StringName &shape_prop = path_subnames[1]; + if (shape_prop == StringName("size")) { + split_json_pointer.append("size"); + ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3); + } else if (shape_prop == StringName("radius")) { + split_json_pointer.append("radius"); + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + } else if (shape_prop == StringName("height")) { + split_json_pointer.append("height"); + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + } else { + // Not something we handle, return without setting the JSON pointer. + return ret; + } + ret->set_json_pointers({ split_json_pointer }); + } + return ret; } Error GLTFDocumentExtensionPhysics::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_node_json, Node *p_node) { @@ -465,13 +710,16 @@ Error GLTFDocumentExtensionPhysics::export_node(Ref<GLTFState> p_state, Ref<GLTF trigger_property["nodes"] = compound_trigger_nodes; } } - Ref<GLTFPhysicsShape> collider_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShape")); - if (collider_shape.is_valid()) { - physics_body_ext["collider"] = _export_node_shape(p_state, collider_shape); + Variant collider_shape_index = p_gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShapeIndex")); + if (collider_shape_index.get_type() == Variant::INT) { + Dictionary collider_dict; + collider_dict["shape"] = collider_shape_index; + physics_body_ext["collider"] = collider_dict; } - Ref<GLTFPhysicsShape> trigger_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShape")); - if (trigger_shape.is_valid()) { - physics_body_ext["trigger"] = _export_node_shape(p_state, trigger_shape); + Variant trigger_shape_index = p_gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShapeIndex")); + if (trigger_shape_index.get_type() == Variant::INT) { + Dictionary trigger_dict = physics_body_ext.get_or_add("trigger", {}); + trigger_dict["shape"] = trigger_shape_index; } if (!physics_body_ext.is_empty()) { Dictionary node_extensions = r_node_json["extensions"]; diff --git a/modules/gltf/extensions/physics/gltf_document_extension_physics.h b/modules/gltf/extensions/physics/gltf_document_extension_physics.h index 3d5027c0df..76a60a6375 100644 --- a/modules/gltf/extensions/physics/gltf_document_extension_physics.h +++ b/modules/gltf/extensions/physics/gltf_document_extension_physics.h @@ -43,9 +43,12 @@ public: Error import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) override; Vector<String> get_supported_extensions() override; Error parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions) override; + Ref<GLTFObjectModelProperty> import_object_model_property(Ref<GLTFState> p_state, const PackedStringArray &p_split_json_pointer, const TypedArray<NodePath> &p_partial_paths) override; Node3D *generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) override; // Export process. void convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node) override; + Error export_preserialize(Ref<GLTFState> p_state) override; + Ref<GLTFObjectModelProperty> export_object_model_property(Ref<GLTFState> p_state, const NodePath &p_node_path, const Node *p_godot_node, GLTFNodeIndex p_gltf_node_index, const Object *p_target_object, int p_target_depth) override; Error export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_node_json, Node *p_scene_node) override; }; diff --git a/modules/gltf/extensions/physics/gltf_physics_body.cpp b/modules/gltf/extensions/physics/gltf_physics_body.cpp index c11aa5d2ff..7c40f96e0a 100644 --- a/modules/gltf/extensions/physics/gltf_physics_body.cpp +++ b/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->center_of_mass = body->get_center_of_mass(); 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)) { physics_body->body_type = PhysicsBodyType::VEHICLE; } else { diff --git a/modules/gltf/gltf_defines.h b/modules/gltf/gltf_defines.h index c1918e5908..4d88f7c342 100644 --- a/modules/gltf/gltf_defines.h +++ b/modules/gltf/gltf_defines.h @@ -43,6 +43,7 @@ class GLTFDocumentExtension; class GLTFLight; class GLTFMesh; class GLTFNode; +class GLTFObjectModelProperty; class GLTFSkeleton; class GLTFSkin; class GLTFSpecGloss; diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 0a487430a3..571a04936d 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -69,6 +69,10 @@ #include <stdlib.h> #include <cstdint> +constexpr int COMPONENT_COUNT_FOR_ACCESSOR_TYPE[7] = { + 1, 2, 3, 4, 4, 9, 16 +}; + static void _attach_extras_to_meta(const Dictionary &p_extras, Ref<Resource> p_node) { if (!p_extras.is_empty()) { p_node->set_meta("extras", p_extras); @@ -1013,7 +1017,7 @@ Error GLTFDocument::_parse_accessors(Ref<GLTFState> p_state) { accessor.instantiate(); ERR_FAIL_COND_V(!d.has("componentType"), ERR_PARSE_ERROR); - accessor->component_type = d["componentType"]; + accessor->component_type = (GLTFAccessor::GLTFComponentType)(int32_t)d["componentType"]; ERR_FAIL_COND_V(!d.has("count"), ERR_PARSE_ERROR); accessor->count = d["count"]; ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR); @@ -1050,7 +1054,7 @@ Error GLTFDocument::_parse_accessors(Ref<GLTFState> p_state) { ERR_FAIL_COND_V(!si.has("bufferView"), ERR_PARSE_ERROR); accessor->sparse_indices_buffer_view = si["bufferView"]; ERR_FAIL_COND_V(!si.has("componentType"), ERR_PARSE_ERROR); - accessor->sparse_indices_component_type = si["componentType"]; + accessor->sparse_indices_component_type = (GLTFAccessor::GLTFComponentType)(int32_t)si["componentType"]; if (si.has("byteOffset")) { accessor->sparse_indices_byte_offset = si["byteOffset"]; @@ -1082,31 +1086,39 @@ double GLTFDocument::_filter_number(double p_float) { return (double)(float)p_float; } -String GLTFDocument::_get_component_type_name(const uint32_t p_component) { +String GLTFDocument::_get_component_type_name(const GLTFAccessor::GLTFComponentType p_component) { switch (p_component) { - case GLTFDocument::COMPONENT_TYPE_BYTE: + case GLTFAccessor::COMPONENT_TYPE_NONE: + return "None"; + case GLTFAccessor::COMPONENT_TYPE_SIGNED_BYTE: return "Byte"; - case GLTFDocument::COMPONENT_TYPE_UNSIGNED_BYTE: + case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_BYTE: return "UByte"; - case GLTFDocument::COMPONENT_TYPE_SHORT: + case GLTFAccessor::COMPONENT_TYPE_SIGNED_SHORT: return "Short"; - case GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT: + case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT: return "UShort"; - case GLTFDocument::COMPONENT_TYPE_INT: + case GLTFAccessor::COMPONENT_TYPE_SIGNED_INT: return "Int"; - case GLTFDocument::COMPONENT_TYPE_FLOAT: + case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_INT: + return "UInt"; + case GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT: return "Float"; + case GLTFAccessor::COMPONENT_TYPE_DOUBLE_FLOAT: + return "Double"; + case GLTFAccessor::COMPONENT_TYPE_HALF_FLOAT: + return "Half"; + case GLTFAccessor::COMPONENT_TYPE_SIGNED_LONG: + return "Long"; + case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_LONG: + return "ULong"; } return "<Error>"; } -Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_src, const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type, const int p_component_type, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex, GLTFBufferViewIndex &r_accessor, const bool p_for_vertex_indices) { - const int component_count_for_type[7] = { - 1, 2, 3, 4, 4, 9, 16 - }; - - const int component_count = component_count_for_type[p_accessor_type]; +Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_src, const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type, const GLTFAccessor::GLTFComponentType p_component_type, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex, GLTFBufferViewIndex &r_accessor, const bool p_for_vertex_indices) { + const int component_count = COMPONENT_COUNT_FOR_ACCESSOR_TYPE[p_accessor_type]; const int component_size = _get_component_type_size(p_component_type); ERR_FAIL_COND_V(component_size == 0, FAILED); @@ -1114,8 +1126,8 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_ int skip_bytes = 0; //special case of alignments, as described in spec switch (p_component_type) { - case COMPONENT_TYPE_BYTE: - case COMPONENT_TYPE_UNSIGNED_BYTE: { + case GLTFAccessor::COMPONENT_TYPE_SIGNED_BYTE: + case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_BYTE: { if (p_accessor_type == GLTFAccessor::TYPE_MAT2) { skip_every = 2; skip_bytes = 2; @@ -1125,8 +1137,8 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_ skip_bytes = 1; } } break; - case COMPONENT_TYPE_SHORT: - case COMPONENT_TYPE_UNSIGNED_SHORT: { + case GLTFAccessor::COMPONENT_TYPE_SIGNED_SHORT: + case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT: { if (p_accessor_type == GLTFAccessor::TYPE_MAT3) { skip_every = 6; skip_bytes = 4; @@ -1161,7 +1173,10 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_ } switch (p_component_type) { - case COMPONENT_TYPE_BYTE: { + case GLTFAccessor::COMPONENT_TYPE_NONE: { + ERR_FAIL_V_MSG(ERR_INVALID_DATA, "glTF: Failed to encode buffer view, component type not set."); + } + case GLTFAccessor::COMPONENT_TYPE_SIGNED_BYTE: { Vector<int8_t> buffer; buffer.resize(p_count * component_count); int32_t dst_i = 0; @@ -1185,7 +1200,7 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_ memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(int8_t)); bv->byte_length = buffer.size() * sizeof(int8_t); } break; - case COMPONENT_TYPE_UNSIGNED_BYTE: { + case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_BYTE: { Vector<uint8_t> buffer; buffer.resize(p_count * component_count); int32_t dst_i = 0; @@ -1207,7 +1222,7 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_ gltf_buffer.append_array(buffer); bv->byte_length = buffer.size() * sizeof(uint8_t); } break; - case COMPONENT_TYPE_SHORT: { + case GLTFAccessor::COMPONENT_TYPE_SIGNED_SHORT: { Vector<int16_t> buffer; buffer.resize(p_count * component_count); int32_t dst_i = 0; @@ -1231,7 +1246,7 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_ memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(int16_t)); bv->byte_length = buffer.size() * sizeof(int16_t); } break; - case COMPONENT_TYPE_UNSIGNED_SHORT: { + case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT: { Vector<uint16_t> buffer; buffer.resize(p_count * component_count); int32_t dst_i = 0; @@ -1255,8 +1270,28 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_ memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(uint16_t)); bv->byte_length = buffer.size() * sizeof(uint16_t); } break; - case COMPONENT_TYPE_INT: { - Vector<int> buffer; + case GLTFAccessor::COMPONENT_TYPE_SIGNED_INT: { + Vector<int32_t> buffer; + buffer.resize(p_count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < p_count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + double d = *p_src; + buffer.write[dst_i] = d; + p_src++; + dst_i++; + } + } + int64_t old_size = gltf_buffer.size(); + gltf_buffer.resize(old_size + (buffer.size() * sizeof(uint32_t))); + memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(uint32_t)); + bv->byte_length = buffer.size() * sizeof(uint32_t); + } break; + case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_INT: { + Vector<uint32_t> buffer; buffer.resize(p_count * component_count); int32_t dst_i = 0; for (int i = 0; i < p_count; i++) { @@ -1271,11 +1306,11 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_ } } int64_t old_size = gltf_buffer.size(); - gltf_buffer.resize(old_size + (buffer.size() * sizeof(int32_t))); - memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(int32_t)); - bv->byte_length = buffer.size() * sizeof(int32_t); + gltf_buffer.resize(old_size + (buffer.size() * sizeof(uint32_t))); + memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(uint32_t)); + bv->byte_length = buffer.size() * sizeof(uint32_t); } break; - case COMPONENT_TYPE_FLOAT: { + case GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT: { Vector<float> buffer; buffer.resize(p_count * component_count); int32_t dst_i = 0; @@ -1295,6 +1330,71 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_ memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(float)); bv->byte_length = buffer.size() * sizeof(float); } break; + case GLTFAccessor::COMPONENT_TYPE_DOUBLE_FLOAT: { + Vector<double> buffer; + buffer.resize(p_count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < p_count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + double d = *p_src; + buffer.write[dst_i] = d; + p_src++; + dst_i++; + } + } + int64_t old_size = gltf_buffer.size(); + gltf_buffer.resize(old_size + (buffer.size() * sizeof(double))); + memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(double)); + bv->byte_length = buffer.size() * sizeof(double); + } break; + case GLTFAccessor::COMPONENT_TYPE_HALF_FLOAT: { + ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "glTF: Half float not supported yet."); + } break; + case GLTFAccessor::COMPONENT_TYPE_SIGNED_LONG: { + Vector<int64_t> buffer; + buffer.resize(p_count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < p_count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + // FIXME: This can result in precision loss because int64_t can store some values that double can't. + double d = *p_src; + buffer.write[dst_i] = d; + p_src++; + dst_i++; + } + } + int64_t old_size = gltf_buffer.size(); + gltf_buffer.resize(old_size + (buffer.size() * sizeof(int64_t))); + memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(int64_t)); + bv->byte_length = buffer.size() * sizeof(int64_t); + } break; + case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_LONG: { + Vector<uint64_t> buffer; + buffer.resize(p_count * component_count); + int32_t dst_i = 0; + for (int i = 0; i < p_count; i++) { + for (int j = 0; j < component_count; j++) { + if (skip_every && j > 0 && (j % skip_every) == 0) { + dst_i += skip_bytes; + } + // FIXME: This can result in precision loss because int64_t can store some values that double can't. + double d = *p_src; + buffer.write[dst_i] = d; + p_src++; + dst_i++; + } + } + int64_t old_size = gltf_buffer.size(); + gltf_buffer.resize(old_size + (buffer.size() * sizeof(uint64_t))); + memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(uint64_t)); + bv->byte_length = buffer.size() * sizeof(uint64_t); + } break; } ERR_FAIL_COND_V(buffer_end > bv->byte_length, ERR_INVALID_DATA); @@ -1309,7 +1409,7 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_ return OK; } -Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, const GLTFBufferViewIndex p_buffer_view, const int p_skip_every, const int p_skip_bytes, const int p_element_size, const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type, const int p_component_count, const int p_component_type, const int p_component_size, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex) { +Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, const GLTFBufferViewIndex p_buffer_view, const int p_skip_every, const int p_skip_bytes, const int p_element_size, const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type, const int p_component_count, const GLTFAccessor::GLTFComponentType p_component_type, const int p_component_size, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex) { const Ref<GLTFBufferView> bv = p_state->buffer_views[p_buffer_view]; int stride = p_element_size; @@ -1348,7 +1448,10 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, c double d = 0; switch (p_component_type) { - case COMPONENT_TYPE_BYTE: { + case GLTFAccessor::COMPONENT_TYPE_NONE: { + ERR_FAIL_V_MSG(ERR_INVALID_DATA, "glTF: Failed to decode buffer view, component type not set."); + } break; + case GLTFAccessor::COMPONENT_TYPE_SIGNED_BYTE: { int8_t b = int8_t(*src); if (p_normalized) { d = (double(b) / 128.0); @@ -1356,7 +1459,7 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, c d = double(b); } } break; - case COMPONENT_TYPE_UNSIGNED_BYTE: { + case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_BYTE: { uint8_t b = *src; if (p_normalized) { d = (double(b) / 255.0); @@ -1364,7 +1467,7 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, c d = double(b); } } break; - case COMPONENT_TYPE_SHORT: { + case GLTFAccessor::COMPONENT_TYPE_SIGNED_SHORT: { int16_t s = *(int16_t *)src; if (p_normalized) { d = (double(s) / 32768.0); @@ -1372,7 +1475,7 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, c d = double(s); } } break; - case COMPONENT_TYPE_UNSIGNED_SHORT: { + case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT: { uint16_t s = *(uint16_t *)src; if (p_normalized) { d = (double(s) / 65535.0); @@ -1380,12 +1483,27 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, c d = double(s); } } break; - case COMPONENT_TYPE_INT: { - d = *(int *)src; + case GLTFAccessor::COMPONENT_TYPE_SIGNED_INT: { + d = *(int32_t *)src; + } break; + case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_INT: { + d = *(uint32_t *)src; } break; - case COMPONENT_TYPE_FLOAT: { + case GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT: { d = *(float *)src; } break; + case GLTFAccessor::COMPONENT_TYPE_DOUBLE_FLOAT: { + d = *(double *)src; + } break; + case GLTFAccessor::COMPONENT_TYPE_HALF_FLOAT: { + ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "glTF: Half float not supported yet."); + } break; + case GLTFAccessor::COMPONENT_TYPE_SIGNED_LONG: { + d = *(int64_t *)src; + } break; + case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_LONG: { + d = *(uint64_t *)src; + } break; } *p_dst++ = d; @@ -1396,25 +1514,27 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, c return OK; } -int GLTFDocument::_get_component_type_size(const int p_component_type) { +int GLTFDocument::_get_component_type_size(const GLTFAccessor::GLTFComponentType p_component_type) { switch (p_component_type) { - case COMPONENT_TYPE_BYTE: - case COMPONENT_TYPE_UNSIGNED_BYTE: + case GLTFAccessor::COMPONENT_TYPE_NONE: + ERR_FAIL_V(0); + case GLTFAccessor::COMPONENT_TYPE_SIGNED_BYTE: + case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_BYTE: return 1; - break; - case COMPONENT_TYPE_SHORT: - case COMPONENT_TYPE_UNSIGNED_SHORT: + case GLTFAccessor::COMPONENT_TYPE_SIGNED_SHORT: + case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT: + case GLTFAccessor::COMPONENT_TYPE_HALF_FLOAT: return 2; - break; - case COMPONENT_TYPE_INT: - case COMPONENT_TYPE_FLOAT: + case GLTFAccessor::COMPONENT_TYPE_SIGNED_INT: + case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_INT: + case GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT: return 4; - break; - default: { - ERR_FAIL_V(0); - } + case GLTFAccessor::COMPONENT_TYPE_DOUBLE_FLOAT: + case GLTFAccessor::COMPONENT_TYPE_SIGNED_LONG: + case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_LONG: + return 8; } - return 0; + ERR_FAIL_V(0); } Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { @@ -1425,11 +1545,7 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTF const Ref<GLTFAccessor> a = p_state->accessors[p_accessor]; - const int component_count_for_type[7] = { - 1, 2, 3, 4, 4, 9, 16 - }; - - const int component_count = component_count_for_type[a->accessor_type]; + const int component_count = COMPONENT_COUNT_FOR_ACCESSOR_TYPE[a->accessor_type]; const int component_size = _get_component_type_size(a->component_type); ERR_FAIL_COND_V(component_size == 0, Vector<double>()); int element_size = component_count * component_size; @@ -1438,8 +1554,8 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTF int skip_bytes = 0; //special case of alignments, as described in spec switch (a->component_type) { - case COMPONENT_TYPE_BYTE: - case COMPONENT_TYPE_UNSIGNED_BYTE: { + case GLTFAccessor::COMPONENT_TYPE_SIGNED_BYTE: + case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_BYTE: { if (a->accessor_type == GLTFAccessor::TYPE_MAT2) { skip_every = 2; skip_bytes = 2; @@ -1451,8 +1567,8 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTF element_size = 12; //override for this case } } break; - case COMPONENT_TYPE_SHORT: - case COMPONENT_TYPE_UNSIGNED_SHORT: { + case GLTFAccessor::COMPONENT_TYPE_SIGNED_SHORT: + case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT: { if (a->accessor_type == GLTFAccessor::TYPE_MAT3) { skip_every = 6; skip_bytes = 4; @@ -1550,11 +1666,11 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_ints(Ref<GLTFState> p_state, } int64_t size = p_state->buffers[0].size(); const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_SCALAR; - int component_type; + GLTFAccessor::GLTFComponentType component_type; if (max_index > 65535 || p_for_vertex) { - component_type = GLTFDocument::COMPONENT_TYPE_INT; + component_type = GLTFAccessor::COMPONENT_TYPE_UNSIGNED_INT; } else { - component_type = GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT; + component_type = GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT; } accessor->max = type_max; @@ -1664,7 +1780,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec2(Ref<GLTFState> p_state, } int64_t size = p_state->buffers[0].size(); const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC2; - const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT; accessor->max = type_max; accessor->min = type_min; @@ -1717,7 +1833,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_color(Ref<GLTFState> p_state } int64_t size = p_state->buffers[0].size(); const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC4; - const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT; accessor->max = type_max; accessor->min = type_min; @@ -1784,7 +1900,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_weights(Ref<GLTFState> p_sta } int64_t size = p_state->buffers[0].size(); const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC4; - const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT; accessor->max = type_max; accessor->min = type_min; @@ -1835,7 +1951,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_joints(Ref<GLTFState> p_stat } int64_t size = p_state->buffers[0].size(); const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC4; - const int component_type = GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT; + const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT; accessor->max = type_max; accessor->min = type_min; @@ -1888,7 +2004,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_quaternions(Ref<GLTFState> p } int64_t size = p_state->buffers[0].size(); const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC4; - const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT; accessor->max = type_max; accessor->min = type_min; @@ -1932,7 +2048,7 @@ Vector<Vector2> GLTFDocument::_decode_accessor_as_vec2(Ref<GLTFState> p_state, c return ret; } -GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref<GLTFState> p_state, const Vector<real_t> p_attribs, const bool p_for_vertex) { +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref<GLTFState> p_state, const Vector<double> p_attribs, const bool p_for_vertex) { if (p_attribs.size() == 0) { return -1; } @@ -1963,7 +2079,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref<GLTFState> p_stat } int64_t size = p_state->buffers[0].size(); const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_SCALAR; - const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT; accessor->max = type_max; accessor->min = type_min; @@ -2013,7 +2129,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec3(Ref<GLTFState> p_state, } int64_t size = p_state->buffers[0].size(); const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC3; - const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT; accessor->max = type_max; accessor->min = type_min; @@ -2089,7 +2205,7 @@ GLTFAccessorIndex GLTFDocument::_encode_sparse_accessor_as_vec3(Ref<GLTFState> p } int64_t size = p_state->buffers[0].size(); const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC3; - const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT; sparse_accessor->normalized = false; sparse_accessor->count = p_attribs.size(); @@ -2112,9 +2228,9 @@ GLTFAccessorIndex GLTFDocument::_encode_sparse_accessor_as_vec3(Ref<GLTFState> p GLTFBufferIndex buffer_view_i_indices = -1; GLTFBufferIndex buffer_view_i_values = -1; if (sparse_accessor_index_stride == 4) { - sparse_accessor->sparse_indices_component_type = GLTFDocument::COMPONENT_TYPE_INT; + sparse_accessor->sparse_indices_component_type = GLTFAccessor::COMPONENT_TYPE_UNSIGNED_INT; } else { - sparse_accessor->sparse_indices_component_type = GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT; + sparse_accessor->sparse_indices_component_type = GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT; } if (_encode_buffer_view(p_state, changed_indices.ptr(), changed_indices.size(), GLTFAccessor::TYPE_SCALAR, sparse_accessor->sparse_indices_component_type, sparse_accessor->normalized, sparse_accessor->sparse_indices_byte_offset, false, buffer_view_i_indices) != OK) { return -1; @@ -2194,7 +2310,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_xform(Ref<GLTFState> p_state } int64_t size = p_state->buffers[0].size(); const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_MAT4; - const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; + const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT; accessor->max = type_max; accessor->min = type_min; @@ -2343,6 +2459,325 @@ Vector<Transform3D> GLTFDocument::_decode_accessor_as_xform(Ref<GLTFState> p_sta return ret; } +Vector<Variant> GLTFDocument::_decode_accessor_as_variant(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, Variant::Type p_variant_type, GLTFAccessor::GLTFAccessorType p_accessor_type) { + const Vector<double> attribs = _decode_accessor(p_state, p_accessor, false); + Vector<Variant> ret; + ERR_FAIL_COND_V_MSG(attribs.is_empty(), ret, "glTF: The accessor was empty."); + const int component_count = COMPONENT_COUNT_FOR_ACCESSOR_TYPE[p_accessor_type]; + ERR_FAIL_COND_V_MSG(attribs.size() % component_count != 0, ret, "glTF: The accessor size was not a multiple of the component count."); + const int ret_size = attribs.size() / component_count; + ret.resize(ret_size); + for (int i = 0; i < ret_size; i++) { + switch (p_variant_type) { + case Variant::BOOL: { + ret.write[i] = attribs[i * component_count] != 0.0; + } break; + case Variant::INT: { + ret.write[i] = (int64_t)attribs[i * component_count]; + } break; + case Variant::FLOAT: { + ret.write[i] = attribs[i * component_count]; + } break; + case Variant::VECTOR2: + case Variant::RECT2: + case Variant::VECTOR3: + case Variant::VECTOR4: + case Variant::PLANE: + case Variant::QUATERNION: { + // General-purpose code for importing glTF accessor data with any component count into structs up to 4 `real_t`s in size. + Variant v; + switch (component_count) { + case 1: { + v = Vector4(attribs[i * component_count], 0.0f, 0.0f, 0.0f); + } break; + case 2: { + v = Vector4(attribs[i * component_count], attribs[i * component_count + 1], 0.0f, 0.0f); + } break; + case 3: { + v = Vector4(attribs[i * component_count], attribs[i * component_count + 1], attribs[i * component_count + 2], 0.0f); + } break; + default: { + v = Vector4(attribs[i * component_count], attribs[i * component_count + 1], attribs[i * component_count + 2], attribs[i * component_count + 3]); + } break; + } + // Evil hack that relies on the structure of Variant, but it's the + // only way to accomplish this without a ton of code duplication. + *(Variant::Type *)&v = p_variant_type; + ret.write[i] = v; + } break; + case Variant::VECTOR2I: + case Variant::RECT2I: + case Variant::VECTOR3I: + case Variant::VECTOR4I: { + // General-purpose code for importing glTF accessor data with any component count into structs up to 4 `int32_t`s in size. + Variant v; + switch (component_count) { + case 1: { + v = Vector4i((int32_t)attribs[i * component_count], 0, 0, 0); + } break; + case 2: { + v = Vector4i((int32_t)attribs[i * component_count], (int32_t)attribs[i * component_count + 1], 0, 0); + } break; + case 3: { + v = Vector4i((int32_t)attribs[i * component_count], (int32_t)attribs[i * component_count + 1], (int32_t)attribs[i * component_count + 2], 0); + } break; + default: { + v = Vector4i((int32_t)attribs[i * component_count], (int32_t)attribs[i * component_count + 1], (int32_t)attribs[i * component_count + 2], (int32_t)attribs[i * component_count + 3]); + } break; + } + // Evil hack that relies on the structure of Variant, but it's the + // only way to accomplish this without a ton of code duplication. + *(Variant::Type *)&v = p_variant_type; + ret.write[i] = v; + } break; + // No more generalized hacks, each of the below types needs a lot of repetitive code. + case Variant::COLOR: { + Variant v; + switch (component_count) { + case 1: { + v = Color(attribs[i * component_count], 0.0f, 0.0f, 1.0f); + } break; + case 2: { + v = Color(attribs[i * component_count], attribs[i * component_count + 1], 0.0f, 1.0f); + } break; + case 3: { + v = Color(attribs[i * component_count], attribs[i * component_count + 1], attribs[i * component_count + 2], 1.0f); + } break; + default: { + v = Color(attribs[i * component_count], attribs[i * component_count + 1], attribs[i * component_count + 2], attribs[i * component_count + 3]); + } break; + } + ret.write[i] = v; + } break; + case Variant::TRANSFORM2D: { + Transform2D t; + switch (component_count) { + case 4: { + t.columns[0] = Vector2(attribs[i * component_count + 0], attribs[i * component_count + 1]); + t.columns[1] = Vector2(attribs[i * component_count + 2], attribs[i * component_count + 3]); + } break; + case 9: { + t.columns[0] = Vector2(attribs[i * component_count + 0], attribs[i * component_count + 1]); + t.columns[1] = Vector2(attribs[i * component_count + 3], attribs[i * component_count + 4]); + t.columns[2] = Vector2(attribs[i * component_count + 6], attribs[i * component_count + 7]); + } break; + case 16: { + t.columns[0] = Vector2(attribs[i * component_count + 0], attribs[i * component_count + 1]); + t.columns[1] = Vector2(attribs[i * component_count + 4], attribs[i * component_count + 5]); + t.columns[2] = Vector2(attribs[i * component_count + 12], attribs[i * component_count + 13]); + } break; + } + ret.write[i] = t; + } break; + case Variant::BASIS: { + Basis b; + switch (component_count) { + case 4: { + b.rows[0] = Vector3(attribs[i * component_count + 0], attribs[i * component_count + 2], 0.0f); + b.rows[1] = Vector3(attribs[i * component_count + 1], attribs[i * component_count + 3], 0.0f); + } break; + case 9: { + b.rows[0] = Vector3(attribs[i * component_count + 0], attribs[i * component_count + 3], attribs[i * component_count + 6]); + b.rows[1] = Vector3(attribs[i * component_count + 1], attribs[i * component_count + 4], attribs[i * component_count + 7]); + b.rows[2] = Vector3(attribs[i * component_count + 2], attribs[i * component_count + 5], attribs[i * component_count + 8]); + } break; + case 16: { + b.rows[0] = Vector3(attribs[i * component_count + 0], attribs[i * component_count + 4], attribs[i * component_count + 8]); + b.rows[1] = Vector3(attribs[i * component_count + 1], attribs[i * component_count + 5], attribs[i * component_count + 9]); + b.rows[2] = Vector3(attribs[i * component_count + 2], attribs[i * component_count + 6], attribs[i * component_count + 10]); + } break; + } + ret.write[i] = b; + } break; + case Variant::TRANSFORM3D: { + Transform3D t; + switch (component_count) { + case 4: { + t.basis.rows[0] = Vector3(attribs[i * component_count + 0], attribs[i * component_count + 2], 0.0f); + t.basis.rows[1] = Vector3(attribs[i * component_count + 1], attribs[i * component_count + 3], 0.0f); + } break; + case 9: { + t.basis.rows[0] = Vector3(attribs[i * component_count + 0], attribs[i * component_count + 3], attribs[i * component_count + 6]); + t.basis.rows[1] = Vector3(attribs[i * component_count + 1], attribs[i * component_count + 4], attribs[i * component_count + 7]); + t.basis.rows[2] = Vector3(attribs[i * component_count + 2], attribs[i * component_count + 5], attribs[i * component_count + 8]); + } break; + case 16: { + t.basis.rows[0] = Vector3(attribs[i * component_count + 0], attribs[i * component_count + 4], attribs[i * component_count + 8]); + t.basis.rows[1] = Vector3(attribs[i * component_count + 1], attribs[i * component_count + 5], attribs[i * component_count + 9]); + t.basis.rows[2] = Vector3(attribs[i * component_count + 2], attribs[i * component_count + 6], attribs[i * component_count + 10]); + t.origin = Vector3(attribs[i * component_count + 12], attribs[i * component_count + 13], attribs[i * component_count + 14]); + } break; + } + ret.write[i] = t; + } break; + case Variant::PROJECTION: { + Projection p; + switch (component_count) { + case 4: { + p.columns[0] = Vector4(attribs[i * component_count + 0], attribs[i * component_count + 1], 0.0f, 0.0f); + p.columns[1] = Vector4(attribs[i * component_count + 4], attribs[i * component_count + 5], 0.0f, 0.0f); + } break; + case 9: { + p.columns[0] = Vector4(attribs[i * component_count + 0], attribs[i * component_count + 1], attribs[i * component_count + 2], 0.0f); + p.columns[1] = Vector4(attribs[i * component_count + 4], attribs[i * component_count + 5], attribs[i * component_count + 6], 0.0f); + p.columns[2] = Vector4(attribs[i * component_count + 8], attribs[i * component_count + 9], attribs[i * component_count + 10], 0.0f); + } break; + case 16: { + p.columns[0] = Vector4(attribs[i * component_count + 0], attribs[i * component_count + 1], attribs[i * component_count + 2], attribs[i * component_count + 3]); + p.columns[1] = Vector4(attribs[i * component_count + 4], attribs[i * component_count + 5], attribs[i * component_count + 6], attribs[i * component_count + 7]); + p.columns[2] = Vector4(attribs[i * component_count + 8], attribs[i * component_count + 9], attribs[i * component_count + 10], attribs[i * component_count + 11]); + p.columns[3] = Vector4(attribs[i * component_count + 12], attribs[i * component_count + 13], attribs[i * component_count + 14], attribs[i * component_count + 15]); + } break; + } + ret.write[i] = p; + } break; + default: { + ERR_FAIL_V_MSG(ret, "glTF: Cannot decode accessor as Variant of type " + Variant::get_type_name(p_variant_type) + "."); + } + } + } + return ret; +} + +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_variant(Ref<GLTFState> p_state, Vector<Variant> p_attribs, Variant::Type p_variant_type, GLTFAccessor::GLTFAccessorType p_accessor_type, GLTFAccessor::GLTFComponentType p_component_type) { + const int accessor_component_count = COMPONENT_COUNT_FOR_ACCESSOR_TYPE[p_accessor_type]; + Vector<double> encoded_attribs; + for (const Variant &v : p_attribs) { + switch (p_variant_type) { + case Variant::NIL: + case Variant::BOOL: + case Variant::INT: + case Variant::FLOAT: { + // For scalar values, just append them. Variant can convert all of these to double. Some padding may also be needed. + encoded_attribs.append(v); + if (unlikely(accessor_component_count > 1)) { + for (int i = 1; i < accessor_component_count; i++) { + encoded_attribs.append(0.0); + } + } + } break; + case Variant::VECTOR2: + case Variant::VECTOR2I: + case Variant::VECTOR3: + case Variant::VECTOR3I: + case Variant::VECTOR4: + case Variant::VECTOR4I: { + // Variant can handle converting Vector2/2i/3/3i/4/4i to Vector4 for us. + Vector4 vec = v; + if (likely(accessor_component_count < 5)) { + for (int i = 0; i < accessor_component_count; i++) { + encoded_attribs.append(vec[i]); + } + } + } break; + case Variant::PLANE: { + Plane p = v; + if (likely(accessor_component_count == 4)) { + encoded_attribs.append(p.normal.x); + encoded_attribs.append(p.normal.y); + encoded_attribs.append(p.normal.z); + encoded_attribs.append(p.d); + } + } break; + case Variant::QUATERNION: { + Quaternion q = v; + if (likely(accessor_component_count < 5)) { + for (int i = 0; i < accessor_component_count; i++) { + encoded_attribs.append(q[i]); + } + } + } break; + case Variant::COLOR: { + Color c = v; + if (likely(accessor_component_count < 5)) { + for (int i = 0; i < accessor_component_count; i++) { + encoded_attribs.append(c[i]); + } + } + } break; + case Variant::RECT2: + case Variant::RECT2I: { + // Variant can handle converting Rect2i to Rect2 for us. + Rect2 r = v; + if (likely(accessor_component_count == 4)) { + encoded_attribs.append(r.position.x); + encoded_attribs.append(r.position.y); + encoded_attribs.append(r.size.x); + encoded_attribs.append(r.size.y); + } + } break; + case Variant::TRANSFORM2D: + case Variant::BASIS: + case Variant::TRANSFORM3D: + case Variant::PROJECTION: { + // Variant can handle converting Transform2D/Transform3D/Basis to Projection for us. + Projection p = v; + if (accessor_component_count == 16) { + for (int i = 0; i < 4; i++) { + encoded_attribs.append(p.columns[i][0]); + encoded_attribs.append(p.columns[i][1]); + encoded_attribs.append(p.columns[i][2]); + encoded_attribs.append(p.columns[i][3]); + } + } else if (accessor_component_count == 9) { + for (int i = 0; i < 3; i++) { + encoded_attribs.append(p.columns[i][0]); + encoded_attribs.append(p.columns[i][1]); + encoded_attribs.append(p.columns[i][2]); + } + } else if (accessor_component_count == 4) { + encoded_attribs.append(p.columns[0][0]); + encoded_attribs.append(p.columns[0][1]); + encoded_attribs.append(p.columns[1][0]); + encoded_attribs.append(p.columns[1][1]); + } + } break; + default: { + ERR_FAIL_V_MSG(-1, "glTF: Cannot encode accessor from Variant of type " + Variant::get_type_name(p_variant_type) + "."); + } + } + } + // Determine the min and max values for the accessor. + Vector<double> type_max; + type_max.resize(accessor_component_count); + Vector<double> type_min; + type_min.resize(accessor_component_count); + for (int i = 0; i < encoded_attribs.size(); i++) { + if (Math::is_zero_approx(encoded_attribs[i])) { + encoded_attribs.write[i] = 0.0; + } else { + encoded_attribs.write[i] = _filter_number(encoded_attribs[i]); + } + } + for (int i = 0; i < p_attribs.size(); i++) { + _calc_accessor_min_max(i, accessor_component_count, type_max, encoded_attribs, type_min); + } + _round_min_max_components(type_min, type_max); + // Encode the data in a buffer view. + GLTFBufferIndex buffer_view_index = 0; + if (p_state->buffers.is_empty()) { + p_state->buffers.push_back(Vector<uint8_t>()); + } + const int64_t buffer_size = p_state->buffers[buffer_view_index].size(); + Error err = _encode_buffer_view(p_state, encoded_attribs.ptr(), p_attribs.size(), p_accessor_type, p_component_type, false, buffer_size, false, buffer_view_index); + if (err != OK) { + return -1; + } + // Create the accessor and fill it with the data. + Ref<GLTFAccessor> accessor; + accessor.instantiate(); + accessor->max = type_max; + accessor->min = type_min; + accessor->count = p_attribs.size(); + accessor->accessor_type = p_accessor_type; + accessor->component_type = p_component_type; + accessor->byte_offset = 0; + accessor->buffer_view = buffer_view_index; + const GLTFAccessorIndex new_accessor_index = p_state->accessors.size(); + p_state->accessors.push_back(accessor); + return new_accessor_index; +} + Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) { Array meshes; for (GLTFMeshIndex gltf_mesh_i = 0; gltf_mesh_i < p_state->meshes.size(); gltf_mesh_i++) { @@ -2778,41 +3213,42 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { Array meshes = p_state->json["meshes"]; for (GLTFMeshIndex i = 0; i < meshes.size(); i++) { print_verbose("glTF: Parsing mesh: " + itos(i)); - Dictionary d = meshes[i]; + Dictionary mesh_dict = meshes[i]; Ref<GLTFMesh> mesh; mesh.instantiate(); 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); Ref<ImporterMesh> import_mesh; import_mesh.instantiate(); 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); } 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()); + TypedArray<Material> instance_materials; for (int j = 0; j < primitives.size(); j++) { uint64_t flags = RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES; - Dictionary p = primitives[j]; + Dictionary mesh_prim = primitives[j]; Array array; 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; - 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); // 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 @@ -2843,8 +3279,8 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { Vector<int> indices_mapping; Vector<int> indices_rev_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(); if (primitive == Mesh::PRIMITIVE_TRIANGLES) { @@ -3102,7 +3538,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; } @@ -3134,9 +3570,9 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { Array morphs; // Blend shapes - if (p.has("targets")) { + if (mesh_prim.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); @@ -3267,8 +3703,8 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { Ref<Material> mat; String mat_name; 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); Ref<Material> mat3d = p_state->materials[material]; ERR_FAIL_COND_V(mat3d.is_null(), ERR_FILE_CORRUPT); @@ -3288,6 +3724,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { mat = mat3d; } ERR_FAIL_COND_V(mat.is_null(), ERR_FILE_CORRUPT); + instance_materials.append(mat); mat_name = mat->get_name(); } import_mesh->add_surface(primitive, array, morphs, @@ -3300,8 +3737,8 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { 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++) { if (j >= blend_weights.size()) { break; @@ -3310,6 +3747,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { } } mesh->set_blend_weights(blend_weights); + mesh->set_instance_materials(instance_materials); mesh->set_mesh(import_mesh); p_state->meshes.push_back(mesh); @@ -3501,18 +3939,19 @@ void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const Vector< } #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint() && handling == GLTFState::GLTFHandleBinary::HANDLE_BINARY_EXTRACT_TEXTURES) { - if (p_state->base_path.is_empty()) { - p_state->images.push_back(Ref<Texture2D>()); - p_state->source_images.push_back(Ref<Image>()); - } else if (p_image->get_name().is_empty()) { - WARN_PRINT(vformat("glTF: Image index '%d' couldn't be named. Skipping it.", p_index)); - p_state->images.push_back(Ref<Texture2D>()); - p_state->source_images.push_back(Ref<Image>()); + if (p_state->extract_path.is_empty()) { + WARN_PRINT("glTF: Couldn't extract image because the base and extract paths are empty. It will be loaded directly instead, uncompressed."); + } else if (p_state->extract_path.begins_with("res://.godot/imported")) { + WARN_PRINT(vformat("glTF: Extract path is in the imported directory. Image index '%d' will be loaded directly, uncompressed.", p_index)); } else { + if (p_image->get_name().is_empty()) { + WARN_PRINT(vformat("glTF: Image index '%d' did not have a name. It will be automatically given a name based on its index.", p_index)); + p_image->set_name(itos(p_index)); + } bool must_import = true; Vector<uint8_t> img_data = p_image->get_data(); Dictionary generator_parameters; - String file_path = p_state->get_base_path().path_join(p_state->filename.get_basename() + "_" + p_image->get_name()); + String file_path = p_state->get_extract_path().path_join(p_state->get_extract_prefix() + "_" + p_image->get_name()); file_path += p_file_extension.is_empty() ? ".png" : p_file_extension; if (FileAccess::exists(file_path + ".import")) { Ref<ConfigFile> config; @@ -3559,14 +3998,11 @@ void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const Vector< if (saved_image.is_valid()) { p_state->images.push_back(saved_image); p_state->source_images.push_back(saved_image->get_image()); + return; } else { - WARN_PRINT(vformat("glTF: Image index '%d' couldn't be loaded with the name: %s. Skipping it.", p_index, p_image->get_name())); - // Placeholder to keep count. - p_state->images.push_back(Ref<Texture2D>()); - p_state->source_images.push_back(Ref<Image>()); + WARN_PRINT(vformat("glTF: Image index '%d' with the name '%s' couldn't be imported. It will be loaded directly instead, uncompressed.", p_index, p_image->get_name())); } } - return; } #endif // TOOLS_ENABLED if (handling == GLTFState::GLTFHandleBinary::HANDLE_BINARY_EMBED_AS_BASISU) { @@ -3649,16 +4085,19 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p ERR_FAIL_COND_V(p_base_path.is_empty(), ERR_INVALID_PARAMETER); uri = uri.uri_decode(); uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows. - // ResourceLoader will rely on the file extension to use the relevant loader. - // The spec says that if mimeType is defined, it should take precedence (e.g. - // there could be a `.png` image which is actually JPEG), but there's no easy - // API for that in Godot, so we'd have to load as a buffer (i.e. embedded in - // the material), so we only do that only as fallback. - Ref<Texture2D> texture = ResourceLoader::load(uri); - if (texture.is_valid()) { - p_state->images.push_back(texture); - p_state->source_images.push_back(texture->get_image()); - continue; + // If the image is in the .godot/imported directory, we can't use ResourceLoader. + if (!p_base_path.begins_with("res://.godot/imported")) { + // ResourceLoader will rely on the file extension to use the relevant loader. + // The spec says that if mimeType is defined, it should take precedence (e.g. + // there could be a `.png` image which is actually JPEG), but there's no easy + // API for that in Godot, so we'd have to load as a buffer (i.e. embedded in + // the material), so we only do that only as fallback. + Ref<Texture2D> texture = ResourceLoader::load(uri, "Texture2D"); + if (texture.is_valid()) { + p_state->images.push_back(texture); + p_state->source_images.push_back(texture->get_image()); + continue; + } } // mimeType is optional, but if we have it in the file extension, let's use it. // If the mimeType does not match with the file extension, either it should be @@ -4781,7 +5220,7 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) { for (GLTFAnimationIndex animation_i = 0; animation_i < p_state->animations.size(); animation_i++) { Dictionary d; Ref<GLTFAnimation> gltf_animation = p_state->animations[animation_i]; - if (!gltf_animation->get_tracks().size()) { + if (gltf_animation->is_empty_of_tracks()) { continue; } @@ -4790,18 +5229,18 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) { } Array channels; Array samplers; - - for (KeyValue<int, GLTFAnimation::Track> &track_i : gltf_animation->get_tracks()) { - GLTFAnimation::Track track = track_i.value; + // Serialize glTF node tracks with the vanilla glTF animation system. + for (KeyValue<int, GLTFAnimation::NodeTrack> &track_i : gltf_animation->get_node_tracks()) { + GLTFAnimation::NodeTrack track = track_i.value; if (track.position_track.times.size()) { Dictionary t; t["sampler"] = samplers.size(); Dictionary s; s["interpolation"] = interpolation_to_string(track.position_track.interpolation); - Vector<real_t> times = Variant(track.position_track.times); + Vector<double> times = track.position_track.times; s["input"] = _encode_accessor_as_floats(p_state, times, false); - Vector<Vector3> values = Variant(track.position_track.values); + Vector<Vector3> values = track.position_track.values; s["output"] = _encode_accessor_as_vec3(p_state, values, false); samplers.push_back(s); @@ -4819,7 +5258,7 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) { Dictionary s; s["interpolation"] = interpolation_to_string(track.rotation_track.interpolation); - Vector<real_t> times = Variant(track.rotation_track.times); + Vector<double> times = track.rotation_track.times; s["input"] = _encode_accessor_as_floats(p_state, times, false); Vector<Quaternion> values = track.rotation_track.values; s["output"] = _encode_accessor_as_quaternions(p_state, values, false); @@ -4839,9 +5278,9 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) { Dictionary s; s["interpolation"] = interpolation_to_string(track.scale_track.interpolation); - Vector<real_t> times = Variant(track.scale_track.times); + Vector<double> times = track.scale_track.times; s["input"] = _encode_accessor_as_floats(p_state, times, false); - Vector<Vector3> values = Variant(track.scale_track.values); + Vector<Vector3> values = track.scale_track.values; s["output"] = _encode_accessor_as_vec3(p_state, values, false); samplers.push_back(s); @@ -4864,7 +5303,7 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) { Dictionary t; t["sampler"] = samplers.size(); Dictionary s; - Vector<real_t> times; + Vector<double> times; const double increment = 1.0 / p_state->get_bake_fps(); { double time = 0.0; @@ -4905,8 +5344,8 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) { track.weight_tracks.write[track_idx].values = weight_track; } - Vector<real_t> all_track_times = times; - Vector<real_t> all_track_values; + Vector<double> all_track_times = times; + Vector<double> all_track_values; int32_t values_size = track.weight_tracks[0].values.size(); int32_t weight_tracks_size = track.weight_tracks.size(); all_track_values.resize(weight_tracks_size * values_size); @@ -4933,6 +5372,33 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) { channels.push_back(t); } } + if (!gltf_animation->get_pointer_tracks().is_empty()) { + // Serialize glTF pointer tracks with the KHR_animation_pointer extension. + if (!p_state->extensions_used.has("KHR_animation_pointer")) { + p_state->extensions_used.push_back("KHR_animation_pointer"); + } + for (KeyValue<String, GLTFAnimation::Channel<Variant>> &pointer_track_iter : gltf_animation->get_pointer_tracks()) { + const String &json_pointer = pointer_track_iter.key; + const GLTFAnimation::Channel<Variant> &pointer_track = pointer_track_iter.value; + const Ref<GLTFObjectModelProperty> &obj_model_prop = p_state->object_model_properties[json_pointer]; + Dictionary channel; + channel["sampler"] = samplers.size(); + Dictionary channel_target; + channel_target["path"] = "pointer"; + Dictionary channel_target_ext; + Dictionary channel_target_ext_khr_anim_ptr; + channel_target_ext_khr_anim_ptr["pointer"] = json_pointer; + channel_target_ext["KHR_animation_pointer"] = channel_target_ext_khr_anim_ptr; + channel_target["extensions"] = channel_target_ext; + channel["target"] = channel_target; + channels.push_back(channel); + Dictionary sampler; + sampler["input"] = _encode_accessor_as_floats(p_state, pointer_track.times, false); + sampler["interpolation"] = interpolation_to_string(pointer_track.interpolation); + sampler["output"] = _encode_accessor_as_variant(p_state, pointer_track.values, obj_model_prop->get_variant_type(), obj_model_prop->get_accessor_type()); + samplers.push_back(sampler); + } + } if (channels.size() && samplers.size()) { d["channels"] = channels; d["samplers"] = samplers; @@ -4957,21 +5423,21 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> p_state) { const Array &animations = p_state->json["animations"]; - for (GLTFAnimationIndex i = 0; i < animations.size(); i++) { - const Dictionary &d = animations[i]; + for (GLTFAnimationIndex anim_index = 0; anim_index < animations.size(); anim_index++) { + const Dictionary &anim_dict = animations[anim_index]; Ref<GLTFAnimation> animation; animation.instantiate(); - if (!d.has("channels") || !d.has("samplers")) { + if (!anim_dict.has("channels") || !anim_dict.has("samplers")) { continue; } - Array channels = d["channels"]; - Array samplers = d["samplers"]; + Array channels = anim_dict["channels"]; + Array samplers = anim_dict["samplers"]; - if (d.has("name")) { - const String anim_name = d["name"]; + if (anim_dict.has("name")) { + const String anim_name = anim_dict["name"]; const String anim_name_lower = anim_name.to_lower(); if (anim_name_lower.begins_with("loop") || anim_name_lower.ends_with("loop") || anim_name_lower.begins_with("cycle") || anim_name_lower.ends_with("cycle")) { animation->set_loop(true); @@ -4980,46 +5446,22 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> p_state) { animation->set_name(_gen_unique_animation_name(p_state, anim_name)); } - for (int j = 0; j < channels.size(); j++) { - const Dictionary &c = channels[j]; - if (!c.has("target")) { - continue; - } - - const Dictionary &t = c["target"]; - if (!t.has("node") || !t.has("path")) { - continue; - } - - ERR_FAIL_COND_V(!c.has("sampler"), ERR_PARSE_ERROR); - const int sampler = c["sampler"]; - ERR_FAIL_INDEX_V(sampler, samplers.size(), ERR_PARSE_ERROR); - - GLTFNodeIndex node = t["node"]; - String path = t["path"]; - - ERR_FAIL_INDEX_V(node, p_state->nodes.size(), ERR_PARSE_ERROR); - - GLTFAnimation::Track *track = nullptr; - - if (!animation->get_tracks().has(node)) { - animation->get_tracks()[node] = GLTFAnimation::Track(); - } - - track = &animation->get_tracks()[node]; - - const Dictionary &s = samplers[sampler]; - - ERR_FAIL_COND_V(!s.has("input"), ERR_PARSE_ERROR); - ERR_FAIL_COND_V(!s.has("output"), ERR_PARSE_ERROR); - - const int input = s["input"]; - const int output = s["output"]; - + for (int channel_index = 0; channel_index < channels.size(); channel_index++) { + const Dictionary &anim_channel = channels[channel_index]; + ERR_FAIL_COND_V_MSG(!anim_channel.has("sampler"), ERR_PARSE_ERROR, "glTF: Animation channel missing required 'sampler' property."); + ERR_FAIL_COND_V_MSG(!anim_channel.has("target"), ERR_PARSE_ERROR, "glTF: Animation channel missing required 'target' property."); + // Parse sampler. + const int sampler_index = anim_channel["sampler"]; + ERR_FAIL_INDEX_V(sampler_index, samplers.size(), ERR_PARSE_ERROR); + const Dictionary &sampler_dict = samplers[sampler_index]; + ERR_FAIL_COND_V(!sampler_dict.has("input"), ERR_PARSE_ERROR); + ERR_FAIL_COND_V(!sampler_dict.has("output"), ERR_PARSE_ERROR); + const int input_time_accessor_index = sampler_dict["input"]; + const int output_value_accessor_index = sampler_dict["output"]; GLTFAnimation::Interpolation interp = GLTFAnimation::INTERP_LINEAR; int output_count = 1; - if (s.has("interpolation")) { - const String &in = s["interpolation"]; + if (sampler_dict.has("interpolation")) { + const String &in = sampler_dict["interpolation"]; if (in == "STEP") { interp = GLTFAnimation::INTERP_STEP; } else if (in == "LINEAR") { @@ -5032,52 +5474,83 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> p_state) { output_count = 3; } } + const Vector<double> times = _decode_accessor(p_state, input_time_accessor_index, false); + // Parse target. + const Dictionary &anim_target = anim_channel["target"]; + ERR_FAIL_COND_V_MSG(!anim_target.has("path"), ERR_PARSE_ERROR, "glTF: Animation channel target missing required 'path' property."); + String path = anim_target["path"]; + if (path == "pointer") { + ERR_FAIL_COND_V(!anim_target.has("extensions"), ERR_PARSE_ERROR); + Dictionary target_extensions = anim_target["extensions"]; + ERR_FAIL_COND_V(!target_extensions.has("KHR_animation_pointer"), ERR_PARSE_ERROR); + Dictionary khr_anim_ptr = target_extensions["KHR_animation_pointer"]; + ERR_FAIL_COND_V(!khr_anim_ptr.has("pointer"), ERR_PARSE_ERROR); + String anim_json_ptr = khr_anim_ptr["pointer"]; + _parse_animation_pointer(p_state, anim_json_ptr, animation, interp, times, output_value_accessor_index); + } else { + // If it's not a pointer, it's a regular animation channel from vanilla glTF (pos/rot/scale/weights). + if (!anim_target.has("node")) { + WARN_PRINT("glTF: Animation channel target missing 'node' property. Ignoring this channel."); + continue; + } - const Vector<float> times = _decode_accessor_as_floats(p_state, input, false); - if (path == "translation") { - const Vector<Vector3> positions = _decode_accessor_as_vec3(p_state, output, false); - track->position_track.interpolation = interp; - track->position_track.times = Variant(times); //convert via variant - track->position_track.values = Variant(positions); //convert via variant - } else if (path == "rotation") { - const Vector<Quaternion> rotations = _decode_accessor_as_quaternion(p_state, output, false); - track->rotation_track.interpolation = interp; - track->rotation_track.times = Variant(times); //convert via variant - track->rotation_track.values = rotations; - } else if (path == "scale") { - const Vector<Vector3> scales = _decode_accessor_as_vec3(p_state, output, false); - track->scale_track.interpolation = interp; - track->scale_track.times = Variant(times); //convert via variant - track->scale_track.values = Variant(scales); //convert via variant - } else if (path == "weights") { - const Vector<float> weights = _decode_accessor_as_floats(p_state, output, false); - - ERR_FAIL_INDEX_V(p_state->nodes[node]->mesh, p_state->meshes.size(), ERR_PARSE_ERROR); - Ref<GLTFMesh> mesh = p_state->meshes[p_state->nodes[node]->mesh]; - ERR_CONTINUE(!mesh->get_blend_weights().size()); - const int wc = mesh->get_blend_weights().size(); - - track->weight_tracks.resize(wc); - - const int expected_value_count = times.size() * output_count * wc; - ERR_CONTINUE_MSG(weights.size() != expected_value_count, "Invalid weight data, expected " + itos(expected_value_count) + " weight values, got " + itos(weights.size()) + " instead."); - - const int wlen = weights.size() / wc; - for (int k = 0; k < wc; k++) { //separate tracks, having them together is not such a good idea - GLTFAnimation::Channel<real_t> cf; - cf.interpolation = interp; - cf.times = Variant(times); - Vector<real_t> wdata; - wdata.resize(wlen); - for (int l = 0; l < wlen; l++) { - wdata.write[l] = weights[l * wc + k]; - } + GLTFNodeIndex node = anim_target["node"]; + + ERR_FAIL_INDEX_V(node, p_state->nodes.size(), ERR_PARSE_ERROR); - cf.values = wdata; - track->weight_tracks.write[k] = cf; + GLTFAnimation::NodeTrack *track = nullptr; + + if (!animation->get_node_tracks().has(node)) { + animation->get_node_tracks()[node] = GLTFAnimation::NodeTrack(); + } + + track = &animation->get_node_tracks()[node]; + + if (path == "translation") { + const Vector<Vector3> positions = _decode_accessor_as_vec3(p_state, output_value_accessor_index, false); + track->position_track.interpolation = interp; + track->position_track.times = times; + track->position_track.values = positions; + } else if (path == "rotation") { + const Vector<Quaternion> rotations = _decode_accessor_as_quaternion(p_state, output_value_accessor_index, false); + track->rotation_track.interpolation = interp; + track->rotation_track.times = times; + track->rotation_track.values = rotations; + } else if (path == "scale") { + const Vector<Vector3> scales = _decode_accessor_as_vec3(p_state, output_value_accessor_index, false); + track->scale_track.interpolation = interp; + track->scale_track.times = times; + track->scale_track.values = scales; + } else if (path == "weights") { + const Vector<float> weights = _decode_accessor_as_floats(p_state, output_value_accessor_index, false); + + ERR_FAIL_INDEX_V(p_state->nodes[node]->mesh, p_state->meshes.size(), ERR_PARSE_ERROR); + Ref<GLTFMesh> mesh = p_state->meshes[p_state->nodes[node]->mesh]; + const int wc = mesh->get_blend_weights().size(); + ERR_CONTINUE_MSG(wc == 0, "glTF: Animation tried to animate weights, but mesh has no weights."); + + track->weight_tracks.resize(wc); + + const int expected_value_count = times.size() * output_count * wc; + ERR_CONTINUE_MSG(weights.size() != expected_value_count, "Invalid weight data, expected " + itos(expected_value_count) + " weight values, got " + itos(weights.size()) + " instead."); + + const int wlen = weights.size() / wc; + for (int k = 0; k < wc; k++) { //separate tracks, having them together is not such a good idea + GLTFAnimation::Channel<real_t> cf; + cf.interpolation = interp; + cf.times = Variant(times); + Vector<real_t> wdata; + wdata.resize(wlen); + for (int l = 0; l < wlen; l++) { + wdata.write[l] = weights[l * wc + k]; + } + + cf.values = wdata; + track->weight_tracks.write[k] = cf; + } + } else { + WARN_PRINT("Invalid path '" + path + "'."); } - } else { - WARN_PRINT("Invalid path '" + path + "'."); } } @@ -5089,6 +5562,96 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> p_state) { return OK; } +void GLTFDocument::_parse_animation_pointer(Ref<GLTFState> p_state, const String &p_animation_json_pointer, const Ref<GLTFAnimation> p_gltf_animation, const GLTFAnimation::Interpolation p_interp, const Vector<double> &p_times, const int p_output_value_accessor_index) { + // Special case: Convert TRS animation pointers to node track pos/rot/scale. + // This is required to handle skeleton bones, and improves performance for regular nodes. + // Mark this as unlikely because TRS animation pointers are not recommended, + // since vanilla glTF animations can already animate TRS properties directly. + // But having this code exist is required to be spec-compliant and handle all test files. + // Note that TRS still needs to be handled in the general case as well, for KHR_interactivity. + const PackedStringArray split = p_animation_json_pointer.split("/", false, 3); + if (unlikely(split.size() == 3 && split[0] == "nodes" && (split[2] == "translation" || split[2] == "rotation" || split[2] == "scale" || split[2] == "matrix" || split[2] == "weights"))) { + const GLTFNodeIndex node_index = split[1].to_int(); + HashMap<int, GLTFAnimation::NodeTrack> &node_tracks = p_gltf_animation->get_node_tracks(); + if (!node_tracks.has(node_index)) { + node_tracks[node_index] = GLTFAnimation::NodeTrack(); + } + GLTFAnimation::NodeTrack *track = &node_tracks[node_index]; + if (split[2] == "translation") { + const Vector<Vector3> positions = _decode_accessor_as_vec3(p_state, p_output_value_accessor_index, false); + track->position_track.interpolation = p_interp; + track->position_track.times = p_times; + track->position_track.values = positions; + } else if (split[2] == "rotation") { + const Vector<Quaternion> rotations = _decode_accessor_as_quaternion(p_state, p_output_value_accessor_index, false); + track->rotation_track.interpolation = p_interp; + track->rotation_track.times = p_times; + track->rotation_track.values = rotations; + } else if (split[2] == "scale") { + const Vector<Vector3> scales = _decode_accessor_as_vec3(p_state, p_output_value_accessor_index, false); + track->scale_track.interpolation = p_interp; + track->scale_track.times = p_times; + track->scale_track.values = scales; + } else if (split[2] == "matrix") { + const Vector<Transform3D> transforms = _decode_accessor_as_xform(p_state, p_output_value_accessor_index, false); + track->position_track.interpolation = p_interp; + track->position_track.times = p_times; + track->position_track.values.resize(transforms.size()); + track->rotation_track.interpolation = p_interp; + track->rotation_track.times = p_times; + track->rotation_track.values.resize(transforms.size()); + track->scale_track.interpolation = p_interp; + track->scale_track.times = p_times; + track->scale_track.values.resize(transforms.size()); + for (int i = 0; i < transforms.size(); i++) { + track->position_track.values.write[i] = transforms[i].get_origin(); + track->rotation_track.values.write[i] = transforms[i].basis.get_rotation_quaternion(); + track->scale_track.values.write[i] = transforms[i].basis.get_scale(); + } + } else { // if (split[2] == "weights") + const Vector<float> accessor_weights = _decode_accessor_as_floats(p_state, p_output_value_accessor_index, false); + const GLTFMeshIndex mesh_index = p_state->nodes[node_index]->mesh; + ERR_FAIL_INDEX(mesh_index, p_state->meshes.size()); + const Ref<GLTFMesh> gltf_mesh = p_state->meshes[mesh_index]; + const Vector<float> &blend_weights = gltf_mesh->get_blend_weights(); + const int blend_weight_count = gltf_mesh->get_blend_weights().size(); + const int anim_weights_size = accessor_weights.size(); + // For example, if a mesh has 2 blend weights, and the accessor provides 10 values, then there are 5 frames of animation, each with 2 blend weights. + ERR_FAIL_COND_MSG(blend_weight_count == 0 || ((anim_weights_size % blend_weight_count) != 0), "glTF: Cannot apply " + itos(accessor_weights.size()) + " weights to a mesh with " + itos(blend_weights.size()) + " blend weights."); + const int frame_count = anim_weights_size / blend_weight_count; + track->weight_tracks.resize(blend_weight_count); + for (int blend_weight_index = 0; blend_weight_index < blend_weight_count; blend_weight_index++) { + GLTFAnimation::Channel<real_t> weight_track; + weight_track.interpolation = p_interp; + weight_track.times = p_times; + weight_track.values.resize(frame_count); + for (int frame_index = 0; frame_index < frame_count; frame_index++) { + // For example, if a mesh has 2 blend weights, and the accessor provides 10 values, + // then the first frame has indices [0, 1], the second frame has [2, 3], and so on. + // Here we process all frames of one blend weight, so we want [0, 2, 4, 6, 8] or [1, 3, 5, 7, 9]. + // For the fist one we calculate 0 * 2 + 0, 1 * 2 + 0, 2 * 2 + 0, etc, then for the second 0 * 2 + 1, 1 * 2 + 1, 2 * 2 + 1, etc. + weight_track.values.write[frame_index] = accessor_weights[frame_index * blend_weight_count + blend_weight_index]; + } + track->weight_tracks.write[blend_weight_index] = weight_track; + } + } + // The special case was handled, return to skip the general case. + return; + } + // General case: Convert animation pointers to Variant value pointer tracks. + Ref<GLTFObjectModelProperty> obj_model_prop = import_object_model_property(p_state, p_animation_json_pointer); + if (obj_model_prop.is_null() || !obj_model_prop->has_node_paths()) { + // Exit quietly, `import_object_model_property` already prints a warning if the property is not found. + return; + } + HashMap<String, GLTFAnimation::Channel<Variant>> &anim_ptr_map = p_gltf_animation->get_pointer_tracks(); + GLTFAnimation::Channel<Variant> channel; + channel.interpolation = p_interp; + channel.times = p_times; + channel.values = _decode_accessor_as_variant(p_state, p_output_value_accessor_index, obj_model_prop->get_variant_type(), obj_model_prop->get_accessor_type()); + anim_ptr_map[p_animation_json_pointer] = channel; +} + void GLTFDocument::_assign_node_names(Ref<GLTFState> p_state) { for (int i = 0; i < p_state->nodes.size(); i++) { Ref<GLTFNode> gltf_node = p_state->nodes[i]; @@ -5254,46 +5817,46 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current, gltf_node->set_original_name(p_current->get_name()); gltf_node->set_name(_gen_unique_name(p_state, p_current->get_name())); 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); } - 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); - } 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); 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); // We ignore the Godot Engine node that is the skeleton. 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); #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()) { _convert_csg_shape_to_gltf(shape, p_gltf_parent, gltf_node, p_state); } #endif // MODULE_CSG_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); _convert_grid_map_to_gltf(gridmap, p_gltf_parent, p_gltf_root, gltf_node, p_state); #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); _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); _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); - _convert_animation_player_to_gltf(animation_player, p_state, p_gltf_parent, p_gltf_root, gltf_node, p_current); + p_state->animation_players.push_back(animation_player); } for (Ref<GLTFDocumentExtension> ext : document_extensions) { ERR_CONTINUE(ext.is_null()); @@ -5371,12 +5934,6 @@ void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeInd } #endif // MODULE_CSG_ENABLED -void GLTFDocument::_convert_animation_player_to_gltf(AnimationPlayer *p_animation_player, Ref<GLTFState> p_state, GLTFNodeIndex p_gltf_current, GLTFNodeIndex p_gltf_root_index, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) { - ERR_FAIL_NULL(p_animation_player); - p_state->animation_players.push_back(p_animation_player); - print_verbose(String("glTF: Converting animation player: ") + p_animation_player->get_name()); -} - void GLTFDocument::_check_visibility(Node *p_node, bool &r_retflag) { r_retflag = true; Node3D *spatial = Object::cast_to<Node3D>(p_node); @@ -5569,9 +6126,9 @@ void GLTFDocument::_convert_bone_attachment_to_gltf(BoneAttachment3D *p_bone_att Skeleton3D *skeleton; // Note that relative transforms to external skeletons and pose overrides are not supported. 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 { - skeleton = cast_to<Skeleton3D>(p_bone_attachment->get_parent()); + skeleton = Object::cast_to<Skeleton3D>(p_bone_attachment->get_parent()); } GLTFSkeletonIndex skel_gltf_i = -1; if (skeleton != nullptr && p_state->skeleton3d_to_gltf_skeleton.has(skeleton->get_instance_id())) { @@ -5854,7 +6411,7 @@ struct SceneFormatImporterGLTFInterpolate<Quaternion> { }; template <typename T> -T GLTFDocument::_interpolate_track(const Vector<real_t> &p_times, const Vector<T> &p_values, const float p_time, const GLTFAnimation::Interpolation p_interp) { +T GLTFDocument::_interpolate_track(const Vector<double> &p_times, const Vector<T> &p_values, const float p_time, const GLTFAnimation::Interpolation p_interp) { ERR_FAIL_COND_V(p_values.is_empty(), T()); if (p_times.size() != (p_values.size() / (p_interp == GLTFAnimation::INTERP_CUBIC_SPLINE ? 3 : 1))) { ERR_PRINT_ONCE("The interpolated values are not corresponding to its times."); @@ -5925,8 +6482,433 @@ T GLTFDocument::_interpolate_track(const Vector<real_t> &p_times, const Vector<T ERR_FAIL_V(p_values[0]); } +NodePath GLTFDocument::_find_material_node_path(Ref<GLTFState> p_state, Ref<Material> p_material) { + int mesh_index = 0; + for (Ref<GLTFMesh> gltf_mesh : p_state->meshes) { + TypedArray<Material> materials = gltf_mesh->get_instance_materials(); + for (int mat_index = 0; mat_index < materials.size(); mat_index++) { + if (materials[mat_index] == p_material) { + for (Ref<GLTFNode> gltf_node : p_state->nodes) { + if (gltf_node->mesh == mesh_index) { + NodePath node_path = gltf_node->get_scene_node_path(p_state); + // Example: MyNode:mesh:surface_0/material:albedo_color, so we want the mesh:surface_0/material part. + Vector<StringName> subpath; + subpath.append("mesh"); + subpath.append("surface_" + itos(mat_index) + "/material"); + return NodePath(node_path.get_names(), subpath, false); + } + } + } + } + mesh_index++; + } + return NodePath(); +} + +Ref<GLTFObjectModelProperty> GLTFDocument::import_object_model_property(Ref<GLTFState> p_state, const String &p_json_pointer) { + if (p_state->object_model_properties.has(p_json_pointer)) { + return p_state->object_model_properties[p_json_pointer]; + } + Ref<GLTFObjectModelProperty> ret; + // Split the JSON pointer into its components. + const PackedStringArray split = p_json_pointer.split("/", false); + ERR_FAIL_COND_V_MSG(split.size() < 3, ret, "glTF: Cannot use JSON pointer '" + p_json_pointer + "' because it does not contain enough elements. The only animatable properties are at least 3 levels deep (ex: '/nodes/0/translation' or '/materials/0/emissiveFactor')."); + ret.instantiate(); + ret->set_json_pointers({ split }); + // Partial paths are passed to GLTFDocumentExtension classes if GLTFDocument cannot handle a given JSON pointer. + TypedArray<NodePath> partial_paths; + // Note: This might not be an integer, but in that case, we don't use this value anyway. + const int top_level_index = split[1].to_int(); + // For JSON pointers present in the core glTF Object Model, hard-code them in GLTFDocument. + // https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/ObjectModel.adoc + if (split[0] == "nodes") { + ERR_FAIL_INDEX_V_MSG(top_level_index, p_state->nodes.size(), ret, vformat("glTF: Unable to find node %d for JSON pointer '%s'.", top_level_index, p_json_pointer)); + Ref<GLTFNode> pointed_gltf_node = p_state->nodes[top_level_index]; + NodePath node_path = pointed_gltf_node->get_scene_node_path(p_state); + partial_paths.append(node_path); + // Check if it's something we should be able to handle. + const String &node_prop = split[2]; + if (node_prop == "translation") { + ret->append_path_to_property(node_path, "position"); + ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3); + } else if (node_prop == "rotation") { + ret->append_path_to_property(node_path, "quaternion"); + ret->set_types(Variant::QUATERNION, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4); + } else if (node_prop == "scale") { + ret->append_path_to_property(node_path, "scale"); + ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3); + } else if (node_prop == "matrix") { + ret->append_path_to_property(node_path, "transform"); + ret->set_types(Variant::TRANSFORM3D, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4X4); + } else if (node_prop == "globalMatrix") { + ret->append_path_to_property(node_path, "global_transform"); + ret->set_types(Variant::TRANSFORM3D, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4X4); + } else if (node_prop == "weights") { + if (split.size() > 3) { + const String &weight_index_string = split[3]; + ret->append_path_to_property(node_path, "blend_shapes/morph_" + weight_index_string); + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + } + // Else, Godot's MeshInstance3D does not expose the blend shape weights as one property. + // But that's fine, we handle this case in _parse_animation_pointer instead. + } + } else if (split[0] == "cameras") { + const String &camera_prop = split[2]; + for (Ref<GLTFNode> gltf_node : p_state->nodes) { + if (gltf_node->camera == top_level_index) { + NodePath node_path = gltf_node->get_scene_node_path(p_state); + partial_paths.append(node_path); + // Check if it's something we should be able to handle. + if (camera_prop == "orthographic" || camera_prop == "perspective") { + ERR_FAIL_COND_V(split.size() < 4, ret); + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + const String &sub_prop = split[3]; + if (sub_prop == "xmag" || sub_prop == "ymag") { + ret->append_path_to_property(node_path, "size"); + } else if (sub_prop == "yfov") { + ret->append_path_to_property(node_path, "fov"); + GLTFCamera::set_fov_conversion_expressions(ret); + } else if (sub_prop == "zfar") { + ret->append_path_to_property(node_path, "far"); + } else if (sub_prop == "znear") { + ret->append_path_to_property(node_path, "near"); + } + } + } + } + } else if (split[0] == "materials") { + ERR_FAIL_INDEX_V_MSG(top_level_index, p_state->materials.size(), ret, vformat("glTF: Unable to find material %d for JSON pointer '%s'.", top_level_index, p_json_pointer)); + Ref<Material> pointed_material = p_state->materials[top_level_index]; + NodePath mat_path = _find_material_node_path(p_state, pointed_material); + if (mat_path.is_empty()) { + WARN_PRINT(vformat("glTF: Unable to find a path to the material %d for JSON pointer '%s'. This is likely bad data but it's also possible this is intentional. Continuing anyway.", top_level_index, p_json_pointer)); + } else { + partial_paths.append(mat_path); + const String &mat_prop = split[2]; + if (mat_prop == "alphaCutoff") { + ret->append_path_to_property(mat_path, "alpha_scissor_threshold"); + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + } else if (mat_prop == "emissiveFactor") { + ret->append_path_to_property(mat_path, "emission"); + ret->set_types(Variant::COLOR, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3); + } else if (mat_prop == "extensions") { + ERR_FAIL_COND_V(split.size() < 5, ret); + const String &ext_name = split[3]; + const String &ext_prop = split[4]; + if (ext_name == "KHR_materials_emissive_strength" && ext_prop == "emissiveStrength") { + ret->append_path_to_property(mat_path, "emission_energy_multiplier"); + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + } + } else { + ERR_FAIL_COND_V(split.size() < 4, ret); + const String &sub_prop = split[3]; + if (mat_prop == "normalTexture") { + if (sub_prop == "scale") { + ret->append_path_to_property(mat_path, "normal_scale"); + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + } + } else if (mat_prop == "occlusionTexture") { + if (sub_prop == "strength") { + // This is the closest thing Godot has to an occlusion strength property. + ret->append_path_to_property(mat_path, "ao_light_affect"); + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + } + } else if (mat_prop == "pbrMetallicRoughness") { + if (sub_prop == "baseColorFactor") { + ret->append_path_to_property(mat_path, "albedo_color"); + ret->set_types(Variant::COLOR, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4); + } else if (sub_prop == "metallicFactor") { + ret->append_path_to_property(mat_path, "metallic"); + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + } else if (sub_prop == "roughnessFactor") { + ret->append_path_to_property(mat_path, "roughness"); + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + } else if (sub_prop == "baseColorTexture") { + ERR_FAIL_COND_V(split.size() < 6, ret); + const String &tex_ext_dict = split[4]; + const String &tex_ext_name = split[5]; + const String &tex_ext_prop = split[6]; + if (tex_ext_dict == "extensions" && tex_ext_name == "KHR_texture_transform") { + // Godot only supports UVs for the whole material, not per texture. + // We treat the albedo texture as the main texture, and import as UV1. + // Godot does not support texture rotation, only offset and scale. + if (tex_ext_prop == "offset") { + ret->append_path_to_property(mat_path, "uv1_offset"); + ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT2); + } else if (tex_ext_prop == "scale") { + ret->append_path_to_property(mat_path, "uv1_scale"); + ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT2); + } + } + } + } + } + } + } else if (split[0] == "meshes") { + for (Ref<GLTFNode> gltf_node : p_state->nodes) { + if (gltf_node->mesh == top_level_index) { + NodePath node_path = gltf_node->get_scene_node_path(p_state); + Vector<StringName> subpath; + subpath.append("mesh"); + partial_paths.append(NodePath(node_path.get_names(), subpath, false)); + break; + } + } + } else if (split[0] == "extensions") { + if (split[1] == "KHR_lights_punctual" && split[2] == "lights" && split.size() > 4) { + const int light_index = split[3].to_int(); + ERR_FAIL_INDEX_V_MSG(light_index, p_state->lights.size(), ret, vformat("glTF: Unable to find light %d for JSON pointer '%s'.", light_index, p_json_pointer)); + const String &light_prop = split[4]; + const Ref<GLTFLight> pointed_light = p_state->lights[light_index]; + for (Ref<GLTFNode> gltf_node : p_state->nodes) { + if (gltf_node->light == light_index) { + NodePath node_path = gltf_node->get_scene_node_path(p_state); + partial_paths.append(node_path); + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + // Check if it's something we should be able to handle. + if (light_prop == "color") { + ret->append_path_to_property(node_path, "light_color"); + ret->set_types(Variant::COLOR, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3); + } else if (light_prop == "intensity") { + ret->append_path_to_property(node_path, "light_energy"); + } else if (light_prop == "range") { + const String &light_type = p_state->lights[light_index]->light_type; + if (light_type == "spot") { + ret->append_path_to_property(node_path, "spot_range"); + } else { + ret->append_path_to_property(node_path, "omni_range"); + } + } else if (light_prop == "spot") { + ERR_FAIL_COND_V(split.size() < 6, ret); + const String &sub_prop = split[5]; + if (sub_prop == "innerConeAngle") { + ret->append_path_to_property(node_path, "spot_angle_attenuation"); + GLTFLight::set_cone_inner_attenuation_conversion_expressions(ret); + } else if (sub_prop == "outerConeAngle") { + ret->append_path_to_property(node_path, "spot_angle"); + } + } + } + } + } + } + // Additional JSON pointers can be added by GLTFDocumentExtension classes. + // We only need this if no mapping has been found yet from GLTFDocument's internal code. + // When available, we pass the partial paths to the extension to help it generate the full path. + // For example, for `/nodes/3/extensions/MY_ext/prop`, we pass a NodePath that leads to node 3, + // so the GLTFDocumentExtension class only needs to resolve the last `MY_ext/prop` part of the path. + // It should check `split.size() > 4 and split[0] == "nodes" and split[2] == "extensions" and split[3] == "MY_ext"` + // at the start of the function to check if this JSON pointer applies to it, then it can handle `split[4]`. + if (!ret->has_node_paths()) { + for (Ref<GLTFDocumentExtension> ext : all_document_extensions) { + ret = ext->import_object_model_property(p_state, split, partial_paths); + if (ret.is_valid() && ret->has_node_paths()) { + if (!ret->has_json_pointers()) { + ret->set_json_pointers({ split }); + } + break; + } + } + if (ret.is_null() || !ret->has_node_paths()) { + if (split.has("KHR_texture_transform")) { + WARN_VERBOSE(vformat("glTF: Texture transforms are only supported per material in Godot. All KHR_texture_transform properties will be ignored except for the albedo texture. Ignoring JSON pointer '%s'.", p_json_pointer)); + } else { + WARN_PRINT(vformat("glTF: Animation contained JSON pointer '%s' which could not be resolved. This property will not be animated.", p_json_pointer)); + } + } + } + p_state->object_model_properties[p_json_pointer] = ret; + return ret; +} + +Ref<GLTFObjectModelProperty> GLTFDocument::export_object_model_property(Ref<GLTFState> p_state, const NodePath &p_node_path, const Node *p_godot_node, GLTFNodeIndex p_gltf_node_index) { + Ref<GLTFObjectModelProperty> ret; + const Object *target_object = p_godot_node; + const Vector<StringName> subpath = p_node_path.get_subnames(); + ERR_FAIL_COND_V_MSG(subpath.is_empty(), ret, "glTF: Cannot export empty property. No property was specified in the NodePath: " + p_node_path); + int target_prop_depth = 0; + for (StringName subname : subpath) { + Variant target_property = target_object->get(subname); + if (target_property.get_type() == Variant::OBJECT) { + target_object = target_property; + if (target_object) { + target_prop_depth++; + continue; + } + } + break; + } + const String &target_prop = subpath[target_prop_depth]; + ret.instantiate(); + ret->set_node_paths({ p_node_path }); + Vector<PackedStringArray> split_json_pointers; + PackedStringArray split_json_pointer; + if (Object::cast_to<BaseMaterial3D>(target_object)) { + for (int i = 0; i < p_state->materials.size(); i++) { + if (p_state->materials[i].ptr() == target_object) { + split_json_pointer.append("materials"); + split_json_pointer.append(itos(i)); + if (target_prop == "alpha_scissor_threshold") { + split_json_pointer.append("alphaCutoff"); + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + } else if (target_prop == "emission") { + split_json_pointer.append("emissiveFactor"); + ret->set_types(Variant::COLOR, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3); + } else if (target_prop == "emission_energy_multiplier") { + split_json_pointer.append("extensions"); + split_json_pointer.append("KHR_materials_emissive_strength"); + split_json_pointer.append("emissiveStrength"); + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + } else if (target_prop == "normal_scale") { + split_json_pointer.append("normalTexture"); + split_json_pointer.append("scale"); + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + } else if (target_prop == "ao_light_affect") { + split_json_pointer.append("occlusionTexture"); + split_json_pointer.append("strength"); + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + } else if (target_prop == "albedo_color") { + split_json_pointer.append("pbrMetallicRoughness"); + split_json_pointer.append("baseColorFactor"); + ret->set_types(Variant::COLOR, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4); + } else if (target_prop == "metallic") { + split_json_pointer.append("pbrMetallicRoughness"); + split_json_pointer.append("metallicFactor"); + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + } else if (target_prop == "roughness") { + split_json_pointer.append("pbrMetallicRoughness"); + split_json_pointer.append("roughnessFactor"); + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + } else if (target_prop == "uv1_offset" || target_prop == "uv1_scale") { + split_json_pointer.append("pbrMetallicRoughness"); + split_json_pointer.append("baseColorTexture"); + split_json_pointer.append("extensions"); + split_json_pointer.append("KHR_texture_transform"); + if (target_prop == "uv1_offset") { + split_json_pointer.append("offset"); + } else { + split_json_pointer.append("scale"); + } + ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT2); + } else { + split_json_pointer.clear(); + } + break; + } + } + } else { + // Properties directly on Godot nodes. + Ref<GLTFNode> gltf_node = p_state->nodes[p_gltf_node_index]; + if (Object::cast_to<Camera3D>(target_object) && gltf_node->camera >= 0) { + split_json_pointer.append("cameras"); + split_json_pointer.append(itos(gltf_node->camera)); + const Camera3D *camera_node = Object::cast_to<Camera3D>(target_object); + const Camera3D::ProjectionType projection_type = camera_node->get_projection(); + if (projection_type == Camera3D::PROJECTION_PERSPECTIVE) { + split_json_pointer.append("perspective"); + } else { + split_json_pointer.append("orthographic"); + } + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + if (target_prop == "size") { + PackedStringArray xmag = split_json_pointer.duplicate(); + xmag.append("xmag"); + split_json_pointers.append(xmag); + split_json_pointer.append("ymag"); + } else if (target_prop == "fov") { + split_json_pointer.append("yfov"); + GLTFCamera::set_fov_conversion_expressions(ret); + } else if (target_prop == "far") { + split_json_pointer.append("zfar"); + } else if (target_prop == "near") { + split_json_pointer.append("znear"); + } else { + split_json_pointer.clear(); + } + } else if (Object::cast_to<Light3D>(target_object) && gltf_node->light >= 0) { + split_json_pointer.append("extensions"); + split_json_pointer.append("KHR_lights_punctual"); + split_json_pointer.append("lights"); + split_json_pointer.append(itos(gltf_node->light)); + ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT); + if (target_prop == "light_color") { + split_json_pointer.append("color"); + ret->set_types(Variant::COLOR, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3); + } else if (target_prop == "light_energy") { + split_json_pointer.append("intensity"); + } else if (target_prop == "spot_range") { + split_json_pointer.append("range"); + } else if (target_prop == "omni_range") { + split_json_pointer.append("range"); + } else if (target_prop == "spot_angle") { + split_json_pointer.append("spot"); + split_json_pointer.append("outerConeAngle"); + } else if (target_prop == "spot_angle_attenuation") { + split_json_pointer.append("spot"); + split_json_pointer.append("innerConeAngle"); + GLTFLight::set_cone_inner_attenuation_conversion_expressions(ret); + } else { + split_json_pointer.clear(); + } + } else if (Object::cast_to<MeshInstance3D>(target_object) && target_prop.begins_with("blend_shapes/morph_")) { + const String &weight_index_string = target_prop.trim_prefix("blend_shapes/morph_"); + split_json_pointer.append("nodes"); + split_json_pointer.append(itos(p_gltf_node_index)); + split_json_pointer.append("weights"); + split_json_pointer.append(weight_index_string); + } + // Transform properties. Check for all 3D nodes if we haven't resolved the JSON pointer yet. + // Note: Do not put this in an `else`, because otherwise this will not be reached. + if (split_json_pointer.is_empty() && Object::cast_to<Node3D>(target_object)) { + split_json_pointer.append("nodes"); + split_json_pointer.append(itos(p_gltf_node_index)); + if (target_prop == "position") { + split_json_pointer.append("translation"); + ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3); + } else if (target_prop == "quaternion") { + // Note: Only Quaternion rotation can be converted from Godot in this mapping. + // Struct methods like from_euler are not accessible from the Expression class. :( + split_json_pointer.append("rotation"); + ret->set_types(Variant::QUATERNION, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4); + } else if (target_prop == "scale") { + split_json_pointer.append("scale"); + ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3); + } else if (target_prop == "transform") { + split_json_pointer.append("matrix"); + ret->set_types(Variant::TRANSFORM3D, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4X4); + } else if (target_prop == "global_transform") { + split_json_pointer.append("globalMatrix"); + ret->set_types(Variant::TRANSFORM3D, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4X4); + } else { + split_json_pointer.clear(); + } + } + } + // Additional JSON pointers can be added by GLTFDocumentExtension classes. + // We only need this if no mapping has been found yet from GLTFDocument's internal code. + // We pass as many pieces of information as we can to the extension to give it lots of context. + if (split_json_pointer.is_empty()) { + for (Ref<GLTFDocumentExtension> ext : all_document_extensions) { + ret = ext->export_object_model_property(p_state, p_node_path, p_godot_node, p_gltf_node_index, target_object, target_prop_depth); + if (ret.is_valid() && ret->has_json_pointers()) { + if (!ret->has_node_paths()) { + ret->set_node_paths({ p_node_path }); + } + break; + } + } + } else { + // GLTFDocument's internal code found a mapping, so set it and return it. + split_json_pointers.append(split_json_pointer); + ret->set_json_pointers(split_json_pointers); + } + return ret; +} + void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, const GLTFAnimationIndex p_index, const bool p_trimming, const bool p_remove_immutable_tracks) { ERR_FAIL_COND(p_state.is_null()); + Node *scene_root = p_animation_player->get_parent(); + ERR_FAIL_NULL(scene_root); Ref<GLTFAnimation> anim = p_state->animations[p_index]; String anim_name = anim->get_name(); @@ -5947,8 +6929,8 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_ double anim_start = p_trimming ? INFINITY : 0.0; double anim_end = 0.0; - for (const KeyValue<int, GLTFAnimation::Track> &track_i : anim->get_tracks()) { - const GLTFAnimation::Track &track = track_i.value; + for (const KeyValue<int, GLTFAnimation::NodeTrack> &track_i : anim->get_node_tracks()) { + const GLTFAnimation::NodeTrack &track = track_i.value; //need to find the path: for skeletons, weight tracks will affect the mesh NodePath node_path; //for skeletons, transform tracks always affect bones @@ -5960,14 +6942,12 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_ const Ref<GLTFNode> gltf_node = p_state->nodes[track_i.key]; - Node *root = p_animation_player->get_parent(); - ERR_FAIL_NULL(root); HashMap<GLTFNodeIndex, Node *>::Iterator node_element = p_state->scene_nodes.find(node_index); ERR_CONTINUE_MSG(!node_element, vformat("Unable to find node %d for animation.", node_index)); - node_path = root->get_path_to(node_element->value); + node_path = scene_root->get_path_to(node_element->value); HashMap<GLTFNodeIndex, ImporterMeshInstance3D *>::Iterator mesh_instance_element = p_state->scene_mesh_instances.find(node_index); if (mesh_instance_element) { - mesh_instance_node_path = root->get_path_to(mesh_instance_element->value); + mesh_instance_node_path = scene_root->get_path_to(mesh_instance_element->value); } else { mesh_instance_node_path = node_path; } @@ -6201,6 +7181,56 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_ } } + for (const KeyValue<String, GLTFAnimation::Channel<Variant>> &track_iter : anim->get_pointer_tracks()) { + // Determine the property to animate. + const String json_pointer = track_iter.key; + const Ref<GLTFObjectModelProperty> prop = import_object_model_property(p_state, json_pointer); + ERR_FAIL_COND(prop.is_null()); + // Adjust the animation duration to encompass all keyframes. + const GLTFAnimation::Channel<Variant> &channel = track_iter.value; + ERR_CONTINUE_MSG(channel.times.size() != channel.values.size(), vformat("glTF: Animation pointer '%s' has mismatched keyframe times and values.", json_pointer)); + if (p_trimming) { + for (int i = 0; i < channel.times.size(); i++) { + anim_start = MIN(anim_start, channel.times[i]); + anim_end = MAX(anim_end, channel.times[i]); + } + } else { + for (int i = 0; i < channel.times.size(); i++) { + anim_end = MAX(anim_end, channel.times[i]); + } + } + // Begin converting the glTF animation to a Godot animation. + const Ref<Expression> gltf_to_godot_expr = prop->get_gltf_to_godot_expression(); + const bool is_gltf_to_godot_expr_valid = gltf_to_godot_expr.is_valid(); + for (const NodePath node_path : prop->get_node_paths()) { + // If using an expression, determine the base instance to pass to the expression. + Object *base_instance = nullptr; + if (is_gltf_to_godot_expr_valid) { + Ref<Resource> resource; + Vector<StringName> leftover_subpath; + base_instance = scene_root->get_node_and_resource(node_path, resource, leftover_subpath); + if (resource.is_valid()) { + base_instance = resource.ptr(); + } + } + // Add a track and insert all keys and values. + const int track_index = animation->get_track_count(); + animation->add_track(Animation::TYPE_VALUE); + animation->track_set_interpolation_type(track_index, GLTFAnimation::gltf_to_godot_interpolation(channel.interpolation)); + animation->track_set_path(track_index, node_path); + for (int i = 0; i < channel.times.size(); i++) { + const double time = channel.times[i]; + Variant value = channel.values[i]; + if (is_gltf_to_godot_expr_valid) { + Array inputs; + inputs.append(value); + value = gltf_to_godot_expr->execute(inputs, base_instance); + } + animation->track_insert_key(track_index, time, value); + } + } + } + animation->set_length(anim_end - anim_start); Ref<AnimationLibrary> library; @@ -6371,41 +7401,56 @@ void GLTFDocument::_process_mesh_instances(Ref<GLTFState> p_state, Node *p_scene } } -GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_state, GLTFAnimation::Track p_track, Ref<Animation> p_animation, int32_t p_track_i, GLTFNodeIndex p_node_i) { - Animation::InterpolationType interpolation = p_animation->track_get_interpolation_type(p_track_i); - - GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::INTERP_LINEAR; - if (interpolation == Animation::InterpolationType::INTERPOLATION_LINEAR) { - gltf_interpolation = GLTFAnimation::INTERP_LINEAR; - } else if (interpolation == Animation::InterpolationType::INTERPOLATION_NEAREST) { - gltf_interpolation = GLTFAnimation::INTERP_STEP; - } else if (interpolation == Animation::InterpolationType::INTERPOLATION_CUBIC) { - gltf_interpolation = GLTFAnimation::INTERP_CUBIC_SPLINE; +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 = Object::cast_to<Skeleton3D>(p_godot_node); + if (skeleton && p_node_subpath.size() == 1) { + // 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. + const String &bone_name = p_node_subpath[0]; + const int32_t bone_index = skeleton->find_bone(bone_name); + if (bone_index != -1) { + // A bone was found! But we still need to figure out which glTF node it corresponds to. + for (GLTFSkeletonIndex skeleton_i = 0; skeleton_i < p_state->skeletons.size(); skeleton_i++) { + const Ref<GLTFSkeleton> &skeleton_gltf = p_state->skeletons[skeleton_i]; + if (skeleton == skeleton_gltf->godot_skeleton) { + GLTFNodeIndex node_i = skeleton_gltf->godot_bone_node[bone_index]; + return node_i; + } + } + ERR_FAIL_V_MSG(-1, vformat("glTF: Found a bone %s in a Skeleton3D that wasn't in the GLTFState. Ensure that all nodes referenced by the AnimationPlayer are in the scene you are exporting.", bone_name)); + } } - Animation::TrackType track_type = p_animation->track_get_type(p_track_i); - int32_t key_count = p_animation->track_get_key_count(p_track_i); - Vector<real_t> times; - times.resize(key_count); - String path = p_animation->track_get_path(p_track_i); - for (int32_t key_i = 0; key_i < key_count; key_i++) { - times.write[key_i] = p_animation->track_get_key_time(p_track_i, key_i); + // General case: Not a skeleton bone, usually this means a normal node, or it could be the Skeleton3D itself. + for (const KeyValue<GLTFNodeIndex, Node *> &scene_node_i : p_state->scene_nodes) { + if (scene_node_i.value == p_godot_node) { + return scene_node_i.key; + } } - double anim_end = p_animation->get_length(); + ERR_FAIL_V_MSG(-1, vformat("glTF: A node was animated, but it wasn't found in the GLTFState. Ensure that all nodes referenced by the AnimationPlayer are in the scene you are exporting.")); +} + +bool GLTFDocument::_convert_animation_node_track(Ref<GLTFState> p_state, GLTFAnimation::NodeTrack &p_gltf_node_track, const Ref<Animation> &p_godot_animation, int32_t p_godot_anim_track_index, Vector<double> &p_times) { + GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::godot_to_gltf_interpolation(p_godot_animation, p_godot_anim_track_index); + const Animation::TrackType track_type = p_godot_animation->track_get_type(p_godot_anim_track_index); + const int32_t key_count = p_godot_animation->track_get_key_count(p_godot_anim_track_index); + const NodePath node_path = p_godot_animation->track_get_path(p_godot_anim_track_index); + const Vector<StringName> subpath = node_path.get_subnames(); + double anim_end = p_godot_animation->get_length(); if (track_type == Animation::TYPE_SCALE_3D) { if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) { gltf_interpolation = GLTFAnimation::INTERP_LINEAR; - p_track.scale_track.times.clear(); - p_track.scale_track.values.clear(); + p_gltf_node_track.scale_track.times.clear(); + p_gltf_node_track.scale_track.values.clear(); // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. const double increment = 1.0 / p_state->get_bake_fps(); double time = 0.0; bool last = false; while (true) { Vector3 scale; - Error err = p_animation->try_scale_track_interpolate(p_track_i, time, &scale); + Error err = p_godot_animation->try_scale_track_interpolate(p_godot_anim_track_index, time, &scale); ERR_CONTINUE(err != OK); - p_track.scale_track.values.push_back(scale); - p_track.scale_track.times.push_back(time); + p_gltf_node_track.scale_track.values.push_back(scale); + p_gltf_node_track.scale_track.times.push_back(time); if (last) { break; } @@ -6416,31 +7461,31 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta } } } else { - p_track.scale_track.times = times; - p_track.scale_track.interpolation = gltf_interpolation; - p_track.scale_track.values.resize(key_count); + p_gltf_node_track.scale_track.times = p_times; + p_gltf_node_track.scale_track.interpolation = gltf_interpolation; + p_gltf_node_track.scale_track.values.resize(key_count); for (int32_t key_i = 0; key_i < key_count; key_i++) { Vector3 scale; - Error err = p_animation->scale_track_get_key(p_track_i, key_i, &scale); + Error err = p_godot_animation->scale_track_get_key(p_godot_anim_track_index, key_i, &scale); ERR_CONTINUE(err != OK); - p_track.scale_track.values.write[key_i] = scale; + p_gltf_node_track.scale_track.values.write[key_i] = scale; } } } else if (track_type == Animation::TYPE_POSITION_3D) { if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) { gltf_interpolation = GLTFAnimation::INTERP_LINEAR; - p_track.position_track.times.clear(); - p_track.position_track.values.clear(); + p_gltf_node_track.position_track.times.clear(); + p_gltf_node_track.position_track.values.clear(); // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. const double increment = 1.0 / p_state->get_bake_fps(); double time = 0.0; bool last = false; while (true) { Vector3 scale; - Error err = p_animation->try_position_track_interpolate(p_track_i, time, &scale); + Error err = p_godot_animation->try_position_track_interpolate(p_godot_anim_track_index, time, &scale); ERR_CONTINUE(err != OK); - p_track.position_track.values.push_back(scale); - p_track.position_track.times.push_back(time); + p_gltf_node_track.position_track.values.push_back(scale); + p_gltf_node_track.position_track.times.push_back(time); if (last) { break; } @@ -6451,31 +7496,31 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta } } } else { - p_track.position_track.times = times; - p_track.position_track.values.resize(key_count); - p_track.position_track.interpolation = gltf_interpolation; + p_gltf_node_track.position_track.times = p_times; + p_gltf_node_track.position_track.values.resize(key_count); + p_gltf_node_track.position_track.interpolation = gltf_interpolation; for (int32_t key_i = 0; key_i < key_count; key_i++) { Vector3 position; - Error err = p_animation->position_track_get_key(p_track_i, key_i, &position); + Error err = p_godot_animation->position_track_get_key(p_godot_anim_track_index, key_i, &position); ERR_CONTINUE(err != OK); - p_track.position_track.values.write[key_i] = position; + p_gltf_node_track.position_track.values.write[key_i] = position; } } } else if (track_type == Animation::TYPE_ROTATION_3D) { if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) { gltf_interpolation = GLTFAnimation::INTERP_LINEAR; - p_track.rotation_track.times.clear(); - p_track.rotation_track.values.clear(); + p_gltf_node_track.rotation_track.times.clear(); + p_gltf_node_track.rotation_track.values.clear(); // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. const double increment = 1.0 / p_state->get_bake_fps(); double time = 0.0; bool last = false; while (true) { Quaternion rotation; - Error err = p_animation->try_rotation_track_interpolate(p_track_i, time, &rotation); + Error err = p_godot_animation->try_rotation_track_interpolate(p_godot_anim_track_index, time, &rotation); ERR_CONTINUE(err != OK); - p_track.rotation_track.values.push_back(rotation); - p_track.rotation_track.times.push_back(time); + p_gltf_node_track.rotation_track.values.push_back(rotation); + p_gltf_node_track.rotation_track.times.push_back(time); if (last) { break; } @@ -6486,306 +7531,326 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta } } } else { - p_track.rotation_track.times = times; - p_track.rotation_track.values.resize(key_count); - p_track.rotation_track.interpolation = gltf_interpolation; + p_gltf_node_track.rotation_track.times = p_times; + p_gltf_node_track.rotation_track.values.resize(key_count); + p_gltf_node_track.rotation_track.interpolation = gltf_interpolation; for (int32_t key_i = 0; key_i < key_count; key_i++) { Quaternion rotation; - Error err = p_animation->rotation_track_get_key(p_track_i, key_i, &rotation); + Error err = p_godot_animation->rotation_track_get_key(p_godot_anim_track_index, key_i, &rotation); ERR_CONTINUE(err != OK); - p_track.rotation_track.values.write[key_i] = rotation; - } - } - } else if (track_type == Animation::TYPE_VALUE) { - if (path.contains(":position")) { - p_track.position_track.interpolation = gltf_interpolation; - p_track.position_track.times = times; - p_track.position_track.values.resize(key_count); - - if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) { - gltf_interpolation = GLTFAnimation::INTERP_LINEAR; - p_track.position_track.times.clear(); - p_track.position_track.values.clear(); - // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. - const double increment = 1.0 / p_state->get_bake_fps(); - double time = 0.0; - bool last = false; - while (true) { - Vector3 position; - Error err = p_animation->try_position_track_interpolate(p_track_i, time, &position); - ERR_CONTINUE(err != OK); - p_track.position_track.values.push_back(position); - p_track.position_track.times.push_back(time); - if (last) { - break; + p_gltf_node_track.rotation_track.values.write[key_i] = rotation; + } + } + } else if (subpath.size() > 0) { + const StringName &node_prop = subpath[0]; + if (track_type == Animation::TYPE_VALUE) { + if (node_prop == "position") { + p_gltf_node_track.position_track.interpolation = gltf_interpolation; + p_gltf_node_track.position_track.times = p_times; + p_gltf_node_track.position_track.values.resize(key_count); + + if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) { + gltf_interpolation = GLTFAnimation::INTERP_LINEAR; + p_gltf_node_track.position_track.times.clear(); + p_gltf_node_track.position_track.values.clear(); + // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. + const double increment = 1.0 / p_state->get_bake_fps(); + double time = 0.0; + bool last = false; + while (true) { + Vector3 position; + Error err = p_godot_animation->try_position_track_interpolate(p_godot_anim_track_index, time, &position); + ERR_CONTINUE(err != OK); + p_gltf_node_track.position_track.values.push_back(position); + p_gltf_node_track.position_track.times.push_back(time); + if (last) { + break; + } + time += increment; + if (time >= anim_end) { + last = true; + time = anim_end; + } } - time += increment; - if (time >= anim_end) { - last = true; - time = anim_end; + } else { + for (int32_t key_i = 0; key_i < key_count; key_i++) { + Vector3 position = p_godot_animation->track_get_key_value(p_godot_anim_track_index, key_i); + p_gltf_node_track.position_track.values.write[key_i] = position; } } - } else { - for (int32_t key_i = 0; key_i < key_count; key_i++) { - Vector3 position = p_animation->track_get_key_value(p_track_i, key_i); - p_track.position_track.values.write[key_i] = position; - } - } - } else if (path.contains(":rotation")) { - p_track.rotation_track.interpolation = gltf_interpolation; - p_track.rotation_track.times = times; - p_track.rotation_track.values.resize(key_count); - if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) { - gltf_interpolation = GLTFAnimation::INTERP_LINEAR; - p_track.rotation_track.times.clear(); - p_track.rotation_track.values.clear(); - // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. - const double increment = 1.0 / p_state->get_bake_fps(); - double time = 0.0; - bool last = false; - while (true) { - Quaternion rotation; - Error err = p_animation->try_rotation_track_interpolate(p_track_i, time, &rotation); - ERR_CONTINUE(err != OK); - p_track.rotation_track.values.push_back(rotation); - p_track.rotation_track.times.push_back(time); - if (last) { - break; + } else if (node_prop == "rotation" || node_prop == "rotation_degrees" || node_prop == "quaternion") { + p_gltf_node_track.rotation_track.interpolation = gltf_interpolation; + p_gltf_node_track.rotation_track.times = p_times; + p_gltf_node_track.rotation_track.values.resize(key_count); + if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) { + gltf_interpolation = GLTFAnimation::INTERP_LINEAR; + p_gltf_node_track.rotation_track.times.clear(); + p_gltf_node_track.rotation_track.values.clear(); + // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. + const double increment = 1.0 / p_state->get_bake_fps(); + double time = 0.0; + bool last = false; + while (true) { + Quaternion rotation; + Error err = p_godot_animation->try_rotation_track_interpolate(p_godot_anim_track_index, time, &rotation); + ERR_CONTINUE(err != OK); + p_gltf_node_track.rotation_track.values.push_back(rotation); + p_gltf_node_track.rotation_track.times.push_back(time); + if (last) { + break; + } + time += increment; + if (time >= anim_end) { + last = true; + time = anim_end; + } } - time += increment; - if (time >= anim_end) { - last = true; - time = anim_end; + } else { + for (int32_t key_i = 0; key_i < key_count; key_i++) { + Quaternion rotation_quaternion; + if (node_prop == "quaternion") { + rotation_quaternion = p_godot_animation->track_get_key_value(p_godot_anim_track_index, key_i); + } else { + Vector3 rotation_euler = p_godot_animation->track_get_key_value(p_godot_anim_track_index, key_i); + if (node_prop == "rotation_degrees") { + rotation_euler *= Math_TAU / 360.0; + } + rotation_quaternion = Quaternion::from_euler(rotation_euler); + } + p_gltf_node_track.rotation_track.values.write[key_i] = rotation_quaternion; } } - } else { - for (int32_t key_i = 0; key_i < key_count; key_i++) { - Vector3 rotation_radian = p_animation->track_get_key_value(p_track_i, key_i); - p_track.rotation_track.values.write[key_i] = Quaternion::from_euler(rotation_radian); + } else if (node_prop == "scale") { + p_gltf_node_track.scale_track.interpolation = gltf_interpolation; + p_gltf_node_track.scale_track.times = p_times; + p_gltf_node_track.scale_track.values.resize(key_count); + + if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) { + gltf_interpolation = GLTFAnimation::INTERP_LINEAR; + p_gltf_node_track.scale_track.times.clear(); + p_gltf_node_track.scale_track.values.clear(); + // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. + const double increment = 1.0 / p_state->get_bake_fps(); + double time = 0.0; + bool last = false; + while (true) { + Vector3 scale; + Error err = p_godot_animation->try_scale_track_interpolate(p_godot_anim_track_index, time, &scale); + ERR_CONTINUE(err != OK); + p_gltf_node_track.scale_track.values.push_back(scale); + p_gltf_node_track.scale_track.times.push_back(time); + if (last) { + break; + } + time += increment; + if (time >= anim_end) { + last = true; + time = anim_end; + } + } + } else { + for (int32_t key_i = 0; key_i < key_count; key_i++) { + Vector3 scale_track = p_godot_animation->track_get_key_value(p_godot_anim_track_index, key_i); + p_gltf_node_track.scale_track.values.write[key_i] = scale_track; + } } - } - } else if (path.contains(":scale")) { - p_track.scale_track.times = times; - p_track.scale_track.interpolation = gltf_interpolation; - - p_track.scale_track.values.resize(key_count); - p_track.scale_track.interpolation = gltf_interpolation; - - if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) { - gltf_interpolation = GLTFAnimation::INTERP_LINEAR; - p_track.scale_track.times.clear(); - p_track.scale_track.values.clear(); - // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. - const double increment = 1.0 / p_state->get_bake_fps(); - double time = 0.0; - bool last = false; - while (true) { - Vector3 scale; - Error err = p_animation->try_scale_track_interpolate(p_track_i, time, &scale); - ERR_CONTINUE(err != OK); - p_track.scale_track.values.push_back(scale); - p_track.scale_track.times.push_back(time); - if (last) { - break; + } else if (node_prop == "transform") { + p_gltf_node_track.position_track.interpolation = gltf_interpolation; + p_gltf_node_track.position_track.times = p_times; + p_gltf_node_track.position_track.values.resize(key_count); + p_gltf_node_track.rotation_track.interpolation = gltf_interpolation; + p_gltf_node_track.rotation_track.times = p_times; + p_gltf_node_track.rotation_track.values.resize(key_count); + p_gltf_node_track.scale_track.interpolation = gltf_interpolation; + p_gltf_node_track.scale_track.times = p_times; + p_gltf_node_track.scale_track.values.resize(key_count); + if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) { + gltf_interpolation = GLTFAnimation::INTERP_LINEAR; + p_gltf_node_track.position_track.times.clear(); + p_gltf_node_track.position_track.values.clear(); + p_gltf_node_track.rotation_track.times.clear(); + p_gltf_node_track.rotation_track.values.clear(); + p_gltf_node_track.scale_track.times.clear(); + p_gltf_node_track.scale_track.values.clear(); + // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. + const double increment = 1.0 / p_state->get_bake_fps(); + double time = 0.0; + bool last = false; + while (true) { + Vector3 position; + Quaternion rotation; + Vector3 scale; + Error err = p_godot_animation->try_position_track_interpolate(p_godot_anim_track_index, time, &position); + ERR_CONTINUE(err != OK); + err = p_godot_animation->try_rotation_track_interpolate(p_godot_anim_track_index, time, &rotation); + ERR_CONTINUE(err != OK); + err = p_godot_animation->try_scale_track_interpolate(p_godot_anim_track_index, time, &scale); + ERR_CONTINUE(err != OK); + p_gltf_node_track.position_track.values.push_back(position); + p_gltf_node_track.position_track.times.push_back(time); + p_gltf_node_track.rotation_track.values.push_back(rotation); + p_gltf_node_track.rotation_track.times.push_back(time); + p_gltf_node_track.scale_track.values.push_back(scale); + p_gltf_node_track.scale_track.times.push_back(time); + if (last) { + break; + } + time += increment; + if (time >= anim_end) { + last = true; + time = anim_end; + } } - time += increment; - if (time >= anim_end) { - last = true; - time = anim_end; + } else { + for (int32_t key_i = 0; key_i < key_count; key_i++) { + Transform3D transform = p_godot_animation->track_get_key_value(p_godot_anim_track_index, key_i); + p_gltf_node_track.position_track.values.write[key_i] = transform.get_origin(); + p_gltf_node_track.rotation_track.values.write[key_i] = transform.basis.get_rotation_quaternion(); + p_gltf_node_track.scale_track.values.write[key_i] = transform.basis.get_scale(); } } } else { - for (int32_t key_i = 0; key_i < key_count; key_i++) { - Vector3 scale_track = p_animation->track_get_key_value(p_track_i, key_i); - p_track.scale_track.values.write[key_i] = scale_track; - } - } - } - } else if (track_type == Animation::TYPE_BEZIER) { - const int32_t keys = anim_end * p_state->get_bake_fps(); - if (path.contains(":scale")) { - if (!p_track.scale_track.times.size()) { - p_track.scale_track.interpolation = gltf_interpolation; - Vector<real_t> new_times; - new_times.resize(keys); - for (int32_t key_i = 0; key_i < keys; key_i++) { - new_times.write[key_i] = key_i / p_state->get_bake_fps(); - } - p_track.scale_track.times = new_times; + // This is a Value track animating a property, but not a TRS property, so it can't be converted into a node track. + return false; + } + } else if (track_type == Animation::TYPE_BEZIER) { + const int32_t keys = anim_end * p_state->get_bake_fps(); + if (node_prop == "scale") { + if (p_gltf_node_track.scale_track.times.is_empty()) { + p_gltf_node_track.scale_track.interpolation = gltf_interpolation; + Vector<double> new_times; + new_times.resize(keys); + for (int32_t key_i = 0; key_i < keys; key_i++) { + new_times.write[key_i] = key_i / p_state->get_bake_fps(); + } + p_gltf_node_track.scale_track.times = new_times; - p_track.scale_track.values.resize(keys); + p_gltf_node_track.scale_track.values.resize(keys); - for (int32_t key_i = 0; key_i < keys; key_i++) { - p_track.scale_track.values.write[key_i] = Vector3(1.0f, 1.0f, 1.0f); - } + for (int32_t key_i = 0; key_i < keys; key_i++) { + p_gltf_node_track.scale_track.values.write[key_i] = Vector3(1.0f, 1.0f, 1.0f); + } - for (int32_t key_i = 0; key_i < keys; key_i++) { - Vector3 bezier_track = p_track.scale_track.values[key_i]; - if (path.contains(":scale:x")) { - bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps()); - } else if (path.contains(":scale:y")) { - bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps()); - } else if (path.contains(":scale:z")) { - bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps()); + for (int32_t key_i = 0; key_i < keys; key_i++) { + Vector3 bezier_track = p_gltf_node_track.scale_track.values[key_i]; + if (subpath.size() == 2) { + if (subpath[1] == StringName("x")) { + bezier_track.x = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps()); + } else if (subpath[1] == StringName("y")) { + bezier_track.y = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps()); + } else if (subpath[1] == StringName("z")) { + bezier_track.z = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps()); + } + } + p_gltf_node_track.scale_track.values.write[key_i] = bezier_track; } - p_track.scale_track.values.write[key_i] = bezier_track; } - } - } else if (path.contains(":position")) { - if (!p_track.position_track.times.size()) { - p_track.position_track.interpolation = gltf_interpolation; - Vector<real_t> new_times; - new_times.resize(keys); - for (int32_t key_i = 0; key_i < keys; key_i++) { - new_times.write[key_i] = key_i / p_state->get_bake_fps(); + } else if (node_prop == "position") { + if (p_gltf_node_track.position_track.times.is_empty()) { + p_gltf_node_track.position_track.interpolation = gltf_interpolation; + Vector<double> new_times; + new_times.resize(keys); + for (int32_t key_i = 0; key_i < keys; key_i++) { + new_times.write[key_i] = key_i / p_state->get_bake_fps(); + } + p_gltf_node_track.position_track.times = new_times; + + p_gltf_node_track.position_track.values.resize(keys); } - p_track.position_track.times = new_times; - p_track.position_track.values.resize(keys); - } + for (int32_t key_i = 0; key_i < keys; key_i++) { + Vector3 bezier_track = p_gltf_node_track.position_track.values[key_i]; + if (subpath.size() == 2) { + if (subpath[1] == StringName("x")) { + bezier_track.x = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps()); + } else if (subpath[1] == StringName("y")) { + bezier_track.y = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps()); + } else if (subpath[1] == StringName("z")) { + bezier_track.z = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps()); + } + } + p_gltf_node_track.position_track.values.write[key_i] = bezier_track; + } + } else if (node_prop == "quaternion") { + if (p_gltf_node_track.rotation_track.times.is_empty()) { + p_gltf_node_track.rotation_track.interpolation = gltf_interpolation; + Vector<double> new_times; + new_times.resize(keys); + for (int32_t key_i = 0; key_i < keys; key_i++) { + new_times.write[key_i] = key_i / p_state->get_bake_fps(); + } + p_gltf_node_track.rotation_track.times = new_times; - for (int32_t key_i = 0; key_i < keys; key_i++) { - Vector3 bezier_track = p_track.position_track.values[key_i]; - if (path.contains(":position:x")) { - bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps()); - } else if (path.contains(":position:y")) { - bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps()); - } else if (path.contains(":position:z")) { - bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps()); + p_gltf_node_track.rotation_track.values.resize(keys); } - p_track.position_track.values.write[key_i] = bezier_track; - } - } else if (path.contains(":rotation")) { - if (!p_track.rotation_track.times.size()) { - p_track.rotation_track.interpolation = gltf_interpolation; - Vector<real_t> new_times; - new_times.resize(keys); for (int32_t key_i = 0; key_i < keys; key_i++) { - new_times.write[key_i] = key_i / p_state->get_bake_fps(); - } - p_track.rotation_track.times = new_times; - - p_track.rotation_track.values.resize(keys); - } - for (int32_t key_i = 0; key_i < keys; key_i++) { - Quaternion bezier_track = p_track.rotation_track.values[key_i]; - if (path.contains(":rotation:x")) { - bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps()); - } else if (path.contains(":rotation:y")) { - bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps()); - } else if (path.contains(":rotation:z")) { - bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps()); - } else if (path.contains(":rotation:w")) { - bezier_track.w = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps()); + Quaternion bezier_track = p_gltf_node_track.rotation_track.values[key_i]; + if (subpath.size() == 2) { + if (subpath[1] == StringName("x")) { + bezier_track.x = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps()); + } else if (subpath[1] == StringName("y")) { + bezier_track.y = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps()); + } else if (subpath[1] == StringName("z")) { + bezier_track.z = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps()); + } else if (subpath[1] == StringName("w")) { + bezier_track.w = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps()); + } + } + p_gltf_node_track.rotation_track.values.write[key_i] = bezier_track; } - p_track.rotation_track.values.write[key_i] = bezier_track; + } else { + // This is a Bezier track animating a property, but not a TRS property, so it can't be converted into a node track. + return false; } + } else { + // This property track isn't a Value track or Bezier track, so it can't be converted into a node track. + return false; } + } else { + // This isn't a TRS track or a property track, so it can't be converted into a node track. + return false; } - return p_track; + // If we reached this point, the track was some kind of TRS track and was successfully converted. + // All failure paths should return false before this point to indicate this + // isn't a node track so it can be handled by KHR_animation_pointer instead. + return true; } -void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, String p_animation_track_name) { +void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, const String &p_animation_track_name) { Ref<Animation> animation = p_animation_player->get_animation(p_animation_track_name); Ref<GLTFAnimation> gltf_animation; gltf_animation.instantiate(); gltf_animation->set_original_name(p_animation_track_name); gltf_animation->set_name(_gen_unique_name(p_state, p_animation_track_name)); - for (int32_t track_i = 0; track_i < animation->get_track_count(); track_i++) { - if (!animation->track_is_enabled(track_i)) { + HashMap<int, GLTFAnimation::NodeTrack> &node_tracks = gltf_animation->get_node_tracks(); + for (int32_t track_index = 0; track_index < animation->get_track_count(); track_index++) { + if (!animation->track_is_enabled(track_index)) { continue; } - String final_track_path = animation->track_get_path(track_i); - Node *animation_base_node = p_animation_player->get_parent(); - ERR_CONTINUE_MSG(!animation_base_node, "Cannot get the parent of the animation player."); - if (String(final_track_path).contains(":position")) { - const Vector<String> node_suffix = String(final_track_path).split(":position"); - const NodePath path = node_suffix[0]; - const Node *node = animation_base_node->get_node_or_null(path); - ERR_CONTINUE_MSG(!node, "Cannot get the node from a position path."); - for (const KeyValue<GLTFNodeIndex, Node *> &position_scene_node_i : p_state->scene_nodes) { - if (position_scene_node_i.value == node) { - GLTFNodeIndex node_index = position_scene_node_i.key; - HashMap<int, GLTFAnimation::Track>::Iterator position_track_i = gltf_animation->get_tracks().find(node_index); - GLTFAnimation::Track track; - if (position_track_i) { - track = position_track_i->value; - } - track = _convert_animation_track(p_state, track, animation, track_i, node_index); - gltf_animation->get_tracks().insert(node_index, track); - } - } - } else if (String(final_track_path).contains(":rotation_degrees")) { - const Vector<String> node_suffix = String(final_track_path).split(":rotation_degrees"); - const NodePath path = node_suffix[0]; - const Node *node = animation_base_node->get_node_or_null(path); - ERR_CONTINUE_MSG(!node, "Cannot get the node from a rotation degrees path."); - for (const KeyValue<GLTFNodeIndex, Node *> &rotation_degree_scene_node_i : p_state->scene_nodes) { - if (rotation_degree_scene_node_i.value == node) { - GLTFNodeIndex node_index = rotation_degree_scene_node_i.key; - HashMap<int, GLTFAnimation::Track>::Iterator rotation_degree_track_i = gltf_animation->get_tracks().find(node_index); - GLTFAnimation::Track track; - if (rotation_degree_track_i) { - track = rotation_degree_track_i->value; - } - track = _convert_animation_track(p_state, track, animation, track_i, node_index); - gltf_animation->get_tracks().insert(node_index, track); - } - } - } else if (String(final_track_path).contains(":scale")) { - const Vector<String> node_suffix = String(final_track_path).split(":scale"); - const NodePath path = node_suffix[0]; - const Node *node = animation_base_node->get_node_or_null(path); - ERR_CONTINUE_MSG(!node, "Cannot get the node from a scale path."); - for (const KeyValue<GLTFNodeIndex, Node *> &scale_scene_node_i : p_state->scene_nodes) { - if (scale_scene_node_i.value == node) { - GLTFNodeIndex node_index = scale_scene_node_i.key; - HashMap<int, GLTFAnimation::Track>::Iterator scale_track_i = gltf_animation->get_tracks().find(node_index); - GLTFAnimation::Track track; - if (scale_track_i) { - track = scale_track_i->value; - } - track = _convert_animation_track(p_state, track, animation, track_i, node_index); - gltf_animation->get_tracks().insert(node_index, track); - } - } - } else if (String(final_track_path).contains(":transform")) { - const Vector<String> node_suffix = String(final_track_path).split(":transform"); - const NodePath path = node_suffix[0]; - const Node *node = animation_base_node->get_node_or_null(path); - ERR_CONTINUE_MSG(!node, "Cannot get the node from a transform path."); - for (const KeyValue<GLTFNodeIndex, Node *> &transform_track_i : p_state->scene_nodes) { - if (transform_track_i.value == node) { - GLTFAnimation::Track track; - track = _convert_animation_track(p_state, track, animation, track_i, transform_track_i.key); - gltf_animation->get_tracks().insert(transform_track_i.key, track); - } - } - } else if (String(final_track_path).contains(":") && animation->track_get_type(track_i) == Animation::TYPE_BLEND_SHAPE) { - const Vector<String> node_suffix = String(final_track_path).split(":"); - const NodePath path = node_suffix[0]; - const String suffix = node_suffix[1]; - Node *node = animation_base_node->get_node_or_null(path); - ERR_CONTINUE_MSG(!node, "Cannot get the node from a blend shape path."); - MeshInstance3D *mi = cast_to<MeshInstance3D>(node); - if (!mi) { - continue; - } - Ref<Mesh> mesh = mi->get_mesh(); + // Get the Godot node and the glTF node index for the animation track. + const NodePath track_path = animation->track_get_path(track_index); + const Node *anim_player_parent = p_animation_player->get_parent(); + const Node *animated_node = anim_player_parent->get_node_or_null(track_path); + ERR_CONTINUE_MSG(!animated_node, "glTF: Cannot get node for animated track using path: " + String(track_path)); + const GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::godot_to_gltf_interpolation(animation, track_index); + // First, check if it's a Blend Shape track. + if (animation->track_get_type(track_index) == Animation::TYPE_BLEND_SHAPE) { + 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."); + Ref<Mesh> mesh = mesh_instance->get_mesh(); ERR_CONTINUE(mesh.is_null()); int32_t mesh_index = -1; for (const KeyValue<GLTFNodeIndex, Node *> &mesh_track_i : p_state->scene_nodes) { - if (mesh_track_i.value == node) { + if (mesh_track_i.value == animated_node) { mesh_index = mesh_track_i.key; } } ERR_CONTINUE(mesh_index == -1); - HashMap<int, GLTFAnimation::Track> &tracks = gltf_animation->get_tracks(); - GLTFAnimation::Track track = gltf_animation->get_tracks().has(mesh_index) ? gltf_animation->get_tracks()[mesh_index] : GLTFAnimation::Track(); - if (!tracks.has(mesh_index)) { + GLTFAnimation::NodeTrack track = node_tracks.has(mesh_index) ? node_tracks[mesh_index] : GLTFAnimation::NodeTrack(); + if (!node_tracks.has(mesh_index)) { for (int32_t shape_i = 0; shape_i < mesh->get_blend_shape_count(); shape_i++) { String shape_name = mesh->get_blend_shape_name(shape_i); - NodePath shape_path = String(path) + ":" + shape_name; + NodePath shape_path = NodePath(track_path.get_names(), { shape_name }, false); int32_t shape_track_i = animation->find_track(shape_path, Animation::TYPE_BLEND_SHAPE); if (shape_track_i == -1) { GLTFAnimation::Channel<real_t> weight; @@ -6797,15 +7862,6 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p track.weight_tracks.push_back(weight); continue; } - Animation::InterpolationType interpolation = animation->track_get_interpolation_type(track_i); - GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::INTERP_LINEAR; - if (interpolation == Animation::InterpolationType::INTERPOLATION_LINEAR) { - gltf_interpolation = GLTFAnimation::INTERP_LINEAR; - } else if (interpolation == Animation::InterpolationType::INTERPOLATION_NEAREST) { - gltf_interpolation = GLTFAnimation::INTERP_STEP; - } else if (interpolation == Animation::InterpolationType::INTERPOLATION_CUBIC) { - gltf_interpolation = GLTFAnimation::INTERP_CUBIC_SPLINE; - } int32_t key_count = animation->track_get_key_count(shape_track_i); GLTFAnimation::Channel<real_t> weight; weight.interpolation = gltf_interpolation; @@ -6819,64 +7875,74 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p } track.weight_tracks.push_back(weight); } - tracks[mesh_index] = track; - } - } else if (String(final_track_path).contains(":")) { - //Process skeleton - const Vector<String> node_suffix = String(final_track_path).split(":"); - const String &node = node_suffix[0]; - const NodePath node_path = node; - const String &suffix = node_suffix[1]; - Node *godot_node = animation_base_node->get_node_or_null(node_path); - if (!godot_node) { - continue; - } - Skeleton3D *skeleton = cast_to<Skeleton3D>(animation_base_node->get_node_or_null(node)); - if (!skeleton) { - continue; - } - GLTFSkeletonIndex skeleton_gltf_i = -1; - for (GLTFSkeletonIndex skeleton_i = 0; skeleton_i < p_state->skeletons.size(); skeleton_i++) { - if (p_state->skeletons[skeleton_i]->godot_skeleton == cast_to<Skeleton3D>(godot_node)) { - skeleton = p_state->skeletons[skeleton_i]->godot_skeleton; - skeleton_gltf_i = skeleton_i; - ERR_CONTINUE(!skeleton); - Ref<GLTFSkeleton> skeleton_gltf = p_state->skeletons[skeleton_gltf_i]; - int32_t bone = skeleton->find_bone(suffix); - ERR_CONTINUE_MSG(bone == -1, vformat("Cannot find the bone %s.", suffix)); - if (!skeleton_gltf->godot_bone_node.has(bone)) { - continue; - } - GLTFNodeIndex node_i = skeleton_gltf->godot_bone_node[bone]; - HashMap<int, GLTFAnimation::Track>::Iterator property_track_i = gltf_animation->get_tracks().find(node_i); - GLTFAnimation::Track track; - if (property_track_i) { - track = property_track_i->value; - } - track = _convert_animation_track(p_state, track, animation, track_i, node_i); - gltf_animation->get_tracks()[node_i] = track; - } - } - } else if (!String(final_track_path).contains(":")) { - ERR_CONTINUE(!animation_base_node); - Node *godot_node = animation_base_node->get_node_or_null(final_track_path); - ERR_CONTINUE_MSG(!godot_node, vformat("Cannot get the node from a skeleton path %s.", final_track_path)); - for (const KeyValue<GLTFNodeIndex, Node *> &scene_node_i : p_state->scene_nodes) { - if (scene_node_i.value == godot_node) { - GLTFNodeIndex node_i = scene_node_i.key; - HashMap<int, GLTFAnimation::Track>::Iterator node_track_i = gltf_animation->get_tracks().find(node_i); - GLTFAnimation::Track track; - if (node_track_i) { - track = node_track_i->value; - } - track = _convert_animation_track(p_state, track, animation, track_i, node_i); - gltf_animation->get_tracks()[node_i] = track; - break; - } + node_tracks[mesh_index] = track; } + continue; } - } - if (gltf_animation->get_tracks().size()) { + // If it's not a Blend Shape track, it must either be a TRS track, a property Value track, or something we can't handle. + // For the cases we can handle, we will need to know the glTF node index, glTF interpolation, and the times of the track. + const Vector<StringName> subnames = track_path.get_subnames(); + const GLTFNodeIndex node_i = _node_and_or_bone_to_gltf_node_index(p_state, subnames, animated_node); + ERR_CONTINUE_MSG(node_i == -1, "glTF: Cannot get glTF node index for animated track using path: " + String(track_path)); + const int anim_key_count = animation->track_get_key_count(track_index); + Vector<double> times; + times.resize(anim_key_count); + for (int32_t key_i = 0; key_i < anim_key_count; key_i++) { + times.write[key_i] = animation->track_get_key_time(track_index, key_i); + } + // Try converting the track to a TRS glTF node track. This will only succeed if the Godot animation is a TRS track. + const HashMap<int, GLTFAnimation::NodeTrack>::Iterator node_track_iter = node_tracks.find(node_i); + GLTFAnimation::NodeTrack track; + if (node_track_iter) { + track = node_track_iter->value; + } + if (_convert_animation_node_track(p_state, track, animation, track_index, times)) { + // If the track was successfully converted, save it and continue to the next track. + node_tracks[node_i] = track; + continue; + } + // If the track wasn't a TRS track or Blend Shape track, it might be a Value track animating a property. + // Then this is something that we need to handle with KHR_animation_pointer. + Ref<GLTFObjectModelProperty> obj_model_prop = export_object_model_property(p_state, track_path, animated_node, node_i); + if (obj_model_prop.is_valid() && obj_model_prop->has_json_pointers()) { + // Insert the property track into the KHR_animation_pointer pointer tracks. + GLTFAnimation::Channel<Variant> channel; + channel.interpolation = gltf_interpolation; + channel.times = times; + channel.values.resize(anim_key_count); + // If using an expression, determine the base instance to pass to the expression. + const Ref<Expression> godot_to_gltf_expr = obj_model_prop->get_godot_to_gltf_expression(); + const bool is_godot_to_gltf_expr_valid = godot_to_gltf_expr.is_valid(); + Object *base_instance = nullptr; + if (is_godot_to_gltf_expr_valid) { + Ref<Resource> resource; + Vector<StringName> leftover_subpath; + base_instance = anim_player_parent->get_node_and_resource(track_path, resource, leftover_subpath); + if (resource.is_valid()) { + base_instance = resource.ptr(); + } + } + // Convert the Godot animation values into glTF animation values (still Variant). + for (int32_t key_i = 0; key_i < anim_key_count; key_i++) { + Variant value = animation->track_get_key_value(track_index, key_i); + if (is_godot_to_gltf_expr_valid) { + Array inputs; + inputs.append(value); + value = godot_to_gltf_expr->execute(inputs, base_instance); + } + channel.values.write[key_i] = value; + } + // Use the JSON pointer to insert the property track into the pointer tracks. There will usually be just one JSON pointer. + HashMap<String, GLTFAnimation::Channel<Variant>> &pointer_tracks = gltf_animation->get_pointer_tracks(); + Vector<PackedStringArray> split_json_pointers = obj_model_prop->get_json_pointers(); + for (const PackedStringArray &split_json_pointer : split_json_pointers) { + String json_pointer_str = "/" + String("/").join(split_json_pointer); + p_state->object_model_properties[json_pointer_str] = obj_model_prop; + pointer_tracks[json_pointer_str] = channel; + } + } + } + if (!gltf_animation->is_empty_of_tracks()) { p_state->animations.push_back(gltf_animation); } } @@ -7075,6 +8141,9 @@ void GLTFDocument::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lossy_quality"), "set_lossy_quality", "get_lossy_quality"); ADD_PROPERTY(PropertyInfo(Variant::INT, "root_node_mode"), "set_root_node_mode", "get_root_node_mode"); + ClassDB::bind_static_method("GLTFDocument", D_METHOD("import_object_model_property", "state", "json_pointer"), &GLTFDocument::import_object_model_property); + ClassDB::bind_static_method("GLTFDocument", D_METHOD("export_object_model_property", "state", "node_path", "godot_node", "gltf_node_index"), &GLTFDocument::export_object_model_property); + ClassDB::bind_static_method("GLTFDocument", D_METHOD("register_gltf_document_extension", "extension", "first_priority"), &GLTFDocument::register_gltf_document_extension, DEFVAL(false)); ClassDB::bind_static_method("GLTFDocument", D_METHOD("unregister_gltf_document_extension", "extension"), @@ -7136,6 +8205,7 @@ HashSet<String> GLTFDocument::get_supported_gltf_extensions_hashset() { // If the extension is supported directly in GLTFDocument, list it here. // Other built-in extensions are supported by GLTFDocumentExtension classes. supported_extensions.insert("GODOT_single_root"); + supported_extensions.insert("KHR_animation_pointer"); supported_extensions.insert("KHR_lights_punctual"); supported_extensions.insert("KHR_materials_emissive_strength"); supported_extensions.insert("KHR_materials_pbrSpecularGlossiness"); @@ -7313,6 +8383,10 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_se err = SkinTool::_determine_skeletons(p_state->skins, p_state->nodes, p_state->skeletons, p_state->get_import_as_skeleton_bones() ? p_state->root_nodes : Vector<GLTFNodeIndex>()); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); + /* ASSIGN SCENE NODE NAMES */ + // This must be run AFTER determining skeletons, and BEFORE parsing animations. + _assign_node_names(p_state); + /* PARSE MESHES (we have enough info now) */ err = _parse_meshes(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); @@ -7329,9 +8403,6 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_se err = _parse_animations(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); - /* ASSIGN SCENE NAMES */ - _assign_node_names(p_state); - return OK; } @@ -7350,7 +8421,7 @@ PackedByteArray GLTFDocument::generate_buffer(Ref<GLTFState> p_state) { Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_path) { Ref<GLTFState> state = p_state; ERR_FAIL_COND_V(state.is_null(), ERR_INVALID_PARAMETER); - state->base_path = p_path.get_base_dir(); + state->set_base_path(p_path.get_base_dir()); state->filename = p_path.get_file(); Error err = _serialize(state); if (err != OK) { @@ -7463,7 +8534,7 @@ Error GLTFDocument::append_from_buffer(PackedByteArray p_bytes, String p_base_pa Ref<FileAccessMemory> file_access; file_access.instantiate(); file_access->open_custom(p_bytes.ptr(), p_bytes.size()); - state->base_path = p_base_path.get_base_dir(); + state->set_base_path(p_base_path.get_base_dir()); err = _parse(p_state, state->base_path, file_access); ERR_FAIL_COND_V(err != OK, err); for (Ref<GLTFDocumentExtension> ext : document_extensions) { @@ -7480,7 +8551,7 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint if (state == Ref<GLTFState>()) { state.instantiate(); } - state->filename = p_path.get_file().get_basename(); + state->set_filename(p_path.get_file().get_basename()); state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS; state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS; state->force_generate_tangents = p_flags & GLTF_IMPORT_GENERATE_TANGENT_ARRAYS; @@ -7494,7 +8565,7 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint if (base_path.is_empty()) { base_path = p_path.get_base_dir(); } - state->base_path = base_path; + state->set_base_path(base_path); err = _parse(p_state, base_path, file); ERR_FAIL_COND_V(err != OK, err); for (Ref<GLTFDocumentExtension> ext : document_extensions) { diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h index d347d49102..a6d6caa3f0 100644 --- a/modules/gltf/gltf_document.h +++ b/modules/gltf/gltf_document.h @@ -56,13 +56,6 @@ public: enum { ARRAY_BUFFER = 34962, ELEMENT_ARRAY_BUFFER = 34963, - - COMPONENT_TYPE_BYTE = 5120, - COMPONENT_TYPE_UNSIGNED_BYTE = 5121, - COMPONENT_TYPE_SHORT = 5122, - COMPONENT_TYPE_UNSIGNED_SHORT = 5123, - COMPONENT_TYPE_INT = 5125, - COMPONENT_TYPE_FLOAT = 5126, }; enum { TEXTURE_TYPE_GENERIC = 0, @@ -95,6 +88,10 @@ public: static Vector<String> get_supported_gltf_extensions(); static HashSet<String> get_supported_gltf_extensions_hashset(); + static NodePath _find_material_node_path(Ref<GLTFState> p_state, Ref<Material> p_material); + static Ref<GLTFObjectModelProperty> import_object_model_property(Ref<GLTFState> p_state, const String &p_json_pointer); + static Ref<GLTFObjectModelProperty> export_object_model_property(Ref<GLTFState> p_state, const NodePath &p_node_path, const Node *p_godot_node, GLTFNodeIndex p_gltf_node_index); + void set_naming_version(int p_version); int get_naming_version() const; void set_image_format(const String &p_image_format); @@ -109,8 +106,8 @@ private: void _build_parent_hierachy(Ref<GLTFState> p_state); double _filter_number(double p_float); void _round_min_max_components(Vector<double> &r_type_min, Vector<double> &r_type_max); - String _get_component_type_name(const uint32_t p_component); - int _get_component_type_size(const int p_component_type); + String _get_component_type_name(const GLTFAccessor::GLTFComponentType p_component_type); + int _get_component_type_size(const GLTFAccessor::GLTFComponentType p_component_type); Error _parse_scenes(Ref<GLTFState> p_state); Error _parse_nodes(Ref<GLTFState> p_state); String _get_accessor_type_name(const GLTFAccessor::GLTFAccessorType p_accessor_type); @@ -140,7 +137,7 @@ private: const int p_skip_every, const int p_skip_bytes, const int p_element_size, const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type, const int p_component_count, - const int p_component_type, const int p_component_size, + const GLTFAccessor::GLTFComponentType p_component_type, const int p_component_size, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex); Vector<double> _decode_accessor(Ref<GLTFState> p_state, @@ -178,6 +175,15 @@ private: Vector<Transform3D> _decode_accessor_as_xform(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); + Vector<Variant> _decode_accessor_as_variant(Ref<GLTFState> p_state, + const GLTFAccessorIndex p_accessor, + Variant::Type p_variant_type, + GLTFAccessor::GLTFAccessorType p_accessor_type); + GLTFAccessorIndex _encode_accessor_as_variant(Ref<GLTFState> p_state, + Vector<Variant> p_attribs, + Variant::Type p_variant_type, + GLTFAccessor::GLTFAccessorType p_accessor_type, + GLTFAccessor::GLTFComponentType p_component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT); Error _parse_meshes(Ref<GLTFState> p_state); Error _serialize_textures(Ref<GLTFState> p_state); Error _serialize_texture_samplers(Ref<GLTFState> p_state); @@ -205,6 +211,7 @@ private: Error _parse_cameras(Ref<GLTFState> p_state); Error _parse_lights(Ref<GLTFState> p_state); Error _parse_animations(Ref<GLTFState> p_state); + void _parse_animation_pointer(Ref<GLTFState> p_state, const String &p_animation_json_pointer, const Ref<GLTFAnimation> p_gltf_animation, const GLTFAnimation::Interpolation p_interp, const Vector<double> &p_times, const int p_output_value_accessor_index); Error _serialize_animations(Ref<GLTFState> p_state); BoneAttachment3D *_generate_bone_attachment(Ref<GLTFState> p_state, Skeleton3D *p_skeleton, @@ -216,7 +223,7 @@ private: Node3D *_generate_spatial(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index); void _assign_node_names(Ref<GLTFState> p_state); template <typename T> - T _interpolate_track(const Vector<real_t> &p_times, const Vector<T> &p_values, + T _interpolate_track(const Vector<double> &p_times, const Vector<T> &p_values, const float p_time, const GLTFAnimation::Interpolation p_interp); GLTFAccessorIndex _encode_accessor_as_quaternions(Ref<GLTFState> p_state, @@ -229,7 +236,7 @@ private: const Vector<Color> p_attribs, const bool p_for_vertex); GLTFAccessorIndex _encode_accessor_as_floats(Ref<GLTFState> p_state, - const Vector<real_t> p_attribs, + const Vector<double> p_attribs, const bool p_for_vertex); GLTFAccessorIndex _encode_accessor_as_vec2(Ref<GLTFState> p_state, const Vector<Vector2> p_attribs, @@ -269,7 +276,7 @@ private: const bool p_for_vertex); Error _encode_buffer_view(Ref<GLTFState> p_state, const double *p_src, const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type, - const int p_component_type, const bool p_normalized, + const GLTFAccessor::GLTFComponentType p_component_type, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex, GLTFBufferViewIndex &r_accessor, const bool p_for_indices = false); @@ -280,11 +287,6 @@ private: Error _serialize_nodes(Ref<GLTFState> p_state); Error _serialize_scenes(Ref<GLTFState> p_state); String interpolation_to_string(const GLTFAnimation::Interpolation p_interp); - GLTFAnimation::Track _convert_animation_track(Ref<GLTFState> p_state, - GLTFAnimation::Track p_track, - Ref<Animation> p_animation, - int32_t p_track_i, - GLTFNodeIndex p_node_i); Error _encode_buffer_bins(Ref<GLTFState> p_state, const String &p_path); Error _encode_buffer_glb(Ref<GLTFState> p_state, const String &p_path); PackedByteArray _serialize_glb_buffer(Ref<GLTFState> p_state, Error *r_err); @@ -342,11 +344,6 @@ public: void _convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeIndex p_gltf_parent, Ref<GLTFNode> p_gltf_node, Ref<GLTFState> p_state); #endif // MODULE_CSG_ENABLED - void _convert_animation_player_to_gltf( - AnimationPlayer *p_animation_player, Ref<GLTFState> p_state, - GLTFNodeIndex p_gltf_current, - GLTFNodeIndex p_gltf_root_index, - Ref<GLTFNode> p_gltf_node, Node *p_scene_parent); void _check_visibility(Node *p_node, bool &r_retflag); void _convert_camera_to_gltf(Camera3D *p_camera, Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node); @@ -377,7 +374,15 @@ public: Ref<GLTFNode> p_gltf_node); GLTFMeshIndex _convert_mesh_to_gltf(Ref<GLTFState> p_state, MeshInstance3D *p_mesh_instance); - void _convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, String p_animation_track_name); + + GLTFNodeIndex _node_and_or_bone_to_gltf_node_index(Ref<GLTFState> p_state, const Vector<StringName> &p_node_subpath, const Node *p_godot_node); + bool _convert_animation_node_track(Ref<GLTFState> p_state, + GLTFAnimation::NodeTrack &p_gltf_node_track, + const Ref<Animation> &p_godot_animation, + int32_t p_godot_anim_track_index, + Vector<double> &p_times); + void _convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, const String &p_animation_track_name); + Error _serialize(Ref<GLTFState> p_state); Error _parse(Ref<GLTFState> p_state, String p_path, Ref<FileAccess> p_file); }; diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp index 7763874d02..2488e73d08 100644 --- a/modules/gltf/gltf_state.cpp +++ b/modules/gltf/gltf_state.cpp @@ -397,8 +397,27 @@ String GLTFState::get_base_path() { return base_path; } -void GLTFState::set_base_path(String p_base_path) { +void GLTFState::set_base_path(const String &p_base_path) { base_path = p_base_path; + if (extract_path.is_empty()) { + extract_path = p_base_path; + } +} + +String GLTFState::get_extract_path() { + return extract_path; +} + +void GLTFState::set_extract_path(const String &p_extract_path) { + extract_path = p_extract_path; +} + +String GLTFState::get_extract_prefix() { + return extract_prefix; +} + +void GLTFState::set_extract_prefix(const String &p_extract_prefix) { + extract_prefix = p_extract_prefix; } String GLTFState::get_filename() const { @@ -407,6 +426,9 @@ String GLTFState::get_filename() const { void GLTFState::set_filename(const String &p_filename) { filename = p_filename; + if (extract_prefix.is_empty()) { + extract_prefix = p_filename.get_basename(); + } } Variant GLTFState::get_additional_data(const StringName &p_extension_name) { diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h index 7954049192..d667cf8858 100644 --- a/modules/gltf/gltf_state.h +++ b/modules/gltf/gltf_state.h @@ -38,6 +38,7 @@ #include "structures/gltf_camera.h" #include "structures/gltf_mesh.h" #include "structures/gltf_node.h" +#include "structures/gltf_object_model_property.h" #include "structures/gltf_skeleton.h" #include "structures/gltf_skin.h" #include "structures/gltf_texture.h" @@ -48,9 +49,12 @@ class GLTFState : public Resource { GDCLASS(GLTFState, Resource); friend class GLTFDocument; + friend class GLTFNode; protected: String base_path; + String extract_path; + String extract_prefix; String filename; Dictionary json; int major_version = 0; @@ -100,6 +104,7 @@ protected: Vector<Ref<GLTFAnimation>> animations; HashMap<GLTFNodeIndex, Node *> scene_nodes; HashMap<GLTFNodeIndex, ImporterMeshInstance3D *> scene_mesh_instances; + HashMap<String, Ref<GLTFObjectModelProperty>> object_model_properties; HashMap<ObjectID, GLTFSkeletonIndex> skeleton3d_to_gltf_skeleton; HashMap<ObjectID, HashMap<ObjectID, GLTFSkinIndex>> skin_and_skeleton3d_to_gltf_skin; @@ -186,7 +191,13 @@ public: void set_scene_name(String p_scene_name); String get_base_path(); - void set_base_path(String p_base_path); + void set_base_path(const String &p_base_path); + + String get_extract_path(); + void set_extract_path(const String &p_extract_path); + + String get_extract_prefix(); + void set_extract_prefix(const String &p_extract_prefix); String get_filename() const; void set_filename(const String &p_filename); diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp index 53e9f2b84c..fbc3ae611c 100644 --- a/modules/gltf/register_types.cpp +++ b/modules/gltf/register_types.cpp @@ -37,6 +37,7 @@ #include "extensions/physics/gltf_document_extension_physics.h" #include "gltf_document.h" #include "gltf_state.h" +#include "structures/gltf_object_model_property.h" #ifdef TOOLS_ENABLED #include "editor/editor_import_blend_runner.h" @@ -112,6 +113,7 @@ void initialize_gltf_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(GLTFLight); GDREGISTER_CLASS(GLTFMesh); GDREGISTER_CLASS(GLTFNode); + GDREGISTER_CLASS(GLTFObjectModelProperty); GDREGISTER_CLASS(GLTFPhysicsBody); GDREGISTER_CLASS(GLTFPhysicsShape); GDREGISTER_CLASS(GLTFSkeleton); diff --git a/modules/gltf/structures/gltf_accessor.cpp b/modules/gltf/structures/gltf_accessor.cpp index 1ebc00a514..300fce09ff 100644 --- a/modules/gltf/structures/gltf_accessor.cpp +++ b/modules/gltf/structures/gltf_accessor.cpp @@ -39,6 +39,19 @@ void GLTFAccessor::_bind_methods() { BIND_ENUM_CONSTANT(TYPE_MAT3); BIND_ENUM_CONSTANT(TYPE_MAT4); + BIND_ENUM_CONSTANT(COMPONENT_TYPE_NONE); + BIND_ENUM_CONSTANT(COMPONENT_TYPE_SIGNED_BYTE); + BIND_ENUM_CONSTANT(COMPONENT_TYPE_UNSIGNED_BYTE); + BIND_ENUM_CONSTANT(COMPONENT_TYPE_SIGNED_SHORT); + BIND_ENUM_CONSTANT(COMPONENT_TYPE_UNSIGNED_SHORT); + BIND_ENUM_CONSTANT(COMPONENT_TYPE_SIGNED_INT); + BIND_ENUM_CONSTANT(COMPONENT_TYPE_UNSIGNED_INT); + BIND_ENUM_CONSTANT(COMPONENT_TYPE_SINGLE_FLOAT); + BIND_ENUM_CONSTANT(COMPONENT_TYPE_DOUBLE_FLOAT); + BIND_ENUM_CONSTANT(COMPONENT_TYPE_HALF_FLOAT); + BIND_ENUM_CONSTANT(COMPONENT_TYPE_SIGNED_LONG); + BIND_ENUM_CONSTANT(COMPONENT_TYPE_UNSIGNED_LONG); + ClassDB::bind_method(D_METHOD("get_buffer_view"), &GLTFAccessor::get_buffer_view); ClassDB::bind_method(D_METHOD("set_buffer_view", "buffer_view"), &GLTFAccessor::set_buffer_view); ClassDB::bind_method(D_METHOD("get_byte_offset"), &GLTFAccessor::get_byte_offset); @@ -108,7 +121,7 @@ int GLTFAccessor::get_component_type() { } void GLTFAccessor::set_component_type(int p_component_type) { - component_type = p_component_type; + component_type = (GLTFComponentType)p_component_type; } bool GLTFAccessor::get_normalized() { @@ -188,7 +201,7 @@ int GLTFAccessor::get_sparse_indices_component_type() { } void GLTFAccessor::set_sparse_indices_component_type(int p_sparse_indices_component_type) { - sparse_indices_component_type = p_sparse_indices_component_type; + sparse_indices_component_type = (GLTFComponentType)p_sparse_indices_component_type; } int GLTFAccessor::get_sparse_values_buffer_view() { diff --git a/modules/gltf/structures/gltf_accessor.h b/modules/gltf/structures/gltf_accessor.h index 1a3a2cb494..b00e6a0f92 100644 --- a/modules/gltf/structures/gltf_accessor.h +++ b/modules/gltf/structures/gltf_accessor.h @@ -50,10 +50,25 @@ public: TYPE_MAT4, }; + enum GLTFComponentType { + COMPONENT_TYPE_NONE = 0, + COMPONENT_TYPE_SIGNED_BYTE = 5120, + COMPONENT_TYPE_UNSIGNED_BYTE = 5121, + COMPONENT_TYPE_SIGNED_SHORT = 5122, + COMPONENT_TYPE_UNSIGNED_SHORT = 5123, + COMPONENT_TYPE_SIGNED_INT = 5124, + COMPONENT_TYPE_UNSIGNED_INT = 5125, + COMPONENT_TYPE_SINGLE_FLOAT = 5126, + COMPONENT_TYPE_DOUBLE_FLOAT = 5130, + COMPONENT_TYPE_HALF_FLOAT = 5131, + COMPONENT_TYPE_SIGNED_LONG = 5134, + COMPONENT_TYPE_UNSIGNED_LONG = 5135, + }; + private: GLTFBufferViewIndex buffer_view = -1; int byte_offset = 0; - int component_type = 0; + GLTFComponentType component_type = COMPONENT_TYPE_NONE; bool normalized = false; int count = 0; GLTFAccessorType accessor_type = GLTFAccessorType::TYPE_SCALAR; @@ -62,7 +77,7 @@ private: int sparse_count = 0; int sparse_indices_buffer_view = 0; int sparse_indices_byte_offset = 0; - int sparse_indices_component_type = 0; + GLTFComponentType sparse_indices_component_type = COMPONENT_TYPE_NONE; int sparse_values_buffer_view = 0; int sparse_values_byte_offset = 0; @@ -117,5 +132,6 @@ public: }; VARIANT_ENUM_CAST(GLTFAccessor::GLTFAccessorType); +VARIANT_ENUM_CAST(GLTFAccessor::GLTFComponentType); #endif // GLTF_ACCESSOR_H diff --git a/modules/gltf/structures/gltf_animation.cpp b/modules/gltf/structures/gltf_animation.cpp index 94fda8e2f5..adc0354c4b 100644 --- a/modules/gltf/structures/gltf_animation.cpp +++ b/modules/gltf/structures/gltf_animation.cpp @@ -42,6 +42,34 @@ void GLTFAnimation::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "get_loop"); // bool } +GLTFAnimation::Interpolation GLTFAnimation::godot_to_gltf_interpolation(const Ref<Animation> &p_godot_animation, int32_t p_godot_anim_track_index) { + Animation::InterpolationType interpolation = p_godot_animation->track_get_interpolation_type(p_godot_anim_track_index); + switch (interpolation) { + case Animation::INTERPOLATION_LINEAR: + case Animation::INTERPOLATION_LINEAR_ANGLE: + return INTERP_LINEAR; + case Animation::INTERPOLATION_NEAREST: + return INTERP_STEP; + case Animation::INTERPOLATION_CUBIC: + case Animation::INTERPOLATION_CUBIC_ANGLE: + return INTERP_CUBIC_SPLINE; + } + return INTERP_LINEAR; +} + +Animation::InterpolationType GLTFAnimation::gltf_to_godot_interpolation(Interpolation p_gltf_interpolation) { + switch (p_gltf_interpolation) { + case INTERP_LINEAR: + return Animation::INTERPOLATION_LINEAR; + case INTERP_STEP: + return Animation::INTERPOLATION_NEAREST; + case INTERP_CATMULLROMSPLINE: + case INTERP_CUBIC_SPLINE: + return Animation::INTERPOLATION_CUBIC; + } + return Animation::INTERPOLATION_LINEAR; +} + String GLTFAnimation::get_original_name() { return original_name; } @@ -58,8 +86,16 @@ void GLTFAnimation::set_loop(bool p_val) { loop = p_val; } -HashMap<int, GLTFAnimation::Track> &GLTFAnimation::get_tracks() { - return tracks; +HashMap<int, GLTFAnimation::NodeTrack> &GLTFAnimation::get_node_tracks() { + return node_tracks; +} + +HashMap<String, GLTFAnimation::Channel<Variant>> &GLTFAnimation::get_pointer_tracks() { + return pointer_tracks; +} + +bool GLTFAnimation::is_empty_of_tracks() const { + return node_tracks.is_empty() && pointer_tracks.is_empty(); } GLTFAnimation::GLTFAnimation() { diff --git a/modules/gltf/structures/gltf_animation.h b/modules/gltf/structures/gltf_animation.h index afc9784895..6b692d06e6 100644 --- a/modules/gltf/structures/gltf_animation.h +++ b/modules/gltf/structures/gltf_animation.h @@ -50,33 +50,41 @@ public: template <typename T> struct Channel { Interpolation interpolation = INTERP_LINEAR; - Vector<real_t> times; + Vector<double> times; Vector<T> values; }; - struct Track { + struct NodeTrack { Channel<Vector3> position_track; Channel<Quaternion> rotation_track; Channel<Vector3> scale_track; Vector<Channel<real_t>> weight_tracks; }; + String original_name; + bool loop = false; + HashMap<int, NodeTrack> node_tracks; + HashMap<String, Channel<Variant>> pointer_tracks; + Dictionary additional_data; + public: + static Interpolation godot_to_gltf_interpolation(const Ref<Animation> &p_godot_animation, int32_t p_godot_anim_track_index); + static Animation::InterpolationType gltf_to_godot_interpolation(Interpolation p_gltf_interpolation); + String get_original_name(); void set_original_name(String p_name); bool get_loop() const; void set_loop(bool p_val); - HashMap<int, GLTFAnimation::Track> &get_tracks(); + + HashMap<int, GLTFAnimation::NodeTrack> &get_node_tracks(); + HashMap<String, GLTFAnimation::Channel<Variant>> &get_pointer_tracks(); + bool is_empty_of_tracks() const; + Variant get_additional_data(const StringName &p_extension_name); void set_additional_data(const StringName &p_extension_name, Variant p_additional_data); - GLTFAnimation(); -private: - String original_name; - bool loop = false; - HashMap<int, Track> tracks; - Dictionary additional_data; + GLTFAnimation(); }; #endif // GLTF_ANIMATION_H diff --git a/modules/gltf/structures/gltf_camera.cpp b/modules/gltf/structures/gltf_camera.cpp index 863e1df967..2960ec351d 100644 --- a/modules/gltf/structures/gltf_camera.cpp +++ b/modules/gltf/structures/gltf_camera.cpp @@ -30,6 +30,7 @@ #include "gltf_camera.h" +#include "gltf_object_model_property.h" #include "scene/3d/camera_3d.h" void GLTFCamera::_bind_methods() { @@ -57,6 +58,21 @@ void GLTFCamera::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth_near"), "set_depth_near", "get_depth_near"); } +void GLTFCamera::set_fov_conversion_expressions(Ref<GLTFObjectModelProperty> &r_obj_model_prop) { + // Expression to convert glTF yfov in radians to Godot fov in degrees. + Ref<Expression> gltf_to_godot_expr; + gltf_to_godot_expr.instantiate(); + PackedStringArray gltf_to_godot_args = { "yfov_rad" }; + gltf_to_godot_expr->parse("rad_to_deg(yfov_rad)", gltf_to_godot_args); + r_obj_model_prop->set_gltf_to_godot_expression(gltf_to_godot_expr); + // Expression to convert Godot fov in degrees to glTF yfov in radians. + Ref<Expression> godot_to_gltf_expr; + godot_to_gltf_expr.instantiate(); + PackedStringArray godot_to_gltf_args = { "fov_deg" }; + godot_to_gltf_expr->parse("deg_to_rad(fov_deg)", godot_to_gltf_args); + r_obj_model_prop->set_godot_to_gltf_expression(godot_to_gltf_expr); +} + Ref<GLTFCamera> GLTFCamera::from_node(const Camera3D *p_camera) { Ref<GLTFCamera> c; c.instantiate(); diff --git a/modules/gltf/structures/gltf_camera.h b/modules/gltf/structures/gltf_camera.h index 1a583c82cc..497b6cd4f1 100644 --- a/modules/gltf/structures/gltf_camera.h +++ b/modules/gltf/structures/gltf_camera.h @@ -34,6 +34,7 @@ #include "core/io/resource.h" class Camera3D; +class GLTFObjectModelProperty; // Reference and test file: // https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_015_SimpleCameras.md @@ -54,6 +55,8 @@ protected: static void _bind_methods(); public: + static void set_fov_conversion_expressions(Ref<GLTFObjectModelProperty> &r_obj_model_prop); + bool get_perspective() const { return perspective; } void set_perspective(bool p_val) { perspective = p_val; } real_t get_fov() const { return fov; } diff --git a/modules/gltf/structures/gltf_node.cpp b/modules/gltf/structures/gltf_node.cpp index ccee5e8ca4..1626313551 100644 --- a/modules/gltf/structures/gltf_node.cpp +++ b/modules/gltf/structures/gltf_node.cpp @@ -30,6 +30,8 @@ #include "gltf_node.h" +#include "../gltf_state.h" + void GLTFNode::_bind_methods() { 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); @@ -60,6 +62,7 @@ void GLTFNode::_bind_methods() { 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("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::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]; } +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) { 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); +} diff --git a/modules/gltf/structures/gltf_node.h b/modules/gltf/structures/gltf_node.h index f3f6bfa2f1..f72b65a003 100644 --- a/modules/gltf/structures/gltf_node.h +++ b/modules/gltf/structures/gltf_node.h @@ -103,7 +103,10 @@ public: void set_light(GLTFLightIndex p_light); 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); + + NodePath get_scene_node_path(Ref<GLTFState> p_state, bool p_handle_skeletons = true); }; #endif // GLTF_NODE_H diff --git a/modules/gltf/structures/gltf_object_model_property.cpp b/modules/gltf/structures/gltf_object_model_property.cpp new file mode 100644 index 0000000000..d405c362db --- /dev/null +++ b/modules/gltf/structures/gltf_object_model_property.cpp @@ -0,0 +1,173 @@ +/**************************************************************************/ +/* gltf_object_model_property.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "gltf_object_model_property.h" + +#include "../gltf_template_convert.h" + +void GLTFObjectModelProperty::_bind_methods() { + BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_UNKNOWN); + BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_BOOL); + BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT); + BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT_ARRAY); + BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT2); + BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT3); + BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT4); + BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT2X2); + BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT3X3); + BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT4X4); + BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_INT); + + ClassDB::bind_method(D_METHOD("append_node_path", "node_path"), &GLTFObjectModelProperty::append_node_path); + ClassDB::bind_method(D_METHOD("append_path_to_property", "node_path", "prop_name"), &GLTFObjectModelProperty::append_path_to_property); + + ClassDB::bind_method(D_METHOD("get_accessor_type"), &GLTFObjectModelProperty::get_accessor_type); + ClassDB::bind_method(D_METHOD("get_gltf_to_godot_expression"), &GLTFObjectModelProperty::get_gltf_to_godot_expression); + ClassDB::bind_method(D_METHOD("set_gltf_to_godot_expression", "gltf_to_godot_expr"), &GLTFObjectModelProperty::set_gltf_to_godot_expression); + ClassDB::bind_method(D_METHOD("get_godot_to_gltf_expression"), &GLTFObjectModelProperty::get_godot_to_gltf_expression); + ClassDB::bind_method(D_METHOD("set_godot_to_gltf_expression", "godot_to_gltf_expr"), &GLTFObjectModelProperty::set_godot_to_gltf_expression); + ClassDB::bind_method(D_METHOD("get_node_paths"), &GLTFObjectModelProperty::get_node_paths); + ClassDB::bind_method(D_METHOD("has_node_paths"), &GLTFObjectModelProperty::has_node_paths); + ClassDB::bind_method(D_METHOD("set_node_paths", "node_paths"), &GLTFObjectModelProperty::set_node_paths); + ClassDB::bind_method(D_METHOD("get_object_model_type"), &GLTFObjectModelProperty::get_object_model_type); + ClassDB::bind_method(D_METHOD("set_object_model_type", "type"), &GLTFObjectModelProperty::set_object_model_type); + ClassDB::bind_method(D_METHOD("get_json_pointers"), &GLTFObjectModelProperty::get_json_pointers_bind); + ClassDB::bind_method(D_METHOD("has_json_pointers"), &GLTFObjectModelProperty::has_json_pointers); + ClassDB::bind_method(D_METHOD("set_json_pointers", "json_pointers"), &GLTFObjectModelProperty::set_json_pointers_bind); + ClassDB::bind_method(D_METHOD("get_variant_type"), &GLTFObjectModelProperty::get_variant_type); + ClassDB::bind_method(D_METHOD("set_variant_type", "variant_type"), &GLTFObjectModelProperty::set_variant_type); + ClassDB::bind_method(D_METHOD("set_types", "variant_type", "obj_model_type"), &GLTFObjectModelProperty::set_types); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gltf_to_godot_expression", PROPERTY_HINT_RESOURCE_TYPE, "Expression"), "set_gltf_to_godot_expression", "get_gltf_to_godot_expression"); // Ref<Expression> + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "godot_to_gltf_expression", PROPERTY_HINT_RESOURCE_TYPE, "Expression"), "set_godot_to_gltf_expression", "get_godot_to_gltf_expression"); // Ref<Expression> + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "node_paths", PROPERTY_HINT_TYPE_STRING, "NodePath"), "set_node_paths", "get_node_paths"); // TypedArray<NodePath> + ADD_PROPERTY(PropertyInfo(Variant::INT, "object_model_type"), "set_object_model_type", "get_object_model_type"); // GLTFObjectModelType + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "json_pointers"), "set_json_pointers", "get_json_pointers"); // TypedArray<PackedStringArray> + ADD_PROPERTY(PropertyInfo(Variant::INT, "variant_type"), "set_variant_type", "get_variant_type"); // Variant::Type +} + +void GLTFObjectModelProperty::append_node_path(const NodePath &p_node_path) { + node_paths.push_back(p_node_path); +} + +void GLTFObjectModelProperty::append_path_to_property(const NodePath &p_node_path, const StringName &p_prop_name) { + Vector<StringName> node_names = p_node_path.get_names(); + Vector<StringName> subpath = p_node_path.get_subnames(); + subpath.append(p_prop_name); + node_paths.push_back(NodePath(node_names, subpath, false)); +} + +GLTFAccessor::GLTFAccessorType GLTFObjectModelProperty::get_accessor_type() const { + switch (object_model_type) { + case GLTF_OBJECT_MODEL_TYPE_FLOAT2: + return GLTFAccessor::TYPE_VEC2; + case GLTF_OBJECT_MODEL_TYPE_FLOAT3: + return GLTFAccessor::TYPE_VEC3; + case GLTF_OBJECT_MODEL_TYPE_FLOAT4: + return GLTFAccessor::TYPE_VEC4; + case GLTF_OBJECT_MODEL_TYPE_FLOAT2X2: + return GLTFAccessor::TYPE_MAT2; + case GLTF_OBJECT_MODEL_TYPE_FLOAT3X3: + return GLTFAccessor::TYPE_MAT3; + case GLTF_OBJECT_MODEL_TYPE_FLOAT4X4: + return GLTFAccessor::TYPE_MAT4; + default: + return GLTFAccessor::TYPE_SCALAR; + } +} + +Ref<Expression> GLTFObjectModelProperty::get_gltf_to_godot_expression() const { + return gltf_to_godot_expr; +} + +void GLTFObjectModelProperty::set_gltf_to_godot_expression(Ref<Expression> p_gltf_to_godot_expr) { + gltf_to_godot_expr = p_gltf_to_godot_expr; +} + +Ref<Expression> GLTFObjectModelProperty::get_godot_to_gltf_expression() const { + return godot_to_gltf_expr; +} + +void GLTFObjectModelProperty::set_godot_to_gltf_expression(Ref<Expression> p_godot_to_gltf_expr) { + godot_to_gltf_expr = p_godot_to_gltf_expr; +} + +TypedArray<NodePath> GLTFObjectModelProperty::get_node_paths() const { + return node_paths; +} + +bool GLTFObjectModelProperty::has_node_paths() const { + return !node_paths.is_empty(); +} + +void GLTFObjectModelProperty::set_node_paths(TypedArray<NodePath> p_node_paths) { + node_paths = p_node_paths; +} + +GLTFObjectModelProperty::GLTFObjectModelType GLTFObjectModelProperty::get_object_model_type() const { + return object_model_type; +} + +void GLTFObjectModelProperty::set_object_model_type(GLTFObjectModelType p_type) { + object_model_type = p_type; +} + +Vector<PackedStringArray> GLTFObjectModelProperty::get_json_pointers() const { + return json_pointers; +} + +bool GLTFObjectModelProperty::has_json_pointers() const { + return !json_pointers.is_empty(); +} + +void GLTFObjectModelProperty::set_json_pointers(const Vector<PackedStringArray> &p_json_pointers) { + json_pointers = p_json_pointers; +} + +TypedArray<PackedStringArray> GLTFObjectModelProperty::get_json_pointers_bind() const { + return GLTFTemplateConvert::to_array(json_pointers); +} + +void GLTFObjectModelProperty::set_json_pointers_bind(const TypedArray<PackedStringArray> &p_json_pointers) { + GLTFTemplateConvert::set_from_array(json_pointers, p_json_pointers); +} + +Variant::Type GLTFObjectModelProperty::get_variant_type() const { + return variant_type; +} + +void GLTFObjectModelProperty::set_variant_type(Variant::Type p_variant_type) { + variant_type = p_variant_type; +} + +void GLTFObjectModelProperty::set_types(Variant::Type p_variant_type, GLTFObjectModelType p_obj_model_type) { + variant_type = p_variant_type; + object_model_type = p_obj_model_type; +} diff --git a/modules/gltf/structures/gltf_object_model_property.h b/modules/gltf/structures/gltf_object_model_property.h new file mode 100644 index 0000000000..d8a4ed420a --- /dev/null +++ b/modules/gltf/structures/gltf_object_model_property.h @@ -0,0 +1,104 @@ +/**************************************************************************/ +/* gltf_object_model_property.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef GLTF_OBJECT_MODEL_PROPERTY_H +#define GLTF_OBJECT_MODEL_PROPERTY_H + +#include "core/math/expression.h" +#include "core/variant/typed_array.h" +#include "gltf_accessor.h" + +// Object model: https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/ObjectModel.adoc +// KHR_animation_pointer: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_animation_pointer + +class GLTFObjectModelProperty : public RefCounted { + GDCLASS(GLTFObjectModelProperty, RefCounted); + +public: + enum GLTFObjectModelType { + GLTF_OBJECT_MODEL_TYPE_UNKNOWN, + GLTF_OBJECT_MODEL_TYPE_BOOL, + GLTF_OBJECT_MODEL_TYPE_FLOAT, + GLTF_OBJECT_MODEL_TYPE_FLOAT_ARRAY, + GLTF_OBJECT_MODEL_TYPE_FLOAT2, + GLTF_OBJECT_MODEL_TYPE_FLOAT3, + GLTF_OBJECT_MODEL_TYPE_FLOAT4, + GLTF_OBJECT_MODEL_TYPE_FLOAT2X2, + GLTF_OBJECT_MODEL_TYPE_FLOAT3X3, + GLTF_OBJECT_MODEL_TYPE_FLOAT4X4, + GLTF_OBJECT_MODEL_TYPE_INT, + }; + +private: + Ref<Expression> gltf_to_godot_expr; + Ref<Expression> godot_to_gltf_expr; + TypedArray<NodePath> node_paths; + GLTFObjectModelType object_model_type = GLTF_OBJECT_MODEL_TYPE_UNKNOWN; + Vector<PackedStringArray> json_pointers; + Variant::Type variant_type = Variant::NIL; + +protected: + static void _bind_methods(); + +public: + void append_node_path(const NodePath &p_node_path); + void append_path_to_property(const NodePath &p_node_path, const StringName &p_prop_name); + + GLTFAccessor::GLTFAccessorType get_accessor_type() const; + + Ref<Expression> get_gltf_to_godot_expression() const; + void set_gltf_to_godot_expression(Ref<Expression> p_gltf_to_godot_expr); + + Ref<Expression> get_godot_to_gltf_expression() const; + void set_godot_to_gltf_expression(Ref<Expression> p_godot_to_gltf_expr); + + TypedArray<NodePath> get_node_paths() const; + bool has_node_paths() const; + void set_node_paths(TypedArray<NodePath> p_node_paths); + + GLTFObjectModelType get_object_model_type() const; + void set_object_model_type(GLTFObjectModelType p_type); + + Vector<PackedStringArray> get_json_pointers() const; + bool has_json_pointers() const; + void set_json_pointers(const Vector<PackedStringArray> &p_json_pointers); + + TypedArray<PackedStringArray> get_json_pointers_bind() const; + void set_json_pointers_bind(const TypedArray<PackedStringArray> &p_json_pointers); + + Variant::Type get_variant_type() const; + void set_variant_type(Variant::Type p_variant_type); + + void set_types(Variant::Type p_variant_type, GLTFObjectModelType p_obj_model_type); +}; + +VARIANT_ENUM_CAST(GLTFObjectModelProperty::GLTFObjectModelType); + +#endif // GLTF_OBJECT_MODEL_PROPERTY_H diff --git a/modules/godot_physics_2d/godot_joints_2d.cpp b/modules/godot_physics_2d/godot_joints_2d.cpp index 5c76eb9dad..d5a779ebb5 100644 --- a/modules/godot_physics_2d/godot_joints_2d.cpp +++ b/modules/godot_physics_2d/godot_joints_2d.cpp @@ -311,7 +311,7 @@ bool GodotPinJoint2D::get_flag(PhysicsServer2D::PinJointFlag p_flag) const { return motor_enabled; } } - ERR_FAIL_V(0); + ERR_FAIL_V(false); } GodotPinJoint2D::GodotPinJoint2D(const Vector2 &p_pos, GodotBody2D *p_body_a, GodotBody2D *p_body_b) : diff --git a/modules/godot_physics_2d/godot_physics_server_2d.cpp b/modules/godot_physics_2d/godot_physics_server_2d.cpp index 71d1d3b6b1..2516ed3616 100644 --- a/modules/godot_physics_2d/godot_physics_server_2d.cpp +++ b/modules/godot_physics_2d/godot_physics_server_2d.cpp @@ -1169,8 +1169,8 @@ void GodotPhysicsServer2D::pin_joint_set_flag(RID p_joint, PinJointFlag p_flag, bool GodotPhysicsServer2D::pin_joint_get_flag(RID p_joint, PinJointFlag p_flag) const { GodotJoint2D *joint = joint_owner.get_or_null(p_joint); - ERR_FAIL_NULL_V(joint, 0); - ERR_FAIL_COND_V(joint->get_type() != JOINT_TYPE_PIN, 0); + ERR_FAIL_NULL_V(joint, false); + ERR_FAIL_COND_V(joint->get_type() != JOINT_TYPE_PIN, false); GodotPinJoint2D *pin_joint = static_cast<GodotPinJoint2D *>(joint); return pin_joint->get_flag(p_flag); diff --git a/modules/godot_physics_2d/godot_space_2d.cpp b/modules/godot_physics_2d/godot_space_2d.cpp index 2966818beb..df3c9d8265 100644 --- a/modules/godot_physics_2d/godot_space_2d.cpp +++ b/modules/godot_physics_2d/godot_space_2d.cpp @@ -342,7 +342,7 @@ bool GodotPhysicsDirectSpaceState2D::collide_shape(const ShapeParameters &p_para } GodotShape2D *shape = GodotPhysicsServer2D::godot_singleton->shape_owner.get_or_null(p_parameters.shape_rid); - ERR_FAIL_NULL_V(shape, 0); + ERR_FAIL_NULL_V(shape, false); Rect2 aabb = p_parameters.transform.xform(shape->get_aabb()); aabb = aabb.merge(Rect2(aabb.position + p_parameters.motion, aabb.size)); //motion @@ -439,7 +439,7 @@ static void _rest_cbk_result(const Vector2 &p_point_A, const Vector2 &p_point_B, bool GodotPhysicsDirectSpaceState2D::rest_info(const ShapeParameters &p_parameters, ShapeRestInfo *r_info) { GodotShape2D *shape = GodotPhysicsServer2D::godot_singleton->shape_owner.get_or_null(p_parameters.shape_rid); - ERR_FAIL_NULL_V(shape, 0); + ERR_FAIL_NULL_V(shape, false); real_t margin = MAX(p_parameters.margin, TEST_MOTION_MARGIN_MIN_VALUE); diff --git a/modules/godot_physics_3d/godot_collision_solver_3d_sat.cpp b/modules/godot_physics_3d/godot_collision_solver_3d_sat.cpp index c53c8481f4..2adbb51297 100644 --- a/modules/godot_physics_3d/godot_collision_solver_3d_sat.cpp +++ b/modules/godot_physics_3d/godot_collision_solver_3d_sat.cpp @@ -76,8 +76,9 @@ struct _CollectorCallback { Vector3 *prev_axis = nullptr; _FORCE_INLINE_ void call(const Vector3 &p_point_A, const Vector3 &p_point_B, Vector3 p_normal) { - if (p_normal.dot(p_point_B - p_point_A) < 0) + if (p_normal.dot(p_point_B - p_point_A) < 0) { p_normal = -p_normal; + } if (swap) { callback(p_point_B, 0, p_point_A, 0, -p_normal, userdata); } else { @@ -175,10 +176,11 @@ static void _generate_contacts_edge_edge(const Vector3 *p_points_A, int p_point_ // The normal should be perpendicular to both edges. Vector3 normal = rel_A.cross(rel_B); real_t normal_len = normal.length(); - if (normal_len > 1e-3) + if (normal_len > 1e-3) { normal /= normal_len; - else + } else { normal = p_callback->normal; + } p_callback->call(closest_A, closest_B, normal); } @@ -784,8 +786,9 @@ static void analytic_sphere_collision(const Vector3 &p_origin_a, real_t p_radius // Calculate the sphere overlap, and bail if not overlapping real_t overlap = p_radius_a + p_radius_b - b_to_a_len; - if (overlap < 0) + if (overlap < 0) { return; + } // Report collision p_collector->collided = true; diff --git a/modules/godot_physics_3d/godot_physics_server_3d.cpp b/modules/godot_physics_3d/godot_physics_server_3d.cpp index 43f8d2658d..ad55e415e6 100644 --- a/modules/godot_physics_3d/godot_physics_server_3d.cpp +++ b/modules/godot_physics_3d/godot_physics_server_3d.cpp @@ -826,7 +826,7 @@ void GodotPhysicsServer3D::body_set_axis_lock(RID p_body, BodyAxis p_axis, bool bool GodotPhysicsServer3D::body_is_axis_locked(RID p_body, BodyAxis p_axis) const { const GodotBody3D *body = body_owner.get_or_null(p_body); - ERR_FAIL_NULL_V(body, 0); + ERR_FAIL_NULL_V(body, false); return body->is_axis_locked(p_axis); } diff --git a/modules/godot_physics_3d/godot_shape_3d.cpp b/modules/godot_physics_3d/godot_shape_3d.cpp index 70b6bcf19e..a7cccc5cb8 100644 --- a/modules/godot_physics_3d/godot_shape_3d.cpp +++ b/modules/godot_physics_3d/godot_shape_3d.cpp @@ -1133,8 +1133,9 @@ void GodotConvexPolygonShape3D::_setup(const Vector<Vector3> &p_vertices) { max_support = s; } } - if (!extreme_vertices.has(best_vertex)) + if (!extreme_vertices.has(best_vertex)) { extreme_vertices.push_back(best_vertex); + } } } } diff --git a/modules/godot_physics_3d/godot_space_3d.cpp b/modules/godot_physics_3d/godot_space_3d.cpp index 9a6ba776b4..9f82a87f85 100644 --- a/modules/godot_physics_3d/godot_space_3d.cpp +++ b/modules/godot_physics_3d/godot_space_3d.cpp @@ -385,7 +385,7 @@ bool GodotPhysicsDirectSpaceState3D::collide_shape(const ShapeParameters &p_para } GodotShape3D *shape = GodotPhysicsServer3D::godot_singleton->shape_owner.get_or_null(p_parameters.shape_rid); - ERR_FAIL_NULL_V(shape, 0); + ERR_FAIL_NULL_V(shape, false); AABB aabb = p_parameters.transform.xform(shape->get_aabb()); aabb = aabb.grow(p_parameters.margin); @@ -511,7 +511,7 @@ static void _rest_cbk_result(const Vector3 &p_point_A, int p_index_A, const Vect bool GodotPhysicsDirectSpaceState3D::rest_info(const ShapeParameters &p_parameters, ShapeRestInfo *r_info) { GodotShape3D *shape = GodotPhysicsServer3D::godot_singleton->shape_owner.get_or_null(p_parameters.shape_rid); - ERR_FAIL_NULL_V(shape, 0); + ERR_FAIL_NULL_V(shape, false); real_t margin = MAX(p_parameters.margin, TEST_MOTION_MARGIN_MIN_VALUE); diff --git a/modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.cpp b/modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.cpp index 226f8a0f7f..5f9cf9de49 100644 --- a/modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.cpp +++ b/modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.cpp @@ -647,7 +647,7 @@ void GodotGeneric6DOFJoint3D::set_flag(Vector3::Axis p_axis, PhysicsServer3D::G6 } bool GodotGeneric6DOFJoint3D::get_flag(Vector3::Axis p_axis, PhysicsServer3D::G6DOFJointAxisFlag p_flag) const { - ERR_FAIL_INDEX_V(p_axis, 3, 0); + ERR_FAIL_INDEX_V(p_axis, 3, false); switch (p_flag) { case PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_LINEAR_LIMIT: { return m_linearLimits.enable_limit[p_axis]; diff --git a/modules/meshoptimizer/register_types.cpp b/modules/meshoptimizer/register_types.cpp index 781f928f66..ebfe5d9c5b 100644 --- a/modules/meshoptimizer/register_types.cpp +++ b/modules/meshoptimizer/register_types.cpp @@ -40,10 +40,10 @@ void initialize_meshoptimizer_module(ModuleInitializationLevel p_level) { } SurfaceTool::optimize_vertex_cache_func = meshopt_optimizeVertexCache; + SurfaceTool::optimize_vertex_fetch_remap_func = meshopt_optimizeVertexFetchRemap; SurfaceTool::simplify_func = meshopt_simplify; SurfaceTool::simplify_with_attrib_func = meshopt_simplifyWithAttributes; SurfaceTool::simplify_scale_func = meshopt_simplifyScale; - SurfaceTool::simplify_sloppy_func = meshopt_simplifySloppy; SurfaceTool::generate_remap_func = meshopt_generateVertexRemap; SurfaceTool::remap_vertex_func = meshopt_remapVertexBuffer; SurfaceTool::remap_index_func = meshopt_remapIndexBuffer; @@ -55,9 +55,9 @@ void uninitialize_meshoptimizer_module(ModuleInitializationLevel p_level) { } SurfaceTool::optimize_vertex_cache_func = nullptr; + SurfaceTool::optimize_vertex_fetch_remap_func = nullptr; SurfaceTool::simplify_func = nullptr; SurfaceTool::simplify_scale_func = nullptr; - SurfaceTool::simplify_sloppy_func = nullptr; SurfaceTool::generate_remap_func = nullptr; SurfaceTool::remap_vertex_func = nullptr; SurfaceTool::remap_index_func = nullptr; diff --git a/modules/minimp3/resource_importer_mp3.h b/modules/minimp3/resource_importer_mp3.h index 2df44deaea..037756328f 100644 --- a/modules/minimp3/resource_importer_mp3.h +++ b/modules/minimp3/resource_importer_mp3.h @@ -59,6 +59,8 @@ public: virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override; + virtual bool can_import_threaded() const override { return true; } + ResourceImporterMP3(); }; diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index e97229c621..db90ac5a6e 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -4372,9 +4372,45 @@ bool BindingsGenerator::_populate_object_type_interfaces() { return true; } +static String _get_vector2_cs_ctor_args(const Vector2 &p_vec2) { + return String::num_real(p_vec2.x, true) + "f, " + + String::num_real(p_vec2.y, true) + "f"; +} + +static String _get_vector3_cs_ctor_args(const Vector3 &p_vec3) { + return String::num_real(p_vec3.x, true) + "f, " + + String::num_real(p_vec3.y, true) + "f, " + + String::num_real(p_vec3.z, true) + "f"; +} + +static String _get_vector4_cs_ctor_args(const Vector4 &p_vec4) { + return String::num_real(p_vec4.x, true) + "f, " + + String::num_real(p_vec4.y, true) + "f, " + + String::num_real(p_vec4.z, true) + "f, " + + String::num_real(p_vec4.w, true) + "f"; +} + +static String _get_vector2i_cs_ctor_args(const Vector2i &p_vec2i) { + return itos(p_vec2i.x) + ", " + itos(p_vec2i.y); +} + +static String _get_vector3i_cs_ctor_args(const Vector3i &p_vec3i) { + return itos(p_vec3i.x) + ", " + itos(p_vec3i.y) + ", " + itos(p_vec3i.z); +} + +static String _get_vector4i_cs_ctor_args(const Vector4i &p_vec4i) { + return itos(p_vec4i.x) + ", " + itos(p_vec4i.y) + ", " + itos(p_vec4i.z) + ", " + itos(p_vec4i.w); +} + +static String _get_color_cs_ctor_args(const Color &p_color) { + return String::num(p_color.r, 4) + "f, " + + String::num(p_color.g, 4) + "f, " + + String::num(p_color.b, 4) + "f, " + + String::num(p_color.a, 4) + "f"; +} + bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, ArgumentInterface &r_iarg) { r_iarg.def_param_value = p_val; - r_iarg.default_argument = p_val.operator String(); switch (p_val.get_type()) { case Variant::NIL: @@ -4387,10 +4423,14 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar break; case Variant::INT: if (r_iarg.type.cname != name_cache.type_int) { - r_iarg.default_argument = "(%s)(" + r_iarg.default_argument + ")"; + r_iarg.default_argument = "(%s)(" + p_val.operator String() + ")"; + } else { + r_iarg.default_argument = p_val.operator String(); } break; case Variant::FLOAT: + r_iarg.default_argument = p_val.operator String(); + if (r_iarg.type.cname == name_cache.type_float) { r_iarg.default_argument += "f"; } @@ -4400,7 +4440,7 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar case Variant::NODE_PATH: if (r_iarg.type.cname == name_cache.type_StringName || r_iarg.type.cname == name_cache.type_NodePath) { if (r_iarg.default_argument.length() > 0) { - r_iarg.default_argument = "(%s)\"" + r_iarg.default_argument + "\""; + r_iarg.default_argument = "(%s)\"" + p_val.operator String() + "\""; r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF; } else { // No need for a special `in` statement to change `null` to `""`. Marshaling takes care of this already. @@ -4408,40 +4448,62 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar } } else { CRASH_COND(r_iarg.type.cname != name_cache.type_String); - r_iarg.default_argument = "\"" + r_iarg.default_argument + "\""; + r_iarg.default_argument = "\"" + p_val.operator String() + "\""; } break; case Variant::PLANE: { Plane plane = p_val.operator Plane(); - r_iarg.default_argument = "new Plane(new Vector3" + plane.normal.operator String() + ", " + rtos(plane.d) + ")"; + r_iarg.default_argument = "new Plane(new Vector3(" + + _get_vector3_cs_ctor_args(plane.normal) + "), " + rtos(plane.d) + "f)"; r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; } break; case Variant::AABB: { AABB aabb = p_val.operator ::AABB(); - r_iarg.default_argument = "new Aabb(new Vector3" + aabb.position.operator String() + ", new Vector3" + aabb.size.operator String() + ")"; + r_iarg.default_argument = "new Aabb(new Vector3(" + + _get_vector3_cs_ctor_args(aabb.position) + "), new Vector3(" + + _get_vector3_cs_ctor_args(aabb.size) + "))"; r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; } break; case Variant::RECT2: { Rect2 rect = p_val.operator Rect2(); - r_iarg.default_argument = "new Rect2(new Vector2" + rect.position.operator String() + ", new Vector2" + rect.size.operator String() + ")"; + r_iarg.default_argument = "new Rect2(new Vector2(" + + _get_vector2_cs_ctor_args(rect.position) + "), new Vector2(" + + _get_vector2_cs_ctor_args(rect.size) + "))"; r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; } break; case Variant::RECT2I: { Rect2i rect = p_val.operator Rect2i(); - r_iarg.default_argument = "new Rect2I(new Vector2I" + rect.position.operator String() + ", new Vector2I" + rect.size.operator String() + ")"; + r_iarg.default_argument = "new Rect2I(new Vector2I(" + + _get_vector2i_cs_ctor_args(rect.position) + "), new Vector2I(" + + _get_vector2i_cs_ctor_args(rect.size) + "))"; r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; } break; case Variant::COLOR: + r_iarg.default_argument = "new Color(" + _get_color_cs_ctor_args(p_val.operator Color()) + ")"; + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; + break; case Variant::VECTOR2: + r_iarg.default_argument = "new Vector2(" + _get_vector2_cs_ctor_args(p_val.operator Vector2()) + ")"; + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; + break; case Variant::VECTOR2I: + r_iarg.default_argument = "new Vector2I(" + _get_vector2i_cs_ctor_args(p_val.operator Vector2i()) + ")"; + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; + break; case Variant::VECTOR3: + r_iarg.default_argument = "new Vector3(" + _get_vector3_cs_ctor_args(p_val.operator Vector3()) + ")"; + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; + break; case Variant::VECTOR3I: - r_iarg.default_argument = "new %s" + r_iarg.default_argument; + r_iarg.default_argument = "new Vector3I(" + _get_vector3i_cs_ctor_args(p_val.operator Vector3i()) + ")"; r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; break; case Variant::VECTOR4: + r_iarg.default_argument = "new Vector4(" + _get_vector4_cs_ctor_args(p_val.operator Vector4()) + ")"; + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; + break; case Variant::VECTOR4I: - r_iarg.default_argument = "new %s" + r_iarg.default_argument; + r_iarg.default_argument = "new Vector4I(" + _get_vector4i_cs_ctor_args(p_val.operator Vector4i()) + ")"; r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; break; case Variant::OBJECT: @@ -4491,7 +4553,10 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar if (transform == Transform2D()) { r_iarg.default_argument = "Transform2D.Identity"; } else { - r_iarg.default_argument = "new Transform2D(new Vector2" + transform.columns[0].operator String() + ", new Vector2" + transform.columns[1].operator String() + ", new Vector2" + transform.columns[2].operator String() + ")"; + r_iarg.default_argument = "new Transform2D(new Vector2(" + + _get_vector2_cs_ctor_args(transform.columns[0]) + "), new Vector2(" + + _get_vector2_cs_ctor_args(transform.columns[1]) + "), new Vector2(" + + _get_vector2_cs_ctor_args(transform.columns[2]) + "))"; } r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; } break; @@ -4501,7 +4566,11 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar r_iarg.default_argument = "Transform3D.Identity"; } else { Basis basis = transform.basis; - r_iarg.default_argument = "new Transform3D(new Vector3" + basis.get_column(0).operator String() + ", new Vector3" + basis.get_column(1).operator String() + ", new Vector3" + basis.get_column(2).operator String() + ", new Vector3" + transform.origin.operator String() + ")"; + r_iarg.default_argument = "new Transform3D(new Vector3(" + + _get_vector3_cs_ctor_args(basis.get_column(0)) + "), new Vector3(" + + _get_vector3_cs_ctor_args(basis.get_column(1)) + "), new Vector3(" + + _get_vector3_cs_ctor_args(basis.get_column(2)) + "), new Vector3(" + + _get_vector3_cs_ctor_args(transform.origin) + "))"; } r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; } break; @@ -4510,7 +4579,11 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar if (projection == Projection()) { r_iarg.default_argument = "Projection.Identity"; } else { - r_iarg.default_argument = "new Projection(new Vector4" + projection.columns[0].operator String() + ", new Vector4" + projection.columns[1].operator String() + ", new Vector4" + projection.columns[2].operator String() + ", new Vector4" + projection.columns[3].operator String() + ")"; + r_iarg.default_argument = "new Projection(new Vector4(" + + _get_vector4_cs_ctor_args(projection.columns[0]) + "), new Vector4(" + + _get_vector4_cs_ctor_args(projection.columns[1]) + "), new Vector4(" + + _get_vector4_cs_ctor_args(projection.columns[2]) + "), new Vector4(" + + _get_vector4_cs_ctor_args(projection.columns[3]) + "))"; } r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; } break; @@ -4519,7 +4592,10 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar if (basis == Basis()) { r_iarg.default_argument = "Basis.Identity"; } else { - r_iarg.default_argument = "new Basis(new Vector3" + basis.get_column(0).operator String() + ", new Vector3" + basis.get_column(1).operator String() + ", new Vector3" + basis.get_column(2).operator String() + ")"; + r_iarg.default_argument = "new Basis(new Vector3(" + + _get_vector3_cs_ctor_args(basis.get_column(0)) + "), new Vector3(" + + _get_vector3_cs_ctor_args(basis.get_column(1)) + "), new Vector3(" + + _get_vector3_cs_ctor_args(basis.get_column(2)) + "))"; } r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; } break; @@ -4528,7 +4604,11 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar if (quaternion == Quaternion()) { r_iarg.default_argument = "Quaternion.Identity"; } else { - r_iarg.default_argument = "new Quaternion" + quaternion.operator String(); + r_iarg.default_argument = "new Quaternion(" + + String::num_real(quaternion.x, false) + "f, " + + String::num_real(quaternion.y, false) + "f, " + + String::num_real(quaternion.z, false) + "f, " + + String::num_real(quaternion.w, false) + "f)"; } r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; } break; diff --git a/modules/navigation/nav_agent.h b/modules/navigation/nav_agent.h index e3671504f2..d56e053ac4 100644 --- a/modules/navigation/nav_agent.h +++ b/modules/navigation/nav_agent.h @@ -67,7 +67,7 @@ class NavAgent : public NavRid { uint32_t avoidance_mask = 1; real_t avoidance_priority = 1.0; - Callable avoidance_callback = Callable(); + Callable avoidance_callback; bool agent_dirty = true; diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp index 8df1db533d..04c8a5a943 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -426,26 +426,42 @@ void NavMap::sync() { _new_pm_polygon_count = polygon_count; + struct ConnectionPair { + gd::Edge::Connection connections[2]; + int size = 0; + }; + // Group all edges per key. - HashMap<gd::EdgeKey, Vector<gd::Edge::Connection>, gd::EdgeKey> connections; + HashMap<gd::EdgeKey, ConnectionPair, gd::EdgeKey> connection_pairs_map; + connection_pairs_map.reserve(polygons.size()); + int free_edges_count = 0; // How many ConnectionPairs have only one Connection. + for (gd::Polygon &poly : polygons) { for (uint32_t p = 0; p < poly.points.size(); p++) { - int next_point = (p + 1) % poly.points.size(); - gd::EdgeKey ek(poly.points[p].key, poly.points[next_point].key); + const int next_point = (p + 1) % poly.points.size(); + const gd::EdgeKey ek(poly.points[p].key, poly.points[next_point].key); - HashMap<gd::EdgeKey, Vector<gd::Edge::Connection>, gd::EdgeKey>::Iterator connection = connections.find(ek); - if (!connection) { - connections[ek] = Vector<gd::Edge::Connection>(); + HashMap<gd::EdgeKey, ConnectionPair, gd::EdgeKey>::Iterator pair_it = connection_pairs_map.find(ek); + if (!pair_it) { + pair_it = connection_pairs_map.insert(ek, ConnectionPair()); _new_pm_edge_count += 1; + ++free_edges_count; } - if (connections[ek].size() <= 1) { + ConnectionPair &pair = pair_it->value; + if (pair.size < 2) { // Add the polygon/edge tuple to this key. gd::Edge::Connection new_connection; new_connection.polygon = &poly; new_connection.edge = p; new_connection.pathway_start = poly.points[p].pos; new_connection.pathway_end = poly.points[next_point].pos; - connections[ek].push_back(new_connection); + + pair.connections[pair.size] = new_connection; + ++pair.size; + if (pair.size == 2) { + --free_edges_count; + } + } else { // The edge is already connected with another edge, skip. ERR_PRINT_ONCE("Navigation map synchronization error. Attempted to merge a navigation mesh polygon edge with another already-merged edge. This is usually caused by crossing edges, overlapping polygons, or a mismatch of the NavigationMesh / NavigationPolygon baked 'cell_size' and navigation map 'cell_size'. If you're certain none of above is the case, change 'navigation/3d/merge_rasterizer_cell_scale' to 0.001."); @@ -453,20 +469,23 @@ void NavMap::sync() { } } - Vector<gd::Edge::Connection> free_edges; - for (KeyValue<gd::EdgeKey, Vector<gd::Edge::Connection>> &E : connections) { - if (E.value.size() == 2) { + LocalVector<gd::Edge::Connection> free_edges; + free_edges.reserve(free_edges_count); + + for (const KeyValue<gd::EdgeKey, ConnectionPair> &pair_it : connection_pairs_map) { + const ConnectionPair &pair = pair_it.value; + if (pair.size == 2) { // Connect edge that are shared in different polygons. - gd::Edge::Connection &c1 = E.value.write[0]; - gd::Edge::Connection &c2 = E.value.write[1]; + const gd::Edge::Connection &c1 = pair.connections[0]; + const gd::Edge::Connection &c2 = pair.connections[1]; c1.polygon->edges[c1.edge].connections.push_back(c2); c2.polygon->edges[c2.edge].connections.push_back(c1); // Note: The pathway_start/end are full for those connection and do not need to be modified. _new_pm_edge_merge_count += 1; } else { - CRASH_COND_MSG(E.value.size() != 1, vformat("Number of connection != 1. Found: %d", E.value.size())); - if (use_edge_connections && E.value[0].polygon->owner->get_use_edge_connections()) { - free_edges.push_back(E.value[0]); + CRASH_COND_MSG(pair.size != 1, vformat("Number of connection != 1. Found: %d", pair.size)); + if (use_edge_connections && pair.connections[0].polygon->owner->get_use_edge_connections()) { + free_edges.push_back(pair.connections[0]); } } } @@ -480,14 +499,14 @@ void NavMap::sync() { // connection, integration and path finding. _new_pm_edge_free_count = free_edges.size(); - real_t sqr_edge_connection_margin = edge_connection_margin * edge_connection_margin; + const real_t edge_connection_margin_squared = edge_connection_margin * edge_connection_margin; - for (int i = 0; i < free_edges.size(); i++) { + for (uint32_t i = 0; i < free_edges.size(); i++) { const gd::Edge::Connection &free_edge = free_edges[i]; Vector3 edge_p1 = free_edge.polygon->points[free_edge.edge].pos; Vector3 edge_p2 = free_edge.polygon->points[(free_edge.edge + 1) % free_edge.polygon->points.size()].pos; - for (int j = 0; j < free_edges.size(); j++) { + for (uint32_t j = 0; j < free_edges.size(); j++) { const gd::Edge::Connection &other_edge = free_edges[j]; if (i == j || free_edge.polygon->owner == other_edge.polygon->owner) { continue; @@ -512,7 +531,7 @@ void NavMap::sync() { } else { other1 = other_edge_p1.lerp(other_edge_p2, (1.0 - projected_p1_ratio) / (projected_p2_ratio - projected_p1_ratio)); } - if (other1.distance_squared_to(self1) > sqr_edge_connection_margin) { + if (other1.distance_squared_to(self1) > edge_connection_margin_squared) { continue; } @@ -523,7 +542,7 @@ void NavMap::sync() { } else { other2 = other_edge_p1.lerp(other_edge_p2, (0.0 - projected_p1_ratio) / (projected_p2_ratio - projected_p1_ratio)); } - if (other2.distance_squared_to(self2) > sqr_edge_connection_margin) { + if (other2.distance_squared_to(self2) > edge_connection_margin_squared) { continue; } diff --git a/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml b/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml index 813c9d582e..182fe32f9c 100644 --- a/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml +++ b/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml @@ -90,6 +90,21 @@ Called right after the main swapchains are (re)created. </description> </method> + <method name="_on_post_draw_viewport" qualifiers="virtual"> + <return type="void" /> + <param index="0" name="viewport" type="RID" /> + <description> + Called right after the given viewport is rendered. + [b]Note:[/b] The draw commands might only be queued at this point, not executed. + </description> + </method> + <method name="_on_pre_draw_viewport" qualifiers="virtual"> + <return type="void" /> + <param index="0" name="viewport" type="RID" /> + <description> + Called right before the given viewport is rendered. + </description> + </method> <method name="_on_pre_render" qualifiers="virtual"> <return type="void" /> <description> diff --git a/modules/openxr/extensions/openxr_composition_layer_extension.cpp b/modules/openxr/extensions/openxr_composition_layer_extension.cpp index dc30b95b27..2d29b8a82c 100644 --- a/modules/openxr/extensions/openxr_composition_layer_extension.cpp +++ b/modules/openxr/extensions/openxr_composition_layer_extension.cpp @@ -341,7 +341,7 @@ XrCompositionLayerBaseHeader *OpenXRViewportCompositionLayerProvider::get_compos } XrSwapchainSubImage subimage = { - 0, // swapchain + 0, // swapchain // NOLINT(modernize-use-nullptr) - 32-bit uses non-pointer uint64 { { 0, 0 }, { 0, 0 } }, // imageRect 0, // imageArrayIndex }; diff --git a/modules/openxr/extensions/openxr_debug_utils_extension.cpp b/modules/openxr/extensions/openxr_debug_utils_extension.cpp index 10dbe629f7..cb7f72d02d 100644 --- a/modules/openxr/extensions/openxr_debug_utils_extension.cpp +++ b/modules/openxr/extensions/openxr_debug_utils_extension.cpp @@ -173,7 +173,7 @@ void OpenXRDebugUtilsExtension::begin_debug_label_region(const char *p_label_nam const XrDebugUtilsLabelEXT session_active_region_label = { XR_TYPE_DEBUG_UTILS_LABEL_EXT, // type - NULL, // next + nullptr, // next p_label_name, // labelName }; @@ -199,7 +199,7 @@ void OpenXRDebugUtilsExtension::insert_debug_label(const char *p_label_name) { const XrDebugUtilsLabelEXT session_active_region_label = { XR_TYPE_DEBUG_UTILS_LABEL_EXT, // type - NULL, // next + nullptr, // next p_label_name, // labelName }; diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp index 07ca476421..fb8d1b9d69 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp +++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp @@ -51,6 +51,8 @@ void OpenXRExtensionWrapperExtension::_bind_methods() { GDVIRTUAL_BIND(_on_process); GDVIRTUAL_BIND(_on_pre_render); GDVIRTUAL_BIND(_on_main_swapchains_created); + GDVIRTUAL_BIND(_on_pre_draw_viewport, "viewport"); + GDVIRTUAL_BIND(_on_post_draw_viewport, "viewport"); GDVIRTUAL_BIND(_on_session_destroyed); GDVIRTUAL_BIND(_on_state_idle); GDVIRTUAL_BIND(_on_state_ready); @@ -208,6 +210,14 @@ void OpenXRExtensionWrapperExtension::on_session_destroyed() { GDVIRTUAL_CALL(_on_session_destroyed); } +void OpenXRExtensionWrapperExtension::on_pre_draw_viewport(RID p_render_target) { + GDVIRTUAL_CALL(_on_pre_draw_viewport, p_render_target); +} + +void OpenXRExtensionWrapperExtension::on_post_draw_viewport(RID p_render_target) { + GDVIRTUAL_CALL(_on_post_draw_viewport, p_render_target); +} + void OpenXRExtensionWrapperExtension::on_state_idle() { GDVIRTUAL_CALL(_on_state_idle); } @@ -298,8 +308,7 @@ void OpenXRExtensionWrapperExtension::register_extension_wrapper() { OpenXRAPI::register_extension_wrapper(this); } -OpenXRExtensionWrapperExtension::OpenXRExtensionWrapperExtension() : - Object(), OpenXRExtensionWrapper() { +OpenXRExtensionWrapperExtension::OpenXRExtensionWrapperExtension() { openxr_api.instantiate(); } diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.h b/modules/openxr/extensions/openxr_extension_wrapper_extension.h index 5cdf288c93..8fc6511277 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper_extension.h +++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.h @@ -88,6 +88,8 @@ public: virtual void on_pre_render() override; virtual void on_main_swapchains_created() override; virtual void on_session_destroyed() override; + virtual void on_pre_draw_viewport(RID p_render_target) override; + virtual void on_post_draw_viewport(RID p_render_target) override; GDVIRTUAL0(_on_register_metadata); GDVIRTUAL0(_on_before_instance_created); @@ -98,6 +100,8 @@ public: GDVIRTUAL0(_on_pre_render); GDVIRTUAL0(_on_main_swapchains_created); GDVIRTUAL0(_on_session_destroyed); + GDVIRTUAL1(_on_pre_draw_viewport, RID); + GDVIRTUAL1(_on_post_draw_viewport, RID); virtual void on_state_idle() override; virtual void on_state_ready() override; diff --git a/modules/openxr/extensions/platform/openxr_opengl_extension.cpp b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp index f75da49d87..4fcb3f7b75 100644 --- a/modules/openxr/extensions/platform/openxr_opengl_extension.cpp +++ b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp @@ -193,7 +193,7 @@ void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_nex // spec says to use proper values but runtimes don't care graphics_binding_gl.visualid = 0; - graphics_binding_gl.glxFBConfig = 0; + graphics_binding_gl.glxFBConfig = nullptr; #endif #endif diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 715d24cfd9..a6fd727290 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -2752,8 +2752,9 @@ void OpenXRAPI::parse_velocities(const XrSpaceVelocity &p_velocity, Vector3 &r_l } bool OpenXRAPI::xr_result(XrResult result, const char *format, Array args) const { - if (XR_SUCCEEDED(result)) + if (XR_SUCCEEDED(result)) { return true; + } char resultString[XR_MAX_RESULT_STRING_SIZE]; xrResultToString(instance, result, resultString); diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 1c6e62650a..8d557aae2a 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -5264,42 +5264,44 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ // Find usable fonts, if fonts from the last glyph do not have required chars. RID dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid; - if (!_font_has_char(dot_gl_font_rid, sd->el_char)) { - const Array &fonts = spans[spans.size() - 1].fonts; - for (int i = 0; i < fonts.size(); i++) { - if (_font_has_char(fonts[i], sd->el_char)) { - dot_gl_font_rid = fonts[i]; - found_el_char = true; - break; - } - } - if (!found_el_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) { - const char32_t u32str[] = { sd->el_char, 0 }; - RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, u32str); - if (rid.is_valid()) { - dot_gl_font_rid = rid; - found_el_char = true; - } - } - } else { - found_el_char = true; - } - if (!found_el_char) { - bool found_dot_char = false; - dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid; - if (!_font_has_char(dot_gl_font_rid, '.')) { + if (add_ellipsis || enforce_ellipsis) { + if (!_font_has_char(dot_gl_font_rid, sd->el_char)) { const Array &fonts = spans[spans.size() - 1].fonts; for (int i = 0; i < fonts.size(); i++) { - if (_font_has_char(fonts[i], '.')) { + if (_font_has_char(fonts[i], sd->el_char)) { dot_gl_font_rid = fonts[i]; - found_dot_char = true; + found_el_char = true; break; } } - if (!found_dot_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) { - RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, "."); + if (!found_el_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) { + const char32_t u32str[] = { sd->el_char, 0 }; + RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, u32str); if (rid.is_valid()) { dot_gl_font_rid = rid; + found_el_char = true; + } + } + } else { + found_el_char = true; + } + if (!found_el_char) { + bool found_dot_char = false; + dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid; + if (!_font_has_char(dot_gl_font_rid, '.')) { + const Array &fonts = spans[spans.size() - 1].fonts; + for (int i = 0; i < fonts.size(); i++) { + if (_font_has_char(fonts[i], '.')) { + dot_gl_font_rid = fonts[i]; + found_dot_char = true; + break; + } + } + if (!found_dot_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) { + RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, "."); + if (rid.is_valid()) { + dot_gl_font_rid = rid; + } } } } @@ -5315,8 +5317,8 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ } } - int32_t dot_gl_idx = dot_gl_font_rid.is_valid() ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, (found_el_char ? sd->el_char : '.'), 0) : -1; - Vector2 dot_adv = dot_gl_font_rid.is_valid() ? _font_get_glyph_advance(dot_gl_font_rid, last_gl_font_size, dot_gl_idx) : Vector2(); + int32_t dot_gl_idx = ((add_ellipsis || enforce_ellipsis) && dot_gl_font_rid.is_valid()) ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, (found_el_char ? sd->el_char : '.'), 0) : -1; + Vector2 dot_adv = ((add_ellipsis || enforce_ellipsis) && dot_gl_font_rid.is_valid()) ? _font_get_glyph_advance(dot_gl_font_rid, last_gl_font_size, dot_gl_idx) : Vector2(); int32_t whitespace_gl_idx = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_index(whitespace_gl_font_rid, last_gl_font_size, ' ', 0) : -1; Vector2 whitespace_adv = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_advance(whitespace_gl_font_rid, last_gl_font_size, whitespace_gl_idx) : Vector2(); diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index d30b2aae19..ae636a3151 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -4077,42 +4077,44 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ // Find usable fonts, if fonts from the last glyph do not have required chars. RID dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid; - if (!_font_has_char(dot_gl_font_rid, sd->el_char)) { - const Array &fonts = spans[spans.size() - 1].fonts; - for (int i = 0; i < fonts.size(); i++) { - if (_font_has_char(fonts[i], sd->el_char)) { - dot_gl_font_rid = fonts[i]; - found_el_char = true; - break; - } - } - if (!found_el_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) { - const char32_t u32str[] = { sd->el_char, 0 }; - RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, u32str); - if (rid.is_valid()) { - dot_gl_font_rid = rid; - found_el_char = true; - } - } - } else { - found_el_char = true; - } - if (!found_el_char) { - bool found_dot_char = false; - dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid; - if (!_font_has_char(dot_gl_font_rid, '.')) { + if (add_ellipsis || enforce_ellipsis) { + if (!_font_has_char(dot_gl_font_rid, sd->el_char)) { const Array &fonts = spans[spans.size() - 1].fonts; for (int i = 0; i < fonts.size(); i++) { - if (_font_has_char(fonts[i], '.')) { + if (_font_has_char(fonts[i], sd->el_char)) { dot_gl_font_rid = fonts[i]; - found_dot_char = true; + found_el_char = true; break; } } - if (!found_dot_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) { - RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, "."); + if (!found_el_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) { + const char32_t u32str[] = { sd->el_char, 0 }; + RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, u32str); if (rid.is_valid()) { dot_gl_font_rid = rid; + found_el_char = true; + } + } + } else { + found_el_char = true; + } + if (!found_el_char) { + bool found_dot_char = false; + dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid; + if (!_font_has_char(dot_gl_font_rid, '.')) { + const Array &fonts = spans[spans.size() - 1].fonts; + for (int i = 0; i < fonts.size(); i++) { + if (_font_has_char(fonts[i], '.')) { + dot_gl_font_rid = fonts[i]; + found_dot_char = true; + break; + } + } + if (!found_dot_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) { + RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, "."); + if (rid.is_valid()) { + dot_gl_font_rid = rid; + } } } } @@ -4128,8 +4130,8 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ } } - int32_t dot_gl_idx = dot_gl_font_rid.is_valid() ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, (found_el_char ? sd->el_char : '.'), 0) : -1; - Vector2 dot_adv = dot_gl_font_rid.is_valid() ? _font_get_glyph_advance(dot_gl_font_rid, last_gl_font_size, dot_gl_idx) : Vector2(); + int32_t dot_gl_idx = ((add_ellipsis || enforce_ellipsis) && dot_gl_font_rid.is_valid()) ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, (found_el_char ? sd->el_char : '.'), 0) : -1; + Vector2 dot_adv = ((add_ellipsis || enforce_ellipsis) && dot_gl_font_rid.is_valid()) ? _font_get_glyph_advance(dot_gl_font_rid, last_gl_font_size, dot_gl_idx) : Vector2(); int32_t whitespace_gl_idx = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_index(whitespace_gl_font_rid, last_gl_font_size, ' ', 0) : -1; Vector2 whitespace_adv = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_advance(whitespace_gl_font_rid, last_gl_font_size, whitespace_gl_idx) : Vector2(); diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp index f7c6ea899f..8b2c58acd5 100644 --- a/modules/theora/video_stream_theora.cpp +++ b/modules/theora/video_stream_theora.cpp @@ -115,7 +115,7 @@ void VideoStreamPlaybackTheora::video_write() { format = Image::FORMAT_RGBA8; } - Ref<Image> img = memnew(Image(size.x, size.y, 0, Image::FORMAT_RGBA8, frame_data)); //zero copy image creation + Ref<Image> img = memnew(Image(size.x, size.y, false, Image::FORMAT_RGBA8, frame_data)); //zero copy image creation texture->update(img); //zero copy send to rendering server diff --git a/modules/vorbis/resource_importer_ogg_vorbis.h b/modules/vorbis/resource_importer_ogg_vorbis.h index 59ae3378a0..f378b80694 100644 --- a/modules/vorbis/resource_importer_ogg_vorbis.h +++ b/modules/vorbis/resource_importer_ogg_vorbis.h @@ -65,6 +65,8 @@ public: virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override; + virtual bool can_import_threaded() const override { return true; } + ResourceImporterOggVorbis(); }; diff --git a/modules/webp/webp_common.cpp b/modules/webp/webp_common.cpp index 3a2ac5a90e..0284eec574 100644 --- a/modules/webp/webp_common.cpp +++ b/modules/webp/webp_common.cpp @@ -149,7 +149,7 @@ Ref<Image> _webp_unpack(const Vector<uint8_t> &p_buffer) { ERR_FAIL_COND_V_MSG(errdec, Ref<Image>(), "Failed decoding WebP image."); - Ref<Image> img = memnew(Image(features.width, features.height, 0, features.has_alpha ? Image::FORMAT_RGBA8 : Image::FORMAT_RGB8, dst_image)); + Ref<Image> img = memnew(Image(features.width, features.height, false, features.has_alpha ? Image::FORMAT_RGBA8 : Image::FORMAT_RGB8, dst_image)); return img; } |
