M1+M2: gr2 -> Skeleton3D/ArrayMesh/Skin + libgr2-driven animation
- gr2_bridge: gr2 Mat4 (row-major) -> Godot Transform3D (4x4 transpose); build_skeleton (rest from local_transform, initial_placement folded into roots), build_skin (bind i -> bone i, pose = inverse_world), build_mesh (surface per gr2 mesh, ARRAY_BONES = skeleton idx via bone_bindings, rigid meshes bound to bone_bindings[0]). - dxt: DDS DXT1/3/5 decoder ported from xrender-poc. - Metin2Model: load_gr2 -> Skeleton3D + MeshInstance3D + StandardMaterial3D (runtime DDS albedo, face/body split by surface name). Z-up cm -> Y-up m conversion on the node transform (unit_scale, flip_z, flip_winding props). - Metin2AnimPlayer: _process samples a (separate) anim .gr2 with gr2::sample_pose(model.skeleton, anim, t) -> set_bone_global_pose per bone; Godot GPU-skins via skin_matrix = global_pose(i) * bind_pose(i) == gr2 deformer matrix. selfcheck() NaN-scans all anims. Verified: warrior_cheongrin renders (75 bones, 5 surfaces, 3324 verts, textured, upright). dance01 (dur 28.3s, 74 tracks) plays, 0 NaN over 25 samples, distinct poses at t=2/9/16/23. Screenshots in test/golden/. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaHYEY9rwLWt21PULiYjeJ
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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)
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
// engine/dxt —— DDS(DXT1/3/5) + 未压缩 BGRA8 → RGBA8 软解。M1 T3。
|
||||
#include "dxt.h"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
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<uint8_t> 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
|
||||
@@ -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 <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace mtgodot {
|
||||
|
||||
struct Image {
|
||||
uint16_t w = 0, h = 0;
|
||||
std::vector<uint8_t> 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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user