// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "LidarPointCloudShared.generated.h" DECLARE_LOG_CATEGORY_EXTERN(LogLidarPointCloud, Log, All); DECLARE_STATS_GROUP(TEXT("Lidar Point Cloud"), STATGROUP_LidarPointCloud, STATCAT_Advanced); #define PC_LOG(Format, ...) UE_LOG(LogLidarPointCloud, Log, TEXT(Format), ##__VA_ARGS__) #define PC_WARNING(Format, ...) UE_LOG(LogLidarPointCloud, Warning, TEXT(Format), ##__VA_ARGS__) #define PC_ERROR(Format, ...) UE_LOG(LogLidarPointCloud, Error, TEXT(Format), ##__VA_ARGS__) #pragma pack(push) #pragma pack(1) /** 3D vector represented using only a single byte per component */ USTRUCT(BlueprintType) struct LIDARPOINTCLOUDRUNTIME_API FLidarPointCloudNormal { GENERATED_BODY() public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Lidar Point Normal") uint8 X; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Lidar Point Normal") uint8 Y; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Lidar Point Normal") uint8 Z; public: FLidarPointCloudNormal() { Reset(); } FLidarPointCloudNormal(const FVector3f& Normal) { SetFromVector(Normal); } FLidarPointCloudNormal(const FPlane& Normal) { SetFromFloats(Normal.X, Normal.Y, Normal.Z); } FLidarPointCloudNormal(const float& X, const float& Y, const float& Z) { SetFromFloats(X, Y, Z); } bool operator==(const FLidarPointCloudNormal& Other) const { return X == Other.X && Y == Other.Y && Z == Other.Z; } FORCEINLINE bool IsValid() const { return X != 127 || Y != 127 || Z != 127; } FORCEINLINE void SetFromVector(const FVector3f& Normal) { SetFromFloats(Normal.X, Normal.Y, Normal.Z); } FORCEINLINE void SetFromFloats(const float& InX, const float& InY, const float& InZ) { X = FMath::Min((InX + 1) * 127.5f, 255.0f); Y = FMath::Min((InY + 1) * 127.5f, 255.0f); Z = FMath::Min((InZ + 1) * 127.5f, 255.0f); } FORCEINLINE void Reset() { X = Y = Z = 127; } FORCEINLINE FVector3f ToVector() const { return FVector3f(X / 127.5f - 1, Y / 127.5f - 1, Z / 127.5f - 1); } }; /** Used for backwards compatibility with pre-normal datasets */ struct FLidarPointCloudPoint_Legacy { FVector3f Location; FColor Color; uint8 bVisible : 1; uint8 ClassificationID : 5; uint8 Dummy : 2; }; USTRUCT(BlueprintType) struct LIDARPOINTCLOUDRUNTIME_API FLidarPointCloudPoint { GENERATED_BODY() public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Lidar Point Cloud Point") FVector3f Location; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Lidar Point Cloud Point") FColor Color; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Lidar Point Cloud Point") FLidarPointCloudNormal Normal; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Lidar Point Cloud Point") uint8 bVisible : 1; /** Valid range is 0 - 31. */ uint8 ClassificationID : 5; uint8 bSelected : 1; private: uint8 bMarkedForDeletion : 1; public: FLidarPointCloudPoint() : Location(FVector3f::ZeroVector) , Color(FColor::White) , Normal() , bVisible(true) , ClassificationID(0) , bSelected(false) , bMarkedForDeletion(false) { } FLidarPointCloudPoint(const float& X, const float& Y, const float& Z) : FLidarPointCloudPoint() { Location.X = X; Location.Y = Y; Location.Z = Z; } FLidarPointCloudPoint(const float& X, const float& Y, const float& Z, const float& I) : FLidarPointCloudPoint(X, Y, Z) { Color.A = FMath::RoundToInt(FMath::Clamp(I, 0.0f, 1.0f) * 255.f); } FLidarPointCloudPoint(const float& X, const float& Y, const float& Z, const float& R, const float& G, const float& B, const float& A = 1.0f) : FLidarPointCloudPoint(X, Y, Z) { Color = FLinearColor(R, G, B, A).ToFColor(false); } FLidarPointCloudPoint(const float& X, const float& Y, const float& Z, const float& R, const float& G, const float& B, const float& A, const float& NX, const float& NY, const float& NZ) : FLidarPointCloudPoint(X, Y, Z) { Color = FLinearColor(R, G, B, A).ToFColor(false); Normal.SetFromFloats(NX, NY, NZ); } FLidarPointCloudPoint(const FVector3f& Location) : FLidarPointCloudPoint(Location.X, Location.Y, Location.Z) {} FLidarPointCloudPoint(const FVector3f& Location, const float& R, const float& G, const float& B, const float& A = 1.0f) : FLidarPointCloudPoint(Location) { Color = FLinearColor(R, G, B, A).ToFColor(false); } FLidarPointCloudPoint(const FVector3f& Location, const float& R, const float& G, const float& B, const float& A, const uint8& ClassificationID) : FLidarPointCloudPoint(Location, R, G, B, A) { this->ClassificationID = ClassificationID; } FLidarPointCloudPoint(const FVector3f& Location, const uint8& R, const uint8& G, const uint8& B, const uint8& A, const uint8& ClassificationID) : FLidarPointCloudPoint(Location.X, Location.Y, Location.Z) { Color = FColor(R, G, B, A); this->ClassificationID = ClassificationID; } FLidarPointCloudPoint(const FVector3f& Location, const FColor& Color, const bool& bVisible, const uint8& ClassificationID) : FLidarPointCloudPoint(Location) { this->Color = Color; this->bVisible = bVisible; this->ClassificationID = ClassificationID; } FLidarPointCloudPoint(const FVector3f& Location, const FColor& Color, const bool& bVisible, const uint8& ClassificationID, const FLidarPointCloudNormal& Normal) : FLidarPointCloudPoint(Location) { this->Color = Color; this->bVisible = bVisible; this->ClassificationID = ClassificationID; this->Normal = Normal; } // Questionable behavior here - the copy constructor only copies part of the state but the assignment operator copies it all FLidarPointCloudPoint(const FLidarPointCloudPoint& Other) : FLidarPointCloudPoint() { CopyFrom(Other); } FLidarPointCloudPoint& operator=(const FLidarPointCloudPoint&) = default; FLidarPointCloudPoint(const FLidarPointCloudPoint_Legacy& Other) : FLidarPointCloudPoint(Other.Location, Other.Color, Other.bVisible, Other.ClassificationID) { } FORCEINLINE void CopyFrom(const FLidarPointCloudPoint& Other) { Location = Other.Location; Color = Other.Color; Normal = Other.Normal; bVisible = Other.bVisible; ClassificationID = Other.ClassificationID; } FORCEINLINE FLidarPointCloudPoint Transform(const FTransform3f& Transform) const { return FLidarPointCloudPoint(Transform.TransformPosition(Location), Color, bVisible, ClassificationID); } bool operator==(const FLidarPointCloudPoint& P) const { return Location == P.Location && Color == P.Color && bVisible == P.bVisible && ClassificationID == P.ClassificationID && Normal == P.Normal; } friend class FLidarPointCloudOctree; #if WITH_EDITOR friend class FLidarPointCloudEditor; #endif }; #pragma pack(pop) /** Used in blueprint latent function execution */ UENUM(BlueprintType) enum class ELidarPointCloudAsyncMode : uint8 { Success, Failure, Progress }; UENUM(BlueprintType) enum class ELidarPointCloudScalingMethod : uint8 { /** * Points are scaled based on the estimated density of their containing node. * Recommended for assets with high variance of point densities, but may produce less fine detail overall. * Default method in 4.25 and 4.26 */ PerNode, /** * Similar to PerNode, but the density is calculated adaptively based on the current view. * Produces good amount of fine detail while being generally resistant to density variance. */ PerNodeAdaptive, /** * Points are scaled based on their individual calculated depth. * Capable of resolving the highest amount of fine detail, but is the most susceptible to * density changes across the dataset, and may result in patches of varying point sizes. */ PerPoint, /** * Sprites will be rendered using screen-space scaling method. * In that mode, Point Size property will work as Screen Percentage. */ FixedScreenSize }; /** Used to help track multiple buffer allocations */ class LIDARPOINTCLOUDRUNTIME_API FLidarPointCloudDataBuffer { public: FLidarPointCloudDataBuffer() : bInUse(false), PendingSize(0) {} ~FLidarPointCloudDataBuffer() = default; FLidarPointCloudDataBuffer(const FLidarPointCloudDataBuffer& Other) { Data = Other.Data; bInUse = false; } FLidarPointCloudDataBuffer(FLidarPointCloudDataBuffer&&) = delete; FLidarPointCloudDataBuffer& operator=(const FLidarPointCloudDataBuffer& Other) { Data = Other.Data; bInUse = false; return *this; } FLidarPointCloudDataBuffer& operator=(FLidarPointCloudDataBuffer&&) = delete; FORCEINLINE uint8* GetData() { return Data.GetData(); } FORCEINLINE bool InUse() const { return bInUse; } /** Marks the buffer as no longer in use so it can be reassigned to another read thread. */ void MarkAsFree(); void Initialize(const int32& Size); void Resize(const int32& NewBufferSize, bool bForce = false); private: TAtomic bInUse; TArray Data; int32 PendingSize; friend class FLidarPointCloudDataBufferManager; }; /** Used to help track multiple buffer allocations */ class LIDARPOINTCLOUDRUNTIME_API FLidarPointCloudDataBufferManager { public: /** If MaxNumberOfBuffers is 0, no limit is applied */ FLidarPointCloudDataBufferManager(const int32& BufferSize, const int32& MaxNumberOfBuffers = 0); ~FLidarPointCloudDataBufferManager(); FLidarPointCloudDataBufferManager(const FLidarPointCloudDataBufferManager&) = delete; FLidarPointCloudDataBufferManager(FLidarPointCloudDataBufferManager&&) = delete; FLidarPointCloudDataBufferManager& operator=(const FLidarPointCloudDataBufferManager&) = delete; FLidarPointCloudDataBufferManager& operator=(FLidarPointCloudDataBufferManager&&) = delete; FLidarPointCloudDataBuffer* GetFreeBuffer(); void Resize(const int32& NewBufferSize); private: int32 BufferSize; int32 MaxNumberOfBuffers; int32 NumBuffersCreated; TList Head; TList* Tail; }; /** Used for Raycasting */ struct LIDARPOINTCLOUDRUNTIME_API FLidarPointCloudRay { public: FVector3f Origin; private: FVector3f Direction; FVector3f InvDirection; public: FLidarPointCloudRay() : FLidarPointCloudRay(FVector3f::ZeroVector, FVector3f::ForwardVector) {} FLidarPointCloudRay(const FVector3f& Origin, const FVector3f& Direction) : Origin(Origin) { SetDirection(Direction); } FLidarPointCloudRay(const FVector& Origin, const FVector& Direction) : FLidarPointCloudRay((FVector3f)Origin, (FVector3f)Direction) {} static FORCEINLINE FLidarPointCloudRay FromLocations(const FVector3f& Origin, const FVector3f& Destination) { return FLidarPointCloudRay(Origin, (Destination - Origin).GetSafeNormal()); } FLidarPointCloudRay& TransformBy(const FTransform& Transform) { Origin = (FVector3f)Transform.TransformPosition((FVector)Origin); SetDirection((FVector3f)Transform.TransformVector((FVector)Direction)); return *this; } FLidarPointCloudRay TransformBy(const FTransform& Transform) const { return FLidarPointCloudRay(Transform.TransformPosition((FVector)Origin), Transform.TransformVector((FVector)Direction)); } FORCEINLINE FLidarPointCloudRay ShiftBy(const FVector3f& Offset) const { return FLidarPointCloudRay(Origin + Offset, Direction); } FORCEINLINE FLidarPointCloudRay ShiftBy(const FVector& Offset) const { return FLidarPointCloudRay(Origin + (FVector3f)Offset, Direction); } FORCEINLINE FVector3f GetDirection() const { return Direction; } FORCEINLINE void SetDirection(const FVector3f& NewDirection) { Direction = NewDirection; InvDirection = Direction.Reciprocal(); } /** An Efficient and Robust Ray-Box Intersection Algorithm. Amy Williams et al. 2004. */ FORCEINLINE bool Intersects(const FBox& Box) const { float tmin, tmax, tymin, tymax, tzmin, tzmax; tmin = ((InvDirection.X < 0 ? Box.Max.X : Box.Min.X) - Origin.X) * InvDirection.X; tmax = ((InvDirection.X < 0 ? Box.Min.X : Box.Max.X) - Origin.X) * InvDirection.X; tymin = ((InvDirection.Y < 0 ? Box.Max.Y : Box.Min.Y) - Origin.Y) * InvDirection.Y; tymax = ((InvDirection.Y < 0 ? Box.Min.Y : Box.Max.Y) - Origin.Y) * InvDirection.Y; if ((tmin > tymax) || (tymin > tmax)) { return false; } if (tymin > tmin) { tmin = tymin; } if (tymax < tmax) { tmax = tymax; } tzmin = ((InvDirection.Z < 0 ? Box.Max.Z : Box.Min.Z) - Origin.Z) * InvDirection.Z; tzmax = ((InvDirection.Z < 0 ? Box.Min.Z : Box.Max.Z) - Origin.Z) * InvDirection.Z; if ((tmin > tzmax) || (tzmin > tmax)) { return false; } return true; } FORCEINLINE bool Intersects(const FLidarPointCloudPoint* Point, const float& RadiusSq) const { const FVector3f L = Point->Location - Origin; const float tca = FVector3f::DotProduct(L, Direction); if (tca < 0) { return false; } const float d2 = FVector3f::DotProduct(L, L) - tca * tca; return d2 <= RadiusSq; } }; UENUM(BlueprintType) enum class ELidarClippingVolumeMode : uint8 { /** This will clip all points inside the volume */ ClipInside, /** This will clip all points outside of the volume */ ClipOutside, }; /** Used to pass clipping information for async processing, to avoid accessing UObjects in non-GT */ struct FLidarPointCloudClippingVolumeParams { ELidarClippingVolumeMode Mode; int32 Priority; FBox Bounds; FMatrix PackedShaderData; FORCEINLINE bool operator<(const FLidarPointCloudClippingVolumeParams& O) const { return (Priority < O.Priority) || (Priority == O.Priority && Mode > O.Mode); } FLidarPointCloudClippingVolumeParams(const class ALidarClippingVolume* ClippingVolume); }; UENUM(BlueprintType) enum class ELidarPointCloudColorationMode : uint8 { /** Uses color tint only */ None, /** Uses imported RGB / Intensity data */ Data, /** Uses imported RGB / Intensity data combined with Alpha mask from Classification Colors */ DataWithClassificationAlpha, /** The cloud's color will be overridden with elevation-based color */ Elevation, /** The cloud's color will be overridden with relative position-based color */ Position, /** Uses Classification ID of the point along with the component's Classification Colors property to sample the color */ Classification }; UENUM(BlueprintType) enum class ELidarPointCloudSpriteShape : uint8 { Square, Circle, }; /** Convenience struct to group all component's rendering params into one */ struct FLidarPointCloudComponentRenderParams { int32 MinDepth; int32 MaxDepth; float BoundsScale; FVector3f BoundsSize; FVector3f LocationOffset; float ComponentScale; float PointSize; float PointSizeBias; float GapFillingStrength; bool bOwnedByEditor; bool bDrawNodeBounds; bool bUseScreenSizeScaling; bool bShouldRenderFacingNormals; bool bUseFrustumCulling; ELidarPointCloudColorationMode ColorSource; ELidarPointCloudSpriteShape PointShape; ELidarPointCloudScalingMethod ScalingMethod; FVector4f Saturation; FVector4f Contrast; FVector4f Gamma; FVector4f Offset; FVector3f ColorTint; float IntensityInfluence; TMap ClassificationColors; FLinearColor ElevationColorBottom; FLinearColor ElevationColorTop; class UMaterialInterface* Material = nullptr; void UpdateFromComponent(class ULidarPointCloudComponent* Component); }; struct FBenchmarkTimer { static void Reset() { Time = FPlatformTime::Seconds(); } static double Split(uint8 Decimal = 2) { double Now = FPlatformTime::Seconds(); double Delta = Now - Time; Time = Now; uint32 Multiplier = FMath::Pow(10.f, Decimal); return FMath::RoundToDouble(Delta * Multiplier * 1000) / Multiplier; } static void Log(FString Text, uint8 Decimal = 2) { const double SplitTime = Split(Decimal); PC_LOG("%s: %f ms", *Text, SplitTime); } private: static double Time; }; struct FScopeBenchmarkTimer { public: bool bActive; private: double Time; FString Label; float* OutTimer; public: FScopeBenchmarkTimer(const FString& Label) : bActive(true) , Time(FPlatformTime::Seconds()) , Label(Label) , OutTimer(nullptr) { } FScopeBenchmarkTimer(float* OutTimer) : bActive(true) , Time(FPlatformTime::Seconds()) , OutTimer(OutTimer) { } ~FScopeBenchmarkTimer() { if (bActive) { float Delta = FMath::RoundToDouble((FPlatformTime::Seconds() - Time) * 100000) * 0.01; if (OutTimer) { *OutTimer += Delta; } else { PC_LOG("%s: %f ms", *Label, Delta); } } } };