diff --git a/common/custom_data/TFrag3Data.cpp b/common/custom_data/TFrag3Data.cpp index cd46ba29e..d850dd720 100644 --- a/common/custom_data/TFrag3Data.cpp +++ b/common/custom_data/TFrag3Data.cpp @@ -233,6 +233,10 @@ void Texture::serialize(Serializer& ser) { ser.from_ptr(&load_to_pool); } +void CollisionMesh::serialize(Serializer& ser) { + ser.from_pod_vector(&vertices); +} + void Level::serialize(Serializer& ser) { ser.from_ptr(&version); if (ser.is_loading() && version != TFRAG3_VERSION) { @@ -282,6 +286,8 @@ void Level::serialize(Serializer& ser) { tree.serialize(ser); } + collision.serialize(ser); + ser.from_ptr(&version2); if (ser.is_loading() && version2 != TFRAG3_VERSION) { ASSERT_MSG(false, fmt::format( @@ -352,6 +358,9 @@ std::array Level::get_memory_usage() c result[SHRUB_IND] += sizeof(u32) * shrub_tree.indices.size(); } + // collision + result[COLLISION] += sizeof(CollisionMesh::Vertex) * collision.vertices.size(); + return result; } diff --git a/common/custom_data/Tfrag3Data.h b/common/custom_data/Tfrag3Data.h index 50a895a55..a22a1dbd1 100644 --- a/common/custom_data/Tfrag3Data.h +++ b/common/custom_data/Tfrag3Data.h @@ -44,10 +44,13 @@ enum MemoryUsageCategory { SHRUB_TIME_OF_DAY, SHRUB_VERT, SHRUB_IND, + + COLLISION, + NUM_CATEGORIES }; -constexpr int TFRAG3_VERSION = 14; +constexpr int TFRAG3_VERSION = 15; // These vertices should be uploaded to the GPU at load time and don't change struct PreloadedVertex { @@ -306,6 +309,18 @@ struct ShrubTree { void unpack(); }; +struct CollisionMesh { + struct Vertex { + float x, y, z; + u32 flags; + s16 nx, ny, nz; + u16 pad; + }; + static_assert(sizeof(Vertex) == 24); + std::vector vertices; + void serialize(Serializer& ser); +}; + constexpr int TFRAG_GEOS = 3; constexpr int TIE_GEOS = 4; @@ -316,6 +331,7 @@ struct Level { std::array, TFRAG_GEOS> tfrag_trees; std::array, TIE_GEOS> tie_trees; std::vector shrub_trees; + CollisionMesh collision; u16 version2 = TFRAG3_VERSION; void serialize(Serializer& ser); diff --git a/common/math/Vector.h b/common/math/Vector.h index 6774f64bb..bb0ba31a1 100644 --- a/common/math/Vector.h +++ b/common/math/Vector.h @@ -96,6 +96,13 @@ class Vector { return *this; } + Vector& operator-=(const Vector& other) { + for (int i = 0; i < Size; i++) { + m_data[i] -= other[i]; + } + return *this; + } + Vector elementwise_multiply(const Vector& other) const { Vector result; for (int i = 0; i < Size; i++) { diff --git a/decompiler/CMakeLists.txt b/decompiler/CMakeLists.txt index 1f64eabc2..c623693a4 100644 --- a/decompiler/CMakeLists.txt +++ b/decompiler/CMakeLists.txt @@ -51,10 +51,11 @@ add_library( IR2/LabelDB.cpp IR2/OpenGoalMapping.cpp + level_extractor/BspHeader.cpp + level_extractor/extract_collide_frags.cpp level_extractor/extract_level.cpp level_extractor/extract_tfrag.cpp level_extractor/extract_tie.cpp - level_extractor/BspHeader.cpp level_extractor/extract_shrub.cpp ObjectFile/LinkedObjectFile.cpp diff --git a/decompiler/config.cpp b/decompiler/config.cpp index 9654568f2..75324c5b2 100644 --- a/decompiler/config.cpp +++ b/decompiler/config.cpp @@ -67,6 +67,7 @@ Config read_config_file(const std::string& path_to_config_file, config.generate_symbol_definition_map = cfg.at("generate_symbol_definition_map").get(); config.is_pal = cfg.at("is_pal").get(); config.rip_levels = cfg.at("levels_convert_to_obj").get(); + config.extract_collision = cfg.at("extract_collision").get(); auto allowed = cfg.at("allowed_objects").get>(); for (const auto& x : allowed) { diff --git a/decompiler/config.h b/decompiler/config.h index 52a61c6e2..876ade06b 100644 --- a/decompiler/config.h +++ b/decompiler/config.h @@ -100,6 +100,7 @@ struct Config { bool process_game_text = false; bool process_game_count = false; bool rip_levels = false; + bool extract_collision = false; bool write_hex_near_instructions = false; bool hexdump_code = false; diff --git a/decompiler/config/jak1_ntsc_black_label.jsonc b/decompiler/config/jak1_ntsc_black_label.jsonc index 2c9038279..517131699 100644 --- a/decompiler/config/jak1_ntsc_black_label.jsonc +++ b/decompiler/config/jak1_ntsc_black_label.jsonc @@ -86,5 +86,8 @@ // turn this on to extract level background graphics data "levels_extract": true, // turn this on if you want extracted levels to be saved out as .obj files - "levels_convert_to_obj": false + "levels_convert_to_obj": false, + // should we extract collision meshes? + // these can be displayed in game, but makes the .fr3 files slightly larger + "extract_collision": true } diff --git a/decompiler/extractor/main.cpp b/decompiler/extractor/main.cpp index 943352bec..702155bdb 100644 --- a/decompiler/extractor/main.cpp +++ b/decompiler/extractor/main.cpp @@ -290,7 +290,7 @@ void decompile(std::filesystem::path jak1_input_files) { // levels { extract_all_levels(db, tex_db, config.levels_to_extract, "GAME.CGO", config.hacks, - config.rip_levels); + config.rip_levels, config.extract_collision); } } diff --git a/decompiler/level_extractor/BspHeader.cpp b/decompiler/level_extractor/BspHeader.cpp index e27016785..1247c3218 100644 --- a/decompiler/level_extractor/BspHeader.cpp +++ b/decompiler/level_extractor/BspHeader.cpp @@ -59,6 +59,14 @@ std::string Vector::print_meters(int indent) const { return result; } +std::string Vector::print_decimal(int indent) const { + s32 d[4]; + memcpy(d, data, 16); + std::string is(indent, ' '); + std::string result; + result += fmt::format("{}\n", is, d[0], d[1], d[2], d[3]); + return result; +} void FileInfo::read_from_file(TypedRef ref, const decompiler::DecompilerTypeSystem& dts) { file_type = read_type_field(ref, "file-type", dts, true); file_name = read_string_field(ref, "file-name", dts, true); @@ -887,6 +895,15 @@ void PrototypeBucketTie::read_from_file(TypedRef ref, dists.read_from_file(get_field_ref(ref, "dists", dts)); rdists.read_from_file(get_field_ref(ref, "rdists", dts)); stiffness = read_plain_data_field(ref, "stiffness", dts); + auto fr = get_field_ref(ref, "collide-frag", dts); + { + const auto& word = fr.data->words_by_seg.at(fr.seg).at(fr.byte_offset / 4); + if (word.kind() == decompiler::LinkedWord::PTR) { + auto p = deref_label(fr); + p.byte_offset -= 4; + collide_frag.read_from_file(typed_ref_from_basic(p, dts), dts, stats); + } + } auto next_slot = get_field_ref(ref, "next", dts); for (int i = 0; i < 4; i++) { @@ -1117,6 +1134,120 @@ std::string DrawableTreeInstanceTie::my_type() const { return "drawable-tree-instance-tie"; } +void DrawableTreeCollideFragment::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + s16 length = read_plain_data_field(ref, "length", dts); + auto data_ref = get_field_ref(ref, "data", dts); + if ((data_ref.byte_offset % 4) != 0) { + throw Error("misaligned data array"); + } + + Ref array_slot_ref = data_ref; + array_slot_ref.byte_offset += (length - 1) * 4; + + Ref object_ref = deref_label(array_slot_ref); + object_ref.byte_offset -= 4; + last_array.read_from_file(typed_ref_from_basic(object_ref, dts), dts, stats); +} + +std::string DrawableTreeCollideFragment::print(const PrintSettings& settings, int indent) const { + return last_array.print(settings, indent); +} + +std::string DrawableTreeCollideFragment::my_type() const { + return "drawable-tree-collide-fragment"; +} + +void DrawableInlineArrayCollideFragment::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + ASSERT(ref.type->get_name() == "drawable-inline-array-collide-fragment"); + id = read_plain_data_field(ref, "id", dts); + length = read_plain_data_field(ref, "length", dts); + bsphere.read_from_file(get_field_ref(ref, "bsphere", dts)); + + auto data_ref = get_field_ref(ref, "data", dts); + for (int i = 0; i < length; i++) { + Ref obj_ref = data_ref; + obj_ref.byte_offset += 32 * i; // todo not a constant here + auto type = get_type_of_basic(obj_ref); + if (type != "collide-fragment") { + throw Error("bad collide fragment type: {}", type); + } + collide_fragments.emplace_back(); + collide_fragments.back().read_from_file(typed_ref_from_basic(obj_ref, dts), dts, stats); + } +} + +std::string DrawableInlineArrayCollideFragment::print(const PrintSettings& settings, + int indent) const { + std::string is(indent, ' '); + std::string result; + int next_indent = indent + 4; + result += fmt::format("{}id: {}\n", is, id); + result += fmt::format("{}length: {}\n", is, length); + result += fmt::format("{}bsphere: {}", is, bsphere.print_meters()); + + if (settings.expand_collide) { + for (u32 i = 0; i < collide_fragments.size(); i++) { + result += fmt::format("{}data [{}]:\n", is, i); + result += collide_fragments[i].print(settings, next_indent); + } + } + return result; +} + +std::string DrawableInlineArrayCollideFragment::my_type() const { + return "drawable-inline-array-collide-fragment"; +} + +void CollideFragment::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + bsphere.read_from_file(get_field_ref(ref, "bsphere", dts)); + auto r = deref_label(get_field_ref(ref, "mesh", dts)); + r.byte_offset -= 4; + mesh.read_from_file(typed_ref_from_basic(r, dts), dts, stats); +} + +std::string CollideFragment::print(const PrintSettings& settings, int indent) const { + std::string is(indent, ' '); + std::string result; + result += fmt::format("{}bsphere: {}", is, bsphere.print_meters()); + result += mesh.print(settings, indent); + + return result; +} + +void CollideFragMesh::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + strip_data_len = read_plain_data_field(ref, "strip-data-len", dts); + poly_count = read_plain_data_field(ref, "poly-count", dts); + vertex_count = read_plain_data_field(ref, "vertex-count", dts); + vertex_data_qwc = read_plain_data_field(ref, "vertex-data-qwc", dts); + total_qwc = read_plain_data_field(ref, "total-qwc", dts); + base_trans.read_from_file(get_field_ref(ref, "base-trans", dts)); + base_trans.data[3] = 0; + + packed_data = deref_label(get_field_ref(ref, "packed-data", dts)); + pat_array = deref_label(get_field_ref(ref, "pat-array", dts)); +} + +std::string CollideFragMesh::print(const PrintSettings& settings, int indent) const { + std::string is(indent, ' '); + std::string result; + result += fmt::format("{}strip-data-len: {}\n", is, strip_data_len); + result += fmt::format("{}poly-count: {}\n", is, poly_count); + result += fmt::format("{}vertex-count: {}\n", is, vertex_count); + result += fmt::format("{}vertex-data-qwc: {}\n", is, vertex_data_qwc); + result += fmt::format("{}total-qwc: {}\n", is, total_qwc); + result += fmt::format("{}base-trans: {}", is, base_trans.print_decimal()); + + return result; +} + ////////////////////////// // shrub ////////////////////////// @@ -1584,6 +1715,13 @@ std::unique_ptr make_drawable_tree(TypedRef ref, tree->read_from_file(ref, dts, stats); return tree; } + + if (ref.type->get_name() == "drawable-tree-collide-fragment") { + auto tree = std::make_unique(); + tree->read_from_file(ref, dts, stats); + return tree; + } + auto tree = std::make_unique(); tree->read_from_file(ref, dts, stats); return tree; diff --git a/decompiler/level_extractor/BspHeader.h b/decompiler/level_extractor/BspHeader.h index 1aae78f48..815c78e9b 100644 --- a/decompiler/level_extractor/BspHeader.h +++ b/decompiler/level_extractor/BspHeader.h @@ -14,6 +14,9 @@ class DecompilerTypeSystem; } // namespace decompiler namespace level_tools { + +u32 deref_u32(const Ref& ref, int word_offset); + struct PrintSettings { bool print_tfrag = false; bool expand_draw_node = false; @@ -23,7 +26,8 @@ struct PrintSettings { bool expand_drawable_tree_tie_proto_data = false; bool expand_drawable_tree_instance_tie = false; bool expand_drawable_tree_actor = false; - bool expand_shrub = true; + bool expand_shrub = false; + bool expand_collide = false; }; struct DrawStats { @@ -50,6 +54,7 @@ struct Vector { std::string print(int indent = 0) const; std::string print_meters(int indent = 0) const; + std::string print_decimal(int indent = 0) const; }; // a matrix with 16-bit integers. @@ -181,6 +186,68 @@ struct DrawableTreeActor : public DrawableTree { std::vector> arrays; }; +///////////////////// +// Collision +///////////////////// + +struct CollideFragMesh { + /* + ((packed-data uint32 :offset-assert 4) + (pat-array uint32 :offset-assert 8) + (strip-data-len uint16 :offset-assert 12) + (poly-count uint16 :offset-assert 14) + (base-trans vector :inline :offset-assert 16) + ;; these go in the w of the vector above. + (vertex-count uint8 :offset 28) + (vertex-data-qwc uint8 :offset 29) + (total-qwc uint8 :offset 30) + (unused uint8 :offset 31) + ) + */ + void read_from_file(TypedRef ref, const decompiler::DecompilerTypeSystem& dts, DrawStats* stats); + std::string print(const PrintSettings& settings, int indent) const; + + u16 strip_data_len; + u16 poly_count; + // appears to be integers... + Vector base_trans; + u8 vertex_count; + u8 vertex_data_qwc; + u8 total_qwc; + + Ref packed_data; + Ref pat_array; +}; + +struct CollideFragment { + void read_from_file(TypedRef ref, const decompiler::DecompilerTypeSystem& dts, DrawStats* stats); + std::string print(const PrintSettings& settings, int indent) const; + Vector bsphere; + CollideFragMesh mesh; +}; + +struct DrawableInlineArrayCollideFragment : public DrawableInlineArray { + void read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) override; + std::string print(const PrintSettings& settings, int indent) const override; + std::string my_type() const override; + std::vector collide_fragments; + s16 id; + s16 length; + Vector bsphere; +}; + +struct DrawableTreeCollideFragment : public DrawableTree { + void read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) override; + std::string print(const PrintSettings& settings, int indent) const override; + std::string my_type() const override; + + DrawableInlineArrayCollideFragment last_array; +}; + ///////////////////// // TFRAG ///////////////////// @@ -382,6 +449,7 @@ struct PrototypeBucketTie { // todo envmap shader // todo collide-frag + DrawableInlineArrayCollideFragment collide_frag; // todo tie-colors // todo data diff --git a/decompiler/level_extractor/extract_collide_frags.cpp b/decompiler/level_extractor/extract_collide_frags.cpp new file mode 100644 index 000000000..17bd995dc --- /dev/null +++ b/decompiler/level_extractor/extract_collide_frags.cpp @@ -0,0 +1,280 @@ +#include "extract_collide_frags.h" + +#include "common/util/FileUtil.h" + +namespace decompiler { + +struct CollideListItem { + const level_tools::CollideFragMesh* mesh = nullptr; + const level_tools::InstanceTie* inst = nullptr; + + struct { + std::vector vu0_buffer; + std::vector> faces; + } unpacked; +}; + +/*! + * Get all collide frags. + */ +std::vector build_all_frags_list( + const level_tools::DrawableTreeCollideFragment* tree, + const std::vector& ties) { + std::vector list; + for (auto& cf : tree->last_array.collide_fragments) { + auto& elt = list.emplace_back(); + elt.mesh = &cf.mesh; + elt.inst = nullptr; + } + + for (auto tt : ties) { + auto last_array = tt->arrays.back().get(); + auto as_instance_array = dynamic_cast(last_array); + ASSERT(as_instance_array); + for (auto& inst : as_instance_array->instances) { + auto& frags = tt->prototypes.prototype_array_tie.data.at(inst.bucket_index) + .collide_frag.collide_fragments; + for (auto& frag : frags) { + auto& elt = list.emplace_back(); + elt.inst = &inst; + elt.mesh = &frag.mesh; + } + } + } + return list; +} + +float u32_to_float(u32 in) { + float r; + memcpy(&r, &in, sizeof(float)); + return r; +} + +u16 deref_u16(const Ref& ref, int array_idx) { + u32 u32_offset = array_idx / 2; + u32 u32_val = level_tools::deref_u32(ref, u32_offset); + if (array_idx & 1) { + return u32_val >> 16; + } else { + return (u16)u32_val; + } +} + +s8 deref_s8(const Ref& ref, int byte) { + u32 u32_offset = byte / 4; + u32 u32_val = level_tools::deref_u32(ref, u32_offset); + s8 vals[4]; + memcpy(vals, &u32_val, 4); + return vals[byte & 3]; +} + +void unpack_part1_collide_list_item(CollideListItem& item) { + int in_idx = 0; + int out_idx = 0; + int qw_to_write = item.mesh->vertex_count; + + while (out_idx < qw_to_write * 4) { + auto& vert = item.unpacked.vu0_buffer.emplace_back(); + vert[0] = u32_to_float(0x4d000000 + deref_u16(item.mesh->packed_data, in_idx++)); + vert[1] = u32_to_float(0x4d000000 + deref_u16(item.mesh->packed_data, in_idx++)); + vert[2] = u32_to_float(0x4d000000 + deref_u16(item.mesh->packed_data, in_idx++)); + vert[3] = u32_to_float(0x3f800000); + out_idx += 4; + } +} + +math::Vector transform_tie(const std::array mat, + const math::Vector4f& pt) { + auto temp = mat[0] * pt.x() + mat[1] * pt.y() + mat[2] * pt.z() + mat[3]; + math::Vector4f result; + result.x() = temp.x(); + result.y() = temp.y(); + result.z() = temp.z(); + result.w() = 1.f; + return result; +} + +namespace { +// todo dedup +std::array extract_tie_matrix(const u16* data) { + std::array result; + for (int i = 0; i < 4; i++) { + s32 x = data[12 + i]; + x <<= 16; + x >>= 10; + result[3][i] = x; + } + + for (int vec = 0; vec < 3; vec++) { + for (int i = 0; i < 4; i++) { + s32 x = data[vec * 4 + i]; + x <<= 16; + x >>= 16; + result[vec][i] = (float)x / 4096.f; + } + } + + return result; +} +} // namespace + +void unpack_part2_collide_list_item(CollideListItem& item) { + float base_offset = u32_to_float(0x4d000000); + + math::Vector4f vf13_combo_offset(base_offset, base_offset, base_offset, 0); + s32 base_trans_int[4]; + memcpy(base_trans_int, item.mesh->base_trans.data, 16); + math::Vector4f vf14_base_trans_float(base_trans_int[0], base_trans_int[1], base_trans_int[2], + base_trans_int[3]); + vf13_combo_offset -= vf14_base_trans_float; + std::array mat; + if (item.inst) { + mat = extract_tie_matrix(item.inst->origin.data); + mat[3][0] += item.inst->bsphere.data[0]; + mat[3][1] += item.inst->bsphere.data[1]; + mat[3][2] += item.inst->bsphere.data[2]; + } + + for (auto& ver : item.unpacked.vu0_buffer) { + ver -= vf13_combo_offset; + if (item.inst) { + ver = transform_tie(mat, ver); + } + } +} + +void find_faces(CollideListItem& item) { + u32 byte_offset = 16 * item.mesh->vertex_data_qwc; + + while (true) { + s8 t7_start_idx = deref_s8(item.mesh->packed_data, byte_offset++); + if (t7_start_idx < 0) { + return; + } + s8 t8_start_idx = deref_s8(item.mesh->packed_data, byte_offset++); + s8 t6_start_idx = deref_s8(item.mesh->packed_data, byte_offset++); + + s8 t6_run_idx = t7_start_idx; + s8 t7_run_idx = t8_start_idx; + s8 ra_run_idx = t6_start_idx; + + item.unpacked.faces.emplace_back(t6_run_idx, t7_run_idx, ra_run_idx); + s8 s5 = 1; + while (true) { + s8 next = deref_s8(item.mesh->packed_data, byte_offset++); + if (!next) { + break; + } + if (next < 0) { + next = -((s32)next); + } else { + t6_run_idx = t7_run_idx; + s5 = -s5; + } + next--; + t7_run_idx = ra_run_idx; + ra_run_idx = next; + u8 result[3]; + result[1] = t7_run_idx; + result[1 + s5] = ra_run_idx; + result[1 - s5] = t6_run_idx; + item.unpacked.faces.emplace_back(result[0], result[1], result[2]); + ASSERT((u32)next < item.unpacked.vu0_buffer.size()); + } + } +} + +std::string debug_dump_to_obj(const std::vector& list) { + std::vector verts; + std::vector> faces; + + for (auto& item : list) { + u32 f_off = verts.size() + 1; + for (auto& f : item.unpacked.faces) { + faces.emplace_back(f[0] + f_off, f[1] + f_off, f[2] + f_off); + } + for (u32 t = 0; t < item.unpacked.vu0_buffer.size(); t++) { + verts.push_back(item.unpacked.vu0_buffer[t] / 65536); + } + } + + std::string result; + for (auto& vert : verts) { + result += fmt::format("v {} {} {}\n", vert.x(), vert.y(), vert.z()); + } + + for (auto& face : faces) { + result += fmt::format("f {}/{} {}/{} {}/{}\n", face.x(), face.x(), face.y(), face.y(), face.z(), + face.z()); + } + + return result; +} + +void set_vertices_for_tri(tfrag3::CollisionMesh::Vertex* out, const math::Vector4f* in) { + math::Vector3f v10 = in[1].xyz() - in[0].xyz(); + math::Vector3f v20 = in[2].xyz() - in[0].xyz(); + auto normal = (v10.cross(v20).normalized() * INT16_MAX).cast(); + + for (int i = 0; i < 3; i++) { + out[i].x = in[i].x(); + out[i].y = in[i].y(); + out[i].z = in[i].z(); + out[i].nx = normal.x(); + out[i].ny = normal.y(); + out[i].nz = normal.z(); + out[i].flags = 0; // todo + out[i].pad = 0; + } +} + +void extract_collide_frags(const level_tools::DrawableTreeCollideFragment* tree, + const std::vector& ties, + const std::string& debug_name, + tfrag3::Level& out, + bool dump_level) { + // in-game, the broad-phase collision builds a list of fragments, then unpacks them with: + /* + *(dotimes (i (-> *collide-list* num-items)) + (let ((frag (-> *collide-list* items i))) + ;; to VU0 memory + (__pc-upload-collide-frag (-> frag mesh packed-data) (-> frag mesh vertex-data-qwc) (-> + frag mesh vertex-count)) + ;; from VU0 memory to scratchpad + (unpack-background-collide-mesh obj (-> frag mesh) (-> frag inst) 0) + ;; from scratchpad to collide-cache memory. + (import-mesh-func obj (-> frag mesh)) + ) + ) + */ + + auto all_frags = build_all_frags_list(tree, ties); + u32 total_faces = 0; + for (auto& frag : all_frags) { + unpack_part1_collide_list_item(frag); + unpack_part2_collide_list_item(frag); + find_faces(frag); + total_faces += frag.unpacked.faces.size(); + } + + if (dump_level) { + auto debug_out = debug_dump_to_obj(all_frags); + file_util::write_text_file( + file_util::get_file_path({"debug_out", fmt::format("collide-{}.obj", debug_name)}), + debug_out); + } + + for (auto& item : all_frags) { + for (auto& f : item.unpacked.faces) { + math::Vector4f verts[3] = {item.unpacked.vu0_buffer[f[0]], item.unpacked.vu0_buffer[f[1]], + item.unpacked.vu0_buffer[f[2]]}; + tfrag3::CollisionMesh::Vertex out_verts[3]; + set_vertices_for_tri(out_verts, verts); + for (int i = 0; i < 3; i++) { + out.collision.vertices.push_back(out_verts[i]); + } + } + } +} + +} // namespace decompiler \ No newline at end of file diff --git a/decompiler/level_extractor/extract_collide_frags.h b/decompiler/level_extractor/extract_collide_frags.h new file mode 100644 index 000000000..44fc2bf44 --- /dev/null +++ b/decompiler/level_extractor/extract_collide_frags.h @@ -0,0 +1,14 @@ +#pragma once + +#include "BspHeader.h" +#include "common/custom_data/Tfrag3Data.h" + +namespace decompiler { + +void extract_collide_frags(const level_tools::DrawableTreeCollideFragment* tree, + const std::vector& ties, + const std::string& debug_name, + tfrag3::Level& out, + bool dump_level); + +} // namespace decompiler \ No newline at end of file diff --git a/decompiler/level_extractor/extract_level.cpp b/decompiler/level_extractor/extract_level.cpp index 3889fa259..a43f028ef 100644 --- a/decompiler/level_extractor/extract_level.cpp +++ b/decompiler/level_extractor/extract_level.cpp @@ -6,6 +6,7 @@ #include "decompiler/level_extractor/extract_tfrag.h" #include "decompiler/level_extractor/extract_tie.h" #include "decompiler/level_extractor/extract_shrub.h" +#include "decompiler/level_extractor/extract_collide_frags.h" #include "common/util/compress.h" #include "common/util/FileUtil.h" #include "common/util/SimpleThreadGroup.h" @@ -77,7 +78,8 @@ void print_memory_usage(const tfrag3::Level& lev, int uncompressed_data_size) { {"tfrag-bvh", memory_use_by_category[tfrag3::MemoryUsageCategory::TFRAG_BVH]}, {"shrub-colors", memory_use_by_category[tfrag3::MemoryUsageCategory::SHRUB_TIME_OF_DAY]}, {"shrub-vert", memory_use_by_category[tfrag3::MemoryUsageCategory::SHRUB_VERT]}, - {"shrub-ind", memory_use_by_category[tfrag3::MemoryUsageCategory::SHRUB_IND]}}; + {"shrub-ind", memory_use_by_category[tfrag3::MemoryUsageCategory::SHRUB_IND]}, + {"collision", memory_use_by_category[tfrag3::MemoryUsageCategory::COLLISION]}}; for (auto& known : known_categories) { total_accounted += known.second; } @@ -166,7 +168,8 @@ void extract_from_level(const ObjectFileDB& db, const TextureDB& tex_db, const std::string& dgo_name, const DecompileHacks& hacks, - bool dump_level) { + bool dump_level, + bool extract_collision) { if (db.obj_files_by_dgo.count(dgo_name) == 0) { lg::warn("Skipping extract for {} because the DGO was not part of the input", dgo_name); return; @@ -192,9 +195,9 @@ void extract_from_level(const ObjectFileDB& db, /* level_tools::PrintSettings settings; - settings.expand_shrub = true; + settings.expand_collide = true; fmt::print("{}\n", bsp_header.print(settings)); - */ + */ const std::set tfrag_trees = { "drawable-tree-tfrag", "drawable-tree-trans-tfrag", "drawable-tree-dirt-tfrag", @@ -204,6 +207,15 @@ void extract_from_level(const ObjectFileDB& db, add_all_textures_from_level(tfrag_level, dgo_name, tex_db); + std::vector all_ties; + for (auto& draw_tree : bsp_header.drawable_tree_array.trees) { + auto as_tie_tree = dynamic_cast(draw_tree.get()); + if (as_tie_tree) { + all_ties.push_back(as_tie_tree); + } + } + + bool got_collide = false; for (auto& draw_tree : bsp_header.drawable_tree_array.trees) { if (tfrag_trees.count(draw_tree->my_type())) { auto as_tfrag_tree = dynamic_cast(draw_tree.get()); @@ -227,11 +239,21 @@ void extract_from_level(const ObjectFileDB& db, ASSERT(as_shrub_tree); extract_shrub(as_shrub_tree, fmt::format("{}-{}-shrub", dgo_name, i++), bsp_header.texture_remap_table, tex_db, {}, tfrag_level, dump_level); + } else if (draw_tree->my_type() == "drawable-tree-collide-fragment" && extract_collision) { + auto as_collide_frags = + dynamic_cast(draw_tree.get()); + ASSERT(as_collide_frags); + ASSERT(!got_collide); + got_collide = true; + extract_collide_frags(as_collide_frags, all_ties, fmt::format("{}-{}-collide", dgo_name, i++), + tfrag_level, dump_level); } else { // fmt::print(" unsupported tree {}\n", draw_tree->my_type()); } } + tfrag_level.level_name = level_name; + Serializer ser; tfrag_level.serialize(ser); auto compressed = @@ -249,11 +271,14 @@ void extract_all_levels(const ObjectFileDB& db, const std::vector& dgo_names, const std::string& common_name, const DecompileHacks& hacks, - bool debug_dump_level) { + bool debug_dump_level, + bool extract_collision) { extract_common(db, tex_db, common_name); SimpleThreadGroup threads; threads.run( - [&](int idx) { extract_from_level(db, tex_db, dgo_names[idx], hacks, debug_dump_level); }, + [&](int idx) { + extract_from_level(db, tex_db, dgo_names[idx], hacks, debug_dump_level, extract_collision); + }, dgo_names.size()); threads.join(); } diff --git a/decompiler/level_extractor/extract_level.h b/decompiler/level_extractor/extract_level.h index a97fcf37d..d09da915d 100644 --- a/decompiler/level_extractor/extract_level.h +++ b/decompiler/level_extractor/extract_level.h @@ -10,7 +10,8 @@ void extract_from_level(const ObjectFileDB& db, const TextureDB& tex_db, const std::string& dgo_name, const DecompileHacks& hacks, - bool dump_level); + bool dump_level, + bool extract_collision); void extract_common(const ObjectFileDB& db, const TextureDB& tex_db, const std::string& dgo_name); // extract everything @@ -19,5 +20,6 @@ void extract_all_levels(const ObjectFileDB& db, const std::vector& dgo_names, const std::string& common_name, const DecompileHacks& hacks, - bool debug_dump_level); + bool debug_dump_level, + bool extract_collision); } // namespace decompiler diff --git a/decompiler/main.cpp b/decompiler/main.cpp index 99b49c73e..f7ce060da 100644 --- a/decompiler/main.cpp +++ b/decompiler/main.cpp @@ -210,7 +210,7 @@ int main(int argc, char** argv) { if (config.levels_extract) { extract_all_levels(db, tex_db, config.levels_to_extract, "GAME.CGO", config.hacks, - config.rip_levels); + config.rip_levels, config.extract_collision); } fmt::print("[Mem] After extraction: {} MB\n", get_peak_rss() / (1024 * 1024)); diff --git a/decompiler/util/goal_data_reader.cpp b/decompiler/util/goal_data_reader.cpp index 9b216e28d..de4b7d64b 100644 --- a/decompiler/util/goal_data_reader.cpp +++ b/decompiler/util/goal_data_reader.cpp @@ -234,7 +234,8 @@ TypedRef typed_ref_from_basic(const Ref& object, const decompiler::DecompilerTyp const auto& word = object.data->words_by_seg.at(object.seg).at(byte_in_words / 4); if (word.kind() != decompiler::LinkedWord::TYPE_PTR) { - throw Error("typed_ref_from_basic did not get a type tag (offset {} words)", byte_in_words / 4); + throw Error("typed_ref_from_basic did not get a type tag (offset {} words). Got {}", + byte_in_words / 4, (int)word.kind()); } TypedRef result; @@ -252,7 +253,8 @@ Ref deref_label(const Ref& object) { const auto& word = object.data->words_by_seg.at(object.seg).at(byte_in_words / 4); if (word.kind() != decompiler::LinkedWord::PTR) { - throw Error("deref_label did not get a label (offset {} words)", byte_in_words / 4); + throw Error("deref_label did not get a label (offset {} words). Got {} {}", byte_in_words / 4, + (int)word.kind(), word.data); } const auto& lab = object.data->labels.at(word.label_id()); diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index 52487f556..910573783 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -96,6 +96,7 @@ set(RUNTIME_SOURCE graphics/opengl_renderer/ocean/OceanTexture.cpp graphics/opengl_renderer/ocean/OceanTexture_PC.cpp graphics/opengl_renderer/BucketRenderer.cpp + graphics/opengl_renderer/CollideMeshRenderer.cpp graphics/opengl_renderer/debug_gui.cpp graphics/opengl_renderer/DirectRenderer.cpp graphics/opengl_renderer/DirectRenderer2.cpp diff --git a/game/graphics/opengl_renderer/BucketRenderer.cpp b/game/graphics/opengl_renderer/BucketRenderer.cpp index 7edc882e6..3a1d7bd56 100644 --- a/game/graphics/opengl_renderer/BucketRenderer.cpp +++ b/game/graphics/opengl_renderer/BucketRenderer.cpp @@ -59,7 +59,7 @@ void SkipRenderer::render(DmaFollower& dma, } void SharedRenderState::reset() { - has_camera_planes = false; + has_pc_data = false; for (auto& x : occlusion_vis) { x.valid = false; } diff --git a/game/graphics/opengl_renderer/BucketRenderer.h b/game/graphics/opengl_renderer/BucketRenderer.h index 77d9aea1f..a87f79fdf 100644 --- a/game/graphics/opengl_renderer/BucketRenderer.h +++ b/game/graphics/opengl_renderer/BucketRenderer.h @@ -43,11 +43,17 @@ struct SharedRenderState { math::Vector fog_color; float fog_intensity = 1.f; bool no_multidraw = false; + bool render_collision_mesh = false; void reset(); - bool has_camera_planes = false; + bool has_pc_data = false; LevelVis occlusion_vis[2]; + math::Vector4f camera_planes[4]; + math::Vector4f camera_matrix[4]; + math::Vector4f camera_hvdf_off; + math::Vector4f camera_fog; + math::Vector4f camera_pos; EyeRenderer* eye_renderer = nullptr; diff --git a/game/graphics/opengl_renderer/CollideMeshRenderer.cpp b/game/graphics/opengl_renderer/CollideMeshRenderer.cpp new file mode 100644 index 000000000..ac3b5fcfc --- /dev/null +++ b/game/graphics/opengl_renderer/CollideMeshRenderer.cpp @@ -0,0 +1,97 @@ +#include "CollideMeshRenderer.h" + +#include "game/graphics/opengl_renderer/background/background_common.h" + +CollideMeshRenderer::CollideMeshRenderer() { + glGenVertexArrays(1, &m_vao); +} + +CollideMeshRenderer::~CollideMeshRenderer() { + glDeleteVertexArrays(1, &m_vao); +} + +void CollideMeshRenderer::render(SharedRenderState* render_state, ScopedProfilerNode& prof) { + if (!render_state->has_pc_data) { + return; + } + + auto levels = render_state->loader->get_in_use_levels(); + if (levels.empty()) { + return; + } + render_state->shaders[ShaderId::COLLISION].activate(); + + glBindVertexArray(m_vao); + TfragRenderSettings settings; + memcpy(settings.math_camera.data(), render_state->camera_matrix[0].data(), 64); + settings.hvdf_offset = render_state->camera_hvdf_off; + settings.fog = render_state->camera_fog; + settings.tree_idx = 0; + for (int i = 0; i < 4; i++) { + settings.planes[i] = render_state->camera_planes[i]; + } + glUniformMatrix4fv( + glGetUniformLocation(render_state->shaders[ShaderId::COLLISION].id(), "camera"), 1, GL_FALSE, + settings.math_camera.data()); + glUniform4f(glGetUniformLocation(render_state->shaders[ShaderId::COLLISION].id(), "hvdf_offset"), + settings.hvdf_offset[0], settings.hvdf_offset[1], settings.hvdf_offset[2], + settings.hvdf_offset[3]); + const auto& trans = render_state->camera_pos; + glUniform4f( + glGetUniformLocation(render_state->shaders[ShaderId::COLLISION].id(), "camera_position"), + trans[0], trans[1], trans[2], trans[3]); + glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::COLLISION].id(), "fog_constant"), + settings.fog.x()); + glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::COLLISION].id(), "fog_min"), + settings.fog.y()); + glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::COLLISION].id(), "fog_max"), + settings.fog.z()); + glUniform1i(glGetUniformLocation(render_state->shaders[ShaderId::COLLISION].id(), "mode"), 0); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_GEQUAL); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // ? + glDepthMask(GL_TRUE); + + for (auto lev : levels) { + glBindBuffer(GL_ARRAY_BUFFER, lev->collide_vertices); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + glVertexAttribPointer(0, // location 0 in the shader + 3, // 3 values per vert + GL_FLOAT, // floats + GL_FALSE, // normalized + sizeof(tfrag3::CollisionMesh::Vertex), // stride + 0 // offset (0) + ); + glVertexAttribIPointer(1, // location 1 in the shader + 1, // 3 values per vert + GL_UNSIGNED_INT, // u32 + sizeof(tfrag3::CollisionMesh::Vertex), // stride + (void*)offsetof(tfrag3::CollisionMesh::Vertex, flags) // offset + ); + glVertexAttribPointer(2, // location 2 in the shader + 3, // 3 values per vert + GL_SHORT, // floats + GL_TRUE, // normalized + sizeof(tfrag3::CollisionMesh::Vertex), // stride + (void*)offsetof(tfrag3::CollisionMesh::Vertex, nx) // offset (0) + ); + glUniform1i(glGetUniformLocation(render_state->shaders[ShaderId::COLLISION].id(), "mode"), 0); + glDrawArrays(GL_TRIANGLES, 0, lev->level->collision.vertices.size()); + + bool kDrawWireframe = false; + if (kDrawWireframe) { + glUniform1i(glGetUniformLocation(render_state->shaders[ShaderId::COLLISION].id(), "mode"), 1); + glDisable(GL_BLEND); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + glDrawArrays(GL_TRIANGLES, 0, lev->level->collision.vertices.size()); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glEnable(GL_BLEND); + } + + prof.add_draw_call(); + prof.add_tri(lev->level->collision.vertices.size() / 3); + } +} \ No newline at end of file diff --git a/game/graphics/opengl_renderer/CollideMeshRenderer.h b/game/graphics/opengl_renderer/CollideMeshRenderer.h new file mode 100644 index 000000000..13bb1c420 --- /dev/null +++ b/game/graphics/opengl_renderer/CollideMeshRenderer.h @@ -0,0 +1,12 @@ +#pragma once +#include "game/graphics/opengl_renderer/BucketRenderer.h" + +class CollideMeshRenderer { + public: + CollideMeshRenderer(); + void render(SharedRenderState* render_state, ScopedProfilerNode& prof); + ~CollideMeshRenderer(); + + private: + GLuint m_vao; +}; diff --git a/game/graphics/opengl_renderer/Loader.cpp b/game/graphics/opengl_renderer/Loader.cpp index 71b99cdc8..70dd3a0cc 100644 --- a/game/graphics/opengl_renderer/Loader.cpp +++ b/game/graphics/opengl_renderer/Loader.cpp @@ -75,6 +75,21 @@ void Loader::set_want_levels(const std::vector& levels) { } } +/*! + * Get all levels that are in memory and used very recently. + */ +std::vector Loader::get_in_use_levels() { + std::vector result; + std::unique_lock lk(m_loader_mutex); + + for (auto& lev : m_loaded_tfrag3_levels) { + if (lev.second.frames_since_last_used < 5) { + result.push_back(&lev.second.data); + } + } + return result; +} + /*! * Loader function that runs in a completely separate thread. * This is used for file I/O and unpacking. @@ -474,6 +489,15 @@ bool Loader::init_tie(Timer& timer, LevelData& data) { return false; } +bool Loader::init_collide(Timer& /*timer*/, LevelData& data) { + glGenBuffers(1, &data.collide_vertices); + glBindBuffer(GL_ARRAY_BUFFER, data.collide_vertices); + glBufferData(GL_ARRAY_BUFFER, + data.level->collision.vertices.size() * sizeof(tfrag3::CollisionMesh::Vertex), + data.level->collision.vertices.data(), GL_STATIC_DRAW); + return true; +} + bool Loader::upload_textures(Timer& timer, LevelData& data, TexturePool& texture_pool) { // try to move level from initializing to initialized: @@ -583,12 +607,14 @@ void Loader::update(TexturePool& texture_pool) { if (init_tie(loader_timer, lev.data)) { if (init_tfrag(loader_timer, lev.data)) { if (init_shrub(loader_timer, lev.data)) { - // we're done! lock before removing from loaded. - lk.lock(); - it->second.data.load_id = m_id++; + if (init_collide(loader_timer, lev.data)) { + // we're done! lock before removing from loaded. + lk.lock(); + it->second.data.load_id = m_id++; - m_loaded_tfrag3_levels[name] = std::move(lev); - m_initializing_tfrag3_levels.erase(it); + m_loaded_tfrag3_levels[name] = std::move(lev); + m_initializing_tfrag3_levels.erase(it); + } } } } @@ -640,6 +666,8 @@ void Loader::update(TexturePool& texture_pool) { } } + glDeleteBuffers(1, &lev.second.data.collide_vertices); + m_loaded_tfrag3_levels.erase(lev.first); break; } diff --git a/game/graphics/opengl_renderer/Loader.h b/game/graphics/opengl_renderer/Loader.h index b03f31c30..70577752b 100644 --- a/game/graphics/opengl_renderer/Loader.h +++ b/game/graphics/opengl_renderer/Loader.h @@ -31,6 +31,7 @@ class Loader { std::array, tfrag3::TIE_GEOS> tie_data; std::array, tfrag3::TIE_GEOS> tfrag_vertex_data; std::vector shrub_vertex_data; + GLuint collide_vertices; // internal load state bool tie_opengl_created = false; @@ -56,6 +57,7 @@ class Loader { const LevelData* get_tfrag3_level(const std::string& level_name); void load_common(TexturePool& tex_pool, const std::string& name); void set_want_levels(const std::vector& levels); + std::vector get_in_use_levels(); private: struct Level { @@ -70,6 +72,7 @@ class Loader { bool init_tie(Timer& timer, LevelData& data); bool init_tfrag(Timer& timer, LevelData& data); bool init_shrub(Timer& timer, LevelData& data); + bool init_collide(Timer& timer, LevelData& data); // used by game and loader thread std::unordered_map m_initializing_tfrag3_levels; diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.cpp b/game/graphics/opengl_renderer/OpenGLRenderer.cpp index 7858b133e..c715169ee 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.cpp +++ b/game/graphics/opengl_renderer/OpenGLRenderer.cpp @@ -55,7 +55,6 @@ OpenGLRenderer::OpenGLRenderer(std::shared_ptr texture_pool, std::shared_ptr loader) : m_render_state(texture_pool, loader) { // setup OpenGL errors - glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(opengl_error_callback, nullptr); // disable specific errors @@ -295,7 +294,6 @@ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) { m_render_state.reset(); m_render_state.ee_main_memory = g_ee_main_mem; m_render_state.offset_of_s7 = offset_of_s7(); - m_render_state.has_camera_planes = false; { auto prof = m_profiler.root()->make_scoped_child("frame-setup"); @@ -368,6 +366,7 @@ void OpenGLRenderer::draw_renderer_selection_window() { ImGui::Begin("Renderer Debug"); ImGui::Checkbox("Use old single-draw", &m_render_state.no_multidraw); + ImGui::Checkbox("Render collision mesh", &m_render_state.render_collision_mesh); ImGui::SliderFloat("Fog Adjust", &m_render_state.fog_intensity, 0, 10); ImGui::Checkbox("Sky CPU", &m_render_state.use_sky_cpu); ImGui::Checkbox("Occlusion Cull", &m_render_state.use_occlusion_culling); @@ -458,6 +457,12 @@ void OpenGLRenderer::dispatch_buckets(DmaFollower dma, ScopedProfilerNode& prof) m_render_state.next_bucket += 16; vif_interrupt_callback(); m_category_times[(int)m_bucket_categories[bucket_id]] += bucket_prof.get_elapsed_time(); + + // hack to draw the collision mesh in the middle the drawing + if (bucket_id == (int)BucketId::ALPHA_TEX_LEVEL0 - 1 && m_render_state.render_collision_mesh) { + auto p = prof.make_scoped_child("collision-draw"); + m_collide_renderer.render(&m_render_state, p); + } } g_current_render = ""; diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.h b/game/graphics/opengl_renderer/OpenGLRenderer.h index bc2e5e98b..b73c28622 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.h +++ b/game/graphics/opengl_renderer/OpenGLRenderer.h @@ -8,6 +8,7 @@ #include "game/graphics/opengl_renderer/BucketRenderer.h" #include "game/graphics/opengl_renderer/Profiler.h" #include "game/graphics/opengl_renderer/opengl_utils.h" +#include "game/graphics/opengl_renderer/CollideMeshRenderer.h" struct RenderOptions { int window_height_px = 0; @@ -24,9 +25,19 @@ struct RenderOptions { float pmode_alp_register = 0.f; }; +/*! + * Main OpenGL renderer. + * This handles the glClear and all game rendering, but not actual setup, synchronization or imgui + * stuff. + * + * It is simply a collection of bucket renderers, and a few special case ones. + */ class OpenGLRenderer { public: OpenGLRenderer(std::shared_ptr texture_pool, std::shared_ptr loader); + + // rendering interface: takes the dma chain from the game, and some size/debug settings from + // the graphics system. void render(DmaFollower dma, const RenderOptions& settings); private: @@ -58,6 +69,7 @@ class OpenGLRenderer { std::array m_category_times; FullScreenDraw m_blackout_renderer; + CollideMeshRenderer m_collide_renderer; float m_last_pmode_alp = 1.; bool m_enable_fast_blackout_loads = true; diff --git a/game/graphics/opengl_renderer/Shader.cpp b/game/graphics/opengl_renderer/Shader.cpp index 022c23718..f92992d45 100644 --- a/game/graphics/opengl_renderer/Shader.cpp +++ b/game/graphics/opengl_renderer/Shader.cpp @@ -83,4 +83,9 @@ ShaderLibrary::ShaderLibrary() { at(ShaderId::OCEAN_COMMON) = {"ocean_common"}; at(ShaderId::SHRUB) = {"shrub"}; at(ShaderId::SHADOW) = {"shadow"}; + at(ShaderId::COLLISION) = {"collision"}; + + for (auto& shader : m_shaders) { + ASSERT_MSG(shader.okay(), "Shader compiled"); + } } diff --git a/game/graphics/opengl_renderer/Shader.h b/game/graphics/opengl_renderer/Shader.h index 52d013928..622aae73f 100644 --- a/game/graphics/opengl_renderer/Shader.h +++ b/game/graphics/opengl_renderer/Shader.h @@ -40,6 +40,7 @@ enum class ShaderId { OCEAN_COMMON = 15, SHADOW = 16, SHRUB = 17, + COLLISION = 18, MAX_SHADERS }; diff --git a/game/graphics/opengl_renderer/Sprite3.cpp b/game/graphics/opengl_renderer/Sprite3.cpp index 0dd2fc466..0e41da2f1 100644 --- a/game/graphics/opengl_renderer/Sprite3.cpp +++ b/game/graphics/opengl_renderer/Sprite3.cpp @@ -649,7 +649,7 @@ void Sprite3::do_block_common(SpriteMode mode, flush_sprites(render_state, prof, mode == ModeHUD); } - if (mode == Mode2D && render_state->has_camera_planes && m_enable_culling) { + if (mode == Mode2D && render_state->has_pc_data && m_enable_culling) { // we can skip sprites that are out of view // it's probably possible to do this for 3D as well. auto bsphere = m_vec_data_2d[sprite_idx].xyz_sx; diff --git a/game/graphics/opengl_renderer/SpriteRenderer.cpp b/game/graphics/opengl_renderer/SpriteRenderer.cpp index 4e26d6951..0bbe697a6 100644 --- a/game/graphics/opengl_renderer/SpriteRenderer.cpp +++ b/game/graphics/opengl_renderer/SpriteRenderer.cpp @@ -653,7 +653,7 @@ void SpriteRenderer::do_block_common(SpriteMode mode, flush_sprites(render_state, prof); } - if (mode == Mode2D && render_state->has_camera_planes && m_enable_culling) { + if (mode == Mode2D && render_state->has_pc_data && m_enable_culling) { // we can skip sprites that are out of view // it's probably possible to do this for 3D as well. auto bsphere = m_vec_data_2d[sprite_idx].xyz_sx; diff --git a/game/graphics/opengl_renderer/background/Shrub.cpp b/game/graphics/opengl_renderer/background/Shrub.cpp index 38f5d06d4..980c30632 100644 --- a/game/graphics/opengl_renderer/background/Shrub.cpp +++ b/game/graphics/opengl_renderer/background/Shrub.cpp @@ -51,11 +51,11 @@ void Shrub::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProf 2 * (0xff & m_pc_port_data.itimes[i / 2].data()[2 * (i % 2)]) / 127.f; } + update_render_state_from_pc_settings(render_state, m_pc_port_data); + for (int i = 0; i < 4; i++) { settings.planes[i] = m_pc_port_data.planes[i]; - render_state->camera_planes[i] = m_pc_port_data.planes[i]; } - render_state->has_camera_planes = true; m_has_level = setup_for_level(m_pc_port_data.level_name, render_state); render_all_trees(settings, render_state, prof); diff --git a/game/graphics/opengl_renderer/background/TFragment.cpp b/game/graphics/opengl_renderer/background/TFragment.cpp index d6833c1a9..20273b571 100644 --- a/game/graphics/opengl_renderer/background/TFragment.cpp +++ b/game/graphics/opengl_renderer/background/TFragment.cpp @@ -125,11 +125,11 @@ void TFragment::render(DmaFollower& dma, settings.occlusion_culling = render_state->occlusion_vis[m_level_id].data; } + update_render_state_from_pc_settings(render_state, m_pc_port_data); + for (int i = 0; i < 4; i++) { settings.planes[i] = m_pc_port_data.planes[i]; - render_state->camera_planes[i] = m_pc_port_data.planes[i]; } - render_state->has_camera_planes = true; if (m_override_time_of_day) { for (int i = 0; i < 8; i++) { diff --git a/game/graphics/opengl_renderer/background/Tie3.cpp b/game/graphics/opengl_renderer/background/Tie3.cpp index 5590bf657..47d18b418 100644 --- a/game/graphics/opengl_renderer/background/Tie3.cpp +++ b/game/graphics/opengl_renderer/background/Tie3.cpp @@ -353,11 +353,11 @@ void Tie3::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfi settings.occlusion_culling = render_state->occlusion_vis[m_level_id].data; } + update_render_state_from_pc_settings(render_state, m_pc_port_data); + for (int i = 0; i < 4; i++) { settings.planes[i] = m_pc_port_data.planes[i]; - render_state->camera_planes[i] = m_pc_port_data.planes[i]; } - render_state->has_camera_planes = true; if (false) { // for (int i = 0; i < 8; i++) { diff --git a/game/graphics/opengl_renderer/background/background_common.cpp b/game/graphics/opengl_renderer/background/background_common.cpp index 7326d9863..343f3294c 100644 --- a/game/graphics/opengl_renderer/background/background_common.cpp +++ b/game/graphics/opengl_renderer/background/background_common.cpp @@ -658,4 +658,17 @@ u32 make_all_visible_index_list(std::pair* group_out, } *num_tris_out = num_tris; return idx_buffer_ptr; +} + +void update_render_state_from_pc_settings(SharedRenderState* state, const TfragPcPortData& data) { + if (!state->has_pc_data) { + for (int i = 0; i < 4; i++) { + state->camera_planes[i] = data.planes[i]; + state->camera_matrix[i] = data.camera[i]; + } + state->camera_pos = data.cam_trans; + state->camera_hvdf_off = data.hvdf_off; + state->camera_fog = data.fog; + state->has_pc_data = true; + } } \ No newline at end of file diff --git a/game/graphics/opengl_renderer/background/background_common.h b/game/graphics/opengl_renderer/background/background_common.h index 908386dc3..d48a37a13 100644 --- a/game/graphics/opengl_renderer/background/background_common.h +++ b/game/graphics/opengl_renderer/background/background_common.h @@ -3,6 +3,19 @@ #include "common/math/Vector.h" #include "game/graphics/opengl_renderer/BucketRenderer.h" +// data passed from game to PC renderers +// the GOAL code assumes this memory layout. +struct TfragPcPortData { + math::Vector4f planes[4]; + math::Vector itimes[4]; + math::Vector4f camera[4]; + math::Vector4f hvdf_off; + math::Vector4f fog; + math::Vector4f cam_trans; + char level_name[16]; +}; + +// inputs to background renderers. struct TfragRenderSettings { math::Matrix4f math_camera; math::Vector4f hvdf_offset; @@ -12,7 +25,6 @@ struct TfragRenderSettings { math::Vector4f planes[4]; bool debug_culling = false; const u8* occlusion_culling = nullptr; - // todo occlusion culling string. }; enum class DoubleDrawKind { NONE, AFAIL_NO_DEPTH_WRITE }; @@ -50,15 +62,7 @@ void cull_check_all_slow(const math::Vector4f* planes, u8* out); bool sphere_in_view_ref(const math::Vector4f& sphere, const math::Vector4f* planes); -struct TfragPcPortData { - math::Vector4f planes[4]; - math::Vector itimes[4]; - math::Vector4f camera[4]; - math::Vector4f hvdf_off; - math::Vector4f fog; - char level_name[12]; - u32 tree_idx; -}; +void update_render_state_from_pc_settings(SharedRenderState* state, const TfragPcPortData& data); void make_all_visible_multidraws(std::pair* draw_ptrs_out, GLsizei* counts_out, diff --git a/game/graphics/opengl_renderer/shaders/collision.frag b/game/graphics/opengl_renderer/shaders/collision.frag new file mode 100644 index 000000000..635a50c27 --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/collision.frag @@ -0,0 +1,9 @@ +#version 430 core + +out vec4 color; + +in vec4 fragment_color; + +void main() { + color = fragment_color; +} diff --git a/game/graphics/opengl_renderer/shaders/collision.vert b/game/graphics/opengl_renderer/shaders/collision.vert new file mode 100644 index 000000000..b95fc892f --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/collision.vert @@ -0,0 +1,67 @@ +#version 430 core + +layout (location = 0) in vec3 position_in; +layout (location = 1) in uint flags; +layout (location = 2) in vec3 normal_in; + +uniform vec4 hvdf_offset; +uniform mat4 camera; +uniform vec4 camera_position; +uniform float fog_constant; +uniform float fog_min; +uniform float fog_max; +uniform int mode; + +out vec4 fragment_color; + +void main() { + // Step 3, the camera transform + vec4 transformed = -camera[3].xyzw; + transformed += -camera[0] * position_in.x; + transformed += -camera[1] * position_in.y; + transformed += -camera[2] * position_in.z; + + // compute Q + float Q = fog_constant / transformed[3]; + + // perspective divide! + transformed.xyz *= Q; + + // offset + transformed.xyz += hvdf_offset.xyz; + + // ftoi4 + //transformed.xyzw *= 16; + + // correct xy offset + transformed.xy -= (2048.); + + // correct z scale + transformed.z /= (8388608); + transformed.z -= 1; + + // correct xy scale + transformed.x /= (256); + transformed.y /= -(128); + + // hack + transformed.xyz *= transformed.w; + + gl_Position = transformed; + // scissoring area adjust + gl_Position.y *= 512.0/448.0; + + vec3 to_cam = camera_position.xyz - position_in; + float dist_from_cam = length(to_cam); + vec3 to_cam_n = to_cam / dist_from_cam; + float cam_dot = abs(dot(to_cam_n, normal_in)); + + // color + if (mode == 0) { + fragment_color = vec4(0.4, 0.5, 0.5, 1); + fragment_color.xyz *= (pow(cam_dot, 3) + 0.3); + } else { + fragment_color = vec4(0.0, 0.3, 0.3, 1); + } + +} diff --git a/goal_src/engine/gfx/tfrag/tfrag.gc b/goal_src/engine/gfx/tfrag/tfrag.gc index 901602761..e44fbdea9 100644 --- a/goal_src/engine/gfx/tfrag/tfrag.gc +++ b/goal_src/engine/gfx/tfrag/tfrag.gc @@ -322,7 +322,7 @@ (defun add-pc-tfrag3-data ((dma-buf dma-buffer) (lev level)) "Add PC-port specific tfrag data" (let ((packet (the-as dma-packet (-> dma-buf base)))) - (set! (-> packet dma) (new 'static 'dma-tag :id (dma-tag-id cnt) :qwc 15)) + (set! (-> packet dma) (new 'static 'dma-tag :id (dma-tag-id cnt) :qwc 16)) (set! (-> packet vif0) (new 'static 'vif-tag)) (set! (-> packet vif1) (new 'static 'vif-tag :cmd (vif-cmd pc-port))) (set! (-> dma-buf base) (the pointer (&+ packet 16))) @@ -348,9 +348,10 @@ (set! (-> vec y) (-> *math-camera* fog-min)) (set! (-> vec z) (-> *math-camera* fog-max)) ) - (charp<-string (the (pointer uint8) (&-> data-ptr 14)) (symbol->string (-> lev nickname))) + (set! (-> data-ptr 14) (-> (math-camera-pos) quad)) + (charp<-string (the (pointer uint8) (&-> data-ptr 15)) (symbol->string (-> lev nickname))) ) - (&+! (-> dma-buf base) (* 16 15)) + (&+! (-> dma-buf base) (* 16 16)) ) ;;;;;;;;;;;;;;;;;;;;;