Index: source/graphics/Terrain.h =================================================================== --- source/graphics/Terrain.h +++ source/graphics/Terrain.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -22,10 +22,10 @@ #ifndef INCLUDED_TERRAIN #define INCLUDED_TERRAIN -#include "maths/Vector3D.h" -#include "maths/Fixed.h" -#include "graphics/SColor.h" #include "graphics/HeightMipmap.h" +#include "graphics/SColor.h" +#include "maths/Fixed.h" +#include "maths/Vector3D.h" class CPatch; class CMiniPatch; @@ -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 center of the source. + void ResizeAndOffset(ssize_t size, ssize_t horizontalOffset = 0, ssize_t verticalOffset = 0); // set up a new heightmap from 16 bit data; assumes heightmap matches current terrain size void SetHeightMap(u16* heightmap); @@ -179,4 +180,4 @@ CHeightMipmap m_HeightMipmap; }; -#endif +#endif // INCLUDED_TERRAIN Index: source/graphics/Terrain.cpp =================================================================== --- source/graphics/Terrain.cpp +++ source/graphics/Terrain.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -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,116 +496,239 @@ return (mid1 < mid2); } -/////////////////////////////////////////////////////////////////////////////// -// Resize: resize this terrain to the given size (in patches per side) -void CTerrain::Resize(ssize_t size) +void CTerrain::ResizeAndOffset(ssize_t size, ssize_t horizontalOffset, ssize_t 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) >= size / 2 + m_MapSizePatches / 2 || + std::abs(verticalOffset) >= size / 2 + 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)); - } - - // 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;im_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;i(0), m_MapSizePatches / 2 - size / 2 + horizontalOffset); + const ssize_t sourceUpperLeftZ = std::max( + static_cast(0), m_MapSizePatches / 2 - size / 2 + verticalOffset); + + const ssize_t destUpperLeftX = std::max( + static_cast(0), (size / 2 - m_MapSizePatches / 2 - horizontalOffset)); + const ssize_t destUpperLeftZ = std::max( + static_cast(0), (size / 2 - m_MapSizePatches / 2 - verticalOffset)); + + const ssize_t width = + std::min(m_MapSizePatches, m_MapSizePatches / 2 + horizontalOffset + size / 2) - sourceUpperLeftX; + const ssize_t depth = + std::min(m_MapSizePatches, m_MapSizePatches / 2 + verticalOffset + size / 2) - sourceUpperLeftZ; + + for (ssize_t j = 0; j < depth * PATCH_SIZE; ++j) + { + // Copy the main part from the source. Destination heightmap: + // +----------+ + // | | + // | 1234 | < current j-th row for example. + // | 5678 | + // | | + // +----------+ + u16* dst = newHeightmap + (j + destUpperLeftZ * PATCH_SIZE) * newMapSize + destUpperLeftX * PATCH_SIZE; + u16* src = m_Heightmap + (j + sourceUpperLeftZ * PATCH_SIZE) * m_MapSize + sourceUpperLeftX * PATCH_SIZE; + std::copy_n(src, width * PATCH_SIZE, dst); + if (destUpperLeftX > 0) + { + // Fill the preceding part by copying the first elements of the + // main part. Destination heightmap: + // +----------+ + // | | + // |1111234 | < current j-th row for example. + // | 5678 | + // | | + // +----------+ + u16* dst_prefix = newHeightmap + (j + destUpperLeftZ * PATCH_SIZE) * newMapSize; + std::fill_n(dst_prefix, destUpperLeftX * PATCH_SIZE, dst[0]); + } + if ((destUpperLeftX + width) * PATCH_SIZE < newMapSize) + { + // Fill the succeeding part by copying the last elements of the + // main part. Destination heightmap: + // +----------+ + // | | + // |1111234444| < current j-th row for example. + // | 5678 | + // | | + // +----------+ + u16* dst_suffix = dst + width * PATCH_SIZE; + std::fill_n( + dst_suffix, + newMapSize - (width + destUpperLeftX) * PATCH_SIZE, + dst[width * PATCH_SIZE - 1]); + } + } + // Copy over heights from the preceding row. Destination heightmap: + // +----------+ + // |1111234444| < copied from the row below + // |1111234444| + // |5555678888| + // | | + // +----------+ + for (ssize_t j = 0; j < destUpperLeftZ * PATCH_SIZE; ++j) + { + + u16* dst = newHeightmap + j * newMapSize; + u16* src = newHeightmap + destUpperLeftZ * PATCH_SIZE * newMapSize; + std::copy_n(src, newMapSize, dst); + } + // Copy over heights from the succeeding row. Destination heightmap: + // +----------+ + // |1111234444| + // |1111234444| + // |5555678888| + // |5555678888| < copied from the row above + // +----------+ + for (ssize_t j = (destUpperLeftZ + depth) * PATCH_SIZE; j < newMapSize; ++j) + { + u16* dst = newHeightmap + j * newMapSize; + u16* src = newHeightmap + ((destUpperLeftZ + depth) * PATCH_SIZE - 1) * newMapSize; + std::copy_n(src, newMapSize, dst); + } + + // Now build new patches. The same process as for the heightmap. + for (ssize_t j = 0; j < depth; ++j) + { + for (size_t i = 0; i < width; ++i) + { + const CPatch& src = + m_Patches[(sourceUpperLeftZ + j) * m_MapSizePatches + sourceUpperLeftX + i]; + CPatch& dst = + newPatches[(destUpperLeftZ + j) * size + destUpperLeftX + i]; + std::copy_n(&src.m_MiniPatches[0][0], PATCH_SIZE * PATCH_SIZE, &dst.m_MiniPatches[0][0]); + } + for (ssize_t i = 0; i < destUpperLeftX; ++i) + for (ssize_t jPatch = 0; jPatch < PATCH_SIZE; ++jPatch) + { + const CMiniPatch& src = + newPatches[(destUpperLeftZ + j) * size + destUpperLeftX] + .m_MiniPatches[jPatch][0]; + for (ssize_t iPatch = 0; iPatch < PATCH_SIZE; ++iPatch) + { + CMiniPatch& dst = + newPatches[(destUpperLeftZ + j) * size + i] + .m_MiniPatches[jPatch][iPatch]; + dst = src; + } } - } - - if (jm_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 < destUpperLeftZ; ++j) + for (ssize_t i = 0; i < size; ++i) + for (ssize_t iPatch = 0; iPatch < PATCH_SIZE; ++iPatch) + { + const CMiniPatch& src = + newPatches[destUpperLeftZ * size + i].m_MiniPatches[0][iPatch]; + for (ssize_t jPatch = 0; jPatch < PATCH_SIZE; ++jPatch) + { + CMiniPatch& dst = + newPatches[j * size + i].m_MiniPatches[jPatch][iPatch]; + dst = src; + } + } + for (ssize_t j = destUpperLeftZ + depth; j < size; ++j) + for (ssize_t i = 0; i < size; ++i) + for (ssize_t iPatch = 0; iPatch < PATCH_SIZE; ++iPatch) + { + const CMiniPatch& src = + newPatches[(destUpperLeftZ + depth - 1) * size + i].m_MiniPatches[0][iPatch]; + for (ssize_t jPatch = 0; jPatch < PATCH_SIZE; ++jPatch) + { + CMiniPatch& dst = + newPatches[j * size + i].m_MiniPatches[jPatch][iPatch]; + dst = src; } - srcpatch++; - dstpatch++; } - } - } - - // release all the original data + // Release all the original data. ReleaseData(); - // store new data - m_Heightmap=newHeightmap; - m_Patches=newPatches; - m_MapSize=(ssize_t)newMapSize; - m_MapSizePatches=(ssize_t)size; + // Store new data. + m_Heightmap = newHeightmap; + m_Patches = newPatches; + m_MapSize = newMapSize; + m_MapSizePatches = size; - // initialise all the new patches + // Initialise all the new patches. InitialisePatches(); - // initialise mipmap - m_HeightMipmap.Initialize(m_MapSize,m_Heightmap); + // Initialise mipmap. + m_HeightMipmap.Initialize(m_MapSize, m_Heightmap); } /////////////////////////////////////////////////////////////////////////////// Index: source/graphics/tests/test_Terrain.h =================================================================== --- source/graphics/tests/test_Terrain.h +++ source/graphics/tests/test_Terrain.h @@ -203,11 +203,14 @@ // value per a patch. struct ResizeTestCase { + ssize_t horizontalOffset, verticalOffset; std::vector> sourcePatches; std::vector> expectedPatches; }; const ResizeTestCase testCases[] = { + // Without offset. { + 0, 0, { {42} }, @@ -216,25 +219,154 @@ } }, { + 0, 0, { {1, 2}, {3, 4} }, { - {1}, + {1, 2}, + {3, 4} } }, { + 0, 0, { {1, 2}, {3, 4} }, { - {1, 2, 0}, - {3, 4, 0}, - {0, 0, 0} + {1, 1, 2, 2}, + {1, 1, 2, 2}, + {3, 3, 4, 4}, + {3, 3, 4, 4} } - } + }, + { + 0, 0, + { + { 1, 2 , 3, 4}, + { 5, 6 , 7, 8}, + { 9, 10, 11, 12}, + {13, 14, 15, 16} + }, + { + { 6, 7}, + {10, 11}, + } + }, + // With offset. + { + -2, -2, + { + {1, 2}, + {3, 4} + }, + { + {0, 0}, + {0, 0} + } + }, + { + -2, 0, + { + {1, 2}, + {3, 4} + }, + { + {0, 0}, + {0, 0} + } + }, + { + 4, 4, + { + {1, 2}, + {3, 4} + }, + { + {0, 0}, + {0, 0} + } + }, + { + 1, 1, + { + { 1, 2 , 3, 4}, + { 5, 6 , 7, 8}, + { 9, 10, 11, 12}, + {13, 14, 15, 16} + }, + { + { 6 , 7, 8, 8}, + {10, 11, 12, 12}, + {14, 15, 16, 16}, + {14, 15, 16, 16} + } + }, + { + 1, 1, + { + {1, 2}, + {3, 4} + }, + { + {4, 4}, + {4, 4} + } + }, + { + -2, 0, + { + { 1, 2 , 3, 4}, + { 5, 6 , 7, 8}, + { 9, 10, 11, 12}, + {13, 14, 15, 16} + }, + { + {5, 5}, + {9, 9} + } + }, + { + 2, -2, + { + { 1, 2 , 3, 4}, + { 5, 6 , 7, 8}, + { 9, 10, 11, 12}, + {13, 14, 15, 16} + }, + { + {4, 4}, + {4, 4} + } + }, + { + 3, -1, + { + { 1, 2 , 3, 4}, + { 5, 6 , 7, 8}, + { 9, 10, 11, 12}, + {13, 14, 15, 16} + }, + { + {0, 0}, + {0, 0} + } + }, + { + -2, -1, + { + {1, 2}, + {3, 4} + }, + { + {1, 1, 1, 1}, + {1, 1, 1, 1}, + {1, 1, 1, 1}, + {3, 3, 3, 3} + } + }, }; for (const ResizeTestCase& testCase : testCases) @@ -251,37 +383,37 @@ CTerrain terrain; { std::vector heightmap(sourceMapSize * sourceMapSize); - for (ssize_t iTile = 0; iTile < sourceSize; ++iTile) + for (ssize_t jTile = 0; jTile < sourceSize; ++jTile) { - TS_ASSERT_EQUALS(sourceSize, testCase.sourcePatches[iTile].size()); - for (ssize_t jTile = 0; jTile < sourceSize; ++jTile) + TS_ASSERT_EQUALS(sourceSize, testCase.sourcePatches[jTile].size()); + for (ssize_t iTile = 0; iTile < sourceSize; ++iTile) { - for (ssize_t i = 0; i < PATCH_SIZE; ++i) - for (ssize_t j = 0; j < PATCH_SIZE; ++j) + for (ssize_t j = 0; j < PATCH_SIZE; ++j) + for (ssize_t i = 0; i < PATCH_SIZE; ++i) { const ssize_t idx = (jTile * PATCH_SIZE + j) * sourceMapSize + iTile * PATCH_SIZE + i; - heightmap[idx] = testCase.sourcePatches[iTile][jTile]; + heightmap[idx] = testCase.sourcePatches[jTile][iTile]; } } } terrain.Initialize(sourceSize, heightmap.data()); } - terrain.Resize(expectedSize); + terrain.ResizeAndOffset(expectedSize, testCase.horizontalOffset, testCase.verticalOffset); TS_ASSERT_EQUALS(expectedMapSize, terrain.GetVerticesPerSide()); TS_ASSERT_EQUALS(expectedSize, terrain.GetPatchesPerSide()); - for (ssize_t iTile = 0; iTile < expectedSize; ++iTile) + for (ssize_t jTile = 0; jTile < expectedSize; ++jTile) { TS_ASSERT_EQUALS( - expectedSize, testCase.expectedPatches[iTile].size()); - for (ssize_t jTile = 0; jTile < expectedSize; ++jTile) + expectedSize, testCase.expectedPatches[jTile].size()); + for (ssize_t iTile = 0; iTile < expectedSize; ++iTile) { - for (ssize_t i = 0; i < PATCH_SIZE; ++i) - for (ssize_t j = 0; j < PATCH_SIZE; ++j) + for (ssize_t j = 0; j < PATCH_SIZE; ++j) + for (ssize_t i = 0; i < PATCH_SIZE; ++i) { // The whole patch should have the same height, // since we resize by patches. @@ -304,7 +436,7 @@ return; } - if (testCase.expectedPatches[iTile][jTile] == + if (testCase.expectedPatches[jTile][iTile] == GetVertex(terrain, iTile * PATCH_SIZE, jTile * PATCH_SIZE)) continue; @@ -313,19 +445,19 @@ << " (i=" << iTile << " j=" << jTile << "):" << " found=" << GetVertex(terrain, iTile * PATCH_SIZE, jTile * PATCH_SIZE) - << " expected=" << testCase.expectedPatches[iTile][jTile]; + << " expected=" << testCase.expectedPatches[jTile][iTile]; TS_FAIL(ss.str()); ss.str(std::string()); ss << "Terrain (" << terrain.GetPatchesPerSide() << "x" << terrain.GetPatchesPerSide() << "):"; TS_WARN(ss.str()); - for (ssize_t iTile = 0; iTile < expectedSize; ++iTile) + for (ssize_t jTile = 0; jTile < expectedSize; ++jTile) { ss.str(std::string()); ss << "["; - for (ssize_t jTile = 0; jTile < expectedSize; ++jTile) + for (ssize_t iTile = 0; iTile < expectedSize; ++iTile) { - if (jTile) + if (iTile) ss << ", "; ss << GetVertex(terrain, iTile * PATCH_SIZE, jTile * PATCH_SIZE); Index: source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp =================================================================== --- source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp +++ source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -191,7 +191,9 @@ // 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); + const ssize_t newSize = (sqrt(heightmap_source.size()) - 1) / PATCH_SIZE; + const ssize_t offset = (newSize - terrain->GetPatchesPerSide()) / 2; + terrain->ResizeAndOffset(newSize, offset, offset); // copy heightmap data into map u16* heightmap = g_Game->GetWorld()->GetTerrain()->GetHeightMap(); @@ -297,7 +299,9 @@ { CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); - terrain->Resize(tiles / PATCH_SIZE); + const ssize_t newSize = tiles / PATCH_SIZE; + const ssize_t offset = (newSize - terrain->GetPatchesPerSide()) / 2; + terrain->ResizeAndOffset(newSize, offset, offset); MakeDirty(); }