Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…

#include "vbuf_reader.hpp" | |
#include "bit_flags.hpp" | |
#include "magic_number.hpp" | |
#include "string_helpers.hpp" | |
#include "synced_cout.hpp" | |
#include "ucfb_reader.hpp" | |
#include <array> | |
#include <cmath> | |
#include <cstdint> | |
#include <iterator> | |
#include <optional> | |
#include <sstream> | |
using namespace std::literals; | |
namespace { | |
enum class Vbuf_flags : std::uint32_t { | |
none = 0b0u, | |
position = 0b10u, | |
blendindices = 0b100u, | |
blendweight = 0b1000u, | |
normal = 0b100000u, | |
tangents = 0b1000000u, | |
color = 0b10000000u, | |
static_lighting = 0b100000000u, | |
texcoords = 0b1000000000u, | |
position_compressed = 0b1000000000000u, | |
blendinfo_compressed = 0b10000000000000u, | |
normal_compressed = 0b100000000000000u, | |
texcoord_compressed = 0b1000000000000000u | |
}; | |
constexpr bool marked_as_enum_flag(Vbuf_flags) | |
{ | |
return true; | |
} | |
constexpr auto vbuf_all_flags_mask = | |
Vbuf_flags::position | Vbuf_flags::blendindices | Vbuf_flags::blendweight | | |
Vbuf_flags::normal | Vbuf_flags::tangents | Vbuf_flags::color | | |
Vbuf_flags::static_lighting | Vbuf_flags::texcoords | Vbuf_flags::position_compressed | | |
Vbuf_flags::blendinfo_compressed | Vbuf_flags::normal_compressed | | |
Vbuf_flags::texcoord_compressed; | |
constexpr auto vbuf_unknown_flags_mask = ~vbuf_all_flags_mask; | |
constexpr auto vbuf_compressed_mask = | |
Vbuf_flags::position_compressed | Vbuf_flags::blendinfo_compressed | | |
Vbuf_flags::normal_compressed | Vbuf_flags::texcoord_compressed; | |
struct Vbuf_info { | |
std::uint32_t count; | |
std::uint32_t stride; | |
Vbuf_flags flags; | |
}; | |
static_assert(std::is_trivially_copyable_v<Vbuf_info>); | |
static_assert(sizeof(Vbuf_info) == 12); | |
class Position_decompress { | |
public: | |
Position_decompress(const std::array<glm::vec3, 2> vert_box) noexcept | |
{ | |
low = vert_box[0]; | |
mul = (vert_box[1] - vert_box[0]); | |
} | |
glm::vec3 operator()(const glm::i16vec3 compressed) const noexcept | |
{ | |
const auto c = static_cast<glm::vec3>(compressed); | |
constexpr float i16min = std::numeric_limits<glm::int16>::min(); | |
constexpr float i16max = std::numeric_limits<glm::int16>::max(); | |
return low + (c - i16min) * mul / (i16max - i16min); | |
} | |
private: | |
glm::vec3 low; | |
glm::vec3 mul; | |
}; | |
auto select_best_vbuf(const std::vector<Ucfb_reader_strict<"VBUF"_mn>>& vbufs) | |
-> Ucfb_reader_strict<"VBUF"_mn> | |
{ | |
if (vbufs.empty()) throw std::runtime_error{"modl segm has no VBUFs"}; | |
std::vector<Ucfb_reader_strict<"VBUF"_mn>> sorted_vbufs; | |
sorted_vbufs.reserve(vbufs.size()); | |
std::copy_if(vbufs.cbegin(), vbufs.cbegin(), std::back_inserter(sorted_vbufs), | |
[](Ucfb_reader_strict<"VBUF"_mn> vbuf) { | |
const auto flags = vbuf.read_trivial<Vbuf_info>().flags; | |
return (flags & vbuf_compressed_mask) == Vbuf_flags::none; | |
}); | |
const auto pick_vbuf = [](std::vector<Ucfb_reader_strict<"VBUF"_mn>>& vbufs_to_sort) { | |
const auto sort_predicate = [](Ucfb_reader_strict<"VBUF"_mn> left, | |
Ucfb_reader_strict<"VBUF"_mn> right) { | |
const auto lflags = left.read_trivial<Vbuf_info>().flags; | |
const auto rflags = right.read_trivial<Vbuf_info>().flags; | |
return lflags < rflags; | |
}; | |
std::sort(std::begin(vbufs_to_sort), std::end(vbufs_to_sort), sort_predicate); | |
return vbufs_to_sort.back(); | |
}; | |
if (!sorted_vbufs.empty()) return pick_vbuf(sorted_vbufs); | |
sorted_vbufs.clear(); | |
std::copy_if(vbufs.cbegin(), vbufs.cbegin(), std::back_inserter(sorted_vbufs), | |
[](Ucfb_reader_strict<"VBUF"_mn> vbuf) { | |
const auto flags = vbuf.read_trivial<Vbuf_info>().flags; | |
return (flags & vbuf_compressed_mask) != Vbuf_flags::none; | |
}); | |
if (!sorted_vbufs.empty()) return pick_vbuf(sorted_vbufs); | |
return vbufs.back(); | |
} | |
bool is_pretransformed_vbuf(const Vbuf_flags flags) noexcept | |
{ | |
return are_flags_set(flags, Vbuf_flags::blendindices) && | |
!are_flags_set(flags, Vbuf_flags::blendweight); | |
} | |
auto get_vertices_create_flags(const Vbuf_flags flags) -> model::Vertices::Create_flags | |
{ | |
return {.positions = (Vbuf_flags::position & flags) != Vbuf_flags::none, | |
.normals = (Vbuf_flags::normal & flags) != Vbuf_flags::none, | |
.tangents = (Vbuf_flags::tangents & flags) != Vbuf_flags::none, | |
.bitangents = (Vbuf_flags::tangents & flags) != Vbuf_flags::none, | |
.colors = ((Vbuf_flags::color & flags) | | |
(Vbuf_flags::static_lighting & flags)) != Vbuf_flags::none, | |
.texcoords = (Vbuf_flags::texcoords & flags) != Vbuf_flags::none, | |
.bones = (Vbuf_flags::blendindices & flags) != Vbuf_flags::none, | |
.weights = (Vbuf_flags::blendweight & flags) != Vbuf_flags::none}; | |
} | |
glm::vec4 read_colour(Ucfb_reader_strict<"VBUF"_mn>& vbuf) | |
{ | |
const auto colour = vbuf.read_trivial_unaligned<std::uint32_t>(); | |
return glm::unpackUnorm4x8(colour).bgra; | |
} | |
glm::vec3 read_compressed_normal_pc(Ucfb_reader_strict<"VBUF"_mn>& vbuf) | |
{ | |
return (glm::unpackUnorm4x8(vbuf.read_trivial_unaligned<std::uint32_t>()) * 2.0f - | |
1.0f) | |
.zyx; | |
} | |
glm::vec3 read_compressed_normal_xbox(Ucfb_reader_strict<"VBUF"_mn>& vbuf) | |
{ | |
constexpr std::array<std::uint32_t, 2> sign_extend_xy = {0x0u, 0xfffffc00u}; | |
constexpr std::array<std::uint32_t, 2> sign_extend_z = {0x0u, 0xfffff800u}; | |
const auto udec_normal = vbuf.read_trivial_unaligned<std::uint32_t>(); | |
const auto x_unsigned = udec_normal & 0x7ffu; | |
const auto y_unsigned = (udec_normal >> 11u) & 0x7ffu; | |
const auto z_unsigned = (udec_normal >> 22u) & 0x3ffu; | |
const auto x_signed = | |
static_cast<std::int32_t>(x_unsigned | sign_extend_xy[x_unsigned >> 10u]); | |
const auto y_signed = | |
static_cast<std::int32_t>(y_unsigned | sign_extend_xy[y_unsigned >> 10u]); | |
const auto z_signed = | |
static_cast<std::int32_t>(z_unsigned | sign_extend_z[z_unsigned >> 9u]); | |
const auto x = static_cast<float>(x_signed) / 1023.f; | |
const auto y = static_cast<float>(y_signed) / 1023.f; | |
const auto z = static_cast<float>(z_signed) / 511.f; | |
return glm::vec3{x, y, z}; | |
} | |
glm::vec3 read_weights(Ucfb_reader_strict<"VBUF"_mn>& vbuf) | |
{ | |
const auto weights = vbuf.read_trivial_unaligned<glm::vec2>(); | |
return glm::vec3{weights.x, weights.y, 1.f - weights.x - weights.y}; | |
} | |
glm::vec3 read_weights_compressed_pc(Ucfb_reader_strict<"VBUF"_mn>& vbuf) | |
{ | |
const auto weights = glm::unpackUnorm4x8(vbuf.read_trivial_unaligned<std::uint32_t>()); | |
return glm::vec3{weights.z, weights.y, 1.f - weights.z - weights.y}; | |
} | |
glm::vec3 read_weights_compressed_xbox(Ucfb_reader_strict<"VBUF"_mn>& vbuf) | |
{ | |
const auto weights = glm::unpackUnorm4x8(vbuf.read_trivial_unaligned<std::uint16_t>()); | |
return glm::vec3{weights.x, weights.y, 1.f - weights.x - weights.y}; | |
} | |
glm::u8vec3 read_bone_indices_pc(Ucfb_reader_strict<"VBUF"_mn>& vbuf) | |
{ | |
const auto packed = vbuf.read_trivial<std::uint32_t>(); | |
glm::u8vec3 indices; | |
indices.x = static_cast<std::uint8_t>(packed & 0xffu); | |
indices.y = static_cast<std::uint8_t>((packed >> 8u) & 0xffu); | |
indices.z = static_cast<std::uint8_t>((packed >> 16u) & 0xffu); | |
return indices; | |
} | |
glm::vec2 read_compressed_texcoords(Ucfb_reader_strict<"VBUF"_mn>& vbuf) | |
{ | |
const auto compressed = | |
static_cast<glm::vec2>(vbuf.read_trivial_unaligned<glm::i16vec2>()); | |
return compressed / 2048.f; | |
} | |
glm::vec2 read_texcoords(Ucfb_reader_strict<"VBUF"_mn>& vbuf) | |
{ | |
const auto coords = vbuf.read_trivial_unaligned<glm::vec2>(); | |
return {coords.x, 1.f - glm::fract(coords.y)}; | |
} | |
void read_vertex_pc(Ucfb_reader_strict<"VBUF"_mn> vbuf, const Vbuf_flags flags, | |
const std::size_t index, const Position_decompress& pos_decompress, | |
model::Vertices& out) | |
{ | |
if ((flags & Vbuf_flags::position) == Vbuf_flags::position) { | |
if ((flags & Vbuf_flags::position_compressed) == Vbuf_flags::position_compressed) { | |
const auto compressed = vbuf.read_trivial_unaligned<glm::i16vec4>(); | |
out.positions[index] = pos_decompress(compressed); | |
} | |
else { | |
out.positions[index] = vbuf.read_trivial_unaligned<glm::vec3>(); | |
} | |
} | |
if ((flags & Vbuf_flags::blendweight) == Vbuf_flags::blendweight) { | |
if ((flags & Vbuf_flags::blendinfo_compressed) == | |
Vbuf_flags::blendinfo_compressed) { | |
out.weights[index] = read_weights_compressed_pc(vbuf); | |
} | |
else { | |
out.weights[index] = read_weights(vbuf); | |
} | |
} | |
if ((flags & Vbuf_flags::blendindices) == Vbuf_flags::blendindices) { | |
out.bones[index] = read_bone_indices_pc(vbuf); | |
} | |
if ((flags & Vbuf_flags::normal) == Vbuf_flags::normal) { | |
if ((flags & Vbuf_flags::normal_compressed) == Vbuf_flags::normal_compressed) { | |
out.normals[index] = read_compressed_normal_pc(vbuf); | |
} | |
else { | |
out.normals[index] = vbuf.read_trivial_unaligned<glm::vec3>(); | |
} | |
} | |
if ((flags & Vbuf_flags::tangents) == Vbuf_flags::tangents) { | |
if ((flags & Vbuf_flags::normal_compressed) == Vbuf_flags::normal_compressed) { | |
out.bitangents[index] = read_compressed_normal_pc(vbuf); | |
out.tangents[index] = read_compressed_normal_pc(vbuf); | |
} | |
else { | |
out.bitangents[index] = vbuf.read_trivial_unaligned<glm::vec3>(); | |
out.tangents[index] = vbuf.read_trivial_unaligned<glm::vec3>(); | |
} | |
} | |
if ((flags & Vbuf_flags::color) == Vbuf_flags::color) { | |
out.colors[index] = read_colour(vbuf); | |
} | |
if ((flags & Vbuf_flags::static_lighting) == Vbuf_flags::static_lighting) { | |
out.colors[index] = read_colour(vbuf); | |
} | |
if ((flags & Vbuf_flags::texcoords) == Vbuf_flags::texcoords) { | |
if ((flags & Vbuf_flags::texcoord_compressed) == Vbuf_flags::texcoord_compressed) { | |
out.texcoords[index] = read_compressed_texcoords(vbuf); | |
} | |
else { | |
out.texcoords[index] = vbuf.read_trivial_unaligned<glm::vec2>(); | |
} | |
} | |
} | |
void read_vertex_xbox(Ucfb_reader_strict<"VBUF"_mn> vbuf, const Vbuf_flags flags, | |
const std::size_t index, const Position_decompress& pos_decompress, | |
model::Vertices& out) | |
{ | |
if ((flags & Vbuf_flags::position) == Vbuf_flags::position) { | |
if ((flags & Vbuf_flags::position_compressed) == Vbuf_flags::position_compressed) { | |
const auto compressed = vbuf.read_trivial_unaligned<glm::i16vec4>(); | |
out.positions[index] = pos_decompress(compressed); | |
} | |
else { | |
out.positions[index] = vbuf.read_trivial_unaligned<glm::vec3>(); | |
} | |
} | |
if ((flags & Vbuf_flags::blendweight) == Vbuf_flags::blendweight) { | |
if ((flags & Vbuf_flags::blendinfo_compressed) == | |
Vbuf_flags::blendinfo_compressed) { | |
out.weights[index] = read_weights_compressed_xbox(vbuf); | |
} | |
else { | |
out.weights[index] = read_weights(vbuf); | |
} | |
} | |
if ((flags & Vbuf_flags::blendindices) == Vbuf_flags::blendindices) { | |
if ((flags & Vbuf_flags::blendweight) == Vbuf_flags::blendweight) { | |
out.bones[index] = vbuf.read_trivial_unaligned<glm::u8vec3>(); | |
} | |
else { | |
out.bones[index] = glm::u8vec3{vbuf.read_trivial_unaligned<glm::u8>()}; | |
} | |
} | |
if ((flags & Vbuf_flags::normal) == Vbuf_flags::normal) { | |
if ((flags & Vbuf_flags::normal_compressed) == Vbuf_flags::normal_compressed) { | |
out.normals[index] = read_compressed_normal_xbox(vbuf); | |
} | |
else { | |
out.normals[index] = vbuf.read_trivial_unaligned<glm::vec3>(); | |
} | |
} | |
if ((flags & Vbuf_flags::normal) == Vbuf_flags::normal) { | |
if ((flags & Vbuf_flags::normal_compressed) == Vbuf_flags::normal_compressed) { | |
out.normals[index] = read_compressed_normal_xbox(vbuf); | |
} | |
else { | |
out.normals[index] = vbuf.read_trivial_unaligned<glm::vec3>(); | |
} | |
} | |
if ((flags & Vbuf_flags::tangents) == Vbuf_flags::tangents) { | |
if ((flags & Vbuf_flags::normal_compressed) == Vbuf_flags::normal_compressed) { | |
out.bitangents[index] = read_compressed_normal_xbox(vbuf); | |
out.tangents[index] = read_compressed_normal_xbox(vbuf); | |
} | |
else { | |
out.bitangents[index] = vbuf.read_trivial_unaligned<glm::vec3>(); | |
out.tangents[index] = vbuf.read_trivial_unaligned<glm::vec3>(); | |
} | |
} | |
if ((flags & Vbuf_flags::color) == Vbuf_flags::color) { | |
out.colors[index] = read_colour(vbuf); | |
} | |
if ((flags & Vbuf_flags::static_lighting) == Vbuf_flags::static_lighting) { | |
out.colors[index] = read_colour(vbuf); | |
} | |
if ((flags & Vbuf_flags::texcoords) == Vbuf_flags::texcoords) { | |
if ((flags & Vbuf_flags::texcoord_compressed) == Vbuf_flags::texcoord_compressed) { | |
out.texcoords[index] = read_compressed_texcoords(vbuf); | |
} | |
else { | |
out.texcoords[index] = vbuf.read_trivial_unaligned<glm::vec2>(); | |
} | |
} | |
} | |
template<auto read_vertex> | |
void read_vertices(Ucfb_reader_strict<"VBUF"_mn>& vbuf, const Vbuf_info info, | |
const std::array<glm::vec3, 2> vert_box, model::Vertices& out) | |
{ | |
const Position_decompress pos_decompress{vert_box}; | |
for (auto i = 0u; i < info.count; ++i) { | |
read_vertex( | |
Ucfb_reader_strict<"VBUF"_mn>{vbuf}, // copy reader to emulate stride overlap | |
info.flags, i, pos_decompress, out); | |
vbuf.consume_unaligned(info.stride); | |
} | |
} | |
} | |
auto read_vbuf(const std::vector<Ucfb_reader_strict<"VBUF"_mn>>& vbufs, | |
const std::array<glm::vec3, 2> vert_box, const bool xbox) | |
-> model::Vertices | |
{ | |
auto vbuf = select_best_vbuf(vbufs); | |
const auto info = vbuf.read_trivial<Vbuf_info>(); | |
if ((info.flags & vbuf_unknown_flags_mask) != Vbuf_flags::none) { | |
synced_cout::print("VBUF with unknown flags encountered."s, "\n size : "s, | |
vbuf.size(), "\n entry count: "s, info.count, "\n stride: "s, | |
info.stride, "\n entry flags: "s, | |
to_hexstring(static_cast<std::uint32_t>(info.flags)), '\n'); | |
throw std::runtime_error{"vbuf with unknown flags"}; | |
} | |
model::Vertices vertices{info.count, get_vertices_create_flags(info.flags)}; | |
vertices.pretransformed = is_pretransformed_vbuf(info.flags); | |
vertices.static_lighting = | |
(info.flags & Vbuf_flags::static_lighting) == Vbuf_flags::static_lighting; | |
vertices.softskinned = | |
(info.flags & Vbuf_flags::blendweight) == Vbuf_flags::blendweight; | |
try { | |
if (xbox) { | |
read_vertices<read_vertex_xbox>(vbuf, info, vert_box, vertices); | |
} | |
else { | |
read_vertices<read_vertex_pc>(vbuf, info, vert_box, vertices); | |
} | |
} | |
catch (std::exception&) { | |
synced_cout::print( | |
"Failed to completely read VBUF. Model may be incomplete or invalid."); | |
} | |
return vertices; | |
} |