Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
+95
View File
@@ -0,0 +1,95 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
ly_add_target(
NAME SurfaceData.Static STATIC
NAMESPACE Gem
FILES_CMAKE
surfacedata_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Source
PUBLIC
Include
BUILD_DEPENDENCIES
PRIVATE
Legacy::CryCommon
PUBLIC
Gem::LmbrCentral
)
ly_add_target(
NAME SurfaceData ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
NAMESPACE Gem
OUTPUT_NAME Gem.SurfaceData.5de82d29d6094bfe97c1a4d35fcd5fbe.v0.1.0
FILES_CMAKE
surfacedata_shared_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Source
PUBLIC
Include
BUILD_DEPENDENCIES
PRIVATE
Legacy::CryCommon
Gem::SurfaceData.Static
)
if (PAL_TRAIT_BUILD_HOST_TOOLS)
ly_add_target(
NAME SurfaceData.Editor MODULE
NAMESPACE Gem
OUTPUT_NAME Gem.SurfaceData.Editor.5de82d29d6094bfe97c1a4d35fcd5fbe.v0.1.0
FILES_CMAKE
surfacedata_editor_files.cmake
COMPILE_DEFINITIONS
PRIVATE
SURFACEDATA_EDITOR
INCLUDE_DIRECTORIES
PRIVATE
Source
PUBLIC
Include
BUILD_DEPENDENCIES
PRIVATE
Legacy::CryCommon
AZ::AzToolsFramework
Gem::SurfaceData.Static
)
endif()
################################################################################
# Tests
################################################################################
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_target(
NAME SurfaceData.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE Gem
FILES_CMAKE
surfacedata_tests_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
.
Tests
Source
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
Legacy::CryCommon
Gem::SurfaceData.Static
)
ly_add_googletest(
NAME Gem::SurfaceData.Tests
)
endif()
@@ -0,0 +1,36 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Math/Crc.h>
namespace SurfaceData
{
namespace Constants
{
static const char* s_unassignedTagName = "(unassigned)";
static const char* s_terrainHoleTagName = "terrainHole";
static const char* s_terrainTagName = "terrain";
static const AZ::Crc32 s_unassignedTagCrc = AZ::Crc32(s_unassignedTagName);
static const AZ::Crc32 s_terrainHoleTagCrc = AZ::Crc32(s_terrainHoleTagName);
static const AZ::Crc32 s_terrainTagCrc = AZ::Crc32(s_terrainTagName);
static const char* s_allTagNames[] =
{
s_unassignedTagName,
s_terrainHoleTagName,
s_terrainTagName,
};
}
}
@@ -0,0 +1,42 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/Math/Aabb.h>
#include <SurfaceData/SurfaceDataTypes.h>
namespace SurfaceData
{
/**
* the EBus is used to request information about a surface
*/
class SurfaceDataModifierRequests
: public AZ::EBusTraits
{
public:
////////////////////////////////////////////////////////////////////////
// EBusTraits
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
typedef AZ::u32 BusIdType;
////////////////////////////////////////////////////////////////////////
//! allows multiple threads to call
using MutexType = AZStd::recursive_mutex;
virtual void ModifySurfacePoints(SurfacePointList& surfacePointList) const = 0;
};
typedef AZ::EBus<SurfaceDataModifierRequests> SurfaceDataModifierRequestBus;
}
@@ -0,0 +1,41 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <SurfaceData/SurfaceDataTypes.h>
namespace SurfaceData
{
/**
* the EBus is used to request information about a surface
*/
class SurfaceDataProviderRequests
: public AZ::EBusTraits
{
public:
////////////////////////////////////////////////////////////////////////
// EBusTraits
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
typedef AZ::u32 BusIdType;
////////////////////////////////////////////////////////////////////////
//! allows multiple threads to call
using MutexType = AZStd::recursive_mutex;
virtual void GetSurfacePoints(const AZ::Vector3& inPosition, SurfacePointList& surfacePointList) const = 0;
};
typedef AZ::EBus<SurfaceDataProviderRequests> SurfaceDataProviderRequestBus;
}
@@ -0,0 +1,45 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/Component/Entity.h>
namespace AZ
{
class Aabb;
}
namespace SurfaceData
{
/**
* the EBus is used to send notification information about surfaces
*/
class SurfaceDataSystemNotifications
: public AZ::EBusTraits
{
public:
////////////////////////////////////////////////////////////////////////
// EBusTraits
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
////////////////////////////////////////////////////////////////////////
//! allows multiple threads to call
using MutexType = AZStd::recursive_mutex;
virtual void OnSurfaceChanged(const AZ::EntityId& entityId, const AZ::Aabb& oldBounds, const AZ::Aabb& newBounds) = 0;
};
typedef AZ::EBus<SurfaceDataSystemNotifications> SurfaceDataSystemNotificationBus;
}
@@ -0,0 +1,61 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/Entity.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Math/Vector2.h>
#include <SurfaceData/SurfaceDataTypes.h>
namespace SurfaceData
{
/**
* the EBus is used to request information about a surface
*/
class SurfaceDataSystemRequests
: public AZ::EBusTraits
{
public:
////////////////////////////////////////////////////////////////////////
// EBusTraits
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
////////////////////////////////////////////////////////////////////////
//! allows multiple threads to call
using MutexType = AZStd::recursive_mutex;
// Get all surface points located at the inPosition that matches one or more of the desiredTags. Only the XY components of inPosition are used.
virtual void GetSurfacePoints(const AZ::Vector3& inPosition, const SurfaceTagVector& desiredTags, SurfacePointList& surfacePointList) const = 0;
// Get all surface points for every input position within an AABB region. Only the XY dimensions of the AABB region are used.
// The input positions are chosen by starting at the min sides of inRegion and incrementing by stepSize. This method is inclusive
// on the min sides of the AABB, and exclusive on the max sides (i.e. for a box of (0,0) - (4,4), the point (0,0) is included but (4,4) isn't).
virtual void GetSurfacePointsFromRegion(const AZ::Aabb& inRegion, const AZ::Vector2 stepSize, const SurfaceTagVector& desiredTags,
SurfacePointListPerPosition& surfacePointListPerPosition) const = 0;
virtual SurfaceDataRegistryHandle RegisterSurfaceDataProvider(const SurfaceDataRegistryEntry& entry) = 0;
virtual void UnregisterSurfaceDataProvider(const SurfaceDataRegistryHandle& handle) = 0;
virtual void UpdateSurfaceDataProvider(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry) = 0;
virtual SurfaceDataRegistryHandle RegisterSurfaceDataModifier(const SurfaceDataRegistryEntry& entry) = 0;
virtual void UnregisterSurfaceDataModifier(const SurfaceDataRegistryHandle& handle) = 0;
virtual void UpdateSurfaceDataModifier(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry) = 0;
// Notify any dependent systems that they need to refresh their surface data for the provided area.
virtual void RefreshSurfaceData(const AZ::Aabb& dirtyArea) = 0;
};
typedef AZ::EBus<SurfaceDataSystemRequests> SurfaceDataSystemRequestBus;
}
@@ -0,0 +1,35 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/ComponentBus.h>
#include <SurfaceData/SurfaceDataTypes.h>
namespace SurfaceData
{
/**
* the EBus is used to request tags
*/
class SurfaceDataTagEnumeratorRequests : public AZ::ComponentBus
{
public:
//! allows multiple threads to call
using MutexType = AZStd::recursive_mutex;
//tags are accumulated from all enumerators so implementers should not clear container
virtual void GetInclusionSurfaceTags([[maybe_unused]] SurfaceTagVector& tags, [[maybe_unused]] bool& includeAll) const {}
virtual void GetExclusionSurfaceTags([[maybe_unused]] SurfaceTagVector& tags) const {}
};
typedef AZ::EBus<SurfaceDataTagEnumeratorRequests> SurfaceDataTagEnumeratorRequestBus;
}
@@ -0,0 +1,41 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/Math/Aabb.h>
#include <SurfaceData/SurfaceDataTypes.h>
namespace SurfaceData
{
/**
* the EBus is used to request registered tags
*/
class SurfaceDataTagProviderRequests
: public AZ::EBusTraits
{
public:
////////////////////////////////////////////////////////////////////////
// EBusTraits
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
////////////////////////////////////////////////////////////////////////
//! allows multiple threads to call
using MutexType = AZStd::recursive_mutex;
virtual void GetRegisteredSurfaceTagNames(SurfaceTagNameSet& names) const = 0;
};
typedef AZ::EBus<SurfaceDataTagProviderRequests> SurfaceDataTagProviderRequestBus;
}
@@ -0,0 +1,52 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Math/Aabb.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/unordered_set.h>
#include <SurfaceData/SurfaceTag.h>
namespace SurfaceData
{
//map of id or crc to contribution factor
using SurfaceTagWeightMap = AZStd::unordered_map<AZ::Crc32, float>;
using SurfaceTagNameSet = AZStd::unordered_set<AZStd::string>;
using SurfaceTagVector = AZStd::vector<SurfaceTag>;
struct SurfacePoint final
{
AZ_CLASS_ALLOCATOR(SurfacePoint, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(SurfacePoint, "{0DC7E720-68D6-47D4-BB6D-B89EF23C5A5C}");
AZ::EntityId m_entityId;
AZ::Vector3 m_position;
AZ::Vector3 m_normal;
SurfaceTagWeightMap m_masks;
};
using SurfacePointList = AZStd::vector<SurfacePoint>;
using SurfacePointListPerPosition = AZStd::vector<AZStd::pair<AZ::Vector3, SurfacePointList>>;
struct SurfaceDataRegistryEntry
{
AZ::EntityId m_entityId;
AZ::Aabb m_bounds = AZ::Aabb::CreateNull();
SurfaceTagVector m_tags;
};
using SurfaceDataRegistryHandle = AZ::u32;
const SurfaceDataRegistryHandle InvalidSurfaceDataRegistryHandle = 0;
}
@@ -0,0 +1,85 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <SurfaceData/SurfaceDataConstants.h>
namespace SurfaceData
{
/**
* Represents a tag value to match with surface materials and/or masks
*/
class SurfaceTag final
{
public:
AZ_CLASS_ALLOCATOR(SurfaceTag, AZ::SystemAllocator, 0);
AZ_RTTI(SurfaceTag, "{67C8C6ED-F32A-443E-A777-1CAE48B22CD7}");
static void Reflect(AZ::ReflectContext* context);
SurfaceTag()
: m_surfaceTagCrc(Constants::s_unassignedTagCrc)
{
}
SurfaceTag(const AZStd::string& value)
: m_surfaceTagCrc(AZ::Crc32(value.data()))
{
}
SurfaceTag(const AZ::Crc32& value)
: m_surfaceTagCrc(value)
{
}
AZ_INLINE bool operator==(const SurfaceTag& other) const;
AZ_INLINE bool operator<(const SurfaceTag& other) const;
AZ_INLINE operator AZ::Crc32() const;
AZ_INLINE operator AZ::u32() const;
void SetTag(const AZStd::string& value)
{
m_surfaceTagCrc = AZ::Crc32(value.data());
}
static AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>> GetRegisteredTags();
private:
bool FindDisplayName(const AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>>& selectableTags, AZStd::string& name) const;
AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>> BuildSelectableTagList() const;
AZStd::string GetDisplayName() const;
AZ::u32 m_surfaceTagCrc;
};
AZ_INLINE bool SurfaceTag::operator==(const SurfaceTag& other) const
{
return other.m_surfaceTagCrc == m_surfaceTagCrc;
}
AZ_INLINE bool SurfaceTag::operator<(const SurfaceTag& other) const
{
return other.m_surfaceTagCrc < m_surfaceTagCrc;
}
AZ_INLINE SurfaceTag::operator AZ::Crc32() const
{
return m_surfaceTagCrc;
}
AZ_INLINE SurfaceTag::operator AZ::u32() const
{
return static_cast<AZ::u32>(m_surfaceTagCrc);
}
}
@@ -0,0 +1,317 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzTest/AzTest.h>
#include <AzCore/std/hash.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Component/TransformBus.h>
#include <LmbrCentral/Shape/ShapeComponentBus.h>
#include <SurfaceData/SurfaceDataSystemRequestBus.h>
#include <AzCore/Casting/lossy_cast.h>
namespace UnitTest
{
struct SurfaceDataTest
: public ::testing::Test
{
protected:
AZ::ComponentApplication m_app;
AZ::Entity* m_systemEntity = nullptr;
void SetUp() override
{
AZ::ComponentApplication::Descriptor appDesc;
appDesc.m_memoryBlocksByteSize = 128 * 1024 * 1024;
m_systemEntity = m_app.Create(appDesc);
m_app.AddEntity(m_systemEntity);
}
void TearDown() override
{
m_app.Destroy();
m_systemEntity = nullptr;
}
AZStd::unique_ptr<AZ::Entity> CreateEntity()
{
return AZStd::make_unique<AZ::Entity>();
}
void ActivateEntity(AZ::Entity* entity)
{
entity->Init();
EXPECT_EQ(AZ::Entity::State::Init, entity->GetState());
entity->Activate();
EXPECT_EQ(AZ::Entity::State::Active, entity->GetState());
}
template <typename Component, typename Configuration>
AZ::Component* CreateComponent(AZ::Entity* entity, const Configuration& config)
{
m_app.RegisterComponentDescriptor(Component::CreateDescriptor());
return entity->CreateComponent<Component>(config);
}
template <typename Component>
AZ::Component* CreateComponent(AZ::Entity* entity)
{
m_app.RegisterComponentDescriptor(Component::CreateDescriptor());
return entity->CreateComponent<Component>();
}
};
struct MockShapeComponentHandler
: public LmbrCentral::ShapeComponentRequestsBus::Handler
{
MockShapeComponentHandler(const AZ::EntityId& id)
{
BusConnect(id);
}
~MockShapeComponentHandler() override
{
BusDisconnect();
}
AZ::Aabb m_GetLocalBounds = AZ::Aabb::CreateCenterRadius(AZ::Vector3::CreateZero(), 0.5f);
AZ::Transform m_GetTransform = AZ::Transform::CreateIdentity();
void GetTransformAndLocalBounds(AZ::Transform& transform, AZ::Aabb& bounds) override
{
transform = m_GetTransform;
bounds = m_GetLocalBounds;
}
AZ::Crc32 m_GetShapeType = AZ_CRC("MockShapeComponentHandler", 0x5189d279);
AZ::Crc32 GetShapeType() override
{
return m_GetShapeType;
}
AZ::Aabb m_GetEncompassingAabb = AZ::Aabb::CreateCenterRadius(AZ::Vector3::CreateZero(), 0.5f);
AZ::Aabb GetEncompassingAabb() override
{
return m_GetEncompassingAabb;
}
bool IsPointInside(const AZ::Vector3& point) override
{
return m_GetEncompassingAabb.Contains(point);
}
float DistanceSquaredFromPoint(const AZ::Vector3& point) override
{
return m_GetEncompassingAabb.GetDistanceSq(point);
}
};
struct MockShapeComponent
: public AZ::Component
{
public:
AZ_COMPONENT(MockShapeComponent, "{DD9590BC-916B-4EFA-98B8-AC5023941672}", AZ::Component);
void Activate() override {}
void Deactivate() override {}
static void Reflect(AZ::ReflectContext* reflect) { AZ_UNUSED(reflect); }
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("ShapeService", 0xe86aa5fe));
}
};
struct MockTransformHandler
: public AZ::TransformBus::Handler
{
~MockTransformHandler()
{
AZ::TransformBus::Handler::BusDisconnect();
}
AZ::Transform m_GetLocalTMOutput = AZ::Transform::CreateIdentity();
const AZ::Transform & GetLocalTM() override
{
return m_GetLocalTMOutput;
}
AZ::Transform m_GetWorldTMOutput = AZ::Transform::CreateIdentity();
const AZ::Transform & GetWorldTM() override
{
return m_GetWorldTMOutput;
}
bool IsStaticTransform() override
{
return false;
}
bool IsPositionInterpolated() override
{
return false;
}
bool IsRotationInterpolated() override
{
return false;
}
};
struct MockSurfaceDataSystem
: public SurfaceData::SurfaceDataSystemRequestBus::Handler
{
MockSurfaceDataSystem()
{
BusConnect();
}
~MockSurfaceDataSystem()
{
BusDisconnect();
}
AZStd::unordered_map<AZStd::pair<float, float>, SurfaceData::SurfacePointList> m_GetSurfacePoints;
void GetSurfacePoints(const AZ::Vector3& inPosition, [[maybe_unused]] const SurfaceData::SurfaceTagVector& masks, SurfaceData::SurfacePointList& surfacePointList) const override
{
auto surfacePoints = m_GetSurfacePoints.find(AZStd::make_pair(inPosition.GetX(), inPosition.GetY()));
if (surfacePoints != m_GetSurfacePoints.end())
{
surfacePointList = surfacePoints->second;
}
}
void GetSurfacePointsFromRegion([[maybe_unused]] const AZ::Aabb& inRegion, [[maybe_unused]] const AZ::Vector2 stepSize, [[maybe_unused]] const SurfaceData::SurfaceTagVector& desiredTags,
[[maybe_unused]] SurfaceData::SurfacePointListPerPosition& surfacePointListPerPosition) const override
{
}
SurfaceData::SurfaceDataRegistryHandle RegisterSurfaceDataProvider(const SurfaceData::SurfaceDataRegistryEntry& entry) override
{
return RegisterEntry(entry, m_providers);
}
void UnregisterSurfaceDataProvider(const SurfaceData::SurfaceDataRegistryHandle& handle) override
{
UnregisterEntry(handle, m_providers);
}
SurfaceData::SurfaceDataRegistryHandle RegisterSurfaceDataModifier(const SurfaceData::SurfaceDataRegistryEntry& entry) override
{
return RegisterEntry(entry, m_modifiers);
}
void UnregisterSurfaceDataModifier(const SurfaceData::SurfaceDataRegistryHandle& handle) override
{
UnregisterEntry(handle, m_modifiers);
}
void UpdateSurfaceDataModifier(const SurfaceData::SurfaceDataRegistryHandle& handle, const SurfaceData::SurfaceDataRegistryEntry& entry) override
{
UpdateEntry(handle, entry, m_providers);
}
void UpdateSurfaceDataProvider(const SurfaceData::SurfaceDataRegistryHandle& handle, const SurfaceData::SurfaceDataRegistryEntry& entry) override
{
UpdateEntry(handle, entry, m_modifiers);
}
void RefreshSurfaceData([[maybe_unused]] const AZ::Aabb& dirtyBounds) override
{
}
SurfaceData::SurfaceDataRegistryHandle GetSurfaceProviderHandle(AZ::EntityId id)
{
return GetEntryHandle(id, m_providers);
}
SurfaceData::SurfaceDataRegistryHandle GetSurfaceModifierHandle(AZ::EntityId id)
{
return GetEntryHandle(id, m_modifiers);
}
SurfaceData::SurfaceDataRegistryEntry GetSurfaceProviderEntry(AZ::EntityId id)
{
return GetEntry(id, m_providers);
}
SurfaceData::SurfaceDataRegistryEntry GetSurfaceModifierEntry(AZ::EntityId id)
{
return GetEntry(id, m_modifiers);
}
protected:
AZStd::vector<SurfaceData::SurfaceDataRegistryEntry> m_providers;
AZStd::vector<SurfaceData::SurfaceDataRegistryEntry> m_modifiers;
SurfaceData::SurfaceDataRegistryHandle RegisterEntry(const SurfaceData::SurfaceDataRegistryEntry& entry, AZStd::vector<SurfaceData::SurfaceDataRegistryEntry>& entryList)
{
// Keep a list of registered entries. Use the "list index + 1" as the handle. (We add +1 because 0 is used to mean "invalid handle")
entryList.emplace_back(entry);
return entryList.size();
}
void UnregisterEntry(const SurfaceData::SurfaceDataRegistryHandle& handle, AZStd::vector<SurfaceData::SurfaceDataRegistryEntry>& entryList)
{
// We don't actually remove the entry from our list because we use handles as indices, so the indices can't change.
// Clearing out the entity Id should be good enough.
uint32 index = static_cast<uint32>(handle) - 1;
if (index < entryList.size())
{
entryList[index].m_entityId = AZ::EntityId();
}
}
void UpdateEntry(const SurfaceData::SurfaceDataRegistryHandle& handle, const SurfaceData::SurfaceDataRegistryEntry& entry,
AZStd::vector<SurfaceData::SurfaceDataRegistryEntry>& entryList)
{
uint32 index = static_cast<uint32>(handle) - 1;
if (index < entryList.size())
{
entryList[index] = entry;
}
}
SurfaceData::SurfaceDataRegistryHandle GetEntryHandle(AZ::EntityId id, const AZStd::vector<SurfaceData::SurfaceDataRegistryEntry>& entryList)
{
// Look up the requested entity Id and see if we have a registered surface entry with that handle. If so, return the handle.
auto result = AZStd::find_if(entryList.begin(), entryList.end(), [this, id](const SurfaceData::SurfaceDataRegistryEntry& entry) { return entry.m_entityId == id; });
if (result == entryList.end())
{
return SurfaceData::InvalidSurfaceDataRegistryHandle;
}
else
{
// We use "index + 1" as our handle
return static_cast<SurfaceData::SurfaceDataRegistryHandle>(result - entryList.begin() + 1);
}
}
SurfaceData::SurfaceDataRegistryEntry GetEntry(AZ::EntityId id, const AZStd::vector<SurfaceData::SurfaceDataRegistryEntry>& entryList)
{
SurfaceData::SurfaceDataRegistryHandle handle = GetEntryHandle(id, entryList);
if (handle != SurfaceData::InvalidSurfaceDataRegistryHandle)
{
return entryList[handle - 1];
}
else
{
SurfaceData::SurfaceDataRegistryEntry emptyEntry;
return emptyEntry;
}
}
};
}
@@ -0,0 +1,261 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <SurfaceData/SurfaceDataTypes.h>
#include <AzCore/Math/MathUtils.h>
#include <AzCore/Math/IntersectSegment.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Math/Vector3.h>
#include <LmbrCentral/Rendering/MeshAsset.h>
#include <IStatObj.h>
#include <MathConversion.h>
namespace SurfaceData
{
AZ_INLINE bool GetQuadListRayIntersection(
const AZStd::vector<AZ::Vector3>& vertices,
const AZ::Vector3& rayOrigin,
const AZ::Vector3& rayDirection,
const float& rayMaxRange,
AZ::Vector3& outPosition,
AZ::Vector3& outNormal)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity);
const size_t vertexCount = vertices.size();
if (vertexCount > 0 && vertexCount % 4 == 0)
{
// Make sure our raycast segment is at least 1 mm long. If we have a 0-length ray, we'll never intersect.
const float adjustedMaxRange = AZStd::max(0.001f, rayMaxRange);
const AZ::Vector3 rayLength = rayDirection * adjustedMaxRange;
const AZ::Vector3 rayEnd = rayOrigin + rayLength;
for (size_t vertexIndex = 0; vertexIndex < vertexCount; vertexIndex += 4)
{
// This could potentially be optimized further with a single segment / quad intersection check.
// Unfortunately, AZ::Intersect::IntersectRayQuad() currently returns different
// (and worse) results than IntersectSegmentTriangle. It might be that our surface
// quads aren't actually planar, or it might just be a precision or winding order issue.
float resultDistance = 0.0f;
if (AZ::Intersect::IntersectSegmentTriangle(
rayOrigin,
rayEnd,
vertices[vertexIndex + 0],
vertices[vertexIndex + 2],
vertices[vertexIndex + 3],
outNormal,
resultDistance))
{
outPosition = rayOrigin + (rayLength * resultDistance);
return true;
}
resultDistance = 0.0f;
if (AZ::Intersect::IntersectSegmentTriangle(
rayOrigin,
rayEnd,
vertices[vertexIndex + 0],
vertices[vertexIndex + 3],
vertices[vertexIndex + 1],
outNormal,
resultDistance))
{
outPosition = rayOrigin + (rayLength * resultDistance);
return true;
}
}
}
return false;
}
AZ_INLINE bool GetMeshRayIntersection(
const LmbrCentral::MeshAsset& meshAsset,
const AZ::Transform& meshTransform,
const AZ::Transform& meshTransformInverse,
const AZ::Vector3& rayOrigin,
const AZ::Vector3& rayDirection,
AZ::Vector3& outPosition,
AZ::Vector3& outNormal)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity);
IStatObj* pStatObj = meshAsset.m_statObj.get();
if (pStatObj)
{
const Vec3 rayOriginLocal = AZVec3ToLYVec3(meshTransformInverse.TransformPoint(rayOrigin));
const Vec3 rayDirectionLocal = AZVec3ToLYVec3(meshTransformInverse.TransformVector(rayDirection).GetNormalized());
SRayHitInfo hitInfo = {};
hitInfo.inReferencePoint = rayOriginLocal;
hitInfo.inRay = Ray(rayOriginLocal, rayDirectionLocal);
if (pStatObj->RayIntersection(hitInfo))
{
outPosition = meshTransform.TransformPoint(LYVec3ToAZVec3(hitInfo.vHitPos));
outNormal = meshTransform.TransformVector(LYVec3ToAZVec3(hitInfo.vHitNormal)).GetNormalized();
return true;
}
}
return false;
}
AZ_INLINE void AddMaxValueForMasks(SurfaceTagWeightMap& masks, const AZ::Crc32 tag, const float value)
{
const auto maskItr = masks.find(tag);
const float valueOld = maskItr != masks.end() ? maskItr->second : 0.0f;
masks[tag] = AZ::GetMax(value, valueOld);
}
AZ_INLINE void AddMaxValueForMasks(SurfaceTagWeightMap& masks, const SurfaceTagVector& tags, const float value)
{
for (const auto& tag : tags)
{
AddMaxValueForMasks(masks, tag, value);
}
}
AZ_INLINE void AddMaxValueForMasks(SurfaceTagWeightMap& outMasks, const SurfaceTagWeightMap& inMasks)
{
for (const auto& inMask : inMasks)
{
AddMaxValueForMasks(outMasks, inMask.first, inMask.second);
}
}
template<typename Container, typename Element>
AZ_INLINE void AddItemIfNotFound(Container& container, const Element& element)
{
if (AZStd::find(container.begin(), container.end(), element) == container.end())
{
container.insert(container.end(), element);
}
}
template<typename SourceContainer>
AZ_INLINE bool HasMatchingTag(const SourceContainer& sourceTags, const AZ::Crc32& sampleTag)
{
return AZStd::find(sourceTags.begin(), sourceTags.end(), sampleTag) != sourceTags.end();
}
template<typename SourceContainer, typename SampleContainer>
AZ_INLINE bool HasMatchingTags(const SourceContainer& sourceTags, const SampleContainer& sampleTags)
{
for (const auto& sampleTag : sampleTags)
{
if (HasMatchingTag(sourceTags, sampleTag))
{
return true;
}
}
return false;
}
AZ_INLINE bool HasMatchingTag(const SurfaceTagWeightMap& sourceTags, const AZ::Crc32& sampleTag)
{
return sourceTags.find(sampleTag) != sourceTags.end();
}
template<typename SampleContainer>
AZ_INLINE bool HasMatchingTags(const SurfaceTagWeightMap& sourceTags, const SampleContainer& sampleTags)
{
for (const auto& sampleTag : sampleTags)
{
if (HasMatchingTag(sourceTags, sampleTag))
{
return true;
}
}
return false;
}
AZ_INLINE bool HasMatchingTag(const SurfaceTagWeightMap& sourceTags, const AZ::Crc32& sampleTag, float valueMin, float valueMax)
{
auto maskItr = sourceTags.find(sampleTag);
return maskItr != sourceTags.end() && valueMin <= maskItr->second && valueMax >= maskItr->second;
}
template<typename SampleContainer>
AZ_INLINE bool HasMatchingTags(const SurfaceTagWeightMap& sourceTags, const SampleContainer& sampleTags, float valueMin, float valueMax)
{
for (const auto& sampleTag : sampleTags)
{
if (HasMatchingTag(sourceTags, sampleTag, valueMin, valueMax))
{
return true;
}
}
return false;
}
template<typename SourceContainer>
AZ_INLINE void RemoveUnassignedTags(const SourceContainer& sourceTags)
{
sourceTags.erase(AZStd::remove(sourceTags.begin(), sourceTags.end(), Constants::s_unassignedTagCrc), sourceTags.end());
}
template<typename SourceContainer>
AZ_INLINE bool HasValidTags(const SourceContainer& sourceTags)
{
for (const auto& sourceTag : sourceTags)
{
if (sourceTag != Constants::s_unassignedTagCrc)
{
return true;
}
}
return false;
}
AZ_INLINE bool HasValidTags(const SurfaceTagWeightMap& sourceTags)
{
for (const auto& sourceTag : sourceTags)
{
if (sourceTag.first != Constants::s_unassignedTagCrc)
{
return true;
}
}
return false;
}
// Utility method to compare two AABBs for overlapping XY coordinates while ignoring the Z coordinates.
AZ_INLINE bool AabbOverlaps2D(const AZ::Aabb& box1, const AZ::Aabb& box2)
{
return box1.GetMin().GetX() <= box2.GetMax().GetX() &&
box1.GetMin().GetY() <= box2.GetMax().GetY() &&
box1.GetMax().GetX() >= box2.GetMin().GetX() &&
box1.GetMax().GetY() >= box2.GetMin().GetY();
}
// Utility method to compare an AABB and a point for overlapping XY coordinates while ignoring the Z coordinates.
AZ_INLINE bool AabbContains2D(const AZ::Aabb& box, const AZ::Vector2& point)
{
return box.GetMin().GetX() <= point.GetX() &&
box.GetMin().GetY() <= point.GetY() &&
box.GetMax().GetX() >= point.GetX() &&
box.GetMax().GetY() >= point.GetY();
}
// Utility method to compare an AABB and a point for overlapping XY coordinates while ignoring the Z coordinates.
AZ_INLINE bool AabbContains2D(const AZ::Aabb& box, const AZ::Vector3& point)
{
return box.GetMin().GetX() <= point.GetX() &&
box.GetMin().GetY() <= point.GetY() &&
box.GetMax().GetX() >= point.GetX() &&
box.GetMax().GetY() >= point.GetY();
}
}
@@ -0,0 +1,380 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensor's.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SurfaceData_precompiled.h"
#include "SurfaceDataColliderComponent.h"
#include <AzCore/Debug/Profiler.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Math/IntersectSegment.h>
#include <AzFramework/Physics/Casts.h>
#include <AzFramework/Physics/World.h>
#include <AzFramework/Physics/WorldBodyBus.h>
#include <SurfaceData/SurfaceDataSystemRequestBus.h>
#include <SurfaceData/Utility/SurfaceDataUtility.h>
namespace SurfaceData
{
void SurfaceDataColliderConfig::Reflect(AZ::ReflectContext* context)
{
if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<SurfaceDataColliderConfig, AZ::ComponentConfig>()
->Version(0)
->Field("ProviderTags", &SurfaceDataColliderConfig::m_providerTags)
->Field("ModifierTags", &SurfaceDataColliderConfig::m_modifierTags)
;
if (auto edit = serialize->GetEditContext())
{
edit->Class<SurfaceDataColliderConfig>(
"PhysX Collider Surface Tag Emitter", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(0, &SurfaceDataColliderConfig::m_providerTags, "Generated Tags", "Surface tags to add to created points")
->DataElement(0, &SurfaceDataColliderConfig::m_modifierTags, "Extended Tags", "Surface tags to add to contained points")
;
}
}
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<SurfaceDataColliderConfig>()
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::Preview)
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Category, "Vegetation")
->Attribute(AZ::Script::Attributes::Module, "surface_data")
->Constructor()
->Property("providerTags", BehaviorValueProperty(&SurfaceDataColliderConfig::m_providerTags))
->Property("modifierTags", BehaviorValueProperty(&SurfaceDataColliderConfig::m_modifierTags))
;
}
}
void SurfaceDataColliderComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("SurfaceDataProviderService", 0xfe9fb95e));
services.push_back(AZ_CRC("SurfaceDataModifierService", 0x68f8aa72));
}
void SurfaceDataColliderComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("SurfaceDataProviderService", 0xfe9fb95e));
services.push_back(AZ_CRC("SurfaceDataModifierService", 0x68f8aa72));
}
void SurfaceDataColliderComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("PhysXColliderService", 0x4ff43f7c));
}
void SurfaceDataColliderComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("PhysicsWorldBodyService", 0x944da0cc));
}
void SurfaceDataColliderComponent::Reflect(AZ::ReflectContext* context)
{
SurfaceDataColliderConfig::Reflect(context);
if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<SurfaceDataColliderComponent, AZ::Component>()
->Version(0)
->Field("Configuration", &SurfaceDataColliderComponent::m_configuration)
;
}
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<SurfaceDataColliderComponent>()
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::Preview)
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Category, "Vegetation")
->Attribute(AZ::Script::Attributes::Module, "surface_data")
->Constructor()
->Property("providerTags",
[](SurfaceDataColliderComponent* component) { return component->m_configuration.m_providerTags; },
[](SurfaceDataColliderComponent* component, SurfaceData::SurfaceTagVector value)
{
component->m_configuration.m_providerTags = value;
component->OnCompositionChanged();
})
->Property("modifierTags",
[](SurfaceDataColliderComponent* component) { return component->m_configuration.m_modifierTags; },
[](SurfaceDataColliderComponent* component, SurfaceData::SurfaceTagVector value)
{
component->m_configuration.m_modifierTags = value;
component->OnCompositionChanged();
})
;
}
}
SurfaceDataColliderComponent::SurfaceDataColliderComponent(const SurfaceDataColliderConfig& configuration)
: m_configuration(configuration)
{
}
void SurfaceDataColliderComponent::Activate()
{
m_providerHandle = InvalidSurfaceDataRegistryHandle;
m_modifierHandle = InvalidSurfaceDataRegistryHandle;
m_refresh = false;
AZ::TransformNotificationBus::Handler::BusConnect(GetEntityId());
Physics::ColliderComponentEventBus::Handler::BusConnect(GetEntityId());
// Update the cached collider data and bounds, then register the surface data provider / modifier
UpdateColliderData();
}
void SurfaceDataColliderComponent::Deactivate()
{
if (m_providerHandle != InvalidSurfaceDataRegistryHandle)
{
SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UnregisterSurfaceDataProvider, m_providerHandle);
m_providerHandle = InvalidSurfaceDataRegistryHandle;
}
if (m_modifierHandle != InvalidSurfaceDataRegistryHandle)
{
SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UnregisterSurfaceDataModifier, m_modifierHandle);
m_modifierHandle = InvalidSurfaceDataRegistryHandle;
}
AZ::TickBus::Handler::BusDisconnect();
AZ::TransformNotificationBus::Handler::BusDisconnect();
Physics::ColliderComponentEventBus::Handler::BusDisconnect();
SurfaceDataProviderRequestBus::Handler::BusDisconnect();
SurfaceDataModifierRequestBus::Handler::BusDisconnect();
m_refresh = false;
// Clear the cached mesh data
{
AZStd::lock_guard<decltype(m_cacheMutex)> lock(m_cacheMutex);
m_colliderBounds = AZ::Aabb::CreateNull();
}
}
bool SurfaceDataColliderComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig)
{
if (auto config = azrtti_cast<const SurfaceDataColliderConfig*>(baseConfig))
{
m_configuration = *config;
return true;
}
return false;
}
bool SurfaceDataColliderComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const
{
if (auto config = azrtti_cast<SurfaceDataColliderConfig*>(outBaseConfig))
{
*config = m_configuration;
return true;
}
return false;
}
bool SurfaceDataColliderComponent::DoRayTrace(const AZ::Vector3& inPosition, bool queryPointOnly, AZ::Vector3& outPosition, AZ::Vector3& outNormal) const
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity);
AZStd::lock_guard<decltype(m_cacheMutex)> lock(m_cacheMutex);
// test AABB as first pass to claim the point
const AZ::Vector3 testPosition = AZ::Vector3(
inPosition.GetX(),
inPosition.GetY(),
m_colliderBounds.GetCenter().GetZ());
if (!m_colliderBounds.Contains(testPosition))
{
return false;
}
Physics::RayCastRequest request;
request.m_direction = AZ::Vector3(0.0f, 0.0f, -1.0f);
if (queryPointOnly)
{
// We're checking to see if the point is *inside* the collider, so give it a distance of 0.
request.m_start = inPosition;
request.m_distance = 0.0f;
}
else
{
// We're casting the ray to look for a collision, so start at the top of the collider and cast downwards
// the full height of the collider.
request.m_start = AZ::Vector3(inPosition.GetX(), inPosition.GetY(), m_colliderBounds.GetMax().GetZ());
request.m_distance = m_colliderBounds.GetExtents().GetZ();
}
Physics::RayCastHit result;
Physics::WorldBodyRequestBus::EventResult(result, GetEntityId(), &Physics::WorldBodyRequestBus::Events::RayCast, request);
if (result)
{
outPosition = result.m_position;
outNormal = result.m_normal;
return true;
}
return false;
}
void SurfaceDataColliderComponent::GetSurfacePoints(const AZ::Vector3& inPosition, SurfacePointList& surfacePointList) const
{
AZ::Vector3 hitPosition;
AZ::Vector3 hitNormal;
// We want a full raycast, so don't just query the start point.
constexpr bool queryPointOnly = false;
if (DoRayTrace(inPosition, queryPointOnly, hitPosition, hitNormal))
{
SurfacePoint point;
point.m_entityId = GetEntityId();
point.m_position = hitPosition;
point.m_normal = hitNormal;
AddMaxValueForMasks(point.m_masks, m_configuration.m_providerTags, 1.0f);
surfacePointList.push_back(point);
}
}
void SurfaceDataColliderComponent::ModifySurfacePoints(SurfacePointList& surfacePointList) const
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity);
AZStd::lock_guard<decltype(m_cacheMutex)> lock(m_cacheMutex);
if (m_colliderBounds.IsValid() && !m_configuration.m_modifierTags.empty())
{
const AZ::EntityId entityId = GetEntityId();
for (auto& point : surfacePointList)
{
if (point.m_entityId != entityId && m_colliderBounds.Contains(point.m_position))
{
AZ::Vector3 hitPosition;
AZ::Vector3 hitNormal;
constexpr bool queryPointOnly = true;
if (DoRayTrace(point.m_position, queryPointOnly, hitPosition, hitNormal))
{
AddMaxValueForMasks(point.m_masks, m_configuration.m_modifierTags, 1.0f);
}
}
}
}
}
void SurfaceDataColliderComponent::OnCompositionChanged()
{
if (!m_refresh)
{
m_refresh = true;
AZ::TickBus::Handler::BusConnect();
}
}
void SurfaceDataColliderComponent::OnColliderChanged()
{
OnCompositionChanged();
}
void SurfaceDataColliderComponent::OnTransformChanged([[maybe_unused]] const AZ::Transform& local, [[maybe_unused]] const AZ::Transform& world)
{
OnCompositionChanged();
}
void SurfaceDataColliderComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
if (m_refresh)
{
UpdateColliderData();
m_refresh = false;
}
AZ::TickBus::Handler::BusDisconnect();
}
void SurfaceDataColliderComponent::UpdateColliderData()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity);
bool colliderValidBeforeUpdate = false;
bool colliderValidAfterUpdate = false;
{
AZStd::lock_guard<decltype(m_cacheMutex)> lock(m_cacheMutex);
colliderValidBeforeUpdate = m_colliderBounds.IsValid();
m_colliderBounds = AZ::Aabb::CreateNull();
Physics::WorldBodyRequestBus::EventResult(m_colliderBounds, GetEntityId(), &Physics::WorldBodyRequestBus::Events::GetAabb);
colliderValidAfterUpdate = m_colliderBounds.IsValid();
}
SurfaceDataRegistryEntry providerRegistryEntry;
providerRegistryEntry.m_entityId = GetEntityId();
providerRegistryEntry.m_bounds = m_colliderBounds;
providerRegistryEntry.m_tags = m_configuration.m_providerTags;
SurfaceDataRegistryEntry modifierRegistryEntry(providerRegistryEntry);
modifierRegistryEntry.m_tags = m_configuration.m_modifierTags;
if (!colliderValidBeforeUpdate && !colliderValidAfterUpdate)
{
// We didn't have a valid collider before or after running this, so do nothing.
}
else if (!colliderValidBeforeUpdate && colliderValidAfterUpdate)
{
// Our collider has become valid, so register as a provider and save off the provider handle
AZ_Assert((m_providerHandle == InvalidSurfaceDataRegistryHandle), "Surface data handle is initialized before our collider became valid");
AZ_Assert((m_modifierHandle == InvalidSurfaceDataRegistryHandle), "Surface Modifier data handle is initialized before our collider became valid");
AZ_Assert(m_colliderBounds.IsValid(), "Collider Geometry isn't correctly initialized.");
SurfaceDataSystemRequestBus::BroadcastResult(m_providerHandle, &SurfaceDataSystemRequestBus::Events::RegisterSurfaceDataProvider, providerRegistryEntry);
SurfaceDataSystemRequestBus::BroadcastResult(m_modifierHandle, &SurfaceDataSystemRequestBus::Events::RegisterSurfaceDataModifier, modifierRegistryEntry);
// Start listening for surface data events
AZ_Assert((m_providerHandle != InvalidSurfaceDataRegistryHandle), "Invalid surface data handle");
AZ_Assert((m_modifierHandle != InvalidSurfaceDataRegistryHandle), "Invalid surface data handle");
SurfaceDataProviderRequestBus::Handler::BusConnect(m_providerHandle);
SurfaceDataModifierRequestBus::Handler::BusConnect(m_modifierHandle);
}
else if (colliderValidBeforeUpdate && !colliderValidAfterUpdate)
{
// Our collider has stopped being valid, so unregister and stop listening for surface data events
AZ_Assert((m_providerHandle != InvalidSurfaceDataRegistryHandle), "Invalid surface data handle");
AZ_Assert((m_modifierHandle != InvalidSurfaceDataRegistryHandle), "Invalid surface data handle");
SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UnregisterSurfaceDataProvider, m_providerHandle);
SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UnregisterSurfaceDataModifier, m_modifierHandle);
m_providerHandle = InvalidSurfaceDataRegistryHandle;
m_modifierHandle = InvalidSurfaceDataRegistryHandle;
SurfaceDataProviderRequestBus::Handler::BusDisconnect();
SurfaceDataModifierRequestBus::Handler::BusDisconnect();
}
else if (colliderValidBeforeUpdate && colliderValidAfterUpdate)
{
// Our collider was valid before and after, it just changed in some way, so update our registry entry.
AZ_Assert((m_providerHandle != InvalidSurfaceDataRegistryHandle), "Invalid surface data handle");
AZ_Assert((m_modifierHandle != InvalidSurfaceDataRegistryHandle), "Invalid modifier data handle");
SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UpdateSurfaceDataProvider, m_providerHandle, providerRegistryEntry);
SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UpdateSurfaceDataModifier, m_modifierHandle, modifierRegistryEntry);
}
}
}
@@ -0,0 +1,108 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Component/TransformBus.h>
#include <AzFramework/Physics/ColliderComponentBus.h>
#include <SurfaceData/SurfaceDataTypes.h>
#include <SurfaceData/SurfaceDataProviderRequestBus.h>
#include <SurfaceData/SurfaceDataModifierRequestBus.h>
namespace LmbrCentral
{
template<typename, typename>
class EditorWrappedComponentBase;
}
namespace SurfaceData
{
class SurfaceDataColliderConfig
: public AZ::ComponentConfig
{
public:
AZ_CLASS_ALLOCATOR(SurfaceDataColliderConfig, AZ::SystemAllocator, 0);
AZ_RTTI(SurfaceDataColliderConfig, "{D435DDB9-C513-4A2E-B0AC-9933E9360857}", AZ::ComponentConfig);
static void Reflect(AZ::ReflectContext* context);
SurfaceTagVector m_providerTags;
SurfaceTagVector m_modifierTags;
};
class SurfaceDataColliderComponent
: public AZ::Component
, public AZ::TickBus::Handler
, public AZ::TransformNotificationBus::Handler
, public SurfaceDataProviderRequestBus::Handler
, private SurfaceDataModifierRequestBus::Handler
, public Physics::ColliderComponentEventBus::Handler
{
public:
template<typename, typename> friend class LmbrCentral::EditorWrappedComponentBase;
AZ_COMPONENT(SurfaceDataColliderComponent, "{8BECC930-9B2A-442D-A291-8A3F6B6D1071}");
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void Reflect(AZ::ReflectContext* context);
SurfaceDataColliderComponent(const SurfaceDataColliderConfig& configuration);
SurfaceDataColliderComponent() = default;
~SurfaceDataColliderComponent() = default;
//////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
void Activate() override;
void Deactivate() override;
bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override;
bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override;
//////////////////////////////////////////////////////////////////////////
// ColliderComponentEventBus
// For physics meshes only
void OnColliderChanged() override;
//////////////////////////////////////////////////////////////////////////
// TransformNotificationBus
void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
////////////////////////////////////////////////////////////////////////
// AZ::TickBus
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
////////////////////////////////////////////////////////////////////////
// SurfaceDataProviderRequestBus
void GetSurfacePoints(const AZ::Vector3& inPosition, SurfacePointList& surfacePointList) const override;
//////////////////////////////////////////////////////////////////////////
// SurfaceDataModifierRequestBus
void ModifySurfacePoints(SurfacePointList& surfacePointList) const override;
private:
bool DoRayTrace(const AZ::Vector3& inPosition, bool queryPointOnly, AZ::Vector3& outPosition, AZ::Vector3& outNormal) const;
void UpdateColliderData();
void OnCompositionChanged();
SurfaceDataColliderConfig m_configuration;
SurfaceDataRegistryHandle m_providerHandle = InvalidSurfaceDataRegistryHandle;
SurfaceDataRegistryHandle m_modifierHandle = InvalidSurfaceDataRegistryHandle;
// cached data
AZStd::atomic_bool m_refresh{ false };
mutable AZStd::recursive_mutex m_cacheMutex;
AZ::Aabb m_colliderBounds = AZ::Aabb::CreateNull();
};
}
@@ -0,0 +1,303 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensor's.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SurfaceData_precompiled.h"
#include "SurfaceDataMeshComponent.h"
#include <AzCore/Debug/Profiler.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <LmbrCentral/Rendering/MeshAsset.h>
#include <SurfaceData/SurfaceDataSystemRequestBus.h>
#include <SurfaceData/Utility/SurfaceDataUtility.h>
#include <Cry_Matrix34.h>
#include <Cry_GeoIntersect.h>
#include <Cry_Geo.h>
#include <IStatObj.h>
#include <MathConversion.h>
namespace SurfaceData
{
void SurfaceDataMeshConfig::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<SurfaceDataMeshConfig, AZ::ComponentConfig>()
->Version(0)
->Field("SurfaceTags", &SurfaceDataMeshConfig::m_tags)
;
AZ::EditContext* edit = serialize->GetEditContext();
if (edit)
{
edit->Class<SurfaceDataMeshConfig>(
"Mesh Surface Tag Emitter", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(0, &SurfaceDataMeshConfig::m_tags, "Generated Tags", "")
;
}
}
}
void SurfaceDataMeshComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("SurfaceDataProviderService", 0xfe9fb95e));
}
void SurfaceDataMeshComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("SurfaceDataProviderService", 0xfe9fb95e));
}
void SurfaceDataMeshComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("MeshService", 0x71d8a455));
}
void SurfaceDataMeshComponent::Reflect(AZ::ReflectContext* context)
{
SurfaceDataMeshConfig::Reflect(context);
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<SurfaceDataMeshComponent, AZ::Component>()
->Version(0)
->Field("Configuration", &SurfaceDataMeshComponent::m_configuration)
;
}
}
SurfaceDataMeshComponent::SurfaceDataMeshComponent(const SurfaceDataMeshConfig& configuration)
: m_configuration(configuration)
{
}
void SurfaceDataMeshComponent::Activate()
{
AZ::TransformNotificationBus::Handler::BusConnect(GetEntityId());
LmbrCentral::MeshComponentNotificationBus::Handler::BusConnect(GetEntityId());
m_providerHandle = InvalidSurfaceDataRegistryHandle;
m_refresh = false;
// Update the cached mesh data and bounds, then register the surface data provider
UpdateMeshData();
}
void SurfaceDataMeshComponent::Deactivate()
{
if (m_providerHandle != InvalidSurfaceDataRegistryHandle)
{
SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UnregisterSurfaceDataProvider, m_providerHandle);
m_providerHandle = InvalidSurfaceDataRegistryHandle;
}
SurfaceDataProviderRequestBus::Handler::BusDisconnect();
AZ::TickBus::Handler::BusDisconnect();
AZ::TransformNotificationBus::Handler::BusDisconnect();
LmbrCentral::MeshComponentNotificationBus::Handler::BusDisconnect();
m_refresh = false;
// Clear the cached mesh data
{
AZStd::lock_guard<decltype(m_cacheMutex)> lock(m_cacheMutex);
m_meshAssetData = {};
m_meshBounds = AZ::Aabb::CreateNull();
m_meshWorldTM = AZ::Transform::CreateIdentity();
m_meshWorldTMInverse = AZ::Transform::CreateIdentity();
}
}
bool SurfaceDataMeshComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig)
{
if (auto config = azrtti_cast<const SurfaceDataMeshConfig*>(baseConfig))
{
m_configuration = *config;
return true;
}
return false;
}
bool SurfaceDataMeshComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const
{
if (auto config = azrtti_cast<SurfaceDataMeshConfig*>(outBaseConfig))
{
*config = m_configuration;
return true;
}
return false;
}
bool SurfaceDataMeshComponent::DoRayTrace(const AZ::Vector3& inPosition, AZ::Vector3& outPosition, AZ::Vector3& outNormal) const
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity);
AZStd::lock_guard<decltype(m_cacheMutex)> lock(m_cacheMutex);
// test AABB as first pass to claim the point
const AZ::Vector3 testPosition = AZ::Vector3(
inPosition.GetX(),
inPosition.GetY(),
(m_meshBounds.GetMax().GetZ() + m_meshBounds.GetMin().GetZ()) * 0.5f);
if (!m_meshBounds.Contains(testPosition))
{
return false;
}
LmbrCentral::MeshAsset* mesh = m_meshAssetData.GetAs<LmbrCentral::MeshAsset>();
if (!mesh)
{
return false;
}
const AZ::Vector3 rayOrigin = AZ::Vector3(inPosition.GetX(), inPosition.GetY(), m_meshBounds.GetMax().GetZ() + s_rayAABBHeightPadding);
const AZ::Vector3 rayDirection = -AZ::Vector3::CreateAxisZ();
return GetMeshRayIntersection(*mesh, m_meshWorldTM, m_meshWorldTMInverse, rayOrigin, rayDirection, outPosition, outNormal);
}
void SurfaceDataMeshComponent::GetSurfacePoints(const AZ::Vector3& inPosition, SurfacePointList& surfacePointList) const
{
AZ::Vector3 hitPosition;
AZ::Vector3 hitNormal;
if (DoRayTrace(inPosition, hitPosition, hitNormal))
{
SurfacePoint point;
point.m_entityId = GetEntityId();
point.m_position = hitPosition;
point.m_normal = hitNormal;
AddMaxValueForMasks(point.m_masks, m_configuration.m_tags, 1.0f);
surfacePointList.push_back(point);
}
}
AZ::Aabb SurfaceDataMeshComponent::GetSurfaceAabb() const
{
return m_meshBounds;
}
SurfaceTagVector SurfaceDataMeshComponent::GetSurfaceTags() const
{
return m_configuration.m_tags;
}
void SurfaceDataMeshComponent::OnCompositionChanged()
{
if (!m_refresh)
{
m_refresh = true;
AZ::TickBus::Handler::BusConnect();
}
}
void SurfaceDataMeshComponent::OnMeshDestroyed()
{
OnCompositionChanged();
}
void SurfaceDataMeshComponent::OnMeshCreated(const AZ::Data::Asset<AZ::Data::AssetData>& asset)
{
(void)asset;
OnCompositionChanged();
}
void SurfaceDataMeshComponent::OnBoundsReset()
{
OnCompositionChanged();
}
void SurfaceDataMeshComponent::OnTransformChanged(const AZ::Transform & local, const AZ::Transform & world)
{
(void)local;
(void)world;
OnCompositionChanged();
}
void SurfaceDataMeshComponent::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/)
{
if (m_refresh)
{
UpdateMeshData();
m_refresh = false;
}
AZ::TickBus::Handler::BusDisconnect();
}
void SurfaceDataMeshComponent::UpdateMeshData()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity);
bool meshValidBeforeUpdate = false;
bool meshValidAfterUpdate = false;
{
AZStd::lock_guard<decltype(m_cacheMutex)> lock(m_cacheMutex);
meshValidBeforeUpdate = (m_meshAssetData.GetAs<LmbrCentral::MeshAsset>() != nullptr) && (m_meshBounds.IsValid());
m_meshAssetData = {};
LmbrCentral::MeshComponentRequestBus::EventResult(m_meshAssetData, GetEntityId(), &LmbrCentral::MeshComponentRequestBus::Events::GetMeshAsset);
m_meshBounds = AZ::Aabb::CreateNull();
LmbrCentral::MeshComponentRequestBus::EventResult(m_meshBounds, GetEntityId(), &LmbrCentral::MeshComponentRequestBus::Events::GetWorldBounds);
m_meshWorldTM = AZ::Transform::CreateIdentity();
AZ::TransformBus::EventResult(m_meshWorldTM, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM);
m_meshWorldTMInverse = m_meshWorldTM.GetInverse();
meshValidAfterUpdate = (m_meshAssetData.GetAs<LmbrCentral::MeshAsset>() != nullptr) && (m_meshBounds.IsValid());
}
SurfaceDataRegistryEntry registryEntry;
registryEntry.m_entityId = GetEntityId();
registryEntry.m_bounds = GetSurfaceAabb();
registryEntry.m_tags = GetSurfaceTags();
if (!meshValidBeforeUpdate && !meshValidAfterUpdate)
{
// We didn't have a valid mesh asset before or after running this, so do nothing.
}
else if (!meshValidBeforeUpdate && meshValidAfterUpdate)
{
// Our mesh has become valid, so register as a provider and save off the provider handle
AZ_Assert((m_providerHandle == InvalidSurfaceDataRegistryHandle), "Surface data handle is initialized before our mesh became active");
AZ_Assert(m_meshBounds.IsValid(), "Mesh Geometry isn't correctly initialized.");
SurfaceDataSystemRequestBus::BroadcastResult(m_providerHandle, &SurfaceDataSystemRequestBus::Events::RegisterSurfaceDataProvider, registryEntry);
// Start listening for surface data events
AZ_Assert((m_providerHandle != InvalidSurfaceDataRegistryHandle), "Invalid surface data handle");
SurfaceDataProviderRequestBus::Handler::BusConnect(m_providerHandle);
}
else if (meshValidBeforeUpdate && !meshValidAfterUpdate)
{
// Our mesh has stopped being valid, so unregister and stop listening for surface data events
AZ_Assert((m_providerHandle != InvalidSurfaceDataRegistryHandle), "Invalid surface data handle");
SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UnregisterSurfaceDataProvider, m_providerHandle);
m_providerHandle = InvalidSurfaceDataRegistryHandle;
SurfaceDataProviderRequestBus::Handler::BusDisconnect();
}
else if (meshValidBeforeUpdate && meshValidAfterUpdate)
{
// Our mesh was valid before and after, it just changed in some way, so update our registry entry.
AZ_Assert((m_providerHandle != InvalidSurfaceDataRegistryHandle), "Invalid surface data handle");
SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UpdateSurfaceDataProvider, m_providerHandle, registryEntry);
}
}
}
@@ -0,0 +1,110 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/std/containers/unordered_map.h>
#include <LmbrCentral/Rendering/MeshComponentBus.h>
#include <SurfaceData/SurfaceDataProviderRequestBus.h>
#include <SurfaceData/SurfaceDataTypes.h>
namespace LmbrCentral
{
class MeshAsset;
template<typename, typename>
class EditorWrappedComponentBase;
}
namespace SurfaceData
{
constexpr float s_rayAABBHeightPadding = 0.1f;
class SurfaceDataMeshConfig
: public AZ::ComponentConfig
{
public:
AZ_CLASS_ALLOCATOR(SurfaceDataMeshConfig, AZ::SystemAllocator, 0);
AZ_RTTI(SurfaceDataMeshConfig, "{764C602E-7CA8-4BCC-AB2D-3E46623B3A20}", AZ::ComponentConfig);
static void Reflect(AZ::ReflectContext* context);
SurfaceTagVector m_tags;
};
class SurfaceDataMeshComponent
: public AZ::Component
, public AZ::TickBus::Handler
, public AZ::TransformNotificationBus::Handler
, public LmbrCentral::MeshComponentNotificationBus::Handler
, public SurfaceDataProviderRequestBus::Handler
{
public:
template<typename, typename> friend class LmbrCentral::EditorWrappedComponentBase;
AZ_COMPONENT(SurfaceDataMeshComponent, "{F8915F34-BE8B-40B4-B7E8-01EBF3DA1C95}");
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void Reflect(AZ::ReflectContext* context);
SurfaceDataMeshComponent(const SurfaceDataMeshConfig& configuration);
SurfaceDataMeshComponent() = default;
~SurfaceDataMeshComponent() = default;
//////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
void Activate() override;
void Deactivate() override;
bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override;
bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override;
//////////////////////////////////////////////////////////////////////////
// MeshComponentNotificationBus
void OnMeshCreated(const AZ::Data::Asset<AZ::Data::AssetData>& asset) override;
void OnMeshDestroyed() override;
void OnBoundsReset() override;;
//////////////////////////////////////////////////////////////////////////
// TransformNotificationBus
void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
////////////////////////////////////////////////////////////////////////
// AZ::TickBus
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
////////////////////////////////////////////////////////////////////////
// SurfaceDataProviderRequestBus
void GetSurfacePoints(const AZ::Vector3& inPosition, SurfacePointList& surfacePointList) const override;
private:
bool DoRayTrace(const AZ::Vector3& inPosition, AZ::Vector3& outPosition, AZ::Vector3& outNormal) const;
void UpdateMeshData();
void OnCompositionChanged();
AZ::Aabb GetSurfaceAabb() const;
SurfaceTagVector GetSurfaceTags() const;
SurfaceDataMeshConfig m_configuration;
SurfaceDataRegistryHandle m_providerHandle = InvalidSurfaceDataRegistryHandle;
// cached data
AZStd::atomic_bool m_refresh{ false };
mutable AZStd::recursive_mutex m_cacheMutex;
AZ::Data::Asset<AZ::Data::AssetData> m_meshAssetData;
AZ::Transform m_meshWorldTM = AZ::Transform::CreateIdentity();
AZ::Transform m_meshWorldTMInverse = AZ::Transform::CreateIdentity();
AZ::Aabb m_meshBounds = AZ::Aabb::CreateNull();
};
}
@@ -0,0 +1,296 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensor's.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SurfaceData_precompiled.h"
#include "SurfaceDataShapeComponent.h"
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <SurfaceData/SurfaceDataSystemRequestBus.h>
#include <SurfaceData/Utility/SurfaceDataUtility.h>
namespace SurfaceData
{
void SurfaceDataShapeConfig::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<SurfaceDataShapeConfig, AZ::ComponentConfig>()
->Version(0)
->Field("ProviderTags", &SurfaceDataShapeConfig::m_providerTags)
->Field("ModifierTags", &SurfaceDataShapeConfig::m_modifierTags)
;
AZ::EditContext* edit = serialize->GetEditContext();
if (edit)
{
edit->Class<SurfaceDataShapeConfig>(
"Shape Surface Tag Emitter", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(0, &SurfaceDataShapeConfig::m_providerTags, "Generated Tags", "Surface tags to add to created points")
->DataElement(0, &SurfaceDataShapeConfig::m_modifierTags, "Extended Tags", "Surface tags to add to contained points")
;
}
}
}
void SurfaceDataShapeComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("SurfaceDataProviderService", 0xfe9fb95e));
services.push_back(AZ_CRC("SurfaceDataModifierService", 0x68f8aa72));
}
void SurfaceDataShapeComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("SurfaceDataProviderService", 0xfe9fb95e));
services.push_back(AZ_CRC("SurfaceDataModifierService", 0x68f8aa72));
}
void SurfaceDataShapeComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("ShapeService", 0xe86aa5fe));
}
void SurfaceDataShapeComponent::Reflect(AZ::ReflectContext* context)
{
SurfaceDataShapeConfig::Reflect(context);
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<SurfaceDataShapeComponent, AZ::Component>()
->Version(0)
->Field("Configuration", &SurfaceDataShapeComponent::m_configuration)
;
}
}
SurfaceDataShapeComponent::SurfaceDataShapeComponent(const SurfaceDataShapeConfig& configuration)
: m_configuration(configuration)
{
}
void SurfaceDataShapeComponent::Activate()
{
m_providerHandle = InvalidSurfaceDataRegistryHandle;
m_modifierHandle = InvalidSurfaceDataRegistryHandle;
m_refresh = false;
AZ::TransformNotificationBus::Handler::BusConnect(GetEntityId());
LmbrCentral::ShapeComponentNotificationsBus::Handler::BusConnect(GetEntityId());
// Update the cached shape data and bounds, then register the surface data provider / modifier
UpdateShapeData();
}
void SurfaceDataShapeComponent::Deactivate()
{
if (m_providerHandle != InvalidSurfaceDataRegistryHandle)
{
SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UnregisterSurfaceDataProvider, m_providerHandle);
m_providerHandle = InvalidSurfaceDataRegistryHandle;
}
if (m_modifierHandle != InvalidSurfaceDataRegistryHandle)
{
SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UnregisterSurfaceDataModifier, m_modifierHandle);
m_modifierHandle = InvalidSurfaceDataRegistryHandle;
}
m_refresh = false;
AZ::TickBus::Handler::BusDisconnect();
AZ::TransformNotificationBus::Handler::BusDisconnect();
LmbrCentral::ShapeComponentNotificationsBus::Handler::BusDisconnect();
SurfaceDataProviderRequestBus::Handler::BusDisconnect();
SurfaceDataModifierRequestBus::Handler::BusDisconnect();
// Clear the cached shape data
{
AZStd::lock_guard<decltype(m_cacheMutex)> lock(m_cacheMutex);
m_shapeBounds = AZ::Aabb::CreateNull();
m_shapeBoundsIsValid = false;
}
}
bool SurfaceDataShapeComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig)
{
if (auto config = azrtti_cast<const SurfaceDataShapeConfig*>(baseConfig))
{
m_configuration = *config;
return true;
}
return false;
}
bool SurfaceDataShapeComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const
{
if (auto config = azrtti_cast<SurfaceDataShapeConfig*>(outBaseConfig))
{
*config = m_configuration;
return true;
}
return false;
}
void SurfaceDataShapeComponent::GetSurfacePoints(const AZ::Vector3& inPosition, SurfacePointList& surfacePointList) const
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity);
AZStd::lock_guard<decltype(m_cacheMutex)> lock(m_cacheMutex);
if (m_shapeBoundsIsValid)
{
const AZ::Vector3 rayOrigin = AZ::Vector3(inPosition.GetX(), inPosition.GetY(), m_shapeBounds.GetMax().GetZ());
const AZ::Vector3 rayDirection = -AZ::Vector3::CreateAxisZ();
float intersectionDistance = 0.0f;
bool hitShape = false;
LmbrCentral::ShapeComponentRequestsBus::EventResult(hitShape, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::IntersectRay, rayOrigin, rayDirection, intersectionDistance);
if (hitShape)
{
SurfacePoint point;
point.m_entityId = GetEntityId();
point.m_position = rayOrigin + intersectionDistance * rayDirection;
point.m_normal = AZ::Vector3::CreateAxisZ();
AddMaxValueForMasks(point.m_masks, m_configuration.m_providerTags, 1.0f);
surfacePointList.push_back(point);
}
}
}
void SurfaceDataShapeComponent::ModifySurfacePoints(SurfacePointList& surfacePointList) const
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity);
AZStd::lock_guard<decltype(m_cacheMutex)> lock(m_cacheMutex);
if (m_shapeBoundsIsValid && !m_configuration.m_modifierTags.empty())
{
const AZ::EntityId entityId = GetEntityId();
for (auto& point : surfacePointList)
{
if (point.m_entityId != entityId && m_shapeBounds.Contains(point.m_position))
{
bool inside = false;
LmbrCentral::ShapeComponentRequestsBus::EventResult(inside, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::IsPointInside, point.m_position);
if (inside)
{
AddMaxValueForMasks(point.m_masks, m_configuration.m_modifierTags, 1.0f);
}
}
}
}
}
void SurfaceDataShapeComponent::OnTransformChanged(const AZ::Transform& /*local*/, const AZ::Transform& /*world*/)
{
OnCompositionChanged();
}
void SurfaceDataShapeComponent::OnShapeChanged([[maybe_unused]] ShapeChangeReasons changeReason)
{
OnCompositionChanged();
}
void SurfaceDataShapeComponent::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/)
{
if (m_refresh)
{
UpdateShapeData();
m_refresh = false;
}
AZ::TickBus::Handler::BusDisconnect();
}
void SurfaceDataShapeComponent::OnCompositionChanged()
{
if (!m_refresh)
{
m_refresh = true;
AZ::TickBus::Handler::BusConnect();
}
}
void SurfaceDataShapeComponent::UpdateShapeData()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity);
bool shapeValidBeforeUpdate = false;
bool shapeValidAfterUpdate = false;
{
AZStd::lock_guard<decltype(m_cacheMutex)> lock(m_cacheMutex);
shapeValidBeforeUpdate = m_shapeBoundsIsValid;
m_shapeBounds = AZ::Aabb::CreateNull();
LmbrCentral::ShapeComponentRequestsBus::EventResult(m_shapeBounds, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb);
m_shapeBoundsIsValid = m_shapeBounds.IsValid();
shapeValidAfterUpdate = m_shapeBoundsIsValid;
}
SurfaceDataRegistryEntry providerRegistryEntry;
providerRegistryEntry.m_entityId = GetEntityId();
providerRegistryEntry.m_bounds = m_shapeBounds;
providerRegistryEntry.m_tags = m_configuration.m_providerTags;
SurfaceDataRegistryEntry modifierRegistryEntry(providerRegistryEntry);
modifierRegistryEntry.m_tags = m_configuration.m_modifierTags;
if (shapeValidBeforeUpdate && shapeValidAfterUpdate)
{
// Our shape was valid before and after, it just changed in some way, so update our registry entries
AZ_Assert((m_providerHandle != InvalidSurfaceDataRegistryHandle), "Invalid surface data handle");
AZ_Assert((m_modifierHandle != InvalidSurfaceDataRegistryHandle), "Invalid modifier data handle");
SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UpdateSurfaceDataProvider, m_providerHandle, providerRegistryEntry);
SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UpdateSurfaceDataModifier, m_modifierHandle, modifierRegistryEntry);
}
else if (!shapeValidBeforeUpdate && shapeValidAfterUpdate)
{
// Our shape has become valid, so register as a provider and save off the registry handles
AZ_Assert((m_providerHandle == InvalidSurfaceDataRegistryHandle), "Surface Provider data handle is initialized before our shape became valid");
AZ_Assert((m_modifierHandle == InvalidSurfaceDataRegistryHandle), "Surface Modifier data handle is initialized before our shape became valid");
SurfaceDataSystemRequestBus::BroadcastResult(m_providerHandle, &SurfaceDataSystemRequestBus::Events::RegisterSurfaceDataProvider, providerRegistryEntry);
SurfaceDataSystemRequestBus::BroadcastResult(m_modifierHandle, &SurfaceDataSystemRequestBus::Events::RegisterSurfaceDataModifier, modifierRegistryEntry);
// Start listening for surface data events
AZ_Assert((m_providerHandle != InvalidSurfaceDataRegistryHandle), "Invalid surface data handle");
AZ_Assert((m_modifierHandle != InvalidSurfaceDataRegistryHandle), "Invalid surface data handle");
SurfaceDataProviderRequestBus::Handler::BusConnect(m_providerHandle);
SurfaceDataModifierRequestBus::Handler::BusConnect(m_modifierHandle);
}
else if (shapeValidBeforeUpdate && !shapeValidAfterUpdate)
{
// Our shape has stopped being valid, so unregister and stop listening for surface data events
AZ_Assert((m_providerHandle != InvalidSurfaceDataRegistryHandle), "Invalid surface data handle");
AZ_Assert((m_modifierHandle != InvalidSurfaceDataRegistryHandle), "Invalid surface data handle");
SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UnregisterSurfaceDataProvider, m_providerHandle);
SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UnregisterSurfaceDataModifier, m_modifierHandle);
m_providerHandle = InvalidSurfaceDataRegistryHandle;
m_modifierHandle = InvalidSurfaceDataRegistryHandle;
SurfaceDataProviderRequestBus::Handler::BusDisconnect();
SurfaceDataModifierRequestBus::Handler::BusDisconnect();
}
else
{
// We didn't have a valid shape before or after running this, so do nothing.
}
}
const float SurfaceDataShapeComponent::s_rayAABBHeightPadding = 0.1f;
}
@@ -0,0 +1,104 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Component/TransformBus.h>
#include <LmbrCentral/Shape/ShapeComponentBus.h>
#include <SurfaceData/SurfaceDataModifierRequestBus.h>
#include <SurfaceData/SurfaceDataProviderRequestBus.h>
#include <SurfaceData/SurfaceDataTypes.h>
namespace LmbrCentral
{
template<typename, typename>
class EditorWrappedComponentBase;
}
namespace SurfaceData
{
class SurfaceDataShapeConfig
: public AZ::ComponentConfig
{
public:
AZ_CLASS_ALLOCATOR(SurfaceDataShapeConfig, AZ::SystemAllocator, 0);
AZ_RTTI(SurfaceDataShapeConfig, "{1EE196EF-8986-4A2B-B8DD-DA73F85CD597}", AZ::ComponentConfig);
static void Reflect(AZ::ReflectContext* context);
SurfaceTagVector m_providerTags;
SurfaceTagVector m_modifierTags;
};
class SurfaceDataShapeComponent
: public AZ::Component
, private AZ::TickBus::Handler
, private AZ::TransformNotificationBus::Handler
, private LmbrCentral::ShapeComponentNotificationsBus::Handler
, private SurfaceDataModifierRequestBus::Handler
, private SurfaceDataProviderRequestBus::Handler
{
public:
template<typename, typename> friend class LmbrCentral::EditorWrappedComponentBase;
AZ_COMPONENT(SurfaceDataShapeComponent, "{F746C7F6-EF59-45C3-AB5C-011F7AC43415}");
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void Reflect(AZ::ReflectContext* context);
SurfaceDataShapeComponent(const SurfaceDataShapeConfig& configuration);
SurfaceDataShapeComponent() = default;
~SurfaceDataShapeComponent() = default;
//////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
void Activate() override;
void Deactivate() override;
bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override;
bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override;
//////////////////////////////////////////////////////////////////////////
// SurfaceDataProviderRequestBus
void GetSurfacePoints(const AZ::Vector3& inPosition, SurfacePointList& surfacePointList) const;
//////////////////////////////////////////////////////////////////////////
// SurfaceDataModifierRequestBus
void ModifySurfacePoints(SurfacePointList& surfacePointList) const override;
//////////////////////////////////////////////////////////////////////////
// AZ::TransformNotificationBus
void OnTransformChanged(const AZ::Transform& /*local*/, const AZ::Transform& /*world*/) override;
// ShapeComponentNotificationsBus
void OnShapeChanged(ShapeChangeReasons changeReason) override;
////////////////////////////////////////////////////////////////////////
// AZ::TickBus
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
private:
void OnCompositionChanged();
void UpdateShapeData();
SurfaceDataShapeConfig m_configuration;
SurfaceDataRegistryHandle m_providerHandle = InvalidSurfaceDataRegistryHandle;
SurfaceDataRegistryHandle m_modifierHandle = InvalidSurfaceDataRegistryHandle;
// cached data
AZStd::atomic_bool m_refresh{ false };
mutable AZStd::recursive_mutex m_cacheMutex;
AZ::Aabb m_shapeBounds = AZ::Aabb::CreateNull();
bool m_shapeBoundsIsValid = false;
static const float s_rayAABBHeightPadding;
};
}
@@ -0,0 +1,26 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SurfaceData_precompiled.h"
#include "EditorSurfaceDataColliderComponent.h"
#include <AzCore/Serialization/Utils.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
#include <LmbrCentral/Dependency/DependencyNotificationBus.h>
namespace SurfaceData
{
void EditorSurfaceDataColliderComponent::Reflect(AZ::ReflectContext* context)
{
BaseClassType::ReflectSubClass<EditorSurfaceDataColliderComponent, BaseClassType>(context, 2, &LmbrCentral::EditorWrappedComponentBaseVersionConverter<typename BaseClassType::WrappedComponentType, typename BaseClassType::WrappedConfigType,2>);
}
}
@@ -0,0 +1,38 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Module/Module.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <AzToolsFramework/ToolsComponents/EditorVisibilityBus.h>
#include <Components/SurfaceDataColliderComponent.h>
#include <LmbrCentral/Component/EditorWrappedComponentBase.h>
namespace SurfaceData
{
class EditorSurfaceDataColliderComponent
: public LmbrCentral::EditorWrappedComponentBase<SurfaceDataColliderComponent, SurfaceDataColliderConfig>
{
public:
using BaseClassType = LmbrCentral::EditorWrappedComponentBase<SurfaceDataColliderComponent, SurfaceDataColliderConfig>;
AZ_EDITOR_COMPONENT(EditorSurfaceDataColliderComponent, "{0F44367D-B9A3-4C25-A769-24993C8BF7A7}", BaseClassType);
static void Reflect(AZ::ReflectContext* context);
static constexpr const char* const s_categoryName = "Surface Data";
static constexpr const char* const s_componentName = "PhysX Collider Surface Tag Emitter";
static constexpr const char* const s_componentDescription = "Enables a physics collider to emit surface tags";
static constexpr const char* const s_icon = "Editor/Icons/Components/SurfaceData.svg";
static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/SurfaceData.png";
static constexpr const char* const s_helpUrl = "https://docs.aws.amazon.com/console/lumberyard/surfacedata/physics-collider-surface-tag-emitter";
};
}
@@ -0,0 +1,26 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SurfaceData_precompiled.h"
#include "EditorSurfaceDataMeshComponent.h"
#include <AzCore/Serialization/Utils.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
#include <LmbrCentral/Dependency/DependencyNotificationBus.h>
namespace SurfaceData
{
void EditorSurfaceDataMeshComponent::Reflect(AZ::ReflectContext* context)
{
BaseClassType::ReflectSubClass<EditorSurfaceDataMeshComponent, BaseClassType>(context, 2, &LmbrCentral::EditorWrappedComponentBaseVersionConverter<typename BaseClassType::WrappedComponentType, typename BaseClassType::WrappedConfigType,2>);
}
}
@@ -0,0 +1,38 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Module/Module.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <AzToolsFramework/ToolsComponents/EditorVisibilityBus.h>
#include <Components/SurfaceDataMeshComponent.h>
#include <LmbrCentral/Component/EditorWrappedComponentBase.h>
namespace SurfaceData
{
class EditorSurfaceDataMeshComponent
: public LmbrCentral::EditorWrappedComponentBase<SurfaceDataMeshComponent, SurfaceDataMeshConfig>
{
public:
using BaseClassType = LmbrCentral::EditorWrappedComponentBase<SurfaceDataMeshComponent, SurfaceDataMeshConfig>;
AZ_EDITOR_COMPONENT(EditorSurfaceDataMeshComponent, "{4D73E979-5463-4B75-AE46-70B1E52CBF43}", BaseClassType);
static void Reflect(AZ::ReflectContext* context);
static constexpr const char* const s_categoryName = "Surface Data";
static constexpr const char* const s_componentName = "Mesh Surface Tag Emitter";
static constexpr const char* const s_componentDescription = "Enables a static mesh to emit surface tags";
static constexpr const char* const s_icon = "Editor/Icons/Components/SurfaceData.svg";
static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/SurfaceData.png";
static constexpr const char* const s_helpUrl = "https://docs.aws.amazon.com/console/lumberyard/surfacedata/mesh-surface-tag-emitter";
};
}
@@ -0,0 +1,27 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SurfaceData_precompiled.h"
#include "EditorSurfaceDataShapeComponent.h"
#include <AzCore/Serialization/Utils.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
#include <LmbrCentral/Dependency/DependencyNotificationBus.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
namespace SurfaceData
{
void EditorSurfaceDataShapeComponent::Reflect(AZ::ReflectContext* context)
{
BaseClassType::ReflectSubClass<EditorSurfaceDataShapeComponent, BaseClassType>(context, 2, &LmbrCentral::EditorWrappedComponentBaseVersionConverter<typename BaseClassType::WrappedComponentType, typename BaseClassType::WrappedConfigType,2>);
}
}
@@ -0,0 +1,38 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Module/Module.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <AzToolsFramework/ToolsComponents/EditorVisibilityBus.h>
#include <Components/SurfaceDataShapeComponent.h>
#include <LmbrCentral/Component/EditorWrappedComponentBase.h>
namespace SurfaceData
{
class EditorSurfaceDataShapeComponent
: public LmbrCentral::EditorWrappedComponentBase<SurfaceDataShapeComponent, SurfaceDataShapeConfig>
{
public:
using BaseClassType = LmbrCentral::EditorWrappedComponentBase<SurfaceDataShapeComponent, SurfaceDataShapeConfig>;
AZ_EDITOR_COMPONENT(EditorSurfaceDataShapeComponent, "{A2E691AC-A027-4B90-A604-689E543BEA91}", BaseClassType);
static void Reflect(AZ::ReflectContext* context);
static constexpr const char* const s_categoryName = "Surface Data";
static constexpr const char* const s_componentName = "Shape Surface Tag Emitter";
static constexpr const char* const s_componentDescription = "Enables a shape to emit surface tags";
static constexpr const char* const s_icon = "Editor/Icons/Components/SurfaceData.svg";
static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/SurfaceData.png";
static constexpr const char* const s_helpUrl = "https://docs.aws.amazon.com/console/lumberyard/surfacedata/shape-surface-tag-emitter";
};
}
@@ -0,0 +1,218 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SurfaceData_precompiled.h"
#include "EditorSurfaceDataSystemComponent.h"
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzFramework/Asset/GenericAssetHandler.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
namespace SurfaceData
{
namespace Details
{
AzFramework::GenericAssetHandler<EditorSurfaceTagListAsset>* s_surfaceTagListAssetHandler = nullptr;
void RegisterAssethandlers()
{
s_surfaceTagListAssetHandler = aznew AzFramework::GenericAssetHandler<EditorSurfaceTagListAsset>("Surface Tag Name List", "Other", "surfaceTagNameList");
s_surfaceTagListAssetHandler->Register();
}
void UnregisterAssethandlers()
{
if (s_surfaceTagListAssetHandler)
{
s_surfaceTagListAssetHandler->Unregister();
delete s_surfaceTagListAssetHandler;
s_surfaceTagListAssetHandler = nullptr;
}
}
}
void EditorSurfaceDataSystemConfig::Reflect(AZ::ReflectContext* context)
{
EditorSurfaceTagListAsset::Reflect(context);
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<EditorSurfaceDataSystemConfig, AZ::ComponentConfig>()
->Version(0)
;
AZ::EditContext* edit = serialize->GetEditContext();
if (edit)
{
edit->Class<EditorSurfaceDataSystemConfig>(
"Editor Surface Data System Config", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
;
}
}
}
void EditorSurfaceDataSystemComponent::Reflect(AZ::ReflectContext* context)
{
EditorSurfaceDataSystemConfig::Reflect(context);
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<EditorSurfaceDataSystemComponent, AzToolsFramework::Components::EditorComponentBase>()
->Version(0)
->Field("Configuration", &EditorSurfaceDataSystemComponent::m_configuration)
;
if (AZ::EditContext* ec = serialize->GetEditContext())
{
ec->Class<EditorSurfaceDataSystemComponent>("Editor Surface Data System", "Manages discovery and registration of surface tag list assets")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(0, &EditorSurfaceDataSystemComponent::m_configuration, "Configuration", "")
;
}
}
}
void EditorSurfaceDataSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("SurfaceDataTagProviderService", 0x21e6b583));
}
void EditorSurfaceDataSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("SurfaceDataTagProviderService", 0x21e6b583));
}
void EditorSurfaceDataSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC("SurfaceDataSystemService", 0x1d44d25f));
}
void EditorSurfaceDataSystemComponent::Init()
{
AzToolsFramework::Components::EditorComponentBase::Init();
}
void EditorSurfaceDataSystemComponent::Activate()
{
Details::RegisterAssethandlers();
AzFramework::AssetCatalogEventBus::Handler::BusConnect();
AzToolsFramework::Components::EditorComponentBase::Activate();
SurfaceDataTagProviderRequestBus::Handler::BusConnect();
}
void EditorSurfaceDataSystemComponent::Deactivate()
{
AzFramework::AssetCatalogEventBus::Handler::BusDisconnect();
AzToolsFramework::Components::EditorComponentBase::Deactivate();
SurfaceDataTagProviderRequestBus::Handler::BusDisconnect();
Details::UnregisterAssethandlers();
AZ::Data::AssetBus::MultiHandler::BusDisconnect();
}
void EditorSurfaceDataSystemComponent::GetRegisteredSurfaceTagNames(SurfaceTagNameSet& masks) const
{
for (const auto& tagName : Constants::s_allTagNames)
{
masks.insert(tagName);
}
for (const auto& assetPair : m_surfaceTagNameAssets)
{
const auto& asset = assetPair.second;
if (asset.IsReady())
{
const auto& tags = asset.Get()->m_surfaceTagNames;
masks.insert(tags.begin(), tags.end());
}
}
}
void EditorSurfaceDataSystemComponent::OnCatalogLoaded(const char* /*catalogFile*/)
{
//automatically register all surface tag list assets
// First run through all the assets and trigger loads on them.
AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::EnumerateAssets,
nullptr,
[this](const AZ::Data::AssetId assetId, const AZ::Data::AssetInfo& assetInfo) {
const auto assetType = azrtti_typeid<EditorSurfaceTagListAsset>();
if (assetInfo.m_assetType == assetType)
{
m_surfaceTagNameAssets[assetId] = AZ::Data::AssetManager::Instance().GetAsset(assetId, assetType, AZ::Data::AssetLoadBehavior::Default);
}
},
nullptr);
// After all the loads are triggered, block to make sure they've all completed.
for (auto& asset : m_surfaceTagNameAssets)
{
asset.second.BlockUntilLoadComplete();
}
}
void EditorSurfaceDataSystemComponent::OnCatalogAssetAdded(const AZ::Data::AssetId& assetId)
{
AZ::Data::AssetInfo assetInfo;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, assetId);
const auto assetType = azrtti_typeid<EditorSurfaceTagListAsset>();
if (assetInfo.m_assetType == assetType)
{
AZ::Data::AssetBus::MultiHandler::BusConnect(assetId);
}
}
void EditorSurfaceDataSystemComponent::OnCatalogAssetChanged(const AZ::Data::AssetId& assetId)
{
AZ::Data::AssetInfo assetInfo;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, assetId);
const auto assetType = azrtti_typeid<EditorSurfaceTagListAsset>();
if (assetInfo.m_assetType == assetType)
{
AZ::Data::AssetBus::MultiHandler::BusConnect(assetId);
}
}
void EditorSurfaceDataSystemComponent::OnCatalogAssetRemoved(const AZ::Data::AssetId& assetId, const AZ::Data::AssetInfo& /*assetInfo*/)
{
m_surfaceTagNameAssets.erase(assetId);
}
void EditorSurfaceDataSystemComponent::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
OnAssetReady(asset);
}
void EditorSurfaceDataSystemComponent::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect(asset.GetId());
AddAsset(asset);
}
void EditorSurfaceDataSystemComponent::AddAsset(AZ::Data::Asset<AZ::Data::AssetData>& asset)
{
const auto assetType = azrtti_typeid<EditorSurfaceTagListAsset>();
if (asset.GetType() == assetType)
{
m_surfaceTagNameAssets[asset.GetId()] = asset;
AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&AzToolsFramework::PropertyEditorGUIMessages::RequestRefresh, AzToolsFramework::PropertyModificationRefreshLevel::Refresh_AttributesAndValues);
}
}
}
@@ -0,0 +1,82 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include "EditorSurfaceTagListAsset.h"
#include <AzCore/Component/Component.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzFramework/Asset/AssetCatalogBus.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <SurfaceData/SurfaceDataTagProviderRequestBus.h>
namespace AZ::Data
{
class AssetInfo;
}
namespace SurfaceData
{
class EditorSurfaceDataSystemConfig
: public AZ::ComponentConfig
{
public:
AZ_CLASS_ALLOCATOR(EditorSurfaceDataSystemConfig, AZ::SystemAllocator, 0);
AZ_RTTI(EditorSurfaceDataSystemConfig, "{13B511DF-B649-474C-AC32-1E1026DBB303}", AZ::ComponentConfig);
static void Reflect(AZ::ReflectContext* context);
};
class EditorSurfaceDataSystemComponent
: public AzToolsFramework::Components::EditorComponentBase
, private AzFramework::AssetCatalogEventBus::Handler
, private SurfaceDataTagProviderRequestBus::Handler
, private AZ::Data::AssetBus::MultiHandler
{
public:
AZ_EDITOR_COMPONENT(EditorSurfaceDataSystemComponent, "{F3EE5137-856B-4E29-AADD-84F358AEA75F}");
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
private:
void AddAsset(AZ::Data::Asset<AZ::Data::AssetData>& asset);
////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
void Init() override;
void Activate() override;
void Deactivate() override;
////////////////////////////////////////////////////////////////////////
// SurfaceDataTagProviderRequestBus
void GetRegisteredSurfaceTagNames(SurfaceTagNameSet& names) const override;
////////////////////////////////////////////////////////////////////////
// AzFramework::AssetCatalogEventBus
void OnCatalogLoaded(const char* /*catalogFile*/) override;
void OnCatalogAssetChanged(const AZ::Data::AssetId& assetId) override;
void OnCatalogAssetAdded(const AZ::Data::AssetId& assetId) override;
void OnCatalogAssetRemoved(const AZ::Data::AssetId& assetId, const AZ::Data::AssetInfo& assetInfo) override;
////////////////////////////////////////////////////////////////////////
// AZ::Data::AssetBus
void OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
EditorSurfaceDataSystemConfig m_configuration;
AZStd::unordered_map<AZ::Data::AssetId, AZ::Data::Asset<EditorSurfaceTagListAsset>> m_surfaceTagNameAssets;
};
}
@@ -0,0 +1,44 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SurfaceData_precompiled.h"
#include "EditorSurfaceTagListAsset.h"
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace SurfaceData
{
void EditorSurfaceTagListAsset::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<EditorSurfaceTagListAsset>()
->Attribute(AZ::Edit::Attributes::EnableForAssetEditor, true)
->Version(0)
->Field("SurfaceTagNames", &EditorSurfaceTagListAsset::m_surfaceTagNames)
;
AZ::EditContext* edit = serialize->GetEditContext();
if (edit)
{
edit->Class<EditorSurfaceTagListAsset>(
"Surface Tag Name List Asset", "Contains a list of tag names")
->DataElement(0, &EditorSurfaceTagListAsset::m_surfaceTagNames, "Surface Tag Name List", "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, true)
->ElementAttribute(AZ::Edit::Attributes::MaxLength, 64)
;
}
}
}
}
@@ -0,0 +1,38 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/RTTI/RTTI.h>
namespace AZ
{
class ReflectContext;
}
namespace SurfaceData
{
/**
* Asset containing dictionary of known tags
*/
class EditorSurfaceTagListAsset final
: public AZ::Data::AssetData
{
public:
AZ_RTTI(EditorSurfaceTagListAsset, "{A471B2A9-85FC-4993-842D-1881CBC03A2B}", AZ::Data::AssetData);
AZ_CLASS_ALLOCATOR(EditorSurfaceTagListAsset, AZ::SystemAllocator, 0);
static void Reflect(AZ::ReflectContext* context);
AZStd::vector<AZStd::string> m_surfaceTagNames;
};
} // namespace SurfaceData
@@ -0,0 +1,41 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SurfaceData_precompiled.h"
#include <SurfaceDataEditorModule.h>
#include <SurfaceDataSystemComponent.h>
#include <Editor/EditorSurfaceDataSystemComponent.h>
#include <Editor/EditorSurfaceDataColliderComponent.h>
#include <Editor/EditorSurfaceDataMeshComponent.h>
#include <Editor/EditorSurfaceDataShapeComponent.h>
namespace SurfaceData
{
SurfaceDataEditorModule::SurfaceDataEditorModule()
{
m_descriptors.insert(m_descriptors.end(), {
EditorSurfaceDataSystemComponent::CreateDescriptor(),
EditorSurfaceDataColliderComponent::CreateDescriptor(),
EditorSurfaceDataMeshComponent::CreateDescriptor(),
EditorSurfaceDataShapeComponent::CreateDescriptor()
});
}
AZ::ComponentTypeList SurfaceDataEditorModule::GetRequiredSystemComponents() const
{
AZ::ComponentTypeList requiredComponents = SurfaceDataModule::GetRequiredSystemComponents();
requiredComponents.push_back(azrtti_typeid<EditorSurfaceDataSystemComponent>());
return requiredComponents;
}
}
AZ_DECLARE_MODULE_CLASS(Gem_SurfaceDataEditor, SurfaceData::SurfaceDataEditorModule)
@@ -0,0 +1,31 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include "SurfaceData_precompiled.h"
#include <SurfaceDataModule.h>
namespace SurfaceData
{
class SurfaceDataEditorModule
: public SurfaceDataModule
{
public:
AZ_RTTI(SurfaceDataEditorModule, "{B80F2321-B79A-4161-B586-3E508655DFAF}", SurfaceDataModule);
AZ_CLASS_ALLOCATOR(SurfaceDataEditorModule, AZ::SystemAllocator, 0);
SurfaceDataEditorModule();
AZ::ComponentTypeList GetRequiredSystemComponents() const override;
};
}
@@ -0,0 +1,49 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SurfaceData_precompiled.h"
#include <SurfaceDataModule.h>
#include <SurfaceDataSystemComponent.h>
#include <Components/SurfaceDataColliderComponent.h>
#include <Components/SurfaceDataMeshComponent.h>
#include <Components/SurfaceDataShapeComponent.h>
#include <TerrainSurfaceDataSystemComponent.h>
namespace SurfaceData
{
SurfaceDataModule::SurfaceDataModule()
{
m_descriptors.insert(m_descriptors.end(), {
SurfaceDataSystemComponent::CreateDescriptor(),
SurfaceDataColliderComponent::CreateDescriptor(),
SurfaceDataMeshComponent::CreateDescriptor(),
SurfaceDataShapeComponent::CreateDescriptor(),
TerrainSurfaceDataSystemComponent::CreateDescriptor(),
});
}
AZ::ComponentTypeList SurfaceDataModule::GetRequiredSystemComponents() const
{
return AZ::ComponentTypeList{
azrtti_typeid<SurfaceDataSystemComponent>(),
azrtti_typeid<TerrainSurfaceDataSystemComponent>(),
};
}
}
#if !defined(SURFACEDATA_EDITOR)
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
// The first parameter should be GemName_GemIdLower
// The second should be the fully qualified name of the class above
AZ_DECLARE_MODULE_CLASS(Gem_SurfaceData, SurfaceData::SurfaceDataModule)
#endif
@@ -0,0 +1,31 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include "SurfaceData_precompiled.h"
#include <AzCore/Module/Module.h>
namespace SurfaceData
{
class SurfaceDataModule
: public AZ::Module
{
public:
AZ_RTTI(SurfaceDataModule, "{B58B7CA8-98C9-4DC8-8607-E094989BBBE2}", AZ::Module);
AZ_CLASS_ALLOCATOR(SurfaceDataModule, AZ::SystemAllocator, 0);
SurfaceDataModule();
AZ::ComponentTypeList GetRequiredSystemComponents() const override;
};
}
@@ -0,0 +1,466 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SurfaceData_precompiled.h"
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/std/sort.h>
#include "SurfaceDataSystemComponent.h"
#include <SurfaceData/SurfaceDataConstants.h>
#include <SurfaceData/SurfaceTag.h>
#include <SurfaceData/SurfaceDataSystemNotificationBus.h>
#include <SurfaceData/SurfaceDataProviderRequestBus.h>
#include <SurfaceData/SurfaceDataModifierRequestBus.h>
#include <SurfaceData/Utility/SurfaceDataUtility.h>
namespace SurfaceData
{
void SurfaceDataSystemComponent::Reflect(AZ::ReflectContext* context)
{
SurfaceTag::Reflect(context);
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<SurfaceDataSystemComponent, AZ::Component>()
->Version(0)
;
if (AZ::EditContext* ec = serialize->GetEditContext())
{
ec->Class<SurfaceDataSystemComponent>("Surface Data System", "Manages registration of surface data providers and forwards intersection data requests to them")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
;
}
}
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<SurfacePoint>()
->Constructor()
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::Preview)
->Attribute(AZ::Script::Attributes::Category, "Vegetation")
->Attribute(AZ::Script::Attributes::Module, "surface_data")
->Property("entityId", BehaviorValueProperty(&SurfacePoint::m_entityId))
->Property("position", BehaviorValueProperty(&SurfacePoint::m_position))
->Property("normal", BehaviorValueProperty(&SurfacePoint::m_normal))
->Property("masks", BehaviorValueProperty(&SurfacePoint::m_masks))
;
behaviorContext->Class<SurfaceDataSystemComponent>()
->RequestBus("SurfaceDataSystemRequestBus")
;
behaviorContext->EBus<SurfaceDataSystemRequestBus>("SurfaceDataSystemRequestBus")
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::Preview)
->Attribute(AZ::Script::Attributes::Category, "Vegetation")
->Attribute(AZ::Script::Attributes::Module, "surface_data")
->Event("GetSurfacePoints", &SurfaceDataSystemRequestBus::Events::GetSurfacePoints)
;
behaviorContext->EBus<SurfaceDataSystemNotificationBus>("SurfaceDataSystemNotificationBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Category, "Vegetation")
->Attribute(AZ::Script::Attributes::Module, "surface_data")
->Event("OnSurfaceChanged", &SurfaceDataSystemNotificationBus::Events::OnSurfaceChanged)
;
}
}
void SurfaceDataSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("SurfaceDataSystemService", 0x1d44d25f));
}
void SurfaceDataSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("SurfaceDataSystemService", 0x1d44d25f));
}
void SurfaceDataSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
(void)required;
}
void SurfaceDataSystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
(void)dependent;
}
void SurfaceDataSystemComponent::Init()
{
}
void SurfaceDataSystemComponent::Activate()
{
SurfaceDataSystemRequestBus::Handler::BusConnect();
}
void SurfaceDataSystemComponent::Deactivate()
{
SurfaceDataSystemRequestBus::Handler::BusDisconnect();
}
SurfaceDataRegistryHandle SurfaceDataSystemComponent::RegisterSurfaceDataProvider(const SurfaceDataRegistryEntry& entry)
{
const SurfaceDataRegistryHandle handle = RegisterSurfaceDataProviderInternal(entry);
if (handle != InvalidSurfaceDataRegistryHandle)
{
// Send in the entry's bounds as both the old and new bounds, since a null Aabb for old bounds
// would cause *all* vegetation sectors to get marked as dirty.
SurfaceDataSystemNotificationBus::Broadcast(&SurfaceDataSystemNotificationBus::Events::OnSurfaceChanged, entry.m_entityId, entry.m_bounds, entry.m_bounds);
}
return handle;
}
void SurfaceDataSystemComponent::UnregisterSurfaceDataProvider(const SurfaceDataRegistryHandle& handle)
{
const SurfaceDataRegistryEntry entry = UnregisterSurfaceDataProviderInternal(handle);
if (entry.m_entityId.IsValid())
{
// Send in the entry's bounds as both the old and new bounds, since a null Aabb for new bounds
// would cause *all* vegetation sectors to get marked as dirty.
SurfaceDataSystemNotificationBus::Broadcast(&SurfaceDataSystemNotificationBus::Events::OnSurfaceChanged, entry.m_entityId, entry.m_bounds, entry.m_bounds);
}
}
void SurfaceDataSystemComponent::UpdateSurfaceDataProvider(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry)
{
AZ::Aabb oldBounds = AZ::Aabb::CreateNull();
if (UpdateSurfaceDataProviderInternal(handle, entry, oldBounds))
{
SurfaceDataSystemNotificationBus::Broadcast(&SurfaceDataSystemNotificationBus::Events::OnSurfaceChanged, entry.m_entityId, oldBounds, entry.m_bounds);
}
}
SurfaceDataRegistryHandle SurfaceDataSystemComponent::RegisterSurfaceDataModifier(const SurfaceDataRegistryEntry& entry)
{
const SurfaceDataRegistryHandle handle = RegisterSurfaceDataModifierInternal(entry);
if (handle != InvalidSurfaceDataRegistryHandle)
{
// Send in the entry's bounds as both the old and new bounds, since a null Aabb for old bounds
// would cause *all* vegetation sectors to get marked as dirty.
SurfaceDataSystemNotificationBus::Broadcast(&SurfaceDataSystemNotificationBus::Events::OnSurfaceChanged, entry.m_entityId, entry.m_bounds, entry.m_bounds);
}
return handle;
}
void SurfaceDataSystemComponent::UnregisterSurfaceDataModifier(const SurfaceDataRegistryHandle& handle)
{
const SurfaceDataRegistryEntry entry = UnregisterSurfaceDataModifierInternal(handle);
if (entry.m_entityId.IsValid())
{
// Send in the entry's bounds as both the old and new bounds, since a null Aabb for new bounds
// would cause *all* vegetation sectors to get marked as dirty.
SurfaceDataSystemNotificationBus::Broadcast(&SurfaceDataSystemNotificationBus::Events::OnSurfaceChanged, entry.m_entityId, entry.m_bounds, entry.m_bounds);
}
}
void SurfaceDataSystemComponent::UpdateSurfaceDataModifier(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry)
{
AZ::Aabb oldBounds = AZ::Aabb::CreateNull();
if (UpdateSurfaceDataModifierInternal(handle, entry, oldBounds))
{
SurfaceDataSystemNotificationBus::Broadcast(&SurfaceDataSystemNotificationBus::Events::OnSurfaceChanged, entry.m_entityId, oldBounds, entry.m_bounds);
}
}
void SurfaceDataSystemComponent::RefreshSurfaceData(const AZ::Aabb& dirtyBounds)
{
SurfaceDataSystemNotificationBus::Broadcast(&SurfaceDataSystemNotificationBus::Events::OnSurfaceChanged, AZ::EntityId(), dirtyBounds, dirtyBounds);
}
void SurfaceDataSystemComponent::GetSurfacePoints(const AZ::Vector3& inPosition, const SurfaceTagVector& desiredTags, SurfacePointList& surfacePointList) const
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity);
const bool hasDesiredTags = HasValidTags(desiredTags);
const bool hasModifierTags = hasDesiredTags && HasMatchingTags(desiredTags, m_registeredModifierTags);
AZStd::lock_guard<decltype(m_registrationMutex)> registrationLock(m_registrationMutex);
surfacePointList.clear();
//gather all intersecting points
for (const auto& entryPair : m_registeredSurfaceDataProviders)
{
const AZ::u32 entryAddress = entryPair.first;
const SurfaceDataRegistryEntry& entry = entryPair.second;
AZ::Vector3 point2d(inPosition.GetX(), inPosition.GetY(), entry.m_bounds.GetMax().GetZ());
if (!entry.m_bounds.IsValid() || entry.m_bounds.Contains(point2d))
{
if (!hasDesiredTags || hasModifierTags || HasMatchingTags(desiredTags, entry.m_tags))
{
SurfaceDataProviderRequestBus::Event(entryAddress, &SurfaceDataProviderRequestBus::Events::GetSurfacePoints, point2d, surfacePointList);
}
}
}
if (!surfacePointList.empty())
{
//modify or annotate reported points
for (const auto& entryPair : m_registeredSurfaceDataModifiers)
{
const AZ::u32 entryAddress = entryPair.first;
const SurfaceDataRegistryEntry& entry = entryPair.second;
AZ::Vector3 point2d(inPosition.GetX(), inPosition.GetY(), entry.m_bounds.GetMax().GetZ());
if (!entry.m_bounds.IsValid() || entry.m_bounds.Contains(point2d))
{
SurfaceDataModifierRequestBus::Event(entryAddress, &SurfaceDataModifierRequestBus::Events::ModifySurfacePoints, surfacePointList);
}
}
// After we've finished creating and annotating all the surface points, combine any points together that have effectively the
// same XY coordinates and extremely similar Z values. This produces results that are sorted in decreasing Z order.
// Also, this filters out any remaining points that don't match the desired tag list. This can happen when a surface provider
// doesn't add a desired tag, and a surface modifier has the *potential* to add it, but then doesn't.
CombineSortAndFilterNeighboringPoints(surfacePointList, hasDesiredTags, desiredTags);
}
}
void SurfaceDataSystemComponent::GetSurfacePointsFromRegion(const AZ::Aabb& inRegion, const AZ::Vector2 stepSize, const SurfaceTagVector& desiredTags, SurfacePointListPerPosition& surfacePointListPerPosition) const
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity);
AZStd::lock_guard<decltype(m_registrationMutex)> registrationLock(m_registrationMutex);
surfacePointListPerPosition.clear();
surfacePointListPerPosition.reserve(aznumeric_cast<uint32_t>(ceil(inRegion.GetXExtent() / stepSize.GetX())) * aznumeric_cast<uint32_t>(ceil(inRegion.GetYExtent() / stepSize.GetY())));
// Initialize our list-per-position list with every input position to query from the region.
// This is inclusive on the min sides of inRegion, and exclusive on the max sides.
for (float y = inRegion.GetMin().GetY(); y < inRegion.GetMax().GetY(); y += stepSize.GetY())
{
for (float x = inRegion.GetMin().GetX(); x < inRegion.GetMax().GetX(); x += stepSize.GetX())
{
surfacePointListPerPosition.emplace_back(AZ::Vector3(x, y, AZ::Constants::FloatMax), SurfaceData::SurfacePointList{});
}
}
const bool hasDesiredTags = HasValidTags(desiredTags);
const bool hasModifierTags = hasDesiredTags && HasMatchingTags(desiredTags, m_registeredModifierTags);
// Loop through each data provider, and query all the points for each one. This allows us to check the tags and the overall
// AABB bounds just once per provider, instead of once per point. It also allows for an eventual optimization in which we could send
// the list of points directly into each SurfaceDataProvider.
for (const auto& entryPair : m_registeredSurfaceDataProviders)
{
const SurfaceDataRegistryEntry& entry = entryPair.second;
bool alwaysApplies = !entry.m_bounds.IsValid();
if ((!hasDesiredTags || hasModifierTags || HasMatchingTags(desiredTags, entry.m_tags)) &&
( alwaysApplies || AabbOverlaps2D(entry.m_bounds, inRegion) )
)
{
for (auto& surfacePointListAndPoint : surfacePointListPerPosition)
{
const auto& point2d = surfacePointListAndPoint.first;
SurfacePointList& surfacePointList = surfacePointListAndPoint.second;
AZ::Vector3 point3d(point2d.GetX(), point2d.GetY(), entry.m_bounds.GetMax().GetZ());
if (alwaysApplies || entry.m_bounds.Contains(point3d))
{
SurfaceDataProviderRequestBus::Event(entryPair.first, &SurfaceDataProviderRequestBus::Events::GetSurfacePoints, point3d, surfacePointList);
}
}
}
}
// Once we have our list of surface points created, run through the list of surface data modifiers to potentially add
// surface tags / values onto each point. The difference between this and the above loop is that surface data *providers*
// create new surface points, but surface data *modifiers* simply annotate points that have already been created. The modifiers
// are used to annotate points that occur within a volume. A common example is marking points as "underwater" for points that occur
// within a water volume.
for (const auto& entryPair : m_registeredSurfaceDataModifiers)
{
const SurfaceDataRegistryEntry& entry = entryPair.second;
bool alwaysApplies = !entry.m_bounds.IsValid();
if (alwaysApplies || AabbOverlaps2D(entry.m_bounds, inRegion))
{
for (auto& surfacePointListAndPoint : surfacePointListPerPosition)
{
const auto& point2d = surfacePointListAndPoint.first;
SurfacePointList& surfacePointList = surfacePointListAndPoint.second;
if (!surfacePointList.empty())
{
AZ::Vector3 point3d(point2d.GetX(), point2d.GetY(), entry.m_bounds.GetMax().GetZ());
if (alwaysApplies || entry.m_bounds.Contains(point3d))
{
SurfaceDataModifierRequestBus::Event(entryPair.first, &SurfaceDataModifierRequestBus::Events::ModifySurfacePoints, surfacePointList);
}
}
}
}
}
// After we've finished creating and annotating all the surface points, combine any points together that have effectively the
// same XY coordinates and extremely similar Z values. This produces results that are sorted in decreasing Z order.
// Also, this filters out any remaining points that don't match the desired tag list. This can happen when a surface provider
// doesn't add a desired tag, and a surface modifier has the *potential* to add it, but then doesn't.
for (auto& surfacePointListAndPoint : surfacePointListPerPosition)
{
auto& surfacePointList = surfacePointListAndPoint.second;
if (!surfacePointList.empty())
{
CombineSortAndFilterNeighboringPoints(surfacePointList, hasDesiredTags, desiredTags);
}
}
}
void SurfaceDataSystemComponent::CombineSortAndFilterNeighboringPoints(SurfacePointList& sourcePointList, bool hasDesiredTags, const SurfaceTagVector& desiredTags) const
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity);
if (sourcePointList.empty())
{
return;
}
// Sorting only makes sense if we have two or more points
if (sourcePointList.size() > 1)
{
//sort by depth/distance before combining points
AZStd::sort(sourcePointList.begin(), sourcePointList.end(), [](const SurfacePoint& a, const SurfacePoint& b)
{
return a.m_position.GetZ() > b.m_position.GetZ();
});
}
//efficient point consolidation requires the points to be pre-sorted so we are only comparing/combining neighbors
const size_t sourcePointCount = sourcePointList.size();
size_t targetPointIndex = 0;
size_t sourcePointIndex = 0;
m_targetPointList.clear();
m_targetPointList.reserve(sourcePointCount);
// Locate the first point that matches our desired tags, if one exists.
for (sourcePointIndex = 0; sourcePointIndex < sourcePointCount; sourcePointIndex++)
{
if (!hasDesiredTags || (HasMatchingTags(sourcePointList[sourcePointIndex].m_masks, desiredTags)))
{
break;
}
}
if (sourcePointIndex < sourcePointCount)
{
// We found a point that matches our tags, so add it to our target list as the first point.
m_targetPointList.push_back(sourcePointList[sourcePointIndex++]);
//iterate over subsequent source points for comparison and consolidation with the last added target/unique point
for (; sourcePointIndex < sourcePointCount; ++sourcePointIndex)
{
const auto& sourcePoint = sourcePointList[sourcePointIndex];
if (!hasDesiredTags || (HasMatchingTags(sourcePoint.m_masks, desiredTags)))
{
auto& targetPoint = m_targetPointList[targetPointIndex];
// [LY-90907] need to add a configurable tolerance for comparison
if (targetPoint.m_position.IsClose(sourcePoint.m_position) &&
targetPoint.m_normal.IsClose(sourcePoint.m_normal))
{
//consolidate points with similar attributes by adding masks to the target point and ignoring the source
AddMaxValueForMasks(targetPoint.m_masks, sourcePoint.m_masks);
continue;
}
//if the points were too different, we have to add a new target point to compare against
m_targetPointList.push_back(sourcePoint);
++targetPointIndex;
}
}
AZStd::swap(sourcePointList, m_targetPointList);
}
}
SurfaceDataRegistryHandle SurfaceDataSystemComponent::RegisterSurfaceDataProviderInternal(const SurfaceDataRegistryEntry& entry)
{
AZStd::lock_guard<decltype(m_registrationMutex)> registrationLock(m_registrationMutex);
SurfaceDataRegistryHandle handle = ++m_registeredSurfaceDataProviderHandleCounter;
m_registeredSurfaceDataProviders[handle] = entry;
return handle;
}
SurfaceDataRegistryEntry SurfaceDataSystemComponent::UnregisterSurfaceDataProviderInternal(const SurfaceDataRegistryHandle& handle)
{
AZStd::lock_guard<decltype(m_registrationMutex)> registrationLock(m_registrationMutex);
SurfaceDataRegistryEntry entry;
auto entryItr = m_registeredSurfaceDataProviders.find(handle);
if (entryItr != m_registeredSurfaceDataProviders.end())
{
entry = entryItr->second;
m_registeredSurfaceDataProviders.erase(entryItr);
}
return entry;
}
bool SurfaceDataSystemComponent::UpdateSurfaceDataProviderInternal(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry, AZ::Aabb& oldBounds)
{
AZStd::lock_guard<decltype(m_registrationMutex)> registrationLock(m_registrationMutex);
auto entryItr = m_registeredSurfaceDataProviders.find(handle);
if (entryItr != m_registeredSurfaceDataProviders.end())
{
oldBounds = entryItr->second.m_bounds;
entryItr->second = entry;
return true;
}
return false;
}
SurfaceDataRegistryHandle SurfaceDataSystemComponent::RegisterSurfaceDataModifierInternal(const SurfaceDataRegistryEntry& entry)
{
AZStd::lock_guard<decltype(m_registrationMutex)> registrationLock(m_registrationMutex);
SurfaceDataRegistryHandle handle = ++m_registeredSurfaceDataModifierHandleCounter;
m_registeredSurfaceDataModifiers[handle] = entry;
m_registeredModifierTags.insert(entry.m_tags.begin(), entry.m_tags.end());
return handle;
}
SurfaceDataRegistryEntry SurfaceDataSystemComponent::UnregisterSurfaceDataModifierInternal(const SurfaceDataRegistryHandle& handle)
{
AZStd::lock_guard<decltype(m_registrationMutex)> registrationLock(m_registrationMutex);
SurfaceDataRegistryEntry entry;
auto entryItr = m_registeredSurfaceDataModifiers.find(handle);
if (entryItr != m_registeredSurfaceDataModifiers.end())
{
entry = entryItr->second;
m_registeredSurfaceDataModifiers.erase(entryItr);
}
return entry;
}
bool SurfaceDataSystemComponent::UpdateSurfaceDataModifierInternal(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry, AZ::Aabb& oldBounds)
{
AZStd::lock_guard<decltype(m_registrationMutex)> registrationLock(m_registrationMutex);
auto entryItr = m_registeredSurfaceDataModifiers.find(handle);
if (entryItr != m_registeredSurfaceDataModifiers.end())
{
oldBounds = entryItr->second.m_bounds;
entryItr->second = entry;
m_registeredModifierTags.insert(entry.m_tags.begin(), entry.m_tags.end());
return true;
}
return false;
}
}
@@ -0,0 +1,78 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Math/Aabb.h>
#include <SurfaceData/SurfaceDataSystemRequestBus.h>
namespace SurfaceData
{
class SurfaceDataSystemComponent
: public AZ::Component
, private SurfaceDataSystemRequestBus::Handler
{
public:
AZ_COMPONENT(SurfaceDataSystemComponent, "{6F334BAA-7BD5-45F8-A9BA-760667D25FA0}");
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
protected:
////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
void Init() override;
void Activate() override;
void Deactivate() override;
////////////////////////////////////////////////////////////////////////
// SurfaceDataSystemRequestBus implementation
void GetSurfacePoints(const AZ::Vector3& inPosition, const SurfaceTagVector& desiredTags, SurfacePointList& surfacePointList) const override;
void GetSurfacePointsFromRegion(const AZ::Aabb& inRegion, const AZ::Vector2 stepSize, const SurfaceTagVector& desiredTags, SurfacePointListPerPosition& surfacePointListPerPosition) const override;
SurfaceDataRegistryHandle RegisterSurfaceDataProvider(const SurfaceDataRegistryEntry& entry) override;
void UnregisterSurfaceDataProvider(const SurfaceDataRegistryHandle& handle) override;
void UpdateSurfaceDataProvider(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry) override;
SurfaceDataRegistryHandle RegisterSurfaceDataModifier(const SurfaceDataRegistryEntry& entry) override;
void UnregisterSurfaceDataModifier(const SurfaceDataRegistryHandle& handle) override;
void UpdateSurfaceDataModifier(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry) override;
void RefreshSurfaceData(const AZ::Aabb& dirtyArea) override;
private:
void CombineSortAndFilterNeighboringPoints(SurfacePointList& sourcePointList, bool hasDesiredTags, const SurfaceTagVector& desiredTags) const;
SurfaceDataRegistryHandle RegisterSurfaceDataProviderInternal(const SurfaceDataRegistryEntry& entry);
SurfaceDataRegistryEntry UnregisterSurfaceDataProviderInternal(const SurfaceDataRegistryHandle& handle);
bool UpdateSurfaceDataProviderInternal(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry, AZ::Aabb& oldBounds);
SurfaceDataRegistryHandle RegisterSurfaceDataModifierInternal(const SurfaceDataRegistryEntry& entry);
SurfaceDataRegistryEntry UnregisterSurfaceDataModifierInternal(const SurfaceDataRegistryHandle& handle);
bool UpdateSurfaceDataModifierInternal(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry, AZ::Aabb& oldBounds);
mutable AZStd::recursive_mutex m_registrationMutex;
AZStd::unordered_map<SurfaceDataRegistryHandle, SurfaceDataRegistryEntry> m_registeredSurfaceDataProviders;
AZStd::unordered_map<SurfaceDataRegistryHandle, SurfaceDataRegistryEntry> m_registeredSurfaceDataModifiers;
SurfaceDataRegistryHandle m_registeredSurfaceDataProviderHandleCounter = InvalidSurfaceDataRegistryHandle;
SurfaceDataRegistryHandle m_registeredSurfaceDataModifierHandleCounter = InvalidSurfaceDataRegistryHandle;
AZStd::unordered_set<AZ::u32> m_registeredModifierTags;
//point vector reserved for reuse
mutable SurfacePointList m_targetPointList;
};
}
@@ -0,0 +1,12 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SurfaceData_precompiled.h"
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <platform.h> // Many CryCommon files require that this is included first.
+167
View File
@@ -0,0 +1,167 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SurfaceData_precompiled.h"
#include <SurfaceData/SurfaceTag.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <SurfaceData/SurfaceDataTagProviderRequestBus.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/std/sort.h>
namespace SurfaceData
{
namespace SurfaceTagUtil
{
static bool UpdateVersion(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
{
if (classElement.GetVersion() < 1)
{
AZStd::string surfaceTag;
if (classElement.GetChildData(AZ_CRC("SurfaceTag", 0xafd98787), surfaceTag))
{
classElement.RemoveElementByName(AZ_CRC("SurfaceTag", 0xafd98787));
classElement.AddElementWithData(context, "SurfaceTagCrc", (AZ::u32)(AZ::Crc32(surfaceTag.data())));
}
}
if (classElement.GetVersion() < 2)
{
SurfaceTag surfaceTag;
if (classElement.GetData(surfaceTag))
{
if (surfaceTag == AZ_CRC("(default)", 0x3c3d0dd8))
{
surfaceTag = Constants::s_unassignedTagCrc;
classElement.SetData(context, surfaceTag);
}
}
}
return true;
}
}
void SurfaceTag::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<SurfaceTag>()
->Version(2, &SurfaceTagUtil::UpdateVersion)
->Field("SurfaceTagCrc", &SurfaceTag::m_surfaceTagCrc)
;
AZ::EditContext* edit = serialize->GetEditContext();
if (edit)
{
edit->Class<SurfaceTag>(
"Surface Tag", "Matches a surface value like a mask or material")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &SurfaceTag::m_surfaceTagCrc, "Surface Tag", "Matches a surface value like a mask or material")
->Attribute(AZ::Edit::Attributes::EnumValues, &SurfaceTag::BuildSelectableTagList)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
;
}
}
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<SurfaceTag>()
->Constructor()
->Constructor<const AZStd::string&>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::Preview)
->Attribute(AZ::Script::Attributes::Category, "Vegetation")
->Attribute(AZ::Script::Attributes::Module, "surface_data")
->Method("SetTag", &SurfaceTag::SetTag)
->Method("Equal", &SurfaceTag::operator==)
->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Equal)
;
}
}
AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>> SurfaceTag::GetRegisteredTags()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity);
SurfaceTagNameSet labels;
SurfaceDataTagProviderRequestBus::Broadcast(&SurfaceDataTagProviderRequestBus::Events::GetRegisteredSurfaceTagNames, labels);
labels.insert(Constants::s_unassignedTagName);
AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>> registeredTags;
registeredTags.reserve(labels.size());
for (const auto& label : labels)
{
const AZ::u32 crc = AZ::Crc32(label.data());
//warn when two tags have the same crc
auto entryItr = AZStd::find_if(registeredTags.begin(), registeredTags.end(), [crc](const auto& entry) {return entry.first == crc;});
if (entryItr != registeredTags.end())
{
AZ_Warning("SurfaceData", false, "SurfaceTag CRC collision between \"%s\" and \"%s\"! \"%s\" not added.", entryItr->second.data(), label.data(), label.data());
continue;
}
registeredTags.push_back({ crc, label });
}
return registeredTags;
}
bool SurfaceTag::FindDisplayName(const AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>>& selectableTags, AZStd::string& name) const
{
auto it = AZStd::find_if(
selectableTags.begin(),
selectableTags.end(),
[this](const auto& entry) {return m_surfaceTagCrc == entry.first; });
if (it == selectableTags.end())
{
//if a match was not found, generate a name using the crc
name = AZStd::string::format("(unregistered %u)", m_surfaceTagCrc);
return false;
}
name = it->second;
return true;
}
AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>> SurfaceTag::BuildSelectableTagList() const
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity);
AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>> selectableTags = GetRegisteredTags();
AZStd::string name;
if (!FindDisplayName(selectableTags, name))
{
//if a match was not found, add the generated name to the selectable set
selectableTags.push_back({ m_surfaceTagCrc, name });
AZ_Warning("SurfaceData", false, "SurfaceTag CRC %u is not a registered tag.", m_surfaceTagCrc);
}
AZStd::sort(selectableTags.begin(), selectableTags.end(), [](const auto& lhs, const auto& rhs) {return lhs.second < rhs.second;});
return selectableTags;
}
AZStd::string SurfaceTag::GetDisplayName() const
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Entity);
AZStd::string name;
FindDisplayName(GetRegisteredTags(), name);
return name;
}
}
@@ -0,0 +1,267 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SurfaceData_precompiled.h"
#include "TerrainSurfaceDataSystemComponent.h"
#include <AzCore/Debug/Profiler.h>
#include <AzCore/Math/MathUtils.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
#include <MathConversion.h>
#include <SurfaceData/SurfaceDataSystemRequestBus.h>
#include <SurfaceData/SurfaceTag.h>
#include <SurfaceData/Utility/SurfaceDataUtility.h>
#include <ISystem.h>
#include <I3DEngine.h>
namespace SurfaceData
{
//////////////////////////////////////////////////////////////////////////
// TerrainSurfaceDataSystemConfig
void TerrainSurfaceDataSystemConfig::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<TerrainSurfaceDataSystemConfig, AZ::ComponentConfig>()
->Version(0)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<TerrainSurfaceDataSystemConfig>(
"Terrain Surface Data System", "Configures management of surface data requests against legacy terrain")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
;
}
}
}
//////////////////////////////////////////////////////////////////////////
// TerrainSurfaceDataSystemComponent
void TerrainSurfaceDataSystemComponent::Reflect(AZ::ReflectContext* context)
{
TerrainSurfaceDataSystemConfig::Reflect(context);
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<TerrainSurfaceDataSystemComponent, AZ::Component>()
->Version(0)
->Field("Configuration", &TerrainSurfaceDataSystemComponent::m_configuration)
;
if (AZ::EditContext* editContext = serialize->GetEditContext())
{
editContext->Class<TerrainSurfaceDataSystemComponent>("Terrain Surface Data System", "Manages surface data requests against legacy terrain")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Surface Data")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(0, &TerrainSurfaceDataSystemComponent::m_configuration, "Configuration", "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
;
}
}
}
TerrainSurfaceDataSystemComponent::TerrainSurfaceDataSystemComponent(const TerrainSurfaceDataSystemConfig& configuration)
: m_configuration(configuration)
{
}
TerrainSurfaceDataSystemComponent::TerrainSurfaceDataSystemComponent()
{
}
void TerrainSurfaceDataSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("SurfaceDataProviderService", 0xfe9fb95e));
services.push_back(AZ_CRC("TerrainSurfaceDataProviderService", 0xa1ac7717));
}
void TerrainSurfaceDataSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("TerrainSurfaceDataProviderService", 0xa1ac7717));
}
void TerrainSurfaceDataSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC("SurfaceDataSystemService", 0x1d44d25f));
}
void TerrainSurfaceDataSystemComponent::Activate()
{
m_providerHandle = InvalidSurfaceDataRegistryHandle;
m_system = GetISystem();
CrySystemEventBus::Handler::BusConnect();
AZ::HeightmapUpdateNotificationBus::Handler::BusConnect();
UpdateTerrainData(AZ::Aabb::CreateNull());
}
void TerrainSurfaceDataSystemComponent::Deactivate()
{
if (m_providerHandle != InvalidSurfaceDataRegistryHandle)
{
SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UnregisterSurfaceDataProvider, m_providerHandle);
m_providerHandle = InvalidSurfaceDataRegistryHandle;
}
SurfaceDataProviderRequestBus::Handler::BusDisconnect();
AZ::HeightmapUpdateNotificationBus::Handler::BusDisconnect();
CrySystemEventBus::Handler::BusDisconnect();
m_system = nullptr;
// Clear the cached terrain bounds data
{
m_terrainBounds = AZ::Aabb::CreateNull();
m_terrainBoundsIsValid = false;
}
}
bool TerrainSurfaceDataSystemComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig)
{
if (const auto config = azrtti_cast<const TerrainSurfaceDataSystemConfig*>(baseConfig))
{
m_configuration = *config;
return true;
}
return false;
}
bool TerrainSurfaceDataSystemComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const
{
if (auto config = azrtti_cast<TerrainSurfaceDataSystemConfig*>(outBaseConfig))
{
*config = m_configuration;
return true;
}
return false;
}
void TerrainSurfaceDataSystemComponent::OnCrySystemInitialized(ISystem& system, [[maybe_unused]] const SSystemInitParams& systemInitParams)
{
m_system = &system;
}
void TerrainSurfaceDataSystemComponent::OnCrySystemShutdown([[maybe_unused]] ISystem& system)
{
m_system = nullptr;
}
void TerrainSurfaceDataSystemComponent::GetSurfacePoints(const AZ::Vector3& inPosition, SurfacePointList& surfacePointList) const
{
if (m_terrainBoundsIsValid)
{
auto enumerationCallback = [&](AzFramework::Terrain::TerrainDataRequests* terrain) -> bool
{
if (terrain->GetTerrainAabb().Contains(inPosition))
{
bool isTerrainValidAtPoint = false;
const float terrainHeight = terrain->GetHeight(inPosition, AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR, &isTerrainValidAtPoint);
const bool isHole = !isTerrainValidAtPoint;
SurfacePoint point;
point.m_entityId = GetEntityId();
point.m_position = AZ::Vector3(inPosition.GetX(), inPosition.GetY(), terrainHeight);
point.m_normal = terrain->GetNormal(inPosition);
const AZ::Crc32 terrainTag = isHole ? Constants::s_terrainHoleTagCrc : Constants::s_terrainTagCrc;
AddMaxValueForMasks(point.m_masks, terrainTag, 1.0f);
surfacePointList.push_back(point);
}
// Only one handler should exist.
return false;
};
AzFramework::Terrain::TerrainDataRequestBus::EnumerateHandlers(enumerationCallback);
}
}
AZ::Aabb TerrainSurfaceDataSystemComponent::GetSurfaceAabb() const
{
auto terrain = AzFramework::Terrain::TerrainDataRequestBus::FindFirstHandler();
return terrain ? terrain->GetTerrainAabb() : AZ::Aabb::CreateNull();
}
SurfaceTagVector TerrainSurfaceDataSystemComponent::GetSurfaceTags() const
{
SurfaceTagVector tags;
tags.push_back(Constants::s_terrainHoleTagCrc);
tags.push_back(Constants::s_terrainTagCrc);
return tags;
}
void TerrainSurfaceDataSystemComponent::UpdateTerrainData(const AZ::Aabb& dirtyRegion)
{
bool terrainValidBeforeUpdate = m_terrainBoundsIsValid;
bool terrainValidAfterUpdate = false;
AZ::Aabb terrainBoundsBeforeUpdate = m_terrainBounds;
SurfaceDataRegistryEntry registryEntry;
registryEntry.m_entityId = GetEntityId();
registryEntry.m_bounds = GetSurfaceAabb();
registryEntry.m_tags = GetSurfaceTags();
m_terrainBounds = registryEntry.m_bounds;
m_terrainBoundsIsValid = m_terrainBounds.IsValid();
terrainValidAfterUpdate = m_terrainBoundsIsValid;
if (terrainValidBeforeUpdate && terrainValidAfterUpdate)
{
AZ_Assert((m_providerHandle != InvalidSurfaceDataRegistryHandle), "Invalid surface data handle");
// Our terrain was valid before and after, it just changed in some way. If we have a valid dirty region passed in
// then it's possible that the heightmap has been modified in the Editor. Otherwise, just notify that the entire
// terrain has changed in some way.
if (dirtyRegion.IsValid())
{
SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::RefreshSurfaceData, dirtyRegion);
}
else
{
SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UpdateSurfaceDataProvider, m_providerHandle, registryEntry);
}
}
else if (!terrainValidBeforeUpdate && terrainValidAfterUpdate)
{
// Our terrain has become valid, so register as a provider and save off the registry handles
AZ_Assert((m_providerHandle == InvalidSurfaceDataRegistryHandle), "Surface Provider data handle is initialized before our terrain became valid");
SurfaceDataSystemRequestBus::BroadcastResult(m_providerHandle, &SurfaceDataSystemRequestBus::Events::RegisterSurfaceDataProvider, registryEntry);
// Start listening for surface data events
AZ_Assert((m_providerHandle != InvalidSurfaceDataRegistryHandle), "Invalid surface data handle");
SurfaceDataProviderRequestBus::Handler::BusConnect(m_providerHandle);
}
else if (terrainValidBeforeUpdate && !terrainValidAfterUpdate)
{
// Our terrain has stopped being valid, so unregister and stop listening for surface data events
AZ_Assert((m_providerHandle != InvalidSurfaceDataRegistryHandle), "Invalid surface data handle");
SurfaceDataSystemRequestBus::Broadcast(&SurfaceDataSystemRequestBus::Events::UnregisterSurfaceDataProvider, m_providerHandle);
m_providerHandle = InvalidSurfaceDataRegistryHandle;
SurfaceDataProviderRequestBus::Handler::BusDisconnect();
}
else
{
// We didn't have a valid terrain before or after running this, so do nothing.
}
}
void TerrainSurfaceDataSystemComponent::HeightmapModified(const AZ::Aabb& bounds)
{
UpdateTerrainData(bounds);
}
}
@@ -0,0 +1,88 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/EBus/EBus.h>
#include <CrySystemBus.h>
#include <HeightmapUpdateNotificationBus.h>
#include <SurfaceData/SurfaceDataModifierRequestBus.h>
#include <SurfaceData/SurfaceDataProviderRequestBus.h>
namespace SurfaceData
{
class TerrainSurfaceDataSystemConfig
: public AZ::ComponentConfig
{
public:
AZ_CLASS_ALLOCATOR(TerrainSurfaceDataSystemConfig, AZ::SystemAllocator, 0);
AZ_RTTI(TerrainSurfaceDataSystemConfig, "{2B93F5E5-5346-47A1-9C4D-EFBC6BDF468F}", AZ::ComponentConfig);
static void Reflect(AZ::ReflectContext* context);
};
/**
* The system component to serve for the game side queries for surface values
*/
class TerrainSurfaceDataSystemComponent
: public AZ::Component
, private SurfaceDataProviderRequestBus::Handler
, private AZ::HeightmapUpdateNotificationBus::Handler
, private CrySystemEventBus::Handler
{
friend class EditorTerrainSurfaceDataSystemComponent;
TerrainSurfaceDataSystemComponent(const TerrainSurfaceDataSystemConfig&);
public:
TerrainSurfaceDataSystemComponent();
//////////////////////////////////////////////////////////////////////////
// Component static
AZ_COMPONENT(TerrainSurfaceDataSystemComponent, "{0C821DA4-6DB1-4860-BE25-CB57B3E3F4D4}", AZ::Component);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void Reflect(AZ::ReflectContext* context);
//////////////////////////////////////////////////////////////////////////
// Component
void Activate() override;
void Deactivate() override;
bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override;
bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override;
//////////////////////////////////////////////////////////////////////////
// SurfaceDataProviderRequestBus
void GetSurfacePoints(const AZ::Vector3& inPosition, SurfacePointList& surfacePointList) const;
////////////////////////////////////////////////////////////////////////////
// CrySystemEvents
void OnCrySystemInitialized(ISystem& system, const SSystemInitParams& systemInitParams) override;
void OnCrySystemShutdown(ISystem& system) override;
//////////////////////////////////////////////////////////////////////////
// AZ::HeightmapUpdateNotificationBus
void HeightmapModified(const AZ::Aabb& bounds) override;
private:
void UpdateTerrainData(const AZ::Aabb& dirtyRegion);
AZ::Aabb GetSurfaceAabb() const;
SurfaceTagVector GetSurfaceTags() const;
SurfaceDataRegistryHandle m_providerHandle = InvalidSurfaceDataRegistryHandle;
TerrainSurfaceDataSystemConfig m_configuration;
ISystem* m_system = nullptr;
AZ::Aabb m_terrainBounds = AZ::Aabb::CreateNull();
AZStd::atomic_bool m_terrainBoundsIsValid{ false };
};
}
@@ -0,0 +1,360 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SurfaceData_precompiled.h"
#include <SurfaceData/Tests/SurfaceDataTestMocks.h>
#include <AzTest/AzTest.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/Math/MathUtils.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Component/TransformBus.h>
#include <Source/Components/SurfaceDataColliderComponent.h>
#include <AzFramework/Physics/Casts.h>
#include <AzFramework/Physics/Shape.h>
#include <AzFramework/Physics/WorldBodyBus.h>
namespace UnitTest
{
// Mock out a generic Physics Collider Component, which is a required dependency for adding a SurfaceDataColliderComponent.
struct MockPhysicsColliderComponent
: public AZ::Component
{
public:
AZ_COMPONENT(MockPhysicsColliderComponent, "{4F7C36DE-6475-4E0A-96A7-BFAF21C07C95}", AZ::Component);
void Activate() override {}
void Deactivate() override {}
static void Reflect(AZ::ReflectContext* reflect) { AZ_UNUSED(reflect); }
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("PhysXColliderService", 0x4ff43f7c));
}
};
class MockPhysicsWorldBusProvider
: public Physics::WorldBodyRequestBus::Handler
{
public:
MockPhysicsWorldBusProvider(const AZ::EntityId& id, AZ::Vector3 inPosition, bool setHitResult, const SurfaceData::SurfacePoint& hitResult)
{
Physics::WorldBodyRequestBus::Handler::BusConnect(id);
// Whether or not the test should return a successful hit, we still want to create a valid
// AABB so that the SurfaceData component registers itself as a provider.
m_aabb = AZ::Aabb::CreateCenterRadius(inPosition, 1.0f);
// Only initialize our mock physics to return a raycast result if the test wants the point to hit.
if (setHitResult)
{
m_rayCastHit.m_distance = 0.0f;
m_rayCastHit.m_position = hitResult.m_position;
m_rayCastHit.m_normal = hitResult.m_normal;
// Just need to set this to a non-null value, it gets checked vs null but not otherwise used.
m_rayCastHit.m_body = reinterpret_cast<Physics::WorldBody*>(1);
}
}
virtual ~MockPhysicsWorldBusProvider()
{
Physics::WorldBodyRequestBus::Handler::BusDisconnect();
}
// Minimal mocks needed to mock out this ebus
void EnablePhysics() override {}
void DisablePhysics() override {}
bool IsPhysicsEnabled() const override { return true; }
Physics::WorldBody* GetWorldBody() override { return nullptr; }
// Functional mocks to mock out the data needed by the component
AZ::Aabb GetAabb() const override { return m_aabb; }
Physics::RayCastHit RayCast([[maybe_unused]] const Physics::RayCastRequest& request) override { return m_rayCastHit; }
AZ::Aabb m_aabb = AZ::Aabb::CreateNull();
Physics::RayCastHit m_rayCastHit;
};
// Provide a set of common helper methods for our tests.
struct SurfaceDataTestFixture
: public SurfaceDataTest
{
protected:
// Create a new SurfacePoint with the given fields.
SurfaceData::SurfacePoint CreateSurfacePoint(AZ::EntityId id, AZ::Vector3 position, AZ::Vector3 normal, AZStd::vector<AZStd::pair<AZStd::string, float>> tags)
{
SurfaceData::SurfacePoint point;
point.m_entityId = id;
point.m_position = position;
point.m_normal = normal;
for (auto& tag : tags)
{
point.m_masks[SurfaceData::SurfaceTag(tag.first)] = tag.second;
}
return point;
}
// Compare two surface points.
bool SurfacePointsAreEqual(const SurfaceData::SurfacePoint& lhs, const SurfaceData::SurfacePoint& rhs)
{
return (lhs.m_entityId == rhs.m_entityId)
&& (lhs.m_position == rhs.m_position)
&& (lhs.m_normal == rhs.m_normal)
&& (lhs.m_masks == rhs.m_masks);
}
// Common test function for testing the "Provider" functionality of the component.
// Given a set of tags and an expected output, check to see if the component provides the
// expected output point.
void TestSurfaceDataColliderProvider(AZStd::vector<AZStd::string> providerTags, bool pointOnProvider,
AZ::Vector3 queryPoint, const SurfaceData::SurfacePoint& expectedOutput)
{
// This lets our component register with surfaceData successfully.
MockSurfaceDataSystem mockSurfaceDataSystem;
// Create the test configuration for the SurfaceDataColliderComponent component
SurfaceData::SurfaceDataColliderConfig config;
for (auto& tag : providerTags)
{
config.m_providerTags.emplace_back(tag);
}
// Create the test entity with the SurfaceDataCollider component and the required physics collider dependency
auto entity = CreateEntity();
// Initialize our Entity ID to the one passed in on the expectedOutput
entity->SetId(expectedOutput.m_entityId);
// Create the components
CreateComponent<MockPhysicsColliderComponent>(entity.get());
CreateComponent<SurfaceData::SurfaceDataColliderComponent>(entity.get(), config);
// Before activating the entity, set up our mock physics provider for this entity
MockPhysicsWorldBusProvider mockPhysics(entity->GetId(), expectedOutput.m_position, pointOnProvider, expectedOutput);
// Now that our mocks are set up, activate the entity.
ActivateEntity(entity.get());
// Get our registered provider handle (and verify that it's valid)
auto providerHandle = mockSurfaceDataSystem.GetSurfaceProviderHandle(entity->GetId());
EXPECT_TRUE(providerHandle != SurfaceData::InvalidSurfaceDataRegistryHandle);
// Call GetSurfacePoints and verify the results
SurfaceData::SurfacePointList pointList;
SurfaceData::SurfaceDataProviderRequestBus::Event(providerHandle, &SurfaceData::SurfaceDataProviderRequestBus::Events::GetSurfacePoints,
queryPoint, pointList);
if (pointOnProvider)
{
ASSERT_TRUE(pointList.size() == 1);
EXPECT_TRUE(SurfacePointsAreEqual(pointList[0], expectedOutput));
}
else
{
EXPECT_TRUE(pointList.empty());
}
}
void TestSurfaceDataColliderModifier(AZStd::vector<AZStd::string> modifierTags,
const SurfaceData::SurfacePoint& input, bool pointInCollider, const SurfaceData::SurfacePoint& expectedOutput)
{
// This lets our component register with surfaceData successfully.
MockSurfaceDataSystem mockSurfaceDataSystem;
// Create the test configuration for the SurfaceDataColliderComponent component
SurfaceData::SurfaceDataColliderConfig config;
for (auto& tag : modifierTags)
{
config.m_modifierTags.emplace_back(tag);
}
// Create the test entity with the SurfaceDataCollider component and the required physics collider dependency
auto entity = CreateEntity();
CreateComponent<MockPhysicsColliderComponent>(entity.get());
CreateComponent<SurfaceData::SurfaceDataColliderComponent>(entity.get(), config);
// Before activating the entity, set up our mock physics provider for this entity
MockPhysicsWorldBusProvider mockPhysics(entity->GetId(), input.m_position, pointInCollider, expectedOutput);
// Now that our mocks are set up, activate the entity.
ActivateEntity(entity.get());
// Get our registered modifier handle (and verify that it's valid)
auto modifierHandle = mockSurfaceDataSystem.GetSurfaceModifierHandle(entity->GetId());
EXPECT_TRUE(modifierHandle != SurfaceData::InvalidSurfaceDataRegistryHandle);
// Call ModifySurfacePoints and verify the results
SurfaceData::SurfacePointList pointList;
pointList.emplace_back(input);
SurfaceData::SurfaceDataModifierRequestBus::Event(modifierHandle, &SurfaceData::SurfaceDataModifierRequestBus::Events::ModifySurfacePoints, pointList);
ASSERT_TRUE(pointList.size() == 1);
EXPECT_TRUE(SurfacePointsAreEqual(pointList[0], expectedOutput));
}
};
TEST_F(SurfaceDataTestFixture, SurfaceDataColliderComponent_CreateComponent)
{
// Verify that we can trivially create and destroy the component.
// This lets our component potentially register with surfaceData successfully.
MockSurfaceDataSystem mockSurfaceDataSystem;
// Create an empty configuration for the SurfaceDataColliderComponent component
SurfaceData::SurfaceDataColliderConfig config;
// Create the test entity with the SurfaceDataCollider component with the required PhysicsCollider dependency
auto entity = CreateEntity();
CreateComponent<MockPhysicsColliderComponent>(entity.get());
CreateComponent<SurfaceData::SurfaceDataColliderComponent>(entity.get(), config);
ActivateEntity(entity.get());
// Verify that we haven't registered as a provider or modifier, because we never mocked up a valid AABB
// for this collider.
auto providerHandle = mockSurfaceDataSystem.GetSurfaceProviderHandle(entity->GetId());
auto modifierHandle = mockSurfaceDataSystem.GetSurfaceModifierHandle(entity->GetId());
EXPECT_TRUE(providerHandle == SurfaceData::InvalidSurfaceDataRegistryHandle);
EXPECT_TRUE(modifierHandle == SurfaceData::InvalidSurfaceDataRegistryHandle);
}
TEST_F(SurfaceDataTestFixture, SurfaceDataColliderComponent_ProvidePointOnCollider)
{
// Verify that for a point on the collider, the output point contains the correct tag and value.
// Set the expected output to an arbitrary entity ID, position, and normal.
// We'll use this to initialize the mock physics, so the output of the query should match.
const char* tag = "test_mask";
SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3::CreateAxisZ(),
{ AZStd::make_pair<AZStd::string, float>(tag, 1.0f) });
// Query from the same XY, but one unit higher on Z, just so we can verify that the output returns the collision
// result, not the input point.
constexpr bool pointOnCollider = true;
TestSurfaceDataColliderProvider({ tag }, pointOnCollider, expectedOutput.m_position + AZ::Vector3::CreateAxisZ(), expectedOutput);
}
TEST_F(SurfaceDataTestFixture, SurfaceDataColliderComponent_DoNotProvidePointNotOnCollider)
{
// Verify that for a point not on the collider, the output point is empty.
// Set the expected output to an arbitrary entity ID, position, and normal.
// We'll use this to initialize the mock physics.
const char* tag = "test_mask";
SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3::CreateAxisZ(),
{ AZStd::make_pair<AZStd::string, float>(tag, 1.0f) });
// Query from the same XY, but one unit higher on Z. However, we're also telling our test to provide
// a "no hit" result from physics, so the expectedOutput will be ignored on the result check, and instead
// the output will be verified to be an empty list of points.
constexpr bool pointOnCollider = true;
TestSurfaceDataColliderProvider({ tag }, !pointOnCollider, expectedOutput.m_position + AZ::Vector3::CreateAxisZ(), expectedOutput);
}
TEST_F(SurfaceDataTestFixture, SurfaceDataColliderComponent_ProvidePointOnColliderWithMultipleTags)
{
// Verify that if the component has multiple tags, all of them get put on the output with the same value.
// Set the expected output to an arbitrary entity ID, position, and normal.
// We'll use this to initialize the mock physics.
const char* tag1 = "test_mask1";
const char* tag2 = "test_mask2";
SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3::CreateAxisZ(),
{ AZStd::make_pair<AZStd::string, float>(tag1, 1.0f),
AZStd::make_pair<AZStd::string, float>(tag2, 1.0f) });
// Query from the same XY, but one unit higher on Z, just so we can verify that the output returns the collision
// result, not the input point.
constexpr bool pointOnCollider = true;
TestSurfaceDataColliderProvider({ tag1, tag2 }, pointOnCollider, expectedOutput.m_position + AZ::Vector3::CreateAxisZ(), expectedOutput);
}
TEST_F(SurfaceDataTestFixture, SurfaceDataColliderComponent_ModifyPointInCollider)
{
// Verify that for a point inside the collider, the output point contains the correct tag and value.
// Set arbitrary input data
SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), {});
// Output should match the input, but with an added tag / value
const char* tag = "test_mask";
SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(input.m_entityId, input.m_position, input.m_normal,
{ AZStd::make_pair<AZStd::string, float>(tag, 1.0f) });
constexpr bool pointInCollider = true;
TestSurfaceDataColliderModifier({ tag }, input, pointInCollider, expectedOutput);
}
TEST_F(SurfaceDataTestFixture, SurfaceDataColliderComponent_DoNotModifyPointOutsideCollider)
{
// Verify that for a point outside the collider, the output point contains no tags / values.
// Set arbitrary input data
SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), {});
// Output should match the input - no extra tags / values should be added.
const char* tag = "test_mask";
SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(input.m_entityId, input.m_position, input.m_normal, {});
constexpr bool pointInCollider = true;
TestSurfaceDataColliderModifier({ tag }, input, !pointInCollider, expectedOutput);
}
TEST_F(SurfaceDataTestFixture, SurfaceDataColliderComponent_ModifyPointInColliderWithMultipleTags)
{
// Verify that if the component has multiple tags, all of them get put on the output with the same value.
// Set arbitrary input data
SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), {});
// Output should match the input, but with two added tags
const char* tag1 = "test_mask1";
const char* tag2 = "test_mask2";
SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(input.m_entityId, input.m_position, input.m_normal,
{ AZStd::make_pair<AZStd::string, float>(tag1, 1.0f), AZStd::make_pair<AZStd::string, float>(tag2, 1.0f) });
constexpr bool pointInCollider = true;
TestSurfaceDataColliderModifier({ tag1, tag2 }, input, pointInCollider, expectedOutput);
}
TEST_F(SurfaceDataTestFixture, SurfaceDataColliderComponent_ModifierPreservesInputTags)
{
// Verify that the output contains input tags that are NOT on the modification list and adds any
// new tags that weren't in the input
// Set arbitrary input data
const char* preservedTag = "preserved_tag";
SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f),
{ AZStd::make_pair<AZStd::string, float>(preservedTag, 1.0f) });
// Output should match the input, but with two added tags
const char* modifierTag = "modifier_tag";
SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(input.m_entityId, input.m_position, input.m_normal,
{ AZStd::make_pair<AZStd::string, float>(preservedTag, 1.0f), AZStd::make_pair<AZStd::string, float>(modifierTag, 1.0f) });
constexpr bool pointInCollider = true;
TestSurfaceDataColliderModifier({ modifierTag }, input, pointInCollider, expectedOutput);
}
TEST_F(SurfaceDataTestFixture, SurfaceDataColliderComponent_KeepsHigherValueFromModifier)
{
// Verify that if the input has a lower value on the tag than the modifier, it keeps the higher value.
const char* tag = "test_mask";
// Select an input value that's lower than the collider value
float inputValue = 0.25f;
// Set arbitrary input data
SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f),
{ AZStd::make_pair<AZStd::string, float>(tag, inputValue) });
// Output should match the input, except that the value on the tag gets the higher modifier value
SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(input.m_entityId, input.m_position, input.m_normal,
{ AZStd::make_pair<AZStd::string, float>(tag, 1.0f) });
constexpr bool pointInCollider = true;
TestSurfaceDataColliderModifier({ tag }, input, pointInCollider, expectedOutput);
}
}
@@ -0,0 +1,752 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SurfaceData_precompiled.h"
#include <AzTest/AzTest.h>
#include <Mocks/ITimerMock.h>
#include <Mocks/ICryPakMock.h>
#include <Mocks/IConsoleMock.h>
#include <Mocks/ISystemMock.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Math/Random.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Script/ScriptContext.h>
#include <AzCore/std/chrono/clocks.h>
#include <SurfaceDataSystemComponent.h>
#include <SurfaceDataModule.h>
#include <SurfaceData/SurfaceDataProviderRequestBus.h>
#include <SurfaceData/SurfaceDataModifierRequestBus.h>
#include <SurfaceData/SurfaceTag.h>
#include <SurfaceData/Utility/SurfaceDataUtility.h>
struct MockGlobalEnvironment
{
MockGlobalEnvironment()
{
m_stubEnv.pTimer = &m_stubTimer;
m_stubEnv.pCryPak = &m_stubPak;
m_stubEnv.pConsole = &m_stubConsole;
m_stubEnv.pSystem = &m_stubSystem;
m_stubEnv.p3DEngine = nullptr;
gEnv = &m_stubEnv;
}
~MockGlobalEnvironment()
{
gEnv = nullptr;
}
private:
SSystemGlobalEnvironment m_stubEnv;
testing::NiceMock<TimerMock> m_stubTimer;
testing::NiceMock<CryPakMock> m_stubPak;
testing::NiceMock<ConsoleMock> m_stubConsole;
testing::NiceMock<SystemMock> m_stubSystem;
};
// Simple class for mocking out a surface provider, so that we can control exactly what points we expect to query in our tests.
// This can be used to either provide a surface or modify a surface.
class MockSurfaceProvider
: private SurfaceData::SurfaceDataProviderRequestBus::Handler
, private SurfaceData::SurfaceDataModifierRequestBus::Handler
{
public:
enum class ProviderType
{
SURFACE_PROVIDER,
SURFACE_MODIFIER
};
MockSurfaceProvider(ProviderType providerType, const SurfaceData::SurfaceTagVector& surfaceTags,
AZ::Vector3 start, AZ::Vector3 end, AZ::Vector3 stepSize,
AZ::EntityId id = AZ::EntityId(0x12345678))
{
m_tags = surfaceTags;
m_providerType = providerType;
m_id = id;
SetPoints(start, end, stepSize);
Register();
}
~MockSurfaceProvider()
{
Unregister();
}
private:
AZStd::unordered_map<AZStd::pair<float, float>, SurfaceData::SurfacePointList> m_GetSurfacePoints;
SurfaceData::SurfaceTagVector m_tags;
ProviderType m_providerType;
AZ::EntityId m_id;
void SetPoints(AZ::Vector3 start, AZ::Vector3 end, AZ::Vector3 stepSize)
{
m_GetSurfacePoints.clear();
// Create a set of points that go from start to end (exclusive), with one
// point per step size.
// The XY values create new SurfacePoint entries, the Z values are used to create
// the list of surface points at each XY input point.
for (float y = start.GetY(); y < end.GetY(); y += stepSize.GetY())
{
for (float x = start.GetX(); x < end.GetX(); x += stepSize.GetX())
{
SurfaceData::SurfacePointList points;
for (float z = start.GetZ(); z < end.GetZ(); z += stepSize.GetZ())
{
SurfaceData::SurfacePoint point;
point.m_entityId = m_id;
point.m_position = AZ::Vector3(x, y, z);
point.m_normal = AZ::Vector3::CreateAxisZ();
AddMaxValueForMasks(point.m_masks, m_tags, 1.0f);
points.push_back(point);
}
m_GetSurfacePoints[AZStd::pair<float, float>(x, y)] = points;
}
}
}
AZ::Aabb GetBounds()
{
AZ::Aabb bounds = AZ::Aabb::CreateNull();
for (auto& entry : m_GetSurfacePoints)
{
for (auto& point : entry.second)
{
bounds.AddPoint(point.m_position);
}
}
return bounds;
}
void Register()
{
SurfaceData::SurfaceDataRegistryEntry registryEntry;
registryEntry.m_entityId = m_id;
registryEntry.m_bounds = GetBounds();
registryEntry.m_tags = m_tags;
m_providerHandle = SurfaceData::InvalidSurfaceDataRegistryHandle;
if (m_providerType == ProviderType::SURFACE_PROVIDER)
{
SurfaceData::SurfaceDataSystemRequestBus::BroadcastResult(m_providerHandle, &SurfaceData::SurfaceDataSystemRequestBus::Events::RegisterSurfaceDataProvider, registryEntry);
SurfaceData::SurfaceDataProviderRequestBus::Handler::BusConnect(m_providerHandle);
}
else
{
SurfaceData::SurfaceDataSystemRequestBus::BroadcastResult(m_providerHandle, &SurfaceData::SurfaceDataSystemRequestBus::Events::RegisterSurfaceDataModifier, registryEntry);
SurfaceData::SurfaceDataModifierRequestBus::Handler::BusConnect(m_providerHandle);
}
}
void Unregister()
{
if (m_providerType == ProviderType::SURFACE_PROVIDER)
{
SurfaceData::SurfaceDataProviderRequestBus::Handler::BusDisconnect();
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::UnregisterSurfaceDataProvider, m_providerHandle);
}
else
{
SurfaceData::SurfaceDataModifierRequestBus::Handler::BusDisconnect();
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::UnregisterSurfaceDataModifier, m_providerHandle);
}
m_providerHandle = SurfaceData::InvalidSurfaceDataRegistryHandle;
}
//////////////////////////////////////////////////////////////////////////
// SurfaceDataProviderRequestBus
void GetSurfacePoints(const AZ::Vector3& inPosition, SurfaceData::SurfacePointList& surfacePointList) const override
{
auto surfacePoints = m_GetSurfacePoints.find(AZStd::make_pair(inPosition.GetX(), inPosition.GetY()));
if (surfacePoints != m_GetSurfacePoints.end())
{
for (auto& point : surfacePoints->second)
{
surfacePointList.push_back(point);
}
}
}
//////////////////////////////////////////////////////////////////////////
// SurfaceDataModifierRequestBus
void ModifySurfacePoints(SurfaceData::SurfacePointList& surfacePointList) const override
{
for (auto& point : surfacePointList)
{
auto surfacePoints = m_GetSurfacePoints.find(AZStd::make_pair(point.m_position.GetX(), point.m_position.GetY()));
if (surfacePoints != m_GetSurfacePoints.end())
{
AddMaxValueForMasks(point.m_masks, m_tags, 1.0f);
}
}
}
SurfaceData::SurfaceDataRegistryHandle m_providerHandle = SurfaceData::InvalidSurfaceDataRegistryHandle;
};
TEST(SurfaceDataTest, ComponentsWithComponentApplication)
{
AZ::ComponentApplication::Descriptor appDesc;
appDesc.m_memoryBlocksByteSize = 10 * 1024 * 1024;
appDesc.m_recordingMode = AZ::Debug::AllocationRecords::RECORD_FULL;
appDesc.m_stackRecordLevels = 20;
MockGlobalEnvironment mocks;
AZ::ComponentApplication app;
AZ::Entity* systemEntity = app.Create(appDesc);
ASSERT_TRUE(systemEntity != nullptr);
app.RegisterComponentDescriptor(SurfaceData::SurfaceDataSystemComponent::CreateDescriptor());
systemEntity->CreateComponent<SurfaceData::SurfaceDataSystemComponent>();
systemEntity->Init();
systemEntity->Activate();
app.Destroy();
ASSERT_TRUE(true);
}
class SurfaceDataTestApp
: public ::testing::Test
{
public:
SurfaceDataTestApp()
: m_application()
, m_systemEntity(nullptr)
{
}
void SetUp() override
{
AZ::ComponentApplication::Descriptor appDesc;
appDesc.m_memoryBlocksByteSize = 50 * 1024 * 1024;
appDesc.m_recordingMode = AZ::Debug::AllocationRecords::RECORD_FULL;
appDesc.m_stackRecordLevels = 20;
AZ::ComponentApplication::StartupParameters appStartup;
appStartup.m_createStaticModulesCallback =
[](AZStd::vector<AZ::Module*>& modules)
{
modules.emplace_back(new SurfaceData::SurfaceDataModule);
};
m_systemEntity = m_application.Create(appDesc, appStartup);
m_systemEntity->Init();
m_systemEntity->Activate();
}
void TearDown() override
{
m_application.Destroy();
}
bool ValidateRegionListSize(AZ::Aabb bounds, AZ::Vector2 stepSize, const SurfaceData::SurfacePointListPerPosition& outputList)
{
// We expect the output list to contain width * height output entries.
// The right edge of the AABB should be treated as exclusive, so a 4x4 box with 1 step size will produce 16 entries (0, 1, 2, 3 on each dimension),
// but a 4.1 x 4.1 box with 1 step size will produce 25 entries (0, 1, 2, 3, 4 on each dimension).
return (outputList.size() == aznumeric_cast<size_t>(ceil(bounds.GetXExtent() * stepSize.GetX()) * ceil(bounds.GetYExtent() * stepSize.GetY())));
}
AZ::ComponentApplication m_application;
AZ::Entity* m_systemEntity;
MockGlobalEnvironment m_mocks;
// Test Surface Data tags that we can use for testing query functionality
const AZ::Crc32 m_testSurface1Crc = AZ::Crc32("test_surface1");
const AZ::Crc32 m_testSurface2Crc = AZ::Crc32("test_surface2");
const AZ::Crc32 m_testSurfaceNoMatchCrc = AZ::Crc32("test_surface_no_match");
};
TEST_F(SurfaceDataTestApp, SurfaceData_TestRegisteredTags)
{
AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>> registeredTags = SurfaceData::SurfaceTag::GetRegisteredTags();
for (const auto& searchTerm : SurfaceData::Constants::s_allTagNames)
{
ASSERT_TRUE(AZStd::find_if(registeredTags.begin(), registeredTags.end(), [searchTerm](decltype(registeredTags)::value_type pair)
{
return pair.second == searchTerm;
}));
}
}
#if AZ_TRAIT_DISABLE_FAILED_SURFACE_DATA_TESTS
TEST_F(SurfaceDataTestApp, DISABLED_SurfaceData_TestGetQuadListRayIntersection)
#else
TEST_F(SurfaceDataTestApp, SurfaceData_TestGetQuadListRayIntersection)
#endif // AZ_TRAIT_DISABLE_FAILED_SURFACE_DATA_TESTS
{
AZStd::vector<AZ::Vector3> quads;
AZ::Vector3 outPosition;
AZ::Vector3 outNormal;
bool result;
struct RayTest
{
// Input quad
AZ::Vector3 quadVertices[4];
// Input ray
AZ::Vector3 rayOrigin;
AZ::Vector3 rayDirection;
float rayMaxRange;
// Expected outputs
bool expectedResult;
AZ::Vector3 expectedOutPosition;
AZ::Vector3 expectedOutNormal;
};
RayTest tests[] =
{
// Ray intersects quad
{{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(100.0f, 0.0f, 0.0f), AZ::Vector3(0.0f, 100.0f, 0.0f), AZ::Vector3(100.0f, 100.0f, 0.0f)},
AZ::Vector3(50.0f, 50.0f, 10.0f), AZ::Vector3(0.0f, 0.0f, -1.0f), 20.0f, true, AZ::Vector3(50.0f, 50.0f, 0.0f), AZ::Vector3(0.0f, 0.0f, 1.0f)},
// Ray not long enough to intersect
{{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(100.0f, 0.0f, 0.0f), AZ::Vector3(0.0f, 100.0f, 0.0f), AZ::Vector3(100.0f, 100.0f, 0.0f)},
AZ::Vector3(50.0f, 50.0f, 10.0f), AZ::Vector3(0.0f, 0.0f, -1.0f), 5.0f, false, AZ::Vector3( 0.0f, 0.0f, 0.0f), AZ::Vector3(0.0f, 0.0f, 0.0f)},
// 0-length ray on quad surface
{{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(100.0f, 0.0f, 0.0f), AZ::Vector3(0.0f, 100.0f, 0.0f), AZ::Vector3(100.0f, 100.0f, 0.0f)},
AZ::Vector3(50.0f, 50.0f, 0.0f), AZ::Vector3(0.0f, 0.0f, -1.0f), 0.0f, true, AZ::Vector3(50.0f, 50.0f, 0.0f), AZ::Vector3(0.0f, 0.0f, 1.0f)},
// ray in wrong direction
{{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(100.0f, 0.0f, 0.0f), AZ::Vector3(0.0f, 100.0f, 0.0f), AZ::Vector3(100.0f, 100.0f, 0.0f)},
AZ::Vector3(50.0f, 50.0f, 10.0f), AZ::Vector3(0.0f, 0.0f, 1.0f), 20.0f, false, AZ::Vector3( 0.0f, 0.0f, 0.0f), AZ::Vector3(0.0f, 0.0f, 0.0f)},
// The following tests are specific cases that worked differently when the implementation of GetQuadRayListIntersection used AZ::Intersect::IntersectRayQuad
// instead of IntersectSegmentTriangle. We'll keep them here both as good non-trivial tests and to ensure that if anyone ever tries to change the implmentation,
// they can easily validate whether or not IntersectRayQuad will produce the same results.
// ray passes IntersectSegmentTriangle but fails IntersectRayQuad
{{AZ::Vector3(499.553, 688.946, 48.788), AZ::Vector3(483.758, 698.655, 48.788), AZ::Vector3(498.463, 687.181, 48.916), AZ::Vector3(482.701, 696.942, 48.916)},
AZ::Vector3(485.600, 695.200, 49.501), AZ::Vector3(-0.000f, -0.000f, -1.000f), 18.494f, true, AZ::Vector3(485.600, 695.200, 48.913), AZ::Vector3(0.033, 0.053, 0.998)},
// ray fails IntersectSegmentTriangle but passes IntersectRayQuad
// IntersectRayQuad hits with the following position / normal: AZ::Vector3(480.000, 688.800, 49.295), AZ::Vector3(0.020, 0.032, 0.999)
{{AZ::Vector3(495.245, 681.984, 49.218), AZ::Vector3(479.450, 691.692, 49.218), AZ::Vector3(494.205, 680.282, 49.292), AZ::Vector3(478.356, 689.902, 49.292)},
AZ::Vector3(480.000, 688.800, 49.501), AZ::Vector3(-0.000, -0.000, -1.000), 18.494f, false, AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(0.0f, 0.0f, 0.0f)},
// ray passes IntersectSegmentTriangle and IntersectRayQuad, but hits at different positions
// IntersectRayQuad hits with the following position / normal: AZ::Vector3(498.400, 700.000, 48.073), AZ::Vector3(0.046, 0.085, 0.995)
{{AZ::Vector3(504.909, 698.078, 47.913), AZ::Vector3(488.641, 706.971, 47.913), AZ::Vector3(503.867, 696.206, 48.121), AZ::Vector3(487.733, 705.341, 48.121)},
AZ::Vector3(498.400, 700.000, 49.501), AZ::Vector3(-0.000f, -0.000f, -1.000f), 53.584f, true, AZ::Vector3(498.400, 700.000, 48.062), AZ::Vector3(0.048, 0.084, 0.995)},
// ray passes IntersectSegmentTriangle and IntersectRayQuad, but hits at different normals
// IntersectRayQuad hits with the following position / normal: AZ::Vector3(492.800, 703.200, 48.059), AZ::Vector3(0.046, 0.085, 0.995)
{{AZ::Vector3(504.909, 698.078, 47.913), AZ::Vector3(488.641, 706.971, 47.913), AZ::Vector3(503.867, 696.206, 48.121), AZ::Vector3(487.733, 705.341, 48.121)},
AZ::Vector3(492.800, 703.200, 49.501), AZ::Vector3(-0.000f, -0.000f, -1.000f), 18.494f, true, AZ::Vector3(492.800, 703.200, 48.059), AZ::Vector3(0.053, 0.097, 0.994)},
};
for (const auto &test : tests)
{
quads.clear();
outPosition.Set(0.0f, 0.0f, 0.0f);
outNormal.Set(0.0f, 0.0f, 0.0f);
quads.push_back(test.quadVertices[0]);
quads.push_back(test.quadVertices[1]);
quads.push_back(test.quadVertices[2]);
quads.push_back(test.quadVertices[3]);
result = SurfaceData::GetQuadListRayIntersection(quads, test.rayOrigin, test.rayDirection, test.rayMaxRange, outPosition, outNormal);
ASSERT_TRUE(result == test.expectedResult);
if (result || test.expectedResult)
{
ASSERT_TRUE(outPosition.IsClose(test.expectedOutPosition));
ASSERT_TRUE(outNormal.IsClose(test.expectedOutNormal));
}
}
}
TEST_F(SurfaceDataTestApp, SurfaceData_TestAabbOverlaps2D)
{
// Test to make sure the utility method "AabbOverlaps2D" functions as expected.
struct TestCase
{
enum TestIndex
{
SOURCE_MIN,
SOURCE_MAX,
DEST_MIN,
DEST_MAX,
NUM_PARAMS
};
AZ::Vector3 m_testData[NUM_PARAMS];
bool m_overlaps;
};
TestCase testCases[]
{
// Overlap=TRUE Boxes fully overlap in 3D space
{{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(1.0f, 1.0f, 1.0f), AZ::Vector3(3.0f, 3.0f, 3.0f)}, true },
// Overlap=TRUE Boxes overlap in 2D space, but not 3D
{{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(1.0f, 1.0f, 4.0f), AZ::Vector3(3.0f, 3.0f, 6.0f)}, true},
// Overlap=TRUE Boxes are equal
{{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f)}, true},
// Overlap=TRUE Box contains other box
{{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(1.0f, 1.0f, 1.0f), AZ::Vector3(1.5f, 1.5f, 1.5f)}, true },
// Overlap=FALSE Boxes only overlap in X and Z, not Y
{{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(1.0f, 4.0f, 1.0f), AZ::Vector3(3.0f, 6.0f, 3.0f)}, false},
// Overlap=FALSE Boxes only overlap in Y and Z, not X
{{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(4.0f, 1.0f, 1.0f), AZ::Vector3(6.0f, 3.0f, 3.0f)}, false },
};
for (auto& testCase : testCases)
{
AZ::Aabb box1 = AZ::Aabb::CreateFromMinMax(testCase.m_testData[TestCase::SOURCE_MIN], testCase.m_testData[TestCase::SOURCE_MAX]);
AZ::Aabb box2 = AZ::Aabb::CreateFromMinMax(testCase.m_testData[TestCase::DEST_MIN], testCase.m_testData[TestCase::DEST_MAX]);
// Make sure the test produces the correct result.
// Also make sure it's correct regardless of which order the boxes are passed in.
EXPECT_TRUE(SurfaceData::AabbOverlaps2D(box1, box2) == testCase.m_overlaps);
EXPECT_TRUE(SurfaceData::AabbOverlaps2D(box2, box1) == testCase.m_overlaps);
}
}
TEST_F(SurfaceDataTestApp, SurfaceData_TestAabbContains2D)
{
// Test to make sure the utility method "AabbContains2D" functions as expected.
struct TestCase
{
enum TestIndex
{
BOX_MIN,
BOX_MAX,
POINT,
NUM_PARAMS
};
AZ::Vector3 m_testData[NUM_PARAMS];
bool m_contains;
};
TestCase testCases[]
{
// Contains=TRUE Box and point fully overlap in 3D space
{{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(1.0f, 1.0f, 1.0f)}, true},
// Contains=TRUE Box and point overlap in 2D space, but not 3D
{{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(1.0f, 1.0f, 4.0f)}, true},
// Contains=TRUE Point on box min corner
{{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(0.0f, 0.0f, 0.0f)}, true },
// Contains=TRUE Point on box max corner
{{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(2.0f, 2.0f, 2.0f)}, true},
// Contains=FALSE Box and point only overlap in X and Z, not Y
{{ AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(1.0f, 4.0f, 1.0f) }, false},
// Contains=FALSE Box and point only overlap in Y and Z, not X
{{ AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(4.0f, 1.0f, 1.0f) }, false},
// Contains=FALSE Box and point don't overlap at all
{{ AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(4.0f, 4.0f, 4.0f) }, false},
};
for (auto& testCase : testCases)
{
AZ::Aabb box = AZ::Aabb::CreateFromMinMax(testCase.m_testData[TestCase::BOX_MIN], testCase.m_testData[TestCase::BOX_MAX]);
AZ::Vector3& point = testCase.m_testData[TestCase::POINT];
// Make sure the test produces the correct result.
EXPECT_TRUE(SurfaceData::AabbContains2D(box, point) == testCase.m_contains);
// Test the Vector2 version as well.
EXPECT_TRUE(SurfaceData::AabbContains2D(box, AZ::Vector2(point.GetX(), point.GetY())) == testCase.m_contains);
}
}
TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion)
{
// This tests the basic functionality of GetSurfacePointsFromRegion:
// - The surface points are queried by stepping through an AABB, which is inclusive on one side, and exclusive on the other.
// i.e. (0,0) - (4,4) will include (0,0), but exclude (4,4)
// - The Z range of the input region is ignored when querying for points. (This is consistent with GetSurfacePoints)
// - The output has one list entry per surface point queried
// - The output has the correct expected points and masks
// Create a mock Surface Provider that covers from (0, 0) - (8, 8) in space.
// It defines points spaced 0.25 apart, with heights of 0 and 4, and with the tags "test_surface1" and "test_surface2".
// (We're creating points spaced more densely than we'll query just to verify that we only get back the queried points)
SurfaceData::SurfaceTagVector providerTags = { SurfaceData::SurfaceTag(m_testSurface1Crc), SurfaceData::SurfaceTag(m_testSurface2Crc) };
MockSurfaceProvider mockProvider(MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, providerTags,
AZ::Vector3(0.0f), AZ::Vector3(8.0f), AZ::Vector3(0.25f, 0.25f, 4.0f));
// Query for all the surface points from (0, 0, 16) - (4, 4, 16) with a step size of 1.
// Note that the Z range is deliberately chosen to be outside the surface provider range to demonstrate
// that it is ignored when selecting points.
SurfaceData::SurfacePointListPerPosition availablePointsPerPosition;
AZ::Vector2 stepSize(1.0f, 1.0f);
AZ::Aabb regionBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f, 0.0f, 16.0f), AZ::Vector3(4.0f, 4.0f, 16.0f));
SurfaceData::SurfaceTagVector testTags = providerTags;
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(
&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromRegion,
regionBounds, stepSize, testTags, availablePointsPerPosition);
EXPECT_TRUE(ValidateRegionListSize(regionBounds, stepSize, availablePointsPerPosition));
// We expect every entry in the output list to have two surface points, at heights 0 and 4, sorted in
// decreasing height order. The XY positions should match the query positions, and the masks list should
// be the same size as the set of masks the provider owns. We *could* check every mask as well for completeness,
// but that seems like overkill.
for (auto& queryPosition : availablePointsPerPosition)
{
const SurfaceData::SurfacePointList& pointList = queryPosition.second;
EXPECT_TRUE(pointList.size() == 2);
EXPECT_TRUE(pointList[0].m_position.GetZ() == 4.0f);
EXPECT_TRUE(pointList[1].m_position.GetZ() == 0.0f);
for (auto& point : pointList)
{
EXPECT_TRUE(queryPosition.first.GetX() == point.m_position.GetX());
EXPECT_TRUE(queryPosition.first.GetY() == point.m_position.GetY());
EXPECT_TRUE(point.m_masks.size() == providerTags.size());
}
}
}
TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_NoMatchingMasks)
{
// This test verifies that if we query surfaces with a non-matching mask, the points will get filtered out.
// Create a mock Surface Provider that covers from (0, 0) - (8, 8) in space.
// It defines points spaced 0.25 apart, with heights of 0 and 4, and with the tags "test_surface1" and "test_surface2".
SurfaceData::SurfaceTagVector providerTags = { SurfaceData::SurfaceTag(m_testSurface1Crc), SurfaceData::SurfaceTag(m_testSurface2Crc) };
MockSurfaceProvider mockProvider(MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, providerTags,
AZ::Vector3(0.0f), AZ::Vector3(8.0f), AZ::Vector3(0.25f, 0.25f, 4.0f));
// Query for all the surface points from (0, 0, 0) - (4, 4, 4) with a step size of 1.
// We only include a surface tag that does NOT exist in the surface provider.
SurfaceData::SurfacePointListPerPosition availablePointsPerPosition;
AZ::Vector2 stepSize(1.0f, 1.0f);
AZ::Aabb regionBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f), AZ::Vector3(4.0f));
SurfaceData::SurfaceTagVector testTags = { SurfaceData::SurfaceTag(m_testSurfaceNoMatchCrc) };
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(
&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromRegion,
regionBounds, stepSize, testTags, availablePointsPerPosition);
EXPECT_TRUE(ValidateRegionListSize(regionBounds, stepSize, availablePointsPerPosition));
// We expect every entry in the output list to have no surface points, since the requested mask doesn't match
// any of the masks from our mock surface provider.
for (auto& queryPosition : availablePointsPerPosition)
{
EXPECT_TRUE(queryPosition.second.size() == 0);
}
}
TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_NoMatchingRegion)
{
// This test verifies that if we query surfaces with a non-overlapping region, no points are returned.
// Create a mock Surface Provider that covers from (0,0) - (8, 8) in space.
// It defines points spaced 0.25 apart, with heights of 0 and 4, and with the tags "test_surface1" and "test_surface2".
SurfaceData::SurfaceTagVector providerTags = { SurfaceData::SurfaceTag(m_testSurface1Crc), SurfaceData::SurfaceTag(m_testSurface2Crc) };
MockSurfaceProvider mockProvider(MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, providerTags,
AZ::Vector3(0.0f), AZ::Vector3(8.0f), AZ::Vector3(0.25f, 0.25f, 4.0f));
// Query for all the surface points from (16, 16) - (20, 20) with a step size of 1.
SurfaceData::SurfacePointListPerPosition availablePointsPerPosition;
AZ::Vector2 stepSize(1.0f, 1.0f);
AZ::Aabb regionBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(16.0f), AZ::Vector3(20.0f));
SurfaceData::SurfaceTagVector testTags = providerTags;
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(
&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromRegion,
regionBounds, stepSize, testTags, availablePointsPerPosition);
EXPECT_TRUE(ValidateRegionListSize(regionBounds, stepSize, availablePointsPerPosition));
// We expect every entry in the output list to have no surface points, since the input points don't overlap with
// our surface provider.
for (auto& queryPosition : availablePointsPerPosition)
{
const SurfaceData::SurfacePointList& pointList = queryPosition.second;
EXPECT_TRUE(pointList.size() == 0);
}
}
TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_ProviderModifierMasksCombine)
{
// This test verifies that SurfaceDataModifiers can successfully modify the tags on each point.
// It also verifies that points won't be dropped from the results as long as either the provider
// or the modifier add the correct tag to the point.
// Create a mock Surface Provider that covers from (0,0) - (8, 8) in space.
// It defines points spaced 1 apart, with heights of 0 and 4, and with the tag "test_surface1".
SurfaceData::SurfaceTagVector providerTags = { SurfaceData::SurfaceTag(m_testSurface1Crc) };
MockSurfaceProvider mockProvider(MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, providerTags,
AZ::Vector3(0.0f), AZ::Vector3(8.0f), AZ::Vector3(1.0f, 1.0f, 4.0f));
// Create a mock Surface Modifier that covers from (0,0) - (8, 8) in space.
// It will modify points spaced 1 apart, with heights of 0 and 4, and add the tag "test_surface2".
SurfaceData::SurfaceTagVector modifierTags = { SurfaceData::SurfaceTag(m_testSurface2Crc) };
MockSurfaceProvider mockModifier(MockSurfaceProvider::ProviderType::SURFACE_MODIFIER, modifierTags,
AZ::Vector3(0.0f), AZ::Vector3(8.0f), AZ::Vector3(1.0f, 1.0f, 4.0f));
// Query for all the surface points from (0, 0) - (4, 4) with a step size of 1.
// We perform this test 3 times - once with just the provider tag, once with just the modifier tag,
// and once with both. We expect identical results on each test, since each point should get both
// the provider and the modifier tag.
SurfaceData::SurfaceTagVector tagTests[] =
{
{ SurfaceData::SurfaceTag(m_testSurface1Crc) },
{ SurfaceData::SurfaceTag(m_testSurface2Crc) },
{ SurfaceData::SurfaceTag(m_testSurface1Crc), SurfaceData::SurfaceTag(m_testSurface2Crc) },
};
for (auto& tagTest : tagTests)
{
SurfaceData::SurfacePointListPerPosition availablePointsPerPosition;
AZ::Vector2 stepSize(1.0f, 1.0f);
AZ::Aabb regionBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f), AZ::Vector3(4.0f));
SurfaceData::SurfaceTagVector testTags = tagTest;
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(
&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromRegion,
regionBounds, stepSize, testTags, availablePointsPerPosition);
EXPECT_TRUE(ValidateRegionListSize(regionBounds, stepSize, availablePointsPerPosition));
// We expect every entry in the output list to have two surface points (with heights 0 and 4),
// and each point should have both the "test_surface1" and "test_surface2" tag.
for (auto& queryPosition : availablePointsPerPosition)
{
const SurfaceData::SurfacePointList& pointList = queryPosition.second;
EXPECT_TRUE(pointList.size() == 2);
for (auto& point : pointList)
{
EXPECT_TRUE(point.m_masks.size() == 2);
}
}
}
}
TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_SimilarPointsMergeTogether)
{
// This test verifies that if two separate providers create points at very similar heights, the
// points will get merged together in the results, with the resulting point ending up with both
// sets of tags.
// Create two mock Surface Providers that covers from (0, 0) - (8, 8) in space, with points spaced 0.25 apart.
// The first has heights 0 and 4, with the tag "surfaceTag1". The second has heights 0.005 and 4.005, with the tag "surfaceTag2".
SurfaceData::SurfaceTagVector provider1Tags = { SurfaceData::SurfaceTag(m_testSurface1Crc) };
MockSurfaceProvider mockProvider1(MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, provider1Tags,
AZ::Vector3(0.0f), AZ::Vector3(8.0f), AZ::Vector3(0.25f, 0.25f, 4.0f),
AZ::EntityId(0x11111111));
SurfaceData::SurfaceTagVector provider2Tags = { SurfaceData::SurfaceTag(m_testSurface2Crc) };
MockSurfaceProvider mockProvider2(MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, provider2Tags,
AZ::Vector3(0.0f, 0.0f, 0.0f + (AZ::Constants::Tolerance / 2.0f)),
AZ::Vector3(8.0f, 8.0f, 8.0f + (AZ::Constants::Tolerance / 2.0f)),
AZ::Vector3(0.25f, 0.25f, 4.0f),
AZ::EntityId(0x22222222));
// Query for all the surface points from (0, 0) - (4, 4) with a step size of 1.
SurfaceData::SurfacePointListPerPosition availablePointsPerPosition;
AZ::Vector2 stepSize(1.0f, 1.0f);
AZ::Aabb regionBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f), AZ::Vector3(4.0f));
SurfaceData::SurfaceTagVector testTags = { SurfaceData::SurfaceTag(m_testSurface1Crc), SurfaceData::SurfaceTag(m_testSurface2Crc) };
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(
&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromRegion,
regionBounds, stepSize, testTags, availablePointsPerPosition);
EXPECT_TRUE(ValidateRegionListSize(regionBounds, stepSize, availablePointsPerPosition));
// We expect every entry in the output list to have two surface points, not four. The two points
// should have both surface tags on them.
for (auto& queryPosition : availablePointsPerPosition)
{
const SurfaceData::SurfacePointList& pointList = queryPosition.second;
EXPECT_TRUE(pointList.size() == 2);
for (auto& point : pointList)
{
EXPECT_TRUE(point.m_masks.size() == 2);
}
}
}
TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_DissimilarPointsDoNotMergeTogether)
{
// This test verifies that if two separate providers create points at dissimilar heights, the
// points will NOT get merged together in the results.
// Create two mock Surface Providers that covers from (0, 0) - (8, 8) in space, with points spaced 0.25 apart.
// The first has heights 0 and 4, with the tag "surfaceTag1". The second has heights 0.02 and 4.02, with the tag "surfaceTag2".
SurfaceData::SurfaceTagVector provider1Tags = { SurfaceData::SurfaceTag(m_testSurface1Crc) };
MockSurfaceProvider mockProvider1(MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, provider1Tags,
AZ::Vector3(0.0f), AZ::Vector3(8.0f), AZ::Vector3(0.25f, 0.25f, 4.0f),
AZ::EntityId(0x11111111));
SurfaceData::SurfaceTagVector provider2Tags = { SurfaceData::SurfaceTag(m_testSurface2Crc) };
MockSurfaceProvider mockProvider2(MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, provider2Tags,
AZ::Vector3(0.0f, 0.0f, 0.0f + (AZ::Constants::Tolerance * 2.0f)),
AZ::Vector3(8.0f, 8.0f, 8.0f + (AZ::Constants::Tolerance * 2.0f)),
AZ::Vector3(0.25f, 0.25f, 4.0f),
AZ::EntityId(0x22222222));
// Query for all the surface points from (0, 0) - (4, 4) with a step size of 1.
SurfaceData::SurfacePointListPerPosition availablePointsPerPosition;
AZ::Vector2 stepSize(1.0f, 1.0f);
AZ::Aabb regionBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f), AZ::Vector3(4.0f));
SurfaceData::SurfaceTagVector testTags = { SurfaceData::SurfaceTag(m_testSurface1Crc), SurfaceData::SurfaceTag(m_testSurface2Crc) };
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(
&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromRegion,
regionBounds, stepSize, testTags, availablePointsPerPosition);
EXPECT_TRUE(ValidateRegionListSize(regionBounds, stepSize, availablePointsPerPosition));
// We expect every entry in the output list to have four surface points with one tag each,
// because the points are far enough apart that they won't merge.
for (auto& queryPosition : availablePointsPerPosition)
{
const SurfaceData::SurfacePointList& pointList = queryPosition.second;
EXPECT_TRUE(pointList.size() == 4);
for (auto& point : pointList)
{
EXPECT_TRUE(point.m_masks.size() == 1);
}
}
}
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
@@ -0,0 +1,27 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
Source/SurfaceDataEditorModule.cpp
Source/SurfaceDataEditorModule.h
Source/Editor/EditorSurfaceTagListAsset.cpp
Source/Editor/EditorSurfaceTagListAsset.h
Source/Editor/EditorSurfaceDataSystemComponent.cpp
Source/Editor/EditorSurfaceDataSystemComponent.h
Source/Editor/EditorSurfaceDataColliderComponent.cpp
Source/Editor/EditorSurfaceDataColliderComponent.h
Source/Editor/EditorSurfaceDataMeshComponent.cpp
Source/Editor/EditorSurfaceDataMeshComponent.h
Source/Editor/EditorSurfaceDataShapeComponent.cpp
Source/Editor/EditorSurfaceDataShapeComponent.h
Source/SurfaceDataModule.cpp
Source/SurfaceDataModule.h
)
@@ -0,0 +1,36 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
Source/SurfaceData_precompiled.cpp
Source/SurfaceData_precompiled.h
Include/SurfaceData/SurfaceDataConstants.h
Include/SurfaceData/SurfaceDataTypes.h
Include/SurfaceData/SurfaceDataSystemRequestBus.h
Include/SurfaceData/SurfaceDataSystemNotificationBus.h
Include/SurfaceData/SurfaceDataTagEnumeratorRequestBus.h
Include/SurfaceData/SurfaceDataTagProviderRequestBus.h
Include/SurfaceData/SurfaceDataProviderRequestBus.h
Include/SurfaceData/SurfaceDataModifierRequestBus.h
Include/SurfaceData/SurfaceTag.h
Include/SurfaceData/Utility/SurfaceDataUtility.h
Source/SurfaceDataSystemComponent.cpp
Source/SurfaceDataSystemComponent.h
Source/TerrainSurfaceDataSystemComponent.cpp
Source/TerrainSurfaceDataSystemComponent.h
Source/SurfaceTag.cpp
Source/Components/SurfaceDataColliderComponent.cpp
Source/Components/SurfaceDataColliderComponent.h
Source/Components/SurfaceDataMeshComponent.cpp
Source/Components/SurfaceDataMeshComponent.h
Source/Components/SurfaceDataShapeComponent.cpp
Source/Components/SurfaceDataShapeComponent.h
)
@@ -0,0 +1,15 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
Source/SurfaceDataModule.h
Source/SurfaceDataModule.cpp
)
@@ -0,0 +1,18 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
Include/SurfaceData/Tests/SurfaceDataTestMocks.h
Tests/SurfaceDataColliderComponentTest.cpp
Tests/SurfaceDataTest.cpp
Source/SurfaceDataModule.cpp
Source/SurfaceDataModule.h
)