466 lines
15 KiB
C++
466 lines
15 KiB
C++
/*
|
|
* Copyright (c) <2021> Side Effects Software Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. The name of Side Effects Software may not be used to endorse or
|
|
* promote products derived from this software without specific prior
|
|
* written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS
|
|
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
|
* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
|
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "HoudiniStaticMesh.h"
|
|
#include "HoudiniEngineRuntimePrivatePCH.h"
|
|
|
|
#include "Async/ParallelFor.h"
|
|
#include "MeshUtilitiesCommon.h"
|
|
|
|
UHoudiniStaticMesh::UHoudiniStaticMesh(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
bHasNormals = false;
|
|
bHasTangents = false;
|
|
bHasColors = false;
|
|
NumUVLayers = 0;
|
|
bHasPerFaceMaterials = false;
|
|
}
|
|
|
|
void UHoudiniStaticMesh::Initialize(uint32 InNumVertices, uint32 InNumTriangles, uint32 InNumUVLayers, uint32 InInitialNumStaticMaterials, bool bInHasNormals, bool bInHasTangents, bool bInHasColors, bool bInHasPerFaceMaterials)
|
|
{
|
|
// Initialize the vertex positions and triangle indices arrays
|
|
VertexPositions.Init(FVector::ZeroVector, InNumVertices);
|
|
TriangleIndices.Init(FIntVector(-1, -1, -1), InNumTriangles);
|
|
if (InInitialNumStaticMaterials > 0)
|
|
StaticMaterials.Init(FStaticMaterial(), InInitialNumStaticMaterials);
|
|
else
|
|
StaticMaterials.Empty();
|
|
|
|
SetNumUVLayers(InNumUVLayers);
|
|
SetHasNormals(bInHasNormals);
|
|
SetHasTangents(bInHasTangents);
|
|
SetHasColors(bInHasColors);
|
|
SetHasPerFaceMaterials(bInHasPerFaceMaterials);
|
|
}
|
|
|
|
void UHoudiniStaticMesh::SetHasPerFaceMaterials(bool bInHasPerFaceMaterials)
|
|
{
|
|
bHasPerFaceMaterials = bInHasPerFaceMaterials;
|
|
if (bHasPerFaceMaterials)
|
|
MaterialIDsPerTriangle.Init(-1, GetNumTriangles());
|
|
else
|
|
MaterialIDsPerTriangle.Empty();
|
|
}
|
|
|
|
void UHoudiniStaticMesh::SetHasNormals(bool bInHasNormals)
|
|
{
|
|
bHasNormals = bInHasNormals;
|
|
if (bHasNormals)
|
|
VertexInstanceNormals.Init(FVector(0, 0, 1), GetNumVertexInstances());
|
|
else
|
|
VertexInstanceNormals.Empty();
|
|
}
|
|
|
|
void UHoudiniStaticMesh::SetHasTangents(bool bInHasTangents)
|
|
{
|
|
bHasTangents = bInHasTangents;
|
|
if (bHasTangents)
|
|
{
|
|
VertexInstanceUTangents.Init(FVector(1, 0, 0), GetNumVertexInstances());
|
|
VertexInstanceVTangents.Init(FVector(0, 1, 0), GetNumVertexInstances());
|
|
}
|
|
else
|
|
{
|
|
VertexInstanceUTangents.Empty();
|
|
VertexInstanceVTangents.Empty();
|
|
}
|
|
}
|
|
|
|
void UHoudiniStaticMesh::SetHasColors(bool bInHasColors)
|
|
{
|
|
bHasColors = bInHasColors;
|
|
if (bHasColors)
|
|
VertexInstanceColors.Init(FColor(127, 127, 127), GetNumVertexInstances());
|
|
else
|
|
VertexInstanceColors.Empty();
|
|
}
|
|
|
|
void UHoudiniStaticMesh::SetNumUVLayers(uint32 InNumUVLayers)
|
|
{
|
|
NumUVLayers = InNumUVLayers;
|
|
if (NumUVLayers > 0)
|
|
VertexInstanceUVs.Init(FVector2D::ZeroVector, GetNumVertexInstances() * NumUVLayers);
|
|
else
|
|
VertexInstanceUVs.Empty();
|
|
}
|
|
|
|
void UHoudiniStaticMesh::SetNumStaticMaterials(uint32 InNumMaterials)
|
|
{
|
|
if (InNumMaterials > 0)
|
|
StaticMaterials.SetNum(InNumMaterials);
|
|
else
|
|
StaticMaterials.Empty();
|
|
}
|
|
|
|
void UHoudiniStaticMesh::SetVertexPosition(uint32 InVertexIndex, const FVector& InPosition)
|
|
{
|
|
check(VertexPositions.IsValidIndex(InVertexIndex));
|
|
|
|
VertexPositions[InVertexIndex] = InPosition;
|
|
}
|
|
|
|
void UHoudiniStaticMesh::SetTriangleVertexIndices(uint32 InTriangleIndex, const FIntVector& InTriangleVertexIndices)
|
|
{
|
|
check(TriangleIndices.IsValidIndex(InTriangleIndex));
|
|
check(VertexPositions.IsValidIndex(InTriangleVertexIndices[0]));
|
|
check(VertexPositions.IsValidIndex(InTriangleVertexIndices[1]));
|
|
check(VertexPositions.IsValidIndex(InTriangleVertexIndices[2]));
|
|
|
|
TriangleIndices[InTriangleIndex] = InTriangleVertexIndices;
|
|
}
|
|
|
|
void UHoudiniStaticMesh::SetTriangleVertexNormal(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InNormal)
|
|
{
|
|
if (!bHasNormals)
|
|
{
|
|
return;
|
|
}
|
|
|
|
check(TriangleIndices.IsValidIndex(InTriangleIndex));
|
|
const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex;
|
|
check(VertexInstanceNormals.IsValidIndex(VertexInstanceIndex));
|
|
|
|
VertexInstanceNormals[VertexInstanceIndex] = InNormal;
|
|
}
|
|
|
|
void UHoudiniStaticMesh::SetTriangleVertexUTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InUTangent)
|
|
{
|
|
if (!bHasTangents)
|
|
{
|
|
return;
|
|
}
|
|
|
|
check(TriangleIndices.IsValidIndex(InTriangleIndex));
|
|
const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex;
|
|
check(VertexInstanceUTangents.IsValidIndex(VertexInstanceIndex));
|
|
|
|
VertexInstanceUTangents[VertexInstanceIndex] = InUTangent;
|
|
}
|
|
|
|
void UHoudiniStaticMesh::SetTriangleVertexVTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InVTangent)
|
|
{
|
|
if (!bHasTangents)
|
|
{
|
|
return;
|
|
}
|
|
|
|
check(TriangleIndices.IsValidIndex(InTriangleIndex));
|
|
const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex;
|
|
check(VertexInstanceVTangents.IsValidIndex(VertexInstanceIndex));
|
|
|
|
VertexInstanceVTangents[VertexInstanceIndex] = InVTangent;
|
|
}
|
|
|
|
void UHoudiniStaticMesh::SetTriangleVertexColor(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FColor& InColor)
|
|
{
|
|
if (!bHasColors)
|
|
{
|
|
return;
|
|
}
|
|
|
|
check(TriangleIndices.IsValidIndex(InTriangleIndex));
|
|
const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex;
|
|
check(VertexInstanceColors.IsValidIndex(VertexInstanceIndex));
|
|
|
|
VertexInstanceColors[VertexInstanceIndex] = InColor;
|
|
}
|
|
|
|
void UHoudiniStaticMesh::SetTriangleVertexUV(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, uint8 InUVLayer, const FVector2D& InUV)
|
|
{
|
|
if (NumUVLayers <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
check(TriangleIndices.IsValidIndex(InTriangleIndex));
|
|
const uint32 VertexInstanceUVIndex = InUVLayer * GetNumVertexInstances() + InTriangleIndex * 3 + InTriangleVertexIndex;
|
|
check(VertexInstanceUVs.IsValidIndex(VertexInstanceUVIndex));
|
|
|
|
VertexInstanceUVs[VertexInstanceUVIndex] = InUV;
|
|
}
|
|
|
|
void UHoudiniStaticMesh::SetTriangleMaterialID(uint32 InTriangleIndex, int32 InMaterialID)
|
|
{
|
|
if (!bHasPerFaceMaterials)
|
|
{
|
|
return;
|
|
}
|
|
|
|
check(TriangleIndices.IsValidIndex(InTriangleIndex));
|
|
check(MaterialIDsPerTriangle.IsValidIndex(InTriangleIndex));
|
|
|
|
MaterialIDsPerTriangle[InTriangleIndex] = InMaterialID;
|
|
}
|
|
|
|
void UHoudiniStaticMesh::SetStaticMaterial(uint32 InMaterialIndex, const FStaticMaterial& InStaticMaterial)
|
|
{
|
|
check(StaticMaterials.IsValidIndex(InMaterialIndex));
|
|
StaticMaterials[InMaterialIndex] = InStaticMaterial;
|
|
}
|
|
|
|
void UHoudiniStaticMesh::CalculateNormals(bool bInComputeWeightedNormals)
|
|
{
|
|
const int32 NumVertexInstances = GetNumVertexInstances();
|
|
|
|
// Pre-allocate space in the vertex instance normals array
|
|
VertexInstanceNormals.SetNum(NumVertexInstances);
|
|
|
|
const int32 NumTriangles = GetNumTriangles();
|
|
const int32 NumVertices = GetNumVertices();
|
|
|
|
// Setup a vertex normal array
|
|
TArray<FVector> VertexNormals;
|
|
VertexNormals.SetNum(NumVertices);
|
|
|
|
// Zero all entries in VertexNormals
|
|
// for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex)
|
|
ParallelFor(NumVertices, [&VertexNormals](int32 VertexIndex)
|
|
{
|
|
VertexNormals[VertexIndex] = FVector::ZeroVector;
|
|
});
|
|
|
|
// Calculate face normals and sum them for each vertex that shares the triangle
|
|
// for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex)
|
|
ParallelFor(NumTriangles, [this, &VertexNormals, bInComputeWeightedNormals](int32 TriangleIndex)
|
|
{
|
|
const FIntVector& TriangleVertexIndices = TriangleIndices[TriangleIndex];
|
|
|
|
if (!VertexPositions.IsValidIndex(TriangleVertexIndices[0]) ||
|
|
!VertexPositions.IsValidIndex(TriangleVertexIndices[1]) ||
|
|
!VertexPositions.IsValidIndex(TriangleVertexIndices[2]))
|
|
{
|
|
HOUDINI_LOG_WARNING(
|
|
TEXT("[UHoudiniStaticMesh::CalculateNormals]: VertexPositions index out of range %d, %d, %d, Num %d"),
|
|
TriangleVertexIndices[0], TriangleVertexIndices[1], TriangleVertexIndices[2], VertexPositions.Num());
|
|
return;
|
|
}
|
|
|
|
const FVector& V0 = VertexPositions[TriangleVertexIndices[0]];
|
|
const FVector& V1 = VertexPositions[TriangleVertexIndices[1]];
|
|
const FVector& V2 = VertexPositions[TriangleVertexIndices[2]];
|
|
|
|
FVector TriangleNormal = FVector::CrossProduct(V2 - V0, V1 - V0);
|
|
float Area = TriangleNormal.Size();
|
|
TriangleNormal /= Area;
|
|
Area /= 2.0f;
|
|
|
|
const float Weight[3] = {
|
|
bInComputeWeightedNormals ? Area * TriangleUtilities::ComputeTriangleCornerAngle(V0, V1, V2) : 1.0f,
|
|
bInComputeWeightedNormals ? Area * TriangleUtilities::ComputeTriangleCornerAngle(V1, V2, V0) : 1.0f,
|
|
bInComputeWeightedNormals ? Area * TriangleUtilities::ComputeTriangleCornerAngle(V2, V0, V1) : 1.0f,
|
|
};
|
|
|
|
for (int CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
|
|
{
|
|
const FVector WeightedNormal = TriangleNormal * Weight[CornerIndex];
|
|
if (!WeightedNormal.IsNearlyZero(SMALL_NUMBER) && !WeightedNormal.ContainsNaN())
|
|
{
|
|
if (!VertexNormals.IsValidIndex(TriangleVertexIndices[CornerIndex]))
|
|
{
|
|
HOUDINI_LOG_WARNING(
|
|
TEXT("[UHoudiniStaticMesh::CalculateNormals]: VertexNormal index out of range %d, Num %d"),
|
|
TriangleVertexIndices[CornerIndex], VertexNormals.Num());
|
|
continue;
|
|
}
|
|
VertexNormals[TriangleVertexIndices[CornerIndex]] += WeightedNormal;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Normalize the vertex normals
|
|
// for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex)
|
|
ParallelFor(NumVertices, [&VertexNormals](int32 VertexIndex)
|
|
{
|
|
VertexNormals[VertexIndex].Normalize();
|
|
});
|
|
|
|
// Copy vertex normals to vertex instance normals
|
|
// for (int32 VertexInstanceIndex = 0; VertexInstanceIndex < NumVertexInstances; ++VertexInstanceIndex)
|
|
ParallelFor(NumVertexInstances, [this, &VertexNormals](int32 VertexInstanceIndex)
|
|
{
|
|
const int32 TriangleIndex = VertexInstanceIndex / 3;
|
|
const int32 CornerIndex = VertexInstanceIndex % 3;
|
|
const FIntVector& TriangleVertexIndices = TriangleIndices[TriangleIndex];
|
|
if (!VertexNormals.IsValidIndex(TriangleVertexIndices[CornerIndex]))
|
|
{
|
|
HOUDINI_LOG_WARNING(
|
|
TEXT("[UHoudiniStaticMesh::CalculateNormals]: VertexNormals index out of range %d, Num %d"),
|
|
TriangleVertexIndices[CornerIndex], VertexNormals.Num());
|
|
return;
|
|
}
|
|
VertexInstanceNormals[VertexInstanceIndex] = VertexNormals[TriangleVertexIndices[CornerIndex]];
|
|
});
|
|
|
|
bHasNormals = true;
|
|
}
|
|
|
|
void UHoudiniStaticMesh::CalculateTangents(bool bInComputeWeightedNormals)
|
|
{
|
|
const int32 NumVertexInstances = GetNumVertexInstances();
|
|
|
|
VertexInstanceUTangents.SetNum(NumVertexInstances);
|
|
VertexInstanceVTangents.SetNum(NumVertexInstances);
|
|
|
|
// Calculate normals first if we don't have any
|
|
if (!HasNormals() || VertexInstanceNormals.Num() != NumVertexInstances)
|
|
CalculateNormals(bInComputeWeightedNormals);
|
|
|
|
// for (int32 VertexInstanceIndex = 0; VertexInstanceIndex < NumVertexInstances; ++VertexInstanceIndex)
|
|
ParallelFor(NumVertexInstances, [this](int32 VertexInstanceIndex)
|
|
{
|
|
const FVector& Normal = VertexInstanceNormals[VertexInstanceIndex];
|
|
Normal.FindBestAxisVectors(
|
|
VertexInstanceUTangents[VertexInstanceIndex], VertexInstanceVTangents[VertexInstanceIndex]);
|
|
});
|
|
|
|
bHasTangents = true;
|
|
}
|
|
|
|
void UHoudiniStaticMesh::Optimize()
|
|
{
|
|
VertexPositions.Shrink();
|
|
TriangleIndices.Shrink();
|
|
VertexInstanceColors.Shrink();
|
|
VertexInstanceNormals.Shrink();
|
|
VertexInstanceUTangents.Shrink();
|
|
VertexInstanceVTangents.Shrink();
|
|
VertexInstanceUVs.Shrink();
|
|
MaterialIDsPerTriangle.Shrink();
|
|
StaticMaterials.Shrink();
|
|
}
|
|
|
|
FBox UHoudiniStaticMesh::CalcBounds() const
|
|
{
|
|
const uint32 NumVertices = VertexPositions.Num();
|
|
|
|
if (NumVertices == 0)
|
|
return FBox();
|
|
|
|
const FVector InitPosition = VertexPositions[0];
|
|
double MinX = InitPosition.X, MaxX = InitPosition.X, MinY = InitPosition.Y, MaxY = InitPosition.Y, MinZ = InitPosition.Z, MaxZ = InitPosition.Z;
|
|
for (uint32 VertIdx = 0; VertIdx < NumVertices; ++VertIdx)
|
|
{
|
|
const FVector Position = VertexPositions[VertIdx];
|
|
if (Position.X < MinX) MinX = Position.X; else if (Position.X > MaxX) MaxX = Position.X;
|
|
if (Position.Y < MinY) MinY = Position.Y; else if (Position.Y > MaxY) MaxY = Position.Y;
|
|
if (Position.Z < MinZ) MinZ = Position.Z; else if (Position.Z > MaxZ) MaxZ = Position.Z;
|
|
}
|
|
|
|
return FBox(FVector(MinX, MinY, MinZ), FVector(MaxX, MaxY, MaxZ));
|
|
}
|
|
|
|
UMaterialInterface* UHoudiniStaticMesh::GetMaterial(int32 InMaterialIndex)
|
|
{
|
|
check(StaticMaterials.IsValidIndex(InMaterialIndex));
|
|
|
|
return StaticMaterials[InMaterialIndex].MaterialInterface;
|
|
}
|
|
|
|
int32 UHoudiniStaticMesh::GetMaterialIndex(FName InMaterialSlotName) const
|
|
{
|
|
if (InMaterialSlotName == NAME_None)
|
|
return -1;
|
|
|
|
const uint32 NumMaterials = StaticMaterials.Num();
|
|
for (uint32 MaterialIndex = 0; MaterialIndex < NumMaterials; ++MaterialIndex)
|
|
{
|
|
if (StaticMaterials[MaterialIndex].MaterialSlotName == InMaterialSlotName)
|
|
return (int32)MaterialIndex;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
bool UHoudiniStaticMesh::IsValid(bool bInSkipVertexIndicesCheck) const
|
|
{
|
|
// Validate the number of vertices, indices and triangles. This is basically the same function as FRawMesh::IsValid()
|
|
const int32 NumVertices = GetNumVertices();
|
|
const int32 NumVertexInstances = GetNumVertexInstances();
|
|
const int32 NumTriangles = GetNumTriangles();
|
|
|
|
auto ValidateAttributeArraySize = [](int32 InArrayNum, int32 InExpectedSize)
|
|
{
|
|
return InArrayNum == 0 || InArrayNum == InExpectedSize;
|
|
};
|
|
|
|
bool bValid = NumVertices > 0
|
|
&& NumVertexInstances > 0
|
|
&& NumTriangles > 0
|
|
&& (NumVertexInstances / 3) == NumTriangles
|
|
&& ValidateAttributeArraySize(MaterialIDsPerTriangle.Num(), NumTriangles)
|
|
&& ValidateAttributeArraySize(VertexInstanceNormals.Num(), NumVertexInstances)
|
|
&& ValidateAttributeArraySize(VertexInstanceUTangents.Num(), NumVertexInstances)
|
|
&& ValidateAttributeArraySize(VertexInstanceVTangents.Num(), NumVertexInstances)
|
|
&& ValidateAttributeArraySize(VertexInstanceColors.Num(), NumVertexInstances)
|
|
&& NumUVLayers >= 0
|
|
&& VertexInstanceUVs.Num() == NumUVLayers * NumVertexInstances;
|
|
|
|
if (!bInSkipVertexIndicesCheck)
|
|
{
|
|
int32 TriangleIndex = 0;
|
|
while (bValid && TriangleIndex < NumTriangles)
|
|
{
|
|
bValid = bValid && (TriangleIndices[TriangleIndex].X < NumVertices);
|
|
bValid = bValid && (TriangleIndices[TriangleIndex].Y < NumVertices);
|
|
bValid = bValid && (TriangleIndices[TriangleIndex].Z < NumVertices);
|
|
TriangleIndex++;
|
|
}
|
|
}
|
|
|
|
return bValid;
|
|
}
|
|
|
|
void UHoudiniStaticMesh::Serialize(FArchive &InArchive)
|
|
{
|
|
Super::Serialize(InArchive);
|
|
|
|
VertexPositions.Shrink();
|
|
VertexPositions.BulkSerialize(InArchive);
|
|
|
|
TriangleIndices.Shrink();
|
|
TriangleIndices.BulkSerialize(InArchive);
|
|
|
|
VertexInstanceColors.Shrink();
|
|
VertexInstanceColors.BulkSerialize(InArchive);
|
|
|
|
VertexInstanceNormals.Shrink();
|
|
VertexInstanceNormals.BulkSerialize(InArchive);
|
|
|
|
VertexInstanceUTangents.Shrink();
|
|
VertexInstanceUTangents.BulkSerialize(InArchive);
|
|
|
|
VertexInstanceVTangents.Shrink();
|
|
VertexInstanceVTangents.BulkSerialize(InArchive);
|
|
|
|
VertexInstanceUVs.Shrink();
|
|
VertexInstanceUVs.BulkSerialize(InArchive);
|
|
|
|
MaterialIDsPerTriangle.Shrink();
|
|
MaterialIDsPerTriangle.BulkSerialize(InArchive);
|
|
}
|
|
|