mirror of
https://github.com/open-goal/jak-project
synced 2026-10-08 21:42:33 -04:00
d04ac9d787
With help from @gratefulforest, I've implemented storing colours for each time of day for meshes, and updated the code to store 32 channel colours (8 times of day * 4 rgba channels). https://github.com/user-attachments/assets/5d2fb82c-40b7-44ad-976f-acd5d4d57e1c In addition, I've implemented a system @gratefulforest suggested which makes adding different times of day really easy for developers. - If some times of day are missing, they use the closest time of day that is defined, defaulting to the earlier of the two if there are two equally closest defined times of day. - If no times of day are defined, COLOR_0 is used, otherwise white is used. Log messages are concise and very informative: ``` [46:23:278] [info] Reading gltf mesh: /home/jakjm/git/jak-project/custom_assets/jak1/levels/test-zone/test-zone2.glb [46:23:278] [warn] Plane missing some times of day, using sunrise:_EVENING, morning:_EVENING, noon:_TWILIGHT, afternoon:_TWILIGHT, sunset:_TWILIGHT, twilight:_TWILIGHT, evening:_EVENING, green sun:_EVENING [46:23:278] [error] Cube.001 didn't have any colors, using white. [46:23:278] [warn] Cube.002 had colors but no times of day, using COLOR_0. [46:23:278] [warn] Cube.003 missing some times of day, using sunrise:_SUNRISE, morning:_SUNRISE, noon:_SUNRISE, afternoon:_SUNSET, sunset:_SUNSET, twilight:_SUNSET, evening:_SUNSET, green sun:_SUNRISE [46:23:278] [warn] Cube.004 had colors but no times of day, using COLOR_0. [46:23:278] [error] Cube.005 didn't have any colors, using white. ``` --------- Co-authored-by: Tyler Wilding <xtvaser@gmail.com>
953 lines
35 KiB
C++
953 lines
35 KiB
C++
#include "gltf_util.h"
|
|
|
|
#include "algorithm"
|
|
#include "image_resize.h"
|
|
|
|
#include "common/log/log.h"
|
|
|
|
namespace gltf_util {
|
|
|
|
/*!
|
|
* Convert a GLTF position buffer or similar to std::vector<Vec3f>
|
|
*/
|
|
std::vector<math::Vector3f> extract_vec3f(const u8* data, u32 count, u32 stride) {
|
|
std::vector<math::Vector3f> result;
|
|
result.reserve(count);
|
|
for (u32 i = 0; i < count; i++) {
|
|
memcpy(&result.emplace_back(), data, sizeof(math::Vector3f));
|
|
data += stride;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::vector<math::Vector2f> extract_vec2f(const u8* data, u32 count, u32 stride) {
|
|
std::vector<math::Vector2f> result;
|
|
result.reserve(count);
|
|
for (u32 i = 0; i < count; i++) {
|
|
memcpy(&result.emplace_back(), data, sizeof(math::Vector2f));
|
|
data += stride;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*!
|
|
* Convert a GLTF color buffer (float format) to u8 colors.
|
|
*/
|
|
std::vector<math::Vector<u8, 4>> extract_color_from_vec4_float(const u8* data,
|
|
u32 count,
|
|
u32 stride) {
|
|
std::vector<math::Vector<u8, 4>> result;
|
|
result.reserve(count);
|
|
for (u32 i = 0; i < count; i++) {
|
|
math::Vector<float, 4> temp;
|
|
memcpy(&temp, data, sizeof(math::Vector<float, 4>));
|
|
data += stride;
|
|
result.emplace_back(temp.x() * 255, temp.y() * 255, temp.z() * 255, temp.w() * 255);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::vector<math::Vector<u8, 4>> extract_color_from_vec3_float(const u8* data,
|
|
u32 count,
|
|
u32 stride) {
|
|
std::vector<math::Vector<u8, 4>> result;
|
|
result.reserve(count);
|
|
for (u32 i = 0; i < count; i++) {
|
|
math::Vector<float, 3> temp;
|
|
memcpy(&temp, data, sizeof(math::Vector<float, 3>));
|
|
data += stride;
|
|
result.emplace_back(temp.x() * 255, temp.y() * 255, temp.z() * 255, 255);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*!
|
|
* Convert a GLTF color buffer (u16 format) to u8 colors.
|
|
*/
|
|
std::vector<math::Vector<u8, 4>> extract_color_from_vec4_u16(const u8* data,
|
|
u32 count,
|
|
u32 stride) {
|
|
std::vector<math::Vector<u8, 4>> result;
|
|
result.reserve(count);
|
|
for (u32 i = 0; i < count; i++) {
|
|
math::Vector<u16, 4> temp;
|
|
memcpy(&temp, data, sizeof(math::Vector<u16, 4>));
|
|
data += stride;
|
|
result.emplace_back(temp.x() >> 8, temp.y() >> 8, temp.z() >> 8, temp.w() >> 8);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::vector<math::Vector<u8, 4>> extract_color_from_vec4_u8(const u8* data, u32 count, u32 stride) {
|
|
std::vector<math::Vector<u8, 4>> result;
|
|
result.reserve(count);
|
|
for (u32 i = 0; i < count; i++) {
|
|
result.push_back(math::Vector<u8, 4>(data[0], data[1], data[2], data[3]));
|
|
data += stride;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*!
|
|
* Convert a GLTF index buffer
|
|
*/
|
|
std::vector<u32> gltf_index_buffer(const tinygltf::Model& model,
|
|
int indices_idx,
|
|
u32 index_offset) {
|
|
const auto& indices_accessor = model.accessors[indices_idx];
|
|
const auto& buffer_view = model.bufferViews[indices_accessor.bufferView];
|
|
const auto& buffer = model.buffers[buffer_view.buffer];
|
|
const auto data_ptr = buffer.data.data() + buffer_view.byteOffset + indices_accessor.byteOffset;
|
|
const auto stride = indices_accessor.ByteStride(buffer_view);
|
|
const auto count = indices_accessor.count;
|
|
|
|
switch (indices_accessor.componentType) {
|
|
case TINYGLTF_COMPONENT_TYPE_BYTE:
|
|
return index_list_to_u32<s8>(data_ptr, count, index_offset, stride);
|
|
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
|
|
return index_list_to_u32<u8>(data_ptr, count, index_offset, stride);
|
|
case TINYGLTF_COMPONENT_TYPE_SHORT:
|
|
return index_list_to_u32<s16>(data_ptr, count, index_offset, stride);
|
|
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
|
|
return index_list_to_u32<u16>(data_ptr, count, index_offset, stride);
|
|
case TINYGLTF_COMPONENT_TYPE_INT:
|
|
return index_list_to_u32<s32>(data_ptr, count, index_offset, stride);
|
|
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
|
|
return index_list_to_u32<u32>(data_ptr, count, index_offset, stride);
|
|
default:
|
|
ASSERT_MSG(false, "unsupported component type");
|
|
}
|
|
}
|
|
|
|
std::vector<math::Matrix4f> extract_mat4(const tinygltf::Model& model, int accessor_idx) {
|
|
const auto& accessor = model.accessors[accessor_idx];
|
|
const auto& buffer_view = model.bufferViews[accessor.bufferView];
|
|
const auto& buffer = model.buffers[buffer_view.buffer];
|
|
const u8* data_ptr = buffer.data.data() + buffer_view.byteOffset + accessor.byteOffset;
|
|
const auto stride = accessor.ByteStride(buffer_view);
|
|
const auto count = accessor.count;
|
|
|
|
// ASSERT(buffer_view.target == TINYGLTF_TARGET_ARRAY_BUFFER); // ??
|
|
ASSERT(accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT);
|
|
ASSERT(accessor.type == TINYGLTF_TYPE_MAT4);
|
|
|
|
std::vector<math::Matrix4f> result(accessor.count);
|
|
for (size_t x = 0; x < count; x++) {
|
|
for (int i = 0; i < 4; i++) {
|
|
for (int j = 0; j < 4; j++) {
|
|
memcpy(&result[x](j, i), data_ptr + sizeof(float) * (i * 4 + j), sizeof(float));
|
|
}
|
|
}
|
|
data_ptr += stride;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
JointsAndWeights convert_per_vertex_data(const math::Vector4f& weights,
|
|
const math::Vector<u8, 4>& joints) {
|
|
int discard_idx = -1;
|
|
float discard_weight = 100;
|
|
for (int i = 0; i < 4; i++) {
|
|
if (weights[i] < discard_weight) {
|
|
discard_idx = i;
|
|
discard_weight = weights[i];
|
|
}
|
|
}
|
|
|
|
JointsAndWeights ret;
|
|
int dst = 0;
|
|
float sum = 0;
|
|
for (int src = 0; src < 4; src++) {
|
|
if (src == discard_idx) {
|
|
continue;
|
|
}
|
|
ret.joints[dst] = joints[src] + 1;
|
|
ret.weights[dst] = weights[src];
|
|
sum += ret.weights[dst];
|
|
dst++;
|
|
}
|
|
|
|
ret.weights /= sum;
|
|
return ret;
|
|
}
|
|
|
|
std::vector<JointsAndWeights> extract_and_flatten_joints_and_weights(
|
|
const tinygltf::Model& model,
|
|
const tinygltf::Primitive& prim) {
|
|
auto weights =
|
|
extract_vec<float, 4>(model, prim.attributes.at("WEIGHTS_0"), TINYGLTF_COMPONENT_TYPE_FLOAT);
|
|
auto joints = extract_vec<u8, 4>(model, prim.attributes.at("JOINTS_0"),
|
|
TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE);
|
|
std::vector<JointsAndWeights> ret;
|
|
ASSERT(weights.size() == joints.size());
|
|
for (size_t i = 0; i < weights.size(); i++) {
|
|
ret.push_back(convert_per_vertex_data(weights[i], joints[i]));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
std::vector<math::Vector<u8, 4>> colors_from_attribute(const tinygltf::Model& model, int attrib) {
|
|
const auto attrib_accessor = model.accessors[attrib];
|
|
const auto& buffer_view = model.bufferViews[attrib_accessor.bufferView];
|
|
const auto& buffer = model.buffers[buffer_view.buffer];
|
|
const auto data_ptr = buffer.data.data() + buffer_view.byteOffset + attrib_accessor.byteOffset;
|
|
const auto byte_stride = attrib_accessor.ByteStride(buffer_view);
|
|
const auto count = attrib_accessor.count;
|
|
std::vector<math::Vector<u8, 4>> colors;
|
|
|
|
switch (attrib_accessor.type) {
|
|
case TINYGLTF_TYPE_VEC4:
|
|
switch (attrib_accessor.componentType) {
|
|
case TINYGLTF_COMPONENT_TYPE_FLOAT:
|
|
colors = extract_color_from_vec4_float(data_ptr, count, byte_stride);
|
|
break;
|
|
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
|
|
colors = extract_color_from_vec4_u16(data_ptr, count, byte_stride);
|
|
break;
|
|
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
|
|
colors = extract_color_from_vec4_u8(data_ptr, count, byte_stride);
|
|
break;
|
|
default:
|
|
lg::die("Unknown type for {}", attrib_accessor.componentType);
|
|
}
|
|
break;
|
|
case TINYGLTF_TYPE_VEC3:
|
|
switch (attrib_accessor.componentType) {
|
|
case TINYGLTF_COMPONENT_TYPE_FLOAT:
|
|
colors = extract_color_from_vec3_float(data_ptr, count, byte_stride);
|
|
break;
|
|
default:
|
|
lg::die("Unknown component type for vec3 color {}", attrib_accessor.componentType);
|
|
}
|
|
break;
|
|
default:
|
|
lg::die("Unknown attribute type for color {}", attrib_accessor.type);
|
|
}
|
|
return colors;
|
|
}
|
|
|
|
/*!
|
|
* Extract positions, colors, and normals from a mesh.
|
|
*/
|
|
ExtractedVertices gltf_vertices(const tinygltf::Model& model,
|
|
const std::map<std::string, int>& attributes,
|
|
const math::Matrix4f& w_T_local,
|
|
bool get_colors,
|
|
bool get_normals,
|
|
const std::string& debug_name) {
|
|
std::vector<tfrag3::PreloadedVertex> result;
|
|
std::vector<math::Vector<u8, 32>> vtx_colors;
|
|
|
|
{
|
|
const auto& position_attrib = attributes.find("POSITION");
|
|
ASSERT_MSG(position_attrib != attributes.end(), "Did not find position attribute.");
|
|
|
|
const auto attrib_accessor = model.accessors[position_attrib->second];
|
|
const auto& buffer_view = model.bufferViews[attrib_accessor.bufferView];
|
|
const auto& buffer = model.buffers[buffer_view.buffer];
|
|
const auto data_ptr = buffer.data.data() + buffer_view.byteOffset + attrib_accessor.byteOffset;
|
|
const auto byte_stride = attrib_accessor.ByteStride(buffer_view);
|
|
const auto count = attrib_accessor.count;
|
|
|
|
ASSERT_MSG(attrib_accessor.type == TINYGLTF_TYPE_VEC3, "POSITION wasn't vec3");
|
|
ASSERT_MSG(attrib_accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT,
|
|
"POSITION wasn't float");
|
|
// for (auto& attrib : attributes) {
|
|
// lg::print("attrib: {}\n", attrib.first);
|
|
//}
|
|
auto mesh_verts = extract_vec3f(data_ptr, count, byte_stride);
|
|
result.reserve(mesh_verts.size());
|
|
for (auto& vert : mesh_verts) {
|
|
auto& new_vert = result.emplace_back();
|
|
math::Vector4f v_in(vert.x(), vert.y(), vert.z(), 1);
|
|
math::Vector4f v_w = w_T_local * v_in;
|
|
new_vert.x = v_w.x() * 4096;
|
|
new_vert.y = v_w.y() * 4096;
|
|
new_vert.z = v_w.z() * 4096;
|
|
}
|
|
}
|
|
if (get_colors) {
|
|
std::array<std::pair<std::string, std::optional<std::vector<math::Vector<u8, 4>>>>, 8>
|
|
times_of_day = {{{"_SUNRISE", std::nullopt},
|
|
{"_MORNING", std::nullopt},
|
|
{"_NOON", std::nullopt},
|
|
{"_AFTERNOON", std::nullopt},
|
|
{"_SUNSET", std::nullopt},
|
|
{"_TWILIGHT", std::nullopt},
|
|
{"_EVENING", std::nullopt},
|
|
{"_GREENSUN", std::nullopt}}};
|
|
std::optional<std::vector<math::Vector<u8, 4>>> color0 = std::nullopt;
|
|
vtx_colors.resize(result.size());
|
|
bool found_one = false;
|
|
bool found_all = true;
|
|
|
|
for (auto& slot : times_of_day) {
|
|
if (const auto attr_val = attributes.find(slot.first); attr_val != attributes.end()) {
|
|
slot.second = colors_from_attribute(model, attr_val->second);
|
|
found_one = true;
|
|
} else {
|
|
found_all = false;
|
|
}
|
|
}
|
|
|
|
if (!found_one) {
|
|
if (const auto color0_iter = attributes.find("COLOR_0"); color0_iter != attributes.end()) {
|
|
color0 = colors_from_attribute(model, color0_iter->second);
|
|
lg::warn("{} had colors but no times of day, using COLOR_0.", debug_name);
|
|
} else {
|
|
const u32 WHITE_COLOR =
|
|
0xFF808080; // Because of little-endianness, FF is at the largest address in memory.
|
|
for (auto& vtx_color : vtx_colors) {
|
|
// Write white into the color slot for all times of day.
|
|
for (u8 byte_offset = 0; byte_offset < 32; byte_offset += 4) {
|
|
u8* target_ptr = vtx_color.data() + byte_offset;
|
|
std::memcpy(target_ptr, &WHITE_COLOR, sizeof(u32));
|
|
}
|
|
}
|
|
lg::error("{} didn't have any colors, using white.", debug_name);
|
|
}
|
|
}
|
|
|
|
if (found_one || color0.has_value()) {
|
|
if (found_all) {
|
|
lg::info("{} had all times of day.", debug_name);
|
|
} else if (found_one) {
|
|
std::string log_string = "";
|
|
for (size_t slot_index = 0; slot_index < times_of_day.size(); ++slot_index) {
|
|
auto& slot = times_of_day[slot_index];
|
|
std::string time_name = slot.first;
|
|
std::string value_name;
|
|
if (slot.second.has_value()) {
|
|
value_name = slot.first;
|
|
} else { // If this time_of_day lacks a color, use color of closest time of day.
|
|
for (int i = 1; !slot.second.has_value(); ++i) { // Takes at most 4 iterations.
|
|
// Look forward and backward for closest time of day color.
|
|
int neg_index = (slot_index - i + 8) % 8;
|
|
int pos_index = (slot_index + i + 8) % 8;
|
|
|
|
if (const auto& neg_slot = times_of_day[neg_index];
|
|
attributes.contains(neg_slot.first)) {
|
|
slot.second = neg_slot.second.value();
|
|
value_name = neg_slot.first;
|
|
} else if (const auto& pos_slot = times_of_day[pos_index];
|
|
attributes.contains(pos_slot.first)) {
|
|
slot.second = pos_slot.second.value();
|
|
value_name = pos_slot.first;
|
|
}
|
|
}
|
|
}
|
|
time_name = time_name.substr(1);
|
|
std::transform(time_name.begin(), time_name.end(), time_name.begin(),
|
|
[](unsigned char c) { return std::tolower(c); });
|
|
if (time_name == "greensun")
|
|
time_name = "green sun";
|
|
std::string mapping = time_name + ":" + value_name;
|
|
log_string += log_string.empty() ? mapping : ", " + mapping;
|
|
}
|
|
lg::warn("{} missing some times of day, using {}", debug_name, log_string);
|
|
}
|
|
|
|
// Create iterators to colors for each time of day.
|
|
std::array<std::vector<math::Vector<u8, 4>>::const_iterator, 8> iters;
|
|
for (int time = 0; time < 8; ++time) {
|
|
const std::vector<math::Vector<u8, 4>>& time_color = times_of_day[time].second.has_value()
|
|
? times_of_day[time].second.value()
|
|
: color0.value();
|
|
|
|
iters[time] = time_color.begin();
|
|
}
|
|
|
|
// Write the color for each time of day into vtx_colors.
|
|
for (auto& vtx_color : vtx_colors) {
|
|
for (int slot_index = 0; slot_index < 8; ++slot_index) {
|
|
u8* target_ptr = vtx_color.data() + (4 * slot_index);
|
|
auto& color_iter = iters[slot_index];
|
|
const u8* source_ptr = color_iter->data();
|
|
std::memcpy(target_ptr, source_ptr, sizeof(u32));
|
|
|
|
++color_iter; // Advance the iterator for this time of day.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool got_texture = false;
|
|
{
|
|
const auto& texcoord_attrib = attributes.find("TEXCOORD_0");
|
|
if (texcoord_attrib != attributes.end()) {
|
|
const auto attrib_accessor = model.accessors[texcoord_attrib->second];
|
|
const auto& buffer_view = model.bufferViews[attrib_accessor.bufferView];
|
|
const auto& buffer = model.buffers[buffer_view.buffer];
|
|
const auto data_ptr =
|
|
buffer.data.data() + buffer_view.byteOffset + attrib_accessor.byteOffset;
|
|
const auto byte_stride = attrib_accessor.ByteStride(buffer_view);
|
|
const auto count = attrib_accessor.count;
|
|
|
|
ASSERT_MSG(attrib_accessor.type == TINYGLTF_TYPE_VEC2, "TEXCOORD wasn't vec2");
|
|
ASSERT_MSG(attrib_accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT,
|
|
"TEXCOORD wasn't float");
|
|
auto mesh_verts = extract_vec2f(data_ptr, count, byte_stride);
|
|
ASSERT(mesh_verts.size() == result.size());
|
|
got_texture = true;
|
|
for (size_t i = 0; i < mesh_verts.size(); i++) {
|
|
result[i].s = mesh_verts[i].x();
|
|
result[i].t = mesh_verts[i].y();
|
|
}
|
|
} else {
|
|
if (!get_normals) {
|
|
// don't warn if we're just getting collision
|
|
lg::warn("No texcoord attribute for mesh: {}", debug_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<math::Vector3f> normals;
|
|
if (get_normals) {
|
|
const auto& normal_attrib = attributes.find("NORMAL");
|
|
if (normal_attrib != attributes.end()) {
|
|
const auto attrib_accessor = model.accessors[normal_attrib->second];
|
|
const auto& buffer_view = model.bufferViews[attrib_accessor.bufferView];
|
|
const auto& buffer = model.buffers[buffer_view.buffer];
|
|
const auto data_ptr =
|
|
buffer.data.data() + buffer_view.byteOffset + attrib_accessor.byteOffset;
|
|
const auto byte_stride = attrib_accessor.ByteStride(buffer_view);
|
|
const auto count = attrib_accessor.count;
|
|
|
|
ASSERT_MSG(attrib_accessor.type == TINYGLTF_TYPE_VEC3, "NORMAL wasn't vec3");
|
|
ASSERT_MSG(attrib_accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT,
|
|
"NORMAL wasn't float");
|
|
normals = extract_vec3f(data_ptr, count, byte_stride);
|
|
for (auto& nrm : normals) {
|
|
math::Vector4f nrm4(nrm.x(), nrm.y(), nrm.z(), 0.f);
|
|
// we found that normals aren't normalized if an object is scaled in blender.
|
|
nrm = (w_T_local * nrm4).xyz().normalized();
|
|
}
|
|
ASSERT(normals.size() == result.size());
|
|
} else {
|
|
lg::error("No NORMAL attribute for mesh: {}", debug_name);
|
|
}
|
|
}
|
|
|
|
for (auto& v : result) {
|
|
v.color_index = 0;
|
|
if (!got_texture) {
|
|
v.s = 0;
|
|
v.t = 0;
|
|
}
|
|
}
|
|
// TODO: other properties
|
|
return {result, vtx_colors, normals};
|
|
}
|
|
|
|
DrawMode make_default_draw_mode() {
|
|
DrawMode mode;
|
|
mode.set_depth_write_enable(true);
|
|
mode.set_depth_test(GsTest::ZTest::GEQUAL);
|
|
mode.set_alpha_blend(DrawMode::AlphaBlend::DISABLED);
|
|
mode.set_aref(0);
|
|
mode.set_alpha_fail(GsTest::AlphaFail::KEEP);
|
|
mode.set_clamp_s_enable(false);
|
|
mode.set_clamp_t_enable(false);
|
|
mode.disable_filt(); // for checkerboard...
|
|
mode.enable_tcc(); // ?
|
|
mode.disable_at();
|
|
mode.enable_zt();
|
|
mode.disable_ab();
|
|
mode.disable_decal();
|
|
mode.enable_fog();
|
|
return mode;
|
|
}
|
|
|
|
int texture_pool_debug_checker(TexturePool* pool) {
|
|
const auto& existing = pool->textures_by_name.find("DEBUG_CHECKERBOARD");
|
|
if (existing == pool->textures_by_name.end() || existing->second.size() == 0) {
|
|
size_t idx = pool->textures_by_idx.size();
|
|
std::vector<int> v;
|
|
v.push_back(idx);
|
|
pool->textures_by_name["DEBUG_CHECKERBOARD"] = v;
|
|
auto& tex = pool->textures_by_idx.emplace_back();
|
|
tex.w = 16;
|
|
tex.h = 16;
|
|
tex.debug_name = "DEBUG_CHECKERBOARD";
|
|
tex.debug_tpage_name = "DEBUG";
|
|
tex.load_to_pool = false;
|
|
tex.combo_id = 0; // doesn't matter, not a pool tex
|
|
tex.data.resize(16 * 16);
|
|
u32 c0 = 0xa0303030;
|
|
u32 c1 = 0xa0e0e0e0;
|
|
for (int i = 0; i < 16; i++) {
|
|
for (int j = 0; j < 16; j++) {
|
|
tex.data[i * 16 + j] = (((i / 4) & 1) ^ ((j / 4) & 1)) ? c1 : c0;
|
|
}
|
|
}
|
|
return idx;
|
|
} else {
|
|
return existing->second[0];
|
|
}
|
|
}
|
|
|
|
int texture_pool_add_texture(TexturePool* pool, const tinygltf::Image& tex, int alpha_shift) {
|
|
const auto& existing = pool->textures_by_name.find(tex.name);
|
|
if (existing != pool->textures_by_name.end() && existing->second.size() > 0) {
|
|
// name was found, see if we have a match on height+width+data
|
|
for (int ti : existing->second) {
|
|
auto& t = pool->textures_by_idx[ti];
|
|
if (t.w == tex.width && t.h == tex.height &&
|
|
memcmp(t.data.data(), tex.image.data(), t.data.size() * 4) == 0) {
|
|
lg::info("Reusing image: {} at idx {}", tex.name, ti);
|
|
return ti;
|
|
}
|
|
}
|
|
} else {
|
|
// new name, insert empty vector
|
|
std::vector<int> v;
|
|
pool->textures_by_name[tex.name] = v;
|
|
}
|
|
|
|
lg::info("adding new texture: {}, size {} kB", tex.name, tex.width * tex.height * 4 / 1024);
|
|
|
|
ASSERT(tex.bits == 8);
|
|
ASSERT(tex.component == 4);
|
|
ASSERT(tex.pixel_type == TINYGLTF_TEXTURE_TYPE_UNSIGNED_BYTE);
|
|
|
|
size_t idx = pool->textures_by_idx.size();
|
|
pool->textures_by_name[tex.name].push_back(idx);
|
|
auto& tt = pool->textures_by_idx.emplace_back();
|
|
tt.w = tex.width;
|
|
tt.h = tex.height;
|
|
tt.debug_name = tex.name;
|
|
tt.debug_tpage_name = "custom-level";
|
|
tt.load_to_pool = false;
|
|
tt.combo_id = 0; // doesn't matter, not a pool tex
|
|
tt.data.resize(tt.w * tt.h);
|
|
ASSERT(tex.image.size() >= tt.data.size());
|
|
memcpy(tt.data.data(), tex.image.data(), tt.data.size() * 4);
|
|
|
|
// adjust alpha colors for PS2/PC difference
|
|
for (auto& color : tt.data) {
|
|
u32 alpha = color >> 24;
|
|
alpha >>= alpha_shift;
|
|
color &= 0xff'ff'ff;
|
|
color |= (alpha << 24);
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
int texture_pool_add_envmap_control_texture(TexturePool* pool,
|
|
const tinygltf::Model& model,
|
|
int rgb_image_id,
|
|
int mr_image_id,
|
|
bool wrap_w,
|
|
bool wrap_h) {
|
|
const auto& existing = pool->envmap_textures_by_gltf_id.find({rgb_image_id, mr_image_id});
|
|
if (existing != pool->envmap_textures_by_gltf_id.end()) {
|
|
lg::info("Reusing envmap textures");
|
|
return existing->second;
|
|
}
|
|
const auto& rgb_tex = model.images.at(rgb_image_id);
|
|
const auto& mr_tex = model.images.at(mr_image_id);
|
|
lg::info("new envmap texture {} {}", rgb_tex.name, mr_tex.name);
|
|
ASSERT(rgb_tex.bits == 8);
|
|
ASSERT(rgb_tex.component == 4);
|
|
ASSERT(rgb_tex.pixel_type == TINYGLTF_TEXTURE_TYPE_UNSIGNED_BYTE);
|
|
|
|
ASSERT(mr_tex.bits == 8);
|
|
ASSERT(mr_tex.pixel_type == TINYGLTF_TEXTURE_TYPE_UNSIGNED_BYTE);
|
|
ASSERT(mr_tex.component == 4);
|
|
|
|
std::vector<u8> resized_mr_tex;
|
|
const u8* mr_src;
|
|
if (rgb_tex.width == mr_tex.width && rgb_tex.height == mr_tex.height) {
|
|
mr_src = mr_tex.image.data();
|
|
} else {
|
|
resized_mr_tex.resize(rgb_tex.width * rgb_tex.height * 4);
|
|
resize_rgba_image(resized_mr_tex.data(), rgb_tex.width, rgb_tex.height, mr_tex.image.data(),
|
|
mr_tex.width, mr_tex.height, wrap_w, wrap_h);
|
|
mr_src = resized_mr_tex.data();
|
|
}
|
|
|
|
size_t idx = pool->textures_by_idx.size();
|
|
pool->envmap_textures_by_gltf_id[{rgb_image_id, mr_image_id}] = idx;
|
|
auto& tt = pool->textures_by_idx.emplace_back();
|
|
tt.w = rgb_tex.width;
|
|
tt.h = rgb_tex.height;
|
|
tt.debug_name = rgb_tex.name;
|
|
tt.debug_tpage_name = "custom-level";
|
|
tt.load_to_pool = false;
|
|
tt.combo_id = 0; // doesn't matter, not a pool tex
|
|
tt.data.resize(tt.w * tt.h);
|
|
ASSERT(rgb_tex.image.size() >= tt.data.size());
|
|
memcpy(tt.data.data(), rgb_tex.image.data(), tt.data.size() * 4);
|
|
|
|
// adjust alpha from metallic channel
|
|
for (size_t i = 0; i < tt.data.size(); i++) {
|
|
u32 rgb = tt.data[i];
|
|
u32 metal = mr_src[4 * i + 2] / 4;
|
|
rgb &= 0xff'ff'ff;
|
|
rgb |= (metal << 24);
|
|
tt.data[i] = rgb;
|
|
}
|
|
|
|
return idx;
|
|
}
|
|
|
|
math::Matrix4f affine_translation(const math::Vector3f& translation) {
|
|
math::Matrix4f result = math::Matrix4f::identity();
|
|
result(0, 3) = translation[0];
|
|
result(1, 3) = translation[1];
|
|
result(2, 3) = translation[2];
|
|
result(3, 3) = 1;
|
|
return result;
|
|
}
|
|
|
|
math::Matrix4f affine_scale(const math::Vector3f& scale) {
|
|
math::Matrix4f result = math::Matrix4f::zero();
|
|
result(0, 0) = scale[0];
|
|
result(1, 1) = scale[1];
|
|
result(2, 2) = scale[2];
|
|
result(3, 3) = 1;
|
|
return result;
|
|
}
|
|
|
|
math::Matrix4f affine_rot_qxyzw(const math::Vector4f& quat) {
|
|
math::Matrix4f result = math::Matrix4f::zero();
|
|
result(3, 3) = 1;
|
|
result(0, 0) = 1.0 - 2.0 * (quat.y() * quat.y() + quat.z() * quat.z());
|
|
result(0, 1) = 2.0 * (quat.x() * quat.y() - quat.z() * quat.w());
|
|
result(0, 2) = 2.0 * (quat.x() * quat.z() + quat.y() * quat.w());
|
|
result(1, 0) = 2.0 * (quat.x() * quat.y() + quat.z() * quat.w());
|
|
result(1, 1) = 1.0 - 2.0 * (quat.x() * quat.x() + quat.z() * quat.z());
|
|
result(1, 2) = 2.0 * (quat.y() * quat.z() - quat.x() * quat.w());
|
|
result(2, 0) = 2.0 * (quat.x() * quat.z() - quat.y() * quat.w());
|
|
result(2, 1) = 2.0 * (quat.y() * quat.z() + quat.x() * quat.w());
|
|
result(2, 2) = 1.0 - 2.0 * (quat.x() * quat.x() + quat.y() * quat.y());
|
|
return result;
|
|
}
|
|
|
|
math::Vector3f vector3f_from_gltf(const std::vector<double>& in) {
|
|
ASSERT(in.size() == 3);
|
|
return math::Vector3f{in[0], in[1], in[2]};
|
|
}
|
|
|
|
math::Vector4f vector4f_from_gltf(const std::vector<double>& in) {
|
|
ASSERT(in.size() == 4);
|
|
return math::Vector4f{in[0], in[1], in[2], in[3]};
|
|
}
|
|
|
|
math::Matrix4f matrix_from_trs(const math::Vector3f& trans,
|
|
const math::Vector4f& quat,
|
|
const math::Vector3f& scale) {
|
|
math::Matrix4f t = affine_translation(trans);
|
|
math::Matrix4f r = affine_rot_qxyzw(quat);
|
|
math::Matrix4f s = affine_scale(scale);
|
|
return t * r * s;
|
|
}
|
|
|
|
math::Matrix4f matrix_from_node(const tinygltf::Node& node) {
|
|
if (!node.matrix.empty()) {
|
|
math::Matrix4f result;
|
|
for (int i = 0; i < 16; i++) {
|
|
result.data()[i] = node.matrix[i];
|
|
}
|
|
return result;
|
|
} else {
|
|
// from trs
|
|
math::Matrix4f t, r, s;
|
|
if (!node.translation.empty()) {
|
|
t = affine_translation(vector3f_from_gltf(node.translation));
|
|
} else {
|
|
t = math::Matrix4f::identity();
|
|
}
|
|
|
|
if (!node.rotation.empty()) {
|
|
r = affine_rot_qxyzw(vector4f_from_gltf(node.rotation));
|
|
} else {
|
|
r = math::Matrix4f::identity();
|
|
}
|
|
|
|
if (!node.scale.empty()) {
|
|
s = affine_scale(vector3f_from_gltf(node.scale));
|
|
} else {
|
|
s = math::Matrix4f::identity();
|
|
}
|
|
|
|
return t * r * s;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Recursively walk the tree of nodes, flatten, and compute w_T_node for each.
|
|
*/
|
|
void node_find_helper(const tinygltf::Model& model,
|
|
const math::Matrix4f& w_T_parent,
|
|
int node_idx,
|
|
std::vector<NodeWithTransform>* out) {
|
|
const auto& node = model.nodes.at(node_idx);
|
|
math::Matrix4f w_T_node = w_T_parent * matrix_from_node(node);
|
|
out->push_back({node_idx, w_T_node});
|
|
for (auto& child : node.children) {
|
|
node_find_helper(model, w_T_node, child, out);
|
|
}
|
|
}
|
|
|
|
std::vector<NodeWithTransform> flatten_nodes_from_all_scenes(const tinygltf::Model& model) {
|
|
std::vector<NodeWithTransform> out;
|
|
for (auto& scene : model.scenes) {
|
|
for (auto& nidx : scene.nodes) {
|
|
math::Matrix4f identity = math::Matrix4f::identity();
|
|
node_find_helper(model, identity, nidx, &out);
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
void dedup_vertices(const std::vector<tfrag3::PreloadedVertex>& vertices_in,
|
|
std::vector<tfrag3::PreloadedVertex>& vertices_out,
|
|
std::vector<u32>& old_to_new_out) {
|
|
ASSERT(vertices_out.empty());
|
|
ASSERT(old_to_new_out.empty());
|
|
old_to_new_out.resize(vertices_in.size(), -1);
|
|
|
|
std::unordered_map<tfrag3::PreloadedVertex, u32, tfrag3::PreloadedVertex::hash> vtx_to_new;
|
|
|
|
for (size_t in_idx = 0; in_idx < vertices_in.size(); in_idx++) {
|
|
auto& vtx = vertices_in[in_idx];
|
|
const auto& lookup = vtx_to_new.find(vtx);
|
|
if (lookup == vtx_to_new.end()) {
|
|
// first time seeing this one
|
|
size_t new_idx = vertices_out.size();
|
|
vertices_out.push_back(vtx);
|
|
old_to_new_out[in_idx] = new_idx;
|
|
vtx_to_new[vtx] = new_idx;
|
|
} else {
|
|
old_to_new_out[in_idx] = lookup->second;
|
|
}
|
|
}
|
|
}
|
|
|
|
void setup_alpha_from_material(const tinygltf::Material& material, DrawMode* mode) {
|
|
if (material.alphaMode == "OPAQUE") {
|
|
mode->disable_ab();
|
|
} else if (material.alphaMode == "MASK") {
|
|
mode->enable_at();
|
|
mode->set_alpha_test(DrawMode::AlphaTest::GEQUAL);
|
|
mode->set_alpha_fail(GsTest::AlphaFail::KEEP);
|
|
mode->set_aref(material.alphaCutoff * 127);
|
|
} else if (material.alphaMode == "BLEND") {
|
|
mode->enable_ab();
|
|
mode->set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_SRC_DST);
|
|
mode->set_depth_write_enable(false);
|
|
} else {
|
|
lg::die("Unknown GLTF alphaMode {}", material.alphaMode);
|
|
}
|
|
}
|
|
|
|
void setup_draw_mode_from_sampler(const tinygltf::Sampler& sampler, DrawMode* mode) {
|
|
if (sampler.magFilter == TINYGLTF_TEXTURE_FILTER_NEAREST) {
|
|
ASSERT(sampler.minFilter == TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST);
|
|
mode->set_filt_enable(false);
|
|
} else {
|
|
ASSERT(sampler.minFilter != TINYGLTF_TEXTURE_FILTER_NEAREST);
|
|
mode->set_filt_enable(true);
|
|
}
|
|
|
|
switch (sampler.wrapS) {
|
|
case TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE:
|
|
mode->set_clamp_s_enable(true);
|
|
break;
|
|
case TINYGLTF_TEXTURE_WRAP_REPEAT:
|
|
mode->set_clamp_s_enable(false);
|
|
break;
|
|
default:
|
|
ASSERT(false);
|
|
}
|
|
|
|
switch (sampler.wrapT) {
|
|
case TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE:
|
|
mode->set_clamp_t_enable(true);
|
|
break;
|
|
case TINYGLTF_TEXTURE_WRAP_REPEAT:
|
|
mode->set_clamp_t_enable(false);
|
|
break;
|
|
default:
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
|
|
EnvmapSettings envmap_settings_from_gltf(const tinygltf::Material& mat) {
|
|
EnvmapSettings settings;
|
|
|
|
ASSERT(mat.extensions.contains("KHR_materials_specular"));
|
|
const auto& specular_extension = mat.extensions.at("KHR_materials_specular");
|
|
ASSERT(specular_extension.Has("specularColorTexture"));
|
|
|
|
auto& texture = specular_extension.Get("specularColorTexture");
|
|
ASSERT(texture.Has("index"));
|
|
settings.texture_idx = texture.Get("index").Get<int>();
|
|
return settings;
|
|
}
|
|
|
|
bool material_has_envmap(const tinygltf::Material& mat) {
|
|
return mat.extensions.contains("KHR_materials_specular");
|
|
}
|
|
|
|
bool envmap_is_valid(const tinygltf::Material& mat) {
|
|
auto envmap = material_has_envmap(mat);
|
|
if (envmap && mat.pbrMetallicRoughness.metallicRoughnessTexture.index < 0) {
|
|
lg::warn(fmt::format(
|
|
"Material \"{}\" has specular property set, but is missing a metallic roughness texture, "
|
|
"ignoring envmap!",
|
|
mat.name));
|
|
return false;
|
|
}
|
|
return envmap;
|
|
}
|
|
|
|
std::optional<int> find_single_skin(const tinygltf::Model& model,
|
|
const std::vector<NodeWithTransform>& all_nodes) {
|
|
std::optional<int> skin_index;
|
|
for (const auto& n : all_nodes) {
|
|
const auto& node = model.nodes.at(n.node_idx);
|
|
if (node.skin >= 0) {
|
|
if (skin_index && *skin_index != node.skin) {
|
|
lg::die("GLTF contains multiple skins, but only one skin per actor is supported.");
|
|
}
|
|
skin_index = node.skin;
|
|
}
|
|
}
|
|
return skin_index;
|
|
}
|
|
|
|
int get_joint_count(const tinygltf::Model& model, int skin_idx) {
|
|
const auto& skin = model.skins.at(skin_idx);
|
|
return skin.joints.size();
|
|
}
|
|
|
|
std::vector<float> extract_floats(const tinygltf::Model& model, int accessor_idx) {
|
|
const auto& accessor = model.accessors[accessor_idx];
|
|
const auto& buffer_view = model.bufferViews[accessor.bufferView];
|
|
const auto& buffer = model.buffers[buffer_view.buffer];
|
|
const u8* data_ptr = buffer.data.data() + buffer_view.byteOffset + accessor.byteOffset;
|
|
const auto stride = accessor.ByteStride(buffer_view);
|
|
const auto count = accessor.count;
|
|
|
|
// ASSERT(buffer_view.target == TINYGLTF_TARGET_ARRAY_BUFFER); // ??
|
|
if (accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) {
|
|
lg::die("mismatched format, wanted {} but got {}", TINYGLTF_COMPONENT_TYPE_FLOAT,
|
|
accessor.componentType);
|
|
}
|
|
ASSERT(accessor.type == TINYGLTF_TYPE_SCALAR);
|
|
|
|
std::vector<float> result(accessor.count);
|
|
for (size_t x = 0; x < count; x++) {
|
|
memcpy(&result[x], data_ptr, sizeof(float));
|
|
data_ptr += stride;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::size_t TieFullVertex::hash::operator()(const TieFullVertex& x) const {
|
|
return tfrag3::PackedTieVertices::Vertex::hash()(x.vertex) ^ std::hash<u16>()(x.color_index);
|
|
}
|
|
|
|
tfrag3::PackedTimeOfDay pack_time_of_day(const std::vector<math::Vector<u8, 32>>& color_palette) {
|
|
tfrag3::PackedTimeOfDay colors;
|
|
colors.color_count = (color_palette.size() + 3) & (~3);
|
|
colors.data.resize(colors.color_count * 8 * 4);
|
|
for (u32 color_index = 0; color_index < color_palette.size(); color_index++) {
|
|
for (u32 palette = 0; palette < 8; palette++) {
|
|
for (u32 channel = 0; channel < 4; channel++) {
|
|
colors.read(color_index, palette, channel) =
|
|
color_palette[color_index][4 * palette + channel];
|
|
}
|
|
}
|
|
}
|
|
return colors;
|
|
}
|
|
|
|
void process_normal_merc_draw(const tinygltf::Model& model,
|
|
MercExtractData& out,
|
|
u32 tex_offset,
|
|
tfrag3::MercEffect& eff,
|
|
int mat_idx,
|
|
const tfrag3::MercDraw& d_) {
|
|
const auto& mat = model.materials[mat_idx];
|
|
eff.all_draws.push_back(d_);
|
|
auto& draw = eff.all_draws.back();
|
|
draw.mode = gltf_util::make_default_draw_mode();
|
|
|
|
if (mat_idx == -1) {
|
|
lg::warn("Draw had a material index of -1, using default texture.");
|
|
draw.tree_tex_id = 0;
|
|
return;
|
|
}
|
|
int tex_idx = mat.pbrMetallicRoughness.baseColorTexture.index;
|
|
if (tex_idx == -1) {
|
|
lg::warn("Material {} has no texture, using default texture.", mat.name);
|
|
draw.tree_tex_id = 0;
|
|
return;
|
|
}
|
|
|
|
const auto& tex = model.textures[tex_idx];
|
|
ASSERT(tex.sampler >= 0);
|
|
ASSERT(tex.source >= 0);
|
|
gltf_util::setup_draw_mode_from_sampler(model.samplers.at(tex.sampler), &draw.mode);
|
|
gltf_util::setup_alpha_from_material(mat, &draw.mode);
|
|
|
|
const auto& img = model.images[tex.source];
|
|
draw.tree_tex_id = tex_offset + texture_pool_add_texture(&out.tex_pool, img);
|
|
};
|
|
|
|
void process_envmap_merc_draw(const tinygltf::Model& model,
|
|
MercExtractData& out,
|
|
u32 tex_offset,
|
|
tfrag3::MercEffect& eff,
|
|
int mat_idx,
|
|
const tfrag3::MercDraw& d_) {
|
|
const auto& mat = model.materials[mat_idx];
|
|
eff.all_draws.push_back(d_);
|
|
auto& draw = eff.all_draws.back();
|
|
draw.mode = gltf_util::make_default_draw_mode();
|
|
|
|
if (mat_idx == -1) {
|
|
lg::warn("Envmap draw had a material index of -1, using default texture.");
|
|
draw.tree_tex_id = texture_pool_debug_checker(&out.tex_pool);
|
|
return;
|
|
}
|
|
int base_tex_idx = mat.pbrMetallicRoughness.baseColorTexture.index;
|
|
if (base_tex_idx == -1) {
|
|
lg::warn("Envmap material {} has no texture, using default texture.", mat.name);
|
|
draw.tree_tex_id = texture_pool_debug_checker(&out.tex_pool);
|
|
return;
|
|
}
|
|
|
|
const auto& base_tex = model.textures[base_tex_idx];
|
|
ASSERT(base_tex.sampler >= 0);
|
|
ASSERT(base_tex.source >= 0);
|
|
gltf_util::setup_draw_mode_from_sampler(model.samplers.at(base_tex.sampler), &draw.mode);
|
|
gltf_util::setup_alpha_from_material(mat, &draw.mode);
|
|
const auto& roughness_tex =
|
|
model.textures.at(mat.pbrMetallicRoughness.metallicRoughnessTexture.index);
|
|
ASSERT(roughness_tex.sampler >= 0);
|
|
ASSERT(roughness_tex.source >= 0);
|
|
|
|
draw.tree_tex_id =
|
|
tex_offset + gltf_util::texture_pool_add_envmap_control_texture(
|
|
&out.tex_pool, model, base_tex.source, roughness_tex.source,
|
|
!draw.mode.get_clamp_s_enable(), !draw.mode.get_clamp_t_enable());
|
|
|
|
// now, setup envmap draw:
|
|
auto envmap_settings = gltf_util::envmap_settings_from_gltf(mat);
|
|
const auto& envmap_tex = model.textures[envmap_settings.texture_idx];
|
|
ASSERT(envmap_tex.sampler >= 0);
|
|
ASSERT(envmap_tex.source >= 0);
|
|
auto env_mode = gltf_util::make_default_draw_mode();
|
|
gltf_util::setup_draw_mode_from_sampler(model.samplers.at(envmap_tex.sampler), &env_mode);
|
|
eff.envmap_texture = tex_offset + gltf_util::texture_pool_add_texture(
|
|
&out.tex_pool, model.images[envmap_tex.source]);
|
|
env_mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_0_DST_DST);
|
|
env_mode.enable_ab();
|
|
eff.envmap_mode = env_mode;
|
|
};
|
|
|
|
} // namespace gltf_util
|