// Copyright Epic Games, Inc. All Rights Reserved. #include "BaseGizmos/ViewAdjustedStaticMeshGizmoComponent.h" #include "BaseGizmos/GizmoViewContext.h" #include "BaseGizmos/CombinedTransformGizmo.h" #include "BaseGizmos/ViewBasedTransformAdjusters.h" #include "BaseGizmos/GizmoRenderingUtil.h" #include "MaterialShared.h" #include "Materials/MaterialRenderProxy.h" #include "SceneInterface.h" #include "StaticMeshResources.h" #include "StaticMeshSceneProxy.h" #include "StaticMeshSceneProxyDesc.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(ViewAdjustedStaticMeshGizmoComponent) namespace ViewAdjustedStaticMeshGizmoComponentLocals { // Overriding a function here is necessary to be able to properly update the used materials to include // the hover material. class FViewAdjustedStaticMeshSceneProxyDesc : public FStaticMeshSceneProxyDesc { public: FViewAdjustedStaticMeshSceneProxyDesc(const UViewAdjustedStaticMeshGizmoComponent* Component) : FStaticMeshSceneProxyDesc(Component) { } virtual void GetUsedMaterials(TArray&OutMaterials, bool bGetDebugMaterials = false) const override { FStaticMeshSceneProxyDesc::GetUsedMaterials(OutMaterials); if (UViewAdjustedStaticMeshGizmoComponent* CastComponent = Cast(Component)) { if (UMaterialInterface* HoverMaterial = CastComponent->GetHoverOverrideMaterial()) { OutMaterials.Add(HoverMaterial); } } } }; class FViewAdjustedStaticMeshGizmoComponentProxy : public FStaticMeshSceneProxy { public: FViewAdjustedStaticMeshGizmoComponentProxy(UViewAdjustedStaticMeshGizmoComponent* Component) : FStaticMeshSceneProxy(FViewAdjustedStaticMeshSceneProxyDesc(Component), false) , TransformAdjuster(Component->GetTransformAdjuster()) , HoverOverrideMaterial(Component->GetHoverOverrideMaterial()) , bHovered(Component->IsBeingHovered()) , bHiddenByInteraction(Component->IsHiddenByInteraction()) , RenderVisibilityFunc(Component->GetRenderVisibilityFunction()) { } void SetIsHovered(bool bHoveredIn) { bHovered = bHoveredIn; } void SetIsHiddenByInteraction(bool bIsHidden) { bHiddenByInteraction = bIsHidden; } void SetTransformAdjuster(TSharedPtr TransformAdjusterIn) { TransformAdjuster = TransformAdjusterIn; } virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override { if (bHiddenByInteraction) { return; } // For the most part, the below is modeled off the FStaticMeshSceneProxy version of this method, with // various things cut out (debug view modes, etc) and some deep nesting turned out into early outs. for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { const FSceneView* View = Views[ViewIndex]; if (!(IsShown(View) && (VisibilityMap & (1 << ViewIndex)))) { continue; } // We can calculate our adjusted transform at this point, now that we have the view const TUniformBuffer* AdjustedTransformBuffer = nullptr; bool bAdjustedTransformDeterminantIsNegative = IsLocalToWorldDeterminantNegative(); if (TransformAdjuster.IsValid()) { UE::GizmoRenderingUtil::FSceneViewWrapper WrappedView(*View); FTransform NewTransform = TransformAdjuster->GetAdjustedComponentToWorld_RenderThread( WrappedView, FTransform(GetLocalToWorld())); if (RenderVisibilityFunc && !RenderVisibilityFunc(WrappedView, NewTransform)) { continue; } FMatrix NewTransformMatrix = NewTransform.ToMatrixWithScale(); bAdjustedTransformDeterminantIsNegative = (NewTransformMatrix.Determinant() < 0); const FBoxSphereBounds3d& OriginalLocalBounds = GetLocalBounds(); // This way of setting the transform is copied from FTriangleSetSceneProxy::GetDynamicMeshElements FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer = Collector.AllocateOneFrameResource(); DynamicPrimitiveUniformBuffer.Set(Collector.GetRHICommandList(), NewTransformMatrix, NewTransformMatrix, GetLocalBounds().TransformBy(NewTransformMatrix), GetLocalBounds(), /*bReceivesDecals*/ true, /*bHasPrecomputedVolumetricLightmap*/ false, AlwaysHasVelocity()); AdjustedTransformBuffer = &DynamicPrimitiveUniformBuffer.UniformBuffer; }//end getting adjusted transform ready else { // If we're not adjusting the transform, just check visibility with the original transform if (RenderVisibilityFunc && !RenderVisibilityFunc(UE::GizmoRenderingUtil::FSceneViewWrapper(*View), FTransform(GetLocalToWorld()))) { continue; } } FLODMask LODMask = GetLODMask(View); for (int32 LODIndex = 0; LODIndex < RenderData->LODResources.Num(); LODIndex++) { if (!(LODMask.ContainsLOD(LODIndex) && LODIndex >= ClampedMinLOD)) { continue; } const FStaticMeshLODResources& LODModel = RenderData->LODResources[LODIndex]; for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++) { const int32 NumBatches = GetNumMeshBatches(); for (int32 BatchIndex = 0; BatchIndex < NumBatches; BatchIndex++) { FMeshBatch& MeshBatch = Collector.AllocateMesh(); const FLODInfo::FSectionInfo& Section = LODs[LODIndex].Sections[SectionIndex]; // This selection and hit proxy id setting seems unneeded but we'll just keep it bool bSectionIsSelected = false; #if WITH_EDITOR if (GIsEditor) { bSectionIsSelected = Section.bSelected; MeshBatch.BatchHitProxyId = Section.HitProxy ? Section.HitProxy->Id : FHitProxyId(); } #endif if (!GetMeshElement(LODIndex, BatchIndex, SectionIndex, GetStaticDepthPriorityGroup(), bSectionIsSelected, // Not sure what this bAllowPreCulledIndices parameter is, but this is what FStaticMeshSceneProxy does true, MeshBatch)) { continue; } // Seems like there is only ever one of these... FMeshBatchElement& BatchElement = MeshBatch.Elements[0]; // The above GetMeshElement does not reflect the adjusted transform determinant, so we have to // redo those pieces of MeshBatch setup. // Updated output of FStaticMeshSceneProxy::ShouldRenderBackFaces() bool bShouldRenderBackFaces = bReverseCulling != bAdjustedTransformDeterminantIsNegative; // Updated value for bUseReversedIndices UMaterialInterface* MaterialInterface = Section.Material; const FMaterialRenderProxy* MaterialRenderProxy = MaterialInterface->GetRenderProxy(); const ERHIFeatureLevel::Type FeatureLevel = GetScene().GetFeatureLevel(); const FMaterial& Material = MaterialRenderProxy->GetIncompleteMaterialWithFallback(FeatureLevel); const bool bUseReversedIndices = bShouldRenderBackFaces && RenderData->LODResources[LODIndex].bHasReversedDepthOnlyIndices && !Material.IsTwoSided(); // This is originally done in FStaticMeshSceneProxy::SetMeshElementGeometrySource BatchElement.IndexBuffer = bUseReversedIndices ? &LODModel.AdditionalIndexBuffers->ReversedIndexBuffer : &LODModel.IndexBuffer; // Updated output of FStaticMeshSceneProxy::IsReversedCullingNeeded MeshBatch.ReverseCulling = bShouldRenderBackFaces && !bUseReversedIndices; // Done updating based on transform determinant // Gizmos probably don't want to be affected by view mode? MeshBatch.bCanApplyViewModeOverrides = false; // This is where we bind our adjusted transform: if (AdjustedTransformBuffer) { BatchElement.PrimitiveUniformBufferResource = AdjustedTransformBuffer; } // Apply hover override material if (bHovered && HoverOverrideMaterial) { MeshBatch.MaterialRenderProxy = HoverOverrideMaterial->GetRenderProxy(); } Collector.AddMesh(ViewIndex, MeshBatch); INC_DWORD_STAT_BY(STAT_StaticMeshTriangles, MeshBatch.GetNumPrimitives()); }// for each MeshBatch }// for each mesh section }// for each LOD }// for each view }//GetDynamicMeshElements() virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override { FPrimitiveViewRelevance Result = FStaticMeshSceneProxy::GetViewRelevance(View); Result.bDynamicRelevance = true; Result.bStaticRelevance = false; Result.bShadowRelevance = false; MaterialRelevance.SetPrimitiveViewRelevance(Result); return Result; } virtual void DrawStaticElements(FStaticPrimitiveDrawInterface* PDI) override {} virtual bool CanBeOccluded() const override { // If we're using a transform adjuster, we're going to say that we can't be occluded because in the common case // of keeping the component a constant view size, it can be arbitrarily large as we move away from it. This prevents // the component from occluding itself too. // This isn't actually necessary if we're using a non-depth-tested material. return !TransformAdjuster.IsValid() && FStaticMeshSceneProxy::CanBeOccluded(); }; private: TSharedPtr TransformAdjuster; UMaterialInterface* HoverOverrideMaterial = nullptr; bool bHovered = false; // It is tempting to use the visibility of the component to hide it during interaction, but that turns out to be // problematic because other things affect the visibility- for example the TRS gizmo constantly updates visibility // depending on the current gizmo mode (translate/rotate/scale). Instead, we want this setting to be an extra flag // that forces invisibility. bool bHiddenByInteraction = false; TFunction RenderVisibilityFunc = nullptr; }; } bool UViewAdjustedStaticMeshGizmoComponent::LineTraceComponent(FHitResult& OutHit, const FVector Start, const FVector End, const FCollisionQueryParams& Params) { if (!ensure(GizmoViewContext)) { return Super::LineTraceComponent(OutHit, Start, End, Params); } // If needed, update the physics data, then do the line trace if (TransformAdjuster.IsValid()) { FTransform OriginalComponentToWorld = GetComponentToWorld(); FTransform AdjustedComponentToWorld = TransformAdjuster->GetAdjustedComponentToWorld(*GizmoViewContext, OriginalComponentToWorld); if (RenderVisibilityFunc && !RenderVisibilityFunc(*GizmoViewContext, AdjustedComponentToWorld)) { return false; } if (!OriginalComponentToWorld.Equals(AdjustedComponentToWorld)) { BodyInstance.SetBodyTransform(AdjustedComponentToWorld, ETeleportType::None); BodyInstance.UpdateBodyScale(AdjustedComponentToWorld.GetScale3D()); } } else { if (RenderVisibilityFunc && !RenderVisibilityFunc(*GizmoViewContext, GetComponentToWorld())) { return false; } } return Super::LineTraceComponent(OutHit, Start, End, Params); } FBoxSphereBounds UViewAdjustedStaticMeshGizmoComponent::CalcBounds(const FTransform& LocalToWorld) const { FBoxSphereBounds OriginalBounds = Super::CalcBounds(LocalToWorld); if (!TransformAdjuster.IsValid()) { return OriginalBounds; } return TransformAdjuster->GetViewIndependentBounds(LocalToWorld, OriginalBounds); } FPrimitiveSceneProxy* UViewAdjustedStaticMeshGizmoComponent::CreateStaticMeshSceneProxy(Nanite::FMaterialAudit& NaniteMaterials, bool bCreateNanite) { return ::new ViewAdjustedStaticMeshGizmoComponentLocals::FViewAdjustedStaticMeshGizmoComponentProxy(this); } // Deprecated in 5.7 FMaterialRelevance UViewAdjustedStaticMeshGizmoComponent::GetMaterialRelevance(ERHIFeatureLevel::Type InFeatureLevel) const { return GetMaterialRelevance(GetFeatureLevelShaderPlatform_Checked(InFeatureLevel)); } FMaterialRelevance UViewAdjustedStaticMeshGizmoComponent::GetMaterialRelevance(EShaderPlatform InShaderPlatform) const { FMaterialRelevance Result = Super::GetMaterialRelevance(InShaderPlatform); if (HoverOverrideMaterial) { Result |= HoverOverrideMaterial->GetRelevance_Concurrent(InShaderPlatform); } return Result; } void UViewAdjustedStaticMeshGizmoComponent::SetTransformAdjuster(TSharedPtr Adjuster) { TransformAdjuster = Adjuster; MarkRenderStateDirty(); } void UViewAdjustedStaticMeshGizmoComponent::SetRenderVisibilityFunction(TFunction RenderVisibilityFuncIn) { RenderVisibilityFunc = RenderVisibilityFuncIn; MarkRenderStateDirty(); } void UViewAdjustedStaticMeshGizmoComponent::SetAllMaterials(UMaterialInterface* Material) { for (int i = 0; i < GetNumMaterials(); ++i) { SetMaterial(i, Material); } } void UViewAdjustedStaticMeshGizmoComponent::SetHoverOverrideMaterial(UMaterialInterface* Material) { HoverOverrideMaterial = Material; MarkRenderStateDirty(); } void UViewAdjustedStaticMeshGizmoComponent::SetSubstituteInteractionComponent(UPrimitiveComponent* NewComponent, const FTransform& RelativeTransform) { if (SubstituteInteractionComponent == NewComponent) { return; } if (SubstituteInteractionComponent) { if (SubstituteInteractionComponent->IsRegistered()) { SubstituteInteractionComponent->UnregisterComponent(); } SubstituteInteractionComponent->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); } SubstituteInteractionComponent = NewComponent; if (!NewComponent) { // If we're clearing the substitute component, we're done return; } if (NewComponent->IsRegistered()) { NewComponent->UnregisterComponent(); } NewComponent->AttachToComponent(this, FAttachmentTransformRules::KeepWorldTransform); NewComponent->SetRelativeTransform(RelativeTransform); NewComponent->RegisterComponent(); UpdateInteractingState(false); } void UViewAdjustedStaticMeshGizmoComponent::UpdateInteractingState(bool bInteracting) { // Note: we don't early out if we're already interacting because we want to be able to call // this function to update the scene proxy. bInteracted = bInteracting; if (SubstituteInteractionComponent) { SubstituteInteractionComponent->SetVisibility(bInteracting); } if (FPrimitiveSceneProxy* Proxy = GetSceneProxy()) { static_cast(Proxy) ->SetIsHiddenByInteraction(bInteracted && SubstituteInteractionComponent); } } void UViewAdjustedStaticMeshGizmoComponent::UpdateHoverState(bool bHoveringIn) { if (bHoveringIn != bHovered) { bHovered = bHoveringIn; if (FPrimitiveSceneProxy* Proxy = GetSceneProxy()) { static_cast(Proxy) ->SetIsHovered(bHovered); } } } void UViewAdjustedStaticMeshGizmoComponent::UpdateWorldLocalState(bool bWorldIn) { if (TransformAdjuster.IsValid()) { TransformAdjuster->UpdateWorldLocalState(bWorldIn); } // If able to, forward this information to the substitute component // Tempting to only do this if we're interacting, but what if we get the update just once, and // never update the substitute... if (IGizmoBaseComponentInterface* CastSubstituteComp = Cast(SubstituteInteractionComponent)) { CastSubstituteComp->UpdateWorldLocalState(bWorldIn); } }