Index: build/premake/premake5.lua =================================================================== --- build/premake/premake5.lua +++ build/premake/premake5.lua @@ -1115,6 +1115,7 @@ "CustomControls/FileHistory", "CustomControls/HighResTimer", "CustomControls/MapDialog", + "CustomControls/MapResizeDialog", "CustomControls/SnapSplitterWindow", "CustomControls/VirtualDirTreeCtrl", "CustomControls/Windows", Index: source/graphics/Terrain.h =================================================================== --- source/graphics/Terrain.h +++ source/graphics/Terrain.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -98,8 +98,9 @@ // should be in the direction (1,-1); false if it should be (1,1) bool GetTriangulationDir(ssize_t i, ssize_t j) const; - // resize this terrain such that each side has given number of patches - void Resize(ssize_t size); + // Resize this terrain such that each side has given number of patches, + // with the center offset in patches from the larger of the two sizes. + void ResizeRecenter(ssize_t size, int horizontalOffset = 0, int verticalOffset = 0); // set up a new heightmap from 16 bit data; assumes heightmap matches current terrain size void SetHeightMap(u16* heightmap); Index: source/graphics/Terrain.cpp =================================================================== --- source/graphics/Terrain.cpp +++ source/graphics/Terrain.cpp @@ -74,11 +74,11 @@ ReleaseData(); // store terrain size - m_MapSize = patchesPerSide*PATCH_SIZE+1; + m_MapSize = patchesPerSide * PATCH_SIZE + 1; m_MapSizePatches = patchesPerSide; // allocate data for new terrain - m_Heightmap = new u16[m_MapSize*m_MapSize]; - m_Patches = new CPatch[m_MapSizePatches*m_MapSizePatches]; + m_Heightmap = new u16[m_MapSize * m_MapSize]; + m_Patches = new CPatch[m_MapSizePatches * m_MapSizePatches]; // given a heightmap? if (data) @@ -496,98 +496,136 @@ return (mid1 < mid2); } -/////////////////////////////////////////////////////////////////////////////// -// Resize: resize this terrain to the given size (in patches per side) -void CTerrain::Resize(ssize_t size) +// Resize/Recenter: resize this terrain to the given size (in patches per side). +// The offset is in patches from the center of the source. +void CTerrain::ResizeRecenter(ssize_t size, i32 horizontalOffset, i32 verticalOffset) { - if (size==m_MapSizePatches) { - // inexplicable request to resize terrain to the same size .. ignore it + if (size == m_MapSizePatches && horizontalOffset == 0 && verticalOffset == 0) + { + // Inexplicable request to resize terrain to the same size .. ignore it return; } - if (!m_Heightmap) { - // not yet created a terrain; build a default terrain of the given size now - Initialize(size,0); + if (!m_Heightmap || + std::abs(horizontalOffset) > std::max(size, m_MapSizePatches) / 2 || + std::abs(verticalOffset) > std::max(size, m_MapSizePatches) / 2) + { + // We have not yet created a terrain, or we are offsetting outside the current source. + // Let's build a default terrain of the given size now. + Initialize(size, 0); return; } - // allocate data for new terrain - ssize_t newMapSize=size*PATCH_SIZE+1; - u16* newHeightmap=new u16[newMapSize*newMapSize]; - CPatch* newPatches=new CPatch[size*size]; - - if (size>m_MapSizePatches) { - // new map is bigger than old one - zero the heightmap so we don't get uninitialised - // height data along the expanded edges - memset(newHeightmap,0,newMapSize*newMapSize*sizeof(u16)); - } + // Allocate data for new terrain + ssize_t newMapSize = size * PATCH_SIZE + 1; + u16* newHeightmap = new u16[newMapSize * newMapSize]; + CPatch* newPatches = new CPatch[size * size]; - // now copy over rows of data - u16* src=m_Heightmap; - u16* dst=newHeightmap; - ssize_t copysize=std::min(newMapSize, m_MapSize); - for (ssize_t j=0;jm_MapSize) { - // extend the last height to the end of the row - for (size_t i=0;i(0), (size - m_MapSizePatches) / 2 + horizontalOffset); + const ssize_t upperLeftZ = std::max(static_cast(0), (size - m_MapSizePatches) / 2 + verticalOffset); + const ssize_t width = std::min(size, (size - m_MapSizePatches) / 2 + horizontalOffset + m_MapSizePatches) - upperLeftX; + const ssize_t height = std::min(size, (size - m_MapSizePatches) / 2 + verticalOffset + m_MapSizePatches) - upperLeftZ; + const ssize_t upperLeftXSource = std::max(static_cast(0), (m_MapSizePatches - size) / 2 - horizontalOffset); + const ssize_t upperLeftZSource = std::max(static_cast(0), (m_MapSizePatches - size) / 2 - verticalOffset); - if (newMapSize>m_MapSize) { - // copy over heights of the last row to any remaining rows - src=newHeightmap+((m_MapSize-1)*newMapSize); - dst=src+newMapSize; - for (ssize_t i=0;im_MapSizePatches) { - // copy over the last tile from each column - for (ssize_t n=0;nm_MapSizePatches) { - // copy over the last tile from each column - CPatch* srcpatch=&newPatches[(m_MapSizePatches-1)*size]; - CPatch* dstpatch=srcpatch+size; - for (ssize_t p=0;p<(ssize_t)size-m_MapSizePatches;p++) { - for (ssize_t n=0;n<(ssize_t)size;n++) { - for (ssize_t m=0;mm_MiniPatches[15][k]; - CMiniPatch& dst=dstpatch->m_MiniPatches[m][k]; - dst = src; - } + for (ssize_t j = 0; j < upperLeftZ; ++j) + { + CPatch* srcpatch = newPatches + (upperLeftZ * size); + CPatch* dstpatch = newPatches + j * size; + for (ssize_t i = 0; i < size; ++i) + { + for (ssize_t patch_j = 0; patch_j < PATCH_SIZE; ++patch_j) + { + for (ssize_t patch_i = 0; patch_i < PATCH_SIZE; ++patch_i) + { + CMiniPatch& src = srcpatch->m_MiniPatches[0][patch_i]; + CMiniPatch& dst = dstpatch->m_MiniPatches[patch_j][patch_i]; + dst = src; + } + } + ++srcpatch; + ++dstpatch; + } + } + for (ssize_t j = upperLeftZ + height; j < size; ++j) + { + CPatch* srcpatch = newPatches + ((upperLeftZ + height - 1) * size); + CPatch* dstpatch = newPatches + j * size; + for (ssize_t i = 0; i < size; ++i) + { + for (ssize_t patch_j = 0; patch_j < PATCH_SIZE; ++patch_j) + { + for (ssize_t patch_i = 0; patch_i < PATCH_SIZE; ++patch_i) + { + CMiniPatch& src = srcpatch->m_MiniPatches[PATCH_SIZE - 1][patch_i]; + CMiniPatch& dst = dstpatch->m_MiniPatches[patch_j][patch_i]; + dst = src; } - srcpatch++; - dstpatch++; } + ++srcpatch; + ++dstpatch; } } @@ -596,16 +634,16 @@ ReleaseData(); // store new data - m_Heightmap=newHeightmap; - m_Patches=newPatches; - m_MapSize=(ssize_t)newMapSize; - m_MapSizePatches=(ssize_t)size; + m_Heightmap = newHeightmap; + m_Patches = newPatches; + m_MapSize = newMapSize; + m_MapSizePatches = size; // initialise all the new patches InitialisePatches(); // initialise mipmap - m_HeightMipmap.Initialize(m_MapSize,m_Heightmap); + m_HeightMipmap.Initialize(m_MapSize, m_Heightmap); } /////////////////////////////////////////////////////////////////////////////// Index: source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/MapResizeDialog.h =================================================================== --- source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/MapResizeDialog.h +++ source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/MapResizeDialog.h @@ -18,9 +18,10 @@ #ifndef INCLUDED_MAPRESIZEDIALOG #define INCLUDED_MAPRESIZEDIALOG -#include #include "PseudoMiniMapPanel.h" +#include + class MapResizeDialog : public wxDialog { public: @@ -29,7 +30,7 @@ /** * Returns selected new size. */ - size_t GetNewSize() const; + ssize_t GetNewSize() const; /** * Returns the offset from center. */ @@ -40,7 +41,7 @@ void OnOK(wxCommandEvent& evt); void OnListBox(wxCommandEvent& evt); - size_t m_NewSize; + ssize_t m_NewSize; PseudoMiniMapPanel* m_MiniMap; DECLARE_EVENT_TABLE(); Index: source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/MapResizeDialog.cpp =================================================================== --- source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/MapResizeDialog.cpp +++ source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/MapResizeDialog.cpp @@ -41,7 +41,7 @@ wxBoxSizer* listAndMap = new wxBoxSizer(wxHORIZONTAL); - wxListBox* listBox = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_SINGLE | wxLB_HSCROLL); + wxListBox* listBox = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxLB_SINGLE | wxLB_HSCROLL); // Load the map sizes list AtlasMessage::qGetMapSizes qrySizes; qrySizes.Post(); @@ -50,7 +50,7 @@ { wxString size(s["Tiles"]); listBox->Append(wxString(s["Name"]), new wxStringClientData(size)); - if (m_NewSize == static_cast(wxAtoi(size))) + if (m_NewSize == static_cast(wxAtoi(size))) listBox->SetSelection(listBox->GetCount() - 1); } listAndMap->Add(listBox, wxSizerFlags().Align(wxALIGN_LEFT).Proportion(1).Expand()); @@ -78,7 +78,7 @@ Thaw(); } -size_t MapResizeDialog::GetNewSize() const +ssize_t MapResizeDialog::GetNewSize() const { return m_NewSize; } Index: source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/PseudoMiniMapPanel.h =================================================================== --- source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/PseudoMiniMapPanel.h +++ source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/PseudoMiniMapPanel.h @@ -16,8 +16,8 @@ */ -#ifndef INCLUDED_PseudoMINIMAPPANEL -#define INCLUDED_PseudoMINIMAPPANEL +#ifndef INCLUDED_PSEUDOMINIMAPPANEL +#define INCLUDED_PSEUDOMINIMAPPANEL #include @@ -53,4 +53,4 @@ DECLARE_EVENT_TABLE(); }; -#endif // INCLUDED_PseudoMINIMAPPANEL +#endif // INCLUDED_PSEUDOMINIMAPPANEL Index: source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/PseudoMiniMapPanel.cpp =================================================================== --- source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/PseudoMiniMapPanel.cpp +++ source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/PseudoMiniMapPanel.cpp @@ -19,12 +19,11 @@ #include "PseudoMiniMapPanel.h" -#include "GameInterface/Messages.h" #include "GameInterface/MessagePasser.h" +#include "GameInterface/Messages.h" #include "ScenarioEditor/Tools/Common/Tools.h" -#include "math.h" - +#include #include #include #include @@ -59,13 +58,13 @@ m_SelectionRadius(PanelRadius), m_SelectionCenter(PanelCenter), m_SameOrGrowing(true), m_NewSize(currentSize) { - AtlasMessage::qGetMiniMapDisplay qryBackground; + AtlasMessage::qRasterizeMinimap qryBackground; qryBackground.Post(); int dim = qryBackground.dimension; - std::vector imageBytes = *qryBackground.imageBytes; + std::vector imageBytes = *qryBackground.imageBytes; // Data is destined for a wxImage, which uses free. - unsigned char* data = static_cast(malloc(imageBytes.size())); + uint8_t* data = static_cast(malloc(imageBytes.size())); std::copy(imageBytes.cbegin(), imageBytes.cend(), data); m_Background = wxImage(dim, dim, data); m_Background.Rescale(PanelRadius * 2, PanelRadius * 2, wxIMAGE_QUALITY_BOX_AVERAGE); Index: source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.h =================================================================== --- source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.h +++ source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -41,6 +41,7 @@ void OnRandomReseed(wxCommandEvent& evt); void OnRandomGenerate(wxCommandEvent& evt); void OnOpenPlayerPanel(wxCommandEvent& evt); + void OnResizeMap(wxCommandEvent& evt); void UpdateSimButtons(); int m_SimState; Index: source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp =================================================================== --- source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp +++ source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp @@ -21,6 +21,7 @@ #include "AtlasObject/AtlasObject.h" #include "GameInterface/Messages.h" +#include "MapResizeDialog/MapResizeDialog.h" #include "ScenarioEditor/ScenarioEditor.h" #include "ScenarioEditor/Tools/Common/Tools.h" @@ -60,7 +61,8 @@ ID_SimSlow, ID_SimPause, ID_SimReset, - ID_OpenPlayerPanel + ID_OpenPlayerPanel, + ID_ResizeMap }; enum @@ -410,6 +412,15 @@ _("Run selected random map script")), wxSizerFlags().Expand()); } + { + + ///////////////////////////////////////////////////////////////////////// + // Misc tools + wxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, scrolledWindow, _("Misc tools")); + sizer->Add(new wxButton(scrolledWindow, ID_ResizeMap, _("Resize/Recenter map")), wxSizerFlags().Expand()); + scrollSizer->Add(sizer, wxSizerFlags().Expand().Border(wxTOP, 10)); + } + { ///////////////////////////////////////////////////////////////////////// // Simulation buttons @@ -645,6 +656,16 @@ m_ScenarioEditor.SelectPage(_T("PlayerSidebar")); } +void MapSidebar::OnResizeMap(wxCommandEvent& WXUNUSED(evt)) +{ + MapResizeDialog dlg(this); + + if (dlg.ShowModal() != wxID_OK) + return; + wxPoint offset = dlg.GetOffset(); + POST_COMMAND(ResizeMap, (dlg.GetNewSize(), offset.x, offset.y)); +} + BEGIN_EVENT_TABLE(MapSidebar, Sidebar) EVT_COLLAPSIBLEPANE_CHANGED(wxID_ANY, MapSidebar::OnCollapse) EVT_BUTTON(ID_SimPlay, MapSidebar::OnSimPlay) @@ -655,4 +676,5 @@ EVT_BUTTON(ID_RandomReseed, MapSidebar::OnRandomReseed) EVT_BUTTON(ID_RandomGenerate, MapSidebar::OnRandomGenerate) EVT_BUTTON(ID_OpenPlayerPanel, MapSidebar::OnOpenPlayerPanel) + EVT_BUTTON(ID_ResizeMap, MapSidebar::OnResizeMap) END_EVENT_TABLE(); Index: source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.h =================================================================== --- source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.h +++ source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -30,11 +30,9 @@ private: void OnPassabilityChoice(wxCommandEvent& evt); void OnShowPriorities(wxCommandEvent& evt); - void OnResizeMap(wxCommandEvent& evt); wxChoice* m_PassabilityChoice; TexturePreviewPanel* m_TexturePreview; DECLARE_EVENT_TABLE(); }; - Index: source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp =================================================================== --- source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp +++ source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp @@ -47,8 +47,7 @@ enum { ID_Passability = 1, - ID_ShowPriorities, - ID_ResizeMap + ID_ShowPriorities }; // Helper function for adding tooltips @@ -247,14 +246,6 @@ _("Show terrain texture priorities"))); } - { - ///////////////////////////////////////////////////////////////////////// - // Misc tools - wxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, scrolledWindow, _("Misc tools")); - sizer->Add(new wxButton(scrolledWindow, ID_ResizeMap, _("Resize map")), wxSizerFlags().Expand()); - scrollSizer->Add(sizer, wxSizerFlags().Expand().Border(wxTOP, 10)); - } - m_BottomBar = new TerrainBottomBar(scenarioEditor, bottomBarContainer); } @@ -283,37 +274,9 @@ POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::GAME, L"priorities", evt.IsChecked())); } -void TerrainSidebar::OnResizeMap(wxCommandEvent& WXUNUSED(evt)) -{ - wxArrayString sizeNames; - std::vector sizeTiles; - - // Load the map sizes list - AtlasMessage::qGetMapSizes qrySizes; - qrySizes.Post(); - AtObj sizes = AtlasObject::LoadFromJSON(*qrySizes.sizes); - for (AtIter s = sizes["Data"]["item"]; s.defined(); ++s) - { - sizeNames.Add(wxString::FromUTF8(s["Name"])); - sizeTiles.push_back((*s["Tiles"]).getLong()); - } - - // TODO: set default based on current map size - - wxSingleChoiceDialog dlg(this, _("Select new map size. WARNING: This probably only works reliably on blank maps."), - _("Resize map"), sizeNames); - - if (dlg.ShowModal() != wxID_OK) - return; - - size_t tiles = sizeTiles.at(dlg.GetSelection()); - POST_COMMAND(ResizeMap, (tiles)); -} - BEGIN_EVENT_TABLE(TerrainSidebar, Sidebar) EVT_CHOICE(ID_Passability, TerrainSidebar::OnPassabilityChoice) EVT_CHECKBOX(ID_ShowPriorities, TerrainSidebar::OnShowPriorities) - EVT_BUTTON(ID_ResizeMap, TerrainSidebar::OnResizeMap) END_EVENT_TABLE(); ////////////////////////////////////////////////////////////////////////// Index: source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp =================================================================== --- source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp +++ source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp @@ -18,8 +18,9 @@ #include "precompiled.h" #include "MessageHandler.h" -#include "../GameLoop.h" #include "../CommandProc.h" +#include "../GameLoop.h" +#include "../MessagePasser.h" #include "graphics/GameView.h" #include "graphics/LOSTexture.h" @@ -36,16 +37,21 @@ #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Game.h" +#include "ps/GameSetup/GameSetup.h" #include "ps/Loader.h" #include "ps/World.h" #include "renderer/Renderer.h" +#include "renderer/WaterManager.h" #include "scriptinterface/ScriptInterface.h" #include "simulation2/Simulation2.h" +#include "simulation2/components/ICmpOwnership.h" #include "simulation2/components/ICmpPlayer.h" #include "simulation2/components/ICmpPlayerManager.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpRangeManager.h" +#include "simulation2/components/ICmpTemplateManager.h" #include "simulation2/components/ICmpTerrain.h" +#include "simulation2/system/ParamNode.h" namespace { @@ -191,7 +197,7 @@ // Notice that the number of tiles/pixels per side of the heightmap image is // one less than the number of vertices per side of the heightmap. CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); - terrain->Resize((sqrt(heightmap_source.size()) - 1) / PATCH_SIZE); + terrain->ResizeRecenter((sqrt(heightmap_source.size()) - 1) / PATCH_SIZE); // copy heightmap data into map u16* heightmap = g_Game->GetWorld()->GetTerrain()->GetHeightMap(); @@ -269,19 +275,134 @@ msg->sizes = g_Game->GetSimulation2()->GetMapSizes(); } +QUERYHANDLER(RasterizeMinimap) +{ + const CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); + const ssize_t dimension = terrain->GetVerticesPerSide() - 1; + const ssize_t bpp = 24; + const ssize_t buf_size = dimension * dimension * (bpp / 8); + + std::vector imageBytes = std::vector(buf_size); + + // Stolen from MiniMap.cpp + float shallowPassageHeight = 0.0f; + // Get the maximum height for unit passage in water. + CParamNode externalParamNode; + CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder"); + const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses"); + if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk()) + shallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat(); + + ssize_t w = dimension; + ssize_t h = dimension; + float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight; + + for (ssize_t j = 0; j < h; ++j) + { + // Work backwards to vertically flip the image. + ssize_t position = 3 * (h - j - 1) * dimension; + for (ssize_t i = 0; i < w; ++i) + { + float avgHeight = (terrain->GetVertexGroundLevel(i, j) + + terrain->GetVertexGroundLevel(i + 1, j) + + terrain->GetVertexGroundLevel(i, j + 1) + + terrain->GetVertexGroundLevel(i + 1, j + 1) + ) / 4.0f; + + if (avgHeight < waterHeight && avgHeight > waterHeight - shallowPassageHeight) + { + // shallow water + imageBytes[position++] = 0x70; + imageBytes[position++] = 0x98; + imageBytes[position++] = 0xc0; + } + else if (avgHeight < waterHeight) + { + // Set water as constant color for consistency on different maps + imageBytes[position++] = 0x50; + imageBytes[position++] = 0x78; + imageBytes[position++] = 0xa0; + } + else + { + u32 color = std::numeric_limits::max(); + u32 hmap = static_cast(terrain->GetHeightMap()[j * dimension + i]) >> 8; + float scale = hmap / 3.0f + 170.0f / 255.0f; + + CMiniPatch* mp = terrain->GetTile(i, j); + if (mp) + { + CTerrainTextureEntry* tex = mp->GetTextureEntry(); + if (tex) + color = tex->GetBaseColor(); + } + + // Convert + imageBytes[position++] = static_cast(static_cast(color & 0xff) * scale); + imageBytes[position++] = static_cast(static_cast((color >> 8) & 0xff) * scale); + imageBytes[position++] = static_cast(static_cast((color >> 16) & 0xff) * scale); + } + } + } + + msg->imageBytes = imageBytes; + msg->dimension = dimension; +} + QUERYHANDLER(GetRMSData) { msg->data = g_Game->GetSimulation2()->GetRMSData(); } +QUERYHANDLER(GetCurrentMapSize) +{ + msg->size = g_Game->GetWorld()->GetTerrain()->GetTilesPerSide(); +} + BEGIN_COMMAND(ResizeMap) { - int m_OldTiles, m_NewTiles; + bool Within(const CFixedVector3D& pos, const int centerX, const int centerZ, const int radius) + { + int dx = abs(pos.X.ToInt_RoundToZero() - centerX); + if (dx > radius) + return false; + int dz = abs(pos.Z.ToInt_RoundToZero() - centerZ); + if (dz > radius) + return false; + if (dx + dz <= radius) + return true; + return (dx * dx + dz * dz <= radius * radius); + } + + struct DeletedObject + { + entity_id_t entityId; + CStr templateName; + int32_t owner; + CFixedVector3D pos; + CFixedVector3D rot; + }; + + ssize_t m_OldPatches, m_NewPatches; + int m_OffsetX, m_OffsetY; + + u16* m_Heightmap; + CPatch* m_Patches; + + std::vector m_DeletedObjects; + std::vector> m_OldPositions; + std::vector> m_NewPositions; cResizeMap() { } + ~cResizeMap() + { + delete m_Heightmap; + delete m_Patches; + } + void MakeDirty() { CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY); @@ -293,39 +414,186 @@ g_Game->GetView()->GetLOSTexture().MakeDirty(); } - void ResizeTerrain(int tiles) + + void ResizeTerrain(ssize_t patches, int offsetX, int offsetY) { CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); + terrain->ResizeRecenter(patches, offsetX, offsetY); + } + + void DeleteAll(const std::vector& deletedObjects) + { + for (const DeletedObject& deleted : deletedObjects) + { + g_Game->GetSimulation2()->DestroyEntity(deleted.entityId); + } - terrain->Resize(tiles / PATCH_SIZE); + g_Game->GetSimulation2()->FlushDestroyedEntities(); + } - MakeDirty(); + void UndeleteAll(const std::vector& deletedObjects) + { + CSimulation2& sim = *g_Game->GetSimulation2(); + + for (const DeletedObject& deleted : deletedObjects) + { + entity_id_t ent = sim.AddEntity(deleted.templateName.FromUTF8(), deleted.entityId); + if (ent == INVALID_ENTITY) + { + LOGERROR("Failed to load entity template '%s'", deleted.templateName.c_str()); + } + else + { + CmpPtr cmpPosition(sim, deleted.entityId); + if (cmpPosition) + { + cmpPosition->JumpTo(deleted.pos.X, deleted.pos.Z); + cmpPosition->SetXZRotation(deleted.rot.X, deleted.rot.Z); + cmpPosition->SetYRotation(deleted.rot.Y); + } + + CmpPtr cmpOwnership(sim, deleted.entityId); + if (cmpOwnership) + cmpOwnership->SetOwner(deleted.owner); + } + } + } + + void SetMovedEntitiesPosition(const std::vector>& movedObjects) + { + for (std::pair const& kv : movedObjects) + { + entity_id_t id = kv.first; + CFixedVector3D position = kv.second; + CmpPtr cmpPosition(*g_Game->GetSimulation2(), id); + ENSURE(cmpPosition); + cmpPosition->JumpTo(position.X, position.Z); + } } void Do() { - CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY); + CSimulation2& sim = *g_Game->GetSimulation2(); + CmpPtr cmpTemplateManager(sim, SYSTEM_ENTITY); + ENSURE(cmpTemplateManager); + + CmpPtr cmpTerrain(sim, SYSTEM_ENTITY); if (!cmpTerrain) { - m_OldTiles = m_NewTiles = 0; + m_OldPatches = m_NewPatches = 0; + m_OffsetX = m_OffsetY = 0; } else { - m_OldTiles = (int)cmpTerrain->GetTilesPerSide(); - m_NewTiles = msg->tiles; + m_OldPatches = static_cast(cmpTerrain->GetTilesPerSide() / PATCH_SIZE); + m_NewPatches = msg->tiles / PATCH_SIZE; + m_OffsetX = msg->offsetX / PATCH_SIZE; + // Need to flip direction of vertical offset, due to screen mapping order. + m_OffsetY = -(msg->offsetY / PATCH_SIZE); + + CTerrain* terrain = cmpTerrain->GetCTerrain(); + m_Heightmap = new u16[(m_OldPatches * PATCH_SIZE + 1) * (m_OldPatches * PATCH_SIZE + 1)]; + std::copy_n(terrain->GetHeightMap(), (m_OldPatches * PATCH_SIZE + 1) * (m_OldPatches * PATCH_SIZE + 1), m_Heightmap); + m_Patches = new CPatch[m_OldPatches * m_OldPatches]; + for (ssize_t j = 0; j < m_OldPatches; ++j) + { + for (ssize_t i = 0; i < m_OldPatches; ++i) + { + CPatch& src = *(terrain->GetPatch(i, j)); + CPatch& dst = m_Patches[j * m_OldPatches + i]; + std::copy_n(&src.m_MiniPatches[0][0], PATCH_SIZE * PATCH_SIZE, &dst.m_MiniPatches[0][0]); + } + } } - ResizeTerrain(m_NewTiles); + const int radiusInTerrainUnits = m_NewPatches * PATCH_SIZE * TERRAIN_TILE_SIZE / 2 * (1.f - 1e-6f); + // Opposite direction offset, as we move the destination onto the source, not the source into the destination. + const int mapCenterX = (m_OldPatches / 2 - m_OffsetX) * PATCH_SIZE * TERRAIN_TILE_SIZE; + const int mapCenterZ = (m_OldPatches / 2 - m_OffsetY) * PATCH_SIZE * TERRAIN_TILE_SIZE; + // The offset to move units by is opposite the direction the map is moved, and from the corner. + const int offsetX = ((m_NewPatches - m_OldPatches) / 2 + m_OffsetX) * PATCH_SIZE * TERRAIN_TILE_SIZE; + const int offsetZ = ((m_NewPatches - m_OldPatches) / 2 + m_OffsetY) * PATCH_SIZE * TERRAIN_TILE_SIZE; + const CFixedVector3D offset = CFixedVector3D(fixed::FromInt(offsetX), fixed::FromInt(0), fixed::FromInt(offsetZ)); + + const CSimulation2::InterfaceListUnordered& ents = sim.GetEntitiesWithInterfaceUnordered(IID_Selectable); + + for (std::pair const& kv : ents) + { + const entity_id_t entityId = kv.first; + + CmpPtr cmpPosition(sim, entityId); + + if (cmpPosition && cmpPosition->IsInWorld() && Within(cmpPosition->GetPosition(), mapCenterX, mapCenterZ, radiusInTerrainUnits)) + { + CFixedVector3D position = cmpPosition->GetPosition(); + + m_NewPositions.emplace_back(entityId, position + offset); + m_OldPositions.emplace_back(entityId, position); + } + else + { + DeletedObject deleted; + deleted.entityId = entityId; + deleted.templateName = cmpTemplateManager->GetCurrentTemplateName(entityId); + + // If the entity has a position, but the ending position is not valid; + if (cmpPosition) + { + deleted.pos = cmpPosition->GetPosition(); + deleted.rot = cmpPosition->GetRotation(); + } + + CmpPtr cmpOwnership(sim, entityId); + if (cmpOwnership) + deleted.owner = cmpOwnership->GetOwner(); + + m_DeletedObjects.push_back(deleted); + } + } + + DeleteAll(m_DeletedObjects); + ResizeTerrain(m_NewPatches, m_OffsetX, m_OffsetY); + SetMovedEntitiesPosition(m_NewPositions); + MakeDirty(); } + void Undo() { - ResizeTerrain(m_OldTiles); + if (m_Heightmap == nullptr || m_Patches == nullptr) + { + // If there previously was no data, just resize to old (probably not originally valid). + ResizeTerrain(m_OldPatches, -m_OffsetX, -m_OffsetY); + } + else + { + CSimulation2& sim = *g_Game->GetSimulation2(); + CmpPtr cmpTerrain(sim, SYSTEM_ENTITY); + CTerrain* terrain = cmpTerrain->GetCTerrain(); + + terrain->Initialize(m_OldPatches, m_Heightmap); + // Copy terrain data back. + for (ssize_t j = 0; j < m_OldPatches; ++j) + { + for (ssize_t i = 0; i < m_OldPatches; ++i) + { + CPatch& src = m_Patches[j * m_OldPatches + i]; + CPatch& dst = *(terrain->GetPatch(i, j)); + std::copy_n(&src.m_MiniPatches[0][0], PATCH_SIZE * PATCH_SIZE, &dst.m_MiniPatches[0][0]); + } + } + } + UndeleteAll(m_DeletedObjects); + SetMovedEntitiesPosition(m_OldPositions); + MakeDirty(); } void Redo() { - ResizeTerrain(m_NewTiles); + DeleteAll(m_DeletedObjects); + ResizeTerrain(m_NewPatches, m_OffsetX, m_OffsetY); + SetMovedEntitiesPosition(m_NewPositions); + MakeDirty(); } }; END_COMMAND(ResizeMap) Index: source/tools/atlas/GameInterface/Messages.h =================================================================== --- source/tools/atlas/GameInterface/Messages.h +++ source/tools/atlas/GameInterface/Messages.h @@ -195,6 +195,17 @@ ((std::string, sizes)) ); +QUERY(GetCurrentMapSize, + , + ((int, size)) + ); + +QUERY(RasterizeMinimap, + , + ((int, dimension)) + ((std::vector, imageBytes)) + ); + QUERY(GetRMSData, , ((std::vector, data)) @@ -202,6 +213,8 @@ COMMAND(ResizeMap, NOMERGE, ((int, tiles)) + ((int, offsetX)) + ((int, offsetY)) ); QUERY(VFSFileExists,