diff --git a/CMakeLists.txt b/CMakeLists.txt index 74f9cf2f..dcd37a86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,15 +9,15 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}) file(GLOB MDL_SRC_FILES src/mdl-to-json/*.cpp) file(GLOB DTS_SRC_FILES src/dts-to-json/*.cpp) -file(GLOB OBJ_SRC_FILES src/dts-to-obj/*.cpp) +file(GLOB OBJ_SRC_FILES src/dts_renderable_shape.cpp src/dts-to-obj/*.cpp) file(GLOB JSON_SRC_FILES src/json-to-dts/*.cpp) -file(GLOB DTS_VIEWER_SRC_FILES src/dts-viewer/*.cpp) +file(GLOB DTS_VIEWER_SRC_FILES src/dts_renderable_shape.cpp src/3space-studio/utility.cpp src/3space-studio/*.cpp) add_executable(mdl-to-json ${MDL_SRC_FILES}) add_executable(dts-to-json ${DTS_SRC_FILES}) add_executable(dts-to-obj ${OBJ_SRC_FILES}) add_executable(json-to-dts ${JSON_SRC_FILES}) -add_executable(dts-viewer ${DTS_VIEWER_SRC_FILES}) +add_executable(3space-studio ${DTS_VIEWER_SRC_FILES}) include_directories(${CONAN_INCLUDE_DIRS}) include_directories(packages/include) @@ -27,18 +27,18 @@ conan_target_link_libraries(mdl-to-json) conan_target_link_libraries(dts-to-json) conan_target_link_libraries(dts-to-obj) conan_target_link_libraries(json-to-dts) -target_link_libraries(dts-viewer PRIVATE ${CONAN_LIBS}) +target_link_libraries(3space-studio PRIVATE ${CONAN_LIBS}) if(MSVC) target_compile_options(mdl-to-json PRIVATE /W4 /WX $<$:/O2>) target_compile_options(dts-to-json PRIVATE /W4 /WX $<$:/O2>) - target_compile_options(dts-to-obj PRIVATE /W4 /WX $<$:/O2>) + target_compile_options(dts-to-obj PRIVATE /W3 /WX $<$:/O2>) target_compile_options(json-to-dts PRIVATE /W4 /WX $<$:/O2>) - target_compile_options(dts-viewer PRIVATE $<$:/O2>) + target_compile_options(3space-studio PRIVATE $<$:/O2>) else() target_compile_options(mdl-to-json PRIVATE -Wall -Wextra -Werror -pedantic $<$:-O3>) target_compile_options(dts-to-json PRIVATE -Wall -Wextra -Werror -pedantic $<$:-O3>) target_compile_options(dts-to-obj PRIVATE -Wall -Wextra -Werror -pedantic $<$:-O3>) target_compile_options(json-to-dts PRIVATE -Wall -Wextra -Werror -pedantic $<$:-O3>) - target_compile_options(dts-viewer PRIVATE $<$:-O3>) + target_compile_options(3space-studio PRIVATE $<$:-O3>) endif() \ No newline at end of file diff --git a/README.md b/README.md index a4d08a6f..939ca330 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,102 @@ -# Darkstar DTS Converter +# 3Space Studio -### Summary -A set of C++ tools for converting to and from the DTS and DML formats used in the following games: -* Starsiege -* Starsiege Tribes -* Front Page Sports Ski Racing -* Trophy Bass 3D -* Driver's Education 98/99 - -Current converting DTS and DML files to JSON is supported, and soon OBJ, which was in the original Python version of this project. +![](https://openclipart.org/image/400px/svg_to_png/97921/rubik-3D-colored.png) -This is the third generation of the DTS format, so versions will be described as 3.x in the documentation, but as simply x in the file format itself. - -Darkstar DTS versions 3.2, 3.3, 3.5, 3.6, 3.7 and 3.8 can be processed, which covers files from each of the games above. +### Summary -Inspired by a DTS converter for Earthsiege 2 (https://github.com/booto/convert_dts) +Classic Games, Modern Technology. + +3Space Studio exists to breathe new life into games made by Dynamix, which use the 3Space engine and its descendants, while also reverse engineering their formats for fun, modding and preservation. + +For a list of current development tasks, check out this ClickUp board here: https://share.clickup.com/b/h/5-15151441-2/aeb3a2cc99994ef + +A first goal for each game is to do the following: + +* Support the viewing of 3D assets from various games (specifically DTS - Dynamix Three Space files). +* Support the extraction of archive files for specified games (typically VOL - a game volume archive file). +* Support viewing of texture data for the 3D assets and the games in question. + +Secondary goals include: + +* The ability to save new 3D assets using the supported format for that game. +* The ability to do some basic manipulation of the 3D assets. +* The ability to convert the 3D asset to a more common format for use in other 3D software. + +Tertiary goals would be: + +* Game specific file format support, for example VEH for Starsiege, which would allow for editing of vehicle load-outs and saving them back to the file system. +* Support of other game formats which serve a similar function (like MDL or PAK from Quake). +* Become a tool of choice for modding games using 3Space or Torque technology and potentially other game engines. + +### Game Support + +Because the 3Space engine has a long history, and has morphed into engines with new names, here is a matrix of the games, most of which were made by Dynamix, (focusing specifically on DOS or Windows) which are intended to be supported or are supported: + +| Game | Engine | Release Date | Formats: | Model | Texture | Archive | Other | +| ------------------------------------------------------------ | ----------------------------------- | -------------- | -------- | ------------------------------------------------------------ | ---------------------------- | ------------------ | ------------------------------------------ | +| Arcticfox | 3Space 1 | 1987 (for DOS) | | ? | ? | ? | | +| A-10 Tank Killer | 3Space 1 | 1989 (for DOS) | | | ? | ? | | +| Abrams Battle Tank | 3Space 1 | 1989 (for DOS) | | ? | ? | ? | | +| David Wolf Secret Agent | 3Space 1 | 1989 | | ? | ? | ? | | +| DeathTrack | 3Space 1 | 1989 | | ? | ? | ? | | +| Die Hard | 3Space 1 | 1989 | | ? | ? | ? | | +| MechWarrior | 3Space 1 | 1989 (for DOS) | | ? | ? | ? | | +| F-14 Tomcat | 3Space 1 | 1990 (for DOS) | | ? | ? | ? | | +| Red Baron | 3Space 1.5 | 1990 (for DOS) | | ? | ? | RMF❌ | | +| Stellar 7 (re-release) | 3Space 1.5 | 1990 (for DOS) | | ? | ? | ? | | +| A-10 Tank Killer 1.5 | 3Space 1.5 | 1991 | | ? | ? | RMF❌ | | +| Nova 9: The Return of Gir Draxon | 3Space 1.5 | 1991 (for DOS) | | ? | ? | RMF❌ | | +| Aces of the Pacific | 3Space 1.5 | 1992 | | ? | ? | DYN❌ | | +| Aces Over Europe | 3Space 1.5 | 1993 | | ? | ? | DYN❌ | | +| Aces of the Deep | 3Space 2.0 | 1994 | | DTS❌ | ? | * DYN❌
* VOL❌ | | +| Metaltech: Battledrome | 3Space 2.0 | 1994 | | * DCS?❌
* DTS❌ | ? | ? | | +| Metaltech: Earthsiege | 3Space 2.0 | 1994 | | DTS❌ | ? | VOL❌ | | +| Command: Aces of the Deep | 3Space 2.0 | 1995 | | DTS❌ | ? | * DYN❌
* VOL❌ | | +| Silent Thunder: A-10 Tank Killer 2 | 3Space 2.0 | 1996 | | DTS❌ | BMP❌ | VOL❌ | | +| Red Baron 2 | 3Space 2.0 | 1997 | | DTS❌ | BMP❌ | VOL❌ | | +| Pro Pilot '98 | 3Space 2.0 | 1997 | | DTS❌ | BMP❌ | VOL❌ | | +| Front Page Sports: Ski Racing | 3Space 3.0 aka Darkstar | 1997 | | DTS✅ | BMP❌ | VOL❌ | | +| Red Baron 3D | 3Space 2.0 | 1998 | | DTS❌ | BMP❌ | VOL❌ | | +| Pro Pilot '99 | 3Space 2.0 | 1998 | | DTS❌ | BMP❌ | VOL❌ | | +| Driver's Education '98 | 3Space 3.0 aka Darkstar (partially) | 1998 | | DTS❌(has special version of Darkstar DTS which is not yet supported) | BMP❌ | VOL❌ | | +| Starsiege | 3Space 3.0 aka Darkstar | 1999 | | DTS✅ | * BMP❌
* PBA❌ | VOL❌ | * VEH❌
* MIS❌
* GUI❌
* DLG❌ | +| Starsiege: Tribes | 3Space 3.0 aka Darkstar | 1999 | | DTS✅ | * BMP❌
* PBA❌ | VOL❌ | | +| Driver's Education '99 | 3Space 3.0 aka Darkstar (partially) | 1998 | | DTS❌(has special version of Darkstar DTS which is not yet supported) | BMP❌ | VOL❌ | | +| Field & Stream: Trophy Bass 3D | 3Space 3.0 aka Darkstar (partially) | 1999 | | DTS✅ | BMP❌ | VOL❌
| | +| Tribes 2 | Torque | 2001 | | DTS❌ | ? | VL2 (ZIP)❌ | DSO❌ | +| Notable post-Dynamix games: | | | | | | | | +| Marble Blast Gold | Torque | 2002 | | DTS❌ | ? | ? | DSO❌ | +| Marble Blast Ultra | Torque | 2006 | | DTS❌ | ? | ? | DSO❌ | +| Dark Horizons - Lore | Torque | 2006 | | DTS❌ | ? | ? | DSO❌ | +| Legions: Overdrive (re-release) | Torque | 2010 | | DTS❌ | ? | ? | DSO❌ | +| Blockland (Steam) | Torque | 2013 | | DTS❌ | ? | ? | DSO❌ | +| BeamNG.drive | Torque | 2015 | | DTS❌ | ? | ? | DSO❌ | +| Wacky Wheels HD | Torque | 2016 | | DTS❌ | ? | ? | DSO❌ | +| More Torque games: https://github.com/John3/awesome-torque3d#games | Torque | - | | DTS❌ | * PNG
* DDS
* More | VL2 (ZIP)❌ | DSO❌ | + +Supported format matrix: + +| Format | Reading/Unpacking | Writing/Packing | Rendering/Viewing | Editing | Converting | Affects Games | +| ------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ----------------- | ------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 3Space 3.0 DTS aka Darkstar DTS | ✅ | ✅ | ✅ | ❌ | To:
* OBJ✅
* JSON✅
* Torque DTS❌
From:
* OBJ❌
* JSON✅
* Torque DTS❌ | * Front Page Sports: Ski Racing✅
* Driver's Education '98❌
* Starsiege✅
* Starsiege: Tribes✅
* Driver's Education '99❌
* Field & Stream: Trophy Bass 3D✅ | +| 3Space 3.0 VOL aka Darkstar VOL | Only via https://github.com/matthew-rindel/darkstar-vol-extractor
Rewrite and merging of codebase on backlog. | Only via https://github.com/matthew-rindel/darkstar-vol-extractor
Rewrite and merging of codebase on backlog. | ❌ | ❌ | ❌ | * Front Page Sports: Ski Racing❌
* Driver's Education '98❌
* Starsiege❌
* Starsiege: Tribes❌
* Driver's Education '99❌
* Field & Stream: Trophy Bass 3D❌ | +| 3Space 2.0 RMF | Only via https://github.com/matthew-rindel/3space-vol-extractor
Rewrite and merging of codebase on backlog. | ❌ | ❌ | ❌ | ❌ | * Red Baron❌
* A-10 Tank-Killer 1.5❌
* Nova 9❌ | +| 3Space 2.0 DYN | Only via https://github.com/matthew-rindel/3space-vol-extractor
Rewrite and merging of codebase on backlog. | ❌ | ❌ | ❌ | ❌ | * Aces of the Pacific❌
* Aces over Europe❌
* Aces of the Deep❌ | +| 3Space 2.0 VOL | Only via https://github.com/matthew-rindel/3space-vol-extractor
Rewrite and merging of codebase on backlog. | ❌ | ❌ | ❌ | ❌ | * Metaltech: Earthsiege❌
* Metaltech: Battledrome❌
* Earthsiege 2❌
* Aces of the Deep❌ | +| Starsiege VEH | Only via https://github.com/matthew-rindel/starsiege-veh-editor
Rewrite and merging of codebase on backlog | ❌ | ❌ | ❌ | ❌ | Starsiege❌ | +| Starsiege MIS | Only via https://github.com/matthew-rindel/darkstar-mis-extractor
Rewrite and merging of codebase on backlog | ❌ | ❌ | ❌ | ❌ | Starsiege❌ | + + + +### Format Background + +Despite each version of the engine having some of the same extensions for some of their formats, each iteration of the engine has completely different structures and binary layouts for said formats. + +In other words, 3Space 2.0 DTS files are fundamentally different to 3Space 3.0 DTS files, which are in turn completely different to Torque DTS files. + +They do share a similar high level structure and some features, but the overall format changes over time and even between games there is a big difference between the format used. + +For example, while Earthsiege and Red Baron 2 might share a 3Space 2.0 core (of sorts), the DTS files themselves have different version tags for each entity and need different code to handle them. Depending on how different they are, they may need completely separate implementations for parsing and viewing. ### Setup and Build Instructions @@ -39,6 +121,10 @@ Generated files will go into the **build/bin** folder. ### Usage Instructions +#### 3space-studio + +TODO: write a decent description of how to use the 3Space Studio GUI. + #### dts-to-json With dts-to-json, you can convert either individual or multiple DTS or DML files to JSON. @@ -66,10 +152,3 @@ The result will be a new DTS or DML file that can be put back into the game of c If an existing DTS or DML file is present, then it will be renamed with the extension **.old** appended to it. Any existing **.old** files will not be overwritten for backup purposes of the original file being modified. - -### Known Issues -* A small amount of files from Tribes have _null_ instead of a number in the JSON output. -* DTS files from Driver's Education have a variant of DTS 3.5, which is not fully supported currently. - -### Other Projects -See here: https://github.com/matthew-rindel/darkstar-projects diff --git a/conanfile.py b/conanfile.py index e8e76da2..d7266c5f 100644 --- a/conanfile.py +++ b/conanfile.py @@ -5,7 +5,7 @@ class LocalConanFile(ConanFile): system_requires = "opengl/system" build_requires = "cmake/3.17.3", "cppcheck_installer/2.0@bincrafters/stable" settings = "os", "compiler", "build_type", "arch" - requires = "toml11/3.4.0", "nlohmann_json/3.9.0", "boost_endian/1.69.0@bincrafters/stable", "imgui-sfml/2.1@bincrafters/stable", "wxwidgets/3.1.3@bincrafters/stable" + requires = "toml11/3.4.0", "nlohmann_json/3.9.0", "boost_endian/1.69.0@bincrafters/stable", "imgui-sfml/2.1@bincrafters/stable", "wxwidgets/3.1.3@bincrafters/stable", "glm/0.9.9.8" generators = "cmake", "virtualenv" build_folder = "build" diff --git a/src/3space-studio/dts-viewer.cpp b/src/3space-studio/dts-viewer.cpp new file mode 100644 index 00000000..effabf80 --- /dev/null +++ b/src/3space-studio/dts-viewer.cpp @@ -0,0 +1,736 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "dts_io.hpp" +#include "dts_renderable_shape.hpp" +#include "gl_renderer.hpp" +#include "sfml_keys.hpp" +#include "utility.hpp" + +namespace fs = std::filesystem; +namespace dts = darkstar::dts; + +struct shape_instance +{ + std::unique_ptr shape; + + glm::vec3 translation; + dts::vector3f rotation; +}; + +std::optional get_shape_path() +{ + constexpr static auto filetypes = "Darkstar DTS files|*.dts;*.DTS"; + auto dialog = std::make_unique(nullptr, "Open a Darkstar DTS File", "", "", filetypes, wxFD_OPEN, wxDefaultPosition); + + if (dialog->ShowModal() == wxID_OK) + { + const auto buffer = dialog->GetPath().ToAscii(); + return std::string_view{ buffer.data(), buffer.length() }; + } + + return std::nullopt; +} + +std::optional get_workspace_path() +{ + auto dialog = std::make_unique(nullptr, "Open a folder to use as a workspace"); + + if (dialog->ShowModal() == wxID_OK) + { + const auto buffer = dialog->GetPath().ToAscii(); + return std::string_view{ buffer.data(), buffer.length() }; + } + + return std::nullopt; +} + +std::optional get_shape_path(int argc, char** argv) +{ + if (argc > 1) + { + return argv[1]; + } + + return get_shape_path(); +} + +dts::shape_variant get_shape(std::optional shape_path) +{ + if (!shape_path.has_value()) + { + return dts::shape_variant{}; + } + + try + { + std::basic_ifstream input(shape_path.value(), std::ios::binary); + return dts::read_shape(shape_path.value(), input, std::nullopt); + } + catch (const std::exception& ex) + { + wxMessageBox(ex.what(), "Error Loading Model.", wxICON_ERROR); + return dts::shape_variant{}; + } +} + +void setup_opengl(wxControl* parent) +{ + auto [width, height] = parent->GetClientSize(); + + if (height == 0) + { + return; + } + + glViewport(0, 0, width, height); + + glClearDepth(1.f); + glClearColor(0.3f, 0.3f, 0.3f, 0.f); + + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_TRUE); + + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + perspectiveGL(90.f, double(width) / double(height), 1.f, 1200.0f); +} + +auto apply_configuration(std::map>& actions) +{ + //TODO read this from config one day + std::map>> callbacks; + callbacks.emplace(config::get_key_for_name("Numpad4"), std::ref(actions.at("pan_left"))); + callbacks.emplace(config::get_key_for_name("Numpad6"), std::ref(actions.at("pan_right"))); + callbacks.emplace(config::get_key_for_name("Numpad8"), std::ref(actions.at("pan_up"))); + callbacks.emplace(config::get_key_for_name("Numpad2"), std::ref(actions.at("pan_down"))); + callbacks.emplace(config::get_key_for_name("Numpad1"), std::ref(actions.at("increase_x_rotation"))); + callbacks.emplace(config::get_key_for_name("Numpad3"), std::ref(actions.at("decrease_x_rotation"))); + callbacks.emplace(config::get_key_for_name("Numpad7"), std::ref(actions.at("increase_z_rotation"))); + callbacks.emplace(config::get_key_for_name("Numpad9"), std::ref(actions.at("decrease_z_rotation"))); + + callbacks.emplace(config::get_key_for_name("Add"), std::ref(actions.at("zoom_in"))); + callbacks.emplace(config::get_key_for_name("Subtract"), std::ref(actions.at("zoom_out"))); + + callbacks.emplace(config::get_key_for_name("Divide"), std::ref(actions.at("increase_y_rotation"))); + callbacks.emplace(config::get_key_for_name("Insert"), std::ref(actions.at("increase_y_rotation"))); + callbacks.emplace(config::get_key_for_name("Numpad0"), std::ref(actions.at("increase_y_rotation"))); + + callbacks.emplace(config::get_key_for_name("Multiply"), std::ref(actions.at("decrease_y_rotation"))); + callbacks.emplace(config::get_key_for_name("Delete"), std::ref(actions.at("decrease_y_rotation"))); + callbacks.emplace(config::get_key_for_name("Period"), std::ref(actions.at("decrease_y_rotation"))); + + return callbacks; +} + + +void render_tree_view(const std::string& node, bool& node_visible, std::map, std::map>& visible_nodes, std::map>& visible_objects) +{ + ImGui::Checkbox(node.c_str(), &node_visible); + ImGui::Indent(8); + + if (visible_objects[node].size() > 1) + { + for (auto& [child_object, object_visible] : visible_objects[node]) + { + if (node == child_object) + { + ImGui::Checkbox((child_object + " (object)").c_str(), &object_visible); + } + else + { + ImGui::Checkbox(child_object.c_str(), &object_visible); + } + } + } + + for (auto& [child_node, child_node_visible] : visible_nodes[node]) + { + render_tree_view(child_node, child_node_visible, visible_nodes, visible_objects); + } + + ImGui::Unindent(8); +} + +auto renderer_main(std::optional shape_path, sf::RenderWindow* window, wxControl* parent, ImGuiContext* guiContext) +{ + static std::map, shape_instance> shape_instances; + + auto instance_iterator = shape_instances.emplace(shape_path, shape_instance{ std::make_unique(get_shape(shape_path)), { 0, 0, -20 }, { 115, 180, -35 } }); + auto& instance = instance_iterator.first; + + static std::map> actions; + + actions.emplace("increase_x_rotation", [](auto& instance) { instance.rotation.x++; }); + actions.emplace("decrease_x_rotation", [](auto& instance) { instance.rotation.x--; }); + + actions.emplace("increase_y_rotation", [](auto& instance) { instance.rotation.y++; }); + actions.emplace("decrease_y_rotation", [](auto& instance) { instance.rotation.y--; }); + actions.emplace("increase_z_rotation", [](auto& instance) { instance.rotation.z++; }); + actions.emplace("decrease_z_rotation", [](auto& instance) { instance.rotation.z--; }); + + actions.emplace("zoom_in", [](auto& instance) { instance.translation.z++; }); + actions.emplace("zoom_out", [](auto& instance) { instance.translation.z--; }); + + actions.emplace("pan_left", [](auto& instance) { instance.translation.x--; }); + actions.emplace("pan_right", [](auto& instance) { instance.translation.x++; }); + + actions.emplace("pan_up", [](auto& instance) { instance.translation.y++; }); + actions.emplace("pan_down", [](auto& instance) { instance.translation.y--; }); + + + std::map, std::map> visible_nodes; + std::map> visible_objects; + auto detail_levels = instance->second.shape->get_detail_levels(); + std::vector detail_level_indexes = { 0 }; + auto sequences = instance->second.shape->get_sequences(detail_level_indexes); + + bool root_visible = true; + + auto qRot1 = glm::quat(1.f, 0.f, 0.f, 0.f); + + auto callbacks = apply_configuration(actions); + + sf::Clock clock; + + setup_opengl(parent); + + return [=](auto& wx_event) mutable { + wxPaintDC Dc(parent); + + sf::Event event; + + ImGui::SetCurrentContext(guiContext); + + while (window->pollEvent(event)) + { + ImGui::SFML::ProcessEvent(event); + if (event.type == sf::Event::KeyPressed && (event.key.code != sf::Keyboard::Escape)) + { + const auto callback = callbacks.find(event.key.code); + + if (callback != callbacks.end()) + { + callback->second(instance->second); + } + } + + if (event.type == sf::Event::Closed) + { + window->close(); + } + + if ((event.type == sf::Event::KeyPressed) && (event.key.code == sf::Keyboard::Escape)) + { + window->close(); + } + } + + auto& [shape, translation, rotation] = instance->second; + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glTranslatef(translation.x, translation.y, translation.z); + + glRotatef(rotation.x, 1.f, 0.f, 0.f); + glRotatef(rotation.y, 0.f, 1.f, 0.f); + glRotatef(rotation.z, 0.f, 0.f, 1.f); + + glBegin(GL_TRIANGLES); + auto renderer = gl_renderer{ visible_nodes, visible_objects }; + shape->render_shape(renderer, detail_level_indexes, sequences); + glEnd(); + + window->pushGLStates(); + + ImGui::SFML::Update(*window, clock.restart()); + + if (!detail_levels.empty()) + { + ImGui::Begin("Details and Nodes"); + + if (ImGui::CollapsingHeader("Detail Levels", ImGuiTreeNodeFlags_::ImGuiTreeNodeFlags_DefaultOpen)) + { + for (auto i = 0u; i < detail_levels.size(); ++i) + { + auto selected_item = std::find_if(std::begin(detail_level_indexes), std::end(detail_level_indexes), [i](auto value) { + return value == i; + }); + + bool is_selected = selected_item != std::end(detail_level_indexes); + + if (ImGui::Checkbox(detail_levels[i].c_str(), &is_selected)) + { + if (is_selected) + { + detail_level_indexes.emplace_back(i); + sequences = shape->get_sequences(detail_level_indexes); + } + else if (selected_item != std::end(detail_level_indexes)) + { + detail_level_indexes.erase(selected_item); + sequences = shape->get_sequences(detail_level_indexes); + } + } + } + } + + if (ImGui::CollapsingHeader("Nodes", ImGuiTreeNodeFlags_::ImGuiTreeNodeFlags_DefaultOpen)) + { + for (auto index : detail_level_indexes) + { + render_tree_view(detail_levels[index], root_visible, visible_nodes, visible_objects); + } + } + + ImGui::End(); + } + + if (!sequences.empty()) + { + ImGui::Begin("Sequences"); + + if (ImGui::CollapsingHeader("Sequences", ImGuiTreeNodeFlags_::ImGuiTreeNodeFlags_DefaultOpen)) + { + for (auto it = sequences.begin(); it != sequences.end(); it++) + { + auto& sequence = *it; + + if (ImGui::Checkbox(sequence.name.c_str(), &sequence.enabled)) + { + auto enabled_count = std::count_if(sequence.sub_sequences.begin(), sequence.sub_sequences.end(), [](auto& sub_sequence) { + return sub_sequence.enabled; + }); + + if (enabled_count == sequence.sub_sequences.size() || enabled_count == 0) + { + for (auto& sub_sequence : sequence.sub_sequences) + { + sub_sequence.enabled = sequence.enabled; + } + } + } + } + } + + if (ImGui::CollapsingHeader("Sub Sequences", ImGuiTreeNodeFlags_::ImGuiTreeNodeFlags_DefaultOpen)) + { + for (auto& sequence : sequences) + { + ImGui::LabelText("", sequence.name.c_str()); + for (auto& sub_sequence : sequence.sub_sequences) + { + ImGui::Checkbox((sequence.name + "/" + sub_sequence.node_name).c_str(), &sub_sequence.enabled); + + ImGui::SliderInt(sequence.enabled ? " " : "", &sub_sequence.frame_index, 0, sub_sequence.num_key_frames - 1); + } + } + } + + ImGui::End(); + } + + + ImGui::SFML::Render(*window); + window->popGLStates(); + window->display(); + }; +} + +wxAppConsole* createApp() +{ + wxAppConsole::CheckBuildOptions(WX_BUILD_OPTIONS_SIGNATURE, + "Hello wxWidgets"); + return new wxApp(); +} + +constexpr auto event_open_in_new_tab = 1; +constexpr auto event_open_folder_as_workspace = 2; + +wxMenuBar* create_menu_bar() +{ + auto* menuFile = new wxMenu(); + menuFile->Append(wxID_OPEN); + menuFile->Append(event_open_in_new_tab, "Open in New Tab..."); + menuFile->Append(event_open_folder_as_workspace, "Open Folder as Workspace"); + menuFile->AppendSeparator(); + menuFile->Append(wxID_EXIT); + + auto* menuHelp = new wxMenu(); + menuHelp->Append(wxID_ABOUT); + + auto* menuBar = new wxMenuBar(); + menuBar->Append(menuFile, "&File"); + menuBar->Append(menuHelp, "&Help"); + + return menuBar; +} + +void create_render_view(wxWindow* panel, std::optional path) +{ + auto* graphics = new wxControl(panel, -1, wxDefaultPosition, wxDefaultSize, 0); + + panel->GetSizer()->Add(graphics, 1, wxEXPAND | wxALL, 5); + + sf::ContextSettings context; + context.depthBits = 24; + auto* window = new sf::RenderWindow(get_handle(graphics), context); + static bool is_init = false; + static ImGuiContext* primary_gui_context; + + ImGuiContext* gui_context; + + if (!is_init) + { + ImGui::SFML::Init(*window); + + primary_gui_context = gui_context = ImGui::GetCurrentContext(); + is_init = true; + } + else + { + gui_context = ImGui::CreateContext(ImGui::GetIO().Fonts); + } + + graphics->Bind(wxEVT_ERASE_BACKGROUND, [](auto& event) {}); + + graphics->Bind(wxEVT_SIZE, [=](auto& event) { + setup_opengl(graphics); + }); + + graphics->Bind(wxEVT_IDLE, [=](auto& event) { + graphics->Refresh(); + }); + graphics->Bind(wxEVT_PAINT, renderer_main(path, window, graphics, gui_context)); + + graphics->Bind(wxEVT_DESTROY, [=](auto& event) { + delete window; + if (gui_context != primary_gui_context) + { + ImGui::DestroyContext(gui_context); + ImGui::SetCurrentContext(primary_gui_context); + } + }); +} + +auto get_path_from_tree_item(wxTreeCtrl* tree_view, wxTreeItemId item, const fs::path& search_path) +{ + std::deque path_fragments; + + auto parent_item = tree_view->GetItemParent(item); + + do + { + if (parent_item != tree_view->GetRootItem()) + { + path_fragments.emplace_front(tree_view->GetItemText(parent_item).ToAscii().data()); + parent_item = tree_view->GetItemParent(parent_item); + } + } while (parent_item != tree_view->GetRootItem()); + + path_fragments.emplace_back(std::filesystem::path{ tree_view->GetItemText(item).ToAscii().data() }); + + auto new_path = search_path; + + for (auto& path : path_fragments) + { + new_path = new_path / path; + } + + return new_path; +} + +void populate_tree_view(wxTreeCtrl* tree_view, fs::path search_path, std::optional parent = std::nullopt) +{ + using namespace std::literals; + constexpr std::array extensions = { ".dts"sv, ".DTS"sv }; + + if (!fs::is_directory(search_path)) + { + return; + } + + bool is_root = false; + if (!parent.has_value()) + { + tree_view->DeleteAllItems(); + is_root = true; + } + + parent = parent.has_value() ? parent : tree_view->AddRoot(search_path.string()); + + for (auto& item : fs::directory_iterator(search_path)) + { + if (item.is_directory()) + { + auto new_parent = tree_view->AppendItem(parent.value(), item.path().stem().string()); + + if (is_root) + { + populate_tree_view(tree_view, item, new_parent); + } + } + } + + for (auto& item : fs::directory_iterator(search_path)) + { + if (!item.is_directory()) + { + auto filename = item.path().filename().string(); + if (std::any_of(extensions.begin(), extensions.end(), [&filename](const auto& ext) { + return ends_with(filename, ext); + })) + { + tree_view->AppendItem(parent.value(), filename); + } + } + } + + if (is_root) + { + tree_view->Expand(parent.value()); + } +} + +int main(int argc, char** argv) +{ + auto search_path = fs::current_path(); + + wxApp::SetInitializerFunction(createApp); + wxEntryStart(argc, argv); + auto* app = wxApp::GetInstance(); + app->CallOnInit(); + + auto* frame = new wxFrame(nullptr, wxID_ANY, "3Space Studio"); + + frame->Bind( + wxEVT_MENU, [](auto& event) { + wxMessageBox("This is a tool to explore files using the 3Space or Darkstar engines. Currently only Starsiege, Starsiege Tribes, Trophy Bass 3D and Front Page Sports: Ski Racing are supported.", + "About 3Space Studio", + wxOK | wxICON_INFORMATION); + }, + wxID_ABOUT); + + auto* sizer = new wxBoxSizer(wxHORIZONTAL); + + auto* tree_view = new wxTreeCtrl(frame); + sizer->Add(tree_view, 20, wxEXPAND, 0); + + populate_tree_view(tree_view, search_path); + + wxAuiNotebook* notebook = new wxAuiNotebook(frame, wxID_ANY); + auto num_elements = notebook->GetPageCount(); + + sizer->Add(notebook, 80, wxEXPAND, 0); + + auto add_element_from_file = [notebook, &num_elements](auto& new_path, bool replace_selection = false) { + if (fs::is_directory(new_path)) + { + return; + } + + wxPanel* panel = new wxPanel(notebook, wxID_ANY); + panel->SetSizer(new wxBoxSizer(wxHORIZONTAL)); + create_render_view(panel, new_path); + + if (replace_selection) + { + auto selection = notebook->GetSelection(); + notebook->InsertPage(selection, panel, new_path.filename().string()); + num_elements = notebook->GetPageCount(); + + if (num_elements > 2) + { + notebook->DeletePage(selection + 1); + } + + notebook->ChangeSelection(selection); + } + else + { + notebook->InsertPage(notebook->GetPageCount() - 1, panel, new_path.filename().string()); + num_elements = notebook->GetPageCount(); + notebook->ChangeSelection(notebook->GetPageCount() - 2); + } + }; + + auto add_new_element = [notebook, &num_elements]() { + wxPanel* panel = new wxPanel(notebook, wxID_ANY); + panel->SetSizer(new wxBoxSizer(wxHORIZONTAL)); + create_render_view(panel, std::nullopt); + notebook->InsertPage(notebook->GetPageCount() - 1, panel, "New Tab"); + notebook->ChangeSelection(notebook->GetPageCount() - 2); + num_elements = notebook->GetPageCount(); + }; + + tree_view->Bind(wxEVT_TREE_ITEM_EXPANDING, [tree_view, &search_path](wxTreeEvent& event) { + auto item = event.GetItem(); + + if (item == tree_view->GetRootItem()) + { + return; + } + + if (tree_view->HasChildren(item)) + { + wxTreeItemIdValue cookie = nullptr; + + auto child = tree_view->GetFirstChild(item, cookie); + + if (cookie && !tree_view->HasChildren(child)) + { + populate_tree_view(tree_view, get_path_from_tree_item(tree_view, child, search_path), child); + } + + do { + child = tree_view->GetNextChild(item, cookie); + + if (cookie && !tree_view->HasChildren(child)) + { + populate_tree_view(tree_view, get_path_from_tree_item(tree_view, child, search_path), child); + } + } while (cookie != nullptr); + } + }); + + tree_view->Bind(wxEVT_TREE_ITEM_ACTIVATED, [tree_view, &search_path, &add_element_from_file](wxTreeEvent& event) { + auto item = event.GetItem(); + + if (item == tree_view->GetRootItem()) + { + return; + } + + + auto item_path = get_path_from_tree_item(tree_view, item, search_path); + + add_element_from_file(item_path, true); + }); + + wxPanel* panel = new wxPanel(notebook, wxID_ANY); + panel->SetSizer(new wxBoxSizer(wxHORIZONTAL)); + create_render_view(panel, std::nullopt); + notebook->AddPage(panel, "New Tab"); + + panel = new wxPanel(notebook, wxID_ANY); + panel->SetName("+"); + notebook->AddPage(panel, "+"); + + notebook->Bind(wxEVT_AUINOTEBOOK_PAGE_CHANGED, + [notebook, &add_new_element](wxAuiNotebookEvent& event) { + auto* tab = notebook->GetPage(event.GetSelection()); + + if (tab->GetName() == "+") + { + if (notebook->GetPageCount() == 1) + { + add_new_element(); + } + else + { + notebook->ChangeSelection(event.GetSelection() - 1); + } + } + }); + + notebook->Bind(wxEVT_AUINOTEBOOK_PAGE_CHANGING, + [notebook, &num_elements, &add_new_element](wxAuiNotebookEvent& event) mutable { + auto* tab = notebook->GetPage(event.GetSelection()); + + if (tab->GetName() == "+") + { + if (num_elements > notebook->GetPageCount()) + { + num_elements = notebook->GetPageCount(); + return; + } + + add_new_element(); + } + else + { + event.Skip(); + } + }); + + + frame->Bind( + wxEVT_MENU, [&](auto& event) { + const auto new_path = get_shape_path(); + + if (new_path.has_value()) + { + add_element_from_file(new_path.value(), true); + + search_path = new_path.value().parent_path(); + populate_tree_view(tree_view, search_path); + } + }, + wxID_OPEN); + + frame->Bind( + wxEVT_MENU, [&](auto& event) { + const auto new_path = get_shape_path(); + + if (new_path.has_value()) + { + add_element_from_file(new_path.value()); + } + }, + event_open_in_new_tab); + + frame->Bind( + wxEVT_MENU, [&](auto& event) { + const auto new_path = get_workspace_path(); + + if (new_path.has_value()) + { + search_path = new_path.value(); + populate_tree_view(tree_view, search_path); + } + }, + event_open_folder_as_workspace); + + frame->Bind( + wxEVT_MENU, [frame](auto& event) { + frame->Close(true); + }, + wxID_EXIT); + + sizer->SetSizeHints(frame); + frame->SetSizer(sizer); + + frame->SetMenuBar(create_menu_bar()); + frame->CreateStatusBar(); + frame->SetStatusText("3Space Studio"); + frame->Maximize(); + frame->Show(true); + + app->OnRun(); + + ImGui::SFML::Shutdown(); + + return 0; +} \ No newline at end of file diff --git a/src/3space-studio/gl_renderer.hpp b/src/3space-studio/gl_renderer.hpp new file mode 100644 index 00000000..9ce5a11e --- /dev/null +++ b/src/3space-studio/gl_renderer.hpp @@ -0,0 +1,84 @@ +#ifndef DARKSTARDTSCONVERTER_GL_RENDERER_HPP +#define DARKSTARDTSCONVERTER_GL_RENDERER_HPP + +#include +#include +#include "renderable_shape.hpp" + +struct gl_renderer final : shape_renderer +{ + const std::array max_colour = { 255, 255, 0 }; + std::string_view current_object_name; + std::uint8_t num_faces = 0; + std::map, std::map>& visible_nodes; + std::map>& visible_objects; + bool current_object_visible = true; + bool current_node_visible = true; + + static std::optional to_string(std::optional value) + { + if (value.has_value()) + { + return std::string{ value.value() }; + } + + return std::nullopt; + } + + + gl_renderer(std::map, std::map>& visible_nodes, + std::map>& visible_objects) : visible_nodes(visible_nodes), visible_objects(visible_objects) + { + } + + void update_node(std::optional parent_node, std::string_view node_name) override + { + auto [iterator, added] = visible_nodes.emplace(to_string(parent_node), std::map{}); + + auto [new_node_iterator, object_added] = iterator->second.emplace(node_name, true); + + current_node_visible = new_node_iterator->second; + } + + void update_object(std::optional parent_node, std::string_view object_name) override + { + num_faces = 0; + current_object_name = object_name; + + if (parent_node.has_value()) + { + auto [iterator, added] = visible_objects.emplace(parent_node.value(), std::map{}); + + auto [new_object_iterator, object_added] = iterator->second.emplace(object_name, true); + + current_object_visible = current_node_visible && new_object_iterator->second; + } + } + + void new_face(std::size_t) override + { + if (current_object_visible) { + const auto [red, green, blue] = max_colour; + glColor4ub(red - num_faces, green - num_faces, std::uint8_t(current_object_name.size()), 255); + num_faces += 255 / 15; + } + } + + void end_face() override + { + } + + void emit_vertex(const darkstar::dts::vector3f& vertex) override + { + if (current_object_visible) + { + glVertex3f(vertex.x, vertex.y, vertex.z); + } + } + + void emit_texture_vertex(const darkstar::dts::mesh::v1::texture_vertex&) override + { + } +}; + +#endif//DARKSTARDTSCONVERTER_GL_RENDERER_HPP diff --git a/src/dts-viewer/settings.toml b/src/3space-studio/settings.toml similarity index 100% rename from src/dts-viewer/settings.toml rename to src/3space-studio/settings.toml diff --git a/src/dts-viewer/sfml_keys.hpp b/src/3space-studio/sfml_keys.hpp similarity index 91% rename from src/dts-viewer/sfml_keys.hpp rename to src/3space-studio/sfml_keys.hpp index 3c41af6f..08d8eaa1 100644 --- a/src/dts-viewer/sfml_keys.hpp +++ b/src/3space-studio/sfml_keys.hpp @@ -70,24 +70,21 @@ namespace config static auto numpad_key_names = get_numpad_key_names(); static auto letter_names = get_letter_key_names(); - auto numpad_key = find_if(numpad_key_names.begin(), numpad_key_names.end(), - [&](const auto& value) { return value.first == name;}); + auto numpad_key = find_if(numpad_key_names.begin(), numpad_key_names.end(), [&](const auto& value) { return value.first == name; }); if (numpad_key != numpad_key_names.end()) { return numpad_key->second; } - auto special_key = find_if(special_key_names.begin(), special_key_names.end(), - [&](const auto& value) { return value.first == name;}); + auto special_key = find_if(special_key_names.begin(), special_key_names.end(), [&](const auto& value) { return value.first == name; }); if (special_key != special_key_names.end()) { return special_key->second; } - auto letter_key = find_if(letter_names.begin(), letter_names.end(), - [&](const auto& value) { return value.first == name;}); + auto letter_key = find_if(letter_names.begin(), letter_names.end(), [&](const auto& value) { return value.first == name; }); if (letter_key != letter_names.end()) { @@ -97,9 +94,7 @@ namespace config return sf::Keyboard::Key::Unknown; } -} - - +}// namespace config #endif//DARKSTARDTSCONVERTER_SFML_KEYS_HPP diff --git a/src/3space-studio/utility.cpp b/src/3space-studio/utility.cpp new file mode 100644 index 00000000..0a452c00 --- /dev/null +++ b/src/3space-studio/utility.cpp @@ -0,0 +1,36 @@ + +#include "utility.hpp" + +#ifdef __WXGTK__ +#include +#include +#include +#endif + +WXWidget get_handle(const wxControl* const control) +{ +#ifdef __WXGTK__ + + // GTK implementation requires to go deeper to find the + // low-level X11 identifier of the widget + gtk_widget_realize(m_wxwindow); + gtk_widget_set_double_buffered(m_wxwindow, false); + GdkWindow* Win = GTK_PIZZA(m_wxwindow)->bin_window; + XFlush(GDK_WINDOW_XDISPLAY(Win)); + return GDK_WINDOW_XWINDOW(Win)); +#else + return control->GetHandle(); +#endif +} + +void perspectiveGL(GLdouble fovY, GLdouble aspect, GLdouble zNear, GLdouble zFar) +{ + constexpr GLdouble pi = 3.1415926535897932384626433832795; + GLdouble fW, fH; + + //fH = tan( (fovY / 2) / 180 * pi ) * zNear; + fH = tan(fovY / 360 * pi) * zNear; + fW = fH * aspect; + + glFrustum(-fW, fW, -fH, fH, zNear, zFar); +} \ No newline at end of file diff --git a/src/3space-studio/utility.hpp b/src/3space-studio/utility.hpp new file mode 100644 index 00000000..a835cfce --- /dev/null +++ b/src/3space-studio/utility.hpp @@ -0,0 +1,22 @@ +#ifndef DARKSTARDTSCONVERTER_UTILTY_HPP +#define DARKSTARDTSCONVERTER_UTILTY_HPP + +#include +#include +#include +#include + +inline bool ends_with(std::string_view value, std::string_view ending) +{ + if (ending.size() > value.size()) + { + return false; + } + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); +} + +WXWidget get_handle(const wxControl* const control); + +void perspectiveGL(GLdouble fovY, GLdouble aspect, GLdouble zNear, GLdouble zFar); + +#endif//DARKSTARDTSCONVERTER_UTILTY_HPP diff --git a/src/dts-to-json/convert_dts.cpp b/src/dts-to-json/convert_dts.cpp index 1f5a7f25..69bd9f37 100644 --- a/src/dts-to-json/convert_dts.cpp +++ b/src/dts-to-json/convert_dts.cpp @@ -37,6 +37,9 @@ int main(int argc, const char** argv) std::visit([&](const auto& item) { nlohmann::ordered_json item_as_json = item; + //TODO make this have a flag + // and reduce the amount of formatting to only what makes sense + // and what is easy to parse again //format_json(item_as_json); auto new_file_name = file_name.string() + ".json"; diff --git a/src/dts-to-obj/convert_dts.cpp b/src/dts-to-obj/convert_dts.cpp index 748d54ae..8c53ca47 100644 --- a/src/dts-to-obj/convert_dts.cpp +++ b/src/dts-to-obj/convert_dts.cpp @@ -9,6 +9,7 @@ #include "complex_serializer.hpp" #include "shared.hpp" #include "dts_io.hpp" +#include "dts_renderable_shape.hpp" #include "obj_renderer.hpp" namespace fs = std::filesystem; @@ -58,7 +59,10 @@ int main(int argc, const char** argv) std::ofstream output(file_name.string() + "." + root_node_name + ".obj", std::ios::trunc); auto renderer = obj_renderer{output}; - render_dts(main_shape, renderer, i); + dts_renderable_shape instance{core_shape}; + std::vector details{i}; + auto sequences = instance.get_sequences(details); + instance.render_shape(renderer, details, sequences); } }, core_shape); diff --git a/src/dts-viewer/dts-viewer.cpp b/src/dts-viewer/dts-viewer.cpp deleted file mode 100644 index 42b42046..00000000 --- a/src/dts-viewer/dts-viewer.cpp +++ /dev/null @@ -1,308 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "dts_io.hpp" -#include "dts_render.hpp" -#include "sfml_keys.hpp" - -namespace fs = std::filesystem; -namespace dts = darkstar::dts; - -struct gl_renderer final : shape_renderer -{ - const std::array max_colour = { 255, 255, 0 }; - std::string_view current_object_name; - std::uint8_t num_faces = 0; - std::map& visible_nodes; - std::map::iterator current_node; - - gl_renderer(std::map& visible_nodes) : visible_nodes(visible_nodes) - { - current_node = visible_nodes.end(); - } - - void update_node(std::string_view node_name) override - { - current_node = visible_nodes.emplace(node_name, true).first; - } - - void update_object(std::string_view object_name) override - { - num_faces = 0; - current_object_name = object_name; - } - - void new_face(std::size_t) override - { - if (current_node->second) - { - const auto [red, green, blue] = max_colour; - glColor4ub(red - num_faces, green - num_faces, std::uint8_t(current_object_name.size()), 255); - num_faces += 255 / 15; - } - } - - void end_face() override - { - } - - void emit_vertex(const darkstar::dts::vector3f& vertex) override - { - if (current_node->second) - { - glVertex3f(vertex.x, vertex.y, vertex.z); - } - } - - void emit_texture_vertex(const darkstar::dts::mesh::v1::texture_vertex&) override - { - } -}; - - -void perspectiveGL(GLdouble fovY, GLdouble aspect, GLdouble zNear, GLdouble zFar) -{ - constexpr GLdouble pi = 3.1415926535897932384626433832795; - GLdouble fW, fH; - - //fH = tan( (fovY / 2) / 180 * pi ) * zNear; - fH = tan(fovY / 360 * pi) * zNear; - fW = fH * aspect; - - glFrustum(-fW, fW, -fH, fH, zNear, zFar); -} - - -std::optional get_shape_path() -{ - constexpr static auto filetypes = "Darkstar DTS files|*.dts;*.DTS"; - auto dialog = std::make_unique(nullptr, "Open a Darkstar DTS File", "", "", filetypes, wxFD_OPEN, wxDefaultPosition); - - if (dialog->ShowModal() == wxID_OK) - { - const auto buffer = dialog->GetPath().ToAscii(); - return std::string_view{ buffer.data(), buffer.length() }; - } - - return std::nullopt; -} - -int main(int argc, const char** argv) -{ - using namespace std::literals; - std::optional shape_path; - - dts::shape_variant shape; - - if (argc > 1) - { - shape_path = argv[1]; - } - - if (!shape_path.has_value()) - { - shape_path = get_shape_path(); - } - - if (!shape_path.has_value()) - { - wxMessageBox("No file has been selected.", "Cannot Continue.", wxICON_ERROR); - return EXIT_FAILURE; - } - - try - { - std::basic_ifstream input(shape_path.value(), std::ios::binary); - shape = dts::read_shape(shape_path.value(), input, std::nullopt); - } - catch (const std::exception& ex) - { - wxMessageBox(ex.what(), "Error Loading Model.", wxICON_ERROR); - return EXIT_FAILURE; - } - - sf::ContextSettings context; - context.depthBits = 24; - constexpr auto main_title = "3Space Studio - Darkstar DTS Viewer - "; - - sf::RenderWindow window(sf::VideoMode(800, 600, 32), main_title + shape_path.value().filename().string(), sf::Style::Default, context); - - ImGui::SFML::Init(window); - - std::array color = { 0.f, 0.f, 0.f }; - sf::Clock clock; - - glClearDepth(1.f); - glClearColor(0.3f, 0.3f, 0.3f, 0.f); - - glEnable(GL_DEPTH_TEST); - glDepthMask(GL_TRUE); - - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - perspectiveGL(90.f, 1.f, 1.f, 300.0f); - - - float x_angle = 180; - float y_angle = 120; - float z_angle = -30; - - dts::vector3f translation = { 0, 0, -15 }; - - std::map> callbacks; - std::map visible_nodes; - auto detail_levels = get_detail_levels(shape); - int detail_level_index = 0; - - callbacks.emplace(config::get_key_for_name("Left"), [&]() { x_angle--; }); - callbacks.emplace(config::get_key_for_name("Numpad4"), [&]() { x_angle--; }); - callbacks.emplace(config::get_key_for_name("Right"), [&]() { x_angle++; }); - callbacks.emplace(config::get_key_for_name("Numpad6"), [&]() { x_angle++; }); - callbacks.emplace(config::get_key_for_name("Up"), [&]() { y_angle++; }); - callbacks.emplace(config::get_key_for_name("Numpad8"), [&]() { y_angle++; }); - callbacks.emplace(config::get_key_for_name("Down"), [&]() { y_angle--; }); - callbacks.emplace(config::get_key_for_name("Numpad2"), [&]() { y_angle--; }); - callbacks.emplace(config::get_key_for_name("Home"), [&]() { z_angle++; }); - callbacks.emplace(config::get_key_for_name("Numpad7"), [&]() { z_angle++; }); - callbacks.emplace(config::get_key_for_name("PageUp"), [&]() { z_angle--; }); - callbacks.emplace(config::get_key_for_name("Numpad9"), [&]() { z_angle--; }); - callbacks.emplace(config::get_key_for_name("End"), [&]() { translation.x++; }); - callbacks.emplace(config::get_key_for_name("Numpad1"), [&]() { translation.x++; }); - callbacks.emplace(config::get_key_for_name("PageDown"), [&]() { translation.x--; }); - callbacks.emplace(config::get_key_for_name("Numpad3"), [&]() { translation.x--; }); - - callbacks.emplace(config::get_key_for_name("Add"), [&]() { translation.z++; }); - callbacks.emplace(config::get_key_for_name("Subtract"), [&]() { translation.z--; }); - - callbacks.emplace(config::get_key_for_name("Divide"), [&]() { translation.y++; }); - callbacks.emplace(config::get_key_for_name("Insert"), [&]() { translation.y++; }); - callbacks.emplace(config::get_key_for_name("Numpad0"), [&]() { translation.y++; }); - - callbacks.emplace(config::get_key_for_name("Multiply"), [&]() { translation.y--; }); - callbacks.emplace(config::get_key_for_name("Delete"), [&]() { translation.y--; }); - callbacks.emplace(config::get_key_for_name("Period"), [&]() { translation.y--; }); - - while (window.isOpen()) - { - sf::Event event; - while (window.pollEvent(event)) - { - ImGui::SFML::ProcessEvent(event); - if (event.type == sf::Event::KeyPressed && (event.key.code != sf::Keyboard::Escape)) - { - const auto callback = callbacks.find(event.key.code); - - if (callback != callbacks.end()) - { - callback->second(); - } - } - - if (event.type == sf::Event::Closed) - { - window.close(); - } - - if ((event.type == sf::Event::KeyPressed) && (event.key.code == sf::Keyboard::Escape)) - { - window.close(); - } - } - - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glTranslatef(translation.x, translation.y, translation.z); - - glRotatef(y_angle, 1.f, 0.f, 0.f); - glRotatef(x_angle, 0.f, 1.f, 0.f); - glRotatef(z_angle, 0.f, 0.f, 1.f); - - glBegin(GL_TRIANGLES); - auto renderer = gl_renderer{ visible_nodes }; - render_dts(shape, renderer, detail_level_index); - glEnd(); - - window.pushGLStates(); - - ImGui::SFML::Update(window, clock.restart()); - - if (ImGui::Begin("Options")) - { - if (ImGui::Button("Open")) - { - const auto path = get_shape_path(); - - if (path.has_value()) - { - try - { - std::basic_ifstream input(path.value(), std::ios::binary); - shape = dts::read_shape(path.value(), input, std::nullopt); - window.setTitle(main_title + path.value().filename().string()); - visible_nodes.clear(); - } - catch (const std::exception& ex) - { - wxMessageBox(ex.what(), "Error Loading Model.", wxICON_ERROR); - } - } - } - ImGui::SameLine(); - if (ImGui::Button("Exit")) - { - window.close(); - break; - } - - ImGui::End(); - } - - ImGui::Begin("Nodes"); - - for (auto& [node_name, is_visible] : visible_nodes) - { - ImGui::Checkbox(node_name.c_str(), &is_visible); - } - - ImGui::End(); - - ImGui::Begin("Detail Levels"); - - if (ImGui::ListBox( - "", &detail_level_index, [](void* data, int idx, const char** out_text) { - *out_text = reinterpret_cast(data)[idx].c_str(); - return true; - }, - detail_levels.data(), - detail_levels.size())) - { - visible_nodes.clear(); - } - - ImGui::End(); - - - ImGui::SFML::Render(window); - - window.popGLStates(); - window.display(); - } - - ImGui::SFML::Shutdown(); - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/src/dts_io.hpp b/src/dts_io.hpp index 2d435463..117201a0 100644 --- a/src/dts_io.hpp +++ b/src/dts_io.hpp @@ -19,8 +19,8 @@ namespace darkstar::dts if (file_header.tag != dts::pers_tag) { std::stringstream msg; - msg << "There was an error trying to parse a portion of the DTS/DML file at " << stream.tellg() << ". "; - msg << "Expected " << std::string(reinterpret_cast(dts::pers_tag.data()), dts::pers_tag.size()) << " to be present but was not found.\n"; + msg << "There was an error trying to parse a portion of the DTS/DML file at byte number " << stream.tellg() << ". "; + msg << "Expected the " << std::string(reinterpret_cast(dts::pers_tag.data()), dts::pers_tag.size()) << " file header to be present but it was not found.\n"; throw std::invalid_argument(msg.str()); } diff --git a/src/dts_render.hpp b/src/dts_render.hpp deleted file mode 100644 index ba81a08b..00000000 --- a/src/dts_render.hpp +++ /dev/null @@ -1,200 +0,0 @@ -#ifndef DARKSTARDTSCONVERTER_DTS_RENDER_HPP -#define DARKSTARDTSCONVERTER_DTS_RENDER_HPP - -#include -#include -#include -#include -#include -#include -#include -#include "dts_structures.hpp" - -struct shape_renderer -{ - virtual void update_node(std::string_view node_name) = 0; - virtual void update_object(std::string_view object_name) = 0; - virtual void new_face(std::size_t num_vertices) = 0; - virtual void end_face() = 0; - virtual void emit_vertex(const darkstar::dts::vector3f& vertex) = 0; - virtual void emit_texture_vertex(const darkstar::dts::mesh::v1::texture_vertex& vertex) = 0; - - virtual ~shape_renderer() = default; - - shape_renderer() = default; - shape_renderer(const shape_renderer&) = delete; - shape_renderer(shape_renderer&&) = delete; -}; - - -std::vector get_detail_levels(const darkstar::dts::shape_variant& shape) -{ - return std::visit([](const auto& instance) { - std::vector results; - results.reserve(instance.details.size()); - - for (const auto& detail : instance.details) - { - const auto root_note_index = detail.root_node_index; - const auto& node = instance.nodes[root_note_index]; - results.emplace_back(instance.names[node.name_index].data()); - } - - return results; - }, - shape); -} - -void render_dts(const darkstar::dts::shape_variant& shape_variant, shape_renderer& renderer, std::optional detail_level_index = std::nullopt) -{ - std::visit([&](const auto& shape) { - namespace dts = darkstar::dts; - std::vector buffer(8192, std::byte{ 0 }); - std::pmr::monotonic_buffer_resource resource{ buffer.data(), buffer.size() }; - - using transform_set = std::pmr::set; - - detail_level_index = detail_level_index.has_value() ? detail_level_index : 0; - const auto& detail_level = shape.details[detail_level_index.value()]; - const auto root_note_index = detail_level.root_node_index; - - std::pmr::unordered_map node_indexes{ &resource }; - std::pmr::unordered_map> object_indexes{ &resource }; - - std::list valid_nodes; - - valid_nodes.emplace_back(object_indexes.emplace(root_note_index, std::pmr::set{ &resource }).first->first); - - for (const auto parent_index : valid_nodes) - { - const auto [location, added] = node_indexes.emplace(parent_index, transform_set{ &resource }); - - auto transform_index = shape.nodes[parent_index].default_transform_index; - const auto* transform = &shape.transforms[transform_index]; - location->second.emplace(transform); - - for (auto other_node = std::begin(shape.nodes); other_node != std::end(shape.nodes); ++other_node) - { - if (other_node->parent_node_index == parent_index) - { - const std::string_view data = shape.names[other_node->name_index].data(); - const auto index = static_cast(std::distance(std::begin(shape.nodes), other_node)); - - auto [iterator, node_added] = object_indexes.emplace(index, std::pmr::set{ &resource }); - - if (node_added) - { - valid_nodes.emplace_back(iterator->first); - } - - for (const auto* other_transform : node_indexes[parent_index]) - { - const auto [child_location, child_added] = node_indexes.emplace(index, transform_set{ &resource }); - child_location->second.emplace(other_transform); - } - } - } - } - - for (auto object = std::begin(shape.objects); object != std::end(shape.objects); ++object) - { - if (const auto item = object_indexes.find(object->node_index); - item != object_indexes.cend()) - { - const auto index = static_cast(std::distance(std::begin(shape.objects), object)); - item->second.emplace(index); - } - } - - for (const auto& [child_node_index, transforms] : node_indexes) - { - std::optional default_scale; - dts::vector3f default_translation = { 0, 0, 0 }; - - for (auto transform : transforms) - { - if constexpr (std::remove_reference_t::version < 8) - { - if (!default_scale.has_value()) - { - default_scale = transform->scale; - } - } - default_translation = default_translation + transform->translation; - } - - const auto& child_node = shape.nodes[child_node_index]; - const std::string_view child_node_name = shape.names[child_node.name_index].data(); - renderer.update_node(child_node_name); - - auto& objects = object_indexes[child_node_index]; - - for (const std::int32_t object_index : objects) - { - const auto& object = shape.objects[object_index]; - const std::string_view object_name = shape.names[object.name_index].data(); - - renderer.update_object(object_name); - - std::visit([&](const auto& mesh) { - dts::vector3f mesh_scale; - dts::vector3f mesh_origin; - - if constexpr (std::remove_reference_t::version < 3) - { - mesh_scale = mesh.header.scale; - mesh_origin = mesh.header.origin; - } - else if constexpr (std::remove_reference_t::version >= 3) - { - if (!mesh.frames.empty()) - { - mesh_scale = mesh.frames[0].scale; - mesh_origin = mesh.frames[0].origin; - } - else - { - mesh_scale = { 1, 1, 1 }; - mesh_origin = { 0, 0, 0 }; - } - } - - if (default_scale.has_value()) - { - mesh_scale = mesh_scale * default_scale.value(); - } - - mesh_origin = mesh_origin + default_translation; - - for (const auto& face : mesh.faces) - { - renderer.new_face(3); - std::array vertices{ std::cref(mesh.vertices[face.vi3]), - std::cref(mesh.vertices[face.vi2]), - std::cref(mesh.vertices[face.vi1]) }; - - std::array texture_vertices{ std::cref(mesh.texture_vertices[face.ti3]), - std::cref(mesh.texture_vertices[face.ti2]), - std::cref(mesh.texture_vertices[face.ti1]) }; - - for (const auto& raw_vertex : vertices) - { - renderer.emit_vertex(raw_vertex.get() * mesh_scale + mesh_origin); - } - - for (const auto& raw_texture_vertex : texture_vertices) - { - renderer.emit_texture_vertex(raw_texture_vertex.get()); - } - - renderer.end_face(); - } - }, - shape.meshes[object.mesh_index]); - } - } - }, - shape_variant); -} - -#endif//DARKSTARDTSCONVERTER_DTS_RENDER_HPP diff --git a/src/dts_renderable_shape.cpp b/src/dts_renderable_shape.cpp new file mode 100644 index 00000000..a011d0c2 --- /dev/null +++ b/src/dts_renderable_shape.cpp @@ -0,0 +1,361 @@ +// +// Created by Matthew on 2020/10/05. +// + +#include "dts_renderable_shape.hpp" +#include +#include +#include +#include +#include + +template +struct overloaded : Ts... +{ + using Ts::operator()...; +}; + +template +overloaded(Ts...) -> overloaded; + +std::tuple get_translation(const darkstar::dts::shape::v2::transform& transform) +{ + return std::make_tuple(transform.translation, darkstar::dts::to_float(transform.rotation), transform.scale); +} + +std::tuple get_translation(const darkstar::dts::shape::v7::transform& transform) +{ + return std::make_tuple(transform.translation, darkstar::dts::to_float(transform.rotation), transform.scale); +} + +std::tuple get_translation(const darkstar::dts::shape::v8::transform& transform) +{ + return std::make_tuple(transform.translation, darkstar::dts::to_float(transform.rotation), darkstar::dts::vector3f{ 1.0f, 1.0f, 1.0f }); +} + +std::int32_t get_transform_index(const darkstar::dts::shape_variant& shape, std::int32_t node_index, const std::vector& sequences) +{ + return std::visit([&](const auto& local_shape) { + std::int32_t transform_index = local_shape.nodes[node_index].default_transform_index; + + for (auto& sequence : sequences) + { + if (sequence.enabled) + { + for (auto& sub_sequence : sequence.sub_sequences) + { + if (sub_sequence.enabled && node_index == sub_sequence.node_index) + { + const auto& key_frame = local_shape.keyframes[sub_sequence.first_key_frame_index + sub_sequence.frame_index]; + transform_index = key_frame.transform_index; + break; + } + } + } + } + + return transform_index; + }, + shape); +} + +instance_info get_instance(const darkstar::dts::shape_variant& shape, std::size_t detail_level_index) +{ + return std::visit([&](const auto& local_shape) { + const auto& detail_level = local_shape.details[detail_level_index]; + const std::int32_t root_note_index = detail_level.root_node_index; + + instance_info info{}; + + info.root_node = std::make_pair(root_note_index, node_instance{}); + + std::list> valid_nodes{ std::make_pair(root_note_index, &info.root_node.second) }; + + for (const auto& iterator : valid_nodes) + { + auto& [parent_index, node_info] = iterator; + for (auto other_node = std::begin(local_shape.nodes); other_node != std::end(local_shape.nodes); ++other_node) + { + if (other_node->parent_node_index == parent_index) + { + const auto node_index = static_cast(std::distance(std::begin(local_shape.nodes), other_node)); + + auto [new_node_info, new_added] = node_info->node_indexes.emplace(node_index, node_instance{}); + + if (new_added) + { + valid_nodes.emplace_back(std::make_pair(new_node_info->first, &new_node_info->second)); + } + } + } + + for (auto object = std::begin(local_shape.objects); object != std::end(local_shape.objects); ++object) + { + if (object->node_index == parent_index) + { + const auto object_index = static_cast(std::distance(std::begin(local_shape.objects), object)); + node_info->object_indexes.emplace(object_index); + } + } + } + + return info; + }, + shape); +} + +std::vector dts_renderable_shape::get_sequences(const std::vector& detail_level_indexes) const +{ + std::vector results; + + if (shape.index() == std::variant_npos) + { + return results; + } + + std::visit([&](auto& local_shape) { + if (local_shape.sequences.empty() || local_shape.sub_sequences.empty()) + { + return; + } + + std::vector sequences; + results.reserve(local_shape.sequences.size()); + + for (auto i = 0; i < local_shape.sequences.size(); ++i) + { + auto& sequence = local_shape.sequences[i]; + results.push_back({ i, local_shape.names[sequence.name_index].data(), i == 0, std::vector{} }); + } + + if (local_shape.details.empty()) + { + return; + } + + for (auto detail_level_index : detail_level_indexes) + { + auto instance = get_instance(shape, detail_level_index); + + std::function populate_sequences = [&](const auto& node_index, const auto& node_instance) { + if (node_index == -1) + { + return; + } + + const auto& node = local_shape.nodes[node_index]; + std::string node_name = local_shape.names[node.name_index].data(); + + auto create_sub_info = [&](auto node_index, auto& sub_sequence) { + sub_sequence_info info; + info.node_index = node_index; + info.node_name = node_name; + info.first_key_frame_index = sub_sequence.first_key_frame_index; + info.num_key_frames = sub_sequence.num_key_frames; + info.enabled = sub_sequence.sequence_index == 0; + + if (sub_sequence.num_key_frames > 0) + { + info.min_position = local_shape.keyframes[sub_sequence.first_key_frame_index].position; + info.max_position = local_shape.keyframes[sub_sequence.first_key_frame_index + sub_sequence.num_key_frames - 1].position; + } + else + { + info.min_position = 0; + info.max_position = 0; + } + + info.frame_index = 0; + info.position = info.min_position; + + return info; + }; + + if (node.num_sub_sequences == 0) + { + for (auto& object : local_shape.objects) + { + if (object.node_index == node_index) + { + for (auto i = object.first_sub_sequence_index; i < object.first_sub_sequence_index + object.num_sub_sequences; ++i) + { + auto& sub_sequence = local_shape.sub_sequences[i]; + auto& sequence = results[sub_sequence.sequence_index]; + + sequence.sub_sequences.emplace_back(create_sub_info(node_index, sub_sequence)); + } + break; + } + } + } + else + { + for (auto i = node.first_sub_sequence_index; i < node.first_sub_sequence_index + node.num_sub_sequences; ++i) + { + auto& sub_sequence = local_shape.sub_sequences[i]; + auto& sequence = results[sub_sequence.sequence_index]; + + sequence.sub_sequences.emplace_back(create_sub_info(node_index, sub_sequence)); + } + } + + for (const auto& [child_node_index, child_node_instance] : node_instance.node_indexes) + { + populate_sequences(child_node_index, child_node_instance); + } + }; + + populate_sequences(instance.root_node.first, instance.root_node.second); + } + }, + shape); + + return results; +} + +std::vector dts_renderable_shape::get_detail_levels() const +{ + return std::visit([](const auto& instance) { + std::vector results; + results.reserve(instance.details.size()); + + for (const auto& detail : instance.details) + { + const auto root_note_index = detail.root_node_index; + const auto& node = instance.nodes[root_note_index]; + results.emplace_back(instance.names[node.name_index].data()); + } + + return results; + }, + shape); +} + +void dts_renderable_shape::render_shape(shape_renderer& renderer, const std::vector& detail_level_indexes, const std::vector& sequences) const +{ + + std::visit([&](const auto& local_shape) { + namespace dts = darkstar::dts; + + if (local_shape.details.empty()) + { + return; + } + + for (auto detail_level_index : detail_level_indexes) + { + auto instance = get_instance(shape, detail_level_index); + + std::function&, std::optional)> render_node = + [&](const auto& node_item, const auto parent_node_matrix) { + auto& [node_index, node_instance] = node_item; + + const auto& node = local_shape.nodes[node_index]; + const std::string_view node_name = local_shape.names[node.name_index].data(); + + glm::mat4 node_matrix; + + auto transform_index = get_transform_index(shape, node_index, sequences); + + const auto& [translation, rotation, scale] = get_translation(local_shape.transforms[transform_index]); + + auto translation_matrix = glm::translate(glm::mat4(1.0f), glm::vec3(translation.x, translation.y, translation.z)); + auto rotation_matrix = glm::transpose(glm::toMat4(glm::quat(rotation.w, rotation.x, rotation.y, rotation.z))); + + auto scale_matrix = glm::scale(glm::mat4(1.0f), glm::vec3(scale.x, scale.y, scale.z)); + + if (parent_node_matrix.has_value()) + { + node_matrix = parent_node_matrix.value() * (translation_matrix * rotation_matrix * scale_matrix); + } + else + { + node_matrix = translation_matrix * rotation_matrix * scale_matrix; + } + + std::optional parent_node_name; + + if (node.parent_node_index != -1) + { + const auto& parent_node = local_shape.nodes[node.parent_node_index]; + parent_node_name = local_shape.names[parent_node.name_index].data(); + } + + renderer.update_node(parent_node_name, node_name); + + for (const std::int32_t object_index : node_instance.object_indexes) + { + const auto& object = local_shape.objects[object_index]; + const std::string_view object_name = local_shape.names[object.name_index].data(); + + renderer.update_object(node_name, object_name); + + std::visit([&](const auto& mesh) { + dts::vector3f mesh_scale; + dts::vector3f mesh_origin; + + if constexpr (std::remove_reference_t::version < 3) + { + mesh_scale = mesh.header.scale; + mesh_origin = mesh.header.origin; + } + else if constexpr (std::remove_reference_t::version >= 3) + { + if (!mesh.frames.empty()) + { + mesh_scale = mesh.frames[0].scale; + mesh_origin = mesh.frames[0].origin; + } + else + { + mesh_scale = { 1, 1, 1 }; + mesh_origin = { 0, 0, 0 }; + } + } + for (const auto& face : mesh.faces) + { + renderer.new_face(3); + std::array vertices{ std::cref(mesh.vertices[face.vi3]), + std::cref(mesh.vertices[face.vi2]), + std::cref(mesh.vertices[face.vi1]) }; + + std::array texture_vertices{ std::cref(mesh.texture_vertices[face.ti3]), + std::cref(mesh.texture_vertices[face.ti2]), + std::cref(mesh.texture_vertices[face.ti1]) }; + + for (const auto& raw_vertex : vertices) + { + auto vertex = glm::vec4(raw_vertex.get().x, raw_vertex.get().y, raw_vertex.get().z, 1.0f); + + auto translation_matrix = glm::translate(glm::mat4(1.0f), glm::vec3(mesh_origin.x, mesh_origin.y, mesh_origin.z)); + auto scale_matrix = glm::scale(glm::mat4(1.0f), glm::vec3(mesh_scale.x, mesh_scale.y, mesh_scale.z)); + + vertex = translation_matrix * scale_matrix * vertex; + + vertex = node_matrix * vertex; + + renderer.emit_vertex(darkstar::dts::vector3f{ vertex.x, vertex.y, vertex.z }); + } + + for (const auto& raw_texture_vertex : texture_vertices) + { + renderer.emit_texture_vertex(raw_texture_vertex.get()); + } + + renderer.end_face(); + } + }, + local_shape.meshes[object.mesh_index]); + } + + for (const auto& child_node_item : node_instance.node_indexes) + { + render_node(child_node_item, node_matrix); + } + }; + + render_node(instance.root_node, std::nullopt); + } + }, + shape); +} \ No newline at end of file diff --git a/src/dts_renderable_shape.hpp b/src/dts_renderable_shape.hpp new file mode 100644 index 00000000..412d3146 --- /dev/null +++ b/src/dts_renderable_shape.hpp @@ -0,0 +1,46 @@ +#ifndef DARKSTARDTSCONVERTER_DTS_RENDER_HPP +#define DARKSTARDTSCONVERTER_DTS_RENDER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "renderable_shape.hpp" +#include "dts_structures.hpp" + +struct node_instance +{ + std::set object_indexes; + std::unordered_map node_indexes; +}; + +struct instance_info +{ + using transform = std::tuple; + std::pair root_node; +}; + +class dts_renderable_shape : public renderable_shape +{ +public: + dts_renderable_shape(darkstar::dts::shape_variant shape) + : shape(std::move(shape)) + { + } + + std::vector get_sequences(const std::vector& detail_level_indexes) const override; + + std::vector get_detail_levels() const override; + void render_shape(shape_renderer& renderer, const std::vector& detail_level_indexes, const std::vector& sequences) const override; + +private: + darkstar::dts::shape_variant shape; +}; + + +#endif//DARKSTARDTSCONVERTER_DTS_RENDER_HPP diff --git a/src/dts_structures.hpp b/src/dts_structures.hpp index 96c92ef3..b96133bb 100644 --- a/src/dts_structures.hpp +++ b/src/dts_structures.hpp @@ -60,30 +60,32 @@ namespace darkstar::dts float z; }; - vector3f operator+(const vector3f& left, const vector3f& right) + inline vector3f operator+(const vector3f& left, const vector3f& right) { return { left.x + right.x, left.y + right.y, left.z + right.z }; } - vector3f operator-(const vector3f& left, const vector3f& right) + inline vector3f operator-(const vector3f& left, const vector3f& right) { return { left.x - right.x, left.y - right.y, left.z - right.z }; } -// vector3f operator+=(const vector3f& left, const vector3f& right) -// { -// return left + right; -// } + inline vector3f operator+=(vector3f& left, const vector3f& right) + { + left = left + right; + return left; + } - vector3f operator*(const vector3f& left, const vector3f& right) + inline vector3f operator*(const vector3f& left, const vector3f& right) { return { left.x * right.x, left.y * right.y, left.z * right.z }; } -// -// vector3f operator*=(const vector3f& left, const vector3f& right) -// { -// return left * right; -// } + + inline vector3f operator*=(vector3f& left, const vector3f& right) + { + left = left * right; + return left; + } struct vector3f_pair { @@ -112,6 +114,23 @@ namespace darkstar::dts float w; }; + inline quaternion4f to_float(const quaternion4f& other) + { + return other; + } + + inline quaternion4f to_float(const quaternion4s& other) + { + constexpr std::int16_t max = SHRT_MAX; + + return { + float(other.x) / max, + float(other.y) / max, + float(other.z) / max, + float(other.w) / max + }; + } + static_assert(sizeof(quaternion4s) == sizeof(std::array)); struct rgb_data @@ -159,7 +178,7 @@ namespace darkstar::dts static_assert(sizeof(vertex) == sizeof(std::int32_t)); - vector3f operator*(const vertex& left, const vector3f& right) + inline vector3f operator*(const vertex& left, const vector3f& right) { vector3f result{}; @@ -171,7 +190,7 @@ namespace darkstar::dts } - vector3f operator+(const vertex& left, const vector3f& right) + inline vector3f operator+(const vertex& left, const vector3f& right) { vector3f result{}; diff --git a/src/obj_renderer.hpp b/src/obj_renderer.hpp index 1cea7268..17b47907 100644 --- a/src/obj_renderer.hpp +++ b/src/obj_renderer.hpp @@ -5,7 +5,7 @@ #include #include #include -#include "dts_render.hpp" +#include "dts_renderable_shape.hpp" struct obj_renderer final : shape_renderer { @@ -19,11 +19,11 @@ struct obj_renderer final : shape_renderer output << std::setprecision(32); } - void update_node(std::string_view) override + void update_node(std::optional, std::string_view) override { } - void update_object(std::string_view object_name) override + void update_object(std::optional, std::string_view object_name) override { output << "o " << object_name << '\n'; } diff --git a/src/renderable_shape.hpp b/src/renderable_shape.hpp new file mode 100644 index 00000000..f0c1a814 --- /dev/null +++ b/src/renderable_shape.hpp @@ -0,0 +1,59 @@ +#ifndef DARKSTARDTSCONVERTER_RENDERABLE_SHAPE_HPP +#define DARKSTARDTSCONVERTER_RENDERABLE_SHAPE_HPP + +#include +#include +#include +#include "dts_structures.hpp" + +struct shape_renderer +{ + virtual void update_node(std::optional parent_node_name, std::string_view node_name) = 0; + virtual void update_object(std::optional parent_node_name, std::string_view object_name) = 0; + virtual void new_face(std::size_t num_vertices) = 0; + virtual void end_face() = 0; + virtual void emit_vertex(const darkstar::dts::vector3f& vertex) = 0; + virtual void emit_texture_vertex(const darkstar::dts::mesh::v1::texture_vertex& vertex) = 0; + + virtual ~shape_renderer() = default; + + shape_renderer() = default; + shape_renderer(const shape_renderer&) = delete; + shape_renderer(shape_renderer&&) = delete; +}; + +struct sub_sequence_info +{ + std::int32_t node_index; + std::string node_name; + std::int32_t frame_index; + std::int32_t first_key_frame_index; + std::int32_t num_key_frames; + float min_position; + float max_position; + float position; + bool enabled; +}; + +struct sequence_info +{ + std::int32_t index; + std::string name; + bool enabled; + std::vector sub_sequences; +}; + +struct renderable_shape +{ + virtual std::vector get_sequences(const std::vector& detail_level_indexes) const = 0; + + virtual std::vector get_detail_levels() const = 0; + + virtual void render_shape(shape_renderer& renderer, const std::vector& detail_level_indexes, const std::vector& sequences) const = 0; + + virtual ~renderable_shape() = default; +}; + + + +#endif//DARKSTARDTSCONVERTER_RENDERABLE_SHAPE_HPP diff --git a/src/torque_dts_structures.hpp b/src/torque_dts_structures.hpp new file mode 100644 index 00000000..d7e21e94 --- /dev/null +++ b/src/torque_dts_structures.hpp @@ -0,0 +1,366 @@ +#ifndef DARKSTARDTSCONVERTER_TORQUE_DTS_STRUCTURES_HPP +#define DARKSTARDTSCONVERTER_TORQUE_DTS_STRUCTURES_HPP + +#include +#include +#include +#include "endian_arithmetic.hpp" +#include "dts_structures.hpp" + +namespace torque::dts +{ + namespace endian = boost::endian; + + struct guard + { + endian::little_int32_t guard_32; + endian::little_int16_t guard_16; + std::byte guard_8; + }; + + struct bit_set + { + endian::little_int32_t dummy; + endian::little_int32_t num_words; + std::vector bits; + }; + + struct sequence + { + endian::little_int32_t name_index; + endian::little_uint32_t flags; + endian::little_int32_t num_key_frames; + float duration; + endian::little_int32_t priority; + endian::little_int32_t first_ground_frame; + endian::little_int32_t num_ground_frames; + endian::little_int32_t base_rotation_index; + endian::little_int32_t base_translation_index; + endian::little_int32_t base_scale_index; + endian::little_int32_t base_object_state; + endian::little_int32_t base_decal_state; + endian::little_int32_t first_trigger; + endian::little_int32_t num_triggers; + float tool_begin; + bit_set rotation_matters; + bit_set translation_matters; + bit_set scale_matters; + bit_set decal_matters; + bit_set ifl_matters; + bit_set vis_matters; + bit_set frame_matters; + bit_set mat_frame_matters; + }; + + struct node + { + endian::little_int32_t name_index; + endian::little_int32_t parent_index; + endian::little_int32_t first_object_index; + endian::little_int32_t first_child_index; + endian::little_int32_t next_sibling_index; + }; + + struct object + { + endian::little_int32_t name_index; + endian::little_int32_t num_meshes; + endian::little_int32_t start_mesh_index; + endian::little_int32_t node_index; + endian::little_int32_t next_sibling_index; + endian::little_int32_t first_decal_index; + }; + + struct detail + { + endian::little_int32_t name_index; + endian::little_int32_t sub_shape_num; + endian::little_int32_t object_detail_num; + float size; + float average_error; + float max_error; + endian::little_int32_t poly_count; + }; + + namespace v26 + { + struct detail + { + endian::little_int32_t name_index; + endian::little_int32_t sub_shape_num; + endian::little_int32_t object_detail_num; + float size; + float average_error; + float max_error; + endian::little_int32_t poly_count; + endian::little_int32_t bounding_box_dimension; + endian::little_int32_t bounding_box_detail_level; + endian::little_int32_t bounding_box_equator_steps; + endian::little_int32_t bounding_box_polar_steps; + float bounding_box_polar_angle; + endian::little_int32_t bounding_box_include_poles; + }; + }// namespace v26 + + struct primitive + { + endian::little_int16_t start_index; + endian::little_int16_t num_elements; + }; + + struct primitive_material + { + endian::little_uint32_t mat_index; + }; + + namespace v25 + { + struct primitive + { + endian::little_int32_t start_index; + endian::little_int32_t num_elements; + endian::little_uint32_t mat_index; + }; + }// namespace v25 + + struct mesh_header + { + endian::little_int32_t type; + guard checkpoint1; + endian::little_int32_t num_frames; + endian::little_int32_t num_material_frames; + endian::little_int32_t parent_mesh; + darkstar::dts::vector3f_pair bounds; + darkstar::dts::vector3f center; + float radius; + endian::little_int32_t num_vertices; + std::vector vertices; + endian::little_int32_t num_texture_vertices; + std::vector> texture_vertices; + }; + + //TODO finish this + struct skin_mesh_details + { + endian::little_int32_t num_initial_vertices; + endian::little_int32_t initial_verices; + }; + + struct sorted_mesh + { + }; + + //TODO deal with multiple versions + + struct mesh + { + mesh_header header; + //normal + std::vector norms; + std::vector encoded_normals; + + endian::little_int32_t num_primitives; + std::vector primitives; + + std::vector primitive_materials; + + endian::little_int32_t num_indices; + + std::vector indices; + + endian::little_int32_t num_merge_indices; + + std::vector merge_indices; + + endian::little_int32_t vertices_per_frame; + endian::little_uint32_t flags; + + guard checkpoint2; + }; + + namespace v25 + { + struct mesh + { + mesh_header header; + //normal + std::vector norms; + std::vector encoded_normals; + + endian::little_int32_t num_primitives; + + std::vector primitives; + + endian::little_int32_t num_indices; + + std::vector indices; + + endian::little_int32_t num_merge_indices; + + std::vector merge_indices; + + endian::little_int32_t vertices_per_frame; + endian::little_uint32_t flags; + + guard checkpoint2; + }; + }// namespace v26 + + + namespace v26 + { + struct mesh + { + mesh_header header; + //v26 + endian::little_int32_t num_texture_vertices2; + std::vector> texture_vertices2; + endian::little_int32_t num_vertex_colors; + std::vector vertex_colors; + + //normal + std::vector norms; + std::vector encoded_normals; + + endian::little_int32_t num_primitives; + + std::vector primitives; + + endian::little_int32_t num_indices; + + std::vector indices; + + endian::little_int32_t num_merge_indices; + + std::vector merge_indices; + + endian::little_int32_t vertices_per_frame; + endian::little_uint32_t flags; + + guard checkpoint2; + }; + }// namespace v26 + + struct object_state + { + float visibility; + endian::little_int32_t frame_index; + endian::little_int32_t mat_frame; + }; + + struct trigger + { + endian::little_uint32_t state; + float position; + }; + + struct ifl_material + { + endian::little_int32_t name_index; + endian::little_int32_t material_slot; + endian::little_int32_t first_frame_index; + endian::little_int32_t first_frame_of_time_index; + endian::little_int32_t num_frames; + }; + + struct decal + { + std::array dummy; + }; + + struct file_data + { + endian::little_int16_t dts_version_number; + endian::little_int16_t exporter_version_number; + endian::little_int32_t total_buffer_size; + endian::little_int32_t offset_buffer_16; + endian::little_int32_t offset_buffer_8; + + std::vector buffer_32; + endian::little_uint16_t* buffer_16; + std::byte* buffer_8; + + endian::little_int32_t num_sequences; + std::vector sequences; + std::byte mat_stream_type; + endian::little_int32_t num_materials; + std::vector material_names; + std::vector material_flags; + std::vector material_reflectance_maps; + std::vector material_bump_maps; + std::vector material_detail_maps; + std::vector material_detail_scales; + std::vector material_detail_reflectance; + }; + + struct buffer_data + { + endian::little_int32_t num_nodes; + endian::little_int32_t num_objects; + endian::little_int32_t num_decals; + endian::little_int32_t num_sub_shapes; + endian::little_int32_t num_ifls; + endian::little_int32_t num_node_rotations; + endian::little_int32_t num_node_translations; + endian::little_int32_t num_node_uniform_scales; + endian::little_int32_t num_node_aligned_scales; + endian::little_int32_t num_node_arb_scales; + endian::little_int32_t num_ground_frames; + endian::little_int32_t num_object_states; + endian::little_int32_t num_decal_states; + endian::little_int32_t num_triggers; + endian::little_int32_t num_details; + endian::little_int32_t num_meshes; + endian::little_int32_t num_names; + float smallest_visible_size; + endian::little_int32_t smallest_visible_detail_level; + guard checkpoint1; + float radius; + float tube_radius; + darkstar::dts::vector3f center; + darkstar::dts::vector3f_pair bounds; + guard checkpoint2; + std::vector nodes; + guard checkpoint3; + std::vector objects; + guard checkpoint4; + std::vector decals; + guard checkpoint5; + std::vector ifl_materials; + guard checkpoint6; + std::vector sub_shapes_first_node; + std::vector sub_shapes_first_object; + std::vector sub_shapes_first_decal; + std::vector sub_shapes_first_translucent_object; + guard checkpoint7; + std::vector default_rotations; + std::vector default_translations; + std::vector node_rotations; + std::vector node_translations; + guard checkpoint8; + std::vector node_uniform_scales; + std::vector node_aligned_scales; + std::vector node_arbitrary_scale_factors; + std::vector node_arbitrary_scale_rotations; + guard checkpoint9; + std::vector ground_translations; + std::vector ground_rotations; + guard checkpoint10; + std::vector object_states; + guard checkpoint11; + std::vector decal_states; + guard checkpoint12; + std::vector triggers; + guard checkpoint13; + std::vector> details; + guard checkpoint14; + std::vector meshes; + guard checkpoint15; + std::vector names; + guard checkpoint16; + std::vector details_alpha_in; + std::vector details_alpha_out; + }; +}// namespace torque::dts + +#endif//DARKSTARDTSCONVERTER_TORQUE_DTS_STRUCTURES_HPP