#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 PlaneMesh; UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Living Plane|Rendering") TObjectPtr 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 ObjectInputs; private: void CreateFieldTargets(); void EnqueueSimulationSteps(int32 NumSteps); void UpdateMaterialField(); float GetStableStepSeconds() const; UPROPERTY(Transient) TObjectPtr FieldTargets[2]; UPROPERTY(Transient) TObjectPtr DynamicPlaneMaterial; int32 CurrentFieldIndex = 0; int64 SimulationStepCount = 0; double SimulationAccumulatorSeconds = 0.0; double FixedStepSeconds = 0.0; };