diff --git a/trlevel/Level.h b/trlevel/Level.h index c353783db..e2dbe49ba 100644 --- a/trlevel/Level.h +++ b/trlevel/Level.h @@ -282,6 +282,9 @@ namespace trlevel std::vector _entities; std::unordered_map _static_meshes; std::vector _ai_objects; + std::vector _animations; + std::vector _state_changes; + std::vector _anim_dispatches; uint16_t _lara_type{ 0u }; uint16_t _weather_type{ 0u }; diff --git a/trlevel/Level_tr1_pc.cpp b/trlevel/Level_tr1_pc.cpp index 37ab62076..1e1c5a5f9 100644 --- a/trlevel/Level_tr1_pc.cpp +++ b/trlevel/Level_tr1_pc.cpp @@ -468,8 +468,8 @@ namespace trlevel uint32_t num_animations = read(wad_file); skip(wad_file, num_animations * 26); - read_state_changes(activity, wad_file, callbacks); - read_anim_dispatches(activity, wad_file, callbacks); + _state_changes = read_state_changes(activity, wad_file, callbacks); + _anim_dispatches = read_anim_dispatches(activity, wad_file, callbacks); read_anim_commands(activity, wad_file, callbacks); _meshtree = read_meshtree(activity, wad_file, callbacks); _frames = read_frames(activity, wad_file, callbacks); @@ -685,9 +685,9 @@ namespace trlevel _floor_data = read_floor_data(activity, file, callbacks); _mesh_data = read_mesh_data(activity, file, callbacks); _mesh_pointers = read_mesh_pointers(activity, file, callbacks); - read_animations_tr1_3(activity, file, callbacks); - read_state_changes(activity, file, callbacks); - read_anim_dispatches(activity, file, callbacks); + _animations = convert_animations(read_animations_tr1_3(activity, file, callbacks)); + _state_changes = read_state_changes(activity, file, callbacks); + _anim_dispatches = read_anim_dispatches(activity, file, callbacks); read_anim_commands(activity, file, callbacks); _meshtree = read_meshtree(activity, file, callbacks); _frames = read_frames(activity, file, callbacks); diff --git a/trlevel/Level_tr1_psx.cpp b/trlevel/Level_tr1_psx.cpp index 93f17b9fb..0fdb6fe4a 100644 --- a/trlevel/Level_tr1_psx.cpp +++ b/trlevel/Level_tr1_psx.cpp @@ -305,9 +305,9 @@ namespace trlevel _floor_data = read_floor_data(activity, file, callbacks); _mesh_data = read_mesh_data(activity, file, callbacks); _mesh_pointers = read_mesh_pointers(activity, file, callbacks); - read_animations_tr1_3(activity, file, callbacks); - read_state_changes(activity, file, callbacks); - read_anim_dispatches(activity, file, callbacks); + _animations = convert_animations(read_animations_tr1_3(activity, file, callbacks)); + _state_changes = read_state_changes(activity, file, callbacks); + _anim_dispatches = read_anim_dispatches(activity, file, callbacks); read_anim_commands(activity, file, callbacks); _meshtree = read_meshtree(activity, file, callbacks); _frames = read_frames(activity, file, callbacks); @@ -434,9 +434,9 @@ namespace trlevel _floor_data = read_floor_data(activity, file, callbacks); _mesh_data = read_mesh_data(activity, file, callbacks); _mesh_pointers = read_mesh_pointers(activity, file, callbacks); - read_animations_tr1_3(activity, file, callbacks); - read_state_changes(activity, file, callbacks); - read_anim_dispatches(activity, file, callbacks); + _animations = convert_animations(read_animations_tr1_3(activity, file, callbacks)); + _state_changes = read_state_changes(activity, file, callbacks); + _anim_dispatches = read_anim_dispatches(activity, file, callbacks); read_anim_commands(activity, file, callbacks); _meshtree = read_meshtree(activity, file, callbacks); _frames = read_frames(activity, file, callbacks); @@ -484,11 +484,11 @@ namespace trlevel _mesh_data = read_mesh_data(activity, file, callbacks); _mesh_pointers = read_mesh_pointers(activity, file, callbacks); - uint32_t num_animations = read(file); + uint32_t num_animations = read(file); // TODO: Read animations skip(file, num_animations * 28); - read_state_changes(activity, file, callbacks); - read_anim_dispatches(activity, file, callbacks); + _state_changes = read_state_changes(activity, file, callbacks); + _anim_dispatches = read_anim_dispatches(activity, file, callbacks); read_anim_commands(activity, file, callbacks); _meshtree = read_meshtree(activity, file, callbacks); _frames = read_frames(activity, file, callbacks); diff --git a/trlevel/Level_tr2_pc.cpp b/trlevel/Level_tr2_pc.cpp index 763356c1e..5bc94c07d 100644 --- a/trlevel/Level_tr2_pc.cpp +++ b/trlevel/Level_tr2_pc.cpp @@ -105,9 +105,9 @@ namespace trlevel _floor_data = read_floor_data(activity, file, callbacks); _mesh_data = read_mesh_data(activity, file, callbacks); _mesh_pointers = read_mesh_pointers(activity, file, callbacks); - read_animations_tr1_3(activity, file, callbacks); - read_state_changes(activity, file, callbacks); - read_anim_dispatches(activity, file, callbacks); + _animations = convert_animations(read_animations_tr1_3(activity, file, callbacks)); + _state_changes = read_state_changes(activity, file, callbacks); + _anim_dispatches = read_anim_dispatches(activity, file, callbacks); read_anim_commands(activity, file, callbacks); _meshtree = read_meshtree(activity, file, callbacks); _frames = read_frames(activity, file, callbacks); @@ -162,9 +162,9 @@ namespace trlevel _floor_data = read_floor_data(activity, file, callbacks); _mesh_data = read_mesh_data(activity, file, callbacks); _mesh_pointers = read_mesh_pointers(activity, file, callbacks); - read_animations_tr1_3(activity, file, callbacks); - read_state_changes(activity, file, callbacks); - read_anim_dispatches(activity, file, callbacks); + _animations = convert_animations(read_animations_tr1_3(activity, file, callbacks)); + _state_changes = read_state_changes(activity, file, callbacks); + _anim_dispatches = read_anim_dispatches(activity, file, callbacks); read_anim_commands(activity, file, callbacks); _meshtree = read_meshtree(activity, file, callbacks); _frames = read_frames(activity, file, callbacks); diff --git a/trlevel/Level_tr2_psx.cpp b/trlevel/Level_tr2_psx.cpp index 25d52e342..a71435447 100644 --- a/trlevel/Level_tr2_psx.cpp +++ b/trlevel/Level_tr2_psx.cpp @@ -408,9 +408,9 @@ namespace trlevel _floor_data = read_floor_data(activity, file, callbacks); _mesh_data = read_mesh_data(activity, file, callbacks); _mesh_pointers = read_mesh_pointers(activity, file, callbacks); - read_animations_tr1_3(activity, file, callbacks); - read_state_changes(activity, file, callbacks); - read_anim_dispatches(activity, file, callbacks); + _animations = convert_animations(read_animations_tr1_3(activity, file, callbacks)); + _state_changes = read_state_changes(activity, file, callbacks); + _anim_dispatches = read_anim_dispatches(activity, file, callbacks); read_anim_commands(activity, file, callbacks); _meshtree = read_meshtree(activity, file, callbacks); _frames = read_frames(activity, file, callbacks); @@ -457,9 +457,9 @@ namespace trlevel _floor_data = read_floor_data(activity, file, callbacks); _mesh_data = read_mesh_data(activity, file, callbacks); _mesh_pointers = read_mesh_pointers(activity, file, callbacks); - read_animations_tr1_3(activity, file, callbacks); - read_state_changes(activity, file, callbacks); - read_anim_dispatches(activity, file, callbacks); + _animations = convert_animations(read_animations_tr1_3(activity, file, callbacks)); + _state_changes = read_state_changes(activity, file, callbacks); + _anim_dispatches = read_anim_dispatches(activity, file, callbacks); read_anim_commands(activity, file, callbacks); _meshtree = read_meshtree(activity, file, callbacks); _frames = read_frames(activity, file, callbacks); @@ -511,9 +511,9 @@ namespace trlevel _floor_data = read_floor_data(activity, file, callbacks); _mesh_data = read_mesh_data(activity, file, callbacks); _mesh_pointers = read_mesh_pointers(activity, file, callbacks); - read_animations_tr1_3(activity, file, callbacks); - read_state_changes(activity, file, callbacks); - read_anim_dispatches(activity, file, callbacks); + _animations = convert_animations(read_animations_tr1_3(activity, file, callbacks)); + _state_changes = read_state_changes(activity, file, callbacks); + _anim_dispatches = read_anim_dispatches(activity, file, callbacks); read_anim_commands(activity, file, callbacks); _meshtree = read_meshtree(activity, file, callbacks); _frames = read_frames(activity, file, callbacks); @@ -578,9 +578,9 @@ namespace trlevel _floor_data = read_floor_data(activity, file, callbacks); _mesh_data = read_mesh_data(activity, file, callbacks); _mesh_pointers = read_mesh_pointers(activity, file, callbacks); - read_animations_tr1_3(activity, file, callbacks); - read_state_changes(activity, file, callbacks); - read_anim_dispatches(activity, file, callbacks); + _animations = convert_animations(read_animations_tr1_3(activity, file, callbacks)); + _state_changes = read_state_changes(activity, file, callbacks); + _anim_dispatches = read_anim_dispatches(activity, file, callbacks); read_anim_commands(activity, file, callbacks); _meshtree = read_meshtree(activity, file, callbacks); _frames = read_frames(activity, file, callbacks); diff --git a/trlevel/Level_tr3_pc.cpp b/trlevel/Level_tr3_pc.cpp index 4a952e7ae..1c21bb57c 100644 --- a/trlevel/Level_tr3_pc.cpp +++ b/trlevel/Level_tr3_pc.cpp @@ -54,9 +54,9 @@ namespace trlevel _floor_data = read_floor_data(activity, file, callbacks); _mesh_data = read_mesh_data(activity, file, callbacks); _mesh_pointers = read_mesh_pointers(activity, file, callbacks); - read_animations_tr1_3(activity, file, callbacks); - read_state_changes(activity, file, callbacks); - read_anim_dispatches(activity, file, callbacks); + _animations = convert_animations(read_animations_tr1_3(activity, file, callbacks)); + _state_changes = read_state_changes(activity, file, callbacks); + _anim_dispatches = read_anim_dispatches(activity, file, callbacks); read_anim_commands(activity, file, callbacks); _meshtree = read_meshtree(activity, file, callbacks); _frames = read_frames(activity, file, callbacks); diff --git a/trlevel/Level_tr3_psx.cpp b/trlevel/Level_tr3_psx.cpp index a08c3c9f9..62d123ec3 100644 --- a/trlevel/Level_tr3_psx.cpp +++ b/trlevel/Level_tr3_psx.cpp @@ -262,9 +262,9 @@ namespace trlevel _mesh_data = read_mesh_data(activity, file, callbacks); _mesh_pointers = read_mesh_pointers(activity, file, callbacks); - read_animations_tr1_3(activity, file, callbacks); - read_state_changes(activity, file, callbacks); - read_anim_dispatches(activity, file, callbacks); + _animations = convert_animations(read_animations_tr1_3(activity, file, callbacks)); + _state_changes = read_state_changes(activity, file, callbacks); + _anim_dispatches = read_anim_dispatches(activity, file, callbacks); read_anim_commands(activity, file, callbacks); _meshtree = read_meshtree(activity, file, callbacks); _frames = read_frames(activity, file, callbacks); diff --git a/trlevel/Level_tr4_pc.cpp b/trlevel/Level_tr4_pc.cpp index 480d363ea..57577533f 100644 --- a/trlevel/Level_tr4_pc.cpp +++ b/trlevel/Level_tr4_pc.cpp @@ -119,9 +119,9 @@ namespace trlevel _floor_data = read_floor_data(activity, data_stream, callbacks); _mesh_data = read_mesh_data(activity, data_stream, callbacks); _mesh_pointers = read_mesh_pointers(activity, data_stream, callbacks); - read_animations_tr4_5(activity, data_stream, callbacks); - read_state_changes(activity, data_stream, callbacks); - read_anim_dispatches(activity, data_stream, callbacks); + _animations = read_animations_tr4_5(activity, data_stream, callbacks); + _state_changes = read_state_changes(activity, data_stream, callbacks); + _anim_dispatches = read_anim_dispatches(activity, data_stream, callbacks); read_anim_commands(activity, data_stream, callbacks); _meshtree = read_meshtree(activity, data_stream, callbacks); _frames = read_frames(activity, data_stream, callbacks); @@ -211,9 +211,9 @@ namespace trlevel _floor_data = read_floor_data(activity, file, callbacks); _mesh_data = read_mesh_data(activity, file, callbacks); _mesh_pointers = read_mesh_pointers(activity, file, callbacks); - read_animations_tr4_5(activity, file, callbacks); - read_state_changes(activity, file, callbacks); - read_anim_dispatches(activity, file, callbacks); + _animations = read_animations_tr4_5(activity, file, callbacks); + _state_changes = read_state_changes(activity, file, callbacks); + _anim_dispatches = read_anim_dispatches(activity, file, callbacks); read_anim_commands(activity, file, callbacks); _meshtree = read_meshtree(activity, file, callbacks); _frames = read_frames(activity, file, callbacks); diff --git a/trlevel/Level_tr4_psx.cpp b/trlevel/Level_tr4_psx.cpp index cb987a1cb..3f3f3da42 100644 --- a/trlevel/Level_tr4_psx.cpp +++ b/trlevel/Level_tr4_psx.cpp @@ -738,7 +738,7 @@ namespace trlevel _mesh_data = read_mesh_data(activity, file, info, callbacks); _mesh_pointers = read_mesh_pointers(activity, file, info, callbacks); - skip(file, info.animations_size); + skip(file, info.animations_size); // TODO: Load animations skip(file, info.state_changes_size); skip(file, info.dispatches_size); skip(file, info.commands_size); @@ -905,7 +905,7 @@ namespace trlevel _mesh_data = read_mesh_data(activity, file, info, callbacks); _mesh_pointers = read_mesh_pointers(activity, file, info, callbacks); - skip(file, info.animations_size); + skip(file, info.animations_size); // TODO: Read and store these skip(file, info.state_changes_size); skip(file, info.dispatches_size); skip(file, info.commands_size); diff --git a/trlevel/Level_tr5_dc.cpp b/trlevel/Level_tr5_dc.cpp index 26e875f30..61a29a378 100644 --- a/trlevel/Level_tr5_dc.cpp +++ b/trlevel/Level_tr5_dc.cpp @@ -217,9 +217,9 @@ namespace trlevel DreamcastPage page{ file }; _mesh_data = read_mesh_data(activity, file, callbacks); _mesh_pointers = read_mesh_pointers(activity, file, callbacks); - read_animations_tr4_5(activity, file, callbacks); - read_state_changes(activity, file, callbacks); - read_anim_dispatches(activity, file, callbacks); + _animations = read_animations_tr4_5(activity, file, callbacks); + _state_changes = read_state_changes(activity, file, callbacks); + _anim_dispatches = read_anim_dispatches(activity, file, callbacks); read_anim_commands(activity, file, callbacks); _meshtree = read_meshtree(activity, file, callbacks); _frames = read_frames(activity, file, callbacks); diff --git a/trlevel/Level_tr5_pc.cpp b/trlevel/Level_tr5_pc.cpp index 1144df8d1..ac9c15d03 100644 --- a/trlevel/Level_tr5_pc.cpp +++ b/trlevel/Level_tr5_pc.cpp @@ -222,9 +222,9 @@ namespace trlevel _floor_data = read_floor_data(activity, file, callbacks); _mesh_data = read_mesh_data(activity, file, callbacks); _mesh_pointers = read_mesh_pointers(activity, file, callbacks); - read_animations_tr4_5(activity, file, callbacks); - read_state_changes(activity, file, callbacks); - read_anim_dispatches(activity, file, callbacks); + _animations = read_animations_tr4_5(activity, file, callbacks); + _state_changes = read_state_changes(activity, file, callbacks); + _anim_dispatches = read_anim_dispatches(activity, file, callbacks); read_anim_commands(activity, file, callbacks); _meshtree = read_meshtree(activity, file, callbacks); _frames = read_frames(activity, file, callbacks); @@ -299,9 +299,9 @@ namespace trlevel _floor_data = read_floor_data(activity, file, callbacks); _mesh_data = read_mesh_data(activity, file, callbacks); _mesh_pointers = read_mesh_pointers(activity, file, callbacks); - read_animations_tr4_5(activity, file, callbacks); - read_state_changes(activity, file, callbacks); - read_anim_dispatches(activity, file, callbacks); + _animations = read_animations_tr4_5(activity, file, callbacks); + _state_changes = read_state_changes(activity, file, callbacks); + _anim_dispatches = read_anim_dispatches(activity, file, callbacks); read_anim_commands(activity, file, callbacks); _meshtree = read_meshtree(activity, file, callbacks); _frames = read_frames(activity, file, callbacks); diff --git a/trlevel/Level_tr5_psx.cpp b/trlevel/Level_tr5_psx.cpp index a48178f97..2d6293918 100644 --- a/trlevel/Level_tr5_psx.cpp +++ b/trlevel/Level_tr5_psx.cpp @@ -170,7 +170,7 @@ namespace trlevel _mesh_data = read_mesh_data(activity, file, info, callbacks); _mesh_pointers = read_mesh_pointers(activity, file, info, callbacks); - skip(file, info.animations_size); + skip(file, info.animations_size); // TODO: Load animations skip(file, info.state_changes_size); skip(file, info.dispatches_size); skip(file, info.commands_size); diff --git a/trlevel/trtypes.cpp b/trlevel/trtypes.cpp index fb9c9a904..847b1cb1c 100644 --- a/trlevel/trtypes.cpp +++ b/trlevel/trtypes.cpp @@ -201,4 +201,30 @@ namespace trlevel }; }) | std::ranges::to(); } + + std::vector convert_animations(std::vector animations) + { + return animations | std::views::transform([](auto&& a) -> tr4_animation + { + return + { + .FrameOffset = a.FrameOffset, + .FrameRate = a.FrameRate, + .FrameSize = a.FrameSize, + .State_ID = a.State_ID, + .Speed = a.Speed, + .Accel = a.Accel, + .SpeedLateral = 0, // TODO: Is this correct? + .AccelLateral = 0, // TODO: Is this correct? + .FrameStart = a.FrameStart, + .FrameEnd = a.FrameEnd, + .NextAnimation = a.NextAnimation, + .NextFrame = a.NextFrame, + .NumStateChanges = a.NumStateChanges, + .StateChangeOffset = a.StateChangeOffset, + .NumAnimCommands = a.NumAnimCommands, + .AnimCommand = a.AnimCommand, + }; + }) | std::ranges::to(); + } } \ No newline at end of file diff --git a/trlevel/trtypes.h b/trlevel/trtypes.h index cdb440d3e..54a1bd9c4 100644 --- a/trlevel/trtypes.h +++ b/trlevel/trtypes.h @@ -1003,6 +1003,8 @@ namespace trlevel std::vector convert_tr3_psx_room_triangles(std::vector triangles, uint16_t total_vertices); + std::vector convert_animations(std::vector animations); + constexpr LightType convert(LightTypeTR3 type); } diff --git a/trview.app/ApplicationCreate.cpp b/trview.app/ApplicationCreate.cpp index 06ef73c41..307809aa9 100644 --- a/trview.app/ApplicationCreate.cpp +++ b/trview.app/ApplicationCreate.cpp @@ -68,6 +68,7 @@ #include "Windows/TriggersWindow.h" #include "Windows/Viewer.h" #include "Windows/Log/LogWindow.h" +#include "Windows/Models/ModelsWindow.h" #include "UI/DX11ImGuiBackend.h" #include "Windows/Textures/TexturesWindow.h" #include "Windows/CameraSink/CameraSinkWindow.h" @@ -498,6 +499,15 @@ namespace trview return pack_window; }; + auto transparency_buffer_source = [=](auto&& lts) { return std::make_unique(device, lts); }; + + auto models_window_source = [=]() + { + auto models_window = std::make_shared(device, render_target_source, shader_storage, buffer_source, transparency_buffer_source, sampler_source, std::make_unique(window, std::make_unique(window)), messaging); + models_window->initialise(); + return models_window; + }; + auto windows = std::make_shared(window, shortcuts); windows->register_window("About", about_window_source); windows->register_window("CameraSink", camera_sink_window_source); @@ -506,6 +516,7 @@ namespace trview windows->register_window("Items", items_window_source); windows->register_window("Lights", lights_window_source); windows->register_window("Log", log_window_source); + windows->register_window("Models", models_window_source); windows->register_window("Pack", pack_window_source); windows->register_window("Plugins", plugins_window_source); windows->register_window("Rooms", rooms_window_source); diff --git a/trview.app/Elements/ILevel.h b/trview.app/Elements/ILevel.h index a65047a67..d438ceaed 100644 --- a/trview.app/Elements/ILevel.h +++ b/trview.app/Elements/ILevel.h @@ -154,6 +154,8 @@ namespace trview virtual std::string hash() const = 0; virtual std::vector> flybys() const = 0; virtual void update(float delta) = 0; + virtual std::weak_ptr model_storage() const = 0; + virtual std::weak_ptr level_texture_storage() const = 0; // Event raised when the level needs to change the alternate mode. Event on_alternate_mode_selected; diff --git a/trview.app/Elements/Level.cpp b/trview.app/Elements/Level.cpp index baf41d681..835f193d9 100644 --- a/trview.app/Elements/Level.cpp +++ b/trview.app/Elements/Level.cpp @@ -455,7 +455,7 @@ namespace trview _pixel_shader_data->apply(context, graphics::IBuffer::ApplyTo::PS); _room_sampler_state->apply(); - _transparency->render(camera); + _transparency->render(camera.view_projection()); } void Level::render_selected_item(const ICamera& camera) @@ -1647,6 +1647,16 @@ namespace trview return _flybys | std::ranges::to>>(); } + std::weak_ptr Level::model_storage() const + { + return _model_storage; + } + + std::weak_ptr Level::level_texture_storage() const + { + return _texture_storage; + } + void Level::update(float delta) { if (!_show_animation) diff --git a/trview.app/Elements/Level.h b/trview.app/Elements/Level.h index b71de1c2d..7e2835f6b 100644 --- a/trview.app/Elements/Level.h +++ b/trview.app/Elements/Level.h @@ -145,6 +145,8 @@ namespace trview void update(float delta) override; void set_show_animation(bool show) override; void receive_message(const Message& message) override; + std::weak_ptr model_storage() const override; + std::weak_ptr level_texture_storage() const override; private: void generate_rooms(const trlevel::ILevel& level, const IRoom::Source& room_source, const IMeshStorage& mesh_storage); void generate_triggers(const ITrigger::Source& trigger_source); diff --git a/trview.app/Geometry/ITransparencyBuffer.h b/trview.app/Geometry/ITransparencyBuffer.h index 6698d3ada..afa1d1175 100644 --- a/trview.app/Geometry/ITransparencyBuffer.h +++ b/trview.app/Geometry/ITransparencyBuffer.h @@ -5,8 +5,11 @@ namespace trview { + struct ILevelTextureStorage; struct ITransparencyBuffer { + using Source = std::function(const std::weak_ptr&)>; + virtual ~ITransparencyBuffer() = 0; // Add a triangle to the transparency buffer. The triangle will be added to the end // of the collection. @@ -19,10 +22,10 @@ namespace trview virtual void sort(const DirectX::SimpleMath::Vector3& eye_position) = 0; /// Render the accumulated transparent triangles. Sort should be called before this function is called. - /// @param camera The current camera. + /// @param view_project The view projection. /// @param texture_storage Texture storage for the level. /// @param ignore_blend Optional. Set to true to render this without transparency. - virtual void render(const ICamera& camera, bool ignore_blend = false) = 0; + virtual void render(const DirectX::SimpleMath::Matrix& view_projection, bool ignore_blend = false) = 0; // Reset the triangles buffer. virtual void reset() = 0; diff --git a/trview.app/Geometry/Model/IModel.h b/trview.app/Geometry/Model/IModel.h index ffb4003c8..83ebcdbd5 100644 --- a/trview.app/Geometry/Model/IModel.h +++ b/trview.app/Geometry/Model/IModel.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "../PickResult.h" namespace trview diff --git a/trview.app/Geometry/Model/IModelStorage.h b/trview.app/Geometry/Model/IModelStorage.h index 3838f0606..ad2b446be 100644 --- a/trview.app/Geometry/Model/IModelStorage.h +++ b/trview.app/Geometry/Model/IModelStorage.h @@ -7,5 +7,6 @@ namespace trview { virtual ~IModelStorage() = 0; virtual std::weak_ptr find_by_type_id(uint16_t type_id) const = 0; + virtual std::vector> models() const = 0; }; } diff --git a/trview.app/Geometry/Model/ModelStorage.cpp b/trview.app/Geometry/Model/ModelStorage.cpp index 1d9e351f7..fefb2651e 100644 --- a/trview.app/Geometry/Model/ModelStorage.cpp +++ b/trview.app/Geometry/Model/ModelStorage.cpp @@ -1,5 +1,6 @@ #include "ModelStorage.h" #include "../../Graphics/IMeshStorage.h" +#include namespace trview { @@ -69,6 +70,39 @@ namespace trview return false; } + std::vector> load_meshes( + const trlevel::tr_model& model, + const trlevel::PlatformAndVersion& version, + const std::shared_ptr mesh_storage) + { + std::vector> meshes; + if (version.platform == trlevel::Platform::PSX && equals_any(version.version, trlevel::LevelVersion::Tomb4, trlevel::LevelVersion::Tomb5)) + { + const uint32_t end_pointer = static_cast(model.StartingMesh + model.NumMeshes * 2); + for (uint32_t mesh_pointer = model.StartingMesh; mesh_pointer < end_pointer; mesh_pointer += 2) + { + auto mesh = mesh_storage->mesh(mesh_pointer); + if (mesh) + { + meshes.push_back(mesh); + } + } + } + else + { + const uint32_t end_pointer = static_cast(model.StartingMesh + model.NumMeshes); + for (uint32_t mesh_pointer = model.StartingMesh; mesh_pointer < end_pointer; ++mesh_pointer) + { + auto mesh = mesh_storage->mesh(mesh_pointer); + if (mesh) + { + meshes.push_back(mesh); + } + } + } + return meshes; + } + std::vector load_transforms( const trlevel::tr_model& model, const trlevel::ILevel& level) @@ -176,38 +210,12 @@ namespace trview for (uint32_t i = 0; i < level.num_models(); ++i) { const auto model = level.get_model(i); - if (model.NumMeshes > 0xff00) + if (model.NumMeshes > 0xff00 || model.NumMeshes == 0) { continue; } - std::vector> meshes; - - if (version.platform == trlevel::Platform::PSX && equals_any(version.version, trlevel::LevelVersion::Tomb4, trlevel::LevelVersion::Tomb5)) - { - const uint32_t end_pointer = static_cast(model.StartingMesh + model.NumMeshes * 2); - for (uint32_t mesh_pointer = model.StartingMesh; mesh_pointer < end_pointer; mesh_pointer += 2) - { - auto mesh = mesh_storage->mesh(mesh_pointer); - if (mesh) - { - meshes.push_back(mesh); - } - } - } - else - { - const uint32_t end_pointer = static_cast(model.StartingMesh + model.NumMeshes); - for (uint32_t mesh_pointer = model.StartingMesh; mesh_pointer < end_pointer; ++mesh_pointer) - { - auto mesh = mesh_storage->mesh(mesh_pointer); - if (mesh) - { - meshes.push_back(mesh); - } - } - } - + auto meshes = load_meshes(model, version, mesh_storage); auto transforms = load_transforms(model, level); _models.push_back(model_source(model, meshes, transforms)); } @@ -225,4 +233,9 @@ namespace trview } return {}; } + + std::vector> ModelStorage::models() const + { + return _models | std::ranges::to>>(); + } } diff --git a/trview.app/Geometry/Model/ModelStorage.h b/trview.app/Geometry/Model/ModelStorage.h index f2a989ab3..01b16f40d 100644 --- a/trview.app/Geometry/Model/ModelStorage.h +++ b/trview.app/Geometry/Model/ModelStorage.h @@ -16,6 +16,7 @@ namespace trview const trlevel::ILevel& level); virtual ~ModelStorage() = default; std::weak_ptr find_by_type_id(uint16_t type_id) const override; + std::vector> models() const override; private: void load_models(const std::shared_ptr& mesh_storage, const IModel::Source& model_source, diff --git a/trview.app/Geometry/TransparencyBuffer.cpp b/trview.app/Geometry/TransparencyBuffer.cpp index 7bfe2e7fa..970940420 100644 --- a/trview.app/Geometry/TransparencyBuffer.cpp +++ b/trview.app/Geometry/TransparencyBuffer.cpp @@ -77,7 +77,7 @@ namespace trview complete(); } - void TransparencyBuffer::render(const ICamera& camera, bool ignore_blend) + void TransparencyBuffer::render(const Matrix& view_projection, bool ignore_blend) { if (!_vertices.size()) { @@ -101,7 +101,7 @@ namespace trview D3D11_MAPPED_SUBRESOURCE mapped_resource; memset(&mapped_resource, 0, sizeof(mapped_resource)); - MeshData data{ camera.view_projection(), Color(1,1,1,1), Vector4::Zero }; + MeshData data{ view_projection, Color(1,1,1,1), Vector4::Zero }; context->Map(_matrix_buffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_resource); memcpy(mapped_resource.pData, &data, sizeof(data)); diff --git a/trview.app/Geometry/TransparencyBuffer.h b/trview.app/Geometry/TransparencyBuffer.h index b0ad792cc..cff2b8bc3 100644 --- a/trview.app/Geometry/TransparencyBuffer.h +++ b/trview.app/Geometry/TransparencyBuffer.h @@ -37,7 +37,7 @@ namespace trview /// @param camera The current camera. /// @param texture_storage Texture storage for the level. /// @param ignore_blend Optional. Set to true to render this without transparency. - void render(const ICamera& camera, bool ignore_blend = false) override; + void render(const DirectX::SimpleMath::Matrix& view_projection, bool ignore_blend = false) override; // Reset the triangles buffer. void reset() override; diff --git a/trview.app/Graphics/IMeshStorage.h b/trview.app/Graphics/IMeshStorage.h index f7572b930..ed0a402c0 100644 --- a/trview.app/Graphics/IMeshStorage.h +++ b/trview.app/Graphics/IMeshStorage.h @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include #include @@ -12,5 +14,6 @@ namespace trview using Source = std::function(const trlevel::ILevel&, const ILevelTextureStorage&)>; virtual ~IMeshStorage() = 0; virtual std::shared_ptr mesh(uint32_t mesh_pointer) const = 0; + virtual std::map> meshes() const = 0; }; } diff --git a/trview.app/Graphics/MeshStorage.cpp b/trview.app/Graphics/MeshStorage.cpp index b7895aca5..97b245e92 100644 --- a/trview.app/Graphics/MeshStorage.cpp +++ b/trview.app/Graphics/MeshStorage.cpp @@ -1,4 +1,5 @@ #include "MeshStorage.h" +#include namespace trview { @@ -24,4 +25,14 @@ namespace trview } return nullptr; } + + std::map> MeshStorage::meshes() const + { + std::map> meshes; + for (const auto& m : _meshes) + { + meshes[m.first] = m.second; + } + return meshes; + } } diff --git a/trview.app/Graphics/MeshStorage.h b/trview.app/Graphics/MeshStorage.h index 2e87e8f86..eab774b6a 100644 --- a/trview.app/Graphics/MeshStorage.h +++ b/trview.app/Graphics/MeshStorage.h @@ -17,6 +17,7 @@ namespace trview explicit MeshStorage(const IMesh::Source& mesh_source, const trlevel::ILevel& level, const ILevelTextureStorage& texture_storage); virtual ~MeshStorage() = default; virtual std::shared_ptr mesh(uint32_t mesh_pointer) const override; + virtual std::map> meshes() const override; private: mutable std::unordered_map> _meshes; }; diff --git a/trview.app/Graphics/SelectionRenderer.cpp b/trview.app/Graphics/SelectionRenderer.cpp index 0d50d1496..8eea6019a 100644 --- a/trview.app/Graphics/SelectionRenderer.cpp +++ b/trview.app/Graphics/SelectionRenderer.cpp @@ -150,7 +150,7 @@ namespace trview _transparency->reset(); selected_item.get_transparent_triangles(*_transparency, camera, IRenderable::SelectionFill); _transparency->sort(camera.rendering_position()); - _transparency->render(camera, true); + _transparency->render(camera.view_projection(), true); selected_item.set_visible(was_visible); } diff --git a/trview.app/Mocks/Elements/ILevel.h b/trview.app/Mocks/Elements/ILevel.h index dedb475c7..1ce22f4fb 100644 --- a/trview.app/Mocks/Elements/ILevel.h +++ b/trview.app/Mocks/Elements/ILevel.h @@ -91,6 +91,8 @@ namespace trview MOCK_METHOD(void, update, (float), (override)); MOCK_METHOD(void, set_show_animation, (bool), (override)); MOCK_METHOD(void, receive_message, (const Message&), (override)); + MOCK_METHOD(std::weak_ptr, model_storage, (), (const, override)); + MOCK_METHOD(std::weak_ptr, level_texture_storage, (), (const, override)); std::shared_ptr with_version(trlevel::LevelVersion version) { diff --git a/trview.app/Mocks/Geometry/IModelStorage.h b/trview.app/Mocks/Geometry/IModelStorage.h index 173e6693f..67a91efe0 100644 --- a/trview.app/Mocks/Geometry/IModelStorage.h +++ b/trview.app/Mocks/Geometry/IModelStorage.h @@ -11,6 +11,7 @@ namespace trview MockModelStorage(); virtual ~MockModelStorage(); MOCK_METHOD(std::weak_ptr, find_by_type_id, (uint16_t), (const, override)); + MOCK_METHOD(std::vector>, models, (), (const, override)); }; } } \ No newline at end of file diff --git a/trview.app/Mocks/Geometry/ITransparencyBuffer.h b/trview.app/Mocks/Geometry/ITransparencyBuffer.h index cd2eea345..39957d740 100644 --- a/trview.app/Mocks/Geometry/ITransparencyBuffer.h +++ b/trview.app/Mocks/Geometry/ITransparencyBuffer.h @@ -12,7 +12,7 @@ namespace trview virtual ~MockTransparencyBuffer(); MOCK_METHOD(void, add, (const Triangle&), (override)); MOCK_METHOD(void, sort, (const DirectX::SimpleMath::Vector3&), (override)); - MOCK_METHOD(void, render, (const ICamera&, bool), (override)); + MOCK_METHOD(void, render, (const DirectX::SimpleMath::Matrix&, bool), (override)); MOCK_METHOD(void, reset, (), (override)); }; } diff --git a/trview.app/Mocks/Graphics/IMeshStorage.h b/trview.app/Mocks/Graphics/IMeshStorage.h index 0368601ec..93239f110 100644 --- a/trview.app/Mocks/Graphics/IMeshStorage.h +++ b/trview.app/Mocks/Graphics/IMeshStorage.h @@ -11,6 +11,7 @@ namespace trview MockMeshStorage(); virtual ~MockMeshStorage(); MOCK_METHOD(std::shared_ptr, mesh, (uint32_t), (const, override)); + MOCK_METHOD((std::map>), meshes, (), (const, override)); }; } } \ No newline at end of file diff --git a/trview.app/Resources/resource.h b/trview.app/Resources/resource.h index 09dff1b60..48eb3e2e7 100644 --- a/trview.app/Resources/resource.h +++ b/trview.app/Resources/resource.h @@ -54,6 +54,7 @@ #define ID_WINDOWS_DIFF 33031 #define ID_WINDOWS_PACK 33032 #define IDR_LEVEL_HASHES 33033 +#define ID_WINDOWS_MODELS 33033 // Next default values for new objects // diff --git a/trview.app/Resources/trview.app.rc b/trview.app/Resources/trview.app.rc index 9698b36d6..7d57e812a 100644 --- a/trview.app/Resources/trview.app.rc +++ b/trview.app/Resources/trview.app.rc @@ -72,6 +72,7 @@ BEGIN MENUITEM "&Lights\tCtrl+L" ID_WINDOWS_LIGHTS MENUITEM "Camera/Sink\tCtrl+K" ID_WINDOWS_CAMERA_SINK MENUITEM "Log" ID_WINDOWS_LOG + MENUITEM "Models" ID_WINDOWS_MODELS MENUITEM "Textures" ID_WINDOWS_TEXTURES MENUITEM "Console\tF11" ID_WINDOWS_CONSOLE MENUITEM "Plugins\tCtrl+P" ID_WINDOWS_PLUGINS diff --git a/trview.app/Windows/Models/ModelsWindow.cpp b/trview.app/Windows/Models/ModelsWindow.cpp new file mode 100644 index 000000000..2d56af9b2 --- /dev/null +++ b/trview.app/Windows/Models/ModelsWindow.cpp @@ -0,0 +1,221 @@ +#include "ModelsWindow.h" +#include "../../Geometry/Model/IModelStorage.h" +#include "../../Geometry/Model/IModel.h" +#include "../../Messages/Messages.h" +#include "../../Elements/ILevel.h" + +#include +#include + +namespace trview +{ + namespace + { +#pragma warning(push) +#pragma warning(disable : 4324) +#pragma pack(push, 16) + __declspec(align(16)) struct PixelShaderData + { + bool disable_transparency; + }; +#pragma pack(pop) +#pragma warning(pop) + } + + ModelsWindow::ModelsWindow(const std::shared_ptr& device, + const graphics::IRenderTarget::SizeSource& render_target_source, + const std::shared_ptr& shader_storage, + const graphics::IBuffer::ConstantSource& buffer_source, + ITransparencyBuffer::Source transparency_buffer_source, + const graphics::ISamplerState::Source& sampler_source, + std::unique_ptr mouse, + const std::weak_ptr& messaging) + : _device(device), _render_target(render_target_source(512, 512, graphics::IRenderTarget::DepthStencilMode::Enabled)), _transparency_buffer_source(transparency_buffer_source), + _sampler_state(sampler_source(graphics::ISamplerState::AddressMode::Clamp)), _mouse(std::move(mouse)), _messaging(messaging) + { + _vertex_shader = shader_storage->get("level_vertex_shader"); + _pixel_shader = shader_storage->get("level_pixel_shader"); + + D3D11_DEPTH_STENCIL_DESC stencil_desc; + memset(&stencil_desc, 0, sizeof(stencil_desc)); + stencil_desc.DepthEnable = true; + stencil_desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; + stencil_desc.DepthFunc = D3D11_COMPARISON_LESS; + stencil_desc.StencilEnable = true; + stencil_desc.StencilReadMask = 0xFF; + stencil_desc.StencilWriteMask = 0xFF; + stencil_desc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; + stencil_desc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR; + stencil_desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; + stencil_desc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; + stencil_desc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; + stencil_desc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR; + stencil_desc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; + stencil_desc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS; + _depth_stencil_state = device->create_depth_stencil_state(stencil_desc); + + _pixel_shader_data = buffer_source(sizeof(PixelShaderData)); + + _token_store += _mouse->mouse_down += [&](auto button) { _camera_input.mouse_down(button); }; + _token_store += _mouse->mouse_up += [&](auto button) { _camera_input.mouse_up(button); }; + _token_store += _mouse->mouse_move += [&](long x, long y) { _camera_input.mouse_move(x, y); }; + _token_store += _camera_input.on_rotate += [&](float x, float y) + { + if (_mouse_over) + { + const float low_sensitivity = 200.0f; + const float high_sensitivity = 25.0f; + const float sensitivity = low_sensitivity + (high_sensitivity - low_sensitivity); + _rotation += x / sensitivity; + _rotation_pitch += y / sensitivity; + } + }; + } + + void ModelsWindow::render() + { + if (!render_meshes_window()) + { + on_window_closed(); + return; + } + } + + void ModelsWindow::set_level_texture_storage(const std::weak_ptr& level_texture_storage) + { + _transparency_buffer = _transparency_buffer_source(level_texture_storage); + _selected_model.reset(); + } + + void ModelsWindow::set_model_storage(const std::weak_ptr& model_storage) + { + _model_storage = model_storage; + } + + void ModelsWindow::set_number(int32_t number) + { + _id = std::format("Models {}", number); + } + + void ModelsWindow::update(float) + { + } + + bool ModelsWindow::render_meshes_window() + { + _mouse_over = false; + + using namespace DirectX::SimpleMath; + + auto selected_model = _selected_model.lock(); + + auto context = _device->context(); + context->OMSetDepthStencilState(_depth_stencil_state.Get(), 1); + _render_target->clear(Color(0.2f, 0.2f, 0.2f)); + { + context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + _vertex_shader->apply(context); + _pixel_shader->apply(context); + + graphics::RenderTargetStore rs_store(context); + graphics::ViewportStore vp_store(context); + _render_target->apply(); + + if (selected_model) + { + _sampler_state->apply(); + + const auto box = selected_model->bounding_box(); + const float ex = Vector3(box.Extents).Length(); + const auto camera_pos = Vector3(0, box.Center.y - 0.25f, ex * 3.5f); + + Matrix world = Matrix::Identity; + Matrix camera_rotation = Matrix::CreateFromYawPitchRoll(_rotation, _rotation_pitch, 0); + + const auto eye = Vector3::Transform(camera_pos, camera_rotation); + + Matrix view = Matrix::CreateLookAt(eye, box.Center, Vector3(0, -1, 0)); + Matrix projection = Matrix::CreatePerspectiveFieldOfView(DirectX::XM_PIDIV4, 1.0f, 0.1f, 1000.0f); + Matrix view_projection = view * projection; + + graphics::set_data(*_pixel_shader_data, context, PixelShaderData{ false }); + _pixel_shader_data->apply(context, graphics::IBuffer::ApplyTo::PS); + + selected_model->render(world, view_projection, Colour::White); + + _transparency_buffer->reset(); + selected_model->render_transparency(world, *_transparency_buffer, Colour::White); + _transparency_buffer->sort(eye); + + graphics::set_data(*_pixel_shader_data, context, PixelShaderData{ false }); + _pixel_shader_data->apply(context, graphics::IBuffer::ApplyTo::PS); + _transparency_buffer->render(view_projection); + } + } + + bool stay_open = true; + ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(540, 500)); + if (ImGui::Begin(_id.c_str(), &stay_open)) + { + if (ImGui::BeginTable("Meshes List", 1, ImGuiTableFlags_ScrollY, ImVec2(100, 0))) + { + ImGui::TableSetupColumn("ID"); + ImGui::TableSetupScrollFreeze(1, 1); + ImGui::TableHeadersRow(); + + if (auto model_storage = _model_storage.lock()) + { + for (const auto model : model_storage->models()) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + auto model_ptr = model.lock(); + bool selected = model_ptr == selected_model; + if (ImGui::Selectable(std::format("{}", model_ptr->type_id()).c_str(), &selected, ImGuiSelectableFlags_SelectOnNav)) + { + _selected_model = model; + } + } + } + ImGui::EndTable(); + } + + ImGui::SameLine(); + + const auto texture = _render_target->texture(); + ImGui::Image(texture.view().Get(), ImVec2(texture.size().width, texture.size().height)); + _mouse_over = ImGui::GetIO().WantCaptureMouse && ImGui::IsItemHovered(); + } + ImGui::End(); + ImGui::PopStyleVar(); + return stay_open; + } + + void ModelsWindow::initialise() + { + messages::get_open_level(_messaging, weak_from_this()); + } + + void ModelsWindow::receive_message(const Message& message) + { + if (auto level = messages::read_open_level(message)) + { + if (auto level_ptr = level->lock()) + { + set_level_texture_storage(level_ptr->texture_storage()); + set_model_storage(level_ptr->model_storage()); + } + } + } + + std::string ModelsWindow::title() const + { + return _id; + } + + std::string ModelsWindow::type() const + { + return "Models"; + } +} diff --git a/trview.app/Windows/Models/ModelsWindow.h b/trview.app/Windows/Models/ModelsWindow.h new file mode 100644 index 000000000..0da9e4e7e --- /dev/null +++ b/trview.app/Windows/Models/ModelsWindow.h @@ -0,0 +1,60 @@ +#pragma once + +#include "../IWindow.h" +#include +#include +#include "../../Geometry/ITransparencyBuffer.h" +#include +#include +#include "../../Camera/CameraInput.h" +#include +#include "../../Geometry/Model/IModelStorage.h" + +namespace trview +{ + struct IModel; + class ModelsWindow final : public IWindow, public std::enable_shared_from_this + { + public: + ModelsWindow(const std::shared_ptr& device, + const graphics::IRenderTarget::SizeSource& render_target_source, + const std::shared_ptr& shader_storage, + const graphics::IBuffer::ConstantSource& buffer_source, + ITransparencyBuffer::Source transparency_buffer_source, + const graphics::ISamplerState::Source& sampler_source, + std::unique_ptr mouse, + const std::weak_ptr& messaging); + virtual ~ModelsWindow() = default; + void render() override; + void initialise(); + void set_number(int32_t number) override; + void update(float delta) override; + void receive_message(const Message& message) override; + std::string title() const override; + std::string type() const override; + private: + bool render_meshes_window(); + void set_level_texture_storage(const std::weak_ptr& level_texture_storage); + void set_model_storage(const std::weak_ptr& model_storage); + + std::string _id{ "Models 0" }; + std::weak_ptr _model_storage; + std::weak_ptr _selected_model; + std::unique_ptr _render_target; + std::shared_ptr _device; + Microsoft::WRL::ComPtr _depth_stencil_state; + graphics::IShader* _vertex_shader; + graphics::IShader* _pixel_shader; + float _rotation{ 0.0f }; + float _rotation_pitch{ 0.0f }; + std::unique_ptr _pixel_shader_data; + std::unique_ptr _transparency_buffer; + ITransparencyBuffer::Source _transparency_buffer_source; + std::shared_ptr _sampler_state; + std::unique_ptr _mouse; + TokenStore _token_store; + CameraInput _camera_input; + bool _mouse_over{ false }; + std::weak_ptr _messaging; + }; +} diff --git a/trview.app/Windows/Windows.cpp b/trview.app/Windows/Windows.cpp index 5113f012f..685af627e 100644 --- a/trview.app/Windows/Windows.cpp +++ b/trview.app/Windows/Windows.cpp @@ -72,6 +72,11 @@ namespace trview create("Log"); break; } + case ID_WINDOWS_MODELS: + { + create("Models"); + break; + } case ID_WINDOWS_PACK: { create("Pack"); diff --git a/trview.app/trview.app.vcxproj b/trview.app/trview.app.vcxproj index 48224e360..f1d9a8878 100644 --- a/trview.app/trview.app.vcxproj +++ b/trview.app/trview.app.vcxproj @@ -88,6 +88,7 @@ copy ""$(OutDir)*.cso"" ""$(ProjectDir)Resources\Generated"" + @@ -223,6 +224,7 @@ copy ""$(OutDir)*.cso"" ""$(ProjectDir)Resources\Generated"" + diff --git a/trview.app/trview.app.vcxproj.filters b/trview.app/trview.app.vcxproj.filters index ef3c040e0..dad6da89f 100644 --- a/trview.app/trview.app.vcxproj.filters +++ b/trview.app/trview.app.vcxproj.filters @@ -118,6 +118,7 @@ + @@ -357,12 +358,9 @@ - - Windows - - - Mocks\Windows - + + + @@ -587,6 +585,9 @@ {03a8e721-8c98-438d-a8eb-c67a9e88e956} + + {bb2435da-29e4-489f-a207-f9e9f86ef14b} + {52dec27a-a711-4587-9a97-4ae9bdb7a050}