Skip to content

Commit daed80b

Browse files
committed
TriAxis-Games#281 A couple of the old samples but using the new code
1 parent a6083e8 commit daed80b

File tree

4 files changed

+613
-0
lines changed

4 files changed

+613
-0
lines changed
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
// Copyright TriAxis Games, L.L.C. & xixgames All Rights Reserved.
2+
3+
#include "RealtimeMeshBranchingLinesActor.h"
4+
5+
#include "RealtimeMeshSimple.h"
6+
7+
ARealtimeMeshBranchingLinesActor::ARealtimeMeshBranchingLinesActor()
8+
{
9+
PrimaryActorTick.bCanEverTick = false;
10+
}
11+
12+
void ARealtimeMeshBranchingLinesActor::OnGenerateMesh_Implementation()
13+
{
14+
// Initialize to a simple mesh, this behaves the most like a ProceduralMeshComponent
15+
// Where you can set the mesh data and forget about it.
16+
URealtimeMeshSimple* RealtimeMesh = GetRealtimeMeshComponent()->InitializeRealtimeMesh<URealtimeMeshSimple>();
17+
18+
// The most important part of the mesh data is the StreamSet, it contains the individual buffers,
19+
// like position, tangents, texcoords, triangles etc.
20+
FRealtimeMeshStreamSet StreamSet;
21+
22+
// For this example we'll use a helper class to build the mesh data
23+
// You can make your own helpers or skip them and use individual TRealtimeMeshStreamBuilder,
24+
// or skip them entirely and copy data directly into the streams
25+
TRealtimeMeshBuilderLocal<uint16, FPackedNormal, FVector2DHalf, 1> Builder(StreamSet);
26+
27+
// here we go ahead and enable all the basic mesh data parts
28+
Builder.EnableTangents();
29+
Builder.EnableTexCoords();
30+
Builder.EnableColors(); // TODO
31+
32+
// Poly groups allow us to easily create a single set of buffers with multiple sections by adding an index to the triangle data
33+
Builder.EnablePolyGroups();
34+
35+
// CUSTOM BUILD OF THE BRANCHES
36+
PreCacheCrossSection();
37+
GenerateMesh(Builder);
38+
39+
// Setup the two material slots
40+
RealtimeMesh->SetupMaterialSlot(0, "PrimaryMaterial", Material);
41+
//RealtimeMesh->SetupMaterialSlot(1, "SecondaryMaterial");
42+
43+
// Now create the group key. This is a unique identifier for the section group
44+
// A section group contains one or more sections that all share the underlying buffers
45+
// these sections can overlap the used vertex/index ranges depending on use case.
46+
const FRealtimeMeshSectionGroupKey GroupKey = FRealtimeMeshSectionGroupKey::Create(0, FName("TestTriangle"));
47+
48+
// Now create the section key, this is a unique identifier for a section within a group
49+
// The section contains the configuration for the section, like the material slot,
50+
// and the draw type, as well as the range of the index/vertex buffers to use to render.
51+
// Here we're using the version to create the key based on the PolyGroup index
52+
const FRealtimeMeshSectionKey PolyGroup0SectionKey = FRealtimeMeshSectionKey::CreateForPolyGroup(GroupKey, 0);
53+
//const FRealtimeMeshSectionKey PolyGroup1SectionKey = FRealtimeMeshSectionKey::CreateForPolyGroup(GroupKey, 1);
54+
55+
// Now we create the section group, since the stream set has polygroups, this will create the sections as well
56+
RealtimeMesh->CreateSectionGroup(GroupKey, StreamSet);
57+
58+
// Update the configuration of both the polygroup sections.
59+
RealtimeMesh->UpdateSectionConfig(PolyGroup0SectionKey, FRealtimeMeshSectionConfig(ERealtimeMeshSectionDrawType::Static, 0));
60+
//RealtimeMesh->UpdateSectionConfig(PolyGroup1SectionKey, FRealtimeMeshSectionConfig(ERealtimeMeshSectionDrawType::Static, 1));
61+
62+
Super::OnGenerateMesh_Implementation();
63+
}
64+
65+
void ARealtimeMeshBranchingLinesActor::GenerateMesh(RealtimeMesh::TRealtimeMeshBuilderLocal<uint16, FPackedNormal, FVector2DHalf, 1>& Builder)
66+
{
67+
// -------------------------------------------------------
68+
// Setup the random number generator and create the branching structure
69+
RngStream.Initialize(RandomSeed);
70+
CreateSegments();
71+
72+
// -------------------------------------------------------
73+
// Now lets loop through all the defined segments and create a cylinder for each
74+
for (int32 i = 0; i < Segments.Num(); i++)
75+
{
76+
GenerateCylinder(Segments[i].Start, Segments[i].End, Segments[i].Width,
77+
RadialSegmentCount, Builder, bSmoothNormals);
78+
}
79+
}
80+
81+
FVector ARealtimeMeshBranchingLinesActor::RotatePointAroundPivot(const FVector& InPoint, const FVector& InPivot, const FVector& InAngles)
82+
{
83+
FVector Direction = InPoint - InPivot; // get point direction relative to pivot
84+
Direction = FQuat::MakeFromEuler(InAngles) * Direction; // rotate it
85+
return Direction + InPivot; // calculate rotated point
86+
}
87+
88+
void ARealtimeMeshBranchingLinesActor::PreCacheCrossSection()
89+
{
90+
if (LastCachedCrossSectionCount == RadialSegmentCount)
91+
{
92+
return;
93+
}
94+
95+
// Generate a cross-section for use in cylinder generation
96+
const float AngleBetweenQuads = (2.0f / static_cast<float>(RadialSegmentCount)) * PI;
97+
CachedCrossSectionPoints.Empty();
98+
99+
// Pre-calculate cross section points of a circle, two more than needed
100+
for (int32 PointIndex = 0; PointIndex < (RadialSegmentCount + 2); PointIndex++)
101+
{
102+
const float Angle = static_cast<float>(PointIndex) * AngleBetweenQuads;
103+
CachedCrossSectionPoints.Add(FVector(FMath::Cos(Angle), FMath::Sin(Angle), 0));
104+
}
105+
106+
LastCachedCrossSectionCount = RadialSegmentCount;
107+
}
108+
109+
void ARealtimeMeshBranchingLinesActor::CreateSegments()
110+
{
111+
// We create the branching structure by constantly subdividing a line between two points by creating a new point in the middle.
112+
// We then take that point and offset it in a random direction, by a random amount defined within limits.
113+
// Next we take both of the newly created line halves, and subdivide them the same way.
114+
// Each new midpoint also has a chance to create a new branch
115+
// TODO This should really be recursive
116+
Segments.Empty();
117+
float CurrentBranchOffset = MaxBranchOffset;
118+
119+
if (bMaxBranchOffsetAsPercentageOfLength)
120+
{
121+
CurrentBranchOffset = (Start - End).Size() * (FMath::Clamp(MaxBranchOffset, 0.1f, 100.0f) / 100.0f);
122+
}
123+
124+
// Pre-calc a few floats from percentages
125+
const float ChangeOfFork = FMath::Clamp(ChanceOfForkPercentage, 0.0f, 100.0f) / 100.0f;
126+
const float BranchOffsetReductionEachGeneration = FMath::Clamp(BranchOffsetReductionEachGenerationPercentage, 0.0f, 100.0f) / 100.0f;
127+
128+
// Add the first segment which is simply between the start and end points
129+
Segments.Add(FRealtimeMeshBranchSegment(Start, End, TrunkWidth));
130+
131+
for (int32 iGen = 0; iGen < Iterations; iGen++)
132+
{
133+
TArray<FRealtimeMeshBranchSegment> NewGen;
134+
135+
for (const FRealtimeMeshBranchSegment& EachSegment : Segments)
136+
{
137+
FVector Midpoint = (EachSegment.End + EachSegment.Start) / 2;
138+
139+
// Offset the midpoint by a random number along the normal
140+
const FVector Normal = FVector::CrossProduct(EachSegment.End - EachSegment.Start, OffsetDirections[RngStream.RandRange(0, 1)]).GetSafeNormal();
141+
Midpoint += Normal * RngStream.RandRange(-CurrentBranchOffset, CurrentBranchOffset);
142+
143+
// Create two new segments
144+
NewGen.Add(FRealtimeMeshBranchSegment(EachSegment.Start, Midpoint, EachSegment.Width, EachSegment.ForkGeneration));
145+
NewGen.Add(FRealtimeMeshBranchSegment(Midpoint, EachSegment.End, EachSegment.Width, EachSegment.ForkGeneration));
146+
147+
// Chance of fork?
148+
if (RngStream.FRand() > (1 - ChangeOfFork))
149+
{
150+
// TODO Normalize the direction vector and calculate a new total length and then subdiv that for X generations
151+
const FVector Direction = Midpoint - EachSegment.Start;
152+
const FVector SplitEnd = (Direction * RngStream.FRandRange(ForkLengthMin, ForkLengthMax)).RotateAngleAxis(RngStream.FRandRange(ForkRotationMin, ForkRotationMax), OffsetDirections[RngStream.RandRange(0, 1)]) + Midpoint;
153+
NewGen.Add(FRealtimeMeshBranchSegment(Midpoint, SplitEnd, EachSegment.Width * WidthReductionOnFork, EachSegment.ForkGeneration + 1));
154+
}
155+
}
156+
157+
Segments.Empty();
158+
Segments = NewGen;
159+
160+
// Reduce the offset slightly each generation
161+
CurrentBranchOffset = CurrentBranchOffset * BranchOffsetReductionEachGeneration;
162+
}
163+
}
164+
165+
void ARealtimeMeshBranchingLinesActor::GenerateCylinder(const FVector& StartPoint, const FVector& EndPoint, const float InWidth,
166+
const int32 InCrossSectionCount, TRealtimeMeshBuilderLocal<uint16, FPackedNormal, FVector2DHalf, 1>& Builder,
167+
const bool bInSmoothNormals/* = true*/)
168+
{
169+
// Make a cylinder section
170+
const float AngleBetweenQuads = (2.0f / static_cast<float>(InCrossSectionCount)) * PI;
171+
const float UMapPerQuad = 1.0f / static_cast<float>(InCrossSectionCount);
172+
173+
const FVector StartOffset = StartPoint - FVector(0, 0, 0);
174+
const FVector Offset = EndPoint - StartPoint;
175+
176+
// Find angle between vectors
177+
const FVector LineDirection = (StartPoint - EndPoint).GetSafeNormal();
178+
const FVector RotationAngle = LineDirection.Rotation().Add(90.f, 0.f, 0.f).Euler();
179+
180+
// Start by building up vertices that make up the cylinder sides
181+
for (int32 QuadIndex = 0; QuadIndex < InCrossSectionCount; QuadIndex++)
182+
{
183+
// Set up the vertices
184+
FVector P0 = (CachedCrossSectionPoints[QuadIndex] * InWidth) + StartOffset;
185+
P0 = RotatePointAroundPivot(P0, StartPoint, RotationAngle);
186+
FVector P1 = CachedCrossSectionPoints[QuadIndex + 1] * InWidth + StartOffset;
187+
P1 = RotatePointAroundPivot(P1, StartPoint, RotationAngle);
188+
const FVector P2 = P1 + Offset;
189+
const FVector P3 = P0 + Offset;
190+
191+
// Normals
192+
const FVector NormalCurrent = FVector::CrossProduct(P0 - P3, P1 - P3).GetSafeNormal();
193+
FVector NormalNext, NormalPrevious, AverageNormalRight, AverageNormalLeft;
194+
if (bInSmoothNormals)
195+
{
196+
FVector P4 = (CachedCrossSectionPoints[QuadIndex + 2] * InWidth) + StartOffset;
197+
P4 = RotatePointAroundPivot(P4, StartPoint, RotationAngle);
198+
199+
// p1 to p4 to p2
200+
NormalNext = FVector::CrossProduct(P1 - P2, P4 - P2).GetSafeNormal();
201+
AverageNormalRight = ((NormalCurrent + NormalNext) / 2).GetSafeNormal();
202+
203+
const float PreviousAngle = static_cast<float>(QuadIndex - 1) * AngleBetweenQuads;
204+
FVector PMinus1 = FVector(FMath::Cos(PreviousAngle) * InWidth, FMath::Sin(PreviousAngle) * InWidth, 0.f) + StartOffset;
205+
PMinus1 = RotatePointAroundPivot(PMinus1, StartPoint, RotationAngle);
206+
207+
// p0 to p3 to pMinus1
208+
NormalPrevious = FVector::CrossProduct(P0 - PMinus1, P3 - PMinus1).GetSafeNormal();
209+
AverageNormalLeft = ((NormalCurrent + NormalPrevious) / 2).GetSafeNormal();
210+
}
211+
212+
// Tangents (perpendicular to the surface)
213+
const FVector Tangent = (P0 - P1).GetSafeNormal();
214+
215+
// UVs
216+
const FVector2D UV0 = FVector2D(1.0f - (UMapPerQuad * QuadIndex), 1.0f);
217+
const FVector2D UV1 = FVector2D(1.0f - (UMapPerQuad * (QuadIndex + 1)), 1.0f);
218+
const FVector2D UV2 = FVector2D(1.0f - (UMapPerQuad * (QuadIndex + 1)), 0.0f);
219+
const FVector2D UV3 = FVector2D(1.0f - (UMapPerQuad * QuadIndex), 0.0f);
220+
221+
const int32 V0 = Builder.AddVertex(static_cast<FVector3f>(P0))
222+
.SetNormalAndTangent(static_cast<FVector3f>(bInSmoothNormals ? AverageNormalLeft : NormalCurrent), static_cast<FVector3f>(Tangent))
223+
.SetTexCoord(static_cast<FVector2f>(UV0));
224+
const int32 V1 = Builder.AddVertex(static_cast<FVector3f>(P1))
225+
.SetNormalAndTangent(static_cast<FVector3f>(bInSmoothNormals ? AverageNormalRight : NormalCurrent), static_cast<FVector3f>(Tangent))
226+
.SetTexCoord(static_cast<FVector2f>(UV1));
227+
const int32 V2 = Builder.AddVertex(static_cast<FVector3f>(P2))
228+
.SetNormalAndTangent(static_cast<FVector3f>(bInSmoothNormals ? AverageNormalRight : NormalCurrent), static_cast<FVector3f>(Tangent))
229+
.SetTexCoord(static_cast<FVector2f>(UV2));
230+
const int32 V3 = Builder.AddVertex(static_cast<FVector3f>(P3))
231+
.SetNormalAndTangent(static_cast<FVector3f>(bInSmoothNormals ? AverageNormalLeft : NormalCurrent), static_cast<FVector3f>(Tangent))
232+
.SetTexCoord(static_cast<FVector2f>(UV3));
233+
234+
// Add our 2 triangles, placing the vertices in counter clockwise order
235+
Builder.AddTriangle(V3, V2, V0, 0);
236+
Builder.AddTriangle(V2, V1, V0, 0);
237+
}
238+
}

0 commit comments

Comments
 (0)