diff --git a/custom_levels/test-zone/test-zone.jsonc b/custom_levels/test-zone/test-zone.jsonc index 73c66edf0..56b6a41d2 100644 --- a/custom_levels/test-zone/test-zone.jsonc +++ b/custom_levels/test-zone/test-zone.jsonc @@ -11,5 +11,43 @@ // Must have vertex colors. Use the blender cycles renderer, bake, diffuse, uncheck color, // and bake to vertex colors. For now, only the first vertex color group is used, so make sure you // only have 1. - "gltf_file": "custom_levels/test-zone/test-zone2.glb" + "gltf_file": "custom_levels/test-zone/test-zone2.glb", + "automatic_wall_detection": true, + "automatic_wall_angle": 45.0, + "actors" : [ + { + "trans": [-21.6238, 20.0496, 17.1191], // translation + "etype": "fuel-cell", // actor type + "game_task": 0, // associated game task (for powercells, etc) + "quat" : [0, 0, 0, 1], // quaternion + "bsphere": [-21.6238, 19.3496, 17.1191, 10], // bounding sphere + "lump": { + "name":"test-fuel-cell" + } + }, + + { + "trans": [-15.2818, 15.2461, 17.1360], // translation + "etype": "crate", // actor type + "game_task": 0, // associated game task (for powercells, etc) + "quat" : [0, 0, 0, 1], // quaternion + "bsphere": [-15.2818, 15.2461, 17.1360, 10], // bounding sphere + "lump": { + "name":"test-crate", + "crate-type":"'steel", + "eco-info": ["int32", 5, 10] + } + }, + + { + "trans": [-5.4630, 17.4553, 1.6169], // translation + "etype": "eco-yellow", // actor type + "game_task": 0, // associated game task (for powercells, etc) + "quat" : [0, 0, 0, 1], // quaternion + "bsphere": [-5.4630, 17.4553, 1.6169, 10], // bounding sphere + "lump": { + "name":"test-eco" + } + } + ] } \ No newline at end of file diff --git a/goal_src/engine/debug/default-menu.gc b/goal_src/engine/debug/default-menu.gc index 48abac677..2b4385e33 100644 --- a/goal_src/engine/debug/default-menu.gc +++ b/goal_src/engine/debug/default-menu.gc @@ -4664,3 +4664,69 @@ ) +(defun bg-custom ((level-name symbol)) + "Modified version of bg for the PC Port custom levels." + + ;; lookup info + (format 0 "(bg-custom ~A)%" level-name) + (let ((lev-info (lookup-level-info level-name))) + (when (= lev-info default-level) + (format 0 "Unable to (bg-custom ~A), the level was not found in *level-load-list*~%" level-name) + (return #f) + ) + + ;; kill jak (rip) + (format 0 "doing stop~%") + (stop 'play) + + ;; kill levels + (dotimes (i 2) + (unload! (-> *level* data i)) + ) + + ;; enable visiblity. the custom level won't use it, but we want it on so other levels can be loaded. + (set! (-> *level* vis?) #t) + + ;; disable border and play mode to prevent loading levels + (set! (-> *level* border?) #f) + (set! (-> *setting-control* default border-mode) #f) + (set! (-> *level* play?) #f) + + ;; disable actor vis + (set! *vis-actors* #f) + + (format 0 "doing level load~%") + ;; allocate level. This may start the loading process, but won't finish it. + (let ((lev (level-get-for-use *level* level-name 'active))) + (when (not lev) + (format 0 "Unable to load level, could not level-get-for-use~%") + (return #f) + ) + (format 0 "about to start load loop, game will freeze and hopefully come back soon~%") + + ;; spin in a loop and load it. This will cause the game to freeze during the load, + ;; but this is good enough for now. + (while (or (= (-> lev status) 'loading) + (= (-> lev status) 'loading-bt) + (= (-> lev status) 'login) + ) + (load-continue lev) + ) + + (when (not (-> lev info continues)) + (format 0 "level info has no continues, can't load it.~%") + ) + + (let ((cont (car (-> lev info continues)))) + (start 'play (the continue-point cont)) + ) + + (vis-load lev) + (set! (-> lev all-visible?) #f) + (set! (-> lev force-all-visible?) #t) + + ;; reset things + ;(initialize! *game-info* 'game (the-as game-save #f) (the-as string #f)) + ) + ) + ) \ No newline at end of file diff --git a/goal_src/engine/level/level.gc b/goal_src/engine/level/level.gc index 371e0fcf8..63531ddca 100644 --- a/goal_src/engine/level/level.gc +++ b/goal_src/engine/level/level.gc @@ -1500,61 +1500,6 @@ 0 ) -(defun bg-custom ((level-name symbol)) - "Modified version of bg for the PC Port custom levels." - - ;; lookup info - (format 0 "(bg-custom ~A)%" level-name) - (let ((lev-info (lookup-level-info level-name))) - (when (= lev-info default-level) - (format 0 "Unable to (bg-custom ~A), the level was not found in *level-load-list*~%" level-name) - (return #f) - ) - - ;; kill jak (rip) - (format 0 "doing stop~%") - (stop 'play) - - ;; enable visiblity. the custom level won't use it, but we want it on so other levels can be loaded. - (set! (-> *level* vis?) #t) - - ;; disable border and play mode to prevent loading levels - (set! (-> *level* border?) #f) - (set! (-> *setting-control* default border-mode) #f) - (set! (-> *level* play?) #f) - - (format 0 "doing level load~%") - ;; allocate level. This may start the loading process, but won't finish it. - (let ((lev (level-get-for-use *level* level-name 'active))) - (when (not lev) - (format 0 "Unable to load level, could not level-get-for-use~%") - (return #f) - ) - (format 0 "about to start load loop, game will freeze and hopefully come back soon~%") - - ;; spin in a loop and load it. This will cause the game to freeze during the load, - ;; but this is good enough for now. - (while (or (= (-> lev status) 'loading) - (= (-> lev status) 'loading-bt) - (= (-> lev status) 'login) - ) - (load-continue lev) - ) - - (when (not (-> lev info continues)) - (format 0 "level info has no continues, can't load it.~%") - ) - - (let ((cont (car (-> lev info continues)))) - (start 'play (the continue-point cont)) - ) - - (vis-load lev) - (set! (-> lev all-visible?) #f) - (set! (-> lev force-all-visible?) #t) - ) - ) - ) (defun play ((use-vis symbol) (init-game symbol)) "The entry point to the actual game! This allocates the level heaps, loads some data, sets some default parameters and sets the startup level." diff --git a/goal_src/engine/math/vector-h.gc b/goal_src/engine/math/vector-h.gc index 3e503c7f5..4c67cdd5a 100644 --- a/goal_src/engine/math/vector-h.gc +++ b/goal_src/engine/math/vector-h.gc @@ -736,6 +736,10 @@ ) ) +(defmacro print-vector4m (vec &key (dst #t)) + `(format ,dst "~m ~m ~m ~m~%" (-> ,vec x) (-> ,vec y) (-> ,vec z) (-> ,vec w)) + ) + (define *zero-vector* (new 'static 'vector :x 0. :y 0. :z 0. :w 0.)) (define-extern vector-identity! (function vector vector)) (define-extern vector-length (function vector float)) diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index 40aa875e4..fa1cc0063 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -10,6 +10,7 @@ add_library(compiler 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 diff --git a/goalc/build_level/Entity.cpp b/goalc/build_level/Entity.cpp new file mode 100644 index 000000000..05941765c --- /dev/null +++ b/goalc/build_level/Entity.cpp @@ -0,0 +1,159 @@ +#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" + +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(0); // nav mesh + 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; +} + +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 { + ASSERT_MSG(false, fmt::format("unsupported array type: {}\n", array_type)); + } +} +} // namespace + +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.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(); + } +} \ No newline at end of file diff --git a/goalc/build_level/Entity.h b/goalc/build_level/Entity.h new file mode 100644 index 000000000..ea84dc532 --- /dev/null +++ b/goalc/build_level/Entity.h @@ -0,0 +1,36 @@ +#pragma once + +#include "goalc/build_level/ResLump.h" + +#include "third-party/json.hpp" + +/* + * (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; + u32 aid = 0; + math::Vector4f trans; // w = 1 here + 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); \ No newline at end of file diff --git a/goalc/build_level/LevelFile.cpp b/goalc/build_level/LevelFile.cpp index 92a931707..4195fb471 100644 --- a/goalc/build_level/LevelFile.cpp +++ b/goalc/build_level/LevelFile.cpp @@ -56,6 +56,15 @@ size_t DrawableTreeArray::add_to_object_file(DataObjectGenerator& gen) const { 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"); @@ -87,6 +96,7 @@ std::vector LevelFile::save_object_file() const { 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) @@ -99,6 +109,7 @@ std::vector LevelFile::save_object_file() const { //(unk-data-5 float :offset-assert 164) //(adgifs adgif-shader-array :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)); //(split-box-indices (pointer uint16) :offset-assert 176) //(unk-data-8 uint32 55 :offset-assert 180) diff --git a/goalc/build_level/LevelFile.h b/goalc/build_level/LevelFile.h index 5ae7e48d7..312b9a17e 100644 --- a/goalc/build_level/LevelFile.h +++ b/goalc/build_level/LevelFile.h @@ -6,6 +6,7 @@ #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/collide_bvh.h" @@ -41,10 +42,6 @@ struct TextureId {}; struct VisInfo {}; -struct DrawableActor {}; - -struct DrawableInlineArrayActor {}; - struct EntityCamera {}; struct BspNode {}; @@ -94,7 +91,7 @@ struct LevelFile { std::array vis_infos; // (actors drawable-inline-array-actor :offset-assert 112) - DrawableInlineArrayActor actors; + std::vector actors; // (cameras (array entity-camera) :offset-assert 116) std::vector cameras; diff --git a/goalc/build_level/ResLump.cpp b/goalc/build_level/ResLump.cpp index 541d37a11..83bcebd9d 100644 --- a/goalc/build_level/ResLump.cpp +++ b/goalc/build_level/ResLump.cpp @@ -1,5 +1,314 @@ #include "ResLump.h" +#include + +#include "common/util/BitUtils.h" + #include "goalc/data_compiler/DataObjectGenerator.h" #include "third-party/fmt/core.h" + +/* + * name: crate-3141 + * .symbol name + .word 0xce6e6b28 + .type string + .word 0x10000 + +scale = 1., 1., 1., 1., + .symbol scale + .word 0xce6e6b28 + .type vector + .word 0x80010010 + + .symbol visvol + .word 0xce6e6b28 + .type vector + .word 0x80020020 + + .symbol shadow-mask + .word 0xce6e6b28 + .type uint8 + .word 0x80010040 + + .symbol eco-info + .word 0xce6e6b28 + .type int32 + .word 0x80020044 + + .symbol movie-pos + .word 0xce6e6b28 + .type vector + .word 0x80010050 + .symbol vis-dist + .word 0xce6e6b28 + .type float + .word 0x80010060 + */ + +Res::Res(const std::string& name, float key_frame) : m_name(name), m_key_frame(key_frame) {} + +ResFloat::ResFloat(const std::string& name, const std::vector& values, float key_frame) + : Res(name, key_frame), m_values(values) {} + +TagInfo ResFloat::get_tag_info() const { + TagInfo result; + result.elt_type = "float"; + result.elt_count = m_values.size(); + result.inlined = true; + result.data_size = m_values.size() * sizeof(float); + return result; +} + +void ResFloat::write_data(DataObjectGenerator& gen) const { + for (auto& val : m_values) { + gen.add_word_float(val); + } +} + +int ResFloat::get_alignment() const { + return 16; +} + +ResInt32::ResInt32(const std::string& name, const std::vector& values, float key_frame) + : Res(name, key_frame), m_values(values) {} + +TagInfo ResInt32::get_tag_info() const { + TagInfo result; + result.elt_type = "int32"; + result.elt_count = m_values.size(); + result.inlined = true; + result.data_size = m_values.size() * sizeof(s32); + return result; +} + +void ResInt32::write_data(DataObjectGenerator& gen) const { + for (auto& val : m_values) { + gen.add_word(val); + } +} + +int ResInt32::get_alignment() const { + return 16; +} + +ResUint8::ResUint8(const std::string& name, const std::vector& values, float key_frame) + : Res(name, key_frame), m_values(values) {} + +TagInfo ResUint8::get_tag_info() const { + TagInfo result; + result.elt_type = "uint8"; + result.elt_count = m_values.size(); + result.inlined = true; + result.data_size = align4(m_values.size()) * sizeof(u8); + return result; +} + +void ResUint8::write_data(DataObjectGenerator& gen) const { + u32 size_words = align4(m_values.size()) / 4; + auto offset = gen.current_offset_bytes(); + for (u32 i = 0; i < size_words; i++) { + gen.add_word(0); + } + memcpy(gen.data() + offset, m_values.data(), m_values.size()); +} + +int ResUint8::get_alignment() const { + return 16; +} + +ResVector::ResVector(const std::string& name, + const std::vector& values, + float key_frame) + : Res(name, key_frame), m_values(values) {} + +TagInfo ResVector::get_tag_info() const { + TagInfo result; + result.elt_type = "vector"; + result.elt_count = m_values.size(); + result.inlined = true; + result.data_size = m_values.size() * sizeof(math::Vector4f); + return result; +} + +void ResVector::write_data(DataObjectGenerator& gen) const { + for (auto& val : m_values) { + for (int i = 0; i < 4; i++) { + gen.add_word_float(val[i]); + } + } +} + +int ResVector::get_alignment() const { + return 16; +} + +ResString::ResString(const std::string& name, const std::vector& str, float key_frame) + : Res(name, key_frame), m_str(str) {} + +ResString::ResString(const std::string& name, const std::string& str, float key_frame) + : Res(name, key_frame), m_str({str}) {} + +TagInfo ResString::get_tag_info() const { + TagInfo result; + result.elt_type = "string"; + result.elt_count = m_str.size(); + result.inlined = false; + result.data_size = 4 * m_str.size(); + return result; +} + +void ResString::write_data(DataObjectGenerator& gen) const { + for (auto& str : m_str) { + gen.add_ref_to_string_in_pool(str); + } +} + +int ResString::get_alignment() const { + return 4; +} + +ResSymbol::ResSymbol(const std::string& name, const std::vector& str, float key_frame) + : Res(name, key_frame), m_str(str) {} + +ResSymbol::ResSymbol(const std::string& name, const std::string& str, float key_frame) + : Res(name, key_frame), m_str({str}) {} + +TagInfo ResSymbol::get_tag_info() const { + TagInfo result; + result.elt_type = "symbol"; + result.elt_count = m_str.size(); + result.inlined = false; + result.data_size = 4 * m_str.size(); + return result; +} + +void ResSymbol::write_data(DataObjectGenerator& gen) const { + for (auto& str : m_str) { + gen.add_symbol_link(str); + } +} + +int ResSymbol::get_alignment() const { + return 4; +} + +void ResLump::add_res(std::unique_ptr res) { + m_sorted = false; + m_res.emplace_back(std::move(res)); +} + +constexpr int kExtraTagSlots = 10; + +void ResLump::sort_res() { + std::stable_sort(m_res.begin(), m_res.end(), + [](const std::unique_ptr& a, const std::unique_ptr& b) { + u64 a_chars = 0; + u64 b_chars = 0; + auto& a_name = a->name(); + auto& b_name = b->name(); + memcpy(&a_chars, a_name.data(), std::min(sizeof(u64), a_name.size())); + memcpy(&b_chars, b_name.data(), std::min(sizeof(u64), b_name.size())); + return a_chars < b_chars; + }); + m_sorted = true; +} + +size_t ResLump::generate_header(DataObjectGenerator& gen, + const std::string& most_specific_type) const { + gen.align_to_basic(); + gen.add_type_tag(most_specific_type); + auto result = gen.current_offset_bytes(); + gen.add_word(m_res.size()); // length; + gen.add_word(m_res.size() + kExtraTagSlots); // allocated-length + gen.add_word(0); // data base + gen.add_word(0); // data top + gen.add_word(0); // data size; + gen.add_word(0); // extra + gen.add_word(0); // tag. + return result; +} + +void ResLump::generate_tag_list_and_data(DataObjectGenerator& gen, size_t header_to_update) const { + ASSERT(m_sorted); + gen.align_to_basic(); + // first is the tag array. + const size_t tag_array_start = gen.current_offset_bytes(); + const size_t tag_array_size = 16 * (m_res.size() + kExtraTagSlots); + const size_t tag_array_end = tag_array_start + tag_array_size; + + // next is data + const size_t data_start = align16(tag_array_end); + size_t current_data_ptr = data_start; + + // on the first pass through, we'll also build these: + struct ResRec { + size_t align; + size_t data; + size_t reported_size; + }; + std::vector recs; + + // first pass to write tags and figure out data layout + for (auto& res : m_res) { + auto& rec = recs.emplace_back(); + auto alignment = res->get_alignment(); + while (current_data_ptr % alignment) { + current_data_ptr++; + } + auto tag = res->get_tag_info(); + + // name: + gen.add_symbol_link(res->name()); + // key frame + gen.add_word_float(res->key_frame()); + // elt type + gen.add_type_tag(tag.elt_type); + + // packed + u32 packed = 0; + ASSERT(current_data_ptr - data_start < UINT16_MAX); + packed |= (u16)(current_data_ptr - data_start); + ASSERT(tag.elt_count < (UINT16_MAX >> 1)); + packed |= (((u16)tag.elt_count) << 16); + if (tag.inlined) { + packed |= (1 << 31); + } + gen.add_word(packed); + rec.data = current_data_ptr; + rec.reported_size = tag.data_size; + rec.align = alignment; + current_data_ptr += tag.data_size; + } + for (int i = 0; i < kExtraTagSlots * 4; i++) { + gen.add_word(0); + } + const size_t data_end = current_data_ptr; // todo, does this get rounded up at all? + + gen.align_to_basic(); + ASSERT(gen.current_offset_bytes() == data_start); + current_data_ptr = data_start; + + // second pass to write data + for (size_t res_idx = 0; res_idx < m_res.size(); res_idx++) { + const auto& res = m_res[res_idx]; + const auto& rec = recs[res_idx]; + // pad! + while (current_data_ptr % rec.align) { + current_data_ptr += 4; + gen.add_word(0); + } + + res->write_data(gen); + ASSERT(gen.current_offset_bytes() - current_data_ptr == rec.reported_size); + current_data_ptr = gen.current_offset_bytes(); + } + ASSERT(gen.current_offset_bytes() == data_end); + ASSERT(data_end == current_data_ptr); + + // update header + gen.link_word_to_byte((header_to_update + 2 * 4) / 4, data_start); + gen.link_word_to_byte((header_to_update + 3 * 4) / 4, data_end); + gen.set_word((header_to_update + 4 * 4) / 4, data_end - data_start); + gen.link_word_to_byte((header_to_update + 6 * 4) / 4, tag_array_start); +} \ No newline at end of file diff --git a/goalc/build_level/ResLump.h b/goalc/build_level/ResLump.h index 865faeeef..991926f2a 100644 --- a/goalc/build_level/ResLump.h +++ b/goalc/build_level/ResLump.h @@ -5,3 +5,134 @@ #include #include "common/common_types.h" +#include "common/math/Vector.h" + +/* + (deftype res-tag (uint128) + ((name symbol :offset 0) + (key-frame float :offset 32) + (elt-type type :offset 64) + (data-offset uint16 :offset 96) + (elt-count uint32 :offset 112 :size 15) + (inlined? uint8 :offset 127 :size 1) ;; guess. + ) + :flag-assert #x900000010 + ) + */ + +class DataObjectGenerator; + +struct TagInfo { + std::string elt_type; + u32 elt_count = 0; + bool inlined = false; + u32 data_size = 0; +}; + +class Res { + public: + Res(const std::string& name, float key_frame); + const std::string& name() const { return m_name; } + float key_frame() const { return m_key_frame; } + virtual TagInfo get_tag_info() const = 0; + virtual void write_data(DataObjectGenerator& gen) const = 0; + virtual int get_alignment() const = 0; + virtual ~Res() = default; + + private: + std::string m_name; + float m_key_frame = 0; +}; + +class ResFloat : public Res { + public: + ResFloat(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 ResInt32 : public Res { + public: + ResInt32(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 ResUint8 : public Res { + public: + ResUint8(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); + TagInfo get_tag_info() const override; + void write_data(DataObjectGenerator& gen) const override; + int get_alignment() const override; + + private: + std::vector m_values; +}; + +class ResString : public Res { + public: + ResString(const std::string& name, const std::vector& str, float key_frame); + ResString(const std::string& name, const std::string& str, 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_str; +}; + +class ResSymbol : public Res { + public: + ResSymbol(const std::string& name, const std::vector& str, float key_frame); + ResSymbol(const std::string& name, const std::string& str, 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_str; +}; + +/* +(deftype res-lump (basic) + ((length int32 :offset-assert 4) + (allocated-length int32 :offset-assert 8) + (data-base pointer :offset-assert 12) + (data-top pointer :offset-assert 16) + (data-size int32 :offset-assert 20) + (extra entity-links :offset-assert 24) ; looks like 0 here + (tag (pointer res-tag) :offset-assert 28) + */ +class ResLump { + public: + void add_res(std::unique_ptr res); + void sort_res(); + // extra tag slots seems to be 10, in all cases? + size_t generate_header(DataObjectGenerator& gen, const std::string& most_specific_type) const; + void generate_tag_list_and_data(DataObjectGenerator& gen, size_t header_to_update) const; + + private: + std::vector> m_res; + bool m_sorted = false; +}; \ No newline at end of file diff --git a/goalc/build_level/build_level.cpp b/goalc/build_level/build_level.cpp index cc5a77a13..a8da5a184 100644 --- a/goalc/build_level/build_level.cpp +++ b/goalc/build_level/build_level.cpp @@ -4,6 +4,7 @@ #include "common/util/compress.h" #include "common/util/json_util.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" @@ -43,6 +44,8 @@ bool run_build_level(const std::string& input_file, const std::string& output_fi 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.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); @@ -61,13 +64,19 @@ bool run_build_level(const std::string& input_file, const std::string& output_fi file.nickname = level_json.at("nickname").get(); // vis infos // actors + std::vector actors; + add_actors_from_json(level_json.at("actors"), actors, 1234); + file.actors = std::move(actors); // cameras // nodes // boxes // ambients // subdivs // adgifs - // actor birht + // actor birth + for (size_t i = 0; i < file.actors.size(); i++) { + file.actor_birth_order.push_back(i); + } // split box // add stuff to the PC level structure diff --git a/goalc/build_level/collide_bvh.cpp b/goalc/build_level/collide_bvh.cpp index ece6a40b3..719d12a34 100644 --- a/goalc/build_level/collide_bvh.cpp +++ b/goalc/build_level/collide_bvh.cpp @@ -32,7 +32,9 @@ struct CNode { struct BBox { math::Vector3f mins, maxs; std::string sz_to_string() const { - return fmt::format("({})", ((maxs - mins) / 4096.f).to_string_aligned()); + return fmt::format("{} {} ({})", (mins / 4096.f).to_string_aligned(), + (maxs / 4096.f).to_string_aligned(), + ((maxs - mins) / 4096.f).to_string_aligned()); } }; @@ -79,10 +81,54 @@ void update_bsphere_recursive(const CNode& node, const math::Vector3f& origin, f } } +void collect_vertices(const CNode& node, std::vector& verts) { + for (auto& child : node.child_nodes) { + collect_vertices(child, verts); + } + for (auto& face : node.faces) { + verts.push_back(face.v[0]); + verts.push_back(face.v[1]); + verts.push_back(face.v[2]); + } +} + +size_t find_most_distant(math::Vector3f pt, const std::vector& verts) { + float max_dist_squared = 0; + size_t idx_of_best = 0; + for (size_t i = 0; i < verts.size(); i++) { + float dist = (pt - verts[i]).squared_length(); + if (dist > max_dist_squared) { + max_dist_squared = dist; + idx_of_best = i; + } + } + return idx_of_best; +} + +void compute_my_bsphere_ritters(CNode& node) { + std::vector verts; + collect_vertices(node, verts); + ASSERT(verts.size() > 0); + auto px = verts[0]; + auto py = verts[find_most_distant(px, verts)]; + auto pz = verts[find_most_distant(py, verts)]; + + auto origin = (pz + py) / 2.f; + node.bsphere.x() = origin.x(); + node.bsphere.y() = origin.y(); + node.bsphere.z() = origin.z(); + + float max_squared = 0; + for (auto& pt : verts) { + max_squared = std::max(max_squared, (pt - origin).squared_length()); + } + node.bsphere.w() = std::sqrt(max_squared); +} + /*! * Compute the bsphere of a single node. */ -void compute_my_bsphere(CNode& node) { +BBox compute_my_bsphere_bad(CNode& node) { // first compute bbox. BBox bbox = bbox_of_node(node); float r = 0; @@ -92,6 +138,7 @@ void compute_my_bsphere(CNode& node) { node.bsphere.y() = origin.y(); node.bsphere.z() = origin.z(); node.bsphere.w() = std::sqrt(r); + return bbox; } /*! @@ -114,6 +161,7 @@ void split_along_dim(std::vector& faces, * Split a node into two nodes. The outputs should be uninitialized nodes */ void split_node_once(CNode& node, CNode* out0, CNode* out1) { + compute_my_bsphere_ritters(node); CNode temps[6]; // split_along_dim(node.faces, pick_dim_for_split(node.faces), &out0->faces, &out1->faces); split_along_dim(node.faces, 0, &temps[0].faces, &temps[1].faces); @@ -121,10 +169,11 @@ void split_node_once(CNode& node, CNode* out0, CNode* out1) { split_along_dim(node.faces, 2, &temps[4].faces, &temps[5].faces); node.faces.clear(); for (auto& t : temps) { - compute_my_bsphere(t); + compute_my_bsphere_ritters(t); } float max_bspheres[3] = {0, 0, 0}; + for (int i = 0; i < 3; i++) { max_bspheres[i] = std::max(temps[i * 2].bsphere.w(), temps[i * 2 + 1].bsphere.w()); } @@ -206,7 +255,7 @@ void split_as_needed(CNode& root) { num_leaves *= 8; lg::info("after splitting, the worst leaf has {} tris, {} radius", worst.max_leaf_count, worst.max_bsphere_w / 4096.f); - if (worst.max_leaf_count < MAX_FACES_IN_FRAG && worst.max_bsphere_w < (100.f * 4096.f)) { + if (worst.max_leaf_count < MAX_FACES_IN_FRAG && worst.max_bsphere_w < (125.f * 4096.f)) { need_to_split = false; } } @@ -219,7 +268,7 @@ void split_as_needed(CNode& root) { * (note that we don't do bspheres of bspheres... I think this is better?) */ void bsphere_recursive(CNode& node) { - compute_my_bsphere(node); + compute_my_bsphere_ritters(node); for (auto& child : node.child_nodes) { bsphere_recursive(child); } diff --git a/goalc/build_level/gltf_mesh_extract.cpp b/goalc/build_level/gltf_mesh_extract.cpp index 741530001..12b77d68e 100644 --- a/goalc/build_level/gltf_mesh_extract.cpp +++ b/goalc/build_level/gltf_mesh_extract.cpp @@ -665,7 +665,7 @@ std::optional> subdivide_face_if_needed(CollideFace fac } } -void extract(const Input& /*in*/, +void extract(const Input& in, CollideOutput& out, const tinygltf::Model& model, const std::vector& all_nodes) { @@ -718,7 +718,7 @@ void extract(const Input& /*in*/, } face.bsphere = math::bsphere_of_triangle(face.v); - face.bsphere.w() += 1e-1; + face.bsphere.w() += 1e-1 * 5; for (int j = 0; j < 3; j++) { float output_dist = face.bsphere.w() - (face.bsphere.xyz() - face.v[j]).length(); if (output_dist < 0) { @@ -748,6 +748,21 @@ void extract(const Input& /*in*/, } out.faces = std::move(fixed_faces); + if (in.auto_wall_enable) { + lg::info("automatically detecting walls with angle {}", in.auto_wall_angle); + int wall_count = 0; + float wall_cos = std::cos(in.auto_wall_angle * 2.f * 3.14159 / 360.f); + for (auto& face : out.faces) { + 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); + wall_count++; + } + } + lg::info("automatic wall: {}/{} converted to walls", wall_count, out.faces.size()); + } + lg::info("{} out of {} faces appeared to have wrong orientation and were flipped", suspicious_faces, out.faces.size()); lg::info("{} faces were too big and were subdivided", fix_count); diff --git a/goalc/build_level/gltf_mesh_extract.h b/goalc/build_level/gltf_mesh_extract.h index f429f07b9..2980a8873 100644 --- a/goalc/build_level/gltf_mesh_extract.h +++ b/goalc/build_level/gltf_mesh_extract.h @@ -13,6 +13,8 @@ struct Input { std::string filename; TexturePool* tex_pool = nullptr; bool get_colors = true; + bool auto_wall_enable = true; + float auto_wall_angle = 30.f; }; struct TfragOutput {