// Copyright 2020 Phyronnaz #pragma once #include "CoreMinimal.h" #include "VoxelAssets/VoxelHeightmapAsset.h" #include "VoxelAssets/VoxelHeightmapAssetSamplerWrapper.h" #include "VoxelGenerators/VoxelGeneratorHelpers.h" template struct TVoxelHeightmapAssetSelector; template<> struct TVoxelHeightmapAssetSelector { using Type = UVoxelHeightmapAssetFloat; }; template<> struct TVoxelHeightmapAssetSelector { using Type = UVoxelHeightmapAssetUINT16; }; template class TVoxelHeightmapAssetInstance : public TVoxelGeneratorInstanceHelper, typename TVoxelHeightmapAssetSelector::Type> { public: using Super = TVoxelGeneratorInstanceHelper, typename TVoxelHeightmapAssetSelector::Type>; const TVoxelHeightmapAssetSamplerWrapper Wrapper; const float Precision; const bool bInfiniteExtent; const FVoxelIntBox WorldBounds; public: explicit TVoxelHeightmapAssetInstance(typename TVoxelHeightmapAssetSelector::Type& Asset) : Super(&Asset) , Wrapper(&Asset) , Precision(Asset.Precision) , bInfiniteExtent(Asset.bInfiniteExtent) , WorldBounds(Asset.GetBounds()) { } //~ Begin FVoxelGeneratorInstance Interface FORCEINLINE v_flt GetValueImpl(v_flt X, v_flt Y, v_flt Z, int32 LOD, const FVoxelItemStack& Items) const { if (bInfiniteExtent || WorldBounds.ContainsFloat(X, Y, Z)) // Note: it's safe to access outside the bounds { const float Height = Wrapper.GetHeight(X + Wrapper.GetWidth() / 2, Y + Wrapper.GetHeight() / 2, EVoxelSamplerMode::Clamp); return (Z - Height) / Precision; } else { // Outside asset bounds return 1; } } FORCEINLINE FVoxelMaterial GetMaterialImpl(v_flt X, v_flt Y, v_flt Z, int32 LOD, const FVoxelItemStack& Items) const { return Wrapper.GetMaterial(X + Wrapper.GetWidth() / 2, Y + Wrapper.GetHeight() / 2, EVoxelSamplerMode::Clamp); } TVoxelRange GetValueRangeImpl(const FVoxelIntBox& Bounds, int32 LOD, const FVoxelItemStack& Items) const { if (!bInfiniteExtent && !Bounds.Intersect(WorldBounds)) { return 1; } const bool bEntirelyContained = WorldBounds.Contains(Bounds); const auto XRange = TVoxelRange(Bounds.Min.X, Bounds.Max.X) + Wrapper.GetWidth() / 2; const auto YRange = TVoxelRange(Bounds.Min.Y, Bounds.Max.Y) + Wrapper.GetHeight() / 2; const auto ZRange = TVoxelRange(Bounds.Min.Z, Bounds.Max.Z); auto HeightRange = TVoxelRange(Wrapper.GetHeightRange(XRange, YRange, EVoxelSamplerMode::Clamp)); if (!bEntirelyContained && bInfiniteExtent) { // Height will be 0 outside the boundaries if bInfiniteExtent = true HeightRange = TVoxelRange::Union(HeightRange, 0.f); } auto Range = (ZRange - HeightRange) / Precision; if (!bEntirelyContained && !bInfiniteExtent) { // Intersects with the boundary Range = TVoxelRange::Union(1.f, Range); } return Range; } virtual void GetValues(TVoxelQueryZone& QueryZone, int32 LOD, const FVoxelItemStack& Items) const override final { for (VOXEL_QUERY_ZONE_ITERATE(QueryZone, X)) { for (VOXEL_QUERY_ZONE_ITERATE(QueryZone, Y)) { const float Height = Wrapper.GetHeight(X + Wrapper.GetWidth() / 2, Y + Wrapper.GetHeight() / 2, EVoxelSamplerMode::Clamp); for (VOXEL_QUERY_ZONE_ITERATE(QueryZone, Z)) { FVoxelValue Value; if (bInfiniteExtent || WorldBounds.Contains(X, Y, Z)) { Value = FVoxelValue((Z - Height) / Precision); } else { // Outside asset bounds Value = FVoxelValue::Empty(); } QueryZone.Set(X, Y, Z, Value); } } } } virtual FVector GetUpVector(v_flt X, v_flt Y, v_flt Z) const override final { return FVector::UpVector; } //~ End FVoxelGeneratorInstance Interface };