1
0
forked from 0ad/0ad

Compute camera height and zoom limits based on smoothed terrain heightmap. Fixes #794, based on patch by Dietger.

This was SVN commit r11556.
This commit is contained in:
Ykkrosh 2012-04-18 20:39:00 +00:00
parent 9572a4314a
commit 769350a3e7
11 changed files with 516 additions and 65 deletions

View File

@ -96,6 +96,8 @@ view.rotate.y.smoothness = 0.3
view.near = 2.0 ; Near plane distance
view.far = 4096.0 ; Far plane distance
view.fov = 45.0 ; Field of view (degrees), lower is narrow, higher is wide
view.height.smoothness = 0.5
view.height.min = 16
; HOTKEY MAPPINGS:

View File

@ -190,6 +190,8 @@ public:
JoystickRotateY(-1),
JoystickZoomIn(-1),
JoystickZoomOut(-1),
HeightSmoothness(0.5f),
HeightMin(16.f),
PosX(0, 0, 0.01f),
PosY(0, 0, 0.01f),
@ -289,6 +291,8 @@ public:
int JoystickRotateY;
int JoystickZoomIn;
int JoystickZoomOut;
float HeightSmoothness;
float HeightMin;
////////////////////////////////////////
// Camera Controls State
@ -426,6 +430,9 @@ int CGameView::Initialize()
CFG_GET_SYS_VAL("joystick.camera.zoom.in", Int, m->JoystickZoomIn);
CFG_GET_SYS_VAL("joystick.camera.zoom.out", Int, m->JoystickZoomOut);
CFG_GET_SYS_VAL("view.height.smoothness", Float, m->HeightSmoothness);
CFG_GET_SYS_VAL("view.height.min", Float, m->HeightMin);
CFG_GET_SYS_VAL("view.pos.smoothness", Float, m->PosX.m_Smoothness);
CFG_GET_SYS_VAL("view.pos.smoothness", Float, m->PosY.m_Smoothness);
CFG_GET_SYS_VAL("view.pos.smoothness", Float, m->PosZ.m_Smoothness);
@ -509,7 +516,7 @@ void CGameView::EnumerateObjects(const CFrustum& frustum, SceneCollector* c)
if(bounds[1].Y < waterHeight) {
bounds[1].Y = waterHeight;
}
if (!m->Culling || frustum.IsBoxVisible (CVector3D(0,0,0), bounds)) {
//c->Submit(patch);
@ -595,40 +602,69 @@ void CGameView::UnloadResources()
g_Renderer.GetWaterManager()->UnloadWaterTextures();
}
static void ClampDistance(CGameViewImpl* m, bool smooth)
static void FocusHeight(CGameViewImpl* m, bool smooth)
{
/*
The camera pivot height is moved towards ground level.
To prevent excessive zoom when looking over a cliff,
the target ground level is the maximum of the ground level at the camera's near and pivot points.
The ground levels are filtered to achieve smooth camera movement.
The filter radius is proportional to the zoom level.
The camera height is clamped to prevent map penetration.
*/
if (!m->ConstrainCamera)
return;
CCamera targetCam = m->ViewCamera;
SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation);
CVector3D forwards = targetCam.m_Orientation.GetIn();
const CVector3D position = targetCam.m_Orientation.GetTranslation();
const CVector3D forwards = targetCam.m_Orientation.GetIn();
CVector3D delta = targetCam.GetFocus() - targetCam.m_Orientation.GetTranslation();
// horizontal view radius
const float radius = sqrtf(forwards.X * forwards.X + forwards.Z * forwards.Z) * m->Zoom.GetSmoothedValue();
const float near_radius = radius * m->HeightSmoothness;
const float pivot_radius = radius * m->HeightSmoothness;
float dist = delta.Dot(forwards);
float clampedDist = Clamp(dist, m->ViewZoomMin, m->ViewZoomMax);
float diff = clampedDist - dist;
const CVector3D nearPoint = position + forwards * m->ViewNear;
const CVector3D pivotPoint = position + forwards * m->Zoom.GetSmoothedValue();
if (!diff)
const float ground = m->Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z);
// filter ground levels for smooth camera movement
const float filtered_near_ground = m->Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(nearPoint.X, nearPoint.Z, near_radius);
const float filtered_pivot_ground = m->Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(pivotPoint.X, pivotPoint.Z, pivot_radius);
// filtered maximum visible ground level in view
const float filtered_ground = std::max(filtered_near_ground, filtered_pivot_ground);
// target camera height above pivot point
const float pivot_height = -forwards.Y * (m->Zoom.GetSmoothedValue() - m->ViewNear);
// minimum camera height above filtered ground level
const float min_height = (m->HeightMin + ground - filtered_ground);
const float target_height = std::max(pivot_height, min_height);
const float height = (nearPoint.Y - filtered_ground);
const float diff = target_height - height;
if (fabsf(diff) < 0.0001f)
return;
if (smooth)
{
m->PosX.AddSmoothly(forwards.X * -diff);
m->PosY.AddSmoothly(forwards.Y * -diff);
m->PosZ.AddSmoothly(forwards.Z * -diff);
m->PosY.AddSmoothly(diff);
}
else
{
m->PosX.Add(forwards.X * -diff);
m->PosY.Add(forwards.Y * -diff);
m->PosZ.Add(forwards.Z * -diff);
m->PosY.Add(diff);
}
}
CVector3D CGameView::GetSmoothPivot(CCamera& camera) const
{
return camera.m_Orientation.GetTranslation() + camera.m_Orientation.GetIn() * m->Zoom.GetSmoothedValue();
}
void CGameView::Update(float DeltaTime)
{
// If camera movement is being handled by the touch-input system,
@ -759,7 +795,7 @@ void CGameView::Update(float DeltaTime)
CCamera targetCam = m->ViewCamera;
SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation);
CVector3D pivot = targetCam.GetFocus();
CVector3D pivot = GetSmoothPivot(targetCam);
CVector3D delta = pos - pivot;
m->PosX.AddSmoothly(delta.X);
m->PosY.AddSmoothly(delta.Y);
@ -774,11 +810,14 @@ void CGameView::Update(float DeltaTime)
}
if (HotkeyIsPressed("camera.zoom.in"))
m->Zoom.AddSmoothly(m->ViewZoomSpeed * DeltaTime);
if (HotkeyIsPressed("camera.zoom.out"))
m->Zoom.AddSmoothly(-m->ViewZoomSpeed * DeltaTime);
if (HotkeyIsPressed("camera.zoom.out"))
m->Zoom.AddSmoothly(m->ViewZoomSpeed * DeltaTime);
float zoomDelta = m->Zoom.Update(DeltaTime);
if (m->ConstrainCamera)
m->Zoom.ClampSmoothly(m->ViewZoomMin, m->ViewZoomMax);
float zoomDelta = -m->Zoom.Update(DeltaTime);
if (zoomDelta)
{
CVector3D forwards = m->ViewCamera.m_Orientation.GetIn();
@ -790,7 +829,7 @@ void CGameView::Update(float DeltaTime)
if (m->ConstrainCamera)
m->RotateX.ClampSmoothly(DEGTORAD(m->ViewRotateXMin), DEGTORAD(m->ViewRotateXMax));
ClampDistance(m, true);
FocusHeight(m, true);
// Ensure the ViewCamera focus is inside the map with the chosen margins
// if not so - apply margins to the camera
@ -801,7 +840,7 @@ void CGameView::Update(float DeltaTime)
CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain();
CVector3D pivot = targetCam.GetFocus();
CVector3D pivot = GetSmoothPivot(targetCam);
CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot;
CVector3D desiredPivot = pivot;
@ -846,7 +885,7 @@ void CGameView::Update(float DeltaTime)
CVector3D upwards(0.0f, 1.0f, 0.0f);
CVector3D pivot = targetCam.GetFocus();
CVector3D pivot = GetSmoothPivot(targetCam);
CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot;
CQuaternion q;
@ -869,7 +908,7 @@ void CGameView::Update(float DeltaTime)
{
CVector3D rightwards = targetCam.m_Orientation.GetLeft() * -1.0f;
CVector3D pivot = m->ViewCamera.GetFocus();
CVector3D pivot = GetSmoothPivot(targetCam);
CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot;
CQuaternion q;
@ -919,13 +958,13 @@ void CGameView::MoveCameraTarget(const CVector3D& target)
CCamera targetCam = m->ViewCamera;
SetupCameraMatrixNonSmooth(m, &targetCam.m_Orientation);
CVector3D pivot = targetCam.GetFocus();
CVector3D pivot = GetSmoothPivot(targetCam);
CVector3D delta = target - pivot;
m->PosX.SetValueSmoothly(delta.X + m->PosX.GetValue());
m->PosZ.SetValueSmoothly(delta.Z + m->PosZ.GetValue());
ClampDistance(m, false);
FocusHeight(m, false);
// Break out of following mode so the camera really moves to the target
m->FollowEntity = INVALID_ENTITY;
@ -944,8 +983,9 @@ void CGameView::ResetCameraTarget(const CVector3D& target)
m->PosZ.SetValue(target.Z - delta.Z);
m->RotateX.SetValue(DEGTORAD(m->ViewRotateXDefault));
m->RotateY.SetValue(DEGTORAD(m->ViewRotateYDefault));
m->Zoom.SetValue(m->ViewZoomDefault);
ClampDistance(m, false);
FocusHeight(m, false);
SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation);
m->ViewCamera.UpdateFrustum();
@ -961,9 +1001,11 @@ void CGameView::ResetCameraAngleZoom()
// Compute the zoom adjustment to get us back to the default
CVector3D forwards = targetCam.m_Orientation.GetIn();
CVector3D delta = targetCam.GetFocus() - targetCam.m_Orientation.GetTranslation();
CVector3D pivot = GetSmoothPivot(targetCam);
CVector3D delta = pivot - targetCam.m_Orientation.GetTranslation();
float dist = delta.Dot(forwards);
m->Zoom.AddSmoothly(dist - m->ViewZoomDefault);
m->Zoom.AddSmoothly(m->ViewZoomDefault - dist);
// Reset orientations to default
m->RotateX.SetValueSmoothly(DEGTORAD(m->ViewRotateXDefault));
@ -1049,12 +1091,12 @@ InReaction CGameView::HandleEvent(const SDL_Event_* ev)
// and we never get to see the "down" state inside Update().
else if (hotkey == "camera.zoom.wheel.in")
{
m->Zoom.AddSmoothly(m->ViewZoomSpeedWheel);
m->Zoom.AddSmoothly(-m->ViewZoomSpeedWheel);
return IN_HANDLED;
}
else if (hotkey == "camera.zoom.wheel.out")
{
m->Zoom.AddSmoothly(-m->ViewZoomSpeedWheel);
m->Zoom.AddSmoothly(m->ViewZoomSpeedWheel);
return IN_HANDLED;
}
else if (hotkey == "camera.rotate.wheel.cw")

View File

@ -85,6 +85,8 @@ public:
void CameraFollow(entity_id_t entity, bool firstPerson);
entity_id_t GetFollowedEntity();
CVector3D GetSmoothPivot(CCamera &camera) const;
float GetNear() const;
float GetFar() const;
float GetFOV() const;

View File

@ -0,0 +1,261 @@
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "HeightMipmap.h"
#include "lib/bits.h"
#include "lib/timer.h"
#include "lib/allocators/shared_ptr.h"
#include "lib/tex/tex.h"
#include "maths/MathUtil.h"
#include "ps/Filesystem.h"
#include <cmath>
CHeightMipmap::CHeightMipmap()
{
}
CHeightMipmap::~CHeightMipmap()
{
ReleaseData();
}
void CHeightMipmap::ReleaseData()
{
for (size_t i = 0; i < m_Mipmap.size(); ++i)
{
delete[] m_Mipmap[i].m_Heightmap;
m_Mipmap[i].m_MapSize = 0;
}
m_Mipmap.clear();
}
void CHeightMipmap::Update(const u16* ptr)
{
ENSURE(ptr != 0);
Update(ptr, 0, 0, m_MapSize, m_MapSize);
}
void CHeightMipmap::Update(const u16* ptr, size_t left, size_t bottom, size_t right, size_t top)
{
ENSURE(ptr != 0);
size_t mapSize = m_MapSize;
for (size_t i = 0; i < m_Mipmap.size(); ++i)
{
// update window
left = clamp<size_t>((size_t)floorf((float)left / mapSize * m_Mipmap[i].m_MapSize), 0, m_Mipmap[i].m_MapSize - 1);
bottom = clamp<size_t>((size_t)floorf((float)bottom / mapSize * m_Mipmap[i].m_MapSize), 0, m_Mipmap[i].m_MapSize - 1);
right = clamp<size_t>((size_t)ceilf((float)right / mapSize * m_Mipmap[i].m_MapSize), 0, m_Mipmap[i].m_MapSize);
top = clamp<size_t>((size_t)ceilf((float)top / mapSize * m_Mipmap[i].m_MapSize), 0, m_Mipmap[i].m_MapSize);
// TODO: should verify that the bounds calculations are actually correct
// update mipmap
BilinearUpdate(m_Mipmap[i], mapSize, ptr, left, bottom, right, top);
mapSize = m_Mipmap[i].m_MapSize;
ptr = m_Mipmap[i].m_Heightmap;
}
}
void CHeightMipmap::Initialize(size_t mapSize, const u16* ptr)
{
ENSURE(ptr != 0);
ENSURE(mapSize > 0);
ReleaseData();
m_MapSize = mapSize;
size_t mipmapSize = round_down_to_pow2(mapSize);
while (mipmapSize > 1)
{
m_Mipmap.push_back(SMipmap(mipmapSize, new u16[mipmapSize*mipmapSize]));
mipmapSize >>= 1;
};
Update(ptr);
}
float CHeightMipmap::GetTrilinearGroundLevel(float x, float z, float radius) const
{
float y;
if (radius <= 0.0f) // avoid logf of non-positive value
y = 0.0f;
else
y = clamp<float>(logf(radius * m_Mipmap[0].m_MapSize) / logf(2), 0, m_Mipmap.size());
const size_t iy = (size_t)clamp<ssize_t>((ssize_t)floorf(y), 0, m_Mipmap.size() - 2);
const float fy = y - iy;
const float h0 = BilinearFilter(m_Mipmap[iy], x, z);
const float h1 = BilinearFilter(m_Mipmap[iy + 1], x, z);
return (1 - fy) * h0 + fy * h1;
}
float CHeightMipmap::BilinearFilter(const SMipmap &mipmap, float x, float z) const
{
x *= mipmap.m_MapSize;
z *= mipmap.m_MapSize;
const size_t xi = (size_t)clamp<ssize_t>((ssize_t)floor(x), 0, mipmap.m_MapSize - 2);
const size_t zi = (size_t)clamp<ssize_t>((ssize_t)floor(z), 0, mipmap.m_MapSize - 2);
const float xf = clamp<float>(x-xi, 0.0f, 1.0f);
const float zf = clamp<float>(z-zi, 0.0f, 1.0f);
const float h00 = mipmap.m_Heightmap[zi*mipmap.m_MapSize + xi];
const float h01 = mipmap.m_Heightmap[(zi+1)*mipmap.m_MapSize + xi];
const float h10 = mipmap.m_Heightmap[zi*mipmap.m_MapSize + (xi+1)];
const float h11 = mipmap.m_Heightmap[(zi+1)*mipmap.m_MapSize + (xi+1)];
return
(1.f - xf) * (1.f - zf) * h00 +
xf * (1.f - zf) * h10 +
(1.f - xf) * zf * h01 +
xf * zf * h11;
}
void CHeightMipmap::HalfResizeUpdate(SMipmap &out_mipmap, size_t mapSize, const u16* ptr, size_t left, size_t bottom, size_t right, size_t top)
{
// specialized, faster version of BilinearUpdate for powers of 2
ENSURE(out_mipmap.m_MapSize != 0);
if (out_mipmap.m_MapSize * 2 != mapSize)
debug_warn(L"wrong size");
// valid update window
ENSURE(left < out_mipmap.m_MapSize);
ENSURE(bottom < out_mipmap.m_MapSize);
ENSURE(right > left && right <= out_mipmap.m_MapSize);
ENSURE(top > bottom && top <= out_mipmap.m_MapSize);
for (size_t dstZ = bottom; dstZ < top; ++dstZ)
{
for (size_t dstX = left; dstX < right; ++dstX)
{
size_t srcX = dstX << 1;
size_t srcZ = dstZ << 1;
u16 h00 = ptr[srcX + 0 + srcZ * mapSize];
u16 h10 = ptr[srcX + 1 + srcZ * mapSize];
u16 h01 = ptr[srcX + 0 + (srcZ + 1) * mapSize];
u16 h11 = ptr[srcX + 1 + (srcZ + 1) * mapSize];
out_mipmap.m_Heightmap[dstX + dstZ * out_mipmap.m_MapSize] = (h00 + h10 + h01 + h11) / 4;
}
}
}
void CHeightMipmap::BilinearUpdate(SMipmap &out_mipmap, size_t mapSize, const u16* ptr, size_t left, size_t bottom, size_t right, size_t top)
{
ENSURE(out_mipmap.m_MapSize != 0);
// filter should have full coverage
ENSURE(out_mipmap.m_MapSize <= mapSize && out_mipmap.m_MapSize * 2 >= mapSize);
// valid update window
ENSURE(left < out_mipmap.m_MapSize);
ENSURE(bottom < out_mipmap.m_MapSize);
ENSURE(right > left && right <= out_mipmap.m_MapSize);
ENSURE(top > bottom && top <= out_mipmap.m_MapSize);
if (out_mipmap.m_MapSize * 2 == mapSize)
{
// optimized for powers of 2
HalfResizeUpdate(out_mipmap, mapSize, ptr, left, bottom, right, top);
}
else
{
for (size_t dstZ = bottom; dstZ < top; ++dstZ)
{
for (size_t dstX = left; dstX < right; ++dstX)
{
const float x = ((float)dstX / (float)out_mipmap.m_MapSize) * mapSize;
const float z = ((float)dstZ / (float)out_mipmap.m_MapSize) * mapSize;
const size_t srcX = clamp<size_t>((size_t)x, 0, mapSize - 2);
const size_t srcZ = clamp<size_t>((size_t)z, 0, mapSize - 2);
const float fx = clamp<float>(x - srcX, 0.0f, 1.0f);
const float fz = clamp<float>(z - srcZ, 0.0f, 1.0f);
const float h00 = ptr[srcX + 0 + srcZ * mapSize];
const float h10 = ptr[srcX + 1 + srcZ * mapSize];
const float h01 = ptr[srcX + 0 + (srcZ + 1) * mapSize];
const float h11 = ptr[srcX + 1 + (srcZ + 1) * mapSize];
out_mipmap.m_Heightmap[dstX + dstZ * out_mipmap.m_MapSize] = (u16)
((1.f - fx) * (1.f - fz) * h00 +
fx * (1.f - fz) * h10 +
(1.f - fx) * fz * h01 +
fx * fz * h11);
}
}
}
}
void CHeightMipmap::DumpToDisk(const VfsPath& filename) const
{
const size_t w = m_MapSize;
const size_t h = m_MapSize * 2;
const size_t bpp = 8;
int flags = TEX_GREY|TEX_TOP_DOWN;
const size_t img_size = w * h * bpp/8;
const size_t hdr_size = tex_hdr_size(filename);
shared_ptr<u8> buf;
AllocateAligned(buf, hdr_size+img_size, maxSectorSize);
void* img = buf.get() + hdr_size;
Tex t;
WARN_IF_ERR(tex_wrap(w, h, bpp, flags, buf, hdr_size, &t));
memset(img, 0x00, img_size);
size_t yoff = 0;
for (size_t i = 0; i < m_Mipmap.size(); ++i)
{
size_t size = m_Mipmap[i].m_MapSize;
u16* heightmap = m_Mipmap[i].m_Heightmap;
ENSURE(size+yoff <= h);
for (size_t y = 0; y < size; ++y)
{
for (size_t x = 0; x < size; ++x)
{
u16 val = heightmap[x + y*size];
((u8*)img)[x + (y+yoff)*w] = val >> 8;
}
}
yoff += size;
}
DynArray da;
WARN_IF_ERR(tex_encode(&t, filename.Extension(), &da));
g_VFS->CreateFile(filename, DummySharedPtr(da.base), da.pos);
(void)da_free(&da);
tex_free(&t);
}

View File

@ -0,0 +1,79 @@
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Describes ground using heightmap mipmaps
* Used for camera movement
*/
#ifndef INCLUDED_HEIGHTMIPMAP
#define INCLUDED_HEIGHTMIPMAP
#include "lib/file/vfs/vfs_path.h"
struct SMipmap
{
SMipmap() : m_MapSize(0), m_Heightmap(0) { }
SMipmap(size_t MapSize, u16* Heightmap) : m_MapSize(MapSize), m_Heightmap(Heightmap) { }
size_t m_MapSize;
u16* m_Heightmap;
};
class CHeightMipmap
{
NONCOPYABLE(CHeightMipmap);
public:
CHeightMipmap();
~CHeightMipmap();
void Initialize(size_t mapSize, const u16* ptr);
void ReleaseData();
// update the heightmap mipmaps
void Update(const u16* ptr);
// update a section of the heightmap mipmaps
// (coordinates are heightmap cells, inclusive of lower bounds,
// exclusive of upper bounds)
void Update(const u16* ptr, size_t left, size_t bottom, size_t right, size_t top);
float GetTrilinearGroundLevel(float x, float z, float radius) const;
void DumpToDisk(const VfsPath& path) const;
private:
// get bilinear filtered height from mipmap
float BilinearFilter(const SMipmap &mipmap, float x, float z) const;
// update rectangle of the output mipmap by bilinear interpolating an input mipmap of exactly twice its size
void HalfResizeUpdate(SMipmap &out_mipmap, size_t mapSize, const u16* ptr, size_t left, size_t bottom, size_t right, size_t top);
// update rectangle of the output mipmap by bilinear interpolating the input mipmap
void BilinearUpdate(SMipmap &out_mipmap, size_t mapSize, const u16* ptr, size_t left, size_t bottom, size_t right, size_t top);
// size of this map in each direction
size_t m_MapSize;
// mipmap list
std::vector<SMipmap> m_Mipmap;
};
#endif

View File

@ -57,6 +57,8 @@ CTerrain::~CTerrain()
// ReleaseData: delete any data allocated by this terrain
void CTerrain::ReleaseData()
{
m_HeightMipmap.ReleaseData();
delete[] m_Heightmap;
delete[] m_Patches;
}
@ -65,30 +67,36 @@ void CTerrain::ReleaseData()
///////////////////////////////////////////////////////////////////////////////
// Initialise: initialise this terrain to the given size
// using given heightmap to setup elevation data
bool CTerrain::Initialize(ssize_t patchesPerSide,const u16* data)
bool CTerrain::Initialize(ssize_t patchesPerSide, const u16* data)
{
// clean up any previous terrain
ReleaseData();
// store terrain size
m_MapSize=patchesPerSide*PATCH_SIZE+1;
m_MapSizePatches=patchesPerSide;
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) {
if (data)
{
// yes; keep a copy of it
memcpy(m_Heightmap,data,m_MapSize*m_MapSize*sizeof(u16));
} else {
memcpy(m_Heightmap, data, m_MapSize*m_MapSize*sizeof(u16));
}
else
{
// build a flat terrain
memset(m_Heightmap,0,m_MapSize*m_MapSize*sizeof(u16));
memset(m_Heightmap, 0, m_MapSize*m_MapSize*sizeof(u16));
}
// setup patch parents, indices etc
InitialisePatches();
// initialise mipmap
m_HeightMipmap.Initialize(m_MapSize, m_Heightmap);
return true;
}
@ -330,6 +338,17 @@ fixed CTerrain::GetSlopeFixed(ssize_t i, ssize_t j) const
return fixed::FromInt(delta / TERRAIN_TILE_SIZE) / (int)HEIGHT_UNITS_PER_METRE;
}
float CTerrain::GetFilteredGroundLevel(float x, float z, float radius) const
{
// convert to [0,1] interval
float nx = x / (TERRAIN_TILE_SIZE*m_MapSize);
float nz = z / (TERRAIN_TILE_SIZE*m_MapSize);
float nr = radius / (TERRAIN_TILE_SIZE*m_MapSize);
// get trilinear filtered mipmap height
return HEIGHT_SCALE * m_HeightMipmap.GetTrilinearGroundLevel(nx, nz, nr);
}
float CTerrain::GetExactGroundLevel(float x, float z) const
{
// Clamp to size-2 so we can use the tiles (xi,zi)-(xi+1,zi+1)
@ -530,6 +549,9 @@ void CTerrain::Resize(ssize_t size)
// initialise all the new patches
InitialisePatches();
// initialise mipmap
m_HeightMipmap.Initialize(m_MapSize,m_Heightmap);
}
///////////////////////////////////////////////////////////////////////////////
@ -564,6 +586,9 @@ void CTerrain::SetHeightMap(u16* heightmap)
patch->SetDirty(RENDERDATA_UPDATE_VERTICES);
}
}
// update mipmap
m_HeightMipmap.Update(m_Heightmap);
}
@ -587,6 +612,9 @@ void CTerrain::MakeDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1, int dir
patch->SetDirty(dirtyFlags);
}
}
if (m_Heightmap)
m_HeightMipmap.Update(m_Heightmap, i0, j0, i1, j1);
}
void CTerrain::MakeDirty(int dirtyFlags)
@ -601,6 +629,9 @@ void CTerrain::MakeDirty(int dirtyFlags)
patch->SetDirty(dirtyFlags);
}
}
if (m_Heightmap)
m_HeightMipmap.Update(m_Heightmap);
}
CBoundingBoxAligned CTerrain::GetVertexesBound(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1)

View File

@ -25,6 +25,7 @@
#include "maths/Vector3D.h"
#include "maths/Fixed.h"
#include "graphics/SColor.h"
#include "graphics/HeightMipmap.h"
class CPatch;
class CMiniPatch;
@ -84,6 +85,7 @@ public:
fixed GetVertexGroundLevelFixed(ssize_t i, ssize_t j) const;
float GetExactGroundLevel(float x, float z) const;
fixed GetExactGroundLevelFixed(fixed x, fixed z) const;
float GetFilteredGroundLevel(float x, float z, float radius) const;
// get the approximate slope (0 = horizontal, 0.5 = 30 degrees, 1.0 = 45 degrees, etc)
fixed GetSlopeFixed(ssize_t i, ssize_t j) const;
@ -151,6 +153,8 @@ public:
// set the base colour for the terrain
void SetBaseColour(SColor4ub colour) { m_BaseColour = colour; }
const CHeightMipmap& GetHeightMipmap() const { return m_HeightMipmap; }
private:
// delete any data allocated by this terrain
void ReleaseData();
@ -167,6 +171,8 @@ private:
u16* m_Heightmap;
// base colour (usually white)
SColor4ub m_BaseColour;
// heightmap mipmap
CHeightMipmap m_HeightMipmap;
};
#endif

View File

@ -531,6 +531,15 @@ void DumpSimState(void* UNUSED(cbdata))
g_Game->GetSimulation2()->DumpDebugState(file);
}
void DumpTerrainMipmap(void* UNUSED(cbdata))
{
VfsPath filename(L"screenshots/terrainmipmap.png");
g_Game->GetWorld()->GetTerrain()->GetHeightMipmap().DumpToDisk(filename);
OsPath realPath;
g_VFS->GetRealPath(filename, realPath);
LOGMESSAGERENDER(L"Terrain mipmap written to '%ls'", realPath.string().c_str());
}
void EnableTimeWarpRecording(void* UNUSED(cbdata), unsigned int numTurns)
{
g_Game->GetTurnManager()->EnableTimeWarpRecording(numTurns);
@ -628,6 +637,7 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
scriptInterface.RegisterFunction<void, &DebugWarn>("DebugWarn");
scriptInterface.RegisterFunction<void, &ForceGC>("ForceGC");
scriptInterface.RegisterFunction<void, &DumpSimState>("DumpSimState");
scriptInterface.RegisterFunction<void, &DumpTerrainMipmap>("DumpTerrainMipmap");
scriptInterface.RegisterFunction<void, unsigned int, &EnableTimeWarpRecording>("EnableTimeWarpRecording");
scriptInterface.RegisterFunction<void, &RewindTimeWarp>("RewindTimeWarp");
scriptInterface.RegisterFunction<void, bool, &SetBoundingBoxDebugOverlay>("SetBoundingBoxDebugOverlay");

View File

@ -245,6 +245,15 @@ inline T round_up_to_pow2(T x)
return T(1) << ceil_log2(x);
}
/**
* round down to next larger power of two.
**/
template<typename T>
inline T round_down_to_pow2(T x)
{
return T(1) << floor_log2(x);
}
/**
* round number up/down to the next given multiple.
*

View File

@ -24,15 +24,16 @@
#include "lib/bits.h"
#define EQUALS(actual, expected) ENSURE((actual) == (expected))
//#define EQUALS(actual, expected) ENSURE((actual) == (expected))
#define EQUALS TS_ASSERT_EQUALS
class TestBits : public CxxTest::TestSuite
{
public:
void test_Bit()
{
EQUALS(Bit<unsigned>(0), 1);
EQUALS(Bit<unsigned>(8), 0x100);
EQUALS(Bit<unsigned>(0), 1u);
EQUALS(Bit<unsigned>(8), 0x100u);
EQUALS(Bit<u32>(31), u32(0x80000000ul));
EQUALS(Bit<u64>(1), u64(2));
EQUALS(Bit<u64>(32), u64(0x100000000ull));
@ -57,11 +58,11 @@ public:
EQUALS(bit_mask<u16>(0), 0);
EQUALS(bit_mask<u16>(2), 0x3);
EQUALS(bit_mask<u16>(16), 0xFFFF);
EQUALS(bit_mask<u32>(0), 0);
EQUALS(bit_mask<u32>(2), 0x3);
EQUALS(bit_mask<u32>(0), 0u);
EQUALS(bit_mask<u32>(2), 0x3u);
EQUALS(bit_mask<u32>(32), 0xFFFFFFFFul);
EQUALS(bit_mask<u64>(0), 0);
EQUALS(bit_mask<u64>(2), 0x3);
EQUALS(bit_mask<u64>(0), 0u);
EQUALS(bit_mask<u64>(2), 0x3u);
EQUALS(bit_mask<u64>(32), 0xFFFFFFFFull);
EQUALS(bit_mask<u64>(64), 0xFFFFFFFFFFFFFFFFull);
}
@ -85,20 +86,20 @@ public:
void test_PopulationCount()
{
EQUALS(PopulationCount<u8>(0), 0);
EQUALS(PopulationCount<u8>(4), 1);
EQUALS(PopulationCount<u8>(0x28), 2);
EQUALS(PopulationCount<u8>(0xFF), 8);
EQUALS(PopulationCount<u32>(0x0ul), 0);
EQUALS(PopulationCount<u32>(0x8ul), 1);
EQUALS(PopulationCount<u32>(0xFFFFul), 16);
EQUALS(PopulationCount<u32>(0xFFFFFFFFul), 32);
EQUALS(PopulationCount<u64>(0x0ull), 0);
EQUALS(PopulationCount<u64>(0x10ull), 1);
EQUALS(PopulationCount<u64>(0xFFFFull), 16);
EQUALS(PopulationCount<u64>(0xFFFFFFFFull), 32);
EQUALS(PopulationCount<u64>(0xFFFFFFFFFFFFFFFEull), 63);
EQUALS(PopulationCount<u64>(0xFFFFFFFFFFFFFFFFull), 64);
EQUALS(PopulationCount<u8>(0), 0u);
EQUALS(PopulationCount<u8>(4), 1u);
EQUALS(PopulationCount<u8>(0x28), 2u);
EQUALS(PopulationCount<u8>(0xFF), 8u);
EQUALS(PopulationCount<u32>(0x0ul), 0u);
EQUALS(PopulationCount<u32>(0x8ul), 1u);
EQUALS(PopulationCount<u32>(0xFFFFul), 16u);
EQUALS(PopulationCount<u32>(0xFFFFFFFFul), 32u);
EQUALS(PopulationCount<u64>(0x0ull), 0u);
EQUALS(PopulationCount<u64>(0x10ull), 1u);
EQUALS(PopulationCount<u64>(0xFFFFull), 16u);
EQUALS(PopulationCount<u64>(0xFFFFFFFFull), 32u);
EQUALS(PopulationCount<u64>(0xFFFFFFFFFFFFFFFEull), 63u);
EQUALS(PopulationCount<u64>(0xFFFFFFFFFFFFFFFFull), 64u);
}
void test_is_pow2()
@ -135,6 +136,14 @@ public:
EQUALS(round_up_to_pow2(129u), 256u);
}
void test_round_down_to_pow2()
{
EQUALS(round_down_to_pow2(1u), 1u);
EQUALS(round_down_to_pow2(127u), 64u);
EQUALS(round_down_to_pow2(128u), 128u);
EQUALS(round_down_to_pow2(129u), 128u);
}
void test_round_up()
{
EQUALS(round_up( 0u, 16u), 0u);

View File

@ -56,7 +56,7 @@ public:
brush.Slice(plane, result);
// verify that the resulting brush consists of exactly our 8 expected, unique vertices
TS_ASSERT_EQUALS(8, result.GetVertices().size());
TS_ASSERT_EQUALS((size_t)8, result.GetVertices().size());
size_t LBF = GetUniqueVertexIndex(result, CVector3D(0, 0, 0)); // left-bottom-front <=> XYZ
size_t RBF = GetUniqueVertexIndex(result, CVector3D(1, 0, 0)); // right-bottom-front
size_t RBB = GetUniqueVertexIndex(result, CVector3D(1, 0, 0.5f)); // right-bottom-back
@ -86,7 +86,7 @@ public:
brush.Slice(plane, result);
// verify that the resulting brush consists of exactly our 8 expected, unique vertices
TS_ASSERT_EQUALS(8, result.GetVertices().size());
TS_ASSERT_EQUALS((size_t)8, result.GetVertices().size());
size_t LBF = GetUniqueVertexIndex(result, CVector3D(0, 0, 0)); // left-bottom-front <=> XYZ
size_t RBF = GetUniqueVertexIndex(result, CVector3D(1, 0, 0)); // right-bottom-front
size_t RBB = GetUniqueVertexIndex(result, CVector3D(1, 0, 1)); // right-bottom-back
@ -115,12 +115,12 @@ public:
CBrush result;
brush.Slice(plane, result);
TS_ASSERT_EQUALS(0, result.GetVertices().size());
TS_ASSERT_EQUALS((size_t)0, result.GetVertices().size());
std::vector<std::vector<size_t> > faces;
result.GetFaces(faces);
TS_ASSERT_EQUALS(0, faces.size());
TS_ASSERT_EQUALS((size_t)0, faces.size());
}
private: