// Copyright 2020 Phyronnaz #include "VoxelAssets/VoxelHeightmapAsset.h" #include "VoxelAssets/VoxelHeightmapAssetInstance.h" #include "VoxelIntBox.h" #include "VoxelMessages.h" #include "VoxelFeedbackContext.h" #include "VoxelUtilities/VoxelSerializationUtilities.h" #include "VoxelGenerators/VoxelEmptyGenerator.h" #include "VoxelGenerators/VoxelTransformableGeneratorHelper.h" #include "Serialization/LargeMemoryReader.h" #include "Serialization/LargeMemoryWriter.h" #include "Engine/Texture2D.h" #include "Misc/ScopedSlowTask.h" #define LANDSCAPE_ASSET_THUMBNAIL_RES 128 DEFINE_VOXEL_MEMORY_STAT(STAT_VoxelHeightmapAssetMemory); /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// template<> VOXEL_API TVoxelHeightmapAssetSamplerWrapper::TVoxelHeightmapAssetSamplerWrapper(UVoxelHeightmapAsset* Asset) : Scale(Asset ? FMath::Max(SMALL_NUMBER, Asset->Scale) : 1) , HeightScale(Asset ? Asset->HeightScale : 1) , HeightOffset(Asset ? Asset->HeightOffset : 0) , Data(Asset ? CastChecked(Asset)->GetDataSharedPtr() : MakeVoxelShared>()) { } template<> VOXEL_API TVoxelHeightmapAssetSamplerWrapper::TVoxelHeightmapAssetSamplerWrapper(UVoxelHeightmapAsset* Asset) : Scale(Asset ? FMath::Max(SMALL_NUMBER, Asset->Scale) : 1) , HeightScale(Asset ? Asset->HeightScale : 1) , HeightOffset(Asset ? Asset->HeightOffset : 0) , Data(Asset ? CastChecked(Asset)->GetDataSharedPtr() : MakeVoxelShared>()) { } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// template void UVoxelHeightmapAsset::TryLoad(TVoxelHeightmapAssetData& Data) { if (Data.IsEmpty()) { // Seems invalid, try to load LoadData(Data); } } template void UVoxelHeightmapAsset::SaveData(const TVoxelHeightmapAssetData& Data) { Modify(); FVoxelScopedSlowTask Saving(2.f); VoxelCustomVersion = FVoxelHeightmapAssetDataVersion::LatestVersion; MaterialConfigFlag = GVoxelMaterialConfigFlag; Saving.EnterProgressFrame(1.f, VOXEL_LOCTEXT("Serializing")); FLargeMemoryWriter MemoryWriter(Data.GetAllocatedSize()); bool bNeedToSave = false; const_cast&>(Data).Serialize(MemoryWriter, MaterialConfigFlag, FVoxelHeightmapAssetDataVersion::Type(VoxelCustomVersion), bNeedToSave); ensure(!bNeedToSave); Saving.EnterProgressFrame(1.f, VOXEL_LOCTEXT("Compressing")); FVoxelSerializationUtilities::CompressData(MemoryWriter, CompressedData); SyncProperties(Data); #if WITH_EDITOR // Clear thumbnail ThumbnailSave.Reset(); ThumbnailTexture = nullptr; #endif } template void UVoxelHeightmapAsset::LoadData(TVoxelHeightmapAssetData& Data) { if (CompressedData.Num() == 0) { // Nothing to load return; } TArray64 UncompressedData; if (!FVoxelSerializationUtilities::DecompressData(CompressedData, UncompressedData)) { FVoxelMessages::Error("Decompression failed, data is corrupted", this); return; } FLargeMemoryReader MemoryReader(UncompressedData.GetData(), UncompressedData.Num()); bool bNeedToSave = false; Data.Serialize(MemoryReader, MaterialConfigFlag, FVoxelHeightmapAssetDataVersion::Type(VoxelCustomVersion), bNeedToSave); if (!ensure(!MemoryReader.IsError())) { FVoxelMessages::Error("Serialization failed, data is corrupted", this); Data.ClearData(); return; } ensure(MemoryReader.AtEnd()); if (bNeedToSave) { SaveData(Data); } SyncProperties(Data); } template void UVoxelHeightmapAsset::SyncProperties(const TVoxelHeightmapAssetData& Data) { // To access those properties without loading the asset Width = Data.GetWidth(); Height = Data.GetHeight(); } template FVoxelIntBox UVoxelHeightmapAsset::GetBoundsImpl() const { // const cast to load auto Wrapper = TVoxelHeightmapAssetSamplerWrapper(const_cast(this)); return FVoxelIntBox( FIntVector( 0, 0, FMath::FloorToInt(-Precision + Wrapper.GetMinHeight() - AdditionalThickness)), FIntVector( Wrapper.GetWidth(), Wrapper.GetHeight(), FMath::CeilToInt(Precision + Wrapper.GetMaxHeight()))).Translate( FIntVector( -Wrapper.GetWidth() / 2, -Wrapper.GetHeight() / 2, 0)); } void UVoxelHeightmapAsset::Serialize(FArchive& Ar) { Super::Serialize(Ar); if ((Ar.IsLoading() || Ar.IsSaving()) && !Ar.IsTransacting()) { if (VoxelCustomVersion == FVoxelHeightmapAssetDataVersion::BeforeCustomVersionWasAdded) { Ar << MaterialConfigFlag; Ar << CompressedData; } else { CompressedData.BulkSerialize(Ar); } } } #if WITH_EDITOR template UTexture2D* UVoxelHeightmapAsset::GetThumbnailInternal() { if (ThumbnailSave.Num() == 0) { const TVoxelHeightmapAssetData& Data = CastChecked(this)->GetData(); ThumbnailSave.SetNumUninitialized(LANDSCAPE_ASSET_THUMBNAIL_RES * LANDSCAPE_ASSET_THUMBNAIL_RES); for (int32 X = 0; X < LANDSCAPE_ASSET_THUMBNAIL_RES; X++) { for (int32 Y = 0; Y < LANDSCAPE_ASSET_THUMBNAIL_RES; Y++) { const float HeightValue = Data.GetHeight( float(X) / LANDSCAPE_ASSET_THUMBNAIL_RES * Data.GetWidth(), float(Y) / LANDSCAPE_ASSET_THUMBNAIL_RES * Data.GetHeight(), EVoxelSamplerMode::Clamp); const float Value = (HeightValue - Data.GetMinHeight()) / float(Data.GetMaxHeight() - Data.GetMinHeight()); const uint8 Byte = FVoxelUtilities::FloatToUINT8(Value); ThumbnailSave[X + LANDSCAPE_ASSET_THUMBNAIL_RES * Y] = FColor(Byte, Byte, Byte, 255); } } } if (!ThumbnailTexture) { ThumbnailTexture = UTexture2D::CreateTransient(LANDSCAPE_ASSET_THUMBNAIL_RES, LANDSCAPE_ASSET_THUMBNAIL_RES); ThumbnailTexture->CompressionSettings = TC_HDR; ThumbnailTexture->SRGB = false; ThumbnailSave.SetNumZeroed(LANDSCAPE_ASSET_THUMBNAIL_RES * LANDSCAPE_ASSET_THUMBNAIL_RES); FTexture2DMipMap& Mip = ThumbnailTexture->GetPlatformData()->Mips[0]; void* TextureData = Mip.BulkData.Lock(LOCK_READ_WRITE); FMemory::Memcpy(TextureData, ThumbnailSave.GetData(), ThumbnailSave.Num() * sizeof(FColor)); Mip.BulkData.Unlock(); ThumbnailTexture->UpdateResource(); } return ThumbnailTexture; } UTexture2D* UVoxelHeightmapAsset::GetThumbnail() { if (IsA()) { return GetThumbnailInternal(); } else if (IsA()) { return GetThumbnailInternal(); } else { check(false); return nullptr; } } #endif /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// UVoxelHeightmapAssetFloat::UVoxelHeightmapAssetFloat() : Data(MakeVoxelShared>()) { HeightScale = 0.01f; } TVoxelHeightmapAssetData& UVoxelHeightmapAssetFloat::GetData() { return *GetDataSharedPtr(); } TVoxelSharedRef> UVoxelHeightmapAssetFloat::GetDataSharedPtr() { TryLoad(*Data); return Data.ToSharedRef(); } void UVoxelHeightmapAssetFloat::Save() { SaveData(*Data); } TVoxelSharedRef> UVoxelHeightmapAssetFloat::GetInstanceImpl() { return MakeVoxelShared>(*this); } TVoxelSharedRef UVoxelHeightmapAssetFloat::GetInstance() { return GetInstanceImpl(); } TVoxelSharedRef UVoxelHeightmapAssetFloat::GetTransformableInstance() { return MakeVoxelShared>>(GetInstanceImpl(), false); } FVoxelIntBox UVoxelHeightmapAssetFloat::GetBounds() const { return GetBoundsImpl(); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// UVoxelHeightmapAssetUINT16::UVoxelHeightmapAssetUINT16() : Data(MakeVoxelShared>()) { HeightOffset = -32768 / 128.f; HeightScale = 1.f / 128.f; AdditionalThickness = -HeightOffset; } TVoxelHeightmapAssetData& UVoxelHeightmapAssetUINT16::GetData() { return *GetDataSharedPtr(); } TVoxelSharedRef> UVoxelHeightmapAssetUINT16::GetDataSharedPtr() { TryLoad(*Data); return Data.ToSharedRef(); } void UVoxelHeightmapAssetUINT16::Save() { SaveData(*Data); } TVoxelSharedRef> UVoxelHeightmapAssetUINT16::GetInstanceImpl() { return MakeVoxelShared>(*this); } TVoxelSharedRef UVoxelHeightmapAssetUINT16::GetInstance() { return GetInstanceImpl(); } TVoxelSharedRef UVoxelHeightmapAssetUINT16::GetTransformableInstance() { return MakeVoxelShared>>(GetInstanceImpl(), false); } FVoxelIntBox UVoxelHeightmapAssetUINT16::GetBounds() const { return GetBoundsImpl(); }