Skip to content

Instantly share code, notes, and snippets.

@hallatore
Created Apr 11, 2015
Embed
What would you like to do?
Procedural foliage - Unreal Engine
#include "TestProject.h"
#include "FoliageTileActor.h"
#include "Runtime/Landscape/Classes/Landscape.h"
#include "Kismet/KismetSystemLibrary.h"
AFoliageTileActor::AFoliageTileActor() {
OffsetFactor = 0.5f;
ScaleMin = 1.0f;
ScaleMax = 1.0f;
HeightMin = INT32_MIN;
HeightMax = INT32_MAX;
MeshesPrTile = 1;
SpawnChance = 1.0f;
MaxSlope = 30.0f;
AlignWithSlope = true;
Collision = false;
int32 arraySize = (int32)sqrt((double)MeshesPrTile);
TotalMeshes = 81 * arraySize * arraySize * SpawnChance;
}
void AFoliageTileActor::BeginPlay()
{
Super::BeginPlay();
uint32 seed = Hash(InitialSeed);
if (Mesh == NULL)
return;
FoliageTiles.AddUninitialized(81);
int32 size = 9;
float FoliageTilesize = Radius / size;
float startCullDistance = (Radius / 2) - (FoliageTilesize * 2.5f);
float endCullDistance = (Radius / 2) - (FoliageTilesize * 1.5f);
for (int32 y = 0; y < 81; y++)
{
UFoliageTile* tile = NewObject<UFoliageTile>(this, NAME_None);
int32 arraySize = (int32)sqrt((double)MeshesPrTile);
seed = Hash(seed);
UHierarchicalInstancedStaticMeshComponent* component = NewObject<UHierarchicalInstancedStaticMeshComponent>(this);
component->StaticMesh = Mesh;
component->bSelectable = false;
component->bHasPerInstanceHitProxies = true;
component->InstancingRandomSeed = seed % INT32_MAX;
component->bAffectDistanceFieldLighting = false;
component->InstanceStartCullDistance = startCullDistance;
component->InstanceEndCullDistance = endCullDistance;
if (Collision == false)
component->SetCollisionEnabled(ECollisionEnabled::NoCollision);
else
component->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
int32 tmpX = 0;
int32 tmpY = 0;
int32 max = sqrt((double)MeshesPrTile);
float split = FoliageTilesize / max;
for (int32 i = 0; i < (arraySize * arraySize); i++)
{
if (SpawnChance >(double)seed / UINT32_MAX) {
seed = Hash(seed);
float r1 = (double)seed / UINT32_MAX;
seed = Hash(seed);
float r2 = (double)seed / UINT32_MAX;
seed = Hash(seed);
float r3 = (double)seed / UINT32_MAX;
FFoliageInstance foliageInstance = FFoliageInstance();
foliageInstance.Location = FVector(split * tmpX + (split * r1 * OffsetFactor), split * tmpY + (split * r2 * OffsetFactor), 0.0f);
foliageInstance.Scale = ScaleMin + ((ScaleMax - ScaleMin) * r3);
foliageInstance.InstanceId = component->AddInstance(FTransform(foliageInstance.Location));
tile->Instances.Add(foliageInstance);
}
seed = Hash(seed);
tmpX++;
if (tmpX >= max) {
tmpX = 0;
tmpY++;
}
}
component->AttachTo(GetRootComponent());
component->RegisterComponent();
tile->MeshComponent = component;
FoliageTiles[y] = tile;
}
}
// This should to be done on a different thread if possible
void AFoliageTileActor::UpdateTile(int32 x, int32 y, FVector location) {
UFoliageTile* tile = FoliageTiles[GetIndex(x, y)];
uint32 seed = Hash(InitialSeed + (x * 9) + y);
for (int32 i = 0; i < tile->Instances.Num(); i++)
{
FTransform transform = GetTransform(location + tile->Instances[i].Location, seed);
if (transform.GetTranslation().Z != -100000.0f)
{
transform.SetScale3D(FVector(tile->Instances[i].Scale, tile->Instances[i].Scale, tile->Instances[i].Scale));
tile->MeshComponent->UpdateInstanceTransform(tile->Instances[i].InstanceId, transform, true);
}
}
}
FTransform AFoliageTileActor::GetTransform(FVector location, uint32 seed) {
const FVector Start = FVector(location.X, location.Y, 100000.0f);
const FVector End = FVector(location.X, location.Y, -100000.0f);
float r = (double)seed / UINT32_MAX;
FTransform result = FTransform();
FQuat rotator = FQuat(FRotator(0.0f, 360.0f * r, 0.0f));
result.SetTranslation(FVector(-1.0f, -1.0f, -100000.0f));
result.SetRotation(rotator);
FHitResult HitData(ForceInit);
TArray<AActor*> ActorsToIgnore;
ActorsToIgnore.Add(NULL);
TArray<TEnumAsByte<enum EObjectTypeQuery> > Objects;
Objects.Add(EObjectTypeQuery::ObjectTypeQuery1);
// Todo: Find a way to trace only landscapes for better performance
if (UKismetSystemLibrary::LineTraceSingleForObjects(GetWorld(), Start, End, Objects, false, ActorsToIgnore, EDrawDebugTrace::None, HitData, true))
{
if (HitData.GetActor() && HitData.Actor->IsA(ALandscape::StaticClass()))
{
if (PhysicalMaterials.Num() > 0) {
if (HitData.PhysMaterial == NULL) {
return result;
}
auto name = HitData.PhysMaterial->GetName();
bool found = false;
for (int i = 0; i < PhysicalMaterials.Num(); i++)
{
if (PhysicalMaterials[i]->GetName() == name)
found = true;
}
if (found == false)
return result;
}
result.SetTranslation(FVector(location.X, location.Y, HitData.ImpactPoint.Z));
if (AlignWithSlope == true)
{
FRotator rotation = FRotationMatrix::MakeFromZ(HitData.Normal).Rotator();
result.SetRotation(FQuat(rotation) * rotator);
}
}
}
return result;
}
void AFoliageTileActor::PostEditChangeProperty(struct FPropertyChangedEvent& e)
{
Super::PostEditChangeProperty(e);
int32 arraySize = (int32)sqrt((double)MeshesPrTile);
TotalMeshes = 81 * arraySize * arraySize * SpawnChance;
}
uint32 AFoliageTileActor::Hash(uint32 a)
{
a = (a ^ 61) ^ (a >> 16);
a = a + (a << 3);
a = a ^ (a >> 4);
a = a * 0x27d4eb2d;
a = a ^ (a >> 15);
return a;
}
#pragma once
#include "TileActor.h"
#include "Components/HierarchicalInstancedStaticMeshComponent.h"
#include "FoliageTileActor.generated.h"
USTRUCT()
struct FFoliageInstance
{
GENERATED_USTRUCT_BODY()
public:
UPROPERTY()
int32 InstanceId;
UPROPERTY()
FVector Location;
UPROPERTY()
float Scale;
FFoliageInstance()
{
}
};
UCLASS()
class UFoliageTile : public UObject
{
GENERATED_BODY()
public:
UPROPERTY()
UHierarchicalInstancedStaticMeshComponent* MeshComponent;
UPROPERTY()
TArray<FFoliageInstance> Instances;
};
UCLASS()
class TESTPROJECT_API AFoliageTileActor : public ATileActor
{
GENERATED_BODY()
public:
AFoliageTileActor();
virtual void BeginPlay() override;
virtual void PostEditChangeProperty(struct FPropertyChangedEvent& e) override;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
UStaticMesh* Mesh;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
int32 MeshesPrTile;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float SpawnChance;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float OffsetFactor;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float ScaleMin;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float ScaleMax;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float HeightMin;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float HeightMax;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float MaxSlope;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
bool AlignWithSlope;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
bool Collision;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
TArray<UPhysicalMaterial*> PhysicalMaterials;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
int32 InitialSeed;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
int32 TotalMeshes;
protected:
virtual void UpdateTile(int32 x, int32 y, FVector location) override;
private:
UPROPERTY(transient, duplicatetransient)
TArray<UFoliageTile*> FoliageTiles;
uint32 Hash(uint32 a);
FTransform GetTransform(FVector location, uint32 seed);
};
#include "TestProject.h"
#include "TileActor.h"
ATileActor::ATileActor()
{
PrimaryActorTick.bCanEverTick = true;
Radius = 10000.0f;
CurrentTileX = 1;
CurrentTileY = 1;
CurrentTileLocation = FVector(-1000000.0f, -1000000.0f, -1000000.0f);
}
void ATileActor::BeginPlay()
{
Super::BeginPlay();
Tiles.AddUninitialized(81);
for (int32 i = 0; i < 81; i++)
{
Tiles[i] = FTile();
}
}
void ATileActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
double StartTime = FPlatformTime::Seconds();
int32 size = 9;
FVector currentLocation = GetWorld()->GetFirstLocalPlayerFromController()->LastViewLocation;
currentLocation.Z = 0.0f;
float tileSize = Radius / size;
float disX = currentLocation.X - CurrentTileLocation.X;
float disY = CurrentCameraLocation.Y - CurrentTileLocation.Y;
bool updated = false;
if (disX > tileSize || disX * -1 > tileSize / 2) {
int32 currentX = (int32)floor(CurrentCameraLocation.X / tileSize);
CurrentTileLocation.X = currentX * (int32)tileSize;
CurrentTileX = currentX % size;
updated = true;
if (CurrentTileX < 0)
CurrentTileX += 9;
}
if (disY > tileSize || disY * -1 > tileSize / 2) {
int32 currentY = (int32)floor(CurrentCameraLocation.Y / tileSize);
CurrentTileLocation.Y = currentY * (int32)tileSize;
CurrentTileY = currentY % size;
updated = true;
if (CurrentTileY < 0)
CurrentTileY += 9;
}
CurrentCameraLocation = currentLocation;
if (updated == false)
return;
for (int32 x = 0; x < size; x++)
{
for (int32 y = 0; y < size; y++)
{
FTile & tile = Tiles[GetIndex(x, y)];
tile.NewLocation = GetTileLocation(x, y);
if (FVector::Dist(tile.Location, tile.NewLocation) > tileSize / 3) {
tile.Location = tile.NewLocation;
UpdateTile(x, y, tile.Location);
}
}
}
}
FVector ATileActor::GetTileLocation(int32 x, int32 y) {
int32 size = 9;
int32 tileSize = Radius / size;
int32 tmpX = (x - CurrentTileX);
int32 tmpY = (y - CurrentTileY);
// I can't has math.. There has to be a better way...
switch (tmpX)
{
case -5:
tmpX = 4;
break;
case -6:
tmpX = 3;
break;
case -7:
tmpX = 2;
break;
case -8:
tmpX = 1;
break;
case 5:
tmpX = -4;
break;
case 6:
tmpX = -3;
break;
case 7:
tmpX = -2;
break;
case 8:
tmpX = -1;
break;
default:
break;
}
switch (tmpY)
{
case -5:
tmpY = 4;
break;
case -6:
tmpY = 3;
break;
case -7:
tmpY = 2;
break;
case -8:
tmpY = 1;
break;
case 5:
tmpY = -4;
break;
case 6:
tmpY = -3;
break;
case 7:
tmpY = -2;
break;
case 8:
tmpY = -1;
break;
default:
break;
}
float xOffset = tmpX * tileSize;
float yOffset = tmpY * tileSize;
FVector offset = FVector(xOffset, yOffset, 0.0f);
return CurrentTileLocation + offset;
}
int32 ATileActor::GetIndex(int32 x, int32 y) {
int32 result = 0;
for (int32 x2 = 0; x2 < 9; x2++)
{
for (int32 y2 = 0; y2 < 9; y2++)
{
if (x2 == x && y2 == y)
return result;
result++;
if (x2 == x && y2 == y)
return result;
}
}
return result;
}
void ATileActor::UpdateTile(int32 x, int32 y, FVector location){}
void ATileActor::PostUpdateTiles(){}
#pragma once
#include "GameFramework/Actor.h"
#include "TileActor.generated.h"
USTRUCT()
struct FTile
{
GENERATED_USTRUCT_BODY()
public:
UPROPERTY()
FVector Location;
UPROPERTY()
FVector NewLocation;
FTile()
{
}
};
UCLASS()
class TESTPROJECT_API ATileActor : public AActor
{
GENERATED_BODY()
public:
ATileActor();
virtual void BeginPlay() override;
virtual void Tick(float DeltaSeconds) override;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float Radius;
protected:
int32 GetIndex(int32 x, int32 y);
virtual void UpdateTile(int32 x, int32 y, FVector location);
virtual void PostUpdateTiles();
private:
UPROPERTY()
int32 CurrentTileX;
UPROPERTY()
int32 CurrentTileY;
UPROPERTY()
FVector CurrentTileLocation;
UPROPERTY()
FVector CurrentCameraLocation;
UPROPERTY(transient, duplicatetransient)
TArray<FTile> Tiles;
FVector GetTileLocation(int32 x, int32 y);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment