- 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
161 lines
5.8 KiB
C++
161 lines
5.8 KiB
C++
// 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
|