Clipmap bounds class (#7134)
* ClipmapBounds class - This class is built to keep track of textures for clipmap like structures where there is a virtual center point and the edges need to be updated as the camera moves around the world Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Removing dead code Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Updates from PR feedback. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * comment update Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Updates from review suggestions Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * More updates. Moved the snapped center point calculation out to a separate function so the constructor doesn't need to do unnecessary work. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Removing unused variable. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Fixing bug in unit test that was doing a comparison and throwing away the result instead of actually testing it. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Adding some comments and constifying some functions. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Fixing numeric casting issue on linux Signed-off-by: Ken Pruiksma <pruiksma@amazon.com>
This commit is contained in:
@@ -18,12 +18,40 @@ namespace Terrain
|
||||
|
||||
Aabb2i Aabb2i::operator+(const Vector2i& rhs) const
|
||||
{
|
||||
return { m_min + rhs, m_max + rhs };
|
||||
Aabb2i returnValue = *this;
|
||||
returnValue += rhs;
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
Aabb2i& Aabb2i::operator+=(const Vector2i& rhs)
|
||||
{
|
||||
m_min += rhs;
|
||||
m_max += rhs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Aabb2i Aabb2i::operator-(const Vector2i& rhs) const
|
||||
{
|
||||
return *this + -rhs;
|
||||
Aabb2i returnValue = *this;
|
||||
returnValue -= rhs;
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
Aabb2i& Aabb2i::operator-=(const Vector2i& rhs)
|
||||
{
|
||||
m_min -= rhs;
|
||||
m_max -= rhs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Aabb2i::operator==(const Aabb2i& other) const
|
||||
{
|
||||
return m_min == other.m_min && m_max == other.m_max;
|
||||
}
|
||||
|
||||
bool Aabb2i::operator!=(const Aabb2i& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
Aabb2i Aabb2i::GetClamped(Aabb2i rhs) const
|
||||
|
||||
@@ -21,7 +21,11 @@ namespace Terrain
|
||||
Aabb2i(const Vector2i& min, const Vector2i& max);
|
||||
|
||||
Aabb2i operator+(const Vector2i& offset) const;
|
||||
Aabb2i& operator+=(const Vector2i& offset);
|
||||
Aabb2i operator-(const Vector2i& offset) const;
|
||||
Aabb2i& operator-=(const Vector2i& offset);
|
||||
bool operator==(const Aabb2i& other) const;
|
||||
bool operator!=(const Aabb2i& other) const;
|
||||
|
||||
Aabb2i GetClamped(Aabb2i rhs) const;
|
||||
bool IsValid() const;
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <TerrainRenderer/ClipmapBounds.h>
|
||||
#include <AzCore/Casting/numeric_cast.h>
|
||||
#include <AzCore/Math/MathUtils.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
bool ClipmapBoundsRegion::operator==(const ClipmapBoundsRegion& other) const
|
||||
{
|
||||
return m_localAabb == other.m_localAabb && m_worldAabb.IsClose(other.m_worldAabb);
|
||||
}
|
||||
|
||||
bool ClipmapBoundsRegion::operator!=(const ClipmapBoundsRegion& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
ClipmapBounds::ClipmapBounds(const ClipmapBoundsDescriptor& desc)
|
||||
: m_size(desc.m_size)
|
||||
, m_halfSize(desc.m_size >> 1)
|
||||
, m_clipmapUpdateMultiple(AZ::GetMax<uint32_t>(desc.m_clipmapUpdateMultiple, 1))
|
||||
, m_scale(desc.m_clipToWorldScale)
|
||||
, m_rcpScale(1.0f / desc.m_clipToWorldScale)
|
||||
{
|
||||
AZ_Error("ClipmapBounds", m_scale > 0.0f, "ClipmapBounds should have a scale that is greater than 0.0f.");
|
||||
m_scale = AZ::GetMax(m_scale, AZ::Constants::FloatEpsilon);
|
||||
|
||||
// recalculate m_center
|
||||
m_center = GetSnappedCenter(GetClipSpaceVector(desc.m_worldSpaceCenter));
|
||||
}
|
||||
|
||||
auto ClipmapBounds::UpdateCenter(const AZ::Vector2& newCenter, AZ::Aabb* untouchedRegion) -> ClipmapBoundsRegionList
|
||||
{
|
||||
return UpdateCenter(GetClipSpaceVector(newCenter), untouchedRegion);
|
||||
}
|
||||
|
||||
auto ClipmapBounds::UpdateCenter(const Vector2i& newCenter, AZ::Aabb* untouchedRegion) -> ClipmapBoundsRegionList
|
||||
{
|
||||
AZStd::vector<Aabb2i> updateRegions;
|
||||
|
||||
// If the new snapped center isn't the same as the old, then generate update regions in clipmap space
|
||||
Vector2i updatedCenter = GetSnappedCenter(newCenter);
|
||||
|
||||
int32_t xDiff = updatedCenter.m_x - m_center.m_x;
|
||||
int32_t updateWidth = AZStd::GetMin<uint32_t>(abs(xDiff), m_size);
|
||||
|
||||
/*
|
||||
Calculate the update regions. In the common case, there will be two update regions that form either
|
||||
an L or inverted L shape. To avoid double-counting the corner, it is always put in the vertical box:
|
||||
_
|
||||
| |
|
||||
| |____
|
||||
|_|____|
|
||||
|
||||
*/
|
||||
|
||||
// Calculate the vertical box
|
||||
if (updatedCenter.m_x != m_center.m_x)
|
||||
{
|
||||
updateRegions.push_back();
|
||||
Aabb2i& updateRegion = updateRegions.back();
|
||||
|
||||
if (updatedCenter.m_x < m_center.m_x)
|
||||
{
|
||||
updateRegion.m_min.m_x = updatedCenter.m_x - m_halfSize;
|
||||
updateRegion.m_max.m_x = updateRegion.m_min.m_x + updateWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
updateRegion.m_max.m_x = updatedCenter.m_x + m_halfSize;
|
||||
updateRegion.m_min.m_x = updateRegion.m_max.m_x - updateWidth;
|
||||
}
|
||||
updateRegion.m_min.m_y = updatedCenter.m_y - m_halfSize;
|
||||
updateRegion.m_max.m_y = updatedCenter.m_y + m_halfSize;
|
||||
}
|
||||
|
||||
// Calculate the horizontal box
|
||||
if (updatedCenter.m_y != m_center.m_y && updateWidth < m_size)
|
||||
{
|
||||
updateRegions.push_back();
|
||||
Aabb2i& updateRegion = updateRegions.back();
|
||||
|
||||
uint32_t updateHeight = AZStd::GetMin<uint32_t>(abs(updatedCenter.m_y - m_center.m_y), m_size);
|
||||
if (updatedCenter.m_y < m_center.m_y)
|
||||
{
|
||||
updateRegion.m_min.m_y = updatedCenter.m_y - m_halfSize;
|
||||
updateRegion.m_max.m_y = updateRegion.m_min.m_y + updateHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
updateRegion.m_max.m_y = updatedCenter.m_y + m_halfSize;
|
||||
updateRegion.m_min.m_y = updateRegion.m_max.m_y - updateHeight;
|
||||
}
|
||||
|
||||
// If there was a vertical box, then don't double-count the corner of the update.
|
||||
if (xDiff < 0)
|
||||
{
|
||||
updateRegion.m_min.m_x = updatedCenter.m_x - m_halfSize + updateWidth;
|
||||
updateRegion.m_max.m_x = updatedCenter.m_x + m_halfSize;
|
||||
}
|
||||
else if (xDiff > 0)
|
||||
{
|
||||
updateRegion.m_min.m_x = updatedCenter.m_x - m_halfSize;
|
||||
updateRegion.m_max.m_x = updatedCenter.m_x + m_halfSize - updateWidth;
|
||||
}
|
||||
}
|
||||
|
||||
if (untouchedRegion)
|
||||
{
|
||||
// Default to the entire area being untouched.
|
||||
AZ::Aabb worldBounds = GetWorldBounds();
|
||||
float maxX = worldBounds.GetMax().GetX();
|
||||
float minX = worldBounds.GetMin().GetX();
|
||||
float maxY = worldBounds.GetMax().GetY();
|
||||
float minY = worldBounds.GetMin().GetY();
|
||||
|
||||
if (updatedCenter.m_x < m_center.m_x)
|
||||
{
|
||||
maxX = (updatedCenter.m_x + m_halfSize) * m_rcpScale;
|
||||
}
|
||||
else if (updatedCenter.m_x > m_center.m_x)
|
||||
{
|
||||
minX = (updatedCenter.m_x - m_halfSize) * m_rcpScale;
|
||||
}
|
||||
if (updatedCenter.m_y < m_center.m_y)
|
||||
{
|
||||
maxY = (updatedCenter.m_y + m_halfSize) * m_rcpScale;
|
||||
}
|
||||
else if (updatedCenter.m_y > m_center.m_y)
|
||||
{
|
||||
minY = (updatedCenter.m_y - m_halfSize) * m_rcpScale;
|
||||
}
|
||||
|
||||
untouchedRegion->Set(AZ::Vector3(minX, minY, 0.0f), AZ::Vector3(maxX, maxY, 0.0f));
|
||||
}
|
||||
|
||||
m_center = updatedCenter;
|
||||
|
||||
m_modCenter.m_x = (m_size + (m_center.m_x % m_size)) % m_size;
|
||||
m_modCenter.m_y = (m_size + (m_center.m_y % m_size)) % m_size;
|
||||
|
||||
ClipmapBoundsRegionList boundsUpdate;
|
||||
for (Aabb2i& updateRegion : updateRegions)
|
||||
{
|
||||
ClipmapBoundsRegionList update = TransformRegion(updateRegion);
|
||||
boundsUpdate.insert(boundsUpdate.end(), update.begin(), update.end());
|
||||
}
|
||||
|
||||
return boundsUpdate;
|
||||
}
|
||||
|
||||
auto ClipmapBounds::TransformRegion(AZ::Aabb worldSpaceRegion) -> ClipmapBoundsRegionList
|
||||
{
|
||||
AZ::Vector2 worldMin = AZ::Vector2(worldSpaceRegion.GetMin().GetX(), worldSpaceRegion.GetMin().GetY());
|
||||
AZ::Vector2 worldMax = AZ::Vector2(worldSpaceRegion.GetMax().GetX(), worldSpaceRegion.GetMax().GetY());
|
||||
|
||||
Aabb2i clipSpaceRegion;
|
||||
clipSpaceRegion.m_min = GetClipSpaceVector(worldMin);
|
||||
clipSpaceRegion.m_max = GetClipSpaceVector(worldMax);
|
||||
|
||||
return TransformRegion(clipSpaceRegion);
|
||||
}
|
||||
|
||||
auto ClipmapBounds::TransformRegion(Aabb2i region) -> ClipmapBoundsRegionList
|
||||
{
|
||||
ClipmapBoundsRegionList transformedRegions;
|
||||
|
||||
Aabb2i clampedRegion = region.GetClamped(GetLocalBounds());
|
||||
if (!clampedRegion.IsValid())
|
||||
{
|
||||
// Early out if the region is outside the bounds
|
||||
return transformedRegions;
|
||||
}
|
||||
|
||||
Vector2i minCorner = m_center - m_halfSize;
|
||||
|
||||
Vector2i minBoundary;
|
||||
minBoundary.m_x = (minCorner.m_x / m_size - (minCorner.m_x < 0 ? 1 : 0)) * m_size;
|
||||
minBoundary.m_y = (minCorner.m_y / m_size - (minCorner.m_y < 0 ? 1 : 0)) * m_size;
|
||||
|
||||
Aabb2i bottomLeftTile = Aabb2i(minBoundary, minBoundary + m_size);
|
||||
|
||||
// For each of the 4 quadrants:
|
||||
auto calculateQuadrant = [&](Aabb2i tile)
|
||||
{
|
||||
Aabb2i regionClampedToTile = clampedRegion.GetClamped(tile);
|
||||
if (regionClampedToTile.IsValid())
|
||||
{
|
||||
transformedRegions.push_back(
|
||||
ClipmapBoundsRegion({
|
||||
GetWorldSpaceAabb(regionClampedToTile),
|
||||
regionClampedToTile - tile.m_min
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
calculateQuadrant(bottomLeftTile);
|
||||
calculateQuadrant(bottomLeftTile + Vector2i(m_size, 0));
|
||||
calculateQuadrant(bottomLeftTile + Vector2i(0, m_size));
|
||||
calculateQuadrant(bottomLeftTile + Vector2i(m_size, m_size));
|
||||
|
||||
return transformedRegions;
|
||||
}
|
||||
|
||||
AZ::Aabb ClipmapBounds::GetWorldBounds() const
|
||||
{
|
||||
Aabb2i localBounds = GetLocalBounds();
|
||||
|
||||
return AZ::Aabb::CreateFromMinMaxValues(
|
||||
localBounds.m_min.m_x * m_scale, localBounds.m_min.m_y * m_scale, 0.0f,
|
||||
localBounds.m_max.m_x * m_scale, localBounds.m_max.m_y * m_scale, 0.0f);
|
||||
}
|
||||
|
||||
float ClipmapBounds::GetWorldSpaceSafeDistance() const
|
||||
{
|
||||
return (m_halfSize - m_clipmapUpdateMultiple) * m_scale;
|
||||
}
|
||||
|
||||
Vector2i ClipmapBounds::GetSnappedCenter(const Vector2i& center)
|
||||
{
|
||||
Vector2i updatedCenter = m_center;
|
||||
|
||||
// Update the snapped center if the new center has drifted beyond the margin
|
||||
auto UpdateDim = [&](int32_t centerDim, int32_t& snappedCenterDim) -> void
|
||||
{
|
||||
int32_t diff = centerDim - snappedCenterDim;
|
||||
|
||||
int32_t scaledCenterDim = (centerDim / m_clipmapUpdateMultiple);
|
||||
if (centerDim < 0)
|
||||
{
|
||||
// Force rounding down for negatives
|
||||
scaledCenterDim--;
|
||||
}
|
||||
|
||||
if (diff >= m_clipmapUpdateMultiple)
|
||||
{
|
||||
snappedCenterDim = scaledCenterDim * m_clipmapUpdateMultiple;
|
||||
}
|
||||
if (diff < -m_clipmapUpdateMultiple)
|
||||
{
|
||||
snappedCenterDim = (scaledCenterDim + 1) * m_clipmapUpdateMultiple;
|
||||
}
|
||||
};
|
||||
UpdateDim(center.m_x, updatedCenter.m_x);
|
||||
UpdateDim(center.m_y, updatedCenter.m_y);
|
||||
|
||||
return updatedCenter;
|
||||
}
|
||||
|
||||
Aabb2i ClipmapBounds::GetLocalBounds() const
|
||||
{
|
||||
return Aabb2i(m_center - m_halfSize, m_center + m_halfSize);
|
||||
}
|
||||
|
||||
Vector2i ClipmapBounds::GetClipSpaceVector(const AZ::Vector2& worldSpaceVector) const
|
||||
{
|
||||
// Get rounded integer x/y coords in clipmap space.
|
||||
int32_t x = AZStd::lround(worldSpaceVector.GetX() * m_rcpScale);
|
||||
int32_t y = AZStd::lround(worldSpaceVector.GetY() * m_rcpScale);
|
||||
return Vector2i(x, y);
|
||||
}
|
||||
|
||||
AZ::Aabb ClipmapBounds::GetWorldSpaceAabb(const Aabb2i& clipSpaceAabb) const
|
||||
{
|
||||
return AZ::Aabb::CreateFromMinMaxValues(
|
||||
clipSpaceAabb.m_min.m_x * m_scale, clipSpaceAabb.m_min.m_y * m_scale, 0.0f,
|
||||
clipSpaceAabb.m_max.m_x * m_scale, clipSpaceAabb.m_max.m_y * m_scale, 0.0f
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/base.h>
|
||||
#include <AzCore/Math/Aabb.h>
|
||||
#include <AzCore/Math/Vector2.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <TerrainRenderer/Vector2i.h>
|
||||
#include <TerrainRenderer/Aabb2i.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
struct ClipmapBoundsDescriptor
|
||||
{
|
||||
//! Width and height of the clipmap in texels.
|
||||
uint32_t m_size = 1024;
|
||||
|
||||
//! Current center location of the clipmap in world space
|
||||
AZ::Vector2 m_worldSpaceCenter = AZ::Vector2::CreateZero();
|
||||
|
||||
// Updates to the clipmap will be produced in multiples of this value. This
|
||||
// allows for larger but less frequent updates, and gives some wiggle room
|
||||
// for each movement before an update is triggered.
|
||||
// Note: This also means that whatever uses this clipmap should only ever
|
||||
// display m_size - (2 * m_clipmapUpdateMultiple) pixels from the clipmap.
|
||||
// Use GetWorldSpaceSafeDistance() to get the safe distance from center.
|
||||
uint32_t m_clipmapUpdateMultiple = 4;
|
||||
|
||||
//! Scale of the clip map compared to the world. A scale of 0.5 means that
|
||||
//! a clipmap of size 1024 would cover 512 meters.
|
||||
float m_clipToWorldScale = 1.0f;
|
||||
};
|
||||
|
||||
struct ClipmapBoundsRegion
|
||||
{
|
||||
//! The world bounds of the updated region. Z is ignored.
|
||||
AZ::Aabb m_worldAabb;
|
||||
|
||||
//! The clipmaps bounds of the updated region. Will always be between 0 and size.
|
||||
//! Min inclusive, max exclusive.
|
||||
Aabb2i m_localAabb;
|
||||
|
||||
bool operator==(const ClipmapBoundsRegion& other) const;
|
||||
bool operator!=(const ClipmapBoundsRegion& other) const;
|
||||
};
|
||||
|
||||
// This class manages a single clipmap region. A clipmap is a virtual view into a much larger
|
||||
// region, where the clipmap view is centered around a point like the current camera position.
|
||||
// The clipmap texture wraps to form a repeating grid and never moves, but only data within
|
||||
// the clipmap bounds is actually valid. This makes looking up data in the clipmap trivial
|
||||
// since it's just the world coordinate scaled by some amount. This technique also allows for
|
||||
// only the edge areas of the clipmap to be updated as the center point moves around the world.
|
||||
//
|
||||
// The edges of the clipmap bounds will typically run through the texture, dividing it into 4
|
||||
// regions, except in cases where the clipmap bounds happen to be aligned with the underlying
|
||||
// grid. This means whenever some bounding box needs to be updated in the clipmap, it may actually
|
||||
// translate to 4 different areas of the underlying texture - one for each quadrant.
|
||||
//
|
||||
// This class aids in figuring out which areas of a clipmap need to be updated as its center point
|
||||
// moves around in the world, and can map a single region that needs to be updated into several
|
||||
// separate regions for each quadrant.
|
||||
|
||||
/*
|
||||
___________________________
|
||||
| | | | | Clipmap Clipmap
|
||||
| | | | | Bounds Texture (Tiled)
|
||||
|______|______|______|______| ______ ______
|
||||
| | _|____ | | | | | |____|_|
|
||||
| | | | | | | |_|_*__| | | |
|
||||
|______|____|_|_*__|_|______| |_|____| |_*__|_|
|
||||
| | |_|____| | |
|
||||
| | | | |
|
||||
|______|______|______|______|
|
||||
| | | | |
|
||||
| | | | |
|
||||
|______|______|______|______|
|
||||
|
||||
*/
|
||||
|
||||
class ClipmapBounds
|
||||
{
|
||||
public:
|
||||
|
||||
explicit ClipmapBounds(const ClipmapBoundsDescriptor& desc);
|
||||
~ClipmapBounds() = default;
|
||||
|
||||
using ClipmapBoundsRegionList = AZStd::vector<ClipmapBoundsRegion>;
|
||||
|
||||
//! Updates the clipmap bounds using a world coordinate center position and returns
|
||||
//! 0-2 regions that need to be updated due to moving beyond the margins. These update
|
||||
//! regions will always be at least the size of the margin, and will represent horizontal
|
||||
//! and/or vertical strips along the edges of the clipmap. An optional untouched region
|
||||
//! aabb can be passed to this function to get an aabb of areas inside the bounds of the
|
||||
//! clipmap but not updated by the center moving. This can be useful in cases where part
|
||||
//! of the bounds of the clipmap is dirty, but areas that will already be updated due
|
||||
//! to the center moving shouldn't be updated twice.
|
||||
ClipmapBoundsRegionList UpdateCenter(const AZ::Vector2& newCenter, AZ::Aabb* untouchedRegion = nullptr);
|
||||
|
||||
//! Updates the clipmap bounds using a position in clipmap space (no scaling) and returns
|
||||
//! 0-2 regions that need to be updated due to moving beyond the margins. These update
|
||||
//! regions will always be at least the size of the margin, and will represent horizontal
|
||||
//! and/or vertical strips along the edges of the clipmap. An optional untouched region
|
||||
//! aabb can be passed to this function to get an aabb of areas inside the bounds of the
|
||||
//! clipmap but not updated by the center moving. This can be useful in cases where part
|
||||
//! of the bounds of the clipmap is dirty, but areas that will already be updated due
|
||||
//! to the center moving shouldn't be updated twice.
|
||||
ClipmapBoundsRegionList UpdateCenter(const Vector2i& newCenter, AZ::Aabb* untouchedRegion = nullptr);
|
||||
|
||||
//! Takes in a single world space region and transforms it into 0-4 regions in the clipmap clamped
|
||||
//! to the bounds of the clipmap.
|
||||
ClipmapBoundsRegionList TransformRegion(AZ::Aabb worldSpaceRegion);
|
||||
|
||||
//! Takes in a single unscaled clipmap space region and transforms it into 0-4 regions in the clipmap clamped
|
||||
//! to the bounds of the clipmap.
|
||||
ClipmapBoundsRegionList TransformRegion(Aabb2i clipSpaceRegion);
|
||||
|
||||
//! Returns the bounds covered by this clipmap in world space. Z component is always 0.
|
||||
AZ::Aabb GetWorldBounds() const;
|
||||
|
||||
//! Returns the safe x and y distance from the center in world space. This is based on the scale,
|
||||
//! clipmap size, and m_clipmapUpdateMultiple. For example, a clipmap size 1024 with scale
|
||||
//! 0.25 and margin of 4 would have a safe distance of (1024 * 0.5 - 4) * 0.25 = 127.0f.
|
||||
float GetWorldSpaceSafeDistance() const;
|
||||
|
||||
private:
|
||||
|
||||
//! Returns the center point snapped to a multiple of m_clipmapUpdateMultiple. This isn't
|
||||
//! a simple rounding operation. The value returned will only be different from the curernt
|
||||
//! center if the value passed in is greater than m_clipmapUpdateMultiple away from the center.
|
||||
Vector2i GetSnappedCenter(const Vector2i& center);
|
||||
|
||||
//! Returns the bounds covered by the clipmap in local space
|
||||
Aabb2i GetLocalBounds() const;
|
||||
|
||||
//! Applies scale and averages a world space vector to get a clip space vector.
|
||||
Vector2i GetClipSpaceVector(const AZ::Vector2& worldSpaceVector) const;
|
||||
|
||||
//! Applies inverse scale to get a world aabb from clip space aabb.
|
||||
AZ::Aabb GetWorldSpaceAabb(const Aabb2i& clipSpaceAabb) const;
|
||||
|
||||
Vector2i m_center;
|
||||
Vector2i m_modCenter;
|
||||
int32_t m_size;
|
||||
int32_t m_halfSize;
|
||||
int32_t m_clipmapUpdateMultiple;
|
||||
float m_scale;
|
||||
float m_rcpScale;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -7,35 +7,90 @@
|
||||
*/
|
||||
|
||||
#include <TerrainRenderer/Vector2i.h>
|
||||
#include <AzCore/Casting/numeric_cast.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
auto Vector2i::operator+(const Vector2i& rhs) const -> Vector2i
|
||||
|
||||
Vector2i::Vector2i(int32_t x, int32_t y)
|
||||
: m_x(x)
|
||||
, m_y(y)
|
||||
{}
|
||||
|
||||
Vector2i::Vector2i(uint32_t value)
|
||||
: m_x(aznumeric_cast<int32_t>(value))
|
||||
, m_y(aznumeric_cast<int32_t>(value))
|
||||
{}
|
||||
|
||||
Vector2i::Vector2i(int32_t value)
|
||||
: m_x(value)
|
||||
, m_y(value)
|
||||
{}
|
||||
|
||||
Vector2i Vector2i::operator+(const Vector2i& rhs) const
|
||||
{
|
||||
Vector2i offsetPoint = *this;
|
||||
offsetPoint += rhs;
|
||||
return offsetPoint;
|
||||
Vector2i returnPoint = *this;
|
||||
returnPoint += rhs;
|
||||
return returnPoint;
|
||||
}
|
||||
|
||||
auto Vector2i::operator+=(const Vector2i& rhs) -> Vector2i&
|
||||
Vector2i& Vector2i::operator+=(const Vector2i& rhs)
|
||||
{
|
||||
m_x += rhs.m_x;
|
||||
m_y += rhs.m_y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto Vector2i::operator-(const Vector2i& rhs) const -> Vector2i
|
||||
Vector2i Vector2i::operator-(const Vector2i& rhs) const
|
||||
{
|
||||
return *this + -rhs;
|
||||
}
|
||||
|
||||
auto Vector2i::operator-=(const Vector2i& rhs) -> Vector2i&
|
||||
Vector2i& Vector2i::operator-=(const Vector2i& rhs)
|
||||
{
|
||||
return *this += -rhs;
|
||||
}
|
||||
|
||||
auto Vector2i::operator-() const -> Vector2i
|
||||
Vector2i Vector2i::operator-() const
|
||||
{
|
||||
return {-m_x, -m_y};
|
||||
}
|
||||
|
||||
Vector2i Vector2i::operator*(const Vector2i& rhs) const
|
||||
{
|
||||
Vector2i returnPoint = *this;
|
||||
returnPoint *= rhs;
|
||||
return returnPoint;
|
||||
}
|
||||
|
||||
Vector2i& Vector2i::operator*=(const Vector2i& rhs)
|
||||
{
|
||||
m_x *= rhs.m_x;
|
||||
m_y *= rhs.m_y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Vector2i Vector2i::operator/(const Vector2i& rhs) const
|
||||
{
|
||||
Vector2i returnPoint = *this;
|
||||
returnPoint /= rhs;
|
||||
return returnPoint;
|
||||
}
|
||||
|
||||
Vector2i& Vector2i::operator/=(const Vector2i& rhs)
|
||||
{
|
||||
m_x /= rhs.m_x;
|
||||
m_y /= rhs.m_y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Vector2i::operator==(const Vector2i& rhs) const
|
||||
{
|
||||
return rhs.m_x == m_x && rhs.m_y == m_y;
|
||||
}
|
||||
|
||||
bool Vector2i::operator!=(const Vector2i& rhs) const
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,14 +16,26 @@ namespace Terrain
|
||||
{
|
||||
public:
|
||||
|
||||
Vector2i() = default;
|
||||
Vector2i(int32_t x, int32_t y);
|
||||
Vector2i(uint32_t value);
|
||||
Vector2i(int32_t value);
|
||||
|
||||
Vector2i operator+(const Vector2i& rhs) const;
|
||||
Vector2i& operator+=(const Vector2i& rhs);
|
||||
Vector2i operator-(const Vector2i& rhs) const;
|
||||
Vector2i& operator-=(const Vector2i& rhs);
|
||||
Vector2i operator-() const;
|
||||
|
||||
Vector2i operator*(const Vector2i& rhs) const;
|
||||
Vector2i& operator*=(const Vector2i& rhs);
|
||||
Vector2i operator/(const Vector2i& rhs) const;
|
||||
Vector2i& operator/=(const Vector2i& rhs);
|
||||
|
||||
bool operator==(const Vector2i& rhs) const;
|
||||
bool operator!=(const Vector2i& rhs) const;
|
||||
|
||||
int32_t m_x{ 0 };
|
||||
int32_t m_y{ 0 };
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include <TerrainRenderer/ClipmapBounds.h>
|
||||
#include <TerrainRenderer/Aabb2i.h>
|
||||
|
||||
#include <AzCore/std/containers/span.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class ClipmapBoundsTests
|
||||
: public UnitTest::AllocatorsTestFixture
|
||||
{
|
||||
public:
|
||||
void CheckTransformRegionFullBounds(const Terrain::ClipmapBoundsDescriptor& desc);
|
||||
};
|
||||
|
||||
void ClipmapBoundsTests::CheckTransformRegionFullBounds(const Terrain::ClipmapBoundsDescriptor& desc)
|
||||
{
|
||||
Terrain::ClipmapBounds bounds(desc);
|
||||
|
||||
AZ::Aabb worldBounds = bounds.GetWorldBounds();
|
||||
float worldBoundsSize = worldBounds.GetXExtent();
|
||||
|
||||
auto output = bounds.TransformRegion(worldBounds);
|
||||
ASSERT_EQ(output.size(), 4);
|
||||
|
||||
AZ::Vector2 boundary = AZ::Vector2(
|
||||
floorf(worldBounds.GetMax().GetX() / worldBoundsSize),
|
||||
floorf(worldBounds.GetMax().GetY() / worldBoundsSize)
|
||||
) * worldBoundsSize;
|
||||
|
||||
Terrain::Vector2i localMax = {
|
||||
aznumeric_cast<int32_t>(AZStd::lround(desc.m_worldSpaceCenter.GetX() / desc.m_clipToWorldScale)),
|
||||
aznumeric_cast<int32_t>(AZStd::lround(desc.m_worldSpaceCenter.GetY() / desc.m_clipToWorldScale))
|
||||
};
|
||||
localMax += aznumeric_cast<int32_t>(desc.m_size / 2ul);
|
||||
|
||||
int32_t intSize = int32_t(desc.m_size);
|
||||
Terrain::Vector2i localBoundary = {
|
||||
((localMax.m_x % intSize) + intSize) % intSize,
|
||||
((localMax.m_y % intSize) + intSize) % intSize
|
||||
};
|
||||
|
||||
// Check each quadrant returned
|
||||
AZStd::vector<Terrain::ClipmapBoundsRegion> expected;
|
||||
expected.resize(4);
|
||||
|
||||
expected.at(0).m_localAabb = Terrain::Aabb2i({localBoundary.m_x, localBoundary.m_y}, {intSize, intSize});
|
||||
expected.at(0).m_worldAabb = AZ::Aabb::CreateFromMinMaxValues(
|
||||
worldBounds.GetMin().GetX(), worldBounds.GetMin().GetY(), 0.0f,
|
||||
boundary.GetX(), boundary.GetY(), 0.0f);
|
||||
|
||||
expected.at(1).m_localAabb = Terrain::Aabb2i({0, localBoundary.m_y}, {localBoundary.m_x, intSize});
|
||||
expected.at(1).m_worldAabb = AZ::Aabb::CreateFromMinMaxValues(
|
||||
boundary.GetX(), worldBounds.GetMin().GetY(), 0.0f,
|
||||
worldBounds.GetMax().GetX(), boundary.GetY(), 0.0f);
|
||||
|
||||
expected.at(2).m_localAabb = Terrain::Aabb2i({localBoundary.m_x, 0}, {intSize, localBoundary.m_y});
|
||||
expected.at(2).m_worldAabb = AZ::Aabb::CreateFromMinMaxValues(
|
||||
worldBounds.GetMin().GetX(), boundary.GetY(), 0.0f,
|
||||
boundary.GetX(), worldBounds.GetMax().GetY(), 0.0f);
|
||||
|
||||
expected.at(3).m_localAabb = Terrain::Aabb2i({ 0, 0 }, { localBoundary.m_x, localBoundary.m_y });
|
||||
expected.at(3).m_worldAabb = AZ::Aabb::CreateFromMinMaxValues(
|
||||
boundary.GetX(), boundary.GetY(), 0.0f,
|
||||
worldBounds.GetMax().GetX(), worldBounds.GetMax().GetY(), 0.0f);
|
||||
|
||||
EXPECT_THAT(output, ::testing::UnorderedElementsAreArray(expected));
|
||||
}
|
||||
|
||||
TEST_F(ClipmapBoundsTests, Construction)
|
||||
{
|
||||
Terrain::ClipmapBoundsDescriptor desc;
|
||||
Terrain::ClipmapBounds bounds(desc);
|
||||
}
|
||||
|
||||
TEST_F(ClipmapBoundsTests, BasicTransform)
|
||||
{
|
||||
// Create clipmap around 0.0, so it's perfectly divided into 4 quadrants
|
||||
Terrain::ClipmapBoundsDescriptor desc;
|
||||
desc.m_worldSpaceCenter = AZ::Vector2(0.0f, 0.0f);
|
||||
desc.m_clipmapUpdateMultiple = 0;
|
||||
desc.m_clipToWorldScale = 1.0f;
|
||||
desc.m_size = 1024;
|
||||
Terrain::ClipmapBounds bounds(desc);
|
||||
|
||||
auto output = bounds.TransformRegion(AZ::Aabb::CreateFromMinMaxValues(-512.0f, -512.0f, 0.0f, 512.0f, 512.0f, 0.0f));
|
||||
|
||||
ASSERT_EQ(output.size(), 4);
|
||||
|
||||
// Check each quadrant returned
|
||||
EXPECT_EQ(output.at(0).m_localAabb, Terrain::Aabb2i({512, 512}, {1024, 1024}));
|
||||
EXPECT_TRUE(output.at(0).m_worldAabb.IsClose(AZ::Aabb::CreateFromMinMaxValues(-512.0f, -512.0f, 0.0f, 0.0f, 0.0f, 0.0f)));
|
||||
EXPECT_EQ(output.at(1).m_localAabb, Terrain::Aabb2i({0, 512}, {512, 1024}));
|
||||
EXPECT_TRUE(output.at(1).m_worldAabb.IsClose(AZ::Aabb::CreateFromMinMaxValues(0.0f, -512.0f, 0.0f, 512.0f, 0.0f, 0.0f)));
|
||||
EXPECT_EQ(output.at(2).m_localAabb, Terrain::Aabb2i({512, 0}, {1024, 512}));
|
||||
EXPECT_TRUE(output.at(2).m_worldAabb.IsClose(AZ::Aabb::CreateFromMinMaxValues(-512.0f, 0.0f, 0.0f, 0.0f, 512.0f, 0.0f)));
|
||||
EXPECT_EQ(output.at(3).m_localAabb, Terrain::Aabb2i({0, 0}, {512, 512}));
|
||||
EXPECT_TRUE(output.at(3).m_worldAabb.IsClose(AZ::Aabb::CreateFromMinMaxValues(0.0f, 0.0f, 0.0f, 512.0f, 512.0f, 0.0f)));
|
||||
}
|
||||
|
||||
TEST_F(ClipmapBoundsTests, ScaledTransform)
|
||||
{
|
||||
// Create clipmap around 0.0, so it's perfectly divided into 4 quadrants, but half-scale
|
||||
Terrain::ClipmapBoundsDescriptor desc;
|
||||
desc.m_worldSpaceCenter = AZ::Vector2(0.0f, 0.0f);
|
||||
desc.m_clipmapUpdateMultiple = 0;
|
||||
desc.m_clipToWorldScale = 0.5f;
|
||||
desc.m_size = 1024;
|
||||
Terrain::ClipmapBounds bounds(desc);
|
||||
|
||||
auto output = bounds.TransformRegion(AZ::Aabb::CreateFromMinMaxValues(-256.0f, -256.0f, 0.0f, 256.0f, 256.0f, 0.0f));
|
||||
|
||||
ASSERT_EQ(output.size(), 4);
|
||||
|
||||
// Check each quadrant returned
|
||||
EXPECT_EQ(output.at(0).m_localAabb, Terrain::Aabb2i({512, 512}, {1024, 1024}));
|
||||
EXPECT_TRUE(output.at(0).m_worldAabb.IsClose(AZ::Aabb::CreateFromMinMaxValues(-256.0f, -256.0f, 0.0f, 0.0f, 0.0f, 0.0f)));
|
||||
EXPECT_EQ(output.at(1).m_localAabb, Terrain::Aabb2i({0, 512}, {512, 1024}));
|
||||
EXPECT_TRUE(output.at(1).m_worldAabb.IsClose(AZ::Aabb::CreateFromMinMaxValues(0.0f, -256.0f, 0.0f, 256.0f, 0.0f, 0.0f)));
|
||||
EXPECT_EQ(output.at(2).m_localAabb, Terrain::Aabb2i({512, 0}, {1024, 512}));
|
||||
EXPECT_TRUE(output.at(2).m_worldAabb.IsClose(AZ::Aabb::CreateFromMinMaxValues(-256.0f, 0.0f, 0.0f, 0.0f, 256.0f, 0.0f)));
|
||||
EXPECT_EQ(output.at(3).m_localAabb, Terrain::Aabb2i({0, 0}, {512, 512}));
|
||||
EXPECT_TRUE(output.at(3).m_worldAabb.IsClose(AZ::Aabb::CreateFromMinMaxValues(0.0f, 0.0f, 0.0f, 256.0f, 256.0f, 0.0f)));
|
||||
}
|
||||
|
||||
TEST_F(ClipmapBoundsTests, ComplexTransformsFullBounds)
|
||||
{
|
||||
// Check 4 different clipmaps - one in completely positive space, one in negative space, and two straddling the axis
|
||||
|
||||
// Clipmap in negative space
|
||||
{
|
||||
Terrain::ClipmapBoundsDescriptor desc;
|
||||
desc.m_worldSpaceCenter = AZ::Vector2(-1234.0f, -5432.0f);
|
||||
desc.m_clipmapUpdateMultiple = 0;
|
||||
desc.m_clipToWorldScale = 0.75f;
|
||||
desc.m_size = 512;
|
||||
CheckTransformRegionFullBounds(desc);
|
||||
}
|
||||
|
||||
// Clipmap in positive space
|
||||
{
|
||||
Terrain::ClipmapBoundsDescriptor desc;
|
||||
desc.m_worldSpaceCenter = AZ::Vector2(1234.0f, 5432.0f);
|
||||
desc.m_clipmapUpdateMultiple = 0;
|
||||
desc.m_clipToWorldScale = 1.25f;
|
||||
desc.m_size = 1024;
|
||||
CheckTransformRegionFullBounds(desc);
|
||||
}
|
||||
|
||||
// Clipmap on x axis
|
||||
{
|
||||
Terrain::ClipmapBoundsDescriptor desc;
|
||||
desc.m_worldSpaceCenter = AZ::Vector2(1234.0f, -100.0f);
|
||||
desc.m_clipmapUpdateMultiple = 0;
|
||||
desc.m_clipToWorldScale = 1.5f;
|
||||
desc.m_size = 256;
|
||||
CheckTransformRegionFullBounds(desc);
|
||||
}
|
||||
// Clipmap on y axis
|
||||
{
|
||||
Terrain::ClipmapBoundsDescriptor desc;
|
||||
desc.m_worldSpaceCenter = AZ::Vector2(-100.0f, 5432.0f);
|
||||
desc.m_clipmapUpdateMultiple = 0;
|
||||
desc.m_clipToWorldScale = 1.0f;
|
||||
desc.m_size = 2048;
|
||||
CheckTransformRegionFullBounds(desc);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ClipmapBoundsTests, TransformSmallBounds)
|
||||
{
|
||||
// Create clipmap around 0.0, so it's perfectly divided into 4 quadrants
|
||||
Terrain::ClipmapBoundsDescriptor desc;
|
||||
desc.m_worldSpaceCenter = AZ::Vector2(0.0f, 0.0f);
|
||||
desc.m_clipmapUpdateMultiple = 0;
|
||||
desc.m_clipToWorldScale = 1.0f;
|
||||
desc.m_size = 1024;
|
||||
Terrain::ClipmapBounds bounds(desc);
|
||||
|
||||
{
|
||||
// Single quadrant positive
|
||||
|
||||
AZ::Aabb smallArea = AZ::Aabb::CreateFromMinMaxValues(
|
||||
10.0f, 10.0f, 0.0f, 50.0f, 50.0f, 0.0f
|
||||
);
|
||||
|
||||
auto output = bounds.TransformRegion(smallArea);
|
||||
|
||||
ASSERT_EQ(output.size(), 1);
|
||||
EXPECT_EQ(output.at(0).m_localAabb, Terrain::Aabb2i({10, 10}, {50, 50}));
|
||||
EXPECT_TRUE(output.at(0).m_worldAabb.IsClose(AZ::Aabb::CreateFromMinMaxValues(10.0f, 10.0f, 0.0f, 50.0f, 50.0f, 0.0f)));
|
||||
}
|
||||
|
||||
{
|
||||
// Single quadrant negative
|
||||
|
||||
AZ::Aabb smallArea = AZ::Aabb::CreateFromMinMaxValues(
|
||||
-50.0f, -50.0f, 0.0f, -10.0f, -10.0f, 0.0f
|
||||
);
|
||||
|
||||
auto output = bounds.TransformRegion(smallArea);
|
||||
|
||||
ASSERT_EQ(output.size(), 1);
|
||||
EXPECT_EQ(output.at(0).m_localAabb, Terrain::Aabb2i({974, 974}, {1014, 1014}));
|
||||
EXPECT_TRUE(output.at(0).m_worldAabb.IsClose(AZ::Aabb::CreateFromMinMaxValues(-50.0f, -50.0f, 0.0f, -10.0f, -10.0f, 0.0f)));
|
||||
}
|
||||
|
||||
{
|
||||
// 2 quadrant positive
|
||||
|
||||
AZ::Aabb smallArea = AZ::Aabb::CreateFromMinMaxValues(
|
||||
10.0f, -10.0f, 0.0f, 50.0f, 50.0f, 0.0f
|
||||
);
|
||||
|
||||
auto output = bounds.TransformRegion(smallArea);
|
||||
|
||||
ASSERT_EQ(output.size(), 2);
|
||||
EXPECT_EQ(output.at(0).m_localAabb, Terrain::Aabb2i({10, 1014}, {50, 1024}));
|
||||
EXPECT_TRUE(output.at(0).m_worldAabb.IsClose(AZ::Aabb::CreateFromMinMaxValues(10.0f, -10.0f, 0.0f, 50.0f, 0.0f, 0.0f)));
|
||||
EXPECT_EQ(output.at(1).m_localAabb, Terrain::Aabb2i({10, 0}, {50, 50}));
|
||||
EXPECT_TRUE(output.at(1).m_worldAabb.IsClose(AZ::Aabb::CreateFromMinMaxValues(10.0f, 0.0f, 0.0f, 50.0f, 50.0f, 0.0f)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ClipmapBoundsTests, MarginReducesUpdates)
|
||||
{
|
||||
// With a margin defined, the bounds should only trigger updates when the camera moves outside the margins
|
||||
|
||||
// Create clipmap around 0.0, so it's perfectly divided into 4 quadrants
|
||||
Terrain::ClipmapBoundsDescriptor desc;
|
||||
desc.m_worldSpaceCenter = AZ::Vector2(0.0f, 0.0f);
|
||||
desc.m_clipmapUpdateMultiple = 16;
|
||||
desc.m_clipToWorldScale = 1.0f;
|
||||
desc.m_size = 1024;
|
||||
Terrain::ClipmapBounds bounds(desc);
|
||||
|
||||
// center moved forward to 10, still within margin
|
||||
auto output1 = bounds.UpdateCenter(AZ::Vector2(10.0f, 10.0f));
|
||||
EXPECT_EQ(output1.size(), 0);
|
||||
// center moved forwrd to 20, beyond margin, triggers update
|
||||
auto output2 = bounds.UpdateCenter(AZ::Vector2(20.0f, 20.0f));
|
||||
EXPECT_GT(output2.size(), 0);
|
||||
// center moved back to 10, still within margin
|
||||
auto output3 = bounds.UpdateCenter(AZ::Vector2(10.0f, 10.0f));
|
||||
EXPECT_EQ(output3.size(), 0);
|
||||
// center moved back to 0, still within margin (on edge)
|
||||
auto output4 = bounds.UpdateCenter(AZ::Vector2(0.0f, 0.0f));
|
||||
EXPECT_EQ(output4.size(), 0);
|
||||
// center moved back to -10, beyond margin, triggers update
|
||||
auto output5 = bounds.UpdateCenter(AZ::Vector2(-10.0f, -10.0f));
|
||||
EXPECT_GT(output5.size(), 0);
|
||||
}
|
||||
|
||||
TEST_F(ClipmapBoundsTests, CenterMovementUpdates)
|
||||
{
|
||||
// Create clipmap around 0.0, so it's perfectly divided into 4 quadrants
|
||||
Terrain::ClipmapBoundsDescriptor desc;
|
||||
desc.m_worldSpaceCenter = AZ::Vector2(0.0f, 0.0f);
|
||||
desc.m_clipmapUpdateMultiple = 16;
|
||||
desc.m_clipToWorldScale = 1.0f;
|
||||
desc.m_size = 1024;
|
||||
Terrain::ClipmapBounds bounds(desc);
|
||||
|
||||
{
|
||||
AZ::Aabb untouchedRegion = AZ::Aabb::CreateNull();
|
||||
auto output = bounds.UpdateCenter(AZ::Vector2(20.0f, 20.0f), &untouchedRegion);
|
||||
ASSERT_EQ(output.size(), 4);
|
||||
|
||||
// Instead of checking bounds directly, do several checks to make sure the bounds are appropriate. Since
|
||||
// the center moved just outside the margin along the diagonal, we should expect two edges to be updated
|
||||
// that are the width of the margin.
|
||||
|
||||
// 1. The number of pixels updated in the bounds should be two sides of margin width
|
||||
float pixelsCovered = 0;
|
||||
for (auto& region : output)
|
||||
{
|
||||
// Note: GetSurfaceArea() returns the area of all 6 sides of the aabb. With a Z extent of 0, that
|
||||
// means that only the top and bottom will be counted, so we need to multiply by 0.5.
|
||||
pixelsCovered += region.m_worldAabb.GetSurfaceArea() * 0.5f;
|
||||
}
|
||||
|
||||
// Two edges of margin * size, minus the overlap in the corner.
|
||||
const uint32_t updateMultiple = desc.m_clipmapUpdateMultiple;
|
||||
float expectedCoverage = updateMultiple * desc.m_size * 2.0f - updateMultiple * updateMultiple;
|
||||
EXPECT_NEAR(pixelsCovered, expectedCoverage, 0.0001f);
|
||||
|
||||
// 2. The untouched region area should match what's expected
|
||||
float untouchedRegionArea = untouchedRegion.GetSurfaceArea() * 0.5f;
|
||||
float expectedUntouchedRegionSide = aznumeric_cast<float>(desc.m_size - desc.m_clipmapUpdateMultiple);
|
||||
float expectedUntouchedRegionArea = expectedUntouchedRegionSide * expectedUntouchedRegionSide;
|
||||
EXPECT_NEAR(untouchedRegionArea, expectedUntouchedRegionArea, 0.0001f);
|
||||
|
||||
// 3. All of the update regions should be inside the world bounds of the clipmap
|
||||
AZ::Aabb worldBounds = bounds.GetWorldBounds();
|
||||
for (auto& region : output)
|
||||
{
|
||||
EXPECT_EQ(region.m_worldAabb.GetClamped(worldBounds), region.m_worldAabb);
|
||||
}
|
||||
|
||||
// 4. The untouched region should also be inside the world bounds of the clipmap;
|
||||
EXPECT_EQ(untouchedRegion.GetClamped(worldBounds), untouchedRegion);
|
||||
|
||||
// 5. None of the update regions should overlap each other or the untouched region
|
||||
|
||||
// push the untouched region on the vector to make comparisons easier
|
||||
output.push_back(Terrain::ClipmapBoundsRegion({untouchedRegion, Terrain::Aabb2i({}) }));
|
||||
for (uint32_t i = 0; i < output.size(); ++i)
|
||||
{
|
||||
const AZ::Aabb boundsToCheck = output.at(i).m_worldAabb;
|
||||
for (uint32_t j = i + 1; j < output.size(); ++j)
|
||||
{
|
||||
// AZ::Aabb::Overlaps() counts touching edges as overlapping, so we need a strict version
|
||||
auto strictOverlaps = [](const AZ::Aabb& aabb1, const AZ::Aabb& aabb2) -> bool
|
||||
{
|
||||
return aabb1.GetMin().IsLessThan(aabb2.GetMax()) &&
|
||||
aabb1.GetMax().IsGreaterThan(aabb2.GetMin());
|
||||
};
|
||||
EXPECT_FALSE(strictOverlaps(boundsToCheck, output.at(j).m_worldAabb));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,10 @@ set(FILES
|
||||
Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.h
|
||||
Source/TerrainRenderer/Aabb2i.cpp
|
||||
Source/TerrainRenderer/Aabb2i.h
|
||||
Source/TerrainRenderer/BindlessImageArrayHandler.cpp
|
||||
Source/TerrainRenderer/BindlessImageArrayHandler.h
|
||||
Source/TerrainRenderer/ClipmapBounds.cpp
|
||||
Source/TerrainRenderer/ClipmapBounds.h
|
||||
Source/TerrainRenderer/TerrainFeatureProcessor.cpp
|
||||
Source/TerrainRenderer/TerrainFeatureProcessor.h
|
||||
Source/TerrainRenderer/TerrainDetailMaterialManager.cpp
|
||||
@@ -43,8 +47,6 @@ set(FILES
|
||||
Source/TerrainRenderer/TerrainMacroMaterialManager.h
|
||||
Source/TerrainRenderer/TerrainMeshManager.cpp
|
||||
Source/TerrainRenderer/TerrainMeshManager.h
|
||||
Source/TerrainRenderer/BindlessImageArrayHandler.cpp
|
||||
Source/TerrainRenderer/BindlessImageArrayHandler.h
|
||||
Source/TerrainRenderer/TerrainAreaMaterialRequestBus.h
|
||||
Source/TerrainRenderer/TerrainMacroMaterialBus.cpp
|
||||
Source/TerrainRenderer/TerrainMacroMaterialBus.h
|
||||
|
||||
@@ -7,14 +7,15 @@
|
||||
#
|
||||
|
||||
set(FILES
|
||||
Tests/TerrainTest.cpp
|
||||
Tests/TerrainSystemTest.cpp
|
||||
Tests/ClipmapBoundsTests.cpp
|
||||
Tests/LayerSpawnerTests.cpp
|
||||
Tests/TerrainPhysicsColliderTests.cpp
|
||||
Tests/SurfaceMaterialsListTest.cpp
|
||||
Tests/MockAxisAlignedBoxShapeComponent.h
|
||||
Tests/TerrainHeightGradientListTests.cpp
|
||||
Tests/TerrainMacroMaterialTests.cpp
|
||||
Tests/SurfaceMaterialsListTest.cpp
|
||||
Tests/TerrainPhysicsColliderTests.cpp
|
||||
Tests/TerrainSurfaceGradientListTests.cpp
|
||||
Tests/TerrainSystemBenchmarks.cpp
|
||||
Tests/TerrainSystemTest.cpp
|
||||
Tests/TerrainTest.cpp
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user