diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index 48e09db03d..9d1a52e3af 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -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 diff --git a/goalc/build_level/collide/common/collide_common.h b/goalc/build_level/collide/common/collide_common.h new file mode 100644 index 0000000000..e756dce450 --- /dev/null +++ b/goalc/build_level/collide/common/collide_common.h @@ -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 diff --git a/goalc/build_level/collide_bvh.cpp b/goalc/build_level/collide/jak1/collide_bvh.cpp similarity index 94% rename from goalc/build_level/collide_bvh.cpp rename to goalc/build_level/collide/jak1/collide_bvh.cpp index abbab4531e..8afaee51f3 100644 --- a/goalc/build_level/collide_bvh.cpp +++ b/goalc/build_level/collide/jak1/collide_bvh.cpp @@ -27,7 +27,7 @@ constexpr int MAX_UNIQUE_VERTS_IN_FRAG = 128; */ struct CNode { std::vector child_nodes; - std::vector faces; + std::vector 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& faces, +void split_along_dim(std::vector& faces, int dim, - std::vector* out0, - std::vector* out1) { - std::sort(faces.begin(), faces.end(), [=](const CollideFace& a, const CollideFace& b) { - return a.bsphere[dim] < b.bsphere[dim]; - }); + std::vector* out0, + std::vector* 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& tris) { +CollideTree construct_collide_bvh(const std::vector& tris) { // part 1: build the tree Timer bvh_timer; lg::info("Building collide bvh from {} triangles", tris.size()); diff --git a/goalc/build_level/collide_bvh.h b/goalc/build_level/collide/jak1/collide_bvh.h similarity index 80% rename from goalc/build_level/collide_bvh.h rename to goalc/build_level/collide/jak1/collide_bvh.h index dca6b47e0d..15261b5a87 100644 --- a/goalc/build_level/collide_bvh.h +++ b/goalc/build_level/collide/jak1/collide_bvh.h @@ -2,7 +2,7 @@ #include -#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 faces; + std::vector faces; }; struct DrawableInlineArrayNode { @@ -36,5 +36,5 @@ struct CollideTree { DrawableInlineArrayCollideFrag frags; }; -CollideTree construct_collide_bvh(const std::vector& tris); +CollideTree construct_collide_bvh(const std::vector& tris); } // namespace collide diff --git a/goalc/build_level/collide_drawable.cpp b/goalc/build_level/collide/jak1/collide_drawable.cpp similarity index 99% rename from goalc/build_level/collide_drawable.cpp rename to goalc/build_level/collide/jak1/collide_drawable.cpp index 2ef770a394..d32f1e6ca1 100644 --- a/goalc/build_level/collide_drawable.cpp +++ b/goalc/build_level/collide/jak1/collide_drawable.cpp @@ -68,7 +68,7 @@ :size-assert #x44 */ -size_t generate_pat_array(DataObjectGenerator& gen, const std::vector& pats) { +size_t generate_pat_array(DataObjectGenerator& gen, const std::vector& pats) { gen.align_to_basic(); size_t result = gen.current_offset_bytes(); for (auto& pat : pats) { diff --git a/goalc/build_level/collide_drawable.h b/goalc/build_level/collide/jak1/collide_drawable.h similarity index 71% rename from goalc/build_level/collide_drawable.h rename to goalc/build_level/collide/jak1/collide_drawable.h index 5804dbb5c1..a9a637a675 100644 --- a/goalc/build_level/collide_drawable.h +++ b/goalc/build_level/collide/jak1/collide_drawable.h @@ -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; diff --git a/goalc/build_level/collide_pack.cpp b/goalc/build_level/collide/jak1/collide_pack.cpp similarity index 97% rename from goalc/build_level/collide_pack.cpp rename to goalc/build_level/collide/jak1/collide_pack.cpp index 04e1b115fc..9e864896a7 100644 --- a/goalc/build_level/collide_pack.cpp +++ b/goalc/build_level/collide/jak1/collide_pack.cpp @@ -91,7 +91,7 @@ PackedU16Verts pack_verts_to_u16(const std::vector& input) { } struct PatSurfaceHash { - size_t operator()(const PatSurface& in) const { return std::hash()(in.val); } + size_t operator()(const jak1::PatSurface& in) const { return std::hash()(in.val); } }; /*! @@ -99,10 +99,10 @@ struct PatSurfaceHash { * There's a pat "palette" with up 255 unique pats. */ struct PatMap { - std::unordered_map map; - std::vector pats; + std::unordered_map map; + std::vector 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(); diff --git a/goalc/build_level/collide_pack.h b/goalc/build_level/collide/jak1/collide_pack.h similarity index 87% rename from goalc/build_level/collide_pack.h rename to goalc/build_level/collide/jak1/collide_pack.h index 316b56e9f4..b34068ad1c 100644 --- a/goalc/build_level/collide_pack.h +++ b/goalc/build_level/collide/jak1/collide_pack.h @@ -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 packed_frag_data; - std::vector pats; + std::vector pats; }; CollideFragMeshDataArray pack_collide_frags(const std::vector& frag_data); \ No newline at end of file diff --git a/goalc/build_level/collide_common.h b/goalc/build_level/collide_common.h deleted file mode 100644 index a2da824600..0000000000 --- a/goalc/build_level/collide_common.h +++ /dev/null @@ -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; -}; diff --git a/goalc/build_level/common/Entity.cpp b/goalc/build_level/common/Entity.cpp new file mode 100644 index 0000000000..4f3ed66c77 --- /dev/null +++ b/goalc/build_level/common/Entity.cpp @@ -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() * 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() * 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() * METER_LENGTH; + } + result[3] = json[3].get() * 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(); + } + return result; +} + +std::unique_ptr 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(); + if (array_type == "int32") { + std::vector data; + for (size_t i = 1; i < json_array.size(); i++) { + data.push_back(json_array[i].get()); + } + return std::make_unique(name, data, -1000000000.0000); + } else if (array_type == "uint32") { + std::vector data; + for (size_t i = 1; i < json_array.size(); i++) { + data.push_back(json_array[i].get()); + } + return std::make_unique(name, data, -1000000000.0000); + } else if (array_type == "vector") { + std::vector data; + for (size_t i = 1; i < json_array.size(); i++) { + data.push_back(vector_from_json(json_array[i])); + } + return std::make_unique(name, data, -1000000000.0000); + } else if (array_type == "vector4m") { + std::vector data; + for (size_t i = 1; i < json_array.size(); i++) { + data.push_back(vectorm4_from_json(json_array[i])); + } + return std::make_unique(name, data, -1000000000.0000); + } else if (array_type == "movie-pos") { + std::vector 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(name, data, -1000000000.0000); + } else if (array_type == "float") { + std::vector data; + for (size_t i = 1; i < json_array.size(); i++) { + data.push_back(json_array[i].get()); + } + return std::make_unique(name, data, -1000000000.0000); + } else if (array_type == "meters") { + std::vector data; + for (size_t i = 1; i < json_array.size(); i++) { + data.push_back(json_array[i].get() * METER_LENGTH); + } + return std::make_unique(name, data, -1000000000.0000); + } else if (array_type == "degrees") { + std::vector data; + for (size_t i = 1; i < json_array.size(); i++) { + data.push_back(json_array[i].get() * DEGREES_LENGTH); + } + return std::make_unique(name, data, -1000000000.0000); + } else { + ASSERT_MSG(false, fmt::format("unsupported array type: {}\n", array_type)); + } +} \ No newline at end of file diff --git a/goalc/build_level/common/Entity.h b/goalc/build_level/common/Entity.h new file mode 100644 index 0000000000..3b22503375 --- /dev/null +++ b/goalc/build_level/common/Entity.h @@ -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_from_json_array(const std::string& name, const nlohmann::json& json_array); \ No newline at end of file diff --git a/goalc/build_level/FileInfo.cpp b/goalc/build_level/common/FileInfo.cpp similarity index 61% rename from goalc/build_level/FileInfo.cpp rename to goalc/build_level/common/FileInfo.cpp index e93781dd01..34fb3c4972 100644 --- a/goalc/build_level/FileInfo.cpp +++ b/goalc/build_level/common/FileInfo.cpp @@ -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; -} diff --git a/goalc/build_level/FileInfo.h b/goalc/build_level/common/FileInfo.h similarity index 91% rename from goalc/build_level/FileInfo.h rename to goalc/build_level/common/FileInfo.h index 7932018ca1..8dd0f5f4aa 100644 --- a/goalc/build_level/FileInfo.h +++ b/goalc/build_level/common/FileInfo.h @@ -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); \ No newline at end of file +}; \ No newline at end of file diff --git a/goalc/build_level/ResLump.cpp b/goalc/build_level/common/ResLump.cpp similarity index 94% rename from goalc/build_level/ResLump.cpp rename to goalc/build_level/common/ResLump.cpp index 83bcebd9d3..ec8cc8b699 100644 --- a/goalc/build_level/ResLump.cpp +++ b/goalc/build_level/common/ResLump.cpp @@ -117,6 +117,28 @@ int ResUint8::get_alignment() const { return 16; } +ResUint32::ResUint32(const std::string& name, const std::vector& 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& values, float key_frame) diff --git a/goalc/build_level/ResLump.h b/goalc/build_level/common/ResLump.h similarity index 92% rename from goalc/build_level/ResLump.h rename to goalc/build_level/common/ResLump.h index 991926f2a8..fc51dd846d 100644 --- a/goalc/build_level/ResLump.h +++ b/goalc/build_level/common/ResLump.h @@ -77,6 +77,17 @@ class ResUint8 : public Res { std::vector m_values; }; +class ResUint32 : public Res { + public: + ResUint32(const std::string& name, const std::vector& 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 m_values; +}; + class ResVector : public Res { public: ResVector(const std::string& name, const std::vector& values, float key_frame); diff --git a/goalc/build_level/TexturePool.h b/goalc/build_level/common/TexturePool.h similarity index 100% rename from goalc/build_level/TexturePool.h rename to goalc/build_level/common/TexturePool.h diff --git a/goalc/build_level/Tfrag.cpp b/goalc/build_level/common/Tfrag.cpp similarity index 98% rename from goalc/build_level/Tfrag.cpp rename to goalc/build_level/common/Tfrag.cpp index 1b0e277941..a7bd8e957f 100644 --- a/goalc/build_level/Tfrag.cpp +++ b/goalc/build_level/common/Tfrag.cpp @@ -2,9 +2,10 @@ #include +#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, diff --git a/goalc/build_level/Tfrag.h b/goalc/build_level/common/Tfrag.h similarity index 80% rename from goalc/build_level/Tfrag.h rename to goalc/build_level/common/Tfrag.h index 5fe9198a0c..5ceade14a6 100644 --- a/goalc/build_level/Tfrag.h +++ b/goalc/build_level/common/Tfrag.h @@ -2,10 +2,11 @@ #include +#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; diff --git a/goalc/build_level/common/build_level.cpp b/goalc/build_level/common/build_level.cpp new file mode 100644 index 0000000000..258fd81b90 --- /dev/null +++ b/goalc/build_level/common/build_level.cpp @@ -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 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()}; +} + +// 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 find_art_groups( + std::vector& processed_ags, + const std::vector& custom_level_ag, + const std::vector& dgo_files) { + std::vector 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; +} diff --git a/goalc/build_level/common/build_level.h b/goalc/build_level/common/build_level.h new file mode 100644 index 0000000000..d71a8bba50 --- /dev/null +++ b/goalc/build_level/common/build_level.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +#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 get_build_level_deps(const std::string& input_file); +std::vector find_art_groups( + std::vector& processed_ags, + const std::vector& custom_level_ag, + const std::vector& dgo_files); diff --git a/goalc/build_level/color_quantization.cpp b/goalc/build_level/common/color_quantization.cpp similarity index 100% rename from goalc/build_level/color_quantization.cpp rename to goalc/build_level/common/color_quantization.cpp diff --git a/goalc/build_level/color_quantization.h b/goalc/build_level/common/color_quantization.h similarity index 100% rename from goalc/build_level/color_quantization.h rename to goalc/build_level/common/color_quantization.h diff --git a/goalc/build_level/gltf_mesh_extract.cpp b/goalc/build_level/common/gltf_mesh_extract.cpp similarity index 97% rename from goalc/build_level/gltf_mesh_extract.cpp rename to goalc/build_level/common/gltf_mesh_extract.cpp index bdf2a1e115..3935b67948 100644 --- a/goalc/build_level/gltf_mesh_extract.cpp +++ b/goalc/build_level/common/gltf_mesh_extract.cpp @@ -6,12 +6,12 @@ #include +#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> subdivide_face_if_needed(CollideFace face_in) { +std::optional> 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> 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> subdivide_face_if_needed(CollideFace fac fs[3].bsphere = math::bsphere_of_triangle(fs[3].v); fs[3].pat = face_in.pat; - std::vector result; + std::vector result; for (auto f : fs) { auto next_faces = subdivide_face_if_needed(f); if (next_faces) { @@ -671,7 +671,7 @@ std::optional> 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(); - 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(); - 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()) { 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(); - 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()) { @@ -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 fixed_faces; + std::vector 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++; } } diff --git a/goalc/build_level/gltf_mesh_extract.h b/goalc/build_level/common/gltf_mesh_extract.h similarity index 81% rename from goalc/build_level/gltf_mesh_extract.h rename to goalc/build_level/common/gltf_mesh_extract.h index 1c5c62a894..94dfc09d6b 100644 --- a/goalc/build_level/gltf_mesh_extract.h +++ b/goalc/build_level/common/gltf_mesh_extract.h @@ -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 faces; + std::vector faces; }; struct Output { diff --git a/goalc/build_level/Entity.cpp b/goalc/build_level/jak1/Entity.cpp similarity index 72% rename from goalc/build_level/Entity.cpp rename to goalc/build_level/jak1/Entity.cpp index 3521597afe..857ae9c4f3 100644 --- a/goalc/build_level/Entity.cpp +++ b/goalc/build_level/jak1/Entity.cpp @@ -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& actors) { std::vector 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& ambients) { - std::vector 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() * 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() * 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(); - } - return result; -} - -std::unique_ptr 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(); - if (array_type == "int32") { - std::vector data; - for (size_t i = 1; i < json_array.size(); i++) { - data.push_back(json_array[i].get()); - } - return std::make_unique(name, data, -1000000000.0000); - } else if (array_type == "vector") { - std::vector data; - for (size_t i = 1; i < json_array.size(); i++) { - data.push_back(vector_from_json(json_array[i])); - } - return std::make_unique(name, data, -1000000000.0000); - } else if (array_type == "vector4m") { - std::vector data; - for (size_t i = 1; i < json_array.size(); i++) { - data.push_back(vectorm4_from_json(json_array[i])); - } - return std::make_unique(name, data, -1000000000.0000); - } else if (array_type == "float") { - std::vector data; - for (size_t i = 1; i < json_array.size(); i++) { - data.push_back(json_array[i].get()); - } - return std::make_unique(name, data, -1000000000.0000); - } else if (array_type == "meters") { - std::vector data; - for (size_t i = 1; i < json_array.size(); i++) { - data.push_back(json_array[i].get() * METER_LENGTH); - } - return std::make_unique(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& 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(); - if (value_string.size() > 0 && value_string[0] == '\'') { - ambient.res_lump.add_res( - std::make_unique(key, value_string.substr(1), -1000000000.0000)); - } else { - ambient.res_lump.add_res( - std::make_unique(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& actor_list, u32 base_aid) { @@ -262,4 +104,87 @@ void add_actors_from_json(const nlohmann::json& json, } actor.res_lump.sort_res(); } -} \ No newline at end of file +} + +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& ambients) { + std::vector 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& 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(); + if (value_string.size() > 0 && value_string[0] == '\'') { + ambient.res_lump.add_res( + std::make_unique(key, value_string.substr(1), -1000000000.0000)); + } else { + ambient.res_lump.add_res( + std::make_unique(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 diff --git a/goalc/build_level/Entity.h b/goalc/build_level/jak1/Entity.h similarity index 68% rename from goalc/build_level/Entity.h rename to goalc/build_level/jak1/Entity.h index 15df5a2631..1cb2aafd6f 100644 --- a/goalc/build_level/Entity.h +++ b/goalc/build_level/jak1/Entity.h @@ -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& actors); + +void add_actors_from_json(const nlohmann::json& json, + std::vector& 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& actors); - size_t generate_inline_array_ambients(DataObjectGenerator& gen, const std::vector& ambients); void add_ambients_from_json(const nlohmann::json& json, std::vector& ambient_list, u32 base_aid); - -void add_actors_from_json(const nlohmann::json& json, - std::vector& actor_list, - u32 base_aid); \ No newline at end of file +} // namespace jak1 \ No newline at end of file diff --git a/goalc/build_level/jak1/FileInfo.cpp b/goalc/build_level/jak1/FileInfo.cpp new file mode 100644 index 0000000000..245b2b69cc --- /dev/null +++ b/goalc/build_level/jak1/FileInfo.cpp @@ -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 \ No newline at end of file diff --git a/goalc/build_level/jak1/FileInfo.h b/goalc/build_level/jak1/FileInfo.h new file mode 100644 index 0000000000..115ed1fa95 --- /dev/null +++ b/goalc/build_level/jak1/FileInfo.h @@ -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 \ No newline at end of file diff --git a/goalc/build_level/LevelFile.cpp b/goalc/build_level/jak1/LevelFile.cpp similarity index 97% rename from goalc/build_level/LevelFile.cpp rename to goalc/build_level/jak1/LevelFile.cpp index e93040321c..7393ca3f71 100644 --- a/goalc/build_level/LevelFile.cpp +++ b/goalc/build_level/jak1/LevelFile.cpp @@ -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 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 LevelFile::save_object_file() const { //(unk-data-8 uint32 55 :offset-assert 180) return gen.generate_v2(); -} \ No newline at end of file +} +} // namespace jak1 \ No newline at end of file diff --git a/goalc/build_level/LevelFile.h b/goalc/build_level/jak1/LevelFile.h similarity index 91% rename from goalc/build_level/LevelFile.h rename to goalc/build_level/jak1/LevelFile.h index c3a6c2419a..17844e9e20 100644 --- a/goalc/build_level/LevelFile.h +++ b/goalc/build_level/jak1/LevelFile.h @@ -4,17 +4,19 @@ #include #include +#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 bytes; }; @@ -134,4 +136,5 @@ struct LevelFile { // (unk-data-8 uint32 55 :offset-assert 180) std::vector save_object_file() const; -}; \ No newline at end of file +}; +} // namespace jak1 \ No newline at end of file diff --git a/goalc/build_level/ambient.cpp b/goalc/build_level/jak1/ambient.cpp similarity index 96% rename from goalc/build_level/ambient.cpp rename to goalc/build_level/jak1/ambient.cpp index 85b45a06dc..759d3ac887 100644 --- a/goalc/build_level/ambient.cpp +++ b/goalc/build_level/jak1/ambient.cpp @@ -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; -} \ No newline at end of file +} +} // namespace jak1 \ No newline at end of file diff --git a/goalc/build_level/ambient.h b/goalc/build_level/jak1/ambient.h similarity index 81% rename from goalc/build_level/ambient.h rename to goalc/build_level/jak1/ambient.h index 8128f906f2..a5f6e62ff0 100644 --- a/goalc/build_level/ambient.h +++ b/goalc/build_level/jak1/ambient.h @@ -4,6 +4,8 @@ class DataObjectGenerator; +namespace jak1 { struct DrawableTreeAmbient { static size_t add_to_object_file(DataObjectGenerator& gen, size_t ambient_array); -}; \ No newline at end of file +}; +} // namespace jak1 diff --git a/goalc/build_level/build_level.cpp b/goalc/build_level/jak1/build_level.cpp similarity index 76% rename from goalc/build_level/build_level.cpp rename to goalc/build_level/jak1/build_level.cpp index 417be470d8..70683b4df5 100644 --- a/goalc/build_level/build_level.cpp +++ b/goalc/build_level/jak1/build_level.cpp @@ -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 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()}; -} - -// 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 find_art_groups( - std::vector& processed_ags, - const std::vector& custom_level_ag, - const std::vector& dgo_files) { - std::vector 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 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 \ No newline at end of file diff --git a/goalc/build_level/build_level.h b/goalc/build_level/jak1/build_level.h similarity index 51% rename from goalc/build_level/build_level.h rename to goalc/build_level/jak1/build_level.h index e5f1c689f6..239891b540 100644 --- a/goalc/build_level/build_level.h +++ b/goalc/build_level/jak1/build_level.h @@ -1,11 +1,9 @@ #pragma once -#include -#include - -#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 get_build_level_deps(const std::string& input_file); +} // namespace jak1 \ No newline at end of file diff --git a/goalc/build_level/jak2/Entity.cpp b/goalc/build_level/jak2/Entity.cpp new file mode 100644 index 0000000000..c8ab118ba3 --- /dev/null +++ b/goalc/build_level/jak2/Entity.cpp @@ -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& actors) { + std::vector 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& 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(); + 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(); + if (value_string.size() > 0 && value_string[0] == '\'') { + actor.res_lump.add_res( + std::make_unique(key, value_string.substr(1), -1000000000.0000)); + } else { + actor.res_lump.add_res( + std::make_unique(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 diff --git a/goalc/build_level/jak2/Entity.h b/goalc/build_level/jak2/Entity.h new file mode 100644 index 0000000000..72e181d454 --- /dev/null +++ b/goalc/build_level/jak2/Entity.h @@ -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& actors); + +void add_actors_from_json(const nlohmann::json& json, + std::vector& actor_list, + u32 base_aid); + +struct Region {}; +} // namespace jak2 \ No newline at end of file diff --git a/goalc/build_level/jak2/FileInfo.cpp b/goalc/build_level/jak2/FileInfo.cpp new file mode 100644 index 0000000000..c223ce35c6 --- /dev/null +++ b/goalc/build_level/jak2/FileInfo.cpp @@ -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 diff --git a/goalc/build_level/jak2/FileInfo.h b/goalc/build_level/jak2/FileInfo.h new file mode 100644 index 0000000000..79a0dd1a98 --- /dev/null +++ b/goalc/build_level/jak2/FileInfo.h @@ -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 diff --git a/goalc/build_level/jak2/LevelFile.cpp b/goalc/build_level/jak2/LevelFile.cpp new file mode 100644 index 0000000000..4e8d4be48e --- /dev/null +++ b/goalc/build_level/jak2/LevelFile.cpp @@ -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& 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 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 \ No newline at end of file diff --git a/goalc/build_level/jak2/LevelFile.h b/goalc/build_level/jak2/LevelFile.h new file mode 100644 index 0000000000..df49979a3c --- /dev/null +++ b/goalc/build_level/jak2/LevelFile.h @@ -0,0 +1,187 @@ +#pragma once + +#include +#include +#include + +#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 bytes; +}; + +struct DrawableTreeInstanceTie {}; + +struct DrawableTreeActor {}; + +struct DrawableTreeInstanceShrub {}; + +struct DrawableTreeRegionPrim {}; + +struct DrawableTreeArray { + std::vector tfrags; + std::vector ties; + std::vector actors; // unused? + std::vector regions; + std::vector collides; + std::vector 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 pat; + + // (texture-remap-table (pointer uint64) :offset-assert 52) + // (texture-remap-table-len int32 :offset-assert 56) + std::vector texture_remap_table; + + // (texture-ids (pointer texture-id) :offset-assert 60) + // (texture-page-count int32 :offset-assert 64) + std::vector 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 vis_infos; + + // (actors drawable-inline-array-actor :offset-assert 112) + std::vector actors; + + // (cameras (array entity-camera) :offset-assert 116) + std::vector cameras; + + // (nodes (inline-array bsp-node) :offset-assert 120) + std::vector 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 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 race_meshes; + + // (actor-birth-order (pointer uint32) :offset-assert 172) + std::vector actor_birth_order; + + // (light-hash light-hash :offset-assert 176) + LightHash light_hash; + // (nav-meshes (array entity-nav-mesh) :offset-assert 180) + std::vector entity_nav_meshes; + // (actor-groups (array actor-group) :offset-assert 184) + std::vector actor_groups; + // (region-trees (array drawable-tree-region-prim) :offset-assert 188) + std::vector 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 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 save_object_file() const; +}; +} // namespace jak2 \ No newline at end of file diff --git a/goalc/build_level/jak2/build_level.cpp b/goalc/build_level/jak2/build_level.cpp new file mode 100644 index 0000000000..04738ce451 --- /dev/null +++ b/goalc/build_level/jak2/build_level.cpp @@ -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()}); + 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(); + 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(); + // nick + file.nickname = level_json.at("nickname").get(); + // vis infos + // actors + std::vector 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 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 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>(), 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 processed_textures; + std::vector wanted_texs = + level_json.at("textures").get>(); + // 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 \ No newline at end of file diff --git a/goalc/build_level/jak2/build_level.h b/goalc/build_level/jak2/build_level.h new file mode 100644 index 0000000000..98ea4c34f7 --- /dev/null +++ b/goalc/build_level/jak2/build_level.h @@ -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 \ No newline at end of file