M2.5: wire libgr2 dump_materials (opt-in), keep filename heuristic default
- Metin2Model stores gr2::dump_materials() and can resolve surface textures via tri_groups[0].material_index -> material.diffuse_texture basename, gated behind use_gr2_materials (default off): the mapping is unverified and currently regresses warrior (body surfaces pick the face texture), so the filename-convention + dir-scan path stays the default. - Property + setter for use_gr2_materials so it can be A/B'd once a Granny oracle exists. Logged: "gr2 materials: N (M with diffuse name)". warrior/assassin/shaman render unchanged from the heuristic path. docs/GODOT-POC-PLAN.md §M2.5 T2.5.6 gap list already covers the open item. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaHYEY9rwLWt21PULiYjeJ
@@ -43,6 +43,8 @@ void Metin2Model::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_flip_winding"), &Metin2Model::get_flip_winding);
|
||||
ClassDB::bind_method(D_METHOD("set_material_mode", "m"), &Metin2Model::set_material_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_material_mode"), &Metin2Model::get_material_mode);
|
||||
ClassDB::bind_method(D_METHOD("set_use_gr2_materials", "v"), &Metin2Model::set_use_gr2_materials);
|
||||
ClassDB::bind_method(D_METHOD("get_use_gr2_materials"), &Metin2Model::get_use_gr2_materials);
|
||||
ClassDB::bind_method(D_METHOD("set_surface_texture", "surface", "path"), &Metin2Model::set_surface_texture);
|
||||
ClassDB::bind_method(D_METHOD("reload"), &Metin2Model::reload);
|
||||
ClassDB::bind_method(D_METHOD("get_info"), &Metin2Model::get_info);
|
||||
@@ -58,6 +60,8 @@ void Metin2Model::_bind_methods() {
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_winding"), "set_flip_winding", "get_flip_winding");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "material_mode", PROPERTY_HINT_ENUM, "metin2,standard"),
|
||||
"set_material_mode", "get_material_mode");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_gr2_materials"),
|
||||
"set_use_gr2_materials", "get_use_gr2_materials");
|
||||
}
|
||||
|
||||
void Metin2Model::set_material_mode(const String &m) {
|
||||
@@ -67,6 +71,13 @@ void Metin2Model::set_material_mode(const String &m) {
|
||||
}
|
||||
}
|
||||
|
||||
void Metin2Model::set_use_gr2_materials(bool v) {
|
||||
use_gr2_materials = v;
|
||||
if (is_inside_tree()) {
|
||||
_apply_materials();
|
||||
}
|
||||
}
|
||||
|
||||
void Metin2Model::set_surface_texture(int surface, const String &path) {
|
||||
if (surface < 0) {
|
||||
return;
|
||||
@@ -178,12 +189,24 @@ void Metin2Model::reload() {
|
||||
mi->set_custom_aabb(bounds.grow(0.01));
|
||||
}
|
||||
|
||||
materials = gr2::dump_materials(**file);
|
||||
|
||||
_apply_materials();
|
||||
|
||||
int vtot = 0;
|
||||
for (const auto &m : fi.meshes) {
|
||||
vtot += (int)m.vertices.size();
|
||||
}
|
||||
{
|
||||
int with_tex = 0;
|
||||
for (const auto &mm : materials) {
|
||||
if (!mm.diffuse_texture.empty()) {
|
||||
++with_tex;
|
||||
}
|
||||
}
|
||||
UtilityFunctions::print(vformat("[Metin2Model] gr2 materials: %d (%d with diffuse name)",
|
||||
(int)materials.size(), with_tex));
|
||||
}
|
||||
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,
|
||||
@@ -327,14 +350,56 @@ void Metin2Model::_apply_materials() {
|
||||
}
|
||||
}
|
||||
|
||||
// Surface s corresponds to the s-th gr2 mesh that has geometry (same skip
|
||||
// rule as build_mesh). Recover that mapping to reach tri_groups/material_index.
|
||||
const gr2::FileInfo &fi = (*file)->file_info();
|
||||
std::vector<int> surf_to_mesh;
|
||||
for (int mi_ = 0; mi_ < (int)fi.meshes.size(); ++mi_) {
|
||||
if (!fi.meshes[mi_].vertices.empty() && !fi.meshes[mi_].indices.empty()) {
|
||||
surf_to_mesh.push_back(mi_);
|
||||
}
|
||||
}
|
||||
|
||||
Ref<ArrayMesh> mesh = mi->get_mesh();
|
||||
int textured = 0;
|
||||
for (int s = 0; s < mesh->get_surface_count(); ++s) {
|
||||
const String sname = mesh->surface_get_name(s);
|
||||
Ref<ImageTexture> tex;
|
||||
auto mat_texture_name = [&]() -> String {
|
||||
if (s >= (int)surf_to_mesh.size()) {
|
||||
return String();
|
||||
}
|
||||
const gr2::Mesh &gm = fi.meshes[surf_to_mesh[s]];
|
||||
int matidx = gm.tri_groups.empty() ? -1 : gm.tri_groups[0].material_index;
|
||||
if (matidx < 0 || matidx >= (int)materials.size() ||
|
||||
materials[matidx].diffuse_texture.empty()) {
|
||||
return String();
|
||||
}
|
||||
return String(materials[matidx].diffuse_texture.c_str())
|
||||
.replace("\\", "/")
|
||||
.get_file()
|
||||
.get_basename()
|
||||
.to_lower();
|
||||
};
|
||||
|
||||
if (s < surface_tex_override.size() && !String(surface_tex_override[s]).is_empty()) {
|
||||
tex = _load_dds(surface_tex_override[s]);
|
||||
} else {
|
||||
}
|
||||
// gr2 material name -> texture, only when opted in (unverified vs oracle;
|
||||
// see GODOT-POC-PLAN.md §M2.5 T2.5.6).
|
||||
if (tex.is_null() && use_gr2_materials) {
|
||||
String want = mat_texture_name();
|
||||
if (!want.is_empty()) {
|
||||
for (int i = 0; i < dds_files.size(); ++i) {
|
||||
if (dds_files[i].get_basename().to_lower() == want) {
|
||||
tex = _load_dds(dir.path_join(dds_files[i]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// filename-convention resolution (default path)
|
||||
if (tex.is_null()) {
|
||||
tex = _resolve_texture(dir, stem, sname, dds_files);
|
||||
}
|
||||
if (tex.is_valid()) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace godot {
|
||||
class Skeleton3D;
|
||||
@@ -54,6 +55,9 @@ public:
|
||||
void set_material_mode(const godot::String &m);
|
||||
godot::String get_material_mode() const { return material_mode; }
|
||||
|
||||
void set_use_gr2_materials(bool v);
|
||||
bool get_use_gr2_materials() const { return use_gr2_materials; }
|
||||
|
||||
// Explicit per-surface texture path override (index -> absolute .dds path).
|
||||
void set_surface_texture(int surface, const godot::String &path);
|
||||
|
||||
@@ -81,9 +85,11 @@ private:
|
||||
bool flip_winding = false;
|
||||
|
||||
godot::String material_mode = "metin2"; // "metin2" (ShaderMaterial) | "standard"
|
||||
bool use_gr2_materials = false; // drive textures from gr2 material names (unverified)
|
||||
godot::PackedStringArray surface_tex_override;
|
||||
|
||||
std::shared_ptr<std::optional<gr2::File>> file; // shared_ptr so accessor stays valid
|
||||
std::vector<gr2::MaterialInfo> materials;
|
||||
godot::Skeleton3D *skel = nullptr;
|
||||
godot::MeshInstance3D *mi = nullptr;
|
||||
godot::String last_info;
|
||||
|
||||
|
Before Width: | Height: | Size: 255 KiB After Width: | Height: | Size: 258 KiB |
|
Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 262 KiB |
|
Before Width: | Height: | Size: 281 KiB After Width: | Height: | Size: 256 KiB |
|
Before Width: | Height: | Size: 258 KiB |
|
Before Width: | Height: | Size: 277 KiB |