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:
parent
9572a4314a
commit
769350a3e7
@ -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:
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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;
|
||||
|
261
source/graphics/HeightMipmap.cpp
Normal file
261
source/graphics/HeightMipmap.cpp
Normal 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);
|
||||
}
|
79
source/graphics/HeightMipmap.h
Normal file
79
source/graphics/HeightMipmap.h
Normal 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
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user