diff --git a/trview.app.tests/Elements/RoomTests.cpp b/trview.app.tests/Elements/RoomTests.cpp index c0641fbd0..e49d80898 100644 --- a/trview.app.tests/Elements/RoomTests.cpp +++ b/trview.app.tests/Elements/RoomTests.cpp @@ -14,6 +14,7 @@ #include #include #include +#include using namespace trview; using namespace trview::mocks; @@ -40,11 +41,12 @@ namespace ISector::Source sector_source{ [](auto&&...) { return mock_shared(); } }; std::shared_ptr log{ mock_shared() }; graphics::ISamplerState::Source sampler_source{ [](auto&&...) { return mock_shared(); } }; + IPortal::Source portal_source{ [](auto&&...) { return mock_shared(); } }; std::shared_ptr build() { auto new_room = std::make_shared(room, mesh_source, level_texture_storage, index, level, sampler_source); - new_room->initialise(*tr_level, room, *mesh_storage, static_mesh_source, static_mesh_position_source, sector_source, 0, Activity(log, "Level", "Room 0")); + new_room->initialise(*tr_level, room, *mesh_storage, static_mesh_source, static_mesh_position_source, sector_source, 0, portal_source, Activity(log, "Level", "Room 0")); return new_room; } diff --git a/trview.app/ApplicationCreate.cpp b/trview.app/ApplicationCreate.cpp index 084547c90..4b9b68969 100644 --- a/trview.app/ApplicationCreate.cpp +++ b/trview.app/ApplicationCreate.cpp @@ -329,11 +329,16 @@ namespace trview auto ngplus = std::make_shared(entity_source); + auto portal_source = [&](auto&&... args) + { + return std::make_shared(args...); + }; + auto room_source = [=](const trlevel::ILevel& level, const trlevel::tr3_room& room, const std::shared_ptr& texture_storage, const IMeshStorage& mesh_storage, uint32_t index, const std::weak_ptr& parent_level, uint32_t sector_base_index, const Activity& activity) { auto new_room = std::make_shared(room, mesh_source, texture_storage, index, parent_level, sampler_source); - new_room->initialise(level, room, mesh_storage, static_mesh_source, static_mesh_position_source, sector_source, sector_base_index, activity); + new_room->initialise(level, room, mesh_storage, static_mesh_source, static_mesh_position_source, sector_source, sector_base_index, portal_source, activity); return new_room; }; auto trigger_source = [=](auto&&... args) { return std::make_shared(args..., mesh_source); }; diff --git a/trview.app/Elements/ILevel.h b/trview.app/Elements/ILevel.h index 36b4a9277..8f3bf97a5 100644 --- a/trview.app/Elements/ILevel.h +++ b/trview.app/Elements/ILevel.h @@ -114,13 +114,16 @@ namespace trview virtual void set_show_water(bool show) = 0; virtual void set_show_wireframe(bool show) = 0; virtual void set_show_bounding_boxes(bool show) = 0; + virtual void set_show_horizontal_portals(bool show) = 0; virtual void set_show_lighting(bool show) = 0; virtual void set_show_lights(bool show) = 0; + virtual void set_show_portals(bool show) = 0; virtual void set_show_items(bool show) = 0; virtual void set_show_rooms(bool show) = 0; virtual void set_show_camera_sinks(bool show) = 0; virtual void set_show_sound_sources(bool show) = 0; virtual void set_show_animation(bool show) = 0; + virtual void set_show_vertical_portals(bool show) = 0; virtual void set_neighbour_depth(uint32_t depth) = 0; virtual void set_selected_room(const std::weak_ptr& room) = 0; virtual void set_selected_item(const std::weak_ptr& item) = 0; diff --git a/trview.app/Elements/IRoom.h b/trview.app/Elements/IRoom.h index 11d20dc4f..a401667aa 100644 --- a/trview.app/Elements/IRoom.h +++ b/trview.app/Elements/IRoom.h @@ -21,6 +21,7 @@ #include "CameraSink/ICameraSink.h" #include "IStaticMesh.h" #include "../Filters/IFilterable.h" +#include "Portal/Portal.h" namespace trview { @@ -330,6 +331,7 @@ namespace trview virtual std::vector> static_meshes() const = 0; virtual void update(float delta) = 0; virtual uint16_t water_scheme() const = 0; + virtual std::vector> portals() const = 0; }; /// diff --git a/trview.app/Elements/Level.cpp b/trview.app/Elements/Level.cpp index 49bb7e49d..fcb2e7daf 100644 --- a/trview.app/Elements/Level.cpp +++ b/trview.app/Elements/Level.cpp @@ -1767,6 +1767,24 @@ namespace trview { _show_animation = show; } + + void Level::set_show_portals(bool show) + { + _render_filters = set_flag(_render_filters, RenderFilter::Portals, show); + _regenerate_transparency = true; + } + + void Level::set_show_horizontal_portals(bool show) + { + _render_filters = set_flag(_render_filters, RenderFilter::PortalsHorizontal, show); + _regenerate_transparency = true; + } + + void Level::set_show_vertical_portals(bool show) + { + _render_filters = set_flag(_render_filters, RenderFilter::PortalsVertical, show); + _regenerate_transparency = true; + } void Level::receive_message(const Message& message) { diff --git a/trview.app/Elements/Level.h b/trview.app/Elements/Level.h index 045f95272..6a80eaa1c 100644 --- a/trview.app/Elements/Level.h +++ b/trview.app/Elements/Level.h @@ -148,6 +148,9 @@ namespace trview void update(float delta) override; void set_show_animation(bool show) override; void receive_message(const Message& message) override; + void set_show_horizontal_portals(bool show) override; + void set_show_vertical_portals(bool show) override; + void set_show_portals(bool show) 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/Elements/Portal/IPortal.h b/trview.app/Elements/Portal/IPortal.h new file mode 100644 index 000000000..3b8e9d052 --- /dev/null +++ b/trview.app/Elements/Portal/IPortal.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#include + +namespace trview +{ + struct ITransparencyBuffer; + struct ICamera; + + struct IPortal + { + using Source = std::function(const trlevel::tr_room_portal&, const DirectX::SimpleMath::Vector3&)>; + virtual ~IPortal() = 0; + virtual void get_transparent_triangles(ITransparencyBuffer& transparency, const ICamera& camera) = 0; + virtual DirectX::SimpleMath::Vector3 normal() const = 0; + virtual uint16_t room() const = 0; + virtual std::vector vertices() const = 0; + }; +} diff --git a/trview.app/Elements/Portal/Portal.cpp b/trview.app/Elements/Portal/Portal.cpp new file mode 100644 index 000000000..49e1b901b --- /dev/null +++ b/trview.app/Elements/Portal/Portal.cpp @@ -0,0 +1,51 @@ +#include "Portal.h" +#include "../../Geometry/ITransparencyBuffer.h" + +namespace trview +{ + IPortal::~IPortal() + { + } + + Portal::Portal(const trlevel::tr_room_portal& portal, DirectX::SimpleMath::Vector3 room_offset) + : _room(portal.adjoining_room), _normal(portal.normal.x, portal.normal.y, portal.normal.z), _room_offset(room_offset) + { + _vertices = + { + (DirectX::SimpleMath::Vector3(portal.vertices[0].x, portal.vertices[0].y, portal.vertices[0].z) / trlevel::Scale), + (DirectX::SimpleMath::Vector3(portal.vertices[1].x, portal.vertices[1].y, portal.vertices[1].z) / trlevel::Scale), + (DirectX::SimpleMath::Vector3(portal.vertices[2].x, portal.vertices[2].y, portal.vertices[2].z) / trlevel::Scale), + (DirectX::SimpleMath::Vector3(portal.vertices[3].x, portal.vertices[3].y, portal.vertices[3].z) / trlevel::Scale) + }; + } + + void Portal::get_transparent_triangles(ITransparencyBuffer& transparency, const ICamera&) + { + const DirectX::SimpleMath::Color portal_colour = DirectX::SimpleMath::Color(1.0f, 0.0f, 0.0f, 0.5f); + transparency.add({ + .colours = { portal_colour, portal_colour, portal_colour }, + .texture_mode = Triangle::TextureMode::Untextured, + .transparency_mode = Triangle::TransparencyMode::Normal, + .vertices = { _vertices[0] + _room_offset, _vertices[1] + _room_offset, _vertices[2] + _room_offset } }); + transparency.add({ + .colours = { portal_colour, portal_colour, portal_colour }, + .texture_mode = Triangle::TextureMode::Untextured, + .transparency_mode = Triangle::TransparencyMode::Normal, + .vertices = { _vertices[2] + _room_offset, _vertices[3] + _room_offset, _vertices[0] + _room_offset } }); + } + + DirectX::SimpleMath::Vector3 Portal::normal() const + { + return _normal; + } + + uint16_t Portal::room() const + { + return _room; + } + + std::vector Portal::vertices() const + { + return _vertices; + } +} diff --git a/trview.app/Elements/Portal/Portal.h b/trview.app/Elements/Portal/Portal.h new file mode 100644 index 000000000..36295e32a --- /dev/null +++ b/trview.app/Elements/Portal/Portal.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include "IPortal.h" + +namespace trview +{ + class Portal final : public IPortal + { + public: + virtual ~Portal() = default; + explicit Portal(const trlevel::tr_room_portal& portal, DirectX::SimpleMath::Vector3 room_offset); + void get_transparent_triangles(ITransparencyBuffer& transparency, const ICamera& camera) override; + DirectX::SimpleMath::Vector3 normal() const override; + uint16_t room() const override; + std::vector vertices() const override; + private: + std::vector _vertices; + DirectX::SimpleMath::Vector3 _room_offset; + DirectX::SimpleMath::Vector3 _normal; + uint16_t _room{ 0u }; + }; +} diff --git a/trview.app/Elements/RenderFilter.h b/trview.app/Elements/RenderFilter.h index 76f95c34f..3d0cbd40b 100644 --- a/trview.app/Elements/RenderFilter.h +++ b/trview.app/Elements/RenderFilter.h @@ -16,6 +16,9 @@ namespace trview Lighting = 0x100, SoundSources = 0x200, NgPlus = 0x400, + Portals = 0x800, + PortalsHorizontal = 0x1000, + PortalsVertical = 0x2000, All = 0xffffffff, Default = Rooms | Entities | Triggers | Water }; diff --git a/trview.app/Elements/Room.cpp b/trview.app/Elements/Room.cpp index 3f707b694..742e5ab63 100644 --- a/trview.app/Elements/Room.cpp +++ b/trview.app/Elements/Room.cpp @@ -161,12 +161,13 @@ namespace trview void Room::initialise(const trlevel::ILevel& level, const trlevel::tr3_room& room, const IMeshStorage& mesh_storage, const IStaticMesh::MeshSource& static_mesh_mesh_source, const IStaticMesh::PositionSource& static_mesh_position_source, - const ISector::Source& sector_source, uint32_t sector_base_index, const Activity& activity) + const ISector::Source& sector_source, uint32_t sector_base_index, const IPortal::Source& portal_source, const Activity& activity) { generate_sectors(level, room, sector_source, sector_base_index); generate_geometry(_mesh_source, room); generate_adjacency(); generate_static_meshes(_mesh_source, level, room, mesh_storage, static_mesh_mesh_source, static_mesh_position_source, activity); + generate_portals(portal_source, room); } RoomInfo Room::info() const @@ -585,6 +586,19 @@ namespace trview } } + if (has_flag(render_filter, RenderFilter::Portals) && + has_any_flag(render_filter, RenderFilter::PortalsHorizontal, RenderFilter::PortalsVertical)) + { + for (const auto& portal : _portals) + { + if ((has_flag(render_filter, RenderFilter::PortalsHorizontal) && portal->normal().y == 0) || + (has_flag(render_filter, RenderFilter::PortalsVertical) && portal->normal().y != 0)) + { + portal->get_transparent_triangles(transparency, camera); + } + } + } + get_contained_transparent_triangles(transparency, camera, colour, render_filter); } @@ -1281,6 +1295,21 @@ namespace trview } } + std::vector> Room::portals() const + { + return _portals | std::ranges::to>>(); + } + + void Room::generate_portals(const IPortal::Source& portal_source, const trlevel::tr3_room& room) + { + if (auto level = _level.lock()) + { + const float offset_y = level->platform_and_version().platform == trlevel::Platform::PSX ? static_cast(room.info.yTop) : 0.0f; + const auto offset = DirectX::SimpleMath::Vector3 { static_cast(room.info.x), offset_y, static_cast(room.info.z) } / trlevel::Scale; + _portals = room.portals | std::views::transform([&](auto&& p) { return portal_source(p, offset); }) | std::ranges::to(); + } + } + uint16_t Room::water_scheme() const { return _water_scheme; diff --git a/trview.app/Elements/Room.h b/trview.app/Elements/Room.h index b2cfb4566..2379598e5 100644 --- a/trview.app/Elements/Room.h +++ b/trview.app/Elements/Room.h @@ -14,6 +14,7 @@ #include #include #include +#include "Portal/IPortal.h" #include #include "IStaticMesh.h" #include "IRoom.h" @@ -101,10 +102,12 @@ namespace trview const IStaticMesh::PositionSource& static_mesh_position_source, const ISector::Source& sector_source, uint32_t sector_base_index, + const IPortal::Source& portal_source, const Activity& activity); std::vector> static_meshes() const override; void update(float delta) override; uint16_t water_scheme() const override; + std::vector> portals() const override; int32_t filterable_index() const override; private: void generate_geometry(const IMesh::Source& mesh_source, const trlevel::tr3_room& room); @@ -116,15 +119,13 @@ namespace trview void generate_sectors(const trlevel::ILevel& level, const trlevel::tr3_room& room, const ISector::Source& sector_source, uint32_t sector_base_index); ISector* get_trigger_sector(const ITrigger& trigger, int32_t dx, int32_t dz); uint32_t get_sector_id(int32_t x, int32_t z) const; - /// Find any transparent triangles that match floor data geometry. /// @param transparent_triangles The transparent triangles in the sector. /// @param collision_triangles The collision output vector. void process_collision_transparency(std::vector& triangles); - void generate_all_geometry_mesh(const IMesh::Source& mesh_source); - void add_centroid_to_pick(const IMesh& mesh, PickResult& geometry_result) const; + void generate_portals(const IPortal::Source& portal_source, const trlevel::tr3_room& room); RoomInfo _info; std::set _neighbours; @@ -171,6 +172,7 @@ namespace trview std::shared_ptr _geometry_sampler_state; std::shared_ptr _sampler_state; + std::vector> _portals; uint16_t _water_scheme{ 0u }; }; } \ No newline at end of file diff --git a/trview.app/Messages/Messages.cpp b/trview.app/Messages/Messages.cpp index 24b7e52a8..35d295f67 100644 --- a/trview.app/Messages/Messages.cpp +++ b/trview.app/Messages/Messages.cpp @@ -288,6 +288,16 @@ namespace trview send_message(messaging, light, "select_light"); } + std::optional> read_select_portal(const Message& message) + { + return read_message>(message, "select_portal"); + } + + void send_select_portal(const std::weak_ptr& messaging, const std::weak_ptr& portal) + { + send_message(messaging, portal, "select_portal"); + } + void get_selected_room(const std::weak_ptr& messaging, const std::weak_ptr& reply_to) { get_message(messaging, reply_to, "get_selected_room"); diff --git a/trview.app/Messages/Messages.h b/trview.app/Messages/Messages.h index c2dbc62e7..00876f653 100644 --- a/trview.app/Messages/Messages.h +++ b/trview.app/Messages/Messages.h @@ -12,6 +12,7 @@ namespace trview struct ILevel; struct ILight; struct IMessageSystem; + struct IPortal; struct IRecipient; struct IRoom; struct IRoute; @@ -105,6 +106,9 @@ namespace trview std::optional> read_select_light(const Message& message); void send_select_light(const std::weak_ptr& messaging, const std::weak_ptr& light); + std::optional> read_select_portal(const Message& message); + void send_select_portal(const std::weak_ptr& messaging, const std::weak_ptr& portal); + void get_selected_room(const std::weak_ptr& messaging, const std::weak_ptr& reply_to); std::optional> read_select_room(const Message& message); void send_select_room(const std::weak_ptr& messaging, const std::weak_ptr& room); diff --git a/trview.app/Mocks/Elements/ILevel.h b/trview.app/Mocks/Elements/ILevel.h index f1b4ba39d..5563c610b 100644 --- a/trview.app/Mocks/Elements/ILevel.h +++ b/trview.app/Mocks/Elements/ILevel.h @@ -91,6 +91,9 @@ 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(void, set_show_horizontal_portals, (bool), (override)); + MOCK_METHOD(void, set_show_vertical_portals, (bool), (override)); + MOCK_METHOD(void, set_show_portals, (bool), (override)); std::shared_ptr with_version(trlevel::LevelVersion version) { diff --git a/trview.app/Mocks/Elements/IPortal.h b/trview.app/Mocks/Elements/IPortal.h new file mode 100644 index 000000000..7c2703f7e --- /dev/null +++ b/trview.app/Mocks/Elements/IPortal.h @@ -0,0 +1,19 @@ +#pragma once + +#include "../../Elements/Portal/IPortal.h" + +namespace trview +{ + namespace mocks + { + struct MockPortal : public IPortal + { + explicit MockPortal(); + virtual ~MockPortal(); + MOCK_METHOD(void, get_transparent_triangles, (ITransparencyBuffer&, const ICamera&), (override)); + MOCK_METHOD(DirectX::SimpleMath::Vector3, normal, (), (const, override)); + MOCK_METHOD(uint16_t, room, (), (const, override)); + MOCK_METHOD(std::vector, vertices, (), (const, override)); + }; + } +} diff --git a/trview.app/Mocks/Elements/IRoom.h b/trview.app/Mocks/Elements/IRoom.h index e837645ce..1b122bd2a 100644 --- a/trview.app/Mocks/Elements/IRoom.h +++ b/trview.app/Mocks/Elements/IRoom.h @@ -64,6 +64,7 @@ namespace trview MOCK_METHOD(std::vector>, static_meshes, (), (const)); MOCK_METHOD(void, update, (float), (override)); MOCK_METHOD(uint16_t, water_scheme, (), (const, override)); + MOCK_METHOD(std::vector>, portals, (), (const, override)); MOCK_METHOD(int32_t, filterable_index, (), (const, override)); bool _visible_state{ false }; diff --git a/trview.app/Mocks/Mocks.cpp b/trview.app/Mocks/Mocks.cpp index a250aa3bc..472b92bb4 100644 --- a/trview.app/Mocks/Mocks.cpp +++ b/trview.app/Mocks/Mocks.cpp @@ -15,6 +15,7 @@ #include "Elements/ITrigger.h" #include "Elements/ITypeInfoLookup.h" #include "Elements/ILevelNameLookup.h" +#include "Elements/IPortal.h" #include "Filters/IFilterStore.h" #include "Filters/IFilterable.h" #include "Geometry/IMesh.h" @@ -219,6 +220,9 @@ namespace trview MockFilterStore::MockFilterStore() {}; MockFilterStore::~MockFilterStore() {}; + MockPortal::MockPortal() {}; + MockPortal::~MockPortal() {}; + MockFilterable::MockFilterable() {}; MockFilterable::~MockFilterable() {}; } diff --git a/trview.app/UI/ViewOptions.cpp b/trview.app/UI/ViewOptions.cpp index 162170f8b..0fb5ab81f 100644 --- a/trview.app/UI/ViewOptions.cpp +++ b/trview.app/UI/ViewOptions.cpp @@ -74,6 +74,8 @@ namespace trview _toggles[IViewer::Options::notes] = true; _toggles[IViewer::Options::sound_sources] = false; _toggles[IViewer::Options::ng_plus] = false; + _toggles[IViewer::Options::horizontal_portals] = false; + _toggles[IViewer::Options::vertical_portals] = false; } void ViewOptions::render() @@ -105,16 +107,6 @@ namespace trview add_toggle(IViewer::Options::rooms); add_toggle(IViewer::Options::camera_sinks); ImGui::TableNextRow(); - add_toggle(IViewer::Options::depth_enabled); - ImGui::TableNextColumn(); - ImGui::PushItemWidth(-1); - if (ImGui::InputInt(IViewer::Options::depth.c_str(), &_scalars[IViewer::Options::depth], 1, 10)) - { - _scalars[IViewer::Options::depth] = std::max(0, _scalars[IViewer::Options::depth]); - on_scalar_changed(IViewer::Options::depth, _scalars[IViewer::Options::depth]); - } - ImGui::PopItemWidth(); - ImGui::TableNextRow(); add_toggle(IViewer::Options::wireframe); add_toggle(IViewer::Options::geometry); ImGui::TableNextRow(); @@ -127,6 +119,60 @@ namespace trview ImGui::BeginDisabled(!_ng_plus_enabled); add_toggle(IViewer::Options::ng_plus); ImGui::EndDisabled(); + + ImGui::TableNextRow(); + add_toggle(IViewer::Options::depth_enabled); + ImGui::TableNextColumn(); + ImGui::BeginDisabled(!_toggles[IViewer::Options::depth_enabled]); + ImGui::PushItemWidth(-1); + if (ImGui::InputInt(IViewer::Options::depth.c_str(), &_scalars[IViewer::Options::depth], 1, 10)) + { + _scalars[IViewer::Options::depth] = std::max(0, _scalars[IViewer::Options::depth]); + on_scalar_changed(IViewer::Options::depth, _scalars[IViewer::Options::depth]); + } + ImGui::PopItemWidth(); + ImGui::EndDisabled(); + + ImGui::TableNextRow(); + add_toggle(IViewer::Options::portals_enabled); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-1); + + const bool horizontal_portals = _toggles[IViewer::Options::horizontal_portals]; + const bool vertical_portals = _toggles[IViewer::Options::vertical_portals]; + const std::string current_portal_option = + (horizontal_portals && vertical_portals) ? "All" : + horizontal_portals ? "Horizontal" : + vertical_portals ? "Vertical" : + "None"; + + ImGui::BeginDisabled(!_toggles[IViewer::Options::portals_enabled]); + if (ImGui::BeginCombo("##Portals", current_portal_option.c_str())) + { + if (ImGui::Selectable("Horizontal")) + { + _toggles[IViewer::Options::horizontal_portals] = true; + _toggles[IViewer::Options::vertical_portals] = false; + on_toggle_changed(IViewer::Options::horizontal_portals, true); + on_toggle_changed(IViewer::Options::vertical_portals, false); + } + if (ImGui::Selectable("Vertical")) + { + _toggles[IViewer::Options::horizontal_portals] = false; + _toggles[IViewer::Options::vertical_portals] = true; + on_toggle_changed(IViewer::Options::horizontal_portals, false); + on_toggle_changed(IViewer::Options::vertical_portals, true); + } + if (ImGui::Selectable("All")) + { + _toggles[IViewer::Options::horizontal_portals] = true; + _toggles[IViewer::Options::vertical_portals] = true; + on_toggle_changed(IViewer::Options::horizontal_portals, true); + on_toggle_changed(IViewer::Options::vertical_portals, true); + } + ImGui::EndCombo(); + } + ImGui::EndDisabled(); ImGui::TableNextRow(); if (!_use_alternate_groups) { diff --git a/trview.app/Windows/IViewer.h b/trview.app/Windows/IViewer.h index 62a2739d8..6ad5acd20 100644 --- a/trview.app/Windows/IViewer.h +++ b/trview.app/Windows/IViewer.h @@ -34,6 +34,9 @@ namespace trview inline static const std::string notes = "Notes"; inline static const std::string sound_sources = "Sounds"; inline static const std::string ng_plus = "NG+"; + inline static const std::string portals_enabled = "Portals"; + inline static const std::string horizontal_portals = "Horizontal Portals"; + inline static const std::string vertical_portals = "Vertical Portals"; }; virtual ~IViewer() = 0; diff --git a/trview.app/Windows/IWindows.h b/trview.app/Windows/IWindows.h index b92698f1d..fb9111510 100644 --- a/trview.app/Windows/IWindows.h +++ b/trview.app/Windows/IWindows.h @@ -1,19 +1,12 @@ #pragma once +#include #include -#include +#include +#include namespace trview { - struct IFlybyNode; - struct ILevel; - struct ILight; - struct IRoom; - struct IRoute; - struct ISector; - struct IStaticMesh; - struct ITrigger; - struct IWaypoint; struct IWindow; struct UserSettings; diff --git a/trview.app/Windows/RoomsWindow.cpp b/trview.app/Windows/RoomsWindow.cpp index d89a4ab5c..1b7126ddd 100644 --- a/trview.app/Windows/RoomsWindow.cpp +++ b/trview.app/Windows/RoomsWindow.cpp @@ -744,6 +744,12 @@ namespace trview ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("Portals")) + { + render_portals_tab(); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); } } @@ -1474,6 +1480,58 @@ namespace trview } } + void RoomsWindow::render_portals_tab() + { + if (ImGui::BeginTable("Portals", 6, ImGuiTableFlags_Sortable | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_ScrollY)) + { + ImGui::TableSetupColumn("Room"); + ImGui::TableSetupColumn("Normal"); + ImGui::TableSetupColumn("Vertex 0"); + ImGui::TableSetupColumn("Vertex 1"); + ImGui::TableSetupColumn("Vertex 2"); + ImGui::TableSetupColumn("Vertex 3"); + ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableHeadersRow(); + + if (const auto room = _selected_room.lock()) + { + for (const auto& portal : room->portals()) + { + if (const auto portal_ptr = portal.lock()) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + bool selected = false; + if (ImGui::Selectable(std::format("{}", portal_ptr->room()).c_str(), &selected, ImGuiSelectableFlags_SpanAllColumns | static_cast(ImGuiSelectableFlags_SelectOnNav))) + { + messages::send_select_portal(_messaging, portal); + } + + ImGui::TableNextColumn(); + const auto normal = portal_ptr->normal(); + ImGui::Text(std::format("{},{},{}", normal.x, normal.y, normal.z).c_str()); + + ImGui::TableNextColumn(); + const auto vertices = portal_ptr->vertices() | std::views::transform([](auto&& v) { return v * trlevel::Scale; }) | std::ranges::to(); + ImGui::Text(std::format("{},{},{}", vertices[0].x, vertices[0].y, vertices[0].z).c_str()); + + ImGui::TableNextColumn(); + ImGui::Text(std::format("{},{},{}", vertices[1].x, vertices[1].y, vertices[1].z).c_str()); + + ImGui::TableNextColumn(); + ImGui::Text(std::format("{},{},{}", vertices[2].x, vertices[2].y, vertices[2].z).c_str()); + + ImGui::TableNextColumn(); + ImGui::Text(std::format("{},{},{}", vertices[3].x, vertices[3].y, vertices[3].z).c_str()); + } + } + } + + ImGui::EndTable(); + } + } + void RoomsWindow::initialise() { messages::get_open_level(_messaging, weak_from_this()); diff --git a/trview.app/Windows/RoomsWindow.h b/trview.app/Windows/RoomsWindow.h index 3982118b1..cb743e246 100644 --- a/trview.app/Windows/RoomsWindow.h +++ b/trview.app/Windows/RoomsWindow.h @@ -79,6 +79,7 @@ namespace trview void render_statics_tab(); void set_static_meshes(const std::vector>& static_meshes); std::optional convert_to_sector_filters() const; + void render_portals_tab(); void apply_sector_filters(); diff --git a/trview.app/Windows/Viewer.cpp b/trview.app/Windows/Viewer.cpp index 3bde0dfc5..f9ca33a1f 100644 --- a/trview.app/Windows/Viewer.cpp +++ b/trview.app/Windows/Viewer.cpp @@ -75,6 +75,9 @@ namespace trview toggles[Options::notes] = [](bool) {}; toggles[Options::sound_sources] = [this](bool value) { set_show_sound_sources(value); }; toggles[Options::ng_plus] = [this](bool value) { set_ng_plus(value); }; + toggles[Options::portals_enabled] = [this](bool value) { set_show_portals(value); }; + toggles[Options::horizontal_portals] = [this](bool value) { set_show_horizontal_portals(value); }; + toggles[Options::vertical_portals] = [this](bool value) { set_show_vertical_portals(value); }; const auto persist_toggle_value = [&](const std::string& name, bool value) { @@ -714,6 +717,9 @@ namespace trview new_level->set_show_sound_sources(_ui->toggle(Options::sound_sources)); new_level->set_ng_plus(_ui->toggle(Options::ng_plus)); new_level->set_show_animation(_ui->toggle(Options::animation)); + new_level->set_show_portals(_ui->toggle(Options::portals_enabled)); + new_level->set_show_horizontal_portals(_ui->toggle(Options::horizontal_portals)); + new_level->set_show_vertical_portals(_ui->toggle(Options::vertical_portals)); // Set up the views. auto rooms = new_level->rooms(); @@ -1669,6 +1675,33 @@ namespace trview } } + void Viewer::set_show_portals(bool show) + { + if (auto level = _level.lock()) + { + level->set_show_portals(show); + } + set_toggle(Options::portals_enabled, show); + } + + void Viewer::set_show_horizontal_portals(bool show) + { + if (auto level = _level.lock()) + { + level->set_show_horizontal_portals(show); + } + set_toggle(Options::horizontal_portals, show); + } + + void Viewer::set_show_vertical_portals(bool show) + { + if (auto level = _level.lock()) + { + level->set_show_vertical_portals(show); + } + set_toggle(Options::vertical_portals, show); + } + void Viewer::initialise() { messages::get_selected_room(_messaging, weak_from_this()); diff --git a/trview.app/Windows/Viewer.h b/trview.app/Windows/Viewer.h index d9216c97d..9c60c9441 100644 --- a/trview.app/Windows/Viewer.h +++ b/trview.app/Windows/Viewer.h @@ -135,6 +135,9 @@ namespace trview void set_show_sound_sources(bool show); void set_ng_plus(bool show); void set_show_animation(bool show); + void set_show_portals(bool show); + void set_show_horizontal_portals(bool show); + void set_show_vertical_portals(bool show); template std::shared_ptr get_entity_and_sync_level(const std::weak_ptr& entity); diff --git a/trview.app/trview.app.vcxproj b/trview.app/trview.app.vcxproj index b33d1858e..6d055edf4 100644 --- a/trview.app/trview.app.vcxproj +++ b/trview.app/trview.app.vcxproj @@ -36,6 +36,7 @@ copy ""$(OutDir)*.cso"" ""$(ProjectDir)Resources\Generated"" + @@ -102,6 +103,8 @@ copy ""$(OutDir)*.cso"" ""$(ProjectDir)Resources\Generated"" + + @@ -124,6 +127,7 @@ copy ""$(OutDir)*.cso"" ""$(ProjectDir)Resources\Generated"" + diff --git a/trview.app/trview.app.vcxproj.filters b/trview.app/trview.app.vcxproj.filters index 5d5575e16..72a2369d3 100644 --- a/trview.app/trview.app.vcxproj.filters +++ b/trview.app/trview.app.vcxproj.filters @@ -119,6 +119,7 @@ + @@ -362,6 +363,9 @@ + + + @@ -599,12 +603,12 @@ {52dec27a-a711-4587-9a97-4ae9bdb7a050} + + {25931d9f-2166-459b-b2fb-b78b5c70b82d} + {a047438d-5a92-49e4-b596-108105134baa} - - {42407171-2e71-4eb4-8831-1c2f46223206} - {a790e61b-eddf-4acc-a368-29a916640695}