[debug] bsp vis viewer

This commit is contained in:
water111
2025-01-11 17:59:03 -05:00
parent e836f09212
commit 93a7379ceb
17 changed files with 1115 additions and 8 deletions
+14 -3
View File
@@ -579,6 +579,11 @@ void MercModelGroup::serialize(Serializer& ser) {
ser.from_pod_vector(&vertices);
}
void DebugVisData::serialize(Serializer& ser) {
ser.from_pod_vector(&bsp_cell_vertices);
ser.from_pod_vector(&bsp_cell_indices);
}
void Level::serialize(Serializer& ser) {
ser.from_ptr(&version);
if (ser.is_loading() && version != TFRAG3_VERSION) {
@@ -642,6 +647,7 @@ void Level::serialize(Serializer& ser) {
collision.serialize(ser);
merc_data.serialize(ser);
debug_data.serialize(ser);
ser.from_ptr(&version2);
if (ser.is_loading() && version2 != TFRAG3_VERSION) {
@@ -762,6 +768,11 @@ void Hfragment::memory_usage(tfrag3::MemoryUsageTracker* tracker) const {
tracker->add(MemoryUsageCategory::HFRAG_CORNERS, corners.size() * sizeof(HfragmentCorner));
}
void DebugVisData::memory_usage(MemoryUsageTracker* tracker) const {
tracker->add(MemoryUsageCategory::DEBUG_VIS, bsp_cell_indices.size() * sizeof(u32));
tracker->add(MemoryUsageCategory::DEBUG_VIS, bsp_cell_vertices.size() * sizeof(BspVisVertex));
}
void Level::memory_usage(MemoryUsageTracker* tracker) const {
for (const auto& texture : textures) {
texture.memory_usage(tracker);
@@ -785,6 +796,7 @@ void Level::memory_usage(MemoryUsageTracker* tracker) const {
hfrag.memory_usage(tracker);
collision.memory_usage(tracker);
merc_data.memory_usage(tracker);
debug_data.memory_usage(tracker);
}
void print_memory_usage(const tfrag3::Level& lev, int uncompressed_data_size) {
@@ -829,9 +841,8 @@ void print_memory_usage(const tfrag3::Level& lev, int uncompressed_data_size) {
{"hfrag-verts", mem_use.data[tfrag3::MemoryUsageCategory::HFRAG_VERTS]},
{"hfrag-index", mem_use.data[tfrag3::MemoryUsageCategory::HFRAG_INDEX]},
{"hfrag-time-of-day", mem_use.data[tfrag3::MemoryUsageCategory::HFRAG_TIME_OF_DAY]},
{"hfrag-corners", mem_use.data[tfrag3::MemoryUsageCategory::HFRAG_CORNERS]}
};
{"hfrag-corners", mem_use.data[tfrag3::MemoryUsageCategory::HFRAG_CORNERS]},
{"debug-vis", mem_use.data[tfrag3::MemoryUsageCategory::DEBUG_VIS]}};
for (auto& known : known_categories) {
total_accounted += known.second;
}
+16 -1
View File
@@ -18,7 +18,7 @@ namespace tfrag3 {
// - if changing any large things (vertices, vis, bvh, colors, textures) update get_memory_usage
// - if adding a new category to the memory usage, update extract_level to print it.
constexpr int TFRAG3_VERSION = 43;
constexpr int TFRAG3_VERSION = 44;
enum MemoryUsageCategory {
TEXTURE,
@@ -67,6 +67,7 @@ enum MemoryUsageCategory {
HFRAG_CORNERS,
COLLISION,
DEBUG_VIS,
NUM_CATEGORIES
};
@@ -614,6 +615,19 @@ struct MercModelGroup {
void memory_usage(MemoryUsageTracker* tracker) const;
};
struct BspVisVertex {
float x, y, z;
u16 bsp_cell;
};
static_assert(sizeof(BspVisVertex) == 16);
struct DebugVisData {
std::vector<BspVisVertex> bsp_cell_vertices;
std::vector<uint32_t> bsp_cell_indices;
void serialize(Serializer& ser);
void memory_usage(MemoryUsageTracker* tracker) const;
};
//
constexpr int TFRAG_GEOS = 3;
@@ -630,6 +644,7 @@ struct Level {
Hfragment hfrag;
CollisionMesh collision;
MercModelGroup merc_data;
DebugVisData debug_data;
u16 version2 = TFRAG3_VERSION;
void serialize(Serializer& ser);
void memory_usage(MemoryUsageTracker* tracker) const;
+1
View File
@@ -58,6 +58,7 @@ add_library(
level_extractor/extract_actors.cpp
level_extractor/extract_collide_frags.cpp
level_extractor/extract_common.cpp
level_extractor/extract_debug_vis.cpp
level_extractor/extract_hfrag.cpp
level_extractor/extract_joint_group.cpp
level_extractor/extract_level.cpp
+87
View File
@@ -1995,6 +1995,92 @@ void AdgifShaderArray::read_from_file(TypedRef ref, const decompiler::Decompiler
sizeof(AdGifData) * length);
}
Jak1BspNodeRef bsp_node_ref_from_file(Ref node_array,
TypedRef parent,
bool is_front,
const decompiler::DecompilerTypeSystem& dts) {
const char* name = is_front ? "front" : "back";
auto type = get_word_kind_for_field(parent, name, dts);
Jak1BspNodeRef ret;
if (type == decompiler::LinkedWord::PTR) {
ret.is_leaf = false;
Ref child = deref_label(get_field_ref(parent, name, dts));
int byte_offset = child.byte_offset - node_array.byte_offset;
ASSERT((byte_offset & 31) == 0);
ret.index = byte_offset / 32;
} else if (type == decompiler::LinkedWord::PLAIN_DATA) {
ret.is_leaf = true;
uint32_t leaf_value = read_plain_data_field<uint32_t>(parent, name, dts);
ASSERT((leaf_value >> 16) == 0x8000);
ret.index = leaf_value & 0xffff;
} else {
ASSERT_NOT_REACHED();
}
return ret;
}
Jak1BspNode bsp_node_from_file(Ref node_array,
Ref ref,
const decompiler::DecompilerTypeSystem& dts) {
Jak1BspNode result;
TypedRef r(ref, dts.ts.lookup_type("bsp-node"));
result.front = bsp_node_ref_from_file(node_array, r, true, dts);
result.back = bsp_node_ref_from_file(node_array, r, false, dts);
result.front_flags = read_plain_data_field<uint32_t>(r, "front-flags", dts);
result.back_flags = read_plain_data_field<uint32_t>(r, "back-flags", dts);
result.plane.read_from_file(get_field_ref(r, "plane", dts));
result.discovered = true;
return result;
}
void print_node(const Jak1BspNode& node) {
fmt::print("[{:.3f} {:.3f} {:.3f} {:.3f}] ({} {} {}) ({} {} {})\n",
node.plane.data[0], node.plane.data[1], node.plane.data[2], node.plane.data[3],
node.front.is_leaf, node.front.index, node.front_flags,
node.back.is_leaf, node.back.index, node.back_flags
);
}
std::vector<Jak1BspNode> bsp_nodes_from_file(Ref node_array,
const decompiler::DecompilerTypeSystem& dts) {
std::vector<Jak1BspNode> nodes;
std::vector<int> to_explore = {0};
while (!to_explore.empty()) {
int node_idx = to_explore.back();
to_explore.pop_back();
// expand node array
if (nodes.size() <= node_idx) {
nodes.resize(node_idx + 1);
}
if (nodes.at(node_idx).discovered) {
continue;
}
Ref node_ref = node_array;
node_ref.byte_offset += 32 * node_idx;
nodes.at(node_idx) = bsp_node_from_file(node_array, node_ref, dts);
auto& n = nodes.at(node_idx);
if (!n.front.is_leaf) {
to_explore.push_back(n.front.index);
}
if (!n.back.is_leaf) {
to_explore.push_back(n.back.index);
}
// print_node(nodes.at(node_idx));
}
int known = 0;
for (auto& n : nodes) {
if (n.discovered) known++;
}
ASSERT((int)nodes.size() == known);
return nodes;
}
void BspHeader::read_from_file(const decompiler::LinkedObjectFile& file,
const decompiler::DecompilerTypeSystem& dts,
GameVersion version,
@@ -2012,6 +2098,7 @@ void BspHeader::read_from_file(const decompiler::LinkedObjectFile& file,
if (version == GameVersion::Jak1) {
adgifs.read_from_file(get_and_check_ref_to_basic(ref, "adgifs", "adgif-shader-array", dts),
dts);
jak1_bsp_nodes = bsp_nodes_from_file(deref_label(get_field_ref(ref, "nodes", dts)), dts);
}
texture_page_count = read_plain_data_field<s32>(ref, "texture-page-count", dts);
+17
View File
@@ -862,6 +862,22 @@ struct FileInfo {
std::string print(int indent = 0) const;
};
struct Jak1BspNodeRef {
bool is_leaf = false;
int index = 0;
};
struct Jak1BspNode {
Jak1BspNodeRef front;
Jak1BspNodeRef back;
uint32_t front_flags = 0;
uint32_t back_flags = 0;
Vector plane;
bool discovered = false;
};
struct BspHeader {
// (info file-info :offset 4)
FileInfo file_info;
@@ -904,6 +920,7 @@ struct BspHeader {
DrawableInlineArrayActor actors;
// (cameras (array entity-camera) :offset-assert 116)
// (nodes (inline-array bsp-node) :offset-assert 120)
std::vector<Jak1BspNode> jak1_bsp_nodes;
//
// (level level :offset-assert 124)
// (current-leaf-idx uint16 :offset-assert 128)
@@ -0,0 +1,703 @@
#include "extract_debug_vis.h"
#include "common/log/log.h"
#include "common/math/Vector.h"
namespace {
using Point = math::Vector3d;
using Plane = math::Vector4d; // Plane(a, b, c, d) -> ax + by + cz = d
Plane flip_plane_normal(const Plane& in) {
return in * -1;
}
math::Vector3d plane_normal(const Plane& in) {
return in.xyz();
}
math::Vector4d convert_vector(const level_tools::Vector& in) {
return math::Vector4d(in.data[0], in.data[1], in.data[2], in.data[3]);
}
double point_plane_check(const Point& pt, const Plane& plane) {
return pt.x() * plane.x() + pt.y() * plane.y() + pt.z() * plane.z() - plane.w();
}
/*!
* Find the largest leaf index.
*/
int max_leaf_idx(const std::vector<level_tools::Jak1BspNode>& nodes) {
int ret = -1;
for (auto& node : nodes) {
if (node.back.is_leaf)
ret = std::max(ret, node.back.index);
if (node.front.is_leaf)
ret = std::max(ret, node.front.index);
}
return ret;
}
/*!
* Description of the tree structure of BSP nodes.
*/
struct ParentData {
std::vector<int> node_parents; // parent_node = node_parents[child_node]
std::vector<int> leaf_parents; // parent_node = leaf_parents[leaf]
int num_leaves() const { return leaf_parents.size(); }
};
/*!
* Find the tree structure of BSP nodes from the array, that the BSP nodes are a tree, and there
* are no unparented/unused slots in either the leaf or node arrays. Things would still work if
* there were unused slots, but it just wastes memory and makes no sense.
*/
ParentData find_parents(const std::vector<level_tools::Jak1BspNode>& nodes) {
ParentData result;
result.node_parents.resize(nodes.size(), -1);
result.leaf_parents.resize(max_leaf_idx(nodes) + 1, -1);
// start at the root:
std::vector<int> to_explore = {0};
while (!to_explore.empty()) {
int node_idx = to_explore.back();
to_explore.pop_back();
auto& n = nodes.at(node_idx);
// add parents, and assert it's the first time we've seen the child (since it's a tree)
// you could imagine mapping two volumes to the same leaf, but it looks like they don't
if (n.front.is_leaf) {
ASSERT(result.leaf_parents.at(n.front.index) == -1);
result.leaf_parents.at(n.front.index) = node_idx;
} else {
to_explore.push_back(n.front.index);
ASSERT(result.node_parents.at(n.front.index) == -1);
result.node_parents.at(n.front.index) = node_idx;
}
if (n.back.is_leaf) {
ASSERT(result.leaf_parents.at(n.back.index) == -1);
result.leaf_parents.at(n.back.index) = node_idx;
} else {
to_explore.push_back(n.back.index);
ASSERT(result.node_parents.at(n.back.index) == -1);
result.node_parents.at(n.back.index) = node_idx;
}
}
// check for unused slots in leaf/node arrays.
int unparented_leaves = 0;
int unparented_nodes = 0;
for (auto np : result.node_parents) {
if (np == -1)
unparented_nodes++;
}
for (auto lp : result.leaf_parents) {
if (lp == -1)
unparented_leaves++;
}
ASSERT(unparented_nodes == 1); // the root is unparented
ASSERT(unparented_leaves == 0); // every leaf should be the child of a
return result;
}
/*!
* For a given leaf, find the bounding planes. A point (x, y, z) is inside the leaf, if for all
* planes (a, b, c, d):
*
* ax + by + cz - d > 0
*
*/
std::vector<Plane> planes_for_leaf(int leaf_idx,
const ParentData& tree,
const std::vector<level_tools::Jak1BspNode>& nodes) {
std::vector<Plane> planes;
int parent_idx = tree.leaf_parents.at(leaf_idx);
int child_idx = leaf_idx;
// each iteration adds the plane from parent_idx, then goes up the tree.
while (parent_idx != -1) {
// printf(" p %d\n", parent_idx);
const auto& node = nodes.at(parent_idx);
if (node.back.index == child_idx) {
// if this is the "back" child of the parent, flip the sign of the plane - this child is
// if the unflipped plane check fails.
planes.push_back(flip_plane_normal(convert_vector(node.plane)));
} else if (node.front.index == child_idx) {
planes.push_back(convert_vector(node.plane));
} else {
ASSERT_NOT_REACHED();
}
int new_parent = tree.node_parents.at(parent_idx);
child_idx = parent_idx;
parent_idx = new_parent;
}
return planes;
}
/*!
* Representation of a convex face as collection of points on a plane. The winding of the points
* should match the normal. (do we really care about this?)
*/
struct Face {
std::vector<Point> points;
Plane plane;
void verify() const;
double area() const;
Point avg_vertex_pos() const;
void flip_if_needed();
};
void Face::flip_if_needed() {
ASSERT(points.size() > 1);
const Point c = avg_vertex_pos();
const Point ab = points.at(1) - points.at(0);
const Point ac = c - points.at(0);
const math::Vector3d abac = ab.cross(ac);
const double winding_check = abac.dot(plane_normal(plane));
if (winding_check < 0) {
std::reverse(points.begin(), points.end());
}
}
/*!
* Get the average of all vertices. Because the face is convex, this returns a point in the area.
*/
Point Face::avg_vertex_pos() const {
Point ret = Point::zero();
for (auto& p : points) {
ret += p;
}
ret /= double(points.size());
return ret;
}
/*!
* Get the area of the face, also checking the winding order.
*/
double Face::area() const {
// let c be any point inside the face:
const Point c = avg_vertex_pos();
double total = 0;
for (int ai = 0; ai < points.size(); ai++) {
const int bi = (ai + 1) % points.size();
const Point ab = points.at(bi) - points.at(ai);
const Point ac = c - points.at(ai);
const math::Vector3d abac = ab.cross(ac);
const double winding_check = abac.dot(plane_normal(plane));
if (winding_check < 0) {
lg::die("winding check failed");
}
// printf("abac: %f\n", abac.length());
total += abac.length();
}
return total * 0.5;
}
/*!
* Verify that a face is valid: all points lie on the plane and the winding order is correct.
*/
void Face::verify() const {
// verify that all points lie on the plane.
for (const auto& pt : points) {
const double dist_from_plane = point_plane_check(pt, plane);
if (std::abs(dist_from_plane) > 200) {
lg::die("Point not on plane error {} {}", dist_from_plane, plane.to_string_aligned());
}
}
// checks winding:
const double a = area();
ASSERT(a > 0);
// TODO: could check for tangled stuff here?
}
/*!
* Representation of a volume as a collection of bounding faces.
*/
struct Volume {
std::vector<Face> faces;
void verify() const;
bool check_point_in_volume(const Point& pt) const;
double surface_area() const;
double volume() const;
Point some_internal_point() const;
};
/*!
* Is pt inside this volume?
*/
bool Volume::check_point_in_volume(const Point& pt) const {
for (auto& face : faces) {
if (point_plane_check(pt, face.plane) < 0) {
return false;
}
}
return true;
}
/*!
* Get a point inside the volume. Average of face centers now.
*/
Point Volume::some_internal_point() const {
Point ret = Point::zero();
for (auto& face : faces) {
ret += face.avg_vertex_pos();
}
return ret /= double(faces.size());
}
/*!
* Verify all faces in the volume, then verify face orientation.
*/
void Volume::verify() const {
for (const auto& face : faces) {
face.verify();
}
if (!check_point_in_volume(some_internal_point())) {
lg::die("pt in volume verify failed");
}
}
/*!
* Compute the surface area.
*/
double Volume::surface_area() const {
double ret = 0;
for (const auto& face : faces) {
ret += face.area();
}
return ret;
}
/*!
* Find the volume enclosed.
*/
double Volume::volume() const {
double ret = 0;
Point d = some_internal_point();
for (const auto& face : faces) {
Point c = face.avg_vertex_pos();
for (size_t ai = 0; ai < face.points.size(); ai++) {
size_t bi = (ai + 1) % face.points.size();
Point a = face.points.at(ai);
Point b = face.points.at(bi);
ret += std::abs((a - d).dot((b - d).cross(c - d))) / 6.;
}
}
return ret;
}
enum class FacePlaneResult { ALL_IN, ALL_OUT, SPLIT };
/*!
* Check a face against a plane to determine if the face is all on one side, all on the other, or in
* the middle.
*/
FacePlaneResult check_face_plane(const Face& face, const Plane& plane) {
bool found_in = false;
bool found_out = false;
for (auto& pt : face.points) {
if (point_plane_check(pt, plane) > 0) {
found_in = true;
} else {
found_out = true;
}
}
if (found_in && found_out) {
return FacePlaneResult::SPLIT;
} else if (found_in && !found_out) {
return FacePlaneResult::ALL_IN;
} else if (found_out && !found_in) {
return FacePlaneResult::ALL_OUT;
} else {
ASSERT_NOT_REACHED();
}
}
struct ClipFaceResult {
Face clipped_face;
Point a, b; // the new points added to this face.
};
/*!
* Compute the intersection between a line segment and plane.
*/
std::optional<Point> plane_line_segment_isect(const Plane& plane,
const Point& p0,
const Point& p1,
double* u) {
// let p = p0 + u * (p1 - p0)
// if p is on the plane, then dot(p, n) = d
// dot(p0 + u * (p1 - p0), n) = d
// dot(p0, n) + u * dot(p1 - p0, n) = d
// u = (d - dot(p0, n)) / dot(p1 - p0, n)
const math::Vector3d n = plane.xyz();
*u = (plane.w() - p0.dot(n)) / (p1 - p0).dot(n);
if (*u >= 0 && *u <= 1) {
Point ret = p0 + (p1 - p0) * *u;
// fmt::print("ret dist: {}, {} {}\n", point_plane_check(ret, plane), point_plane_check(p0,
// plane),
// point_plane_check(p1, plane));
return ret;
} else {
return std::nullopt;
}
}
/*!
* Clip a face, returning the new face, and the two vertices of the new face that intersect the
* clipping plane.
*/
ClipFaceResult clip_face(const Face& face, const Plane& plane) {
ClipFaceResult result;
result.clipped_face.plane = face.plane;
// determine if each point is in the new face.
std::vector<bool> pt_in;
for (auto& pt : face.points) {
pt_in.push_back(point_plane_check(pt, plane) > 0);
}
// loop around points on this face, including them in the new face only if they are inside the
// plane. When the permiter enters and exits the clipping plane, generate new vertices, and store
// these in the a/b outputs.
int saw_exit = 0;
int saw_enter = 0;
for (size_t i = 0; i < face.points.size(); i++) {
const auto p0 = face.points.at(i);
const auto p1 = face.points.at((i + 1) % face.points.size());
if (pt_in[i]) {
// if the point is in, just add it.
result.clipped_face.points.push_back(p0);
// exit point - need to insert a new vertex here!
if (!pt_in[(i + 1) % face.points.size()]) {
saw_exit++;
double u;
auto new_pt = plane_line_segment_isect(plane, p0, p1, &u);
ASSERT(new_pt.has_value());
// fmt::print("CLIP A: {} {} -> {} (u = {})\n", p0.to_string_aligned(),
// p1.to_string_aligned(),
// new_pt->to_string_aligned(), u);
result.clipped_face.points.push_back(*new_pt);
result.a = *new_pt;
}
} else {
if (pt_in[(i + 1) % face.points.size()]) {
// enter
saw_enter++;
double u;
auto new_pt = plane_line_segment_isect(plane, p0, p1, &u);
ASSERT(new_pt.has_value());
// fmt::print("CLIP B: {} {} -> {} (u = {})\n", p0.to_string_aligned(),
// p1.to_string_aligned(),
// new_pt->to_string_aligned(), u);
result.clipped_face.points.push_back(*new_pt);
result.b = *new_pt;
}
}
}
ASSERT(saw_enter == 1);
ASSERT(saw_exit == 1);
result.clipped_face.verify();
return result;
}
std::vector<Point> extract_face_ring(const std::vector<ClipFaceResult>& cfr) {
std::vector<Point> result;
ASSERT(cfr.size() > 1);
std::vector<bool> used(cfr.size(), false);
// add the first one
int num_used = 1;
used[0] = true;
// result.push_back(cfr[0].a);
result.push_back(cfr[0].b);
// for (auto& cf : cfr) {
// fmt::print("{} {}\n", cf.a.to_string_aligned(), cf.b.to_string_aligned());
// }
// gross N^2 loop to guess at the order of the edges in the new face beacuse I didn't track face
// connectivity... :(
// printf("efr: %d %ld\n", num_used, used.size());
while (num_used < used.size()) {
const auto& tgt = result.back();
double best_dist = std::numeric_limits<double>::max();
size_t best_idx = -1;
for (size_t i = 0; i < cfr.size(); i++) {
// printf("checking %ld (%d)\n", i, int(used[i]));
if (used[i]) {
continue;
}
const double dist = (cfr[i].a - tgt).squared_length();
if (dist < best_dist) {
best_dist = dist;
best_idx = i;
}
}
num_used++;
used.at(best_idx) = true;
// printf("PICKED %ld\n", best_idx);
// ASSERT(best_dist < 4096 * 4096 * 10); // hmm
result.push_back(cfr[best_idx].b);
}
// 0 b = [-16777216.000 16777216.000 -2211840.000]
// 3 a = [-16777216.000 16777216.000 -2211840.000] -> [-16777216.000 -16777216.000 -2211840.000]
// 1 a = [-16777216.000 -16777216.000 -2211840.000] -> [16777216.000 -16777216.000 -2211840.000]
// 2 a = [16777216.000 -16777216.000 -2211840.000] -> 16777216.000 16777216.000 -2211840.000]
// fmt::print("last in the result: {}\n", result.back().to_string_aligned());
// fmt::print("cfr[0]a : {}\n", cfr[0].a.to_string_aligned());
// fmt::print("diff : {}\n", (result.back() - cfr[0].a).to_string_aligned());
// fmt::print("diff : {}\n", (result.back() - cfr[0].a).squared_length());
// ASSERT(4096 * 4096 * 10 > (result.back() - cfr[0].a).squared_length());
return result;
}
// TODO: paranoid clipping that checks a = b, b = a, area sums.
Volume clip_volume(const Volume& vol, const Plane& plane) {
Volume new_volume;
std::vector<ClipFaceResult> split_faces;
// categorize faces
for (const auto& face : vol.faces) {
// printf("running on face\n");
switch (check_face_plane(face, plane)) {
case FacePlaneResult::ALL_IN:
new_volume.faces.push_back(face);
break;
case FacePlaneResult::SPLIT:
split_faces.push_back(clip_face(face, plane));
break;
case FacePlaneResult::ALL_OUT:
break;
}
}
size_t all_in_count = new_volume.faces.size();
// printf("face counts: %ld -> %ld in, %ld split\n", vol.faces.size(), all_in_count,
// split_faces.size());
// ASSERT(!new_volume.faces.empty());
if (!split_faces.empty()) {
// add clipped faces
for (auto& cf : split_faces) {
new_volume.faces.push_back(cf.clipped_face);
}
// build the new face
Face new_face;
new_face.plane = plane;
new_face.points = extract_face_ring(split_faces);
new_face.flip_if_needed();
new_face.verify();
new_volume.faces.push_back(new_face);
} else {
}
return new_volume;
}
Volume paranoid_clip_volume(const Volume& vol, const Plane& plane) {
// printf("CLIP STARTING!\n");
Volume clipped = clip_volume(vol, plane);
clipped.verify();
Volume other = clip_volume(vol, flip_plane_normal(plane));
other.verify();
// TODO check areas
const double a1 = vol.surface_area();
const double a2 = clipped.surface_area() + other.surface_area();
ASSERT(a2 >= a1); // todo: could be better.
const double v1 = vol.volume();
const double v2 = clipped.volume() + other.volume();
if (std::abs(v1 - v2) > 0.001 * v1) {
lg::die("Bad volumes: {} != {}\n", v1 / 1e6, v2 / 1e6);
}
// printf("CLIP Completed!\n");
return clipped;
}
/*!
* Given a sphere (x, y, z, radius), build a Volume for the axis-aligned bounding box of this
* sphere.
*/
Volume make_aabb_for_sphere(const level_tools::Vector& sphere) {
Volume volume;
Point origin(sphere.data[0], sphere.data[1], sphere.data[2]);
const double ox = origin.x();
const double oy = origin.y();
const double oz = origin.z();
const double r = sphere.data[3];
Face top_face;
top_face.plane = Plane(0, -1, 0, -oy - r);
top_face.points.emplace_back(ox - r, oy + r, oz - r);
top_face.points.emplace_back(ox + r, oy + r, oz - r);
top_face.points.emplace_back(ox + r, oy + r, oz + r);
top_face.points.emplace_back(ox - r, oy + r, oz + r);
volume.faces.push_back(top_face);
Face bot_face;
bot_face.plane = Plane(0, 1, 0, -oy - r);
bot_face.points.emplace_back(ox - r, oy - r, oz - r);
bot_face.points.emplace_back(ox - r, oy - r, oz + r);
bot_face.points.emplace_back(ox + r, oy - r, oz + r);
bot_face.points.emplace_back(ox + r, oy - r, oz - r);
volume.faces.push_back(bot_face);
Face s1_face;
s1_face.plane = Plane(-1, 0, 0, -ox - r);
s1_face.points.emplace_back(ox + r, oy - r, oz - r);
s1_face.points.emplace_back(ox + r, oy - r, oz + r);
s1_face.points.emplace_back(ox + r, oy + r, oz + r);
s1_face.points.emplace_back(ox + r, oy + r, oz - r);
volume.faces.push_back(s1_face);
Face s2_face;
s2_face.plane = Plane(1, 0, 0, -ox - r);
s2_face.points.emplace_back(ox - r, oy - r, oz - r);
s2_face.points.emplace_back(ox - r, oy + r, oz - r);
s2_face.points.emplace_back(ox - r, oy + r, oz + r);
s2_face.points.emplace_back(ox - r, oy - r, oz + r);
volume.faces.push_back(s2_face);
Face front_face;
front_face.plane = Plane(0, 0, -1, -oz - r);
front_face.points.emplace_back(ox - r, oy - r, oz + r);
front_face.points.emplace_back(ox - r, oy + r, oz + r);
front_face.points.emplace_back(ox + r, oy + r, oz + r);
front_face.points.emplace_back(ox + r, oy - r, oz + r);
volume.faces.push_back(front_face);
Face back_face;
back_face.plane = Plane(0, 0, 1, -oz - r);
back_face.points.emplace_back(ox - r, oy - r, oz - r);
back_face.points.emplace_back(ox + r, oy - r, oz - r);
back_face.points.emplace_back(ox + r, oy + r, oz - r);
back_face.points.emplace_back(ox - r, oy + r, oz - r);
volume.faces.push_back(back_face);
volume.verify();
const double expected_vol = 8 * r * r * r;
const double expected_sa = 24 * r * r;
const double vol = volume.volume();
const double sa = volume.surface_area();
if (std::abs(vol - expected_vol) > expected_vol * 0.001) {
lg::die("volume bad: {} {}\n", vol, expected_vol);
}
if (std::abs(sa - expected_sa) > expected_sa * 0.001) {
lg::die("sa bad: {} {}\n", vol, expected_vol);
}
// lg::print("got {} {}, {} {}\n", vol, expected_vol, sa, expected_sa);
return volume;
}
tfrag3::BspVisVertex make_vtx(const Point& pt, int leaf) {
tfrag3::BspVisVertex v;
v.bsp_cell = leaf;
v.x = pt.x();
v.y = pt.y();
v.z = pt.z();
return v;
}
void generate_volume_verts(std::vector<tfrag3::BspVisVertex>* verts,
std::vector<u32>* indices,
int leaf,
const Volume& volume) {
// TODO: we could reuse vertices between faces...
for (const auto& face : volume.faces) {
// center
indices->push_back(verts->size());
verts->push_back(make_vtx(face.avg_vertex_pos(), leaf));
// points
for (const auto& pt : face.points) {
indices->push_back(verts->size());
verts->push_back(make_vtx(pt, leaf));
}
// next fan
indices->push_back(UINT32_MAX);
}
}
} // namespace
void extract_bsp_cells(const level_tools::BspHeader& bsp, tfrag3::Level* out) {
printf("bsp cells for %s %f %f %f %f\n", bsp.name.c_str(), bsp.bsphere.data[0],
bsp.bsphere.data[1], bsp.bsphere.data[2], bsp.bsphere.data[3]);
// ugh - bsphere seems like it's not set.
level_tools::Vector derp;
derp.data[0] = 0;
derp.data[1] = 0;
derp.data[2] = 0;
derp.data[3] = 4096. * 4096. * 10.;
auto parents = find_parents(bsp.jak1_bsp_nodes);
for (int leaf_idx = 0; leaf_idx < parents.num_leaves(); leaf_idx++) {
// printf("-----------------------------LEAF %d/%d\n", leaf_idx, parents.num_leaves());
auto planes = planes_for_leaf(leaf_idx, parents, bsp.jak1_bsp_nodes);
std::reverse(planes.begin(), planes.end());
Volume vol = make_aabb_for_sphere(derp);
for (auto& clip : planes) {
vol = paranoid_clip_volume(vol, clip);
}
bool skip = false;
for (auto& face : vol.faces) {
for (auto& pt : face.points) {
for (int i = 0; i < 3; i++) {
if (std::abs(pt[i]) > derp.data[3] * 0.8) {
skip = true;
}
}
}
}
if (!skip) {
generate_volume_verts(&out->debug_data.bsp_cell_vertices, &out->debug_data.bsp_cell_indices,
leaf_idx, vol);
}
// ASSERT_NOT_REACHED();
}
}
namespace decompiler {
void extract_debug_vis(const level_tools::BspHeader& bsp, tfrag3::Level* out) {
extract_bsp_cells(bsp, out);
}
} // namespace decompiler
@@ -0,0 +1,10 @@
#pragma once
#include "common/custom_data/Tfrag3Data.h"
#include "decompiler/level_extractor/BspHeader.h"
namespace decompiler {
void extract_debug_vis(const level_tools::BspHeader& bsp, tfrag3::Level* out);
}
+6 -1
View File
@@ -12,6 +12,7 @@
#include "decompiler/level_extractor/BspHeader.h"
#include "decompiler/level_extractor/extract_actors.h"
#include "decompiler/level_extractor/extract_collide_frags.h"
#include "decompiler/level_extractor/extract_debug_vis.h"
#include "decompiler/level_extractor/extract_hfrag.h"
#include "decompiler/level_extractor/extract_joint_group.h"
#include "decompiler/level_extractor/extract_merc.h"
@@ -255,6 +256,10 @@ level_tools::BspHeader extract_bsp_from_level(const ObjectFileDB& db,
if (bsp_header.hfrag) {
extract_hfrag(bsp_header, tex_db, &level_data);
}
if (db.version() == GameVersion::Jak1) { // for now...
extract_debug_vis(bsp_header, &level_data);
}
level_data.level_name = bsp_header.name;
return bsp_header;
@@ -404,7 +409,7 @@ void extract_all_levels(const ObjectFileDB& db,
[&](int idx) {
extract_from_level(db, tex_db, dgo_names[idx], config, output_path, entities_dir);
},
dgo_names.size());
dgo_names.size(), 1);
threads.join();
}
+1
View File
@@ -39,6 +39,7 @@ set(RUNTIME_SOURCE
graphics/opengl_renderer/background/TFragment.cpp
graphics/opengl_renderer/background/Tie3.cpp
graphics/opengl_renderer/BlitDisplays.cpp
graphics/opengl_renderer/BspRenderer.cpp
graphics/opengl_renderer/BucketRenderer.cpp
graphics/opengl_renderer/CollideMeshRenderer.cpp
graphics/opengl_renderer/debug_gui.cpp
@@ -0,0 +1,139 @@
#include "BspRenderer.h"
#include "common/log/log.h"
#include <third-party/imgui/imgui.h>
BspRenderer::BspRenderer(GameVersion version) {
glGenVertexArrays(1, &m_vao);
}
void BspRenderer::render(SharedRenderState* render_state, ScopedProfilerNode& prof) {
// can't render
if (!render_state->has_pc_data) {
return;
}
// check loaded levels
auto levels = render_state->loader->get_in_use_levels();
if (levels.empty()) {
return;
}
glBindVertexArray(m_vao);
// see if we need to load meshes for any
for (const auto& level : levels) {
const auto& cached = m_level_cache.find(level->load_id);
if (cached == m_level_cache.end()) {
lg::info("BspRenderer loading for {}", level->level->level_name);
unload_cached_for_name(level->level->level_name);
load_level(level, &m_level_cache[level->load_id]);
}
render_level(render_state, prof, &m_level_cache.at(level->load_id));
}
}
void BspRenderer::load_level(LevelData* level, LevelCache* lc) {
glGenBuffers(1, &lc->vertex_buffer);
glGenBuffers(1, &lc->index_buffer);
glBindBuffer(GL_ARRAY_BUFFER, lc->vertex_buffer);
glBufferData(GL_ARRAY_BUFFER,
level->level->debug_data.bsp_cell_vertices.size() * sizeof(tfrag3::BspVisVertex),
level->level->debug_data.bsp_cell_vertices.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, lc->index_buffer);
glBufferData(GL_ARRAY_BUFFER, level->level->debug_data.bsp_cell_indices.size() * sizeof(u32),
level->level->debug_data.bsp_cell_indices.data(), GL_STATIC_DRAW);
lc->index_count = level->level->debug_data.bsp_cell_indices.size();
}
void BspRenderer::unload_cached_for_name(const std::string& name) {
for (auto it = m_level_cache.begin(); it != m_level_cache.end();) {
if (it->second.name == name) {
lg::info("BspRenderer first removing loaded {}", name);
unload_level(&it->second);
it = m_level_cache.erase(it);
} else {
++it;
}
}
}
void BspRenderer::unload_level(LevelCache* lc) {
glDeleteBuffers(1, &lc->index_buffer);
glDeleteBuffers(1, &lc->vertex_buffer);
}
BspRenderer::~BspRenderer() {
glDeleteVertexArrays(1, &m_vao);
}
void BspRenderer::draw_debug_window() {
ImGui::InputInt("min-leaf", &min_leaf);
ImGui::InputInt("max-leaf", &max_leaf);
}
void BspRenderer::render_level(SharedRenderState* render_state,
ScopedProfilerNode& prof,
LevelCache* lc) {
auto shader = render_state->shaders[ShaderId::BSP].id();
render_state->shaders[ShaderId::BSP].activate();
glUniformMatrix4fv(glGetUniformLocation(shader, "camera"), 1, GL_FALSE,
render_state->camera_matrix[0].data());
glUniform4f(glGetUniformLocation(shader, "hvdf_offset"), render_state->camera_hvdf_off[0],
render_state->camera_hvdf_off[1], render_state->camera_hvdf_off[2],
render_state->camera_hvdf_off[3]);
const auto& trans = render_state->camera_pos;
glUniform4f(glGetUniformLocation(shader, "camera_position"), trans[0], trans[1], trans[2],
trans[3]);
glUniform1f(glGetUniformLocation(shader, "fog_constant"), render_state->camera_fog.x());
glUniform1f(glGetUniformLocation(shader, "fog_min"), render_state->camera_fog.y());
glUniform1f(glGetUniformLocation(shader, "fog_max"), render_state->camera_fog.z());
glUniform1i(glGetUniformLocation(shader, "min_leaf"), (GLint)min_leaf);
glUniform1i(glGetUniformLocation(shader, "max_leaf"), (GLint)max_leaf);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_GEQUAL);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // ?
glDepthMask(GL_TRUE);
glBindBuffer(GL_ARRAY_BUFFER, lc->vertex_buffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, lc->index_buffer);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
glVertexAttribPointer(0, // location 0 in the shader
3, // 3 values per vert
GL_FLOAT, // floats
GL_FALSE, // normalized
sizeof(tfrag3::BspVisVertex), // stride
0 // offset (0)
);
glVertexAttribIPointer(1, // location 1 in the shader
1, // 3 values per vert
GL_UNSIGNED_SHORT, // u16
sizeof(tfrag3::BspVisVertex), // stride
(void*)offsetof(tfrag3::BspVisVertex, bsp_cell) // offset
);
glEnable(GL_PRIMITIVE_RESTART);
glPrimitiveRestartIndex(UINT32_MAX);
glUniform1i(glGetUniformLocation(shader, "wireframe"), 0);
// glDrawElements(GL_TRIANGLE_FAN, lc->index_count, GL_UNSIGNED_INT, nullptr);
if (true) {
glUniform1i(glGetUniformLocation(shader, "wireframe"), 1);
glDisable(GL_BLEND);
glDepthMask(GL_FALSE);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glLineWidth(3.0);
glDrawElements(GL_TRIANGLE_FAN, lc->index_count, GL_UNSIGNED_INT, nullptr);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glEnable(GL_BLEND);
glDepthMask(GL_TRUE);
}
prof.add_draw_call();
prof.add_tri(lc->index_count); // not exactly, but who cares.
}
@@ -0,0 +1,33 @@
#pragma once
#include "common/versions/versions.h"
#include "game/graphics/opengl_renderer/BucketRenderer.h"
class BspRenderer {
public:
BspRenderer(GameVersion version);
void render(SharedRenderState* render_state, ScopedProfilerNode& prof);
void draw_debug_window();
~BspRenderer();
private:
int min_leaf = 0;
int max_leaf = UINT16_MAX;
struct LevelCache {
std::string name;
GLuint index_buffer = -1;
GLuint vertex_buffer = -1;
int index_count = 0;
};
void unload_cached_for_name(const std::string& name);
void load_level(LevelData* level, LevelCache* lc);
void unload_level(LevelCache* lc);
void render_level(SharedRenderState* render_state, ScopedProfilerNode& prof, LevelCache* lc);
std::map<uint64_t, LevelCache> m_level_cache;
GLuint m_vao;
};
@@ -74,6 +74,7 @@ OpenGLRenderer::OpenGLRenderer(std::shared_ptr<TexturePool> texture_pool,
GameVersion version)
: m_render_state(texture_pool, loader, version),
m_collide_renderer(version),
m_bsp_renderer(version),
m_version(version) {
// requires OpenGL 4.3
#ifndef __APPLE__
@@ -1104,6 +1105,7 @@ void OpenGLRenderer::draw_renderer_selection_window() {
ImGui::Checkbox("Sky CPU", &m_render_state.use_sky_cpu);
ImGui::Checkbox("Occlusion Cull", &m_render_state.use_occlusion_culling);
ImGui::Checkbox("Blackout Loads", &m_enable_fast_blackout_loads);
m_bsp_renderer.draw_debug_window();
if (m_texture_animator && ImGui::TreeNode("Texture Animator")) {
m_texture_animator->draw_debug_window();
@@ -1304,9 +1306,16 @@ void OpenGLRenderer::dispatch_buckets_jak1(DmaFollower dma,
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 == 31 - 1 && Gfx::g_global_settings.collision_enable) {
auto p = prof.make_scoped_child("collision-draw");
m_collide_renderer.render(&m_render_state, p);
if (bucket_id == 31 - 1) {
if (Gfx::g_global_settings.collision_enable) {
auto p = prof.make_scoped_child("collision-draw");
m_collide_renderer.render(&m_render_state, p);
}
{
auto p = prof.make_scoped_child("bsp-draw");
m_bsp_renderer.render(&m_render_state, p);
}
}
}
@@ -5,6 +5,7 @@
#include "common/dma/dma_chain_read.h"
#include "game/graphics/opengl_renderer/BspRenderer.h"
#include "game/graphics/opengl_renderer/BucketRenderer.h"
#include "game/graphics/opengl_renderer/CollideMeshRenderer.h"
#include "game/graphics/opengl_renderer/Fbo.h"
@@ -120,6 +121,7 @@ class OpenGLRenderer {
std::array<float, (int)BucketCategory::MAX_CATEGORIES> m_category_times;
FullScreenDraw m_blackout_renderer;
CollideMeshRenderer m_collide_renderer;
BspRenderer m_bsp_renderer;
float m_last_pmode_alp = 1.;
bool m_enable_fast_blackout_loads = true;
+1
View File
@@ -131,6 +131,7 @@ ShaderLibrary::ShaderLibrary(GameVersion version) {
at(ShaderId::HFRAG) = {"hfrag", version};
at(ShaderId::HFRAG_MONTAGE) = {"hfrag_montage", version};
at(ShaderId::PLAIN_TEXTURE) = {"plain_texture", version};
at(ShaderId::BSP) = {"bsp", version};
for (auto& shader : m_shaders) {
ASSERT_MSG(shader.okay(), "error compiling shader");
+1
View File
@@ -64,6 +64,7 @@ enum class ShaderId {
HFRAG = 37,
HFRAG_MONTAGE = 38,
PLAIN_TEXTURE = 39,
BSP = 40,
MAX_SHADERS
};
@@ -0,0 +1,11 @@
#version 410 core
out vec4 color;
in vec4 fragment_color;
void main() {
if (fragment_color.a <= 0) discard;
color = fragment_color;
}
@@ -0,0 +1,61 @@
#version 410 core
layout (location = 0) in vec3 position_in;
layout (location = 1) in uint bsp_cell;
// layout (location = 2) in vec3 normal_in;
// layout (location = 3) in uint pat;
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 wireframe;
uniform int min_leaf;
uniform int max_leaf;
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;
// 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 *= SCISSOR_ADJUST * HEIGHT_SCALE;
//
fragment_color = vec4(0.12, 0.12, 0.12, 0.5);
if (bsp_cell < min_leaf || bsp_cell > max_leaf) {
fragment_color.a = 0;
}
}