mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 11:26:18 -04:00
Collision mesh extraction (#1330)
* temp * extract collision mesh * temp * improve * toggle, cleanup
This commit is contained in:
parent
1bede6954d
commit
61766d2d22
|
@ -233,6 +233,10 @@ void Texture::serialize(Serializer& ser) {
|
||||||
ser.from_ptr(&load_to_pool);
|
ser.from_ptr(&load_to_pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CollisionMesh::serialize(Serializer& ser) {
|
||||||
|
ser.from_pod_vector(&vertices);
|
||||||
|
}
|
||||||
|
|
||||||
void Level::serialize(Serializer& ser) {
|
void Level::serialize(Serializer& ser) {
|
||||||
ser.from_ptr(&version);
|
ser.from_ptr(&version);
|
||||||
if (ser.is_loading() && version != TFRAG3_VERSION) {
|
if (ser.is_loading() && version != TFRAG3_VERSION) {
|
||||||
|
@ -282,6 +286,8 @@ void Level::serialize(Serializer& ser) {
|
||||||
tree.serialize(ser);
|
tree.serialize(ser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
collision.serialize(ser);
|
||||||
|
|
||||||
ser.from_ptr(&version2);
|
ser.from_ptr(&version2);
|
||||||
if (ser.is_loading() && version2 != TFRAG3_VERSION) {
|
if (ser.is_loading() && version2 != TFRAG3_VERSION) {
|
||||||
ASSERT_MSG(false, fmt::format(
|
ASSERT_MSG(false, fmt::format(
|
||||||
|
@ -352,6 +358,9 @@ std::array<int, MemoryUsageCategory::NUM_CATEGORIES> Level::get_memory_usage() c
|
||||||
result[SHRUB_IND] += sizeof(u32) * shrub_tree.indices.size();
|
result[SHRUB_IND] += sizeof(u32) * shrub_tree.indices.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// collision
|
||||||
|
result[COLLISION] += sizeof(CollisionMesh::Vertex) * collision.vertices.size();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,10 +44,13 @@ enum MemoryUsageCategory {
|
||||||
SHRUB_TIME_OF_DAY,
|
SHRUB_TIME_OF_DAY,
|
||||||
SHRUB_VERT,
|
SHRUB_VERT,
|
||||||
SHRUB_IND,
|
SHRUB_IND,
|
||||||
|
|
||||||
|
COLLISION,
|
||||||
|
|
||||||
NUM_CATEGORIES
|
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
|
// These vertices should be uploaded to the GPU at load time and don't change
|
||||||
struct PreloadedVertex {
|
struct PreloadedVertex {
|
||||||
|
@ -306,6 +309,18 @@ struct ShrubTree {
|
||||||
void unpack();
|
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<Vertex> vertices;
|
||||||
|
void serialize(Serializer& ser);
|
||||||
|
};
|
||||||
|
|
||||||
constexpr int TFRAG_GEOS = 3;
|
constexpr int TFRAG_GEOS = 3;
|
||||||
constexpr int TIE_GEOS = 4;
|
constexpr int TIE_GEOS = 4;
|
||||||
|
|
||||||
|
@ -316,6 +331,7 @@ struct Level {
|
||||||
std::array<std::vector<TfragTree>, TFRAG_GEOS> tfrag_trees;
|
std::array<std::vector<TfragTree>, TFRAG_GEOS> tfrag_trees;
|
||||||
std::array<std::vector<TieTree>, TIE_GEOS> tie_trees;
|
std::array<std::vector<TieTree>, TIE_GEOS> tie_trees;
|
||||||
std::vector<ShrubTree> shrub_trees;
|
std::vector<ShrubTree> shrub_trees;
|
||||||
|
CollisionMesh collision;
|
||||||
u16 version2 = TFRAG3_VERSION;
|
u16 version2 = TFRAG3_VERSION;
|
||||||
void serialize(Serializer& ser);
|
void serialize(Serializer& ser);
|
||||||
|
|
||||||
|
|
|
@ -96,6 +96,13 @@ class Vector {
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector<T, Size>& operator-=(const Vector<T, Size>& other) {
|
||||||
|
for (int i = 0; i < Size; i++) {
|
||||||
|
m_data[i] -= other[i];
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
Vector<T, Size> elementwise_multiply(const Vector<T, Size>& other) const {
|
Vector<T, Size> elementwise_multiply(const Vector<T, Size>& other) const {
|
||||||
Vector<T, Size> result;
|
Vector<T, Size> result;
|
||||||
for (int i = 0; i < Size; i++) {
|
for (int i = 0; i < Size; i++) {
|
||||||
|
|
|
@ -51,10 +51,11 @@ add_library(
|
||||||
IR2/LabelDB.cpp
|
IR2/LabelDB.cpp
|
||||||
IR2/OpenGoalMapping.cpp
|
IR2/OpenGoalMapping.cpp
|
||||||
|
|
||||||
|
level_extractor/BspHeader.cpp
|
||||||
|
level_extractor/extract_collide_frags.cpp
|
||||||
level_extractor/extract_level.cpp
|
level_extractor/extract_level.cpp
|
||||||
level_extractor/extract_tfrag.cpp
|
level_extractor/extract_tfrag.cpp
|
||||||
level_extractor/extract_tie.cpp
|
level_extractor/extract_tie.cpp
|
||||||
level_extractor/BspHeader.cpp
|
|
||||||
level_extractor/extract_shrub.cpp
|
level_extractor/extract_shrub.cpp
|
||||||
|
|
||||||
ObjectFile/LinkedObjectFile.cpp
|
ObjectFile/LinkedObjectFile.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<bool>();
|
config.generate_symbol_definition_map = cfg.at("generate_symbol_definition_map").get<bool>();
|
||||||
config.is_pal = cfg.at("is_pal").get<bool>();
|
config.is_pal = cfg.at("is_pal").get<bool>();
|
||||||
config.rip_levels = cfg.at("levels_convert_to_obj").get<bool>();
|
config.rip_levels = cfg.at("levels_convert_to_obj").get<bool>();
|
||||||
|
config.extract_collision = cfg.at("extract_collision").get<bool>();
|
||||||
|
|
||||||
auto allowed = cfg.at("allowed_objects").get<std::vector<std::string>>();
|
auto allowed = cfg.at("allowed_objects").get<std::vector<std::string>>();
|
||||||
for (const auto& x : allowed) {
|
for (const auto& x : allowed) {
|
||||||
|
|
|
@ -100,6 +100,7 @@ struct Config {
|
||||||
bool process_game_text = false;
|
bool process_game_text = false;
|
||||||
bool process_game_count = false;
|
bool process_game_count = false;
|
||||||
bool rip_levels = false;
|
bool rip_levels = false;
|
||||||
|
bool extract_collision = false;
|
||||||
|
|
||||||
bool write_hex_near_instructions = false;
|
bool write_hex_near_instructions = false;
|
||||||
bool hexdump_code = false;
|
bool hexdump_code = false;
|
||||||
|
|
|
@ -86,5 +86,8 @@
|
||||||
// turn this on to extract level background graphics data
|
// turn this on to extract level background graphics data
|
||||||
"levels_extract": true,
|
"levels_extract": true,
|
||||||
// turn this on if you want extracted levels to be saved out as .obj files
|
// 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
|
||||||
}
|
}
|
||||||
|
|
|
@ -290,7 +290,7 @@ void decompile(std::filesystem::path jak1_input_files) {
|
||||||
// levels
|
// levels
|
||||||
{
|
{
|
||||||
extract_all_levels(db, tex_db, config.levels_to_extract, "GAME.CGO", config.hacks,
|
extract_all_levels(db, tex_db, config.levels_to_extract, "GAME.CGO", config.hacks,
|
||||||
config.rip_levels);
|
config.rip_levels, config.extract_collision);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,14 @@ std::string Vector::print_meters(int indent) const {
|
||||||
return result;
|
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("{}<vector {:d} {:d} {:d} {:d}>\n", is, d[0], d[1], d[2], d[3]);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
void FileInfo::read_from_file(TypedRef ref, const decompiler::DecompilerTypeSystem& dts) {
|
void FileInfo::read_from_file(TypedRef ref, const decompiler::DecompilerTypeSystem& dts) {
|
||||||
file_type = read_type_field(ref, "file-type", dts, true);
|
file_type = read_type_field(ref, "file-type", dts, true);
|
||||||
file_name = read_string_field(ref, "file-name", 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));
|
dists.read_from_file(get_field_ref(ref, "dists", dts));
|
||||||
rdists.read_from_file(get_field_ref(ref, "rdists", dts));
|
rdists.read_from_file(get_field_ref(ref, "rdists", dts));
|
||||||
stiffness = read_plain_data_field<float>(ref, "stiffness", dts);
|
stiffness = read_plain_data_field<float>(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);
|
auto next_slot = get_field_ref(ref, "next", dts);
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
|
@ -1117,6 +1134,120 @@ std::string DrawableTreeInstanceTie::my_type() const {
|
||||||
return "drawable-tree-instance-tie";
|
return "drawable-tree-instance-tie";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DrawableTreeCollideFragment::read_from_file(TypedRef ref,
|
||||||
|
const decompiler::DecompilerTypeSystem& dts,
|
||||||
|
DrawStats* stats) {
|
||||||
|
s16 length = read_plain_data_field<s16>(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<s16>(ref, "id", dts);
|
||||||
|
length = read_plain_data_field<s16>(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<u16>(ref, "strip-data-len", dts);
|
||||||
|
poly_count = read_plain_data_field<u16>(ref, "poly-count", dts);
|
||||||
|
vertex_count = read_plain_data_field<u8>(ref, "vertex-count", dts);
|
||||||
|
vertex_data_qwc = read_plain_data_field<u8>(ref, "vertex-data-qwc", dts);
|
||||||
|
total_qwc = read_plain_data_field<u8>(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
|
// shrub
|
||||||
//////////////////////////
|
//////////////////////////
|
||||||
|
@ -1584,6 +1715,13 @@ std::unique_ptr<DrawableTree> make_drawable_tree(TypedRef ref,
|
||||||
tree->read_from_file(ref, dts, stats);
|
tree->read_from_file(ref, dts, stats);
|
||||||
return tree;
|
return tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ref.type->get_name() == "drawable-tree-collide-fragment") {
|
||||||
|
auto tree = std::make_unique<DrawableTreeCollideFragment>();
|
||||||
|
tree->read_from_file(ref, dts, stats);
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
auto tree = std::make_unique<DrawableTreeUnknown>();
|
auto tree = std::make_unique<DrawableTreeUnknown>();
|
||||||
tree->read_from_file(ref, dts, stats);
|
tree->read_from_file(ref, dts, stats);
|
||||||
return tree;
|
return tree;
|
||||||
|
|
|
@ -14,6 +14,9 @@ class DecompilerTypeSystem;
|
||||||
} // namespace decompiler
|
} // namespace decompiler
|
||||||
|
|
||||||
namespace level_tools {
|
namespace level_tools {
|
||||||
|
|
||||||
|
u32 deref_u32(const Ref& ref, int word_offset);
|
||||||
|
|
||||||
struct PrintSettings {
|
struct PrintSettings {
|
||||||
bool print_tfrag = false;
|
bool print_tfrag = false;
|
||||||
bool expand_draw_node = false;
|
bool expand_draw_node = false;
|
||||||
|
@ -23,7 +26,8 @@ struct PrintSettings {
|
||||||
bool expand_drawable_tree_tie_proto_data = false;
|
bool expand_drawable_tree_tie_proto_data = false;
|
||||||
bool expand_drawable_tree_instance_tie = false;
|
bool expand_drawable_tree_instance_tie = false;
|
||||||
bool expand_drawable_tree_actor = false;
|
bool expand_drawable_tree_actor = false;
|
||||||
bool expand_shrub = true;
|
bool expand_shrub = false;
|
||||||
|
bool expand_collide = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrawStats {
|
struct DrawStats {
|
||||||
|
@ -50,6 +54,7 @@ struct Vector {
|
||||||
|
|
||||||
std::string print(int indent = 0) const;
|
std::string print(int indent = 0) const;
|
||||||
std::string print_meters(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.
|
// a matrix with 16-bit integers.
|
||||||
|
@ -181,6 +186,68 @@ struct DrawableTreeActor : public DrawableTree {
|
||||||
std::vector<std::unique_ptr<DrawableInlineArray>> arrays;
|
std::vector<std::unique_ptr<DrawableInlineArray>> 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<CollideFragment> 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
|
// TFRAG
|
||||||
/////////////////////
|
/////////////////////
|
||||||
|
@ -382,6 +449,7 @@ struct PrototypeBucketTie {
|
||||||
|
|
||||||
// todo envmap shader
|
// todo envmap shader
|
||||||
// todo collide-frag
|
// todo collide-frag
|
||||||
|
DrawableInlineArrayCollideFragment collide_frag;
|
||||||
// todo tie-colors
|
// todo tie-colors
|
||||||
// todo data
|
// todo data
|
||||||
|
|
||||||
|
|
280
decompiler/level_extractor/extract_collide_frags.cpp
Normal file
280
decompiler/level_extractor/extract_collide_frags.cpp
Normal file
|
@ -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<math::Vector4f> vu0_buffer;
|
||||||
|
std::vector<math::Vector<u8, 3>> faces;
|
||||||
|
} unpacked;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Get all collide frags.
|
||||||
|
*/
|
||||||
|
std::vector<CollideListItem> build_all_frags_list(
|
||||||
|
const level_tools::DrawableTreeCollideFragment* tree,
|
||||||
|
const std::vector<const level_tools::DrawableTreeInstanceTie*>& ties) {
|
||||||
|
std::vector<CollideListItem> 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<level_tools::DrawableInlineArrayInstanceTie*>(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<float, 4> transform_tie(const std::array<math::Vector4f, 4> 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<math::Vector4f, 4> extract_tie_matrix(const u16* data) {
|
||||||
|
std::array<math::Vector4f, 4> 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<math::Vector4f, 4> 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<CollideListItem>& list) {
|
||||||
|
std::vector<math::Vector4f> verts;
|
||||||
|
std::vector<math::Vector<u32, 3>> 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<s16>();
|
||||||
|
|
||||||
|
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<const level_tools::DrawableTreeInstanceTie*>& 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
|
14
decompiler/level_extractor/extract_collide_frags.h
Normal file
14
decompiler/level_extractor/extract_collide_frags.h
Normal file
|
@ -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<const level_tools::DrawableTreeInstanceTie*>& ties,
|
||||||
|
const std::string& debug_name,
|
||||||
|
tfrag3::Level& out,
|
||||||
|
bool dump_level);
|
||||||
|
|
||||||
|
} // namespace decompiler
|
|
@ -6,6 +6,7 @@
|
||||||
#include "decompiler/level_extractor/extract_tfrag.h"
|
#include "decompiler/level_extractor/extract_tfrag.h"
|
||||||
#include "decompiler/level_extractor/extract_tie.h"
|
#include "decompiler/level_extractor/extract_tie.h"
|
||||||
#include "decompiler/level_extractor/extract_shrub.h"
|
#include "decompiler/level_extractor/extract_shrub.h"
|
||||||
|
#include "decompiler/level_extractor/extract_collide_frags.h"
|
||||||
#include "common/util/compress.h"
|
#include "common/util/compress.h"
|
||||||
#include "common/util/FileUtil.h"
|
#include "common/util/FileUtil.h"
|
||||||
#include "common/util/SimpleThreadGroup.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]},
|
{"tfrag-bvh", memory_use_by_category[tfrag3::MemoryUsageCategory::TFRAG_BVH]},
|
||||||
{"shrub-colors", memory_use_by_category[tfrag3::MemoryUsageCategory::SHRUB_TIME_OF_DAY]},
|
{"shrub-colors", memory_use_by_category[tfrag3::MemoryUsageCategory::SHRUB_TIME_OF_DAY]},
|
||||||
{"shrub-vert", memory_use_by_category[tfrag3::MemoryUsageCategory::SHRUB_VERT]},
|
{"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) {
|
for (auto& known : known_categories) {
|
||||||
total_accounted += known.second;
|
total_accounted += known.second;
|
||||||
}
|
}
|
||||||
|
@ -166,7 +168,8 @@ void extract_from_level(const ObjectFileDB& db,
|
||||||
const TextureDB& tex_db,
|
const TextureDB& tex_db,
|
||||||
const std::string& dgo_name,
|
const std::string& dgo_name,
|
||||||
const DecompileHacks& hacks,
|
const DecompileHacks& hacks,
|
||||||
bool dump_level) {
|
bool dump_level,
|
||||||
|
bool extract_collision) {
|
||||||
if (db.obj_files_by_dgo.count(dgo_name) == 0) {
|
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);
|
lg::warn("Skipping extract for {} because the DGO was not part of the input", dgo_name);
|
||||||
return;
|
return;
|
||||||
|
@ -192,9 +195,9 @@ void extract_from_level(const ObjectFileDB& db,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
level_tools::PrintSettings settings;
|
level_tools::PrintSettings settings;
|
||||||
settings.expand_shrub = true;
|
settings.expand_collide = true;
|
||||||
fmt::print("{}\n", bsp_header.print(settings));
|
fmt::print("{}\n", bsp_header.print(settings));
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const std::set<std::string> tfrag_trees = {
|
const std::set<std::string> tfrag_trees = {
|
||||||
"drawable-tree-tfrag", "drawable-tree-trans-tfrag", "drawable-tree-dirt-tfrag",
|
"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);
|
add_all_textures_from_level(tfrag_level, dgo_name, tex_db);
|
||||||
|
|
||||||
|
std::vector<const level_tools::DrawableTreeInstanceTie*> all_ties;
|
||||||
|
for (auto& draw_tree : bsp_header.drawable_tree_array.trees) {
|
||||||
|
auto as_tie_tree = dynamic_cast<level_tools::DrawableTreeInstanceTie*>(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) {
|
for (auto& draw_tree : bsp_header.drawable_tree_array.trees) {
|
||||||
if (tfrag_trees.count(draw_tree->my_type())) {
|
if (tfrag_trees.count(draw_tree->my_type())) {
|
||||||
auto as_tfrag_tree = dynamic_cast<level_tools::DrawableTreeTfrag*>(draw_tree.get());
|
auto as_tfrag_tree = dynamic_cast<level_tools::DrawableTreeTfrag*>(draw_tree.get());
|
||||||
|
@ -227,11 +239,21 @@ void extract_from_level(const ObjectFileDB& db,
|
||||||
ASSERT(as_shrub_tree);
|
ASSERT(as_shrub_tree);
|
||||||
extract_shrub(as_shrub_tree, fmt::format("{}-{}-shrub", dgo_name, i++),
|
extract_shrub(as_shrub_tree, fmt::format("{}-{}-shrub", dgo_name, i++),
|
||||||
bsp_header.texture_remap_table, tex_db, {}, tfrag_level, dump_level);
|
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<level_tools::DrawableTreeCollideFragment*>(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 {
|
} else {
|
||||||
// fmt::print(" unsupported tree {}\n", draw_tree->my_type());
|
// fmt::print(" unsupported tree {}\n", draw_tree->my_type());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tfrag_level.level_name = level_name;
|
||||||
|
|
||||||
Serializer ser;
|
Serializer ser;
|
||||||
tfrag_level.serialize(ser);
|
tfrag_level.serialize(ser);
|
||||||
auto compressed =
|
auto compressed =
|
||||||
|
@ -249,11 +271,14 @@ void extract_all_levels(const ObjectFileDB& db,
|
||||||
const std::vector<std::string>& dgo_names,
|
const std::vector<std::string>& dgo_names,
|
||||||
const std::string& common_name,
|
const std::string& common_name,
|
||||||
const DecompileHacks& hacks,
|
const DecompileHacks& hacks,
|
||||||
bool debug_dump_level) {
|
bool debug_dump_level,
|
||||||
|
bool extract_collision) {
|
||||||
extract_common(db, tex_db, common_name);
|
extract_common(db, tex_db, common_name);
|
||||||
SimpleThreadGroup threads;
|
SimpleThreadGroup threads;
|
||||||
threads.run(
|
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());
|
dgo_names.size());
|
||||||
threads.join();
|
threads.join();
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@ void extract_from_level(const ObjectFileDB& db,
|
||||||
const TextureDB& tex_db,
|
const TextureDB& tex_db,
|
||||||
const std::string& dgo_name,
|
const std::string& dgo_name,
|
||||||
const DecompileHacks& hacks,
|
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);
|
void extract_common(const ObjectFileDB& db, const TextureDB& tex_db, const std::string& dgo_name);
|
||||||
|
|
||||||
// extract everything
|
// extract everything
|
||||||
|
@ -19,5 +20,6 @@ void extract_all_levels(const ObjectFileDB& db,
|
||||||
const std::vector<std::string>& dgo_names,
|
const std::vector<std::string>& dgo_names,
|
||||||
const std::string& common_name,
|
const std::string& common_name,
|
||||||
const DecompileHacks& hacks,
|
const DecompileHacks& hacks,
|
||||||
bool debug_dump_level);
|
bool debug_dump_level,
|
||||||
|
bool extract_collision);
|
||||||
} // namespace decompiler
|
} // namespace decompiler
|
||||||
|
|
|
@ -210,7 +210,7 @@ int main(int argc, char** argv) {
|
||||||
|
|
||||||
if (config.levels_extract) {
|
if (config.levels_extract) {
|
||||||
extract_all_levels(db, tex_db, config.levels_to_extract, "GAME.CGO", config.hacks,
|
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));
|
fmt::print("[Mem] After extraction: {} MB\n", get_peak_rss() / (1024 * 1024));
|
||||||
|
|
|
@ -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);
|
const auto& word = object.data->words_by_seg.at(object.seg).at(byte_in_words / 4);
|
||||||
|
|
||||||
if (word.kind() != decompiler::LinkedWord::TYPE_PTR) {
|
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;
|
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);
|
const auto& word = object.data->words_by_seg.at(object.seg).at(byte_in_words / 4);
|
||||||
|
|
||||||
if (word.kind() != decompiler::LinkedWord::PTR) {
|
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());
|
const auto& lab = object.data->labels.at(word.label_id());
|
||||||
|
|
|
@ -96,6 +96,7 @@ set(RUNTIME_SOURCE
|
||||||
graphics/opengl_renderer/ocean/OceanTexture.cpp
|
graphics/opengl_renderer/ocean/OceanTexture.cpp
|
||||||
graphics/opengl_renderer/ocean/OceanTexture_PC.cpp
|
graphics/opengl_renderer/ocean/OceanTexture_PC.cpp
|
||||||
graphics/opengl_renderer/BucketRenderer.cpp
|
graphics/opengl_renderer/BucketRenderer.cpp
|
||||||
|
graphics/opengl_renderer/CollideMeshRenderer.cpp
|
||||||
graphics/opengl_renderer/debug_gui.cpp
|
graphics/opengl_renderer/debug_gui.cpp
|
||||||
graphics/opengl_renderer/DirectRenderer.cpp
|
graphics/opengl_renderer/DirectRenderer.cpp
|
||||||
graphics/opengl_renderer/DirectRenderer2.cpp
|
graphics/opengl_renderer/DirectRenderer2.cpp
|
||||||
|
|
|
@ -59,7 +59,7 @@ void SkipRenderer::render(DmaFollower& dma,
|
||||||
}
|
}
|
||||||
|
|
||||||
void SharedRenderState::reset() {
|
void SharedRenderState::reset() {
|
||||||
has_camera_planes = false;
|
has_pc_data = false;
|
||||||
for (auto& x : occlusion_vis) {
|
for (auto& x : occlusion_vis) {
|
||||||
x.valid = false;
|
x.valid = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,11 +43,17 @@ struct SharedRenderState {
|
||||||
math::Vector<u8, 4> fog_color;
|
math::Vector<u8, 4> fog_color;
|
||||||
float fog_intensity = 1.f;
|
float fog_intensity = 1.f;
|
||||||
bool no_multidraw = false;
|
bool no_multidraw = false;
|
||||||
|
bool render_collision_mesh = false;
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
bool has_camera_planes = false;
|
bool has_pc_data = false;
|
||||||
LevelVis occlusion_vis[2];
|
LevelVis occlusion_vis[2];
|
||||||
|
|
||||||
math::Vector4f camera_planes[4];
|
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;
|
EyeRenderer* eye_renderer = nullptr;
|
||||||
|
|
||||||
|
|
97
game/graphics/opengl_renderer/CollideMeshRenderer.cpp
Normal file
97
game/graphics/opengl_renderer/CollideMeshRenderer.cpp
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
12
game/graphics/opengl_renderer/CollideMeshRenderer.h
Normal file
12
game/graphics/opengl_renderer/CollideMeshRenderer.h
Normal file
|
@ -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;
|
||||||
|
};
|
|
@ -75,6 +75,21 @@ void Loader::set_want_levels(const std::vector<std::string>& levels) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Get all levels that are in memory and used very recently.
|
||||||
|
*/
|
||||||
|
std::vector<Loader::LevelData*> Loader::get_in_use_levels() {
|
||||||
|
std::vector<Loader::LevelData*> result;
|
||||||
|
std::unique_lock<std::mutex> 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.
|
* Loader function that runs in a completely separate thread.
|
||||||
* This is used for file I/O and unpacking.
|
* This is used for file I/O and unpacking.
|
||||||
|
@ -474,6 +489,15 @@ bool Loader::init_tie(Timer& timer, LevelData& data) {
|
||||||
return false;
|
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) {
|
bool Loader::upload_textures(Timer& timer, LevelData& data, TexturePool& texture_pool) {
|
||||||
// try to move level from initializing to initialized:
|
// 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_tie(loader_timer, lev.data)) {
|
||||||
if (init_tfrag(loader_timer, lev.data)) {
|
if (init_tfrag(loader_timer, lev.data)) {
|
||||||
if (init_shrub(loader_timer, lev.data)) {
|
if (init_shrub(loader_timer, lev.data)) {
|
||||||
// we're done! lock before removing from loaded.
|
if (init_collide(loader_timer, lev.data)) {
|
||||||
lk.lock();
|
// we're done! lock before removing from loaded.
|
||||||
it->second.data.load_id = m_id++;
|
lk.lock();
|
||||||
|
it->second.data.load_id = m_id++;
|
||||||
|
|
||||||
m_loaded_tfrag3_levels[name] = std::move(lev);
|
m_loaded_tfrag3_levels[name] = std::move(lev);
|
||||||
m_initializing_tfrag3_levels.erase(it);
|
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);
|
m_loaded_tfrag3_levels.erase(lev.first);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ class Loader {
|
||||||
std::array<std::vector<TieOpenGL>, tfrag3::TIE_GEOS> tie_data;
|
std::array<std::vector<TieOpenGL>, tfrag3::TIE_GEOS> tie_data;
|
||||||
std::array<std::vector<GLuint>, tfrag3::TIE_GEOS> tfrag_vertex_data;
|
std::array<std::vector<GLuint>, tfrag3::TIE_GEOS> tfrag_vertex_data;
|
||||||
std::vector<GLuint> shrub_vertex_data;
|
std::vector<GLuint> shrub_vertex_data;
|
||||||
|
GLuint collide_vertices;
|
||||||
|
|
||||||
// internal load state
|
// internal load state
|
||||||
bool tie_opengl_created = false;
|
bool tie_opengl_created = false;
|
||||||
|
@ -56,6 +57,7 @@ class Loader {
|
||||||
const LevelData* get_tfrag3_level(const std::string& level_name);
|
const LevelData* get_tfrag3_level(const std::string& level_name);
|
||||||
void load_common(TexturePool& tex_pool, const std::string& name);
|
void load_common(TexturePool& tex_pool, const std::string& name);
|
||||||
void set_want_levels(const std::vector<std::string>& levels);
|
void set_want_levels(const std::vector<std::string>& levels);
|
||||||
|
std::vector<LevelData*> get_in_use_levels();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Level {
|
struct Level {
|
||||||
|
@ -70,6 +72,7 @@ class Loader {
|
||||||
bool init_tie(Timer& timer, LevelData& data);
|
bool init_tie(Timer& timer, LevelData& data);
|
||||||
bool init_tfrag(Timer& timer, LevelData& data);
|
bool init_tfrag(Timer& timer, LevelData& data);
|
||||||
bool init_shrub(Timer& timer, LevelData& data);
|
bool init_shrub(Timer& timer, LevelData& data);
|
||||||
|
bool init_collide(Timer& timer, LevelData& data);
|
||||||
|
|
||||||
// used by game and loader thread
|
// used by game and loader thread
|
||||||
std::unordered_map<std::string, Level> m_initializing_tfrag3_levels;
|
std::unordered_map<std::string, Level> m_initializing_tfrag3_levels;
|
||||||
|
|
|
@ -55,7 +55,6 @@ OpenGLRenderer::OpenGLRenderer(std::shared_ptr<TexturePool> texture_pool,
|
||||||
std::shared_ptr<Loader> loader)
|
std::shared_ptr<Loader> loader)
|
||||||
: m_render_state(texture_pool, loader) {
|
: m_render_state(texture_pool, loader) {
|
||||||
// setup OpenGL errors
|
// setup OpenGL errors
|
||||||
|
|
||||||
glEnable(GL_DEBUG_OUTPUT);
|
glEnable(GL_DEBUG_OUTPUT);
|
||||||
glDebugMessageCallback(opengl_error_callback, nullptr);
|
glDebugMessageCallback(opengl_error_callback, nullptr);
|
||||||
// disable specific errors
|
// disable specific errors
|
||||||
|
@ -295,7 +294,6 @@ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) {
|
||||||
m_render_state.reset();
|
m_render_state.reset();
|
||||||
m_render_state.ee_main_memory = g_ee_main_mem;
|
m_render_state.ee_main_memory = g_ee_main_mem;
|
||||||
m_render_state.offset_of_s7 = offset_of_s7();
|
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");
|
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::Begin("Renderer Debug");
|
||||||
|
|
||||||
ImGui::Checkbox("Use old single-draw", &m_render_state.no_multidraw);
|
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::SliderFloat("Fog Adjust", &m_render_state.fog_intensity, 0, 10);
|
||||||
ImGui::Checkbox("Sky CPU", &m_render_state.use_sky_cpu);
|
ImGui::Checkbox("Sky CPU", &m_render_state.use_sky_cpu);
|
||||||
ImGui::Checkbox("Occlusion Cull", &m_render_state.use_occlusion_culling);
|
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;
|
m_render_state.next_bucket += 16;
|
||||||
vif_interrupt_callback();
|
vif_interrupt_callback();
|
||||||
m_category_times[(int)m_bucket_categories[bucket_id]] += bucket_prof.get_elapsed_time();
|
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 = "";
|
g_current_render = "";
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "game/graphics/opengl_renderer/BucketRenderer.h"
|
#include "game/graphics/opengl_renderer/BucketRenderer.h"
|
||||||
#include "game/graphics/opengl_renderer/Profiler.h"
|
#include "game/graphics/opengl_renderer/Profiler.h"
|
||||||
#include "game/graphics/opengl_renderer/opengl_utils.h"
|
#include "game/graphics/opengl_renderer/opengl_utils.h"
|
||||||
|
#include "game/graphics/opengl_renderer/CollideMeshRenderer.h"
|
||||||
|
|
||||||
struct RenderOptions {
|
struct RenderOptions {
|
||||||
int window_height_px = 0;
|
int window_height_px = 0;
|
||||||
|
@ -24,9 +25,19 @@ struct RenderOptions {
|
||||||
float pmode_alp_register = 0.f;
|
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 {
|
class OpenGLRenderer {
|
||||||
public:
|
public:
|
||||||
OpenGLRenderer(std::shared_ptr<TexturePool> texture_pool, std::shared_ptr<Loader> loader);
|
OpenGLRenderer(std::shared_ptr<TexturePool> texture_pool, std::shared_ptr<Loader> 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);
|
void render(DmaFollower dma, const RenderOptions& settings);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -58,6 +69,7 @@ class OpenGLRenderer {
|
||||||
|
|
||||||
std::array<float, (int)BucketCategory::MAX_CATEGORIES> m_category_times;
|
std::array<float, (int)BucketCategory::MAX_CATEGORIES> m_category_times;
|
||||||
FullScreenDraw m_blackout_renderer;
|
FullScreenDraw m_blackout_renderer;
|
||||||
|
CollideMeshRenderer m_collide_renderer;
|
||||||
|
|
||||||
float m_last_pmode_alp = 1.;
|
float m_last_pmode_alp = 1.;
|
||||||
bool m_enable_fast_blackout_loads = true;
|
bool m_enable_fast_blackout_loads = true;
|
||||||
|
|
|
@ -83,4 +83,9 @@ ShaderLibrary::ShaderLibrary() {
|
||||||
at(ShaderId::OCEAN_COMMON) = {"ocean_common"};
|
at(ShaderId::OCEAN_COMMON) = {"ocean_common"};
|
||||||
at(ShaderId::SHRUB) = {"shrub"};
|
at(ShaderId::SHRUB) = {"shrub"};
|
||||||
at(ShaderId::SHADOW) = {"shadow"};
|
at(ShaderId::SHADOW) = {"shadow"};
|
||||||
|
at(ShaderId::COLLISION) = {"collision"};
|
||||||
|
|
||||||
|
for (auto& shader : m_shaders) {
|
||||||
|
ASSERT_MSG(shader.okay(), "Shader compiled");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ enum class ShaderId {
|
||||||
OCEAN_COMMON = 15,
|
OCEAN_COMMON = 15,
|
||||||
SHADOW = 16,
|
SHADOW = 16,
|
||||||
SHRUB = 17,
|
SHRUB = 17,
|
||||||
|
COLLISION = 18,
|
||||||
MAX_SHADERS
|
MAX_SHADERS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -649,7 +649,7 @@ void Sprite3::do_block_common(SpriteMode mode,
|
||||||
flush_sprites(render_state, prof, mode == ModeHUD);
|
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
|
// we can skip sprites that are out of view
|
||||||
// it's probably possible to do this for 3D as well.
|
// it's probably possible to do this for 3D as well.
|
||||||
auto bsphere = m_vec_data_2d[sprite_idx].xyz_sx;
|
auto bsphere = m_vec_data_2d[sprite_idx].xyz_sx;
|
||||||
|
|
|
@ -653,7 +653,7 @@ void SpriteRenderer::do_block_common(SpriteMode mode,
|
||||||
flush_sprites(render_state, prof);
|
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
|
// we can skip sprites that are out of view
|
||||||
// it's probably possible to do this for 3D as well.
|
// it's probably possible to do this for 3D as well.
|
||||||
auto bsphere = m_vec_data_2d[sprite_idx].xyz_sx;
|
auto bsphere = m_vec_data_2d[sprite_idx].xyz_sx;
|
||||||
|
|
|
@ -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;
|
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++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
settings.planes[i] = m_pc_port_data.planes[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);
|
m_has_level = setup_for_level(m_pc_port_data.level_name, render_state);
|
||||||
render_all_trees(settings, render_state, prof);
|
render_all_trees(settings, render_state, prof);
|
||||||
|
|
|
@ -125,11 +125,11 @@ void TFragment::render(DmaFollower& dma,
|
||||||
settings.occlusion_culling = render_state->occlusion_vis[m_level_id].data;
|
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++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
settings.planes[i] = m_pc_port_data.planes[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) {
|
if (m_override_time_of_day) {
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
|
|
|
@ -353,11 +353,11 @@ void Tie3::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfi
|
||||||
settings.occlusion_culling = render_state->occlusion_vis[m_level_id].data;
|
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++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
settings.planes[i] = m_pc_port_data.planes[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) {
|
if (false) {
|
||||||
// for (int i = 0; i < 8; i++) {
|
// for (int i = 0; i < 8; i++) {
|
||||||
|
|
|
@ -659,3 +659,16 @@ u32 make_all_visible_index_list(std::pair<int, int>* group_out,
|
||||||
*num_tris_out = num_tris;
|
*num_tris_out = num_tris;
|
||||||
return idx_buffer_ptr;
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,19 @@
|
||||||
#include "common/math/Vector.h"
|
#include "common/math/Vector.h"
|
||||||
#include "game/graphics/opengl_renderer/BucketRenderer.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<s32, 4> 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 {
|
struct TfragRenderSettings {
|
||||||
math::Matrix4f math_camera;
|
math::Matrix4f math_camera;
|
||||||
math::Vector4f hvdf_offset;
|
math::Vector4f hvdf_offset;
|
||||||
|
@ -12,7 +25,6 @@ struct TfragRenderSettings {
|
||||||
math::Vector4f planes[4];
|
math::Vector4f planes[4];
|
||||||
bool debug_culling = false;
|
bool debug_culling = false;
|
||||||
const u8* occlusion_culling = nullptr;
|
const u8* occlusion_culling = nullptr;
|
||||||
// todo occlusion culling string.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class DoubleDrawKind { NONE, AFAIL_NO_DEPTH_WRITE };
|
enum class DoubleDrawKind { NONE, AFAIL_NO_DEPTH_WRITE };
|
||||||
|
@ -50,15 +62,7 @@ void cull_check_all_slow(const math::Vector4f* planes,
|
||||||
u8* out);
|
u8* out);
|
||||||
bool sphere_in_view_ref(const math::Vector4f& sphere, const math::Vector4f* planes);
|
bool sphere_in_view_ref(const math::Vector4f& sphere, const math::Vector4f* planes);
|
||||||
|
|
||||||
struct TfragPcPortData {
|
void update_render_state_from_pc_settings(SharedRenderState* state, const TfragPcPortData& data);
|
||||||
math::Vector4f planes[4];
|
|
||||||
math::Vector<s32, 4> itimes[4];
|
|
||||||
math::Vector4f camera[4];
|
|
||||||
math::Vector4f hvdf_off;
|
|
||||||
math::Vector4f fog;
|
|
||||||
char level_name[12];
|
|
||||||
u32 tree_idx;
|
|
||||||
};
|
|
||||||
|
|
||||||
void make_all_visible_multidraws(std::pair<int, int>* draw_ptrs_out,
|
void make_all_visible_multidraws(std::pair<int, int>* draw_ptrs_out,
|
||||||
GLsizei* counts_out,
|
GLsizei* counts_out,
|
||||||
|
|
9
game/graphics/opengl_renderer/shaders/collision.frag
Normal file
9
game/graphics/opengl_renderer/shaders/collision.frag
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#version 430 core
|
||||||
|
|
||||||
|
out vec4 color;
|
||||||
|
|
||||||
|
in vec4 fragment_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
color = fragment_color;
|
||||||
|
}
|
67
game/graphics/opengl_renderer/shaders/collision.vert
Normal file
67
game/graphics/opengl_renderer/shaders/collision.vert
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -322,7 +322,7 @@
|
||||||
(defun add-pc-tfrag3-data ((dma-buf dma-buffer) (lev level))
|
(defun add-pc-tfrag3-data ((dma-buf dma-buffer) (lev level))
|
||||||
"Add PC-port specific tfrag data"
|
"Add PC-port specific tfrag data"
|
||||||
(let ((packet (the-as dma-packet (-> dma-buf base))))
|
(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 vif0) (new 'static 'vif-tag))
|
||||||
(set! (-> packet vif1) (new 'static 'vif-tag :cmd (vif-cmd pc-port)))
|
(set! (-> packet vif1) (new 'static 'vif-tag :cmd (vif-cmd pc-port)))
|
||||||
(set! (-> dma-buf base) (the pointer (&+ packet 16)))
|
(set! (-> dma-buf base) (the pointer (&+ packet 16)))
|
||||||
|
@ -348,9 +348,10 @@
|
||||||
(set! (-> vec y) (-> *math-camera* fog-min))
|
(set! (-> vec y) (-> *math-camera* fog-min))
|
||||||
(set! (-> vec z) (-> *math-camera* fog-max))
|
(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))
|
||||||
)
|
)
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
Loading…
Reference in a new issue