3127 lines
123 KiB
C++
3127 lines
123 KiB
C++
// 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<uint16> CropMin;
|
|
UE::Math::TIntVector2<uint16> 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<int32>(EnumHasAnyFlags(Features, EPixelProcessorFeatures::FormatL)) +
|
|
static_cast<int32>(EnumHasAnyFlags(Features, EPixelProcessorFeatures::FormatRGB)) +
|
|
static_cast<int32>(EnumHasAnyFlags(Features, EPixelProcessorFeatures::FormatRGBA)) == 1;
|
|
}
|
|
|
|
constexpr bool CheckExactlyOneSamplingFlag(EPixelProcessorFeatures Features)
|
|
{
|
|
return
|
|
static_cast<int32>(EnumHasAnyFlags(Features, EPixelProcessorFeatures::SamplingPoint)) +
|
|
static_cast<int32>(EnumHasAnyFlags(Features, EPixelProcessorFeatures::SamplingLinear)) == 1;
|
|
}
|
|
|
|
constexpr bool CheckExactlyOneProjectionFlag(EPixelProcessorFeatures Features)
|
|
{
|
|
return
|
|
static_cast<int32>(EnumHasAnyFlags(Features, EPixelProcessorFeatures::ProjectionCylindrical)) +
|
|
static_cast<int32>(EnumHasAnyFlags(Features, EPixelProcessorFeatures::ProjectionPlanar)) +
|
|
static_cast<int32>(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<EPixelProcessorFeatures Features>
|
|
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<uint16>(Source->GetSizeX());
|
|
Context.Source0SizeY = static_cast<uint16>(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<uint16>(Source1Size.X) : Context.Source0SizeX;
|
|
Context.Source1SizeY = bHasNextMip ? static_cast<uint16>(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<bool>((Varying[2] < 0.0f) | (Varying[2] > 1.0f));
|
|
}
|
|
});
|
|
|
|
float Factor = static_cast<float>(!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<float>(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<const uint32*>(Ptr);
|
|
return VectorMultiply(MakeVectorRegister(
|
|
static_cast<float>((PackedData >> (8 * 0)) & 0xFF),
|
|
static_cast<float>((PackedData >> (8 * 1)) & 0xFF),
|
|
static_cast<float>((PackedData >> (8 * 2)) & 0xFF),
|
|
static_cast<float>((PackedData >> (8 * 3)) & 0xFF)), OneOver255);
|
|
}
|
|
else
|
|
{
|
|
alignas(VectorRegister4Float) float PixelData[4];
|
|
for (int32 C = 0; C < PIXEL_SIZE; ++C)
|
|
{
|
|
PixelData[C] = static_cast<float>(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<int32>(Context.RGBFadingEnabledMask),
|
|
static_cast<int32>(Context.RGBFadingEnabledMask),
|
|
static_cast<int32>(Context.RGBFadingEnabledMask),
|
|
static_cast<int32>(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<uint8>(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<bool>((Varying[2] < 0.0f) | (Varying[2] > 1.0f));
|
|
}
|
|
});
|
|
|
|
uint16 Factor = static_cast<uint16>(!bDepthClamp) * 255;
|
|
uint16 MaskFactor = 255;
|
|
|
|
if constexpr (EnumHasAnyFlags(Features, EPixelProcessorFeatures::ProjectionPlanar))
|
|
{
|
|
const float AngleCos = Varying[3];
|
|
|
|
Factor = FMath::Min(Factor,
|
|
static_cast<uint16>(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<uint16>;
|
|
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<const uint32*>(Ptr);
|
|
|
|
Result.Data[0] = static_cast<uint16>((PackedData >> (8 * 0)) & 0xFF);
|
|
Result.Data[1] = static_cast<uint16>((PackedData >> (8 * 1)) & 0xFF);
|
|
Result.Data[2] = static_cast<uint16>((PackedData >> (8 * 2)) & 0xFF);
|
|
Result.Data[3] = static_cast<uint16>((PackedData >> (8 * 3)) & 0xFF);
|
|
}
|
|
else
|
|
{
|
|
for (int32 C = 0; C < PIXEL_SIZE; ++C)
|
|
{
|
|
Result.Data[C] = static_cast<uint16>(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<uint16>(FMath::Frac(CoordsF.X) * 255.0f),
|
|
static_cast<uint16>(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<uint16>(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<int16>(Context.RGBFadingEnabledMask),
|
|
static_cast<int16>(Context.RGBFadingEnabledMask),
|
|
static_cast<int16>(Context.RGBFadingEnabledMask),
|
|
static_cast<int16>(Context.AlphaFadingEnabledMask) };
|
|
|
|
for (int32 I = 0; I < PIXEL_SIZE; ++I)
|
|
{
|
|
BufferPos[I] = static_cast<uint8>(
|
|
(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<uint16> CropMin, UE::Math::TIntVector2<uint16> 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<const FOptimizedVertex*>(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<const uint32*>(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<NumInterpolators>(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<uint16> CropMin, UE::Math::TIntVector2<uint16> 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<const FOptimizedVertex*>(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<const uint64*>(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<const uint16*>(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<const uint32*>(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<NumInterpolators>(pImage->GetLODData(0), pImage->GetDataSize(),
|
|
SizeX, SizeY,
|
|
PixelSize,
|
|
Scratch->Vertices[Index0], Scratch->Vertices[Index1], Scratch->Vertices[Index2],
|
|
PixelProc,
|
|
false);
|
|
}
|
|
});
|
|
}
|
|
|
|
template <EPixelProcessorFeatures Features>
|
|
FORCENOINLINE void InvokePlanarRasterizerImpl(const FImageRasterInvokeArgs& Args)
|
|
{
|
|
static_assert(EnumHasAnyFlags(Features, EPixelProcessorFeatures::ProjectionPlanar));
|
|
|
|
const Private::FProjectedPixelProcessorContext Context =
|
|
Private::TProjectedPixelProcessor<Features>::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<Features>::ProcessPixel(Context, Buffer, Varying);
|
|
};
|
|
|
|
ImageRasterProjected_Optimised(Args.MeshPtr, Args.ImagePtr, PixelProc, Args.FadeEnd, Args.CropMin, Args.UncroppedSize, Args.Scratch);
|
|
}
|
|
|
|
template <EPixelProcessorFeatures Features>
|
|
FORCENOINLINE void InvokeCylindricalRasterizerImpl(const FImageRasterInvokeArgs& Args)
|
|
{
|
|
static_assert(EnumHasAnyFlags(Features, EPixelProcessorFeatures::ProjectionCylindrical));
|
|
|
|
const Private::FProjectedPixelProcessorContext Context =
|
|
Private::TProjectedPixelProcessor<Features>::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<Features>::ProcessPixel(Context, Buffer, Varying);
|
|
};
|
|
|
|
ImageRasterProjected_Optimised(Args.MeshPtr, Args.ImagePtr, PixelProc, Args.FadeEnd, Args.CropMin, Args.UncroppedSize, Args.Scratch);
|
|
}
|
|
|
|
template <EPixelProcessorFeatures Features>
|
|
FORCENOINLINE void InvokeWrappingRasterizerImpl(const FImageRasterInvokeArgs& Args)
|
|
{
|
|
static_assert(EnumHasAnyFlags(Features, EPixelProcessorFeatures::ProjectionWrap));
|
|
|
|
const Private::FProjectedPixelProcessorContext Context =
|
|
Private::TProjectedPixelProcessor<Features>::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<Features>::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<uint16> CropMin, UE::Math::TIntVector2<uint16> 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<EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingPoint>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingPoint):
|
|
{
|
|
InvokePlanarRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingPoint>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingPoint):
|
|
{
|
|
InvokePlanarRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingPoint>(RasterArgs);
|
|
break;
|
|
}
|
|
|
|
case (EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingLinear):
|
|
{
|
|
InvokePlanarRasterizerImpl<EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingLinear>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingLinear):
|
|
{
|
|
InvokePlanarRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingLinear>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingLinear):
|
|
{
|
|
InvokePlanarRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingLinear>(RasterArgs);
|
|
break;
|
|
}
|
|
|
|
case (EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::WithMask):
|
|
{
|
|
InvokePlanarRasterizerImpl<EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::WithMask>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::WithMask):
|
|
{
|
|
InvokePlanarRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::WithMask>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::WithMask):
|
|
{
|
|
InvokePlanarRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingPoint>(RasterArgs);
|
|
break;
|
|
}
|
|
|
|
case (EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::WithMask):
|
|
{
|
|
InvokePlanarRasterizerImpl<EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::WithMask>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::WithMask):
|
|
{
|
|
InvokePlanarRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::WithMask>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::WithMask):
|
|
{
|
|
InvokePlanarRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::WithMask>(RasterArgs);
|
|
break;
|
|
}
|
|
|
|
//case (EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::VectorizedImpl):
|
|
//{
|
|
// InvokePlanarRasterizerImpl<EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::VectorizedImpl>(RasterArgs);
|
|
// break;
|
|
//}
|
|
//case (EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::VectorizedImpl):
|
|
//{
|
|
// InvokePlanarRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::VectorizedImpl>(RasterArgs);
|
|
// break;
|
|
//}
|
|
//case (EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::VectorizedImpl):
|
|
//{
|
|
// InvokePlanarRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::VectorizedImpl>(RasterArgs);
|
|
// break;
|
|
//}
|
|
|
|
case (EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::VectorizedImpl):
|
|
{
|
|
InvokePlanarRasterizerImpl<EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::VectorizedImpl>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::VectorizedImpl):
|
|
{
|
|
InvokePlanarRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::VectorizedImpl>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::VectorizedImpl):
|
|
{
|
|
InvokePlanarRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::VectorizedImpl>(RasterArgs);
|
|
break;
|
|
}
|
|
|
|
//case (EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl):
|
|
//{
|
|
// InvokePlanarRasterizerImpl<EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl>(RasterArgs);
|
|
// break;
|
|
//}
|
|
//case (EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl):
|
|
//{
|
|
// InvokePlanarRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl>(RasterArgs);
|
|
// break;
|
|
//}
|
|
//case (EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl):
|
|
//{
|
|
// InvokePlanarRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingPoint | EPPF::VectorizedImpl>(RasterArgs);
|
|
// break;
|
|
//}
|
|
|
|
case (EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl):
|
|
{
|
|
InvokePlanarRasterizerImpl<EPPF::FormatL | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl):
|
|
{
|
|
InvokePlanarRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::WithMask>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl):
|
|
{
|
|
InvokePlanarRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionPlanar | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl>(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<uint16> CropMin, UE::Math::TIntVector2<uint16> 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<EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingPoint>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingPoint):
|
|
{
|
|
InvokeWrappingRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingPoint>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingPoint):
|
|
{
|
|
InvokeWrappingRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingPoint>(RasterArgs);
|
|
break;
|
|
}
|
|
|
|
case (EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingLinear):
|
|
{
|
|
InvokeWrappingRasterizerImpl<EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingLinear>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingLinear):
|
|
{
|
|
InvokeWrappingRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingLinear>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingLinear):
|
|
{
|
|
InvokeWrappingRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingLinear>(RasterArgs);
|
|
break;
|
|
}
|
|
|
|
case (EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::WithMask):
|
|
{
|
|
InvokeWrappingRasterizerImpl<EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::WithMask>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::WithMask):
|
|
{
|
|
InvokeWrappingRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::WithMask>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::WithMask):
|
|
{
|
|
InvokeWrappingRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingPoint>(RasterArgs);
|
|
break;
|
|
}
|
|
|
|
case (EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::WithMask):
|
|
{
|
|
InvokeWrappingRasterizerImpl<EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::WithMask>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::WithMask):
|
|
{
|
|
InvokeWrappingRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::WithMask>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::WithMask):
|
|
{
|
|
InvokeWrappingRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::WithMask>(RasterArgs);
|
|
break;
|
|
}
|
|
|
|
//case (EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::VectorizedImpl):
|
|
//{
|
|
// InvokeWrappingRasterizerImpl<EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::VectorizedImpl>(RasterArgs);
|
|
// break;
|
|
//}
|
|
//case (EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::VectorizedImpl):
|
|
//{
|
|
// InvokeWrappingRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::VectorizedImpl>(RasterArgs);
|
|
// break;
|
|
//}
|
|
//case (EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::VectorizedImpl):
|
|
//{
|
|
// InvokeWrappingRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::VectorizedImpl>(RasterArgs);
|
|
// break;
|
|
//}
|
|
|
|
case (EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::VectorizedImpl):
|
|
{
|
|
InvokeWrappingRasterizerImpl<EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::VectorizedImpl>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::VectorizedImpl):
|
|
{
|
|
InvokeWrappingRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::VectorizedImpl>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::VectorizedImpl):
|
|
{
|
|
InvokeWrappingRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::VectorizedImpl>(RasterArgs);
|
|
break;
|
|
}
|
|
|
|
//case (EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl):
|
|
//{
|
|
// InvokeWrappingRasterizerImpl<EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl>(RasterArgs);
|
|
// break;
|
|
//}
|
|
//case (EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl):
|
|
//{
|
|
// InvokeWrappingRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl>(RasterArgs);
|
|
// break;
|
|
//}
|
|
//case (EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl):
|
|
//{
|
|
// InvokeWrappingRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingPoint | EPPF::VectorizedImpl>(RasterArgs);
|
|
// break;
|
|
//}
|
|
|
|
case (EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl):
|
|
{
|
|
InvokeWrappingRasterizerImpl<EPPF::FormatL | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl):
|
|
{
|
|
InvokeWrappingRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::WithMask>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl):
|
|
{
|
|
InvokeWrappingRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionWrap | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl>(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<uint16> CropMin, UE::Math::TIntVector2<uint16> 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<EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingPoint>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingPoint):
|
|
{
|
|
InvokeCylindricalRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingPoint>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingPoint):
|
|
{
|
|
InvokeCylindricalRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingPoint>(RasterArgs);
|
|
break;
|
|
}
|
|
|
|
case (EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingLinear):
|
|
{
|
|
InvokeCylindricalRasterizerImpl<EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingLinear>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingLinear):
|
|
{
|
|
InvokeCylindricalRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingLinear>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingLinear):
|
|
{
|
|
InvokeCylindricalRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingLinear>(RasterArgs);
|
|
break;
|
|
}
|
|
|
|
case (EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::WithMask):
|
|
{
|
|
InvokeCylindricalRasterizerImpl<EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::WithMask>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::WithMask):
|
|
{
|
|
InvokeCylindricalRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::WithMask>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::WithMask):
|
|
{
|
|
InvokeCylindricalRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingPoint>(RasterArgs);
|
|
break;
|
|
}
|
|
|
|
case (EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::WithMask):
|
|
{
|
|
InvokeCylindricalRasterizerImpl<EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::WithMask>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::WithMask):
|
|
{
|
|
InvokeCylindricalRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::WithMask>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::WithMask):
|
|
{
|
|
InvokeCylindricalRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::WithMask>(RasterArgs);
|
|
break;
|
|
}
|
|
|
|
//case (EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::VectorizedImpl):
|
|
//{
|
|
// InvokeCylindricalRasterizerImpl<EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::VectorizedImpl>(RasterArgs);
|
|
// break;
|
|
//}
|
|
//case (EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::VectorizedImpl):
|
|
//{
|
|
// InvokeCylindricalRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::VectorizedImpl>(RasterArgs);
|
|
// break;
|
|
//}
|
|
//case (EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::VectorizedImpl):
|
|
//{
|
|
// InvokeCylindricalRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::VectorizedImpl>(RasterArgs);
|
|
// break;
|
|
//}
|
|
|
|
case (EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::VectorizedImpl):
|
|
{
|
|
InvokeCylindricalRasterizerImpl<EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::VectorizedImpl>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::VectorizedImpl):
|
|
{
|
|
InvokeCylindricalRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::VectorizedImpl>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::VectorizedImpl):
|
|
{
|
|
InvokeCylindricalRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::VectorizedImpl>(RasterArgs);
|
|
break;
|
|
}
|
|
|
|
//case (EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl):
|
|
//{
|
|
// InvokeCylindricalRasterizerImpl<EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl>(RasterArgs);
|
|
// break;
|
|
//}
|
|
//case (EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl):
|
|
//{
|
|
// InvokeCylindricalRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl>(RasterArgs);
|
|
// break;
|
|
//}
|
|
//case (EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::WithMask | EPPF::VectorizedImpl):
|
|
//{
|
|
// InvokeCylindricalRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingPoint | EPPF::VectorizedImpl>(RasterArgs);
|
|
// break;
|
|
//}
|
|
|
|
case (EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl):
|
|
{
|
|
InvokeCylindricalRasterizerImpl<EPPF::FormatL | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl):
|
|
{
|
|
InvokeCylindricalRasterizerImpl<EPPF::FormatRGB | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::WithMask>(RasterArgs);
|
|
break;
|
|
}
|
|
case (EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl):
|
|
{
|
|
InvokeCylindricalRasterizerImpl<EPPF::FormatRGBA | EPPF::ProjectionCylindrical | EPPF::SamplingLinear | EPPF::WithMask | EPPF::VectorizedImpl>(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<const uint32*>(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<const FOptimizedVertex*>(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<float>(!FMath::IsNearlyZero(TriangleSourceArea)) *
|
|
static_cast<float>(!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<const FOptimizedVertex*>(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<float>(!FMath::IsNearlyZero(TriangleSourceArea)) *
|
|
static_cast<float>(!FMath::IsNearlyZero(TriangleTargetArea));
|
|
|
|
SourceArea += TriangleSourceArea * TriangleWeight;
|
|
TargetArea += TriangleTargetArea * TriangleWeight;
|
|
}
|
|
}
|
|
else if (bIsWrappingProjection)
|
|
{
|
|
check(pMesh->GetVertexBuffers().GetElementSize(0) == sizeof(FOptimizedVertex));
|
|
const FOptimizedVertex* VerticesPtr = reinterpret_cast<const FOptimizedVertex*>(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<int32>& CollapsedVertices,
|
|
TMultiMap<int32, int32>& CollapsedVerticesMap,
|
|
TArray<FVector3f>& OutVertices)
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(CreateCollapseMap);
|
|
|
|
const int32 NumVertices = pMesh->GetVertexCount();
|
|
|
|
// Used to speed up vertex comparison
|
|
UE::Geometry::TPointHashGrid3f<int32> 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<int32> 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<int32> oldToNewVertex;
|
|
oldToNewVertex.Init(-1,vertexCount);
|
|
|
|
TArray<PROJECTED_VERTEX> projectedPositions;
|
|
projectedPositions.SetNumZeroed(vertexCount);
|
|
|
|
for ( int32 v=0; v<vertexCount; ++v )
|
|
{
|
|
float x = FVector3f::DotProduct(pVertices[v].Position - projectorPosition, projectorSide) / projectorScale[0] + 0.5f;
|
|
float y = FVector3f::DotProduct(pVertices[v].Position - projectorPosition, projectorUp) / projectorScale[1] + 0.5f;
|
|
y = 1.0f-y;
|
|
float z = FVector3f::DotProduct(pVertices[v].Position - projectorPosition, projectorDirection) / projectorScale[2];
|
|
|
|
// Plane mask with bits for each plane discarding the vertex
|
|
uint32 planeMask =
|
|
((x<0.0f)<<0) |
|
|
((x>1.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<faceCount; ++f )
|
|
{
|
|
int32 i0 = pIndices[f*3+0];
|
|
int32 i1 = pIndices[f*3+1];
|
|
int32 i2 = pIndices[f*3+2];
|
|
|
|
// Approximate test: discard the triangle if any of the 6 planes entirely discards all the vertices.
|
|
// This will let some triangles through that could be discarded with a precise test.
|
|
bool discarded = (
|
|
projectedPositions[i0].mask3 &
|
|
projectedPositions[i1].mask3 &
|
|
projectedPositions[i2].mask3 )
|
|
!=
|
|
0;
|
|
|
|
if ( !discarded )
|
|
{
|
|
// This face is required.
|
|
for (int32 v=0;v<3;++v)
|
|
{
|
|
uint32 i = pIndices[f*3+v];
|
|
if (oldToNewVertex[i]<0)
|
|
{
|
|
pResultVertices[currentVertex] = pVertices[i];
|
|
|
|
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
|
|
float angleCos = FVector3f::DotProduct(pVertices[i].Normal, projectorDirection * -1.0f);
|
|
pResultVertices[currentVertex].Normal[0] = angleCos;
|
|
pResultVertices[currentVertex].Normal[1] = angleCos;
|
|
pResultVertices[currentVertex].Normal[2] = angleCos;
|
|
|
|
oldToNewVertex[i] = currentVertex++;
|
|
}
|
|
|
|
pResultIndices[currentIndex++] = oldToNewVertex[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
void MeshProject_Optimised_Cylindrical(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_Cylindrical)
|
|
|
|
TArray<int32> oldToNewVertex;
|
|
oldToNewVertex.Init( -1, vertexCount);
|
|
|
|
TArray<PROJECTED_VERTEX> 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; v<vertexCount; ++v )
|
|
{
|
|
// Cylinder is along the X axis
|
|
|
|
// Project
|
|
FVector3f RelPos = pVertices[v].Position - projectorPosition;
|
|
FVector3f VertexPos_Cylinder = WorldToCylinder.InverseTransformPosition(RelPos);
|
|
|
|
// This final projection needs to be done per pixel
|
|
float x = VertexPos_Cylinder.X / height;
|
|
float r2 = VertexPos_Cylinder.Y * VertexPos_Cylinder.Y
|
|
+ VertexPos_Cylinder.Z * VertexPos_Cylinder.Z;
|
|
projectedPositions[v].pos0 = x;
|
|
projectedPositions[v].pos1 = VertexPos_Cylinder.Y / radius;
|
|
projectedPositions[v].pos2 = VertexPos_Cylinder.Z / radius;
|
|
uint32 planeMask =
|
|
((x<0.0f)<<0) |
|
|
((x>1.0f)<<1) |
|
|
((r2>=(radius*radius))<<2);
|
|
projectedPositions[v].mask3 = planeMask;
|
|
}
|
|
|
|
// Iterate the faces
|
|
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];
|
|
|
|
// Approximate test: discard the triangle if any of the 3 "planes" (top, bottom and radius)
|
|
// entirely discards all the vertices.
|
|
// This will let some triangles through that could be discarded with a precise test.
|
|
// Also, some big triangles can be wrongly discarded by the radius test, since it is not
|
|
// really a plane.
|
|
bool discarded = (
|
|
projectedPositions[i0].mask3 &
|
|
projectedPositions[i1].mask3 &
|
|
projectedPositions[i2].mask3 )
|
|
!=
|
|
0;
|
|
|
|
if ( !discarded )
|
|
{
|
|
// This face is required.
|
|
for (int32 v=0;v<3;++v)
|
|
{
|
|
uint32 i = pIndices[f*3+v];
|
|
if (oldToNewVertex[i]<0)
|
|
{
|
|
pResultVertices[currentVertex] = pVertices[i];
|
|
|
|
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
|
|
//vec3 vertexCylinderDirection =
|
|
float angleCos = 1.0f; //dot( pVertices[i].Normal, vertexCylinderDirection * -1.0f );
|
|
pResultVertices[currentVertex].Normal[0] = angleCos;
|
|
pResultVertices[currentVertex].Normal[1] = angleCos;
|
|
pResultVertices[currentVertex].Normal[2] = angleCos;
|
|
|
|
oldToNewVertex[i] = currentVertex++;
|
|
}
|
|
|
|
pResultIndices[currentIndex++] = oldToNewVertex[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
void MeshProject_Optimised_Wrapping(const FMesh* pMesh,
|
|
const FVector3f& projectorPosition, const FVector3f& projectorDirection,
|
|
const FVector3f& projectorSide, const FVector3f& projectorUp,
|
|
const FVector3f& projectorScale,
|
|
FOptimizedVertex* pResultVertices, uint64* pResultLayoutBlockIds, int32& currentVertex,
|
|
uint32* pResultIndices, int32& currentIndex)
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(MeshProject_Optimised_Wrapping)
|
|
|
|
// Get the vertices
|
|
int32 vertexCount = pMesh->GetVertexCount();
|
|
check(pMesh->GetVertexBuffers().GetElementSize(0) == sizeof(FOptimizedVertex));
|
|
check(pMesh->GetVertexBuffers().Buffers.Num() == 2);
|
|
const FOptimizedVertex* pVertices = reinterpret_cast<const FOptimizedVertex*>(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<const uint32*>(pMesh->GetIndexBuffers().GetBufferData(0));
|
|
int32 faceCount = pMesh->GetFaceCount();
|
|
|
|
TArray<PROJECTED_VERTEX> 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<int32> processedVertices;
|
|
FVector3f projectionPlaneNormal;
|
|
FVector3f out_intersection;
|
|
|
|
TArray<AdjacentFaces> faceConnectivity;
|
|
faceConnectivity.SetNum(faceCount);
|
|
TSet<int32> processedFaces;
|
|
processedFaces.Reserve(faceCount/4);
|
|
TSet<int32> discardedWrapAroundFaces;
|
|
discardedWrapAroundFaces.Reserve(faceCount/16);
|
|
TArray<int32> 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<int32> collapsedVertexMap;
|
|
TArray<FVector3f> vertices;
|
|
TMultiMap<int32, int32> 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<int32, int32> 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<int32,TInlineAllocator<32>> 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<NeighborFace> pendingFaces; // Queue of pending face + new vertex
|
|
pendingFaces.Reserve(faceCount/64);
|
|
|
|
// \TODO: Bool array to speed up?
|
|
TSet<int32> 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<int32> 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<float>(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<int32> 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<int32> 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<float>(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<int32> 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<const uint64*>(pMesh->GetVertexBuffers().GetBufferData(1));
|
|
pResultLayoutBlockIds[currentVertex] = LayoutBlockIds[i];
|
|
break;
|
|
}
|
|
|
|
case EMeshBufferFormat::UInt16:
|
|
{
|
|
const uint16* LayoutBlockIds = reinterpret_cast<const uint16*>(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<uint32*>(Result->GetIndexBuffers().GetBufferData(0));
|
|
FOptimizedVertex* pResultVertices = reinterpret_cast<FOptimizedVertex*>(Result->GetVertexBuffers().GetBufferData(0));
|
|
|
|
// Get the vertices
|
|
check(pMesh->GetVertexBuffers().GetElementSize(0)==sizeof(FOptimizedVertex));
|
|
const FOptimizedVertex* pVertices = reinterpret_cast<const FOptimizedVertex*>(pMesh->GetVertexBuffers().GetBufferData(0));
|
|
|
|
// Get the indices
|
|
check(pMesh->GetIndexBuffers().GetElementSize(0) == 4);
|
|
const uint32* pIndices = reinterpret_cast<const uint32*>(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<uint32*>(Result->GetIndexBuffers().GetBufferData(0));
|
|
FOptimizedVertex* pResultVertices = reinterpret_cast<FOptimizedVertex*>(Result->GetVertexBuffers().GetBufferData(0));
|
|
|
|
// \TODO: be more flexible with the block id formats?
|
|
uint64* pResultLayoutBlockIds = reinterpret_cast<uint64*>(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 );
|
|
}
|
|
|
|
}
|