Files
UnrealEngine/Engine/Source/Programs/Enterprise/Datasmith/DatasmithARCHICADExporter/Private/DatasmithHashTools.cpp
2025-05-18 13:04:45 +08:00

224 lines
6.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DatasmithHashTools.h"
#include "DatasmithMesh.h"
#include "Version.h"
BEGIN_NAMESPACE_UE_AC
FMD5Hash FDatasmithHashTools::GetHashValue()
{
FMD5Hash HashValue;
HashValue.Set(MD5);
return HashValue;
}
// Recommended for values that come from input like coordinates, colors
float FDatasmithHashTools::FixedPointTolerance = 1 / 0.01f;
const float FDatasmithHashTools::MaxInvFixPointTolerance = FLOAT_NON_FRACTIONAL;
// Hash float with a fixed tolerance value.
void FDatasmithHashTools::HashFixedPointFloatTolerance(double InValue, float InvTolerance)
{
if (InvTolerance < MaxInvFixPointTolerance)
{
float Rounded = std::floorf((float)InValue * InvTolerance + 0.5f);
if (Rounded == -0.0f)
{
Rounded = 0.0f; // We want to confond negative near zero to positive near zero
}
TUpdate(Rounded);
}
else
{
// No tolerance -> we take the hash value itself
TUpdate((float)InValue);
}
}
// Recommended for values that come from computation, like normals, UVs, rotations...
float FDatasmithHashTools::FloatTolerance = KINDA_SMALL_NUMBER;
const float FDatasmithHashTools::MaxFloatTolerance = 1.0f / FLOAT_NON_FRACTIONAL;
// Hash float value, Use tolerance to absorb compute error
void FDatasmithHashTools::HashFloatTolerance(double InValue, float InTolerance)
{
if (InTolerance > MaxFloatTolerance)
{
// Near zero or negative
if ((float)InValue <= InTolerance)
{
InValue = -InValue;
if ((float)InValue <= InTolerance)
{
TUpdate(0.0f);
return; // Near zero ?
}
TUpdate(false);
}
// Tricky way to hash value so that near values (because of compute error) give same hash values
float LogValue = log10((float)InValue);
float IntLogValue = std::floorf(LogValue / InTolerance + 0.5f);
if (IntLogValue == -0.0f)
{
IntLogValue = 0.0f; // We want to confond negative near zero to positive near zero
}
TUpdate(IntLogValue);
}
else
{
// No tolerance -> we take the hash value itself
TUpdate((float)InValue);
}
}
// Hash 3d point related value, taking care to absorb input error
void FDatasmithHashTools::HashFixVector(const FVector& InVector, float InInvTolerance)
{
HashFixedPointFloatTolerance(InVector.X, InInvTolerance);
HashFixedPointFloatTolerance(InVector.Y, InInvTolerance);
HashFixedPointFloatTolerance(InVector.Z, InInvTolerance);
}
// Hash 3d vector value, taking care to absorb compute error
void FDatasmithHashTools::HashFloatVector(const FVector& InVector, float InTolerance)
{
HashFloatTolerance(InVector.X, InTolerance);
HashFloatTolerance(InVector.Y, InTolerance);
HashFloatTolerance(InVector.Z, InTolerance);
}
// Hash 3d scaling value, taking care to absorb compute error
void FDatasmithHashTools::HashScaleVector(const FVector& InScale, float InTolerance)
{
// Absorb negative scaling resulting of quad and matix operations
HashFloatTolerance(abs(InScale.X), InTolerance);
HashFloatTolerance(abs(InScale.Y), InTolerance);
HashFloatTolerance(abs(InScale.Z), InTolerance);
}
// Determine if this quaternions come from a operation with a non rotational matrix
static bool QuatNeglectible(FQuat InQuat, float InTolerance)
{
float t2 = InTolerance * InTolerance;
int NbZeros = 0;
if (InQuat.X * InQuat.X < t2)
{
++NbZeros;
}
if (InQuat.Y * InQuat.Y < t2)
{
++NbZeros;
}
if (InQuat.Z * InQuat.Z < t2)
{
++NbZeros;
}
if (InQuat.W * InQuat.W < t2)
{
++NbZeros;
}
return NbZeros > 1;
}
// Normalize quat components values
/* Quat components values usualy are in turn (-0.5..0.5).
* Bring back to 0.0 to 1.0 and make 0.0 turn == 1.0 turn
*/
static float ZeroOne(double x)
{
if (x < 0.0)
x = 1.0 + x;
return (float)FMath::Sqrt(FMath::Abs(x - x * x)); // = sqrt(abs(x * (1 - x))
}
// Hash quaternion value, taking care to absorb compute error
void FDatasmithHashTools::HashQuat(const FQuat& InQuat, float InTolerance)
{
if (QuatNeglectible(InQuat, InTolerance))
{
TUpdate(false);
return;
}
HashFloatTolerance(ZeroOne(InQuat.X), InTolerance);
HashFloatTolerance(ZeroOne(InQuat.Y), InTolerance);
HashFloatTolerance(ZeroOne(InQuat.Z), InTolerance);
HashFloatTolerance(ZeroOne(InQuat.W), InTolerance);
}
void FDatasmithHashTools::ComputeDatasmithMeshHash(const FDatasmithMesh& Mesh)
{
// If Datasmith change something to format on disk, we will increment this value to force new hash value
const uint32 DatasmithMeshVersion = 0;
TUpdate(DatasmithMeshVersion);
int32 VerticesCount = Mesh.GetVerticesCount();
TUpdate(VerticesCount);
for (int32 IdxVertice = 0; IdxVertice < VerticesCount; ++IdxVertice)
{
HashFixVector(FVector(Mesh.GetVertex(IdxVertice)));
TUpdate(Mesh.GetVertexColor(IdxVertice));
}
int32 UVChannelCount = Mesh.GetUVChannelsCount();
TUpdate(UVChannelCount);
for (int32 IdxChannel = 0; IdxChannel < UVChannelCount; ++IdxChannel)
{
int32 UVCount = Mesh.GetUVCount(IdxChannel);
TUpdate(UVCount);
for (int32 IdxUV = 0; IdxUV < UVCount; ++IdxUV)
{
FVector2D UV = Mesh.GetUV(IdxChannel, IdxUV);
HashFloatTolerance(UV.X);
HashFloatTolerance(UV.Y);
}
}
int32 FacesCount = Mesh.GetFacesCount();
TUpdate(FacesCount);
for (int32 IdxFace = 0; IdxFace < FacesCount; ++IdxFace)
{
int32 Vertex1;
int32 Vertex2;
int32 Vertex3;
int32 MaterialId;
Mesh.GetFace(IdxFace, Vertex1, Vertex2, Vertex3, MaterialId);
TUpdate(Vertex1);
TUpdate(Vertex2);
TUpdate(Vertex3);
TUpdate(MaterialId);
TUpdate(Mesh.GetFaceSmoothingMask(IdxFace));
for (int32 IdxComponent = 0; IdxComponent < 3; IdxComponent++)
{
HashFloatVector(FVector(Mesh.GetNormal(IdxFace * 3 + IdxComponent)));
}
for (int32 IdxChannel = 0; IdxChannel < UVChannelCount; ++IdxChannel)
{
Mesh.GetFaceUV(IdxFace, IdxChannel, Vertex1, Vertex2, Vertex3);
TUpdate(Vertex1);
TUpdate(Vertex2);
TUpdate(Vertex3);
}
}
TUpdate(Mesh.GetLightmapSourceUVChannel());
int32 LODsCount = Mesh.GetLODsCount();
TUpdate(LODsCount);
for (int32 IdxLOD = 0; IdxLOD < LODsCount; ++IdxLOD)
{
#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION == 26
ComputeDatasmithMeshHash(Mesh.GetLOD(IdxLOD));
#else
ComputeDatasmithMeshHash(*Mesh.GetLOD(IdxLOD));
#endif
}
}
END_NAMESPACE_UE_AC