Collision mesh extraction (#1330)

* temp

* extract collision mesh

* temp

* improve

* toggle, cleanup
This commit is contained in:
water111 2022-04-25 21:53:23 -04:00 committed by GitHub
parent 1bede6954d
commit 61766d2d22
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 877 additions and 46 deletions

View file

@ -233,6 +233,10 @@ void Texture::serialize(Serializer& ser) {
ser.from_ptr(&load_to_pool);
}
void CollisionMesh::serialize(Serializer& ser) {
ser.from_pod_vector(&vertices);
}
void Level::serialize(Serializer& ser) {
ser.from_ptr(&version);
if (ser.is_loading() && version != TFRAG3_VERSION) {
@ -282,6 +286,8 @@ void Level::serialize(Serializer& ser) {
tree.serialize(ser);
}
collision.serialize(ser);
ser.from_ptr(&version2);
if (ser.is_loading() && version2 != TFRAG3_VERSION) {
ASSERT_MSG(false, fmt::format(
@ -352,6 +358,9 @@ std::array<int, MemoryUsageCategory::NUM_CATEGORIES> Level::get_memory_usage() c
result[SHRUB_IND] += sizeof(u32) * shrub_tree.indices.size();
}
// collision
result[COLLISION] += sizeof(CollisionMesh::Vertex) * collision.vertices.size();
return result;
}

View file

@ -44,10 +44,13 @@ enum MemoryUsageCategory {
SHRUB_TIME_OF_DAY,
SHRUB_VERT,
SHRUB_IND,
COLLISION,
NUM_CATEGORIES
};
constexpr int TFRAG3_VERSION = 14;
constexpr int TFRAG3_VERSION = 15;
// These vertices should be uploaded to the GPU at load time and don't change
struct PreloadedVertex {
@ -306,6 +309,18 @@ struct ShrubTree {
void unpack();
};
struct CollisionMesh {
struct Vertex {
float x, y, z;
u32 flags;
s16 nx, ny, nz;
u16 pad;
};
static_assert(sizeof(Vertex) == 24);
std::vector<Vertex> vertices;
void serialize(Serializer& ser);
};
constexpr int TFRAG_GEOS = 3;
constexpr int TIE_GEOS = 4;
@ -316,6 +331,7 @@ struct Level {
std::array<std::vector<TfragTree>, TFRAG_GEOS> tfrag_trees;
std::array<std::vector<TieTree>, TIE_GEOS> tie_trees;
std::vector<ShrubTree> shrub_trees;
CollisionMesh collision;
u16 version2 = TFRAG3_VERSION;
void serialize(Serializer& ser);

View file

@ -96,6 +96,13 @@ class Vector {
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> result;
for (int i = 0; i < Size; i++) {

View file

@ -51,10 +51,11 @@ add_library(
IR2/LabelDB.cpp
IR2/OpenGoalMapping.cpp
level_extractor/BspHeader.cpp
level_extractor/extract_collide_frags.cpp
level_extractor/extract_level.cpp
level_extractor/extract_tfrag.cpp
level_extractor/extract_tie.cpp
level_extractor/BspHeader.cpp
level_extractor/extract_shrub.cpp
ObjectFile/LinkedObjectFile.cpp

View file

@ -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.is_pal = cfg.at("is_pal").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>>();
for (const auto& x : allowed) {

View file

@ -100,6 +100,7 @@ struct Config {
bool process_game_text = false;
bool process_game_count = false;
bool rip_levels = false;
bool extract_collision = false;
bool write_hex_near_instructions = false;
bool hexdump_code = false;

View file

@ -86,5 +86,8 @@
// turn this on to extract level background graphics data
"levels_extract": true,
// turn this on if you want extracted levels to be saved out as .obj files
"levels_convert_to_obj": false
"levels_convert_to_obj": false,
// should we extract collision meshes?
// these can be displayed in game, but makes the .fr3 files slightly larger
"extract_collision": true
}

View file

@ -290,7 +290,7 @@ void decompile(std::filesystem::path jak1_input_files) {
// levels
{
extract_all_levels(db, tex_db, config.levels_to_extract, "GAME.CGO", config.hacks,
config.rip_levels);
config.rip_levels, config.extract_collision);
}
}

View file

@ -59,6 +59,14 @@ std::string Vector::print_meters(int indent) const {
return result;
}
std::string Vector::print_decimal(int indent) const {
s32 d[4];
memcpy(d, data, 16);
std::string is(indent, ' ');
std::string result;
result += fmt::format("{}<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) {
file_type = read_type_field(ref, "file-type", dts, true);
file_name = read_string_field(ref, "file-name", dts, true);
@ -887,6 +895,15 @@ void PrototypeBucketTie::read_from_file(TypedRef ref,
dists.read_from_file(get_field_ref(ref, "dists", dts));
rdists.read_from_file(get_field_ref(ref, "rdists", dts));
stiffness = read_plain_data_field<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);
for (int i = 0; i < 4; i++) {
@ -1117,6 +1134,120 @@ std::string DrawableTreeInstanceTie::my_type() const {
return "drawable-tree-instance-tie";
}
void DrawableTreeCollideFragment::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
DrawStats* stats) {
s16 length = read_plain_data_field<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
//////////////////////////
@ -1584,6 +1715,13 @@ std::unique_ptr<DrawableTree> make_drawable_tree(TypedRef ref,
tree->read_from_file(ref, dts, stats);
return tree;
}
if (ref.type->get_name() == "drawable-tree-collide-fragment") {
auto tree = std::make_unique<DrawableTreeCollideFragment>();
tree->read_from_file(ref, dts, stats);
return tree;
}
auto tree = std::make_unique<DrawableTreeUnknown>();
tree->read_from_file(ref, dts, stats);
return tree;

View file

@ -14,6 +14,9 @@ class DecompilerTypeSystem;
} // namespace decompiler
namespace level_tools {
u32 deref_u32(const Ref& ref, int word_offset);
struct PrintSettings {
bool print_tfrag = false;
bool expand_draw_node = false;
@ -23,7 +26,8 @@ struct PrintSettings {
bool expand_drawable_tree_tie_proto_data = false;
bool expand_drawable_tree_instance_tie = false;
bool expand_drawable_tree_actor = false;
bool expand_shrub = true;
bool expand_shrub = false;
bool expand_collide = false;
};
struct DrawStats {
@ -50,6 +54,7 @@ struct Vector {
std::string print(int indent = 0) const;
std::string print_meters(int indent = 0) const;
std::string print_decimal(int indent = 0) const;
};
// a matrix with 16-bit integers.
@ -181,6 +186,68 @@ struct DrawableTreeActor : public DrawableTree {
std::vector<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
/////////////////////
@ -382,6 +449,7 @@ struct PrototypeBucketTie {
// todo envmap shader
// todo collide-frag
DrawableInlineArrayCollideFragment collide_frag;
// todo tie-colors
// todo data

View 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

View 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

View file

@ -6,6 +6,7 @@
#include "decompiler/level_extractor/extract_tfrag.h"
#include "decompiler/level_extractor/extract_tie.h"
#include "decompiler/level_extractor/extract_shrub.h"
#include "decompiler/level_extractor/extract_collide_frags.h"
#include "common/util/compress.h"
#include "common/util/FileUtil.h"
#include "common/util/SimpleThreadGroup.h"
@ -77,7 +78,8 @@ void print_memory_usage(const tfrag3::Level& lev, int uncompressed_data_size) {
{"tfrag-bvh", memory_use_by_category[tfrag3::MemoryUsageCategory::TFRAG_BVH]},
{"shrub-colors", memory_use_by_category[tfrag3::MemoryUsageCategory::SHRUB_TIME_OF_DAY]},
{"shrub-vert", memory_use_by_category[tfrag3::MemoryUsageCategory::SHRUB_VERT]},
{"shrub-ind", memory_use_by_category[tfrag3::MemoryUsageCategory::SHRUB_IND]}};
{"shrub-ind", memory_use_by_category[tfrag3::MemoryUsageCategory::SHRUB_IND]},
{"collision", memory_use_by_category[tfrag3::MemoryUsageCategory::COLLISION]}};
for (auto& known : known_categories) {
total_accounted += known.second;
}
@ -166,7 +168,8 @@ void extract_from_level(const ObjectFileDB& db,
const TextureDB& tex_db,
const std::string& dgo_name,
const DecompileHacks& hacks,
bool dump_level) {
bool dump_level,
bool extract_collision) {
if (db.obj_files_by_dgo.count(dgo_name) == 0) {
lg::warn("Skipping extract for {} because the DGO was not part of the input", dgo_name);
return;
@ -192,9 +195,9 @@ void extract_from_level(const ObjectFileDB& db,
/*
level_tools::PrintSettings settings;
settings.expand_shrub = true;
settings.expand_collide = true;
fmt::print("{}\n", bsp_header.print(settings));
*/
*/
const std::set<std::string> tfrag_trees = {
"drawable-tree-tfrag", "drawable-tree-trans-tfrag", "drawable-tree-dirt-tfrag",
@ -204,6 +207,15 @@ void extract_from_level(const ObjectFileDB& db,
add_all_textures_from_level(tfrag_level, dgo_name, tex_db);
std::vector<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) {
if (tfrag_trees.count(draw_tree->my_type())) {
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);
extract_shrub(as_shrub_tree, fmt::format("{}-{}-shrub", dgo_name, i++),
bsp_header.texture_remap_table, tex_db, {}, tfrag_level, dump_level);
} else if (draw_tree->my_type() == "drawable-tree-collide-fragment" && extract_collision) {
auto as_collide_frags =
dynamic_cast<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 {
// fmt::print(" unsupported tree {}\n", draw_tree->my_type());
}
}
tfrag_level.level_name = level_name;
Serializer ser;
tfrag_level.serialize(ser);
auto compressed =
@ -249,11 +271,14 @@ void extract_all_levels(const ObjectFileDB& db,
const std::vector<std::string>& dgo_names,
const std::string& common_name,
const DecompileHacks& hacks,
bool debug_dump_level) {
bool debug_dump_level,
bool extract_collision) {
extract_common(db, tex_db, common_name);
SimpleThreadGroup threads;
threads.run(
[&](int idx) { extract_from_level(db, tex_db, dgo_names[idx], hacks, debug_dump_level); },
[&](int idx) {
extract_from_level(db, tex_db, dgo_names[idx], hacks, debug_dump_level, extract_collision);
},
dgo_names.size());
threads.join();
}

View file

@ -10,7 +10,8 @@ void extract_from_level(const ObjectFileDB& db,
const TextureDB& tex_db,
const std::string& dgo_name,
const DecompileHacks& hacks,
bool dump_level);
bool dump_level,
bool extract_collision);
void extract_common(const ObjectFileDB& db, const TextureDB& tex_db, const std::string& dgo_name);
// extract everything
@ -19,5 +20,6 @@ void extract_all_levels(const ObjectFileDB& db,
const std::vector<std::string>& dgo_names,
const std::string& common_name,
const DecompileHacks& hacks,
bool debug_dump_level);
bool debug_dump_level,
bool extract_collision);
} // namespace decompiler

View file

@ -210,7 +210,7 @@ int main(int argc, char** argv) {
if (config.levels_extract) {
extract_all_levels(db, tex_db, config.levels_to_extract, "GAME.CGO", config.hacks,
config.rip_levels);
config.rip_levels, config.extract_collision);
}
fmt::print("[Mem] After extraction: {} MB\n", get_peak_rss() / (1024 * 1024));

View file

@ -234,7 +234,8 @@ TypedRef typed_ref_from_basic(const Ref& object, const decompiler::DecompilerTyp
const auto& word = object.data->words_by_seg.at(object.seg).at(byte_in_words / 4);
if (word.kind() != decompiler::LinkedWord::TYPE_PTR) {
throw Error("typed_ref_from_basic did not get a type tag (offset {} words)", byte_in_words / 4);
throw Error("typed_ref_from_basic did not get a type tag (offset {} words). Got {}",
byte_in_words / 4, (int)word.kind());
}
TypedRef result;
@ -252,7 +253,8 @@ Ref deref_label(const Ref& object) {
const auto& word = object.data->words_by_seg.at(object.seg).at(byte_in_words / 4);
if (word.kind() != decompiler::LinkedWord::PTR) {
throw Error("deref_label did not get a label (offset {} words)", byte_in_words / 4);
throw Error("deref_label did not get a label (offset {} words). Got {} {}", byte_in_words / 4,
(int)word.kind(), word.data);
}
const auto& lab = object.data->labels.at(word.label_id());

View file

@ -96,6 +96,7 @@ set(RUNTIME_SOURCE
graphics/opengl_renderer/ocean/OceanTexture.cpp
graphics/opengl_renderer/ocean/OceanTexture_PC.cpp
graphics/opengl_renderer/BucketRenderer.cpp
graphics/opengl_renderer/CollideMeshRenderer.cpp
graphics/opengl_renderer/debug_gui.cpp
graphics/opengl_renderer/DirectRenderer.cpp
graphics/opengl_renderer/DirectRenderer2.cpp

View file

@ -59,7 +59,7 @@ void SkipRenderer::render(DmaFollower& dma,
}
void SharedRenderState::reset() {
has_camera_planes = false;
has_pc_data = false;
for (auto& x : occlusion_vis) {
x.valid = false;
}

View file

@ -43,11 +43,17 @@ struct SharedRenderState {
math::Vector<u8, 4> fog_color;
float fog_intensity = 1.f;
bool no_multidraw = false;
bool render_collision_mesh = false;
void reset();
bool has_camera_planes = false;
bool has_pc_data = false;
LevelVis occlusion_vis[2];
math::Vector4f camera_planes[4];
math::Vector4f camera_matrix[4];
math::Vector4f camera_hvdf_off;
math::Vector4f camera_fog;
math::Vector4f camera_pos;
EyeRenderer* eye_renderer = nullptr;

View 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);
}
}

View 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;
};

View file

@ -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.
* This is used for file I/O and unpacking.
@ -474,6 +489,15 @@ bool Loader::init_tie(Timer& timer, LevelData& data) {
return false;
}
bool Loader::init_collide(Timer& /*timer*/, LevelData& data) {
glGenBuffers(1, &data.collide_vertices);
glBindBuffer(GL_ARRAY_BUFFER, data.collide_vertices);
glBufferData(GL_ARRAY_BUFFER,
data.level->collision.vertices.size() * sizeof(tfrag3::CollisionMesh::Vertex),
data.level->collision.vertices.data(), GL_STATIC_DRAW);
return true;
}
bool Loader::upload_textures(Timer& timer, LevelData& data, TexturePool& texture_pool) {
// try to move level from initializing to initialized:
@ -583,12 +607,14 @@ void Loader::update(TexturePool& texture_pool) {
if (init_tie(loader_timer, lev.data)) {
if (init_tfrag(loader_timer, lev.data)) {
if (init_shrub(loader_timer, lev.data)) {
// we're done! lock before removing from loaded.
lk.lock();
it->second.data.load_id = m_id++;
if (init_collide(loader_timer, lev.data)) {
// we're done! lock before removing from loaded.
lk.lock();
it->second.data.load_id = m_id++;
m_loaded_tfrag3_levels[name] = std::move(lev);
m_initializing_tfrag3_levels.erase(it);
m_loaded_tfrag3_levels[name] = std::move(lev);
m_initializing_tfrag3_levels.erase(it);
}
}
}
}
@ -640,6 +666,8 @@ void Loader::update(TexturePool& texture_pool) {
}
}
glDeleteBuffers(1, &lev.second.data.collide_vertices);
m_loaded_tfrag3_levels.erase(lev.first);
break;
}

View file

@ -31,6 +31,7 @@ class Loader {
std::array<std::vector<TieOpenGL>, tfrag3::TIE_GEOS> tie_data;
std::array<std::vector<GLuint>, tfrag3::TIE_GEOS> tfrag_vertex_data;
std::vector<GLuint> shrub_vertex_data;
GLuint collide_vertices;
// internal load state
bool tie_opengl_created = false;
@ -56,6 +57,7 @@ class Loader {
const LevelData* get_tfrag3_level(const std::string& level_name);
void load_common(TexturePool& tex_pool, const std::string& name);
void set_want_levels(const std::vector<std::string>& levels);
std::vector<LevelData*> get_in_use_levels();
private:
struct Level {
@ -70,6 +72,7 @@ class Loader {
bool init_tie(Timer& timer, LevelData& data);
bool init_tfrag(Timer& timer, LevelData& data);
bool init_shrub(Timer& timer, LevelData& data);
bool init_collide(Timer& timer, LevelData& data);
// used by game and loader thread
std::unordered_map<std::string, Level> m_initializing_tfrag3_levels;

View file

@ -55,7 +55,6 @@ OpenGLRenderer::OpenGLRenderer(std::shared_ptr<TexturePool> texture_pool,
std::shared_ptr<Loader> loader)
: m_render_state(texture_pool, loader) {
// setup OpenGL errors
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(opengl_error_callback, nullptr);
// disable specific errors
@ -295,7 +294,6 @@ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) {
m_render_state.reset();
m_render_state.ee_main_memory = g_ee_main_mem;
m_render_state.offset_of_s7 = offset_of_s7();
m_render_state.has_camera_planes = false;
{
auto prof = m_profiler.root()->make_scoped_child("frame-setup");
@ -368,6 +366,7 @@ void OpenGLRenderer::draw_renderer_selection_window() {
ImGui::Begin("Renderer Debug");
ImGui::Checkbox("Use old single-draw", &m_render_state.no_multidraw);
ImGui::Checkbox("Render collision mesh", &m_render_state.render_collision_mesh);
ImGui::SliderFloat("Fog Adjust", &m_render_state.fog_intensity, 0, 10);
ImGui::Checkbox("Sky CPU", &m_render_state.use_sky_cpu);
ImGui::Checkbox("Occlusion Cull", &m_render_state.use_occlusion_culling);
@ -458,6 +457,12 @@ void OpenGLRenderer::dispatch_buckets(DmaFollower dma, ScopedProfilerNode& prof)
m_render_state.next_bucket += 16;
vif_interrupt_callback();
m_category_times[(int)m_bucket_categories[bucket_id]] += bucket_prof.get_elapsed_time();
// hack to draw the collision mesh in the middle the drawing
if (bucket_id == (int)BucketId::ALPHA_TEX_LEVEL0 - 1 && m_render_state.render_collision_mesh) {
auto p = prof.make_scoped_child("collision-draw");
m_collide_renderer.render(&m_render_state, p);
}
}
g_current_render = "";

View file

@ -8,6 +8,7 @@
#include "game/graphics/opengl_renderer/BucketRenderer.h"
#include "game/graphics/opengl_renderer/Profiler.h"
#include "game/graphics/opengl_renderer/opengl_utils.h"
#include "game/graphics/opengl_renderer/CollideMeshRenderer.h"
struct RenderOptions {
int window_height_px = 0;
@ -24,9 +25,19 @@ struct RenderOptions {
float pmode_alp_register = 0.f;
};
/*!
* Main OpenGL renderer.
* This handles the glClear and all game rendering, but not actual setup, synchronization or imgui
* stuff.
*
* It is simply a collection of bucket renderers, and a few special case ones.
*/
class OpenGLRenderer {
public:
OpenGLRenderer(std::shared_ptr<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);
private:
@ -58,6 +69,7 @@ class OpenGLRenderer {
std::array<float, (int)BucketCategory::MAX_CATEGORIES> m_category_times;
FullScreenDraw m_blackout_renderer;
CollideMeshRenderer m_collide_renderer;
float m_last_pmode_alp = 1.;
bool m_enable_fast_blackout_loads = true;

View file

@ -83,4 +83,9 @@ ShaderLibrary::ShaderLibrary() {
at(ShaderId::OCEAN_COMMON) = {"ocean_common"};
at(ShaderId::SHRUB) = {"shrub"};
at(ShaderId::SHADOW) = {"shadow"};
at(ShaderId::COLLISION) = {"collision"};
for (auto& shader : m_shaders) {
ASSERT_MSG(shader.okay(), "Shader compiled");
}
}

View file

@ -40,6 +40,7 @@ enum class ShaderId {
OCEAN_COMMON = 15,
SHADOW = 16,
SHRUB = 17,
COLLISION = 18,
MAX_SHADERS
};

View file

@ -649,7 +649,7 @@ void Sprite3::do_block_common(SpriteMode mode,
flush_sprites(render_state, prof, mode == ModeHUD);
}
if (mode == Mode2D && render_state->has_camera_planes && m_enable_culling) {
if (mode == Mode2D && render_state->has_pc_data && m_enable_culling) {
// we can skip sprites that are out of view
// it's probably possible to do this for 3D as well.
auto bsphere = m_vec_data_2d[sprite_idx].xyz_sx;

View file

@ -653,7 +653,7 @@ void SpriteRenderer::do_block_common(SpriteMode mode,
flush_sprites(render_state, prof);
}
if (mode == Mode2D && render_state->has_camera_planes && m_enable_culling) {
if (mode == Mode2D && render_state->has_pc_data && m_enable_culling) {
// we can skip sprites that are out of view
// it's probably possible to do this for 3D as well.
auto bsphere = m_vec_data_2d[sprite_idx].xyz_sx;

View file

@ -51,11 +51,11 @@ void Shrub::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProf
2 * (0xff & m_pc_port_data.itimes[i / 2].data()[2 * (i % 2)]) / 127.f;
}
update_render_state_from_pc_settings(render_state, m_pc_port_data);
for (int i = 0; i < 4; i++) {
settings.planes[i] = m_pc_port_data.planes[i];
render_state->camera_planes[i] = m_pc_port_data.planes[i];
}
render_state->has_camera_planes = true;
m_has_level = setup_for_level(m_pc_port_data.level_name, render_state);
render_all_trees(settings, render_state, prof);

View file

@ -125,11 +125,11 @@ void TFragment::render(DmaFollower& dma,
settings.occlusion_culling = render_state->occlusion_vis[m_level_id].data;
}
update_render_state_from_pc_settings(render_state, m_pc_port_data);
for (int i = 0; i < 4; i++) {
settings.planes[i] = m_pc_port_data.planes[i];
render_state->camera_planes[i] = m_pc_port_data.planes[i];
}
render_state->has_camera_planes = true;
if (m_override_time_of_day) {
for (int i = 0; i < 8; i++) {

View file

@ -353,11 +353,11 @@ void Tie3::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfi
settings.occlusion_culling = render_state->occlusion_vis[m_level_id].data;
}
update_render_state_from_pc_settings(render_state, m_pc_port_data);
for (int i = 0; i < 4; i++) {
settings.planes[i] = m_pc_port_data.planes[i];
render_state->camera_planes[i] = m_pc_port_data.planes[i];
}
render_state->has_camera_planes = true;
if (false) {
// for (int i = 0; i < 8; i++) {

View file

@ -659,3 +659,16 @@ u32 make_all_visible_index_list(std::pair<int, int>* group_out,
*num_tris_out = num_tris;
return idx_buffer_ptr;
}
void update_render_state_from_pc_settings(SharedRenderState* state, const TfragPcPortData& data) {
if (!state->has_pc_data) {
for (int i = 0; i < 4; i++) {
state->camera_planes[i] = data.planes[i];
state->camera_matrix[i] = data.camera[i];
}
state->camera_pos = data.cam_trans;
state->camera_hvdf_off = data.hvdf_off;
state->camera_fog = data.fog;
state->has_pc_data = true;
}
}

View file

@ -3,6 +3,19 @@
#include "common/math/Vector.h"
#include "game/graphics/opengl_renderer/BucketRenderer.h"
// data passed from game to PC renderers
// the GOAL code assumes this memory layout.
struct TfragPcPortData {
math::Vector4f planes[4];
math::Vector<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 {
math::Matrix4f math_camera;
math::Vector4f hvdf_offset;
@ -12,7 +25,6 @@ struct TfragRenderSettings {
math::Vector4f planes[4];
bool debug_culling = false;
const u8* occlusion_culling = nullptr;
// todo occlusion culling string.
};
enum class DoubleDrawKind { NONE, AFAIL_NO_DEPTH_WRITE };
@ -50,15 +62,7 @@ void cull_check_all_slow(const math::Vector4f* planes,
u8* out);
bool sphere_in_view_ref(const math::Vector4f& sphere, const math::Vector4f* planes);
struct TfragPcPortData {
math::Vector4f planes[4];
math::Vector<s32, 4> itimes[4];
math::Vector4f camera[4];
math::Vector4f hvdf_off;
math::Vector4f fog;
char level_name[12];
u32 tree_idx;
};
void update_render_state_from_pc_settings(SharedRenderState* state, const TfragPcPortData& data);
void make_all_visible_multidraws(std::pair<int, int>* draw_ptrs_out,
GLsizei* counts_out,

View file

@ -0,0 +1,9 @@
#version 430 core
out vec4 color;
in vec4 fragment_color;
void main() {
color = fragment_color;
}

View 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);
}
}

View file

@ -322,7 +322,7 @@
(defun add-pc-tfrag3-data ((dma-buf dma-buffer) (lev level))
"Add PC-port specific tfrag data"
(let ((packet (the-as dma-packet (-> dma-buf base))))
(set! (-> packet dma) (new 'static 'dma-tag :id (dma-tag-id cnt) :qwc 15))
(set! (-> packet dma) (new 'static 'dma-tag :id (dma-tag-id cnt) :qwc 16))
(set! (-> packet vif0) (new 'static 'vif-tag))
(set! (-> packet vif1) (new 'static 'vif-tag :cmd (vif-cmd pc-port)))
(set! (-> dma-buf base) (the pointer (&+ packet 16)))
@ -348,9 +348,10 @@
(set! (-> vec y) (-> *math-camera* fog-min))
(set! (-> vec z) (-> *math-camera* fog-max))
)
(charp<-string (the (pointer uint8) (&-> data-ptr 14)) (symbol->string (-> lev nickname)))
(set! (-> data-ptr 14) (-> (math-camera-pos) quad))
(charp<-string (the (pointer uint8) (&-> data-ptr 15)) (symbol->string (-> lev nickname)))
)
(&+! (-> dma-buf base) (* 16 15))
(&+! (-> dma-buf base) (* 16 16))
)
;;;;;;;;;;;;;;;;;;;;;