Тесты моделей оружия: структура, VVD, скиннинг вьюмодели
Some checks are pending
CI / checks (push) Waiting to run

Замеры на реальных ассетах, которые пригодятся для дальнейших правок:
- v_pistol: 1 бодипарт, 2 меша (1169/4356 и 988/2448), 2157 вершин, 2167 в
  VVD (10 не используются), кадры лежат внутри MDL, флаги дорожек
  2/3/8/12 (RAWROT/RAWPOS/ANIMROT/ANIMPOS);
- скиннинг вьюмодели не разваливает меш: между позой покоя и кадром idle01
  максимальный сдвиг 4 юнита, габарит 20×23×13;
- v_pistol рисуется «лепестком» даже в позе покоя (до скиннинга) — проблема
  загрузки геометрии именно этой модели, отложена; по умолчанию w_pistol.

Все сьюты зелёные.
This commit is contained in:
Niko Marmeladkov 2026-10-23 13:28:49 +03:00
parent c1b35b6263
commit 7490ef3798

View file

@ -537,6 +537,80 @@ fn real_weapon_model_structure() {
}
}
/// Заголовки VVD моделей оружия: версия, смещения, размеры.
#[test]
fn real_weapon_vvd_headers() {
let Some(root) = assets_root() else {
eprintln!("пропуск: ассеты не найдены");
return;
};
let vfs = mount_game(&root);
for name in [
"models/weapons/v_pistol",
"models/weapons/w_pistol",
"models/props_c17/oildrum001",
] {
let Ok(data) = vfs.read(&format!("{name}.vvd")) else {
continue;
};
let vvd = formats::vvd::Vvd::parse(&data).expect("разбор vvd");
let h = &vvd.header;
eprintln!(
"{name}.vvd: байт {}, версия {}, лодов {}, вершин по LOD {:?}, фиксов {}, \
vertex_data_start {}, tangent_data_start {}",
data.len(),
h.version,
h.num_lods,
&h.num_lod_vertexes[..h.num_lods.clamp(0, 8) as usize],
h.num_fixups,
h.vertex_data_start,
h.tangent_data_start
);
let verts = vvd.vertices(0).expect("вершины");
let mut min = [f32::MAX; 3];
let mut max = [f32::MIN; 3];
for v in &verts {
for j in 0..3 {
min[j] = min[j].min(v.position[j]);
max[j] = max[j].max(v.position[j]);
}
}
eprintln!(" габарит вершин: min {min:?}, max {max:?}");
}
}
/// Габарит модели из заголовка MDL: должен совпадать с вершинами VVD.
#[test]
fn real_weapon_mdl_hull() {
let Some(root) = assets_root() else {
eprintln!("пропуск: ассеты не найдены");
return;
};
let vfs = mount_game(&root);
for name in ["models/weapons/v_pistol", "models/weapons/w_pistol"] {
let Ok(data) = vfs.read(&format!("{name}.mdl")) else {
continue;
};
// studiohdr_t: hull_min — 104, hull_max — 116.
let f = |o: usize| f32::from_le_bytes([data[o], data[o + 1], data[o + 2], data[o + 3]]);
eprintln!(
"{name}: hull_min [{:.1} {:.1} {:.1}], hull_max [{:.1} {:.1} {:.1}], view_bb [{:.1} {:.1} {:.1}]..[{:.1} {:.1} {:.1}]",
f(104),
f(108),
f(112),
f(116),
f(120),
f(124),
f(128),
f(132),
f(136),
f(140),
f(144),
f(148)
);
}
}
/// Позиции вершин экземпляра модели.
fn instance_positions(world: &engine::World, index: usize) -> Vec<[f32; 3]> {
let instance = &world.model_instances[index];