experiment: LivingPlane repeatable + standalone UE runtime module (L164)
Readback barrier fix (GL_TEXTURE_UPDATE_BARRIER_BIT before glGetTexImage); check_repeatability.py compares full-field checksums across query/capture settings - Intel GPU PASS (3b9d45905c7aafff), llvmpipe PASS, mismatch control fails. New runtime module VoxelForgeLivingPlane (separate from generation): RDG global shader (Shaders/LivingPlane.usf), ping-pong RGBA32F render targets, actor with WPO plane and object inputs, no CPU readback. Compiles in Linux UE 5.7. Windows build: Source/VoxelForgeLivingPlane/*, Shaders/LivingPlane.usf, VoxelForge.uplugin. Setup: UE-SETUP.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
e1d87eed26
commit
3b71d35eba
13 files changed
+868
-8
No files matched your search
@@ -0,0 +1,303 @@
|
||||
#include "VoxelForgeLivingPlaneActor.h"
|
||||
|
||||
#include "Engine/TextureRenderTarget2D.h"
|
||||
#include "Engine/TextureRenderTarget.h"
|
||||
#include "Engine/StaticMesh.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Materials/MaterialInstanceDynamic.h"
|
||||
#include "Materials/MaterialInterface.h"
|
||||
#include "RenderGraphBuilder.h"
|
||||
#include "RenderGraphUtils.h"
|
||||
#include "RHICommandList.h"
|
||||
#include "RHIResources.h"
|
||||
#include "DataDrivenShaderPlatformInfo.h"
|
||||
#include "GlobalShader.h"
|
||||
#include "ShaderParameterStruct.h"
|
||||
#include "UObject/ConstructorHelpers.h"
|
||||
#include "Components/StaticMeshComponent.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr int32 LivingPlaneThreadGroupSize = 8;
|
||||
|
||||
struct FLivingPlaneStepInput
|
||||
{
|
||||
FVector4f ObjectPositionRadius[2] = {FVector4f(0, 0, 0, -1), FVector4f(0, 0, 0, -1)};
|
||||
FVector4f ObjectVelocityPush[2] = {FVector4f(0, 0, 0, 0), FVector4f(0, 0, 0, 0)};
|
||||
float TimeSeconds = 0.0f;
|
||||
};
|
||||
|
||||
class FVFLivingPlaneStepCS final : public FGlobalShader
|
||||
{
|
||||
DECLARE_GLOBAL_SHADER(FVFLivingPlaneStepCS);
|
||||
SHADER_USE_PARAMETER_STRUCT(FVFLivingPlaneStepCS, FGlobalShader);
|
||||
|
||||
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
|
||||
SHADER_PARAMETER(FIntPoint, GridSize)
|
||||
SHADER_PARAMETER(float, Dt)
|
||||
SHADER_PARAMETER(float, Dx)
|
||||
SHADER_PARAMETER(float, PlaneLength)
|
||||
SHADER_PARAMETER(float, WaveSpeed)
|
||||
SHADER_PARAMETER(float, Damping)
|
||||
SHADER_PARAMETER(float, FoundationStiffness)
|
||||
SHADER_PARAMETER(float, TimeSeconds)
|
||||
SHADER_PARAMETER(FVector2f, BreathingSettings)
|
||||
SHADER_PARAMETER(FVector4f, ImprintSettings)
|
||||
SHADER_PARAMETER_ARRAY(FVector4f, ObjectPositionRadius, [2])
|
||||
SHADER_PARAMETER_ARRAY(FVector4f, ObjectVelocityPush, [2])
|
||||
SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D<float4>, SourceState)
|
||||
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, TargetState)
|
||||
END_SHADER_PARAMETER_STRUCT()
|
||||
|
||||
public:
|
||||
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
|
||||
{
|
||||
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_GLOBAL_SHADER(FVFLivingPlaneStepCS,
|
||||
"/Plugin/VoxelForgeLivingPlane/LivingPlane.usf", "MainCS", SF_Compute)
|
||||
|
||||
UTextureRenderTarget2D* CreateFieldTarget(UObject* Outer, int32 Size, FName Name)
|
||||
{
|
||||
UTextureRenderTarget2D* Target = NewObject<UTextureRenderTarget2D>(Outer, Name, RF_Transient);
|
||||
Target->RenderTargetFormat = RTF_RGBA32f;
|
||||
Target->bCanCreateUAV = true;
|
||||
Target->bAutoGenerateMips = false;
|
||||
Target->ClearColor = FLinearColor::Transparent;
|
||||
Target->Filter = TF_Bilinear;
|
||||
Target->AddressX = TA_Clamp;
|
||||
Target->AddressY = TA_Clamp;
|
||||
Target->InitAutoFormat(Size, Size);
|
||||
Target->UpdateResourceImmediate(true);
|
||||
return Target;
|
||||
}
|
||||
}
|
||||
|
||||
AVoxelForgeLivingPlaneActor::AVoxelForgeLivingPlaneActor()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
PlaneMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("LivingPlaneMesh"));
|
||||
SetRootComponent(PlaneMesh);
|
||||
PlaneMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
||||
PlaneMesh->SetMobility(EComponentMobility::Movable);
|
||||
static ConstructorHelpers::FObjectFinder<UStaticMesh> PlaneAsset(
|
||||
TEXT("/Engine/BasicShapes/Plane.Plane"));
|
||||
if (PlaneAsset.Succeeded())
|
||||
{
|
||||
PlaneMesh->SetStaticMesh(PlaneAsset.Object);
|
||||
}
|
||||
|
||||
ObjectInputs.SetNum(2);
|
||||
}
|
||||
|
||||
void AVoxelForgeLivingPlaneActor::OnConstruction(const FTransform& Transform)
|
||||
{
|
||||
Super::OnConstruction(Transform);
|
||||
if (PlaneMesh)
|
||||
{
|
||||
// /Engine/BasicShapes/Plane is 100 cm square; one Unreal scale unit
|
||||
// therefore corresponds to one metre of authored plane length.
|
||||
PlaneMesh->SetRelativeScale3D(FVector(PlaneLengthMeters, PlaneLengthMeters, 1.0));
|
||||
}
|
||||
}
|
||||
|
||||
float AVoxelForgeLivingPlaneActor::GetStableStepSeconds() const
|
||||
{
|
||||
const float Dx = PlaneLengthMeters / static_cast<float>(FMath::Max(GridSize, 16));
|
||||
const float EigenFrequencySquared = 8.0f * WaveSpeedMetersPerSecond * WaveSpeedMetersPerSecond
|
||||
/ FMath::Max(Dx * Dx, 1.0e-8f) + FoundationStiffnessPerSecondSquared;
|
||||
return 1.30f / FMath::Sqrt(FMath::Max(EigenFrequencySquared, 1.0e-8f));
|
||||
}
|
||||
|
||||
void AVoxelForgeLivingPlaneActor::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
GridSize = FMath::Clamp(GridSize, 16, 2048);
|
||||
PlaneLengthMeters = FMath::Max(PlaneLengthMeters, 1.0f);
|
||||
FixedStepSeconds = GetStableStepSeconds();
|
||||
CreateFieldTargets();
|
||||
|
||||
if (PlaneMaterial && PlaneMesh)
|
||||
{
|
||||
DynamicPlaneMaterial = PlaneMesh->CreateDynamicMaterialInstance(0, PlaneMaterial);
|
||||
if (DynamicPlaneMaterial)
|
||||
{
|
||||
DynamicPlaneMaterial->SetScalarParameterValue(TEXT("PlaneLengthMeters"), PlaneLengthMeters);
|
||||
DynamicPlaneMaterial->SetScalarParameterValue(TEXT("FieldSize"), static_cast<float>(GridSize));
|
||||
}
|
||||
}
|
||||
UpdateMaterialField();
|
||||
}
|
||||
|
||||
void AVoxelForgeLivingPlaneActor::CreateFieldTargets()
|
||||
{
|
||||
FieldTargets[0] = CreateFieldTarget(this, GridSize, TEXT("LivingPlaneStateA"));
|
||||
FieldTargets[1] = CreateFieldTarget(this, GridSize, TEXT("LivingPlaneStateB"));
|
||||
CurrentFieldIndex = 0;
|
||||
SimulationStepCount = 0;
|
||||
SimulationAccumulatorSeconds = 0.0;
|
||||
}
|
||||
|
||||
void AVoxelForgeLivingPlaneActor::SetObjectState(int32 ObjectIndex,
|
||||
const FVFLivingPlaneObjectInput& State)
|
||||
{
|
||||
if (!ObjectInputs.IsValidIndex(ObjectIndex) || ObjectIndex >= 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ObjectInputs[ObjectIndex] = State;
|
||||
}
|
||||
|
||||
void AVoxelForgeLivingPlaneActor::ResetSimulation()
|
||||
{
|
||||
if (!IsInGameThread() || !FieldTargets[0] || !FieldTargets[1])
|
||||
{
|
||||
return;
|
||||
}
|
||||
CreateFieldTargets();
|
||||
FixedStepSeconds = GetStableStepSeconds();
|
||||
UpdateMaterialField();
|
||||
}
|
||||
|
||||
void AVoxelForgeLivingPlaneActor::UpdateMaterialField()
|
||||
{
|
||||
if (DynamicPlaneMaterial && FieldTargets[CurrentFieldIndex])
|
||||
{
|
||||
DynamicPlaneMaterial->SetTextureParameterValue(TEXT("LivingField"), FieldTargets[CurrentFieldIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
void AVoxelForgeLivingPlaneActor::EnqueueSimulationSteps(int32 NumSteps)
|
||||
{
|
||||
if (NumSteps <= 0 || !FieldTargets[0] || !FieldTargets[1])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FTextureRenderTargetResource* ResourceA = FieldTargets[0]->GameThread_GetRenderTargetResource();
|
||||
FTextureRenderTargetResource* ResourceB = FieldTargets[1]->GameThread_GetRenderTargetResource();
|
||||
if (!ResourceA || !ResourceB)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TArray<FLivingPlaneStepInput> Steps;
|
||||
Steps.Reserve(NumSteps);
|
||||
const FTransform PlaneTransform = GetActorTransform();
|
||||
for (int32 Step = 0; Step < NumSteps; ++Step)
|
||||
{
|
||||
FLivingPlaneStepInput& Input = Steps.AddDefaulted_GetRef();
|
||||
Input.TimeSeconds = static_cast<float>(SimulationStepCount + Step) * FixedStepSeconds;
|
||||
for (int32 ObjectIndex = 0; ObjectIndex < 2; ++ObjectIndex)
|
||||
{
|
||||
if (!ObjectInputs.IsValidIndex(ObjectIndex) || !ObjectInputs[ObjectIndex].bActive)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FVFLivingPlaneObjectInput& Object = ObjectInputs[ObjectIndex];
|
||||
const FVector LocalPosition = PlaneTransform.InverseTransformPosition(Object.WorldPositionCm);
|
||||
const FVector LocalVelocity = PlaneTransform.InverseTransformVector(Object.WorldVelocityCmPerSecond);
|
||||
const float RadiusMeters = FMath::Max(Object.RadiusCm * 0.01f, 0.001f);
|
||||
Input.ObjectPositionRadius[ObjectIndex] = FVector4f(
|
||||
static_cast<float>(LocalPosition.X * 0.01),
|
||||
static_cast<float>(LocalPosition.Y * 0.01),
|
||||
static_cast<float>(LocalPosition.Z * 0.01),
|
||||
RadiusMeters);
|
||||
Input.ObjectVelocityPush[ObjectIndex] = FVector4f(
|
||||
static_cast<float>(LocalVelocity.X * 0.01),
|
||||
static_cast<float>(LocalVelocity.Y * 0.01),
|
||||
static_cast<float>(LocalVelocity.Z * 0.01),
|
||||
FMath::Max(Object.PushAccelerationMetersPerSecondSquared, 0.0f));
|
||||
}
|
||||
}
|
||||
|
||||
const int32 Resolution = GridSize;
|
||||
const int32 InitialFieldIndex = CurrentFieldIndex;
|
||||
const float Dt = static_cast<float>(FixedStepSeconds);
|
||||
const float Dx = PlaneLengthMeters / static_cast<float>(Resolution);
|
||||
const float PlaneLength = PlaneLengthMeters;
|
||||
const float WaveSpeed = WaveSpeedMetersPerSecond;
|
||||
const float Damping = DampingPerSecond;
|
||||
const float Foundation = FoundationStiffnessPerSecondSquared;
|
||||
const float BreathingStrength = bEnableBreathing ? BreathingStrengthMetersPerSecondSquared : 0.0f;
|
||||
const FVector2f BreathingSettings(bEnableBreathing ? 1.0f : 0.0f, BreathingStrength);
|
||||
const FVector4f ImprintSettings(bEnableImprint ? 1.0f : 0.0f,
|
||||
ImprintCaptureMetersPerSecond, FMath::Max(ImprintRecoverySeconds, 1.0e-4f),
|
||||
FMath::Max(ImprintMaxDepthMeters, 0.0f));
|
||||
|
||||
ENQUEUE_RENDER_COMMAND(VoxelForgeLivingPlaneStep)(
|
||||
[ResourceA, ResourceB, Steps = MoveTemp(Steps), InitialFieldIndex, Resolution, Dt, Dx, PlaneLength,
|
||||
WaveSpeed, Damping, Foundation, BreathingSettings, ImprintSettings]
|
||||
(FRHICommandListImmediate& RHICmdList) mutable
|
||||
{
|
||||
FRDGBuilder GraphBuilder(RHICmdList, RDG_EVENT_NAME("VoxelForgeLivingPlane"));
|
||||
FRDGTextureRef Fields[2] = {
|
||||
RegisterExternalTexture(GraphBuilder, ResourceA->GetRenderTargetTexture().GetReference(), TEXT("LivingPlaneStateA")),
|
||||
RegisterExternalTexture(GraphBuilder, ResourceB->GetRenderTargetTexture().GetReference(), TEXT("LivingPlaneStateB"))};
|
||||
TShaderMapRef<FVFLivingPlaneStepCS> ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel));
|
||||
int32 CurrentIndex = InitialFieldIndex;
|
||||
for (const FLivingPlaneStepInput& StepInput : Steps)
|
||||
{
|
||||
const int32 TargetIndex = 1 - CurrentIndex;
|
||||
FVFLivingPlaneStepCS::FParameters* PassParameters =
|
||||
GraphBuilder.AllocParameters<FVFLivingPlaneStepCS::FParameters>();
|
||||
PassParameters->GridSize = FIntPoint(Resolution, Resolution);
|
||||
PassParameters->Dt = Dt;
|
||||
PassParameters->Dx = Dx;
|
||||
PassParameters->PlaneLength = PlaneLength;
|
||||
PassParameters->WaveSpeed = WaveSpeed;
|
||||
PassParameters->Damping = Damping;
|
||||
PassParameters->FoundationStiffness = Foundation;
|
||||
PassParameters->TimeSeconds = StepInput.TimeSeconds;
|
||||
PassParameters->BreathingSettings = BreathingSettings;
|
||||
PassParameters->ImprintSettings = ImprintSettings;
|
||||
for (int32 ObjectIndex = 0; ObjectIndex < 2; ++ObjectIndex)
|
||||
{
|
||||
PassParameters->ObjectPositionRadius[ObjectIndex] = StepInput.ObjectPositionRadius[ObjectIndex];
|
||||
PassParameters->ObjectVelocityPush[ObjectIndex] = StepInput.ObjectVelocityPush[ObjectIndex];
|
||||
}
|
||||
PassParameters->SourceState = GraphBuilder.CreateSRV(Fields[CurrentIndex]);
|
||||
PassParameters->TargetState = GraphBuilder.CreateUAV(Fields[TargetIndex]);
|
||||
FComputeShaderUtils::AddPass(GraphBuilder,
|
||||
RDG_EVENT_NAME("LivingPlaneStep"),
|
||||
ComputeShader, PassParameters,
|
||||
FIntVector(FMath::DivideAndRoundUp(Resolution, LivingPlaneThreadGroupSize),
|
||||
FMath::DivideAndRoundUp(Resolution, LivingPlaneThreadGroupSize), 1));
|
||||
CurrentIndex = TargetIndex;
|
||||
}
|
||||
GraphBuilder.Execute();
|
||||
});
|
||||
|
||||
CurrentFieldIndex = (InitialFieldIndex + NumSteps) & 1;
|
||||
SimulationStepCount += NumSteps;
|
||||
UpdateMaterialField();
|
||||
}
|
||||
|
||||
void AVoxelForgeLivingPlaneActor::Tick(float DeltaSeconds)
|
||||
{
|
||||
Super::Tick(DeltaSeconds);
|
||||
if (FixedStepSeconds <= 0.0 || !FieldTargets[0] || !FieldTargets[1])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SimulationAccumulatorSeconds += FMath::Max(static_cast<double>(DeltaSeconds), 0.0);
|
||||
const int32 StepsToRun = FMath::Min(
|
||||
static_cast<int32>(SimulationAccumulatorSeconds / FixedStepSeconds), 32);
|
||||
if (StepsToRun > 0)
|
||||
{
|
||||
SimulationAccumulatorSeconds -= StepsToRun * FixedStepSeconds;
|
||||
EnqueueSimulationSteps(StepsToRun);
|
||||
}
|
||||
}
|
||||
|
||||
void AVoxelForgeLivingPlaneActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
Super::EndPlay(EndPlayReason);
|
||||
DynamicPlaneMaterial = nullptr;
|
||||
FieldTargets[0] = nullptr;
|
||||
FieldTargets[1] = nullptr;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
#include "Interfaces/IPluginManager.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "ShaderCore.h"
|
||||
|
||||
class FVoxelForgeLivingPlaneModule final : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
virtual void StartupModule() override
|
||||
{
|
||||
const TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin(TEXT("VoxelForge"));
|
||||
if (!Plugin.IsValid())
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("VoxelForgeLivingPlane could not find its owning plugin."));
|
||||
return;
|
||||
}
|
||||
|
||||
AddShaderSourceDirectoryMapping(
|
||||
TEXT("/Plugin/VoxelForgeLivingPlane"),
|
||||
FPaths::Combine(Plugin->GetBaseDir(), TEXT("Shaders")));
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_MODULE(FVoxelForgeLivingPlaneModule, VoxelForgeLivingPlane)
|
||||
@@ -0,0 +1,114 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "VoxelForgeLivingPlaneActor.generated.h"
|
||||
|
||||
class UMaterialInterface;
|
||||
class UMaterialInstanceDynamic;
|
||||
class UStaticMeshComponent;
|
||||
class UTextureRenderTarget2D;
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FVFLivingPlaneObjectInput
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Object")
|
||||
FVector WorldPositionCm = FVector(0.0, 0.0, 43.0);
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Object")
|
||||
FVector WorldVelocityCmPerSecond = FVector::ZeroVector;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Object", meta=(ClampMin="0.1"))
|
||||
float RadiusCm = 43.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Object", meta=(ClampMin="0.0"))
|
||||
float PushAccelerationMetersPerSecondSquared = 36.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Object")
|
||||
bool bActive = false;
|
||||
};
|
||||
|
||||
/** A render-only, GPU-stepped heightfield plane. Object state is uploaded; the field is never read back. */
|
||||
UCLASS(BlueprintType, Blueprintable)
|
||||
class VOXELFORGELIVINGPLANE_API AVoxelForgeLivingPlaneActor : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
AVoxelForgeLivingPlaneActor();
|
||||
|
||||
virtual void BeginPlay() override;
|
||||
virtual void Tick(float DeltaSeconds) override;
|
||||
virtual void OnConstruction(const FTransform& Transform) override;
|
||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="Living Plane|Object")
|
||||
void SetObjectState(int32 ObjectIndex, const FVFLivingPlaneObjectInput& State);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="Living Plane")
|
||||
void ResetSimulation();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category="Living Plane")
|
||||
int64 GetSimulationStepCount() const { return SimulationStepCount; }
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Living Plane")
|
||||
TObjectPtr<UStaticMeshComponent> PlaneMesh;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Living Plane|Rendering")
|
||||
TObjectPtr<UMaterialInterface> PlaneMaterial;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Simulation", meta=(ClampMin="16", ClampMax="2048"))
|
||||
int32 GridSize = 256;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Simulation", meta=(ClampMin="1.0", ClampMax="100.0"))
|
||||
float PlaneLengthMeters = 12.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Simulation", meta=(ClampMin="0.0"))
|
||||
float WaveSpeedMetersPerSecond = 4.5f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Simulation", meta=(ClampMin="0.0"))
|
||||
float DampingPerSecond = 1.35f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Simulation", meta=(ClampMin="0.0"))
|
||||
float FoundationStiffnessPerSecondSquared = 28.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Simulation")
|
||||
bool bEnableBreathing = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Simulation", meta=(ClampMin="0.0", ClampMax="30.0"))
|
||||
float BreathingStrengthMetersPerSecondSquared = 4.8f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Imprint")
|
||||
bool bEnableImprint = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Imprint", meta=(ClampMin="0.0", ClampMax="1.0"))
|
||||
float ImprintCaptureMetersPerSecond = 0.15f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Imprint", meta=(ClampMin="0.1", ClampMax="60.0"))
|
||||
float ImprintRecoverySeconds = 6.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Imprint", meta=(ClampMin="0.0", ClampMax="1.0"))
|
||||
float ImprintMaxDepthMeters = 0.34f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Object")
|
||||
TArray<FVFLivingPlaneObjectInput> ObjectInputs;
|
||||
|
||||
private:
|
||||
void CreateFieldTargets();
|
||||
void EnqueueSimulationSteps(int32 NumSteps);
|
||||
void UpdateMaterialField();
|
||||
float GetStableStepSeconds() const;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UTextureRenderTarget2D> FieldTargets[2];
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UMaterialInstanceDynamic> DynamicPlaneMaterial;
|
||||
|
||||
int32 CurrentFieldIndex = 0;
|
||||
int64 SimulationStepCount = 0;
|
||||
double SimulationAccumulatorSeconds = 0.0;
|
||||
double FixedStepSeconds = 0.0;
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class VoxelForgeLivingPlane : ModuleRules
|
||||
{
|
||||
public VoxelForgeLivingPlane(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
if (Target.Platform == UnrealTargetPlatform.Linux)
|
||||
{
|
||||
bUseUnity = false;
|
||||
}
|
||||
|
||||
PublicDependencyModuleNames.AddRange(new string[]
|
||||
{
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Engine"
|
||||
});
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[]
|
||||
{
|
||||
"Projects",
|
||||
"RenderCore",
|
||||
"RHI"
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user