// Copyright Epic Games, Inc. All Rights Reserved. #include "MuR/OpImageProject.h" #include "MuR/ConvertData.h" #include "MuR/Image.h" #include "MuR/ImagePrivate.h" #include "MuR/Layout.h" #include "MuR/MeshBufferSet.h" #include "MuR/MeshPrivate.h" #include "MuR/MutableMath.h" #include "MuR/MutableTrace.h" #include "MuR/OpMeshClipWithMesh.h" #include "MuR/Parameters.h" #include "MuR/ParametersPrivate.h" #include "MuR/Ptr.h" #include "MuR/Raster.h" #include "Async/ParallelFor.h" #include "Containers/Array.h" #include "Intersection/IntrRay3Triangle3.h" #include "Math/IntPoint.h" #include "Math/UnrealMathSSE.h" #include "Misc/AssertionMacros.h" #include "Math/UnrealMathUtility.h" #include "Spatial/PointHashGrid3.h" #include "MuR/Image.h" #include "MuR/Mesh.h" namespace mu { //------------------------------------------------------------------------------------------------- //! This format is the one we assumee the meshes optimised for planar and cylindrical projection //! will have. //! See CreateMeshOptimisedForProjection struct FOptimizedVertex { FVector2f Uv; FVector3f Position; FVector3f Normal; }; static_assert(sizeof(FOptimizedVertex)==32, "UNEXPECTED_STRUCT_SIZE" ); namespace Private { struct FImageRasterInvokeArgs { const FMesh* MeshPtr; FImage* ImagePtr; const FImage* SourcePtr; const FImage* MaskPtr; bool bIsRGBFadingEnabled; bool bIsAlphaFadingEnabled; ESamplingMethod SamplingMethod; float FadeStart; float FadeEnd; float ProjectionAngle; float MipInterpolationFactor; int32 Layout; uint64 BlockId; UE::Math::TIntVector2 CropMin; UE::Math::TIntVector2 UncroppedSize; FScratchImageProject* Scratch; }; enum class EPixelProcessorFeatures { None = 0, WithMask = 1 << 1, SamplingPoint = 1 << 2, SamplingLinear = 1 << 3, ProjectionPlanar = 1 << 4, ProjectionCylindrical = 1 << 5, ProjectionWrap = 1 << 6, FormatRGBA = 1 << 7, FormatRGB = 1 << 8, FormatL = 1 << 9, VectorizedImpl = 1 << 10 }; ENUM_CLASS_FLAGS(EPixelProcessorFeatures); constexpr bool CheckExactlyOneFormatFlag(EPixelProcessorFeatures Features) { return static_cast(EnumHasAnyFlags(Features, EPixelProcessorFeatures::FormatL)) + static_cast(EnumHasAnyFlags(Features, EPixelProcessorFeatures::FormatRGB)) + static_cast(EnumHasAnyFlags(Features, EPixelProcessorFeatures::FormatRGBA)) == 1; } constexpr bool CheckExactlyOneSamplingFlag(EPixelProcessorFeatures Features) { return static_cast(EnumHasAnyFlags(Features, EPixelProcessorFeatures::SamplingPoint)) + static_cast(EnumHasAnyFlags(Features, EPixelProcessorFeatures::SamplingLinear)) == 1; } constexpr bool CheckExactlyOneProjectionFlag(EPixelProcessorFeatures Features) { return static_cast(EnumHasAnyFlags(Features, EPixelProcessorFeatures::ProjectionCylindrical)) + static_cast(EnumHasAnyFlags(Features, EPixelProcessorFeatures::ProjectionPlanar)) + static_cast(EnumHasAnyFlags(Features, EPixelProcessorFeatures::ProjectionWrap)) == 1; } constexpr int32 GetFormatNumChannels(EPixelProcessorFeatures Features) { if (EnumHasAnyFlags(Features, EPixelProcessorFeatures::FormatL)) { return 1; } else if (EnumHasAnyFlags(Features, EPixelProcessorFeatures::FormatRGB)) { return 3; } else if (EnumHasAnyFlags(Features, EPixelProcessorFeatures::FormatRGBA)) { return 4; } return -1; } // The processor constant data fits into a 64 bytes cache line, align to it so potentially have less cache // misses. struct alignas(64) FProjectedPixelProcessorContext { const uint8* TargetData = nullptr; const uint8* Source0Data = nullptr; uint16 Source0SizeX = 0; uint16 Source0SizeY = 0; uint16 Source1SizeX = 0; uint16 Source1SizeY = 0; const uint8* Source1Data = nullptr; const uint8* MaskData = nullptr; //! Cosine of the fading angle range float FadeEndCos = 0.0f; // precomputation of 255 / (FadeStartCos - FadeEndCos) float OneOverFadeRangeTimes255 = 0.0f; // Only used for cylindrical projections. float OneOverProjectionAngle = 0.0f; // Only used with linear sampling. float MipInterpolationFactor = 0.0f; // Those are the seed for a mask 0xFFFFFFFF (-1) mask for enabled and 0 otherwise. // so, the expected value here is -1 for enabled and 0 for disabled. int32 RGBFadingEnabledMask = 0; int32 AlphaFadingEnabledMask = 0; }; static_assert(sizeof(FProjectedPixelProcessorContext) <= 64); template class TProjectedPixelProcessor { static_assert(CheckExactlyOneFormatFlag(Features)); static_assert(CheckExactlyOneSamplingFlag(Features)); static_assert(CheckExactlyOneProjectionFlag(Features)); static constexpr int32 PIXEL_SIZE = GetFormatNumChannels(Features); static_assert(PIXEL_SIZE != -1 && PIXEL_SIZE > 0); public: static FProjectedPixelProcessorContext MakeContext( const FImage* Source, const uint8* InTargetData, const uint8* InMaskData, bool bInIsRGBFadingEnabled, bool bInIsAlphaFadingEnabled, float FadeStart, float FadeEnd, float InProjectionAngle, float InMipInterpolationFactor) { FProjectedPixelProcessorContext Context; Context.Source0SizeX = static_cast(Source->GetSizeX()); Context.Source0SizeY = static_cast(Source->GetSizeY()); Context.Source0Data = (Context.Source0SizeX > 0 && Context.Source0SizeY > 0) ? Source->GetLODData(0) : nullptr; if constexpr (EnumHasAnyFlags(Features, EPixelProcessorFeatures::SamplingLinear)) { // If we don't have the next mip, use the first mip as a fallback. const bool bHasNextMip = Source->GetLODCount() >= 2; Context.Source1Data = bHasNextMip ? Source->GetMipData(1) : Context.Source0Data; FIntVector2 Source1Size = Source->CalculateMipSize(1); Context.Source1SizeX = bHasNextMip ? static_cast(Source1Size.X) : Context.Source0SizeX; Context.Source1SizeY = bHasNextMip ? static_cast(Source1Size.Y) : Context.Source0SizeY; // Invalidate SourceData if either of the two is not valid. if (!(Context.Source0Data && Context.Source1Data)) { Context.Source0Data = nullptr; Context.Source1Data = nullptr; } } Context.MipInterpolationFactor = InMipInterpolationFactor; Context.RGBFadingEnabledMask = bInIsRGBFadingEnabled ? -1 : 0; Context.AlphaFadingEnabledMask = bInIsAlphaFadingEnabled ? -1 : 0; if constexpr (EnumHasAnyFlags(Features, EPixelProcessorFeatures::ProjectionPlanar)) { const float FadeStartCos = FMath::Cos(FadeStart); Context.FadeEndCos = FMath::Cos(FadeEnd); const float FadeCosRangeSafeDiv = FMath::IsNearlyZero(FadeStartCos - Context.FadeEndCos, UE_KINDA_SMALL_NUMBER) ? UE_KINDA_SMALL_NUMBER : FadeStartCos - Context.FadeEndCos; Context.OneOverFadeRangeTimes255 = 255.0f / FadeCosRangeSafeDiv; } else { Context.FadeEndCos = 0.0f; Context.OneOverFadeRangeTimes255 = 255.0f; } Context.OneOverProjectionAngle = FMath::IsNearlyZero(InProjectionAngle) ? 1.0f / UE_KINDA_SMALL_NUMBER : 1.0f / InProjectionAngle; Context.TargetData = InTargetData; Context.MaskData = InMaskData; check(GetImageFormatData(Source->GetFormat()).BytesPerBlock == PIXEL_SIZE); return Context; } static void ProcessPixel(const FProjectedPixelProcessorContext& Context, uint8* BufferPos, float Varying[4]) { if constexpr (EnumHasAnyFlags(Features, EPixelProcessorFeatures::VectorizedImpl)) { ProcessPixelVectorImpl(Context, BufferPos, Varying); } else { ProcessPixelImpl(Context, BufferPos, Varying); } } private: static void ProcessPixelVectorImpl(const FProjectedPixelProcessorContext& Context, uint8* BufferPos, float Varying[4]) { if (!Context.Source0Data) { return; } const bool bDepthClamp = Invoke([&]() -> bool { if constexpr (EnumHasAnyFlags(Features, EPixelProcessorFeatures::ProjectionCylindrical)) { const FVector2f CylinderPolarCoords(Varying[1], Varying[2]); return FVector2f::DotProduct(CylinderPolarCoords, CylinderPolarCoords) > 1.0f; } else { return static_cast((Varying[2] < 0.0f) | (Varying[2] > 1.0f)); } }); float Factor = static_cast(!bDepthClamp) * 255.0f; float MaskFactor = 255.0f; if constexpr (EnumHasAnyFlags(Features, EPixelProcessorFeatures::ProjectionPlanar)) { const float AngleCos = Varying[3]; Factor = FMath::Min(Factor, (FMath::Clamp((AngleCos - Context.FadeEndCos) * Context.OneOverFadeRangeTimes255, 0.0f, 255.0f))); MaskFactor = AngleCos > Context.FadeEndCos ? 255.0f : 0.0f; } if constexpr (EnumHasAnyFlags(Features, EPixelProcessorFeatures::WithMask)) { // Only read from memory if needed. if (Factor > UE_SMALL_NUMBER) { MaskFactor = ((MaskFactor > 0.0f) && Context.MaskData) ? static_cast(Context.MaskData[(BufferPos - Context.TargetData) / PIXEL_SIZE]) //-V609 : MaskFactor; Factor = (Factor * MaskFactor) * (1.0f/255.0f); } } if (Factor > UE_SMALL_NUMBER) { const FVector2f Uv = Invoke([&]() { if constexpr (EnumHasAnyFlags(Features, EPixelProcessorFeatures::ProjectionCylindrical)) { return FVector2f{ 0.5f + FMath::Atan2(Varying[2], -Varying[1]) * Context.OneOverProjectionAngle, Varying[0] }; } else { return FVector2f{ Varying[0], Varying[1] }; } }); if ((Uv.X >= 0.0f) & (Uv.X < 1.0f) & (Uv.Y >= 0.0f) & (Uv.Y < 1.0f)) { using namespace GlobalVectorConstants; auto LoadPixel = [](const uint8* Ptr) -> VectorRegister4Float { constexpr VectorRegister4Float OneOver255 = MakeVectorRegisterFloatConstant(1.0f / 255.0f, 1.0f / 255.0f, 1.0f / 255.0f, 1.0f / 255.0f); if constexpr (PIXEL_SIZE == 4) { const uint32 PackedData = *reinterpret_cast(Ptr); return VectorMultiply(MakeVectorRegister( static_cast((PackedData >> (8 * 0)) & 0xFF), static_cast((PackedData >> (8 * 1)) & 0xFF), static_cast((PackedData >> (8 * 2)) & 0xFF), static_cast((PackedData >> (8 * 3)) & 0xFF)), OneOver255); } else { alignas(VectorRegister4Float) float PixelData[4]; for (int32 C = 0; C < PIXEL_SIZE; ++C) { PixelData[C] = static_cast(Ptr[C]); } return VectorMultiply(VectorLoadAligned(&(PixelData[0])), OneOver255); } }; VectorRegister4Float Result; if constexpr (EnumHasAnyFlags(Features, EPixelProcessorFeatures::SamplingLinear)) { const VectorRegister4Int SizeI = MakeVectorRegisterInt(Context.Source0SizeX, Context.Source0SizeY, Context.Source1SizeX, Context.Source1SizeY); const VectorRegister4Float SizeF = VectorIntToFloat(SizeI); const VectorRegister4Float CoordsF = VectorMin(VectorMax( VectorMultiplyAdd(MakeVectorRegister(Uv.X, Uv.Y, Uv.X, Uv.Y), SizeF, FloatMinusOneHalf), FloatZero), VectorSubtract(SizeF, FloatOne)); const VectorRegister4Float Frac = VectorSubtract(CoordsF, VectorFloor(CoordsF)); const VectorRegister4Int CoordsI = VectorFloatToInt(CoordsF); const VectorRegister4Int CoordsIPlusOne = VectorIntMin( VectorIntAdd(CoordsI, IntOne), VectorIntSubtract(SizeI, IntOne)); alignas(VectorRegister4Int) int32 CoordsIData[4]; VectorIntStoreAligned(CoordsI, CoordsIData); alignas(VectorRegister4Int) int32 CoordsIPlusOneData[4]; VectorIntStoreAligned(CoordsIPlusOne, CoordsIPlusOneData); uint8 const* const Pixel000Ptr = Context.Source0Data + (CoordsIData[1] * Context.Source0SizeX + CoordsIData[0]) * PIXEL_SIZE; uint8 const* const Pixel010Ptr = Context.Source0Data + (CoordsIData[1] * Context.Source0SizeX + CoordsIPlusOneData[0]) * PIXEL_SIZE; uint8 const* const Pixel001Ptr = Context.Source0Data + (CoordsIPlusOneData[1] * Context.Source0SizeX + CoordsIData[0]) * PIXEL_SIZE; uint8 const* const Pixel011Ptr = Context.Source0Data + (CoordsIPlusOneData[1] * Context.Source0SizeX + CoordsIPlusOneData[0]) * PIXEL_SIZE; uint8 const* const Pixel100Ptr = Context.Source1Data + (CoordsIData[3] * Context.Source1SizeX + CoordsIData[2]) * PIXEL_SIZE; uint8 const* const Pixel110Ptr = Context.Source1Data + (CoordsIData[3] * Context.Source1SizeX + CoordsIPlusOneData[2]) * PIXEL_SIZE; uint8 const* const Pixel101Ptr = Context.Source1Data + (CoordsIPlusOneData[3] * Context.Source1SizeX + CoordsIData[2]) * PIXEL_SIZE; uint8 const* const Pixel111Ptr = Context.Source1Data + (CoordsIPlusOneData[3] * Context.Source1SizeX + CoordsIPlusOneData[2]) * PIXEL_SIZE; const VectorRegister4Float Pixel000 = LoadPixel(Pixel000Ptr); const VectorRegister4Float Pixel010 = LoadPixel(Pixel010Ptr); const VectorRegister4Float Pixel001 = LoadPixel(Pixel001Ptr); const VectorRegister4Float Pixel011 = LoadPixel(Pixel011Ptr); const VectorRegister4Float Pixel100 = LoadPixel(Pixel100Ptr); const VectorRegister4Float Pixel110 = LoadPixel(Pixel110Ptr); const VectorRegister4Float Pixel101 = LoadPixel(Pixel101Ptr); const VectorRegister4Float Pixel111 = LoadPixel(Pixel111Ptr); const VectorRegister4Float Frac0X = VectorReplicate(Frac, 0); const VectorRegister4Float Frac0Y = VectorReplicate(Frac, 1); // Bilerp image 0 VectorRegister4Float Sample0 = VectorMultiplyAdd(Frac0X, VectorSubtract(Pixel010, Pixel000), Pixel000); Sample0 = VectorMultiplyAdd( Frac0Y, VectorSubtract( VectorMultiplyAdd(Frac0X, VectorSubtract(Pixel011, Pixel001), Pixel001), Sample0), Sample0); const VectorRegister4Float Frac1X = VectorReplicate(Frac, 2); const VectorRegister4Float Frac1Y = VectorReplicate(Frac, 3); // Bilerp image 1 VectorRegister4Float Sample1 = VectorMultiplyAdd(Frac1X, VectorSubtract(Pixel110, Pixel100), Pixel100); Sample1 = VectorMultiplyAdd( Frac1Y, VectorSubtract( VectorMultiplyAdd(Frac1X, VectorSubtract(Pixel111, Pixel101), Pixel101), Sample1), Sample1); Result = VectorMultiplyAdd(VectorSetFloat1(Context.MipInterpolationFactor), VectorSubtract(Sample1, Sample0), Sample0); } else if (EnumHasAnyFlags(Features, EPixelProcessorFeatures::SamplingPoint)) { const int32 CoordX = FMath::FloorToInt32(Context.Source0SizeX * Uv.X); const int32 CoordY = FMath::FloorToInt32(Context.Source0SizeY * Uv.Y); Result = LoadPixel(Context.Source0Data + (CoordY*Context.Source0SizeX + CoordX) * PIXEL_SIZE); } Result = VectorMultiply( Result, // Fadding factor VectorSelect( // The selction mask needs to be 0xFFFFFFFF if enabled 0 otherwise. FadingEnabledValue will be -1 // if enabled and 0 otherwise. VectorCastIntToFloat(MakeVectorRegisterInt( static_cast(Context.RGBFadingEnabledMask), static_cast(Context.RGBFadingEnabledMask), static_cast(Context.RGBFadingEnabledMask), static_cast(Context.AlphaFadingEnabledMask))), VectorSetFloat1(Factor), VectorSetFloat1(MaskFactor))); if constexpr (PIXEL_SIZE == 4) { VectorStoreByte4(Result, BufferPos); VectorResetFloatRegisters(); } else { const AlignedFloat4 ResultData(VectorMin(VectorMax(Result, FloatZero), Float255)); for (int32 C = 0; C < PIXEL_SIZE; ++C) { BufferPos[C] = static_cast(ResultData[C]); } } } } } static void ProcessPixelImpl(const FProjectedPixelProcessorContext& Context, uint8* BufferPos, float Varying[4]) { if (!Context.Source0Data) { return; } const bool bDepthClamp = Invoke([&]() -> bool { if constexpr (EnumHasAnyFlags(Features, EPixelProcessorFeatures::ProjectionCylindrical)) { const FVector2f CylinderPolarCoords(Varying[1], Varying[2]); return FVector2f::DotProduct(CylinderPolarCoords, CylinderPolarCoords) > 1.0f; } else { return static_cast((Varying[2] < 0.0f) | (Varying[2] > 1.0f)); } }); uint16 Factor = static_cast(!bDepthClamp) * 255; uint16 MaskFactor = 255; if constexpr (EnumHasAnyFlags(Features, EPixelProcessorFeatures::ProjectionPlanar)) { const float AngleCos = Varying[3]; Factor = FMath::Min(Factor, static_cast(FMath::Clamp((AngleCos - Context.FadeEndCos) * Context.OneOverFadeRangeTimes255, 0.0f, 255.0f))); MaskFactor = AngleCos > Context.FadeEndCos ? 255 : 0; } if constexpr (EnumHasAnyFlags(Features, EPixelProcessorFeatures::WithMask)) { // Only read from memory if needed. if (Factor > 0) { MaskFactor = ((MaskFactor > 0) && Context.MaskData) ? Context.MaskData[(BufferPos - Context.TargetData) / PIXEL_SIZE] //-V609 : MaskFactor; Factor = (Factor * MaskFactor) / 255; } } if (Factor > 0) { const FVector2f Uv = Invoke([&]() { if constexpr (EnumHasAnyFlags(Features, EPixelProcessorFeatures::ProjectionCylindrical)) { return FVector2f{ 0.5f + FMath::Atan2(Varying[2], -Varying[1])*Context.OneOverProjectionAngle, Varying[0] }; } else { return FVector2f{ Varying[0], Varying[1] }; } }); const FVector2f Source0SizeF = FVector2f(Context.Source0SizeX, Context.Source0SizeY); const FVector2f Source1SizeF = FVector2f(Context.Source1SizeX, Context.Source1SizeY); if ((Uv.X >= 0.0f) & (Uv.X < 1.0f) & (Uv.Y >= 0.0f) & (Uv.Y < 1.0f)) { using FUInt16Vector2 = UE::Math::TIntVector2; struct FPixelData { alignas(8) uint16 Data[PIXEL_SIZE]; }; auto LoadPixel = [](const uint8* Ptr) -> FPixelData { FPixelData Result; if constexpr (PIXEL_SIZE == 4) { const uint32 PackedData = *reinterpret_cast(Ptr); Result.Data[0] = static_cast((PackedData >> (8 * 0)) & 0xFF); Result.Data[1] = static_cast((PackedData >> (8 * 1)) & 0xFF); Result.Data[2] = static_cast((PackedData >> (8 * 2)) & 0xFF); Result.Data[3] = static_cast((PackedData >> (8 * 3)) & 0xFF); } else { for (int32 C = 0; C < PIXEL_SIZE; ++C) { Result.Data[C] = static_cast(Ptr[C]); } } return Result; }; FPixelData Result; FMemory::Memzero(Result); if constexpr (EnumHasAnyFlags(Features, EPixelProcessorFeatures::SamplingLinear)) { auto SampleImageBilinear = [&](FVector2f Uv, FUInt16Vector2 Size, const uint8* DataPtr) { const FVector2f SizeF(Size.X, Size.Y); const FVector2f CoordsF = FVector2f( FMath::Clamp(Uv.X * SizeF.X - 0.5f, 0.0f, SizeF.X - 1.0f), FMath::Clamp(Uv.Y * SizeF.Y - 0.5f, 0.0f, SizeF.Y - 1.0f)); const FUInt16Vector2 Frac = FUInt16Vector2( static_cast(FMath::Frac(CoordsF.X) * 255.0f), static_cast(FMath::Frac(CoordsF.Y) * 255.0f)); const FIntVector2 Coords = FIntVector2(CoordsF.X, CoordsF.Y); const FIntVector2 CoordsPlusOne = FIntVector2( FMath::Min(Size.X - 1, Coords.X + 1), FMath::Min(Size.Y - 1, Coords.Y + 1)); uint8 const* const Pixel00Ptr = DataPtr + (Coords.Y * Size.X + Coords.X) * PIXEL_SIZE; uint8 const* const Pixel10Ptr = DataPtr + (Coords.Y * Size.X + CoordsPlusOne.X) * PIXEL_SIZE; uint8 const* const Pixel01Ptr = DataPtr + (CoordsPlusOne.Y * Size.X + Coords.X) * PIXEL_SIZE; uint8 const* const Pixel11Ptr = DataPtr + (CoordsPlusOne.Y * Size.X + CoordsPlusOne.X) * PIXEL_SIZE; FPixelData PixelData00 = LoadPixel(Pixel00Ptr); FPixelData PixelData10 = LoadPixel(Pixel10Ptr); FPixelData PixelData01 = LoadPixel(Pixel01Ptr); FPixelData PixelData11 = LoadPixel(Pixel11Ptr); FPixelData FilteredPixelData; for (int32 C = 0; C < PIXEL_SIZE; ++C) { const uint16 LerpY0 = ((PixelData10.Data[C] * Frac.X) + PixelData00.Data[C] * (255 - Frac.X)) / 255; const uint16 LerpY1 = ((PixelData11.Data[C] * Frac.X) + PixelData01.Data[C] * (255 - Frac.X)) / 255; FilteredPixelData.Data[C] = ((LerpY1 * Frac.Y) + LerpY0 * (255 - Frac.Y)) / 255; } return FilteredPixelData; }; Result = SampleImageBilinear(Uv, FUInt16Vector2(Context.Source0SizeX, Context.Source0SizeY), Context.Source0Data); FPixelData Sample1 = SampleImageBilinear(Uv, FUInt16Vector2(Context.Source1SizeX, Context.Source1SizeY), Context.Source1Data); const uint16 MipFactor = static_cast(Context.MipInterpolationFactor * 255.0f); for (int32 C = 0; C < PIXEL_SIZE; ++C) { Result.Data[C] = ((Sample1.Data[C] * MipFactor) + Result.Data[C] * (255 - MipFactor)) / 255; } } else if (EnumHasAnyFlags(Features, EPixelProcessorFeatures::SamplingPoint)) { const int32 CoordX = FMath::FloorToInt32(Context.Source0SizeX * Uv.X); const int32 CoordY = FMath::FloorToInt32(Context.Source0SizeY * Uv.Y); Result = LoadPixel(Context.Source0Data + (CoordY*Context.Source0SizeX + CoordX) * PIXEL_SIZE); } static_assert(EnumHasAnyFlags(Features, EPixelProcessorFeatures::SamplingPoint | EPixelProcessorFeatures::SamplingLinear)); static_assert(PIXEL_SIZE <= 4); // FadingEnabledMaskSeed will be -1 if enabled and 0 otherwise. const int16 FadingMask[4] = { static_cast(Context.RGBFadingEnabledMask), static_cast(Context.RGBFadingEnabledMask), static_cast(Context.RGBFadingEnabledMask), static_cast(Context.AlphaFadingEnabledMask) }; for (int32 I = 0; I < PIXEL_SIZE; ++I) { BufferPos[I] = static_cast( (Result.Data[I] * ((MaskFactor & ~FadingMask[I]) + (Factor & FadingMask[I]))) / 255); } } } } }; //------------------------------------------------------------------------------------------------- FORCENOINLINE void ImageRasterProjected_Optimised(const FMesh* pMesh, FImage* pImage, TTriangleRasterPixelProcRefType<4> PixelProc, float FadeEnd, UE::Math::TIntVector2 CropMin, UE::Math::TIntVector2 UncroppedSize, FScratchImageProject* Scratch) { MUTABLE_CPUPROFILER_SCOPE(ImageRasterProjected_Optimised); if (!pMesh || !pMesh->GetFaceCount()) { return; } EImageFormat Format = pImage->GetFormat(); int32 PixelSize = GetImageFormatData(Format).BytesPerBlock; int32 SizeX = pImage->GetSizeX(); int32 SizeY = pImage->GetSizeY(); bool bUseCropping = UncroppedSize[0] > 0; // Get the vertices int32 VertexCount = pMesh->GetVertexCount(); check((int32)Scratch->Vertices.Num() == VertexCount); check((int32)Scratch->CulledVertex.Num() == VertexCount); check(pMesh->GetVertexBuffers().GetElementSize(0) == sizeof(FOptimizedVertex)); const FOptimizedVertex* pVertices = reinterpret_cast(pMesh->GetVertexBuffers().GetBufferData(0)); float FadeEndCos = FMath::Cos(FadeEnd); for (int32 V = 0; V < VertexCount; ++V) { if (bUseCropping) { Scratch->Vertices[V].x = pVertices[V].Uv[0] * UncroppedSize[0] - CropMin[0]; Scratch->Vertices[V].y = pVertices[V].Uv[1] * UncroppedSize[1] - CropMin[1]; } else { Scratch->Vertices[V].x = pVertices[V].Uv[0] * SizeX; Scratch->Vertices[V].y = pVertices[V].Uv[1] * SizeY; } // TODO: No need to copy all. use scratch for the rest only. Scratch->Vertices[V].interpolators[0] = pVertices[V].Position[0]; Scratch->Vertices[V].interpolators[1] = pVertices[V].Position[1]; Scratch->Vertices[V].interpolators[2] = pVertices[V].Position[2]; Scratch->Vertices[V].interpolators[3] = pVertices[V].Normal[0]; Scratch->CulledVertex[V] = pVertices[V].Normal[0] < FadeEndCos; } // Get the indices check(pMesh->GetIndexBuffers().GetElementSize(0) == 4); const uint32* pIndices = reinterpret_cast(pMesh->GetIndexBuffers().GetBufferData(0)); // The mesh is supposed to contain only the faces in the selected block int32 FaceCount = pMesh->GetFaceCount(); //for (int32 Face = 0; Face < FaceCount; ++Face) ParallelFor(FaceCount, [pIndices, Scratch, SizeX, SizeY, PixelSize, pImage, PixelProc](int32 Face) { int32 Index0 = pIndices[Face*3 + 0]; int32 Index1 = pIndices[Face*3 + 1]; int32 Index2 = pIndices[Face*3 + 2]; // TODO: This optimisation may remove projection in the center of the face, if the angle // range is small. Make it optional or more sophisticated (cross product may help). if (!Scratch->CulledVertex[Index0] || !Scratch->CulledVertex[Index1] || !Scratch->CulledVertex[Index2]) { constexpr int32 NumInterpolators = 4; Triangle(pImage->GetLODData(0), pImage->GetDataSize(), SizeX, SizeY, PixelSize, Scratch->Vertices[Index0], Scratch->Vertices[Index1], Scratch->Vertices[Index2], PixelProc, false); } }); // Update the relevancy map // \TODO: fix it to work with cropping. if (!bUseCropping) { MUTABLE_CPUPROFILER_SCOPE(ImageRasterProjected_Optimised_Relevancy); float MinY = float(SizeY) - 1.0f; float MaxY = 0.0f; for (int32 F = 0; F < FaceCount; ++F) { int32 Index0 = pIndices[F*3 + 0]; int32 Index1 = pIndices[F*3 + 1]; int32 Index2 = pIndices[F*3 + 2]; // A bit ugly, probably can be improved if integrated in above loops and made more precise // inside the pixel processor to account for masked out pixels? if (!Scratch->CulledVertex[Index0] || !Scratch->CulledVertex[Index1] || !Scratch->CulledVertex[Index2]) { MinY = FMath::Min(MinY, Scratch->Vertices[Index0].y); MinY = FMath::Min(MinY, Scratch->Vertices[Index1].y); MinY = FMath::Min(MinY, Scratch->Vertices[Index2].y); MaxY = FMath::Max(MaxY, Scratch->Vertices[Index0].y); MaxY = FMath::Max(MaxY, Scratch->Vertices[Index1].y); MaxY = FMath::Max(MaxY, Scratch->Vertices[Index2].y); } } pImage->Flags |= FImage::IF_HAS_RELEVANCY_MAP; pImage->RelevancyMinY = uint16(FMath::FloorToFloat(MinY)); pImage->RelevancyMaxY = uint16(FMath::Min( int32(FMath::CeilToFloat(MaxY)), SizeY - 1)); } } //------------------------------------------------------------------------------------------------- FORCENOINLINE void ImageRasterProjected_OptimisedWrapping(const FMesh* pMesh, FImage* pImage, TTriangleRasterPixelProcRefType<4> PixelProc, float FadeEnd, uint64 BlockId, UE::Math::TIntVector2 CropMin, UE::Math::TIntVector2 UncroppedSize, FScratchImageProject* Scratch) { MUTABLE_CPUPROFILER_SCOPE(ImageRasterProjected_OptimisedWrapping); if (!pMesh || !pMesh->GetFaceCount()) { return; } EImageFormat Format = pImage->GetFormat(); int32 PixelSize = GetImageFormatData(Format).BytesPerBlock; int32 SizeX = pImage->GetSizeX(); int32 SizeY = pImage->GetSizeY(); // Get the vertices int32 VertexCount = pMesh->GetVertexCount(); check( Scratch->Vertices.Num() == VertexCount ); check( Scratch->CulledVertex.Num() == VertexCount ); check(pMesh->GetVertexBuffers().GetElementSize(0) == sizeof(FOptimizedVertex)); const FOptimizedVertex* pVertices = reinterpret_cast(pMesh->GetVertexBuffers().GetBufferData(0)); float FadeEndCos = FMath::Cos(FadeEnd); // Calculate the culled flag EMeshBufferFormat LayoutBlockType = pMesh->VertexBuffers.Buffers[1].Channels[0].Format; switch (LayoutBlockType) { case EMeshBufferFormat::UInt64: { const uint64* LayoutBlockIds = reinterpret_cast(pMesh->GetVertexBuffers().GetBufferData(1)); for (int32 V = 0; V < VertexCount; ++V) { // Don't cull vertices based on Normal for wrapping projectors. //Scratch->CulledVertex[V] = pVertices[V].Normal[0] < FadeEndCos; if (LayoutBlockIds[V] != BlockId) { Scratch->CulledVertex[V] = true; } } break; } case EMeshBufferFormat::UInt16: { const uint16* LayoutBlockIds = reinterpret_cast(pMesh->GetVertexBuffers().GetBufferData(1)); for (int32 V = 0; V < VertexCount; ++V) { //Scratch->CulledVertex[V] = pVertices[V].Normal[0] < FadeEndCos; if (LayoutBlockIds[V] != BlockId) { Scratch->CulledVertex[V] = true; } } break; } default: // Not implemented? check(false); break; } for (int32 V = 0; V < VertexCount; ++V) { bool bUseCropping = UncroppedSize[0] > 0; if (bUseCropping) { Scratch->Vertices[V].x = pVertices[V].Uv[0] * UncroppedSize[0] - CropMin[0]; Scratch->Vertices[V].y = pVertices[V].Uv[1] * UncroppedSize[1] - CropMin[1]; } else { Scratch->Vertices[V].x = pVertices[V].Uv[0] * SizeX; Scratch->Vertices[V].y = pVertices[V].Uv[1] * SizeY; } // TODO: No need to copy all. use scratch for the rest only. Scratch->Vertices[V].interpolators[0] = pVertices[V].Position[0]; Scratch->Vertices[V].interpolators[1] = pVertices[V].Position[1]; Scratch->Vertices[V].interpolators[2] = pVertices[V].Position[2]; Scratch->Vertices[V].interpolators[3] = pVertices[V].Normal[0]; } // Get the indices check(pMesh->GetIndexBuffers().GetElementSize(0) == 4); const uint32* pIndices = reinterpret_cast(pMesh->GetIndexBuffers().GetBufferData(0)); // The mesh is supposed to contain only the faces in the selected block int32 FaceCount = pMesh->GetFaceCount(); //for (int32 Face = 0; Face < FaceCount; ++Face) ParallelFor(FaceCount, [pIndices, Scratch, SizeX, SizeY, PixelSize, pImage, PixelProc](int32 Face) { int32 Index0 = pIndices[Face*3 + 0]; int32 Index1 = pIndices[Face*3 + 1]; int32 Index2 = pIndices[Face*3 + 2]; // TODO: This optimisation may remove projection in the center of the face, if the angle // range is small. Make it optional or more sophisticated (cross product may help). if (!Scratch->CulledVertex[Index0] || !Scratch->CulledVertex[Index1] || !Scratch->CulledVertex[Index2]) { constexpr int32 NumInterpolators = 4; Triangle(pImage->GetLODData(0), pImage->GetDataSize(), SizeX, SizeY, PixelSize, Scratch->Vertices[Index0], Scratch->Vertices[Index1], Scratch->Vertices[Index2], PixelProc, false); } }); } template FORCENOINLINE void InvokePlanarRasterizerImpl(const FImageRasterInvokeArgs& Args) { static_assert(EnumHasAnyFlags(Features, EPixelProcessorFeatures::ProjectionPlanar)); const Private::FProjectedPixelProcessorContext Context = Private::TProjectedPixelProcessor::MakeContext( Args.SourcePtr, Args.ImagePtr->GetLODData(0), Args.MaskPtr ? Args.MaskPtr->GetLODData(0) : nullptr, Args.bIsRGBFadingEnabled, Args.bIsAlphaFadingEnabled, Args.FadeStart, Args.FadeEnd, Args.ProjectionAngle, Args.MipInterpolationFactor); auto PixelProc = [&Context](uint8* Buffer, float Varying[4]) { Private::TProjectedPixelProcessor::ProcessPixel(Context, Buffer, Varying); }; ImageRasterProjected_Optimised(Args.MeshPtr, Args.ImagePtr, PixelProc, Args.FadeEnd, Args.CropMin, Args.UncroppedSize, Args.Scratch); } template FORCENOINLINE void InvokeCylindricalRasterizerImpl(const FImageRasterInvokeArgs& Args) { static_assert(EnumHasAnyFlags(Features, EPixelProcessorFeatures::ProjectionCylindrical)); const Private::FProjectedPixelProcessorContext Context = Private::TProjectedPixelProcessor::MakeContext( Args.SourcePtr, Args.ImagePtr->GetLODData(0), Args.MaskPtr ? Args.MaskPtr->GetLODData(0) : nullptr, Args.bIsRGBFadingEnabled, Args.bIsAlphaFadingEnabled, Args.FadeStart, Args.FadeEnd, Args.ProjectionAngle, Args.MipInterpolationFactor); auto PixelProc = [&Context](uint8* Buffer, float Varying[4]) { Private::TProjectedPixelProcessor::ProcessPixel(Context, Buffer, Varying); }; ImageRasterProjected_Optimised(Args.MeshPtr, Args.ImagePtr, PixelProc, Args.FadeEnd, Args.CropMin, Args.UncroppedSize, Args.Scratch); } template FORCENOINLINE void InvokeWrappingRasterizerImpl(const FImageRasterInvokeArgs& Args) { static_assert(EnumHasAnyFlags(Features, EPixelProcessorFeatures::ProjectionWrap)); const Private::FProjectedPixelProcessorContext Context = Private::TProjectedPixelProcessor::MakeContext( Args.SourcePtr, Args.ImagePtr->GetLODData(0), Args.MaskPtr ? Args.MaskPtr->GetLODData(0) : nullptr, Args.bIsRGBFadingEnabled, Args.bIsAlphaFadingEnabled, Args.FadeStart, Args.FadeEnd, Args.ProjectionAngle, Args.MipInterpolationFactor); auto PixelProc = [&Context](uint8* Buffer, float Varying[4]) { Private::TProjectedPixelProcessor::ProcessPixel(Context, Buffer, Varying); }; ImageRasterProjected_OptimisedWrapping(Args.MeshPtr, Args.ImagePtr, PixelProc, Args.FadeEnd, Args.BlockId, Args.CropMin, Args.UncroppedSize, Args.Scratch); } } // namespace Private //------------------------------------------------------------------------------------------------- void ImageRasterProjectedPlanar(const FMesh* pMesh, FImage* pImage, const FImage* pSource, const FImage* pMask, bool bIsRGBFadingEnabled, bool bIsAlphaFadingEnabled, ESamplingMethod SamplingMethod, float FadeStart, float FadeEnd, float MipInterpolationFactor, int32 Layout, uint64 BlockId, UE::Math::TIntVector2 CropMin, UE::Math::TIntVector2 UncroppedSize, FScratchImageProject* Scratch, bool bUseVectorImplementation) { using namespace mu::Private; check(!pMask || pMask->GetSizeX() == pImage->GetSizeX()); check(!pMask || pMask->GetSizeY() == pImage->GetSizeY()); check(!pMask || pMask->GetFormat()==EImageFormat::L_UByte); MUTABLE_CPUPROFILER_SCOPE(ImageProject); EPixelProcessorFeatures ProcessorFeatures = EPixelProcessorFeatures::ProjectionPlanar; // Disable vectorized implementation if Point sampling. if (bUseVectorImplementation && SamplingMethod != ESamplingMethod::Point) { ProcessorFeatures |= EPixelProcessorFeatures::VectorizedImpl; } if (pSource->GetFormat() == EImageFormat::L_UByte) { ProcessorFeatures |= EPixelProcessorFeatures::FormatL; } else if (pSource->GetFormat() == EImageFormat::RGB_UByte) { ProcessorFeatures |= EPixelProcessorFeatures::FormatRGB; } else if (pSource->GetFormat() == EImageFormat::RGBA_UByte || pSource->GetFormat() == EImageFormat::BGRA_UByte) { ProcessorFeatures |= EPixelProcessorFeatures::FormatRGBA; } if (SamplingMethod == ESamplingMethod::Point) { ProcessorFeatures |= EPixelProcessorFeatures::SamplingPoint; } else if (SamplingMethod == ESamplingMethod::BiLinear) { ProcessorFeatures |= EPixelProcessorFeatures::SamplingLinear; } if (pMask) { ProcessorFeatures |= EPixelProcessorFeatures::WithMask; } if (EnumHasAnyFlags(pMesh->Flags, EMeshFlags::ProjectFormat)) { float UnusedProjectionAngle = 0; FImageRasterInvokeArgs RasterArgs = { pMesh, pImage, pSource, pMask, bIsRGBFadingEnabled, bIsAlphaFadingEnabled, SamplingMethod, FadeStart, FadeEnd, UnusedProjectionAngle, MipInterpolationFactor, Layout, BlockId, CropMin, UncroppedSize, Scratch }; using EPPF = EPixelProcessorFeatures; // This contains all the permutations, this is way more than what it was supported before // but maybe not needed. Mask permutations can be removed safely switch (ProcessorFeatures) { case (EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingPoint): { InvokePlanarRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingPoint): { InvokePlanarRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingPoint): { InvokePlanarRasterizerImpl(RasterArgs); break; } case (EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingLinear): { InvokePlanarRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingLinear): { InvokePlanarRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingLinear): { InvokePlanarRasterizerImpl(RasterArgs); break; } case (EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::WithMask): { InvokePlanarRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::WithMask): { InvokePlanarRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::WithMask): { InvokePlanarRasterizerImpl(RasterArgs); break; } case (EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::WithMask): { InvokePlanarRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::WithMask): { InvokePlanarRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::WithMask): { InvokePlanarRasterizerImpl(RasterArgs); break; } //case (EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::VectorizedImpl): //{ // InvokePlanarRasterizerImpl(RasterArgs); // break; //} //case (EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::VectorizedImpl): //{ // InvokePlanarRasterizerImpl(RasterArgs); // break; //} //case (EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::VectorizedImpl): //{ // InvokePlanarRasterizerImpl(RasterArgs); // break; //} case (EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::VectorizedImpl): { InvokePlanarRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::VectorizedImpl): { InvokePlanarRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::VectorizedImpl): { InvokePlanarRasterizerImpl(RasterArgs); break; } //case (EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl): //{ // InvokePlanarRasterizerImpl(RasterArgs); // break; //} //case (EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl): //{ // InvokePlanarRasterizerImpl(RasterArgs); // break; //} //case (EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl): //{ // InvokePlanarRasterizerImpl(RasterArgs); // break; //} case (EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl): { InvokePlanarRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl): { InvokePlanarRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl): { InvokePlanarRasterizerImpl(RasterArgs); break; } default: { checkf(false, TEXT("Planar raster pixel processor permutation not implemented.")); } }; } else { check(false); } } //------------------------------------------------------------------------------------------------- void ImageRasterProjectedWrapping( const FMesh* pMesh, FImage* pImage, const FImage* pSource, const FImage* pMask, bool bIsRGBFadingEnabled, bool bIsAlphaFadingEnabled, ESamplingMethod SamplingMethod, float FadeStart, float FadeEnd, float MipInterpolationFactor, int32 Layout, uint64 BlockId, UE::Math::TIntVector2 CropMin, UE::Math::TIntVector2 UncroppedSize, FScratchImageProject* Scratch, bool bUseVectorImplementation) { using namespace mu::Private; check(!pMask || pMask->GetSizeX() == pImage->GetSizeX()); check(!pMask || pMask->GetSizeY() == pImage->GetSizeY()); check(!pMask || pMask->GetFormat()==EImageFormat::L_UByte); MUTABLE_CPUPROFILER_SCOPE(ImageProjectWrapping); EPixelProcessorFeatures ProcessorFeatures = EPixelProcessorFeatures::ProjectionWrap; if (bUseVectorImplementation && SamplingMethod != ESamplingMethod::Point) { ProcessorFeatures |= EPixelProcessorFeatures::VectorizedImpl; } if (pSource->GetFormat() == EImageFormat::L_UByte) { ProcessorFeatures |= EPixelProcessorFeatures::FormatL; } else if (pSource->GetFormat() == EImageFormat::RGB_UByte) { ProcessorFeatures |= EPixelProcessorFeatures::FormatRGB; } else if (pSource->GetFormat() == EImageFormat::RGBA_UByte || pSource->GetFormat() == EImageFormat::BGRA_UByte) { ProcessorFeatures |= EPixelProcessorFeatures::FormatRGBA; } if (SamplingMethod == ESamplingMethod::Point) { ProcessorFeatures |= EPixelProcessorFeatures::SamplingPoint; } else if (SamplingMethod == ESamplingMethod::BiLinear) { ProcessorFeatures |= EPixelProcessorFeatures::SamplingLinear; } if (pMask) { ProcessorFeatures |= EPixelProcessorFeatures::WithMask; } if (EnumHasAnyFlags(pMesh->Flags, EMeshFlags::ProjectWrappingFormat)) { float UnusedProjectionAngle = 0; FImageRasterInvokeArgs RasterArgs = { pMesh, pImage, pSource, pMask, bIsRGBFadingEnabled, bIsAlphaFadingEnabled, SamplingMethod, FadeStart, FadeEnd, UnusedProjectionAngle, MipInterpolationFactor, Layout, BlockId, CropMin, UncroppedSize, Scratch }; using EPPF = EPixelProcessorFeatures; // This contains all the permutations, this is way more than what it was supported before // but maybe not needed. Mask permutations can be removed safely switch (ProcessorFeatures) { case (EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingPoint): { InvokeWrappingRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingPoint): { InvokeWrappingRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingPoint): { InvokeWrappingRasterizerImpl(RasterArgs); break; } case (EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingLinear): { InvokeWrappingRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingLinear): { InvokeWrappingRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingLinear): { InvokeWrappingRasterizerImpl(RasterArgs); break; } case (EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::WithMask): { InvokeWrappingRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::WithMask): { InvokeWrappingRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::WithMask): { InvokeWrappingRasterizerImpl(RasterArgs); break; } case (EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::WithMask): { InvokeWrappingRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::WithMask): { InvokeWrappingRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::WithMask): { InvokeWrappingRasterizerImpl(RasterArgs); break; } //case (EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::VectorizedImpl): //{ // InvokeWrappingRasterizerImpl(RasterArgs); // break; //} //case (EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::VectorizedImpl): //{ // InvokeWrappingRasterizerImpl(RasterArgs); // break; //} //case (EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::VectorizedImpl): //{ // InvokeWrappingRasterizerImpl(RasterArgs); // break; //} case (EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::VectorizedImpl): { InvokeWrappingRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::VectorizedImpl): { InvokeWrappingRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::VectorizedImpl): { InvokeWrappingRasterizerImpl(RasterArgs); break; } //case (EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl): //{ // InvokeWrappingRasterizerImpl(RasterArgs); // break; //} //case (EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl): //{ // InvokeWrappingRasterizerImpl(RasterArgs); // break; //} //case (EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl): //{ // InvokeWrappingRasterizerImpl(RasterArgs); // break; //} case (EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl): { InvokeWrappingRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl): { InvokeWrappingRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl): { InvokeWrappingRasterizerImpl(RasterArgs); break; } default: { checkf(false, TEXT("Wrapping projection raster pixel processor permutation not implemented.")); } }; } else { check(false); } } //------------------------------------------------------------------------------------------------- void ImageRasterProjectedCylindrical( const FMesh* pMesh, FImage* pImage, const FImage* pSource, const FImage* pMask, bool bIsRGBFadingEnabled, bool bIsAlphaFadingEnabled, ESamplingMethod SamplingMethod, float FadeStart, float FadeEnd, float MipInterpolationFactor, int32 /*layout*/, float ProjectionAngle, UE::Math::TIntVector2 CropMin, UE::Math::TIntVector2 UncroppedSize, FScratchImageProject* Scratch, bool bUseVectorImplementation) { using namespace mu::Private; check(!pMask || pMask->GetSizeX() == pImage->GetSizeX()); check(!pMask || pMask->GetSizeY() == pImage->GetSizeY()); check(!pMask || pMask->GetFormat()==EImageFormat::L_UByte); MUTABLE_CPUPROFILER_SCOPE(ImageProjectCylindrical); EPixelProcessorFeatures ProcessorFeatures = EPixelProcessorFeatures::ProjectionCylindrical; if (bUseVectorImplementation && SamplingMethod != ESamplingMethod::Point) { ProcessorFeatures |= EPixelProcessorFeatures::VectorizedImpl; } if (pSource->GetFormat() == EImageFormat::L_UByte) { ProcessorFeatures |= EPixelProcessorFeatures::FormatL; } else if (pSource->GetFormat() == EImageFormat::RGB_UByte) { ProcessorFeatures |= EPixelProcessorFeatures::FormatRGB; } else if (pSource->GetFormat() == EImageFormat::RGBA_UByte || pSource->GetFormat() == EImageFormat::BGRA_UByte) { ProcessorFeatures |= EPixelProcessorFeatures::FormatRGBA; } if (SamplingMethod == ESamplingMethod::Point) { ProcessorFeatures |= EPixelProcessorFeatures::SamplingPoint; } else if (SamplingMethod == ESamplingMethod::BiLinear) { ProcessorFeatures |= EPixelProcessorFeatures::SamplingLinear; } if (pMask) { ProcessorFeatures |= EPixelProcessorFeatures::WithMask; } if (EnumHasAnyFlags(pMesh->Flags, EMeshFlags::ProjectFormat)) { int32 UnusedLayout = 0; int32 UnusedBlock = 0; FImageRasterInvokeArgs RasterArgs = { pMesh, pImage, pSource, pMask, bIsRGBFadingEnabled, bIsAlphaFadingEnabled, SamplingMethod, FadeStart, FadeEnd, ProjectionAngle, MipInterpolationFactor, UnusedLayout, UnusedBlock, CropMin, UncroppedSize, Scratch }; using EPPF = EPixelProcessorFeatures; // This contains all the permutations, this is way more than what it was supported before // but maybe not needed. Mask permutations can be removed safely switch (ProcessorFeatures) { case (EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingPoint): { InvokeCylindricalRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingPoint): { InvokeCylindricalRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingPoint): { InvokeCylindricalRasterizerImpl(RasterArgs); break; } case (EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingLinear): { InvokeCylindricalRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingLinear): { InvokeCylindricalRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingLinear): { InvokeCylindricalRasterizerImpl(RasterArgs); break; } case (EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::WithMask): { InvokeCylindricalRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::WithMask): { InvokeCylindricalRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::WithMask): { InvokeCylindricalRasterizerImpl(RasterArgs); break; } case (EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::WithMask): { InvokeCylindricalRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::WithMask): { InvokeCylindricalRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::WithMask): { InvokeCylindricalRasterizerImpl(RasterArgs); break; } //case (EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::VectorizedImpl): //{ // InvokeCylindricalRasterizerImpl(RasterArgs); // break; //} //case (EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::VectorizedImpl): //{ // InvokeCylindricalRasterizerImpl(RasterArgs); // break; //} //case (EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::VectorizedImpl): //{ // InvokeCylindricalRasterizerImpl(RasterArgs); // break; //} case (EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::VectorizedImpl): { InvokeCylindricalRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::VectorizedImpl): { InvokeCylindricalRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::VectorizedImpl): { InvokeCylindricalRasterizerImpl(RasterArgs); break; } //case (EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl): //{ // InvokeCylindricalRasterizerImpl(RasterArgs); // break; //} //case (EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl): //{ // InvokeCylindricalRasterizerImpl(RasterArgs); // break; //} //case (EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl): //{ // InvokeCylindricalRasterizerImpl(RasterArgs); // break; //} case (EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl): { InvokeCylindricalRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl): { InvokeCylindricalRasterizerImpl(RasterArgs); break; } case (EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl): { InvokeCylindricalRasterizerImpl(RasterArgs); break; } default: { checkf(false, TEXT("Cylindrical projection raster pixel processor permutation not implemented.")); } } } else { check(false); } } float ComputeProjectedFootprintBestMip( const FMesh* pMesh, const FProjector& Projector, const FVector2f& TargetSize, const FVector2f& SourceSize) { // Compute projected mesh footprint on the traget image and extract a optimal mip for source image. const bool bIsPlanarProjection = Projector.type == EProjectorType::Planar; const bool bIsCylindicalProjection = Projector.type == EProjectorType::Cylindrical; const bool bIsWrappingProjection = Projector.type == EProjectorType::Wrapping; check(pMesh->GetIndexBuffers().GetElementSize(0) == sizeof(uint32)); const uint32* IndicesPtr = reinterpret_cast(pMesh->GetIndexBuffers().GetBufferData(0)); const uint32 NumIndices = pMesh->GetIndexBuffers().GetElementCount(); // Source area is the fraction of source image that is covered by the projected mesh. float SourceArea = UE_KINDA_SMALL_NUMBER; // Target area is the fraction of the target image that is covered by the projected mesh. float TargetArea = UE_KINDA_SMALL_NUMBER; auto ComputeTriangleArea = [](const FVector2f& A, const FVector2f& B, const FVector2f& C) -> float { return (A.X * (B.Y - C.Y) + B.X * (C.Y - A.Y) + C.X * (A.Y - B.Y)) * 0.5f; }; check(NumIndices % 3 == 0); // TODO: evaluate other strategies to get a better mip estimate. Maybe use the median of area change? if (bIsPlanarProjection) { check(pMesh->GetVertexBuffers().GetElementSize(0) == sizeof(FOptimizedVertex)); const FOptimizedVertex* VerticesPtr = reinterpret_cast(pMesh->GetVertexBuffers().GetBufferData(0)); check(NumIndices % 3 == 0); for (uint32 I = 0; I < NumIndices; I += 3) { const FOptimizedVertex& A = VerticesPtr[IndicesPtr[I + 0]]; const FOptimizedVertex& B = VerticesPtr[IndicesPtr[I + 1]]; const FOptimizedVertex& C = VerticesPtr[IndicesPtr[I + 2]]; const float TriangleSourceArea = ComputeTriangleArea( FVector2f(A.Position.X, A.Position.Y), FVector2f(B.Position.X, B.Position.Y), FVector2f(C.Position.X, C.Position.Y)); const float TriangleTargetArea = ComputeTriangleArea(A.Uv, B.Uv, C.Uv); // Set weight to zero if source or target area are close to zero to remove outliers. float TriangleWeight = static_cast(!FMath::IsNearlyZero(TriangleSourceArea)) * static_cast(!FMath::IsNearlyZero(TriangleTargetArea)); TriangleWeight *= FMath::Clamp((A.Normal.X + B.Normal.X + C.Normal.X) * (1.0f / 3.0f), 0.0f, 1.0f); SourceArea += TriangleSourceArea * TriangleWeight; TargetArea += TriangleTargetArea * TriangleWeight; } } else if (bIsCylindicalProjection) { check(pMesh->GetVertexBuffers().GetElementSize(0) == sizeof(FOptimizedVertex)); const FOptimizedVertex* VerticesPtr = reinterpret_cast(pMesh->GetVertexBuffers().GetBufferData(0)); check(NumIndices % 3 == 0); for (uint32 I = 0; I < NumIndices; I += 3) { const FOptimizedVertex& A = VerticesPtr[IndicesPtr[I + 0]]; const FOptimizedVertex& B = VerticesPtr[IndicesPtr[I + 1]]; const FOptimizedVertex& C = VerticesPtr[IndicesPtr[I + 2]]; const float ClampedProjectorAngleRad = FMath::Clamp(Projector.projectionAngle, 0.0f, UE_TWO_PI); const float OneOverProjectionAngleSafe = !FMath::IsNearlyZero(ClampedProjectorAngleRad) ? 1.0f / Projector.projectionAngle : 1.0f / KINDA_SMALL_NUMBER; const FVector2f PosA = FVector2f((FMath::Atan2(A.Position.Z, -A.Position.Y) + UE_PI) * OneOverProjectionAngleSafe, A.Position.X); const FVector2f PosB = FVector2f((FMath::Atan2(B.Position.Z, -B.Position.Y) + UE_PI) * OneOverProjectionAngleSafe, B.Position.X); const FVector2f PosC = FVector2f((FMath::Atan2(C.Position.Z, -C.Position.Y) + UE_PI) * OneOverProjectionAngleSafe, C.Position.X); const float TriangleSourceArea = FMath::Abs(ComputeTriangleArea(PosA, PosB, PosC)); const float TriangleTargetArea = FMath::Abs(ComputeTriangleArea(A.Uv, B.Uv, C.Uv)); // Set weight to zero if source or target area are close to zero to remove outliers. float TriangleWeight = static_cast(!FMath::IsNearlyZero(TriangleSourceArea)) * static_cast(!FMath::IsNearlyZero(TriangleTargetArea)); SourceArea += TriangleSourceArea * TriangleWeight; TargetArea += TriangleTargetArea * TriangleWeight; } } else if (bIsWrappingProjection) { check(pMesh->GetVertexBuffers().GetElementSize(0) == sizeof(FOptimizedVertex)); const FOptimizedVertex* VerticesPtr = reinterpret_cast(pMesh->GetVertexBuffers().GetBufferData(0)); check(NumIndices % 3 == 0); for (uint32 I = 0; I < NumIndices; I += 3) { const FOptimizedVertex& A = VerticesPtr[IndicesPtr[I + 0]]; const FOptimizedVertex& B = VerticesPtr[IndicesPtr[I + 1]]; const FOptimizedVertex& C = VerticesPtr[IndicesPtr[I + 2]]; TargetArea += ComputeTriangleArea(A.Uv, B.Uv, C.Uv); } SourceArea = TargetArea; } else { check(false); } const float SourceFootprintAreaOnTarget = TargetArea * (1.0f / SourceArea); const float SourceFootprintAreaOnTargetInPixelsSquared = SourceFootprintAreaOnTarget * (TargetSize.X * TargetSize.Y); // Find the mip that better adapts to the footprint. float BestMip = FMath::Log2(FMath::Max(1.0f, SourceSize.X * SourceSize.Y)) * 0.5f - FMath::Log2(FMath::Max(1.0f, SourceFootprintAreaOnTargetInPixelsSquared)) * 0.5f; return BestMip; } //#define DEBUG_PROJECTION 1 #ifdef DEBUG_PROJECTION UE_DISABLE_OPTIMIZATION #undef assert int32* assert_aux = 0; #define assert(x) if((x) == 0) assert_aux[0] = 1; #endif constexpr float vert_collapse_eps = 0.0001f; //--------------------------------------------------------------------------------------------- //! Create a map from vertices into vertices, collapsing vertices that have the same position //--------------------------------------------------------------------------------------------- inline void MeshCreateCollapsedVertexMap( const FMesh* pMesh, TArray& CollapsedVertices, TMultiMap& CollapsedVerticesMap, TArray& OutVertices) { MUTABLE_CPUPROFILER_SCOPE(CreateCollapseMap); const int32 NumVertices = pMesh->GetVertexCount(); // Used to speed up vertex comparison UE::Geometry::TPointHashGrid3f VertHash(0.01f, INDEX_NONE); VertHash.Reserve(NumVertices); // Info to collect. Vertices, collapsed vertices and unique vertices to nearby vertices map OutVertices.SetNumUninitialized(NumVertices); CollapsedVertices.Init(INDEX_NONE, NumVertices); CollapsedVerticesMap.Reserve(NumVertices); // Get Vertices mu::UntypedMeshBufferIteratorConst ItPosition = mu::UntypedMeshBufferIteratorConst(pMesh->GetVertexBuffers(), mu::EMeshBufferSemantic::Position); FVector3f* VertexData = OutVertices.GetData(); for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) { *VertexData = ItPosition.GetAsVec3f(); VertHash.InsertPointUnsafe(VertexIndex, *VertexData); ++ItPosition; ++VertexData; } // Find unique vertices TArray NearbyVertices; for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) { if (CollapsedVertices[VertexIndex] != INDEX_NONE) { continue; } const FVector3f& Vertex = OutVertices[VertexIndex]; NearbyVertices.Reset(); VertHash.FindPointsInBall(Vertex, vert_collapse_eps, [&Vertex, &OutVertices](const int32& Other) -> float {return FVector3f::DistSquared(OutVertices[Other], Vertex); }, NearbyVertices); for (int32 NearbyVertexIndex : NearbyVertices) { CollapsedVertices[NearbyVertexIndex] = VertexIndex; CollapsedVerticesMap.Add(VertexIndex, NearbyVertexIndex); } } } struct AdjacentFaces { int32 faces[3]; int32 newVertices[3]; bool changesUVIsland[3]; AdjacentFaces() { faces[0] = -1; faces[1] = -1; faces[2] = -1; newVertices[0] = -1; newVertices[1] = -1; newVertices[2] = -1; changesUVIsland[0] = false; changesUVIsland[1] = false; changesUVIsland[2] = false; } void addConnectedFace(int32 newConnectedFace, int32 newVertex, bool changesUVIslandParam) { #ifdef DEBUG_PROJECTION assert( newConnectedFace >= 0 ); #endif for (int32 i = 0; i < 3; ++i) { if (faces[i] == newConnectedFace) { return; } } for (int32 i = 0; i < 3; ++i) { if (faces[i] == -1) { faces[i] = newConnectedFace; newVertices[i] = newVertex; changesUVIsland[i] = changesUVIslandParam; break; } } } }; void PlanarlyProjectVertex(const FVector3f& unfoldedPosition, FVector4f& projectedPosition, const FProjector& projector, const FVector3f& projectorPosition, const FVector3f& projectorDirection, const FVector3f& s, const FVector3f& u) { float x = FVector3f::DotProduct( unfoldedPosition - projectorPosition, s ) / projector.scale[0] + 0.5f; float y = FVector3f::DotProduct( unfoldedPosition - projectorPosition, u ) / projector.scale[1] + 0.5f; y = 1.0f - y; float z = FVector3f::DotProduct( unfoldedPosition - projectorPosition, projectorDirection ) / projector.scale[2]; bool inside = x>=0.0f && x<=1.0f && y>=0.0f && y<=1.0 && z>=0.0f && z<=1.0f; projectedPosition[0] = x; projectedPosition[1] = y; projectedPosition[2] = z; projectedPosition[3] = inside ? 1.0f : 0.0f; } FVector2f ChangeBase2D(const FVector2f& origPosition, const FVector2f& newOrigin, const FVector2f& newBaseX, const FVector2f& newBaseY) { float x = FVector2f::DotProduct(origPosition - newOrigin, newBaseX) / newBaseX.SquaredLength() + 0.5f; float y = FVector2f::DotProduct(origPosition - newOrigin, newBaseY) / newBaseY.SquaredLength() + 0.5f; //y = 1.0f - y; return FVector2f(x, y); } // Compute barycentric coordinates (u, v, w) for // point p with respect to triangle (a, b, c) void GetBarycentricCoords(FVector3f p, FVector3f a, FVector3f b, FVector3f c, float &u, float &v, float &w) { FVector3f v0 = b - a, v1 = c - a, v2 = p - a; float d00 = FVector3f::DotProduct(v0, v0); float d01 = FVector3f::DotProduct(v0, v1); float d11 = FVector3f::DotProduct(v1, v1); float d20 = FVector3f::DotProduct(v2, v0); float d21 = FVector3f::DotProduct(v2, v1); float denom = d00 * d11 - d01 * d01; v = (d11 * d20 - d01 * d21) / denom; w = (d00 * d21 - d01 * d20) / denom; u = 1.0f - v - w; } void GetBarycentricCoords(FVector2f p, FVector2f a, FVector2f b, FVector2f c, float &u, float &v, float &w) { FVector2f v0 = b - a, v1 = c - a, v2 = p - a; float den = v0.X * v1.Y - v1.X * v0.Y; #ifdef DEBUG_PROJECTION assert(den != 0.f); #endif v = (v2.X * v1.Y - v1.X * v2.Y) / den; w = (v0.X * v2.Y - v2.X * v0.Y) / den; u = 1.0f - v - w; } float getTriangleArea(FVector2f& a, FVector2f& b, FVector2f& c) { float area = a.X * (b.Y - c.Y) + b.X * (c.Y - a.Y) + c.X * (a.Y - b.Y); return area / 2.f; } float getTriangleRatio(const FVector2f& a, const FVector2f& b, const FVector2f& c) { float lenSide1 = (b - a).Length(); float lenSide2 = (c - a).Length(); float lenSide3 = (b - c).Length(); float maxLen = FMath::Max3(lenSide1, lenSide2, lenSide3); float minLen = FMath::Min3(lenSide1, lenSide2, lenSide3); return maxLen / minLen; } struct NeighborFace { int32 neighborFace; int32 newVertex; int32 previousFace; int32 numUVIslandChanges = 0; int32 step; bool changesUVIsland; friend bool operator <(const NeighborFace& a, const NeighborFace& b) { //if (a.numUVIslandChanges == b.numUVIslandChanges) //{ return a.step < b.step; //} //return a.numUVIslandChanges > b.numUVIslandChanges; } }; void getEdgeHorizontalLength( int32 edgeVert0, int32 edgeVert1, int32 opposedVert, const FOptimizedVertex* pVertices, float &out_uvSpaceLen, float &out_objSpaceLen, float& out_midEdgePointFraction) { const int32 oldVertices[2] = { edgeVert0, edgeVert1 }; const FVector2f& edge0_oldUVSpace = pVertices[oldVertices[0]].Uv; const FVector2f& edge1_oldUVSpace = pVertices[oldVertices[1]].Uv; const FVector2f& opposedVert_oldUVSpace = pVertices[opposedVert].Uv; FVector2f edgeVector_oldUVSpace = edge1_oldUVSpace - edge0_oldUVSpace; FVector2f sideVector_oldUVSpace = opposedVert_oldUVSpace - edge0_oldUVSpace; float edgeVector_oldUVSpaceLen = edgeVector_oldUVSpace.Length(); float dotEdgeSideVectors_oldUVSpace = FVector2f::DotProduct(edgeVector_oldUVSpace, sideVector_oldUVSpace); float midEdgePointFraction_oldUVSpace = (dotEdgeSideVectors_oldUVSpace / powf(edgeVector_oldUVSpaceLen, 2)); FVector2f edgeVectorProj_oldUVSpace = edgeVector_oldUVSpace * midEdgePointFraction_oldUVSpace; FVector2f midEdgePoint_oldUVSpace = edge0_oldUVSpace + edgeVectorProj_oldUVSpace; FVector2f perpEdgeVector_oldUVSpace = opposedVert_oldUVSpace - midEdgePoint_oldUVSpace; #ifdef DEBUG_PROJECTION assert(fabs(dot(perpEdgeVector_oldUVSpace, edgeVector_oldUVSpace)) < 0.1f); #endif float perpEdgeVector_oldUVSpace_Len = perpEdgeVector_oldUVSpace.Length(); out_uvSpaceLen = perpEdgeVector_oldUVSpace_Len; out_midEdgePointFraction = midEdgePointFraction_oldUVSpace; // Do the same in object space to be able to extract the scale of the original uv space const FVector3f& edge0_objSpace = pVertices[oldVertices[0]].Position; const FVector3f& edge1_objSpace = pVertices[oldVertices[1]].Position; const FVector3f& opposedVert_objSpace = pVertices[opposedVert].Position; // FIXME FVector3f edgeVector_objSpace = edge1_objSpace - edge0_objSpace; FVector3f sideVector_objSpace = opposedVert_objSpace - edge0_objSpace; float edgeVector_objSpaceLen = edgeVector_objSpace.Length(); float dotEdgeSideVectors_objSpace = FVector3f::DotProduct(edgeVector_objSpace, sideVector_objSpace); float midEdgePointFraction_objSpace = (dotEdgeSideVectors_objSpace / powf(edgeVector_objSpaceLen, 2)); FVector3f edgeVectorProj_objSpace = edgeVector_objSpace * midEdgePointFraction_objSpace; FVector3f midEdgePoint_objSpace = edge0_objSpace + edgeVectorProj_objSpace; FVector3f perpEdgeVector_objSpace = opposedVert_objSpace - midEdgePoint_objSpace; #ifdef DEBUG_PROJECTION assert(fabs(dot(perpEdgeVector_objSpace, edgeVector_objSpace)) < 0.1f); #endif float perpEdgeVector_objSpace_Len = perpEdgeVector_objSpace.Length(); out_objSpaceLen = perpEdgeVector_objSpace_Len; } bool testPointsAreInOppositeSidesOfEdge( const FVector2f& pointA, const FVector2f& pointB, const FVector2f& edge0, const FVector2f& edge1 ) { FVector2f edge = edge1 - edge0; FVector2f perp_edge = FVector2f(-edge.Y, edge.X); FVector2f AVertexVector = pointA - edge0; FVector2f BVertexVector = pointB - edge0; float dotAVertexVector = FVector2f::DotProduct(AVertexVector, perp_edge); float dotBVertexVector = FVector2f::DotProduct(BVertexVector, perp_edge); return dotAVertexVector * dotBVertexVector < 0.f; } //------------------------------------------------------------------------------------------------- #pragma pack(push,1) struct PROJECTED_VERTEX { float pos0 = 0, pos1 = 0, pos2 = 0; uint32 mask3 = 0; inline FVector2f xy() const { return FVector2f(pos0,pos1); } }; #pragma pack(pop) static_assert( sizeof(PROJECTED_VERTEX)==16, "Unexpected struct size" ); //------------------------------------------------------------------------------------------------- void MeshProject_Optimised_Planar(const FOptimizedVertex* pVertices, int32 vertexCount, const uint32* pIndices, int32 faceCount, const FVector3f& projectorPosition, const FVector3f& projectorDirection, const FVector3f& projectorSide, const FVector3f& projectorUp, const FVector3f& projectorScale, FOptimizedVertex* pResultVertices, int32& currentVertex, uint32* pResultIndices, int32& currentIndex) { MUTABLE_CPUPROFILER_SCOPE(MeshProject_Optimised_Planar) TArray oldToNewVertex; oldToNewVertex.Init(-1,vertexCount); TArray projectedPositions; projectedPositions.SetNumZeroed(vertexCount); for ( int32 v=0; v1.0f)<<1) | ((y<0.0f)<<2) | ((y>1.0f)<<3) | ((z<0.0f)<<4) | ((z>1.0f)<<5); projectedPositions[v].pos0 = x; projectedPositions[v].pos1 = y; projectedPositions[v].pos2 = z; projectedPositions[v].mask3 = planeMask; } // Iterate the faces for ( int32 f=0; f oldToNewVertex; oldToNewVertex.Init( -1, vertexCount); TArray projectedPositions; projectedPositions.SetNumZeroed(vertexCount); // TODO: support for non uniform scale? float radius = projectorScale[1]; float height = projectorScale[0]; FMatrix44f WorldToCylinder(projectorDirection, projectorSide, projectorUp,FVector3f(0,0,0)); for ( int32 v=0; v1.0f)<<1) | ((r2>=(radius*radius))<<2); projectedPositions[v].mask3 = planeMask; } // Iterate the faces for ( int32 f=0; fGetVertexCount(); check(pMesh->GetVertexBuffers().GetElementSize(0) == sizeof(FOptimizedVertex)); check(pMesh->GetVertexBuffers().Buffers.Num() == 2); const FOptimizedVertex* pVertices = reinterpret_cast(pMesh->GetVertexBuffers().GetBufferData(0)); EMeshBufferFormat LayoutBlockType = pMesh->VertexBuffers.Buffers[1].Channels[0].Format; // Get the indices check(pMesh->GetIndexBuffers().GetElementSize(0) == 4); const uint32* pIndices = reinterpret_cast(pMesh->GetIndexBuffers().GetBufferData(0)); int32 faceCount = pMesh->GetFaceCount(); TArray projectedPositions; projectedPositions.SetNum(vertexCount); // Iterate the faces and trace a ray to find the origin face of the projection const float maxDist = 100000.f; // TODO: This should be proportional to the mesh bounding box size. float min_t = maxDist; float rayLength = maxDist; int32 intersectedFace = -1; TSet processedVertices; FVector3f projectionPlaneNormal; FVector3f out_intersection; TArray faceConnectivity; faceConnectivity.SetNum(faceCount); TSet processedFaces; processedFaces.Reserve(faceCount/4); TSet discardedWrapAroundFaces; discardedWrapAroundFaces.Reserve(faceCount/16); TArray faceStep; faceStep.SetNum(faceCount); // Map vertices to the one they are collapsed to because they are very similar, if they aren't collapsed then they are mapped to themselves TArray collapsedVertexMap; TArray vertices; TMultiMap collapsedVertsMap; // Maps a collapsed vertex to all the vertices that collapse to it MeshCreateCollapsedVertexMap(pMesh, collapsedVertexMap, collapsedVertsMap, vertices); // Used to speed up connectivity building TMultiMap vertToFacesMap; vertToFacesMap.Reserve(vertexCount); for (int32 f = 0; f < faceCount; ++f) { int32 i0 = pIndices[f * 3 + 0]; int32 i1 = pIndices[f * 3 + 1]; int32 i2 = pIndices[f * 3 + 2]; vertToFacesMap.Add(collapsedVertexMap[i0], f); vertToFacesMap.Add(collapsedVertexMap[i1], f); vertToFacesMap.Add(collapsedVertexMap[i2], f); } FRay3f Ray(projectorPosition, projectorDirection, false); UE::Geometry::FIntrRay3Triangle3f Intersector(Ray, UE::Geometry::FTriangle3f()); // Trace a ray in the projection direction to find the face that will be projected planarly and be the root of the unfolding // Also build face connectivity information for (int32 f = 0; f < faceCount; ++f) { int32 i0 = pIndices[f * 3 + 0]; int32 i1 = pIndices[f * 3 + 1]; int32 i2 = pIndices[f * 3 + 2]; FVector3f rayStart = projectorPosition; FVector3f rayEnd = projectorPosition + projectorDirection * maxDist; Intersector.Triangle = UE::Geometry::FTriangle3f(pVertices[i0].Position, pVertices[i1].Position, pVertices[i2].Position); rayLength = (rayEnd - rayStart).Length(); bool bIntersects = Intersector.Find(); float t = Intersector.RayParameter; FVector3f aux_out_intersection = Ray.PointAt(Intersector.RayParameter); if (bIntersects && t < min_t) { intersectedFace = f; min_t = t; out_intersection = aux_out_intersection; FVector3f v0 = pVertices[i0].Position; FVector3f v1 = pVertices[i1].Position; FVector3f v2 = pVertices[i2].Position; projectionPlaneNormal = FVector3f::CrossProduct(v1 - v0, v2 - v0).GetSafeNormal(); } // Face connectivity info for (int32 i = 0; i < 3; ++i) { int32 v = collapsedVertexMap[pIndices[f * 3 + i]]; //int32 v = pIndices[f * 3 + i]; TArray> FoundValues; vertToFacesMap.MultiFind(v, FoundValues); for (int32 f2 : FoundValues ) { if (f != f2 && f2 >= 0) { int32 commonVertices = 0; int32 commonVerticesDifferentIsland = 0; bool commonMask[3] = { false, false, false }; for (int32 ii = 0; ii < 3; ++ii) { int32 f_vertex_orig = pIndices[f * 3 + ii]; int32 f_vertex = collapsedVertexMap[f_vertex_orig]; //int32 f_vertex = pIndices[f * 3 + ii]; for (int32 j = 0; j < 3; ++j) { int32 f2_vertex_orig = pIndices[f2 * 3 + j]; int32 f2_vertex = collapsedVertexMap[f2_vertex_orig]; //int32 f2_vertex = pIndices[f2 * 3 + j]; if (f_vertex != -1 && f_vertex == f2_vertex) { commonVertices++; commonMask[j] = true; if (f_vertex_orig != f2_vertex_orig) { commonVerticesDifferentIsland++; } } } } #ifdef DEBUG_PROJECTION assert(commonVerticesDifferentIsland <= commonVertices); #endif if (commonVertices == 2) { int32 newVertex = -1; for (int32 j = 0; j < 3; ++j) { if (!commonMask[j]) { newVertex = collapsedVertexMap[pIndices[f2 * 3 + j]]; //newVertex = pIndices[f2 * 3 + j]; break; } } #ifdef DEBUG_PROJECTION assert(newVertex >= 0); #endif faceConnectivity[f].addConnectedFace(f2, newVertex, commonVerticesDifferentIsland == 2); } } } } } // New projector located perpendicularly up from the hit face FProjector projector2; projector2.direction[0] = projectionPlaneNormal[0]; projector2.direction[1] = projectionPlaneNormal[1]; projector2.direction[2] = projectionPlaneNormal[2]; FVector3f auxPosition = out_intersection - projectionPlaneNormal * rayLength * min_t; //FVector3f auxPosition = FVector3f( projector.position[0], projector.position[1], projector.position[2] ); projector2.position[0] = auxPosition[0]; projector2.position[1] = auxPosition[1]; projector2.position[2] = auxPosition[2]; FVector3f auxUp = projectorUp; float test = fabs(FVector3f::DotProduct(auxUp, projectionPlaneNormal)); if (test < 0.9f) { FVector3f auxSide = FVector3f::CrossProduct(projectionPlaneNormal, auxUp).GetSafeNormal(); auxUp = FVector3f::CrossProduct(auxSide, projectionPlaneNormal); } else { auxUp = FVector3f::CrossProduct(projectionPlaneNormal, projectorSide); } projector2.up[0] = auxUp[0]; projector2.up[1] = auxUp[1]; projector2.up[2] = auxUp[2]; projector2.scale[0] = projectorScale[0]; projector2.scale[1] = projectorScale[1]; projector2.scale[2] = projectorScale[2]; FVector3f projectorDirection2, s2, u2; FVector3f projectorPosition2 = projector2.position; projector2.GetDirectionSideUp( projectorDirection2, s2, u2 ); float maxDistSquared = powf(projector2.scale[0], 2.f) + powf(projector2.scale[1], 2.f); // Do a BFS walk of the mesh, unfolding a face at each step if (intersectedFace >= 0) { TArray pendingFaces; // Queue of pending face + new vertex pendingFaces.Reserve(faceCount/64); // \TODO: Bool array to speed up? TSet pendingFacesUnique; // Used to quickly check uniqueness in the pendingFaces queue pendingFacesUnique.Reserve(faceCount / 64); //FVector3f hitFaceProjectedNormal; bool hitFaceHasPositiveArea = false; NeighborFace neighborFace; neighborFace.neighborFace = intersectedFace; neighborFace.newVertex = -1; // -1 because all the vertices of the face are new neighborFace.previousFace = -1; neighborFace.numUVIslandChanges = 0; neighborFace.step = 0; neighborFace.changesUVIsland = false; pendingFaces.HeapPush(neighborFace); pendingFacesUnique.Add(intersectedFace); int32 step = 0; float totalUVAreaCovered = 0.f; TArray oldVertices; oldVertices.Reserve(3); while (!pendingFaces.IsEmpty()) { NeighborFace currentFaceStruct; pendingFaces.HeapPop(currentFaceStruct); int32 currentFace = currentFaceStruct.neighborFace; #ifdef DEBUG_PROJECTION int32 newVertexFromQueue = currentFaceStruct.newVertex; #endif pendingFacesUnique.Remove(currentFace); processedFaces.Add(currentFace); faceStep[currentFace] = step; float currentTriangleArea = -2.f; // Process the face's vertices if (currentFace == intersectedFace) { #ifdef DEBUG_PROJECTION assert(newVertexFromQueue == -1); #endif FVector3f outIntersectionBaricentric; int32 i0 = pIndices[currentFace * 3 + 0]; int32 i1 = pIndices[currentFace * 3 + 1]; int32 i2 = pIndices[currentFace * 3 + 2]; FVector3f v3_0 = pVertices[i0].Position; FVector3f v3_1 = pVertices[i1].Position; FVector3f v3_2 = pVertices[i2].Position; GetBarycentricCoords(out_intersection, v3_0, v3_1, v3_2, outIntersectionBaricentric[0], outIntersectionBaricentric[1], outIntersectionBaricentric[2]); FVector2f outIntersectionUV = pVertices[i0].Uv * outIntersectionBaricentric[0] + pVertices[i1].Uv * outIntersectionBaricentric[1] + pVertices[i2].Uv * outIntersectionBaricentric[2]; FVector4f proj4D_v0, proj4D_v1, proj4D_v2; PlanarlyProjectVertex(pVertices[i0].Position, proj4D_v0, projector2, projectorPosition2, projectorDirection2, s2, u2); PlanarlyProjectVertex(pVertices[i1].Position, proj4D_v1, projector2, projectorPosition2, projectorDirection2, s2, u2); PlanarlyProjectVertex(pVertices[i2].Position, proj4D_v2, projector2, projectorPosition2, projectorDirection2, s2, u2); FVector2f proj_v0 = FVector2f(proj4D_v0[0], proj4D_v0[1]); FVector2f proj_v1 = FVector2f(proj4D_v1[0], proj4D_v1[1]); FVector2f proj_v2 = FVector2f(proj4D_v2[0], proj4D_v2[1]); float proj_TriangleArea = getTriangleArea(proj_v0, proj_v1, proj_v2); FVector3f BaricentricBaseU; FVector3f BaricentricBaseV; FVector3f BaricentricOrigin; GetBarycentricCoords(FVector2f(1.f, 0.f), proj_v0, proj_v1, proj_v2, BaricentricBaseU[0], BaricentricBaseU[1], BaricentricBaseU[2]); GetBarycentricCoords(FVector2f(0.f, 1.f), proj_v0, proj_v1, proj_v2, BaricentricBaseV[0], BaricentricBaseV[1], BaricentricBaseV[2]); GetBarycentricCoords(FVector2f(0.f, 0.f), proj_v0, proj_v1, proj_v2, BaricentricOrigin[0], BaricentricOrigin[1], BaricentricOrigin[2]); FVector2f OrigUVs_BaseU_EndPoint = pVertices[i0].Uv * BaricentricBaseU[0] + pVertices[i1].Uv * BaricentricBaseU[1] + pVertices[i2].Uv * BaricentricBaseU[2]; FVector2f OrigUVs_BaseV_EndPoint = pVertices[i0].Uv * BaricentricBaseV[0] + pVertices[i1].Uv * BaricentricBaseV[1] + pVertices[i2].Uv * BaricentricBaseV[2]; FVector2f OrigUVs_Origin = pVertices[i0].Uv * BaricentricOrigin[0] + pVertices[i1].Uv * BaricentricOrigin[1] + pVertices[i2].Uv * BaricentricOrigin[2]; FVector2f OrigUVs_BaseU = OrigUVs_BaseU_EndPoint - OrigUVs_Origin; FVector2f OrigUVs_BaseV_Aux = OrigUVs_BaseV_EndPoint - OrigUVs_Origin; OrigUVs_BaseU = OrigUVs_BaseU.GetSafeNormal(); OrigUVs_BaseV_Aux = OrigUVs_BaseV_Aux.GetSafeNormal(); // Generate perp vector from OrigUVs_BaseU by flipping coords and one sign. Using OrigUVs_BaseV directly sometimes doesn't give an orthogonal basis FVector2f OrigUVs_BaseV = FVector2f(copysign(OrigUVs_BaseU.Y, OrigUVs_BaseV_Aux.X), copysign(OrigUVs_BaseU.X, OrigUVs_BaseV_Aux.Y)); #ifdef DEBUG_PROJECTION assert(fabs(dot(OrigUVs_BaseU, OrigUVs_BaseV)) < 0.3f); #endif OrigUVs_Origin = outIntersectionUV; GetBarycentricCoords(outIntersectionUV, pVertices[i0].Uv, pVertices[i1].Uv, pVertices[i2].Uv, outIntersectionBaricentric[0], outIntersectionBaricentric[1], outIntersectionBaricentric[2]); for (int32 i = 0; i < 3; ++i) { int32 v = pIndices[currentFace * 3 + i]; #ifdef DEBUG_PROJECTION assert(v >= 0 && v < vertexCount); #endif int32 collapsedVert = collapsedVertexMap[v]; processedVertices.Add(collapsedVert); //PlanarlyProjectVertex(pVertices[collapsedVert].pos, projectedPositions[collapsedVert], projector2, projectorPosition2, projectorDirection2, s2, u2); FVector2f projectedVertex = ChangeBase2D(pVertices[v].Uv, OrigUVs_Origin, OrigUVs_BaseU, OrigUVs_BaseV); projectedPositions[collapsedVert].pos0 = projectedVertex[0]; projectedPositions[collapsedVert].pos1 = projectedVertex[1]; projectedPositions[collapsedVert].pos2 = 0.f; projectedPositions[collapsedVert].mask3 = 1; } FVector2f v0 = projectedPositions[collapsedVertexMap[pIndices[currentFace * 3 + 0]]].xy(); FVector2f v1 = projectedPositions[collapsedVertexMap[pIndices[currentFace * 3 + 1]]].xy(); FVector2f v2 = projectedPositions[collapsedVertexMap[pIndices[currentFace * 3 + 2]]].xy(); currentTriangleArea = getTriangleArea(v0, v1, v2); float triangleAreaFactor = sqrt(fabs(proj_TriangleArea) / fabs(currentTriangleArea)); FVector2f origin = v0 * outIntersectionBaricentric[0] + v1 * outIntersectionBaricentric[1] + v2 * outIntersectionBaricentric[2]; #ifdef DEBUG_PROJECTION assert(length(origin - FVector2f(0.5f, 0.5f)) < 0.001f); #endif //hitFaceProjectedNormal = (cross(v1 - v0, v2 - v0)); hitFaceHasPositiveArea = currentTriangleArea >= 0.f; for (int32 i = 0; i < 3; ++i) { int32 v = pIndices[currentFace * 3 + i]; int32 collapsedVert = collapsedVertexMap[v]; projectedPositions[collapsedVert].pos0 = ((projectedPositions[collapsedVert].pos0 - 0.5f) * triangleAreaFactor) + 0.5f; projectedPositions[collapsedVert].pos1 = ((projectedPositions[collapsedVert].pos1 - 0.5f) * triangleAreaFactor) + 0.5f; projectedPositions[collapsedVert].pos2 = 0.f; projectedPositions[collapsedVert].mask3 = projectedPositions[collapsedVert].pos0 >= 0.0f && projectedPositions[collapsedVert].pos0 <= 1.0f && projectedPositions[collapsedVert].pos1 >= 0.0f && projectedPositions[collapsedVert].pos1 <= 1.0f; // Copy the new info to all the vertices that collapse to the same vertex TArray FoundValues; collapsedVertsMap.MultiFind(collapsedVert,FoundValues); for (int32 otherVert:FoundValues) { processedVertices.Add(otherVert); projectedPositions[otherVert] = projectedPositions[collapsedVert]; } } v0 = projectedPositions[collapsedVertexMap[pIndices[currentFace * 3 + 0]]].xy(); v1 = projectedPositions[collapsedVertexMap[pIndices[currentFace * 3 + 1]]].xy(); v2 = projectedPositions[collapsedVertexMap[pIndices[currentFace * 3 + 2]]].xy(); currentTriangleArea = getTriangleArea(v0, v1, v2); #ifdef DEBUG_PROJECTION assert(fabs(proj_TriangleArea - currentTriangleArea) / proj_TriangleArea < 0.1f); #endif origin = v0 * outIntersectionBaricentric[0] + v1 * outIntersectionBaricentric[1] + v2 * outIntersectionBaricentric[2]; #ifdef DEBUG_PROJECTION assert(length(origin - FVector2f(0.5f, 0.5f)) < 0.001f); #endif } else { // Unwrap the rest of the faces #ifdef DEBUG_PROJECTION assert(newVertexFromQueue >= 0); #endif int32 newVertex = -1; //int32 oldVertices[2] = { -1, -1 }; oldVertices.Empty(); bool reverseOrder = false; for (int32 i = 0; i < 3; ++i) { int32 v = pIndices[currentFace * 3 + i]; if (!processedVertices.Contains(collapsedVertexMap[v])) { newVertex = v; if (i == 1) { reverseOrder = true; } } else { //int32 index = oldVertices[0] == -1 ? 0 : 1; //oldVertices[index] = v; //oldVertices.push_back(collapsedVertexMap[v]); oldVertices.Add(v); } } if (reverseOrder && oldVertices.Num() == 2) { int32 aux = oldVertices[0]; oldVertices[0] = oldVertices[1]; oldVertices[1] = aux; } if (newVertex >= 0) { #ifdef DEBUG_PROJECTION assert(newVertex >= 0); //assert(collapsedVertexMap[newVertex] == newVertexFromQueue); assert(oldVertices[0] >= 0 && oldVertices[1] >= 0); assert(oldVertices.Num() == 2); assert(newVertex < vertexCount && oldVertices[0] < vertexCount && oldVertices[1] < vertexCount); #endif int32 previousFace = currentFaceStruct.previousFace; #ifdef DEBUG_PROJECTION assert(previousFace >= 0); assert(processedFaces.count(previousFace) == 1); #endif // Previous face indices int32 pi0 = pIndices[previousFace * 3 + 0]; int32 pi1 = pIndices[previousFace * 3 + 1]; int32 pi2 = pIndices[previousFace * 3 + 2]; // Previous face uv coords FVector2f pv0; FVector2f pv1; FVector2f pv2; // Proj vertices from previous triangle FVector2f pv0_proj; FVector2f pv1_proj; FVector2f pv2_proj; int32 oldVertices_previousFace[2] = { -1, -1 }; int32 oldVertex = -1; for (int32 i = 0; i < 3; ++i) { int32 v = pIndices[previousFace * 3 + i]; int32 collapsed_v = collapsedVertexMap[v]; if (collapsed_v == collapsedVertexMap[oldVertices[0]]) { oldVertices_previousFace[0] = v; } else if (collapsed_v == collapsedVertexMap[oldVertices[1]]) { oldVertices_previousFace[1] = v; } else { oldVertex = v; } } if (currentFaceStruct.changesUVIsland) { // It's crossing a uv island border, so the previous uvs need to be converted to the new islands uv space #ifdef DEBUG_PROJECTION assert(oldVertex >= 0 && oldVertices_previousFace[0] >= 0 && oldVertices_previousFace[1] >= 0); assert(oldVertex != newVertex && oldVertices_previousFace[0] != oldVertices[0] && oldVertices_previousFace[1] != oldVertices[1]); assert(collapsedVertexMap[oldVertices_previousFace[0]] == collapsedVertexMap[oldVertices[0]]); assert(collapsedVertexMap[oldVertices_previousFace[1]] == collapsedVertexMap[oldVertices[1]]); #endif // Compute the horizontal lengths in the previous island old (original) uv space, and in obj space float previousFace_uvSpaceLen, previousFace_objSpaceLen, midEdgePointFraction_previousFace; getEdgeHorizontalLength(oldVertices_previousFace[0], oldVertices_previousFace[1], oldVertex, pVertices, previousFace_uvSpaceLen, previousFace_objSpaceLen, midEdgePointFraction_previousFace); float currentFace_uvSpaceLen, currentFace_objSpaceLen, midEdgePointFraction_currentFace; getEdgeHorizontalLength(oldVertices[0], oldVertices[1], newVertex, pVertices, currentFace_uvSpaceLen, currentFace_objSpaceLen, midEdgePointFraction_currentFace); float objSpaceToCurrentUVSpaceConversion = currentFace_uvSpaceLen / currentFace_objSpaceLen; float previousFaceWidthInCurrentUVspace = previousFace_objSpaceLen * objSpaceToCurrentUVSpaceConversion; const FVector2f& edge0_currentFace_oldUVSpace = pVertices[oldVertices[0]].Uv; const FVector2f& edge1_currentFace_oldUVSpace = pVertices[oldVertices[1]].Uv; const FVector2f& newVertex_currentFace_oldUVSpace = pVertices[newVertex].Uv; FVector2f edge_currentFace_oldUVSpace = edge1_currentFace_oldUVSpace - edge0_currentFace_oldUVSpace; FVector2f midpoint_currentFace_oldUVSpace = edge0_currentFace_oldUVSpace + edge_currentFace_oldUVSpace * midEdgePointFraction_currentFace; FVector2f edgePerpVectorNorm = (midpoint_currentFace_oldUVSpace - newVertex_currentFace_oldUVSpace).GetSafeNormal(); FVector2f test_edge_currentFace_oldUVSpaceNorm = edge_currentFace_oldUVSpace.GetSafeNormal(); (void)test_edge_currentFace_oldUVSpaceNorm; #ifdef DEBUG_PROJECTION FVector2f sideVector_oldUVSpace = newVertex_currentFace_oldUVSpace - edge0_currentFace_oldUVSpace; float edgeVector_oldUVSpaceLen = length(edge_currentFace_oldUVSpace); float dotEdgeSideVectors_oldUVSpace = dot(edge_currentFace_oldUVSpace, sideVector_oldUVSpace); float midEdgePointFraction_oldUVSpace = (dotEdgeSideVectors_oldUVSpace / powf(edgeVector_oldUVSpaceLen, 2)); assert(fabs(midEdgePointFraction_oldUVSpace - midEdgePointFraction_currentFace) < 0.01f); assert(fabs(dot(edgePerpVectorNorm, test_edge_currentFace_oldUVSpaceNorm)) < 0.1f); #endif FVector2f midpoint_previousFace_oldUVSpace = edge0_currentFace_oldUVSpace + edge_currentFace_oldUVSpace * midEdgePointFraction_previousFace; FVector2f oldVertex_currentFace_oldUVSpace = midpoint_previousFace_oldUVSpace + edgePerpVectorNorm * previousFaceWidthInCurrentUVspace; pv0 = edge0_currentFace_oldUVSpace; pv1 = edge1_currentFace_oldUVSpace; pv2 = oldVertex_currentFace_oldUVSpace; pv0_proj = projectedPositions[collapsedVertexMap[oldVertices[0]]].xy(); pv1_proj = projectedPositions[collapsedVertexMap[oldVertices[1]]].xy(); pv2_proj = projectedPositions[collapsedVertexMap[oldVertex]].xy(); #ifdef DEBUG_PROJECTION assert(testPointsAreInOppositeSidesOfEdge(oldVertex_currentFace_oldUVSpace, newVertex_currentFace_oldUVSpace, edge0_currentFace_oldUVSpace, edge1_currentFace_oldUVSpace)); #endif } else { // If there's no UV island border, just use the previous uvs pv0 = pVertices[pi0].Uv; pv1 = pVertices[pi1].Uv; pv2 = pVertices[pi2].Uv; pv0_proj = projectedPositions[pi0].xy(); pv1_proj = projectedPositions[pi1].xy(); pv2_proj = projectedPositions[pi2].xy(); } FVector2f v2 = pVertices[newVertex].Uv; // New vertex baricentric coords in respect to old triangle float a, b, c; GetBarycentricCoords(v2, pv0, pv1, pv2, a, b, c); FVector2f newVertex_proj = pv0_proj * a + pv1_proj * b + pv2_proj * c; #ifdef DEBUG_PROJECTION FVector2f v2Test = pv0 * a + pv1 * b + pv2 * c; float v2TestDist = length(v2 - v2Test); assert(v2TestDist < 0.001f); float a2, b2, c2; GetBarycentricCoords(newVertex_proj, pv0_proj, pv1_proj, pv2_proj, a2, b2, c2); assert(fabs(a - a2) < 0.5f); assert(fabs(b - b2) < 0.5f); assert(fabs(c - c2) < 0.5f); assert(testPointsAreInOppositeSidesOfEdge(projectedPositions[collapsedVertexMap[oldVertex]].xy(), newVertex_proj, projectedPositions[collapsedVertexMap[oldVertices[0]]].xy(), projectedPositions[collapsedVertexMap[oldVertices[1]]].xy())); #endif int32 collapsedNewVertex = collapsedVertexMap[newVertex]; processedVertices.Add(collapsedNewVertex); projectedPositions[collapsedNewVertex].pos0 = newVertex_proj[0]; projectedPositions[collapsedNewVertex].pos1 = newVertex_proj[1]; projectedPositions[collapsedNewVertex].pos2 = projectedPositions[oldVertices[0]].pos2; projectedPositions[collapsedNewVertex].mask3 = newVertex_proj[0] >= 0.0f && newVertex_proj[0] <= 1.0f && newVertex_proj[1] >= 0.0f && newVertex_proj[1] <= 1.0f; // Copy the new info to all the vertices that collapse to the same vertex TArray FoundValues; collapsedVertsMap.MultiFind(collapsedNewVertex,FoundValues); for (int32 otherVert : FoundValues) { processedVertices.Add(otherVert); projectedPositions[otherVert] = projectedPositions[collapsedNewVertex]; } //float oldRatio = getTriangleRatio(pVertices[newVertex].uv, pVertices[oldVertices[0]].uv, pVertices[oldVertices[1]].uv); //float newRatio = getTriangleRatio(newVertex_proj, projectedPositions[collapsedVertexMap[oldVertices[0]]].xy(), projectedPositions[collapsedVertexMap[oldVertices[1]]].xy()); //if (fabs(newRatio - oldRatio) > 31.f) //{ // break; //} } else { #ifdef DEBUG_PROJECTION assert(oldVertices.Num() == 3); #endif } int32 i0 = collapsedVertexMap[pIndices[currentFace * 3 + 0]]; int32 i1 = collapsedVertexMap[pIndices[currentFace * 3 + 1]]; int32 i2 = collapsedVertexMap[pIndices[currentFace * 3 + 2]]; FVector2f v0 = projectedPositions[i0].xy(); FVector2f v1 = projectedPositions[i1].xy(); FVector2f v2 = projectedPositions[i2].xy(); currentTriangleArea = getTriangleArea(v0, v1, v2); bool currentTriangleHasPositiveArea = currentTriangleArea >= 0.f; //FVector3f currentFaceNormal = (cross(v1 - v0, v2 - v0)); //if (hitFaceProjectedNormal.z() * currentFaceNormal.z() < 0) if(hitFaceHasPositiveArea != currentTriangleHasPositiveArea) // Is the current face wound in the opposite direction? { discardedWrapAroundFaces.Add(currentFace); // If so, discard it since it's probably a wrap-around face } } bool anyVertexInUVSpace = false; for (int32 i = 0; i < 3; ++i) { int32 v = collapsedVertexMap[pIndices[currentFace * 3 + i]]; if (projectedPositions[v].mask3 == 1) // Is it inside? { anyVertexInUVSpace = true; break; } } bool anyVertexInObjSpaceRange = false; for (int32 i = 0; i < 3; ++i) { int32 v = pIndices[currentFace * 3 + i]; FVector3f r = pVertices[v].Position - out_intersection; float squaredDist = FVector3f::DotProduct(r, r); if (squaredDist <= maxDistSquared / 4.f) // Is it inside? { anyVertexInObjSpaceRange = true; break; } } if (anyVertexInUVSpace && anyVertexInObjSpaceRange && !discardedWrapAroundFaces.Contains(currentFace) ) { #ifdef DEBUG_PROJECTION assert(currentTriangleArea != -2.f); #endif totalUVAreaCovered += fabs(currentTriangleArea); for(int32 i = 0; i < 3; ++i) { int32 neighborFace2 = faceConnectivity[currentFace].faces[i]; int32 newVertex = faceConnectivity[currentFace].newVertices[i]; bool changesUVIsland = faceConnectivity[currentFace].changesUVIsland[i]; if (neighborFace2 >= 0 && !processedFaces.Contains(neighborFace2) && pendingFacesUnique.Contains(neighborFace2) == 0) { NeighborFace neighborFaceStruct; neighborFaceStruct.neighborFace = neighborFace2; neighborFaceStruct.newVertex = newVertex; neighborFaceStruct.previousFace = currentFace; neighborFaceStruct.numUVIslandChanges = currentFaceStruct.changesUVIsland ? currentFaceStruct.numUVIslandChanges + 1 : currentFaceStruct.numUVIslandChanges; neighborFaceStruct.step = step; neighborFaceStruct.changesUVIsland = changesUVIsland; pendingFaces.Add(neighborFaceStruct); pendingFacesUnique.Add(neighborFace2); } } } else { discardedWrapAroundFaces.Add(currentFace); } //if(step == 1000) //{ // break; //} if (totalUVAreaCovered > 1.5f) { break; } step++; } } TArray oldToNewVertex; oldToNewVertex.Init(-1,vertexCount); // Add the projected face for(int32 f : processedFaces) { if (discardedWrapAroundFaces.Contains(f)) { continue; } #ifdef DEBUG_PROJECTION int32 i0 = pIndices[f * 3 + 0]; int32 i1 = pIndices[f * 3 + 1]; int32 i2 = pIndices[f * 3 + 2]; assert(processedVertices.count(i0)); assert(processedVertices.count(i1)); assert(processedVertices.count(i2)); assert(i0 >= 0 && i0 < vertexCount); assert(i1 >= 0 && i1 < vertexCount); assert(i2 >= 0 && i2 < vertexCount); #endif //// TODO: This test is wrong and may fail for big triangles! Do proper triangle-quad culling. //if ( projectedPositions[i0][3] > 0.0f || // projectedPositions[i1][3] > 0.0f || // projectedPositions[i2][3] > 0.0f ) { // This face is required. for (int32 v = 0; v < 3; ++v) { int32 i = pIndices[f * 3 + v]; #ifdef DEBUG_PROJECTION assert(i >= 0 && i < vertexCount); #endif if (oldToNewVertex[i] < 0) { pResultVertices[currentVertex] = pVertices[i]; // \TOOD: Optimize { switch (LayoutBlockType) { case EMeshBufferFormat::UInt64: { const uint64* LayoutBlockIds = reinterpret_cast(pMesh->GetVertexBuffers().GetBufferData(1)); pResultLayoutBlockIds[currentVertex] = LayoutBlockIds[i]; break; } case EMeshBufferFormat::UInt16: { const uint16* LayoutBlockIds = reinterpret_cast(pMesh->GetVertexBuffers().GetBufferData(1)); uint64 BlockId = (uint64(pMesh->MeshIDPrefix) << 32) | uint64(LayoutBlockIds[i]); pResultLayoutBlockIds[currentVertex] = BlockId; break; } default: // Not implemented? check(false); break; } } pResultVertices[currentVertex].Position[0] = projectedPositions[i].pos0; pResultVertices[currentVertex].Position[1] = projectedPositions[i].pos1; pResultVertices[currentVertex].Position[2] = projectedPositions[i].pos2; // Normal is actually the fade factor constexpr float MaxGradient = 10.f; const int32 Step = faceStep[f]; float StepGradient = FMath::Min(Step, MaxGradient) / MaxGradient; float FadeFactor = 1.0f - StepGradient; //1.f; // dot(pVertices[i].Normal, projectorDirection * -1.0f); pResultVertices[currentVertex].Normal[0] = FadeFactor; pResultVertices[currentVertex].Normal[1] = FadeFactor; pResultVertices[currentVertex].Normal[2] = FadeFactor; oldToNewVertex[i] = currentVertex++; } pResultIndices[currentIndex++] = oldToNewVertex[i]; } } } } //------------------------------------------------------------------------------------------------- void MeshProject_Optimised(FMesh* Result, const FMesh* pMesh, const FProjector& projector, bool& bOutSuccess) { MUTABLE_CPUPROFILER_SCOPE(MeshProject_Optimised); bOutSuccess = true; FVector3f projectorPosition,projectorDirection,projectorSide,projectorUp; projectorPosition = FVector3f( projector.position[0], projector.position[1], projector.position[2] ); projector.GetDirectionSideUp( projectorDirection, projectorSide, projectorUp ); FVector3f projectorScale( projector.scale[0], projector.scale[1], projector.scale[2] ); // Create with worse case, shrink later. int32 vertexCount = pMesh->GetVertexCount(); int32 indexCount = pMesh->GetIndexCount(); int32 currentVertex = 0; int32 currentIndex = 0; // At this point the code generation ensures we are working with layout 0 const int32 layout = 0; switch (projector.type) { case EProjectorType::Planar: case EProjectorType::Cylindrical: { CreateMeshOptimisedForProjection(Result, layout); Result->GetVertexBuffers().SetElementCount(vertexCount); Result->GetIndexBuffers().SetElementCount(indexCount); uint32* pResultIndices = reinterpret_cast(Result->GetIndexBuffers().GetBufferData(0)); FOptimizedVertex* pResultVertices = reinterpret_cast(Result->GetVertexBuffers().GetBufferData(0)); // Get the vertices check(pMesh->GetVertexBuffers().GetElementSize(0)==sizeof(FOptimizedVertex)); const FOptimizedVertex* pVertices = reinterpret_cast(pMesh->GetVertexBuffers().GetBufferData(0)); // Get the indices check(pMesh->GetIndexBuffers().GetElementSize(0) == 4); const uint32* pIndices = reinterpret_cast(pMesh->GetIndexBuffers().GetBufferData(0)); int32 faceCount = pMesh->GetFaceCount(); if (projector.type==EProjectorType::Planar) { MeshProject_Optimised_Planar(pVertices, vertexCount, pIndices, faceCount, projectorPosition, projectorDirection, projectorSide, projectorUp, projectorScale, pResultVertices, currentVertex, pResultIndices, currentIndex); } else { MeshProject_Optimised_Cylindrical(pVertices, vertexCount, pIndices, faceCount, projectorPosition, projectorDirection, projectorSide, projectorUp, projectorScale, pResultVertices, currentVertex, pResultIndices, currentIndex); } break; } case EProjectorType::Wrapping: { CreateMeshOptimisedForWrappingProjection(Result, layout); // Wrapping-projection also needs the layout block IDs Result->GetVertexBuffers().SetElementCount(vertexCount); Result->GetIndexBuffers().SetElementCount(indexCount); uint32* pResultIndices = reinterpret_cast(Result->GetIndexBuffers().GetBufferData(0)); FOptimizedVertex* pResultVertices = reinterpret_cast(Result->GetVertexBuffers().GetBufferData(0)); // \TODO: be more flexible with the block id formats? uint64* pResultLayoutBlockIds = reinterpret_cast(Result->GetVertexBuffers().GetBufferData(1)); MeshProject_Optimised_Wrapping(pMesh, projectorPosition, projectorDirection, projectorSide, projectorUp, projectorScale, pResultVertices, pResultLayoutBlockIds, currentVertex, pResultIndices, currentIndex); break; } default: // Projector type not implemented. check(false); bOutSuccess = false; break; } // Shrink result mesh Result->GetVertexBuffers().SetElementCount(currentVertex); Result->GetIndexBuffers().SetElementCount(currentIndex); } #ifdef DEBUG_PROJECTION UE_ENABLE_OPTIMIZATION #endif //------------------------------------------------------------------------------------------------- void MeshProject(FMesh* Result, const FMesh* pMesh, const FProjector& projector, bool& bOutSuccess) { MUTABLE_CPUPROFILER_SCOPE(MeshProject); if ( EnumHasAnyFlags(pMesh->Flags, EMeshFlags::ProjectFormat) ) { // Mesh-optimised version MeshProject_Optimised(Result, pMesh, projector, bOutSuccess); } else if (EnumHasAnyFlags(pMesh->Flags, EMeshFlags::ProjectWrappingFormat)) { // Mesh-optimised version for wrapping projectors // \todo: make sure the projector is a wrapping projector MeshProject_Optimised(Result, pMesh, projector, bOutSuccess); } else { check(false); } if (bOutSuccess) { Result->Surfaces.Empty(); Result->EnsureSurfaceData(); Result->ResetStaticFormatFlags(); } } //------------------------------------------------------------------------------------------------- void CreateMeshOptimisedForProjection(FMesh* Result, int32 layout) { Result->GetVertexBuffers().SetBufferCount( 1 ); Result->GetIndexBuffers().SetBufferCount( 1 ); EMeshBufferSemantic semantics[3] = { EMeshBufferSemantic::TexCoords, EMeshBufferSemantic::Position, EMeshBufferSemantic::Normal }; int32 semanticIndices[3] = { 0, 0, 0 }; EMeshBufferFormat formats[3] = { EMeshBufferFormat::Float32, EMeshBufferFormat::Float32, EMeshBufferFormat::Float32 }; int32 componentCounts[3] = { 2, 3, 3 }; int32 offsets[3] = { 0, 8, 20 }; semanticIndices[0] = layout; Result->GetVertexBuffers().SetBuffer ( 0, 32, 3, semantics, semanticIndices, formats, componentCounts, offsets ); EMeshBufferSemantic isemantics[1] = { EMeshBufferSemantic::VertexIndex }; int32 isemanticIndices[1] = { 0 }; EMeshBufferFormat iformats[1] = { EMeshBufferFormat::UInt32 }; int32 icomponentCounts[1] = { 1 }; int32 ioffsets[1] = { 0 }; Result->GetIndexBuffers().SetBuffer ( 0, 4, 1, isemantics, isemanticIndices, iformats, icomponentCounts, ioffsets ); } //------------------------------------------------------------------------------------------------- void CreateMeshOptimisedForWrappingProjection(FMesh* Result, int32 LayoutIndex) { Result->GetVertexBuffers().SetBufferCount( 2 ); Result->GetIndexBuffers().SetBufferCount( 1 ); EMeshBufferSemantic semantics[3] = { EMeshBufferSemantic::TexCoords, EMeshBufferSemantic::Position, EMeshBufferSemantic::Normal }; int32 semanticIndices[3] = { 0, 0, 0 }; EMeshBufferFormat formats[3] = { EMeshBufferFormat::Float32, EMeshBufferFormat::Float32, EMeshBufferFormat::Float32 }; int32 componentCounts[3] = { 2, 3, 3 }; int32 offsets[3] = { 0, 8, 20 }; semanticIndices[0] = LayoutIndex; Result->GetVertexBuffers().SetBuffer( 0, 32, 3, semantics, semanticIndices, formats, componentCounts, offsets ); EMeshBufferSemantic LayoutSemantics[1] = { EMeshBufferSemantic::LayoutBlock }; int32 LayoutSemanticIndices[1] = { 0 }; EMeshBufferFormat LayoutFormats[1] = { EMeshBufferFormat::UInt64 }; int32 LayoutComponentCounts[1] = { 1 }; int32 LayoutOffsets[1] = { 0 }; LayoutSemanticIndices[0] = LayoutIndex; Result->GetVertexBuffers().SetBuffer(1, sizeof(uint64), 1, LayoutSemantics, LayoutSemanticIndices, LayoutFormats, LayoutComponentCounts, LayoutOffsets); EMeshBufferSemantic isemantics[1] = { EMeshBufferSemantic::VertexIndex }; int32 isemanticIndices[1] = { 0 }; EMeshBufferFormat iformats[1] = { EMeshBufferFormat::UInt32 }; int32 icomponentCounts[1] = { 1 }; int32 ioffsets[1] = { 0 }; Result->GetIndexBuffers().SetBuffer( 0, 4, 1, isemantics, isemanticIndices, iformats, icomponentCounts, ioffsets ); } }