FProfilerManager::FProfilerManager( const ISessionManagerRef& InSessionManager )
	: SessionManager( InSessionManager )
	, CommandList( new FUICommandList() )
	, ProfilerActionManager( this )
	, Settings()

	, ProfilerType( EProfilerSessionTypes::InvalidOrMax )
	, bLivePreview( false )
	, bHasCaptureFileFullyProcessed( false )
{
	FEventGraphSample::InitializePropertyManagement();

#if	0
	// Performance tests.
	static FTotalTimeAndCount CurrentNative(0.0f, 0);
	static FTotalTimeAndCount CurrentPointer(0.0f, 0);
	static FTotalTimeAndCount CurrentShared(0.0f, 0);

	for( int32 Lx = 0; Lx < 16; ++Lx )
	{
		FRandomStream RandomStream( 666 );
		TArray<FEventGraphSample> EventsNative;
		TArray<FEventGraphSample*> EventsPointer;
		TArray<FEventGraphSamplePtr> EventsShared;

		const int32 NumEvents = 16384;
		for( int32 Nx = 0; Nx < NumEvents; ++Nx )
		{
			const double Rnd = RandomStream.FRandRange( 0.0f, 16384.0f );
			const FString EventName = TTypeToString<double>::ToString( Rnd );
			FEventGraphSample NativeEvent( *EventName );
			NativeEvent._InclusiveTimeMS = Rnd;

			FEventGraphSamplePtr SharedEvent = FEventGraphSample::CreateNamedEvent( *EventName );
			SharedEvent->_InclusiveTimeMS = Rnd;

			EventsNative.Add(NativeEvent);
			EventsPointer.Add(SharedEvent.Get());
			EventsShared.Add(SharedEvent);
		}

		{
			FProfilerScopedLogTime ScopedLog( TEXT("1.NativeSorting"), &CurrentNative );
			EventsNative.Sort( FEventGraphSampleLess() );
		}

		{
			FProfilerScopedLogTime ScopedLog( TEXT("2PointerSorting"), &CurrentPointer );
			EventsPointer.Sort( FEventGraphSampleLess() );
		}

		{
			FProfilerScopedLogTime ScopedLog( TEXT("3.SharedSorting"), &CurrentShared );
			FEventArraySorter::Sort( EventsShared, FEventGraphSample::GetEventPropertyByIndex(EEventPropertyIndex::InclusiveTimeMS).Name, EEventCompareOps::Less );
		}
	}
#endif // 0
}
	FFilesystemInfo( FString& InCachePath, int32 InDaysToDelete, int32 InMaxNumFoldersToCheck, int32 InMaxContinuousFileChecks )
		: CachePath( InCachePath )
		, UnusedFileTime( InDaysToDelete, 0, 0, 0 )
		, MaxNumFoldersToCheck( InMaxNumFoldersToCheck )
		, MaxContinuousFileChecks( InMaxContinuousFileChecks )
		, FoldersChecked( 0 )
	{
		CacheDirectories.Empty( 1000 );
		for( int32 Index = 0; Index < 1000; Index++ )
		{
			CacheDirectories.Add( Index );
		}
			
		// Initialize random stream using Cycles() 
		const uint32 Cycles = FPlatformTime::Cycles();
		FRandomStream RandomStream( *(int32*)&Cycles );
		// Shuffle
		for( int32 Index = CacheDirectories.Num() - 1; Index >= 0; Index-- )
		{
			const int32 RandomIndex = RandomStream.RandHelper(Index + 1);

			Exchange( CacheDirectories[ Index ], CacheDirectories[ RandomIndex ] );
		}
	}
void UPaperTerrainComponent::OnSplineEdited()
{
	// Ensure we have the data structure for the desired collision method
	if (SpriteCollisionDomain == ESpriteCollisionMode::Use3DPhysics)
	{
		CachedBodySetup = NewObject<UBodySetup>(this);
	}
	else
	{
		CachedBodySetup = nullptr;
	}

	const float SlopeAnalysisTimeRate = 10.0f;
	const float FillRasterizationTimeRate = 100.0f;

	GeneratedSpriteGeometry.Empty();

	if ((AssociatedSpline != nullptr) && (TerrainMaterial != nullptr))
	{
		if (AssociatedSpline->ReparamStepsPerSegment != ReparamStepsPerSegment)
		{
			AssociatedSpline->ReparamStepsPerSegment = ReparamStepsPerSegment;
			AssociatedSpline->UpdateSpline();
		}

		FRandomStream RandomStream(RandomSeed);

		const FInterpCurveVector& SplineInfo = AssociatedSpline->SplineInfo;

		float SplineLength = AssociatedSpline->GetSplineLength();


		struct FTerrainRuleHelper
		{
		public:
			FTerrainRuleHelper(const FPaperTerrainMaterialRule* Rule)
				: StartWidth(0.0f)
				, EndWidth(0.0f)
			{
				for (const UPaperSprite* Sprite : Rule->Body)
				{
					if (Sprite != nullptr)
					{
						const float Width = GetSpriteRenderDataBounds2D(Sprite->BakedRenderData).GetSize().X;
						if (Width > 0.0f)
						{
							ValidBodies.Add(Sprite);
							ValidBodyWidths.Add(Width);
						}
					}
				}

				if (Rule->StartCap != nullptr)
				{
					const float Width = GetSpriteRenderDataBounds2D(Rule->StartCap->BakedRenderData).GetSize().X;
					if (Width > 0.0f)
					{
						StartWidth = Width;
					}
				}

				if (Rule->EndCap != nullptr)
				{
					const float Width = GetSpriteRenderDataBounds2D(Rule->EndCap->BakedRenderData).GetSize().X;
					if (Width > 0.0f)
					{
						EndWidth = Width;
					}
				}
			}

			float StartWidth;
			float EndWidth;

			TArray<const UPaperSprite*> ValidBodies;
			TArray<float> ValidBodyWidths;

			int32 GenerateBodyIndex(FRandomStream& InRandomStream) const
			{
				check(ValidBodies.Num() > 0);
				return InRandomStream.GetUnsignedInt() % ValidBodies.Num();
			}
		};

		// Split the spline into segments based on the slope rules in the material
		TArray<FTerrainSegment> Segments;

		FTerrainSegment* ActiveSegment = new (Segments) FTerrainSegment();
		ActiveSegment->StartTime = 0.0f;
		ActiveSegment->EndTime = SplineLength;

		{
			float CurrentTime = 0.0f;
			while (CurrentTime < SplineLength)
			{
				const FTransform Frame(GetTransformAtDistance(CurrentTime));
				const FVector UnitTangent = Frame.GetUnitAxis(EAxis::X);
				const float RawSlopeAngleRadians = FMath::Atan2(FVector::DotProduct(UnitTangent, PaperAxisY), FVector::DotProduct(UnitTangent, PaperAxisX));
				const float RawSlopeAngle = FMath::RadiansToDegrees(RawSlopeAngleRadians);
				const float SlopeAngle = FMath::Fmod(FMath::UnwindDegrees(RawSlopeAngle) + 360.0f, 360.0f);

				const FPaperTerrainMaterialRule* DesiredRule = (TerrainMaterial->Rules.Num() > 0) ? &(TerrainMaterial->Rules[0]) : nullptr;
				for (const FPaperTerrainMaterialRule& TestRule : TerrainMaterial->Rules)
				{
					if ((SlopeAngle >= TestRule.MinimumAngle) && (SlopeAngle < TestRule.MaximumAngle))
					{
						DesiredRule = &TestRule;
					}
				}

				if (ActiveSegment->Rule != DesiredRule)
				{
					if (ActiveSegment->Rule == nullptr)
					{
						ActiveSegment->Rule = DesiredRule;
					}
					else
					{
						ActiveSegment->EndTime = CurrentTime;
						
						// Segment is too small, delete it
						if (ActiveSegment->EndTime < ActiveSegment->StartTime + 2.0f * SegmentOverlapAmount)
						{
							Segments.Pop(false);
						}

						ActiveSegment = new (Segments)FTerrainSegment();
						ActiveSegment->StartTime = CurrentTime;
						ActiveSegment->EndTime = SplineLength;
						ActiveSegment->Rule = DesiredRule;
					}
				}

				CurrentTime += SlopeAnalysisTimeRate;
			}
		}

		// Account for overlap
		for (FTerrainSegment& Segment : Segments)
		{
			Segment.StartTime -= SegmentOverlapAmount;
			Segment.EndTime += SegmentOverlapAmount;
		}

		// Convert those segments to actual geometry
		for (FTerrainSegment& Segment : Segments)
		{
			check(Segment.Rule);
			FTerrainRuleHelper RuleHelper(Segment.Rule);

			float RemainingSegStart = Segment.StartTime + RuleHelper.StartWidth;
			float RemainingSegEnd = Segment.EndTime - RuleHelper.EndWidth;
			const float BodyDistance = RemainingSegEnd - RemainingSegStart;
			float DistanceBudget = BodyDistance;

			bool bUseBodySegments = (DistanceBudget > 0.0f) && (RuleHelper.ValidBodies.Num() > 0);

			// Add the start cap
			if (RuleHelper.StartWidth > 0.0f)
			{
				new (Segment.Stamps) FTerrainSpriteStamp(Segment.Rule->StartCap, Segment.StartTime + RuleHelper.StartWidth * 0.5f, /*bIsEndCap=*/ bUseBodySegments);
			}

			// Add body segments
			if (bUseBodySegments)
			{
				int32 NumSegments = 0;
				float Position = RemainingSegStart;
				
				while (DistanceBudget > 0.0f)
				{
					const int32 BodyIndex = RuleHelper.GenerateBodyIndex(RandomStream);
					const UPaperSprite* Sprite = RuleHelper.ValidBodies[BodyIndex];
					const float Width = RuleHelper.ValidBodyWidths[BodyIndex];

 					if ((NumSegments > 0) && ((Width * 0.5f) > DistanceBudget))
 					{
 						break;
 					}
					new (Segment.Stamps) FTerrainSpriteStamp(Sprite, Position + (Width * 0.5f), /*bIsEndCap=*/ false);

					DistanceBudget -= Width;
					Position += Width;
					++NumSegments;
				}

				const float UsedSpace = (BodyDistance - DistanceBudget);
				const float OverallScaleFactor = BodyDistance / UsedSpace;
				
 				// Stretch body segments
				float PositionCorrectionSum = 0.0f;
 				for (int32 Index = 0; Index < NumSegments; ++Index)
 				{
					FTerrainSpriteStamp& Stamp = Segment.Stamps[Index + (Segment.Stamps.Num() - NumSegments)];
					
					const float WidthChange = (OverallScaleFactor - 1.0f) * Stamp.NominalWidth;
					const float FirstGapIsSmallerFactor = (Index == 0) ? 0.5f : 1.0f;
					PositionCorrectionSum += WidthChange * FirstGapIsSmallerFactor;

					Stamp.Scale = OverallScaleFactor;
					Stamp.Time += PositionCorrectionSum;
 				}
			}
			else
			{
				// Stretch endcaps
			}

			// Add the end cap
			if (RuleHelper.EndWidth > 0.0f)
			{
				new (Segment.Stamps) FTerrainSpriteStamp(Segment.Rule->EndCap, Segment.EndTime - RuleHelper.EndWidth * 0.5f, /*bIsEndCap=*/ bUseBodySegments);
			}
		}

		// Convert stamps into geometry
		SpawnSegments(Segments, !bClosedSpline || (bClosedSpline && !bFilledSpline));

		// Generate the background if the spline is closed
		if (bClosedSpline && bFilledSpline)
		{
			// Create a polygon from the spline
			FBox2D SplineBounds(ForceInit);
			TArray<FVector2D> SplinePolyVertices2D;
			TArray<float> SplineEdgeOffsetAmounts;
			{
				float CurrentTime = 0.0f;
				while (CurrentTime < SplineLength)
				{
					const float Param = AssociatedSpline->SplineReparamTable.Eval(CurrentTime, 0.0f);
					const FVector Position3D = AssociatedSpline->SplineInfo.Eval(Param, FVector::ZeroVector);
					const FVector2D Position2D = FVector2D(FVector::DotProduct(Position3D, PaperAxisX), FVector::DotProduct(Position3D, PaperAxisY));

					SplineBounds += Position2D;
					SplinePolyVertices2D.Add(Position2D);

					// Find the collision offset for this sample point
					float CollisionOffset = 0;
					for (int SegmentIndex = 0; SegmentIndex < Segments.Num(); ++SegmentIndex)
					{
						FTerrainSegment& Segment = Segments[SegmentIndex];
						if (CurrentTime >= Segment.StartTime && CurrentTime <= Segment.EndTime)
						{
							CollisionOffset = (Segment.Rule != nullptr) ? (Segment.Rule->CollisionOffset * 0.25f) : 0;
							break;
						}
					}
					SplineEdgeOffsetAmounts.Add(CollisionOffset);

					CurrentTime += FillRasterizationTimeRate;
				}
			}

			SimplifyPolygon(SplinePolyVertices2D, SplineEdgeOffsetAmounts);

			// Always CCW and facing forward regardless of spline winding
			TArray<FVector2D> CorrectedSplineVertices;
			PaperGeomTools::CorrectPolygonWinding(CorrectedSplineVertices, SplinePolyVertices2D, false);

			TArray<FVector2D> TriangulatedPolygonVertices;
			PaperGeomTools::TriangulatePoly(/*out*/TriangulatedPolygonVertices, CorrectedSplineVertices, false);

			GenerateCollisionDataFromPolygon(SplinePolyVertices2D, SplineEdgeOffsetAmounts, TriangulatedPolygonVertices);

			if (TerrainMaterial->InteriorFill != nullptr)
			{
				const UPaperSprite* FillSprite = TerrainMaterial->InteriorFill;
				FPaperTerrainSpriteGeometry& MaterialBatch = *new (GeneratedSpriteGeometry)FPaperTerrainSpriteGeometry(); //@TODO: Look up the existing one instead
				MaterialBatch.Material = FillSprite->GetDefaultMaterial();

				FSpriteDrawCallRecord& FillDrawCall = *new (MaterialBatch.Records) FSpriteDrawCallRecord();
				FillDrawCall.BuildFromSprite(FillSprite);
				FillDrawCall.RenderVerts.Empty();
				FillDrawCall.Color = TerrainColor;
				FillDrawCall.Destination = PaperAxisZ * 0.1f;

				const FVector2D TextureSize = GetSpriteRenderDataBounds2D(FillSprite->BakedRenderData).GetSize();
				const FVector2D SplineSize = SplineBounds.GetSize();

				GenerateFillRenderDataFromPolygon(FillSprite, FillDrawCall, TextureSize, TriangulatedPolygonVertices);

				//@TODO: Add support for the fill sprite being smaller than the entire texture
#if NOT_WORKING
				const float StartingDivisionPointX = FMath::CeilToFloat(SplineBounds.Min.X / TextureSize.X);
				const float StartingDivisionPointY = FMath::CeilToFloat(SplineBounds.Min.Y / TextureSize.Y);

				FPoly VerticalRemainder = SplineAsPolygon;
				for (float Y = StartingDivisionPointY; VerticalRemainder.Vertices.Num() > 0; Y += TextureSize.Y)
				{
					FPoly Top;
					FPoly Bottom;
					const FVector SplitBaseOuter = (Y * PaperAxisY);
					VerticalRemainder.SplitWithPlane(SplitBaseOuter, -PaperAxisY, &Top, &Bottom, 1);
					VerticalRemainder = Bottom;

					FPoly HorizontalRemainder = Top;
					for (float X = StartingDivisionPointX; HorizontalRemainder.Vertices.Num() > 0; X += TextureSize.X)
					{
						FPoly Left;
						FPoly Right;
						const FVector SplitBaseInner = (X * PaperAxisX) + (Y * PaperAxisY);
						HorizontalRemainder.SplitWithPlane(SplitBaseInner, -PaperAxisX, &Left, &Right, 1);
						HorizontalRemainder = Right;

						//BROKEN, function no longer exists (split into 2 parts)
						SpawnFromPoly(Segments, SplineEdgeOffsetAmounts, FillSprite, FillDrawCall, TextureSize, Left);
					}
				}
#endif
			}
		}

		// Draw debug frames at the start and end of the spline
#if PAPER_TERRAIN_DRAW_DEBUG
		{
			const float Time = 5.0f;
			{
				FTransform WorldTransform = GetTransformAtDistance(0.0f) * ComponentToWorld;
				DrawDebugCoordinateSystem(GetWorld(), WorldTransform.GetLocation(), FRotator(WorldTransform.GetRotation()), 30.0f, true, Time, SDPG_Foreground);
			}
			{
				FTransform WorldTransform = GetTransformAtDistance(SplineLength) * ComponentToWorld;
				DrawDebugCoordinateSystem(GetWorld(), WorldTransform.GetLocation(), FRotator(WorldTransform.GetRotation()), 30.0f, true, Time, SDPG_Foreground);
			}
		}
#endif
	}

	RecreateRenderState_Concurrent();
}
/** Generates a single z layer of VolumeDistanceField. */
void FStaticLightingSystem::CalculateVolumeDistanceFieldWorkRange(int32 ZIndex)
{
	const double StartTime = FPlatformTime::Seconds();
	FStaticLightingMappingContext MappingContext(NULL, *this);

	TArray<FVector4> SampleDirections;
	TArray<FVector2D> SampleDirectionUniforms;
	const int32 NumThetaSteps = FMath::Trunc(FMath::Sqrt(VolumeDistanceFieldSettings.NumVoxelDistanceSamples / (2.0f * (float)PI)));
	const int32 NumPhiSteps = FMath::Trunc(NumThetaSteps * (float)PI);
	FLMRandomStream RandomStream(0);
	GenerateStratifiedUniformHemisphereSamples(NumThetaSteps, NumPhiSteps, RandomStream, SampleDirections, SampleDirectionUniforms);
	TArray<FVector4> OtherHemisphereSamples;
	TArray<FVector2D> OtherSampleDirectionUniforms;
	GenerateStratifiedUniformHemisphereSamples(NumThetaSteps, NumPhiSteps, RandomStream, OtherHemisphereSamples, OtherSampleDirectionUniforms);

	for (int32 i = 0; i < OtherHemisphereSamples.Num(); i++)
	{
		FVector4 Sample = OtherHemisphereSamples[i];
		Sample.Z *= -1;
		SampleDirections.Add(Sample);
	}

	const FVector4 CellExtents = FVector4(DistanceFieldVoxelSize / 2, DistanceFieldVoxelSize / 2, DistanceFieldVoxelSize / 2);
	for (int32 YIndex = 0; YIndex < VolumeSizeY; YIndex++)
	{
		for (int32 XIndex = 0; XIndex < VolumeSizeX; XIndex++)
		{
			const FVector4 VoxelPosition = FVector4(XIndex, YIndex, ZIndex) * DistanceFieldVoxelSize + DistanceFieldVolumeBounds.Min + CellExtents;
			const int32 Index = ZIndex * VolumeSizeY * VolumeSizeX + YIndex * VolumeSizeX + XIndex;
	
			float MinDistance[2];
			MinDistance[0] = FLT_MAX;
			MinDistance[1] = FLT_MAX;

			int32 Hit[2];
			Hit[0] = 0;
			Hit[1] = 0;

			int32 HitFront[2];
			HitFront[0] = 0;
			HitFront[1] = 0;

			// Generate two distance fields
			// The first is for mostly horizontal triangles, the second is for mostly vertical triangles
			// Keeping them separate allows reconstructing a cleaner surface,
			// Otherwise there would be holes in the surface where an unclosed wall mesh intersects an unclosed ground mesh
			for (int32 i = 0; i < 2; i++)
			{
				for (int32 SampleIndex = 0; SampleIndex < SampleDirections.Num(); SampleIndex++)
				{
					const float ExtentDistance = DistanceFieldVolumeBounds.GetExtent().GetMax() * 2.0f;
					FLightRay Ray(
						VoxelPosition,
						VoxelPosition + SampleDirections[SampleIndex] * VolumeDistanceFieldSettings.VolumeMaxDistance,
						NULL,
						NULL
						);

					// Trace rays in all directions to find the closest solid surface
					FLightRayIntersection Intersection;
					AggregateMesh.IntersectLightRay(Ray, true, false, false, MappingContext.RayCache, Intersection);

					if (Intersection.bIntersects)
					{
						if ((i == 0 && FMath::Abs(Intersection.IntersectionVertex.WorldTangentZ.Z) >= .707f || i == 1 && FMath::Abs(Intersection.IntersectionVertex.WorldTangentZ.Z) < .707f))
						{
							Hit[i]++;
							if (Dot3(Ray.Direction, Intersection.IntersectionVertex.WorldTangentZ) < 0)
							{
								HitFront[i]++;
							}

							const float CurrentDistance = (VoxelPosition - Intersection.IntersectionVertex.WorldPosition).Size3();
							if (CurrentDistance < MinDistance[i])
							{
								MinDistance[i] = CurrentDistance;
							}
						}
					}
				}
				// Consider this voxel 'outside' an object if more than 75% of the rays hit front faces
				MinDistance[i] *= (Hit[i] == 0 || HitFront[i] > Hit[i] * .75f) ? 1 : -1;
			}
			
			// Create a mask storing where an intersection can possibly take place
			// This allows the reconstruction to ignore areas where large positive and negative distances come together,
			// Which is caused by unclosed surfaces.
			const uint8 Mask0 = FMath::Abs(MinDistance[0]) < DistanceFieldVoxelSize * 2 ? 255 : 0; 
			// 0 will be -MaxDistance, .5 will be 0, 1 will be +MaxDistance
			const float NormalizedDistance0 = FMath::Clamp(MinDistance[0] / VolumeDistanceFieldSettings.VolumeMaxDistance + .5f, 0.0f, 1.0f);

			const uint8 Mask1 = FMath::Abs(MinDistance[1]) < DistanceFieldVoxelSize * 2 ? 255 : 0; 
			const float NormalizedDistance1 = FMath::Clamp(MinDistance[1] / VolumeDistanceFieldSettings.VolumeMaxDistance + .5f, 0.0f, 1.0f);

			const FColor FinalValue(
				FMath::Clamp<uint8>(FMath::Trunc(NormalizedDistance0 * 255), 0, 255), 
				FMath::Clamp<uint8>(FMath::Trunc(NormalizedDistance1 * 255), 0, 255), 
				Mask0,
				Mask1
				);

			VolumeDistanceField[Index] = FinalValue;
		}
	}
	MappingContext.Stats.VolumeDistanceFieldThreadTime = FPlatformTime::Seconds() - StartTime;
}