f34d3a822e
* Add a ray cast API to the terrain system, implement it, and use it so entities can be placed on top of terrain. Still to do: - Unit tests - Profiling/benchmarks - Optimization (if needed, may not be now after updating to use the iterative approach, but one option could be to use SIMD to ray cast against both triangles at once) Signed-off-by: bosnichd <bosnichd@amazon.com> * Fix typos. Signed-off-by: bosnichd <bosnichd@amazon.com> * Added a FindNearestIntersectionIterative to use in place of the first-pass FindnearestIntersectionRecursive attempt. Signed-off-by: bosnichd <bosnichd@amazon.com> * Remove some functions that are no longer being used. Signed-off-by: bosnichd <bosnichd@amazon.com> * Remove a unicode character from a comment to fix the validation build. Signed-off-by: bosnichd <bosnichd@amazon.com> * Remove another unicode character from a comment to fix the validation build. Signed-off-by: bosnichd <bosnichd@amazon.com> * Update to bail out as soon as t > 1.0f Signed-off-by: bosnichd <bosnichd@amazon.com>
241 lines
13 KiB
C++
241 lines
13 KiB
C++
/*
|
|
* 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/std/containers/fixed_vector.h>
|
|
#include <AzCore/Math/Vector3.h>
|
|
#include <AzCore/std/algorithm.h>
|
|
#include <AzCore/std/smart_ptr/make_shared.h>
|
|
#include <AzCore/std/parallel/shared_mutex.h>
|
|
#include <AzCore/std/containers/map.h>
|
|
#include <AzCore/std/containers/span.h>
|
|
#include <AzCore/Math/Color.h>
|
|
#include <AzCore/Math/Aabb.h>
|
|
|
|
#include <AzCore/Component/TickBus.h>
|
|
#include <AzCore/Jobs/JobManagerBus.h>
|
|
#include <AzCore/Jobs/JobFunction.h>
|
|
|
|
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
|
|
#include <TerrainRaycast/TerrainRaycastContext.h>
|
|
#include <TerrainSystem/TerrainSystemBus.h>
|
|
|
|
namespace Terrain
|
|
{
|
|
struct TerrainLayerPriorityComparator
|
|
{
|
|
bool operator()(const AZ::EntityId& layer1id, const AZ::EntityId& layer2id) const;
|
|
};
|
|
|
|
class TerrainSystem
|
|
: public AzFramework::Terrain::TerrainDataRequestBus::Handler
|
|
, private Terrain::TerrainSystemServiceRequestBus::Handler
|
|
, private AZ::TickBus::Handler
|
|
{
|
|
public:
|
|
TerrainSystem();
|
|
~TerrainSystem();
|
|
|
|
///////////////////////////////////////////
|
|
// TerrainSystemServiceRequestBus::Handler Impl
|
|
void Activate() override;
|
|
void Deactivate() override;
|
|
|
|
void RegisterArea(AZ::EntityId areaId) override;
|
|
void UnregisterArea(AZ::EntityId areaId) override;
|
|
void RefreshArea(
|
|
AZ::EntityId areaId, AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask changeMask) override;
|
|
|
|
///////////////////////////////////////////
|
|
// TerrainDataRequestBus::Handler Impl
|
|
AZ::Vector2 GetTerrainHeightQueryResolution() const override;
|
|
void SetTerrainHeightQueryResolution(AZ::Vector2 queryResolution) override;
|
|
|
|
AZ::Aabb GetTerrainAabb() const override;
|
|
void SetTerrainAabb(const AZ::Aabb& worldBounds) override;
|
|
|
|
|
|
//! Returns terrains height in meters at location x,y.
|
|
//! @terrainExistsPtr: Can be nullptr. If != nullptr then, if there's no terrain at location x,y or location x,y is inside a terrain
|
|
//! HOLE then *terrainExistsPtr will become false,
|
|
//! otherwise *terrainExistsPtr will become true.
|
|
float GetHeight(const AZ::Vector3& position, Sampler sampler = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const override;
|
|
float GetHeightFromVector2(const AZ::Vector2& position, Sampler sampler = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const override;
|
|
float GetHeightFromFloats(float x, float y, Sampler sampler = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const override;
|
|
|
|
//! Given an XY coordinate, return the max surface type and weight.
|
|
//! @terrainExists: Can be nullptr. If != nullptr then, if there's no terrain at location x,y or location x,y is inside a terrain
|
|
//! HOLE then *terrainExistsPtr will be set to false,
|
|
//! otherwise *terrainExistsPtr will be set to true.
|
|
AzFramework::SurfaceData::SurfaceTagWeight GetMaxSurfaceWeight(
|
|
const AZ::Vector3& position, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const override;
|
|
AzFramework::SurfaceData::SurfaceTagWeight GetMaxSurfaceWeightFromVector2(
|
|
const AZ::Vector2& inPosition, Sampler sampleFilter = Sampler::DEFAULT, bool* terrainExistsPtr = nullptr) const override;
|
|
AzFramework::SurfaceData::SurfaceTagWeight GetMaxSurfaceWeightFromFloats(
|
|
const float x, const float y, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const override;
|
|
|
|
void GetSurfaceWeights(
|
|
const AZ::Vector3& inPosition,
|
|
AzFramework::SurfaceData::SurfaceTagWeightList& outSurfaceWeights,
|
|
Sampler sampleFilter = Sampler::DEFAULT,
|
|
bool* terrainExistsPtr = nullptr) const override;
|
|
void GetSurfaceWeightsFromVector2(
|
|
const AZ::Vector2& inPosition,
|
|
AzFramework::SurfaceData::SurfaceTagWeightList& outSurfaceWeights,
|
|
Sampler sampleFilter = Sampler::DEFAULT,
|
|
bool* terrainExistsPtr = nullptr) const override;
|
|
void GetSurfaceWeightsFromFloats(
|
|
float x,
|
|
float y,
|
|
AzFramework::SurfaceData::SurfaceTagWeightList& outSurfaceWeights,
|
|
Sampler sampleFilter = Sampler::DEFAULT,
|
|
bool* terrainExistsPtr = nullptr) const override;
|
|
|
|
//! Convenience function for low level systems that can't do a reverse lookup from Crc to string. Everyone else should use
|
|
//! GetMaxSurfaceWeight or GetMaxSurfaceWeightFromFloats. Not available in the behavior context. Returns nullptr if the position is
|
|
//! inside a hole or outside of the terrain boundaries.
|
|
const char* GetMaxSurfaceName(
|
|
const AZ::Vector3& position, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const override;
|
|
|
|
//! Returns true if there's a hole at location x,y.
|
|
//! Also returns true if there's no terrain data at location x,y.
|
|
bool GetIsHole(const AZ::Vector3& position, Sampler sampleFilter = Sampler::BILINEAR) const override;
|
|
bool GetIsHoleFromVector2(const AZ::Vector2& position, Sampler sampleFilter = Sampler::BILINEAR) const override;
|
|
bool GetIsHoleFromFloats(float x, float y, Sampler sampleFilter = Sampler::BILINEAR) const override;
|
|
|
|
// Given an XY coordinate, return the surface normal.
|
|
//! @terrainExists: Can be nullptr. If != nullptr then, if there's no terrain at location x,y or location x,y is inside a terrain
|
|
//! HOLE then *terrainExistsPtr will be set to false,
|
|
//! otherwise *terrainExistsPtr will be set to true.
|
|
AZ::Vector3 GetNormal(
|
|
const AZ::Vector3& position, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const override;
|
|
AZ::Vector3 GetNormalFromVector2(
|
|
const AZ::Vector2& position, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const override;
|
|
AZ::Vector3 GetNormalFromFloats(
|
|
float x, float y, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const override;
|
|
|
|
void GetSurfacePoint(
|
|
const AZ::Vector3& inPosition,
|
|
AzFramework::SurfaceData::SurfacePoint& outSurfacePoint,
|
|
Sampler sampleFilter = Sampler::DEFAULT,
|
|
bool* terrainExistsPtr = nullptr) const override;
|
|
void GetSurfacePointFromVector2(
|
|
const AZ::Vector2& inPosition,
|
|
AzFramework::SurfaceData::SurfacePoint& outSurfacePoint,
|
|
Sampler sampleFilter = Sampler::DEFAULT,
|
|
bool* terrainExistsPtr = nullptr) const override;
|
|
void GetSurfacePointFromFloats(
|
|
float x,
|
|
float y,
|
|
AzFramework::SurfaceData::SurfacePoint& outSurfacePoint,
|
|
Sampler sampleFilter = Sampler::DEFAULT,
|
|
bool* terrainExistsPtr = nullptr) const override;
|
|
|
|
//! Given a list of XY coordinates, call the provided callback function with surface data corresponding to each
|
|
//! XY coordinate in the list.
|
|
virtual void ProcessHeightsFromList(const AZStd::span<AZ::Vector3>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter = Sampler::DEFAULT) const override;
|
|
virtual void ProcessNormalsFromList(const AZStd::span<AZ::Vector3>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter = Sampler::DEFAULT) const override;
|
|
virtual void ProcessSurfaceWeightsFromList(const AZStd::span<AZ::Vector3>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter = Sampler::DEFAULT) const override;
|
|
virtual void ProcessSurfacePointsFromList(const AZStd::span<AZ::Vector3>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter = Sampler::DEFAULT) const override;
|
|
virtual void ProcessHeightsFromListOfVector2(const AZStd::span<AZ::Vector2>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter = Sampler::DEFAULT) const override;
|
|
virtual void ProcessNormalsFromListOfVector2(const AZStd::span<AZ::Vector2>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter = Sampler::DEFAULT) const override;
|
|
virtual void ProcessSurfaceWeightsFromListOfVector2(const AZStd::span<AZ::Vector2>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter = Sampler::DEFAULT) const override;
|
|
virtual void ProcessSurfacePointsFromListOfVector2(const AZStd::span<AZ::Vector2>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter = Sampler::DEFAULT) const override;
|
|
|
|
//! Returns the number of samples for a given region and step size. The first and second
|
|
//! elements of the pair correspond to the X and Y sample counts respectively.
|
|
virtual AZStd::pair<size_t, size_t> GetNumSamplesFromRegion(const AZ::Aabb& inRegion,
|
|
const AZ::Vector2& stepSize) const override;
|
|
|
|
//! Given a region(aabb) and a step size, call the provided callback function with surface data corresponding to the
|
|
//! coordinates in the region.
|
|
virtual void ProcessHeightsFromRegion(const AZ::Aabb& inRegion,
|
|
const AZ::Vector2& stepSize,
|
|
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
|
Sampler sampleFilter = Sampler::DEFAULT) const override;
|
|
virtual void ProcessNormalsFromRegion(const AZ::Aabb& inRegion,
|
|
const AZ::Vector2& stepSize,
|
|
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
|
Sampler sampleFilter = Sampler::DEFAULT) const override;
|
|
virtual void ProcessSurfaceWeightsFromRegion(const AZ::Aabb& inRegion,
|
|
const AZ::Vector2& stepSize,
|
|
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
|
Sampler sampleFilter = Sampler::DEFAULT) const override;
|
|
virtual void ProcessSurfacePointsFromRegion(const AZ::Aabb& inRegion,
|
|
const AZ::Vector2& stepSize,
|
|
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
|
Sampler sampleFilter = Sampler::DEFAULT) const override;
|
|
|
|
AzFramework::EntityContextId GetTerrainRaycastEntityContextId() const override;
|
|
AzFramework::RenderGeometry::RayResult GetClosestIntersection(
|
|
const AzFramework::RenderGeometry::RayRequest& ray) const override;
|
|
|
|
private:
|
|
void ClampPosition(float x, float y, AZ::Vector2& outPosition, AZ::Vector2& normalizedDelta) const;
|
|
bool InWorldBounds(float x, float y) const;
|
|
|
|
AZ::EntityId FindBestAreaEntityAtPosition(float x, float y, AZ::Aabb& bounds) const;
|
|
void GetOrderedSurfaceWeights(
|
|
const float x,
|
|
const float y,
|
|
Sampler sampler,
|
|
AzFramework::SurfaceData::SurfaceTagWeightList& outSurfaceWeights,
|
|
bool* terrainExistsPtr) const;
|
|
float GetHeightSynchronous(float x, float y, Sampler sampler, bool* terrainExistsPtr) const;
|
|
float GetTerrainAreaHeight(float x, float y, bool& terrainExists) const;
|
|
AZ::Vector3 GetNormalSynchronous(float x, float y, Sampler sampler, bool* terrainExistsPtr) const;
|
|
|
|
// AZ::TickBus::Handler overrides ...
|
|
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
|
|
|
|
struct TerrainSystemSettings
|
|
{
|
|
AZ::Aabb m_worldBounds;
|
|
AZ::Vector2 m_heightQueryResolution{ 1.0f };
|
|
bool m_systemActive{ false };
|
|
};
|
|
|
|
TerrainSystemSettings m_currentSettings;
|
|
TerrainSystemSettings m_requestedSettings;
|
|
|
|
bool m_terrainSettingsDirty = true;
|
|
bool m_terrainHeightDirty = false;
|
|
bool m_terrainSurfacesDirty = false;
|
|
AZ::Aabb m_dirtyRegion;
|
|
|
|
// Cached data for each terrain area to use when looking up terrain data.
|
|
struct TerrainAreaData
|
|
{
|
|
AZ::Aabb m_areaBounds{ AZ::Aabb::CreateNull() };
|
|
bool m_useGroundPlane{ false };
|
|
};
|
|
|
|
mutable AZStd::shared_mutex m_areaMutex;
|
|
AZStd::map<AZ::EntityId, TerrainAreaData, TerrainLayerPriorityComparator> m_registeredAreas;
|
|
|
|
mutable TerrainRaycastContext m_terrainRaycastContext;
|
|
};
|
|
} // namespace Terrain
|