// Copyright 2020 Phyronnaz #pragma once #include "CoreMinimal.h" #include "VoxelMinimal.h" #include "VoxelUtilities/VoxelBaseUtilities.h" namespace ConstExprUtils { template static FORCEINLINE constexpr T Clamp(const T X, const T Min, const T Max) { return X < Min ? Min : X < Max ? X : Max; } static FORCEINLINE constexpr int32 Floor(float F) { if (int32(F) == F) { return int32(F); } if (F < 0) { return int32(F) - 1; } else { return int32(F); } } static FORCEINLINE constexpr int32 RoundToInt(float F) { return Floor(F + 0.5f); } } namespace EVoxelValueConfigFlag { enum Type : uint32 { EightBitsValue = 0x01, SixteenBitsValue = 0x02 }; } constexpr uint32 GVoxelValueConfigFlag = EIGHT_BITS_VOXEL_VALUE ? EVoxelValueConfigFlag::EightBitsValue : EVoxelValueConfigFlag::SixteenBitsValue; template struct TVoxelValueImpl { static constexpr T MAX_VOXELVALUE = TNumericLimits::Max(); static constexpr T MIN_VOXELVALUE = -TNumericLimits::Max(); static constexpr T INVALID_VOXELVALUE = TNumericLimits::Min(); static_assert(int64(INVALID_VOXELVALUE) == int64(MIN_VOXELVALUE) - 1, ""); public: // Full voxel FORCEINLINE constexpr static TVoxelValueImpl Full() { return InternalConstructor(-MAX_VOXELVALUE); } // Completely empty voxel FORCEINLINE constexpr static TVoxelValueImpl Empty() { return InternalConstructor(MAX_VOXELVALUE); } // Special voxel value, never reached using the normal constructor FORCEINLINE constexpr static TVoxelValueImpl Special() { return InternalConstructor(INVALID_VOXELVALUE); } // Precision of FVoxelValue used for comparisons FORCEINLINE constexpr static TVoxelValueImpl Precision() { return InternalConstructor(1); } public: TVoxelValueImpl() { } FORCEINLINE explicit constexpr TVoxelValueImpl(EForceInit) : F(0) { } FORCEINLINE explicit constexpr TVoxelValueImpl(float InValue) : F(ClampToStorage(ConstExprUtils::RoundToInt(ConstExprUtils::Clamp(InValue, -10.f, 10.f) * MAX_VOXELVALUE))) { // Float clamp: to avoid integer overflow. Can use 10.f as ClampToStorage is done on int32 } FORCEINLINE explicit constexpr TVoxelValueImpl(double InValue) : TVoxelValueImpl(float(InValue)) { } public: FORCEINLINE constexpr bool IsNull() const { return GetStorage() == 0; } FORCEINLINE constexpr bool IsEmpty() const { return GetStorage() > 0; } FORCEINLINE constexpr bool IsTotallyEmpty() const { return GetStorage() == MAX_VOXELVALUE; } FORCEINLINE constexpr bool IsTotallyFull() const { return GetStorage() == -MAX_VOXELVALUE; } FORCEINLINE constexpr int32 Sign() const { return GetStorage() > 0 ? 1 : GetStorage() == 0 ? 0 : -1; } FORCEINLINE constexpr float ToFloat() const { return float(GetStorage()) / float(MAX_VOXELVALUE); } FORCEINLINE constexpr TVoxelValueImpl GetInverse() const { return InternalConstructor(-GetStorage()); } FString ToString() const { return FString::SanitizeFloat(ToFloat()); } public: FORCEINLINE constexpr bool operator==(const TVoxelValueImpl& Rhs) const { return GetStorage() == Rhs.GetStorage(); } FORCEINLINE constexpr bool operator!=(const TVoxelValueImpl& Rhs) const { return GetStorage() != Rhs.GetStorage(); } FORCEINLINE constexpr bool operator<(const TVoxelValueImpl& Rhs) const { return GetStorage() < Rhs.GetStorage(); } FORCEINLINE constexpr bool operator>(const TVoxelValueImpl& Rhs) const { return GetStorage() > Rhs.GetStorage(); } FORCEINLINE constexpr bool operator<=(const TVoxelValueImpl& Rhs) const { return GetStorage() <= Rhs.GetStorage(); } FORCEINLINE constexpr bool operator>=(const TVoxelValueImpl& Rhs) const { return GetStorage() >= Rhs.GetStorage(); } FORCEINLINE constexpr TVoxelValueImpl& operator-=(const TVoxelValueImpl& Rhs) { GetStorage() = ClampToStorage(int32(GetStorage()) - int32(Rhs.GetStorage())); return *this; } FORCEINLINE constexpr TVoxelValueImpl& operator+=(const TVoxelValueImpl& Rhs) { GetStorage() = ClampToStorage(int32(GetStorage()) + int32(Rhs.GetStorage())); return *this; } FORCEINLINE constexpr TVoxelValueImpl operator-(const TVoxelValueImpl& Rhs) const { auto Copy = *this; return Copy -= Rhs; } FORCEINLINE constexpr TVoxelValueImpl operator+(const TVoxelValueImpl& Rhs) const { auto Copy = *this; return Copy += Rhs; } public: FORCEINLINE constexpr T& GetStorage() { return F; } FORCEINLINE constexpr T GetStorage() const { return F; } public: FORCEINLINE static constexpr TVoxelValueImpl InternalConstructor(T F) { TVoxelValueImpl Value(ForceInit); Value.F = F; return Value; } FORCEINLINE static constexpr int16 ClampToStorage(int32 F) { return ConstExprUtils::Clamp(F, -MAX_VOXELVALUE, MAX_VOXELVALUE); } private: T F; }; using FVoxelValue8 = TVoxelValueImpl; using FVoxelValue16 = TVoxelValueImpl; #if EIGHT_BITS_VOXEL_VALUE using FVoxelValue = FVoxelValue8; #else using FVoxelValue = FVoxelValue16; #endif static_assert(FVoxelValue(0.f).IsNull(), "FVoxelValue error"); static_assert(!FVoxelValue(0.f).IsEmpty(), "FVoxelValue error"); // 0 is inside static_assert(FVoxelValue::Empty().IsTotallyEmpty(), "FVoxelValue error"); static_assert(FVoxelValue::Full().IsTotallyFull(), "FVoxelValue error"); static_assert(FVoxelValue::Empty().ToFloat() == 1.f, "FVoxelValue error"); static_assert(FVoxelValue::Full().ToFloat() == -1.f, "FVoxelValue error"); static_assert(FVoxelValue(1.f) == FVoxelValue::Empty(), "FVoxelValue error"); static_assert(FVoxelValue(-1.f) == FVoxelValue::Full(), "FVoxelValue error"); static_assert(FVoxelValue(FVoxelValue::Precision().ToFloat()) == FVoxelValue::Precision(), "FVoxelValue error"); template <> struct TTypeTraits : TTypeTraitsBase { enum { IsBytewiseComparable = true }; }; struct FVoxelValueConverter { template::Value && TIsSame::Value, int>::Type = 0> FORCEINLINE static constexpr FVoxelValue ConvertValue(T Value) { // Need to make FVoxelValue16 a dependent type typename TEnableIf::Type Result(ForceInit); // Make sure special cases are handled correctly // eg FVoxelValue16::Empty() >> 8 == FVoxelValue16::Special() >> 8 if (Value == FVoxelValue8::Full()) { Result = FVoxelValue16::Full(); } else if (Value == FVoxelValue8::Empty()) { Result = FVoxelValue16::Empty(); } else if (Value == FVoxelValue8::Special()) { Result = FVoxelValue16::Special(); } else { Result = FVoxelValue16::InternalConstructor(int16(Value.GetStorage()) << 8); } ensureVoxelSlowNoSideEffects(Result.IsEmpty() == Value.IsEmpty()); return Result; } template::Value && TIsSame::Value, int>::Type = 0> FORCEINLINE static constexpr FVoxelValue ConvertValue(T Value) { // Need to make FVoxelValue8 a dependent type typename TEnableIf::Type Result(ForceInit); // Make sure special cases are handled correctly // eg FVoxelValue16::Empty() >> 8 == FVoxelValue16::Special() >> 8 if (Value == FVoxelValue16::Full()) { Result = FVoxelValue8::Full(); } else if (Value == FVoxelValue16::Empty()) { Result = FVoxelValue8::Empty(); } else if (Value == FVoxelValue16::Special()) { Result = FVoxelValue8::Special(); } else { // DivideCeil: so that data between 0 and 255 is mapped to 1 and not 0 which would have a different sign Result = FVoxelValue8::InternalConstructor(FVoxelUtilities::ClampToINT8(FVoxelUtilities::DivideCeil(Value.GetStorage(), 256))); } ensureVoxelSlowNoSideEffects(Result.IsEmpty() == Value.IsEmpty()); return Result; } template::Value, int>::Type = 0> FORCEINLINE static constexpr FVoxelValue ConvertValue(T Value) { return Value; } template::Value, int>::Type = 0> FORCEINLINE static TArray ConvertValues(TArray&& Values) { return MoveTemp(Values); } template::Value, int>::Type = 0> static TArray ConvertValues(TArray&& Values) { static_assert(!TIsSame::Value, ""); TArray Result; Result.Reserve(Values.Num()); for (auto& Value : Values) { Result.Add(ConvertValue(Value)); } return Result; } }; static_assert(FVoxelValueConverter::ConvertValue(FVoxelValue8::Full()) == FVoxelValueConverter::ConvertValue(FVoxelValue16::Full()), "FVoxelValue error"); static_assert(FVoxelValueConverter::ConvertValue(FVoxelValue8::Empty()) == FVoxelValueConverter::ConvertValue(FVoxelValue16::Empty()), "FVoxelValue error"); static_assert(FVoxelValueConverter::ConvertValue(FVoxelValue8::Special()) == FVoxelValueConverter::ConvertValue(FVoxelValue16::Special()), "FVoxelValue error"); static_assert(FVoxelValueConverter::ConvertValue(FVoxelValue8::InternalConstructor(0)) == FVoxelValueConverter::ConvertValue(FVoxelValue16::InternalConstructor(0)), "FVoxelValue error"); static_assert(FVoxelValueConverter::ConvertValue(FVoxelValue16::InternalConstructor(+1)).IsEmpty(), "FVoxelValue error"); static_assert(!FVoxelValueConverter::ConvertValue(FVoxelValue16::InternalConstructor(-1)).IsEmpty(), "FVoxelValue error");