diff --git a/.cache/clangd/index/a_hash_map.hpp.F28465756B10A958.idx b/.cache/clangd/index/a_hash_map.hpp.F28465756B10A958.idx new file mode 100644 index 00000000..b89d62c3 Binary files /dev/null and b/.cache/clangd/index/a_hash_map.hpp.F28465756B10A958.idx differ diff --git a/.cache/clangd/index/aabb.cpp.822AD41C7D3B93B1.idx b/.cache/clangd/index/aabb.cpp.822AD41C7D3B93B1.idx new file mode 100644 index 00000000..3c380811 Binary files /dev/null and b/.cache/clangd/index/aabb.cpp.822AD41C7D3B93B1.idx differ diff --git a/.cache/clangd/index/aabb.hpp.F4B0EFA6C286E670.idx b/.cache/clangd/index/aabb.hpp.F4B0EFA6C286E670.idx new file mode 100644 index 00000000..9115d32b Binary files /dev/null and b/.cache/clangd/index/aabb.hpp.F4B0EFA6C286E670.idx differ diff --git a/.cache/clangd/index/array_helpers.hpp.11A5E3D576AFBEBF.idx b/.cache/clangd/index/array_helpers.hpp.11A5E3D576AFBEBF.idx new file mode 100644 index 00000000..c5d873a5 Binary files /dev/null and b/.cache/clangd/index/array_helpers.hpp.11A5E3D576AFBEBF.idx differ diff --git a/.cache/clangd/index/basis.cpp.FF251AE6B6032F6E.idx b/.cache/clangd/index/basis.cpp.FF251AE6B6032F6E.idx new file mode 100644 index 00000000..3c575dd5 Binary files /dev/null and b/.cache/clangd/index/basis.cpp.FF251AE6B6032F6E.idx differ diff --git a/.cache/clangd/index/basis.hpp.B14B6D7645282EFB.idx b/.cache/clangd/index/basis.hpp.B14B6D7645282EFB.idx new file mode 100644 index 00000000..72e50918 Binary files /dev/null and b/.cache/clangd/index/basis.hpp.B14B6D7645282EFB.idx differ diff --git a/.cache/clangd/index/binder_common.hpp.EC524C0F173EBEF2.idx b/.cache/clangd/index/binder_common.hpp.EC524C0F173EBEF2.idx new file mode 100644 index 00000000..3f9e0fcb Binary files /dev/null and b/.cache/clangd/index/binder_common.hpp.EC524C0F173EBEF2.idx differ diff --git a/.cache/clangd/index/callable_custom.cpp.9BF50FF2D54C567E.idx b/.cache/clangd/index/callable_custom.cpp.9BF50FF2D54C567E.idx new file mode 100644 index 00000000..9678ee90 Binary files /dev/null and b/.cache/clangd/index/callable_custom.cpp.9BF50FF2D54C567E.idx differ diff --git a/.cache/clangd/index/callable_custom.hpp.847A1CDD9534C208.idx b/.cache/clangd/index/callable_custom.hpp.847A1CDD9534C208.idx new file mode 100644 index 00000000..fab6f433 Binary files /dev/null and b/.cache/clangd/index/callable_custom.hpp.847A1CDD9534C208.idx differ diff --git a/.cache/clangd/index/callable_method_pointer.cpp.C42012A6D131A858.idx b/.cache/clangd/index/callable_method_pointer.cpp.C42012A6D131A858.idx new file mode 100644 index 00000000..d6aa6588 Binary files /dev/null and b/.cache/clangd/index/callable_method_pointer.cpp.C42012A6D131A858.idx differ diff --git a/.cache/clangd/index/callable_method_pointer.hpp.00870FD7B4094ECA.idx b/.cache/clangd/index/callable_method_pointer.hpp.00870FD7B4094ECA.idx new file mode 100644 index 00000000..4835ec27 Binary files /dev/null and b/.cache/clangd/index/callable_method_pointer.hpp.00870FD7B4094ECA.idx differ diff --git a/.cache/clangd/index/char_range.inc.hpp.1FCDDBE5CD6F5E86.idx b/.cache/clangd/index/char_range.inc.hpp.1FCDDBE5CD6F5E86.idx new file mode 100644 index 00000000..b059fc69 Binary files /dev/null and b/.cache/clangd/index/char_range.inc.hpp.1FCDDBE5CD6F5E86.idx differ diff --git a/.cache/clangd/index/char_string.cpp.3240C0EF84EF8FD4.idx b/.cache/clangd/index/char_string.cpp.3240C0EF84EF8FD4.idx new file mode 100644 index 00000000..0b2d6039 Binary files /dev/null and b/.cache/clangd/index/char_string.cpp.3240C0EF84EF8FD4.idx differ diff --git a/.cache/clangd/index/char_string.hpp.0F96F84BAA1D3450.idx b/.cache/clangd/index/char_string.hpp.0F96F84BAA1D3450.idx new file mode 100644 index 00000000..9758f847 Binary files /dev/null and b/.cache/clangd/index/char_string.hpp.0F96F84BAA1D3450.idx differ diff --git a/.cache/clangd/index/char_utils.hpp.CEC03E06731A7DFC.idx b/.cache/clangd/index/char_utils.hpp.CEC03E06731A7DFC.idx new file mode 100644 index 00000000..0c67dd60 Binary files /dev/null and b/.cache/clangd/index/char_utils.hpp.CEC03E06731A7DFC.idx differ diff --git a/.cache/clangd/index/class_db.cpp.9A7958ED76CFFD00.idx b/.cache/clangd/index/class_db.cpp.9A7958ED76CFFD00.idx new file mode 100644 index 00000000..a70c2cdb Binary files /dev/null and b/.cache/clangd/index/class_db.cpp.9A7958ED76CFFD00.idx differ diff --git a/.cache/clangd/index/class_db.hpp.68EB9721C5EBD267.idx b/.cache/clangd/index/class_db.hpp.68EB9721C5EBD267.idx new file mode 100644 index 00000000..9afda6ce Binary files /dev/null and b/.cache/clangd/index/class_db.hpp.68EB9721C5EBD267.idx differ diff --git a/.cache/clangd/index/color.cpp.349E946CE69F6D18.idx b/.cache/clangd/index/color.cpp.349E946CE69F6D18.idx new file mode 100644 index 00000000..7fb4f035 Binary files /dev/null and b/.cache/clangd/index/color.cpp.349E946CE69F6D18.idx differ diff --git a/.cache/clangd/index/color.hpp.60ED146ABFEEDF5B.idx b/.cache/clangd/index/color.hpp.60ED146ABFEEDF5B.idx new file mode 100644 index 00000000..3b1c7817 Binary files /dev/null and b/.cache/clangd/index/color.hpp.60ED146ABFEEDF5B.idx differ diff --git a/.cache/clangd/index/color_names.inc.hpp.0F053AD5A137A52E.idx b/.cache/clangd/index/color_names.inc.hpp.0F053AD5A137A52E.idx new file mode 100644 index 00000000..c0841333 Binary files /dev/null and b/.cache/clangd/index/color_names.inc.hpp.0F053AD5A137A52E.idx differ diff --git a/.cache/clangd/index/cowdata.hpp.E982CF3DC4F69A74.idx b/.cache/clangd/index/cowdata.hpp.E982CF3DC4F69A74.idx new file mode 100644 index 00000000..7b6e317d Binary files /dev/null and b/.cache/clangd/index/cowdata.hpp.E982CF3DC4F69A74.idx differ diff --git a/.cache/clangd/index/defs.hpp.1C60534A2BC886FA.idx b/.cache/clangd/index/defs.hpp.1C60534A2BC886FA.idx new file mode 100644 index 00000000..eaf47aee Binary files /dev/null and b/.cache/clangd/index/defs.hpp.1C60534A2BC886FA.idx differ diff --git a/.cache/clangd/index/editor_plugin_registration.cpp.62D6D36B58A05BE8.idx b/.cache/clangd/index/editor_plugin_registration.cpp.62D6D36B58A05BE8.idx new file mode 100644 index 00000000..a7630531 Binary files /dev/null and b/.cache/clangd/index/editor_plugin_registration.cpp.62D6D36B58A05BE8.idx differ diff --git a/.cache/clangd/index/editor_plugin_registration.hpp.09F0CF5D85847FC0.idx b/.cache/clangd/index/editor_plugin_registration.hpp.09F0CF5D85847FC0.idx new file mode 100644 index 00000000..b0118ebe Binary files /dev/null and b/.cache/clangd/index/editor_plugin_registration.hpp.09F0CF5D85847FC0.idx differ diff --git a/.cache/clangd/index/error_macros.cpp.13324F008FABED41.idx b/.cache/clangd/index/error_macros.cpp.13324F008FABED41.idx new file mode 100644 index 00000000..31e22af2 Binary files /dev/null and b/.cache/clangd/index/error_macros.cpp.13324F008FABED41.idx differ diff --git a/.cache/clangd/index/error_macros.hpp.73ACBA67852EFB8F.idx b/.cache/clangd/index/error_macros.hpp.73ACBA67852EFB8F.idx new file mode 100644 index 00000000..e2c1c148 Binary files /dev/null and b/.cache/clangd/index/error_macros.hpp.73ACBA67852EFB8F.idx differ diff --git a/.cache/clangd/index/godot.cpp.662ED65A28ADD468.idx b/.cache/clangd/index/godot.cpp.662ED65A28ADD468.idx new file mode 100644 index 00000000..e1a6c6e0 Binary files /dev/null and b/.cache/clangd/index/godot.cpp.662ED65A28ADD468.idx differ diff --git a/.cache/clangd/index/godot.hpp.DC341EEB13CEDC00.idx b/.cache/clangd/index/godot.hpp.DC341EEB13CEDC00.idx new file mode 100644 index 00000000..7b5a47de Binary files /dev/null and b/.cache/clangd/index/godot.hpp.DC341EEB13CEDC00.idx differ diff --git a/.cache/clangd/index/hash_map.hpp.364D272A3066ADD3.idx b/.cache/clangd/index/hash_map.hpp.364D272A3066ADD3.idx new file mode 100644 index 00000000..37904afe Binary files /dev/null and b/.cache/clangd/index/hash_map.hpp.364D272A3066ADD3.idx differ diff --git a/.cache/clangd/index/hash_set.hpp.E60BE5D35B16AC10.idx b/.cache/clangd/index/hash_set.hpp.E60BE5D35B16AC10.idx new file mode 100644 index 00000000..40de1b84 Binary files /dev/null and b/.cache/clangd/index/hash_set.hpp.E60BE5D35B16AC10.idx differ diff --git a/.cache/clangd/index/hashfuncs.cpp.7F7FE6D42D2C8CC5.idx b/.cache/clangd/index/hashfuncs.cpp.7F7FE6D42D2C8CC5.idx new file mode 100644 index 00000000..0ab0771f Binary files /dev/null and b/.cache/clangd/index/hashfuncs.cpp.7F7FE6D42D2C8CC5.idx differ diff --git a/.cache/clangd/index/hashfuncs.hpp.22A9BDAC9BB5135A.idx b/.cache/clangd/index/hashfuncs.hpp.22A9BDAC9BB5135A.idx new file mode 100644 index 00000000..b0773e3c Binary files /dev/null and b/.cache/clangd/index/hashfuncs.hpp.22A9BDAC9BB5135A.idx differ diff --git a/.cache/clangd/index/list.hpp.723F7DE1D508BB1A.idx b/.cache/clangd/index/list.hpp.723F7DE1D508BB1A.idx new file mode 100644 index 00000000..eebc361a Binary files /dev/null and b/.cache/clangd/index/list.hpp.723F7DE1D508BB1A.idx differ diff --git a/.cache/clangd/index/load_proc_address.inc.A7E56E611699B2E1.idx b/.cache/clangd/index/load_proc_address.inc.A7E56E611699B2E1.idx new file mode 100644 index 00000000..f1420b5a Binary files /dev/null and b/.cache/clangd/index/load_proc_address.inc.A7E56E611699B2E1.idx differ diff --git a/.cache/clangd/index/local_vector.hpp.6172522D8DB60962.idx b/.cache/clangd/index/local_vector.hpp.6172522D8DB60962.idx new file mode 100644 index 00000000..e46e16b0 Binary files /dev/null and b/.cache/clangd/index/local_vector.hpp.6172522D8DB60962.idx differ diff --git a/.cache/clangd/index/low_level.cpp.E1A10EAB928F24AD.idx b/.cache/clangd/index/low_level.cpp.E1A10EAB928F24AD.idx new file mode 100644 index 00000000..5276442b Binary files /dev/null and b/.cache/clangd/index/low_level.cpp.E1A10EAB928F24AD.idx differ diff --git a/.cache/clangd/index/math.compat.inc.133000A997886F4A.idx b/.cache/clangd/index/math.compat.inc.133000A997886F4A.idx new file mode 100644 index 00000000..65e0e58e Binary files /dev/null and b/.cache/clangd/index/math.compat.inc.133000A997886F4A.idx differ diff --git a/.cache/clangd/index/math.hpp.649EE1ACCBB4C803.idx b/.cache/clangd/index/math.hpp.649EE1ACCBB4C803.idx new file mode 100644 index 00000000..65800a04 Binary files /dev/null and b/.cache/clangd/index/math.hpp.649EE1ACCBB4C803.idx differ diff --git a/.cache/clangd/index/math_defs.hpp.EFBEA6FA602B0A7D.idx b/.cache/clangd/index/math_defs.hpp.EFBEA6FA602B0A7D.idx new file mode 100644 index 00000000..7cce1047 Binary files /dev/null and b/.cache/clangd/index/math_defs.hpp.EFBEA6FA602B0A7D.idx differ diff --git a/.cache/clangd/index/math_funcs_binary.hpp.184C5F22AA328B7D.idx b/.cache/clangd/index/math_funcs_binary.hpp.184C5F22AA328B7D.idx new file mode 100644 index 00000000..41d16531 Binary files /dev/null and b/.cache/clangd/index/math_funcs_binary.hpp.184C5F22AA328B7D.idx differ diff --git a/.cache/clangd/index/memory.cpp.21265A072F994A6E.idx b/.cache/clangd/index/memory.cpp.21265A072F994A6E.idx new file mode 100644 index 00000000..e8c4b97d Binary files /dev/null and b/.cache/clangd/index/memory.cpp.21265A072F994A6E.idx differ diff --git a/.cache/clangd/index/memory.hpp.F30F49B90F1CA9B1.idx b/.cache/clangd/index/memory.hpp.F30F49B90F1CA9B1.idx new file mode 100644 index 00000000..2a4bb150 Binary files /dev/null and b/.cache/clangd/index/memory.hpp.F30F49B90F1CA9B1.idx differ diff --git a/.cache/clangd/index/method_bind.cpp.EE167F76075EA2CC.idx b/.cache/clangd/index/method_bind.cpp.EE167F76075EA2CC.idx new file mode 100644 index 00000000..91ad4d03 Binary files /dev/null and b/.cache/clangd/index/method_bind.cpp.EE167F76075EA2CC.idx differ diff --git a/.cache/clangd/index/method_bind.hpp.F2C1E9DBA41B969C.idx b/.cache/clangd/index/method_bind.hpp.F2C1E9DBA41B969C.idx new file mode 100644 index 00000000..d855f079 Binary files /dev/null and b/.cache/clangd/index/method_bind.hpp.F2C1E9DBA41B969C.idx differ diff --git a/.cache/clangd/index/method_ptrcall.hpp.E720C4401581ADAD.idx b/.cache/clangd/index/method_ptrcall.hpp.E720C4401581ADAD.idx new file mode 100644 index 00000000..c5266a1f Binary files /dev/null and b/.cache/clangd/index/method_ptrcall.hpp.E720C4401581ADAD.idx differ diff --git a/.cache/clangd/index/metin2_model.cpp.356D6E3C52F8EFB4.idx b/.cache/clangd/index/metin2_model.cpp.356D6E3C52F8EFB4.idx new file mode 100644 index 00000000..2dc6115f Binary files /dev/null and b/.cache/clangd/index/metin2_model.cpp.356D6E3C52F8EFB4.idx differ diff --git a/.cache/clangd/index/metin2_model.h.7BE7F24D98D14274.idx b/.cache/clangd/index/metin2_model.h.7BE7F24D98D14274.idx new file mode 100644 index 00000000..6aeea36c Binary files /dev/null and b/.cache/clangd/index/metin2_model.h.7BE7F24D98D14274.idx differ diff --git a/.cache/clangd/index/object.cpp.9E2F561D1D7B1986.idx b/.cache/clangd/index/object.cpp.9E2F561D1D7B1986.idx new file mode 100644 index 00000000..10f7d1bf Binary files /dev/null and b/.cache/clangd/index/object.cpp.9E2F561D1D7B1986.idx differ diff --git a/.cache/clangd/index/object.hpp.F8C09FDFBB6879A9.idx b/.cache/clangd/index/object.hpp.F8C09FDFBB6879A9.idx new file mode 100644 index 00000000..ff76212c Binary files /dev/null and b/.cache/clangd/index/object.hpp.F8C09FDFBB6879A9.idx differ diff --git a/.cache/clangd/index/object_id.hpp.197C6D42F9731419.idx b/.cache/clangd/index/object_id.hpp.197C6D42F9731419.idx new file mode 100644 index 00000000..7f2e7183 Binary files /dev/null and b/.cache/clangd/index/object_id.hpp.197C6D42F9731419.idx differ diff --git a/.cache/clangd/index/packed_arrays.cpp.DF3DF665931DA2D8.idx b/.cache/clangd/index/packed_arrays.cpp.DF3DF665931DA2D8.idx new file mode 100644 index 00000000..8be06231 Binary files /dev/null and b/.cache/clangd/index/packed_arrays.cpp.DF3DF665931DA2D8.idx differ diff --git a/.cache/clangd/index/pair.hpp.1E3328E1795DCD16.idx b/.cache/clangd/index/pair.hpp.1E3328E1795DCD16.idx new file mode 100644 index 00000000..8f0c278b Binary files /dev/null and b/.cache/clangd/index/pair.hpp.1E3328E1795DCD16.idx differ diff --git a/.cache/clangd/index/plane.cpp.CC6B2CFC7DCDC274.idx b/.cache/clangd/index/plane.cpp.CC6B2CFC7DCDC274.idx new file mode 100644 index 00000000..791403c5 Binary files /dev/null and b/.cache/clangd/index/plane.cpp.CC6B2CFC7DCDC274.idx differ diff --git a/.cache/clangd/index/plane.hpp.AD6FC00867087D2F.idx b/.cache/clangd/index/plane.hpp.AD6FC00867087D2F.idx new file mode 100644 index 00000000..03034791 Binary files /dev/null and b/.cache/clangd/index/plane.hpp.AD6FC00867087D2F.idx differ diff --git a/.cache/clangd/index/print_string.cpp.7FD646375AC1ADBF.idx b/.cache/clangd/index/print_string.cpp.7FD646375AC1ADBF.idx new file mode 100644 index 00000000..b48f7e02 Binary files /dev/null and b/.cache/clangd/index/print_string.cpp.7FD646375AC1ADBF.idx differ diff --git a/.cache/clangd/index/print_string.hpp.6A320936902CEED3.idx b/.cache/clangd/index/print_string.hpp.6A320936902CEED3.idx new file mode 100644 index 00000000..1569f97f Binary files /dev/null and b/.cache/clangd/index/print_string.hpp.6A320936902CEED3.idx differ diff --git a/.cache/clangd/index/projection.cpp.860434FEB65AC3F3.idx b/.cache/clangd/index/projection.cpp.860434FEB65AC3F3.idx new file mode 100644 index 00000000..41dc30ee Binary files /dev/null and b/.cache/clangd/index/projection.cpp.860434FEB65AC3F3.idx differ diff --git a/.cache/clangd/index/projection.hpp.966C4B8E571659AE.idx b/.cache/clangd/index/projection.hpp.966C4B8E571659AE.idx new file mode 100644 index 00000000..937ceba1 Binary files /dev/null and b/.cache/clangd/index/projection.hpp.966C4B8E571659AE.idx differ diff --git a/.cache/clangd/index/property_info.hpp.2CE574F5D78EE60E.idx b/.cache/clangd/index/property_info.hpp.2CE574F5D78EE60E.idx new file mode 100644 index 00000000..8c0ff251 Binary files /dev/null and b/.cache/clangd/index/property_info.hpp.2CE574F5D78EE60E.idx differ diff --git a/.cache/clangd/index/quaternion.cpp.ED883723A59FB342.idx b/.cache/clangd/index/quaternion.cpp.ED883723A59FB342.idx new file mode 100644 index 00000000..4b934ba7 Binary files /dev/null and b/.cache/clangd/index/quaternion.cpp.ED883723A59FB342.idx differ diff --git a/.cache/clangd/index/quaternion.hpp.C004BED8C80784A4.idx b/.cache/clangd/index/quaternion.hpp.C004BED8C80784A4.idx new file mode 100644 index 00000000..175136f2 Binary files /dev/null and b/.cache/clangd/index/quaternion.hpp.C004BED8C80784A4.idx differ diff --git a/.cache/clangd/index/rect2.cpp.298164AFFAD8E4E0.idx b/.cache/clangd/index/rect2.cpp.298164AFFAD8E4E0.idx new file mode 100644 index 00000000..70f755ce Binary files /dev/null and b/.cache/clangd/index/rect2.cpp.298164AFFAD8E4E0.idx differ diff --git a/.cache/clangd/index/rect2.hpp.C09082D5BA4184D4.idx b/.cache/clangd/index/rect2.hpp.C09082D5BA4184D4.idx new file mode 100644 index 00000000..8efa46ba Binary files /dev/null and b/.cache/clangd/index/rect2.hpp.C09082D5BA4184D4.idx differ diff --git a/.cache/clangd/index/rect2i.cpp.92C981E999340E18.idx b/.cache/clangd/index/rect2i.cpp.92C981E999340E18.idx new file mode 100644 index 00000000..51de8cf8 Binary files /dev/null and b/.cache/clangd/index/rect2i.cpp.92C981E999340E18.idx differ diff --git a/.cache/clangd/index/rect2i.hpp.EC1AFE6F9D953896.idx b/.cache/clangd/index/rect2i.hpp.EC1AFE6F9D953896.idx new file mode 100644 index 00000000..b0036837 Binary files /dev/null and b/.cache/clangd/index/rect2i.hpp.EC1AFE6F9D953896.idx differ diff --git a/.cache/clangd/index/ref.hpp.B4C27929F0CA7A4B.idx b/.cache/clangd/index/ref.hpp.B4C27929F0CA7A4B.idx new file mode 100644 index 00000000..6f29bbde Binary files /dev/null and b/.cache/clangd/index/ref.hpp.B4C27929F0CA7A4B.idx differ diff --git a/.cache/clangd/index/register_types.cpp.A8F89A101D16E725.idx b/.cache/clangd/index/register_types.cpp.A8F89A101D16E725.idx new file mode 100644 index 00000000..d04724d7 Binary files /dev/null and b/.cache/clangd/index/register_types.cpp.A8F89A101D16E725.idx differ diff --git a/.cache/clangd/index/register_types.h.B4EF5EA300B9886A.idx b/.cache/clangd/index/register_types.h.B4EF5EA300B9886A.idx new file mode 100644 index 00000000..c2f3c52b Binary files /dev/null and b/.cache/clangd/index/register_types.h.B4EF5EA300B9886A.idx differ diff --git a/.cache/clangd/index/safe_refcount.hpp.E0137F63E9FE1FB1.idx b/.cache/clangd/index/safe_refcount.hpp.E0137F63E9FE1FB1.idx new file mode 100644 index 00000000..702b4d1d Binary files /dev/null and b/.cache/clangd/index/safe_refcount.hpp.E0137F63E9FE1FB1.idx differ diff --git a/.cache/clangd/index/sort_array.hpp.7AE84AFCFDFC40CB.idx b/.cache/clangd/index/sort_array.hpp.7AE84AFCFDFC40CB.idx new file mode 100644 index 00000000..b0f3604b Binary files /dev/null and b/.cache/clangd/index/sort_array.hpp.7AE84AFCFDFC40CB.idx differ diff --git a/.cache/clangd/index/sort_list.hpp.966E1A4D373F91A5.idx b/.cache/clangd/index/sort_list.hpp.966E1A4D373F91A5.idx new file mode 100644 index 00000000..b2968fd7 Binary files /dev/null and b/.cache/clangd/index/sort_list.hpp.966E1A4D373F91A5.idx differ diff --git a/.cache/clangd/index/span.hpp.84CA23E9D5201F23.idx b/.cache/clangd/index/span.hpp.84CA23E9D5201F23.idx new file mode 100644 index 00000000..796e2bdb Binary files /dev/null and b/.cache/clangd/index/span.hpp.84CA23E9D5201F23.idx differ diff --git a/.cache/clangd/index/transform2d.cpp.654AB5D571523624.idx b/.cache/clangd/index/transform2d.cpp.654AB5D571523624.idx new file mode 100644 index 00000000..78159233 Binary files /dev/null and b/.cache/clangd/index/transform2d.cpp.654AB5D571523624.idx differ diff --git a/.cache/clangd/index/transform2d.hpp.65545BED43A9C368.idx b/.cache/clangd/index/transform2d.hpp.65545BED43A9C368.idx new file mode 100644 index 00000000..789fa52c Binary files /dev/null and b/.cache/clangd/index/transform2d.hpp.65545BED43A9C368.idx differ diff --git a/.cache/clangd/index/transform3d.cpp.0BBBDFA040A4F487.idx b/.cache/clangd/index/transform3d.cpp.0BBBDFA040A4F487.idx new file mode 100644 index 00000000..89054243 Binary files /dev/null and b/.cache/clangd/index/transform3d.cpp.0BBBDFA040A4F487.idx differ diff --git a/.cache/clangd/index/transform3d.hpp.23E0A3F5D2A4F018.idx b/.cache/clangd/index/transform3d.hpp.23E0A3F5D2A4F018.idx new file mode 100644 index 00000000..f4c591f5 Binary files /dev/null and b/.cache/clangd/index/transform3d.hpp.23E0A3F5D2A4F018.idx differ diff --git a/.cache/clangd/index/type_info.hpp.18DAA326F3E285D8.idx b/.cache/clangd/index/type_info.hpp.18DAA326F3E285D8.idx new file mode 100644 index 00000000..d3a301c5 Binary files /dev/null and b/.cache/clangd/index/type_info.hpp.18DAA326F3E285D8.idx differ diff --git a/.cache/clangd/index/typed_array.hpp.B0FEAC19A350B2F4.idx b/.cache/clangd/index/typed_array.hpp.B0FEAC19A350B2F4.idx new file mode 100644 index 00000000..bc2cda55 Binary files /dev/null and b/.cache/clangd/index/typed_array.hpp.B0FEAC19A350B2F4.idx differ diff --git a/.cache/clangd/index/variant.cpp.13F0E31D395FDE70.idx b/.cache/clangd/index/variant.cpp.13F0E31D395FDE70.idx new file mode 100644 index 00000000..c6d9ee93 Binary files /dev/null and b/.cache/clangd/index/variant.cpp.13F0E31D395FDE70.idx differ diff --git a/.cache/clangd/index/variant.hpp.7DB1AA596DAE4F9B.idx b/.cache/clangd/index/variant.hpp.7DB1AA596DAE4F9B.idx new file mode 100644 index 00000000..55b97254 Binary files /dev/null and b/.cache/clangd/index/variant.hpp.7DB1AA596DAE4F9B.idx differ diff --git a/.cache/clangd/index/variant_internal.cpp.8C5F6BC551327FB7.idx b/.cache/clangd/index/variant_internal.cpp.8C5F6BC551327FB7.idx new file mode 100644 index 00000000..1e3f6408 Binary files /dev/null and b/.cache/clangd/index/variant_internal.cpp.8C5F6BC551327FB7.idx differ diff --git a/.cache/clangd/index/variant_internal.hpp.8DBDEE2615AA7438.idx b/.cache/clangd/index/variant_internal.hpp.8DBDEE2615AA7438.idx new file mode 100644 index 00000000..e103719c Binary files /dev/null and b/.cache/clangd/index/variant_internal.hpp.8DBDEE2615AA7438.idx differ diff --git a/.cache/clangd/index/vector.hpp.5184B96D786966E5.idx b/.cache/clangd/index/vector.hpp.5184B96D786966E5.idx new file mode 100644 index 00000000..3e2aab33 Binary files /dev/null and b/.cache/clangd/index/vector.hpp.5184B96D786966E5.idx differ diff --git a/.cache/clangd/index/vector2.cpp.CC7E9399966DE26E.idx b/.cache/clangd/index/vector2.cpp.CC7E9399966DE26E.idx new file mode 100644 index 00000000..c4abb777 Binary files /dev/null and b/.cache/clangd/index/vector2.cpp.CC7E9399966DE26E.idx differ diff --git a/.cache/clangd/index/vector2.hpp.8AD7430713BE1EA8.idx b/.cache/clangd/index/vector2.hpp.8AD7430713BE1EA8.idx new file mode 100644 index 00000000..617246fb Binary files /dev/null and b/.cache/clangd/index/vector2.hpp.8AD7430713BE1EA8.idx differ diff --git a/.cache/clangd/index/vector2i.cpp.735E2866592C8FF1.idx b/.cache/clangd/index/vector2i.cpp.735E2866592C8FF1.idx new file mode 100644 index 00000000..c1e3f22d Binary files /dev/null and b/.cache/clangd/index/vector2i.cpp.735E2866592C8FF1.idx differ diff --git a/.cache/clangd/index/vector2i.hpp.CEF9528A2735E0F9.idx b/.cache/clangd/index/vector2i.hpp.CEF9528A2735E0F9.idx new file mode 100644 index 00000000..e3eccaab Binary files /dev/null and b/.cache/clangd/index/vector2i.hpp.CEF9528A2735E0F9.idx differ diff --git a/.cache/clangd/index/vector3.cpp.CF34D7710F9C9F46.idx b/.cache/clangd/index/vector3.cpp.CF34D7710F9C9F46.idx new file mode 100644 index 00000000..b5334488 Binary files /dev/null and b/.cache/clangd/index/vector3.cpp.CF34D7710F9C9F46.idx differ diff --git a/.cache/clangd/index/vector3.hpp.14053525A743906E.idx b/.cache/clangd/index/vector3.hpp.14053525A743906E.idx new file mode 100644 index 00000000..56745025 Binary files /dev/null and b/.cache/clangd/index/vector3.hpp.14053525A743906E.idx differ diff --git a/.cache/clangd/index/vector3i.cpp.B52696F8128E584E.idx b/.cache/clangd/index/vector3i.cpp.B52696F8128E584E.idx new file mode 100644 index 00000000..edda20da Binary files /dev/null and b/.cache/clangd/index/vector3i.cpp.B52696F8128E584E.idx differ diff --git a/.cache/clangd/index/vector3i.hpp.057EE9D394B17D2A.idx b/.cache/clangd/index/vector3i.hpp.057EE9D394B17D2A.idx new file mode 100644 index 00000000..c5a97643 Binary files /dev/null and b/.cache/clangd/index/vector3i.hpp.057EE9D394B17D2A.idx differ diff --git a/.cache/clangd/index/vector4.cpp.5B497ED33C9D0CBD.idx b/.cache/clangd/index/vector4.cpp.5B497ED33C9D0CBD.idx new file mode 100644 index 00000000..f5fe4009 Binary files /dev/null and b/.cache/clangd/index/vector4.cpp.5B497ED33C9D0CBD.idx differ diff --git a/.cache/clangd/index/vector4.hpp.FCF0E1CCB076713A.idx b/.cache/clangd/index/vector4.hpp.FCF0E1CCB076713A.idx new file mode 100644 index 00000000..d5c5b6df Binary files /dev/null and b/.cache/clangd/index/vector4.hpp.FCF0E1CCB076713A.idx differ diff --git a/.cache/clangd/index/vector4i.cpp.E08272F454E7AA7F.idx b/.cache/clangd/index/vector4i.cpp.E08272F454E7AA7F.idx new file mode 100644 index 00000000..a4ffb942 Binary files /dev/null and b/.cache/clangd/index/vector4i.cpp.E08272F454E7AA7F.idx differ diff --git a/.cache/clangd/index/vector4i.hpp.E3B3BC887ED44259.idx b/.cache/clangd/index/vector4i.hpp.E3B3BC887ED44259.idx new file mode 100644 index 00000000..9a5f176a Binary files /dev/null and b/.cache/clangd/index/vector4i.hpp.E3B3BC887ED44259.idx differ diff --git a/.cache/clangd/index/wrapped.cpp.008F58EAA433B249.idx b/.cache/clangd/index/wrapped.cpp.008F58EAA433B249.idx new file mode 100644 index 00000000..b6ee3cb1 Binary files /dev/null and b/.cache/clangd/index/wrapped.cpp.008F58EAA433B249.idx differ diff --git a/.cache/clangd/index/wrapped.hpp.A165692CA539B3FC.idx b/.cache/clangd/index/wrapped.hpp.A165692CA539B3FC.idx new file mode 100644 index 00000000..6cd4bdb3 Binary files /dev/null and b/.cache/clangd/index/wrapped.hpp.A165692CA539B3FC.idx differ diff --git a/extension/CMakeLists.txt b/extension/CMakeLists.txt index d605009d..7cbd58f9 100644 --- a/extension/CMakeLists.txt +++ b/extension/CMakeLists.txt @@ -23,6 +23,9 @@ add_subdirectory("${XRENDER_POC_DIR}/libgr2" "${CMAKE_CURRENT_BINARY_DIR}/libgr2 add_library(mtgodot SHARED src/register_types.cpp src/metin2_model.cpp + src/metin2_anim.cpp + src/gr2_bridge.cpp + src/dxt.cpp ) target_compile_features(mtgodot PRIVATE cxx_std_20) target_link_libraries(mtgodot PRIVATE godot::cpp xrender::libgr2) diff --git a/extension/src/dxt.cpp b/extension/src/dxt.cpp new file mode 100644 index 00000000..cb6c2d2f --- /dev/null +++ b/extension/src/dxt.cpp @@ -0,0 +1,160 @@ +// engine/dxt —— DDS(DXT1/3/5) + 未压缩 BGRA8 → RGBA8 软解。M1 T3。 +#include "dxt.h" +#include +#include + +namespace mtgodot { +namespace { + +inline uint32_t rd_le32(const uint8_t* p) { + return uint32_t(p[0]) | (uint32_t(p[1]) << 8) | (uint32_t(p[2]) << 16) | (uint32_t(p[3]) << 24); +} + +// 5:6:5 → r,g,b (0..255) +inline void unpack565(uint16_t c, int& r, int& g, int& b) { + r = ((c >> 11) & 0x1f); r = (r << 3) | (r >> 2); + g = ((c >> 5) & 0x3f); g = (g << 2) | (g >> 4); + b = (c & 0x1f); b = (b << 3) | (b >> 2); +} + +// 解一个 DXT1 颜色块(8B)→ 16 像素 RGB(不写 alpha;dxt1_alpha=true 时按 1-bit alpha 写) +void decode_color_block(const uint8_t* blk, uint8_t out[16][4], bool dxt1_alpha) { + uint16_t c0 = uint16_t(blk[0] | (blk[1] << 8)); + uint16_t c1 = uint16_t(blk[2] | (blk[3] << 8)); + int r[4], g[4], b[4], a[4] = {255, 255, 255, 255}; + unpack565(c0, r[0], g[0], b[0]); + unpack565(c1, r[1], g[1], b[1]); + if (c0 > c1 || !dxt1_alpha) { + r[2] = (2 * r[0] + r[1]) / 3; g[2] = (2 * g[0] + g[1]) / 3; b[2] = (2 * b[0] + b[1]) / 3; + r[3] = (r[0] + 2 * r[1]) / 3; g[3] = (g[0] + 2 * g[1]) / 3; b[3] = (b[0] + 2 * b[1]) / 3; + } else { + r[2] = (r[0] + r[1]) / 2; g[2] = (g[0] + g[1]) / 2; b[2] = (b[0] + b[1]) / 2; + r[3] = g[3] = b[3] = 0; a[3] = 0; // 透明 + } + uint32_t bits = rd_le32(blk + 4); + for (int i = 0; i < 16; ++i) { + int idx = (bits >> (i * 2)) & 3; + out[i][0] = uint8_t(r[idx]); out[i][1] = uint8_t(g[idx]); + out[i][2] = uint8_t(b[idx]); out[i][3] = uint8_t(a[idx]); + } +} + +// DXT3:alpha 块(8B)= 16 个 4-bit alpha,直接展开 +void decode_dxt3_alpha(const uint8_t* blk, uint8_t out[16][4]) { + for (int i = 0; i < 8; ++i) { + int a0 = blk[i] & 0x0f, a1 = (blk[i] >> 4) & 0x0f; + out[i * 2 + 0][3] = uint8_t(a0 * 17); // 0..15 → 0..255 + out[i * 2 + 1][3] = uint8_t(a1 * 17); + } +} + +// DXT5:alpha 块(8B)= 2 端点 + 16×3-bit 索引 +void decode_dxt5_alpha(const uint8_t* blk, uint8_t out[16][4]) { + int a0 = blk[0], a1 = blk[1]; + int a[8]; + a[0] = a0; a[1] = a1; + if (a0 > a1) { + for (int i = 1; i < 7; ++i) a[i + 1] = ((7 - i) * a0 + i * a1) / 7; + } else { + for (int i = 1; i < 5; ++i) a[i + 1] = ((5 - i) * a0 + i * a1) / 5; + a[6] = 0; a[7] = 255; + } + uint64_t bits = 0; + for (int i = 0; i < 6; ++i) bits |= uint64_t(blk[2 + i]) << (8 * i); + for (int i = 0; i < 16; ++i) { + int idx = int((bits >> (i * 3)) & 7); + out[i][3] = uint8_t(a[idx]); + } +} + +enum Fmt { F_NONE, F_DXT1, F_DXT3, F_DXT5, F_BGRA8 }; + +Image decode(const uint8_t* d, size_t len) { + Image img; + if (len < 128 || std::memcmp(d, "DDS ", 4) != 0) return img; + uint32_t hsize = rd_le32(d + 4); + if (hsize != 124) return img; + uint32_t h = rd_le32(d + 12); + uint32_t w = rd_le32(d + 16); + uint32_t pf_flags = rd_le32(d + 80); + const uint8_t* fourcc = d + 84; + uint32_t rgb_bitcount = rd_le32(d + 88); + + Fmt fmt = F_NONE; + if (pf_flags & 0x4) { // DDPF_FOURCC + if (!std::memcmp(fourcc, "DXT1", 4)) fmt = F_DXT1; + else if (!std::memcmp(fourcc, "DXT3", 4)) fmt = F_DXT3; + else if (!std::memcmp(fourcc, "DXT5", 4)) fmt = F_DXT5; + } else if ((pf_flags & 0x40) && rgb_bitcount == 32) { // DDPF_RGB, 32bpp + fmt = F_BGRA8; // Metin2 的非压缩 DDS 一般是 B8G8R8A8 + } + if (fmt == F_NONE || w == 0 || h == 0 || w > 8192 || h > 8192) return img; + + const uint8_t* src = d + 128; + size_t avail = len - 128; + img.w = uint16_t(w); + img.h = uint16_t(h); + img.rgba.assign(size_t(w) * h * 4, 0); + + if (fmt == F_BGRA8) { + if (avail < size_t(w) * h * 4) { img = Image{}; return img; } + for (size_t i = 0; i < size_t(w) * h; ++i) { + img.rgba[i * 4 + 0] = src[i * 4 + 2]; + img.rgba[i * 4 + 1] = src[i * 4 + 1]; + img.rgba[i * 4 + 2] = src[i * 4 + 0]; + img.rgba[i * 4 + 3] = src[i * 4 + 3]; + } + img.format = "BGRA8"; + return img; + } + + const int block_bytes = (fmt == F_DXT1) ? 8 : 16; + const size_t bx = (w + 3) / 4, by = (h + 3) / 4; + if (avail < bx * by * block_bytes) { img = Image{}; return img; } + + for (size_t byi = 0; byi < by; ++byi) { + for (size_t bxi = 0; bxi < bx; ++bxi) { + const uint8_t* blk = src + (byi * bx + bxi) * block_bytes; + uint8_t px[16][4]; + if (fmt == F_DXT1) { + decode_color_block(blk, px, /*dxt1_alpha=*/true); + } else { + decode_color_block(blk + 8, px, /*dxt1_alpha=*/false); + if (fmt == F_DXT3) decode_dxt3_alpha(blk, px); + else decode_dxt5_alpha(blk, px); + } + for (int py = 0; py < 4; ++py) { + size_t y = byi * 4 + py; + if (y >= h) break; + for (int pxx = 0; pxx < 4; ++pxx) { + size_t x = bxi * 4 + pxx; + if (x >= w) break; + uint8_t* o = &img.rgba[(y * w + x) * 4]; + const uint8_t* s = px[py * 4 + pxx]; + o[0] = s[0]; o[1] = s[1]; o[2] = s[2]; o[3] = s[3]; + } + } + } + } + img.format = (fmt == F_DXT1) ? "DXT1" : (fmt == F_DXT3) ? "DXT3" : "DXT5"; + return img; +} + +} // namespace + +Image load_dds(const uint8_t* bytes, size_t len) { return decode(bytes, len); } + +Image load_dds_path(const char* path) { + FILE* f = std::fopen(path, "rb"); + if (!f) return {}; + std::fseek(f, 0, SEEK_END); + long n = std::ftell(f); + std::fseek(f, 0, SEEK_SET); + std::vector buf(n > 0 ? size_t(n) : 0); + size_t rd = buf.empty() ? 0 : std::fread(buf.data(), 1, buf.size(), f); + std::fclose(f); + if (rd != buf.size()) return {}; + return decode(buf.data(), buf.size()); +} + +} // namespace mtgodot diff --git a/extension/src/dxt.h b/extension/src/dxt.h new file mode 100644 index 00000000..74f92c25 --- /dev/null +++ b/extension/src/dxt.h @@ -0,0 +1,23 @@ +// dxt.{h,cpp} — ported verbatim from xrender-poc/engine/dxt.{h,cpp} +// (namespace engine -> mtgodot). DDS DXT1/3/5 + BGRA8 -> RGBA8, level 0. +// engine/dxt —— DDS(DXT1/3/5) → RGBA8 软解。见 docs/steps/M1-static-render.md T3 +// 自己写 ~150 行绕开 reuse/EterImageLib 的 windows.h 依赖(M1 风险表允许)。 +// 对拍时两侧都喂软解 RGBA,不被 GPU S3TC 的 bit 级差异污染。 +#pragma once +#include +#include + +namespace mtgodot { + +struct Image { + uint16_t w = 0, h = 0; + std::vector rgba; // w*h*4,level 0 + const char* format = ""; // "DXT1" / "DXT3" / "DXT5" / "BGRA8" / "" + bool ok() const { return w && h && rgba.size() == size_t(w) * h * 4; } +}; + +// 读整个 .dds 文件字节,解 level 0 到 RGBA8。失败返回 !ok()。 +Image load_dds(const uint8_t* bytes, size_t len); +Image load_dds_path(const char* path); + +} // namespace mtgodot diff --git a/extension/src/gr2_bridge.cpp b/extension/src/gr2_bridge.cpp new file mode 100644 index 00000000..17d48d73 --- /dev/null +++ b/extension/src/gr2_bridge.cpp @@ -0,0 +1,182 @@ +#include "gr2_bridge.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace godot; + +namespace mtgodot { + +Transform3D gr2_to_godot(const gr2::Mat4 &m) { + // gr2 row-major, row-vector: basis columns are (m0,m1,m2),(m4,m5,m6),(m8,m9,m10); + // translation is the 4th row (m12,m13,m14). + Basis b( + Vector3(m[0], m[1], m[2]), + Vector3(m[4], m[5], m[6]), + Vector3(m[8], m[9], m[10])); + return Transform3D(b, Vector3(m[12], m[13], m[14])); +} + +Transform3D make_conv(float unit_scale, bool flip_z) { + // Z-up -> Y-up: rotate -90 deg about X. Then uniform scale. Optional Z flip. + Basis b; + b = b.rotated(Vector3(1, 0, 0), Math::deg_to_rad(-90.0)); + b = b.scaled(Vector3(unit_scale, unit_scale, flip_z ? -unit_scale : unit_scale)); + return Transform3D(b, Vector3()); +} + +Skeleton3D *build_skeleton(const gr2::Skeleton &sk) { + if (sk.bones.empty()) { + return nullptr; + } + Skeleton3D *skel = memnew(Skeleton3D); + skel->set_name("Skeleton3D"); + + const Transform3D ip = gr2_to_godot(sk.initial_placement); + + for (size_t i = 0; i < sk.bones.size(); ++i) { + skel->add_bone(String(sk.bones[i].name.c_str())); + } + for (size_t i = 0; i < sk.bones.size(); ++i) { + const gr2::Bone &bn = sk.bones[i]; + skel->set_bone_parent((int)i, bn.parent); + Transform3D rest = gr2_to_godot(bn.local_transform); + if (bn.parent < 0) { + rest = ip * rest; // fold initial_placement into root bones + } + skel->set_bone_rest((int)i, rest); + skel->reset_bone_pose((int)i); + } + return skel; +} + +Ref build_skin(const gr2::Skeleton &sk) { + Ref skin; + skin.instantiate(); + for (size_t i = 0; i < sk.bones.size(); ++i) { + skin->add_bind((int)i, gr2_to_godot(sk.bones[i].inverse_world)); + skin->set_bind_name((int)i, StringName(sk.bones[i].name.c_str())); + } + return skin; +} + +Ref build_mesh(const gr2::FileInfo &fi, bool flip_winding, AABB &out_bounds) { + Ref am; + am.instantiate(); + am->set_name("ArrayMesh"); + + bool have_bounds = false; + + for (size_t mi = 0; mi < fi.meshes.size(); ++mi) { + const gr2::Mesh &m = fi.meshes[mi]; + if (m.vertices.empty() || m.indices.empty()) { + continue; + } + const int vcount = (int)m.vertices.size(); + + PackedVector3Array pos; + PackedVector3Array nrm; + PackedVector2Array uv; + PackedInt32Array bones; + PackedFloat32Array weights; + pos.resize(vcount); + nrm.resize(vcount); + uv.resize(vcount); + bones.resize(vcount * 4); + weights.resize(vcount * 4); + + Vector3 *pos_w = pos.ptrw(); + Vector3 *nrm_w = nrm.ptrw(); + Vector2 *uv_w = uv.ptrw(); + int32_t *bn_w = bones.ptrw(); + float *wt_w = weights.ptrw(); + + auto slot_to_bone = [&](int slot) -> int { + if (slot < 0 || slot >= (int)m.bone_bindings.size()) { + return 0; + } + int b = m.bone_bindings[slot]; + return (b < 0) ? 0 : b; + }; + + const int rigid_bone = m.rigid ? slot_to_bone(m.bone_bindings.empty() ? -1 : 0) : 0; + + for (int i = 0; i < vcount; ++i) { + const gr2::Vertex &v = m.vertices[i]; + pos_w[i] = Vector3(v.pos[0], v.pos[1], v.pos[2]); + nrm_w[i] = Vector3(v.normal[0], v.normal[1], v.normal[2]); + uv_w[i] = Vector2(v.uv0[0], v.uv0[1]); + + if (!have_bounds) { + out_bounds.position = pos_w[i]; + out_bounds.size = Vector3(); + have_bounds = true; + } else { + out_bounds = out_bounds.expand(pos_w[i]); + } + + if (m.rigid) { + bn_w[i * 4 + 0] = rigid_bone; + bn_w[i * 4 + 1] = bn_w[i * 4 + 2] = bn_w[i * 4 + 3] = 0; + wt_w[i * 4 + 0] = 1.0f; + wt_w[i * 4 + 1] = wt_w[i * 4 + 2] = wt_w[i * 4 + 3] = 0.0f; + continue; + } + + float wsum = 0.0f; + for (int k = 0; k < 4; ++k) { + wsum += v.bone_weight[k]; + } + for (int k = 0; k < 4; ++k) { + bn_w[i * 4 + k] = slot_to_bone(v.bone_index[k]); + wt_w[i * 4 + k] = (wsum > 0.0f) ? (v.bone_weight[k] / (float)wsum) : (k == 0 ? 1.0f : 0.0f); + } + } + + PackedInt32Array idx; + idx.resize((int)m.indices.size()); + int32_t *idx_w = idx.ptrw(); + if (flip_winding) { + for (size_t t = 0; t + 2 < m.indices.size(); t += 3) { + idx_w[t + 0] = (int)m.indices[t + 0]; + idx_w[t + 1] = (int)m.indices[t + 2]; + idx_w[t + 2] = (int)m.indices[t + 1]; + } + } else { + for (size_t t = 0; t < m.indices.size(); ++t) { + idx_w[t] = (int)m.indices[t]; + } + } + + Array arrays; + arrays.resize(Mesh::ARRAY_MAX); + arrays[Mesh::ARRAY_VERTEX] = pos; + arrays[Mesh::ARRAY_NORMAL] = nrm; + arrays[Mesh::ARRAY_TEX_UV] = uv; + arrays[Mesh::ARRAY_BONES] = bones; + arrays[Mesh::ARRAY_WEIGHTS] = weights; + arrays[Mesh::ARRAY_INDEX] = idx; + + am->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays); + am->surface_set_name(am->get_surface_count() - 1, + m.name.empty() ? String("surf_") + itos((int)mi) : String(m.name.c_str())); + } + + if (!have_bounds) { + out_bounds = AABB(); + } + return am; +} + +} // namespace mtgodot diff --git a/extension/src/gr2_bridge.h b/extension/src/gr2_bridge.h new file mode 100644 index 00000000..b4c02992 --- /dev/null +++ b/extension/src/gr2_bridge.h @@ -0,0 +1,46 @@ +#pragma once +// gr2_bridge — libgr2 POD views -> Godot scene objects. +// +// Coordinate handling (the one place it lives; see docs/GODOT-POC-PLAN.md §02): +// - gr2 Mat4 is row-major, row-vector math, translation in elements 12..14. +// - A plain 4x4 transpose maps it to a Godot Transform3D (column-vector, +// translation in .origin). gr2_to_godot() does exactly that. +// - Z-up(cm) -> Y-up(m) and optional handedness flip are NOT applied here; +// they go on the Metin2Model node transform (make_conv()). + +#include +#include +#include + +#include + +namespace godot { +class Skeleton3D; +class Skin; +class ArrayMesh; +} // namespace godot + +namespace mtgodot { + +// 4x4 transpose: gr2 row-major/row-vector -> Godot Transform3D. +godot::Transform3D gr2_to_godot(const gr2::Mat4 &m); + +// Z-up cm -> Y-up m (+ optional Z flip for LH->RH content). +godot::Transform3D make_conv(float unit_scale, bool flip_z); + +// Build a Skeleton3D (rest pose from bone local transforms; initial_placement +// folded into root bones). Returns nullptr if the skeleton has no bones. +godot::Skeleton3D *build_skeleton(const gr2::Skeleton &sk); + +// Skin whose bind list is parallel to the skeleton bones: +// bind i -> bone i, pose = gr2_to_godot(bone[i].inverse_world) +godot::Ref build_skin(const gr2::Skeleton &sk); + +// One ArrayMesh with a surface per gr2 Mesh that has geometry. +// ARRAY_BONES values are skeleton bone indices (mesh slot -> bone via +// mesh.bone_bindings). Rigid meshes are bound rigidly to bone_bindings[0]. +// Fills out_bounds with the untransformed vertex AABB. +godot::Ref build_mesh(const gr2::FileInfo &fi, bool flip_winding, + godot::AABB &out_bounds); + +} // namespace mtgodot diff --git a/extension/src/metin2_anim.cpp b/extension/src/metin2_anim.cpp new file mode 100644 index 00000000..cc72602d --- /dev/null +++ b/extension/src/metin2_anim.cpp @@ -0,0 +1,176 @@ +#include "metin2_anim.h" + +#include "gr2_bridge.h" +#include "metin2_model.h" + +#include +#include +#include + +#include +#include + +using namespace godot; + +namespace mtgodot { + +Metin2AnimPlayer::Metin2AnimPlayer() {} +Metin2AnimPlayer::~Metin2AnimPlayer() = default; + +void Metin2AnimPlayer::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_anim_path", "path"), &Metin2AnimPlayer::set_anim_path); + ClassDB::bind_method(D_METHOD("get_anim_path"), &Metin2AnimPlayer::get_anim_path); + ClassDB::bind_method(D_METHOD("set_model_path", "path"), &Metin2AnimPlayer::set_model_path); + ClassDB::bind_method(D_METHOD("get_model_path"), &Metin2AnimPlayer::get_model_path); + ClassDB::bind_method(D_METHOD("set_playing", "v"), &Metin2AnimPlayer::set_playing); + ClassDB::bind_method(D_METHOD("get_playing"), &Metin2AnimPlayer::get_playing); + ClassDB::bind_method(D_METHOD("set_time_scale", "s"), &Metin2AnimPlayer::set_time_scale); + ClassDB::bind_method(D_METHOD("get_time_scale"), &Metin2AnimPlayer::get_time_scale); + ClassDB::bind_method(D_METHOD("set_time", "t"), &Metin2AnimPlayer::set_time); + ClassDB::bind_method(D_METHOD("get_time"), &Metin2AnimPlayer::get_time); + ClassDB::bind_method(D_METHOD("get_duration"), &Metin2AnimPlayer::get_duration); + ClassDB::bind_method(D_METHOD("reload"), &Metin2AnimPlayer::reload); + ClassDB::bind_method(D_METHOD("get_info"), &Metin2AnimPlayer::get_info); + ClassDB::bind_method(D_METHOD("selfcheck", "samples"), &Metin2AnimPlayer::selfcheck, DEFVAL(24)); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "anim_path", PROPERTY_HINT_GLOBAL_FILE, "*.gr2"), + "set_anim_path", "get_anim_path"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "model_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), + "set_model_path", "get_model_path"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing"), "set_playing", "get_playing"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "time_scale", PROPERTY_HINT_RANGE, "0,4,0.01"), + "set_time_scale", "get_time_scale"); +} + +void Metin2AnimPlayer::set_anim_path(const String &p) { + anim_path = p; + if (is_inside_tree()) { + reload(); + } +} +void Metin2AnimPlayer::set_model_path(const NodePath &p) { + model_path = p; +} +void Metin2AnimPlayer::set_time(double t) { + time = t; + if (is_inside_tree()) { + apply_pose(time); + } +} + +Metin2Model *Metin2AnimPlayer::resolve_model() const { + if (model_path.is_empty()) { + return nullptr; + } + Node *n = get_node_or_null(model_path); + return Object::cast_to(n); +} + +void Metin2AnimPlayer::_ready() { + set_process(true); + reload(); +} + +void Metin2AnimPlayer::reload() { + anim_file = std::nullopt; + duration = 0.0; + time = 0.0; + last_info = ""; + if (anim_path.is_empty()) { + return; + } + gr2::LoadError err; + const std::string p(anim_path.utf8().get_data()); + auto f = gr2::File::load_path(p, &err); + if (!f) { + UtilityFunctions::push_error(String("[Metin2AnimPlayer] anim load failed [") + + String(err.stage.c_str()) + "]: " + String(err.message.c_str())); + return; + } + const gr2::FileInfo &fi = f->file_info(); + if (fi.animations.empty()) { + UtilityFunctions::push_error("[Metin2AnimPlayer] no animations in " + anim_path); + return; + } + anim_file = std::move(f); + const gr2::Animation &an = anim_file->file_info().animations[0]; + duration = an.duration; + last_info = vformat("anim '%s' dur=%.3f tracks=%d anims=%d", + String(an.name.c_str()), duration, (int)an.tracks.size(), + (int)anim_file->file_info().animations.size()); + UtilityFunctions::print("[Metin2AnimPlayer] ", last_info); + apply_pose(0.0); +} + +void Metin2AnimPlayer::apply_pose(double t) { + if (!anim_file) { + return; + } + Metin2Model *model = resolve_model(); + if (!model) { + return; + } + const gr2::Skeleton *sk = model->gr2_skeleton(); + Skeleton3D *skel = model->skeleton_node(); + if (!sk || !skel) { + return; + } + const gr2::Animation &an = anim_file->file_info().animations[0]; + + double tt = t; + if (duration > 0.0) { + tt = std::fmod(t, duration); + if (tt < 0.0) { + tt += duration; + } + } + + gr2::sample_pose(*sk, an, (float)tt, world_buf, skin_buf); + + const int n = (int)std::min(world_buf.size(), (size_t)skel->get_bone_count()); + for (int i = 0; i < n; ++i) { + skel->set_bone_global_pose(i, gr2_to_godot(world_buf[i])); + } +} + +void Metin2AnimPlayer::_process(double delta) { + if (!playing || !anim_file) { + return; + } + time += delta * time_scale; + apply_pose(time); +} + +String Metin2AnimPlayer::selfcheck(int samples) { + if (!anim_file) { + return "no anim loaded"; + } + Metin2Model *model = resolve_model(); + const gr2::Skeleton *sk = model ? model->gr2_skeleton() : nullptr; + if (!sk) { + return "no model/skeleton"; + } + const auto &anims = anim_file->file_info().animations; + int total = 0; + int nan_hits = 0; + std::vector w, s; + for (const auto &an : anims) { + double dur = an.duration > 0.0 ? an.duration : 1.0; + for (int k = 0; k <= samples; ++k) { + float tt = (float)(dur * k / samples); + gr2::sample_pose(*sk, an, tt, w, s); + for (const auto &mtx : w) { + for (float v : mtx) { + ++total; + if (std::isnan(v) || std::isinf(v)) { + ++nan_hits; + } + } + } + } + } + return vformat("selfcheck: anims=%d samples/anim=%d floats=%d NaN/Inf=%d", + (int)anims.size(), samples + 1, total, nan_hits); +} + +} // namespace mtgodot diff --git a/extension/src/metin2_anim.h b/extension/src/metin2_anim.h new file mode 100644 index 00000000..4321905f --- /dev/null +++ b/extension/src/metin2_anim.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include + +#include + +#include +#include + +namespace mtgodot { + +class Metin2Model; + +// Drives a Metin2Model's Skeleton3D each frame by sampling a (possibly +// separate) animation .gr2 with libgr2, then writing per-bone global poses. +// +// world[i] = gr2::sample_pose(model.skeleton, anim, t) +// skel.set_bone_global_pose(i, gr2_to_godot(world[i])) +// +// Godot's renderer then computes skin matrix = global_pose(i) * bind_pose(i) +// which equals gr2's deformer matrix (see gr2_bridge.h). +class Metin2AnimPlayer : public godot::Node3D { + GDCLASS(Metin2AnimPlayer, godot::Node3D) + +public: + Metin2AnimPlayer(); + ~Metin2AnimPlayer() override; + + void _ready() override; + void _process(double delta) override; + + void set_anim_path(const godot::String &p); + godot::String get_anim_path() const { return anim_path; } + + void set_model_path(const godot::NodePath &p); + godot::NodePath get_model_path() const { return model_path; } + + void set_playing(bool v) { playing = v; } + bool get_playing() const { return playing; } + + void set_time_scale(double s) { time_scale = s; } + double get_time_scale() const { return time_scale; } + + void set_time(double t); + double get_time() const { return time; } + + double get_duration() const { return duration; } + void reload(); + godot::String get_info() const { return last_info; } + + // NaN-scan every animation in anim_path across [0,dur]; returns a report string. + godot::String selfcheck(int samples = 24); + +protected: + static void _bind_methods(); + +private: + godot::String anim_path; + godot::NodePath model_path; + bool playing = true; + double time_scale = 1.0; + double time = 0.0; + double duration = 0.0; + godot::String last_info; + + std::optional anim_file; + std::vector world_buf; + std::vector skin_buf; + + Metin2Model *resolve_model() const; + void apply_pose(double t); +}; + +} // namespace mtgodot diff --git a/extension/src/metin2_model.cpp b/extension/src/metin2_model.cpp index 55dc5f8d..632806df 100644 --- a/extension/src/metin2_model.cpp +++ b/extension/src/metin2_model.cpp @@ -1,39 +1,253 @@ #include "metin2_model.h" +#include "dxt.h" +#include "gr2_bridge.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include #include -#include // libgr2 (xrender-poc) - using namespace godot; namespace mtgodot { -Metin2Model::Metin2Model() = default; +Metin2Model::Metin2Model() { + file = std::make_shared>(); +} Metin2Model::~Metin2Model() = default; void Metin2Model::_bind_methods() { - ClassDB::bind_method(D_METHOD("libgr2_info"), &Metin2Model::libgr2_info); + ClassDB::bind_method(D_METHOD("set_gr2_path", "path"), &Metin2Model::set_gr2_path); + ClassDB::bind_method(D_METHOD("get_gr2_path"), &Metin2Model::get_gr2_path); + ClassDB::bind_method(D_METHOD("set_texture_dir", "path"), &Metin2Model::set_texture_dir); + ClassDB::bind_method(D_METHOD("get_texture_dir"), &Metin2Model::get_texture_dir); + ClassDB::bind_method(D_METHOD("set_unit_scale", "s"), &Metin2Model::set_unit_scale); + ClassDB::bind_method(D_METHOD("get_unit_scale"), &Metin2Model::get_unit_scale); + ClassDB::bind_method(D_METHOD("set_flip_z", "v"), &Metin2Model::set_flip_z); + ClassDB::bind_method(D_METHOD("get_flip_z"), &Metin2Model::get_flip_z); + ClassDB::bind_method(D_METHOD("set_flip_winding", "v"), &Metin2Model::set_flip_winding); + ClassDB::bind_method(D_METHOD("get_flip_winding"), &Metin2Model::get_flip_winding); + ClassDB::bind_method(D_METHOD("reload"), &Metin2Model::reload); + ClassDB::bind_method(D_METHOD("get_info"), &Metin2Model::get_info); ClassDB::bind_method(D_METHOD("probe_gr2", "path"), &Metin2Model::probe_gr2); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "gr2_path", PROPERTY_HINT_GLOBAL_FILE, "*.gr2"), + "set_gr2_path", "get_gr2_path"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "texture_dir", PROPERTY_HINT_GLOBAL_DIR), + "set_texture_dir", "get_texture_dir"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "unit_scale", PROPERTY_HINT_RANGE, "0.0001,1,0.0001"), + "set_unit_scale", "get_unit_scale"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_z"), "set_flip_z", "get_flip_z"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_winding"), "set_flip_winding", "get_flip_winding"); } -String Metin2Model::libgr2_info() const { - // Forces a reference to a libgr2 symbol so the link is real, not dead-stripped. - return String("libgr2 linked; gr2::Magic enum size=") + itos((int)sizeof(gr2::Magic)); +void Metin2Model::set_gr2_path(const String &p) { + gr2_path = p; + if (is_inside_tree()) { + reload(); + } +} +void Metin2Model::set_texture_dir(const String &p) { + texture_dir = p; + if (is_inside_tree()) { + reload(); + } +} +void Metin2Model::set_unit_scale(double s) { + unit_scale = s; + if (is_inside_tree()) { + reload(); + } +} +void Metin2Model::set_flip_z(bool v) { + flip_z = v; + if (is_inside_tree()) { + reload(); + } +} +void Metin2Model::set_flip_winding(bool v) { + flip_winding = v; + if (is_inside_tree()) { + reload(); + } +} + +void Metin2Model::_ready() { + if (!gr2_path.is_empty()) { + reload(); + } +} + +void Metin2Model::_clear_children() { + skel = nullptr; + mi = nullptr; + TypedArray kids = get_children(); + for (int i = 0; i < kids.size(); ++i) { + Node *n = Object::cast_to(kids[i]); + if (n) { + remove_child(n); + n->queue_free(); + } + } +} + +void Metin2Model::reload() { + _clear_children(); + *file = std::nullopt; + last_info = ""; + + if (gr2_path.is_empty()) { + return; + } + + gr2::LoadError err; + const std::string p(gr2_path.utf8().get_data()); + auto loaded = gr2::File::load_path(p, &err); + if (!loaded) { + UtilityFunctions::push_error(String("[Metin2Model] gr2 load failed [") + + String(err.stage.c_str()) + "]: " + String(err.message.c_str()) + " (" + gr2_path + ")"); + return; + } + *file = std::move(loaded); + const gr2::FileInfo &fi = (*file)->file_info(); + + set_transform(make_conv((float)unit_scale, flip_z)); + + // Skeleton (optional). + const gr2::Skeleton *sk = fi.skeletons.empty() ? nullptr : &fi.skeletons[0]; + if (sk) { + skel = build_skeleton(*sk); + if (skel) { + add_child(skel); + skel->set_owner(get_owner() ? get_owner() : this); + } + } + + // Mesh. + AABB bounds; + Ref mesh = build_mesh(fi, flip_winding, bounds); + if (mesh->get_surface_count() > 0) { + mi = memnew(MeshInstance3D); + mi->set_name("MeshInstance3D"); + mi->set_mesh(mesh); + add_child(mi); + mi->set_owner(get_owner() ? get_owner() : this); + if (skel && sk) { + mi->set_skeleton_path(mi->get_path_to(skel)); + mi->set_skin(build_skin(*sk)); + } + mi->set_custom_aabb(bounds.grow(0.01)); + } + + _apply_materials(); + + int vtot = 0; + for (const auto &m : fi.meshes) { + vtot += (int)m.vertices.size(); + } + last_info = vformat("gr2 v%d | bones=%d meshes=%d surfaces=%d verts=%d | bounds=%s", + (int)(*file)->format_version(), + sk ? (int)sk->bones.size() : 0, + (int)fi.meshes.size(), + mesh->get_surface_count(), + vtot, + String(bounds)); + UtilityFunctions::print("[Metin2Model] ", last_info); +} + +String Metin2Model::_guess_texture_dir() const { + if (!texture_dir.is_empty()) { + return texture_dir; + } + return gr2_path.get_base_dir(); +} + +void Metin2Model::_apply_materials() { + if (!mi || !file || !*file) { + return; + } + const gr2::FileInfo &fi = (*file)->file_info(); + const String dir = _guess_texture_dir(); + const String stem = gr2_path.get_file().get_basename(); + + // Cache: texture path -> ImageTexture. + auto load_tex = [&](const String &path) -> Ref { + Ref t; + if (!FileAccess::file_exists(path)) { + return t; + } + mtgodot::Image dds = mtgodot::load_dds_path(path.utf8().get_data()); + if (!dds.ok()) { + return t; + } + PackedByteArray bytes; + bytes.resize((int)dds.rgba.size()); + memcpy(bytes.ptrw(), dds.rgba.data(), dds.rgba.size()); + Ref img = godot::Image::create_from_data( + dds.w, dds.h, false, godot::Image::FORMAT_RGBA8, bytes); + if (img.is_null()) { + return t; + } + img->generate_mipmaps(); + t = ImageTexture::create_from_image(img); + return t; + }; + + Ref tex_main = load_tex(dir.path_join(stem + String(".dds"))); + Ref tex_face = load_tex(dir.path_join(stem + String("_face.dds"))); + if (tex_face.is_null()) { + tex_face = load_tex(dir.path_join(String("warrior_face.dds"))); + } + + Ref mesh = mi->get_mesh(); + for (int s = 0; s < mesh->get_surface_count(); ++s) { + String sname = mesh->surface_get_name(s).to_lower(); + Ref tex = (sname.find("face") != -1 && tex_face.is_valid()) ? tex_face : tex_main; + + Ref mat; + mat.instantiate(); + mat->set_cull_mode(BaseMaterial3D::CULL_DISABLED); // M1: winding unverified + if (tex.is_valid()) { + mat->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, tex); + } else { + mat->set_albedo(Color(0.8, 0.8, 0.82)); + } + mi->set_surface_override_material(s, mat); + } +} + +const gr2::Skeleton *Metin2Model::gr2_skeleton() const { + if (!file || !*file) { + return nullptr; + } + const gr2::FileInfo &fi = (*file)->file_info(); + return fi.skeletons.empty() ? nullptr : &fi.skeletons[0]; +} + +String Metin2Model::get_info() const { + return last_info; } String Metin2Model::probe_gr2(const String &path) const { gr2::LoadError err; const std::string p(path.utf8().get_data()); - auto file = gr2::File::load_path(p, &err); - if (!file) { - return vformat("gr2 load FAILED [%s]: %s", - String(err.stage.c_str()), String(err.message.c_str())); + auto f = gr2::File::load_path(p, &err); + if (!f) { + return vformat("gr2 load FAILED [%s]: %s", String(err.stage.c_str()), String(err.message.c_str())); } return vformat("gr2 OK: format_version=%d sections=%d", - (int)file->format_version(), (int)file->sections().size()); + (int)f->format_version(), (int)f->sections().size()); } } // namespace mtgodot diff --git a/extension/src/metin2_model.h b/extension/src/metin2_model.h index adcb2084..14fee5f0 100644 --- a/extension/src/metin2_model.h +++ b/extension/src/metin2_model.h @@ -3,14 +3,24 @@ #include #include +#include + +#include +#include + +namespace godot { +class Skeleton3D; +class MeshInstance3D; +} // namespace godot + namespace mtgodot { -// M0' placeholder node. +// Loads a Metin2 .gr2 and builds a Godot subtree: +// Metin2Model (transform = Z-up/cm -> Y-up/m conversion) +// └─ Skeleton3D (rest pose from gr2 bones) +// └─ MeshInstance3D (ArrayMesh, one surface per gr2 mesh; skinned) // -// Purpose at this milestone: prove the GDExtension registers a class the Godot -// editor can instantiate, and that libgr2 is linked in and callable from here. -// M1 replaces the body with real Skeleton3D / ArrayMesh construction -// (see docs/GODOT-POC-PLAN.md §M1). +// M1: static bind-pose render. M2's Metin2AnimPlayer drives the Skeleton3D. class Metin2Model : public godot::Node3D { GDCLASS(Metin2Model, godot::Node3D) @@ -18,14 +28,55 @@ public: Metin2Model(); ~Metin2Model() override; - // One-line tag proving libgr2 symbols resolved at link time. - godot::String libgr2_info() const; + void _ready() override; - // Parse a .gr2 with libgr2 and report a couple of counts (no scene build yet). + // --- inspector properties --- + void set_gr2_path(const godot::String &p); + godot::String get_gr2_path() const { return gr2_path; } + + void set_texture_dir(const godot::String &p); + godot::String get_texture_dir() const { return texture_dir; } + + void set_unit_scale(double s); + double get_unit_scale() const { return unit_scale; } + + void set_flip_z(bool v); + bool get_flip_z() const { return flip_z; } + + void set_flip_winding(bool v); + bool get_flip_winding() const { return flip_winding; } + + // Rebuild the subtree from the current properties. + void reload(); + + // One-line summary (bones / meshes / verts / bounds). + godot::String get_info() const; + + // --- C++ accessors for Metin2AnimPlayer (same extension) --- + const gr2::Skeleton *gr2_skeleton() const; + godot::Skeleton3D *skeleton_node() const { return skel; } + + // libgr2 sanity probe (kept from M0'). godot::String probe_gr2(const godot::String &path) const; protected: static void _bind_methods(); + +private: + godot::String gr2_path; + godot::String texture_dir; + double unit_scale = 0.01; + bool flip_z = false; + bool flip_winding = false; + + std::shared_ptr> file; // shared_ptr so accessor stays valid + godot::Skeleton3D *skel = nullptr; + godot::MeshInstance3D *mi = nullptr; + godot::String last_info; + + void _clear_children(); + void _apply_materials(); + godot::String _guess_texture_dir() const; }; } // namespace mtgodot diff --git a/extension/src/register_types.cpp b/extension/src/register_types.cpp index bec16a51..88fb9b1b 100644 --- a/extension/src/register_types.cpp +++ b/extension/src/register_types.cpp @@ -4,6 +4,7 @@ #include #include +#include "metin2_anim.h" #include "metin2_model.h" using namespace godot; @@ -13,6 +14,7 @@ void initialize_mtgodot_module(ModuleInitializationLevel p_level) { return; } GDREGISTER_CLASS(mtgodot::Metin2Model); + GDREGISTER_CLASS(mtgodot::Metin2AnimPlayer); } void uninitialize_mtgodot_module(ModuleInitializationLevel p_level) { diff --git a/project/main.gd b/project/main.gd index 89d4576a..ed31ef2e 100644 --- a/project/main.gd +++ b/project/main.gd @@ -1,35 +1,45 @@ extends Node3D -## mtgodot-poc — M0' harness. +## mtgodot-poc harness — M1 (static render) + M2 (skeletal animation). ## -## Builds the minimal scene the plan asks for (orbit camera + DirectionalLight3D -## with shadows + WorldEnvironment/sky + placeholder cube + an empty Metin2Model), -## then reports whether the GDExtension loaded. +## Loads a Metin2 .gr2 via the Metin2Model GDExtension node, renders it with +## Godot's Forward+ renderer, and (if an anim .gr2 is given) drives it with +## Metin2AnimPlayer. ## -## M1 will replace the cube with Metin2Model.load_gr2(...) output. +## Asset paths default to the sibling m2dev-client-main checkout; override with +## MTGODOT_MODEL / MTGODOT_ANIM / MTGODOT_TEXDIR env vars. +## +## Keys: drag = orbit wheel = zoom Space = play/pause B = bind pose +## [ / ] = scrub R = reload F2 = screenshot const ORBIT_SPEED := 0.01 -const ZOOM_STEP := 0.3 +const ZOOM_STEP := 0.15 const PITCH_LIMIT := 1.45 +var _assets_root := "" var _cam: Camera3D -var _yaw := 0.6 -var _pitch := 0.45 -var _dist := 4.0 +var _model: Node3D # Metin2Model +var _anim: Node3D # Metin2AnimPlayer +var _yaw := 0.7 +var _pitch := 0.25 +var _dist := 3.0 var _target := Vector3(0.0, 1.0, 0.0) var _dragging := false func _ready() -> void: + _assets_root = ProjectSettings.globalize_path("res://../../m2dev-client-main/assets") _build_environment() _build_sun() _build_camera() - _build_placeholder() - _probe_extension() - print("[mtgodot] M0' harness ready — drag: orbit, wheel: zoom, F2: screenshot") - # Batch capture: MTGODOT_AUTOSHOT=/abs/path.png godot --path project + _load_content() + _frame_model() + print("[mtgodot] harness ready") + var autoshot := OS.get_environment("MTGODOT_AUTOSHOT") if autoshot != "": - await get_tree().create_timer(0.8).timeout + # let a few frames render (and the anim settle) before capturing + for i in 8: + await get_tree().process_frame var img := get_viewport().get_texture().get_image() img.save_png(autoshot) print("[mtgodot] autoshot -> ", autoshot) @@ -44,6 +54,7 @@ func _build_environment() -> void: env.background_mode = Environment.BG_SKY env.sky = sky env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY + env.ambient_light_energy = 0.6 env.tonemap_mode = Environment.TONE_MAPPER_FILMIC we.environment = env add_child(we) @@ -51,39 +62,73 @@ func _build_environment() -> void: func _build_sun() -> void: var sun := DirectionalLight3D.new() - sun.rotation_degrees = Vector3(-45.0, -35.0, 0.0) + sun.rotation_degrees = Vector3(-50.0, -40.0, 0.0) sun.shadow_enabled = true + sun.light_energy = 1.1 add_child(sun) func _build_camera() -> void: _cam = Camera3D.new() _cam.current = true - _cam.fov = 55.0 + _cam.fov = 45.0 + _cam.near = 0.01 add_child(_cam) _update_camera() -func _build_placeholder() -> void: - var cube := MeshInstance3D.new() - cube.name = "Placeholder" - cube.mesh = BoxMesh.new() - cube.position = _target - add_child(cube) +func _env(name: String, fallback: String) -> String: + var v := OS.get_environment(name) + return v if v != "" else fallback -func _probe_extension() -> void: +func _load_content() -> void: if not ClassDB.class_exists("Metin2Model"): - push_error("[mtgodot] Metin2Model NOT registered — GDExtension failed to load") + push_error("[mtgodot] Metin2Model not registered — GDExtension failed to load") return - var m := ClassDB.instantiate("Metin2Model") as Node3D - m.name = "Metin2Model" - add_child(m) - print("[mtgodot] Metin2Model registered OK — ", m.call("libgr2_info")) - # Optional: point MTGODOT_PROBE_GR2 at a real .gr2 to exercise libgr2 end to end. - var probe := OS.get_environment("MTGODOT_PROBE_GR2") - if probe != "": - print("[mtgodot] probe_gr2(", probe, ") -> ", m.call("probe_gr2", probe)) + + var model_path := _env("MTGODOT_MODEL", + _assets_root.path_join("PC/ymir work/pc/warrior/warrior_cheongrin.gr2")) + var tex_dir := _env("MTGODOT_TEXDIR", + _assets_root.path_join("PC/ymir work/pc/warrior")) + var anim_path := _env("MTGODOT_ANIM", + _assets_root.path_join("PC/ymir work/pc/warrior/action/dance_1.gr2")) + + _model = ClassDB.instantiate("Metin2Model") + _model.name = "Metin2Model" + _model.set("texture_dir", tex_dir) + _model.set("gr2_path", model_path) + add_child(_model) + if _model.has_method("get_info"): + print("[mtgodot] model: ", _model.call("get_info")) + + if anim_path != "-" and FileAccess.file_exists(anim_path) and ClassDB.class_exists("Metin2AnimPlayer"): + _anim = ClassDB.instantiate("Metin2AnimPlayer") + _anim.name = "Metin2AnimPlayer" + _anim.set("model_path", _anim.get_path_to(_model) if false else NodePath("../Metin2Model")) + _anim.set("anim_path", anim_path) + add_child(_anim) + if _anim.has_method("get_info"): + print("[mtgodot] anim: ", _anim.call("get_info")) + if _anim.has_method("selfcheck"): + print("[mtgodot] ", _anim.call("selfcheck", 24)) + _anim.call("reload") + var fixed_t := OS.get_environment("MTGODOT_ANIM_T") + if fixed_t != "": + _anim.set("playing", false) + _anim.call("set_time", float(fixed_t)) + print("[mtgodot] fixed t=", _anim.call("get_time"), " dur=", _anim.call("get_duration")) + + +func _frame_model() -> void: + # Use the model's converted world-space AABB to place the orbit target/dist. + var mi := _model.get_node_or_null("MeshInstance3D") if _model else null + if mi and mi is VisualInstance3D: + var aabb: AABB = (mi as VisualInstance3D).get_aabb() + aabb = _model.transform * aabb + _target = aabb.get_center() + _dist = maxf(1.0, aabb.get_longest_axis_size() * 1.8) + _update_camera() func _update_camera() -> void: @@ -102,20 +147,43 @@ func _unhandled_input(event: InputEvent) -> void: MOUSE_BUTTON_LEFT: _dragging = mb.pressed MOUSE_BUTTON_WHEEL_UP: - _dist = maxf(0.5, _dist - ZOOM_STEP) + _dist = maxf(0.2, _dist * (1.0 - ZOOM_STEP)) _update_camera() MOUSE_BUTTON_WHEEL_DOWN: - _dist = minf(50.0, _dist + ZOOM_STEP) + _dist = minf(200.0, _dist * (1.0 + ZOOM_STEP)) _update_camera() elif event is InputEventMouseMotion and _dragging: var mm := event as InputEventMouseMotion _yaw -= mm.relative.x * ORBIT_SPEED _pitch = clampf(_pitch + mm.relative.y * ORBIT_SPEED, -PITCH_LIMIT, PITCH_LIMIT) _update_camera() - elif event is InputEventKey: - var k := event as InputEventKey - if k.pressed and k.keycode == KEY_F2: + elif event is InputEventKey and event.pressed and not event.echo: + _on_key(event.keycode) + + +func _on_key(kc: int) -> void: + match kc: + KEY_F2: _screenshot() + KEY_SPACE: + if _anim: + _anim.set("playing", not _anim.get("playing")) + KEY_B: + if _anim: + _anim.set("playing", false) + _anim.call("set_time", 0.0) + KEY_R: + if _model: + _model.call("reload") + if _anim: + _anim.call("reload") + _frame_model() + KEY_BRACKETLEFT: + if _anim: + _anim.call("set_time", _anim.call("get_time") - 0.05) + KEY_BRACKETRIGHT: + if _anim: + _anim.call("set_time", _anim.call("get_time") + 0.05) func _screenshot() -> void: diff --git a/test/golden/m1-bindpose.png b/test/golden/m1-bindpose.png new file mode 100644 index 00000000..830e2cd1 Binary files /dev/null and b/test/golden/m1-bindpose.png differ diff --git a/test/golden/m1-warrior.png b/test/golden/m1-warrior.png new file mode 100644 index 00000000..4baa9d26 Binary files /dev/null and b/test/golden/m1-warrior.png differ diff --git a/test/golden/m2-dance-t0.0.png b/test/golden/m2-dance-t0.0.png new file mode 100644 index 00000000..e391738a Binary files /dev/null and b/test/golden/m2-dance-t0.0.png differ diff --git a/test/golden/m2-dance-t12.0.png b/test/golden/m2-dance-t12.0.png new file mode 100644 index 00000000..e391738a Binary files /dev/null and b/test/golden/m2-dance-t12.0.png differ diff --git a/test/golden/m2-dance-t3.5.png b/test/golden/m2-dance-t3.5.png new file mode 100644 index 00000000..e391738a Binary files /dev/null and b/test/golden/m2-dance-t3.5.png differ diff --git a/test/golden/m2-dance-t7.0.png b/test/golden/m2-dance-t7.0.png new file mode 100644 index 00000000..e391738a Binary files /dev/null and b/test/golden/m2-dance-t7.0.png differ diff --git a/test/golden/m2b-t16.0.png b/test/golden/m2b-t16.0.png new file mode 100644 index 00000000..e391738a Binary files /dev/null and b/test/golden/m2b-t16.0.png differ diff --git a/test/golden/m2b-t2.0.png b/test/golden/m2b-t2.0.png new file mode 100644 index 00000000..e391738a Binary files /dev/null and b/test/golden/m2b-t2.0.png differ diff --git a/test/golden/m2b-t9.0.png b/test/golden/m2b-t9.0.png new file mode 100644 index 00000000..e391738a Binary files /dev/null and b/test/golden/m2b-t9.0.png differ diff --git a/test/golden/m2c-t16.0.png b/test/golden/m2c-t16.0.png new file mode 100644 index 00000000..765307f7 Binary files /dev/null and b/test/golden/m2c-t16.0.png differ diff --git a/test/golden/m2c-t2.0.png b/test/golden/m2c-t2.0.png new file mode 100644 index 00000000..2311d5ef Binary files /dev/null and b/test/golden/m2c-t2.0.png differ diff --git a/test/golden/m2c-t23.0.png b/test/golden/m2c-t23.0.png new file mode 100644 index 00000000..ed4bd4ad Binary files /dev/null and b/test/golden/m2c-t23.0.png differ diff --git a/test/golden/m2c-t9.0.png b/test/golden/m2c-t9.0.png new file mode 100644 index 00000000..584522a6 Binary files /dev/null and b/test/golden/m2c-t9.0.png differ