claude-code-arcane 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +104 -55
  3. package/agents/career/career-strategist.md +88 -0
  4. package/agents/career/cv-reviewer.md +81 -0
  5. package/agents/career/hiring-manager.md +68 -0
  6. package/agents/career/mock-interviewer.md +76 -0
  7. package/agents/freelance/client-screener.md +84 -0
  8. package/agents/freelance/discovery-call.md +65 -0
  9. package/agents/freelance/pipeline-strategist.md +99 -0
  10. package/agents/freelance/proposal-reviewer.md +82 -0
  11. package/agents/game/ue-blueprint-specialist.md +125 -0
  12. package/agents/game/ue-gas-specialist.md +133 -0
  13. package/agents/game/ue-umg-specialist.md +131 -0
  14. package/dist/cli.js +57 -13
  15. package/docs/RELEASE-SETUP.md +1 -1
  16. package/docs/SKILLS-CATALOG.md +67 -4
  17. package/docs/USER-GUIDE.md +77 -72
  18. package/docs/agent-hierarchy.md +21 -1
  19. package/docs/presentations/build_arcane_deck.py +2 -2
  20. package/docs/presentations/build_arcane_deck_12.py +2 -2
  21. package/hooks/detect-unreal.sh +90 -0
  22. package/package.json +2 -2
  23. package/profiles/freelance.yaml +43 -0
  24. package/profiles/job-hunt.yaml +5 -2
  25. package/profiles/unreal-dev.yaml +97 -0
  26. package/rules/drafter-reviewer.md +16 -3
  27. package/rules/freelance-guardrails.md +53 -0
  28. package/rules/gamedev/unreal-code.md +125 -0
  29. package/skills/career-registry/SKILL.md +95 -0
  30. package/skills/career-registry/scripts/career_registry.py +488 -0
  31. package/skills/client-screen/SKILL.md +83 -0
  32. package/skills/client-screen/references/red-flags.md +95 -0
  33. package/skills/cover-letter/SKILL.md +4 -3
  34. package/skills/cv-tailor/SKILL.md +11 -2
  35. package/skills/freelance-deliver/SKILL.md +90 -0
  36. package/skills/freelance-hunt/SKILL.md +126 -0
  37. package/skills/freelance-hunt/references/templates/Connects.md +39 -0
  38. package/skills/freelance-hunt/references/templates/Contrato.md +60 -0
  39. package/skills/freelance-hunt/references/templates/Propuesta.md +66 -0
  40. package/skills/freelance-kickoff/SKILL.md +94 -0
  41. package/skills/freelance-pipeline/SKILL.md +109 -0
  42. package/skills/freelance-pipeline/scripts/import_history.py +425 -0
  43. package/skills/freelance-profile/SKILL.md +93 -0
  44. package/skills/freelance-proposal/SKILL.md +112 -0
  45. package/skills/freelance-scan/SKILL.md +196 -0
  46. package/skills/freelance-scan/references/dedup.md +35 -0
  47. package/skills/freelance-scan/references/platforms.md +167 -0
  48. package/skills/freelance-scan/scripts/freelance_search.py +1077 -0
  49. package/skills/install-mcp/SKILL.md +135 -46
  50. package/skills/install-mcp/references/unreal-operations.md +55 -0
  51. package/skills/interview-prep/SKILL.md +11 -3
  52. package/skills/job-aplicar/SKILL.md +5 -1
  53. package/skills/job-hunt/SKILL.md +17 -0
  54. package/skills/job-outcome/SKILL.md +15 -1
  55. package/skills/job-upskill/SKILL.md +10 -2
  56. package/skills/ue-actor-component-architecture/SKILL.md +509 -0
  57. package/skills/ue-actor-component-architecture/references/actor-lifecycle.md +301 -0
  58. package/skills/ue-actor-component-architecture/references/component-types.md +592 -0
  59. package/skills/ue-ai-navigation/SKILL.md +526 -0
  60. package/skills/ue-ai-navigation/references/behavior-tree-patterns.md +392 -0
  61. package/skills/ue-ai-navigation/references/behavior-tree-workflow.md +107 -0
  62. package/skills/ue-ai-navigation/references/custom-bttask.md +104 -0
  63. package/skills/ue-ai-navigation/references/eqs-reference.md +423 -0
  64. package/skills/ue-animation-system/SKILL.md +521 -0
  65. package/skills/ue-animation-system/references/anim-notify-reference.md +486 -0
  66. package/skills/ue-animation-system/references/locomotion-setup.md +550 -0
  67. package/skills/ue-async-threading/SKILL.md +433 -0
  68. package/skills/ue-async-threading/references/thread-safety-guide.md +213 -0
  69. package/skills/ue-async-threading/references/threading-patterns.md +345 -0
  70. package/skills/ue-audio-system/SKILL.md +527 -0
  71. package/skills/ue-audio-system/references/audio-setup-patterns.md +700 -0
  72. package/skills/ue-blueprints/SKILL.md +134 -0
  73. package/skills/ue-blueprints/references/communication.md +82 -0
  74. package/skills/ue-character-movement/SKILL.md +475 -0
  75. package/skills/ue-character-movement/references/cmc-extension-patterns.md +335 -0
  76. package/skills/ue-character-movement/references/movement-pipeline.md +189 -0
  77. package/skills/ue-cpp-foundations/SKILL.md +533 -0
  78. package/skills/ue-cpp-foundations/references/components-and-gc.md +107 -0
  79. package/skills/ue-cpp-foundations/references/container-patterns.md +544 -0
  80. package/skills/ue-cpp-foundations/references/delegate-patterns.md +497 -0
  81. package/skills/ue-cpp-foundations/references/gameplay-classes-cpp.md +136 -0
  82. package/skills/ue-cpp-foundations/references/property-specifiers.md +306 -0
  83. package/skills/ue-data-assets-tables/SKILL.md +527 -0
  84. package/skills/ue-data-assets-tables/references/asset-loading-patterns.md +306 -0
  85. package/skills/ue-data-assets-tables/references/data-driven-design.md +378 -0
  86. package/skills/ue-editor-tools/SKILL.md +523 -0
  87. package/skills/ue-editor-tools/references/detail-customization-patterns.md +395 -0
  88. package/skills/ue-editor-tools/references/editor-module-setup.md +480 -0
  89. package/skills/ue-game-features/SKILL.md +477 -0
  90. package/skills/ue-game-features/references/experience-system.md +222 -0
  91. package/skills/ue-game-features/references/game-feature-patterns.md +308 -0
  92. package/skills/ue-gameplay-abilities/SKILL.md +518 -0
  93. package/skills/ue-gameplay-abilities/references/ability-task-reference.md +391 -0
  94. package/skills/ue-gameplay-abilities/references/gameplay-effect-reference.md +397 -0
  95. package/skills/ue-gameplay-abilities/references/gas-setup-patterns.md +369 -0
  96. package/skills/ue-gameplay-framework/SKILL.md +466 -0
  97. package/skills/ue-gameplay-framework/references/framework-class-map.md +243 -0
  98. package/skills/ue-input-system/SKILL.md +394 -0
  99. package/skills/ue-input-system/references/cpp-setup.md +131 -0
  100. package/skills/ue-input-system/references/enhanced-input-workflow.md +117 -0
  101. package/skills/ue-input-system/references/input-action-reference.md +731 -0
  102. package/skills/ue-mass-entity/SKILL.md +476 -0
  103. package/skills/ue-mass-entity/references/mass-entity-patterns.md +319 -0
  104. package/skills/ue-mass-entity/references/mass-fragment-reference.md +119 -0
  105. package/skills/ue-materials-rendering/SKILL.md +526 -0
  106. package/skills/ue-materials-rendering/references/material-parameter-reference.md +216 -0
  107. package/skills/ue-materials-rendering/references/post-process-settings.md +383 -0
  108. package/skills/ue-module-build-system/SKILL.md +487 -0
  109. package/skills/ue-module-build-system/references/build-cs-reference.md +418 -0
  110. package/skills/ue-module-build-system/references/common-build-errors.md +381 -0
  111. package/skills/ue-naming-conventions/SKILL.md +142 -0
  112. package/skills/ue-naming-conventions/references/asset-name-modifiers.md +233 -0
  113. package/skills/ue-naming-conventions/references/content-structure.md +83 -0
  114. package/skills/ue-networking-replication/SKILL.md +452 -0
  115. package/skills/ue-networking-replication/references/replication-patterns.md +526 -0
  116. package/skills/ue-networking-replication/references/rpc-decision-guide.md +393 -0
  117. package/skills/ue-niagara-effects/SKILL.md +531 -0
  118. package/skills/ue-niagara-effects/references/niagara-data-interfaces.md +491 -0
  119. package/skills/ue-niagara-effects/references/niagara-parameter-types.md +153 -0
  120. package/skills/ue-niagara-effects/references/niagara-workflow.md +115 -0
  121. package/skills/ue-packaging/SKILL.md +116 -0
  122. package/skills/ue-physics-collision/SKILL.md +423 -0
  123. package/skills/ue-physics-collision/references/collision-channel-setup.md +218 -0
  124. package/skills/ue-physics-collision/references/trace-patterns.md +499 -0
  125. package/skills/ue-procedural-generation/SKILL.md +505 -0
  126. package/skills/ue-procedural-generation/references/pcg-node-reference.md +381 -0
  127. package/skills/ue-procedural-generation/references/procedural-mesh-patterns.md +940 -0
  128. package/skills/ue-project-context/SKILL.md +305 -0
  129. package/skills/ue-sequencer-cinematics/SKILL.md +506 -0
  130. package/skills/ue-sequencer-cinematics/references/sequencer-patterns.md +629 -0
  131. package/skills/ue-serialization-savegames/SKILL.md +524 -0
  132. package/skills/ue-serialization-savegames/references/save-system-architecture.md +705 -0
  133. package/skills/ue-state-trees/SKILL.md +489 -0
  134. package/skills/ue-state-trees/references/state-tree-mass-integration.md +184 -0
  135. package/skills/ue-state-trees/references/state-tree-patterns.md +321 -0
  136. package/skills/ue-testing-debugging/SKILL.md +520 -0
  137. package/skills/ue-testing-debugging/references/automation-test-patterns.md +387 -0
  138. package/skills/ue-testing-debugging/references/profiling-commands.md +357 -0
  139. package/skills/ue-ui-umg-slate/SKILL.md +431 -0
  140. package/skills/ue-ui-umg-slate/references/common-ui-setup.md +484 -0
  141. package/skills/ue-ui-umg-slate/references/widget-types.md +487 -0
  142. package/skills/ue-world-level-streaming/SKILL.md +518 -0
  143. package/skills/ue-world-level-streaming/references/streaming-patterns.md +414 -0
  144. package/templates/gamedev/unreal-project-context.md +107 -0
@@ -0,0 +1,940 @@
1
+ # Procedural Mesh Patterns
2
+
3
+ Common algorithmic patterns for runtime mesh and content generation in Unreal Engine using `UProceduralMeshComponent`, `UInstancedStaticMeshComponent`, and supporting math utilities.
4
+
5
+ ---
6
+
7
+ ## Component Setup Boilerplate
8
+
9
+ ```cpp
10
+ // Header — MyProceduralActor.h
11
+ #pragma once
12
+ #include "GameFramework/Actor.h"
13
+ #include "ProceduralMeshComponent.h"
14
+ #include "Components/InstancedStaticMeshComponent.h"
15
+ #include "Components/SplineComponent.h"
16
+ #include "MyProceduralActor.generated.h"
17
+
18
+ UCLASS()
19
+ class AMyProceduralActor : public AActor
20
+ {
21
+ GENERATED_BODY()
22
+ public:
23
+ AMyProceduralActor();
24
+
25
+ UPROPERTY(VisibleAnywhere)
26
+ UProceduralMeshComponent* ProceduralMesh;
27
+
28
+ UPROPERTY(VisibleAnywhere)
29
+ UHierarchicalInstancedStaticMeshComponent* HISM;
30
+
31
+ UPROPERTY(VisibleAnywhere)
32
+ USplineComponent* Spline;
33
+ };
34
+
35
+ // Source — MyProceduralActor.cpp
36
+ #include "MyProceduralActor.h"
37
+
38
+ AMyProceduralActor::AMyProceduralActor()
39
+ {
40
+ PrimaryActorTick.bCanEverTick = false;
41
+
42
+ ProceduralMesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("ProceduralMesh"));
43
+ SetRootComponent(ProceduralMesh);
44
+ ProceduralMesh->bUseComplexAsSimpleCollision = false; // Use dedicated collision shapes
45
+
46
+ HISM = CreateDefaultSubobject<UHierarchicalInstancedStaticMeshComponent>(TEXT("HISM"));
47
+ HISM->SetupAttachment(RootComponent);
48
+ HISM->SetNumCustomDataFloats(2); // Reserve per-instance float channels
49
+
50
+ Spline = CreateDefaultSubobject<USplineComponent>(TEXT("Spline"));
51
+ Spline->SetupAttachment(RootComponent);
52
+ }
53
+ ```
54
+
55
+ ---
56
+
57
+ ## 1. Flat Quad Grid (Terrain Base)
58
+
59
+ Generates a simple flat or height-mapped mesh from a 2D grid of vertices.
60
+
61
+ ```cpp
62
+ // GridSize = number of cells per side. Vertex count = (GridSize+1)^2.
63
+ // Preallocate for performance.
64
+ void ATerrainActor::GenerateFlatGrid(int32 GridSize, float CellSize,
65
+ bool bCreateCollision)
66
+ {
67
+ const int32 VertexStride = GridSize + 1;
68
+ const int32 VertCount = VertexStride * VertexStride;
69
+ const int32 TriCount = GridSize * GridSize * 6;
70
+
71
+ TArray<FVector> Vertices; Vertices.Reserve(VertCount);
72
+ TArray<int32> Triangles; Triangles.Reserve(TriCount);
73
+ TArray<FVector> Normals; Normals.Reserve(VertCount);
74
+ TArray<FVector2D> UVs; UVs.Reserve(VertCount);
75
+ TArray<FColor> Colors;
76
+ TArray<FProcMeshTangent> Tangents;
77
+
78
+ for (int32 Row = 0; Row <= GridSize; Row++)
79
+ {
80
+ for (int32 Col = 0; Col <= GridSize; Col++)
81
+ {
82
+ float X = Col * CellSize;
83
+ float Y = Row * CellSize;
84
+ float Z = 0.f; // Replace with height sample for terrain
85
+
86
+ Vertices.Add(FVector(X, Y, Z));
87
+ Normals.Add(FVector::UpVector);
88
+ UVs.Add(FVector2D((float)Col / GridSize, (float)Row / GridSize));
89
+ }
90
+ }
91
+
92
+ for (int32 Row = 0; Row < GridSize; Row++)
93
+ {
94
+ for (int32 Col = 0; Col < GridSize; Col++)
95
+ {
96
+ int32 BL = Row * VertexStride + Col;
97
+ int32 BR = BL + 1;
98
+ int32 TL = BL + VertexStride;
99
+ int32 TR = TL + 1;
100
+
101
+ // Counter-clockwise = front face in UE
102
+ Triangles.Add(BL); Triangles.Add(TL); Triangles.Add(TR);
103
+ Triangles.Add(BL); Triangles.Add(TR); Triangles.Add(BR);
104
+ }
105
+ }
106
+
107
+ ProceduralMesh->CreateMeshSection(0, Vertices, Triangles, Normals,
108
+ UVs, Colors, Tangents, bCreateCollision);
109
+ }
110
+ ```
111
+
112
+ ### Height-Mapped Terrain with Normal Recalculation
113
+
114
+ ```cpp
115
+ float SampleHeight(float X, float Y, float Scale, int32 Seed)
116
+ {
117
+ // Seeded offset to vary noise field per-seed
118
+ float OffsetX = (float)(Seed % 1000) * 0.01f;
119
+ float OffsetY = (float)(Seed / 1000) * 0.01f;
120
+ return SampleOctaveNoise(X / Scale + OffsetX, Y / Scale + OffsetY, 5, 0.5f, 2.0f, 1.0f);
121
+ }
122
+
123
+ void RecalculateNormals(const TArray<FVector>& Vertices, const TArray<int32>& Triangles,
124
+ TArray<FVector>& OutNormals)
125
+ {
126
+ OutNormals.Init(FVector::ZeroVector, Vertices.Num());
127
+
128
+ for (int32 i = 0; i + 2 < Triangles.Num(); i += 3)
129
+ {
130
+ const FVector& A = Vertices[Triangles[i]];
131
+ const FVector& B = Vertices[Triangles[i + 1]];
132
+ const FVector& C = Vertices[Triangles[i + 2]];
133
+ FVector Normal = FVector::CrossProduct(B - A, C - A).GetSafeNormal();
134
+
135
+ OutNormals[Triangles[i]] += Normal;
136
+ OutNormals[Triangles[i + 1]] += Normal;
137
+ OutNormals[Triangles[i + 2]] += Normal;
138
+ }
139
+
140
+ for (FVector& N : OutNormals)
141
+ {
142
+ N = N.GetSafeNormal();
143
+ }
144
+ }
145
+ ```
146
+
147
+ ---
148
+
149
+ ## 2. Marching Cubes (Voxel Isosurface)
150
+
151
+ Extracts a triangulated isosurface from a 3D scalar field. Used for caves, asteroids, destructible terrain.
152
+
153
+ ### Scalar Field Setup
154
+
155
+ ```cpp
156
+ // Density grid: negative = solid, positive = air, zero = surface
157
+ struct FDensityGrid
158
+ {
159
+ TArray<float> Values;
160
+ FIntVector Size; // Width x Height x Depth
161
+ float VoxelSize;
162
+
163
+ float Sample(int32 X, int32 Y, int32 Z) const
164
+ {
165
+ if (X < 0 || Y < 0 || Z < 0 ||
166
+ X >= Size.X || Y >= Size.Y || Z >= Size.Z)
167
+ return 1.f; // Outside = air
168
+ return Values[Z * Size.Y * Size.X + Y * Size.X + X];
169
+ }
170
+
171
+ FVector WorldPos(int32 X, int32 Y, int32 Z) const
172
+ {
173
+ return FVector(X, Y, Z) * VoxelSize;
174
+ }
175
+ };
176
+ ```
177
+
178
+ ### Edge Interpolation
179
+
180
+ ```cpp
181
+ FVector InterpolateEdge(FVector P0, float V0, FVector P1, float V1)
182
+ {
183
+ // Linear interpolation to find zero crossing
184
+ float t = FMath::Clamp(-V0 / (V1 - V0 + SMALL_NUMBER), 0.f, 1.f);
185
+ return FMath::Lerp(P0, P1, t);
186
+ }
187
+ ```
188
+
189
+ ### Cube Processing
190
+
191
+ ```cpp
192
+ // EdgeTable and TriTable are standard 256-entry lookup tables from the original
193
+ // Lorensen & Cline (1987) paper. They map the 8-corner sign configuration
194
+ // to which edges the surface crosses and how to form triangles.
195
+ // These tables are typically 4KB total and stored as compile-time const arrays.
196
+ extern const int EdgeTable[256];
197
+ extern const int TriTable[256][16];
198
+
199
+ void ProcessCube(const FDensityGrid& Grid, int32 X, int32 Y, int32 Z,
200
+ TArray<FVector>& OutVerts, TArray<int32>& OutTris)
201
+ {
202
+ // Sample 8 cube corners
203
+ float CubeValues[8];
204
+ CubeValues[0] = Grid.Sample(X, Y, Z);
205
+ CubeValues[1] = Grid.Sample(X + 1, Y, Z);
206
+ CubeValues[2] = Grid.Sample(X + 1, Y + 1, Z);
207
+ CubeValues[3] = Grid.Sample(X, Y + 1, Z);
208
+ CubeValues[4] = Grid.Sample(X, Y, Z + 1);
209
+ CubeValues[5] = Grid.Sample(X + 1, Y, Z + 1);
210
+ CubeValues[6] = Grid.Sample(X + 1, Y + 1, Z + 1);
211
+ CubeValues[7] = Grid.Sample(X, Y + 1, Z + 1);
212
+
213
+ FVector Corners[8];
214
+ Corners[0] = Grid.WorldPos(X, Y, Z);
215
+ Corners[1] = Grid.WorldPos(X + 1, Y, Z);
216
+ Corners[2] = Grid.WorldPos(X + 1, Y + 1, Z);
217
+ Corners[3] = Grid.WorldPos(X, Y + 1, Z);
218
+ Corners[4] = Grid.WorldPos(X, Y, Z + 1);
219
+ Corners[5] = Grid.WorldPos(X + 1, Y, Z + 1);
220
+ Corners[6] = Grid.WorldPos(X + 1, Y + 1, Z + 1);
221
+ Corners[7] = Grid.WorldPos(X, Y + 1, Z + 1);
222
+
223
+ // Build 8-bit index from which corners are below iso-level (0)
224
+ int32 CubeIndex = 0;
225
+ for (int32 i = 0; i < 8; i++)
226
+ if (CubeValues[i] < 0.f) CubeIndex |= (1 << i);
227
+
228
+ if (EdgeTable[CubeIndex] == 0) return; // Fully inside or outside
229
+
230
+ // Compute intersection vertices on active edges
231
+ FVector EdgeVerts[12];
232
+ if (EdgeTable[CubeIndex] & 1) EdgeVerts[0] = InterpolateEdge(Corners[0], CubeValues[0], Corners[1], CubeValues[1]);
233
+ if (EdgeTable[CubeIndex] & 2) EdgeVerts[1] = InterpolateEdge(Corners[1], CubeValues[1], Corners[2], CubeValues[2]);
234
+ if (EdgeTable[CubeIndex] & 4) EdgeVerts[2] = InterpolateEdge(Corners[2], CubeValues[2], Corners[3], CubeValues[3]);
235
+ if (EdgeTable[CubeIndex] & 8) EdgeVerts[3] = InterpolateEdge(Corners[3], CubeValues[3], Corners[0], CubeValues[0]);
236
+ if (EdgeTable[CubeIndex] & 16) EdgeVerts[4] = InterpolateEdge(Corners[4], CubeValues[4], Corners[5], CubeValues[5]);
237
+ if (EdgeTable[CubeIndex] & 32) EdgeVerts[5] = InterpolateEdge(Corners[5], CubeValues[5], Corners[6], CubeValues[6]);
238
+ if (EdgeTable[CubeIndex] & 64) EdgeVerts[6] = InterpolateEdge(Corners[6], CubeValues[6], Corners[7], CubeValues[7]);
239
+ if (EdgeTable[CubeIndex] & 128) EdgeVerts[7] = InterpolateEdge(Corners[7], CubeValues[7], Corners[4], CubeValues[4]);
240
+ if (EdgeTable[CubeIndex] & 256) EdgeVerts[8] = InterpolateEdge(Corners[0], CubeValues[0], Corners[4], CubeValues[4]);
241
+ if (EdgeTable[CubeIndex] & 512) EdgeVerts[9] = InterpolateEdge(Corners[1], CubeValues[1], Corners[5], CubeValues[5]);
242
+ if (EdgeTable[CubeIndex] & 1024) EdgeVerts[10] = InterpolateEdge(Corners[2], CubeValues[2], Corners[6], CubeValues[6]);
243
+ if (EdgeTable[CubeIndex] & 2048) EdgeVerts[11] = InterpolateEdge(Corners[3], CubeValues[3], Corners[7], CubeValues[7]);
244
+
245
+ // Add triangles from TriTable
246
+ for (int32 i = 0; TriTable[CubeIndex][i] != -1; i += 3)
247
+ {
248
+ int32 BaseIdx = OutVerts.Num();
249
+ OutVerts.Add(EdgeVerts[TriTable[CubeIndex][i]]);
250
+ OutVerts.Add(EdgeVerts[TriTable[CubeIndex][i + 1]]);
251
+ OutVerts.Add(EdgeVerts[TriTable[CubeIndex][i + 2]]);
252
+ OutTris.Add(BaseIdx); OutTris.Add(BaseIdx + 1); OutTris.Add(BaseIdx + 2);
253
+ }
254
+ }
255
+ ```
256
+
257
+ ### Full Grid March
258
+
259
+ ```cpp
260
+ void MarchCubes(const FDensityGrid& Grid, UProceduralMeshComponent* Mesh)
261
+ {
262
+ TArray<FVector> Vertices, Normals;
263
+ TArray<int32> Triangles;
264
+ TArray<FVector2D> UVs;
265
+ TArray<FColor> Colors;
266
+ TArray<FProcMeshTangent> Tangents;
267
+
268
+ for (int32 Z = 0; Z < Grid.Size.Z - 1; Z++)
269
+ for (int32 Y = 0; Y < Grid.Size.Y - 1; Y++)
270
+ for (int32 X = 0; X < Grid.Size.X - 1; X++)
271
+ {
272
+ ProcessCube(Grid, X, Y, Z, Vertices, Triangles);
273
+ }
274
+
275
+ // Compute normals from triangles
276
+ RecalculateNormals(Vertices, Triangles, Normals);
277
+
278
+ // Pad UVs (can project based on position for triplanar)
279
+ UVs.SetNumZeroed(Vertices.Num());
280
+ for (int32 i = 0; i < Vertices.Num(); i++)
281
+ UVs[i] = FVector2D(Vertices[i].X, Vertices[i].Y) / Grid.VoxelSize;
282
+
283
+ Mesh->CreateMeshSection(0, Vertices, Triangles, Normals,
284
+ UVs, Colors, Tangents, /*bCreateCollision=*/true);
285
+ }
286
+ ```
287
+
288
+ ---
289
+
290
+ ## 3. Dungeon Room-and-Corridor Generation
291
+
292
+ BSP-based dungeon layout that partitions a rect into rooms and connects them.
293
+
294
+ ### Data Structures
295
+
296
+ ```cpp
297
+ struct FRoom
298
+ {
299
+ FIntRect Bounds; // X=left, Y=top, Width, Height in grid cells
300
+ FIntPoint Center() const
301
+ {
302
+ return FIntPoint(Bounds.Min.X + Bounds.Width() / 2,
303
+ Bounds.Min.Y + Bounds.Height() / 2);
304
+ }
305
+ };
306
+
307
+ struct FDungeonLevel
308
+ {
309
+ TArray<FRoom> Rooms;
310
+ TArray<TPair<FIntPoint, FIntPoint>> Corridors; // pairs of cell coords
311
+ TArray<TArray<uint8>> Tiles; // 0=wall, 1=floor, 2=corridor
312
+ };
313
+ ```
314
+
315
+ ### BSP Split
316
+
317
+ ```cpp
318
+ void SplitRect(const FIntRect& Rect, FRandomStream& Rand,
319
+ int32 MinSize, TArray<FIntRect>& OutLeaves)
320
+ {
321
+ int32 W = Rect.Width(), H = Rect.Height();
322
+
323
+ if (W < MinSize * 2 && H < MinSize * 2)
324
+ {
325
+ OutLeaves.Add(Rect);
326
+ return;
327
+ }
328
+
329
+ bool bSplitH = (W > H) ? true : (H > W) ? false : Rand.RandBool();
330
+
331
+ if (bSplitH && W >= MinSize * 2)
332
+ {
333
+ int32 Split = Rand.RandRange(MinSize, W - MinSize);
334
+ SplitRect(FIntRect(Rect.Min, FIntPoint(Rect.Min.X + Split, Rect.Max.Y)), Rand, MinSize, OutLeaves);
335
+ SplitRect(FIntRect(FIntPoint(Rect.Min.X + Split, Rect.Min.Y), Rect.Max), Rand, MinSize, OutLeaves);
336
+ }
337
+ else if (!bSplitH && H >= MinSize * 2)
338
+ {
339
+ int32 Split = Rand.RandRange(MinSize, H - MinSize);
340
+ SplitRect(FIntRect(Rect.Min, FIntPoint(Rect.Max.X, Rect.Min.Y + Split)), Rand, MinSize, OutLeaves);
341
+ SplitRect(FIntRect(FIntPoint(Rect.Min.X, Rect.Min.Y + Split), Rect.Max), Rand, MinSize, OutLeaves);
342
+ }
343
+ else
344
+ {
345
+ OutLeaves.Add(Rect);
346
+ }
347
+ }
348
+ ```
349
+
350
+ ### Room Placement and Corridor Carving
351
+
352
+ ```cpp
353
+ FDungeonLevel GenerateDungeon(int32 MapW, int32 MapH, int32 Seed,
354
+ int32 MinRoomSize = 5, int32 Padding = 1)
355
+ {
356
+ FRandomStream Rand(Seed);
357
+ FDungeonLevel Level;
358
+
359
+ // Initialize tile map
360
+ Level.Tiles.SetNum(MapH);
361
+ for (auto& Row : Level.Tiles)
362
+ Row.Init(0, MapW); // all walls
363
+
364
+ // BSP partition
365
+ TArray<FIntRect> Leaves;
366
+ SplitRect(FIntRect(0, 0, MapW, MapH), Rand, MinRoomSize + Padding * 2, Leaves);
367
+
368
+ // Place rooms in leaves
369
+ for (const FIntRect& Leaf : Leaves)
370
+ {
371
+ int32 MaxW = Leaf.Width() - Padding * 2;
372
+ int32 MaxH = Leaf.Height() - Padding * 2;
373
+ if (MaxW < MinRoomSize || MaxH < MinRoomSize) continue;
374
+
375
+ int32 RW = Rand.RandRange(MinRoomSize, MaxW);
376
+ int32 RH = Rand.RandRange(MinRoomSize, MaxH);
377
+ int32 RX = Leaf.Min.X + Padding + Rand.RandRange(0, MaxW - RW);
378
+ int32 RY = Leaf.Min.Y + Padding + Rand.RandRange(0, MaxH - RH);
379
+
380
+ FRoom Room;
381
+ Room.Bounds = FIntRect(RX, RY, RX + RW, RY + RH);
382
+ Level.Rooms.Add(Room);
383
+
384
+ // Carve floor tiles
385
+ for (int32 Y = RY; Y < RY + RH; Y++)
386
+ for (int32 X = RX; X < RX + RW; X++)
387
+ Level.Tiles[Y][X] = 1;
388
+ }
389
+
390
+ // Connect rooms with L-shaped corridors
391
+ for (int32 i = 1; i < Level.Rooms.Num(); i++)
392
+ {
393
+ FIntPoint A = Level.Rooms[i - 1].Center();
394
+ FIntPoint B = Level.Rooms[i].Center();
395
+
396
+ // Horizontal then vertical
397
+ int32 XDir = (B.X > A.X) ? 1 : -1;
398
+ for (int32 X = A.X; X != B.X; X += XDir)
399
+ {
400
+ Level.Tiles[A.Y][X] = 2;
401
+ Level.Corridors.Add({FIntPoint(X, A.Y), FIntPoint(X + XDir, A.Y)});
402
+ }
403
+
404
+ int32 YDir = (B.Y > A.Y) ? 1 : -1;
405
+ for (int32 Y = A.Y; Y != B.Y; Y += YDir)
406
+ {
407
+ Level.Tiles[Y][B.X] = 2;
408
+ Level.Corridors.Add({FIntPoint(B.X, Y), FIntPoint(B.X, Y + YDir)});
409
+ }
410
+
411
+ Level.Tiles[B.Y][B.X] = 2;
412
+ }
413
+
414
+ return Level;
415
+ }
416
+ ```
417
+
418
+ ### Tile-to-Mesh Conversion
419
+
420
+ ```cpp
421
+ void ADungeonActor::BuildMeshFromTiles(const FDungeonLevel& Level, float TileSize)
422
+ {
423
+ TArray<FVector> Vertices;
424
+ TArray<int32> Triangles;
425
+ TArray<FVector> Normals;
426
+ TArray<FVector2D> UVs;
427
+ TArray<FColor> Colors;
428
+ TArray<FProcMeshTangent> Tangents;
429
+
430
+ int32 H = Level.Tiles.Num();
431
+ int32 W = H > 0 ? Level.Tiles[0].Num() : 0;
432
+
433
+ for (int32 Row = 0; Row < H; Row++)
434
+ for (int32 Col = 0; Col < W; Col++)
435
+ {
436
+ if (Level.Tiles[Row][Col] == 0) continue; // Skip walls (or add wall mesh)
437
+
438
+ int32 Base = Vertices.Num();
439
+ float X0 = Col * TileSize;
440
+ float X1 = (Col + 1) * TileSize;
441
+ float Y0 = Row * TileSize;
442
+ float Y1 = (Row + 1) * TileSize;
443
+
444
+ Vertices.Add(FVector(X0, Y0, 0)); // 0 BL
445
+ Vertices.Add(FVector(X1, Y0, 0)); // 1 BR
446
+ Vertices.Add(FVector(X1, Y1, 0)); // 2 TR
447
+ Vertices.Add(FVector(X0, Y1, 0)); // 3 TL
448
+
449
+ Normals.Add(FVector::UpVector);
450
+ Normals.Add(FVector::UpVector);
451
+ Normals.Add(FVector::UpVector);
452
+ Normals.Add(FVector::UpVector);
453
+
454
+ UVs.Add(FVector2D(0, 0)); UVs.Add(FVector2D(1, 0));
455
+ UVs.Add(FVector2D(1, 1)); UVs.Add(FVector2D(0, 1));
456
+
457
+ Triangles.Add(Base); Triangles.Add(Base + 2); Triangles.Add(Base + 1);
458
+ Triangles.Add(Base); Triangles.Add(Base + 3); Triangles.Add(Base + 2);
459
+ }
460
+
461
+ ProceduralMesh->CreateMeshSection(0, Vertices, Triangles, Normals,
462
+ UVs, Colors, Tangents, /*bCreateCollision=*/true);
463
+ }
464
+ ```
465
+
466
+ ---
467
+
468
+ ## 4. L-System Vegetation
469
+
470
+ L-systems expand a string through production rules and interpret characters as 3D drawing commands (turtle graphics) to produce branching structures.
471
+
472
+ ### Axiom and Rules
473
+
474
+ ```cpp
475
+ struct FLSystemRules
476
+ {
477
+ FString Axiom = "F";
478
+ TMap<TCHAR, FString> Rules = {
479
+ {'F', TEXT("F[+F]F[-F]F")} // Standard plant
480
+ };
481
+ int32 Iterations = 4;
482
+ float AngleDeg = 25.f;
483
+ float SegmentLen = 50.f;
484
+ float LenDecay = 0.7f; // Each recursion level shortens segments
485
+ float WidthStart = 8.f;
486
+ float WidthDecay = 0.6f;
487
+ };
488
+
489
+ FString ExpandLSystem(const FLSystemRules& Rules)
490
+ {
491
+ FString Current = Rules.Axiom;
492
+ for (int32 Iter = 0; Iter < Rules.Iterations; Iter++)
493
+ {
494
+ FString Next;
495
+ Next.Reserve(Current.Len() * 3);
496
+ for (TCHAR C : Current)
497
+ {
498
+ const FString* Replacement = Rules.Rules.Find(C);
499
+ if (Replacement) Next.Append(*Replacement);
500
+ else Next.AppendChar(C);
501
+ }
502
+ Current = MoveTemp(Next);
503
+ }
504
+ return Current;
505
+ }
506
+ ```
507
+
508
+ ### Turtle Interpreter to HISM
509
+
510
+ ```cpp
511
+ struct FTurtleState
512
+ {
513
+ FVector Position = FVector::ZeroVector;
514
+ FRotator Rotation = FRotator::ZeroRotator;
515
+ float Length = 50.f;
516
+ float Width = 8.f;
517
+ };
518
+
519
+ void InterpretLSystem(const FString& LString, const FLSystemRules& Rules,
520
+ UHierarchicalInstancedStaticMeshComponent* BranchHISM,
521
+ UHierarchicalInstancedStaticMeshComponent* LeafHISM)
522
+ {
523
+ TArray<FTurtleState> Stack;
524
+ FTurtleState State;
525
+ State.Length = Rules.SegmentLen;
526
+ State.Width = Rules.WidthStart;
527
+
528
+ for (TCHAR C : LString)
529
+ {
530
+ switch (C)
531
+ {
532
+ case 'F':
533
+ {
534
+ FVector Forward = State.Rotation.Vector() * State.Length;
535
+ FVector End = State.Position + Forward;
536
+
537
+ // Place branch segment
538
+ FTransform T;
539
+ T.SetLocation((State.Position + End) * 0.5f);
540
+ T.SetRotation(State.Rotation.Quaternion());
541
+ T.SetScale3D(FVector(State.Width * 0.01f, State.Width * 0.01f,
542
+ State.Length * 0.01f));
543
+ BranchHISM->AddInstance(T, /*bWorldSpace=*/false);
544
+
545
+ State.Position = End;
546
+ break;
547
+ }
548
+ case '+': State.Rotation.Yaw += Rules.AngleDeg; break;
549
+ case '-': State.Rotation.Yaw -= Rules.AngleDeg; break;
550
+ case '&': State.Rotation.Pitch += Rules.AngleDeg; break;
551
+ case '^': State.Rotation.Pitch -= Rules.AngleDeg; break;
552
+ case '/': State.Rotation.Roll += Rules.AngleDeg; break;
553
+ case '\\':State.Rotation.Roll -= Rules.AngleDeg; break;
554
+ case '[':
555
+ Stack.Push(State);
556
+ State.Length *= Rules.LenDecay;
557
+ State.Width *= Rules.WidthDecay;
558
+ break;
559
+ case ']':
560
+ // Place leaf at branch tip before popping
561
+ if (LeafHISM)
562
+ {
563
+ FTransform LT;
564
+ LT.SetLocation(State.Position);
565
+ LT.SetRotation(State.Rotation.Quaternion());
566
+ LeafHISM->AddInstance(LT, /*bWorldSpace=*/false);
567
+ }
568
+ State = Stack.Pop();
569
+ break;
570
+ }
571
+ }
572
+ }
573
+ ```
574
+
575
+ ---
576
+
577
+ ## 5. Wave Function Collapse (Grid Layout)
578
+
579
+ WFC fills a grid by choosing tiles that satisfy adjacency constraints. Suitable for dungeon rooms, city blocks, terrain biome transitions.
580
+
581
+ ### Tile and Constraint Definition
582
+
583
+ ```cpp
584
+ // Each tile has a set of valid neighbor tile IDs per direction
585
+ struct FWFCTile
586
+ {
587
+ int32 ID;
588
+ float Weight; // Relative spawn probability
589
+ TArray<int32> AllowedRight; // IDs allowed to the +X neighbor
590
+ TArray<int32> AllowedLeft; // IDs allowed to the -X neighbor
591
+ TArray<int32> AllowedUp; // IDs allowed to the +Y neighbor
592
+ TArray<int32> AllowedDown; // IDs allowed to the -Y neighbor
593
+ };
594
+
595
+ // Cell state during WFC
596
+ struct FWFCCell
597
+ {
598
+ TArray<int32> PossibleTiles; // Remaining valid tile IDs
599
+ bool bCollapsed = false;
600
+ int32 CollapsedTile = -1;
601
+
602
+ bool IsContradiction() const { return PossibleTiles.Num() == 0 && !bCollapsed; }
603
+ float Entropy() const { return (float)PossibleTiles.Num(); } // Simplified (no weights)
604
+ };
605
+ ```
606
+
607
+ ### WFC Iteration
608
+
609
+ ```cpp
610
+ bool WFCStep(TArray<TArray<FWFCCell>>& Grid,
611
+ const TArray<FWFCTile>& Tiles,
612
+ FRandomStream& Rand,
613
+ int32 Width, int32 Height)
614
+ {
615
+ // 1. Find uncollapsed cell with lowest entropy
616
+ float MinEntropy = FLT_MAX;
617
+ FIntPoint CollapsePos(-1, -1);
618
+
619
+ for (int32 Y = 0; Y < Height; Y++)
620
+ for (int32 X = 0; X < Width; X++)
621
+ {
622
+ FWFCCell& Cell = Grid[Y][X];
623
+ if (Cell.bCollapsed) continue;
624
+ if (Cell.IsContradiction()) return false; // Contradiction — need backtrack
625
+ if (Cell.Entropy() < MinEntropy)
626
+ {
627
+ MinEntropy = Cell.Entropy();
628
+ CollapsePos = FIntPoint(X, Y);
629
+ }
630
+ }
631
+
632
+ if (CollapsePos.X < 0) return true; // All cells collapsed
633
+
634
+ // 2. Collapse: choose a tile weighted by tile weight
635
+ FWFCCell& Cell = Grid[CollapsePos.Y][CollapsePos.X];
636
+ float TotalWeight = 0.f;
637
+ for (int32 TileID : Cell.PossibleTiles)
638
+ TotalWeight += Tiles[TileID].Weight;
639
+
640
+ float Pick = Rand.FRandRange(0.f, TotalWeight);
641
+ float Accum = 0.f;
642
+ int32 ChosenTile = Cell.PossibleTiles[0];
643
+ for (int32 TileID : Cell.PossibleTiles)
644
+ {
645
+ Accum += Tiles[TileID].Weight;
646
+ if (Accum >= Pick) { ChosenTile = TileID; break; }
647
+ }
648
+
649
+ Cell.bCollapsed = true;
650
+ Cell.CollapsedTile = ChosenTile;
651
+ Cell.PossibleTiles = { ChosenTile };
652
+
653
+ // 3. Propagate constraints to neighbors (BFS)
654
+ TQueue<FIntPoint> PropagateQueue;
655
+ PropagateQueue.Enqueue(CollapsePos);
656
+
657
+ while (!PropagateQueue.IsEmpty())
658
+ {
659
+ FIntPoint P;
660
+ PropagateQueue.Dequeue(P);
661
+
662
+ auto Propagate = [&](FIntPoint Neighbor,
663
+ TFunctionRef<const TArray<int32>*(const FWFCTile&)> GetAllowed)
664
+ {
665
+ if (Neighbor.X < 0 || Neighbor.X >= Width ||
666
+ Neighbor.Y < 0 || Neighbor.Y >= Height) return;
667
+
668
+ FWFCCell& NCell = Grid[Neighbor.Y][Neighbor.X];
669
+ if (NCell.bCollapsed) return;
670
+
671
+ // Collect all tiles allowed by current cell's possible set
672
+ TSet<int32> AllowedSet;
673
+ for (int32 TileID : Grid[P.Y][P.X].PossibleTiles)
674
+ {
675
+ const TArray<int32>* Allowed = GetAllowed(Tiles[TileID]);
676
+ if (Allowed) AllowedSet.Append(*Allowed);
677
+ }
678
+
679
+ // Remove incompatible tiles from neighbor
680
+ int32 PrevCount = NCell.PossibleTiles.Num();
681
+ NCell.PossibleTiles.RemoveAll([&](int32 ID) { return !AllowedSet.Contains(ID); });
682
+
683
+ if (NCell.PossibleTiles.Num() != PrevCount)
684
+ PropagateQueue.Enqueue(Neighbor);
685
+ };
686
+
687
+ Propagate({P.X + 1, P.Y}, [](const FWFCTile& T) { return &T.AllowedRight; });
688
+ Propagate({P.X - 1, P.Y}, [](const FWFCTile& T) { return &T.AllowedLeft; });
689
+ Propagate({P.X, P.Y + 1}, [](const FWFCTile& T) { return &T.AllowedUp; });
690
+ Propagate({P.X, P.Y - 1}, [](const FWFCTile& T) { return &T.AllowedDown; });
691
+ }
692
+
693
+ return true;
694
+ }
695
+ ```
696
+
697
+ ### Grid Initialization and Run
698
+
699
+ ```cpp
700
+ void RunWFC(int32 Width, int32 Height, const TArray<FWFCTile>& Tiles,
701
+ FRandomStream& Rand, TArray<TArray<FWFCCell>>& OutGrid)
702
+ {
703
+ TArray<int32> AllTileIDs;
704
+ for (const FWFCTile& T : Tiles) AllTileIDs.Add(T.ID);
705
+
706
+ OutGrid.SetNum(Height);
707
+ for (auto& Row : OutGrid)
708
+ {
709
+ Row.SetNum(Width);
710
+ for (FWFCCell& Cell : Row)
711
+ Cell.PossibleTiles = AllTileIDs;
712
+ }
713
+
714
+ bool bSuccess = false;
715
+ for (int32 MaxSteps = Width * Height; MaxSteps > 0; MaxSteps--)
716
+ {
717
+ bSuccess = WFCStep(OutGrid, Tiles, Rand, Width, Height);
718
+ if (!bSuccess) break; // Contradiction: re-run with different seed
719
+
720
+ // Check all cells collapsed
721
+ bool bDone = true;
722
+ for (auto& Row : OutGrid)
723
+ for (auto& Cell : Row)
724
+ if (!Cell.bCollapsed) { bDone = false; break; }
725
+ if (bDone) { bSuccess = true; break; }
726
+ }
727
+ }
728
+ ```
729
+
730
+ ---
731
+
732
+ ## 6. Async Mesh Generation Pattern
733
+
734
+ For large meshes, compute vertex data on a background thread then apply on the game thread.
735
+
736
+ ```cpp
737
+ void AProceduralTerrain::GenerateAsync(int32 GridSize, float CellSize)
738
+ {
739
+ // Capture data needed on background thread
740
+ int32 Seed = SeedValue;
741
+
742
+ // Lambda runs on a background worker thread
743
+ AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this, GridSize, CellSize, Seed]()
744
+ {
745
+ TArray<FVector> Vertices;
746
+ TArray<int32> Triangles;
747
+ TArray<FVector> Normals;
748
+ TArray<FVector2D> UVs;
749
+
750
+ // -- heavy computation here --
751
+ // GenerateTerrainData(GridSize, CellSize, Seed, Vertices, Triangles, Normals, UVs);
752
+
753
+ // Schedule the mesh section creation back on the game thread
754
+ AsyncTask(ENamedThreads::GameThread, [this,
755
+ V = MoveTemp(Vertices),
756
+ T = MoveTemp(Triangles),
757
+ N = MoveTemp(Normals),
758
+ UV = MoveTemp(UVs)]() mutable
759
+ {
760
+ if (!IsValid(this) || !IsValid(ProceduralMesh)) return;
761
+
762
+ TArray<FColor> Colors;
763
+ TArray<FProcMeshTangent> Tangents;
764
+
765
+ ProceduralMesh->CreateMeshSection(0, V, T, N,
766
+ UV, Colors, Tangents,
767
+ /*bCreateCollision=*/true);
768
+ });
769
+ });
770
+ }
771
+ ```
772
+
773
+ Note: `UProceduralMeshComponent::CreateMeshSection` must be called on the **game thread**. Only the data computation can be parallelized.
774
+
775
+ ---
776
+
777
+ ## 7. Spline-Driven Road Mesh
778
+
779
+ Generates a road mesh by extruding a cross-section profile along a `USplineComponent`.
780
+
781
+ ```cpp
782
+ void ARoadMeshActor::BuildRoad(float RoadWidth, float SegmentLength)
783
+ {
784
+ TArray<FVector> Vertices;
785
+ TArray<int32> Triangles;
786
+ TArray<FVector> Normals;
787
+ TArray<FVector2D> UVs;
788
+ TArray<FColor> Colors;
789
+ TArray<FProcMeshTangent> Tangents;
790
+
791
+ float TotalLen = Spline->GetSplineLength();
792
+ float UVProgress = 0.f;
793
+ int32 SegCount = FMath::CeilToInt(TotalLen / SegmentLength);
794
+ float ActualSeg = TotalLen / SegCount;
795
+
796
+ // Extrude cross-section quads along spline
797
+ for (int32 Seg = 0; Seg <= SegCount; Seg++)
798
+ {
799
+ float Dist = Seg * ActualSeg;
800
+ FTransform T = Spline->GetTransformAtDistanceAlongSpline(
801
+ Dist, ESplineCoordinateSpace::World, /*bUseScale=*/false);
802
+
803
+ FVector Center = T.GetLocation();
804
+ FVector Right = T.GetRotation().GetRightVector();
805
+ FVector Up = T.GetRotation().GetUpVector();
806
+
807
+ FVector Left_V = Center - Right * RoadWidth * 0.5f;
808
+ FVector Right_V = Center + Right * RoadWidth * 0.5f;
809
+
810
+ Vertices.Add(Left_V);
811
+ Vertices.Add(Right_V);
812
+ Normals.Add(Up);
813
+ Normals.Add(Up);
814
+ UVs.Add(FVector2D(0.f, UVProgress));
815
+ UVs.Add(FVector2D(1.f, UVProgress));
816
+
817
+ if (Seg > 0)
818
+ {
819
+ int32 B = (Seg - 1) * 2;
820
+ int32 T2 = Seg * 2;
821
+ // Left triangle
822
+ Triangles.Add(B); Triangles.Add(T2); Triangles.Add(B + 1);
823
+ // Right triangle
824
+ Triangles.Add(B + 1); Triangles.Add(T2); Triangles.Add(T2 + 1);
825
+ }
826
+
827
+ UVProgress += ActualSeg / RoadWidth; // Scale UV to aspect ratio
828
+ }
829
+
830
+ ProceduralMesh->CreateMeshSection(0, Vertices, Triangles, Normals,
831
+ UVs, Colors, Tangents, /*bCreateCollision=*/true);
832
+ ProceduralMesh->SetMaterial(0, RoadMaterial);
833
+ }
834
+ ```
835
+
836
+ ---
837
+
838
+ ## Performance Reference
839
+
840
+ | Scenario | Recommended Approach | Notes |
841
+ |---|---|---|
842
+ | < 100 dynamic instances, moving | ISM + `UpdateInstanceTransform` | Simple, low overhead |
843
+ | 100–10,000 static instances | HISM | Culling hierarchy, LOD transitions |
844
+ | > 10,000 static instances | HISM + `InstanceEndCullDistance` | Aggressive distance culling |
845
+ | Terrain (runtime, < 256x256) | `UProceduralMeshComponent` | One `CreateMeshSection` call |
846
+ | Terrain (large, static) | Landscape or PCG + ISM | PCG + HISM spawner avoids draw calls |
847
+ | Cave/voxel (< 64x64x64) | Marching Cubes + ProcMesh | Async generation on background thread |
848
+ | Vegetation (open world) | PCG Surface Sampler + HISM Spawner | Budget with HiGen grid |
849
+ | Dungeon layout | WFC or BSP + tile mesh | Bake to static at load, or keep ProcMesh |
850
+ | Spline road/river | SplineMeshComponent per segment | Or extruded ProcMesh for custom profile |
851
+
852
+ ---
853
+
854
+ ## 9. Poisson Disc Sampling
855
+
856
+ Generates uniformly distributed points with minimum separation distance (Bridson 2007). Use for natural-looking placement (trees, rocks, enemies) without clumping.
857
+
858
+ ```cpp
859
+ // Poisson disc sampling — Bridson's fast algorithm
860
+ TArray<FVector2D> PoissonDiscSample(FVector2D Min, FVector2D Max,
861
+ float MinDist, int32 MaxAttempts, FRandomStream& Rand)
862
+ {
863
+ float CellSize = MinDist / FMath::Sqrt(2.f);
864
+ FVector2D Size = Max - Min;
865
+ int32 GW = FMath::CeilToInt(Size.X / CellSize);
866
+ int32 GH = FMath::CeilToInt(Size.Y / CellSize);
867
+ TArray<int32> Grid;
868
+ Grid.Init(-1, GW * GH);
869
+ TArray<FVector2D> Points, Active;
870
+
871
+ // Seed with first random point
872
+ FVector2D First(Rand.FRandRange(Min.X, Max.X), Rand.FRandRange(Min.Y, Max.Y));
873
+ Points.Add(First);
874
+ Active.Add(First);
875
+ Grid[FMath::FloorToInt((First.Y - Min.Y) / CellSize) * GW +
876
+ FMath::FloorToInt((First.X - Min.X) / CellSize)] = 0;
877
+
878
+ while (Active.Num() > 0)
879
+ {
880
+ int32 Idx = Rand.RandRange(0, Active.Num() - 1);
881
+ FVector2D Base = Active[Idx];
882
+ bool bFound = false;
883
+
884
+ for (int32 k = 0; k < MaxAttempts; k++)
885
+ {
886
+ float Angle = Rand.FRandRange(0.f, 2.f * PI);
887
+ float R = Rand.FRandRange(MinDist, 2.f * MinDist);
888
+ FVector2D Candidate = Base + FVector2D(FMath::Cos(Angle), FMath::Sin(Angle)) * R;
889
+
890
+ if (Candidate.X < Min.X || Candidate.X > Max.X ||
891
+ Candidate.Y < Min.Y || Candidate.Y > Max.Y)
892
+ continue;
893
+
894
+ int32 GX = FMath::FloorToInt((Candidate.X - Min.X) / CellSize);
895
+ int32 GY = FMath::FloorToInt((Candidate.Y - Min.Y) / CellSize);
896
+ bool bTooClose = false;
897
+
898
+ for (int32 DY = -2; DY <= 2 && !bTooClose; DY++)
899
+ {
900
+ for (int32 DX = -2; DX <= 2 && !bTooClose; DX++)
901
+ {
902
+ int32 NX = GX + DX, NY = GY + DY;
903
+ if (NX < 0 || NX >= GW || NY < 0 || NY >= GH) continue;
904
+ int32 PIdx = Grid[NY * GW + NX];
905
+ if (PIdx >= 0 && FVector2D::Distance(Points[PIdx], Candidate) < MinDist)
906
+ bTooClose = true;
907
+ }
908
+ }
909
+
910
+ if (!bTooClose)
911
+ {
912
+ Grid[GY * GW + GX] = Points.Num();
913
+ Points.Add(Candidate);
914
+ Active.Add(Candidate);
915
+ bFound = true;
916
+ break;
917
+ }
918
+ }
919
+ if (!bFound) Active.RemoveAtSwap(Idx);
920
+ }
921
+ return Points;
922
+ }
923
+ ```
924
+
925
+ **Usage with ISM placement:**
926
+
927
+ ```cpp
928
+ FRandomStream Stream(MySeed);
929
+ TArray<FVector2D> Placements = PoissonDiscSample(
930
+ FVector2D(0, 0), FVector2D(10000, 10000), 200.f, 30, Stream);
931
+
932
+ for (const FVector2D& Pos : Placements)
933
+ {
934
+ FTransform T(FRotator(0, Stream.FRandRange(0, 360), 0),
935
+ FVector(Pos.X, Pos.Y, GetGroundHeight(Pos)));
936
+ TreeISM->AddInstance(T);
937
+ }
938
+ ```
939
+
940
+ Key properties: O(n) time complexity, guaranteed minimum separation, deterministic with `FRandomStream` seed. Increase `MaxAttempts` (default 30) for denser packing; decrease for faster generation.