mirror of
https://github.com/open-goal/jak-project
synced 2026-08-04 01:04:11 -04:00
[debug] bsp vis viewer
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user