// @lint-ignore-every LICENSELINT // Copyright 1998-2020 Epic Games, Inc. All Rights Reserved. #include "OculusXRPassthroughLayerComponent.h" #include "Engine/StaticMesh.h" #include "Components/StaticMeshComponent.h" #include "ProceduralMeshComponent.h" #include "OculusXRHMD.h" #include "OculusXRPassthroughLayerShapes.h" #include "Curves/CurveLinearColor.h" #include "StaticMeshResources.h" DEFINE_LOG_CATEGORY(LogOculusPassthrough); void UOculusXRStereoLayerShapeReconstructed::ApplyShape(IStereoLayers::FLayerDesc& LayerDesc) { const FEdgeStyleParameters EdgeStyleParameters( bEnableEdgeColor, bEnableColorMap, TextureOpacityFactor, Brightness, Contrast, Posterize, Saturation, EdgeColor, ColorScale, ColorOffset, ColorMapType, GetColorArray(bUseColorMapCurve, ColorMapCurve), GenerateColorLutDescription(LutWeight, ColorLUTSource, ColorLUTTarget)); LayerDesc.SetShape(EdgeStyleParameters, LayerOrder); } void UOculusXRStereoLayerShapeUserDefined::ApplyShape(IStereoLayers::FLayerDesc& LayerDesc) { //If there is no user geometry, set the layer hidden to avoid unnecessary cost if (UserGeometryList.IsEmpty()) LayerDesc.Flags |= IStereoLayers::LAYER_FLAG_HIDDEN; const FEdgeStyleParameters EdgeStyleParameters( bEnableEdgeColor, bEnableColorMap, TextureOpacityFactor, Brightness, Contrast, Posterize, Saturation, EdgeColor, ColorScale, ColorOffset, ColorMapType, GetColorArray(bUseColorMapCurve, ColorMapCurve), GenerateColorLutDescription(LutWeight, ColorLUTSource, ColorLUTTarget)); LayerDesc.SetShape(UserGeometryList, EdgeStyleParameters, LayerOrder); } void UOculusXRStereoLayerShapeUserDefined::AddGeometry(const FString& MeshName, OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh, FTransform Transform, bool bUpdateTransform) { FUserDefinedGeometryDesc UserDefinedGeometryDesc( MeshName, PassthroughMesh, Transform, bUpdateTransform); UserGeometryList.Add(UserDefinedGeometryDesc); } void UOculusXRStereoLayerShapeUserDefined::RemoveGeometry(const FString& MeshName) { UserGeometryList.RemoveAll([MeshName](const FUserDefinedGeometryDesc& Desc) { return Desc.MeshName == MeshName; }); } UOculusXRPassthroughLayerComponent::UOculusXRPassthroughLayerComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } void UOculusXRPassthroughLayerComponent::DestroyComponent(bool bPromoteChildren) { Super::DestroyComponent(bPromoteChildren); #ifdef WITH_OCULUS_BRANCH IStereoLayers* StereoLayers; if (LayerId && GEngine->StereoRenderingDevice.IsValid() && (StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers()) != nullptr) { StereoLayers->DestroyLayer(LayerId); LayerId = 0; } #endif } void UOculusXRPassthroughLayerComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { #ifndef WITH_OCULUS_BRANCH if (Texture == nullptr && !LayerRequiresTexture()) { // UStereoLayerComponent hides components without textures Texture = GEngine->DefaultTexture; } #endif UpdatePassthroughObjects(); Super::TickComponent(DeltaTime, TickType, ThisTickFunction); } void UOculusXRPassthroughLayerComponent::UpdatePassthroughObjects() { UOculusXRStereoLayerShapeUserDefined* UserShape = Cast(Shape); if (UserShape) { bool bDirty = false; for (FUserDefinedGeometryDesc& Entry : UserShape->GetUserGeometryList()) { if (Entry.bUpdateTransform) { const UMeshComponent** MeshComponent = PassthroughComponentMap.Find(Entry.MeshName); if (MeshComponent) { Entry.Transform = (*MeshComponent)->GetComponentTransform(); bDirty = true; } } } if (bDirty) { MarkStereoLayerDirty(); } } } OculusXRHMD::FOculusPassthroughMeshRef UOculusXRPassthroughLayerComponent::CreatePassthroughMesh(UProceduralMeshComponent* ProceduralMeshComponent) { if (!ProceduralMeshComponent) { UE_LOG(LogOculusPassthrough, Error, TEXT("Passthrough Procedural Mesh is nullptr")); return nullptr; } TArray Triangles; TArray Vertices; int32 NumSections = ProceduralMeshComponent->GetNumSections(); int VertexOffset = 0; //Each section start with vertex IDs of 0, in order to create a single mesh from all sections we need to offset those IDs by the amount of previous vertices for (int32 s = 0; s < NumSections; ++s) { FProcMeshSection* ProcMeshSection = ProceduralMeshComponent->GetProcMeshSection(s); for (int32 i = 0; i < ProcMeshSection->ProcIndexBuffer.Num(); ++i) { Triangles.Add(VertexOffset + ProcMeshSection->ProcIndexBuffer[i]); } for (int32 i = 0; i < ProcMeshSection->ProcVertexBuffer.Num(); ++i) { Vertices.Add(ProcMeshSection->ProcVertexBuffer[i].Position); } VertexOffset += ProcMeshSection->ProcVertexBuffer.Num(); } OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh = new OculusXRHMD::FOculusPassthroughMesh(Vertices, Triangles); return PassthroughMesh; } OculusXRHMD::FOculusPassthroughMeshRef UOculusXRPassthroughLayerComponent::CreatePassthroughMesh(UStaticMeshComponent* StaticMeshComponent) { if (!StaticMeshComponent) { UE_LOG(LogOculusPassthrough, Error, TEXT("Passthrough Static Mesh is nullptr")); return nullptr; } UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh(); if (!Mesh || !Mesh->GetRenderData()) { UE_LOG(LogOculusPassthrough, Error, TEXT("Passthrough Static Mesh has no Renderdata")); return nullptr; } if (Mesh->GetNumLODs() == 0) { UE_LOG(LogOculusPassthrough, Error, TEXT("Passthrough Static Mesh has no LODs")); return nullptr; } if (!Mesh->bAllowCPUAccess) { UE_LOG(LogOculusPassthrough, Error, TEXT("Passthrough Static Mesh Requires CPU Access")); return nullptr; } const int32 LODIndex = 0; FStaticMeshLODResources& LOD = Mesh->GetRenderData()->LODResources[LODIndex]; TArray Triangles; const int32 NumIndices = LOD.IndexBuffer.GetNumIndices(); for (int32 i = 0; i < NumIndices; ++i) { Triangles.Add(LOD.IndexBuffer.GetIndex(i)); } TArray Vertices; const int32 NumVertices = LOD.VertexBuffers.PositionVertexBuffer.GetNumVertices(); for (int32 i = 0; i < NumVertices; ++i) { Vertices.Add((FVector)LOD.VertexBuffers.PositionVertexBuffer.VertexPosition(i)); } OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh = new OculusXRHMD::FOculusPassthroughMesh(Vertices, Triangles); return PassthroughMesh; } void UOculusXRPassthroughLayerComponent::AddSurfaceGeometry(AStaticMeshActor* StaticMeshActor, bool updateTransform) { if (StaticMeshActor) { UStaticMeshComponent* StaticMeshComponent = StaticMeshActor->GetStaticMeshComponent(); if (StaticMeshComponent) AddStaticSurfaceGeometry(StaticMeshComponent, updateTransform); } } void UOculusXRPassthroughLayerComponent::AddStaticSurfaceGeometry(UStaticMeshComponent* StaticMeshComponent, bool updateTransform) { if (!StaticMeshComponent) return; UOculusXRStereoLayerShapeUserDefined* UserShape = Cast(Shape); if (!UserShape) return; OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh = CreatePassthroughMesh(StaticMeshComponent); if (!PassthroughMesh) return; const FString MeshName = StaticMeshComponent->GetFullName(); const FTransform Transform = StaticMeshComponent->GetComponentTransform(); UserShape->AddGeometry(MeshName, PassthroughMesh, Transform, updateTransform); PassthroughComponentMap.Add(MeshName, StaticMeshComponent); MarkStereoLayerDirty(); } void UOculusXRPassthroughLayerComponent::AddProceduralSurfaceGeometry(UProceduralMeshComponent* ProceduralMeshComponent, bool updateTransform) { if (!ProceduralMeshComponent) return; UOculusXRStereoLayerShapeUserDefined* UserShape = Cast(Shape); if (!UserShape) return; OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh = CreatePassthroughMesh(ProceduralMeshComponent); if (!PassthroughMesh) return; const FString MeshName = ProceduralMeshComponent->GetFullName(); const FTransform Transform = ProceduralMeshComponent->GetComponentTransform(); UserShape->AddGeometry(MeshName, PassthroughMesh, Transform, updateTransform); PassthroughComponentMap.Add(MeshName, ProceduralMeshComponent); MarkStereoLayerDirty(); } void UOculusXRPassthroughLayerComponent::RemoveSurfaceGeometry(AStaticMeshActor* StaticMeshActor) { if (StaticMeshActor) RemoveSurfaceGeometryComponent(StaticMeshActor->GetStaticMeshComponent()); } void UOculusXRPassthroughLayerComponent::RemoveStaticSurfaceGeometry(UStaticMeshComponent* StaticMeshComponent) { RemoveSurfaceGeometryComponent(StaticMeshComponent); } void UOculusXRPassthroughLayerComponent::RemoveProceduralSurfaceGeometry(UProceduralMeshComponent* ProceduralMeshComponent) { RemoveSurfaceGeometryComponent(ProceduralMeshComponent); } void UOculusXRPassthroughLayerComponent::RemoveSurfaceGeometryComponent(UMeshComponent* MeshComponent) { if (!MeshComponent) return; UOculusXRStereoLayerShapeUserDefined* UserShape = Cast(Shape); if (!UserShape) return; const FString MeshName = MeshComponent->GetFullName(); UserShape->RemoveGeometry(MeshName); PassthroughComponentMap.Remove(MeshName); MarkStereoLayerDirty(); } bool UOculusXRPassthroughLayerComponent::IsSurfaceGeometry(AStaticMeshActor* StaticMeshActor) const { return StaticMeshActor ? IsSurfaceGeometryComponent(StaticMeshActor->GetStaticMeshComponent()) : false; } bool UOculusXRPassthroughLayerComponent::IsSurfaceGeometryComponent(const UMeshComponent* MeshComponent) const { return MeshComponent ? PassthroughComponentMap.Contains(MeshComponent->GetFullName()) : false; } void UOculusXRPassthroughLayerComponent::MarkPassthroughStyleForUpdate() { bPassthroughStyleNeedsUpdate = true; } #if WITH_EDITOR bool UOculusXRPassthroughLayerComponent::CanEditChange(const FProperty* InProperty) const { if (!Super::CanEditChange(InProperty)) return false; if (!(Shape && Shape.IsA(UOculusXRPassthroughLayerBase::StaticClass()))) { return true; } const FName PropertyName = InProperty->GetFName(); if (PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, Texture) || PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, bQuadPreserveTextureRatio) || PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, QuadSize) || PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, UVRect) || PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, StereoLayerType)) { return false; } return true; } #endif // WITH_EDITOR bool UOculusXRPassthroughLayerComponent::LayerRequiresTexture() { const bool bIsPassthroughShape = Shape && (Shape->IsA() || Shape->IsA()); return !bIsPassthroughShape; } void UOculusXRPassthroughLayerBase::SetTextureOpacity(float InOpacity) { if (TextureOpacityFactor == InOpacity) { return; } TextureOpacityFactor = InOpacity; MarkStereoLayerDirty(); } void UOculusXRPassthroughLayerBase::EnableEdgeColor(bool bInEnableEdgeColor) { if (bEnableEdgeColor == bInEnableEdgeColor) { return; } bEnableEdgeColor = bInEnableEdgeColor; MarkStereoLayerDirty(); } void UOculusXRPassthroughLayerBase::EnableColorMap(bool bInEnableColorMap) { if (bEnableColorMap == bInEnableColorMap) { return; } bEnableColorMap = bInEnableColorMap; MarkStereoLayerDirty(); } void UOculusXRPassthroughLayerBase::SetEdgeRenderingColor(FLinearColor InEdgeColor) { if (EdgeColor == InEdgeColor) { return; } EdgeColor = InEdgeColor; MarkStereoLayerDirty(); } void UOculusXRPassthroughLayerBase::EnableColorMapCurve(bool bInEnableColorMapCurve) { if (bUseColorMapCurve == bInEnableColorMapCurve) { return; } bUseColorMapCurve = bInEnableColorMapCurve; ColorArray = GenerateColorArray(bUseColorMapCurve, ColorMapCurve); MarkStereoLayerDirty(); } void UOculusXRPassthroughLayerBase::SetColorMapCurve(UCurveLinearColor* InColorMapCurve) { if (ColorMapCurve == InColorMapCurve) { return; } ColorMapCurve = InColorMapCurve; ColorArray = GenerateColorArray(bUseColorMapCurve, ColorMapCurve); MarkStereoLayerDirty(); } void UOculusXRPassthroughLayerBase::SetColorMapType(EOculusXRColorMapType InColorMapType) { if (ColorMapType == InColorMapType) { return; } ColorMapType = InColorMapType; ColorArray = GenerateColorArray(bUseColorMapCurve, ColorMapCurve); MarkStereoLayerDirty(); } void UOculusXRPassthroughLayerBase::SetColorArray(const TArray& InColorArray) { if (InColorArray.Num() == 0) { return; } if (ColorMapType != ColorMapType_GrayscaleToColor) { UE_LOG(LogOculusPassthrough, Warning, TEXT("SetColorArray is ignored for color map types other than Grayscale to Color.")); return; } if (bUseColorMapCurve) { UE_LOG(LogOculusPassthrough, Warning, TEXT("UseColorMapCurve is enabled on the layer. Automatic disable and use the Array for color lookup")); } bUseColorMapCurve = false; ColorArray = InColorArray; MarkStereoLayerDirty(); } void UOculusXRPassthroughLayerBase::ClearColorMap() { ColorArray.Empty(); } void UOculusXRPassthroughLayerBase::SetColorMapControls(float InContrast, float InBrightness, float InPosterize) { if (ColorMapType != ColorMapType_Grayscale && ColorMapType != ColorMapType_GrayscaleToColor) { UE_LOG(LogOculusPassthrough, Warning, TEXT("SetColorMapControls is ignored for color map types other than Grayscale and Grayscale to color.")); return; } Contrast = FMath::Clamp(InContrast, -1.0f, 1.0f); Brightness = FMath::Clamp(InBrightness, -1.0f, 1.0f); Posterize = FMath::Clamp(InPosterize, 0.0f, 1.0f); MarkStereoLayerDirty(); } void UOculusXRPassthroughLayerBase::SetBrightnessContrastSaturation(float InContrast, float InBrightness, float InSaturation) { if (ColorMapType != ColorMapType_ColorAdjustment) { UE_LOG(LogOculusPassthrough, Warning, TEXT("SetBrightnessContrastSaturation is ignored for color map types other than Color Adjustment.")); return; } Contrast = FMath::Clamp(InContrast, -1.0f, 1.0f); Brightness = FMath::Clamp(InBrightness, -1.0f, 1.0f); Saturation = FMath::Clamp(InSaturation, -1.0f, 1.0f); MarkStereoLayerDirty(); } void UOculusXRPassthroughLayerBase::SetColorScaleAndOffset(FLinearColor InColorScale, FLinearColor InColorOffset) { if (ColorScale == InColorScale && ColorOffset == InColorOffset) { return; } ColorScale = InColorScale; ColorOffset = InColorOffset; MarkStereoLayerDirty(); } void UOculusXRPassthroughLayerBase::SetLayerPlacement(EOculusXRPassthroughLayerOrder InLayerOrder) { if (LayerOrder == InLayerOrder) { UE_LOG(LogOculusPassthrough, Warning, TEXT("Same layer order as before, no change needed")); return; } LayerOrder = InLayerOrder; this->MarkStereoLayerDirty(); } void UOculusXRPassthroughLayerBase::SetColorLUTSource(class UOculusXRPassthroughColorLut* InColorLUTSource) { if (ColorMapType != ColorMapType_ColorLut && ColorMapType != ColorMapType_ColorLut_Interpolated) { UE_LOG(LogOculusPassthrough, Warning, TEXT("SetColorLUT is ignored for color map types other than Color LUT.")); return; } if (InColorLUTSource == ColorLUTSource) { UE_LOG(LogOculusPassthrough, Warning, TEXT("Same color LUT source as before, no change needed")); return; } ColorLUTSource = InColorLUTSource; MarkStereoLayerDirty(); } void UOculusXRPassthroughLayerBase::SetColorLUTTarget(class UOculusXRPassthroughColorLut* InColorLUTTarget) { if (ColorMapType != ColorMapType_ColorLut_Interpolated) { UE_LOG(LogOculusPassthrough, Warning, TEXT("SetColorLUTTarget is ignored for color map types other than Interpolated Color LUT.")); return; } if (InColorLUTTarget == ColorLUTTarget) { UE_LOG(LogOculusPassthrough, Warning, TEXT("Same color LUT source as before, no change needed")); return; } ColorLUTTarget = InColorLUTTarget; MarkStereoLayerDirty(); } void UOculusXRPassthroughLayerBase::SetColorLUTWeight(float InWeight) { if (ColorMapType != ColorMapType_ColorLut && ColorMapType != ColorMapType_ColorLut_Interpolated) { UE_LOG(LogOculusPassthrough, Warning, TEXT("SetWeight is ignored for color map types other than Color LUT.")); return; } if (LutWeight == InWeight) { UE_LOG(LogOculusPassthrough, Warning, TEXT("Same lut weight as before, no change needed")); return; } LutWeight = InWeight; MarkStereoLayerDirty(); } void UOculusXRPassthroughLayerBase::RemoveColorLut() { ColorLUTSource = nullptr; ColorLUTTarget = nullptr; MarkStereoLayerDirty(); } TArray UOculusXRPassthroughLayerBase::GenerateColorArrayFromColorCurve(const UCurveLinearColor* InColorMapCurve) const { if (InColorMapCurve == nullptr) { return TArray(); } TArray NewColorArray; constexpr uint32 TotalEntries = 256; NewColorArray.Empty(); NewColorArray.SetNum(TotalEntries); for (int32 Index = 0; Index < TotalEntries; ++Index) { const float Alpha = ((float)Index / TotalEntries); NewColorArray[Index] = InColorMapCurve->GetLinearColorValue(Alpha); } return NewColorArray; } TArray UOculusXRPassthroughLayerBase::GetOrGenerateNeutralColorArray() { if (NeutralColorArray.Num() == 0) { const uint32 TotalEntries = 256; NeutralColorArray.SetNum(TotalEntries); for (int32 Index = 0; Index < TotalEntries; ++Index) { NeutralColorArray[Index] = FLinearColor((float)Index / TotalEntries, (float)Index / TotalEntries, (float)Index / TotalEntries); } } return NeutralColorArray; } TArray UOculusXRPassthroughLayerBase::GenerateColorArray(bool bInUseColorMapCurve, const UCurveLinearColor* InColorMapCurve) { TArray NewColorArray; if (bInUseColorMapCurve) { NewColorArray = GenerateColorArrayFromColorCurve(InColorMapCurve); } // Check for existing Array, otherwise generate a neutral one if (NewColorArray.Num() == 0) { NewColorArray = GetOrGenerateNeutralColorArray(); } return NewColorArray; } TArray UOculusXRPassthroughLayerBase::GetColorArray(bool bInUseColorMapCurve, const UCurveLinearColor* InColorMapCurve) { if (ColorArray.Num() == 0) { if (bInUseColorMapCurve) { return GenerateColorArray(bInUseColorMapCurve, InColorMapCurve); } return GetOrGenerateNeutralColorArray(); } return ColorArray; } FColorLutDesc UOculusXRPassthroughLayerBase::GenerateColorLutDescription(float InLutWeight, UOculusXRPassthroughColorLut* InLutSource, UOculusXRPassthroughColorLut* InLutTarget) { TArray ColorLuts; if (InLutSource != nullptr && InLutSource->ColorLutType != EColorLutType::None) { uint64 ColorLutHandle = InLutSource->GetHandle(); if (ColorLutHandle != 0) { ColorLuts.Add(ColorLutHandle); } } if (ColorMapType == EOculusXRColorMapType::ColorMapType_ColorLut_Interpolated && ColorLuts.Num() > 0 && InLutSource->ColorLutType != EColorLutType::None) { uint64 ColorLutHandle = InLutTarget->GetHandle(); if (ColorLutHandle != 0) { ColorLuts.Add(ColorLutHandle); } } return FColorLutDesc(ColorLuts, InLutWeight); }