From daed80b5d7567a03e2f184f4e57d88216ffab510 Mon Sep 17 00:00:00 2001 From: Juan Belon Date: Sun, 17 Mar 2024 17:01:27 +0100 Subject: [PATCH] #281 A couple of the old samples but using the new code --- .../RealtimeMeshBranchingLinesActor.cpp | 238 ++++++++++++++++++ .../RealtimeMeshHeightFieldAnimatedActor.cpp | 173 +++++++++++++ .../Public/RealtimeMeshBranchingLinesActor.h | 141 +++++++++++ .../RealtimeMeshHeightFieldAnimatedActor.h | 61 +++++ 4 files changed, 613 insertions(+) create mode 100644 Source/RealtimeMeshExamples/Private/RealtimeMeshBranchingLinesActor.cpp create mode 100644 Source/RealtimeMeshExamples/Private/RealtimeMeshHeightFieldAnimatedActor.cpp create mode 100644 Source/RealtimeMeshExamples/Public/RealtimeMeshBranchingLinesActor.h create mode 100644 Source/RealtimeMeshExamples/Public/RealtimeMeshHeightFieldAnimatedActor.h diff --git a/Source/RealtimeMeshExamples/Private/RealtimeMeshBranchingLinesActor.cpp b/Source/RealtimeMeshExamples/Private/RealtimeMeshBranchingLinesActor.cpp new file mode 100644 index 0000000..0c00af8 --- /dev/null +++ b/Source/RealtimeMeshExamples/Private/RealtimeMeshBranchingLinesActor.cpp @@ -0,0 +1,238 @@ +// Copyright TriAxis Games, L.L.C. & xixgames All Rights Reserved. + +#include "RealtimeMeshBranchingLinesActor.h" + +#include "RealtimeMeshSimple.h" + +ARealtimeMeshBranchingLinesActor::ARealtimeMeshBranchingLinesActor() +{ + PrimaryActorTick.bCanEverTick = false; +} + +void ARealtimeMeshBranchingLinesActor::OnGenerateMesh_Implementation() +{ + // Initialize to a simple mesh, this behaves the most like a ProceduralMeshComponent + // Where you can set the mesh data and forget about it. + URealtimeMeshSimple* RealtimeMesh = GetRealtimeMeshComponent()->InitializeRealtimeMesh(); + + // The most important part of the mesh data is the StreamSet, it contains the individual buffers, + // like position, tangents, texcoords, triangles etc. + FRealtimeMeshStreamSet StreamSet; + + // For this example we'll use a helper class to build the mesh data + // You can make your own helpers or skip them and use individual TRealtimeMeshStreamBuilder, + // or skip them entirely and copy data directly into the streams + TRealtimeMeshBuilderLocal Builder(StreamSet); + + // here we go ahead and enable all the basic mesh data parts + Builder.EnableTangents(); + Builder.EnableTexCoords(); + Builder.EnableColors(); // TODO + + // Poly groups allow us to easily create a single set of buffers with multiple sections by adding an index to the triangle data + Builder.EnablePolyGroups(); + + // CUSTOM BUILD OF THE BRANCHES + PreCacheCrossSection(); + GenerateMesh(Builder); + + // Setup the two material slots + RealtimeMesh->SetupMaterialSlot(0, "PrimaryMaterial", Material); + //RealtimeMesh->SetupMaterialSlot(1, "SecondaryMaterial"); + + // Now create the group key. This is a unique identifier for the section group + // A section group contains one or more sections that all share the underlying buffers + // these sections can overlap the used vertex/index ranges depending on use case. + const FRealtimeMeshSectionGroupKey GroupKey = FRealtimeMeshSectionGroupKey::Create(0, FName("TestTriangle")); + + // Now create the section key, this is a unique identifier for a section within a group + // The section contains the configuration for the section, like the material slot, + // and the draw type, as well as the range of the index/vertex buffers to use to render. + // Here we're using the version to create the key based on the PolyGroup index + const FRealtimeMeshSectionKey PolyGroup0SectionKey = FRealtimeMeshSectionKey::CreateForPolyGroup(GroupKey, 0); + //const FRealtimeMeshSectionKey PolyGroup1SectionKey = FRealtimeMeshSectionKey::CreateForPolyGroup(GroupKey, 1); + + // Now we create the section group, since the stream set has polygroups, this will create the sections as well + RealtimeMesh->CreateSectionGroup(GroupKey, StreamSet); + + // Update the configuration of both the polygroup sections. + RealtimeMesh->UpdateSectionConfig(PolyGroup0SectionKey, FRealtimeMeshSectionConfig(ERealtimeMeshSectionDrawType::Static, 0)); + //RealtimeMesh->UpdateSectionConfig(PolyGroup1SectionKey, FRealtimeMeshSectionConfig(ERealtimeMeshSectionDrawType::Static, 1)); + + Super::OnGenerateMesh_Implementation(); +} + +void ARealtimeMeshBranchingLinesActor::GenerateMesh(RealtimeMesh::TRealtimeMeshBuilderLocal& Builder) +{ + // ------------------------------------------------------- + // Setup the random number generator and create the branching structure + RngStream.Initialize(RandomSeed); + CreateSegments(); + + // ------------------------------------------------------- + // Now lets loop through all the defined segments and create a cylinder for each + for (int32 i = 0; i < Segments.Num(); i++) + { + GenerateCylinder(Segments[i].Start, Segments[i].End, Segments[i].Width, + RadialSegmentCount, Builder, bSmoothNormals); + } +} + +FVector ARealtimeMeshBranchingLinesActor::RotatePointAroundPivot(const FVector& InPoint, const FVector& InPivot, const FVector& InAngles) +{ + FVector Direction = InPoint - InPivot; // get point direction relative to pivot + Direction = FQuat::MakeFromEuler(InAngles) * Direction; // rotate it + return Direction + InPivot; // calculate rotated point +} + +void ARealtimeMeshBranchingLinesActor::PreCacheCrossSection() +{ + if (LastCachedCrossSectionCount == RadialSegmentCount) + { + return; + } + + // Generate a cross-section for use in cylinder generation + const float AngleBetweenQuads = (2.0f / static_cast(RadialSegmentCount)) * PI; + CachedCrossSectionPoints.Empty(); + + // Pre-calculate cross section points of a circle, two more than needed + for (int32 PointIndex = 0; PointIndex < (RadialSegmentCount + 2); PointIndex++) + { + const float Angle = static_cast(PointIndex) * AngleBetweenQuads; + CachedCrossSectionPoints.Add(FVector(FMath::Cos(Angle), FMath::Sin(Angle), 0)); + } + + LastCachedCrossSectionCount = RadialSegmentCount; +} + +void ARealtimeMeshBranchingLinesActor::CreateSegments() +{ + // We create the branching structure by constantly subdividing a line between two points by creating a new point in the middle. + // We then take that point and offset it in a random direction, by a random amount defined within limits. + // Next we take both of the newly created line halves, and subdivide them the same way. + // Each new midpoint also has a chance to create a new branch + // TODO This should really be recursive + Segments.Empty(); + float CurrentBranchOffset = MaxBranchOffset; + + if (bMaxBranchOffsetAsPercentageOfLength) + { + CurrentBranchOffset = (Start - End).Size() * (FMath::Clamp(MaxBranchOffset, 0.1f, 100.0f) / 100.0f); + } + + // Pre-calc a few floats from percentages + const float ChangeOfFork = FMath::Clamp(ChanceOfForkPercentage, 0.0f, 100.0f) / 100.0f; + const float BranchOffsetReductionEachGeneration = FMath::Clamp(BranchOffsetReductionEachGenerationPercentage, 0.0f, 100.0f) / 100.0f; + + // Add the first segment which is simply between the start and end points + Segments.Add(FRealtimeMeshBranchSegment(Start, End, TrunkWidth)); + + for (int32 iGen = 0; iGen < Iterations; iGen++) + { + TArray NewGen; + + for (const FRealtimeMeshBranchSegment& EachSegment : Segments) + { + FVector Midpoint = (EachSegment.End + EachSegment.Start) / 2; + + // Offset the midpoint by a random number along the normal + const FVector Normal = FVector::CrossProduct(EachSegment.End - EachSegment.Start, OffsetDirections[RngStream.RandRange(0, 1)]).GetSafeNormal(); + Midpoint += Normal * RngStream.RandRange(-CurrentBranchOffset, CurrentBranchOffset); + + // Create two new segments + NewGen.Add(FRealtimeMeshBranchSegment(EachSegment.Start, Midpoint, EachSegment.Width, EachSegment.ForkGeneration)); + NewGen.Add(FRealtimeMeshBranchSegment(Midpoint, EachSegment.End, EachSegment.Width, EachSegment.ForkGeneration)); + + // Chance of fork? + if (RngStream.FRand() > (1 - ChangeOfFork)) + { + // TODO Normalize the direction vector and calculate a new total length and then subdiv that for X generations + const FVector Direction = Midpoint - EachSegment.Start; + const FVector SplitEnd = (Direction * RngStream.FRandRange(ForkLengthMin, ForkLengthMax)).RotateAngleAxis(RngStream.FRandRange(ForkRotationMin, ForkRotationMax), OffsetDirections[RngStream.RandRange(0, 1)]) + Midpoint; + NewGen.Add(FRealtimeMeshBranchSegment(Midpoint, SplitEnd, EachSegment.Width * WidthReductionOnFork, EachSegment.ForkGeneration + 1)); + } + } + + Segments.Empty(); + Segments = NewGen; + + // Reduce the offset slightly each generation + CurrentBranchOffset = CurrentBranchOffset * BranchOffsetReductionEachGeneration; + } +} + +void ARealtimeMeshBranchingLinesActor::GenerateCylinder(const FVector& StartPoint, const FVector& EndPoint, const float InWidth, + const int32 InCrossSectionCount, TRealtimeMeshBuilderLocal& Builder, + const bool bInSmoothNormals/* = true*/) +{ + // Make a cylinder section + const float AngleBetweenQuads = (2.0f / static_cast(InCrossSectionCount)) * PI; + const float UMapPerQuad = 1.0f / static_cast(InCrossSectionCount); + + const FVector StartOffset = StartPoint - FVector(0, 0, 0); + const FVector Offset = EndPoint - StartPoint; + + // Find angle between vectors + const FVector LineDirection = (StartPoint - EndPoint).GetSafeNormal(); + const FVector RotationAngle = LineDirection.Rotation().Add(90.f, 0.f, 0.f).Euler(); + + // Start by building up vertices that make up the cylinder sides + for (int32 QuadIndex = 0; QuadIndex < InCrossSectionCount; QuadIndex++) + { + // Set up the vertices + FVector P0 = (CachedCrossSectionPoints[QuadIndex] * InWidth) + StartOffset; + P0 = RotatePointAroundPivot(P0, StartPoint, RotationAngle); + FVector P1 = CachedCrossSectionPoints[QuadIndex + 1] * InWidth + StartOffset; + P1 = RotatePointAroundPivot(P1, StartPoint, RotationAngle); + const FVector P2 = P1 + Offset; + const FVector P3 = P0 + Offset; + + // Normals + const FVector NormalCurrent = FVector::CrossProduct(P0 - P3, P1 - P3).GetSafeNormal(); + FVector NormalNext, NormalPrevious, AverageNormalRight, AverageNormalLeft; + if (bInSmoothNormals) + { + FVector P4 = (CachedCrossSectionPoints[QuadIndex + 2] * InWidth) + StartOffset; + P4 = RotatePointAroundPivot(P4, StartPoint, RotationAngle); + + // p1 to p4 to p2 + NormalNext = FVector::CrossProduct(P1 - P2, P4 - P2).GetSafeNormal(); + AverageNormalRight = ((NormalCurrent + NormalNext) / 2).GetSafeNormal(); + + const float PreviousAngle = static_cast(QuadIndex - 1) * AngleBetweenQuads; + FVector PMinus1 = FVector(FMath::Cos(PreviousAngle) * InWidth, FMath::Sin(PreviousAngle) * InWidth, 0.f) + StartOffset; + PMinus1 = RotatePointAroundPivot(PMinus1, StartPoint, RotationAngle); + + // p0 to p3 to pMinus1 + NormalPrevious = FVector::CrossProduct(P0 - PMinus1, P3 - PMinus1).GetSafeNormal(); + AverageNormalLeft = ((NormalCurrent + NormalPrevious) / 2).GetSafeNormal(); + } + + // Tangents (perpendicular to the surface) + const FVector Tangent = (P0 - P1).GetSafeNormal(); + + // UVs + const FVector2D UV0 = FVector2D(1.0f - (UMapPerQuad * QuadIndex), 1.0f); + const FVector2D UV1 = FVector2D(1.0f - (UMapPerQuad * (QuadIndex + 1)), 1.0f); + const FVector2D UV2 = FVector2D(1.0f - (UMapPerQuad * (QuadIndex + 1)), 0.0f); + const FVector2D UV3 = FVector2D(1.0f - (UMapPerQuad * QuadIndex), 0.0f); + + const int32 V0 = Builder.AddVertex(static_cast(P0)) + .SetNormalAndTangent(static_cast(bInSmoothNormals ? AverageNormalLeft : NormalCurrent), static_cast(Tangent)) + .SetTexCoord(static_cast(UV0)); + const int32 V1 = Builder.AddVertex(static_cast(P1)) + .SetNormalAndTangent(static_cast(bInSmoothNormals ? AverageNormalRight : NormalCurrent), static_cast(Tangent)) + .SetTexCoord(static_cast(UV1)); + const int32 V2 = Builder.AddVertex(static_cast(P2)) + .SetNormalAndTangent(static_cast(bInSmoothNormals ? AverageNormalRight : NormalCurrent), static_cast(Tangent)) + .SetTexCoord(static_cast(UV2)); + const int32 V3 = Builder.AddVertex(static_cast(P3)) + .SetNormalAndTangent(static_cast(bInSmoothNormals ? AverageNormalLeft : NormalCurrent), static_cast(Tangent)) + .SetTexCoord(static_cast(UV3)); + + // Add our 2 triangles, placing the vertices in counter clockwise order + Builder.AddTriangle(V3, V2, V0, 0); + Builder.AddTriangle(V2, V1, V0, 0); + } +} diff --git a/Source/RealtimeMeshExamples/Private/RealtimeMeshHeightFieldAnimatedActor.cpp b/Source/RealtimeMeshExamples/Private/RealtimeMeshHeightFieldAnimatedActor.cpp new file mode 100644 index 0000000..0233e89 --- /dev/null +++ b/Source/RealtimeMeshExamples/Private/RealtimeMeshHeightFieldAnimatedActor.cpp @@ -0,0 +1,173 @@ +// Copyright TriAxis Games, L.L.C. & xixgames All Rights Reserved. + +#include "RealtimeMeshHeightFieldAnimatedActor.h" + +#include "RealtimeMeshSimple.h" +#include "Async/ParallelFor.h" + +ARealtimeMeshHeightFieldAnimatedActor::ARealtimeMeshHeightFieldAnimatedActor() +{ + PrimaryActorTick.bCanEverTick = true; +} + +void ARealtimeMeshHeightFieldAnimatedActor::OnGenerateMesh_Implementation() +{ + // Initialize to a simple mesh, this behaves the most like a ProceduralMeshComponent + // Where you can set the mesh data and forget about it. + URealtimeMeshSimple* RealtimeMesh = GetRealtimeMeshComponent()->InitializeRealtimeMesh(); + // The most important part of the mesh data is the StreamSet, it contains the individual buffers, + // like position, tangents, texcoords, triangles etc. + FRealtimeMeshStreamSet StreamSet; + + // For this example we'll use a helper class to build the mesh data + // You can make your own helpers or skip them and use individual TRealtimeMeshStreamBuilder, + // or skip them entirely and copy data directly into the streams + TRealtimeMeshBuilderLocal Builder(StreamSet); + + // here we go ahead and enable all the basic mesh data parts + Builder.EnableTangents(); + Builder.EnableTexCoords(); + Builder.EnableColors(); // TODO + + // Poly groups allow us to easily create a single set of buffers with multiple sections by adding an index to the triangle data + Builder.EnablePolyGroups(); + + // CUSTOM BUILD OF THE WAVE + GenerateMesh(Builder); + + // Setup the two material slots + RealtimeMesh->SetupMaterialSlot(0, "WaveMaterial", Material); + // TODO: more mats? -> RealtimeMesh->SetupMaterialSlot(1, "SecondaryMaterial"); + + // Now create the group key. This is a unique identifier for the section group + // A section group contains one or more sections that all share the underlying buffers + // these sections can overlap the used vertex/index ranges depending on use case. + const FRealtimeMeshSectionGroupKey GroupKey = FRealtimeMeshSectionGroupKey::Create(0, FName("TestTriangle")); + + // Now create the section key, this is a unique identifier for a section within a group + // The section contains the configuration for the section, like the material slot, + // and the draw type, as well as the range of the index/vertex buffers to use to render. + // Here we're using the version to create the key based on the PolyGroup index + const FRealtimeMeshSectionKey PolyGroup0SectionKey = FRealtimeMeshSectionKey::CreateForPolyGroup(GroupKey, 0); + //const FRealtimeMeshSectionKey PolyGroup1SectionKey = FRealtimeMeshSectionKey::CreateForPolyGroup(GroupKey, 1); + + // Now we create the section group, since the stream set has polygroups, this will create the sections as well + RealtimeMesh->CreateSectionGroup(GroupKey, StreamSet); + + // Update the configuration of both the polygroup sections. + RealtimeMesh->UpdateSectionConfig(PolyGroup0SectionKey, FRealtimeMeshSectionConfig(ERealtimeMeshSectionDrawType::Static, 0)); + // TODO: RealtimeMesh->UpdateSectionConfig(PolyGroup1SectionKey, FRealtimeMeshSectionConfig(ERealtimeMeshSectionDrawType::Static, 1)); + + Super::OnGenerateMesh_Implementation(); +} + +void ARealtimeMeshHeightFieldAnimatedActor::GeneratePoints() +{ + // Setup example height data + // Combine variations of sine and cosine to create some variable waves + ParallelFor(HeightValues.Num(), [this](int32 PointIndex) + { + const int32 X = PointIndex / (WidthSections + 1); + const int32 Y = PointIndex % (WidthSections + 1); + + // Just some quick hardcoded offset numbers in there + const float ValueOne = FMath::Cos((X + CurrentAnimationFrameX)*ScaleFactor) * FMath::Sin((Y + CurrentAnimationFrameY)*ScaleFactor); + const float ValueTwo = FMath::Cos((X + CurrentAnimationFrameX*0.7f)*ScaleFactor*2.5f) * FMath::Sin((Y - CurrentAnimationFrameY*0.7f)*ScaleFactor*2.5f); + const float AvgValue = ((ValueOne + ValueTwo) / 2) * Size.Z; + HeightValues[PointIndex] = AvgValue; + + if (AvgValue > MaxHeightValue) + { + MaxHeightValue = AvgValue; + } + }); + +} + +void ARealtimeMeshHeightFieldAnimatedActor::Tick(float DeltaSeconds) +{ + if (bAnimateMesh) + { + CurrentAnimationFrameX += DeltaSeconds * AnimationSpeedX; + CurrentAnimationFrameY += DeltaSeconds * AnimationSpeedY; + OnGenerateMesh_Implementation(); + } +} + +void ARealtimeMeshHeightFieldAnimatedActor::GenerateMesh(TRealtimeMeshBuilderLocal& Builder) +{ + if (Size.X < 1 || Size.Y < 1 || LengthSections < 1 || WidthSections < 1) + { + return; + } + + SetupMesh(); + GeneratePoints(); + GenerateGrid(FVector2D(Size.X, Size.Y), LengthSections, WidthSections, HeightValues, Builder); +} + +void ARealtimeMeshHeightFieldAnimatedActor::SetupMesh() +{ + const int32 NumberOfPoints = (LengthSections + 1) * (WidthSections + 1); + if (NumberOfPoints != HeightValues.Num()) + { + HeightValues.Empty(); + HeightValues.AddUninitialized(NumberOfPoints); + } +} + +void ARealtimeMeshHeightFieldAnimatedActor::GenerateGrid(const FVector2D InSize, + const int32 InLengthSections, const int32 InWidthSections, const TArray& InHeightValues, + TRealtimeMeshBuilderLocal& Builder) +{ + // Note the coordinates are a bit weird here since I aligned it to the transform (X is forwards or "up", which Y is to the right) + // Should really fix this up and use standard X, Y coords then transform into object space? + const FVector2D SectionSize = FVector2D(InSize.X / InLengthSections, InSize.Y / InWidthSections); + int32 VertexIndex = 0; + + const float LengthSectionsAsFloat = static_cast(InLengthSections); + const float WidthSectionsAsFloat = static_cast(InWidthSections); + TArray BuildVertices; + for (int32 X = 0; X < InLengthSections + 1; X++) + { + for (int32 Y = 0; Y < InWidthSections + 1; Y++) + { + // Create a new vertex + const int32 NewVertIndex = VertexIndex++; + const FVector NewVertex = FVector(X * SectionSize.X, Y * SectionSize.Y, InHeightValues[NewVertIndex]); + + // Note that Unreal UV origin (0,0) is top left + const float U = static_cast(X) / LengthSectionsAsFloat; + const float V = static_cast(Y) / WidthSectionsAsFloat; + BuildVertices.Add(Builder.AddVertex(static_cast(NewVertex)).SetTexCoord(FVector2f(U, V))); + + // Once we've created enough verts we can start adding polygons + if (X > 0 && Y > 0) + { + // Each row is InWidthSections+1 number of points. + // And we have InLength+1 rows + // Index of current vertex in position is thus: (X * (InWidthSections + 1)) + Y; + const int32 TopRightIndex = BuildVertices[(X * (InWidthSections + 1)) + Y]; + const int32 TopLeftIndex = BuildVertices[TopRightIndex - 1]; + const int32 BottomRightIndex = BuildVertices[((X - 1) * (InWidthSections + 1)) + Y]; + const int32 BottomLeftIndex = BuildVertices[BottomRightIndex - 1]; + + // Now create two triangles from those four vertices + // The order of these (clockwise/counter-clockwise) dictates which way the normal will face. + // Normals + const FVector3f NormalCurrent = FVector3f::CrossProduct( + Builder.GetPosition(BottomLeftIndex) - Builder.GetPosition(TopLeftIndex), + Builder.GetPosition(TopLeftIndex) - Builder.GetPosition(TopRightIndex)).GetSafeNormal(); + + Builder.EditVertex(BottomLeftIndex).SetNormal(NormalCurrent); + Builder.EditVertex(TopRightIndex).SetNormal(NormalCurrent); + Builder.EditVertex(TopLeftIndex).SetNormal(NormalCurrent); + Builder.EditVertex(BottomRightIndex).SetNormal(NormalCurrent); + + // Add our 2 triangles, placing the vertices in counter clockwise order + Builder.AddTriangle(BottomLeftIndex, TopRightIndex, TopLeftIndex, 0); + Builder.AddTriangle(BottomLeftIndex, BottomRightIndex, TopRightIndex, 0); + } + } + } +} diff --git a/Source/RealtimeMeshExamples/Public/RealtimeMeshBranchingLinesActor.h b/Source/RealtimeMeshExamples/Public/RealtimeMeshBranchingLinesActor.h new file mode 100644 index 0000000..c3262c9 --- /dev/null +++ b/Source/RealtimeMeshExamples/Public/RealtimeMeshBranchingLinesActor.h @@ -0,0 +1,141 @@ +// Copyright TriAxis Games, L.L.C. & xixgames All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "RealtimeMeshActor.h" +#include "Mesh/RealtimeMeshBuilder.h" +#include "RealtimeMeshBranchingLinesActor.generated.h" + +// A simple struct to keep some data together +USTRUCT() +struct FRealtimeMeshBranchSegment +{ + GENERATED_BODY() + + UPROPERTY() + FVector Start = FVector::ZeroVector; + + UPROPERTY() + FVector End = FVector::ZeroVector; + + UPROPERTY() + float Width = 0.f; + + UPROPERTY() + int8 ForkGeneration = 0; + + FRealtimeMeshBranchSegment() + { + Start = FVector::ZeroVector; + End = FVector::ZeroVector; + Width = 1.f; + ForkGeneration = 0; + } + + FRealtimeMeshBranchSegment(const FVector& InStart, const FVector& InEnd) + { + Start = InStart; + End = InEnd; + Width = 1.f; + ForkGeneration = 0; + } + + FRealtimeMeshBranchSegment(const FVector& InStart, const FVector& InEnd, float InWidth) + { + Start = InStart; + End = InEnd; + Width = InWidth; + ForkGeneration = 0; + } + + FRealtimeMeshBranchSegment(const FVector& InStart, const FVector& InEnd, float InWidth, int8 InForkGeneration) + { + Start = InStart; + End = InEnd; + Width = InWidth; + ForkGeneration = InForkGeneration; + } +}; + +UCLASS() +class REALTIMEMESHEXAMPLES_API ARealtimeMeshBranchingLinesActor : public ARealtimeMeshActor +{ + GENERATED_BODY() + +public: + ARealtimeMeshBranchingLinesActor(); + + // Called when the mesh generation should happen. This could be called in the + // editor for placed actors, or at runtime for spawned actors. + virtual void OnGenerateMesh_Implementation() override; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters", meta = (MakeEditWidget)) + FVector Start = FVector::ZeroVector; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters", meta = (MakeEditWidget)) + FVector End = FVector(0, 0, 300); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + uint8 Iterations = 5; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + int32 RadialSegmentCount = 10; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + bool bSmoothNormals = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + int32 RandomSeed = 1238; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters", meta = (UIMin = "0.1", ClampMin = "0.1")) + float MaxBranchOffset = 20.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + bool bMaxBranchOffsetAsPercentageOfLength = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + float BranchOffsetReductionEachGenerationPercentage = 50.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + float TrunkWidth = 2.5f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters", meta = (UIMin = "0", UIMax = "100", ClampMin = "0", ClampMax = "100")) + float ChanceOfForkPercentage = 50.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + float WidthReductionOnFork = 0.75f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + float ForkLengthMin = 0.8f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + float ForkLengthMax = 1.3f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + float ForkRotationMin = 5.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + float ForkRotationMax = 40.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + TObjectPtr Material = nullptr; + +private: + void GenerateMesh(RealtimeMesh::TRealtimeMeshBuilderLocal& Builder); + void CreateSegments(); + void GenerateCylinder(const FVector& StartPoint, const FVector& EndPoint, const float InWidth, + const int32 InCrossSectionCount, + RealtimeMesh::TRealtimeMeshBuilderLocal& Builder, + const bool bInSmoothNormals = true); + + static FVector RotatePointAroundPivot(const FVector& InPoint, const FVector& InPivot, const FVector& InAngles); + void PreCacheCrossSection(); + + TArray Segments; + int32 LastCachedCrossSectionCount = 0; + TArray CachedCrossSectionPoints; + FRandomStream RngStream; + // Setup random offset directions + const TArray OffsetDirections = {FVector(1, 0, 0), FVector(0, 0, 1)}; +}; diff --git a/Source/RealtimeMeshExamples/Public/RealtimeMeshHeightFieldAnimatedActor.h b/Source/RealtimeMeshExamples/Public/RealtimeMeshHeightFieldAnimatedActor.h new file mode 100644 index 0000000..13b0076 --- /dev/null +++ b/Source/RealtimeMeshExamples/Public/RealtimeMeshHeightFieldAnimatedActor.h @@ -0,0 +1,61 @@ +// Copyright TriAxis Games, L.L.C. & xixgames All Rights Reserved. +#pragma once + +#include "CoreMinimal.h" +#include "RealtimeMeshActor.h" +#include "Mesh/RealtimeMeshBuilder.h" +#include "RealtimeMeshHeightFieldAnimatedActor.generated.h" + +UCLASS() +class REALTIMEMESHEXAMPLES_API ARealtimeMeshHeightFieldAnimatedActor : public ARealtimeMeshActor +{ + GENERATED_BODY() + +public: + ARealtimeMeshHeightFieldAnimatedActor(); + + // Called when the mesh generation should happen. This could be called in the + // editor for placed actors, or at runtime for spawned actors. + virtual void OnGenerateMesh_Implementation() override; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + FVector Size = FVector(1000.0f, 1000.0f, 100.0f); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + float ScaleFactor = 1.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + int32 LengthSections = 10; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + int32 WidthSections = 10; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + TObjectPtr Material = nullptr; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + bool bAnimateMesh = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + float AnimationSpeedX = 4.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Procedural Parameters") + float AnimationSpeedY = 4.5f; + + virtual void Tick(float DeltaSeconds) override; + +protected: + float CurrentAnimationFrameX = 0.0f; + float CurrentAnimationFrameY = 0.0f; + +private: + void GenerateMesh(RealtimeMesh::TRealtimeMeshBuilderLocal& Builder); + void SetupMesh(); + void GeneratePoints(); + void GenerateGrid(const FVector2D InSize, const int32 InLengthSections, + const int32 InWidthSections, const TArray& InHeightValues, + RealtimeMesh::TRealtimeMeshBuilderLocal& Builder); + + TArray HeightValues; + float MaxHeightValue = 0.0f; +};