/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 float GetTerrainHeightQueryResolution() const override; void SetTerrainHeightQueryResolution(float 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& inPositions, AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback, Sampler sampleFilter = Sampler::DEFAULT) const override; virtual void ProcessNormalsFromList(const AZStd::span& inPositions, AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback, Sampler sampleFilter = Sampler::DEFAULT) const override; virtual void ProcessSurfaceWeightsFromList(const AZStd::span& inPositions, AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback, Sampler sampleFilter = Sampler::DEFAULT) const override; virtual void ProcessSurfacePointsFromList(const AZStd::span& inPositions, AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback, Sampler sampleFilter = Sampler::DEFAULT) const override; virtual void ProcessHeightsFromListOfVector2(const AZStd::span& inPositions, AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback, Sampler sampleFilter = Sampler::DEFAULT) const override; virtual void ProcessNormalsFromListOfVector2(const AZStd::span& inPositions, AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback, Sampler sampleFilter = Sampler::DEFAULT) const override; virtual void ProcessSurfaceWeightsFromListOfVector2(const AZStd::span& inPositions, AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback, Sampler sampleFilter = Sampler::DEFAULT) const override; virtual void ProcessSurfacePointsFromListOfVector2(const AZStd::span& 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 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; typedef AZStd::function inPositions, AZStd::span outPositions, AZStd::span outTerrainExists, AZStd::span outSurfaceWeights, AZ::EntityId areaId)> BulkQueriesCallback; void GetHeightsSynchronous( const AZStd::span& inPositions, Sampler sampler, AZStd::span heights, AZStd::span terrainExists) const; void GetNormalsSynchronous( const AZStd::span& inPositions, Sampler sampler, AZStd::span normals, AZStd::span terrainExists) const; void GetOrderedSurfaceWeightsFromList( const AZStd::span& inPositions, Sampler sampler, AZStd::span outSurfaceWeightsList, AZStd::span terrainExists) const; void MakeBulkQueries( const AZStd::span inPositions, AZStd::span outPositions, AZStd::span outTerrainExists, AZStd::span outSurfaceWieghts, BulkQueriesCallback queryCallback) const; void GenerateQueryPositions(const AZStd::span& inPositions, AZStd::vector& outPositions, Sampler sampler) const; AZStd::vector GenerateInputPositionsFromRegion( const AZ::Aabb& inRegion, const AZ::Vector2& stepSize) const; // AZ::TickBus::Handler overrides ... void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; struct TerrainSystemSettings { AZ::Aabb m_worldBounds; float 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 m_registeredAreas; mutable TerrainRaycastContext m_terrainRaycastContext; }; } // namespace Terrain