render map objects: decode compiled W3DMesh art, skin and draw over terrain
Read the buildings and props a map places from its compiled art (the uncompressed worldbuilder stream + embedded DDS textures): - ra3.models: BAB asset-stream parser (lazy slices), W3DMesh + D3DHierarchy decode, DDS (DXT1/3/5 + uncompressed) decode, per-mesh bone remap and bind-pose single-joint skinning, flattened to one world-space triangle soup. - ra3.map: parse the ObjectsList chunk into (type, x, y, z, angle). - ra3.terrain: render3d rasterises the scene over the raymarched terrain with a z-buffer; gpu_terrain carries the scene for the GPU backends. - ra3.vulkan: second pipeline + depth attachment, terrain.frag writes gl_FragDepth, and a small depth bias keeps ground decals from z-fighting. - objects.fx shaders (compiled to SPIR-V), embedded like scene/terrain. Only opaque parts are drawn: FX-light billboards (DefaultW3D.fx / BasicW3D.fx) and damage-fill shells (BuildingsGenericDamageFill.fx) are skipped, since the latter paint the wrecked interior (e.g. orange CBBuilding_Wood) over the shell. Ground-decal meshes with no diffuse role and the Road templates themselves are still not drawn.
This commit is contained in:
@@ -222,6 +222,84 @@ auto main() -> int {
|
||||
check(names.lookup("map_mp_2_feasel4") == "Battlebase Beta", "CSF map names decode (byte-XOR 0xFF)");
|
||||
check(names.lookup("unknown_map") == "unknown_map", "an unknown id falls back to itself");
|
||||
|
||||
// Map objects (`ObjectsList`): decode a synthetic one-object chunk.
|
||||
const auto put16 = [](std::vector<core::uint8> &out, std::uint16_t v) {
|
||||
out.push_back(static_cast<core::uint8>(v));
|
||||
out.push_back(static_cast<core::uint8>(v >> 8U));
|
||||
};
|
||||
const auto put32 = [](std::vector<core::uint8> &out, std::uint32_t v) {
|
||||
for (int i = 0; i < 4; ++i) out.push_back(static_cast<core::uint8>(v >> (8 * i)));
|
||||
};
|
||||
const auto putf = [&put32](std::vector<core::uint8> &out, float f) {
|
||||
std::uint32_t bits = 0;
|
||||
std::memcpy(&bits, &f, sizeof(bits));
|
||||
put32(out, bits);
|
||||
};
|
||||
|
||||
std::vector<core::uint8> object_data;
|
||||
putf(object_data, 100.0F);
|
||||
putf(object_data, 200.0F);
|
||||
putf(object_data, 0.0F);
|
||||
putf(object_data, 0.5F);
|
||||
put32(object_data, 0U); // road type
|
||||
const std::string object_type = "BB_TEST";
|
||||
put16(object_data, static_cast<std::uint16_t>(object_type.size()));
|
||||
object_data.insert(object_data.end(), object_type.begin(), object_type.end());
|
||||
put16(object_data, 0U); // no properties
|
||||
std::vector<core::uint8> object_asset;
|
||||
put32(object_asset, 1U); // asset index
|
||||
put16(object_asset, 1U); // version
|
||||
put32(object_asset, static_cast<std::uint32_t>(object_data.size()));
|
||||
object_asset.insert(object_asset.end(), object_data.begin(), object_data.end());
|
||||
|
||||
std::vector<core::uint8> ckmp;
|
||||
ckmp.insert(ckmp.end(), {'C', 'k', 'M', 'p'});
|
||||
put32(ckmp, 1U); // one asset name
|
||||
const std::string chunk_name = "ObjectsList";
|
||||
ckmp.push_back(static_cast<core::uint8>(chunk_name.size()));
|
||||
ckmp.insert(ckmp.end(), chunk_name.begin(), chunk_name.end());
|
||||
put32(ckmp, 1U); // name index
|
||||
put32(ckmp, 1U); // chunk index
|
||||
put16(ckmp, 3U); // chunk version
|
||||
put32(ckmp, static_cast<std::uint32_t>(object_asset.size()));
|
||||
ckmp.insert(ckmp.end(), object_asset.begin(), object_asset.end());
|
||||
|
||||
const auto objects = map::parse_objects(ckmp);
|
||||
check(objects.size() == 1U, "ObjectsList parses one object");
|
||||
check(!objects.empty() && objects[0].type == "BB_TEST", "object type-name decodes");
|
||||
check(!objects.empty() && objects[0].x == 100.0F && objects[0].y == 200.0F && objects[0].angle == 0.5F, "object position/angle decode");
|
||||
|
||||
// DDS: a 1x1 uncompressed RGB32 image decodes with correct channels.
|
||||
std::vector<core::uint8> dds(128U, 0U);
|
||||
dds[0] = 'D';
|
||||
dds[1] = 'D';
|
||||
dds[2] = 'S';
|
||||
dds[3] = ' ';
|
||||
const auto put_dds = [&dds](std::size_t off, std::uint32_t v) {
|
||||
for (int i = 0; i < 4; ++i) dds[off + static_cast<std::size_t>(i)] = static_cast<core::uint8>(v >> (8 * i));
|
||||
};
|
||||
put_dds(4U, 124U);
|
||||
put_dds(8U, 0x1U); // flags: DDSD_CAPS|... (unused)
|
||||
put_dds(12U, 1U); // height
|
||||
put_dds(16U, 1U); // width
|
||||
put_dds(76U, 32U); // pixel format size
|
||||
put_dds(80U, 0x40U); // DDPF_RGB
|
||||
put_dds(84U, 0U); // no fourcc
|
||||
put_dds(88U, 32U); // bits per pixel
|
||||
put_dds(92U, 0x00FF0000U);
|
||||
put_dds(96U, 0x0000FF00U);
|
||||
put_dds(100U, 0x000000FFU);
|
||||
put_dds(104U, 0xFF000000U);
|
||||
dds.push_back(0x00U); // B
|
||||
dds.push_back(0x00U); // G
|
||||
dds.push_back(0xFFU); // R
|
||||
dds.push_back(0xFFU); // A
|
||||
const auto decoded_dds = models::decode_dds(dds);
|
||||
check(decoded_dds.width() == 1U && decoded_dds.height() == 1U, "DDS dimensions decode");
|
||||
check(!decoded_dds.empty() && decoded_dds.data()[0] == render::argb(255, 0, 0), "DDS RGB32 channels map to ARGB");
|
||||
|
||||
check(models::decode_dds(std::vector<core::uint8>{1U, 2U, 3U}).empty(), "a non-DDS payload yields no image");
|
||||
|
||||
if (failures == 0) {
|
||||
std::puts("ra3_tests: OK");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user