1
0
forked from 0ad/0ad

Adds offsets to the terrain resize function and adds comments how the resizing works.

Original Patch By: Clockwork-Muse
Tested By: Stan
Differential Revision: https://code.wildfiregames.com/D2724
This was SVN commit r23639.
This commit is contained in:
Vladislav Belov 2020-05-07 21:46:17 +00:00
parent 5159fe3fa4
commit 1e1ce27eba
4 changed files with 382 additions and 122 deletions

View File

@ -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 @@ bool CTerrain::Initialize(ssize_t patchesPerSide, const u16* data)
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 @@ bool CTerrain::GetTriangulationDir(ssize_t i, ssize_t j) const
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];
// Allocate data for new terrain.
const ssize_t newMapSize = size * PATCH_SIZE + 1;
u16* newHeightmap = new u16[newMapSize * newMapSize];
memset(newHeightmap, 0, newMapSize * newMapSize * sizeof(u16));
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));
// O--------------------+
// | Source |
// | |
// | Source Center (SC) |
// | X |
// | A------+----------------+
// | | | Destination |
// | | | |
// +-------------+------B |
// | Dest. Center (DC) |
// | X |
// | |
// | |
// | |
// | |
// +-----------------------+
//
// Calculations below should also account cases like:
//
// +----------+ +----------+ +----------+ +---+--+---+ +------+
// |S | |D | |S | |S | | D| |D |
// | +---+ | | +---+ | +-+-+ | | | | | | +---+--+
// | | D | | | | S | | |D| | | +---+--+---+ +--+---+ |
// | +---+ | | +---+ | +-+-+ | | S|
// +----------+ +----------+ +----------+ +------+
//
// O = (0, 0)
// SC = (m_MapSizePatches / 2, m_MapSizePatches / 2)
// DC - SC = (horizontalOffset, verticalOffset)
//
// Source upper left:
// A = (max(0, (m_MapSizePatches - size) / 2 + horizontalOffset),
// max(0, (m_MapSizePatches - size) / 2 + verticalOffset))
// Source bottom right:
// B = (min(m_MapSizePatches, (m_MapSizePatches + size) / 2 + horizontalOffset),
// min(m_MapSizePatches, (m_MapSizePatches + size) / 2 + verticalOffset))
//
// A-B is the area that we have to copy from the source to the destination.
// Restate center offset as a window over destination.
// This has the effect of always considering the source to be the same size or smaller.
const ssize_t sourceUpperLeftX = std::max(
static_cast<ssize_t>(0), m_MapSizePatches / 2 - size / 2 + horizontalOffset);
const ssize_t sourceUpperLeftZ = std::max(
static_cast<ssize_t>(0), m_MapSizePatches / 2 - size / 2 + verticalOffset);
const ssize_t destUpperLeftX = std::max(
static_cast<ssize_t>(0), (size / 2 - m_MapSizePatches / 2 - horizontalOffset));
const ssize_t destUpperLeftZ = std::max(
static_cast<ssize_t>(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 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;j<copysize;j++) {
memcpy(dst,src,copysize*sizeof(u16));
dst+=copysize;
src+=m_MapSize;
if (newMapSize>m_MapSize) {
// extend the last height to the end of the row
for (size_t i=0;i<newMapSize-(size_t)m_MapSize;i++) {
*dst++=*(src-1);
// 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 (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;i<newMapSize-m_MapSize;i++) {
memcpy(dst,src,newMapSize*sizeof(u16));
dst+=newMapSize;
}
}
// now build new patches
for (ssize_t j=0;j<size;j++) {
for (ssize_t i=0;i<size;i++) {
// copy over texture data from existing tiles, if possible
if (i<m_MapSizePatches && j<m_MapSizePatches) {
memcpy(newPatches[j*size+i].m_MiniPatches,m_Patches[j*m_MapSizePatches+i].m_MiniPatches,sizeof(CMiniPatch)*PATCH_SIZE*PATCH_SIZE);
}
}
if (j<m_MapSizePatches && size>m_MapSizePatches) {
// copy over the last tile from each column
for (ssize_t n=0;n<size-m_MapSizePatches;n++) {
for (ssize_t m=0;m<PATCH_SIZE;m++) {
CMiniPatch& src=m_Patches[j*m_MapSizePatches+m_MapSizePatches-1].m_MiniPatches[m][15];
for (ssize_t k=0;k<PATCH_SIZE;k++) {
CMiniPatch& dst=newPatches[j*size+m_MapSizePatches+n].m_MiniPatches[m][k];
dst = src;
}
for (ssize_t i = destUpperLeftX + width; i < size; ++i)
{
for (ssize_t jPatch = 0; jPatch < PATCH_SIZE; ++jPatch)
{
const CMiniPatch& src =
newPatches[(destUpperLeftZ + j) * size + destUpperLeftX + width - 1]
.m_MiniPatches[jPatch][PATCH_SIZE - 1];
for (ssize_t iPatch = 0; iPatch < PATCH_SIZE; ++iPatch)
{
CMiniPatch& dst =
newPatches[(destUpperLeftZ + j) * size + i].m_MiniPatches[jPatch][iPatch];
dst = src;
}
}
}
}
if (size>m_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;m<PATCH_SIZE;m++) {
for (ssize_t k=0;k<PATCH_SIZE;k++) {
CMiniPatch& src=srcpatch->m_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);
}
///////////////////////////////////////////////////////////////////////////////

View File

@ -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 @@ public:
// 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 @@ private:
CHeightMipmap m_HeightMipmap;
};
#endif
#endif // INCLUDED_TERRAIN

View File

@ -203,11 +203,14 @@ public:
// value per a patch.
struct ResizeTestCase
{
ssize_t horizontalOffset, verticalOffset;
std::vector<std::vector<u16>> sourcePatches;
std::vector<std::vector<u16>> expectedPatches;
};
const ResizeTestCase testCases[] = {
// Without offset.
{
0, 0,
{
{42}
},
@ -216,25 +219,154 @@ public:
}
},
{
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 @@ public:
CTerrain terrain;
{
std::vector<u16> 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 @@ public:
return;
}
if (testCase.expectedPatches[iTile][jTile] ==
if (testCase.expectedPatches[jTile][iTile] ==
GetVertex(terrain, iTile * PATCH_SIZE,
jTile * PATCH_SIZE))
continue;
@ -313,19 +445,19 @@ public:
<< " (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);

View File

@ -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 @@ MESSAGEHANDLER(ImportHeightmap)
// 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 @@ BEGIN_COMMAND(ResizeMap)
{
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();
}