// Copyright 2024 - Michal Smoleň #include "NiagaraUIComponent.h" #include "Stats/Stats.h" #include "NiagaraRenderer.h" #include "NiagaraRibbonRendererProperties.h" #include "NiagaraSpriteRendererProperties.h" #include "NiagaraSystemInstanceController.h" #include "SNiagaraUISystemWidget.h" DECLARE_STATS_GROUP(TEXT("NiagaraUI"), STATGROUP_NiagaraUI, STATCAT_Advanced); DECLARE_CYCLE_STAT(TEXT("Generate Sprite Data"), STAT_GenerateSpriteData, STATGROUP_NiagaraUI); DECLARE_CYCLE_STAT(TEXT("Generate Ribbon Data"), STAT_GenerateRibbonData, STATGROUP_NiagaraUI); //PRAGMA_DISABLE_OPTIMIZATION void UNiagaraUIComponent::SetAutoActivateParticle(bool AutoActivate) { AutoActivateParticle = AutoActivate; } void UNiagaraUIComponent::RequestActivateSystem(bool Reset) { // If we already have a valid transform activate us right now if (HasSetTransform) { Activate(Reset); return; } // If not, wait until we get one SetAutoActivateParticle(true); } void UNiagaraUIComponent::RequestDeactivateSystem() { Deactivate(); SetAutoActivateParticle(false); } void UNiagaraUIComponent::SetTransformationForUIRendering(FVector2D Location, FVector2f Scale, float Angle) { const FVector NewLocation(Location.X, 0.f, -Location.Y); const FVector NewScale(Scale.X, 1.f, Scale.Y); const FRotator NewRotation(Angle, 0.f, 0.f);; WidgetRotationAngle = Angle; SetRelativeTransform(FTransform(NewRotation, NewLocation, NewScale)); if (AutoActivateParticle) { if (!IsActive()) ActivateSystem(); AutoActivateParticle = false; } HasSetTransform = true; } #if ENGINE_MINOR_VERSION < 1 struct FNiagaraRendererEntry { FNiagaraRendererEntry(UNiagaraRendererProperties* PropertiesIn, TSharedRef EmitterInstIn, UNiagaraEmitter* EmitterIn) : RendererProperties(PropertiesIn), EmitterInstance(EmitterInstIn), Emitter(EmitterIn) {} UNiagaraRendererProperties* RendererProperties; TSharedRef EmitterInstance; UNiagaraEmitter* Emitter; }; #else struct FNiagaraRendererEntry { FNiagaraRendererEntry(UNiagaraRendererProperties* PropertiesIn, TSharedRef EmitterInstIn, FVersionedNiagaraEmitter EmitterIn) : RendererProperties(PropertiesIn), EmitterInstance(EmitterInstIn), Emitter(EmitterIn) {} UNiagaraRendererProperties* RendererProperties; TSharedRef EmitterInstance; FVersionedNiagaraEmitter Emitter; }; #endif void UNiagaraUIComponent::RenderUI(SNiagaraUISystemWidget* NiagaraWidget, const FNiagaraUIRenderProperties& RenderProperties, const FNiagaraWidgetProperties* WidgetProperties) { TRACE_CPUPROFILER_EVENT_SCOPE(UNiagaraUIComponent::RenderUI); NiagaraWidget->ClearRenderData(); if (!IsActive()) return; if (!GetSystemInstanceController()) return; TArray Renderers; for(TSharedRef EmitterInst : GetSystemInstanceController()->GetSystemInstance_Unsafe()->GetEmitters()) { if (EmitterInst->IsDisabled()) continue; #if ENGINE_MINOR_VERSION < 1 if (UNiagaraEmitter* Emitter = EmitterInst->GetCachedEmitter()) { TArray Properties = Emitter->GetRenderers(); for (UNiagaraRendererProperties* Property : Properties) { FNiagaraRendererEntry NewEntry(Property, EmitterInst, Emitter); Renderers.Add(NewEntry); } } #else #if ENGINE_MINOR_VERSION < 4 FVersionedNiagaraEmitter Emitter = EmitterInst->GetCachedEmitter(); #else FVersionedNiagaraEmitter Emitter = EmitterInst->GetVersionedEmitter(); #endif if (Emitter.Emitter) { TArray Properties = Emitter.GetEmitterData()->GetRenderers(); for (UNiagaraRendererProperties* Property : Properties) { FNiagaraRendererEntry NewEntry(Property, EmitterInst, Emitter); Renderers.Add(NewEntry); } } #endif } Algo::Sort(Renderers, [] (FNiagaraRendererEntry& FirstElement, FNiagaraRendererEntry& SecondElement) {return FirstElement.RendererProperties->SortOrderHint < SecondElement.RendererProperties->SortOrderHint;}); for (FNiagaraRendererEntry Renderer : Renderers) { #if ENGINE_MINOR_VERSION < 1 if (Renderer.RendererProperties && Renderer.RendererProperties->GetIsEnabled() && Renderer.RendererProperties->IsSimTargetSupported(Renderer.Emitter->SimTarget)) { if (Renderer.Emitter->SimTarget == ENiagaraSimTarget::CPUSim) { #else if (Renderer.RendererProperties && Renderer.RendererProperties->GetIsEnabled() && Renderer.RendererProperties->IsSimTargetSupported(Renderer.Emitter.GetEmitterData()->SimTarget)) { if (Renderer.Emitter.GetEmitterData()->SimTarget == ENiagaraSimTarget::CPUSim) { #endif if (UNiagaraSpriteRendererProperties* SpriteRenderer = Cast(Renderer.RendererProperties)) { AddSpriteRendererData(NiagaraWidget, Renderer.EmitterInstance, SpriteRenderer, RenderProperties, WidgetProperties); } else if (UNiagaraRibbonRendererProperties* RibbonRenderer = Cast(Renderer.RendererProperties)) { AddRibbonRendererData(NiagaraWidget, Renderer.EmitterInstance, RibbonRenderer, RenderProperties, WidgetProperties); } } } } } FORCEINLINE FVector2D FastRotate(const FVector2D Vector, float Sin, float Cos) { return FVector2D(Cos * Vector.X - Sin * Vector.Y, Sin * Vector.X + Cos * Vector.Y); } void UNiagaraUIComponent::AddSpriteRendererData(SNiagaraUISystemWidget* NiagaraWidget, TSharedRef EmitterInst, UNiagaraSpriteRendererProperties* SpriteRenderer, const FNiagaraUIRenderProperties& RenderProperties, const FNiagaraWidgetProperties* WidgetProperties) { TRACE_CPUPROFILER_EVENT_SCOPE(UNiagaraUIComponent::AddSpriteRendererData); SCOPE_CYCLE_COUNTER(STAT_GenerateSpriteData); FVector ComponentLocation = GetRelativeLocation(); FVector ComponentScale = GetRelativeScale3D(); float WidgetRotationAngleRadians = FMath::DegreesToRadians(WidgetRotationAngle); #if ENGINE_MINOR_VERSION < 4 FNiagaraDataSet& DataSet = EmitterInst->GetData(); #else const FNiagaraDataSet& DataSet = EmitterInst->GetParticleData(); #endif if (!DataSet.IsCurrentDataValid()) return; FNiagaraDataBuffer& ParticleData = DataSet.GetCurrentDataChecked(); const int32 ParticleCount = ParticleData.GetNumInstances(); if (ParticleCount < 1) return; const float& ScaleFactor = RenderProperties.ScaleFactor; const FVector2f& ParentTopLeft = RenderProperties.ParentTopLeft; const FLinearColor& Tint = RenderProperties.Tint; #if ENGINE_MINOR_VERSION < 1 bool LocalSpace = EmitterInst->GetCachedEmitter()->bLocalSpace; #elif ENGINE_MINOR_VERSION < 4 bool LocalSpace = EmitterInst->GetCachedEmitterData()->bLocalSpace; #else bool LocalSpace = EmitterInst->GetVersionedEmitter().GetEmitterData()->bLocalSpace; #endif const float FakeDepthScaler = 1 / WidgetProperties->FakeDepthScaleDistance; FVector2D SubImageSize = SpriteRenderer->SubImageSize; FVector2D SubImageDelta = FVector2D::UnitVector / SubImageSize; const auto PositionData = FNiagaraDataSetAccessor:: CreateReader(DataSet, SpriteRenderer->PositionBinding.GetDataSetBindableVariable().GetName()); const auto ColorData = FNiagaraDataSetAccessor:: CreateReader(DataSet, SpriteRenderer->ColorBinding.GetDataSetBindableVariable().GetName()); const auto VelocityData = FNiagaraDataSetAccessor:: CreateReader(DataSet, SpriteRenderer->VelocityBinding.GetDataSetBindableVariable().GetName()); const auto AlignmentData = FNiagaraDataSetAccessor:: CreateReader(DataSet, SpriteRenderer->SpriteAlignmentBinding.GetDataSetBindableVariable().GetName()); const auto SizeData = FNiagaraDataSetAccessor:: CreateReader(DataSet, SpriteRenderer->SpriteSizeBinding.GetDataSetBindableVariable().GetName()); const auto RotationData = FNiagaraDataSetAccessor:: CreateReader(DataSet, SpriteRenderer->SpriteRotationBinding.GetDataSetBindableVariable().GetName()); const auto SubImageData = FNiagaraDataSetAccessor:: CreateReader(DataSet, SpriteRenderer->SubImageIndexBinding.GetDataSetBindableVariable().GetName()); const auto DynamicMaterialData = FNiagaraDataSetAccessor:: CreateReader(DataSet, SpriteRenderer->DynamicMaterialBinding.GetDataSetBindableVariable().GetName()); auto GetParticlePosition2D = [&PositionData](int32 Index) { const FVector3f Position3D = PositionData.GetSafe(Index, FVector3f::ZeroVector); return FVector2f(Position3D.X, -Position3D.Z); }; auto GetParticleDepth = [&PositionData](int32 Index) { return PositionData.GetSafe(Index, FVector::ZeroVector).Y; }; auto GetParticleColor = [&ColorData, &Tint](int32 Index) { return ColorData.GetSafe(Index, FLinearColor::White) * Tint; }; auto GetParticleVelocity2D = [&VelocityData](int32 Index) { const FVector3f Velocity3D = VelocityData.GetSafe(Index, FVector3f::ZeroVector); return FVector2D(Velocity3D.X, Velocity3D.Z); }; auto GetParticleAlignment2D = [&AlignmentData](int32 Index) { const FVector3f Alignment3D = AlignmentData.GetSafe(Index, FVector3f::ZeroVector); return FVector2D(Alignment3D.X, Alignment3D.Z); }; auto GetParticleSize = [&SizeData](int32 Index) { return SizeData.GetSafe(Index, FVector2f::UnitVector); }; auto GetParticleRotation = [&RotationData](int32 Index) { return RotationData.GetSafe(Index, 0.f); }; auto GetParticleSubImage = [&SubImageData](int32 Index) { return SubImageData.GetSafe(Index, 0.f); }; auto GetDynamicMaterialData = [&DynamicMaterialData](int32 Index) { return DynamicMaterialData.GetSafe(Index, FVector4f(0.f, 0.f, 0.f, 0.f)); }; FSlateVertex* VertexData; SlateIndex* IndexData; FSlateBrush Brush; UMaterialInterface* SpriteMaterial = SpriteRenderer->Material; NiagaraWidget->AddRenderData(&VertexData, &IndexData, SpriteMaterial, ParticleCount * 4, ParticleCount * 6); for (int ParticleIndex = 0; ParticleIndex < ParticleCount; ++ParticleIndex) { FVector2f ParticlePosition = GetParticlePosition2D(ParticleIndex) * ScaleFactor; FVector2f ParticleSize = GetParticleSize(ParticleIndex) * ScaleFactor; if (LocalSpace) { ParticlePosition *= FVector2f(ComponentScale.X, ComponentScale.Z); ParticlePosition = ParticlePosition.GetRotated(-WidgetRotationAngle); ParticlePosition += ParentTopLeft; ParticlePosition += FVector2f(ComponentLocation.X, -ComponentLocation.Z) * ScaleFactor; ParticleSize *= FVector2f(ComponentScale.X, ComponentScale.Z); } else { ParticlePosition += ParentTopLeft; } if (WidgetProperties->FakeDepthScale) { const float ParticleDepth = (-GetParticleDepth(ParticleIndex) + WidgetProperties->FakeDepthScaleDistance) * FakeDepthScaler; ParticleSize *= ParticleDepth; } const FVector2f ParticleHalfSize = ParticleSize * 0.5; const FColor ParticleColor = GetParticleColor(ParticleIndex).ToFColor(true); float ParticleRotationSin, ParticleRotationCos; if (SpriteRenderer->Alignment == ENiagaraSpriteAlignment::VelocityAligned || SpriteRenderer->Alignment == ENiagaraSpriteAlignment::CustomAlignment) { const FVector2D AlignmentVector = SpriteRenderer->Alignment == ENiagaraSpriteAlignment::VelocityAligned ? GetParticleVelocity2D(ParticleIndex) : GetParticleAlignment2D(ParticleIndex); ParticleRotationCos = FVector2D::DotProduct(AlignmentVector.GetSafeNormal(), FVector2D(0.f, 1.f)); const float SinSign = FVector2D::DotProduct(AlignmentVector, FVector2D(1.f, 0.f)) >= 0.f ? 1.f : -1.f; if (LocalSpace) { const float ParticleRotation = FMath::Acos(ParticleRotationCos) * SinSign - WidgetRotationAngleRadians; FMath::SinCos(&ParticleRotationSin, &ParticleRotationCos, ParticleRotation); } else { ParticleRotationSin = FMath::Sqrt(1 - ParticleRotationCos * ParticleRotationCos) * SinSign; } } else { float ParticleRotation = GetParticleRotation(ParticleIndex); if (LocalSpace) ParticleRotation -= WidgetRotationAngle; FMath::SinCos(&ParticleRotationSin, &ParticleRotationCos, FMath::DegreesToRadians(ParticleRotation)); } FVector2D TextureCoordinates[4]; if (SubImageSize != FVector2D(1.f, 1.f)) { const float ParticleSubImage = GetParticleSubImage(ParticleIndex); const int Row = (int)FMath::Floor(ParticleSubImage / SubImageSize.X) % (int)SubImageSize.Y; const int Column = (int)(ParticleSubImage) % (int)(SubImageSize.X); const float LeftUV = SubImageDelta.X * Column; const float Right = SubImageDelta.X * (Column + 1); const float TopUV = SubImageDelta.Y * Row; const float BottomUV = SubImageDelta.Y * (Row + 1); TextureCoordinates[0] = FVector2D(LeftUV, TopUV); TextureCoordinates[1] = FVector2D(Right, TopUV); TextureCoordinates[2] = FVector2D(LeftUV, BottomUV); TextureCoordinates[3] = FVector2D(Right, BottomUV); } else { TextureCoordinates[0] = FVector2D(0.f, 0.f); TextureCoordinates[1] = FVector2D(1.f, 0.f); TextureCoordinates[2] = FVector2D(0.f, 1.f); TextureCoordinates[3] = FVector2D(1.f, 1.f); } const FVector4f MaterialData = GetDynamicMaterialData(ParticleIndex); FVector2D PositionArray[4]; PositionArray[0] = FastRotate(FVector2D(-ParticleHalfSize.X, -ParticleHalfSize.Y), ParticleRotationSin, ParticleRotationCos); PositionArray[1] = FastRotate(FVector2D(ParticleHalfSize.X, -ParticleHalfSize.Y), ParticleRotationSin, ParticleRotationCos); PositionArray[2] = - PositionArray[1]; PositionArray[3] = - PositionArray[0]; const int VertexIndex = ParticleIndex * 4; const int indexIndex = ParticleIndex * 6; for (int i = 0; i < 4; ++i) { VertexData[VertexIndex + i].Position = FVector2f(PositionArray[i]) + ParticlePosition; VertexData[VertexIndex + i].Color = ParticleColor; VertexData[VertexIndex + i].TexCoords[0] = TextureCoordinates[i].X; VertexData[VertexIndex + i].TexCoords[1] = TextureCoordinates[i].Y; VertexData[VertexIndex + i].TexCoords[2] = MaterialData.X; VertexData[VertexIndex + i].TexCoords[3] = MaterialData.Y; } IndexData[indexIndex] = VertexIndex; IndexData[indexIndex + 1] = VertexIndex + 1; IndexData[indexIndex + 2] = VertexIndex + 2; IndexData[indexIndex + 3] = VertexIndex + 2; IndexData[indexIndex + 4] = VertexIndex + 1; IndexData[indexIndex + 5] = VertexIndex + 3; } } void UNiagaraUIComponent::AddRibbonRendererData(SNiagaraUISystemWidget* NiagaraWidget, TSharedRef EmitterInst, UNiagaraRibbonRendererProperties* RibbonRenderer, const FNiagaraUIRenderProperties& RenderProperties, const FNiagaraWidgetProperties* WidgetProperties) { TRACE_CPUPROFILER_EVENT_SCOPE(UNiagaraUIComponent::AddRibbonRendererData); SCOPE_CYCLE_COUNTER(STAT_GenerateRibbonData); FVector ComponentLocation = GetRelativeLocation(); FVector ComponentScale = GetRelativeScale3D(); #if ENGINE_MINOR_VERSION < 4 FNiagaraDataSet& DataSet = EmitterInst->GetData(); #else const FNiagaraDataSet& DataSet = EmitterInst->GetParticleData(); #endif if (!DataSet.IsCurrentDataValid()) return; FNiagaraDataBuffer& ParticleData = DataSet.GetCurrentDataChecked(); const int32 ParticleCount = ParticleData.GetNumInstances(); if (ParticleCount < 2) return; const float& ScaleFactor = RenderProperties.ScaleFactor; const FVector2f& ParentTopLeft = RenderProperties.ParentTopLeft; const FLinearColor& Tint = RenderProperties.Tint; #if ENGINE_MINOR_VERSION < 3 const auto SortKeyReader = RibbonRenderer->SortKeyDataSetAccessor.GetReader(DataSet); if (!ensureMsgf(SortKeyReader.IsValid(), TEXT("Invalid Sort Key Reader encrountered while rendering ribbon particles. This can happen if the particle is missing \"Particle State\" module."))) { return; } auto RibbonLinkOrderSort = [&SortKeyReader](TArray& Container) { Container.Sort([&SortKeyReader](const int32& A, const int32& B) { return (SortKeyReader[A] < SortKeyReader[B]); }); }; #else const auto RibbonLinkOrderFloatData = RibbonRenderer->RibbonLinkOrderFloatAccessor.GetReader(DataSet); const auto RibbonLinkOrderInt32Data = RibbonRenderer->RibbonLinkOrderInt32Accessor.GetReader(DataSet); if (!ensureMsgf(RibbonLinkOrderFloatData.IsValid() || RibbonLinkOrderInt32Data.IsValid(), TEXT("Invalid Sort Key Reader encrountered while rendering ribbon particles. This can happen if the particle is missing \"Particle State\" module."))) { return; } auto RibbonLinkOrderSort = [&RibbonLinkOrderFloatData, &RibbonLinkOrderInt32Data](TArray& Container) { if (RibbonLinkOrderFloatData.IsValid()) { Container.Sort([&RibbonLinkOrderFloatData](const uint32& A, const uint32& B) { return RibbonLinkOrderFloatData[A] < RibbonLinkOrderFloatData[B]; }); } else { Container.Sort([&RibbonLinkOrderInt32Data](const uint32& A, const uint32& B) { return RibbonLinkOrderInt32Data[A] > RibbonLinkOrderInt32Data[B]; }); } }; #endif const auto PositionData = RibbonRenderer->PositionDataSetAccessor.GetReader(DataSet); const auto ColorData = FNiagaraDataSetAccessor::CreateReader(DataSet, RibbonRenderer->ColorBinding.GetDataSetBindableVariable().GetName()); const auto RibbonWidthData = RibbonRenderer->SizeDataSetAccessor.GetReader(DataSet); const auto DynamicMaterialData = FNiagaraDataSetAccessor::CreateReader(DataSet, RibbonRenderer->DynamicMaterialBinding.GetDataSetBindableVariable().GetName()); const auto RibbonFullIDData = RibbonRenderer->RibbonFullIDDataSetAccessor.GetReader(DataSet); auto GetParticlePosition2D = [&PositionData](int32 Index) { const FVector3f Position3D = PositionData.GetSafe(Index, FVector3f::ZeroVector); return FVector2f(Position3D.X, -Position3D.Z); }; auto GetParticleColor = [&ColorData, &Tint](int32 Index) { return ColorData.GetSafe(Index, FLinearColor::White) * Tint; }; auto GetParticleWidth = [&RibbonWidthData](int32 Index) { return RibbonWidthData.GetSafe(Index, 1.f); }; auto GetDynamicMaterialData = [&DynamicMaterialData](int32 Index) { return DynamicMaterialData.GetSafe(Index, FVector4f(0.f, 0.f, 0.f, 0.f)); }; #if ENGINE_MINOR_VERSION < 1 bool LocalSpace = EmitterInst->GetCachedEmitter()->bLocalSpace; #elif ENGINE_MINOR_VERSION < 4 bool LocalSpace = EmitterInst->GetCachedEmitterData()->bLocalSpace; #else bool LocalSpace = EmitterInst->GetVersionedEmitter().GetEmitterData()->bLocalSpace; #endif const bool FullIDs = RibbonFullIDData.IsValid(); const bool MultiRibbons = FullIDs; auto AddRibbonVerts = [&](TArray& RibbonIndices) { const int32 NumParticlesInRibbon = RibbonIndices.Num(); if (NumParticlesInRibbon < 2) return; FSlateVertex* VertexData; SlateIndex* IndexData; FSlateBrush Brush; UMaterialInterface* SpriteMaterial = RibbonRenderer->Material; NiagaraWidget->AddRenderData(&VertexData, &IndexData, SpriteMaterial, (NumParticlesInRibbon) * 2, (NumParticlesInRibbon - 1) * 6); int32 CurrentVertexIndex = 0; int32 CurrentIndexIndex = 0; const int32 StartDataIndex = RibbonIndices[0]; float TotalDistance = 0.0f; FVector2f LastPosition = GetParticlePosition2D(StartDataIndex); FVector2f CurrentPosition = FVector2f::ZeroVector; float CurrentWidth = 0.f; FVector2f LastToCurrentVector = FVector2f::ZeroVector; float LastToCurrentSize = 0.f; float LastU0 = 0.f; float LastU1 = 0.f; FVector2f LastParticleUIPosition = LastPosition * ScaleFactor; if (LocalSpace) { LastParticleUIPosition *= FVector2f(ComponentScale.X, ComponentScale.Z); LastParticleUIPosition = LastParticleUIPosition.GetRotated(-WidgetRotationAngle); LastParticleUIPosition += ParentTopLeft; LastParticleUIPosition += FVector2f(ComponentLocation.X, -ComponentLocation.Z) * ScaleFactor; } else { LastParticleUIPosition += ParentTopLeft; } int32 CurrentIndex = 1; int32 CurrentDataIndex = RibbonIndices[CurrentIndex]; CurrentPosition = GetParticlePosition2D(CurrentDataIndex); LastToCurrentVector = CurrentPosition - LastPosition; LastToCurrentSize = LastToCurrentVector.Size(); // Normalize LastToCurrVec LastToCurrentVector *= 1.f / LastToCurrentSize; const FColor InitialColor = GetParticleColor(StartDataIndex).ToFColor(true); const float InitialWidth = GetParticleWidth(StartDataIndex) * ScaleFactor; FVector2f InitialPositionArray[2]; InitialPositionArray[0] = LastToCurrentVector.GetRotated(90.f) * InitialWidth * 0.5f; InitialPositionArray[1] = -InitialPositionArray[0]; for (int i = 0; i < 2; ++i) { VertexData[CurrentVertexIndex + i].Position = InitialPositionArray[i] + LastParticleUIPosition; VertexData[CurrentVertexIndex + i].Color = InitialColor; VertexData[CurrentVertexIndex + i].TexCoords[0] = 0; VertexData[CurrentVertexIndex + i].TexCoords[1] = 1 - i; } CurrentVertexIndex += 2; int32 NextIndex = CurrentIndex + 1; while (NextIndex <= NumParticlesInRibbon) { const bool IsLastParticle = NextIndex == NumParticlesInRibbon; const int32 NextDataIndex = IsLastParticle ? -1 : RibbonIndices[NextIndex]; const FVector2f NextPosition = IsLastParticle ? FVector2f::ZeroVector : GetParticlePosition2D(NextDataIndex); FVector2f CurrentToNextVector = IsLastParticle ? LastToCurrentVector : NextPosition - CurrentPosition; const float CurrentToNextSize = CurrentToNextVector.Size(); CurrentWidth = GetParticleWidth(CurrentDataIndex) * ScaleFactor; FColor CurrentColor = GetParticleColor(CurrentDataIndex).ToFColor(true); // Normalize CurrToNextVec CurrentToNextVector *= 1.f / CurrentToNextSize; const FVector2f CurrentTangent = (LastToCurrentVector + CurrentToNextVector).GetSafeNormal(); TotalDistance += LastToCurrentSize; FVector2f CurrentPositionArray[2]; CurrentPositionArray[0] = CurrentTangent.GetRotated(90.f) * CurrentWidth * 0.5f; CurrentPositionArray[1] = -CurrentPositionArray[0]; FVector2f CurrentParticleUIPosition = CurrentPosition * ScaleFactor; if (LocalSpace) { CurrentParticleUIPosition *= FVector2f(ComponentScale.X, ComponentScale.Z); CurrentParticleUIPosition = CurrentParticleUIPosition.GetRotated(-WidgetRotationAngle); CurrentParticleUIPosition += ParentTopLeft; CurrentParticleUIPosition += FVector2f(ComponentLocation.X, -ComponentLocation.Z) * ScaleFactor; } else { CurrentParticleUIPosition += ParentTopLeft; } float CurrentU0 = 0.f; if (RibbonRenderer->UV0Settings.DistributionMode == ENiagaraRibbonUVDistributionMode::TiledOverRibbonLength) { CurrentU0 = LastU0 + LastToCurrentSize / RibbonRenderer->UV0Settings.TilingLength; } else { CurrentU0 = (float)CurrentIndex / ((float)NumParticlesInRibbon - 1.f); } FVector2f TextureCoordinates0[2]; TextureCoordinates0[0] = FVector2f(CurrentU0, 1.f); TextureCoordinates0[1] = FVector2f(CurrentU0, 0.f); FVector2f TextureCoordinates1[2]; if (WidgetProperties->PassDynamicParametersFromRibbon) { const FVector4f MaterialData = GetDynamicMaterialData(CurrentIndex); TextureCoordinates1[0] = FVector2f(MaterialData.X, MaterialData.Y); TextureCoordinates1[1] = FVector2f(MaterialData.X, MaterialData.Y); } else { float CurrentU1 = 0.f; if (RibbonRenderer->UV1Settings.DistributionMode == ENiagaraRibbonUVDistributionMode::TiledOverRibbonLength) { CurrentU1 = LastU1 + LastToCurrentSize / RibbonRenderer->UV1Settings.TilingLength; } else { CurrentU1 = (float)CurrentIndex / ((float)NumParticlesInRibbon - 1.f); } TextureCoordinates1[0] = FVector2f(CurrentU1, 1.f); TextureCoordinates1[1] = FVector2f(CurrentU1, 0.f); LastU1 = CurrentU1; } for (int i = 0; i < 2; ++i) { VertexData[CurrentVertexIndex + i].Position = CurrentPositionArray[i] + CurrentParticleUIPosition; VertexData[CurrentVertexIndex + i].Color = CurrentColor; VertexData[CurrentVertexIndex + i].TexCoords[0] = TextureCoordinates0[i].X; VertexData[CurrentVertexIndex + i].TexCoords[1] = TextureCoordinates0[i].Y; VertexData[CurrentVertexIndex + i].TexCoords[2] = TextureCoordinates1[i].X; VertexData[CurrentVertexIndex + i].TexCoords[3] = TextureCoordinates1[i].Y; } IndexData[CurrentIndexIndex] = CurrentVertexIndex - 2; IndexData[CurrentIndexIndex + 1] = CurrentVertexIndex - 1; IndexData[CurrentIndexIndex + 2] = CurrentVertexIndex; IndexData[CurrentIndexIndex + 3] = CurrentVertexIndex - 1; IndexData[CurrentIndexIndex + 4] = CurrentVertexIndex; IndexData[CurrentIndexIndex + 5] = CurrentVertexIndex + 1; CurrentVertexIndex += 2; CurrentIndexIndex += 6; CurrentIndex = NextIndex; CurrentDataIndex = NextDataIndex; LastPosition = CurrentPosition; LastParticleUIPosition = CurrentParticleUIPosition; CurrentPosition = NextPosition; LastToCurrentVector = CurrentToNextVector; LastToCurrentSize = CurrentToNextSize; LastU0 = CurrentU0; ++NextIndex; } }; if (!MultiRibbons) { TArray SortedIndices; for (int32 i = 0; i < ParticleCount; ++i) { SortedIndices.Add(i); } RibbonLinkOrderSort(SortedIndices); AddRibbonVerts(SortedIndices); } else { if (FullIDs) { TMap> MultiRibbonSortedIndices; for (int32 i = 0; i < ParticleCount; ++i) { TArray& Indices = MultiRibbonSortedIndices.FindOrAdd(RibbonFullIDData[i]); Indices.Add(i); } // Sort the ribbons by ID so that the draw order stays consistent. MultiRibbonSortedIndices.KeySort(TLess()); for (TPair>& Pair : MultiRibbonSortedIndices) { TArray& SortedIndices = Pair.Value; RibbonLinkOrderSort(SortedIndices); AddRibbonVerts(SortedIndices); }; } } } //PRAGMA_ENABLE_OPTIMIZATION