Hi,
I’m trying to create a terrain based on a heightmap and chunks.
I can currently add and remove chunks around my character and generate a heightmap based on the chunk’s position in the world (to keep it simple).
My problem is that I have no idea how to “join” the borders of the chunks together. Can someone enlighten me on the way forward?
here some code :
// Fill out your copyright notice in the Description page of Project Settings.
#include "WorldChunk.h"
// Sets default values
AWorldChunk::AWorldChunk()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
RootNode = CreateDefaultSubobject<USceneComponent>("Rootnode");
RootComponent = RootNode;
MeshComponent = CreateDefaultSubobject<URuntimeMeshComponent>(TEXT("ProceduralChunkMesh"));
MeshComponent->SetupAttachment(RootComponent);
}
void AWorldChunk::SetupChunk(int32 inSeed, int32 inindexX, int32 inindexY, int32 inSectionSize, int32 inChunkSize, int32 inMaxHeight)
{
Seed = inSeed;
ChunkX = inindexX;
ChunkY = inindexY;
SectionSize = inSectionSize;
ChunkSize = inChunkSize;
MaxHeight = inMaxHeight;
}
void AWorldChunk::OnConstruction(const FTransform & Transform)
{
int32 NumberOfPoints = (SectionSize + 1) * (SectionSize + 1);
int32 NumberOfVertices = SectionSize * SectionSize * 4; // 4x vertices per quad/section
int32 NumberOfTriangles = SectionSize * SectionSize * 2 * 3; // 2x3 vertex indexes per quad
Vertices.AddUninitialized(NumberOfVertices);
Triangles.AddUninitialized(NumberOfTriangles);
HeightValues.Reserve(NumberOfPoints);
for (int i = 0; i < NumberOfPoints; i++) {
HeightValues.Emplace(0.0f);
}
GenerateMesh();
}
// Generate heightmap
void AWorldChunk::GenerateHeightmap()
{
noiseGenerator->setNoiseSeed(Seed);
for (int32 x = 0; x < SectionSize + 1; x++) {
for (int32 y = 0; y < SectionSize + 1; y++) {
int nX = ((ChunkX * ChunkSize) + (x * SectionSize));
int nY = ((ChunkY * ChunkSize) + (y * SectionSize));
HeightValues[x + (y*SectionSize)] = noiseGenerator->SimplexNoise2D(nX, nY);
}
}
}
void AWorldChunk::GenerateMesh()
{
GenerateHeightmap();
GenerateGrid(Vertices, Triangles, HeightValues);
MeshComponent->ClearAllMeshSections();
MeshComponent->CreateMeshSection(0, Vertices, Triangles, false, EUpdateFrequency::Infrequent);
}
void AWorldChunk::GenerateGrid(TArray<FRuntimeMeshVertexSimple>& InVertices, TArray<int32>& InTriangles, const TArray<float>& InHeightValues)
{
FVector2D NumberOfSection = FVector2D(ChunkSize / SectionSize, ChunkSize / SectionSize);
int32 VertexIndex = 0;
int32 TriangleIndex = 0;
for (int32 X = 0; X < SectionSize; X++) {
for (int32 Y = 0; Y < SectionSize; Y++) {
int32 BottomLeftIndex = VertexIndex++;
int32 BottomRightIndex = VertexIndex++;
int32 TopRightIndex = VertexIndex++;
int32 TopLeftIndex = VertexIndex++;
int32 NoiseIndex_TopLeft = (X + 1) + (Y * (SectionSize + 1));
int32 NoiseIndex_TopRight = (X + 1) + (Y + 1) * (SectionSize + 1);
int32 NoiseIndex_BottomRight = X + (Y + 1) * (SectionSize + 1);
int32 NoiseIndex_BottomLeft = X + (Y * (SectionSize + 1));
FVector pTopLeft = FVector((X + 1) * NumberOfSection.X, Y * NumberOfSection.Y, InHeightValues[NoiseIndex_TopLeft]);
FVector pTopRight = FVector((X + 1) * NumberOfSection.X, (Y + 1) * NumberOfSection.Y, InHeightValues[NoiseIndex_TopRight]);
FVector pBottomRight = FVector(X * NumberOfSection.X, (Y + 1) * NumberOfSection.Y, InHeightValues[NoiseIndex_BottomRight]);
FVector pBottomLeft = FVector(X * NumberOfSection.X, Y * NumberOfSection.Y, InHeightValues[NoiseIndex_BottomLeft]);
InVertices[BottomLeftIndex].Position = pBottomLeft;
InVertices[BottomRightIndex].Position = pBottomRight;
InVertices[TopRightIndex].Position = pTopRight;
InVertices[TopLeftIndex].Position = pTopLeft;
// Note that Unreal UV origin (0,0) is top left
InVertices[TopLeftIndex].UV0 = FVector2D((float)(X + 1) / (float)SectionSize, (float)Y / (float)SectionSize);
InVertices[TopRightIndex].UV0 = FVector2D((float)(X + 1) / (float)SectionSize, (float)(Y + 1) / (float)SectionSize);
InVertices[BottomRightIndex].UV0 = FVector2D((float)X / (float)SectionSize, (float)(Y + 1) / (float)SectionSize);
InVertices[BottomLeftIndex].UV0 = FVector2D((float)X / (float)SectionSize, (float)Y / (float)SectionSize);
// Now create triangles
InTriangles[TriangleIndex++] = BottomLeftIndex;
InTriangles[TriangleIndex++] = TopRightIndex;
InTriangles[TriangleIndex++] = TopLeftIndex;
InTriangles[TriangleIndex++] = BottomLeftIndex;
InTriangles[TriangleIndex++] = BottomRightIndex;
InTriangles[TriangleIndex++] = TopRightIndex;
// Normals
FVector NormalCurrent = FVector::CrossProduct(InVertices[BottomLeftIndex].Position - InVertices[TopLeftIndex].Position, InVertices[TopLeftIndex].Position - InVertices[TopRightIndex].Position).GetSafeNormal();
// Normals
InVertices[BottomLeftIndex].Normal = InVertices[BottomRightIndex].Normal = InVertices[TopRightIndex].Normal = InVertices[TopLeftIndex].Normal = FPackedNormal(NormalCurrent);
// Tangents
FVector SurfaceTangent = pBottomLeft - pBottomRight;
SurfaceTangent = SurfaceTangent.GetSafeNormal();
InVertices[BottomLeftIndex].Tangent = InVertices[BottomRightIndex].Tangent = InVertices[TopRightIndex].Tangent = InVertices[TopLeftIndex].Tangent = FPackedNormal(SurfaceTangent);
}
}
}
// Called when the game starts or when spawned
void AWorldChunk::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AWorldChunk::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
Current result !