// // Cut a partitioning poly by a list of polys, and add the resulting inside pieces to the // front list and back list. // static void SplitPartitioner ( UModel* Model, FPoly** PolyList, FPoly** FrontList, FPoly** BackList, int32 n, int32 nPolys, int32& nFront, int32& nBack, FPoly InfiniteEdPoly, TArray<FPoly*>& AllocatedFPolys ) { FPoly FrontPoly,BackPoly; while( n < nPolys ) { FPoly* Poly = PolyList[n]; switch( InfiniteEdPoly.SplitWithPlane(Poly->Vertices[0],Poly->Normal,&FrontPoly,&BackPoly,0) ) { case SP_Coplanar: // May occasionally happen. // UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got inficoplanar") ); break; case SP_Front: // Shouldn't happen if hull is correct. // UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got infifront") ); return; case SP_Split: InfiniteEdPoly = BackPoly; break; case SP_Back: break; } n++; } FPoly* New = new FPoly; *New = InfiniteEdPoly; New->Reverse(); New->iBrushPoly |= 0x40000000; FrontList[nFront++] = New; AllocatedFPolys.Add( New ); New = new FPoly; *New = InfiniteEdPoly; BackList[nBack++] = New; AllocatedFPolys.Add( New ); }
FPoly FPoly::BuildAndCutInfiniteFPoly(const FPlane& InPlane, const TArray<FPlane>& InCutPlanes, ABrush* InOwnerBrush) { FPoly PolyMerged = BuildInfiniteFPoly( InPlane ); PolyMerged.Finalize( InOwnerBrush, 1 ); FPoly Front, Back; int32 result; for( int32 p = 0 ; p < InCutPlanes.Num() ; ++p ) { const FPlane* Plane = &InCutPlanes[p]; result = PolyMerged.SplitWithPlane( Plane->GetSafeNormal() * Plane->W, Plane->GetSafeNormal(), &Front, &Back, 1 ); if( result == SP_Split ) { PolyMerged = Back; } } PolyMerged.Reverse(); return PolyMerged; }
// // Pick a splitter poly then split a pool of polygons into front and back polygons and // recurse. // // iParent = Parent Bsp node, or INDEX_NONE if this is the root node. // IsFront = 1 if this is the front node of iParent, 0 of back (undefined if iParent==INDEX_NONE) // void FBSPOps::SplitPolyList ( UModel *Model, int32 iParent, ENodePlace NodePlace, int32 NumPolys, FPoly **PolyList, EBspOptimization Opt, int32 Balance, int32 PortalBias, int32 RebuildSimplePolys ) { FMemMark Mark(FMemStack::Get()); // Keeping track of allocated FPoly structures to delete later on. TArray<FPoly*> AllocatedFPolys; // To account for big EdPolys split up. int32 NumPolysToAlloc = NumPolys + 8 + NumPolys/4; int32 NumFront=0; FPoly **FrontList = new(FMemStack::Get(),NumPolysToAlloc)FPoly*; int32 NumBack =0; FPoly **BackList = new(FMemStack::Get(),NumPolysToAlloc)FPoly*; FPoly *SplitPoly = FindBestSplit( NumPolys, PolyList, Opt, Balance, PortalBias ); // Add the splitter poly to the Bsp with either a new BspSurf or an existing one. if( RebuildSimplePolys ) { SplitPoly->iLink = Model->Surfs.Num(); } int32 iOurNode = bspAddNode(Model,iParent,NodePlace,0,SplitPoly); int32 iPlaneNode = iOurNode; // Now divide all polygons in the pool into (A) polygons that are // in front of Poly, and (B) polygons that are in back of Poly. // Coplanar polys are inserted immediately, before recursing. // If any polygons are split by Poly, we ignrore the original poly, // split it into two polys, and add two new polys to the pool. FPoly *FrontEdPoly = new FPoly; FPoly *BackEdPoly = new FPoly; // Keep track of allocations. AllocatedFPolys.Add( FrontEdPoly ); AllocatedFPolys.Add( BackEdPoly ); for( int32 i=0; i<NumPolys; i++ ) { FPoly *EdPoly = PolyList[i]; if( EdPoly == SplitPoly ) { continue; } switch( EdPoly->SplitWithPlane( SplitPoly->Vertices[0], SplitPoly->Normal, FrontEdPoly, BackEdPoly, 0 ) ) { case SP_Coplanar: if( RebuildSimplePolys ) { EdPoly->iLink = Model->Surfs.Num()-1; } iPlaneNode = bspAddNode( Model, iPlaneNode, NODE_Plane, 0, EdPoly ); break; case SP_Front: FrontList[NumFront++] = PolyList[i]; break; case SP_Back: BackList[NumBack++] = PolyList[i]; break; case SP_Split: // Create front & back nodes. FrontList[NumFront++] = FrontEdPoly; BackList [NumBack ++] = BackEdPoly; FrontEdPoly = new FPoly; BackEdPoly = new FPoly; // Keep track of allocations. AllocatedFPolys.Add( FrontEdPoly ); AllocatedFPolys.Add( BackEdPoly ); break; } } // Recursively split the front and back pools. if( NumFront > 0 ) SplitPolyList( Model, iOurNode, NODE_Front, NumFront, FrontList, Opt, Balance, PortalBias, RebuildSimplePolys ); if( NumBack > 0 ) SplitPolyList( Model, iOurNode, NODE_Back, NumBack, BackList, Opt, Balance, PortalBias, RebuildSimplePolys ); // Delete FPolys allocated above. We cannot use FMemStack::Get() for FPoly as the array data FPoly contains will be allocated in regular memory. for( int32 i=0; i<AllocatedFPolys.Num(); i++ ) { FPoly* AllocatedFPoly = AllocatedFPolys[i]; delete AllocatedFPoly; } Mark.Pop(); }
// // Recursively filter a set of polys defining a convex hull down the Bsp, // splitting it into two halves at each node and adding in the appropriate // face polys at splits. // static void FilterBound ( UModel* Model, FBox* ParentBound, int32 iNode, FPoly** PolyList, int32 nPolys, int32 Outside ) { FMemMark Mark(FMemStack::Get()); FBspNode& Node = Model->Nodes [iNode]; FBspSurf& Surf = Model->Surfs [Node.iSurf]; FVector Base = Surf.Plane * Surf.Plane.W; FVector& Normal = Model->Vectors[Surf.vNormal]; FBox Bound(0); Bound.Min.X = Bound.Min.Y = Bound.Min.Z = +WORLD_MAX; Bound.Max.X = Bound.Max.Y = Bound.Max.Z = -WORLD_MAX; // Split bound into front half and back half. FPoly** FrontList = new(FMemStack::Get(),nPolys*2+16)FPoly*; int32 nFront=0; FPoly** BackList = new(FMemStack::Get(),nPolys*2+16)FPoly*; int32 nBack=0; // Keeping track of allocated FPoly structures to delete later on. TArray<FPoly*> AllocatedFPolys; FPoly* FrontPoly = new FPoly; FPoly* BackPoly = new FPoly; // Keep track of allocations. AllocatedFPolys.Add( FrontPoly ); AllocatedFPolys.Add( BackPoly ); for( int32 i=0; i<nPolys; i++ ) { FPoly *Poly = PolyList[i]; switch( Poly->SplitWithPlane( Base, Normal, FrontPoly, BackPoly, 0 ) ) { case SP_Coplanar: // UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got coplanar") ); FrontList[nFront++] = Poly; BackList[nBack++] = Poly; break; case SP_Front: FrontList[nFront++] = Poly; break; case SP_Back: BackList[nBack++] = Poly; break; case SP_Split: FrontList[nFront++] = FrontPoly; BackList [nBack++] = BackPoly; FrontPoly = new FPoly; BackPoly = new FPoly; // Keep track of allocations. AllocatedFPolys.Add( FrontPoly ); AllocatedFPolys.Add( BackPoly ); break; default: UE_LOG(LogBSPOps, Fatal, TEXT("FZoneFilter::FilterToLeaf: Unknown split code") ); } } if( nFront && nBack ) { // Add partitioner plane to front and back. FPoly InfiniteEdPoly = FBSPOps::BuildInfiniteFPoly( Model, iNode ); InfiniteEdPoly.iBrushPoly = iNode; SplitPartitioner(Model,PolyList,FrontList,BackList,0,nPolys,nFront,nBack,InfiniteEdPoly,AllocatedFPolys); } else { // if( !nFront ) UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Empty fronthull") ); // if( !nBack ) UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Empty backhull") ); } // Recursively update all our childrens' bounding volumes. if( nFront > 0 ) { if( Node.iFront != INDEX_NONE ) FilterBound( Model, &Bound, Node.iFront, FrontList, nFront, Outside || Node.IsCsg() ); else if( Outside || Node.IsCsg() ) UpdateBoundWithPolys( Bound, FrontList, nFront ); else UpdateConvolutionWithPolys( Model, iNode, FrontList, nFront ); } if( nBack > 0 ) { if( Node.iBack != INDEX_NONE) FilterBound( Model, &Bound,Node.iBack, BackList, nBack, Outside && !Node.IsCsg() ); else if( Outside && !Node.IsCsg() ) UpdateBoundWithPolys( Bound, BackList, nBack ); else UpdateConvolutionWithPolys( Model, iNode, BackList, nBack ); } // Update parent bound to enclose this bound. if( ParentBound ) *ParentBound += Bound; // Delete FPolys allocated above. We cannot use FMemStack::Get() for FPoly as the array data FPoly contains will be allocated in regular memory. for( int32 i=0; i<AllocatedFPolys.Num(); i++ ) { FPoly* AllocatedFPoly = AllocatedFPolys[i]; delete AllocatedFPoly; } Mark.Pop(); }
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(); }