refactor level building code, split into jak 1 and jak 2 versions

This commit is contained in:
Hat Kid
2023-10-16 17:29:53 +02:00
parent 05e2e56e12
commit b7a91dc29f
42 changed files with 1462 additions and 437 deletions
+19 -12
View File
@@ -5,18 +5,25 @@ 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/Entity.cpp
build_level/FileInfo.cpp
build_level/gltf_mesh_extract.cpp
build_level/LevelFile.cpp
build_level/ResLump.cpp
build_level/Tfrag.cpp
build_level/ambient.cpp
build_level/common/build_level.cpp
build_level/jak1/build_level.cpp
build_level/jak2/build_level.cpp
build_level/collide/jak1/collide_bvh.cpp
build_level/collide/jak1/collide_drawable.cpp
build_level/collide/jak1/collide_pack.cpp
build_level/common/color_quantization.cpp
build_level/common/Entity.cpp
build_level/jak1/Entity.cpp
build_level/jak2/Entity.cpp
build_level/common/FileInfo.cpp
build_level/jak1/FileInfo.cpp
build_level/jak2/FileInfo.cpp
build_level/common/gltf_mesh_extract.cpp
build_level/jak1/LevelFile.cpp
build_level/jak2/LevelFile.cpp
build_level/common/ResLump.cpp
build_level/common/Tfrag.cpp
build_level/jak1/ambient.cpp
compiler/Compiler.cpp
compiler/Env.cpp
compiler/Val.cpp
@@ -0,0 +1,315 @@
#pragma once
#include "common/common_types.h"
#include "common/math/Vector.h"
struct CollideVertex {
float x, y, z;
};
namespace jak1 {
struct PatSurface {
enum class Mode { GROUND = 0, WALL = 1, OBSTACLE = 2, MAX_MODE = 3 };
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,
MAX_MATERIAL = 23
};
enum class Event {
NONE = 0,
DEADLY = 1,
ENDLESSFALL = 2,
BURN = 3,
DEADLYUP = 4,
BURNUP = 5,
MELT = 6,
MAX_EVENT = 7,
};
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 << 14);
}
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 CollideFace {
math::Vector4f bsphere;
math::Vector3f v[3];
PatSurface pat;
};
} // namespace jak1
namespace jak2 {
struct PatSurface {
enum class Mode { GROUND = 0, WALL = 1, OBSTACLE = 2, HALFPIPE = 3, MAX_MODE = 4 };
enum class Material {
NONE = 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,
STONE = 23,
CRMETAL = 24,
CARPET = 25,
GRMETAL = 26,
SHMETAL = 27,
HDWOOD = 28,
MAX_MATERIAL = 29
};
enum class Event {
NONE = 0,
DEADLY = 1,
ENDLESSFALL = 2,
BURN = 3,
DEADLYUP = 4,
BURNUP = 5,
MELT = 6,
SLIDE = 7,
LIP = 8,
LIPRAMP = 9,
SHOCK = 10,
SHOCKUP = 11,
HIDE = 12,
RAIL = 13,
SLIPPERY = 14,
MAX_EVENT = 15,
};
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_nogrind(bool x) {
if (x) {
val |= (1 << 3);
} else {
val &= ~(1 << 3);
}
}
bool get_nogrind() const { return val & (1 << 3); }
void set_nojak(bool x) {
if (x) {
val |= (1 << 4);
} else {
val &= ~(1 << 4);
}
}
bool get_nojak() const { return val & (1 << 4); }
void set_noboard(bool x) {
if (x) {
val |= (1 << 5);
} else {
val &= ~(1 << 5);
}
}
bool get_noboard() const { return val & (1 << 5); }
void set_nopilot(bool x) {
if (x) {
val |= (1 << 6);
} else {
val &= ~(1 << 6);
}
}
bool get_nopilot() const { return val & (1 << 6); }
void set_mode(Mode mode) {
val &= ~(0b111 << 7);
val |= ((u32)mode << 7);
}
Mode get_mode() const { return (Mode)(0b111 & (val >> 7)); }
void set_material(Material mat) {
val &= ~(0b111111 << 10);
val |= ((u32)mat << 10);
}
Material get_material() const { return (Material)(0b111111 & (val >> 10)); }
void set_nolineofsight(bool x) {
if (x) {
val |= (1 << 16);
} else {
val &= ~(1 << 16);
}
}
bool get_nolineofsight() const { return val & (1 << 16); }
void set_event(Event ev) {
val &= ~(0b111111 << 18);
val |= ((u32)ev << 18);
}
Event get_event() const { return (Event)(0b111111 & (val >> 18)); }
void set_probe(bool x) {
if (x) {
val |= (1 << 24);
} else {
val &= ~(1 << 24);
}
}
bool get_probe() const { return val & (1 << 24); }
void set_nomech(bool x) {
if (x) {
val |= (1 << 25);
} else {
val &= ~(1 << 25);
}
}
bool get_nomech() const { return val & (1 << 25); }
void set_noproj(bool x) {
if (x) {
val |= (1 << 26);
} else {
val &= ~(1 << 26);
}
}
bool get_noproj() const { return val & (1 << 26); }
void set_noendlessfall(bool x) {
if (x) {
val |= (1 << 27);
} else {
val &= ~(1 << 27);
}
}
bool get_noendlessfall() const { return val & (1 << 27); }
void set_noprobe(bool x) {
if (x) {
val |= (1 << 28);
} else {
val &= ~(1 << 28);
}
}
bool get_noprobe() const { return val & (1 << 28); }
bool operator==(const PatSurface& other) const { return val == other.val; }
u32 val = 0;
};
struct CollideFace {
math::Vector4f bsphere;
math::Vector3f v[3];
PatSurface pat;
};
} // namespace jak2
@@ -27,7 +27,7 @@ constexpr int MAX_UNIQUE_VERTS_IN_FRAG = 128;
*/
struct CNode {
std::vector<CNode> child_nodes;
std::vector<CollideFace> faces;
std::vector<jak1::CollideFace> faces;
math::Vector4f bsphere;
};
@@ -101,13 +101,14 @@ void compute_my_bsphere_ritters(CNode& node) {
* Split faces in two along a coordinate plane.
* Will clear the input faces
*/
void split_along_dim(std::vector<CollideFace>& faces,
void split_along_dim(std::vector<jak1::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];
});
std::vector<jak1::CollideFace>* out0,
std::vector<jak1::CollideFace>* out1) {
std::sort(faces.begin(), faces.end(),
[=](const jak1::CollideFace& a, const jak1::CollideFace& b) {
return a.bsphere[dim] < b.bsphere[dim];
});
lg::print("splitting with size: {}\n", faces.size());
size_t split_idx = faces.size() / 2;
out0->insert(out0->end(), faces.begin(), faces.begin() + split_idx);
@@ -278,7 +279,7 @@ void debug_stats(const CollideTree& tree) {
} // namespace
CollideTree construct_collide_bvh(const std::vector<CollideFace>& tris) {
CollideTree construct_collide_bvh(const std::vector<jak1::CollideFace>& tris) {
// part 1: build the tree
Timer bvh_timer;
lg::info("Building collide bvh from {} triangles", tris.size());
@@ -2,7 +2,7 @@
#include <vector>
#include "goalc/build_level/collide_common.h"
#include "goalc/build_level/collide/common/collide_common.h"
// requirements:
// max depth of 3 (maybe?)
@@ -19,7 +19,7 @@ struct DrawNode {
struct CollideFrag {
math::Vector4f bsphere;
std::vector<CollideFace> faces;
std::vector<jak1::CollideFace> faces;
};
struct DrawableInlineArrayNode {
@@ -36,5 +36,5 @@ struct CollideTree {
DrawableInlineArrayCollideFrag frags;
};
CollideTree construct_collide_bvh(const std::vector<CollideFace>& tris);
CollideTree construct_collide_bvh(const std::vector<jak1::CollideFace>& tris);
} // namespace collide
@@ -68,7 +68,7 @@
:size-assert #x44
*/
size_t generate_pat_array(DataObjectGenerator& gen, const std::vector<PatSurface>& pats) {
size_t generate_pat_array(DataObjectGenerator& gen, const std::vector<jak1::PatSurface>& pats) {
gen.align_to_basic();
size_t result = gen.current_offset_bytes();
for (auto& pat : pats) {
@@ -1,7 +1,7 @@
#pragma once
#include "goalc/build_level/collide_bvh.h"
#include "goalc/build_level/collide_pack.h"
#include "collide_bvh.h"
#include "collide_pack.h"
class DataObjectGenerator;
@@ -91,7 +91,7 @@ PackedU16Verts pack_verts_to_u16(const std::vector<math::Vector3f>& input) {
}
struct PatSurfaceHash {
size_t operator()(const PatSurface& in) const { return std::hash<u32>()(in.val); }
size_t operator()(const jak1::PatSurface& in) const { return std::hash<u32>()(in.val); }
};
/*!
@@ -99,10 +99,10 @@ struct PatSurfaceHash {
* There's a pat "palette" with up 255 unique pats.
*/
struct PatMap {
std::unordered_map<PatSurface, u32, PatSurfaceHash> map;
std::vector<PatSurface> pats;
std::unordered_map<jak1::PatSurface, u32, PatSurfaceHash> map;
std::vector<jak1::PatSurface> pats;
u32 add_pat(PatSurface pat) {
u32 add_pat(jak1::PatSurface pat) {
const auto& lookup = map.find(pat);
if (lookup == map.end()) {
u32 new_idx = pats.size();
@@ -1,6 +1,6 @@
#pragma once
#include "goalc/build_level/collide_bvh.h"
#include "collide_bvh.h"
struct CollideFragMeshData {
math::Vector4f bsphere; // not part of the collide frag, but is part of the drawable wrapping it
@@ -15,7 +15,7 @@ struct CollideFragMeshData {
struct CollideFragMeshDataArray {
std::vector<CollideFragMeshData> packed_frag_data;
std::vector<PatSurface> pats;
std::vector<jak1::PatSurface> pats;
};
CollideFragMeshDataArray pack_collide_frags(const std::vector<collide::CollideFrag>& frag_data);
-112
View File
@@ -1,112 +0,0 @@
#pragma once
#include "common/common_types.h"
#include "common/math/Vector.h"
struct PatSurface {
enum class Mode { GROUND = 0, WALL = 1, OBSTACLE = 2, MAX_MODE = 3 };
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,
MAX_MATERIAL = 23
};
enum class Event {
NONE = 0,
DEADLY = 1,
ENDLESSFALL = 2,
BURN = 3,
DEADLYUP = 4,
BURNUP = 5,
MELT = 6,
MAX_EVENT = 7,
};
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 << 14);
}
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;
};
+96
View File
@@ -0,0 +1,96 @@
#include "Entity.h"
math::Vector4f vectorm3_from_json(const nlohmann::json& json) {
ASSERT(json.size() == 3);
math::Vector4f result;
for (int i = 0; i < 3; i++) {
result[i] = json[i].get<float>() * METER_LENGTH;
}
result[3] = 1.f;
return result;
}
math::Vector4f vectorm4_from_json(const nlohmann::json& json) {
ASSERT(json.size() == 4);
math::Vector4f result;
for (int i = 0; i < 4; i++) {
result[i] = json[i].get<float>() * METER_LENGTH;
}
return result;
}
math::Vector4f movie_pos_from_json(const nlohmann::json& json) {
ASSERT(json.size() == 4);
math::Vector4f result;
for (int i = 0; i < 3; i++) {
result[i] = json[i].get<float>() * METER_LENGTH;
}
result[3] = json[3].get<float>() * DEGREES_LENGTH;
return result;
}
math::Vector4f vector_from_json(const nlohmann::json& json) {
ASSERT(json.size() == 4);
math::Vector4f result;
for (int i = 0; i < 4; i++) {
result[i] = json[i].get<float>();
}
return result;
}
std::unique_ptr<Res> res_from_json_array(const std::string& name,
const nlohmann::json& json_array) {
ASSERT(json_array.size() > 0);
std::string array_type = json_array[0].get<std::string>();
if (array_type == "int32") {
std::vector<s32> data;
for (size_t i = 1; i < json_array.size(); i++) {
data.push_back(json_array[i].get<int>());
}
return std::make_unique<ResInt32>(name, data, -1000000000.0000);
} else if (array_type == "uint32") {
std::vector<u32> data;
for (size_t i = 1; i < json_array.size(); i++) {
data.push_back(json_array[i].get<u32>());
}
return std::make_unique<ResUint32>(name, data, -1000000000.0000);
} else if (array_type == "vector") {
std::vector<math::Vector4f> data;
for (size_t i = 1; i < json_array.size(); i++) {
data.push_back(vector_from_json(json_array[i]));
}
return std::make_unique<ResVector>(name, data, -1000000000.0000);
} else if (array_type == "vector4m") {
std::vector<math::Vector4f> data;
for (size_t i = 1; i < json_array.size(); i++) {
data.push_back(vectorm4_from_json(json_array[i]));
}
return std::make_unique<ResVector>(name, data, -1000000000.0000);
} else if (array_type == "movie-pos") {
std::vector<math::Vector4f> data;
for (size_t i = 1; i < json_array.size(); i++) {
data.push_back(movie_pos_from_json(json_array[i]));
}
return std::make_unique<ResVector>(name, data, -1000000000.0000);
} else if (array_type == "float") {
std::vector<float> data;
for (size_t i = 1; i < json_array.size(); i++) {
data.push_back(json_array[i].get<float>());
}
return std::make_unique<ResFloat>(name, data, -1000000000.0000);
} else if (array_type == "meters") {
std::vector<float> data;
for (size_t i = 1; i < json_array.size(); i++) {
data.push_back(json_array[i].get<float>() * METER_LENGTH);
}
return std::make_unique<ResFloat>(name, data, -1000000000.0000);
} else if (array_type == "degrees") {
std::vector<float> data;
for (size_t i = 1; i < json_array.size(); i++) {
data.push_back(json_array[i].get<float>() * DEGREES_LENGTH);
}
return std::make_unique<ResFloat>(name, data, -1000000000.0000);
} else {
ASSERT_MSG(false, fmt::format("unsupported array type: {}\n", array_type));
}
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include "common/goal_constants.h"
#include "common/util/Assert.h"
#include "goalc/build_level/common/ResLump.h"
#include "goalc/data_compiler/DataObjectGenerator.h"
#include "third-party/json.hpp"
math::Vector4f vectorm3_from_json(const nlohmann::json& json);
math::Vector4f vectorm4_from_json(const nlohmann::json& json);
math::Vector4f vector_from_json(const nlohmann::json& json);
std::unique_ptr<Res> res_from_json_array(const std::string& name, const nlohmann::json& json_array);
@@ -18,15 +18,3 @@ size_t FileInfo::add_to_object_file(DataObjectGenerator& gen) const {
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;
}
@@ -23,6 +23,4 @@ struct FileInfo {
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);
};
@@ -117,6 +117,28 @@ int ResUint8::get_alignment() const {
return 16;
}
ResUint32::ResUint32(const std::string& name, const std::vector<u32>& values, float key_frame)
: Res(name, key_frame), m_values(values) {}
TagInfo ResUint32::get_tag_info() const {
TagInfo result;
result.elt_type = "uint32";
result.elt_count = m_values.size();
result.inlined = true;
result.data_size = m_values.size() * sizeof(u32);
return result;
}
void ResUint32::write_data(DataObjectGenerator& gen) const {
for (auto& val : m_values) {
gen.add_word(val);
}
}
int ResUint32::get_alignment() const {
return 16;
}
ResVector::ResVector(const std::string& name,
const std::vector<math::Vector4f>& values,
float key_frame)
@@ -77,6 +77,17 @@ class ResUint8 : public Res {
std::vector<u8> m_values;
};
class ResUint32 : public Res {
public:
ResUint32(const std::string& name, const std::vector<u32>& values, float key_frame);
TagInfo get_tag_info() const override;
void write_data(DataObjectGenerator& gen) const override;
int get_alignment() const override;
private:
std::vector<u32> m_values;
};
class ResVector : public Res {
public:
ResVector(const std::string& name, const std::vector<math::Vector4f>& values, float key_frame);
@@ -2,9 +2,10 @@
#include <iostream>
#include "gltf_mesh_extract.h"
#include "common/custom_data/pack_helpers.h"
#include "goalc/build_level/gltf_mesh_extract.h"
#include "goalc/data_compiler/DataObjectGenerator.h"
void tfrag_from_gltf(const gltf_mesh_extract::TfragOutput& mesh_extract_out,
@@ -2,10 +2,11 @@
#include <string>
#include "gltf_mesh_extract.h"
#include "common/custom_data/Tfrag3Data.h"
#include "goalc/build_level/TexturePool.h"
#include "goalc/build_level/gltf_mesh_extract.h"
#include "goalc/build_level/common/TexturePool.h"
class DataObjectGenerator;
+43
View File
@@ -0,0 +1,43 @@
#include "build_level.h"
void save_pc_data(const std::string& nickname,
tfrag3::Level& data,
const fs::path& fr3_output_dir) {
Serializer ser;
data.serialize(ser);
auto compressed =
compression::compress_zstd(ser.get_save_result().first, ser.get_save_result().second);
lg::print("stats for {}\n", data.level_name);
print_memory_usage(data, ser.get_save_result().second);
lg::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(fr3_output_dir / fmt::format("{}.fr3", str_util::to_upper(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>()};
}
// Find all art groups the custom level needs in a list of object files,
// skipping any that we already found in other dgos before
std::vector<decompiler::ObjectFileRecord> find_art_groups(
std::vector<std::string>& processed_ags,
const std::vector<std::string>& custom_level_ag,
const std::vector<decompiler::ObjectFileRecord>& dgo_files) {
std::vector<decompiler::ObjectFileRecord> art_groups;
for (const auto& file : dgo_files) {
// skip any art groups we already added from other dgos
if (std::find(processed_ags.begin(), processed_ags.end(), file.name) != processed_ags.end()) {
continue;
}
if (std::find(custom_level_ag.begin(), custom_level_ag.end(), file.name) !=
custom_level_ag.end()) {
art_groups.push_back(file);
processed_ags.push_back(file.name);
}
}
return art_groups;
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <string>
#include <vector>
#include "common/log/log.h"
#include "common/util/compress.h"
#include "common/util/json_util.h"
#include "common/util/string_util.h"
#include "decompiler/level_extractor/extract_level.h"
void save_pc_data(const std::string& nickname, tfrag3::Level& data, const fs::path& fr3_output_dir);
std::vector<std::string> get_build_level_deps(const std::string& input_file);
std::vector<decompiler::ObjectFileRecord> find_art_groups(
std::vector<std::string>& processed_ags,
const std::vector<std::string>& custom_level_ag,
const std::vector<decompiler::ObjectFileRecord>& dgo_files);
@@ -6,12 +6,12 @@
#include <optional>
#include "color_quantization.h"
#include "common/log/log.h"
#include "common/math/geometry.h"
#include "common/util/Timer.h"
#include "goalc/build_level/color_quantization.h"
#include "third-party/tiny_gltf/tiny_gltf.h"
namespace gltf_mesh_extract {
@@ -609,7 +609,7 @@ void extract(const Input& in,
dedup_vertices(out);
}
std::optional<std::vector<CollideFace>> subdivide_face_if_needed(CollideFace face_in) {
std::optional<std::vector<jak1::CollideFace>> subdivide_face_if_needed(jak1::CollideFace face_in) {
math::Vector3f v_min = face_in.v[0];
v_min.min_in_place(face_in.v[1]);
v_min.min_in_place(face_in.v[2]);
@@ -629,7 +629,7 @@ std::optional<std::vector<CollideFace>> subdivide_face_if_needed(CollideFace fac
math::Vector3f v0 = face_in.v[0];
math::Vector3f v1 = face_in.v[1];
math::Vector3f v2 = face_in.v[2];
CollideFace fs[4];
jak1::CollideFace fs[4];
fs[0].v[0] = v0;
fs[0].v[1] = a;
fs[0].v[2] = c;
@@ -653,7 +653,7 @@ std::optional<std::vector<CollideFace>> subdivide_face_if_needed(CollideFace fac
fs[3].bsphere = math::bsphere_of_triangle(fs[3].v);
fs[3].pat = face_in.pat;
std::vector<CollideFace> result;
std::vector<jak1::CollideFace> result;
for (auto f : fs) {
auto next_faces = subdivide_face_if_needed(f);
if (next_faces) {
@@ -671,7 +671,7 @@ std::optional<std::vector<CollideFace>> subdivide_face_if_needed(CollideFace fac
struct PatResult {
bool set = false;
bool ignore = false;
PatSurface pat;
jak1::PatSurface pat;
};
PatResult custom_props_to_pat(const tinygltf::Value& val, const std::string& /*debug_name*/) {
@@ -691,12 +691,12 @@ PatResult custom_props_to_pat(const tinygltf::Value& val, const std::string& /*d
result.ignore = false;
int mat = val.Get("collide_material").Get<int>();
ASSERT(mat < (int)PatSurface::Material::MAX_MATERIAL);
result.pat.set_material(PatSurface::Material(mat));
ASSERT(mat < (int)jak1::PatSurface::Material::MAX_MATERIAL);
result.pat.set_material(jak1::PatSurface::Material(mat));
int evt = val.Get("collide_event").Get<int>();
ASSERT(evt < (int)PatSurface::Event::MAX_EVENT);
result.pat.set_event(PatSurface::Event(evt));
ASSERT(evt < (int)jak1::PatSurface::Event::MAX_EVENT);
result.pat.set_event(jak1::PatSurface::Event(evt));
if (val.Get("nolineofsight").Get<int>()) {
result.pat.set_nolineofsight(true);
@@ -708,8 +708,8 @@ PatResult custom_props_to_pat(const tinygltf::Value& val, const std::string& /*d
if (val.Has("collide_mode")) {
int mode = val.Get("collide_mode").Get<int>();
ASSERT(mode < (int)PatSurface::Mode::MAX_MODE);
result.pat.set_mode(PatSurface::Mode(mode));
ASSERT(mode < (int)jak1::PatSurface::Mode::MAX_MODE);
result.pat.set_mode(jak1::PatSurface::Mode(mode));
}
if (val.Get("nocamera").Get<int>()) {
@@ -760,7 +760,7 @@ void extract(const Input& in,
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;
jak1::CollideFace face;
// get the positions
for (int j = 0; j < 3; j++) {
@@ -804,7 +804,7 @@ void extract(const Input& in,
}
}
std::vector<CollideFace> fixed_faces;
std::vector<jak1::CollideFace> fixed_faces;
int fix_count = 0;
for (auto& face : out.faces) {
auto try_fix = subdivide_face_if_needed(face);
@@ -835,7 +835,7 @@ void extract(const Input& in,
math::Vector3f face_normal =
(face.v[1] - face.v[0]).cross(face.v[2] - face.v[0]).normalized();
if (face_normal[1] < wall_cos) {
face.pat.set_mode(PatSurface::Mode::WALL);
face.pat.set_mode(jak1::PatSurface::Mode::WALL);
wall_count++;
}
}
@@ -4,8 +4,8 @@
#include "common/custom_data/Tfrag3Data.h"
#include "goalc/build_level/TexturePool.h"
#include "goalc/build_level/collide_common.h"
#include "goalc/build_level/collide/common/collide_common.h"
#include "goalc/build_level/common/TexturePool.h"
namespace gltf_mesh_extract {
@@ -25,7 +25,7 @@ struct TfragOutput {
};
struct CollideOutput {
std::vector<CollideFace> faces;
std::vector<jak1::CollideFace> faces;
};
struct Output {
@@ -1,13 +1,6 @@
#include "Entity.h"
#include "common/goal_constants.h"
#include "common/util/Assert.h"
#include "goalc/build_level/ResLump.h"
#include "goalc/data_compiler/DataObjectGenerator.h"
#include "third-party/json.hpp"
namespace jak1 {
size_t EntityActor::generate(DataObjectGenerator& gen) const {
size_t result = res_lump.generate_header(gen, "entity-actor");
for (int i = 0; i < 4; i++) {
@@ -30,18 +23,6 @@ size_t EntityActor::generate(DataObjectGenerator& gen) const {
return result;
}
size_t EntityAmbient::generate(DataObjectGenerator& gen) const {
size_t result = res_lump.generate_header(gen, "entity-ambient");
for (size_t i = 0; i < 4; i++) {
gen.add_word_float(trans[i]);
}
ASSERT(vis_id < UINT16_MAX);
gen.add_word(aid);
gen.add_word(vis_id);
res_lump.generate_tag_list_and_data(gen, result);
return result;
}
size_t generate_drawable_actor(DataObjectGenerator& gen,
const EntityActor& actor,
size_t actor_loc) {
@@ -58,21 +39,6 @@ size_t generate_drawable_actor(DataObjectGenerator& gen,
return result;
}
size_t generate_drawable_ambient(DataObjectGenerator& gen,
const EntityAmbient& ambient,
size_t ambient_loc) {
gen.align_to_basic();
gen.add_type_tag("drawable-ambient"); // 0
size_t result = gen.current_offset_bytes();
gen.add_word(ambient.vis_id); // 4
gen.link_word_to_byte(gen.add_word(0), ambient_loc); // 8
gen.add_word(0); // 12
for (int i = 0; i < 4; i++) {
gen.add_word_float(ambient.bsphere[i]); // 16, 20, 24, 28
}
return result;
}
size_t generate_inline_array_actors(DataObjectGenerator& gen,
const std::vector<EntityActor>& actors) {
std::vector<size_t> actor_locs;
@@ -101,130 +67,6 @@ size_t generate_inline_array_actors(DataObjectGenerator& gen,
return result;
}
size_t generate_inline_array_ambients(DataObjectGenerator& gen,
const std::vector<EntityAmbient>& ambients) {
std::vector<size_t> ambient_locs;
for (auto& ambient : ambients) {
ambient_locs.push_back(ambient.generate(gen));
}
gen.align_to_basic();
gen.add_type_tag("drawable-inline-array-ambient"); // 0
size_t result = gen.current_offset_bytes();
ASSERT(ambients.size() < UINT16_MAX);
gen.add_word(ambients.size() << 16); // 4
gen.add_word(0);
gen.add_word(0);
gen.add_word(0);
gen.add_word(0);
gen.add_word(0);
gen.add_word(0);
ASSERT((gen.current_offset_bytes() % 16) == 0);
for (size_t i = 0; i < ambients.size(); i++) {
generate_drawable_ambient(gen, ambients[i], ambient_locs[i]);
}
return result;
}
namespace {
math::Vector4f vectorm3_from_json(const nlohmann::json& json) {
ASSERT(json.size() == 3);
math::Vector4f result;
for (int i = 0; i < 3; i++) {
result[i] = json[i].get<float>() * METER_LENGTH;
}
result[3] = 1.f;
return result;
}
math::Vector4f vectorm4_from_json(const nlohmann::json& json) {
ASSERT(json.size() == 4);
math::Vector4f result;
for (int i = 0; i < 4; i++) {
result[i] = json[i].get<float>() * METER_LENGTH;
}
return result;
}
math::Vector4f vector_from_json(const nlohmann::json& json) {
ASSERT(json.size() == 4);
math::Vector4f result;
for (int i = 0; i < 4; i++) {
result[i] = json[i].get<float>();
}
return result;
}
std::unique_ptr<Res> res_from_json_array(const std::string& name,
const nlohmann::json& json_array) {
ASSERT(json_array.size() > 0);
std::string array_type = json_array[0].get<std::string>();
if (array_type == "int32") {
std::vector<s32> data;
for (size_t i = 1; i < json_array.size(); i++) {
data.push_back(json_array[i].get<int>());
}
return std::make_unique<ResInt32>(name, data, -1000000000.0000);
} else if (array_type == "vector") {
std::vector<math::Vector4f> data;
for (size_t i = 1; i < json_array.size(); i++) {
data.push_back(vector_from_json(json_array[i]));
}
return std::make_unique<ResVector>(name, data, -1000000000.0000);
} else if (array_type == "vector4m") {
std::vector<math::Vector4f> data;
for (size_t i = 1; i < json_array.size(); i++) {
data.push_back(vectorm4_from_json(json_array[i]));
}
return std::make_unique<ResVector>(name, data, -1000000000.0000);
} else if (array_type == "float") {
std::vector<float> data;
for (size_t i = 1; i < json_array.size(); i++) {
data.push_back(json_array[i].get<float>());
}
return std::make_unique<ResFloat>(name, data, -1000000000.0000);
} else if (array_type == "meters") {
std::vector<float> data;
for (size_t i = 1; i < json_array.size(); i++) {
data.push_back(json_array[i].get<float>() * METER_LENGTH);
}
return std::make_unique<ResFloat>(name, data, -1000000000.0000);
} else {
ASSERT_MSG(false, fmt::format("unsupported array type: {}\n", array_type));
}
}
} // namespace
void add_ambients_from_json(const nlohmann::json& json,
std::vector<EntityAmbient>& ambient_list,
u32 base_aid) {
for (const auto& ambient_json : json) {
auto& ambient = ambient_list.emplace_back();
ambient.aid = ambient_json.value("aid", base_aid + ambient_list.size());
ambient.vis_id = ambient_json.value("vis_id", 0);
ambient.trans = vectorm4_from_json(ambient_json.at("trans"));
ambient.bsphere = vectorm4_from_json(ambient_json.at("bsphere"));
if (ambient_json.find("lump") != ambient_json.end()) {
for (auto [key, value] : ambient_json.at("lump").items()) {
if (value.is_string()) {
std::string value_string = value.get<std::string>();
if (value_string.size() > 0 && value_string[0] == '\'') {
ambient.res_lump.add_res(
std::make_unique<ResSymbol>(key, value_string.substr(1), -1000000000.0000));
} else {
ambient.res_lump.add_res(
std::make_unique<ResString>(key, value_string, -1000000000.0000));
}
continue;
}
if (value.is_array()) {
ambient.res_lump.add_res(res_from_json_array(key, value));
}
}
}
ambient.res_lump.sort_res();
}
}
void add_actors_from_json(const nlohmann::json& json,
std::vector<EntityActor>& actor_list,
u32 base_aid) {
@@ -262,4 +104,87 @@ void add_actors_from_json(const nlohmann::json& json,
}
actor.res_lump.sort_res();
}
}
}
size_t EntityAmbient::generate(DataObjectGenerator& gen) const {
size_t result = res_lump.generate_header(gen, "entity-ambient");
for (size_t i = 0; i < 4; i++) {
gen.add_word_float(trans[i]);
}
ASSERT(vis_id < UINT16_MAX);
gen.add_word(aid);
gen.add_word(vis_id);
res_lump.generate_tag_list_and_data(gen, result);
return result;
}
size_t generate_drawable_ambient(DataObjectGenerator& gen,
const EntityAmbient& ambient,
size_t ambient_loc) {
gen.align_to_basic();
gen.add_type_tag("drawable-ambient"); // 0
size_t result = gen.current_offset_bytes();
gen.add_word(ambient.vis_id); // 4
gen.link_word_to_byte(gen.add_word(0), ambient_loc); // 8
gen.add_word(0); // 12
for (int i = 0; i < 4; i++) {
gen.add_word_float(ambient.bsphere[i]); // 16, 20, 24, 28
}
return result;
}
size_t generate_inline_array_ambients(DataObjectGenerator& gen,
const std::vector<EntityAmbient>& ambients) {
std::vector<size_t> ambient_locs;
for (auto& ambient : ambients) {
ambient_locs.push_back(ambient.generate(gen));
}
gen.align_to_basic();
gen.add_type_tag("drawable-inline-array-ambient"); // 0
size_t result = gen.current_offset_bytes();
ASSERT(ambients.size() < UINT16_MAX);
gen.add_word(ambients.size() << 16); // 4
gen.add_word(0);
gen.add_word(0);
gen.add_word(0);
gen.add_word(0);
gen.add_word(0);
gen.add_word(0);
ASSERT((gen.current_offset_bytes() % 16) == 0);
for (size_t i = 0; i < ambients.size(); i++) {
generate_drawable_ambient(gen, ambients[i], ambient_locs[i]);
}
return result;
}
void add_ambients_from_json(const nlohmann::json& json,
std::vector<EntityAmbient>& ambient_list,
u32 base_aid) {
for (const auto& ambient_json : json) {
auto& ambient = ambient_list.emplace_back();
ambient.aid = ambient_json.value("aid", base_aid + ambient_list.size());
ambient.vis_id = ambient_json.value("vis_id", 0);
ambient.trans = vectorm4_from_json(ambient_json.at("trans"));
ambient.bsphere = vectorm4_from_json(ambient_json.at("bsphere"));
if (ambient_json.find("lump") != ambient_json.end()) {
for (auto [key, value] : ambient_json.at("lump").items()) {
if (value.is_string()) {
std::string value_string = value.get<std::string>();
if (value_string.size() > 0 && value_string[0] == '\'') {
ambient.res_lump.add_res(
std::make_unique<ResSymbol>(key, value_string.substr(1), -1000000000.0000));
} else {
ambient.res_lump.add_res(
std::make_unique<ResString>(key, value_string, -1000000000.0000));
}
continue;
}
if (value.is_array()) {
ambient.res_lump.add_res(res_from_json_array(key, value));
}
}
}
ambient.res_lump.sort_res();
}
}
} // namespace jak1
@@ -1,18 +1,17 @@
#pragma once
#include "goalc/build_level/ResLump.h"
#include "third-party/json.hpp"
#include "goalc/build_level/common/Entity.h"
namespace jak1 {
/*
* (trans vector :inline :offset-assert 32)
(aid uint32 :offset-assert 48)
* (nav-mesh nav-mesh :offset-assert 52)
(etype type :offset-assert 56) ;; probably type
(task game-task :offset-assert 60)
(vis-id uint16 :offset-assert 62)
(vis-id-signed int16 :offset 62) ;; added
(quat quaternion :inline :offset-assert 64)
* (trans vector :inline :offset-assert 32)
* (aid uint32 :offset-assert 48)
* (nav-mesh nav-mesh :offset-assert 52)
* (etype type :offset-assert 56) ;; probably type
* (task game-task :offset-assert 60)
* (vis-id uint16 :offset-assert 62)
* (vis-id-signed int16 :offset 62) ;; added
* (quat quaternion :inline :offset-assert 64)
*/
struct EntityActor {
ResLump res_lump;
@@ -28,6 +27,13 @@ struct EntityActor {
size_t generate(DataObjectGenerator& gen) const;
};
size_t generate_inline_array_actors(DataObjectGenerator& gen,
const std::vector<EntityActor>& actors);
void add_actors_from_json(const nlohmann::json& json,
std::vector<EntityActor>& actor_list,
u32 base_aid);
struct EntityAmbient {
ResLump res_lump;
u32 aid = 0;
@@ -37,15 +43,9 @@ struct EntityAmbient {
size_t generate(DataObjectGenerator& gen) const;
};
size_t generate_inline_array_actors(DataObjectGenerator& gen,
const std::vector<EntityActor>& actors);
size_t generate_inline_array_ambients(DataObjectGenerator& gen,
const std::vector<EntityAmbient>& ambients);
void add_ambients_from_json(const nlohmann::json& json,
std::vector<EntityAmbient>& ambient_list,
u32 base_aid);
void add_actors_from_json(const nlohmann::json& json,
std::vector<EntityActor>& actor_list,
u32 base_aid);
} // namespace jak1
+19
View File
@@ -0,0 +1,19 @@
#include "FileInfo.h"
#include "common/versions/versions.h"
#include "goalc/data_compiler/DataObjectGenerator.h"
namespace jak1 {
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;
}
} // namespace jak1
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "goalc/build_level/common/FileInfo.h"
namespace jak1 {
FileInfo make_file_info_for_level(const std::string& file_name);
} // namespace jak1
@@ -1,7 +1,6 @@
#include "LevelFile.h"
#include "goalc/data_compiler/DataObjectGenerator.h"
namespace jak1 {
static size_t ambient_arr_slot;
size_t DrawableTreeArray::add_to_object_file(DataObjectGenerator& gen) const {
@@ -85,7 +84,7 @@ std::vector<u8> LevelFile::save_object_file() const {
auto file_info_slot = info.add_to_object_file(gen);
gen.link_word_to_byte(1, file_info_slot);
ambient_arr_slot = generate_inline_array_ambients(gen, ambients);
ambient_arr_slot = jak1::generate_inline_array_ambients(gen, ambients);
//(bsphere vector :inline :offset-assert 16)
//(all-visible-list (pointer uint16) :offset-assert 32)
@@ -124,4 +123,5 @@ std::vector<u8> LevelFile::save_object_file() const {
//(unk-data-8 uint32 55 :offset-assert 180)
return gen.generate_v2();
}
}
} // namespace jak1
@@ -4,17 +4,19 @@
#include <string>
#include <vector>
#include "Entity.h"
#include "FileInfo.h"
#include "ambient.h"
#include "common/common_types.h"
#include "goalc/build_level/Entity.h"
#include "goalc/build_level/FileInfo.h"
#include "goalc/build_level/Tfrag.h"
#include "goalc/build_level/ambient.h"
#include "goalc/build_level/collide_bvh.h"
#include "goalc/build_level/collide_common.h"
#include "goalc/build_level/collide_drawable.h"
#include "goalc/build_level/collide_pack.h"
#include "goalc/build_level/collide/common/collide_common.h"
#include "goalc/build_level/collide/jak1/collide_bvh.h"
#include "goalc/build_level/collide/jak1/collide_drawable.h"
#include "goalc/build_level/collide/jak1/collide_pack.h"
#include "goalc/build_level/common/Tfrag.h"
namespace jak1 {
struct VisibilityString {
std::vector<u8> bytes;
};
@@ -134,4 +136,5 @@ struct LevelFile {
// (unk-data-8 uint32 55 :offset-assert 180)
std::vector<u8> save_object_file() const;
};
};
} // namespace jak1
@@ -24,6 +24,7 @@
)
*/
namespace jak1 {
size_t DrawableTreeAmbient::add_to_object_file(DataObjectGenerator& gen, size_t ambient_array) {
gen.align_to_basic();
gen.add_type_tag("drawable-tree-ambient");
@@ -37,4 +38,5 @@ size_t DrawableTreeAmbient::add_to_object_file(DataObjectGenerator& gen, size_t
gen.add_word(0); // 28
gen.link_word_to_byte(gen.add_word(0), ambient_array);
return result;
}
}
} // namespace jak1
@@ -4,6 +4,8 @@
class DataObjectGenerator;
namespace jak1 {
struct DrawableTreeAmbient {
static size_t add_to_object_file(DataObjectGenerator& gen, size_t ambient_array);
};
};
} // namespace jak1
@@ -1,66 +1,15 @@
#include "build_level.h"
#include "common/custom_data/Tfrag3Data.h"
#include "common/log/log.h"
#include "common/util/FileUtil.h"
#include "common/util/compress.h"
#include "common/util/json_util.h"
#include "common/util/string_util.h"
#include "decompiler/extractor/extractor_util.h"
#include "decompiler/level_extractor/extract_merc.h"
#include "goalc/build_level/Entity.h"
#include "goalc/build_level/FileInfo.h"
#include "goalc/build_level/LevelFile.h"
#include "goalc/build_level/Tfrag.h"
#include "goalc/build_level/collide_bvh.h"
#include "goalc/build_level/collide_pack.h"
#include "goalc/build_level/gltf_mesh_extract.h"
#include "third-party/fmt/core.h"
void save_pc_data(const std::string& nickname,
tfrag3::Level& data,
const fs::path& fr3_output_dir) {
Serializer ser;
data.serialize(ser);
auto compressed =
compression::compress_zstd(ser.get_save_result().first, ser.get_save_result().second);
lg::print("stats for {}\n", data.level_name);
print_memory_usage(data, ser.get_save_result().second);
lg::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(fr3_output_dir / fmt::format("{}.fr3", str_util::to_upper(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>()};
}
// Find all art groups the custom level needs in a list of object files,
// skipping any that we already found in other dgos before
std::vector<decompiler::ObjectFileRecord> find_art_groups(
std::vector<std::string>& processed_ags,
const std::vector<std::string>& custom_level_ag,
const std::vector<decompiler::ObjectFileRecord>& dgo_files) {
std::vector<decompiler::ObjectFileRecord> art_groups;
for (const auto& file : dgo_files) {
// skip any art groups we already added from other dgos
if (std::find(processed_ags.begin(), processed_ags.end(), file.name) != processed_ags.end()) {
continue;
}
if (std::find(custom_level_ag.begin(), custom_level_ag.end(), file.name) !=
custom_level_ag.end()) {
art_groups.push_back(file);
processed_ags.push_back(file.name);
}
}
return art_groups;
}
#include "goalc/build_level/collide/jak1/collide_bvh.h"
#include "goalc/build_level/collide/jak1/collide_pack.h"
#include "goalc/build_level/common/Tfrag.h"
#include "goalc/build_level/jak1/Entity.h"
#include "goalc/build_level/jak1/FileInfo.h"
#include "goalc/build_level/jak1/LevelFile.h"
namespace jak1 {
bool run_build_level(const std::string& input_file,
const std::string& bsp_output_file,
const std::string& output_prefix) {
@@ -100,7 +49,8 @@ bool run_build_level(const std::string& input_file,
file.actors = std::move(actors);
// ambients
std::vector<EntityAmbient> ambients;
add_ambients_from_json(level_json.at("ambients"), ambients, level_json.value("base_id", 12345));
jak1::add_ambients_from_json(level_json.at("ambients"), ambients,
level_json.value("base_id", 12345));
file.ambients = std::move(ambients);
auto& ambient_drawable_tree = file.drawable_trees.ambients.emplace_back();
(void)ambient_drawable_tree;
@@ -259,3 +209,4 @@ bool run_build_level(const std::string& input_file,
return true;
}
} // namespace jak1
@@ -1,11 +1,9 @@
#pragma once
#include <string>
#include <vector>
#include "decompiler/level_extractor/extract_level.h"
#include "goalc/build_level/common/build_level.h"
namespace jak1 {
bool run_build_level(const std::string& input_file,
const std::string& bsp_output_file,
const std::string& output_prefix);
std::vector<std::string> get_build_level_deps(const std::string& input_file);
} // namespace jak1
+109
View File
@@ -0,0 +1,109 @@
#include "Entity.h"
namespace jak2 {
size_t EntityActor::generate(DataObjectGenerator& gen) const {
size_t result = res_lump.generate_header(gen, "entity-actor");
for (int i = 0; i < 4; i++) {
gen.add_word_float(trans[i]);
}
gen.add_word(aid);
gen.add_word(kill_mask);
gen.add_type_tag(etype);
ASSERT(game_task < UINT16_MAX);
ASSERT(vis_id < UINT16_MAX);
u32 packed = (game_task) | (vis_id << 16);
gen.add_word(packed);
for (int i = 0; i < 4; i++) {
gen.add_word_float(quat[i]);
}
res_lump.generate_tag_list_and_data(gen, result);
return result;
}
size_t generate_drawable_actor(DataObjectGenerator& gen,
const EntityActor& actor,
size_t actor_loc) {
gen.align_to_basic();
gen.add_type_tag("drawable-actor"); // 0
size_t result = gen.current_offset_bytes();
gen.add_word(actor.vis_id); // 4
gen.link_word_to_byte(gen.add_word(0), actor_loc); // 8
gen.add_word(0); // 12
for (int i = 0; i < 4; i++) {
gen.add_word_float(actor.bsphere[i]); // 16, 20, 24, 28
}
return result;
}
size_t generate_inline_array_actors(DataObjectGenerator& gen,
const std::vector<EntityActor>& actors) {
std::vector<size_t> actor_locs;
for (auto& actor : actors) {
actor_locs.push_back(actor.generate(gen));
}
gen.align_to_basic();
gen.add_type_tag("drawable-inline-array-actor"); // 0
size_t result = gen.current_offset_bytes();
ASSERT(actors.size() < UINT16_MAX);
gen.add_word(actors.size() << 16); // 4
gen.add_word(0);
gen.add_word(0);
gen.add_word(0);
gen.add_word(0);
gen.add_word(0);
gen.add_word(0);
ASSERT((gen.current_offset_bytes() % 16) == 0);
for (size_t i = 0; i < actors.size(); i++) {
generate_drawable_actor(gen, actors[i], actor_locs[i]);
}
return result;
}
void add_actors_from_json(const nlohmann::json& json,
std::vector<EntityActor>& actor_list,
u32 base_aid) {
for (const auto& actor_json : json) {
auto& actor = actor_list.emplace_back();
actor.aid = actor_json.value("aid", base_aid + actor_list.size());
actor.trans = vectorm3_from_json(actor_json.at("trans"));
actor.etype = actor_json.at("etype").get<std::string>();
actor.kill_mask = actor_json.value("kill_mask", 0);
actor.game_task = actor_json.value("game_task", 0);
actor.vis_id = actor_json.value("vis_id", 0);
actor.quat = math::Vector4f(0, 0, 0, 1);
if (actor_json.find("quat") != actor_json.end()) {
actor.quat = vector_from_json(actor_json.at("quat"));
}
actor.bsphere = vectorm4_from_json(actor_json.at("bsphere"));
if (actor_json.find("lump") != actor_json.end()) {
for (auto [key, value] : actor_json.at("lump").items()) {
if (value.is_string()) {
std::string value_string = value.get<std::string>();
if (value_string.size() > 0 && value_string[0] == '\'') {
actor.res_lump.add_res(
std::make_unique<ResSymbol>(key, value_string.substr(1), -1000000000.0000));
} else {
actor.res_lump.add_res(
std::make_unique<ResString>(key, value_string, -1000000000.0000));
}
continue;
}
if (value.is_array()) {
actor.res_lump.add_res(res_from_json_array(key, value));
}
}
}
actor.res_lump.sort_res();
}
}
} // namespace jak2
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include "goalc/build_level/common/Entity.h"
namespace jak2 {
/*
* (trans vector :inline :offset-assert 32)
* (aid uint32 :offset-assert 48)
* (kill-mask task-mask :offset-assert 52)
* (etype type :offset-assert 56) ;; probably type
* (task game-task :offset-assert 60)
* (vis-id uint16 :offset-assert 62)
* (quat quaternion :inline :offset-assert 64)
*/
struct EntityActor {
ResLump res_lump;
math::Vector4f trans; // w = 1 here
u32 aid = 0;
u32 kill_mask = 0;
std::string etype;
u32 game_task = 0;
u32 vis_id = 0;
math::Vector4f quat;
math::Vector4f bsphere;
size_t generate(DataObjectGenerator& gen) const;
};
size_t generate_inline_array_actors(DataObjectGenerator& gen,
const std::vector<EntityActor>& actors);
void add_actors_from_json(const nlohmann::json& json,
std::vector<EntityActor>& actor_list,
u32 base_aid);
struct Region {};
} // namespace jak2
+19
View File
@@ -0,0 +1,19 @@
#include "FileInfo.h"
#include "common/versions/versions.h"
#include "goalc/data_compiler/DataObjectGenerator.h"
namespace jak2 {
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::jak2::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;
}
} // namespace jak2
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "goalc/build_level/common/FileInfo.h"
namespace jak2 {
FileInfo make_file_info_for_level(const std::string& file_name);
} // namespace jak2
+144
View File
@@ -0,0 +1,144 @@
#include "LevelFile.h"
#include "goalc/data_compiler/DataObjectGenerator.h"
namespace jak2 {
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;
}
size_t generate_u32_array(const std::vector<u32>& array, DataObjectGenerator& gen) {
gen.align(4);
size_t result = gen.current_offset_bytes();
for (auto& entry : array) {
gen.add_word(entry);
}
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)
//(unknown-basic 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)
gen.link_word_to_byte(112 / 4, generate_inline_array_actors(gen, actors));
//(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)
//(cam-outside-bsp uint8 :offset 152)
//(cam-using-back uint8 :offset-assert 153)
//(cam-box-idx uint16 :offset-assert 154)
//(ambients symbol :offset-assert 156)
//(subdivide-close float :offset-assert 160)
//(subdivide-far float :offset-assert 160)
//(race-meshes (array entity-race-mesh) :offset-assert 168)
//(actor-birth-order (pointer uint32) :offset-assert 172)
gen.link_word_to_byte(172 / 4, generate_u32_array(actor_birth_order, gen));
//(light-hash light-hash :offset-assert 176)
//(nav-meshes (array entity-nav-mesh) :offset-assert 180)
//(actor-groups (array actor-group) :offset-assert 184)
//(region-trees (array drawable-tree-region-prim) :offset-assert 188)
//(region-array region-array :offset-assert 192)
//(collide-hash collide-hash :offset-assert 196)
//(wind-array uint32 :offset 200)
//(wind-array-length int32 :offset 204)
//(city-level-info city-level-info :offset 208)
//(vis-spheres vector-array :offset 216)
//(vis-spheres-length uint32 :offset 248)
//(region-tree drawable-tree-region-prim :offset 252)
//(tfrag-masks texture-masks-array :offset-assert 256)
//(tfrag-closest (pointer float) :offset-assert 260)
//(tfrag-mask-count uint32 :offset 260)
//(shrub-masks texture-masks-array :offset-assert 264)
//(shrub-closest (pointer float) :offset-assert 268)
//(shrub-mask-count uint32 :offset 268)
//(alpha-masks texture-masks-array :offset-assert 272)
//(alpha-closest (pointer float) :offset-assert 276)
//(alpha-mask-count uint32 :offset 276)
//(water-masks texture-masks-array :offset-assert 280)
//(water-closest (pointer float) :offset-assert 284)
//(water-mask-count uint32 :offset 284)
//(bsp-scale vector :inline :offset-assert 288)
//(bsp-offset vector :inline :offset-assert 304)
//(end uint8 :offset 399)
return gen.generate_v2();
}
} // namespace jak2
+187
View File
@@ -0,0 +1,187 @@
#pragma once
#include <array>
#include <string>
#include <vector>
#include "common/common_types.h"
#include "goalc/build_level/collide/common/collide_common.h"
#include "goalc/build_level/collide/jak1/collide_bvh.h"
#include "goalc/build_level/collide/jak1/collide_drawable.h"
#include "goalc/build_level/collide/jak1/collide_pack.h"
#include "goalc/build_level/common/Tfrag.h"
#include "goalc/build_level/jak2/Entity.h"
#include "goalc/build_level/jak2/FileInfo.h"
namespace jak2 {
struct VisibilityString {
std::vector<u8> bytes;
};
struct DrawableTreeInstanceTie {};
struct DrawableTreeActor {};
struct DrawableTreeInstanceShrub {};
struct DrawableTreeRegionPrim {};
struct DrawableTreeArray {
std::vector<DrawableTreeTfrag> tfrags;
std::vector<DrawableTreeInstanceTie> ties;
std::vector<DrawableTreeActor> actors; // unused?
std::vector<DrawableTreeRegionPrim> regions;
std::vector<DrawableTreeCollideFragment> collides;
std::vector<DrawableTreeInstanceShrub> shrubs;
size_t add_to_object_file(DataObjectGenerator& gen) const;
};
struct TextureRemap {};
struct TextureId {};
struct VisInfo {};
struct EntityCamera {};
struct BspNode {};
struct RaceMesh {};
struct LightHash {};
struct EntityNavMesh {};
struct ActorGroup {};
struct RegionTree {};
struct RegionArray {};
struct CollideHash {};
struct CityLevelInfo {};
struct TextureMasksArray {};
// 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 int16 :offset-assert 36)
// (extra-vis-list-length int16 :offset-assert 38)
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;
// (unknown-basic 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)
std::vector<EntityActor> 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
// (texture-flags texture-page-flag 10 :offset-assert 130)
std::vector<u16> texture_flags;
// (cam-outside-bsp uint8 :offset 152)
// (cam-using-back uint8 :offset-assert 153)
// (cam-box-idx uint16 :offset-assert 154)
// zero
// (ambients symbol :offset-assert 156)
// #t
// (subdivide-close float :offset-assert 160)
float close_subdiv = 0;
// (subdivide-far float :offset-assert 164)
float far_subdiv = 0;
// (race-meshes (array entity-race-mesh) :offset-assert 168)
std::vector<RaceMesh> race_meshes;
// (actor-birth-order (pointer uint32) :offset-assert 172)
std::vector<u32> actor_birth_order;
// (light-hash light-hash :offset-assert 176)
LightHash light_hash;
// (nav-meshes (array entity-nav-mesh) :offset-assert 180)
std::vector<EntityNavMesh> entity_nav_meshes;
// (actor-groups (array actor-group) :offset-assert 184)
std::vector<ActorGroup> actor_groups;
// (region-trees (array drawable-tree-region-prim) :offset-assert 188)
std::vector<RegionTree> region_trees;
// (region-array region-array :offset-assert 192)
RegionArray region_array;
// (collide-hash collide-hash :offset-assert 196)
CollideHash collide_hash;
// (wind-array uint32 :offset 200)
std::vector<u32> wind_array;
// (wind-array-length int32 :offset 204)
s32 wind_array_length = 0;
// (city-level-info city-level-info :offset 208)
CityLevelInfo city_level_info;
// (vis-spheres vector-array :offset 216)
// (vis-spheres-length uint32 :offset 248)
// (region-tree drawable-tree-region-prim :offset 252)
RegionTree region_tree;
// (tfrag-masks texture-masks-array :offset-assert 256)
// (tfrag-closest (pointer float) :offset-assert 260)
// (tfrag-mask-count uint32 :offset 260)
TextureMasksArray tfrag_masks;
// (shrub-masks texture-masks-array :offset-assert 264)
// (shrub-closest (pointer float) :offset-assert 268)
// (shrub-mask-count uint32 :offset 268)
TextureMasksArray shrub_masks;
// (alpha-masks texture-masks-array :offset-assert 272)
// (alpha-closest (pointer float) :offset-assert 276)
// (alpha-mask-count uint32 :offset 276)
TextureMasksArray alpha_masks;
// (water-masks texture-masks-array :offset-assert 280)
// (water-closest (pointer float) :offset-assert 284)
// (water-mask-count uint32 :offset 284)
TextureMasksArray water_masks;
// (bsp-scale vector :inline :offset-assert 288)
// (bsp-offset vector :inline :offset-assert 304)
std::vector<u8> save_object_file() const;
};
} // namespace jak2
+202
View File
@@ -0,0 +1,202 @@
#include "build_level.h"
#include "decompiler/extractor/extractor_util.h"
#include "decompiler/level_extractor/extract_merc.h"
#include "goalc/build_level/collide/jak1/collide_bvh.h"
#include "goalc/build_level/collide/jak1/collide_pack.h"
#include "goalc/build_level/common/Tfrag.h"
#include "goalc/build_level/jak2/Entity.h"
#include "goalc/build_level/jak2/FileInfo.h"
#include "goalc/build_level/jak2/LevelFile.h"
namespace jak2 {
bool run_build_level(const std::string& input_file,
const std::string& bsp_output_file,
const std::string& output_prefix) {
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.auto_wall_enable = level_json.value("automatic_wall_detection", true);
mesh_extract_in.double_sided_collide = level_json.at("double_sided_collide").get<bool>();
mesh_extract_in.auto_wall_angle = level_json.value("automatic_wall_angle", 30.0);
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(fs::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
std::vector<EntityActor> actors;
add_actors_from_json(level_json.at("actors"), actors, level_json.value("base_id", 1234));
file.actors = std::move(actors);
// cameras
// nodes
// regions
// subdivs
// actor birth
for (size_t i = 0; i < file.actors.size(); i++) {
file.actor_birth_order.push_back(i);
}
// 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();
lg::print("Level bsp file size {} bytes\n", result.size());
auto save_path = file_util::get_jak_project_dir() / bsp_output_file;
file_util::create_dir_if_needed_for_file(save_path);
lg::print("Saving to {}\n", save_path.string());
file_util::write_binary_file(save_path, result.data(), result.size());
// Add textures and models
// TODO remove hardcoded config settings
if ((level_json.contains("art_groups") && !level_json.at("art_groups").empty()) ||
(level_json.contains("textures") && !level_json.at("textures").empty())) {
fs::path iso_folder = "";
lg::info("Looking for ISO path...");
// TODO - add to file_util
for (const auto& entry :
fs::directory_iterator(file_util::get_jak_project_dir() / "iso_data")) {
// TODO - hard-coded to jak 2
if (entry.is_directory() &&
entry.path().filename().string().find("jak2") != std::string::npos) {
lg::info("Found ISO path: {}", entry.path().string());
iso_folder = entry.path();
}
}
if (iso_folder.empty() || !fs::exists(iso_folder)) {
lg::warn("Could not locate ISO path!");
return false;
}
// Look for iso build info if it's available, otherwise default to ntsc_v1
const auto version_info = get_version_info_or_default(iso_folder);
decompiler::Config config;
try {
config = decompiler::read_config_file(
file_util::get_jak_project_dir() / "decompiler/config/jak2/jak2_config.jsonc",
version_info.decomp_config_version,
R"({"decompile_code": false, "find_functions": false, "levels_extract": true, "allowed_objects": [], "save_texture_pngs": false})");
} catch (const std::exception& e) {
lg::error("Failed to parse config: {}", e.what());
return false;
}
std::vector<fs::path> dgos, objs;
for (const auto& dgo_name : config.dgo_names) {
dgos.push_back(iso_folder / dgo_name);
}
for (const auto& obj_name : config.object_file_names) {
objs.push_back(iso_folder / obj_name);
}
decompiler::ObjectFileDB db(dgos, fs::path(config.obj_file_name_map_file), objs, {}, {},
config);
// need to process link data for tpages
db.process_link_data(config);
decompiler::TextureDB tex_db;
auto textures_out = file_util::get_jak_project_dir() / "decompiler_out/jak2/textures";
file_util::create_dir_if_needed(textures_out);
db.process_tpages(tex_db, textures_out, config);
std::vector<std::string> processed_art_groups;
// find all art groups used by the custom level in other dgos
if (level_json.contains("art_groups") && !level_json.at("art_groups").empty()) {
for (auto& dgo : config.dgo_names) {
// remove "DGO/" prefix
const auto& dgo_name = dgo.substr(4);
const auto& files = db.obj_files_by_dgo.at(dgo_name);
auto art_groups =
find_art_groups(processed_art_groups,
level_json.at("art_groups").get<std::vector<std::string>>(), files);
auto tex_remap = decompiler::extract_tex_remap(db, dgo_name);
for (const auto& ag : art_groups) {
if (ag.name.length() > 3 && !ag.name.compare(ag.name.length() - 3, 3, "-ag")) {
const auto& ag_file = db.lookup_record(ag);
lg::print("custom level: extracting art group {}\n", ag_file.name_in_dgo);
decompiler::extract_merc(ag_file, tex_db, db.dts, tex_remap, pc_level, false,
db.version());
}
}
}
}
// add textures
if (level_json.contains("textures") && !level_json.at("textures").empty()) {
std::vector<std::string> processed_textures;
std::vector<std::string> wanted_texs =
level_json.at("textures").get<std::vector<std::string>>();
// first check the texture is not already in the level
for (auto& level_tex : pc_level.textures) {
if (std::find(wanted_texs.begin(), wanted_texs.end(), level_tex.debug_name) !=
wanted_texs.end()) {
processed_textures.push_back(level_tex.debug_name);
}
}
// then add
for (auto& [id, tex] : tex_db.textures) {
for (auto& tex0 : wanted_texs) {
if (std::find(processed_textures.begin(), processed_textures.end(), tex.name) !=
processed_textures.end()) {
continue;
}
if (tex.name == tex0) {
lg::info("custom level: adding texture {} from tpage {} ({})", tex.name, tex.page,
tex_db.tpage_names.at(tex.page));
pc_level.textures.push_back(
make_texture(id, tex, tex_db.tpage_names.at(tex.page), true));
processed_textures.push_back(tex.name);
}
}
}
}
}
// Save the PC level
save_pc_data(file.nickname, pc_level,
file_util::get_jak_project_dir() / "out" / output_prefix / "fr3");
return true;
}
} // namespace jak2
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include "goalc/build_level/common/build_level.h"
namespace jak2 {
bool run_build_level(const std::string& input_file,
const std::string& bsp_output_file,
const std::string& output_prefix);
} // namespace jak2