mirror of
https://github.com/open-goal/jak-project.git
synced 2024-11-23 06:09:57 +00:00
Initial implementation of custom level tool (#1482)
* wip * learning about colors * gltf node stuff working * cleanup * support textures * bvh generation seems reasonable * tree layout * frag packer, untested and doesnt do real stripping yet * temp * working collide frags * handle bad inputs better * clean up * format * include * another include * reorganize for release build use
This commit is contained in:
parent
196c09a232
commit
c13934708a
@ -28,3 +28,4 @@ cp -r $SOURCE/decompiler/config $DEST/data/decompiler/
|
||||
cp -r $SOURCE/goal_src $DEST/data
|
||||
cp -r $SOURCE/game/assets $DEST/data/game/
|
||||
cp -r $SOURCE/game/graphics/opengl_renderer/shaders $DEST/data/game/graphics/opengl_renderer
|
||||
cp -r $SOURCE/custom_levels $DEST/data
|
||||
|
@ -24,3 +24,4 @@ cp -r $SOURCE/decompiler/config $DEST/data/decompiler/
|
||||
cp -r $SOURCE/goal_src $DEST/data
|
||||
cp -r $SOURCE/game/assets $DEST/data/game/
|
||||
cp -r $SOURCE/game/graphics/opengl_renderer/shaders $DEST/data/game/graphics/opengl_renderer
|
||||
cp -r $SOURCE/custom_levels $DEST/data
|
@ -4,6 +4,7 @@ add_library(common
|
||||
cross_sockets/XSocket.cpp
|
||||
cross_sockets/XSocketServer.cpp
|
||||
cross_sockets/XSocketClient.cpp
|
||||
custom_data/pack_helpers.cpp
|
||||
custom_data/TFrag3Data.cpp
|
||||
deserialization/subtitles/subtitles.cpp
|
||||
dma/dma.cpp
|
||||
|
@ -1,3 +1,5 @@
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include "Tfrag3Data.h"
|
||||
#include "common/util/Assert.h"
|
||||
|
||||
@ -21,6 +23,7 @@ void StripDraw::serialize(Serializer& ser) {
|
||||
ser.from_ptr(&mode);
|
||||
ser.from_ptr(&tree_tex_id);
|
||||
ser.from_pod_vector(&runs);
|
||||
ser.from_pod_vector(&plain_indices);
|
||||
ser.from_pod_vector(&vis_groups);
|
||||
ser.from_ptr(&num_triangles);
|
||||
}
|
||||
@ -64,6 +67,7 @@ void TfragTree::serialize(Serializer& ser) {
|
||||
ser.from_pod_vector(&packed_vertices.cluster_origins);
|
||||
ser.from_pod_vector(&colors);
|
||||
bvh.serialize(ser);
|
||||
ser.from_ptr(&use_strips);
|
||||
}
|
||||
|
||||
void TieTree::unpack() {
|
||||
@ -78,7 +82,7 @@ void TieTree::unpack() {
|
||||
vtx.x = proto_vtx.x;
|
||||
vtx.y = proto_vtx.y;
|
||||
vtx.z = proto_vtx.z;
|
||||
vtx.q = 1.f;
|
||||
vtx.q_unused = 1.f;
|
||||
vtx.s = proto_vtx.s;
|
||||
vtx.t = proto_vtx.t;
|
||||
i++;
|
||||
@ -93,7 +97,7 @@ void TieTree::unpack() {
|
||||
vtx.x = temp.x();
|
||||
vtx.y = temp.y();
|
||||
vtx.z = temp.z();
|
||||
vtx.q = 1.f;
|
||||
vtx.q_unused = 1.f;
|
||||
vtx.s = proto_vtx.s;
|
||||
vtx.t = proto_vtx.t;
|
||||
i++;
|
||||
@ -103,6 +107,7 @@ void TieTree::unpack() {
|
||||
|
||||
for (auto& draw : static_draws) {
|
||||
draw.unpacked.idx_of_first_idx_in_full_buffer = unpacked.indices.size();
|
||||
ASSERT(draw.plain_indices.empty());
|
||||
for (auto& run : draw.runs) {
|
||||
for (u32 ri = 0; ri < run.length; ri++) {
|
||||
unpacked.indices.push_back(run.vertex0 + ri);
|
||||
@ -152,7 +157,7 @@ void TfragTree::unpack() {
|
||||
o.z = cz + in.zoff * rescale;
|
||||
o.s = in.s / (1024.f);
|
||||
o.t = in.t / (1024.f);
|
||||
o.q = 1.f;
|
||||
o.q_unused = 1.f;
|
||||
o.color_index = in.color_index;
|
||||
}
|
||||
|
||||
@ -162,8 +167,12 @@ void TfragTree::unpack() {
|
||||
for (u32 ri = 0; ri < run.length; ri++) {
|
||||
unpacked.indices.push_back(run.vertex0 + ri);
|
||||
}
|
||||
unpacked.indices.push_back(UINT32_MAX);
|
||||
if (use_strips) {
|
||||
unpacked.indices.push_back(UINT32_MAX);
|
||||
}
|
||||
}
|
||||
unpacked.indices.insert(unpacked.indices.end(), draw.plain_indices.begin(),
|
||||
draw.plain_indices.end());
|
||||
}
|
||||
}
|
||||
|
||||
@ -359,6 +368,7 @@ std::array<int, MemoryUsageCategory::NUM_CATEGORIES> Level::get_memory_usage() c
|
||||
for (const auto& tfrag_tree : tfrag_tree_geoms) {
|
||||
for (const auto& draw : tfrag_tree.draws) {
|
||||
result[TFRAG_INDEX] += draw.runs.size() * sizeof(StripDraw::VertexRun);
|
||||
result[TFRAG_INDEX] += draw.plain_indices.size() * sizeof(u32);
|
||||
result[TFRAG_VIS] += draw.vis_groups.size() * sizeof(StripDraw::VisGroup);
|
||||
}
|
||||
result[TFRAG_VERTS] +=
|
||||
@ -417,4 +427,54 @@ std::array<int, MemoryUsageCategory::NUM_CATEGORIES> Level::get_memory_usage() c
|
||||
return result;
|
||||
}
|
||||
|
||||
void print_memory_usage(const tfrag3::Level& lev, int uncompressed_data_size) {
|
||||
int total_accounted = 0;
|
||||
auto memory_use_by_category = lev.get_memory_usage();
|
||||
|
||||
std::vector<std::pair<std::string, int>> known_categories = {
|
||||
{"texture", memory_use_by_category[tfrag3::MemoryUsageCategory::TEXTURE]},
|
||||
{"tie-deinst-vis", memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_DEINST_VIS]},
|
||||
{"tie-deinst-idx", memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_DEINST_INDEX]},
|
||||
{"tie-inst-vis", memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_INST_VIS]},
|
||||
{"tie-inst-idx", memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_INST_INDEX]},
|
||||
{"tie-bvh", memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_BVH]},
|
||||
{"tie-verts", memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_VERTS]},
|
||||
{"tie-colors", memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_TIME_OF_DAY]},
|
||||
{"tie-wind-inst-info",
|
||||
memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_WIND_INSTANCE_INFO]},
|
||||
{"tie-cidx", memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_CIDX]},
|
||||
{"tie-mats", memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_MATRICES]},
|
||||
{"tie-grps", memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_GRPS]},
|
||||
{"tfrag-vis", memory_use_by_category[tfrag3::MemoryUsageCategory::TFRAG_VIS]},
|
||||
{"tfrag-idx", memory_use_by_category[tfrag3::MemoryUsageCategory::TFRAG_INDEX]},
|
||||
{"tfrag-vert", memory_use_by_category[tfrag3::MemoryUsageCategory::TFRAG_VERTS]},
|
||||
{"tfrag-colors", memory_use_by_category[tfrag3::MemoryUsageCategory::TFRAG_TIME_OF_DAY]},
|
||||
{"tfrag-cluster", memory_use_by_category[tfrag3::MemoryUsageCategory::TFRAG_CLUSTER]},
|
||||
{"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]},
|
||||
{"collision", memory_use_by_category[tfrag3::MemoryUsageCategory::COLLISION]},
|
||||
{"merc-vert", memory_use_by_category[tfrag3::MemoryUsageCategory::MERC_VERT]},
|
||||
{"merc-idx", memory_use_by_category[tfrag3::MemoryUsageCategory::MERC_INDEX]}};
|
||||
for (auto& known : known_categories) {
|
||||
total_accounted += known.second;
|
||||
}
|
||||
|
||||
known_categories.push_back({"unknown", uncompressed_data_size - total_accounted});
|
||||
|
||||
std::sort(known_categories.begin(), known_categories.end(),
|
||||
[](const auto& a, const auto& b) { return a.second > b.second; });
|
||||
|
||||
for (const auto& x : known_categories) {
|
||||
fmt::print("{:30s} : {:6d} kB {:3.1f}%\n", x.first, x.second / 1024,
|
||||
100.f * (float)x.second / uncompressed_data_size);
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t PreloadedVertex::hash::operator()(const PreloadedVertex& v) const {
|
||||
return std::hash<float>()(v.x) ^ std::hash<float>()(v.y) ^ std::hash<float>()(v.z) ^
|
||||
std::hash<float>()(v.s) ^ std::hash<float>()(v.t) ^ std::hash<u16>()(v.color_index);
|
||||
}
|
||||
|
||||
} // namespace tfrag3
|
||||
|
@ -53,17 +53,26 @@ enum MemoryUsageCategory {
|
||||
NUM_CATEGORIES
|
||||
};
|
||||
|
||||
constexpr int TFRAG3_VERSION = 19;
|
||||
constexpr int TFRAG3_VERSION = 20;
|
||||
|
||||
// These vertices should be uploaded to the GPU at load time and don't change
|
||||
struct PreloadedVertex {
|
||||
// the vertex position
|
||||
float x, y, z;
|
||||
// texture coordinates
|
||||
float s, t, q;
|
||||
float s, t, q_unused;
|
||||
// color table index
|
||||
u16 color_index;
|
||||
u16 pad[3];
|
||||
|
||||
struct hash {
|
||||
std::size_t operator()(const PreloadedVertex& x) const;
|
||||
};
|
||||
|
||||
bool operator==(const PreloadedVertex& other) const {
|
||||
return x == other.x && y == other.y && z == other.z && s == other.s && t == other.t &&
|
||||
color_index == other.color_index;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(PreloadedVertex) == 32, "PreloadedVertex size");
|
||||
|
||||
@ -144,12 +153,14 @@ struct StripDraw {
|
||||
u32 idx_of_first_idx_in_full_buffer = 0;
|
||||
} unpacked;
|
||||
|
||||
// indices can be specified as lists of runs and plain indices.
|
||||
// the runs are still drawn with indexed opengl calls, it just uses less space in the file.
|
||||
struct VertexRun {
|
||||
u32 vertex0;
|
||||
u16 length;
|
||||
};
|
||||
|
||||
std::vector<VertexRun> runs;
|
||||
std::vector<u32> plain_indices;
|
||||
|
||||
// to do culling, the above vertex stream is grouped.
|
||||
// by following the visgroups and checking the visibility, you can leave out invisible vertices.
|
||||
@ -260,6 +271,7 @@ struct TfragTree {
|
||||
PackedTfragVertices packed_vertices;
|
||||
std::vector<TimeOfDayColor> colors; // vertex colors (pre-interpolation)
|
||||
BVH bvh; // the bvh for frustum culling
|
||||
bool use_strips = true;
|
||||
|
||||
struct {
|
||||
std::vector<PreloadedVertex> vertices; // mesh vertices
|
||||
@ -397,4 +409,6 @@ struct Level {
|
||||
std::array<int, MemoryUsageCategory::NUM_CATEGORIES> get_memory_usage() const;
|
||||
};
|
||||
|
||||
void print_memory_usage(const tfrag3::Level& lev, int uncompressed_data_size);
|
||||
|
||||
} // namespace tfrag3
|
||||
|
71
common/custom_data/pack_helpers.cpp
Normal file
71
common/custom_data/pack_helpers.cpp
Normal file
@ -0,0 +1,71 @@
|
||||
#include "pack_helpers.h"
|
||||
#include <map>
|
||||
|
||||
constexpr float kClusterSize = 4096 * 40; // 100 in-game meters
|
||||
constexpr float kMasterOffset = 12000 * 4096;
|
||||
|
||||
std::pair<u64, u16> position_to_cluster_and_offset(float in) {
|
||||
in += kMasterOffset;
|
||||
if (in < 0) {
|
||||
fmt::print("negative: {}\n", in);
|
||||
}
|
||||
ASSERT(in >= 0);
|
||||
int cluster_cell = (in / kClusterSize);
|
||||
float leftover = in - (cluster_cell * kClusterSize);
|
||||
u16 offset = (leftover / kClusterSize) * float(UINT16_MAX);
|
||||
|
||||
float recovered = ((float)cluster_cell + ((float)offset / UINT16_MAX)) * kClusterSize;
|
||||
float diff = std::fabs(recovered - in);
|
||||
ASSERT(diff < 7);
|
||||
ASSERT(cluster_cell >= 0);
|
||||
ASSERT(cluster_cell < UINT16_MAX);
|
||||
return {cluster_cell, offset};
|
||||
}
|
||||
|
||||
void pack_tfrag_vertices(tfrag3::PackedTfragVertices* result,
|
||||
const std::vector<tfrag3::PreloadedVertex>& vertices) {
|
||||
u32 next_cluster_idx = 0;
|
||||
std::map<u64, u32> clusters;
|
||||
|
||||
for (auto& vtx : vertices) {
|
||||
auto x = position_to_cluster_and_offset(vtx.x);
|
||||
auto y = position_to_cluster_and_offset(vtx.y);
|
||||
auto z = position_to_cluster_and_offset(vtx.z);
|
||||
u64 cluster_id = 0;
|
||||
cluster_id |= x.first;
|
||||
cluster_id |= (y.first << 16);
|
||||
cluster_id |= (z.first << 32);
|
||||
|
||||
auto cluster_it = clusters.find(cluster_id);
|
||||
u32 my_cluster_idx = 0;
|
||||
if (cluster_it == clusters.end()) {
|
||||
// first in cluster
|
||||
clusters[cluster_id] = next_cluster_idx;
|
||||
my_cluster_idx = next_cluster_idx;
|
||||
next_cluster_idx++;
|
||||
} else {
|
||||
my_cluster_idx = cluster_it->second;
|
||||
}
|
||||
|
||||
tfrag3::PackedTfragVertices::Vertex out_vtx;
|
||||
out_vtx.xoff = x.second;
|
||||
out_vtx.yoff = y.second;
|
||||
out_vtx.zoff = z.second;
|
||||
out_vtx.cluster_idx = my_cluster_idx;
|
||||
// TODO check these
|
||||
out_vtx.s = vtx.s * 1024;
|
||||
out_vtx.t = vtx.t * 1024;
|
||||
out_vtx.color_index = vtx.color_index;
|
||||
result->vertices.push_back(out_vtx);
|
||||
}
|
||||
|
||||
result->cluster_origins.resize(next_cluster_idx);
|
||||
for (auto& cluster : clusters) {
|
||||
auto& res = result->cluster_origins[cluster.second];
|
||||
res.x() = (u16)cluster.first;
|
||||
res.y() = (u16)(cluster.first >> 16);
|
||||
res.z() = (u16)(cluster.first >> 32);
|
||||
}
|
||||
|
||||
ASSERT(next_cluster_idx < UINT16_MAX);
|
||||
}
|
6
common/custom_data/pack_helpers.h
Normal file
6
common/custom_data/pack_helpers.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/custom_data/Tfrag3Data.h"
|
||||
|
||||
void pack_tfrag_vertices(tfrag3::PackedTfragVertices* result,
|
||||
const std::vector<tfrag3::PreloadedVertex>& vertices);
|
@ -89,6 +89,14 @@ class Vector {
|
||||
return result;
|
||||
}
|
||||
|
||||
Vector<T, Size> operator+(const T& other) const {
|
||||
Vector<T, Size> result;
|
||||
for (int i = 0; i < Size; i++) {
|
||||
result[i] = m_data[i] + other;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Vector<T, Size>& operator+=(const Vector<T, Size>& other) {
|
||||
for (int i = 0; i < Size; i++) {
|
||||
m_data[i] += other[i];
|
||||
@ -103,6 +111,13 @@ class Vector {
|
||||
return *this;
|
||||
}
|
||||
|
||||
Vector<T, Size>& operator-=(const T& other) {
|
||||
for (int i = 0; i < Size; i++) {
|
||||
m_data[i] -= other;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Vector<T, Size> elementwise_multiply(const Vector<T, Size>& other) const {
|
||||
Vector<T, Size> result;
|
||||
for (int i = 0; i < Size; i++) {
|
||||
@ -180,6 +195,18 @@ class Vector {
|
||||
|
||||
void normalize(const T& norm = T(1)) { *this = normalized(norm); }
|
||||
|
||||
void max_in_place(const Vector<T, Size>& other) {
|
||||
for (int i = 0; i < Size; i++) {
|
||||
m_data[i] = std::max(m_data[i], other[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void min_in_place(const Vector<T, Size>& other) {
|
||||
for (int i = 0; i < Size; i++) {
|
||||
m_data[i] = std::min(m_data[i], other[i]);
|
||||
}
|
||||
}
|
||||
|
||||
std::string to_string_aligned() const {
|
||||
std::string result = "[";
|
||||
for (auto x : m_data) {
|
||||
@ -229,6 +256,8 @@ class Vector {
|
||||
}
|
||||
}
|
||||
|
||||
void set_zero() { fill(0); }
|
||||
|
||||
private:
|
||||
T m_data[Size];
|
||||
};
|
||||
@ -246,8 +275,24 @@ struct Matrix {
|
||||
return result;
|
||||
}
|
||||
|
||||
// const T& operator()(int r, int c) const { return m_data[c + r * Cols]; }
|
||||
// T& operator()(int r, int c) { return m_data[r + c * Rows]; }
|
||||
static Matrix identity() {
|
||||
Matrix result;
|
||||
for (int c = 0; c < Cols; c++) {
|
||||
for (int r = 0; r < Rows; r++) {
|
||||
result(r, c) = r == c ? T(1) : T(0);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void set_zero() {
|
||||
for (auto& x : m_data) {
|
||||
x = 0;
|
||||
}
|
||||
}
|
||||
|
||||
T& operator()(int r, int c) { return m_data[r + c * Rows]; }
|
||||
const T& operator()(int r, int c) const { return m_data[r + c * Rows]; }
|
||||
|
||||
Vector<T, Rows> col(int c) const {
|
||||
Vector<T, Rows> result;
|
||||
@ -274,6 +319,31 @@ struct Matrix {
|
||||
return result;
|
||||
}
|
||||
|
||||
template <int OtherCols>
|
||||
Matrix<T, Rows, OtherCols> operator*(const Matrix<T, Cols, OtherCols>& y) const {
|
||||
Matrix<T, Rows, OtherCols> result;
|
||||
result.set_zero();
|
||||
for (int rx = 0; rx < Rows; rx++) {
|
||||
for (int cx = 0; cx < Cols; cx++) {
|
||||
for (int yi = 0; yi < OtherCols; yi++) {
|
||||
result(rx, yi) += operator()(rx, cx) * y(cx, yi);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Vector<T, Rows> operator*(const Vector<T, Cols>& y) const {
|
||||
Vector<T, Rows> result;
|
||||
result.set_zero();
|
||||
for (int rx = 0; rx < Rows; rx++) {
|
||||
for (int cx = 0; cx < Cols; cx++) {
|
||||
result[rx] += operator()(rx, cx) * y[cx];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
T m_data[Rows * Cols];
|
||||
};
|
||||
|
@ -0,0 +1,40 @@
|
||||
#include "geometry.h"
|
||||
|
||||
namespace math {
|
||||
|
||||
Vector4f bsphere_of_triangle(const Vector3f* v) {
|
||||
Vector4f bsphere;
|
||||
auto& p1 = v[0];
|
||||
auto& p2 = v[1];
|
||||
auto& p3 = v[2];
|
||||
float A = (p1 - p2).length();
|
||||
float B = (p2 - p3).length();
|
||||
float C = (p3 - p1).length();
|
||||
|
||||
const Vector3f *a = &p3, *b = &p1, *c = &p2;
|
||||
if (B < C)
|
||||
std::swap(B, C), std::swap(b, c);
|
||||
if (A < B)
|
||||
std::swap(A, B), std::swap(a, b);
|
||||
|
||||
float r;
|
||||
math::Vector3f origin;
|
||||
if ((B * B) + (C * C) <= (A * A)) {
|
||||
r = A / 2.f;
|
||||
origin = (*b + *c) / 2.f;
|
||||
} else {
|
||||
float cos_a = (B * B + C * C - A * A) / (B * C * 2);
|
||||
r = A / (sqrt(1 - cos_a * cos_a) * 2.f);
|
||||
Vector3f alpha = *a - *c, beta = *b - *c;
|
||||
origin = (beta * alpha.dot(alpha) - alpha * beta.dot(beta)).cross(alpha.cross(beta)) /
|
||||
(alpha.cross(beta).dot(alpha.cross(beta)) * 2.f) +
|
||||
*c;
|
||||
}
|
||||
bsphere.x() = origin.x();
|
||||
bsphere.y() = origin.y();
|
||||
bsphere.z() = origin.z();
|
||||
bsphere.w() = r;
|
||||
return bsphere;
|
||||
}
|
||||
|
||||
} // namespace math
|
@ -44,4 +44,10 @@ RaySphereResult<T> ray_sphere_intersect(const Vector3<T>& ray_origin,
|
||||
result.u[1] = minus_b - sqrt_val;
|
||||
return result;
|
||||
}
|
||||
|
||||
math::Vector4f bsphere_of_triangle(const Vector3f* vertices);
|
||||
|
||||
inline bool point_in_bsphere(const Vector4f& sphere, const Vector3f& pt) {
|
||||
return (sphere.xyz() - pt).squared_length() <= (sphere.w() * sphere.w());
|
||||
}
|
||||
} // namespace math
|
36
custom_levels/README.md
Normal file
36
custom_levels/README.md
Normal file
@ -0,0 +1,36 @@
|
||||
# Custom Levels
|
||||
Disclaimer: custom levels are still in development and are missing most features.
|
||||
|
||||
|
||||
The first three steps are already done for "test zone", so this can be used as a starting point.
|
||||
|
||||
# 1: File Setup
|
||||
To create a custom level, copy the layout of `custom_levels/test-zone`. See `test-zone.jsonc` for information on how to name things. The `.gd` file also contains the level name.
|
||||
|
||||
# 2: Modify the engine
|
||||
Modify `goal_src/engine/level/level-info.gc` to add level info for each custom level. There is level info for `test-zone` at the bottom that can be used as an example.
|
||||
|
||||
# 3: Modify the build system
|
||||
Modify `goal_src/game.gp` and add a custom level target:
|
||||
```lisp
|
||||
(build-custom-level "test-zone")
|
||||
;; the DGO file
|
||||
(custom-level-cgo "TESTZONE.DGO" "test-zone/testzone.gd")
|
||||
```
|
||||
|
||||
# 4: Export the GLTF file from blender.
|
||||
For now, all meshes are displayed and treated as ground collision. This causes buggy collision because walls shouldn't use "floor" mode.
|
||||
|
||||
Blender will create a `.glb` file, which must have the name specified in the `.jsonc` file and should be located in `custom_level/your_level`
|
||||
|
||||
# 5: Rebuild the game
|
||||
Any time the `.glb` file is changed, you must rebuild the game. Launch the compiler (`goalc`) and run `(mi)` to rebuild everything. It's recommended to leave the compiler open - it will remember files that haven't changed and skip rebuilding them.
|
||||
|
||||
# 6: Go to the custom level
|
||||
Start the game in debug mode `gk`.
|
||||
|
||||
In the compiler window, run `(lt)` to connect to the game. You must run this again every time you restart the game. If this doesn't work, there could be a firewall issue and you must allow goalc/gk to use the network. They don't make any outside connections.
|
||||
|
||||
In the compiler window, run a command like `(bg-custom 'test-zone-vis)` to load and start at a custom level.
|
||||
|
||||
|
15
custom_levels/test-zone/test-zone.jsonc
Normal file
15
custom_levels/test-zone/test-zone.jsonc
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
// The "in-game" name of the level. Should be lower case, with dashes (GOAL symbol name)
|
||||
// the name of this file, and the folder this file is in must have the same name.
|
||||
"long_name": "test-zone",
|
||||
// The file name, should be upper case and 8 characters or less.
|
||||
"iso_name": "TESTZONE",
|
||||
// The nickname, should be exactly 3 characters
|
||||
"nickname": "TSZ", // 3 char name, all uppercase
|
||||
|
||||
// Background mesh file.
|
||||
// Must have vertex colors. Use the blender cycles renderer, bake, diffuse, uncheck color,
|
||||
// and bake to vertex colors. For now, only the first vertex color group is used, so make sure you
|
||||
// only have 1.
|
||||
"gltf_file": "custom_levels/test-zone/test-zone2.glb"
|
||||
}
|
BIN
custom_levels/test-zone/test-zone2.glb
Normal file
BIN
custom_levels/test-zone/test-zone2.glb
Normal file
Binary file not shown.
8
custom_levels/test-zone/testzone.gd
Normal file
8
custom_levels/test-zone/testzone.gd
Normal file
@ -0,0 +1,8 @@
|
||||
;; DGO definition file for Awful Village level
|
||||
;; We use the convention of having a longer DGO name for levels without precomputed visibility.
|
||||
|
||||
;; the actual file name still needs to be 8.3
|
||||
("TSZ.DGO"
|
||||
("static-screen.o" "static-screen")
|
||||
("test-zone.go" "test-zone")
|
||||
)
|
@ -53,51 +53,6 @@ bool is_valid_bsp(const decompiler::LinkedObjectFile& file) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void print_memory_usage(const tfrag3::Level& lev, int uncompressed_data_size) {
|
||||
int total_accounted = 0;
|
||||
auto memory_use_by_category = lev.get_memory_usage();
|
||||
|
||||
std::vector<std::pair<std::string, int>> known_categories = {
|
||||
{"texture", memory_use_by_category[tfrag3::MemoryUsageCategory::TEXTURE]},
|
||||
{"tie-deinst-vis", memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_DEINST_VIS]},
|
||||
{"tie-deinst-idx", memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_DEINST_INDEX]},
|
||||
{"tie-inst-vis", memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_INST_VIS]},
|
||||
{"tie-inst-idx", memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_INST_INDEX]},
|
||||
{"tie-bvh", memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_BVH]},
|
||||
{"tie-verts", memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_VERTS]},
|
||||
{"tie-colors", memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_TIME_OF_DAY]},
|
||||
{"tie-wind-inst-info",
|
||||
memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_WIND_INSTANCE_INFO]},
|
||||
{"tie-cidx", memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_CIDX]},
|
||||
{"tie-mats", memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_MATRICES]},
|
||||
{"tie-grps", memory_use_by_category[tfrag3::MemoryUsageCategory::TIE_GRPS]},
|
||||
{"tfrag-vis", memory_use_by_category[tfrag3::MemoryUsageCategory::TFRAG_VIS]},
|
||||
{"tfrag-idx", memory_use_by_category[tfrag3::MemoryUsageCategory::TFRAG_INDEX]},
|
||||
{"tfrag-vert", memory_use_by_category[tfrag3::MemoryUsageCategory::TFRAG_VERTS]},
|
||||
{"tfrag-colors", memory_use_by_category[tfrag3::MemoryUsageCategory::TFRAG_TIME_OF_DAY]},
|
||||
{"tfrag-cluster", memory_use_by_category[tfrag3::MemoryUsageCategory::TFRAG_CLUSTER]},
|
||||
{"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]},
|
||||
{"collision", memory_use_by_category[tfrag3::MemoryUsageCategory::COLLISION]},
|
||||
{"merc-vert", memory_use_by_category[tfrag3::MemoryUsageCategory::MERC_VERT]},
|
||||
{"merc-idx", memory_use_by_category[tfrag3::MemoryUsageCategory::MERC_INDEX]}};
|
||||
for (auto& known : known_categories) {
|
||||
total_accounted += known.second;
|
||||
}
|
||||
|
||||
known_categories.push_back({"unknown", uncompressed_data_size - total_accounted});
|
||||
|
||||
std::sort(known_categories.begin(), known_categories.end(),
|
||||
[](const auto& a, const auto& b) { return a.second > b.second; });
|
||||
|
||||
for (const auto& x : known_categories) {
|
||||
fmt::print("{:30s} : {:6d} kB {:3.1f}%\n", x.first, x.second / 1024,
|
||||
100.f * (float)x.second / uncompressed_data_size);
|
||||
}
|
||||
}
|
||||
|
||||
void add_all_textures_from_level(tfrag3::Level& lev,
|
||||
const std::string& level_name,
|
||||
const TextureDB& tex_db) {
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "common/dma/gs.h"
|
||||
#include "common/util/Assert.h"
|
||||
#include "common/custom_data/pack_helpers.h"
|
||||
|
||||
namespace decompiler {
|
||||
namespace {
|
||||
@ -2056,9 +2057,9 @@ void make_tfrag3_data(std::map<u32, std::vector<GroupedDraw>>& draws,
|
||||
vtx.z = vert.pre_cam_trans_pos.z();
|
||||
vtx.s = vert.stq.x();
|
||||
vtx.t = vert.stq.y();
|
||||
vtx.q = vert.stq.z();
|
||||
vtx.q_unused = vert.stq.z();
|
||||
// if this is true, we can remove a divide in the shader
|
||||
ASSERT(vtx.q == 1.f);
|
||||
ASSERT(vtx.q_unused == 1.f);
|
||||
vtx.color_index = vert.rgba / 4;
|
||||
// ASSERT((vert.rgba >> 2) < 1024); spider cave has 2048?
|
||||
ASSERT((vert.rgba & 3) == 0);
|
||||
@ -2137,85 +2138,6 @@ void merge_groups(std::vector<tfrag3::StripDraw::VisGroup>& grps) {
|
||||
|
||||
} // namespace
|
||||
|
||||
constexpr float kClusterSize = 4096 * 40; // 100 in-game meters
|
||||
constexpr float kMasterOffset = 12000 * 4096;
|
||||
|
||||
std::pair<u64, u16> position_to_cluster_and_offset(float in) {
|
||||
in += kMasterOffset;
|
||||
if (in < 0) {
|
||||
fmt::print("negative: {}\n", in);
|
||||
}
|
||||
ASSERT(in >= 0);
|
||||
int cluster_cell = (in / kClusterSize);
|
||||
float leftover = in - (cluster_cell * kClusterSize);
|
||||
u16 offset = (leftover / kClusterSize) * float(UINT16_MAX);
|
||||
|
||||
float recovered = ((float)cluster_cell + ((float)offset / UINT16_MAX)) * kClusterSize;
|
||||
float diff = std::fabs(recovered - in);
|
||||
ASSERT(diff < 7);
|
||||
ASSERT(cluster_cell >= 0);
|
||||
ASSERT(cluster_cell < UINT16_MAX);
|
||||
return {cluster_cell, offset};
|
||||
}
|
||||
|
||||
void pack_vertices(tfrag3::PackedTfragVertices* result,
|
||||
const std::vector<tfrag3::PreloadedVertex>& vertices) {
|
||||
u32 next_cluster_idx = 0;
|
||||
std::map<u64, u32> clusters;
|
||||
|
||||
for (auto& vtx : vertices) {
|
||||
auto x = position_to_cluster_and_offset(vtx.x);
|
||||
auto y = position_to_cluster_and_offset(vtx.y);
|
||||
auto z = position_to_cluster_and_offset(vtx.z);
|
||||
u64 cluster_id = 0;
|
||||
cluster_id |= x.first;
|
||||
cluster_id |= (y.first << 16);
|
||||
cluster_id |= (z.first << 32);
|
||||
|
||||
auto cluster_it = clusters.find(cluster_id);
|
||||
u32 my_cluster_idx = 0;
|
||||
if (cluster_it == clusters.end()) {
|
||||
// first in cluster
|
||||
clusters[cluster_id] = next_cluster_idx;
|
||||
my_cluster_idx = next_cluster_idx;
|
||||
next_cluster_idx++;
|
||||
} else {
|
||||
my_cluster_idx = cluster_it->second;
|
||||
}
|
||||
|
||||
tfrag3::PackedTfragVertices::Vertex out_vtx;
|
||||
out_vtx.xoff = x.second;
|
||||
out_vtx.yoff = y.second;
|
||||
out_vtx.zoff = z.second;
|
||||
out_vtx.cluster_idx = my_cluster_idx;
|
||||
// TODO check these
|
||||
out_vtx.s = vtx.s * 1024;
|
||||
out_vtx.t = vtx.t * 1024;
|
||||
out_vtx.color_index = vtx.color_index;
|
||||
result->vertices.push_back(out_vtx);
|
||||
}
|
||||
|
||||
result->cluster_origins.resize(next_cluster_idx);
|
||||
for (auto& cluster : clusters) {
|
||||
auto& res = result->cluster_origins[cluster.second];
|
||||
res.x() = (u16)cluster.first;
|
||||
res.y() = (u16)(cluster.first >> 16);
|
||||
res.z() = (u16)(cluster.first >> 32);
|
||||
}
|
||||
|
||||
/*
|
||||
std::unordered_set<tfrag3::PackedTfragVertices::Vertex, tfrag3::PackedTfragVertices::Vertex::hash>
|
||||
a;
|
||||
for (auto& v : result->vertices) {
|
||||
a.insert(v);
|
||||
}
|
||||
fmt::print("SIZE: {} vs {} {}\n", a.size(), result->vertices.size(),
|
||||
(float)a.size() / result->vertices.size());
|
||||
*/
|
||||
|
||||
ASSERT(next_cluster_idx < UINT16_MAX);
|
||||
}
|
||||
|
||||
void extract_tfrag(const level_tools::DrawableTreeTfrag* tree,
|
||||
const std::string& debug_name,
|
||||
const std::vector<level_tools::TextureRemap>& map,
|
||||
@ -2282,7 +2204,7 @@ void extract_tfrag(const level_tools::DrawableTreeTfrag* tree,
|
||||
std::vector<tfrag3::PreloadedVertex> vertices;
|
||||
emulate_tfrags(geom, as_tfrag_array->tfragments, debug_name, map, out, this_tree, vertices,
|
||||
tex_db, expected_missing_textures, dump_level);
|
||||
pack_vertices(&this_tree.packed_vertices, vertices);
|
||||
pack_tfrag_vertices(&this_tree.packed_vertices, vertices);
|
||||
extract_time_of_day(tree, this_tree);
|
||||
|
||||
for (auto& draw : this_tree.draws) {
|
||||
|
@ -78,6 +78,7 @@ void Tfrag3::update_load(const std::vector<tfrag3::TFragmentTreeKind>& tree_kind
|
||||
tree_cache.vis = &tree.bvh;
|
||||
tree_cache.index_data = tree.unpacked.indices.data();
|
||||
tree_cache.tod_cache = swizzle_time_of_day(tree.colors);
|
||||
tree_cache.draw_mode = tree.use_strips ? GL_TRIANGLE_STRIP : GL_TRIANGLES;
|
||||
vis_temp_len = std::max(vis_temp_len, tree.bvh.vis_nodes.size());
|
||||
glBindBuffer(GL_ARRAY_BUFFER, tree_cache.vertex_buffer);
|
||||
// glBufferData(GL_ARRAY_BUFFER, verts * sizeof(tfrag3::PreloadedVertex),
|
||||
@ -240,11 +241,11 @@ void Tfrag3::render_tree(int geom,
|
||||
|
||||
prof.add_draw_call();
|
||||
if (render_state->no_multidraw) {
|
||||
glDrawElements(GL_TRIANGLE_STRIP, singledraw_indices.second, GL_UNSIGNED_INT,
|
||||
glDrawElements(tree.draw_mode, singledraw_indices.second, GL_UNSIGNED_INT,
|
||||
(void*)(singledraw_indices.first * sizeof(u32)));
|
||||
} else {
|
||||
glMultiDrawElements(GL_TRIANGLE_STRIP,
|
||||
&m_cache.multidraw_count_buffer[multidraw_indices.first], GL_UNSIGNED_INT,
|
||||
glMultiDrawElements(tree.draw_mode, &m_cache.multidraw_count_buffer[multidraw_indices.first],
|
||||
GL_UNSIGNED_INT,
|
||||
&m_cache.multidraw_index_offset_buffer[multidraw_indices.first],
|
||||
multidraw_indices.second);
|
||||
}
|
||||
@ -260,11 +261,11 @@ void Tfrag3::render_tree(int geom,
|
||||
double_draw.aref_second);
|
||||
glDepthMask(GL_FALSE);
|
||||
if (render_state->no_multidraw) {
|
||||
glDrawElements(GL_TRIANGLE_STRIP, singledraw_indices.second, GL_UNSIGNED_INT,
|
||||
glDrawElements(tree.draw_mode, singledraw_indices.second, GL_UNSIGNED_INT,
|
||||
(void*)(singledraw_indices.first * sizeof(u32)));
|
||||
} else {
|
||||
glMultiDrawElements(
|
||||
GL_TRIANGLE_STRIP, &m_cache.multidraw_count_buffer[multidraw_indices.first],
|
||||
tree.draw_mode, &m_cache.multidraw_count_buffer[multidraw_indices.first],
|
||||
GL_UNSIGNED_INT, &m_cache.multidraw_index_offset_buffer[multidraw_indices.first],
|
||||
multidraw_indices.second);
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ class Tfrag3 {
|
||||
const tfrag3::BVH* vis = nullptr;
|
||||
const u32* index_data = nullptr;
|
||||
SwizzledTimeOfDay tod_cache;
|
||||
u64 draw_mode = 0;
|
||||
|
||||
void reset_stats() {
|
||||
rendered_this_frame = false;
|
||||
|
@ -83,6 +83,7 @@ class TfragLoadStage : public LoaderStage {
|
||||
GLuint& tree_out = data.lev_data->tfrag_vertex_data[geo].emplace_back();
|
||||
glGenBuffers(1, &tree_out);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, tree_out);
|
||||
|
||||
glBufferData(GL_ARRAY_BUFFER,
|
||||
in_tree.unpacked.vertices.size() * sizeof(tfrag3::PreloadedVertex), nullptr,
|
||||
GL_STATIC_DRAW);
|
||||
@ -97,34 +98,38 @@ class TfragLoadStage : public LoaderStage {
|
||||
u32 unique_buffers = 0;
|
||||
|
||||
while (true) {
|
||||
const auto& tree = data.lev_data->level->tfrag_trees[m_next_geo][m_next_tree];
|
||||
u32 end_vert_in_tree = tree.unpacked.vertices.size();
|
||||
// the number of vertices we'd need to finish the tree right now
|
||||
size_t num_verts_left_in_tree = end_vert_in_tree - m_next_vert;
|
||||
size_t start_vert_for_chunk;
|
||||
size_t end_vert_for_chunk;
|
||||
|
||||
bool complete_tree;
|
||||
|
||||
if (num_verts_left_in_tree > CHUNK_SIZE) {
|
||||
complete_tree = false;
|
||||
// should only do partial
|
||||
start_vert_for_chunk = m_next_vert;
|
||||
end_vert_for_chunk = start_vert_for_chunk + CHUNK_SIZE;
|
||||
m_next_vert += CHUNK_SIZE;
|
||||
} else {
|
||||
// should do all!
|
||||
start_vert_for_chunk = m_next_vert;
|
||||
end_vert_for_chunk = end_vert_in_tree;
|
||||
if (data.lev_data->level->tfrag_trees[m_next_geo].empty()) {
|
||||
complete_tree = true;
|
||||
}
|
||||
} else {
|
||||
const auto& tree = data.lev_data->level->tfrag_trees[m_next_geo][m_next_tree];
|
||||
u32 end_vert_in_tree = tree.unpacked.vertices.size();
|
||||
// the number of vertices we'd need to finish the tree right now
|
||||
size_t num_verts_left_in_tree = end_vert_in_tree - m_next_vert;
|
||||
size_t start_vert_for_chunk;
|
||||
size_t end_vert_for_chunk;
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, data.lev_data->tfrag_vertex_data[m_next_geo][m_next_tree]);
|
||||
u32 upload_size =
|
||||
(end_vert_for_chunk - start_vert_for_chunk) * sizeof(tfrag3::PreloadedVertex);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, start_vert_for_chunk * sizeof(tfrag3::PreloadedVertex),
|
||||
upload_size, tree.unpacked.vertices.data() + start_vert_for_chunk);
|
||||
uploaded_bytes += upload_size;
|
||||
if (num_verts_left_in_tree > CHUNK_SIZE) {
|
||||
complete_tree = false;
|
||||
// should only do partial
|
||||
start_vert_for_chunk = m_next_vert;
|
||||
end_vert_for_chunk = start_vert_for_chunk + CHUNK_SIZE;
|
||||
m_next_vert += CHUNK_SIZE;
|
||||
} else {
|
||||
// should do all!
|
||||
start_vert_for_chunk = m_next_vert;
|
||||
end_vert_for_chunk = end_vert_in_tree;
|
||||
complete_tree = true;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, data.lev_data->tfrag_vertex_data[m_next_geo][m_next_tree]);
|
||||
u32 upload_size =
|
||||
(end_vert_for_chunk - start_vert_for_chunk) * sizeof(tfrag3::PreloadedVertex);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, start_vert_for_chunk * sizeof(tfrag3::PreloadedVertex),
|
||||
upload_size, tree.unpacked.vertices.data() + start_vert_for_chunk);
|
||||
uploaded_bytes += upload_size;
|
||||
}
|
||||
|
||||
if (complete_tree) {
|
||||
unique_buffers++;
|
||||
|
@ -384,6 +384,8 @@
|
||||
(set! (-> v1-5 mesh) (-> obj mesh))
|
||||
(set! (-> v1-5 inst) #f)
|
||||
)
|
||||
; (add-debug-sphere #t (bucket-id debug) (-> obj bsphere) (-> obj bsphere w) (new 'static 'rgba :g #x80 :a #x80))
|
||||
; (format 0 "~f~%" (-> obj bsphere w))
|
||||
(+! (-> arg1 num-items) 1)
|
||||
)
|
||||
(&+! obj 32)
|
||||
|
@ -83,6 +83,19 @@
|
||||
|
||||
(defmethod draw collide-fragment ((obj collide-fragment) (arg0 collide-fragment) (arg1 display-frame))
|
||||
;; if we wanted to draw collide-fragment's we'd do it here.
|
||||
; (when (< (-> obj bsphere w) (meters 22.))
|
||||
; (format 0 "sp: ~m : ~D~%" (-> obj bsphere w) (-> obj mesh poly-count))
|
||||
; (let ((mins (vector-copy! (new-stack-vector0) (-> obj bsphere)))
|
||||
; (maxs (vector-copy! (new-stack-vector0) (-> obj bsphere))))
|
||||
; (dotimes (i 3)
|
||||
; (-! (-> mins data i) (-> obj bsphere w))
|
||||
; (+! (-> maxs data i) (-> obj bsphere w))
|
||||
; )
|
||||
; (add-debug-box #t (bucket-id debug) mins maxs (new 'static 'rgba :r #x80 :a #x80)
|
||||
; )
|
||||
; ;(add-debug-sphere #t (bucket-id debug) (-> obj bsphere) (-> obj bsphere w) (new 'static 'rgba :r #x80 :a #x80))
|
||||
; )
|
||||
;; (add-debug-point #t (bucket-id debug) (-> obj bsphere))
|
||||
(none)
|
||||
)
|
||||
|
||||
|
@ -1371,7 +1371,12 @@
|
||||
(dotimes (page-idx 9)
|
||||
(set! (-> level texture-page page-idx) #f)
|
||||
)
|
||||
|
||||
(#when PC_PORT
|
||||
(when (zero? id-array)
|
||||
(format #t "ERROR: texture id array is 0, skipping texture login!~%")
|
||||
(return #f)
|
||||
)
|
||||
)
|
||||
(if (>= max-page-kind 0) ;; tfrag.
|
||||
;; login the texture. If the texture isn't there, it will try to load it
|
||||
;; and allocate with the given allocation function.
|
||||
|
@ -1972,3 +1972,44 @@
|
||||
|
||||
|
||||
|
||||
;;;;;;;;; CUSTOM LEVELS
|
||||
(define test-zone (new 'static 'level-load-info
|
||||
:index 26
|
||||
:name 'test-zone
|
||||
:visname 'test-zone-vis ;; name + -vis
|
||||
:nickname 'tsz ;; nickname
|
||||
:packages '()
|
||||
:sound-banks '()
|
||||
:music-bank #f
|
||||
:ambient-sounds '()
|
||||
:mood '*default-mood*
|
||||
:mood-func 'update-mood-default
|
||||
:ocean #f
|
||||
:sky #t
|
||||
:continues '((new 'static 'continue-point
|
||||
:name "test-zone-start"
|
||||
:level 'test-zone
|
||||
:trans (new 'static 'vector :x 0.0 :y (meters 10.) :z (meters 10.) :w 1.0)
|
||||
:quat (new 'static 'quaternion :w 1.0)
|
||||
:camera-trans (new 'static 'vector :x 0.0 :y 4096.0 :z 0.0 :w 1.0)
|
||||
:camera-rot (new 'static 'array float 9 1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0)
|
||||
:load-commands '()
|
||||
:vis-nick 'none
|
||||
:lev0 'test-zone
|
||||
:disp0 'display
|
||||
:lev1 'village1
|
||||
:disp1 'display
|
||||
))
|
||||
:tasks '()
|
||||
:priority 100
|
||||
:load-commands '()
|
||||
:alt-load-commands '()
|
||||
:bsp-mask #xffffffffffffffff
|
||||
:bsphere (new 'static 'sphere :w 167772160000.0)
|
||||
:bottom-height (meters -20)
|
||||
:run-packages '()
|
||||
:wait-for-load #t
|
||||
)
|
||||
)
|
||||
|
||||
(cons! *level-load-list* 'test-zone)
|
@ -93,13 +93,13 @@
|
||||
"Draw a level!"
|
||||
;; do the draw
|
||||
(draw arg0 arg0 arg3)
|
||||
|
||||
|
||||
(if (nonzero? *display-strip-lines*)
|
||||
(debug-draw arg0 arg0 arg3)
|
||||
)
|
||||
(none)
|
||||
)
|
||||
|
||||
|
||||
|
||||
(defmethod print level ((obj level))
|
||||
"print a level."
|
||||
@ -115,7 +115,7 @@
|
||||
|
||||
(defmethod relocate bsp-header ((obj bsp-header) (dest-heap kheap) (name (pointer uint8)))
|
||||
"Handle a bsp file load."
|
||||
|
||||
|
||||
;; we expect that we'll have a loading-level set when we link/login a bsp-header
|
||||
(let ((s5-0 (-> *level* loading-level)))
|
||||
(if s5-0
|
||||
@ -175,7 +175,7 @@
|
||||
|
||||
(defmethod vis-clear level ((obj level))
|
||||
"Clear the visibility info for when the level is loading."
|
||||
|
||||
|
||||
;; clear vis-infos, so we can't try to look up a vis string.
|
||||
(countdown (v1-0 8)
|
||||
(nop!) ;; the usual.
|
||||
@ -194,11 +194,11 @@
|
||||
(defmethod vis-load level ((obj level))
|
||||
"Start the initial load of a VIS file to the IOP VIS buffer. After this is done, we can use
|
||||
ramdisk-load to load chunks."
|
||||
|
||||
;; check to see if we have a buffer for loaded vis data.
|
||||
|
||||
;; check to see if we have a buffer for loaded vis data.
|
||||
(when (zero? (-> obj vis-info (-> obj vis-self-index) ramdisk))
|
||||
;; nope, we have no vis data buffer, we need to set it up.
|
||||
|
||||
|
||||
;; first, we should see if the other level has loaded vis. if so, kill it.
|
||||
(let ((vis (-> obj other vis-info (-> obj other vis-self-index))))
|
||||
(when (and vis (nonzero? (-> vis ramdisk)))
|
||||
@ -207,7 +207,7 @@
|
||||
0
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;; set up a ramdisk rpc (fill command, actually load the file from DVD to IOP buffer)
|
||||
(let ((visname (make-file-name (file-kind vis) (the-as string (-> obj nickname)) 0 #f))
|
||||
(cmd (the-as ramdisk-rpc-fill (add-element *ramdisk-rpc*)))
|
||||
@ -222,7 +222,7 @@
|
||||
(set! (-> obj vis-info (-> obj vis-self-index) ramdisk) s5-0)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;; return the ramdisk ID.
|
||||
(-> obj vis-info (-> obj vis-self-index) ramdisk)
|
||||
)
|
||||
@ -278,7 +278,7 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;; check for up to 6 neighbor level vis info. The last one is always left as null.
|
||||
(dotimes (s5-1 6)
|
||||
(let* ((s3-0 (+ s5-1 1))
|
||||
@ -425,7 +425,7 @@
|
||||
(('alive)
|
||||
(when (and *dproc* (= want-status 'active))
|
||||
;; only if we want to do alive -> active
|
||||
|
||||
|
||||
;; will set the level to be drawn.
|
||||
(remove-by-param1 *background-draw-engine* (-> obj bsp))
|
||||
(add-connection *background-draw-engine* *dproc* (the (function object object object object object) add-bsp-drawable) (-> obj bsp) obj #f)
|
||||
@ -539,10 +539,10 @@
|
||||
|
||||
;; set the level heap. level code logins called from linker may allocate here
|
||||
(set! loading-level (-> obj heap))
|
||||
|
||||
|
||||
;; relocate method of the bsp will look for this
|
||||
(set! (-> *level* loading-level) obj)
|
||||
|
||||
|
||||
;; clear out old stuff
|
||||
(set! (-> *level* log-in-level-bsp) #f)
|
||||
(set! (-> obj nickname) #f)
|
||||
@ -551,12 +551,12 @@
|
||||
(set! (-> obj ambient) #f)
|
||||
(set! (-> obj linking) #f)
|
||||
(vis-clear obj)
|
||||
|
||||
|
||||
(set! (-> obj status) 'loading)
|
||||
|
||||
|
||||
;; incoming textures should use the level allocator
|
||||
(set! (-> *texture-pool* allocate-func) texture-page-level-allocate)
|
||||
|
||||
|
||||
;; build name
|
||||
(if (= (-> obj load-name) (-> obj info visname))
|
||||
(format (clear *temp-string*) "~S" (-> obj info nickname))
|
||||
@ -564,19 +564,19 @@
|
||||
)
|
||||
(set! (-> *temp-string* data 8) (the-as uint 0))
|
||||
(format *temp-string* ".DGO")
|
||||
|
||||
|
||||
;; reset temporary allocations on level heap
|
||||
(set! (-> obj heap top) (-> obj heap top-base))
|
||||
|
||||
|
||||
;; allocate DGO loading buffers
|
||||
(let ((s4-0 (kmalloc (-> obj heap) (* 2 1024 1024) (kmalloc-flags align-64 top) "dgo-level-buf-2"))
|
||||
(s5-2 (kmalloc (-> obj heap) (* 2 1024 1024) (kmalloc-flags align-64 top) "dgo-level-buf-2"))
|
||||
)
|
||||
(load-dbg " DGO buffers at #x~X #x~X~%" s4-0 s5-2)
|
||||
|
||||
|
||||
;; we expect to load code first, remember where the heap is now.
|
||||
(set! (-> obj code-memory-start) (-> obj heap current))
|
||||
|
||||
|
||||
(format 0 "-----------> begin load ~A [~S]~%" (-> obj load-name) *temp-string*)
|
||||
;; kick off the load!
|
||||
(dgo-load-begin *temp-string* s5-2 s4-0 (the pointer (align64 (-> obj heap current))))
|
||||
@ -586,10 +586,10 @@
|
||||
|
||||
(defmethod login-begin level ((obj level))
|
||||
"Start the login. This is spread over multiple frames."
|
||||
|
||||
|
||||
;; done with load, reset the texture page allocator
|
||||
(set! (-> *texture-pool* allocate-func) texture-page-default-allocate)
|
||||
|
||||
|
||||
(cond
|
||||
((-> obj bsp)
|
||||
(set! (-> *level* log-in-level-bsp) (-> obj bsp))
|
||||
@ -605,7 +605,7 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;; set the login state machine at the beginning.
|
||||
(set! (-> *login-state* state) -1)
|
||||
(set! (-> *login-state* pos) (the-as uint 0))
|
||||
@ -633,16 +633,16 @@
|
||||
(sv-16 prototype-bucket-tie)
|
||||
(sv-32 int)
|
||||
)
|
||||
|
||||
|
||||
;; there is some logic for not doing the whole login all at once...
|
||||
;; for now, we will somewhat ignore that.
|
||||
|
||||
|
||||
|
||||
|
||||
(let ((level-drawable-trees (-> loaded-level bsp drawable-trees)))
|
||||
;;(.mfc0 initial-timer Count)
|
||||
(label cfg-1)
|
||||
;;(.mfc0 current-timer Count)
|
||||
|
||||
|
||||
;; this would quit the login function after some amount of time elapsed.
|
||||
#|
|
||||
(let ((elapsed-timer (- current-timer initial-timer)))
|
||||
@ -652,16 +652,16 @@
|
||||
)
|
||||
)
|
||||
|#
|
||||
|
||||
|
||||
(let ((current-login-pos (the-as int (-> level-login-state pos))))
|
||||
|
||||
|
||||
;; Login state -1.
|
||||
;; in this state, we log in drawables/art-groups that are in referenced in the bsp directly
|
||||
;; the current-login-pos in the index of the drawable/art to login.
|
||||
|
||||
|
||||
(when (= (-> level-login-state state) -1)
|
||||
;;(load-dbg "login state -1~%")
|
||||
|
||||
|
||||
;; login some drawables.
|
||||
(when (< current-login-pos (-> level-drawable-trees length))
|
||||
(let ((current-drawable (-> level-drawable-trees trees (the-as uint current-login-pos))))
|
||||
@ -699,7 +699,7 @@
|
||||
(+! (-> level-login-state pos) 1)
|
||||
(goto cfg-1)
|
||||
)
|
||||
|
||||
|
||||
;; this makes the art groups go at the end.
|
||||
(let ((v1-39 (- (the-as uint current-login-pos) (the-as uint (-> level-drawable-trees length)))))
|
||||
(when (< (the-as int v1-39) (-> loaded-level art-group art-group-array length))
|
||||
@ -713,14 +713,14 @@
|
||||
(goto cfg-1)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;; if we got here, we're done with state -1!
|
||||
(set! (-> level-login-state pos) (the-as uint 0))
|
||||
(set! (-> level-login-state state) 0)
|
||||
(goto cfg-1)
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
;; login state 0.
|
||||
;; we log in children of the drawables from state -1.
|
||||
(when (< (-> level-login-state state) (the-as int (-> level-login-state elts)))
|
||||
@ -755,7 +755,7 @@
|
||||
(when (< current-login-pos (-> s1-2 length))
|
||||
(set! sv-16 (-> s1-2 array-data (the-as uint current-login-pos)))
|
||||
(set! sv-32 0)
|
||||
|
||||
|
||||
(#when PC_PORT
|
||||
;; if a TIE uses environment mapping, disable the fade out so it always renderers with
|
||||
;; the generic renderer. In the port, we just make envmapped things always envmap.
|
||||
@ -763,7 +763,7 @@
|
||||
(*! (-> sv-16 envmap-fade-far) 10000.)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
(while (< sv-32 4)
|
||||
(let ((a0-28 (-> sv-16 geometry sv-32)))
|
||||
;;(load-dbg " login geom: ~A~%" a0-28)
|
||||
@ -810,8 +810,8 @@
|
||||
)
|
||||
(goto cfg-1)
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
(when (= (-> level-login-state state) (-> level-login-state elts))
|
||||
(let ((v1-115 (-> loaded-level bsp)))
|
||||
(cond
|
||||
@ -837,8 +837,7 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
;; done!
|
||||
(set! (-> loaded-level nickname) (-> loaded-level bsp nickname))
|
||||
(if (nonzero? (-> loaded-level bsp nodes))
|
||||
@ -857,7 +856,7 @@
|
||||
(set! (-> *subdivide-settings* close 3) f0-0)
|
||||
(set! (-> *subdivide-settings* far 3) f1-0)
|
||||
)
|
||||
|
||||
|
||||
(load-dbg "init-vis~%")
|
||||
(init-vis loaded-level)
|
||||
(load-dbg "package load~%")
|
||||
@ -905,31 +904,31 @@
|
||||
(case (-> obj status)
|
||||
(('active 'alive)
|
||||
(format 0 "----------- kill ~A (status ~A)~%" obj (-> obj status))
|
||||
|
||||
|
||||
;; copy data from the level to the game-info storage. This will remember permanent level stuff, like
|
||||
;; what you collected/completed.
|
||||
(copy-perms-from-level! *game-info* obj)
|
||||
(send-event *camera* 'level-deactivate (-> obj name))
|
||||
(send-event *target* 'level-deactivate (-> obj name))
|
||||
|
||||
|
||||
;; remove this BSP from the engine. This will stop us from being drawn.
|
||||
(remove-by-param1 *background-draw-engine* (-> obj bsp))
|
||||
|
||||
|
||||
;; track down all the entities and kill them
|
||||
(deactivate-entities (-> obj bsp))
|
||||
|
||||
|
||||
;; kill any remaining particles not associated with a part-tracker
|
||||
(kill-all-particles-in-level obj)
|
||||
|
||||
|
||||
;; clean up our level
|
||||
(set! (-> obj inside-sphere?) #f)
|
||||
(set! (-> obj inside-boxes?) #f)
|
||||
(set! (-> obj meta-inside?) #f)
|
||||
(set! (-> obj force-inside?) #f)
|
||||
|
||||
|
||||
;; we're still loaded.
|
||||
(set! (-> obj status) 'loaded)
|
||||
|
||||
|
||||
(set! (-> obj all-visible?) 'loading)
|
||||
;; clear vis buffers
|
||||
(dotimes (v1-19 128)
|
||||
@ -955,10 +954,10 @@
|
||||
|
||||
(defmethod unload! level ((obj level))
|
||||
"Unloads the level. This does not free the heap. The level will be made inactive and ready to be loaded some other time."
|
||||
|
||||
|
||||
(deactivate obj)
|
||||
(when (!= (-> obj status) 'inactive)
|
||||
|
||||
|
||||
;; if we linked art group, unlink it.
|
||||
(when (or (= (-> obj status) 'loaded)
|
||||
(= (-> obj status) 'alive)
|
||||
@ -973,7 +972,7 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;; turn some things off
|
||||
(set! (-> obj bsp) #f)
|
||||
(set! (-> obj entity) #f)
|
||||
@ -981,7 +980,7 @@
|
||||
(set! (-> obj status) 'inactive)
|
||||
(set! (-> obj art-group string-array length) 0)
|
||||
(set! (-> obj art-group art-group-array length) 0)
|
||||
|
||||
|
||||
;; unload texture pages
|
||||
(countdown (s5-1 (-> obj loaded-texture-page-count))
|
||||
(dotimes (v1-27 32)
|
||||
@ -993,10 +992,10 @@
|
||||
)
|
||||
(set! (-> obj loaded-texture-page-count) 0)
|
||||
(unlink-textures-in-heap! *texture-page-dir* (-> obj heap))
|
||||
|
||||
|
||||
;; unload particle groups that were defined in the level data
|
||||
(unlink-part-group-by-heap (-> obj heap))
|
||||
|
||||
|
||||
;; if there are any in-progress art loads for this level, kill them.
|
||||
(dotimes (s5-2 2)
|
||||
(let ((v1-41 (-> *art-control* buffer s5-2 pending-load-file)))
|
||||
@ -1019,7 +1018,7 @@
|
||||
(a0-29 (car s5-3))
|
||||
)
|
||||
(while (not (null? s5-3))
|
||||
(case (rtype-of a0-29)
|
||||
(case (rtype-of a0-29)
|
||||
((symbol)
|
||||
(unload (symbol->string (the-as symbol a0-29)))
|
||||
)
|
||||
@ -1033,7 +1032,7 @@
|
||||
)
|
||||
|
||||
(vis-clear obj)
|
||||
|
||||
|
||||
;; reset the level heap!
|
||||
(let ((v1-64 (-> obj heap)))
|
||||
(set! (-> v1-64 current) (-> v1-64 base))
|
||||
@ -1057,14 +1056,14 @@
|
||||
|
||||
;; note : pc port added option to show every actor regardless
|
||||
(with-pc (if (-> *pc-settings* force-actors?) (return #t)))
|
||||
|
||||
|
||||
;; check the vis bits!
|
||||
(let* (;; lwu v1, 388(a0)
|
||||
(let* (;; lwu v1, 388(a0)
|
||||
(vis-data (-> obj vis-bits))
|
||||
;; sra a0, a1, 3
|
||||
(byte-idx (sar arg0 3))
|
||||
;; daddu v1, a0, v1
|
||||
;; lb v1, 0(v1)
|
||||
;; lb v1, 0(v1)
|
||||
(vis-byte (-> (the (pointer int8) vis-data) byte-idx))
|
||||
;; andi a0, a1, 7
|
||||
(bit-idx (logand arg0 #b111))
|
||||
@ -1117,7 +1116,7 @@
|
||||
(defmethod debug-print-splitbox level ((obj level) (arg0 vector) (arg1 string))
|
||||
"Print the current splitbox, if we're in one."
|
||||
(cond
|
||||
((or (not (-> obj bsp)) (zero? (-> obj bsp boxes)) (zero? (-> obj bsp split-box-indices)))
|
||||
((or (not (-> obj bsp)) (zero? (-> obj bsp boxes)) (zero? (-> obj bsp split-box-indices)))
|
||||
;; do nothing!
|
||||
)
|
||||
(else
|
||||
@ -1431,6 +1430,11 @@
|
||||
)
|
||||
|
||||
(defun bg ((level-name symbol))
|
||||
"Begin game in a given level.
|
||||
The level name can be the full name (village3), the nickname (vi3), or visname (village3-vis)
|
||||
If the visname is used (and its a recognized level in level-info), it will use vis mode.
|
||||
Otherwise, it will use the non-vis DGO name (like VILLAGE3.DGO) which will usually fail.
|
||||
"
|
||||
(set! *cheat-mode* (if *debug-segment*
|
||||
'debug
|
||||
#f
|
||||
@ -1496,6 +1500,62 @@
|
||||
0
|
||||
)
|
||||
|
||||
(defun bg-custom ((level-name symbol))
|
||||
"Modified version of bg for the PC Port custom levels."
|
||||
|
||||
;; lookup info
|
||||
(format 0 "(bg-custom ~A)%" level-name)
|
||||
(let ((lev-info (lookup-level-info level-name)))
|
||||
(when (= lev-info default-level)
|
||||
(format 0 "Unable to (bg-custom ~A), the level was not found in *level-load-list*~%" level-name)
|
||||
(return #f)
|
||||
)
|
||||
|
||||
;; kill jak (rip)
|
||||
(format 0 "doing stop~%")
|
||||
(stop 'play)
|
||||
|
||||
;; enable visiblity. the custom level won't use it, but we want it on so other levels can be loaded.
|
||||
(set! (-> *level* vis?) #t)
|
||||
|
||||
;; disable border and play mode to prevent loading levels
|
||||
(set! (-> *level* border?) #f)
|
||||
(set! (-> *setting-control* default border-mode) #f)
|
||||
(set! (-> *level* play?) #f)
|
||||
|
||||
(format 0 "doing level load~%")
|
||||
;; allocate level. This may start the loading process, but won't finish it.
|
||||
(let ((lev (level-get-for-use *level* level-name 'active)))
|
||||
(when (not lev)
|
||||
(format 0 "Unable to load level, could not level-get-for-use~%")
|
||||
(return #f)
|
||||
)
|
||||
(format 0 "about to start load loop, game will freeze and hopefully come back soon~%")
|
||||
|
||||
;; spin in a loop and load it. This will cause the game to freeze during the load,
|
||||
;; but this is good enough for now.
|
||||
(while (or (= (-> lev status) 'loading)
|
||||
(= (-> lev status) 'loading-bt)
|
||||
(= (-> lev status) 'login)
|
||||
)
|
||||
(load-continue lev)
|
||||
)
|
||||
|
||||
(when (not (-> lev info continues))
|
||||
(format 0 "level info has no continues, can't load it.~%")
|
||||
)
|
||||
|
||||
(let ((cont (car (-> lev info continues))))
|
||||
(start 'play (the continue-point cont))
|
||||
)
|
||||
|
||||
(vis-load lev)
|
||||
(set! (-> lev all-visible?) #f)
|
||||
(set! (-> lev force-all-visible?) #t)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(defun play ((use-vis symbol) (init-game symbol))
|
||||
"The entry point to the actual game! This allocates the level heaps, loads some data, sets some default parameters and sets the startup level."
|
||||
|
||||
@ -1817,11 +1877,11 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;; load vis info.
|
||||
;; The load-state's vis-nick is the level we want vis data for.
|
||||
;; Note that we won't load vis until we are inside the level's boxes.
|
||||
|
||||
|
||||
;; this will be the level that is currently being used.
|
||||
(let ((s5-3 #f))
|
||||
(dotimes (v1-121 (-> *level* length))
|
||||
@ -1833,7 +1893,7 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;; if we have the wrong vis
|
||||
(when (and (!= s5-3 (-> obj vis-nick)) (-> *level* vis?))
|
||||
;; and we want a vis
|
||||
@ -1859,23 +1919,23 @@
|
||||
;; method 16 level-group (debug text stuff)
|
||||
|
||||
(defmethod level-update level-group ((obj level-group))
|
||||
|
||||
|
||||
;; this does nothing...
|
||||
(camera-pos)
|
||||
(new 'static 'boxed-array :type symbol :length 0 :allocated-length 2)
|
||||
|
||||
|
||||
;; compute the settings for this frame
|
||||
(update *setting-control*)
|
||||
|
||||
|
||||
;; run the art loading system
|
||||
(update *art-control* #t)
|
||||
(clear-rec *art-control*)
|
||||
|
||||
|
||||
;; run level loading!
|
||||
(dotimes (s5-0 2)
|
||||
(load-continue (-> obj level s5-0))
|
||||
)
|
||||
|
||||
|
||||
;; compute inside for each level
|
||||
(dotimes (s5-1 (-> obj length))
|
||||
(let ((s4-0 (-> obj level s5-1)))
|
||||
@ -1889,10 +1949,9 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;; update load state machine (the level-border one)
|
||||
(update! *load-state*)
|
||||
|
||||
;; checkpoint assignment
|
||||
(dotimes (s5-2 (-> obj length))
|
||||
(let ((s4-1 (-> obj level s5-2)))
|
||||
@ -1927,7 +1986,7 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;; determine vis info idx for each level
|
||||
(dotimes (v1-67 (-> obj length))
|
||||
(let ((a0-26 (-> obj level v1-67)))
|
||||
@ -1950,7 +2009,7 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;; display level vis info
|
||||
(when *display-level-border*
|
||||
(dotimes (s5-3 (-> obj length))
|
||||
@ -1978,7 +2037,7 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;; if we have vis for level A, but we aren't "in" it, display an error and
|
||||
;; force us out of the other level. Ideally the boxes and the load boundary system
|
||||
;; will be consistent and there is no way to set a vis to a level that we aren't in.
|
||||
@ -2002,7 +2061,6 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
;; if we are outside of the boxes, we consider ourselves "outside of bsp"
|
||||
;; if we are outside of both levels boxes, then we don't really know what to do
|
||||
;; for vis, and we can display the classic "outside of bsp" error.
|
||||
@ -2041,7 +2099,7 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;; now, handle setting bit 31 (maybe single vis mode?)
|
||||
(cond
|
||||
;; special display self mode.
|
||||
@ -2074,7 +2132,6 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(when (or *display-level-border* *display-texture-download* *display-split-box-info*)
|
||||
(when *display-level-border*
|
||||
(format *stdcon* " want: ~A ~A/~A ~A ~A/~A~%"
|
||||
@ -2144,13 +2201,13 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;; tell PC port about our levels
|
||||
(__pc-set-levels
|
||||
(__pc-set-levels
|
||||
(if (= (-> obj level0 status) 'inactive) "none" (symbol->string (-> obj level0 nickname)))
|
||||
(if (= (-> obj level1 status) 'inactive) "none" (symbol->string (-> obj level1 nickname)))
|
||||
)
|
||||
|
||||
|
||||
0
|
||||
)
|
||||
|
||||
|
@ -180,9 +180,11 @@
|
||||
)
|
||||
)
|
||||
(let ((s5-2 (level-get *level* (-> arg0 level))))
|
||||
(when s5-2
|
||||
|
||||
;; vis info check added for PC, don't bother waiting for vis if the level doesn't have it.
|
||||
(when (and s5-2 (-> s5-2 vis-info 0))
|
||||
(while (and (= (-> s5-2 all-visible?) 'loading) (-> *level* vis?))
|
||||
(suspend)
|
||||
(suspend)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -103,6 +103,17 @@
|
||||
)
|
||||
)
|
||||
|
||||
(defun custom-level-cgo (output-name desc-file-name)
|
||||
"Add a CGO with the given output name (in out/iso) and input name (in custom_levels/)"
|
||||
(let ((out-name (string-append "out/iso/" output-name)))
|
||||
(defstep :in (string-append "custom_levels/" desc-file-name)
|
||||
:tool 'dgo
|
||||
:out `(,out-name)
|
||||
)
|
||||
(set! *all-cgos* (cons out-name *all-cgos*))
|
||||
)
|
||||
)
|
||||
|
||||
(defun cgo (output-name desc-file-name)
|
||||
"Add a CGO with the given output name (in out/iso) and input name (in goal_src/dgos)"
|
||||
(let ((out-name (string-append "out/iso/" output-name)))
|
||||
@ -147,6 +158,12 @@
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro build-custom-level (name)
|
||||
(let* ((path (string-append "custom_levels/" name "/" name ".jsonc")))
|
||||
`(defstep :in ,path
|
||||
:tool 'build-level
|
||||
:out '(,(string-append "out/obj/" name ".go")))))
|
||||
|
||||
(defun get-iso-data-path ()
|
||||
(if *use-iso-data-path*
|
||||
(string-append *iso-data* "/")
|
||||
@ -1553,6 +1570,17 @@
|
||||
"ndi-volumes-ag"
|
||||
"title-vis")
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Example Custom Level
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; Set up the build system to build the level geometry
|
||||
;; this path is relative to the custom_levels/ folder
|
||||
;; it should point to the .jsonc file that specifies the level.
|
||||
(build-custom-level "test-zone")
|
||||
;; the DGO file
|
||||
(custom-level-cgo "TESTZONE.DGO" "test-zone/testzone.gd")
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Game Engine Code
|
||||
;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -5,6 +5,16 @@ add_library(compiler
|
||||
emitter/ObjectGenerator.cpp
|
||||
emitter/Register.cpp
|
||||
debugger/disassemble.cpp
|
||||
build_level/build_level.cpp
|
||||
build_level/collide_bvh.cpp
|
||||
build_level/collide_drawable.cpp
|
||||
build_level/collide_pack.cpp
|
||||
build_level/color_quantization.cpp
|
||||
build_level/FileInfo.cpp
|
||||
build_level/gltf_mesh_extract.cpp
|
||||
build_level/LevelFile.cpp
|
||||
build_level/ResLump.cpp
|
||||
build_level/Tfrag.cpp
|
||||
compiler/Compiler.cpp
|
||||
compiler/Env.cpp
|
||||
compiler/Val.cpp
|
||||
@ -44,7 +54,7 @@ add_library(compiler
|
||||
regalloc/Allocator_v2.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(compiler common Zydis)
|
||||
target_link_libraries(compiler common Zydis tiny_gltf)
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(compiler mman)
|
||||
|
30
goalc/build_level/FileInfo.cpp
Normal file
30
goalc/build_level/FileInfo.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
#include "FileInfo.h"
|
||||
#include "goalc/data_compiler/DataObjectGenerator.h"
|
||||
#include "common/versions.h"
|
||||
|
||||
size_t FileInfo::add_to_object_file(DataObjectGenerator& gen) const {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("file-info");
|
||||
size_t offset = gen.current_offset_bytes();
|
||||
gen.add_type_tag(file_type);
|
||||
gen.add_ref_to_string_in_pool(file_name);
|
||||
gen.add_word(major_version);
|
||||
gen.add_word(minor_version);
|
||||
gen.add_ref_to_string_in_pool(maya_file_name);
|
||||
gen.add_ref_to_string_in_pool(tool_debug);
|
||||
gen.add_ref_to_string_in_pool(tool_debug);
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
FileInfo make_file_info_for_level(const std::string& file_name) {
|
||||
FileInfo info;
|
||||
info.file_type = "bsp-header";
|
||||
info.file_name = file_name;
|
||||
info.major_version = versions::jak1::LEVEL_FILE_VERSION;
|
||||
info.minor_version = 0;
|
||||
info.maya_file_name = "Unknown";
|
||||
info.tool_debug = "Created by OpenGOAL buildlevel";
|
||||
info.mdb_file_name = "Unknown";
|
||||
return info;
|
||||
}
|
28
goalc/build_level/FileInfo.h
Normal file
28
goalc/build_level/FileInfo.h
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
class DataObjectGenerator;
|
||||
|
||||
struct FileInfo {
|
||||
// (file-type symbol :offset-assert 4)
|
||||
std::string file_type;
|
||||
// (file-name basic :offset-assert 8)
|
||||
std::string file_name;
|
||||
// (major-version uint32 :offset-assert 12)
|
||||
u32 major_version = 0;
|
||||
// (minor-version uint32 :offset-assert 16)
|
||||
u32 minor_version = 0;
|
||||
// (maya-file-name basic :offset-assert 20)
|
||||
std::string maya_file_name;
|
||||
// (tool-debug basic :offset-assert 24)
|
||||
std::string tool_debug;
|
||||
// (mdb-file-name basic :offset-assert 28)
|
||||
std::string mdb_file_name;
|
||||
|
||||
size_t add_to_object_file(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
FileInfo make_file_info_for_level(const std::string& file_name);
|
105
goalc/build_level/LevelFile.cpp
Normal file
105
goalc/build_level/LevelFile.cpp
Normal file
@ -0,0 +1,105 @@
|
||||
#include "LevelFile.h"
|
||||
#include "goalc/data_compiler/DataObjectGenerator.h"
|
||||
|
||||
size_t DrawableTreeArray::add_to_object_file(DataObjectGenerator& gen) const {
|
||||
/*
|
||||
(deftype drawable-tree-array (drawable-group)
|
||||
((trees drawable-tree 1 :offset 32 :score 100))
|
||||
:flag-assert #x1200000024
|
||||
)
|
||||
(deftype drawable-group (drawable)
|
||||
((length int16 :offset 6)
|
||||
(data drawable 1 :offset-assert 32)
|
||||
)
|
||||
(:methods
|
||||
(new (symbol type int) _type_)
|
||||
)
|
||||
:flag-assert #x1200000024
|
||||
)
|
||||
*/
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("drawable-tree-array");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
int num_trees = 0;
|
||||
num_trees += tfrags.size();
|
||||
num_trees += collides.size();
|
||||
gen.add_word(num_trees << 16);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
gen.add_word(0);
|
||||
|
||||
// todo add trees...
|
||||
|
||||
if (num_trees == 0) {
|
||||
gen.add_word(0); // the one at the end.
|
||||
} else {
|
||||
int tree_word = (int)gen.current_offset_bytes() / 4;
|
||||
for (int i = 0; i < num_trees; i++) {
|
||||
gen.add_word(0);
|
||||
}
|
||||
|
||||
for (auto& tfrag : tfrags) {
|
||||
// gen.set_word(tree_word++, tfrag.add_to_object_file(gen));
|
||||
gen.link_word_to_byte(tree_word++, tfrag.add_to_object_file(gen));
|
||||
}
|
||||
|
||||
for (auto& collide : collides) {
|
||||
gen.link_word_to_byte(tree_word++, collide.add_to_object_file(gen));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<u8> LevelFile::save_object_file() const {
|
||||
DataObjectGenerator gen;
|
||||
gen.add_type_tag("bsp-header");
|
||||
|
||||
// add blank space for the bsp-header
|
||||
while (gen.words() < 100) {
|
||||
gen.add_word(0);
|
||||
}
|
||||
|
||||
//(info file-info :offset 4)
|
||||
auto file_info_slot = info.add_to_object_file(gen);
|
||||
gen.link_word_to_byte(1, file_info_slot);
|
||||
|
||||
//(bsphere vector :inline :offset-assert 16)
|
||||
//(all-visible-list (pointer uint16) :offset-assert 32)
|
||||
//(visible-list-length int32 :offset-assert 36)
|
||||
//(drawable-trees drawable-tree-array :offset-assert 40)
|
||||
gen.link_word_to_byte(40 / 4, drawable_trees.add_to_object_file(gen));
|
||||
//(pat pointer :offset-assert 44)
|
||||
//(pat-length int32 :offset-assert 48)
|
||||
//(texture-remap-table (pointer uint64) :offset-assert 52)
|
||||
//(texture-remap-table-len int32 :offset-assert 56)
|
||||
//(texture-ids (pointer texture-id) :offset-assert 60)
|
||||
//(texture-page-count int32 :offset-assert 64)
|
||||
//(unk-zero-0 basic :offset-assert 68)
|
||||
//(name symbol :offset-assert 72)
|
||||
gen.link_word_to_symbol(name, 72 / 4);
|
||||
//(nickname symbol :offset-assert 76)
|
||||
gen.link_word_to_symbol(nickname, 76 / 4);
|
||||
//(vis-info level-vis-info 8 :offset-assert 80)
|
||||
//(actors drawable-inline-array-actor :offset-assert 112)
|
||||
//(cameras (array entity-camera) :offset-assert 116)
|
||||
//(nodes (inline-array bsp-node) :offset-assert 120)
|
||||
//(level level :offset-assert 124)
|
||||
//(current-leaf-idx uint16 :offset-assert 128)
|
||||
//(unk-data-2 uint16 9 :offset-assert 130)
|
||||
//(boxes box8s-array :offset-assert 148)
|
||||
//(current-bsp-back-flags uint32 :offset-assert 152)
|
||||
//(ambients drawable-inline-array-ambient :offset-assert 156)
|
||||
//(unk-data-4 float :offset-assert 160)
|
||||
//(unk-data-5 float :offset-assert 164)
|
||||
//(adgifs adgif-shader-array :offset-assert 168)
|
||||
//(actor-birth-order (pointer uint32) :offset-assert 172)
|
||||
//(split-box-indices (pointer uint16) :offset-assert 176)
|
||||
//(unk-data-8 uint32 55 :offset-assert 180)
|
||||
|
||||
return gen.generate_v2();
|
||||
}
|
140
goalc/build_level/LevelFile.h
Normal file
140
goalc/build_level/LevelFile.h
Normal file
@ -0,0 +1,140 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "goalc/build_level/FileInfo.h"
|
||||
#include "goalc/build_level/Tfrag.h"
|
||||
#include "goalc/build_level/collide_pack.h"
|
||||
#include "goalc/build_level/collide_common.h"
|
||||
#include "goalc/build_level/collide_bvh.h"
|
||||
#include "goalc/build_level/collide_drawable.h"
|
||||
|
||||
struct VisibilityString {
|
||||
std::vector<u8> bytes;
|
||||
};
|
||||
|
||||
struct DrawableTreeInstanceTie {};
|
||||
|
||||
struct DrawableTreeActor {};
|
||||
|
||||
struct DrawableTreeAmbient {};
|
||||
|
||||
struct DrawableTreeInstanceShrub {};
|
||||
|
||||
struct DrawableTreeArray {
|
||||
std::vector<DrawableTreeTfrag> tfrags;
|
||||
std::vector<DrawableTreeInstanceTie> ties;
|
||||
std::vector<DrawableTreeActor> actors; // unused?
|
||||
std::vector<DrawableTreeCollideFragment> collides;
|
||||
std::vector<DrawableTreeAmbient> ambients;
|
||||
std::vector<DrawableTreeInstanceShrub> shrubs;
|
||||
size_t add_to_object_file(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
struct TextureRemap {};
|
||||
|
||||
struct TextureId {};
|
||||
|
||||
struct VisInfo {};
|
||||
|
||||
struct DrawableActor {};
|
||||
|
||||
struct DrawableInlineArrayActor {};
|
||||
|
||||
struct EntityCamera {};
|
||||
|
||||
struct BspNode {};
|
||||
|
||||
struct Box8s {};
|
||||
|
||||
struct DrawableAmbient {};
|
||||
|
||||
struct DrawableInlineArrayAmbient {};
|
||||
|
||||
struct AdgifShaderArray {};
|
||||
|
||||
// This is a place to collect all the data that should go into the bsp-header file.
|
||||
struct LevelFile {
|
||||
// (info file-info :offset 4)
|
||||
FileInfo info;
|
||||
|
||||
// (all-visible-list (pointer uint16) :offset-assert 32)
|
||||
// (visible-list-length int32 :offset-assert 36)
|
||||
VisibilityString all_visibile_list;
|
||||
|
||||
// (drawable-trees drawable-tree-array :offset-assert 40)
|
||||
DrawableTreeArray drawable_trees;
|
||||
|
||||
// (pat pointer :offset-assert 44)
|
||||
// (pat-length int32 :offset-assert 48)
|
||||
std::vector<PatSurface> pat;
|
||||
|
||||
// (texture-remap-table (pointer uint64) :offset-assert 52)
|
||||
// (texture-remap-table-len int32 :offset-assert 56)
|
||||
std::vector<TextureRemap> texture_remap_table;
|
||||
|
||||
// (texture-ids (pointer texture-id) :offset-assert 60)
|
||||
// (texture-page-count int32 :offset-assert 64)
|
||||
std::vector<TextureId> texture_ids;
|
||||
|
||||
// (unk-zero-0 basic :offset-assert 68)
|
||||
// "misc", seems like it can be zero and is unused.
|
||||
|
||||
// (name symbol :offset-assert 72)
|
||||
std::string name; // full name
|
||||
|
||||
// (nickname symbol :offset-assert 76)
|
||||
std::string nickname; // 3 char name
|
||||
|
||||
// (vis-info level-vis-info 8 :offset-assert 80) ;; note: 0 when
|
||||
std::array<VisInfo, 8> vis_infos;
|
||||
|
||||
// (actors drawable-inline-array-actor :offset-assert 112)
|
||||
DrawableInlineArrayActor actors;
|
||||
|
||||
// (cameras (array entity-camera) :offset-assert 116)
|
||||
std::vector<EntityCamera> cameras;
|
||||
|
||||
// (nodes (inline-array bsp-node) :offset-assert 120)
|
||||
std::vector<BspNode> nodes;
|
||||
|
||||
// (level level :offset-assert 124)
|
||||
// zero
|
||||
|
||||
// (current-leaf-idx uint16 :offset-assert 128)
|
||||
// zero
|
||||
|
||||
// (unk-data-2 uint16 9 :offset-assert 130)
|
||||
// looks like padding plus 4 floats? unused
|
||||
|
||||
// (boxes box8s-array :offset-assert 148)
|
||||
std::vector<Box8s> boxes;
|
||||
|
||||
// (current-bsp-back-flags uint32 :offset-assert 152)
|
||||
// zero
|
||||
|
||||
// (ambients drawable-inline-array-ambient :offset-assert 156)
|
||||
DrawableInlineArrayAmbient ambients;
|
||||
|
||||
// (unk-data-4 float :offset-assert 160)
|
||||
float close_subdiv = 0;
|
||||
|
||||
// (unk-data-5 float :offset-assert 164)
|
||||
float far_subdiv = 0;
|
||||
|
||||
// (adgifs adgif-shader-array :offset-assert 168)
|
||||
AdgifShaderArray adgifs;
|
||||
|
||||
// (actor-birth-order (pointer uint32) :offset-assert 172)
|
||||
std::vector<u32> actor_birth_order;
|
||||
|
||||
// (split-box-indices (pointer uint16) :offset-assert 176)
|
||||
std::vector<u16> split_box_indices;
|
||||
|
||||
// (unk-data-8 uint32 55 :offset-assert 180)
|
||||
|
||||
std::vector<u8> save_object_file() const;
|
||||
};
|
4
goalc/build_level/ResLump.cpp
Normal file
4
goalc/build_level/ResLump.cpp
Normal file
@ -0,0 +1,4 @@
|
||||
#include "ResLump.h"
|
||||
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "goalc/data_compiler/DataObjectGenerator.h"
|
7
goalc/build_level/ResLump.h
Normal file
7
goalc/build_level/ResLump.h
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "common/common_types.h"
|
12
goalc/build_level/TexturePool.h
Normal file
12
goalc/build_level/TexturePool.h
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
#include "common/custom_data/Tfrag3Data.h"
|
||||
|
||||
struct TexturePool {
|
||||
std::unordered_map<std::string, int> textures_by_name;
|
||||
std::vector<tfrag3::Texture> textures_by_idx;
|
||||
};
|
99
goalc/build_level/Tfrag.cpp
Normal file
99
goalc/build_level/Tfrag.cpp
Normal file
@ -0,0 +1,99 @@
|
||||
#include <iostream>
|
||||
|
||||
#include "Tfrag.h"
|
||||
#include "common/custom_data/pack_helpers.h"
|
||||
#include "goalc/data_compiler/DataObjectGenerator.h"
|
||||
#include "goalc/build_level/gltf_mesh_extract.h"
|
||||
|
||||
void tfrag_from_gltf(const gltf_mesh_extract::TfragOutput& mesh_extract_out,
|
||||
DrawableTreeTfrag& out,
|
||||
tfrag3::TfragTree& out_pc) {
|
||||
out_pc.kind = tfrag3::TFragmentTreeKind::NORMAL; // todo more types?
|
||||
out_pc.draws = std::move(mesh_extract_out.strip_draws);
|
||||
fmt::print("have {} draws\n", out_pc.draws.size());
|
||||
pack_tfrag_vertices(&out_pc.packed_vertices, mesh_extract_out.vertices);
|
||||
fmt::print("have {} vertices\n", out_pc.packed_vertices.vertices.size());
|
||||
|
||||
for (auto& col : mesh_extract_out.color_palette) {
|
||||
tfrag3::TimeOfDayColor todc;
|
||||
for (auto& rgba : todc.rgba) {
|
||||
rgba = col;
|
||||
}
|
||||
out_pc.colors.push_back(todc);
|
||||
}
|
||||
out_pc.use_strips = false;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
(deftype drawable-group (drawable)
|
||||
((length int16 :offset 6)
|
||||
(data drawable 1 :offset-assert 32)
|
||||
)
|
||||
(:methods
|
||||
(new (symbol type int) _type_)
|
||||
)
|
||||
:flag-assert #x1200000024
|
||||
)
|
||||
|
||||
(deftype drawable-tree (drawable-group)
|
||||
()
|
||||
:flag-assert #x1200000024
|
||||
)
|
||||
|
||||
(deftype drawable-inline-array (drawable)
|
||||
((length int16 :offset 6) ;; this is kinda weird.
|
||||
)
|
||||
:method-count-assert 18
|
||||
:size-assert #x20
|
||||
:flag-assert #x1200000020
|
||||
)
|
||||
|
||||
(deftype drawable-inline-array-tfrag (drawable-inline-array)
|
||||
((data tfragment 1 :inline :offset-assert 32)
|
||||
(pad uint32))
|
||||
:method-count-assert 18
|
||||
:size-assert #x64
|
||||
:flag-assert #x1200000064
|
||||
)
|
||||
|
||||
(deftype drawable-tree-tfrag (drawable-tree)
|
||||
((time-of-day-pal time-of-day-palette :offset 12)
|
||||
(arrays drawable-inline-array 1 :offset 32 :score 100) ;; either drawable-inline-array-node
|
||||
or drawable-inline-array-tfrag
|
||||
)
|
||||
:method-count-assert #x12
|
||||
:size-assert #x24
|
||||
:flag-assert #x1200000024
|
||||
)
|
||||
|
||||
|
||||
*/
|
||||
|
||||
size_t add_empty_dia(const std::string& name, DataObjectGenerator& gen, int total_size) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag(name);
|
||||
size_t result = gen.current_offset_bytes();
|
||||
total_size -= 4;
|
||||
while (total_size > 0) {
|
||||
gen.add_word(0);
|
||||
total_size -= 4;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t DrawableTreeTfrag::add_to_object_file(DataObjectGenerator& gen) const {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("drawable-tree-tfrag");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(1 << 16);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
gen.add_word(0);
|
||||
}
|
||||
size_t slot = gen.add_word(0);
|
||||
ASSERT(slot * 4 - result == 28);
|
||||
gen.link_word_to_byte(slot, add_empty_dia("drawable-inline-array-tfrag", gen, 0x64));
|
||||
|
||||
return result;
|
||||
}
|
17
goalc/build_level/Tfrag.h
Normal file
17
goalc/build_level/Tfrag.h
Normal file
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "common/custom_data/Tfrag3Data.h"
|
||||
#include "goalc/build_level/TexturePool.h"
|
||||
#include "goalc/build_level/gltf_mesh_extract.h"
|
||||
|
||||
class DataObjectGenerator;
|
||||
|
||||
struct DrawableTreeTfrag {
|
||||
size_t add_to_object_file(DataObjectGenerator& gen) const;
|
||||
};
|
||||
|
||||
void tfrag_from_gltf(const gltf_mesh_extract::TfragOutput& mesh_extract_out,
|
||||
DrawableTreeTfrag& out,
|
||||
tfrag3::TfragTree& out_pc);
|
102
goalc/build_level/build_level.cpp
Normal file
102
goalc/build_level/build_level.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "common/util/json_util.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "common/log/log.h"
|
||||
#include "goalc/build_level/LevelFile.h"
|
||||
#include "goalc/build_level/FileInfo.h"
|
||||
#include "goalc/build_level/Tfrag.h"
|
||||
#include "goalc/build_level/gltf_mesh_extract.h"
|
||||
#include "goalc/build_level/collide_bvh.h"
|
||||
#include "goalc/build_level/collide_pack.h"
|
||||
|
||||
#include "common/custom_data/Tfrag3Data.h"
|
||||
#include "common/util/compress.h"
|
||||
|
||||
void save_pc_data(const std::string& nickname, tfrag3::Level& data) {
|
||||
Serializer ser;
|
||||
data.serialize(ser);
|
||||
auto compressed =
|
||||
compression::compress_zstd(ser.get_save_result().first, ser.get_save_result().second);
|
||||
fmt::print("stats for {}\n", data.level_name);
|
||||
print_memory_usage(data, ser.get_save_result().second);
|
||||
fmt::print("compressed: {} -> {} ({:.2f}%)\n", ser.get_save_result().second, compressed.size(),
|
||||
100.f * compressed.size() / ser.get_save_result().second);
|
||||
file_util::write_binary_file(file_util::get_file_path({fmt::format("assets/{}.fr3", nickname)}),
|
||||
compressed.data(), compressed.size());
|
||||
}
|
||||
|
||||
std::vector<std::string> get_build_level_deps(const std::string& input_file) {
|
||||
auto level_json = parse_commented_json(
|
||||
file_util::read_text_file(file_util::get_file_path({input_file})), input_file);
|
||||
return {level_json.at("gltf_file").get<std::string>()};
|
||||
}
|
||||
|
||||
bool run_build_level(const std::string& input_file, const std::string& output_file) {
|
||||
auto level_json = parse_commented_json(
|
||||
file_util::read_text_file(file_util::get_file_path({input_file})), input_file);
|
||||
LevelFile file; // GOAL level file
|
||||
tfrag3::Level pc_level; // PC level file
|
||||
TexturePool tex_pool; // pc level texture pool
|
||||
|
||||
// process input mesh from blender
|
||||
gltf_mesh_extract::Input mesh_extract_in;
|
||||
mesh_extract_in.filename =
|
||||
file_util::get_file_path({level_json.at("gltf_file").get<std::string>()});
|
||||
mesh_extract_in.tex_pool = &tex_pool;
|
||||
gltf_mesh_extract::Output mesh_extract_out;
|
||||
gltf_mesh_extract::extract(mesh_extract_in, mesh_extract_out);
|
||||
|
||||
// add stuff to the GOAL level structure
|
||||
file.info = make_file_info_for_level(std::filesystem::path(input_file).filename().string());
|
||||
// all vis
|
||||
// drawable trees
|
||||
// pat
|
||||
// texture remap
|
||||
// texture ids
|
||||
// unk zero
|
||||
// name
|
||||
file.name = level_json.at("long_name").get<std::string>();
|
||||
// nick
|
||||
file.nickname = level_json.at("nickname").get<std::string>();
|
||||
// vis infos
|
||||
// actors
|
||||
// cameras
|
||||
// nodes
|
||||
// boxes
|
||||
// ambients
|
||||
// subdivs
|
||||
// adgifs
|
||||
// actor birht
|
||||
// split box
|
||||
|
||||
// add stuff to the PC level structure
|
||||
pc_level.level_name = file.name;
|
||||
|
||||
// TFRAG
|
||||
auto& tfrag_drawable_tree = file.drawable_trees.tfrags.emplace_back();
|
||||
tfrag_from_gltf(mesh_extract_out.tfrag, tfrag_drawable_tree,
|
||||
pc_level.tfrag_trees[0].emplace_back());
|
||||
pc_level.textures = std::move(tex_pool.textures_by_idx);
|
||||
|
||||
// COLLIDE
|
||||
if (mesh_extract_out.collide.faces.empty()) {
|
||||
lg::error("No collision geometry was found");
|
||||
} else {
|
||||
auto& collide_drawable_tree = file.drawable_trees.collides.emplace_back();
|
||||
collide_drawable_tree.bvh = collide::construct_collide_bvh(mesh_extract_out.collide.faces);
|
||||
collide_drawable_tree.packed_frags = pack_collide_frags(collide_drawable_tree.bvh.frags.frags);
|
||||
}
|
||||
|
||||
// Save the GOAL level
|
||||
auto result = file.save_object_file();
|
||||
fmt::print("Level bsp file size {} bytes\n", result.size());
|
||||
auto save_path = file_util::get_file_path({output_file});
|
||||
file_util::create_dir_if_needed_for_file(save_path);
|
||||
fmt::print("Saving to {}\n", save_path);
|
||||
file_util::write_binary_file(save_path, result.data(), result.size());
|
||||
|
||||
// Save the PC level
|
||||
save_pc_data(file.nickname, pc_level);
|
||||
|
||||
return true;
|
||||
}
|
7
goalc/build_level/build_level.h
Normal file
7
goalc/build_level/build_level.h
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
bool run_build_level(const std::string& input_file, const std::string& output_file);
|
||||
std::vector<std::string> get_build_level_deps(const std::string& input_file);
|
284
goalc/build_level/collide_bvh.cpp
Normal file
284
goalc/build_level/collide_bvh.cpp
Normal file
@ -0,0 +1,284 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include "collide_bvh.h"
|
||||
#include "common/util/Assert.h"
|
||||
#include "common/log/log.h"
|
||||
#include "common/util/Timer.h"
|
||||
|
||||
// Collision BVH algorithm
|
||||
// We start with all the points in a single node, then recursively split nodes in 8 until no nodes
|
||||
// have too many faces.
|
||||
// The splitting is done by doing median cuts along the x, y, or z axis.
|
||||
|
||||
// The bspheres are built at the end.
|
||||
|
||||
namespace collide {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int MAX_FACES_IN_FRAG = 100;
|
||||
|
||||
/*!
|
||||
* The Collide node.
|
||||
* Has either children collide node or children faces, but not both
|
||||
* The size of child_nodes is either 0 or 8 at all times.
|
||||
*/
|
||||
struct CNode {
|
||||
std::vector<CNode> child_nodes;
|
||||
std::vector<CollideFace> faces;
|
||||
math::Vector4f bsphere;
|
||||
};
|
||||
|
||||
struct BBox {
|
||||
math::Vector3f mins, maxs;
|
||||
std::string sz_to_string() const {
|
||||
return fmt::format("({})", ((maxs - mins) / 4096.f).to_string_aligned());
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Make the bounding box hold this node and all its children.
|
||||
*/
|
||||
void add_to_bbox_recursive(const CNode& node, BBox& bbox) {
|
||||
if (node.faces.empty()) {
|
||||
ASSERT(node.child_nodes.size() == 8);
|
||||
for (auto& child : node.child_nodes) {
|
||||
add_to_bbox_recursive(child, bbox);
|
||||
}
|
||||
} else {
|
||||
for (auto& face : node.faces) {
|
||||
for (auto& vert : face.v) {
|
||||
bbox.mins.min_in_place(vert);
|
||||
bbox.maxs.max_in_place(vert);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BBox bbox_of_node(const CNode& node) {
|
||||
BBox bbox;
|
||||
bbox.mins.fill(std::numeric_limits<float>::max());
|
||||
bbox.maxs.fill(-std::numeric_limits<float>::max());
|
||||
add_to_bbox_recursive(node, bbox);
|
||||
return bbox;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Make the bsphere hold this node and all its children.
|
||||
*/
|
||||
void update_bsphere_recursive(const CNode& node, const math::Vector3f& origin, float& r_squared) {
|
||||
if (node.faces.empty()) {
|
||||
ASSERT(node.child_nodes.size() == 8);
|
||||
for (auto& child : node.child_nodes) {
|
||||
update_bsphere_recursive(child, origin, r_squared);
|
||||
}
|
||||
} else {
|
||||
for (auto& face : node.faces) {
|
||||
for (auto& vert : face.v) {
|
||||
r_squared = std::max(r_squared, (vert - origin).squared_length());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Compute the bsphere of a single node.
|
||||
*/
|
||||
void compute_my_bsphere(CNode& node) {
|
||||
// first compute bbox.
|
||||
BBox bbox = bbox_of_node(node);
|
||||
float r = 0;
|
||||
math::Vector3f origin = (bbox.maxs + bbox.mins) * 0.5;
|
||||
update_bsphere_recursive(node, origin, r);
|
||||
node.bsphere.x() = origin.x();
|
||||
node.bsphere.y() = origin.y();
|
||||
node.bsphere.z() = origin.z();
|
||||
node.bsphere.w() = std::sqrt(r);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Split faces in two along a coordinate plane.
|
||||
* Will clear the input faces
|
||||
*/
|
||||
void split_along_dim(std::vector<CollideFace>& faces,
|
||||
int dim,
|
||||
std::vector<CollideFace>* out0,
|
||||
std::vector<CollideFace>* out1) {
|
||||
std::sort(faces.begin(), faces.end(), [=](const CollideFace& a, const CollideFace& b) {
|
||||
return a.bsphere[dim] < b.bsphere[dim];
|
||||
});
|
||||
size_t split_idx = faces.size() / 2;
|
||||
out0->insert(out0->end(), faces.begin(), faces.begin() + split_idx);
|
||||
out1->insert(out1->end(), faces.begin() + split_idx, faces.end());
|
||||
}
|
||||
|
||||
/*!
|
||||
* Split a node into two nodes. The outputs should be uninitialized nodes
|
||||
*/
|
||||
void split_node_once(CNode& node, CNode* out0, CNode* out1) {
|
||||
CNode temps[6];
|
||||
// split_along_dim(node.faces, pick_dim_for_split(node.faces), &out0->faces, &out1->faces);
|
||||
split_along_dim(node.faces, 0, &temps[0].faces, &temps[1].faces);
|
||||
split_along_dim(node.faces, 1, &temps[2].faces, &temps[3].faces);
|
||||
split_along_dim(node.faces, 2, &temps[4].faces, &temps[5].faces);
|
||||
node.faces.clear();
|
||||
for (auto& t : temps) {
|
||||
compute_my_bsphere(t);
|
||||
}
|
||||
|
||||
float max_bspheres[3] = {0, 0, 0};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
max_bspheres[i] = std::max(temps[i * 2].bsphere.w(), temps[i * 2 + 1].bsphere.w());
|
||||
}
|
||||
|
||||
int best_dim = 0;
|
||||
float best_w = max_bspheres[0];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (max_bspheres[i] < best_w) {
|
||||
best_dim = i;
|
||||
best_w = max_bspheres[i];
|
||||
}
|
||||
}
|
||||
|
||||
*out0 = temps[best_dim * 2];
|
||||
*out1 = temps[best_dim * 2 + 1];
|
||||
}
|
||||
|
||||
/*!
|
||||
* Split a node into 8 children and store these in the given node.
|
||||
*/
|
||||
void split_node_to_8_children(CNode& node) {
|
||||
ASSERT(node.child_nodes.empty());
|
||||
node.child_nodes.resize(8);
|
||||
// level 0
|
||||
CNode level0[2];
|
||||
split_node_once(node, &level0[0], &level0[1]);
|
||||
// level 1
|
||||
CNode level1[4];
|
||||
split_node_once(level0[0], &level1[0], &level1[1]);
|
||||
split_node_once(level0[1], &level1[2], &level1[3]);
|
||||
// level 2
|
||||
split_node_once(level1[0], &node.child_nodes[0], &node.child_nodes[1]);
|
||||
split_node_once(level1[1], &node.child_nodes[2], &node.child_nodes[3]);
|
||||
split_node_once(level1[2], &node.child_nodes[4], &node.child_nodes[5]);
|
||||
split_node_once(level1[3], &node.child_nodes[6], &node.child_nodes[7]);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Split all leaf nodes. Returns the number of faces in the leaf with the most faces after
|
||||
* splitting.
|
||||
* This slightly unusual recursion pattern is to make sure we split everything to same depth,
|
||||
* which we believe might be a requirement of the collision system.
|
||||
*/
|
||||
size_t split_all_leaves(CNode& node) {
|
||||
size_t worst_leaf_face_count = 0;
|
||||
if (node.child_nodes.empty()) {
|
||||
// we're a leaf!
|
||||
// split us:
|
||||
split_node_to_8_children(node);
|
||||
for (auto& child : node.child_nodes) {
|
||||
worst_leaf_face_count = std::max(worst_leaf_face_count, child.faces.size());
|
||||
}
|
||||
return worst_leaf_face_count;
|
||||
} else {
|
||||
// not a leaf, recurse
|
||||
for (auto& child : node.child_nodes) {
|
||||
worst_leaf_face_count = std::max(worst_leaf_face_count, split_all_leaves(child));
|
||||
}
|
||||
return worst_leaf_face_count;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Main BVH construction function. Splits leaves until it is no longer needed.
|
||||
*/
|
||||
void split_as_needed(CNode& root) {
|
||||
int initial_tri_count = root.faces.size();
|
||||
int num_leaves = 1;
|
||||
bool need_to_split = true;
|
||||
while (need_to_split) {
|
||||
int faces_in_worst = split_all_leaves(root);
|
||||
num_leaves *= 8;
|
||||
lg::info("after splitting, the worst leaf has {} tris", faces_in_worst);
|
||||
if (faces_in_worst < MAX_FACES_IN_FRAG) {
|
||||
need_to_split = false;
|
||||
}
|
||||
}
|
||||
lg::info("average triangles per leaf: {}", initial_tri_count / num_leaves);
|
||||
lg::info("leaf count: {}", num_leaves);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Recursively compute bspheres of all children
|
||||
* (note that we don't do bspheres of bspheres... I think this is better?)
|
||||
*/
|
||||
void bsphere_recursive(CNode& node) {
|
||||
compute_my_bsphere(node);
|
||||
for (auto& child : node.child_nodes) {
|
||||
bsphere_recursive(child);
|
||||
}
|
||||
}
|
||||
|
||||
void drawable_layout_helper(CNode& node, int depth, CollideTree& tree_out, size_t my_idx_check) {
|
||||
if (node.child_nodes.empty()) {
|
||||
// we're a leaf! add us to the frags
|
||||
auto& frag = tree_out.frags.frags.emplace_back();
|
||||
frag.bsphere = node.bsphere;
|
||||
frag.faces = node.faces;
|
||||
} else {
|
||||
// not a leaf
|
||||
if ((int)tree_out.node_arrays.size() <= depth) {
|
||||
tree_out.node_arrays.resize(depth + 1);
|
||||
}
|
||||
ASSERT(my_idx_check == tree_out.node_arrays.at(depth).nodes.size());
|
||||
auto& draw_node = tree_out.node_arrays[depth].nodes.emplace_back();
|
||||
draw_node.bsphere = node.bsphere;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
draw_node.children[i] = my_idx_check * 8 + i;
|
||||
drawable_layout_helper(node.child_nodes.at(i), depth + 1, tree_out, draw_node.children[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CollideTree build_collide_tree(CNode& root) {
|
||||
CollideTree tree;
|
||||
drawable_layout_helper(root, 0, tree, 0);
|
||||
return tree;
|
||||
}
|
||||
|
||||
void debug_stats(const CollideTree& tree) {
|
||||
lg::info("Tree build: {} draw node layers", tree.node_arrays.size());
|
||||
float sum_w = 0, max_w = 0;
|
||||
for (auto& frag : tree.frags.frags) {
|
||||
sum_w += frag.bsphere.w();
|
||||
max_w = std::max(frag.bsphere.w(), max_w);
|
||||
}
|
||||
lg::info("Max bsphere radius: {:.2f}m, average {:.2f} (aiming for around 20-30m avg)",
|
||||
max_w / 4096, sum_w / (4096 * tree.frags.frags.size()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CollideTree construct_collide_bvh(const std::vector<CollideFace>& tris) {
|
||||
// part 1: build the tree
|
||||
Timer bvh_timer;
|
||||
lg::info("Building collide bvh from {} triangles", tris.size());
|
||||
CNode root;
|
||||
root.faces = tris;
|
||||
split_as_needed(root);
|
||||
lg::info("BVH tree constructed in {:.2f} ms", bvh_timer.getMs());
|
||||
|
||||
// part 2: compute bspheres
|
||||
bvh_timer.start();
|
||||
bsphere_recursive(root);
|
||||
lg::info("Found bspheres in {:.2f} ms", bvh_timer.getMs());
|
||||
|
||||
// part 3: layout tree
|
||||
bvh_timer.start();
|
||||
auto tree = build_collide_tree(root);
|
||||
debug_stats(tree);
|
||||
lg::info("Tree layout done in {:.2f} ms", bvh_timer.getMs());
|
||||
return tree;
|
||||
}
|
||||
|
||||
} // namespace collide
|
37
goalc/build_level/collide_bvh.h
Normal file
37
goalc/build_level/collide_bvh.h
Normal file
@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "goalc/build_level/collide_common.h"
|
||||
|
||||
// requirements:
|
||||
// max depth of 3 (maybe?)
|
||||
// max face per frag = 90
|
||||
// max vert per frag = 110
|
||||
// branching factor of 8 everywhere.
|
||||
namespace collide {
|
||||
|
||||
struct DrawNode {
|
||||
s32 children[8] = {-1, -1, -1, -1, -1, -1, -1, -1};
|
||||
math::Vector4f bsphere;
|
||||
};
|
||||
|
||||
struct CollideFrag {
|
||||
math::Vector4f bsphere;
|
||||
std::vector<CollideFace> faces;
|
||||
};
|
||||
|
||||
struct DrawableInlineArrayNode {
|
||||
std::vector<DrawNode> nodes;
|
||||
};
|
||||
|
||||
struct DrawableInlineArrayCollideFrag {
|
||||
std::vector<CollideFrag> frags;
|
||||
};
|
||||
|
||||
struct CollideTree {
|
||||
std::vector<DrawableInlineArrayNode> node_arrays;
|
||||
DrawableInlineArrayCollideFrag frags;
|
||||
};
|
||||
|
||||
CollideTree construct_collide_bvh(const std::vector<CollideFace>& tris);
|
||||
} // namespace collide
|
110
goalc/build_level/collide_common.h
Normal file
110
goalc/build_level/collide_common.h
Normal file
@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
#include "common/common_types.h"
|
||||
#include "common/math/Vector.h"
|
||||
|
||||
struct PatSurface {
|
||||
enum class Mode { GROUND = 0, WALL = 1, OBSTACLE = 2 };
|
||||
enum class Material {
|
||||
STONE = 0,
|
||||
ICE = 1,
|
||||
QUICKSAND = 2,
|
||||
WATERBOTTOM = 3,
|
||||
TAR = 4,
|
||||
SAND = 5,
|
||||
WOOD = 6,
|
||||
GRASS = 7,
|
||||
PCMETAL = 8,
|
||||
SNOW = 9,
|
||||
DEEPSNOW = 10,
|
||||
HOTCOALS = 11,
|
||||
LAVA = 12,
|
||||
CRWOOD = 13,
|
||||
GRAVEL = 14,
|
||||
DIRT = 15,
|
||||
METAL = 16,
|
||||
STRAW = 17,
|
||||
TUBE = 18,
|
||||
SWAMP = 19,
|
||||
STOPPROJ = 20,
|
||||
ROTATE = 21,
|
||||
NEUTRAL = 22,
|
||||
};
|
||||
|
||||
enum class Event {
|
||||
NONE = 0,
|
||||
DEADLY = 1,
|
||||
ENDLESSFALL = 2,
|
||||
BURN = 3,
|
||||
DEADLYUP = 4,
|
||||
BURNUP = 5,
|
||||
MELT = 6,
|
||||
};
|
||||
|
||||
void set_noentity(bool x) {
|
||||
if (x) {
|
||||
val |= (1 << 0);
|
||||
} else {
|
||||
val &= ~(1 << 0);
|
||||
}
|
||||
}
|
||||
bool get_noentity() const { return val & (1 << 0); }
|
||||
|
||||
void set_nocamera(bool x) {
|
||||
if (x) {
|
||||
val |= (1 << 1);
|
||||
} else {
|
||||
val &= ~(1 << 1);
|
||||
}
|
||||
}
|
||||
bool get_nocamera() const { return val & (1 << 1); }
|
||||
|
||||
void set_noedge(bool x) {
|
||||
if (x) {
|
||||
val |= (1 << 2);
|
||||
} else {
|
||||
val &= ~(1 << 2);
|
||||
}
|
||||
}
|
||||
bool get_noedge() const { return val & (1 << 2); }
|
||||
|
||||
void set_mode(Mode mode) {
|
||||
val &= ~(0b111 << 3);
|
||||
val |= ((u32)mode << 3);
|
||||
}
|
||||
Mode get_mode() const { return (Mode)(0b111 & (val >> 3)); }
|
||||
|
||||
void set_material(Material mat) {
|
||||
val &= ~(0b111111 << 6);
|
||||
val |= ((u32)mat << 6);
|
||||
}
|
||||
Material get_material() const { return (Material)(0b111111 & (val >> 6)); }
|
||||
|
||||
void set_nolineofsight(bool x) {
|
||||
if (x) {
|
||||
val |= (1 << 12);
|
||||
} else {
|
||||
val &= ~(1 << 12);
|
||||
}
|
||||
}
|
||||
bool get_nolineofsight() const { return val & (1 << 12); }
|
||||
|
||||
void set_event(Event ev) {
|
||||
val &= ~(0b111111 << 14);
|
||||
val |= ((u32)ev << 6);
|
||||
}
|
||||
Event get_event() const { return (Event)(0b111111 & (val >> 14)); }
|
||||
|
||||
bool operator==(const PatSurface& other) const { return val == other.val; }
|
||||
// bits 13, [15-31] are unused, or have unknown purpose.
|
||||
u32 val = 0;
|
||||
};
|
||||
|
||||
struct CollideVertex {
|
||||
float x, y, z;
|
||||
};
|
||||
|
||||
struct CollideFace {
|
||||
math::Vector4f bsphere;
|
||||
math::Vector3f v[3];
|
||||
PatSurface pat;
|
||||
};
|
268
goalc/build_level/collide_drawable.cpp
Normal file
268
goalc/build_level/collide_drawable.cpp
Normal file
@ -0,0 +1,268 @@
|
||||
#include "collide_drawable.h"
|
||||
#include "goalc/data_compiler/DataObjectGenerator.h"
|
||||
#include "common/util/Assert.h"
|
||||
|
||||
/*
|
||||
(deftype drawable (basic)
|
||||
((id int16 :offset-assert 4)
|
||||
(bsphere vector :inline :offset-assert 16)
|
||||
)
|
||||
|
||||
(deftype drawable-group (drawable)
|
||||
((length int16 :offset 6)
|
||||
(data drawable 1 :offset-assert 32)
|
||||
)
|
||||
|
||||
(deftype drawable-tree (drawable-group)
|
||||
()
|
||||
|
||||
(deftype drawable-inline-array (drawable)
|
||||
((length int16 :offset 6) ;; this is kinda weird.
|
||||
)
|
||||
|
||||
(deftype drawable-tree-collide-fragment (drawable-tree)
|
||||
((data-override drawable-inline-array 1 :offset 32)) ;; should be 1 there
|
||||
|
||||
(deftype drawable-inline-array-collide-fragment (drawable-inline-array)
|
||||
((data collide-fragment 1 :inline :offset-assert 32)
|
||||
|
||||
(deftype collide-fragment (drawable)
|
||||
((mesh collide-frag-mesh :offset 8)
|
||||
)
|
||||
:method-count-assert 18
|
||||
:size-assert #x20
|
||||
|
||||
(deftype collide-frag-mesh (basic)
|
||||
((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)
|
||||
)
|
||||
:method-count-assert 9
|
||||
:size-assert #x20
|
||||
|
||||
(deftype draw-node (drawable)
|
||||
((child-count uint8 :offset 6)
|
||||
(flags uint8 :offset 7)
|
||||
(child drawable :offset 8)
|
||||
(distance float :offset 12)
|
||||
)
|
||||
:size-assert #x20
|
||||
|
||||
(deftype drawable-inline-array-node (drawable-inline-array)
|
||||
((data draw-node 1 :inline)
|
||||
(pad uint32)
|
||||
)
|
||||
:method-count-assert 18
|
||||
:size-assert #x44
|
||||
*/
|
||||
|
||||
size_t generate_pat_array(DataObjectGenerator& gen, const std::vector<PatSurface>& pats) {
|
||||
gen.align_to_basic();
|
||||
size_t result = gen.current_offset_bytes();
|
||||
for (auto& pat : pats) {
|
||||
gen.add_word(pat.val);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t generate_packed_collide_data(DataObjectGenerator& gen, const std::vector<u8>& data) {
|
||||
gen.align_to_basic();
|
||||
size_t result = gen.current_offset_bytes();
|
||||
ASSERT((data.size() % 4) == 0);
|
||||
for (size_t i = 0; i < data.size(); i += 4) {
|
||||
u32 word;
|
||||
memcpy(&word, data.data() + i, 4);
|
||||
gen.add_word(word);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t generate_collide_frag_mesh(DataObjectGenerator& gen,
|
||||
const CollideFragMeshData& mesh,
|
||||
size_t packed_data_loc,
|
||||
size_t pat_array_loc) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("collide-frag-mesh"); // 0
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.link_word_to_byte(gen.add_word(0), packed_data_loc); // 4
|
||||
gen.link_word_to_byte(gen.add_word(0), pat_array_loc); // 8
|
||||
gen.add_word(mesh.strip_data_len | (mesh.poly_count << 16)); // 12
|
||||
gen.add_word(mesh.base_trans_xyz_s32.x()); // 16
|
||||
gen.add_word(mesh.base_trans_xyz_s32.y()); // 20
|
||||
gen.add_word(mesh.base_trans_xyz_s32.z()); // 24
|
||||
u32 packed = 0;
|
||||
packed |= mesh.vertex_count;
|
||||
packed |= ((u32)mesh.vertex_data_qwc) << 8;
|
||||
packed |= ((u32)mesh.total_qwc) << 16;
|
||||
gen.add_word(packed); // 28
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t generate_collide_fragment(DataObjectGenerator& gen,
|
||||
const CollideFragMeshData& mesh,
|
||||
size_t frag_mesh_loc) {
|
||||
/*
|
||||
.type collide-fragment
|
||||
.word 0x10000
|
||||
.word L705
|
||||
.word 0x0
|
||||
.word 0x46bf480a
|
||||
.word 0x43dc730b
|
||||
.word 0xb71ed4fe
|
||||
.word 0x46c42e44
|
||||
*/
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("collide-fragment");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(0x10000); // ???
|
||||
gen.link_word_to_byte(gen.add_word(0), frag_mesh_loc);
|
||||
gen.add_word(0);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
gen.add_word_float(mesh.bsphere[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t generate_collide_fragment_array(DataObjectGenerator& gen,
|
||||
const std::vector<CollideFragMeshData>& meshes,
|
||||
const std::vector<size_t>& frag_mesh_locs,
|
||||
std::vector<size_t>& parent_ref_out) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("drawable-inline-array-collide-fragment"); // 0
|
||||
size_t result = gen.current_offset_bytes();
|
||||
ASSERT(meshes.size() < UINT16_MAX);
|
||||
gen.add_word(meshes.size() << 16); // 4, 6
|
||||
gen.add_word(0); // 8
|
||||
gen.add_word(0); // 12
|
||||
gen.add_word(0); // 16
|
||||
gen.add_word(0); // 20
|
||||
gen.add_word(0); // 24
|
||||
gen.add_word(0); // 28
|
||||
|
||||
fmt::print("have: {}\n", meshes.size());
|
||||
|
||||
ASSERT(meshes.size() == frag_mesh_locs.size());
|
||||
for (size_t i = 0; i < meshes.size(); i++) {
|
||||
auto& mesh = meshes[i];
|
||||
// should be 8 words here:
|
||||
gen.add_type_tag("collide-fragment"); // 1
|
||||
size_t me = gen.current_offset_bytes();
|
||||
gen.add_word(0x10000); // ???
|
||||
gen.link_word_to_byte(gen.add_word(0), frag_mesh_locs[i]);
|
||||
gen.add_word(0);
|
||||
for (int j = 0; j < 4; j++) {
|
||||
gen.add_word_float(mesh.bsphere[j]);
|
||||
}
|
||||
if ((i % 8) == 0) {
|
||||
parent_ref_out.push_back(me);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t generate_collide_draw_node_array(DataObjectGenerator& gen,
|
||||
const std::vector<collide::DrawNode>& nodes,
|
||||
u32 flag,
|
||||
const std::vector<size_t>& children,
|
||||
std::vector<size_t>& parent_ref_out) {
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("drawable-inline-array-node"); // 0
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word(nodes.size() << 16); // 4, 6
|
||||
gen.add_word(0); // 8
|
||||
gen.add_word(0); // 12
|
||||
gen.add_word(0); // 16
|
||||
gen.add_word(0); // 20
|
||||
gen.add_word(0); // 24
|
||||
gen.add_word(0); // 28
|
||||
|
||||
ASSERT(nodes.size() == children.size());
|
||||
for (size_t i = 0; i < nodes.size(); i++) {
|
||||
auto& node = nodes[i];
|
||||
// should be 8 words here:
|
||||
gen.add_type_tag("draw-node"); // 1
|
||||
size_t me = gen.current_offset_bytes();
|
||||
u32 packed_flags = 0;
|
||||
packed_flags |= (8 << 16); // TODO hard-coded size here
|
||||
packed_flags |= (flag << 24);
|
||||
gen.add_word(packed_flags); // 2
|
||||
gen.link_word_to_byte(gen.add_word(0), children[i]); // 3
|
||||
gen.add_word(0); // 4
|
||||
if ((i % 8) == 0) {
|
||||
parent_ref_out.push_back(me);
|
||||
}
|
||||
gen.add_word_float(node.bsphere.x()); // 5
|
||||
gen.add_word_float(node.bsphere.y()); // 6
|
||||
gen.add_word_float(node.bsphere.z()); // 7
|
||||
gen.add_word_float(node.bsphere.w()); // 8
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t DrawableTreeCollideFragment::add_to_object_file(DataObjectGenerator& gen) const {
|
||||
for (auto& lev : bvh.node_arrays) {
|
||||
fmt::print("lev: {}\n", lev.nodes.size());
|
||||
}
|
||||
// generate pat array
|
||||
size_t pat_array_loc = generate_pat_array(gen, packed_frags.pats);
|
||||
|
||||
// generated packed data
|
||||
std::vector<size_t> packed_data_locs;
|
||||
for (auto& mesh : packed_frags.packed_frag_data) {
|
||||
packed_data_locs.push_back(generate_packed_collide_data(gen, mesh.packed_data));
|
||||
}
|
||||
|
||||
// generate collide frag meshes
|
||||
std::vector<size_t> collide_frag_meshes;
|
||||
for (size_t i = 0; i < packed_data_locs.size(); i++) {
|
||||
collide_frag_meshes.push_back(generate_collide_frag_mesh(gen, packed_frags.packed_frag_data[i],
|
||||
packed_data_locs[i], pat_array_loc));
|
||||
}
|
||||
|
||||
std::vector<size_t> array_locs;
|
||||
array_locs.resize(bvh.node_arrays.size() + 1); // plus one for the frags.
|
||||
int array_slot = bvh.node_arrays.size();
|
||||
|
||||
std::vector<size_t> children_refs;
|
||||
array_locs[array_slot--] = generate_collide_fragment_array(gen, packed_frags.packed_frag_data,
|
||||
collide_frag_meshes, children_refs);
|
||||
u32 flag = 0;
|
||||
while (array_slot >= 0) {
|
||||
fmt::print("sizes: {} {}\n", children_refs.size(), bvh.node_arrays.at(array_slot).nodes.size());
|
||||
ASSERT(children_refs.size() == bvh.node_arrays.at(array_slot).nodes.size());
|
||||
std::vector<size_t> next_children;
|
||||
|
||||
array_locs[array_slot] = generate_collide_draw_node_array(
|
||||
gen, bvh.node_arrays.at(array_slot).nodes, flag, children_refs, next_children);
|
||||
|
||||
children_refs = std::move(next_children);
|
||||
array_slot--;
|
||||
flag = 1;
|
||||
}
|
||||
|
||||
{
|
||||
gen.align_to_basic();
|
||||
gen.add_type_tag("drawable-tree-collide-fragment");
|
||||
size_t result = gen.current_offset_bytes();
|
||||
gen.add_word((array_locs.size() - 1) << 16); // todo the minus one here??
|
||||
for (int i = 0; i < 6; i++) {
|
||||
gen.add_word(0);
|
||||
}
|
||||
|
||||
for (size_t i = 1; i < array_locs.size(); i++) { // todo the offset here?
|
||||
gen.link_word_to_byte(gen.add_word(0), array_locs[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
12
goalc/build_level/collide_drawable.h
Normal file
12
goalc/build_level/collide_drawable.h
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "goalc/build_level/collide_bvh.h"
|
||||
#include "goalc/build_level/collide_pack.h"
|
||||
|
||||
class DataObjectGenerator;
|
||||
|
||||
struct DrawableTreeCollideFragment {
|
||||
CollideFragMeshDataArray packed_frags;
|
||||
collide::CollideTree bvh;
|
||||
size_t add_to_object_file(DataObjectGenerator& gen) const;
|
||||
};
|
263
goalc/build_level/collide_pack.cpp
Normal file
263
goalc/build_level/collide_pack.cpp
Normal file
@ -0,0 +1,263 @@
|
||||
#include <functional>
|
||||
|
||||
#include "collide_pack.h"
|
||||
#include "common/util/Assert.h"
|
||||
#include "common/log/log.h"
|
||||
#include "common/util/Timer.h"
|
||||
|
||||
struct PackedU16Verts {
|
||||
math::Vector<s32, 3> base;
|
||||
std::vector<math::Vector<u16, 3>> vertex;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Assert that the given float can be converted to a packed u16, and return that u16.
|
||||
*/
|
||||
u16 magic_float_to_u16(float in) {
|
||||
u16 u16s[2];
|
||||
memcpy(u16s, &in, 4);
|
||||
ASSERT_MSG(u16s[1] == 0x4d00,
|
||||
"Unable to pack collision vertex data to u16's. Try to make smaller triangles and "
|
||||
"avoid skinny triangles. (or there is a bug in the packer)");
|
||||
return u16s[0];
|
||||
}
|
||||
|
||||
namespace {
|
||||
float u32_to_float(u32 in) {
|
||||
float out;
|
||||
memcpy(&out, &in, 4);
|
||||
return out;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/*!
|
||||
* Pack vertices to base + u16.
|
||||
* The format is quite strange to allow for fast unpacking.
|
||||
*/
|
||||
PackedU16Verts pack_verts_to_u16(const std::vector<math::Vector3f>& input) {
|
||||
PackedU16Verts result;
|
||||
ASSERT(!input.empty());
|
||||
|
||||
// this "magic" offset is a large float where a ulp is 16.f, or 1/256th of a meter.
|
||||
// this means that float -> int can be done as a single addition.
|
||||
// (or, in some cases, we can avoid float->int entirely)
|
||||
math::Vector3f magic_offset;
|
||||
magic_offset.fill(u32_to_float(0x4d000000));
|
||||
|
||||
// we'll be treating everything as an offset from this minimum vertex:
|
||||
math::Vector3f min_vtx = input[0];
|
||||
for (auto& vtx : input) {
|
||||
min_vtx.min_in_place(vtx);
|
||||
}
|
||||
// give us a tiny bit of extra room to avoid rounding problems
|
||||
min_vtx -= 16.f;
|
||||
|
||||
// to round down to nearest integer
|
||||
result.base = min_vtx.cast<s32>();
|
||||
auto base = result.base.cast<float>();
|
||||
|
||||
// compute offset relative to base.
|
||||
for (auto& vtx : input) {
|
||||
// add the "magic offset" to make this a 0x4dXXXXXX float.
|
||||
// subtract the base to make this a 0x4d00XXXX float.
|
||||
auto vertex_magic = vtx + magic_offset - base;
|
||||
// and if we did it right, we should be able to pack to u16's here
|
||||
result.vertex.emplace_back(magic_float_to_u16(vertex_magic[0]),
|
||||
magic_float_to_u16(vertex_magic[1]),
|
||||
magic_float_to_u16(vertex_magic[2]));
|
||||
}
|
||||
|
||||
// verify
|
||||
/*
|
||||
for (size_t i = 0; i < input.size(); i++) {
|
||||
math::Vector3f v;
|
||||
v[0] = u32_to_float(0x4d000000 + result.vertex[i][0]);
|
||||
v[1] = u32_to_float(0x4d000000 + result.vertex[i][1]);
|
||||
v[2] = u32_to_float(0x4d000000 + result.vertex[i][2]);
|
||||
float base_offset = u32_to_float(0x4d000000);
|
||||
math::Vector3f vf13_combo_offset(base_offset, base_offset, base_offset);
|
||||
math::Vector3f vf14_base_trans_float(result.base[0], result.base[1], result.base[2]);
|
||||
vf13_combo_offset -= vf14_base_trans_float;
|
||||
v -= vf13_combo_offset;
|
||||
fmt::print("error {}\n", (v - input[i]).to_string_aligned());;
|
||||
}
|
||||
*/
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
struct PatSurfaceHash {
|
||||
size_t operator()(const PatSurface& in) const { return std::hash<u32>()(in.val); }
|
||||
};
|
||||
|
||||
/*!
|
||||
* pat -> pat index mapping.
|
||||
* There's a pat "palette" with up 255 unique pats.
|
||||
*/
|
||||
struct PatMap {
|
||||
std::unordered_map<PatSurface, u32, PatSurfaceHash> map;
|
||||
std::vector<PatSurface> pats;
|
||||
|
||||
u32 add_pat(PatSurface pat) {
|
||||
const auto& lookup = map.find(pat);
|
||||
if (lookup == map.end()) {
|
||||
u32 new_idx = pats.size();
|
||||
if (new_idx > UINT8_MAX) {
|
||||
lg::die("Too many pats. Use fewer. Or improve the pat code to use multiple pat arrays.");
|
||||
}
|
||||
map[pat] = new_idx;
|
||||
pats.push_back(pat);
|
||||
return new_idx;
|
||||
} else {
|
||||
return lookup->second;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* A face, represented as indices
|
||||
*/
|
||||
struct IndexFace {
|
||||
math::Vector<u16, 3> vertex_indices; // per vertex, winding order matters here
|
||||
u8 pat_idx; // pat for the whole face
|
||||
};
|
||||
|
||||
/*!
|
||||
* All the faces in a frag
|
||||
*/
|
||||
struct IndexedFaces {
|
||||
std::vector<IndexFace> faces; // index in to the two vertex array below:
|
||||
std::vector<math::Vector3f> vertices_float;
|
||||
PackedU16Verts vertices_u16;
|
||||
};
|
||||
|
||||
struct Vector3fHash {
|
||||
size_t operator()(const math::Vector3f& in) const {
|
||||
return std::hash<float>()(in.x()) ^ std::hash<float>()(in.y()) ^ std::hash<float>()(in.z());
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Deduplicate vertices, converted to indexed, add to pat palette, pack to u16s.
|
||||
*/
|
||||
IndexedFaces dedup_frag_mesh(const collide::CollideFrag& frag, PatMap* pat_map) {
|
||||
IndexedFaces result;
|
||||
std::unordered_map<math::Vector3f, u32, Vector3fHash> vertex_map;
|
||||
// avoid confusion with 0 in strip table. (todo, can probably remove)
|
||||
result.vertices_float.push_back(math::Vector3f::zero());
|
||||
|
||||
for (auto& face_in : frag.faces) {
|
||||
auto& face_out = result.faces.emplace_back();
|
||||
// pat:
|
||||
face_out.pat_idx = pat_map->add_pat(face_in.pat);
|
||||
// vertices
|
||||
for (int i = 0; i < 3; i++) {
|
||||
const auto& lookup = vertex_map.find(face_in.v[i]);
|
||||
if (lookup == vertex_map.end()) {
|
||||
u32 idx = result.vertices_float.size();
|
||||
result.vertices_float.push_back(face_in.v[i]);
|
||||
face_out.vertex_indices[i] = idx;
|
||||
vertex_map[face_in.v[i]] = idx;
|
||||
} else {
|
||||
face_out.vertex_indices[i] = lookup->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
// fmt::print("{} -> {}\n", frag.faces.size() * 3, result.vertices_float.size());
|
||||
result.vertices_u16 = pack_verts_to_u16(result.vertices_float);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* make strip table that doesn't do any stripping. It will be quite long, which might cause problem
|
||||
*/
|
||||
std::vector<u8> make_dumb_strip_table(const IndexedFaces& faces) {
|
||||
std::vector<u8> out;
|
||||
ASSERT_MSG(
|
||||
faces.vertices_float.size() < UINT8_MAX,
|
||||
"somehow have UINT8_MAX deduped vertices in a single fragment, likely a bug somewhere.");
|
||||
for (auto& face : faces.faces) {
|
||||
out.push_back(face.vertex_indices[0]);
|
||||
out.push_back(face.vertex_indices[1]);
|
||||
out.push_back(face.vertex_indices[2]);
|
||||
out.push_back(0);
|
||||
}
|
||||
out.push_back(-1);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
CollideFragMeshDataArray pack_collide_frags(const std::vector<collide::CollideFrag>& frag_data) {
|
||||
Timer pack_timer;
|
||||
CollideFragMeshDataArray result;
|
||||
PatMap pat_map;
|
||||
|
||||
size_t total_pack_bytes = 0;
|
||||
lg::info("Packing {} fragments", frag_data.size());
|
||||
|
||||
for (auto& frag_in : frag_data) {
|
||||
auto& frag_out = result.packed_frag_data.emplace_back();
|
||||
auto indexed = dedup_frag_mesh(frag_in, &pat_map);
|
||||
// first part of packed_data is the u16 vertex data:
|
||||
frag_out.vertex_count = indexed.vertices_u16.vertex.size();
|
||||
frag_out.packed_data.resize(sizeof(u16) * frag_out.vertex_count * 3);
|
||||
memcpy(frag_out.packed_data.data(), indexed.vertices_u16.vertex.data(),
|
||||
frag_out.packed_data.size());
|
||||
// align to 16-bytes
|
||||
while (frag_out.packed_data.size() & 0xf) {
|
||||
frag_out.packed_data.push_back(0);
|
||||
}
|
||||
// remember where
|
||||
frag_out.vertex_data_qwc = frag_out.packed_data.size() / 16;
|
||||
|
||||
// up next, the strip table
|
||||
auto strip = make_dumb_strip_table(indexed);
|
||||
frag_out.packed_data.insert(frag_out.packed_data.end(), strip.begin(), strip.end());
|
||||
frag_out.strip_data_len = strip.size();
|
||||
ASSERT(frag_out.strip_data_len < UINT16_MAX); // probably in big trouble in here.
|
||||
|
||||
// pat table
|
||||
for (auto& face : indexed.faces) {
|
||||
frag_out.packed_data.push_back(face.pat_idx);
|
||||
}
|
||||
|
||||
// align to 16-bytes so total_qwc works.
|
||||
while (frag_out.packed_data.size() & 0xf) {
|
||||
frag_out.packed_data.push_back(0);
|
||||
}
|
||||
// gonna guess here:
|
||||
frag_out.poly_count = indexed.faces.size();
|
||||
frag_out.total_qwc = frag_out.packed_data.size() / 16;
|
||||
frag_out.base_trans_xyz_s32 = indexed.vertices_u16.base;
|
||||
frag_out.bsphere = frag_in.bsphere;
|
||||
total_pack_bytes += frag_out.packed_data.size();
|
||||
}
|
||||
|
||||
result.pats = pat_map.pats;
|
||||
lg::info("Total packed data size: {} kB, took {:.2f} ms", total_pack_bytes / 1024,
|
||||
pack_timer.getMs());
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
(deftype collide-frag-mesh (basic)
|
||||
((packed-data uint32 :offset-assert 4) <- ptr
|
||||
(pat-array uint32 :offset-assert 8) <- ptr
|
||||
(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) // done!
|
||||
(vertex-data-qwc uint8 :offset 29) // done!
|
||||
(total-qwc uint8 :offset 30)
|
||||
(unused uint8 :offset 31)
|
||||
)
|
||||
:method-count-assert 9
|
||||
:size-assert #x20
|
||||
:flag-assert #x900000020
|
||||
)
|
||||
*/
|
||||
|
||||
// packed_data:
|
||||
// (u16x3) per vertex, packed float vtx format.
|
||||
//
|
21
goalc/build_level/collide_pack.h
Normal file
21
goalc/build_level/collide_pack.h
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "goalc/build_level/collide_bvh.h"
|
||||
|
||||
struct CollideFragMeshData {
|
||||
math::Vector4f bsphere; // not part of the collide frag, but is part of the drawable wrapping it
|
||||
std::vector<u8> packed_data;
|
||||
u32 strip_data_len;
|
||||
u32 poly_count;
|
||||
math::Vector<s32, 3> base_trans_xyz_s32;
|
||||
u8 vertex_count;
|
||||
u8 vertex_data_qwc;
|
||||
u8 total_qwc;
|
||||
};
|
||||
|
||||
struct CollideFragMeshDataArray {
|
||||
std::vector<CollideFragMeshData> packed_frag_data;
|
||||
std::vector<PatSurface> pats;
|
||||
};
|
||||
|
||||
CollideFragMeshDataArray pack_collide_frags(const std::vector<collide::CollideFrag>& frag_data);
|
220
goalc/build_level/color_quantization.cpp
Normal file
220
goalc/build_level/color_quantization.cpp
Normal file
@ -0,0 +1,220 @@
|
||||
#include <algorithm>
|
||||
#include "color_quantization.h"
|
||||
#include "common/util/Assert.h"
|
||||
#include "common/log/log.h"
|
||||
|
||||
/*!
|
||||
* Just removes duplicate colors, which can work if there are only a few unique colors.
|
||||
*/
|
||||
QuantizedColors quantize_colors_dumb(const std::vector<math::Vector<u8, 4>>& in) {
|
||||
QuantizedColors result;
|
||||
std::unordered_map<u64, u32> color_to_slot;
|
||||
for (auto& vtx : in) {
|
||||
u64 key;
|
||||
memcpy(&key, vtx.data(), sizeof(u64));
|
||||
const auto& existing = color_to_slot.find(key);
|
||||
if (existing == color_to_slot.end()) {
|
||||
auto cidx = result.final_colors.size();
|
||||
result.vtx_to_color.push_back(cidx);
|
||||
color_to_slot[key] = cidx;
|
||||
result.final_colors.push_back(vtx);
|
||||
} else {
|
||||
result.vtx_to_color.push_back(existing->second);
|
||||
}
|
||||
}
|
||||
fmt::print("quantize_colors_dumb: {} -> {}\n", in.size(), result.final_colors.size());
|
||||
ASSERT(result.final_colors.size() < 8192);
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using Color = math::Vector<u8, 4>;
|
||||
|
||||
// An octree node.
|
||||
// Represents a color in the output if rgb_sum_count > 0.
|
||||
// Otherwise, just organizational.
|
||||
struct Node {
|
||||
u32 r_sum = 0;
|
||||
u32 g_sum = 0;
|
||||
u32 b_sum = 0;
|
||||
u32 rgb_sum_count = 0;
|
||||
u8 depth = 0xff; // 0 for root, 7 deepest
|
||||
|
||||
// children stuff
|
||||
u32 leaves_under_me = 0;
|
||||
std::vector<Node> children;
|
||||
Node* parent = nullptr;
|
||||
|
||||
u32 final_idx = UINT32_MAX;
|
||||
};
|
||||
|
||||
u8 child_index(Color color, u8 depth) {
|
||||
u8 r_bit = (color.x() >> (7 - depth)) & 1;
|
||||
u8 g_bit = (color.y() >> (7 - depth)) & 1;
|
||||
u8 b_bit = (color.z() >> (7 - depth)) & 1;
|
||||
return (r_bit) + (g_bit * 2) + (b_bit * 4);
|
||||
}
|
||||
|
||||
void insert(Node& root, Color color, u8 current_depth) {
|
||||
if (current_depth == 7) {
|
||||
root.r_sum += color.x();
|
||||
root.g_sum += color.y();
|
||||
root.b_sum += color.z();
|
||||
if (root.rgb_sum_count == 0) {
|
||||
for (auto* up = root.parent; up; up = up->parent) {
|
||||
up->leaves_under_me++;
|
||||
}
|
||||
}
|
||||
root.rgb_sum_count++;
|
||||
} else {
|
||||
if (root.children.empty()) {
|
||||
root.children.resize(8);
|
||||
}
|
||||
auto& next_node = root.children[child_index(color, current_depth)];
|
||||
if (next_node.depth == 0xff) {
|
||||
next_node.depth = current_depth + 1;
|
||||
next_node.parent = &root;
|
||||
}
|
||||
insert(next_node, color, current_depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void for_each_node(Node& root, T&& func) {
|
||||
func(root);
|
||||
for (auto& child : root.children) {
|
||||
for_each_node(child, func);
|
||||
}
|
||||
}
|
||||
|
||||
u32 count_leaves(Node& root) {
|
||||
u32 result = 0;
|
||||
for_each_node(root, [&](Node& n) {
|
||||
if (n.rgb_sum_count) {
|
||||
ASSERT(n.children.empty());
|
||||
result++;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void collapse1(Node& root) {
|
||||
ASSERT(!root.children.empty());
|
||||
u32 total_children_removed = 0;
|
||||
u32 total_rgb_sum_moved_up = 0;
|
||||
bool started_as_leaf = root.rgb_sum_count;
|
||||
for (auto& child : root.children) {
|
||||
if (child.depth != 0xff) {
|
||||
ASSERT(child.children.empty());
|
||||
ASSERT(child.rgb_sum_count);
|
||||
total_children_removed++;
|
||||
root.r_sum += child.r_sum;
|
||||
root.g_sum += child.g_sum;
|
||||
root.b_sum += child.b_sum;
|
||||
root.rgb_sum_count += child.rgb_sum_count;
|
||||
total_rgb_sum_moved_up += child.rgb_sum_count;
|
||||
}
|
||||
}
|
||||
ASSERT(total_children_removed == root.leaves_under_me);
|
||||
|
||||
if (!started_as_leaf && root.rgb_sum_count) {
|
||||
total_children_removed--;
|
||||
}
|
||||
root.children.clear();
|
||||
root.leaves_under_me = 0;
|
||||
if (total_children_removed) {
|
||||
for (auto* up = root.parent; up; up = up->parent) {
|
||||
up->leaves_under_me -= total_children_removed;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(count_leaves(root) == 1);
|
||||
}
|
||||
|
||||
void find_nodes_at_level(Node& n, std::vector<Node*>& out, u8 level) {
|
||||
if (n.depth == level) {
|
||||
out.push_back(&n);
|
||||
} else if (n.depth < level) {
|
||||
for (auto& child : n.children) {
|
||||
find_nodes_at_level(child, out, level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void collapse_at_level(Node& root, u8 level, u32 target_leaf_count) {
|
||||
std::vector<Node*> nodes_at_level;
|
||||
find_nodes_at_level(root, nodes_at_level, level);
|
||||
std::stable_sort(nodes_at_level.begin(), nodes_at_level.end(),
|
||||
[](Node* a, Node* b) { return a->leaves_under_me < b->leaves_under_me; });
|
||||
|
||||
size_t at_level_to_try = 0;
|
||||
while (root.leaves_under_me > target_leaf_count && at_level_to_try < nodes_at_level.size()) {
|
||||
collapse1(*nodes_at_level[at_level_to_try++]);
|
||||
}
|
||||
}
|
||||
|
||||
void collapse_as_needed(Node& root, u32 target_leaf_count) {
|
||||
u32 level_to_reduce = 6;
|
||||
while (root.leaves_under_me > target_leaf_count) {
|
||||
collapse_at_level(root, level_to_reduce--, target_leaf_count);
|
||||
}
|
||||
}
|
||||
|
||||
void assign_colors(Node& root, std::vector<Color>& palette_out) {
|
||||
u32 idx = 0;
|
||||
for_each_node(root, [&](Node& n) {
|
||||
if (n.rgb_sum_count) {
|
||||
n.final_idx = idx++;
|
||||
palette_out.emplace_back(n.r_sum / n.rgb_sum_count, n.g_sum / n.rgb_sum_count,
|
||||
n.b_sum / n.rgb_sum_count);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
u32 lookup_node_for_color(Node& root, Color c, u8 depth) {
|
||||
if (root.children.empty()) {
|
||||
return root.final_idx;
|
||||
} else {
|
||||
return lookup_node_for_color(root.children[child_index(c, depth)], c, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/*!
|
||||
* Quantize colors using an octree for clustering.
|
||||
*/
|
||||
QuantizedColors quantize_colors_octree(const std::vector<math::Vector<u8, 4>>& in,
|
||||
u32 target_count) {
|
||||
Node root;
|
||||
root.depth = 0;
|
||||
for (auto& color : in) {
|
||||
insert(root, color, 0);
|
||||
}
|
||||
|
||||
collapse_as_needed(root, target_count);
|
||||
|
||||
QuantizedColors out;
|
||||
assign_colors(root, out.final_colors);
|
||||
for (auto& color : in) {
|
||||
out.vtx_to_color.push_back(lookup_node_for_color(root, color, 0));
|
||||
}
|
||||
|
||||
float total_error[3] = {0, 0, 0};
|
||||
for (size_t i = 0; i < in.size(); i++) {
|
||||
// fmt::print(" {} -> {}\n", in[i].to_string_hex_byte(),
|
||||
// out.final_colors[out.vtx_to_color[i]].to_string_hex_byte());
|
||||
auto diff = in[i].cast<int>() - out.final_colors[out.vtx_to_color[i]].cast<int>();
|
||||
|
||||
for (int j = 0; j < 3; j++) {
|
||||
total_error[j] += std::abs(diff[j]);
|
||||
}
|
||||
}
|
||||
|
||||
lg::info("Octree quantize average error (as 8-bit ints): r: {}, g: {} b: {}",
|
||||
total_error[0] / in.size(), total_error[1] / in.size(), total_error[2] / in.size());
|
||||
lg::info("Final palette size: {}", out.final_colors.size());
|
||||
|
||||
return out;
|
||||
}
|
20
goalc/build_level/color_quantization.h
Normal file
20
goalc/build_level/color_quantization.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/math/Vector.h"
|
||||
|
||||
// TODO: come up with something for time of day colors.
|
||||
// time of day colors make the colors effectively 24-channel instead of just 3.
|
||||
// the octree approach doesn't work too well here (we'd need a 16777216-tree)
|
||||
// but a k-d tree seems like the right approach.
|
||||
|
||||
struct QuantizedColors {
|
||||
std::vector<math::Vector<u8, 4>> final_colors;
|
||||
std::vector<u32> vtx_to_color;
|
||||
};
|
||||
|
||||
QuantizedColors quantize_colors_dumb(const std::vector<math::Vector<u8, 4>>& in);
|
||||
|
||||
QuantizedColors quantize_colors_octree(const std::vector<math::Vector<u8, 4>>& in,
|
||||
u32 target_count);
|
688
goalc/build_level/gltf_mesh_extract.cpp
Normal file
688
goalc/build_level/gltf_mesh_extract.cpp
Normal file
@ -0,0 +1,688 @@
|
||||
/*!
|
||||
* Mesh extraction for GLTF meshes.
|
||||
*/
|
||||
|
||||
#include "gltf_mesh_extract.h"
|
||||
#include "goalc/build_level/color_quantization.h"
|
||||
#include "third-party/tiny_gltf/tiny_gltf.h"
|
||||
#include "common/log/log.h"
|
||||
#include "common/util/Timer.h"
|
||||
#include "common/math/geometry.h"
|
||||
|
||||
namespace gltf_mesh_extract {
|
||||
|
||||
namespace {
|
||||
|
||||
/*!
|
||||
* Convert a GLTF index buffer to std::vector<u32>
|
||||
*/
|
||||
template <typename T>
|
||||
std::vector<u32> index_list_to_u32(const u8* data, u32 num_verts, u32 offset, u32 stride) {
|
||||
std::vector<u32> result;
|
||||
result.reserve(num_verts);
|
||||
for (u32 i = 0; i < num_verts; i++) {
|
||||
T val;
|
||||
memcpy(&val, data, sizeof(T));
|
||||
result.push_back(offset + val);
|
||||
data += stride;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Convert a GLTF position buffer or similar to std::vector<Vec3f>
|
||||
*/
|
||||
std::vector<math::Vector3f> extract_vec3f(const u8* data, u32 count, u32 stride) {
|
||||
std::vector<math::Vector3f> result;
|
||||
result.reserve(count);
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
memcpy(&result.emplace_back(), data, sizeof(math::Vector3f));
|
||||
data += stride;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<math::Vector2f> extract_vec2f(const u8* data, u32 count, u32 stride) {
|
||||
std::vector<math::Vector2f> result;
|
||||
result.reserve(count);
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
memcpy(&result.emplace_back(), data, sizeof(math::Vector2f));
|
||||
data += stride;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Convert a GLTF color buffer to u8 colors.
|
||||
*/
|
||||
std::vector<math::Vector<u8, 4>> extract_color_from_vec4_u16(const u8* data,
|
||||
u32 count,
|
||||
u32 stride) {
|
||||
std::vector<math::Vector<u8, 4>> result;
|
||||
result.reserve(count);
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
math::Vector<u16, 4> temp;
|
||||
memcpy(&temp, data, sizeof(math::Vector<u16, 4>));
|
||||
data += stride;
|
||||
result.emplace_back(temp.x() >> 8, temp.y() >> 8, temp.z() >> 8, temp.w() >> 8);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Convert a GLTF index buffer
|
||||
*/
|
||||
std::vector<u32> gltf_index_buffer(const tinygltf::Model& model,
|
||||
int indices_idx,
|
||||
u32 index_offset) {
|
||||
const auto& indices_accessor = model.accessors[indices_idx];
|
||||
const auto& buffer_view = model.bufferViews[indices_accessor.bufferView];
|
||||
const auto& buffer = model.buffers[buffer_view.buffer];
|
||||
const auto data_ptr = buffer.data.data() + buffer_view.byteOffset + indices_accessor.byteOffset;
|
||||
const auto stride = indices_accessor.ByteStride(buffer_view);
|
||||
const auto count = indices_accessor.count;
|
||||
|
||||
switch (indices_accessor.componentType) {
|
||||
case TINYGLTF_COMPONENT_TYPE_BYTE:
|
||||
return index_list_to_u32<s8>(data_ptr, count, index_offset, stride);
|
||||
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
|
||||
return index_list_to_u32<u8>(data_ptr, count, index_offset, stride);
|
||||
case TINYGLTF_COMPONENT_TYPE_SHORT:
|
||||
return index_list_to_u32<s16>(data_ptr, count, index_offset, stride);
|
||||
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
|
||||
return index_list_to_u32<u16>(data_ptr, count, index_offset, stride);
|
||||
case TINYGLTF_COMPONENT_TYPE_INT:
|
||||
return index_list_to_u32<s32>(data_ptr, count, index_offset, stride);
|
||||
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
|
||||
return index_list_to_u32<u32>(data_ptr, count, index_offset, stride);
|
||||
default:
|
||||
ASSERT_MSG(false, "unsupported component type");
|
||||
}
|
||||
}
|
||||
|
||||
struct ExtractedVertices {
|
||||
std::vector<tfrag3::PreloadedVertex> vtx;
|
||||
std::vector<math::Vector<u8, 4>> vtx_colors;
|
||||
std::vector<math::Vector3f> normals;
|
||||
};
|
||||
|
||||
ExtractedVertices gltf_vertices(const tinygltf::Model& model,
|
||||
const std::map<std::string, int>& attributes,
|
||||
const math::Matrix4f& w_T_local,
|
||||
bool get_colors,
|
||||
bool get_normals,
|
||||
const std::string& debug_name) {
|
||||
std::vector<tfrag3::PreloadedVertex> result;
|
||||
std::vector<math::Vector<u8, 4>> vtx_colors;
|
||||
|
||||
{
|
||||
const auto& position_attrib = attributes.find("POSITION");
|
||||
ASSERT_MSG(position_attrib != attributes.end(), "Did not find position attribute.");
|
||||
|
||||
const auto attrib_accessor = model.accessors[position_attrib->second];
|
||||
const auto& buffer_view = model.bufferViews[attrib_accessor.bufferView];
|
||||
const auto& buffer = model.buffers[buffer_view.buffer];
|
||||
const auto data_ptr = buffer.data.data() + buffer_view.byteOffset + attrib_accessor.byteOffset;
|
||||
const auto byte_stride = attrib_accessor.ByteStride(buffer_view);
|
||||
const auto count = attrib_accessor.count;
|
||||
|
||||
ASSERT_MSG(attrib_accessor.type == TINYGLTF_TYPE_VEC3, "POSITION wasn't vec3");
|
||||
ASSERT_MSG(attrib_accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT,
|
||||
"POSITION wasn't float");
|
||||
// for (auto& attrib : attributes) {
|
||||
// fmt::print("attrib: {}\n", attrib.first);
|
||||
//}
|
||||
auto mesh_verts = extract_vec3f(data_ptr, count, byte_stride);
|
||||
result.reserve(mesh_verts.size());
|
||||
for (auto& vert : mesh_verts) {
|
||||
auto& new_vert = result.emplace_back();
|
||||
math::Vector4f v_in(vert.x(), vert.y(), vert.z(), 1);
|
||||
math::Vector4f v_w = w_T_local * v_in;
|
||||
new_vert.x = v_w.x() * 4096;
|
||||
new_vert.y = v_w.y() * 4096;
|
||||
new_vert.z = v_w.z() * 4096;
|
||||
}
|
||||
}
|
||||
|
||||
if (get_colors) {
|
||||
const auto& color_attrib = attributes.find("COLOR_0");
|
||||
if (color_attrib == attributes.end()) {
|
||||
lg::error("Mesh {} didn't have any colors, using white", debug_name);
|
||||
for (size_t i = 0; i < result.size(); i++) {
|
||||
vtx_colors.emplace_back(0x80, 0x80, 0x80, 0xff);
|
||||
}
|
||||
} else {
|
||||
const auto attrib_accessor = model.accessors[color_attrib->second];
|
||||
const auto& buffer_view = model.bufferViews[attrib_accessor.bufferView];
|
||||
const auto& buffer = model.buffers[buffer_view.buffer];
|
||||
const auto data_ptr =
|
||||
buffer.data.data() + buffer_view.byteOffset + attrib_accessor.byteOffset;
|
||||
const auto byte_stride = attrib_accessor.ByteStride(buffer_view);
|
||||
const auto count = attrib_accessor.count;
|
||||
|
||||
ASSERT_MSG(attrib_accessor.type == TINYGLTF_TYPE_VEC4, "COLOR_0 wasn't vec4");
|
||||
ASSERT_MSG(
|
||||
attrib_accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT,
|
||||
fmt::format("COLOR_0 wasn't float, got {} instead", attrib_accessor.componentType));
|
||||
auto colors = extract_color_from_vec4_u16(data_ptr, count, byte_stride);
|
||||
vtx_colors.insert(vtx_colors.end(), colors.begin(), colors.end());
|
||||
}
|
||||
|
||||
// ASSERT_MSG(color_attrib != attributes.end(), "Did not find color attribute.");
|
||||
}
|
||||
|
||||
bool got_texture = false;
|
||||
{
|
||||
const auto& texcoord_attrib = attributes.find("TEXCOORD_0");
|
||||
if (texcoord_attrib != attributes.end()) {
|
||||
const auto attrib_accessor = model.accessors[texcoord_attrib->second];
|
||||
const auto& buffer_view = model.bufferViews[attrib_accessor.bufferView];
|
||||
const auto& buffer = model.buffers[buffer_view.buffer];
|
||||
const auto data_ptr =
|
||||
buffer.data.data() + buffer_view.byteOffset + attrib_accessor.byteOffset;
|
||||
const auto byte_stride = attrib_accessor.ByteStride(buffer_view);
|
||||
const auto count = attrib_accessor.count;
|
||||
|
||||
ASSERT_MSG(attrib_accessor.type == TINYGLTF_TYPE_VEC2, "TEXCOORD wasn't vec2");
|
||||
ASSERT_MSG(attrib_accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT,
|
||||
"TEXCOORD wasn't float");
|
||||
auto mesh_verts = extract_vec2f(data_ptr, count, byte_stride);
|
||||
ASSERT(mesh_verts.size() == result.size());
|
||||
got_texture = true;
|
||||
for (size_t i = 0; i < mesh_verts.size(); i++) {
|
||||
result[i].s = mesh_verts[i].x();
|
||||
result[i].t = mesh_verts[i].y();
|
||||
}
|
||||
} else {
|
||||
if (!get_normals) {
|
||||
// don't warn if we're just getting collision
|
||||
lg::warn("No texcoord attribute for mesh: {}", debug_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<math::Vector3f> normals;
|
||||
if (get_normals) {
|
||||
const auto& normal_attrib = attributes.find("NORMAL");
|
||||
if (normal_attrib != attributes.end()) {
|
||||
const auto attrib_accessor = model.accessors[normal_attrib->second];
|
||||
const auto& buffer_view = model.bufferViews[attrib_accessor.bufferView];
|
||||
const auto& buffer = model.buffers[buffer_view.buffer];
|
||||
const auto data_ptr =
|
||||
buffer.data.data() + buffer_view.byteOffset + attrib_accessor.byteOffset;
|
||||
const auto byte_stride = attrib_accessor.ByteStride(buffer_view);
|
||||
const auto count = attrib_accessor.count;
|
||||
|
||||
ASSERT_MSG(attrib_accessor.type == TINYGLTF_TYPE_VEC3, "NORMAL wasn't vec3");
|
||||
ASSERT_MSG(attrib_accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT,
|
||||
"NORMAL wasn't float");
|
||||
normals = extract_vec3f(data_ptr, count, byte_stride);
|
||||
ASSERT(normals.size() == result.size());
|
||||
} else {
|
||||
lg::error("No NORMAL attribute for mesh: {}", debug_name);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& v : result) {
|
||||
v.color_index = 0;
|
||||
if (!got_texture) {
|
||||
v.s = 0;
|
||||
v.t = 0;
|
||||
}
|
||||
|
||||
v.q_unused = 0;
|
||||
v.pad[0] = 0;
|
||||
v.pad[1] = 0;
|
||||
v.pad[2] = 0;
|
||||
}
|
||||
// TODO: other properties
|
||||
return {result, vtx_colors, normals};
|
||||
}
|
||||
|
||||
DrawMode make_default_draw_mode() {
|
||||
DrawMode mode;
|
||||
mode.set_depth_write_enable(true);
|
||||
mode.set_depth_test(GsTest::ZTest::GEQUAL);
|
||||
mode.set_alpha_blend(DrawMode::AlphaBlend::DISABLED);
|
||||
mode.set_aref(0);
|
||||
mode.set_alpha_fail(GsTest::AlphaFail::KEEP);
|
||||
mode.set_clamp_s_enable(true);
|
||||
mode.set_clamp_t_enable(true);
|
||||
mode.disable_filt(); // for checkerboard...
|
||||
mode.enable_tcc(); // ?
|
||||
mode.disable_at();
|
||||
mode.enable_zt();
|
||||
mode.disable_ab();
|
||||
mode.disable_decal();
|
||||
mode.enable_fog();
|
||||
return mode;
|
||||
}
|
||||
|
||||
int texture_pool_debug_checker(TexturePool* pool) {
|
||||
const auto& existing = pool->textures_by_name.find("DEBUG_CHECKERBOARD");
|
||||
if (existing == pool->textures_by_name.end()) {
|
||||
size_t idx = pool->textures_by_idx.size();
|
||||
pool->textures_by_name["DEBUG_CHECKERBOARD"] = idx;
|
||||
auto& tex = pool->textures_by_idx.emplace_back();
|
||||
tex.w = 16;
|
||||
tex.h = 16;
|
||||
tex.debug_name = "DEBUG_CHECKERBOARD";
|
||||
tex.debug_tpage_name = "DEBUG";
|
||||
tex.load_to_pool = false;
|
||||
tex.combo_id = 0; // doesn't matter, not a pool tex
|
||||
tex.data.resize(16 * 16);
|
||||
u32 c0 = 0xa0303030;
|
||||
u32 c1 = 0xa0e0e0e0;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int j = 0; j < 16; j++) {
|
||||
tex.data[i * 16 + j] = (((i / 4) & 1) ^ ((j / 4) & 1)) ? c1 : c0;
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
} else {
|
||||
return existing->second;
|
||||
}
|
||||
}
|
||||
|
||||
int texture_pool_add_texture(TexturePool* pool, const tinygltf::Image& tex) {
|
||||
const auto& existing = pool->textures_by_name.find(tex.name);
|
||||
if (existing != pool->textures_by_name.end()) {
|
||||
lg::info("Reusing image: {}", tex.name);
|
||||
return existing->second;
|
||||
}
|
||||
|
||||
ASSERT(tex.bits == 8);
|
||||
ASSERT(tex.component == 4);
|
||||
ASSERT(tex.pixel_type == TINYGLTF_TEXTURE_TYPE_UNSIGNED_BYTE);
|
||||
|
||||
size_t idx = pool->textures_by_idx.size();
|
||||
pool->textures_by_name[tex.name] = idx;
|
||||
auto& tt = pool->textures_by_idx.emplace_back();
|
||||
tt.w = tex.width;
|
||||
tt.h = tex.height;
|
||||
tt.debug_name = tex.name;
|
||||
tt.debug_tpage_name = "custom-level";
|
||||
tt.load_to_pool = false;
|
||||
tt.combo_id = 0; // doesn't matter, not a pool tex
|
||||
tt.data.resize(tt.w * tt.h * 4);
|
||||
ASSERT(tex.image.size() >= tt.data.size());
|
||||
memcpy(tt.data.data(), tex.image.data(), tt.data.size());
|
||||
return idx;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
math::Matrix4f affine_translation(const math::Vector3f& translation) {
|
||||
math::Matrix4f result = math::Matrix4f::identity();
|
||||
result(0, 3) = translation[0];
|
||||
result(1, 3) = translation[1];
|
||||
result(2, 3) = translation[2];
|
||||
result(3, 3) = 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
math::Matrix4f affine_scale(const math::Vector3f& scale) {
|
||||
math::Matrix4f result = math::Matrix4f::zero();
|
||||
result(0, 0) = scale[0];
|
||||
result(1, 1) = scale[1];
|
||||
result(2, 2) = scale[2];
|
||||
result(3, 3) = 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
math::Matrix4f affine_rot_qxyzw(const math::Vector4f& quat) {
|
||||
math::Matrix4f result = math::Matrix4f::zero();
|
||||
result(3, 3) = 1;
|
||||
result(0, 0) = 1.0 - 2.0 * (quat.y() * quat.y() + quat.z() * quat.z());
|
||||
result(0, 1) = 2.0 * (quat.x() * quat.y() - quat.z() * quat.w());
|
||||
result(0, 2) = 2.0 * (quat.x() * quat.z() + quat.y() * quat.w());
|
||||
result(1, 0) = 2.0 * (quat.x() * quat.y() + quat.z() * quat.w());
|
||||
result(1, 1) = 1.0 - 2.0 * (quat.x() * quat.x() + quat.z() * quat.z());
|
||||
result(1, 2) = 2.0 * (quat.y() * quat.z() - quat.x() * quat.w());
|
||||
result(2, 0) = 2.0 * (quat.x() * quat.z() - quat.y() * quat.w());
|
||||
result(2, 1) = 2.0 * (quat.y() * quat.z() + quat.x() * quat.w());
|
||||
result(2, 2) = 1.0 - 2.0 * (quat.x() * quat.x() + quat.y() * quat.y());
|
||||
return result;
|
||||
}
|
||||
|
||||
math::Vector3f vector3f_from_gltf(const std::vector<double>& in) {
|
||||
ASSERT(in.size() == 3);
|
||||
return math::Vector3f{in[0], in[1], in[2]};
|
||||
}
|
||||
|
||||
math::Vector4f vector4f_from_gltf(const std::vector<double>& in) {
|
||||
ASSERT(in.size() == 4);
|
||||
return math::Vector4f{in[0], in[1], in[2], in[3]};
|
||||
}
|
||||
|
||||
math::Matrix4f matrix_from_node(const tinygltf::Node& node) {
|
||||
if (!node.matrix.empty()) {
|
||||
math::Matrix4f result;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
result.data()[i] = node.matrix[i];
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
// from trs
|
||||
math::Matrix4f t, r, s;
|
||||
if (!node.translation.empty()) {
|
||||
t = affine_translation(vector3f_from_gltf(node.translation));
|
||||
} else {
|
||||
t = math::Matrix4f::identity();
|
||||
}
|
||||
|
||||
if (!node.rotation.empty()) {
|
||||
r = affine_rot_qxyzw(vector4f_from_gltf(node.rotation));
|
||||
} else {
|
||||
r = math::Matrix4f::identity();
|
||||
}
|
||||
|
||||
if (!node.scale.empty()) {
|
||||
s = affine_scale(vector3f_from_gltf(node.scale));
|
||||
} else {
|
||||
s = math::Matrix4f::identity();
|
||||
}
|
||||
|
||||
return t * r * s;
|
||||
}
|
||||
}
|
||||
|
||||
struct NodeWithTransform {
|
||||
int node_idx;
|
||||
math::Matrix4f w_T_node;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Recursively walk the tree of nodes, flatten, and compute w_T_node for each.
|
||||
*/
|
||||
void node_find_helper(const tinygltf::Model& model,
|
||||
const math::Matrix4f& w_T_parent,
|
||||
int node_idx,
|
||||
std::vector<NodeWithTransform>* out) {
|
||||
const auto& node = model.nodes.at(node_idx);
|
||||
math::Matrix4f w_T_node = w_T_parent * matrix_from_node(node);
|
||||
out->push_back({node_idx, w_T_node});
|
||||
for (auto& child : node.children) {
|
||||
node_find_helper(model, w_T_node, child, out);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<NodeWithTransform> flatten_nodes_from_all_scenes(const tinygltf::Model& model) {
|
||||
std::vector<NodeWithTransform> out;
|
||||
for (auto& scene : model.scenes) {
|
||||
for (auto& nidx : scene.nodes) {
|
||||
math::Matrix4f identity = math::Matrix4f::identity();
|
||||
node_find_helper(model, identity, nidx, &out);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void dedup_vertices(const std::vector<tfrag3::PreloadedVertex>& vertices_in,
|
||||
std::vector<tfrag3::PreloadedVertex>& vertices_out,
|
||||
std::vector<u32>& old_to_new_out) {
|
||||
ASSERT(vertices_out.empty());
|
||||
ASSERT(old_to_new_out.empty());
|
||||
old_to_new_out.resize(vertices_in.size(), -1);
|
||||
|
||||
std::unordered_map<tfrag3::PreloadedVertex, u32, tfrag3::PreloadedVertex::hash> vtx_to_new;
|
||||
|
||||
for (size_t in_idx = 0; in_idx < vertices_in.size(); in_idx++) {
|
||||
auto& vtx = vertices_in[in_idx];
|
||||
const auto& lookup = vtx_to_new.find(vtx);
|
||||
if (lookup == vtx_to_new.end()) {
|
||||
// first time seeing this one
|
||||
size_t new_idx = vertices_out.size();
|
||||
vertices_out.push_back(vtx);
|
||||
old_to_new_out[in_idx] = new_idx;
|
||||
vtx_to_new[vtx] = new_idx;
|
||||
} else {
|
||||
old_to_new_out[in_idx] = lookup->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dedup_vertices(TfragOutput& data) {
|
||||
Timer timer;
|
||||
size_t original_size = data.vertices.size();
|
||||
std::vector<tfrag3::PreloadedVertex> new_verts;
|
||||
std::vector<u32> old_to_new;
|
||||
|
||||
dedup_vertices(data.vertices, new_verts, old_to_new);
|
||||
data.vertices = std::move(new_verts);
|
||||
|
||||
for (auto& draw : data.strip_draws) {
|
||||
ASSERT(draw.runs.empty()); // not supported yet
|
||||
for (auto& idx : draw.plain_indices) {
|
||||
idx = old_to_new.at(idx);
|
||||
}
|
||||
}
|
||||
|
||||
lg::info("Deduplication took {:.2f} ms, {} -> {} ({:.2f} %)", timer.getMs(), original_size,
|
||||
data.vertices.size(), 100.f * data.vertices.size() / original_size);
|
||||
}
|
||||
|
||||
DrawMode draw_mode_from_sampler(const tinygltf::Sampler& sampler) {
|
||||
DrawMode mode = make_default_draw_mode();
|
||||
if (sampler.magFilter == TINYGLTF_TEXTURE_FILTER_NEAREST) {
|
||||
ASSERT(sampler.minFilter == TINYGLTF_TEXTURE_FILTER_NEAREST);
|
||||
mode.set_filt_enable(false);
|
||||
} else {
|
||||
ASSERT(sampler.minFilter != TINYGLTF_TEXTURE_FILTER_NEAREST);
|
||||
mode.set_filt_enable(true);
|
||||
}
|
||||
|
||||
switch (sampler.wrapS) {
|
||||
case TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE:
|
||||
mode.set_clamp_s_enable(true);
|
||||
break;
|
||||
case TINYGLTF_TEXTURE_WRAP_REPEAT:
|
||||
mode.set_clamp_s_enable(false);
|
||||
break;
|
||||
default:
|
||||
ASSERT(false);
|
||||
}
|
||||
|
||||
switch (sampler.wrapT) {
|
||||
case TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE:
|
||||
mode.set_clamp_t_enable(true);
|
||||
break;
|
||||
case TINYGLTF_TEXTURE_WRAP_REPEAT:
|
||||
mode.set_clamp_t_enable(false);
|
||||
break;
|
||||
default:
|
||||
ASSERT(false);
|
||||
}
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
void extract(const Input& in,
|
||||
TfragOutput& out,
|
||||
const tinygltf::Model& model,
|
||||
const std::vector<NodeWithTransform>& all_nodes) {
|
||||
std::vector<math::Vector<u8, 4>> all_vtx_colors;
|
||||
ASSERT(out.vertices.empty());
|
||||
std::map<int, tfrag3::StripDraw> draw_by_material;
|
||||
int mesh_count = 0;
|
||||
int prim_count = 0;
|
||||
|
||||
for (const auto& n : all_nodes) {
|
||||
const auto& node = model.nodes[n.node_idx];
|
||||
if (node.mesh >= 0) {
|
||||
const auto& mesh = model.meshes[node.mesh];
|
||||
if (!mesh.extras.Has("tfrag")) {
|
||||
// fmt::print("skip tfrag: {}\n", mesh.name);
|
||||
// continue;
|
||||
}
|
||||
mesh_count++;
|
||||
for (const auto& prim : mesh.primitives) {
|
||||
prim_count++;
|
||||
// extract index buffer
|
||||
std::vector<u32> prim_indices = gltf_index_buffer(model, prim.indices, out.vertices.size());
|
||||
ASSERT_MSG(prim.mode == TINYGLTF_MODE_TRIANGLES, "Unsupported triangle mode");
|
||||
// extract vertices
|
||||
auto verts =
|
||||
gltf_vertices(model, prim.attributes, n.w_T_node, in.get_colors, false, mesh.name);
|
||||
out.vertices.insert(out.vertices.end(), verts.vtx.begin(), verts.vtx.end());
|
||||
if (in.get_colors) {
|
||||
all_vtx_colors.insert(all_vtx_colors.end(), verts.vtx_colors.begin(),
|
||||
verts.vtx_colors.end());
|
||||
}
|
||||
|
||||
// TODO: just putting it all in one material
|
||||
auto& draw = draw_by_material[prim.material];
|
||||
draw.mode = make_default_draw_mode(); // todo rm
|
||||
draw.tree_tex_id = texture_pool_debug_checker(in.tex_pool); // todo rm
|
||||
draw.num_triangles += prim_indices.size() / 3;
|
||||
if (draw.vis_groups.empty()) {
|
||||
auto& grp = draw.vis_groups.emplace_back();
|
||||
grp.num_inds += prim_indices.size();
|
||||
grp.num_tris += draw.num_triangles;
|
||||
grp.vis_idx_in_pc_bvh = UINT32_MAX;
|
||||
} else {
|
||||
auto& grp = draw.vis_groups.back();
|
||||
grp.num_inds += prim_indices.size();
|
||||
grp.num_tris += draw.num_triangles;
|
||||
grp.vis_idx_in_pc_bvh = UINT32_MAX;
|
||||
}
|
||||
|
||||
draw.plain_indices.insert(draw.plain_indices.end(), prim_indices.begin(),
|
||||
prim_indices.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [mat_idx, d_] : draw_by_material) {
|
||||
out.strip_draws.push_back(d_);
|
||||
auto& draw = out.strip_draws.back();
|
||||
draw.mode = make_default_draw_mode();
|
||||
|
||||
if (mat_idx == -1) {
|
||||
lg::warn("Draw had a material index of -1, using default texture.");
|
||||
draw.tree_tex_id = texture_pool_debug_checker(in.tex_pool);
|
||||
continue;
|
||||
}
|
||||
const auto& mat = model.materials[mat_idx];
|
||||
int tex_idx = mat.pbrMetallicRoughness.baseColorTexture.index;
|
||||
if (tex_idx == -1) {
|
||||
lg::warn("Material {} has no texture, using default texture.", mat.name);
|
||||
draw.tree_tex_id = texture_pool_debug_checker(in.tex_pool);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& tex = model.textures[tex_idx];
|
||||
ASSERT(tex.sampler >= 0);
|
||||
ASSERT(tex.source >= 0);
|
||||
draw.mode = draw_mode_from_sampler(model.samplers.at(tex.sampler));
|
||||
|
||||
const auto& img = model.images[tex.source];
|
||||
draw.tree_tex_id = texture_pool_add_texture(in.tex_pool, img);
|
||||
}
|
||||
lg::info("total of {} unique materials", out.strip_draws.size());
|
||||
|
||||
lg::info("Merged {} meshes and {} prims into {} vertices", mesh_count, prim_count,
|
||||
out.vertices.size());
|
||||
|
||||
if (in.get_colors) {
|
||||
Timer quantize_timer;
|
||||
auto quantized = quantize_colors_octree(all_vtx_colors, 1024);
|
||||
for (size_t i = 0; i < out.vertices.size(); i++) {
|
||||
out.vertices[i].color_index = quantized.vtx_to_color[i];
|
||||
}
|
||||
out.color_palette = std::move(quantized.final_colors);
|
||||
lg::info("Color palette generation took {:.2f} ms", quantize_timer.getMs());
|
||||
}
|
||||
|
||||
dedup_vertices(out);
|
||||
}
|
||||
|
||||
void extract(const Input& in,
|
||||
CollideOutput& out,
|
||||
const tinygltf::Model& model,
|
||||
const std::vector<NodeWithTransform>& all_nodes) {
|
||||
int mesh_count = 0;
|
||||
int prim_count = 0;
|
||||
int suspicious_faces = 0;
|
||||
|
||||
for (const auto& n : all_nodes) {
|
||||
const auto& node = model.nodes[n.node_idx];
|
||||
fmt::print("node: {} {}\n", node.name, node.mesh);
|
||||
if (node.mesh >= 0) {
|
||||
const auto& mesh = model.meshes[node.mesh];
|
||||
if (!mesh.extras.Has("collide")) {
|
||||
// fmt::print("skip collide: {}\n", mesh.name);
|
||||
// continue;
|
||||
}
|
||||
mesh_count++;
|
||||
for (const auto& prim : mesh.primitives) {
|
||||
prim_count++;
|
||||
// extract index buffer
|
||||
std::vector<u32> prim_indices = gltf_index_buffer(model, prim.indices, 0);
|
||||
ASSERT_MSG(prim.mode == TINYGLTF_MODE_TRIANGLES, "Unsupported triangle mode");
|
||||
// extract vertices
|
||||
auto verts = gltf_vertices(model, prim.attributes, n.w_T_node, false, true, mesh.name);
|
||||
|
||||
for (size_t iidx = 0; iidx < prim_indices.size(); iidx += 3) {
|
||||
CollideFace face;
|
||||
|
||||
// get the positions
|
||||
for (int j = 0; j < 3; j++) {
|
||||
auto& vtx = verts.vtx.at(prim_indices.at(iidx + j));
|
||||
face.v[j].x() = vtx.x;
|
||||
face.v[j].y() = vtx.y;
|
||||
face.v[j].z() = vtx.z;
|
||||
}
|
||||
|
||||
// now face normal
|
||||
math::Vector3f face_normal =
|
||||
(face.v[2] - face.v[0]).cross(face.v[1] - face.v[0]).normalized();
|
||||
|
||||
float dots[3];
|
||||
for (int j = 0; j < 3; j++) {
|
||||
dots[j] = face_normal.dot(verts.normals.at(prim_indices.at(iidx + j)).normalized());
|
||||
}
|
||||
|
||||
if (dots[0] > 1e-3 && dots[1] > 1e-3 && dots[2] > 1e-3) {
|
||||
suspicious_faces++;
|
||||
std::swap(face.v[2], face.v[1]);
|
||||
}
|
||||
|
||||
face.bsphere = math::bsphere_of_triangle(face.v);
|
||||
face.bsphere.w() += 1e-1;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
float output_dist = face.bsphere.w() - (face.bsphere.xyz() - face.v[j]).length();
|
||||
if (output_dist < 0) {
|
||||
fmt::print("{}\n", output_dist);
|
||||
fmt::print("BAD:\n{}\n{}\n{}\n", face.v[0].to_string_aligned(),
|
||||
face.v[1].to_string_aligned(), face.v[2].to_string_aligned());
|
||||
fmt::print("bsphere: {}\n", face.bsphere.to_string_aligned());
|
||||
}
|
||||
}
|
||||
|
||||
out.faces.push_back(face);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lg::info("{} out of {} faces were suspicious (a small number is ok)", suspicious_faces,
|
||||
out.faces.size());
|
||||
// lg::info("Collision extract{} {}", mesh_count, prim_count);
|
||||
}
|
||||
|
||||
void extract(const Input& in, Output& out) {
|
||||
lg::info("Reading gltf mesh: {}", in.filename);
|
||||
Timer read_timer;
|
||||
tinygltf::TinyGLTF loader;
|
||||
tinygltf::Model model;
|
||||
std::string err, warn;
|
||||
bool res = loader.LoadBinaryFromFile(&model, &err, &warn, in.filename);
|
||||
ASSERT_MSG(warn.empty(), warn.c_str());
|
||||
ASSERT_MSG(err.empty(), err.c_str());
|
||||
ASSERT_MSG(res, "Failed to load GLTF file!");
|
||||
auto all_nodes = flatten_nodes_from_all_scenes(model);
|
||||
extract(in, out.tfrag, model, all_nodes);
|
||||
extract(in, out.collide, model, all_nodes);
|
||||
lg::info("GLTF total took {:.2f} ms", read_timer.getMs());
|
||||
}
|
||||
} // namespace gltf_mesh_extract
|
34
goalc/build_level/gltf_mesh_extract.h
Normal file
34
goalc/build_level/gltf_mesh_extract.h
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "common/custom_data/Tfrag3Data.h"
|
||||
#include "goalc/build_level/TexturePool.h"
|
||||
#include "goalc/build_level/collide_common.h"
|
||||
|
||||
namespace gltf_mesh_extract {
|
||||
|
||||
struct Input {
|
||||
std::string filename;
|
||||
TexturePool* tex_pool = nullptr;
|
||||
bool get_colors = true;
|
||||
};
|
||||
|
||||
struct TfragOutput {
|
||||
std::vector<tfrag3::StripDraw> strip_draws;
|
||||
std::vector<tfrag3::PreloadedVertex> vertices;
|
||||
std::vector<math::Vector<u8, 4>> color_palette;
|
||||
};
|
||||
|
||||
struct CollideOutput {
|
||||
std::vector<CollideFace> faces;
|
||||
};
|
||||
|
||||
struct Output {
|
||||
TfragOutput tfrag;
|
||||
CollideOutput collide;
|
||||
};
|
||||
|
||||
void extract(const Input& in, Output& out);
|
||||
|
||||
} // namespace gltf_mesh_extract
|
@ -51,6 +51,17 @@ int DataObjectGenerator::add_word(u32 word) {
|
||||
return result;
|
||||
}
|
||||
|
||||
int DataObjectGenerator::add_word_float(float f) {
|
||||
auto result = int(m_words.size());
|
||||
m_words.push_back(0);
|
||||
memcpy(&m_words.back(), &f, sizeof(float));
|
||||
return result;
|
||||
}
|
||||
|
||||
void DataObjectGenerator::set_word(u32 word_idx, u32 val) {
|
||||
m_words.at(word_idx) = val;
|
||||
}
|
||||
|
||||
void DataObjectGenerator::link_word_to_word(int source, int target, int offset) {
|
||||
link_word_to_byte(source, target * 4 + offset);
|
||||
}
|
||||
@ -68,6 +79,10 @@ int DataObjectGenerator::add_ref_to_string_in_pool(const std::string& str) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void DataObjectGenerator::link_word_to_string_in_pool(const std::string& str, int word_idx) {
|
||||
m_string_pool[str].push_back(word_idx);
|
||||
}
|
||||
|
||||
int DataObjectGenerator::add_type_tag(const std::string& str) {
|
||||
auto result = int(m_words.size());
|
||||
m_words.push_back(0);
|
||||
@ -82,12 +97,20 @@ int DataObjectGenerator::add_symbol_link(const std::string& str) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void DataObjectGenerator::link_word_to_symbol(const std::string& str, int word_idx) {
|
||||
m_symbol_links[str].push_back(word_idx);
|
||||
}
|
||||
|
||||
void DataObjectGenerator::align(int alignment_words) {
|
||||
while (m_words.size() % alignment_words) {
|
||||
m_words.push_back(0);
|
||||
}
|
||||
}
|
||||
|
||||
void DataObjectGenerator::align_to_basic() {
|
||||
align(4);
|
||||
}
|
||||
|
||||
int DataObjectGenerator::words() const {
|
||||
return int(m_words.size());
|
||||
}
|
||||
|
@ -8,15 +8,22 @@
|
||||
class DataObjectGenerator {
|
||||
public:
|
||||
int add_word(u32 word);
|
||||
int add_word_float(float f);
|
||||
void set_word(u32 word_idx, u32 val);
|
||||
void link_word_to_word(int source, int target, int offset = 0);
|
||||
void link_word_to_byte(int source_word, int target_byte);
|
||||
int add_ref_to_string_in_pool(const std::string& str);
|
||||
void link_word_to_string_in_pool(const std::string& str, int word_idx);
|
||||
int add_type_tag(const std::string& str);
|
||||
int add_symbol_link(const std::string& str);
|
||||
void link_word_to_symbol(const std::string& str, int word_idx);
|
||||
std::vector<u8> generate_v2();
|
||||
std::vector<u8> generate_v4();
|
||||
void align(int alignment_words);
|
||||
void align_to_basic();
|
||||
int words() const;
|
||||
size_t current_offset_bytes() const { return m_words.size() * sizeof(u32); }
|
||||
u8* data() { return (u8*)m_words.data(); }
|
||||
|
||||
private:
|
||||
void add_strings();
|
||||
|
@ -66,6 +66,7 @@ MakeSystem::MakeSystem() {
|
||||
add_tool<GroupTool>();
|
||||
add_tool<TextTool>();
|
||||
add_tool<SubtitleTool>();
|
||||
add_tool<BuildLevelTool>();
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
#include "Tools.h"
|
||||
|
||||
#include <filesystem>
|
||||
@ -11,6 +9,7 @@
|
||||
#include "goalc/data_compiler/dir_tpages.h"
|
||||
#include "goalc/data_compiler/game_count.h"
|
||||
#include "goalc/data_compiler/game_text_common.h"
|
||||
#include "goalc/build_level/build_level.h"
|
||||
|
||||
CompilerTool::CompilerTool(Compiler* compiler) : Tool("goalc"), m_compiler(compiler) {}
|
||||
|
||||
@ -186,3 +185,20 @@ bool SubtitleTool::run(const ToolInput& task) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
BuildLevelTool::BuildLevelTool() : Tool("build-level") {}
|
||||
|
||||
bool BuildLevelTool::needs_run(const ToolInput& task) {
|
||||
if (task.input.size() != 1) {
|
||||
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
||||
}
|
||||
auto deps = get_build_level_deps(task.input.at(0));
|
||||
return Tool::needs_run({task.input, deps, task.output, task.arg});
|
||||
}
|
||||
|
||||
bool BuildLevelTool::run(const ToolInput& task) {
|
||||
if (task.input.size() != 1) {
|
||||
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
||||
}
|
||||
return run_build_level(task.input.at(0), task.output.at(0));
|
||||
}
|
||||
|
@ -62,3 +62,10 @@ class SubtitleTool : public Tool {
|
||||
bool run(const ToolInput& task) override;
|
||||
bool needs_run(const ToolInput& task) override;
|
||||
};
|
||||
|
||||
class BuildLevelTool : public Tool {
|
||||
public:
|
||||
BuildLevelTool();
|
||||
bool run(const ToolInput& task) override;
|
||||
bool needs_run(const ToolInput& task) override;
|
||||
};
|
||||
|
@ -1,5 +1,4 @@
|
||||
add_subdirectory(level_tools)
|
||||
add_subdirectory(build_level)
|
||||
|
||||
add_executable(dgo_unpacker
|
||||
dgo_unpacker.cpp)
|
||||
|
@ -1,4 +0,0 @@
|
||||
add_executable(build_level
|
||||
main.cpp)
|
||||
|
||||
target_link_libraries(build_level common compiler tiny_gltf)
|
@ -1,3 +0,0 @@
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user