/******************************************************************************* The content of this file includes portions of the proprietary AUDIOKINETIC Wwise Technology released in source code form as part of the game integration package. The content of this file may not be used without valid licenses to the AUDIOKINETIC Wwise Technology. Note that the use of the game engine is subject to the Unreal(R) Engine End User License Agreement at https://www.unrealengine.com/en-US/eula/unreal License Usage Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use this file in accordance with the end user license agreement provided with the software or, alternatively, in accordance with the terms contained in a written agreement between you and Audiokinetic Inc. Copyright (c) 2023 Audiokinetic Inc. *******************************************************************************/ #include "AkGeometryComponent.h" #include "AkAcousticTexture.h" #include "AkAudioDevice.h" #include "AkComponentHelpers.h" #include "AkReverbDescriptor.h" #include "AkRoomComponent.h" #include "AkSettings.h" #include "WwiseUEFeatures.h" #if AK_USE_PHYSX #include "PhysXPublic.h" #endif #if AK_USE_CHAOS #include "ChaosLog.h" #include "Chaos/CollisionConvexMesh.h" #include "Chaos/Convex.h" #endif #if WITH_EDITOR #include "Editor.h" #endif #include "RawIndexBuffer.h" #include "StaticMeshResources.h" #include "Components/StaticMeshComponent.h" #include "Engine/GameEngine.h" #include "Engine/Polys.h" #include "Engine/StaticMesh.h" #include "PhysicsEngine/BodySetup.h" #include "UObject/Object.h" static const float kVertexNear = 0.0001; UAkGeometryComponent::UAkGeometryComponent(const class FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { // Property initialization bWantsOnUpdateTransform = true; MeshType = AkMeshType::CollisionMesh; LOD = 0; CollisionMeshSurfaceOverride.AcousticTexture = nullptr; CollisionMeshSurfaceOverride.bEnableOcclusionOverride = false; CollisionMeshSurfaceOverride.OcclusionValue = 1.f; WeldingThreshold = 0.001; bWasAddedByRoom = 0; bEnableDiffraction = 1; bEnableDiffractionOnBoundaryEdges = 0; #if WITH_EDITOR PrimaryComponentTick.bCanEverTick = true; bTickInEditor = true; #endif } void UAkGeometryComponent::OnComponentDestroyed(bool bDestroyingHierarchy) { Super::OnComponentDestroyed(bDestroyingHierarchy); RemoveGeometry(); StaticMeshSurfaceOverride.Empty(); #if WITH_EDITOR check(!OnMeshMaterialChangedHandle.IsValid()); #endif } void UAkGeometryComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) { Super::OnUpdateTransform(UpdateTransformFlags, Teleport); UpdateGeometry(); if (ReverbDescriptor != nullptr) { DampingEstimationNeedsUpdate = true; } } bool UAkGeometryComponent::MoveComponentImpl( const FVector & Delta, const FQuat & NewRotation, bool bSweep, FHitResult * Hit, EMoveComponentFlags MoveFlags, ETeleportType Teleport) { if (AkComponentHelpers::DoesMovementRecenterChild(this, Parent, Delta)) Super::MoveComponentImpl(Delta, NewRotation, bSweep, Hit, MoveFlags, Teleport); return false; } void UAkGeometryComponent::BeginPlay() { Super::BeginPlay(); if (!IsBeingDestroyed()) { #if WITH_EDITOR if (AkComponentHelpers::ShouldDeferBeginPlay(this)) bRequiresDeferredBeginPlay = true; else BeginPlayInternal(); #else BeginPlayInternal(); #endif } } void UAkGeometryComponent::BeginPlayInternal() { if (Parent == nullptr) InitializeParent(); if (GeometryData.Vertices.Num() == 0) ConvertMesh(); for (int PosIndex = 0; PosIndex < GeometryData.Surfaces.Num(); ++PosIndex) { // set geometry surface names and update textures FString OwnerName; #if WITH_EDITOR OwnerName = GetOwner()->GetActorLabel(); #else OwnerName = GetOwner()->GetName(); #endif GeometryData.Surfaces[PosIndex].Name = OwnerName + GetName() + FString::FromInt(PosIndex); UPhysicalMaterial* physMat = GeometryData.ToOverrideAcousticTexture[PosIndex]; if (physMat) { UAkAcousticTexture* acousticTexture = nullptr; if (GetDefault()->GetAssociatedAcousticTexture(physMat, acousticTexture)) { if (acousticTexture) GeometryData.Surfaces[PosIndex].Texture = acousticTexture->GetShortID(); } } physMat = GeometryData.ToOverrideOcclusion[PosIndex]; if (physMat) { float occlusionValue = 1.f; if (GetDefault()->GetAssociatedOcclusionValue(physMat, occlusionValue)) { GeometryData.Surfaces[PosIndex].Occlusion = occlusionValue; } } } SendGeometry(); UpdateGeometry(); DampingEstimationNeedsUpdate = true; } void UAkGeometryComponent::OnRegister() { Super::OnRegister(); SetRelativeTransform(FTransform::Identity); InitializeParent(); #if WITH_EDITOR OnMeshMaterialChangedHandle = FCoreUObjectDelegates::OnObjectPropertyChanged.AddLambda([this](UObject* Object, FPropertyChangedEvent& PropertyChangedEvent) { if (PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(UMeshComponent, OverrideMaterials) && Parent != nullptr && Parent == Object && MeshType == AkMeshType::StaticMesh ) { UpdateStaticMeshOverride(); } }); if (Parent != nullptr) { if (MeshType == AkMeshType::StaticMesh) { UStaticMeshComponent* MeshParent = Cast(Parent); if (MeshParent != nullptr) CalculateSurfaceArea(MeshParent); } DampingEstimationNeedsUpdate = true; } if (AssociatedRoom != nullptr) { UAkRoomComponent* room = Cast(AssociatedRoom->GetComponentByClass(UAkRoomComponent::StaticClass())); if (room != nullptr) { UE_LOG(LogAkAudio, Warning, TEXT("AkGeometryComponent %s is associated to Room %s. The AssociatedRoom property is deprecated, it will be removed in a future version. We recommend not using it and leaving it set to None."), *GetOwner()->GetName(), *room->GetRoomName()); } } #endif } void UAkGeometryComponent::OnUnregister() { #if WITH_EDITOR FCoreUObjectDelegates::OnObjectPropertyChanged.Remove(OnMeshMaterialChangedHandle); OnMeshMaterialChangedHandle.Reset(); #endif Parent = nullptr; Super::OnUnregister(); } void UAkGeometryComponent::InitializeParent() { USceneComponent* SceneParent = GetAttachParent(); if (SceneParent != nullptr) { Parent = Cast(SceneParent); if (!Parent) { FString actorString = FString("NONE"); if (GetOwner() != nullptr) actorString = GetOwner()->GetName(); FString parentName = SceneParent->GetName(); FString parentClass = SceneParent->GetClass()->GetName(); UE_LOG(LogAkAudio, Error, TEXT("On actor %s, there is a UAkGeometryComponent (%s) attached to parent of type %s (%s).") , *actorString, *GetName(), *parentClass, *parentName); if (MeshType == AkMeshType::StaticMesh) { UE_LOG(LogAkAudio, Error, TEXT("When MeshType is set to Static Mesh, UAkGeometryComponent requires to be nested under a component inheriting from UStaticMeshComponent.")); } else { UE_LOG(LogAkAudio, Error, TEXT("When MeshType is set to Simple Collision, UAkGeometryComponent requires to be nested under a component inheriting from UPrimitiveComponent.")); } return; } if (MeshType == AkMeshType::StaticMesh) { UStaticMeshComponent* MeshParent = Cast(SceneParent); if (MeshParent != nullptr) { bool fillMaterialKeys = true; #if WITH_EDITOR // Fill the materials in the StaticMeshSurfaceOverride map only when there is no ongoing transaction (e.g. a user changing a property in the details panel). if (GetOwner() != nullptr && GetOwner()->CurrentTransactionAnnotation != nullptr) { fillMaterialKeys = false; } #endif if (fillMaterialKeys) { UpdateMeshAndArchetype(MeshParent); } } else { FString actorString = FString("NONE"); if (GetOwner() != nullptr) actorString = GetOwner()->GetName(); FString parentName = SceneParent->GetName(); FString parentClass = SceneParent->GetClass()->GetName(); UE_LOG(LogAkAudio, Warning, TEXT("On actor %s, there is a UAkGeometryComponent (%s) attached to parent of type %s (%s).") , *actorString, *GetName(), *parentClass, *parentName); UE_LOG(LogAkAudio, Warning, TEXT("When MeshType is set to Static Mesh, UAkGeometryComponent requires to be nested under a component inheriting from UStaticMeshComponent. Reverting to Simple Collision.")); MeshType = AkMeshType::CollisionMesh; // If we're in the Blueprint editor, update the Archetype object as well. UWorld* World = GetWorld(); if (World != nullptr && World->WorldType == EWorldType::EditorPreview && CreationMethod == EComponentCreationMethod::SimpleConstructionScript) { UAkGeometryComponent* Archetype = Cast(GetArchetype()); if (Archetype != nullptr) Archetype->MeshType = AkMeshType::CollisionMesh; } } } } } void UAkGeometryComponent::CalculateSurfaceArea(UStaticMeshComponent* StaticMeshComponent) { SurfaceAreas.Empty(); UStaticMesh* mesh = StaticMeshComponent->GetStaticMesh(); #if UE_4_27_OR_LATER if (mesh == nullptr || !mesh->GetRenderData()) return; #else if (mesh == nullptr || !mesh->RenderData) return; #endif const FStaticMeshLODResources& RenderMesh = mesh->GetLODForExport(LOD); FIndexArrayView RawIndices = RenderMesh.IndexBuffer.GetArrayView(); if (RawIndices.Num() == 0) return; const int32 PolygonsCount = RenderMesh.Sections.Num(); double SurfaceArea = 0.0; const auto WorldScale = StaticMeshComponent->GetOwner()->ActorToWorld().GetScale3D(); for (int32 PolygonsIndex = 0; PolygonsIndex < PolygonsCount; ++PolygonsIndex) { const FStaticMeshSection& Polygons = RenderMesh.Sections[PolygonsIndex]; const uint32 TriangleCount = Polygons.NumTriangles; for (uint32 TriangleIndex = 0; TriangleIndex < TriangleCount; ++TriangleIndex) { const uint32 RawVertIndex0 = RawIndices[Polygons.FirstIndex + ((TriangleIndex * 3) + 0)]; const uint32 RawVertIndex1 = RawIndices[Polygons.FirstIndex + ((TriangleIndex * 3) + 1)]; const uint32 RawVertIndex2 = RawIndices[Polygons.FirstIndex + ((TriangleIndex * 3) + 2)]; // Scale to world space to ensure proper area auto ScaledP0 = WorldScale * FVector(RenderMesh.VertexBuffers.PositionVertexBuffer.VertexPosition(RawVertIndex0).GridSnap(WeldingThreshold)); auto ScaledP1 = WorldScale * FVector(RenderMesh.VertexBuffers.PositionVertexBuffer.VertexPosition(RawVertIndex1).GridSnap(WeldingThreshold)); auto ScaledP2 = WorldScale * FVector(RenderMesh.VertexBuffers.PositionVertexBuffer.VertexPosition(RawVertIndex2).GridSnap(WeldingThreshold)); SurfaceArea += FAkReverbDescriptor::TriangleArea(ScaledP0, ScaledP1, ScaledP2); } SurfaceAreas.Add(PolygonsIndex, SurfaceArea); } } bool AddVertsForEdge(const FPositionVertexBuffer& Positions, TArray& UniqueVerts, int32 P0UnrealIdx, int32 P0UniqueIdx, int32 P1UnrealIdx, int32 P1UniqueIdx, TArray< TPair > & VertsOnEdge, float WeldingThreshold) { auto p0 = Positions.VertexPosition(P0UnrealIdx).GridSnap(WeldingThreshold); auto p1 = Positions.VertexPosition(P1UnrealIdx).GridSnap(WeldingThreshold); FUnrealFloatVector Dir; float Length; (p1 - p0).ToDirectionAndLength(Dir, Length); if (Length <= FLT_MIN) return false; for (int32 i = 0; i < UniqueVerts.Num(); i++) { const int32 UnrealVertIdx = UniqueVerts[i]; auto p = Positions.VertexPosition(UnrealVertIdx).GridSnap(WeldingThreshold); float Dot = FUnrealFloatVector::DotProduct(p - p0, Dir); const float RelLength = Dot / Length; if (RelLength > kVertexNear && RelLength < 1.f + kVertexNear) { FUnrealFloatVector PtOnLine = p0 + Dot * Dir; FUnrealFloatVector Diff = PtOnLine - p; const float RelDiff = Diff.GetAbsMax() / Length; if (RelDiff < kVertexNear) { VertsOnEdge.Emplace(i, Dot); } } } // VertsOnEdge should contain p1 but not p0 check(VertsOnEdge.Num() > 0); VertsOnEdge.Sort([](const TPair& One, const TPair& Two) { return One.Value < Two.Value; }); return true; } void DetermineVertsToWeld(TArray& VertRemap, TArray& UniqueVerts, const FStaticMeshLODResources& RenderMesh, float WeldingThreshold) { const int32 VertexCount = RenderMesh.VertexBuffers.PositionVertexBuffer.GetNumVertices(); // Maps unreal verts to reduced list of verts VertRemap.Empty(VertexCount); VertRemap.AddUninitialized(VertexCount); // List of Unreal Verts to keep UniqueVerts.Empty(VertexCount); // Combine matching verts using hashed search to maintain good performance TMap HashedVerts; for (int32 a = 0; a < VertexCount; a++) { auto PositionA = RenderMesh.VertexBuffers.PositionVertexBuffer.VertexPosition(a).GridSnap(WeldingThreshold); const int32* FoundIndex = HashedVerts.Find(PositionA); if (!FoundIndex) { int32 NewIndex = UniqueVerts.Add(a); VertRemap[a] = NewIndex; HashedVerts.Add(PositionA, NewIndex); } else { VertRemap[a] = *FoundIndex; } } } void UAkGeometryComponent::ConvertMesh() { if (!(Parent && IsValid(Parent))) return; const UAkSettings* AkSettings = GetDefault(); switch (MeshType) { case AkMeshType::StaticMesh: { UStaticMeshComponent* MeshParent = Cast(Parent); if (MeshParent != nullptr) ConvertStaticMesh(MeshParent, AkSettings); break; } case AkMeshType::CollisionMesh: { ConvertCollisionMesh(Parent, AkSettings); break; } } } void UAkGeometryComponent::ConvertStaticMesh(UStaticMeshComponent* StaticMeshComponent, const UAkSettings* AkSettings) { UStaticMesh* mesh = StaticMeshComponent->GetStaticMesh(); if (!(mesh && IsValid(mesh))) return; if (LOD > mesh->GetNumLODs() - 1) LOD = mesh->GetNumLODs() - 1; #if UE_4_27_OR_LATER if (!mesh->GetRenderData()) return; #else if (!mesh->RenderData) return; #endif const FStaticMeshLODResources& RenderMesh = mesh->GetLODForExport(LOD); FIndexArrayView RawIndices = RenderMesh.IndexBuffer.GetArrayView(); if (RawIndices.Num() == 0) return; GeometryData.Clear(); TArray VertRemap; TArray UniqueVerts; DetermineVertsToWeld(VertRemap, UniqueVerts, RenderMesh, WeldingThreshold); for (int PosIndex = 0; PosIndex < UniqueVerts.Num(); ++PosIndex) { const int32 UnrealPosIndex = UniqueVerts[PosIndex]; auto VertexInActorSpace = RenderMesh.VertexBuffers.PositionVertexBuffer.VertexPosition(UnrealPosIndex); GeometryData.Vertices.Add(FVector(VertexInActorSpace)); } UpdateMeshAndArchetype(StaticMeshComponent); CalculateSurfaceArea(StaticMeshComponent); const int32 PolygonsCount = RenderMesh.Sections.Num(); for (int32 PolygonsIndex = 0; PolygonsIndex < PolygonsCount; ++PolygonsIndex) { const FStaticMeshSection& Polygons = RenderMesh.Sections[PolygonsIndex]; FAkAcousticSurface Surface; UPhysicalMaterial* physMatTexture = nullptr; UPhysicalMaterial* physMatOcclusion = nullptr; FAkGeometrySurfaceOverride surfaceOverride; UMaterialInterface* Material = StaticMeshComponent->GetMaterial(Polygons.MaterialIndex); if (Material) { UPhysicalMaterial* physicalMaterial = Material->GetPhysicalMaterial(); if (StaticMeshSurfaceOverride.Contains(Material)) surfaceOverride = StaticMeshSurfaceOverride[Material]; if (!surfaceOverride.AcousticTexture) physMatTexture = physicalMaterial; if (!surfaceOverride.bEnableOcclusionOverride) physMatOcclusion = physicalMaterial; } if (surfaceOverride.AcousticTexture) Surface.Texture = surfaceOverride.AcousticTexture->GetShortID(); if (surfaceOverride.bEnableOcclusionOverride) Surface.Occlusion = surfaceOverride.OcclusionValue; GeometryData.Surfaces.Add(Surface); GeometryData.ToOverrideAcousticTexture.Add(physMatTexture); GeometryData.ToOverrideOcclusion.Add(physMatOcclusion); AkSurfIdx surfIdx = (AkSurfIdx)(GeometryData.Surfaces.Num() - 1); TArray< TPair > Edge0, Edge1, Edge2; const uint32 TriangleCount = Polygons.NumTriangles; for (uint32 TriangleIndex = 0; TriangleIndex < TriangleCount; ++TriangleIndex) { uint32 RawVertIndex0 = RawIndices[Polygons.FirstIndex + ((TriangleIndex * 3) + 0)]; uint32 UniqueVertIndex0 = VertRemap[RawVertIndex0]; uint32 RawVertIndex1 = RawIndices[Polygons.FirstIndex + ((TriangleIndex * 3) + 1)]; uint32 UniqueVertIndex1 = VertRemap[RawVertIndex1]; uint32 RawVertIndex2 = RawIndices[Polygons.FirstIndex + ((TriangleIndex * 3) + 2)]; uint32 UniqueVertIndex2 = VertRemap[RawVertIndex2]; Edge0.Empty(8); bool succeeded = AddVertsForEdge(RenderMesh.VertexBuffers.PositionVertexBuffer, UniqueVerts, RawVertIndex0, UniqueVertIndex0, RawVertIndex1, UniqueVertIndex1, Edge0, WeldingThreshold); if (!succeeded) { UE_LOG(LogAkAudio, Warning, TEXT("%s: UAkGeometryComponent::ConvertStaticMesh Vertex IDs %i and %i are too close resulting in a triangle with an area of 0. The triangle will be skipped."), *GetOwner()->GetName(), RawVertIndex0, RawVertIndex1); continue; } Edge1.Empty(8); succeeded = AddVertsForEdge(RenderMesh.VertexBuffers.PositionVertexBuffer, UniqueVerts, RawVertIndex1, UniqueVertIndex1, RawVertIndex2, UniqueVertIndex2, Edge1, WeldingThreshold); if (!succeeded) { UE_LOG(LogAkAudio, Warning, TEXT("%s: UAkGeometryComponent::ConvertStaticMesh Vertex IDs %i and %i are too close resulting in a triangle with an area of 0. The triangle will be skipped."), *GetOwner()->GetName(), RawVertIndex1, RawVertIndex2); continue; } Edge2.Empty(8); succeeded = AddVertsForEdge(RenderMesh.VertexBuffers.PositionVertexBuffer, UniqueVerts, RawVertIndex2, UniqueVertIndex2, RawVertIndex0, UniqueVertIndex0, Edge2, WeldingThreshold); if (!succeeded) { UE_LOG(LogAkAudio, Warning, TEXT("%s: UAkGeometryComponent::ConvertStaticMesh Vertex IDs %i and %i are too close resulting in a triangle with an area of 0. The triangle will be skipped."), *GetOwner()->GetName(), RawVertIndex2, RawVertIndex0); continue; } FAkTriangle triangle; triangle.Surface = surfIdx; bool bDone = false; do { int32 v0, v1, v2; if (Edge0.Num() > 1) { v1 = Edge0.Pop().Key; v0 = Edge0.Last().Key; v2 = Edge1[0].Key; } else if (Edge1.Num() > 1) { v1 = Edge1.Pop().Key; v0 = Edge1.Last().Key; v2 = Edge2[0].Key; } else if (Edge2.Num() > 1) { v1 = Edge2.Pop().Key; v0 = Edge2.Last().Key; v2 = Edge0[0].Key; } else { v0 = Edge0[0].Key; v1 = Edge1[0].Key; v2 = Edge2[0].Key; bDone = true; } triangle.Point0 = (AkVertIdx)v0; triangle.Point1 = (AkVertIdx)v1; triangle.Point2 = (AkVertIdx)v2; if (triangle.Point0 != triangle.Point1 && triangle.Point1 != triangle.Point2 && triangle.Point2 != triangle.Point0) GeometryData.Triangles.Add(triangle); } while (!bDone); } if (SurfaceAreas.Contains(PolygonsIndex)) surfaceOverride.SetSurfaceArea(SurfaceAreas[PolygonsIndex]); } } struct VertexIndexByAngle { AkVertIdx Index; float Angle; }; #if AK_USE_PHYSX void ConvertConvexMeshToGeometryData(AkSurfIdx surfIdx, const FKConvexElem& convexHull, FAkGeometryData* GeometryData) { physx::PxConvexMesh* convexMesh = convexHull.GetConvexMesh(); AkVertIdx initialVertIdx = GeometryData->Vertices.Num(); if (convexMesh != nullptr) { const PxVec3* vertices = convexMesh->getVertices(); uint32 numVerts = (uint32)convexMesh->getNbVertices(); for (uint32 vertIdx = 0; vertIdx < numVerts; ++vertIdx) { FVector akvtx; akvtx.X = vertices[vertIdx].x; akvtx.Y = vertices[vertIdx].y; akvtx.Z = vertices[vertIdx].z; GeometryData->Vertices.Add(akvtx); } const physx::PxU8* indexBuf = convexMesh->getIndexBuffer(); uint32 numPolys = (uint32)convexMesh->getNbPolygons(); for (uint32 polyIdx = 0; polyIdx < numPolys; polyIdx++) { PxHullPolygon polyData; convexMesh->getPolygonData(polyIdx, polyData); // order the vertices of the polygon uint32 numVertsInPoly = (uint32)polyData.mNbVerts; uint32 vertIdxOffset = (uint32)polyData.mIndexBase; TArray orderedIndexes; // first element is first vertex index AkVertIdx firstVertIdx = (AkVertIdx)indexBuf[vertIdxOffset]; orderedIndexes.Add(VertexIndexByAngle{ firstVertIdx, 0 }); // get the center of the polygon FVector center(0, 0, 0); for (uint32 polyVertIdx = 0; polyVertIdx < numVertsInPoly; ++polyVertIdx) { auto vertIdx = indexBuf[vertIdxOffset + polyVertIdx]; center.X += vertices[vertIdx].x; center.Y += vertices[vertIdx].y; center.Z += vertices[vertIdx].z; } center.X /= numVertsInPoly; center.Y /= numVertsInPoly; center.Z /= numVertsInPoly; // get the vector from center to the first vertex FVector v0; v0.X = vertices[firstVertIdx].x - center.X; v0.Y = vertices[firstVertIdx].y - center.Y; v0.Z = vertices[firstVertIdx].z - center.Z; v0.Normalize(); // get the normal of the plane FVector n; n.X = polyData.mPlane[0]; n.Y = polyData.mPlane[1]; n.Z = polyData.mPlane[2]; n.Normalize(); // find the angles between v0 and the other vertices of the polygon for (uint32 polyVertIdx = 1; polyVertIdx < numVertsInPoly; polyVertIdx++) { // get the vector from center to the current vertex AkVertIdx vertIdx = (AkVertIdx)indexBuf[vertIdxOffset + polyVertIdx]; FVector v1; v1.X = vertices[vertIdx].x - center.X; v1.Y = vertices[vertIdx].y - center.Y; v1.Z = vertices[vertIdx].z - center.Z; v1.Normalize(); // get the angle between v0 and v1 // to do so, we need the dot product and the determinant respectively proportional to cos and sin of the angle. // atan2(sin, cos) will give us the angle float dot = FVector::DotProduct(v0, v1); // the determinant of two 3D vectors in the same plane can be found with the dot product of the normal with the result of // a cross product between the vectors float det = FVector::DotProduct(n, FVector::CrossProduct(v0, v1)); float angle = (float)atan2(det, dot); orderedIndexes.Add(VertexIndexByAngle{ vertIdx, angle }); } orderedIndexes.Sort(); // fan triangulation for (uint32 vertIdx = 1; vertIdx < numVertsInPoly - 1; ++vertIdx) { FAkTriangle tri; tri.Point0 = (AkVertIdx)orderedIndexes[0].Index + initialVertIdx; tri.Point1 = (AkVertIdx)orderedIndexes[vertIdx].Index + initialVertIdx; tri.Point2 = (AkVertIdx)orderedIndexes[vertIdx + 1].Index + initialVertIdx; tri.Surface = surfIdx; GeometryData->Triangles.Add(tri); } } } else { // bounding box GeometryData->AddBox(surfIdx, convexHull.ElemBox.GetCenter(), convexHull.ElemBox.GetExtent(), convexHull.GetTransform().Rotator()); } } #endif bool operator<(const VertexIndexByAngle& lhs, const VertexIndexByAngle& rhs) { return lhs.Angle < rhs.Angle; } void UAkGeometryComponent::ConvertCollisionMesh(UPrimitiveComponent* PrimitiveComponent, const UAkSettings* AkSettings) { UBodySetup* bodySetup = PrimitiveComponent->GetBodySetup(); if (!(bodySetup && IsValid(bodySetup))) return; GeometryData.Clear(); FAkAcousticSurface Surface; UPhysicalMaterial* physicalMaterial = bodySetup->GetPhysMaterial(); UPhysicalMaterial* physMatTexture = nullptr; UPhysicalMaterial* physMatOcclusion = nullptr; FAkGeometrySurfaceOverride surfaceOverride = CollisionMeshSurfaceOverride; if (surfaceOverride.AcousticTexture) Surface.Texture = surfaceOverride.AcousticTexture->GetShortID(); else physMatTexture = physicalMaterial; if (surfaceOverride.bEnableOcclusionOverride) Surface.Occlusion = surfaceOverride.OcclusionValue; else physMatOcclusion = physicalMaterial; GeometryData.ToOverrideAcousticTexture.Add(physMatTexture); GeometryData.ToOverrideOcclusion.Add(physMatOcclusion); GeometryData.Surfaces.Add(Surface); AkSurfIdx surfIdx = (AkSurfIdx)(GeometryData.Surfaces.Num() - 1); int32 numBoxes = bodySetup->AggGeom.BoxElems.Num(); for (int32 i = 0; i < numBoxes; i++) { FKBoxElem box = bodySetup->AggGeom.BoxElems[i]; FVector extent; extent.X = box.X / 2; extent.Y = box.Y / 2; extent.Z = box.Z / 2; if ((extent.Z == 0.0f && (extent.X == 0.0f || extent.Y == 0.0f)) || (extent.Y == 0.0f && (extent.X == 0.0f || extent.Z == 0.0f)) || (extent.X == 0.0f && (extent.Y == 0.0f || extent.Z == 0.0f))) { UE_LOG(LogAkAudio, Warning, TEXT("%s: UAkGeometryComponent::ConvertCollisionMesh: Unable to add box geometry for box index %i as the box contains no triangles. The box will be skipped."), *GetOwner()->GetName(), i); continue; } GeometryData.AddBox(surfIdx, box.Center, extent, box.Rotation); } const int sides = 16; int32 numSpheres = bodySetup->AggGeom.SphereElems.Num(); for (int32 i = 0; i < numSpheres; i++) { FKSphereElem sphere = bodySetup->AggGeom.SphereElems[i]; GeometryData.AddSphere(surfIdx, sphere.Center, sphere.Radius, sides, sides / 2); } int32 numCapsules = bodySetup->AggGeom.SphylElems.Num(); for (int32 i = 0; i < numCapsules; i++) { FKSphylElem capsule = bodySetup->AggGeom.SphylElems[i]; FVector X = capsule.GetTransform().GetUnitAxis(EAxis::X); FVector Y = capsule.GetTransform().GetUnitAxis(EAxis::Y); FVector Z = capsule.GetTransform().GetUnitAxis(EAxis::Z); GeometryData.AddCapsule(surfIdx, capsule.Center, X, Y, Z, capsule.Radius, capsule.Length / 2, sides / 2); } int32 numConvexElems = bodySetup->AggGeom.ConvexElems.Num(); for (int32 i = 0; i < numConvexElems; i++) { FKConvexElem& convexElem = bodySetup->AggGeom.ConvexElems[i]; int32 numVertices = convexElem.VertexData.Num(); if (numVertices == 0) continue; #if AK_USE_CHAOS // will compute IndexData if it is empty convexElem.ComputeChaosConvexIndices(false); #endif int32 numTriangles = convexElem.IndexData.Num() / 3; if (numTriangles == 0) { #if AK_USE_PHYSX ConvertConvexMeshToGeometryData(surfIdx, convexElem, &GeometryData); #endif continue; } int32 vertexOffset = GeometryData.Vertices.Num(); for (int32 vertIdx = 0; vertIdx < numVertices; ++vertIdx) { GeometryData.Vertices.Add(convexElem.GetTransform().TransformPosition(convexElem.VertexData[vertIdx])); } for (int32 triIdx = 0; triIdx < numTriangles; ++triIdx) { FAkTriangle tri; tri.Point0 = vertexOffset + convexElem.IndexData[3 * triIdx]; tri.Point1 = vertexOffset + convexElem.IndexData[3 * triIdx + 1]; tri.Point2 = vertexOffset + convexElem.IndexData[3 * triIdx + 2]; tri.Surface = surfIdx; GeometryData.Triangles.Add(tri); } } } void UAkGeometryComponent::SendGeometry() { FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); if (AkAudioDevice && ShouldSendGeometry()) { if (GeometryData.Triangles.Num() > 0 && GeometryData.Vertices.Num() > 0) { AkGeometryParams params; params.NumSurfaces = GeometryData.Surfaces.Num(); params.NumTriangles = GeometryData.Triangles.Num(); params.NumVertices = GeometryData.Vertices.Num(); TArray Surfaces; TArray< TSharedPtr< decltype(StringCast(TEXT(""))) > > SurfaceNames; Surfaces.SetNum(params.NumSurfaces); SurfaceNames.SetNum(params.NumSurfaces); if (params.NumSurfaces) { for (int i = 0; i < params.NumSurfaces; ++i) { Surfaces[i].transmissionLoss = GeometryData.Surfaces[i].Occlusion; Surfaces[i].strName = nullptr; if (!GeometryData.Surfaces[i].Name.IsEmpty()) { SurfaceNames[i] = MakeShareable(new decltype(StringCast(TEXT("")))(*GeometryData.Surfaces[i].Name)); Surfaces[i].strName = SurfaceNames[i].Get()->Get(); } Surfaces[i].textureID = GeometryData.Surfaces[i].Texture; } } params.Surfaces = Surfaces.GetData(); TUniquePtr Triangles = MakeUnique(params.NumTriangles);// temp triangle buffer for (int i = 0; i < params.NumTriangles; ++i) { Triangles[i].point0 = GeometryData.Triangles[i].Point0; Triangles[i].point1 = GeometryData.Triangles[i].Point1; Triangles[i].point2 = GeometryData.Triangles[i].Point2; Triangles[i].surface = GeometryData.Triangles[i].Surface; } params.Triangles = Triangles.Get(); TUniquePtr Vertices = MakeUnique(params.NumVertices); // temp vertex buffer for (int i = 0; i < params.NumVertices; ++i) { Vertices[i].X = GeometryData.Vertices[i].X; Vertices[i].Y = GeometryData.Vertices[i].Y; Vertices[i].Z = GeometryData.Vertices[i].Z; } params.Vertices = Vertices.Get(); params.EnableDiffraction = bEnableDiffraction; params.EnableDiffractionOnBoundaryEdges = bEnableDiffractionOnBoundaryEdges; SendGeometryToWwise(params); } else { UE_LOG(LogAkAudio, Warning, TEXT("%s: UAkGeometryComponent::SendGeometry() Geometry Data is empty. Nothing was sent to Spatial Audio."), *GetOwner()->GetName()); } } } void UAkGeometryComponent::RemoveGeometry() { RemoveGeometryFromWwise(); } void UAkGeometryComponent::UpdateGeometry() { if (Parent) { AkRoomID roomID = AkRoomID(); if (AssociatedRoom) { UAkRoomComponent* room = Cast(AssociatedRoom->GetComponentByClass(UAkRoomComponent::StaticClass())); if (room != nullptr) roomID = room->GetRoomID(); } SendGeometryInstanceToWwise(Parent->GetComponentRotation(), Parent->GetComponentLocation(), Parent->GetComponentTransform().GetScale3D(), roomID, !bWasAddedByRoom); } } void UAkGeometryComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) { Super::EndPlay(EndPlayReason); RemoveGeometry(); } float UAkGeometryComponent::GetSurfaceAreaSquaredMeters(const int& surfaceIndex) const { if (SurfaceAreas.Contains(surfaceIndex)) return SurfaceAreas[surfaceIndex] / AkComponentHelpers::UnrealUnitsPerSquaredMeter(Parent); return 0.0f; } #if WITH_EDITOR void UAkGeometryComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); if (AkComponentHelpers::IsGameWorldBlueprintComponent(this) || IsBeingDestroyed()) return; if (AssociatedRoom && !Cast(AssociatedRoom->GetComponentByClass(UAkRoomComponent::StaticClass()))) { UE_LOG(LogAkAudio, Warning, TEXT("%s: The Associated Room is not of type UAkRoomComponent."), *GetOwner()->GetName()); } const FName memberPropertyName = (PropertyChangedEvent.MemberProperty != nullptr) ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None; const FName PropertyName = (PropertyChangedEvent.Property != nullptr) ? PropertyChangedEvent.Property->GetFName() : NAME_None; if (memberPropertyName == GET_MEMBER_NAME_CHECKED(UAkGeometryComponent, MeshType)) { if (Parent != nullptr && MeshType == AkMeshType::StaticMesh) { UStaticMeshComponent* MeshParent = Cast(Parent); if (MeshParent == nullptr) { FString actorString = FString("NONE"); if (GetOwner() != nullptr) actorString = GetOwner()->GetName(); FString parentName = Parent->GetName(); FString parentClass = Parent->GetClass()->GetName(); UE_LOG(LogAkAudio, Warning, TEXT("On actor %s, there is a UAkGeometryComponent (%s) attached to parent of type %s (%s).") , *actorString, *GetName(), *parentClass, *parentName); UE_LOG(LogAkAudio, Warning, TEXT("When MeshType is set to Static Mesh, UAkGeometryComponent requires to be nested under a component inheriting from UStaticMeshComponent. Reverting to Simple Collision.")); MeshType = AkMeshType::CollisionMesh; } else { UpdateStaticMeshOverride(); } } UnregisterTextureParamChangeCallbacks(); RegisterAllTextureParamCallbacks(); } else if ( (memberPropertyName == GET_MEMBER_NAME_CHECKED(UAkGeometryComponent, StaticMeshSurfaceOverride) && MeshType == AkMeshType::StaticMesh) || (memberPropertyName == GET_MEMBER_NAME_CHECKED(UAkGeometryComponent, CollisionMeshSurfaceOverride) && MeshType == AkMeshType::CollisionMesh)) { UnregisterTextureParamChangeCallbacks(); RegisterAllTextureParamCallbacks(); DampingEstimationNeedsUpdate = true; } if (MeshType == AkMeshType::StaticMesh && memberPropertyName == GET_MEMBER_NAME_CHECKED(UAkGeometryComponent, WeldingThreshold) && PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet) { ConvertMesh(); SendGeometry(); UpdateGeometry(); } else if (memberPropertyName == GET_MEMBER_NAME_CHECKED(UAkGeometryComponent, bEnableDiffraction) || memberPropertyName == GET_MEMBER_NAME_CHECKED(UAkGeometryComponent, bEnableDiffractionOnBoundaryEdges)) { SendGeometry(); UpdateGeometry(); } } void UAkGeometryComponent::PostEditUndo() { OnRefreshDetails.ExecuteIfBound(); Super::PostEditUndo(); } void UAkGeometryComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); if (bRequiresDeferredBeginPlay) { BeginPlayInternal(); bRequiresDeferredBeginPlay = false; } if (MeshType == AkMeshType::StaticMesh) { if (IsValid(Parent) && !Parent->IsBeingDestroyed()) { UStaticMeshComponent* MeshParent = Cast(Parent); if (MeshParent != nullptr && StaticMeshSurfaceOverride.Num() != MeshParent->GetNumMaterials()) UpdateMeshAndArchetype(MeshParent); } if (bMeshMaterialChanged) { OnRefreshDetails.ExecuteIfBound(); bMeshMaterialChanged = false; } } } #endif void UAkGeometryComponent::UpdateStaticMeshOverride() { UStaticMeshComponent* MeshParent = Cast(Parent); if (MeshParent != nullptr) { UpdateMeshAndArchetype(MeshParent); } } void UAkGeometryComponent::UpdateMeshAndArchetype(UStaticMeshComponent* StaticMeshComponent) { _UpdateStaticMeshOverride(StaticMeshComponent); // If we're in the Blueprint editor, update the Archetype object as well. // (The archetype object is the object that's edited when properties are changed in the Blueprint editor details inspector. // This is a separate object from the objects shown in the Blueprint editor viewport.) UWorld* World = GetWorld(); if (World != nullptr && World->WorldType == EWorldType::EditorPreview && CreationMethod == EComponentCreationMethod::SimpleConstructionScript) { UAkGeometryComponent* Archetype = Cast(GetArchetype()); if (Archetype != nullptr) Archetype->_UpdateStaticMeshOverride(StaticMeshComponent); } } void UAkGeometryComponent::_UpdateStaticMeshOverride(UStaticMeshComponent* StaticMeshComponent) { auto ToRemove = StaticMeshSurfaceOverride; int numMaterials = StaticMeshComponent->GetNumMaterials(); for (int i = 0; i < numMaterials; i++) { UMaterialInterface* material = StaticMeshComponent->GetMaterial(i); if (StaticMeshSurfaceOverride.Contains(material)) ToRemove.Remove(material); else { FAkGeometrySurfaceOverride surfaceOverride; if (PreviousStaticMeshSurfaceOverride.Contains(material)) surfaceOverride = PreviousStaticMeshSurfaceOverride[material]; StaticMeshSurfaceOverride.Add(material, surfaceOverride); } } #if WITH_EDITORONLY_DATA if (ToRemove.Num() > 0) bMeshMaterialChanged = true; #endif for (auto& elemToRemove : ToRemove) StaticMeshSurfaceOverride.Remove(elemToRemove.Key); ToRemove.Empty(); PreviousStaticMeshSurfaceOverride.Empty(); PreviousStaticMeshSurfaceOverride = StaticMeshSurfaceOverride; } void UAkGeometryComponent::Serialize(FArchive& Ar) { #if WITH_EDITORONLY_DATA UWorld* World = GetWorld(); if (Ar.IsSaving() && World != nullptr && !World->IsGameWorld()) ConvertMesh(); #endif Super::Serialize(Ar); } void UAkGeometryComponent::GetTexturesAndSurfaceAreas(TArray& textures, TArray& surfaceAreas) const { textures.Empty(); surfaceAreas.Empty(); const UAkSettings* AkSettings = GetDefault(); if (AkSettings != nullptr) { if (MeshType == AkMeshType::CollisionMesh) { if (CollisionMeshSurfaceOverride.AcousticTexture != nullptr) { #if WITH_EDITOR // Get the most accurate absorption values from the list in the AkSettings const FAkAcousticTextureParams* params = AkSettings->GetTextureParams(CollisionMeshSurfaceOverride.AcousticTexture->GetShortID()); if (params != nullptr) { textures.Add(*params); surfaceAreas.Add(1.0f); // When there is only 1 acoustic texture, surface area magnitude is not important. } #else // Get the absorption values from the cooked data FAkAcousticTextureParams params; params.AbsorptionValues = FVector4( CollisionMeshSurfaceOverride.AcousticTexture->AcousticTextureCookedData.AbsorptionLow / 100.0f, CollisionMeshSurfaceOverride.AcousticTexture->AcousticTextureCookedData.AbsorptionMidLow / 100.0f, CollisionMeshSurfaceOverride.AcousticTexture->AcousticTextureCookedData.AbsorptionMidHigh / 100.0f, CollisionMeshSurfaceOverride.AcousticTexture->AcousticTextureCookedData.AbsorptionHigh / 100.0f ); textures.Add(params); surfaceAreas.Add(1.0f); // When there is only 1 acoustic texture, surface area magnitude is not important. #endif } } else { if (StaticMeshSurfaceOverride.Num() > 0) { int surfIdx = 0; float surfaceArea = 0.0f; for (auto it = StaticMeshSurfaceOverride.CreateConstIterator(); it; ++it) { surfaceArea = GetSurfaceAreaSquaredMeters(surfIdx); surfaceAreas.Add(surfaceArea); FAkAcousticTextureParams params; FAkGeometrySurfaceOverride surface = it.Value(); if (surface.AcousticTexture != nullptr) { #if WITH_EDITOR // Get the most accurate absorption values from the list in the AkSettings const FAkAcousticTextureParams* paramsFound = AkSettings->GetTextureParams(surface.AcousticTexture->GetShortID()); if (paramsFound != nullptr) { params = *paramsFound; } #else params.AbsorptionValues = FVector4( surface.AcousticTexture->AcousticTextureCookedData.AbsorptionLow / 100.0f, surface.AcousticTexture->AcousticTextureCookedData.AbsorptionMidLow / 100.0f, surface.AcousticTexture->AcousticTextureCookedData.AbsorptionMidHigh / 100.0f, surface.AcousticTexture->AcousticTextureCookedData.AbsorptionHigh / 100.0f ); #endif } textures.Add(params); ++surfIdx; } } } } } #if WITH_EDITOR void UAkGeometryComponent::HandleObjectsReplaced(const TMap& ReplacementMap) { Super::HandleObjectsReplaced(ReplacementMap); if (ReplacementMap.Contains(Parent)) { InitializeParent(); if (Parent != nullptr) { if (MeshType == AkMeshType::StaticMesh) { UStaticMeshComponent* MeshParent = Cast(Parent); if (MeshParent != nullptr) CalculateSurfaceArea(MeshParent); } DampingEstimationNeedsUpdate = true; } } } bool UAkGeometryComponent::ContainsTexture(const FGuid& textureID) { if (MeshType == AkMeshType::CollisionMesh) { if (CollisionMeshSurfaceOverride.AcousticTexture != nullptr) return CollisionMeshSurfaceOverride.AcousticTexture->AcousticTextureInfo.WwiseGuid == textureID; } else { for (auto it = StaticMeshSurfaceOverride.CreateIterator(); it; ++it) { if (it.Value().AcousticTexture != nullptr) { if (it.Value().AcousticTexture->AcousticTextureInfo.WwiseGuid == textureID) return true; } } } return false; } void UAkGeometryComponent::RegisterAllTextureParamCallbacks() { if (MeshType == AkMeshType::CollisionMesh) { if (CollisionMeshSurfaceOverride.AcousticTexture != nullptr) RegisterTextureParamChangeCallback(CollisionMeshSurfaceOverride.AcousticTexture->AcousticTextureInfo.WwiseGuid); } else { for (auto it = StaticMeshSurfaceOverride.CreateIterator(); it; ++it) { if (it.Value().AcousticTexture != nullptr) { RegisterTextureParamChangeCallback(it.Value().AcousticTexture->AcousticTextureInfo.WwiseGuid); } } } } #endif