You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2502 lines
93 KiB
C++
2502 lines
93 KiB
C++
/*
|
|
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
|
* its licensors.
|
|
*
|
|
* For complete copyright and license terms please see the LICENSE at the root of this
|
|
* distribution (the "License"). All use of this software is governed by the License,
|
|
* or, if provided, by the license below or the license accompanying this file. Do not
|
|
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
*
|
|
*/
|
|
// Original file Copyright Crytek GMBH or its affiliates, used under license.
|
|
|
|
#include "ResourceCompilerABC_precompiled.h"
|
|
#include "AlembicCompiler.h"
|
|
#include "GeomCacheEncoder.h"
|
|
#include "StringHelpers.h"
|
|
#include "IResCompiler.h"
|
|
#include "IXml.h"
|
|
#include "GeomCacheWriter.h"
|
|
#include "UpToDateFileHelpers.h"
|
|
#include "../../CryXML/ICryXML.h"
|
|
#include "../../CryXML/IXMLSerializer.h"
|
|
#include "ForsythFaceReorderer.h" // for ForsythFaceReorderer
|
|
#include "TangentSpaceCalculation.h" // for CTangentSpaceCalculation
|
|
#include "StringUtils.h"
|
|
#include "CryPath.h"
|
|
|
|
#include <AzCore/std/parallel/lock.h>
|
|
|
|
namespace
|
|
{
|
|
QuatTNS FromAlembicMatrix(const Alembic::AbcGeom::M44d& abcMatrix)
|
|
{
|
|
Matrix34 matrix;
|
|
|
|
matrix.m00 = (f32)abcMatrix.x[0][0];
|
|
matrix.m10 = (f32)abcMatrix.x[0][1];
|
|
matrix.m20 = (f32)abcMatrix.x[0][2];
|
|
|
|
matrix.m01 = (f32)abcMatrix.x[1][0];
|
|
matrix.m11 = (f32)abcMatrix.x[1][1];
|
|
matrix.m21 = (f32)abcMatrix.x[1][2];
|
|
|
|
matrix.m02 = (f32)abcMatrix.x[2][0];
|
|
matrix.m12 = (f32)abcMatrix.x[2][1];
|
|
matrix.m22 = (f32)abcMatrix.x[2][2];
|
|
|
|
matrix.m03 = (f32)abcMatrix.x[3][0] / 100.0f;
|
|
matrix.m13 = (f32)abcMatrix.x[3][1] / 100.0f;
|
|
matrix.m23 = (f32)abcMatrix.x[3][2] / 100.0f;
|
|
|
|
return QuatTNS(matrix);
|
|
}
|
|
|
|
Vec3 FromAlembicPosition(const Imath::Vec3<float>& abcPosition)
|
|
{
|
|
Vec3 position;
|
|
position[0] = abcPosition.x / 100.0f;
|
|
position[1] = abcPosition.y / 100.0f;
|
|
position[2] = abcPosition.z / 100.0f;
|
|
return position;
|
|
}
|
|
|
|
Vec4 FromAlembicColor(const Imath::Color3<float>& abcColor)
|
|
{
|
|
Vec4 color;
|
|
color[0] = abcColor.x;
|
|
color[1] = abcColor.y;
|
|
color[2] = abcColor.z;
|
|
color[3] = 0.0f;
|
|
return color;
|
|
}
|
|
|
|
Vec4 FromAlembicColor(const Imath::Color4<float>& abcColor)
|
|
{
|
|
Vec4 color;
|
|
color[0] = abcColor.r;
|
|
color[1] = abcColor.g;
|
|
color[2] = abcColor.b;
|
|
color[3] = abcColor.a;
|
|
return color;
|
|
}
|
|
|
|
Vec2 FromAlembicTexcoord(const Imath::Vec2<float>& abcTexcoord)
|
|
{
|
|
Vec2 texcoord;
|
|
texcoord.x = abcTexcoord.x;
|
|
texcoord.y = -abcTexcoord.y + 1.0f;
|
|
return texcoord;
|
|
}
|
|
|
|
class GeomCacheMeshTriangleInputProxy
|
|
: public ITriangleInputProxy
|
|
{
|
|
public:
|
|
GeomCacheMeshTriangleInputProxy(const std::vector<uint32>& indices, const std::vector<AlembicCompilerVertex>& vertices)
|
|
: m_indices(indices)
|
|
, m_vertices(vertices)
|
|
{
|
|
assert(m_indices.size() % 3 == 0);
|
|
}
|
|
|
|
virtual uint32 GetTriangleCount() const
|
|
{
|
|
return m_indices.size() / 3;
|
|
}
|
|
|
|
virtual void GetTriangleIndices(const uint32 indwTriNo, uint32 outdwPos[3], uint32 outdwNorm[3], uint32 outdwUV[3]) const
|
|
{
|
|
const uint32 indices[3] =
|
|
{
|
|
m_indices[indwTriNo * 3],
|
|
m_indices[indwTriNo * 3 + 1],
|
|
m_indices[indwTriNo * 3 + 2]
|
|
};
|
|
|
|
for (uint i = 0; i < 3; ++i)
|
|
{
|
|
// All attributes of one vertex share the same index
|
|
outdwPos[i] = indices[i];
|
|
outdwNorm[i] = indices[i];
|
|
outdwUV[i] = indices[i];
|
|
}
|
|
}
|
|
|
|
virtual void GetPos(const uint32 indwPos, Vec3& outfPos) const
|
|
{
|
|
outfPos[0] = m_vertices[indwPos].m_position[0];
|
|
outfPos[1] = m_vertices[indwPos].m_position[1];
|
|
outfPos[2] = m_vertices[indwPos].m_position[2];
|
|
}
|
|
|
|
virtual void GetUV(const uint32 indwPos, Vec2& outfUV) const
|
|
{
|
|
outfUV[0] = m_vertices[indwPos].m_texcoords[0];
|
|
outfUV[1] = m_vertices[indwPos].m_texcoords[1];
|
|
}
|
|
|
|
virtual void GetNorm(const uint32 indwTriNo, const uint32 indwVertNo, Vec3& outfNorm) const
|
|
{
|
|
const uint32 index = m_indices[indwTriNo * 3 + indwVertNo];
|
|
outfNorm[0] = m_vertices[index].m_normal[0];
|
|
outfNorm[1] = m_vertices[index].m_normal[1];
|
|
outfNorm[2] = m_vertices[index].m_normal[2];
|
|
}
|
|
|
|
private:
|
|
const std::vector<uint32>& m_indices;
|
|
const std::vector<AlembicCompilerVertex>& m_vertices;
|
|
};
|
|
|
|
// Helper class to wrap different color array types
|
|
class AlembicColorSampleArray
|
|
{
|
|
public:
|
|
AlembicColorSampleArray()
|
|
: pColorSamplesC3h(0)
|
|
, pColorSamplesC3f(0)
|
|
, pColorSamplesC3c(0)
|
|
, pColorSamplesC4h(0)
|
|
, pColorSamplesC4f(0)
|
|
, pColorSamplesC4c(0)
|
|
, pColorIndices(0) {}
|
|
|
|
AlembicColorSampleArray(const std::string& colorParamName, Alembic::AbcGeom::IPolyMeshSchema& meshSchema, Alembic::Abc::index_t index)
|
|
: pColorSamplesC3h(0)
|
|
, pColorSamplesC3f(0)
|
|
, pColorSamplesC3c(0)
|
|
, pColorSamplesC4h(0)
|
|
, pColorSamplesC4f(0)
|
|
, pColorSamplesC4c(0)
|
|
{
|
|
auto arbGeomParams = meshSchema.getArbGeomParams();
|
|
|
|
if (arbGeomParams)
|
|
{
|
|
const Alembic::Abc::PropertyHeader& propertyHeader = *arbGeomParams.getPropertyHeader(colorParamName);
|
|
|
|
if (Alembic::AbcGeom::IC3hGeomParam::matches(propertyHeader))
|
|
{
|
|
Alembic::AbcGeom::IC3hGeomParam param(arbGeomParams, colorParamName);
|
|
Alembic::AbcGeom::IC3hGeomParam::Sample colorSample;
|
|
param.getIndexed(colorSample, index);
|
|
pColorSamplesC3h = colorSample.getVals();
|
|
pColorIndices = colorSample.getIndices();
|
|
}
|
|
else if (Alembic::AbcGeom::IC3fGeomParam::matches(propertyHeader))
|
|
{
|
|
Alembic::AbcGeom::IC3fGeomParam param(arbGeomParams, colorParamName);
|
|
Alembic::AbcGeom::IC3fGeomParam::Sample colorSample;
|
|
param.getIndexed(colorSample, index);
|
|
pColorSamplesC3f = colorSample.getVals();
|
|
pColorIndices = colorSample.getIndices();
|
|
}
|
|
else if (Alembic::AbcGeom::IC3cGeomParam::matches(propertyHeader))
|
|
{
|
|
Alembic::AbcGeom::IC3cGeomParam param(arbGeomParams, colorParamName);
|
|
Alembic::AbcGeom::IC3cGeomParam::Sample colorSample;
|
|
param.getIndexed(colorSample, index);
|
|
pColorSamplesC3c = colorSample.getVals();
|
|
pColorIndices = colorSample.getIndices();
|
|
}
|
|
else if (Alembic::AbcGeom::IC4hGeomParam::matches(propertyHeader))
|
|
{
|
|
Alembic::AbcGeom::IC4hGeomParam param(arbGeomParams, colorParamName);
|
|
Alembic::AbcGeom::IC4hGeomParam::Sample colorSample;
|
|
param.getIndexed(colorSample, index);
|
|
pColorSamplesC4h = colorSample.getVals();
|
|
pColorIndices = colorSample.getIndices();
|
|
}
|
|
else if (Alembic::AbcGeom::IC4fGeomParam::matches(propertyHeader))
|
|
{
|
|
Alembic::AbcGeom::IC4fGeomParam param(arbGeomParams, colorParamName);
|
|
Alembic::AbcGeom::IC4fGeomParam::Sample colorSample;
|
|
param.getIndexed(colorSample, index);
|
|
pColorSamplesC4f = colorSample.getVals();
|
|
pColorIndices = colorSample.getIndices();
|
|
}
|
|
else if (Alembic::AbcGeom::IC4cGeomParam::matches(propertyHeader))
|
|
{
|
|
Alembic::AbcGeom::IC4cGeomParam param(arbGeomParams, colorParamName);
|
|
Alembic::AbcGeom::IC4cGeomParam::Sample colorSample;
|
|
param.getIndexed(colorSample, index);
|
|
pColorSamplesC4c = colorSample.getVals();
|
|
pColorIndices = colorSample.getIndices();
|
|
}
|
|
}
|
|
}
|
|
|
|
int32_t getIndex(int32_t currentIndexArraysIndex) const
|
|
{
|
|
if (pColorIndices)
|
|
{
|
|
return (*pColorIndices)[currentIndexArraysIndex];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
size_t GetSize() const
|
|
{
|
|
if (pColorSamplesC3h)
|
|
{
|
|
return pColorSamplesC3h->size();
|
|
}
|
|
else if (pColorSamplesC3f)
|
|
{
|
|
return pColorSamplesC3f->size();
|
|
}
|
|
else if (pColorSamplesC3c)
|
|
{
|
|
return pColorSamplesC3c->size();
|
|
}
|
|
if (pColorSamplesC4h)
|
|
{
|
|
return pColorSamplesC4h->size();
|
|
}
|
|
else if (pColorSamplesC4f)
|
|
{
|
|
return pColorSamplesC4f->size();
|
|
}
|
|
else if (pColorSamplesC4c)
|
|
{
|
|
return pColorSamplesC4c->size();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
size_t GetNumIndices() const
|
|
{
|
|
return pColorIndices->size();
|
|
}
|
|
|
|
Vec4 operator[](const size_t index) const
|
|
{
|
|
if (pColorSamplesC3h)
|
|
{
|
|
return FromAlembicColor((*pColorSamplesC3h)[index]);
|
|
}
|
|
else if (pColorSamplesC3f)
|
|
{
|
|
return FromAlembicColor((*pColorSamplesC3f)[index]);
|
|
}
|
|
else if (pColorSamplesC3c)
|
|
{
|
|
return FromAlembicColor((*pColorSamplesC3c)[index]);
|
|
}
|
|
else if (pColorSamplesC4h)
|
|
{
|
|
return FromAlembicColor((*pColorSamplesC4h)[index]);
|
|
}
|
|
else if (pColorSamplesC4f)
|
|
{
|
|
return FromAlembicColor((*pColorSamplesC4f)[index]);
|
|
}
|
|
else if (pColorSamplesC4c)
|
|
{
|
|
return FromAlembicColor((*pColorSamplesC4c)[index]);
|
|
}
|
|
|
|
return Vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
|
}
|
|
|
|
private:
|
|
Alembic::Abc::C3hArraySamplePtr pColorSamplesC3h;
|
|
Alembic::Abc::C3fArraySamplePtr pColorSamplesC3f;
|
|
Alembic::Abc::C3cArraySamplePtr pColorSamplesC3c;
|
|
Alembic::Abc::C4hArraySamplePtr pColorSamplesC4h;
|
|
Alembic::Abc::C4fArraySamplePtr pColorSamplesC4f;
|
|
Alembic::Abc::C4cArraySamplePtr pColorSamplesC4c;
|
|
Alembic::Abc::UInt32ArraySamplePtr pColorIndices;
|
|
};
|
|
}
|
|
|
|
AlembicMeshDigest::AlembicMeshDigest(Alembic::AbcGeom::IPolyMeshSchema& meshSchema)
|
|
: m_bHasNormals(meshSchema.getNormalsParam().valid())
|
|
, m_bHasTexcoords(meshSchema.getUVsParam().valid())
|
|
, m_bHasColors(false)
|
|
{
|
|
Alembic::AbcGeom::ArraySampleKey positionDigest;
|
|
meshSchema.getPositionsProperty().getKey(positionDigest);
|
|
|
|
Alembic::AbcGeom::ArraySampleKey positionIndexDigest;
|
|
meshSchema.getFaceIndicesProperty().getKey(positionIndexDigest);
|
|
|
|
m_positionDigest = positionDigest;
|
|
m_positionIndexDigest = positionIndexDigest;
|
|
|
|
if (m_bHasNormals)
|
|
{
|
|
Alembic::AbcGeom::ArraySampleKey normalDigest;
|
|
meshSchema.getNormalsParam().getValueProperty().getKey(normalDigest);
|
|
m_normalsDigest = normalDigest;
|
|
}
|
|
|
|
if (m_bHasTexcoords)
|
|
{
|
|
Alembic::AbcGeom::ArraySampleKey texcoordDigest;
|
|
meshSchema.getUVsParam().getValueProperty().getKey(texcoordDigest);
|
|
m_texcoordDigest = texcoordDigest;
|
|
}
|
|
|
|
Alembic::AbcGeom::ArraySampleKey colorDigest;
|
|
auto arbParams = meshSchema.getArbGeomParams();
|
|
if (arbParams)
|
|
{
|
|
const uint numProperties = arbParams.getNumProperties();
|
|
for (uint i = 0; i < numProperties; ++i)
|
|
{
|
|
auto& propertyHeader = arbParams.getPropertyHeader(i);
|
|
const std::string& colorParamName = propertyHeader.getName();
|
|
|
|
if (Alembic::AbcGeom::IC3hGeomParam::matches(propertyHeader))
|
|
{
|
|
m_bHasColors = true;
|
|
Alembic::AbcGeom::IC3hGeomParam param(arbParams, colorParamName);
|
|
param.getValueProperty().getKey(colorDigest);
|
|
}
|
|
else if (Alembic::AbcGeom::IC3fGeomParam::matches(propertyHeader))
|
|
{
|
|
m_bHasColors = true;
|
|
Alembic::AbcGeom::IC3fGeomParam param(arbParams, colorParamName);
|
|
param.getValueProperty().getKey(colorDigest);
|
|
}
|
|
else if (Alembic::AbcGeom::IC3cGeomParam::matches(propertyHeader))
|
|
{
|
|
m_bHasColors = true;
|
|
Alembic::AbcGeom::IC3cGeomParam param(arbParams, colorParamName);
|
|
param.getValueProperty().getKey(colorDigest);
|
|
}
|
|
else if (Alembic::AbcGeom::IC4hGeomParam::matches(propertyHeader))
|
|
{
|
|
m_bHasColors = true;
|
|
Alembic::AbcGeom::IC4hGeomParam param(arbParams, colorParamName);
|
|
param.getValueProperty().getKey(colorDigest);
|
|
}
|
|
else if (Alembic::AbcGeom::IC4fGeomParam::matches(propertyHeader))
|
|
{
|
|
m_bHasColors = true;
|
|
Alembic::AbcGeom::IC4fGeomParam param(arbParams, colorParamName);
|
|
param.getValueProperty().getKey(colorDigest);
|
|
}
|
|
else if (Alembic::AbcGeom::IC4cGeomParam::matches(propertyHeader))
|
|
{
|
|
m_bHasColors = true;
|
|
Alembic::AbcGeom::IC4cGeomParam param(arbParams, colorParamName);
|
|
param.getValueProperty().getKey(colorDigest);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool AlembicMeshDigest::operator==(const AlembicMeshDigest& digest) const
|
|
{
|
|
if (m_bHasNormals != digest.m_bHasNormals)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (m_bHasTexcoords != digest.m_bHasTexcoords)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (m_bHasColors != digest.m_bHasColors)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (m_bHasNormals && m_normalsDigest != digest.m_normalsDigest)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (m_bHasTexcoords && m_texcoordDigest != digest.m_texcoordDigest)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (m_bHasColors && m_colorsDigest != digest.m_colorsDigest)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return m_positionDigest == digest.m_positionDigest
|
|
&& m_positionIndexDigest == digest.m_positionIndexDigest;
|
|
}
|
|
|
|
AlembicCompiler::AlembicCompiler(ICryXML* pXMLParser)
|
|
: m_pXMLParser(pXMLParser)
|
|
, m_refCount(1)
|
|
, m_errorCount(0)
|
|
, m_numAnimatedMeshes(0)
|
|
, m_b32BitIndices(false)
|
|
{
|
|
m_rootNode.m_type = GeomCacheFile::eNodeType_Transform;
|
|
m_rootNode.m_transformType = GeomCacheFile::eTransformType_Constant;
|
|
m_rootNode.m_staticNodeData.m_bVisible = true;
|
|
m_rootNode.m_staticNodeData.m_transform.SetIdentity();
|
|
m_uvMax = RC_ABC_AUTOMATIC_UVMAX_DETECTION_VALUE;
|
|
}
|
|
|
|
void AlembicCompiler::Release()
|
|
{
|
|
if (--m_refCount <= 0)
|
|
{
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
bool AlembicCompiler::Process()
|
|
{
|
|
const string& sourcePath = m_CC.GetSourcePath();
|
|
|
|
if (!m_CC.m_bForceRecompiling && UpToDateFileHelpers::FileExistsAndUpToDate(GetOutputPath(), sourcePath))
|
|
{
|
|
// The file is up-to-date
|
|
m_CC.m_pRC->AddInputOutputFilePair(sourcePath, GetOutputPath());
|
|
return true;
|
|
}
|
|
|
|
// Open archive
|
|
RCLog(string("Beginning to open archive: ") + sourcePath.c_str());
|
|
|
|
Alembic::AbcCoreFactory::IFactory factory;
|
|
factory.setPolicy(Alembic::Abc::ErrorHandler::kQuietNoopPolicy);
|
|
Alembic::Abc::IArchive archive = factory.getArchive(sourcePath.c_str());
|
|
|
|
if (!archive.valid())
|
|
{
|
|
RCLogError("Not a valid alembic file.");
|
|
Cleanup();
|
|
return false;
|
|
}
|
|
|
|
std::string appName;
|
|
std::string libraryVersionString;
|
|
uint32 libraryVersion;
|
|
std::string whenWritten;
|
|
std::string userDescription;
|
|
|
|
Alembic::Abc::GetArchiveInfo(archive, appName, libraryVersionString, libraryVersion, whenWritten, userDescription);
|
|
if (appName != "")
|
|
{
|
|
RCLog(string(" File written by: ") + appName.c_str());
|
|
RCLog(string(" Using alembic version: ") + libraryVersionString.c_str());
|
|
RCLog(string(" Written on: ") + whenWritten.c_str());
|
|
RCLog(string(" User description: ") + userDescription.c_str());
|
|
}
|
|
|
|
IXMLSerializer* pXMLSerializer = m_pXMLParser->GetXMLSerializer();
|
|
const string configPath = PathUtil::ReplaceExtension(sourcePath, "cbc");
|
|
XmlNodeRef config = ReadConfig(configPath, pXMLSerializer);
|
|
|
|
if (!config)
|
|
{
|
|
Cleanup();
|
|
return false;
|
|
}
|
|
|
|
const string s = m_CC.m_config->GetAsString("VertexIndexFormat", "u16", "u16");
|
|
m_b32BitIndices = StringHelpers::EqualsIgnoreCase(s, "u32");
|
|
|
|
// Reset stats
|
|
m_numExportedMeshes = 0;
|
|
m_numVertexSplits = 0;
|
|
m_numSharedMeshNodes = 0;
|
|
|
|
// Check time sampling
|
|
if (!CheckTimeSampling(archive))
|
|
{
|
|
Cleanup();
|
|
return false;
|
|
}
|
|
|
|
// Overwrite export file name if specified by command line
|
|
string exportFileName = GetOutputPath();
|
|
const size_t numFrames = m_frameTimes.size();
|
|
GeomCacheWriter geomCacheWriter(exportFileName, m_blockCompressionFormat, numFrames, m_bPlaybackFromMemory, m_b32BitIndices);
|
|
GeomCacheEncoder geomCacheEncoder(geomCacheWriter, m_rootNode, m_meshes, m_bUseBFrames, m_indexFrameDistance);
|
|
|
|
// Export static data (create mesh topologies etc.)
|
|
if (!CompileStaticData(archive))
|
|
{
|
|
Cleanup();
|
|
return false;
|
|
}
|
|
|
|
const int verbosityLevel = m_CC.m_config->HasKey("verbose") ? m_CC.m_config->GetAsInt("verbose", 1, 1) : 0;
|
|
if (verbosityLevel > 0)
|
|
{
|
|
RCLog("Compiled node tree:");
|
|
PrintNodeTreeRec(m_rootNode, "");
|
|
}
|
|
|
|
// Normalize frame times (first frame is always 0.0f)
|
|
std::vector<Alembic::Abc::chrono_t> normalizedFrameTimes;
|
|
const Alembic::Abc::chrono_t firstFrameTime = m_frameTimes.front();
|
|
for (uint i = 0; i < m_frameTimes.size(); ++i)
|
|
{
|
|
normalizedFrameTimes.push_back(m_frameTimes[i] - firstFrameTime);
|
|
}
|
|
|
|
geomCacheWriter.WriteStaticData(normalizedFrameTimes, m_meshes, m_rootNode);
|
|
|
|
// Export animated data (frames)
|
|
geomCacheEncoder.Init();
|
|
if (!CompileAnimationData(archive, geomCacheEncoder))
|
|
{
|
|
Cleanup();
|
|
return false;
|
|
}
|
|
|
|
GeomCacheWriterStats stats = geomCacheWriter.FinishWriting();
|
|
|
|
const Alembic::Abc::chrono_t sequenceLength = m_frameTimes.back() - m_frameTimes.front();
|
|
const double headerDataMegaBytes = static_cast<double>(stats.m_headerDataSize) / (1024.0 * 1024.0);
|
|
const double staticDataMegaBytes = static_cast<double>(stats.m_staticDataSize) / (1024.0 * 1024.0);
|
|
const double animationDataMegaBytes = static_cast<double>(stats.m_animationDataSize) / (1024.0 * 1024.0);
|
|
|
|
RCLog("Stats");
|
|
RCLog(" %.2f MiB header data", headerDataMegaBytes);
|
|
RCLog(" %.2f MiB static data", staticDataMegaBytes);
|
|
RCLog(" %.2f MiB animation data", animationDataMegaBytes);
|
|
|
|
if (stats.m_uncompressedAnimationSize > 0)
|
|
{
|
|
const double compressionRate = static_cast<double>(stats.m_animationDataSize) / static_cast<double>(stats.m_uncompressedAnimationSize) * 100.0f;
|
|
RCLog(" Compression ratio: %.1f%%", compressionRate);
|
|
}
|
|
|
|
if (sequenceLength > 0.0)
|
|
{
|
|
RCLog(" Average data rate: %.2f MiB/s", (double)animationDataMegaBytes / sequenceLength);
|
|
}
|
|
|
|
Cleanup();
|
|
|
|
if (!UpToDateFileHelpers::SetMatchingFileTime(GetOutputPath(), sourcePath))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
m_CC.m_pRC->AddInputOutputFilePair(sourcePath, GetOutputPath());
|
|
|
|
return true;
|
|
}
|
|
|
|
string AlembicCompiler::GetOutputFileNameOnly() const
|
|
{
|
|
const string sourceFileFinal = m_CC.m_config->GetAsString("overwritefilename", m_CC.m_sourceFileNameOnly.c_str(), m_CC.m_sourceFileNameOnly.c_str());
|
|
return PathHelpers::ReplaceExtension(sourceFileFinal, CRY_GEOM_CACHE_FILE_EXT);
|
|
}
|
|
|
|
string AlembicCompiler::GetOutputPath() const
|
|
{
|
|
return PathHelpers::Join(m_CC.GetOutputFolder(), GetOutputFileNameOnly());
|
|
}
|
|
|
|
bool AlembicCompiler::CheckTimeSampling(Alembic::Abc::IArchive& archive)
|
|
{
|
|
RCLog("Checking scene time sampling...");
|
|
|
|
m_minTime = std::numeric_limits<Alembic::Abc::chrono_t>::max();
|
|
m_maxTime = -std::numeric_limits<Alembic::Abc::chrono_t>::max();
|
|
|
|
CheckTimeSamplingRec(archive.getTop());
|
|
|
|
const uint numTimeSamplings = m_timeSamplings.size();
|
|
if (m_minTime >= m_maxTime)
|
|
{
|
|
RCLogWarning(" Scene is constant");
|
|
m_minTime = 0.0;
|
|
m_maxTime = 0.0;
|
|
m_frameTimes.push_back(0.0);
|
|
}
|
|
else if (numTimeSamplings == 1)
|
|
{
|
|
const Alembic::Abc::TimeSamplingType& timeSamplingType = m_timeSamplings[0].getTimeSamplingType();
|
|
OutputTimeSamplingType(timeSamplingType);
|
|
|
|
const size_t numSamplesPerCycles = timeSamplingType.getNumSamplesPerCycle();
|
|
const Alembic::Abc::chrono_t timePerCycle = timeSamplingType.getTimePerCycle();
|
|
const size_t numCycles = (timePerCycle > 0.0f) ? (size_t)ceil((m_maxTime - m_minTime) / timePerCycle) : 0;
|
|
|
|
for (size_t cycle = 0; cycle < numCycles; ++cycle)
|
|
{
|
|
for (size_t sample = 0; sample < numSamplesPerCycles; ++sample)
|
|
{
|
|
m_frameTimes.push_back(cycle * timePerCycle + m_timeSamplings[0].getSampleTime(sample));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RCLogWarning(" Found %d different time samplings. Will bake scene to fixed 30 FPS.", numTimeSamplings);
|
|
|
|
Alembic::Abc::chrono_t frameTime = 1.0 / 30.0;
|
|
const size_t numFrames = (size_t)ceil((m_maxTime - m_minTime) / frameTime) + 1;
|
|
for (size_t i = 0; i < numFrames; ++i)
|
|
{
|
|
m_frameTimes.push_back(i + frameTime);
|
|
}
|
|
|
|
for (uint i = 0; i < numTimeSamplings; ++i)
|
|
{
|
|
const Alembic::Abc::TimeSamplingType& timeSamplingType = m_timeSamplings[i].getTimeSamplingType();
|
|
OutputTimeSamplingType(timeSamplingType);
|
|
}
|
|
}
|
|
|
|
m_timeSamplings.clear();
|
|
|
|
RCLog(" Min time in Alembic is %g seconds, max time is %g seconds.", m_minTime, m_maxTime);
|
|
RCLog(" Exporting %Iu frames", m_frameTimes.size());
|
|
|
|
return true;
|
|
}
|
|
|
|
void AlembicCompiler::CheckTimeSamplingRec(const Alembic::Abc::IObject& currentObject)
|
|
{
|
|
const Alembic::Abc::ICompoundProperty& compoundProperty = currentObject.getProperties();
|
|
CheckTimeSamplingRec(compoundProperty);
|
|
|
|
const size_t numChildren = currentObject.getNumChildren();
|
|
for (size_t i = 0; i < numChildren; ++i)
|
|
{
|
|
CheckTimeSamplingRec(currentObject.getChild(i));
|
|
}
|
|
}
|
|
|
|
void AlembicCompiler::CheckTimeSamplingRec(const Alembic::Abc::ICompoundProperty& currentProperty)
|
|
{
|
|
const size_t numProperties = currentProperty.getNumProperties();
|
|
for (size_t i = 0; i < numProperties; ++i)
|
|
{
|
|
Alembic::Abc::PropertyHeader propertyHeader = currentProperty.getPropertyHeader(i);
|
|
|
|
if (propertyHeader.isSimple())
|
|
{
|
|
Alembic::Abc::TimeSamplingPtr timeSampling = propertyHeader.getTimeSampling();
|
|
|
|
if (propertyHeader.isArray())
|
|
{
|
|
Alembic::Abc::IArrayProperty childProperty(currentProperty, propertyHeader.getName());
|
|
|
|
if (!childProperty.isConstant())
|
|
{
|
|
stl::push_back_unique(m_timeSamplings, *timeSampling);
|
|
size_t numSamples = childProperty.getNumSamples();
|
|
m_minTime = std::min(m_minTime, timeSampling->getSampleTime(0));
|
|
m_maxTime = std::max(m_maxTime, timeSampling->getSampleTime(numSamples - 1));
|
|
}
|
|
}
|
|
else if (propertyHeader.isScalar())
|
|
{
|
|
Alembic::Abc::IScalarProperty childProperty(currentProperty, propertyHeader.getName());
|
|
|
|
if (!childProperty.isConstant())
|
|
{
|
|
stl::push_back_unique(m_timeSamplings, *timeSampling);
|
|
size_t numSamples = childProperty.getNumSamples();
|
|
m_minTime = std::min(m_minTime, timeSampling->getSampleTime(0));
|
|
m_maxTime = std::max(m_maxTime, timeSampling->getSampleTime(numSamples - 1));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Alembic::Abc::ICompoundProperty childProperty(currentProperty, propertyHeader.getName());
|
|
CheckTimeSamplingRec(childProperty);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AlembicCompiler::OutputTimeSamplingType(const Alembic::Abc::TimeSamplingType& timeSamplingType)
|
|
{
|
|
if (timeSamplingType.isUniform())
|
|
{
|
|
Alembic::Abc::chrono_t frameTime = timeSamplingType.getTimePerCycle();
|
|
RCLog(" Found uniform time sampling with %g FPS.", 1.0 / frameTime);
|
|
}
|
|
else if (timeSamplingType.isAcyclic())
|
|
{
|
|
RCLog(" Found acyclic time sampling with %d frames.", timeSamplingType.getNumSamplesPerCycle());
|
|
}
|
|
else if (timeSamplingType.isCyclic())
|
|
{
|
|
RCLog(" Found cyclic time sampling with %g second cycle time and %d frames per cycle.",
|
|
timeSamplingType.getTimePerCycle(), timeSamplingType.getNumSamplesPerCycle());
|
|
}
|
|
}
|
|
|
|
bool AlembicCompiler::CompileStaticData(Alembic::Abc::IArchive& archive)
|
|
{
|
|
#if !defined(AZ_PLATFORM_APPLE)
|
|
try
|
|
#endif // !defined(AZ_PLATFORM_APPLE)
|
|
{
|
|
RCLog("Compiling static data...");
|
|
|
|
Alembic::Abc::IObject topObject = archive.getTop();
|
|
std::vector<Alembic::AbcGeom::IXform> abcXformStack;
|
|
CompileStaticDataRec(&m_rootNode, topObject, QuatTNS(IDENTITY), abcXformStack, false, GeomCacheFile::eTransformType_Constant);
|
|
|
|
// Sanity check, this should never happen
|
|
if (m_rootNode.m_transformType != GeomCacheFile::eTransformType_Constant ||
|
|
m_rootNode.m_staticNodeData.m_transform.q != Quat(IDENTITY) || m_rootNode.m_staticNodeData.m_transform.t != Vec3(0.0f) || m_rootNode.m_staticNodeData.m_transform.s != Vec3(1.0))
|
|
{
|
|
RCLogError(" Internal error: Root node is not constant or is not identity.");
|
|
return false;
|
|
}
|
|
|
|
if (m_errorCount > 0)
|
|
{
|
|
RCLogError(" Failed to compile %d meshes", m_errorCount.load());
|
|
return false;
|
|
}
|
|
|
|
if (m_numExportedMeshes == 0)
|
|
{
|
|
RCLogError(" Failed to compile any mesh");
|
|
return false;
|
|
}
|
|
|
|
RCLog(" Compiled %d meshes", m_numExportedMeshes);
|
|
RCLog(" %d nodes with shared mesh", m_numSharedMeshNodes);
|
|
RCLog(" Split %d vertices", m_numVertexSplits.load());
|
|
}
|
|
#if !defined(AZ_PLATFORM_APPLE)
|
|
catch (const Alembic::Util::Exception& alembicException)
|
|
{
|
|
RCLogError("Alembic exception while processing %s static data: %s",
|
|
m_currentObjectPath.c_str(), alembicException.c_str());
|
|
return false;
|
|
}
|
|
catch (...)
|
|
{
|
|
RCLogError("Unknown exception while processing %s static data", m_currentObjectPath.c_str());
|
|
return false;
|
|
}
|
|
#endif // !defined(AZ_PLATFORM_APPLE)
|
|
|
|
if (m_bConvertYUpToZUp)
|
|
{
|
|
m_rootNode.m_staticNodeData.m_transform = m_rootNode.m_staticNodeData.m_transform * Quat(Ang3(gf_PI / 2.0f, 0.0f, 0.0f));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AlembicCompiler::CompileStaticDataRec(GeomCache::Node* pParentNode, Alembic::Abc::IObject& currentObject,
|
|
const QuatTNS& localTransform, std::vector<Alembic::AbcGeom::IXform> abcXformStack,
|
|
const bool bParentRemoved, const GeomCacheFile::ETransformType parentTransform)
|
|
{
|
|
m_currentObjectPath = currentObject.getFullName();
|
|
|
|
QuatTNS newLocalTransform = localTransform;
|
|
const size_t numChildren = currentObject.getNumChildren();
|
|
|
|
// If this node doesn't have children, discard this node
|
|
if (numChildren == 0 && !Alembic::AbcGeom::IPolyMesh::matches(currentObject.getHeader()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check if node is always invisible
|
|
Alembic::AbcGeom::IVisibilityProperty visibilityProperty = Alembic::AbcGeom::GetVisibilityProperty(currentObject);
|
|
|
|
if (visibilityProperty && visibilityProperty.isConstant())
|
|
{
|
|
int8_t rawVisibilityValue;
|
|
rawVisibilityValue = visibilityProperty.getValue();
|
|
Alembic::AbcGeom::ObjectVisibility visibility = Alembic::AbcGeom::ObjectVisibility(rawVisibilityValue);
|
|
|
|
if (visibility == Alembic::AbcGeom::kVisibilityHidden)
|
|
{
|
|
RCLogWarning(" Ignoring invisible node:\n %s.", currentObject.getFullName().c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<GeomCache::Node> pCurrentNode(new GeomCache::Node);
|
|
pCurrentNode->m_staticNodeData.m_transform = localTransform;
|
|
|
|
pCurrentNode->m_name = currentObject.getFullName().c_str();
|
|
|
|
// Flag to indicate to flatten hierarchy when encountering static node transform
|
|
bool bFlatten = false;
|
|
|
|
// Stores if sub tree is valid
|
|
bool bValidSubTree = false;
|
|
|
|
// If node is mesh
|
|
bool bNodeIsTransform = false;
|
|
|
|
// If transform inherits parents transform
|
|
bool bInheritsTransform = true;
|
|
|
|
if (Alembic::AbcGeom::IPolyMesh::matches(currentObject.getHeader()))
|
|
{
|
|
pCurrentNode->m_transformType = bParentRemoved ? parentTransform : GeomCacheFile::eTransformType_Constant;
|
|
Alembic::AbcGeom::IPolyMesh mesh(currentObject, Alembic::Abc::kWrapExisting);
|
|
|
|
if (CryStringUtils::stristr(pCurrentNode->m_name.c_str(), "cryphys"))
|
|
{
|
|
// Export physics proxy
|
|
pCurrentNode->m_type = GeomCacheFile::eNodeType_PhysicsGeometry;
|
|
bValidSubTree = CompilePhysicsGeometry(*pCurrentNode, mesh);
|
|
}
|
|
else
|
|
{
|
|
// Export mesh
|
|
pCurrentNode->m_type = GeomCacheFile::eNodeType_Mesh;
|
|
bValidSubTree = CompileStaticMeshData(*pCurrentNode, mesh);
|
|
}
|
|
}
|
|
else if (Alembic::AbcGeom::IXform::matches(currentObject.getHeader()))
|
|
{
|
|
Alembic::AbcGeom::IXform xForm(currentObject, Alembic::Abc::kWrapExisting);
|
|
Alembic::AbcGeom::IXformSchema& schema = xForm.getSchema();
|
|
bFlatten = schema.isConstant();
|
|
|
|
const Alembic::AbcGeom::M44d& matrix = schema.getValue().getMatrix();
|
|
const QuatTNS abcLocalTransform = FromAlembicMatrix(matrix);
|
|
|
|
if (schema.getInheritsXforms())
|
|
{
|
|
abcXformStack.push_back(Alembic::AbcGeom::IXform(currentObject, Alembic::Abc::kWrapExisting));
|
|
newLocalTransform = localTransform * abcLocalTransform;
|
|
|
|
pCurrentNode->m_transformType = schema.isConstant()
|
|
? (bParentRemoved ? parentTransform : GeomCacheFile::eTransformType_Constant)
|
|
: GeomCacheFile::eTransformType_Animated;
|
|
}
|
|
else
|
|
{
|
|
// Completely new base transform, discard parent transforms and re-parent to root.
|
|
bInheritsTransform = false;
|
|
pParentNode = &m_rootNode;
|
|
abcXformStack.clear();
|
|
abcXformStack.push_back(Alembic::AbcGeom::IXform(currentObject, Alembic::Abc::kWrapExisting));
|
|
newLocalTransform = abcLocalTransform;
|
|
|
|
pCurrentNode->m_transformType = schema.isConstant()
|
|
? GeomCacheFile::eTransformType_Constant
|
|
: GeomCacheFile::eTransformType_Animated;
|
|
}
|
|
|
|
bNodeIsTransform = true;
|
|
pCurrentNode->m_staticNodeData.m_transform = newLocalTransform;
|
|
}
|
|
else
|
|
{
|
|
bFlatten = true;
|
|
}
|
|
|
|
pCurrentNode->m_abcObject = Alembic::Abc::IObject(Alembic::Abc::ALEMBIC_VERSION_NS::GetObjectReaderPtr(currentObject), Alembic::Abc::kWrapExisting);
|
|
pCurrentNode->m_abcXForms = abcXformStack;
|
|
|
|
// Flatten hierarchy if possible
|
|
if (bFlatten || (bNodeIsTransform && numChildren == 1))
|
|
{
|
|
for (size_t i = 0; i < numChildren; ++i)
|
|
{
|
|
Alembic::Abc::IObject child = currentObject.getChild(i);
|
|
bValidSubTree = CompileStaticDataRec(pParentNode, child,
|
|
newLocalTransform, abcXformStack, true, pCurrentNode->m_transformType) || bValidSubTree;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Otherwise use this node as new parent. The children need a new transform stack
|
|
abcXformStack.clear();
|
|
|
|
for (size_t i = 0; i < numChildren; ++i)
|
|
{
|
|
Alembic::Abc::IObject child = currentObject.getChild(i);
|
|
bValidSubTree = CompileStaticDataRec(pCurrentNode.get(), child,
|
|
QuatTNS(IDENTITY), abcXformStack, false, pCurrentNode->m_transformType) || bValidSubTree;
|
|
}
|
|
|
|
if (bValidSubTree)
|
|
{
|
|
assert(pParentNode != pCurrentNode.get());
|
|
pParentNode->m_children.push_back(std::move(pCurrentNode));
|
|
}
|
|
}
|
|
|
|
if (!bValidSubTree)
|
|
{
|
|
RCLogWarning(" Node contains no meshes:\n %s", currentObject.getFullName().c_str());
|
|
}
|
|
|
|
// If node does not inherit parents transform return false, because path
|
|
// to this node is irrelevant and tree can potentially be thrown away.
|
|
return bValidSubTree && bInheritsTransform;
|
|
}
|
|
|
|
bool AlembicCompiler::CompileStaticMeshData(GeomCache::Node& node, Alembic::AbcGeom::IPolyMesh& mesh)
|
|
{
|
|
#if defined(DEBUG)
|
|
RCLog(" Processing %s", mesh.getFullName().c_str());
|
|
#endif
|
|
|
|
// Check basic mesh parameters
|
|
Alembic::AbcGeom::IPolyMeshSchema& meshSchema = mesh.getSchema();
|
|
Alembic::AbcGeom::MeshTopologyVariance topologyVariance = meshSchema.getTopologyVariance();
|
|
|
|
std::shared_ptr<GeomCache::Mesh> pMesh(new GeomCache::Mesh);
|
|
pMesh->m_constantStreams = GeomCacheFile::EStreams(0);
|
|
pMesh->m_animatedStreams = GeomCacheFile::EStreams(0);
|
|
|
|
switch (topologyVariance)
|
|
{
|
|
case Alembic::AbcGeom::kConstantTopology:
|
|
pMesh->m_constantStreams = GeomCacheFile::EStreams(pMesh->m_constantStreams | GeomCacheFile::eStream_Indices);
|
|
pMesh->m_constantStreams = GeomCacheFile::EStreams(pMesh->m_constantStreams | GeomCacheFile::eStream_Positions);
|
|
break;
|
|
case Alembic::AbcGeom::kHomogenousTopology:
|
|
pMesh->m_constantStreams = GeomCacheFile::EStreams(pMesh->m_constantStreams | GeomCacheFile::eStream_Indices);
|
|
pMesh->m_animatedStreams = GeomCacheFile::EStreams(pMesh->m_animatedStreams | GeomCacheFile::eStream_Positions);
|
|
break;
|
|
case Alembic::AbcGeom::kHeterogenousTopology:
|
|
// TODO: There is code that supports heterogeneous topology, but it's not complete and untested.
|
|
RCLogWarning(" Heterogeneous topology is currently not supported. Skipped.");
|
|
return false;
|
|
default:
|
|
RCLogWarning(" Unknown alembic topology variance. Skipped.");
|
|
return false;
|
|
}
|
|
|
|
// Check for normals & texcoords. We assume this is fixed over time
|
|
pMesh->m_bHasNormals = meshSchema.getNormalsParam().valid();
|
|
pMesh->m_bHasTexcoords = meshSchema.getUVsParam().valid();
|
|
CheckMeshForColors(meshSchema, *pMesh);
|
|
|
|
if (!pMesh->m_bHasNormals)
|
|
{
|
|
RCLogWarning(" Mesh doesn't have normals. Generating smooth normals:\n %s", mesh.getFullName().c_str());
|
|
}
|
|
|
|
if (!pMesh->m_bHasTexcoords)
|
|
{
|
|
RCLogWarning(" Mesh doesn't have texcoords:\n %s", mesh.getFullName().c_str());
|
|
}
|
|
|
|
if (!pMesh->m_bHasTexcoords || meshSchema.getUVsParam().getValueProperty().isConstant())
|
|
{
|
|
pMesh->m_constantStreams = GeomCacheFile::EStreams(pMesh->m_constantStreams | GeomCacheFile::eStream_Texcoords);
|
|
}
|
|
else
|
|
{
|
|
pMesh->m_animatedStreams = GeomCacheFile::EStreams(pMesh->m_animatedStreams | GeomCacheFile::eStream_Texcoords);
|
|
}
|
|
|
|
const bool bConstantNormals = pMesh->m_bHasNormals ? meshSchema.getNormalsParam().getValueProperty().isConstant() : true;
|
|
if (bConstantNormals && (pMesh->m_animatedStreams & (GeomCacheFile::eStream_Positions | GeomCacheFile::eStream_Texcoords)) == 0)
|
|
{
|
|
// If normals, positions & texcoords are constant we can use a constant qtangent stream
|
|
pMesh->m_constantStreams = GeomCacheFile::EStreams(pMesh->m_constantStreams | GeomCacheFile::eStream_QTangents);
|
|
}
|
|
else
|
|
{
|
|
pMesh->m_animatedStreams = GeomCacheFile::EStreams(pMesh->m_animatedStreams | GeomCacheFile::eStream_QTangents);
|
|
}
|
|
|
|
assert((pMesh->m_constantStreams & pMesh->m_animatedStreams) == 0);
|
|
|
|
pMesh->m_abcMesh = Alembic::AbcGeom::IPolyMesh(mesh, Alembic::Abc::kWrapExisting);
|
|
if (pMesh->m_animatedStreams == 0)
|
|
{
|
|
AlembicMeshDigest meshDigest(meshSchema);
|
|
|
|
// For constant meshes we allow mesh sharing. Check if we already
|
|
// have a mesh with that digest. If so share it.
|
|
auto findIter = m_digestToMeshMap.find(meshDigest);
|
|
if (findIter != m_digestToMeshMap.end())
|
|
{
|
|
++m_numSharedMeshNodes;
|
|
node.m_pMesh = findIter->second;
|
|
return true;
|
|
}
|
|
|
|
if (!CompileFullMesh(*pMesh, 0, node.m_staticNodeData.m_transform))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Add mesh to digest map
|
|
m_digestToMeshMap[meshDigest] = pMesh;
|
|
}
|
|
else
|
|
{
|
|
if (!CompileFullMesh(*pMesh, 0, node.m_staticNodeData.m_transform))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
node.m_pMesh = pMesh;
|
|
|
|
m_meshes.push_back(pMesh.get());
|
|
|
|
if (pMesh->m_animatedStreams != 0)
|
|
{
|
|
++m_numAnimatedMeshes;
|
|
}
|
|
|
|
// Yay, we exported one more mesh
|
|
++m_numExportedMeshes;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AlembicCompiler::CompilePhysicsGeometry(GeomCache::Node& node, Alembic::AbcGeom::IPolyMesh& mesh)
|
|
{
|
|
AZ_UNUSED(node)
|
|
AZ_UNUSED(mesh)
|
|
return true;
|
|
}
|
|
|
|
XmlNodeRef AlembicCompiler::ReadConfig(const string& configPath, IXMLSerializer* pXMLSerializer)
|
|
{
|
|
RCLog(string("Reading cache build configuration: ") + configPath.c_str());
|
|
XmlNodeRef config = pXMLSerializer->Read(FileXmlBufferSource(configPath), false, 0, 0);
|
|
|
|
// Read in axis from config file
|
|
string upAxis = "Y";
|
|
string meshPrediction = "0";
|
|
string useBFrames = "0";
|
|
string indexFrameDistance = "10";
|
|
string blockCompressionFormat = "deflate";
|
|
string playbackFromMemory = "0";
|
|
string positionPrecision = "1";
|
|
float uvMax = RC_ABC_AUTOMATIC_UVMAX_DETECTION_VALUE;
|
|
|
|
if (config)
|
|
{
|
|
if (config->haveAttr("UpAxis"))
|
|
{
|
|
upAxis = config->getAttr("UpAxis");
|
|
}
|
|
|
|
if (config->haveAttr("MeshPrediction"))
|
|
{
|
|
meshPrediction = config->getAttr("MeshPrediction");
|
|
}
|
|
|
|
if (config->haveAttr("UseBFrames"))
|
|
{
|
|
useBFrames = config->getAttr("UseBFrames");
|
|
}
|
|
|
|
if (config->haveAttr("IndexFrameDistance"))
|
|
{
|
|
indexFrameDistance = config->getAttr("IndexFrameDistance");
|
|
}
|
|
|
|
if (config->haveAttr("BlockCompressionFormat"))
|
|
{
|
|
blockCompressionFormat = config->getAttr("BlockCompressionFormat");
|
|
}
|
|
|
|
if (config->haveAttr("PlaybackFromMemory"))
|
|
{
|
|
playbackFromMemory = config->getAttr("PlaybackFromMemory");
|
|
}
|
|
|
|
if (config->haveAttr("PositionPrecision"))
|
|
{
|
|
positionPrecision = config->getAttr("PositionPrecision");
|
|
}
|
|
if (config->haveAttr("UVmax"))
|
|
{
|
|
uvMax = static_cast<float>(atof(config->getAttr("UVmax")));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool bSkipFilesWithoutBuildConfig = false;
|
|
|
|
if (m_CC.m_config->HasKey("skipFilesWithoutBuildConfig"))
|
|
{
|
|
bSkipFilesWithoutBuildConfig = m_CC.m_config->GetAsBool("skipFilesWithoutBuildConfig", bSkipFilesWithoutBuildConfig, bSkipFilesWithoutBuildConfig);
|
|
}
|
|
|
|
if (!bSkipFilesWithoutBuildConfig)
|
|
{
|
|
RCLogWarning(" Build configuration file not found, writing new one");
|
|
config = pXMLSerializer->CreateNode("CacheBuildConfiguration");
|
|
}
|
|
else
|
|
{
|
|
RCLogError(" Build configuration file not found. Skipped.");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// Command line overrides
|
|
upAxis = m_CC.m_config->GetAsString("upAxis", upAxis, upAxis);
|
|
meshPrediction = m_CC.m_config->GetAsString("meshPrediction", meshPrediction, meshPrediction);
|
|
useBFrames = m_CC.m_config->GetAsString("useBFrames", useBFrames, useBFrames);
|
|
indexFrameDistance = m_CC.m_config->GetAsString("indexFrameDistance", indexFrameDistance, indexFrameDistance);
|
|
blockCompressionFormat = m_CC.m_config->GetAsString("blockCompressionFormat", blockCompressionFormat, blockCompressionFormat);
|
|
playbackFromMemory = m_CC.m_config->GetAsString("playbackFromMemory", playbackFromMemory, playbackFromMemory);
|
|
positionPrecision = m_CC.m_config->GetAsString("positionPrecision", positionPrecision, positionPrecision);
|
|
uvMax = m_CC.m_config->GetAsFloat("uvMax", uvMax, uvMax);
|
|
|
|
// Check if we need to convert the axis
|
|
m_bConvertYUpToZUp = upAxis.compareNoCase("Y") == 0;
|
|
if (m_bConvertYUpToZUp)
|
|
{
|
|
RCLog(" Converting Y up to Z up");
|
|
}
|
|
|
|
m_bPlaybackFromMemory = playbackFromMemory.compareNoCase("1") == 0;
|
|
if (m_bPlaybackFromMemory)
|
|
{
|
|
RCLog(" Playback from memory");
|
|
}
|
|
|
|
m_bMeshPrediction = (meshPrediction.compareNoCase("1") == 0) && (blockCompressionFormat != "store");
|
|
if (m_bMeshPrediction)
|
|
{
|
|
RCLog(" Using mesh prediction");
|
|
}
|
|
|
|
m_indexFrameDistance = 15;
|
|
m_bUseBFrames = (useBFrames.compareNoCase("1") == 0) && (blockCompressionFormat != "store");
|
|
if (m_bUseBFrames)
|
|
{
|
|
m_indexFrameDistance = atoi(indexFrameDistance.c_str());
|
|
RCLog(" Using bi-directional predicted frames");
|
|
RCLog(" Index frame distance is %d", m_indexFrameDistance);
|
|
}
|
|
|
|
if (blockCompressionFormat == "store")
|
|
{
|
|
m_blockCompressionFormat = GeomCacheFile::eBlockCompressionFormat_None;
|
|
RCLog(" No frame compression");
|
|
}
|
|
else if (blockCompressionFormat == "lz4hc")
|
|
{
|
|
m_blockCompressionFormat = GeomCacheFile::eBlockCompressionFormat_LZ4HC;
|
|
RCLog(" Using LZ4 HC compression");
|
|
}
|
|
else if (blockCompressionFormat == "zstd")
|
|
{
|
|
m_blockCompressionFormat = GeomCacheFile::eBlockCompressionFormat_ZSTD;
|
|
RCLog(" Using ZSTANDARD compression");
|
|
}
|
|
else
|
|
{
|
|
m_blockCompressionFormat = GeomCacheFile::eBlockCompressionFormat_Deflate;
|
|
RCLog(" Using deflate (zlib) compression");
|
|
}
|
|
|
|
m_positionPrecision = std::max(atof(positionPrecision), 0.0);
|
|
if (m_positionPrecision == 0.0)
|
|
{
|
|
RCLog(" Maximum position precision", m_positionPrecision);
|
|
}
|
|
else
|
|
{
|
|
RCLog(" %g mm position precision", m_positionPrecision);
|
|
}
|
|
|
|
if (uvMax == RC_ABC_AUTOMATIC_UVMAX_DETECTION_VALUE)
|
|
{
|
|
RCLog(" Using auto-detected per-mesh UVmax range value.");
|
|
}
|
|
else
|
|
{
|
|
m_uvMax = std::max(uvMax, GeomCacheFile::kMinUVrange);
|
|
RCLog(" Using UVmax %g", m_uvMax);
|
|
}
|
|
|
|
config->setAttr("UpAxis", upAxis);
|
|
config->setAttr("MeshPrediction", meshPrediction);
|
|
config->setAttr("UseBFrames", useBFrames);
|
|
config->setAttr("IndexFrameDistance", indexFrameDistance);
|
|
config->setAttr("BlockCompressionFormat", blockCompressionFormat);
|
|
config->setAttr("PlaybackFromMemory", playbackFromMemory);
|
|
config->setAttr("PositionPrecision", positionPrecision);
|
|
config->setAttr("UVmax", uvMax);
|
|
|
|
return config;
|
|
}
|
|
|
|
template<class ParamType>
|
|
inline void CheckColorParam(Alembic::Abc::ICompoundProperty& arbParams,
|
|
const Alembic::Abc::PropertyHeader& propertyHeader, GeomCache::Mesh& mesh, bool& bFoundColorProperty)
|
|
{
|
|
const std::string& propertyName = propertyHeader.getName();
|
|
|
|
if (!bFoundColorProperty)
|
|
{
|
|
mesh.m_bHasColors = true;
|
|
mesh.m_colorParamName = propertyHeader.getName();
|
|
|
|
ParamType param(arbParams, propertyName);
|
|
if (param.isConstant())
|
|
{
|
|
mesh.m_constantStreams = GeomCacheFile::EStreams(mesh.m_constantStreams | GeomCacheFile::eStream_Colors);
|
|
}
|
|
else
|
|
{
|
|
mesh.m_animatedStreams = GeomCacheFile::EStreams(mesh.m_animatedStreams | GeomCacheFile::eStream_Colors);
|
|
}
|
|
|
|
bFoundColorProperty = true;
|
|
}
|
|
else
|
|
{
|
|
RCLogWarning(" Multiple color streams. Ignoring color stream %s", propertyName.c_str());
|
|
}
|
|
}
|
|
|
|
void AlembicCompiler::CheckMeshForColors(Alembic::AbcGeom::IPolyMeshSchema& meshSchema, GeomCache::Mesh& mesh) const
|
|
{
|
|
mesh.m_bHasColors = false;
|
|
|
|
Alembic::Abc::ICompoundProperty arbParams = meshSchema.getArbGeomParams();
|
|
|
|
if (!arbParams)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool bFoundColorProperty = false;
|
|
|
|
const uint numProperties = arbParams.getNumProperties();
|
|
for (uint i = 0; i < numProperties; ++i)
|
|
{
|
|
const Alembic::Abc::PropertyHeader& propertyHeader = arbParams.getPropertyHeader(i);
|
|
|
|
if (Alembic::AbcGeom::IC3hGeomParam::matches(propertyHeader))
|
|
{
|
|
CheckColorParam<Alembic::AbcGeom::IC3hGeomParam>(arbParams, propertyHeader, mesh, bFoundColorProperty);
|
|
}
|
|
else if (Alembic::AbcGeom::IC3fGeomParam::matches(propertyHeader))
|
|
{
|
|
CheckColorParam<Alembic::AbcGeom::IC3fGeomParam>(arbParams, propertyHeader, mesh, bFoundColorProperty);
|
|
}
|
|
else if (Alembic::AbcGeom::IC3cGeomParam::matches(propertyHeader))
|
|
{
|
|
CheckColorParam<Alembic::AbcGeom::IC3cGeomParam>(arbParams, propertyHeader, mesh, bFoundColorProperty);
|
|
}
|
|
else if (Alembic::AbcGeom::IC4hGeomParam::matches(propertyHeader))
|
|
{
|
|
CheckColorParam<Alembic::AbcGeom::IC4hGeomParam>(arbParams, propertyHeader, mesh, bFoundColorProperty);
|
|
}
|
|
else if (Alembic::AbcGeom::IC4fGeomParam::matches(propertyHeader))
|
|
{
|
|
CheckColorParam<Alembic::AbcGeom::IC4fGeomParam>(arbParams, propertyHeader, mesh, bFoundColorProperty);
|
|
}
|
|
else if (Alembic::AbcGeom::IC4cGeomParam::matches(propertyHeader))
|
|
{
|
|
CheckColorParam<Alembic::AbcGeom::IC4cGeomParam>(arbParams, propertyHeader, mesh, bFoundColorProperty);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Prints the node tree
|
|
void AlembicCompiler::PrintNodeTreeRec(GeomCache::Node& node, string padding)
|
|
{
|
|
if (&node != &m_rootNode)
|
|
{
|
|
padding += "\t";
|
|
RCLog("%s%s - %s", padding.c_str(), node.m_name.c_str(), (node.m_transformType == GeomCacheFile::eTransformType_Constant) ? "constant" : "animated");
|
|
}
|
|
|
|
const uint numChildren = node.m_children.size();
|
|
for (uint i = 0; i < numChildren; ++i)
|
|
{
|
|
PrintNodeTreeRec(*node.m_children[i], padding);
|
|
}
|
|
}
|
|
|
|
bool AlembicCompiler::CompileAnimationData([[maybe_unused]] Alembic::Abc::IArchive& archive, GeomCacheEncoder& geomCacheEncoder)
|
|
{
|
|
RCLog("Compiling animation data...");
|
|
|
|
m_jobGroupData.m_pAlembicCompiler = this;
|
|
|
|
const size_t numFrames = m_frameTimes.size();
|
|
for (size_t currentFrame = 0; currentFrame < numFrames; ++currentFrame)
|
|
{
|
|
// Fill job data
|
|
m_jobGroupData.m_frameIndex = currentFrame;
|
|
m_jobGroupData.m_frameTime = m_frameTimes[currentFrame];
|
|
m_jobGroupData.m_frameAABB.Reset();
|
|
|
|
for (size_t meshIndex = 0; meshIndex < m_meshes.size(); ++meshIndex)
|
|
{
|
|
GeomCache::Mesh* pMesh = m_meshes[meshIndex];
|
|
|
|
pMesh->m_meshDataBuffer.m_frameUseCount = 0;
|
|
|
|
if (pMesh->m_animatedStreams != 0)
|
|
{
|
|
AlembicCompiler::UpdateVertexDataWithErrorHandling(pMesh);
|
|
}
|
|
}
|
|
|
|
AlembicCompiler::UpdateTransformsWithErrorHandling();
|
|
|
|
PushCompletedFrames(geomCacheEncoder);
|
|
}
|
|
|
|
if (m_jobGroupData.m_errorCount > 0)
|
|
{
|
|
m_errorCount.fetch_add(m_jobGroupData.m_errorCount);
|
|
RCLogError(" Failed to compile %d meshes", m_jobGroupData.m_errorCount.load());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void AlembicCompiler::PushCompletedFrames(GeomCacheEncoder& geomCacheEncoder)
|
|
{
|
|
const uint numMeshes = m_meshes.size();
|
|
for (uint i = 0; i < numMeshes; ++i)
|
|
{
|
|
GeomCache::Mesh& mesh = *m_meshes[i];
|
|
mesh.m_rawFrames.push_back(std::move(mesh.m_meshDataBuffer));
|
|
}
|
|
|
|
AppendTransformFrameDataRec(m_rootNode, m_jobGroupData.m_jobIndex);
|
|
const bool bIsLastFrame = (m_jobGroupData.m_frameIndex == (m_frameTimes.size() - 1));
|
|
geomCacheEncoder.AddFrame(m_jobGroupData.m_frameTime, m_jobGroupData.m_frameAABB, bIsLastFrame);
|
|
}
|
|
|
|
void AlembicCompiler::UpdateTransformsWithErrorHandling()
|
|
{
|
|
std::string currentObjectPath;
|
|
|
|
#if !defined(AZ_PLATFORM_APPLE)
|
|
try
|
|
#endif // !defined(AZ_PLATFORM_APPLE)
|
|
{
|
|
TMatrixMap matrixMap;
|
|
TVisibilityMap visibilityMap;
|
|
UpdateTransformsRec(m_rootNode, m_jobGroupData.m_frameTime, m_jobGroupData.m_frameAABB, QuatTNS(IDENTITY), matrixMap, visibilityMap, currentObjectPath);
|
|
}
|
|
#if !defined(AZ_PLATFORM_APPLE)
|
|
catch (const Alembic::Util::Exception& alembicException)
|
|
{
|
|
RCLogError("Alembic exception while processing %s in frame %u, time %g: %s",
|
|
currentObjectPath.c_str(), m_jobGroupData.m_frameIndex, m_jobGroupData.m_frameTime, alembicException.c_str());
|
|
++m_jobGroupData.m_errorCount;
|
|
}
|
|
catch (...)
|
|
{
|
|
RCLogError("Unknown exception while processing %s in frame %u, time %g",
|
|
currentObjectPath.c_str(), m_jobGroupData.m_frameIndex, m_jobGroupData.m_frameTime);
|
|
++m_jobGroupData.m_errorCount;
|
|
}
|
|
#endif // !defined(AZ_PLATFORM_APPLE)
|
|
}
|
|
|
|
void AlembicCompiler::UpdateTransformsRec(GeomCache::Node& node, const Alembic::Abc::chrono_t frameTime,
|
|
AABB& frameAABB, QuatTNS currentTransform, TMatrixMap& matrixMap, TVisibilityMap& visibilityMap, std::string& currentObjectPath)
|
|
{
|
|
if (node.m_transformType != GeomCacheFile::eTransformType_Constant)
|
|
{
|
|
node.m_nodeDataBuffer.m_transform.SetIdentity();
|
|
|
|
for (auto iter = node.m_abcXForms.begin(); iter != node.m_abcXForms.end(); ++iter)
|
|
{
|
|
Alembic::AbcGeom::IXform& xform = *iter;
|
|
|
|
currentObjectPath = xform.getFullName().c_str();
|
|
|
|
auto findIter = matrixMap.find(currentObjectPath);
|
|
if (findIter != matrixMap.end())
|
|
{
|
|
const Alembic::AbcGeom::M44d& matrix = findIter->second;
|
|
node.m_nodeDataBuffer.m_transform = node.m_nodeDataBuffer.m_transform * FromAlembicMatrix(matrix);
|
|
}
|
|
else
|
|
{
|
|
Alembic::AbcGeom::IXformSchema& schema = xform.getSchema();
|
|
Alembic::Abc::TimeSampling& timeSampling = *schema.getTimeSampling();
|
|
|
|
auto index = timeSampling.getNearIndex(frameTime, schema.getNumSamples());
|
|
|
|
const Alembic::AbcGeom::M44d& matrix = schema.getValue(index.first).getMatrix();
|
|
node.m_nodeDataBuffer.m_transform = node.m_nodeDataBuffer.m_transform * FromAlembicMatrix(matrix);
|
|
|
|
matrixMap[currentObjectPath] = matrix;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
node.m_nodeDataBuffer.m_transform = node.m_staticNodeData.m_transform;
|
|
}
|
|
|
|
currentTransform = currentTransform * node.m_nodeDataBuffer.m_transform;
|
|
|
|
node.m_nodeDataBuffer.m_bVisible = true;
|
|
|
|
if (node.m_type == GeomCacheFile::eNodeType_Mesh || node.m_type == GeomCacheFile::eNodeType_PhysicsGeometry)
|
|
{
|
|
bool bVisible = true;
|
|
for (Alembic::Abc::IObject currentObject = node.m_abcObject; currentObject; currentObject = currentObject.getParent())
|
|
{
|
|
currentObjectPath = currentObject.getFullName().c_str();
|
|
|
|
auto findIter = visibilityMap.find(currentObjectPath);
|
|
if (findIter != visibilityMap.end())
|
|
{
|
|
const Alembic::AbcGeom::ObjectVisibility& visibility = findIter->second;
|
|
if (visibility == Alembic::AbcGeom::kVisibilityHidden)
|
|
{
|
|
bVisible = false;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Alembic::AbcGeom::IVisibilityProperty visibilityProperty = Alembic::AbcGeom::GetVisibilityProperty(currentObject);
|
|
|
|
if (visibilityProperty)
|
|
{
|
|
Alembic::Abc::TimeSamplingPtr timeSampling = visibilityProperty.getTimeSampling();
|
|
auto index = timeSampling->getNearIndex(frameTime, visibilityProperty.getNumSamples());
|
|
|
|
int8_t rawVisibilityValue;
|
|
rawVisibilityValue = visibilityProperty.getValue(index.first);
|
|
Alembic::AbcGeom::ObjectVisibility visibility = Alembic::AbcGeom::ObjectVisibility(rawVisibilityValue);
|
|
|
|
visibilityMap[currentObjectPath] = visibility;
|
|
|
|
if (visibility == Alembic::AbcGeom::kVisibilityHidden)
|
|
{
|
|
bVisible = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
node.m_nodeDataBuffer.m_bVisible = bVisible;
|
|
|
|
if (bVisible && (node.m_type == GeomCacheFile::eNodeType_Mesh))
|
|
{
|
|
AABB transformedMeshAABB;
|
|
transformedMeshAABB.SetTransformedAABB(Matrix34(currentTransform), node.m_pMesh->m_aabb);
|
|
frameAABB.Add(transformedMeshAABB);
|
|
|
|
++node.m_pMesh->m_meshDataBuffer.m_frameUseCount;
|
|
}
|
|
}
|
|
|
|
const size_t numChildren = node.m_children.size();
|
|
for (size_t i = 0; i < numChildren; ++i)
|
|
{
|
|
UpdateTransformsRec(*node.m_children[i], frameTime, frameAABB, currentTransform, matrixMap, visibilityMap, currentObjectPath);
|
|
}
|
|
}
|
|
|
|
void AlembicCompiler::UpdateVertexDataWithErrorHandling(GeomCache::Mesh* mesh)
|
|
{
|
|
const char* pMeshName = mesh->m_abcMesh.getFullName().c_str();
|
|
|
|
#if !defined(AZ_PLATFORM_APPLE)
|
|
try
|
|
#endif // !defined(AZ_PLATFORM_APPLE)
|
|
{
|
|
if (!UpdateVertexData(*mesh, m_jobGroupData.m_frameIndex))
|
|
{
|
|
// No need to print out an RCLogError for this case as
|
|
// UpdatVertexData and the functions it calls will log messages
|
|
++m_jobGroupData.m_errorCount;
|
|
}
|
|
}
|
|
#if !defined(AZ_PLATFORM_APPLE)
|
|
catch (const Alembic::Util::Exception& alembicException)
|
|
{
|
|
RCLogError("Alembic exception while processing %s in frame %u, time %g: %s",
|
|
pMeshName, m_jobGroupData.m_frameIndex, m_jobGroupData.m_frameTime, alembicException.c_str());
|
|
++m_jobGroupData.m_errorCount;
|
|
}
|
|
catch (...)
|
|
{
|
|
RCLogError("Unknown exception while processing %s in frame %u, time %g",
|
|
pMeshName, m_jobGroupData.m_frameIndex, m_jobGroupData.m_frameTime);
|
|
++m_jobGroupData.m_errorCount;
|
|
}
|
|
#endif // !defined(AZ_PLATFORM_APPLE)
|
|
}
|
|
|
|
std::unordered_map<uint32, uint16> AlembicCompiler::GetMeshMaterialMap(const Alembic::AbcGeom::IPolyMesh& mesh, const Alembic::Abc::chrono_t frameTime)
|
|
{
|
|
std::unordered_map<uint32, uint16> materialIdMap;
|
|
|
|
const uint numChildren = mesh.getNumChildren();
|
|
for (uint i = 0; i < numChildren; ++i)
|
|
{
|
|
Alembic::Abc::IObject child = mesh.getChild(i);
|
|
if (Alembic::AbcGeom::IFaceSet::matches(child.getHeader()))
|
|
{
|
|
Alembic::AbcGeom::IFaceSet faceSet(child, Alembic::Abc::kWrapExisting);
|
|
|
|
Alembic::AbcGeom::IFaceSetSchema::Sample sample;
|
|
faceSet.getSchema().get(sample, Alembic::Abc::ISampleSelector(frameTime));
|
|
|
|
const std::string faceSetName = faceSet.getName();
|
|
|
|
// Parse first number in face set name
|
|
const char* pName = faceSetName.c_str();
|
|
while (*pName && !isdigit(*pName))
|
|
{
|
|
++pName;
|
|
}
|
|
|
|
if (*pName == 0)
|
|
{
|
|
RCLogWarning(" Face set name '%s' contains no number, will map faces to material ID 1", faceSetName.c_str());
|
|
continue;
|
|
}
|
|
|
|
int materialId = atoi(pName);
|
|
|
|
if (materialId < 1 || materialId > 65536)
|
|
{
|
|
RCLogWarning(" Face set name '%s' refers to material ID out of range 1-65536, will map faces to material ID 1", faceSetName.c_str());
|
|
continue;
|
|
}
|
|
|
|
// Engine uses 0 based indices, but the UI displays them 1 based in sandbox. Subtract 1 from user input in DCC applications to be consistent.
|
|
materialId -= 1;
|
|
|
|
const int32* pFaces = static_cast<const int32*>(sample.getFaces()->getData());
|
|
const uint numFaces = sample.getFaces()->size();
|
|
|
|
for (uint i2 = 0; i2 < numFaces; ++i2)
|
|
{
|
|
const int32 face = pFaces[i2];
|
|
if (materialIdMap.find(face) != materialIdMap.end())
|
|
{
|
|
RCLogWarning(" Face %i of mesh is referenced by more than one face set:\n %s", pFaces[i2], mesh.getFullName().c_str());
|
|
}
|
|
|
|
materialIdMap[face] = materialId;
|
|
}
|
|
}
|
|
}
|
|
|
|
return materialIdMap;
|
|
}
|
|
|
|
uint32_t AlembicCompiler::GetIndex(Alembic::AbcGeom::GeometryScope geomScope, const Alembic::Abc::UInt32ArraySamplePtr& indicies, size_t currentIndexArraysIndex, int32_t positionIndex)
|
|
{
|
|
uint32_t index = 0;
|
|
switch (geomScope)
|
|
{
|
|
case Alembic::AbcGeom::kFacevaryingScope:
|
|
index = (*indicies)[currentIndexArraysIndex];
|
|
break;
|
|
case Alembic::AbcGeom::kVertexScope:
|
|
index = positionIndex;
|
|
break;
|
|
default:
|
|
RCLogError("Unsupported geoscope type: %s", geomScope);
|
|
break;
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
bool AlembicCompiler::ComputeVertexHashes(std::vector<uint64>& abcVertexHashes, size_t currentFrame, const size_t numAbcIndices, GeomCache::Mesh& mesh,
|
|
Alembic::Abc::TimeSampling& meshTimeSampling, size_t numMeshSamples, Alembic::AbcGeom::IPolyMeshSchema& meshSchema, const bool bHasNormals,
|
|
const bool bHasTexcoords, const bool bHasColors, const size_t numAbcNormalIndices, const size_t numAbcTexcoordsIndices, const size_t numAbcFaces)
|
|
{
|
|
abcVertexHashes.resize(numAbcIndices, 0);
|
|
|
|
size_t firstFrame;
|
|
size_t lastFrame;
|
|
if (mesh.m_animatedStreams == 0)
|
|
{
|
|
// Only need to process first frame for constant meshes, as they all are equal
|
|
firstFrame = 0;
|
|
lastFrame = 1;
|
|
}
|
|
else if ((mesh.m_animatedStreams & GeomCacheFile::eStream_Indices) == 0)
|
|
{
|
|
// If topology is not homogeneous we create one index buffer for the whole animation per mesh.
|
|
// This means we need to break vertices up if at any point in time normals or texcoords differ
|
|
// for two triangles that share the same vertex. We do this by creating a hash over the data for
|
|
// each triangle vertex over time.
|
|
firstFrame = 0;
|
|
lastFrame = m_frameTimes.size();
|
|
}
|
|
else
|
|
{
|
|
// For heterogeneous meshes we just check the current frame
|
|
firstFrame = currentFrame;
|
|
lastFrame = currentFrame + 1;
|
|
}
|
|
|
|
// Reset mesh AABB
|
|
mesh.m_aabb.Reset();
|
|
|
|
for (size_t currentFrame = firstFrame; currentFrame < lastFrame; ++currentFrame)
|
|
{
|
|
Alembic::Abc::chrono_t frameTime = m_frameTimes[currentFrame];
|
|
auto index = meshTimeSampling.getNearIndex(frameTime, numMeshSamples);
|
|
|
|
Alembic::AbcGeom::IPolyMeshSchema::Sample frameSample = meshSchema.getValue(index.first);
|
|
|
|
// Just check to make sure. This should not happen.
|
|
if (bHasNormals != meshSchema.getNormalsParam().valid() || bHasTexcoords != meshSchema.getUVsParam().valid())
|
|
{
|
|
RCLogWarning(" Mesh schema differs from frame 0 to frame %Iu. Skipped:\n %s", currentFrame, mesh.m_abcMesh.getFullName().c_str());
|
|
return false;
|
|
}
|
|
|
|
// Get normal & texcoord samples
|
|
Alembic::AbcGeom::IN3fGeomParam::Sample frameNormalSample;
|
|
if (bHasNormals)
|
|
{
|
|
meshSchema.getNormalsParam().getIndexed(frameNormalSample, index.first);
|
|
}
|
|
|
|
Alembic::AbcGeom::IV2fGeomParam::Sample frameTexcoordSample;
|
|
if (bHasTexcoords)
|
|
{
|
|
meshSchema.getUVsParam().getIndexed(frameTexcoordSample, index.first);
|
|
}
|
|
|
|
// Get sample arrays
|
|
const auto& frameAbcPositions = *frameSample.getPositions();
|
|
const size_t frameNumAbcVertices = frameAbcPositions.size();
|
|
const auto& frameAbcFaceCounts = *frameSample.getFaceCounts();
|
|
const size_t frameNumAbcFaces = frameAbcFaceCounts.size();
|
|
const auto& frameAbcIndices = *frameSample.getFaceIndices();
|
|
const size_t frameNumAbcIndices = frameAbcIndices.size();
|
|
|
|
const Alembic::Abc::N3fArraySamplePtr pFrameAbcNormals = bHasNormals ? frameNormalSample.getVals() : 0;
|
|
const size_t frameNumAbcNormals = bHasNormals ? pFrameAbcNormals->size() : 0;
|
|
const Alembic::Abc::UInt32ArraySamplePtr pFrameAbcNormalIndices = bHasNormals ? frameNormalSample.getIndices() : 0;
|
|
const size_t frameNumAbcNormalIndices = bHasNormals ? pFrameAbcNormalIndices->size() : 0;
|
|
|
|
const Alembic::Abc::V2fArraySamplePtr pFrameAbcTexcoords = bHasTexcoords ? frameTexcoordSample.getVals() : 0;
|
|
const size_t frameNumAbcTexcoords = bHasTexcoords ? pFrameAbcTexcoords->size() : 0;
|
|
const Alembic::Abc::UInt32ArraySamplePtr pFrameAbcTexcoordIndices = bHasTexcoords ? frameTexcoordSample.getIndices() : 0;
|
|
const size_t frameNumAbcTexcoordsIndices = bHasTexcoords ? pFrameAbcTexcoordIndices->size() : 0;
|
|
|
|
Alembic::AbcGeom::GeometryScope normalGeoScope = Alembic::AbcGeom::kUnknownScope;
|
|
Alembic::AbcGeom::GeometryScope texcoordGeoScope = Alembic::AbcGeom::kUnknownScope;
|
|
|
|
if (bHasNormals)
|
|
{
|
|
normalGeoScope = Alembic::AbcGeom::GetGeometryScope(meshSchema.getNormalsParam().getMetaData());
|
|
if (normalGeoScope != Alembic::AbcGeom::kVertexScope && normalGeoScope != Alembic::AbcGeom::kFacevaryingScope)
|
|
{
|
|
RCLogWarning("Mesh normal vectors are in an format that's not implemented or illegal. mode:%Iu. Skipped:\n %s", normalGeoScope, mesh.m_abcMesh.getFullName().c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (bHasTexcoords)
|
|
{
|
|
texcoordGeoScope = Alembic::AbcGeom::GetGeometryScope(meshSchema.getUVsParam().getMetaData());
|
|
if (texcoordGeoScope != Alembic::AbcGeom::kVertexScope && texcoordGeoScope != Alembic::AbcGeom::kFacevaryingScope)
|
|
{
|
|
RCLogWarning("Mesh uv texture coordinates are in an format that's not implemented or illegal. mode:%Iu. Skipped:\n %s", texcoordGeoScope, mesh.m_abcMesh.getFullName().c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
AlembicColorSampleArray frameColors = bHasColors ? AlembicColorSampleArray(mesh.m_colorParamName, meshSchema, index.first) : AlembicColorSampleArray();
|
|
|
|
// Just check to make sure. This should not happen.
|
|
if (frameNumAbcIndices != numAbcIndices || frameNumAbcNormalIndices != numAbcNormalIndices
|
|
|| frameNumAbcTexcoordsIndices != numAbcTexcoordsIndices || frameNumAbcFaces != numAbcFaces)
|
|
{
|
|
RCLogWarning(" Mesh index/face count differs from frame 0 to frame %Iu. Skipped:\n %s", currentFrame, mesh.m_abcMesh.getFullName().c_str());
|
|
return false;
|
|
}
|
|
|
|
size_t currentIndexArraysIndex = 0;
|
|
for (size_t face = 0; face < frameNumAbcFaces; ++face)
|
|
{
|
|
size_t numFaceVertices = frameAbcFaceCounts[face];
|
|
|
|
if (numFaceVertices < 3)
|
|
{
|
|
currentIndexArraysIndex += numFaceVertices;
|
|
continue;
|
|
}
|
|
|
|
for (size_t faceVertexIndex = 0; faceVertexIndex < numFaceVertices; ++faceVertexIndex)
|
|
{
|
|
// Just to make sure check index array position
|
|
if (currentIndexArraysIndex >= numAbcIndices)
|
|
{
|
|
RCLogWarning("Mesh contains invalid data - trying to index outside the valid number of indices. Skipped:\n%s", mesh.m_abcMesh.getFullName().c_str());
|
|
return false;
|
|
}
|
|
|
|
const int32_t positionIndex = frameAbcIndices[currentIndexArraysIndex];
|
|
|
|
uint32_t normalIndex = bHasNormals ? GetIndex(normalGeoScope, pFrameAbcNormalIndices, currentIndexArraysIndex, positionIndex) : 0;
|
|
uint32_t texcoordsIndex = bHasTexcoords ? GetIndex(texcoordGeoScope, pFrameAbcTexcoordIndices, currentIndexArraysIndex, positionIndex) : 0;
|
|
|
|
const int32_t colorsIndex = frameColors.getIndex(currentIndexArraysIndex);
|
|
|
|
// Just to make sure check indices
|
|
if ((size_t)positionIndex >= frameNumAbcVertices
|
|
|| (bHasNormals && (size_t)normalIndex >= frameNumAbcNormals)
|
|
|| (bHasTexcoords && (size_t)texcoordsIndex >= frameNumAbcTexcoords)
|
|
|| (bHasColors && (size_t)colorsIndex >= frameColors.GetSize()))
|
|
{
|
|
RCLogWarning(" Mesh contains invalid data. Skipped:\n %s", mesh.m_abcMesh.getFullName().c_str());
|
|
return false;
|
|
}
|
|
|
|
// Convert to geom cache vertex
|
|
AlembicCompilerVertex vertex;
|
|
|
|
const Imath::Vec3<float>& abcPosition = frameAbcPositions[positionIndex];
|
|
const Imath::Vec3<float>& abcNormal = bHasNormals ? (*pFrameAbcNormals)[normalIndex] : Imath::Vec3<float>(0.0f, 0.0f, 0.0f);
|
|
const Imath::Vec2<float>& abcTexcoord = bHasTexcoords ? (*pFrameAbcTexcoords)[texcoordsIndex] : Imath::Vec2<float>(0.0f, 0.0f);
|
|
|
|
vertex.m_position = FromAlembicPosition(abcPosition);
|
|
vertex.m_normal = Vec3(abcNormal.x, abcNormal.y, abcNormal.z);
|
|
vertex.m_texcoords = FromAlembicTexcoord(abcTexcoord);
|
|
vertex.m_rgba = frameColors[colorsIndex];
|
|
|
|
mesh.m_aabb.Add(vertex.m_position);
|
|
|
|
// Combine with hash from previous frames
|
|
AlembicCompilerHashCombine(abcVertexHashes[currentIndexArraysIndex], vertex);
|
|
|
|
// Advance index array index
|
|
++currentIndexArraysIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AlembicCompiler::CompileFullMesh(GeomCache::Mesh& mesh, const size_t currentFrame, const QuatTNS& transform)
|
|
{
|
|
const Alembic::Abc::chrono_t frameTime = m_frameTimes[currentFrame];
|
|
mesh.m_materialIdMap = GetMeshMaterialMap(mesh.m_abcMesh, frameTime);
|
|
|
|
GeomCache::MeshData& meshData = mesh.m_staticMeshData;
|
|
|
|
mesh.m_indicesMap.clear();
|
|
meshData.m_positions.clear();
|
|
meshData.m_texcoords.clear();
|
|
meshData.m_qTangents.clear();
|
|
meshData.m_reds.clear();
|
|
meshData.m_greens.clear();
|
|
meshData.m_blues.clear();
|
|
meshData.m_alphas.clear();
|
|
|
|
const bool bHasNormals = mesh.m_bHasNormals;
|
|
const bool bHasTexcoords = mesh.m_bHasTexcoords;
|
|
const bool bHasColors = mesh.m_bHasColors;
|
|
|
|
Alembic::AbcGeom::IPolyMeshSchema& meshSchema = mesh.m_abcMesh.getSchema();
|
|
Alembic::Abc::TimeSampling& meshTimeSampling = *meshSchema.getTimeSampling();
|
|
size_t numMeshSamples = meshSchema.getNumSamples();
|
|
|
|
auto index = meshTimeSampling.getNearIndex(frameTime, numMeshSamples);
|
|
|
|
Alembic::AbcGeom::IN3fGeomParam::Sample normalSample;
|
|
if (bHasNormals)
|
|
{
|
|
meshSchema.getNormalsParam().getIndexed(normalSample, index.first);
|
|
}
|
|
|
|
Alembic::AbcGeom::IV2fGeomParam::Sample texcoordSample;
|
|
if (bHasTexcoords)
|
|
{
|
|
meshSchema.getUVsParam().getIndexed(texcoordSample, index.first);
|
|
}
|
|
|
|
// Get & check mesh data of current frame
|
|
Alembic::AbcGeom::IPolyMeshSchema::Sample sample = meshSchema.getValue(index.first);
|
|
|
|
const auto& abcPositions = *sample.getPositions();
|
|
const size_t numAbcVertices = abcPositions.size();
|
|
const auto& abcFaceCounts = *sample.getFaceCounts();
|
|
const size_t numAbcFaces = abcFaceCounts.size();
|
|
const auto& abcIndices = *sample.getFaceIndices();
|
|
const size_t numAbcIndices = abcIndices.size();
|
|
|
|
const Alembic::Abc::N3fArraySamplePtr pAbcNormals = bHasNormals ? normalSample.getVals() : 0;
|
|
const size_t numAbcNormals = bHasNormals ? pAbcNormals->size() : 0;
|
|
const Alembic::Abc::UInt32ArraySamplePtr pAbcNormalIndices = bHasNormals ? normalSample.getIndices() : 0;
|
|
const size_t numAbcNormalIndices = bHasNormals ? pAbcNormalIndices->size() : 0;
|
|
|
|
const Alembic::Abc::V2fArraySamplePtr pAbcTexcoords = bHasTexcoords ? texcoordSample.getVals() : 0;
|
|
const size_t numAbcTexcoords = bHasTexcoords ? pAbcTexcoords->size() : 0;
|
|
const Alembic::Abc::UInt32ArraySamplePtr pAbcTexcoordIndices = bHasTexcoords ? texcoordSample.getIndices() : 0;
|
|
const size_t numAbcTexcoordsIndices = bHasTexcoords ? pAbcTexcoordIndices->size() : 0;
|
|
|
|
Alembic::AbcGeom::GeometryScope normalGeoScope = Alembic::AbcGeom::kUnknownScope;
|
|
Alembic::AbcGeom::GeometryScope texcoordGeoScope = Alembic::AbcGeom::kUnknownScope;
|
|
|
|
if (bHasNormals)
|
|
{
|
|
normalGeoScope = Alembic::AbcGeom::GetGeometryScope(meshSchema.getNormalsParam().getMetaData());
|
|
}
|
|
|
|
if (bHasTexcoords)
|
|
{
|
|
texcoordGeoScope = Alembic::AbcGeom::GetGeometryScope(meshSchema.getUVsParam().getMetaData());
|
|
}
|
|
|
|
if (bHasTexcoords && numAbcIndices != numAbcTexcoordsIndices)
|
|
{
|
|
RCLogWarning(" Mesh number of position indices doesn't equal number of "
|
|
"texcoord indices (position indices: %u, texcoord indices: %u). Skipped:\n %s",
|
|
(uint)numAbcIndices, (uint)numAbcTexcoordsIndices, mesh.m_abcMesh.getFullName().c_str());
|
|
return false;
|
|
}
|
|
|
|
AlembicColorSampleArray colors = bHasColors ? AlembicColorSampleArray(mesh.m_colorParamName, meshSchema, 0) : AlembicColorSampleArray();
|
|
|
|
if (bHasColors && numAbcIndices != colors.GetNumIndices())
|
|
{
|
|
RCLogWarning(" Mesh number of position indices doesn't equal number of "
|
|
"color indices (position indices: %u, color indices: %u). Skipped:\n %s",
|
|
(uint)numAbcIndices, (uint)colors.GetNumIndices(), mesh.m_abcMesh.getFullName().c_str());
|
|
return false;
|
|
}
|
|
|
|
// Initialize hashes to detect same vertices.
|
|
// TODO: There could be hash collisions, but they are so unlikely, that we don't care for now.
|
|
std::vector<uint64> abcVertexHashes;
|
|
if (!ComputeVertexHashes(abcVertexHashes, currentFrame, numAbcIndices, mesh, meshTimeSampling, numMeshSamples,
|
|
meshSchema, bHasNormals, bHasTexcoords, bHasColors, numAbcNormalIndices, numAbcTexcoordsIndices, numAbcFaces))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Now that we have the vertex hashes we can create the triangle topology.
|
|
// Convert to triangles (alembic supports arbitrary faces) and split vertices if necessary
|
|
// (GPU expects vertex, normals, texcoords, etc. buffers to have same cardinality)
|
|
|
|
// Temp buffer for face indices
|
|
std::vector<uint32> faceIndices;
|
|
|
|
// Map from material ID to indices
|
|
std::unordered_map<uint, std::vector<uint32> > indices;
|
|
|
|
// Stores the positions of vertices in the vertex buffer
|
|
std::unordered_map<uint64, uint32> vertexDigestToVertexBufferIndexMap;
|
|
std::vector<AlembicCompilerVertex> vertices;
|
|
|
|
size_t currentIndexArraysIndex = 0;
|
|
for (size_t face = 0; face < numAbcFaces; ++face)
|
|
{
|
|
size_t numFaceVertices = abcFaceCounts[face];
|
|
|
|
if (numFaceVertices < 3)
|
|
{
|
|
currentIndexArraysIndex += numFaceVertices;
|
|
continue;
|
|
}
|
|
|
|
// If face is not contained in a face set, the default material ID is 0
|
|
auto findIter = mesh.m_materialIdMap.find(face);
|
|
const uint faceMaterialId = (findIter != mesh.m_materialIdMap.end()) ? findIter->second : 0;
|
|
|
|
// First loop through face and create indices/vertices
|
|
faceIndices.resize(0);
|
|
for (size_t faceVertexIndex = 0; faceVertexIndex < numFaceVertices; ++faceVertexIndex)
|
|
{
|
|
const int32_t positionIndex = abcIndices[currentIndexArraysIndex];
|
|
|
|
uint32_t normalIndex = bHasNormals ? GetIndex(normalGeoScope, pAbcNormalIndices, currentIndexArraysIndex, positionIndex) : 0;
|
|
uint32_t texcoordsIndex = bHasTexcoords ? GetIndex(texcoordGeoScope, pAbcTexcoordIndices, currentIndexArraysIndex, positionIndex) : 0;
|
|
|
|
const int32_t colorsIndex = colors.getIndex(currentIndexArraysIndex);
|
|
|
|
// Search if vertex is already in vertex buffer by its digest
|
|
const uint64 vertexDigest = abcVertexHashes[currentIndexArraysIndex];
|
|
|
|
// Get normal & texcoords for that vertex
|
|
const Imath::Vec3<float>& abcPosition = abcPositions[positionIndex];
|
|
const Imath::Vec3<float>& abcNormal = bHasNormals ? (*pAbcNormals)[normalIndex] : Imath::Vec3<float>(0.0f, 0.0f, 0.0f);
|
|
const Imath::Vec2<float>& abcTexcoord = bHasTexcoords ? (*pAbcTexcoords)[texcoordsIndex] : Imath::Vec2<float>(0.0f, 0.0f);
|
|
|
|
// Convert to geom cache vertex
|
|
AlembicCompilerVertex vertex;
|
|
vertex.m_position = FromAlembicPosition(abcPosition);
|
|
vertex.m_normal = Vec3(abcNormal.x, abcNormal.y, abcNormal.z);
|
|
vertex.m_texcoords = FromAlembicTexcoord(abcTexcoord);
|
|
vertex.m_rgba = colors[colorsIndex];
|
|
|
|
uint32 vertexIndex;
|
|
auto findIter2 = vertexDigestToVertexBufferIndexMap.find(vertexDigest);
|
|
if (findIter2 != vertexDigestToVertexBufferIndexMap.end())
|
|
{
|
|
// Vertex already in buffer
|
|
vertexIndex = findIter2->second;
|
|
}
|
|
else
|
|
{
|
|
// We need to add a vertex
|
|
size_t newIndex = vertices.size();
|
|
|
|
// Check if index fits in 16 bits if necessary
|
|
if (!m_b32BitIndices && newIndex >= 65535)
|
|
{
|
|
RCLogWarning(" Mesh results in more than 65536 compiled vertices. Skipped:\n %s", mesh.m_abcMesh.getFullName().c_str());
|
|
return false;
|
|
}
|
|
|
|
vertexIndex = (uint32)newIndex;
|
|
vertexDigestToVertexBufferIndexMap[vertexDigest] = vertexIndex;
|
|
|
|
// Add the vertex to the vertex buffer
|
|
vertices.push_back(vertex);
|
|
}
|
|
|
|
if (mesh.m_animatedStreams != 0 && (mesh.m_animatedStreams & GeomCacheFile::eStream_Indices) == 0 || !mesh.m_bHasNormals)
|
|
{
|
|
// Add to index mapping list
|
|
mesh.m_abcIndexToGeomCacheIndex.push_back(vertexIndex);
|
|
}
|
|
|
|
// Add to index buffer
|
|
faceIndices.push_back(vertexIndex);
|
|
|
|
// Advance to next index
|
|
++currentIndexArraysIndex;
|
|
}
|
|
|
|
// Triangulate face
|
|
for (size_t i = 1; i < numFaceVertices - 1; ++i)
|
|
{
|
|
indices[faceMaterialId].push_back(faceIndices[0]);
|
|
indices[faceMaterialId].push_back(faceIndices[i + 1]);
|
|
indices[faceMaterialId].push_back(faceIndices[i]);
|
|
}
|
|
}
|
|
|
|
if (!mesh.m_bHasNormals)
|
|
{
|
|
CalculateSmoothNormals(vertices, mesh, abcFaceCounts, abcIndices, abcPositions);
|
|
}
|
|
|
|
// Compute mesh hash
|
|
uint64 meshHash = 0;
|
|
const size_t numVertexHashes = abcVertexHashes.size();
|
|
for (size_t i = 0; i < numVertexHashes; ++i)
|
|
{
|
|
uint64 vertexHash = abcVertexHashes[i];
|
|
AlembicCompilerHashCombine<uint64>(meshHash, vertexHash);
|
|
}
|
|
mesh.m_hash = meshHash;
|
|
|
|
// Optimize indices
|
|
for (auto iter = indices.begin(); iter != indices.end(); ++iter)
|
|
{
|
|
const size_t cacheSize = 16;
|
|
const uint verticesPerFace = 3;
|
|
|
|
const uint materialId = iter->first;
|
|
std::vector<uint32>& indices2 = iter->second;
|
|
|
|
ForsythFaceReorderer faceReorderer;
|
|
std::vector<uint32> optimizedIndices;
|
|
optimizedIndices.resize(indices2.size());
|
|
faceReorderer.reorderFaces(cacheSize, verticesPerFace, indices2.size(), &indices2[0], &optimizedIndices[0], 0);
|
|
|
|
mesh.m_indicesMap[materialId] = optimizedIndices;
|
|
}
|
|
|
|
if (vertices.size() < numAbcVertices)
|
|
{
|
|
RCLogWarning(" Mesh contains unused vertices:\n %s", mesh.m_abcMesh.getFullName().c_str());
|
|
}
|
|
else
|
|
{
|
|
m_numVertexSplits.fetch_add((vertices.size() - numAbcVertices));
|
|
}
|
|
|
|
if (m_positionPrecision != 0.0)
|
|
{
|
|
// Calculate needed position precision
|
|
const Vec3 aabbSize = mesh.m_aabb.GetSize();
|
|
const Vec3d worldSize = Vec3d(aabbSize.x * transform.s.x, aabbSize.y * transform.s.y, aabbSize.z * transform.s.z);
|
|
const Vec3d wantedQuantization = (worldSize * 1000.0) / m_positionPrecision;
|
|
mesh.m_positionPrecision[0] = (wantedQuantization.x > 0.0) ? std::max((uint8)(std::min(ceil(log(wantedQuantization.x) / log(2.0)), 16.0)), (uint8)1) : 1;
|
|
mesh.m_positionPrecision[1] = (wantedQuantization.y > 0.0) ? std::max((uint8)(std::min(ceil(log(wantedQuantization.y) / log(2.0)), 16.0)), (uint8)1) : 1;
|
|
mesh.m_positionPrecision[2] = (wantedQuantization.z > 0.0) ? std::max((uint8)(std::min(ceil(log(wantedQuantization.z) / log(2.0)), 16.0)), (uint8)1) : 1;
|
|
}
|
|
else
|
|
{
|
|
// max precision - use all 16 bits
|
|
mesh.m_positionPrecision[0] = 16;
|
|
mesh.m_positionPrecision[1] = 16;
|
|
mesh.m_positionPrecision[2] = 16;
|
|
}
|
|
|
|
if (m_uvMax == RC_ABC_AUTOMATIC_UVMAX_DETECTION_VALUE)
|
|
{
|
|
// loop over mesh to determine the largeest UV value to store in mesh's m_uvMax
|
|
const size_t numVertices = vertices.size();
|
|
mesh.m_uvMax = .0f;
|
|
for (uint32 vertexIndex = 0; vertexIndex < numVertices; ++vertexIndex)
|
|
{
|
|
const AlembicCompilerVertex& vertex = vertices[vertexIndex];
|
|
const float maxCoordUV = max(vertex.m_texcoords.x, vertex.m_texcoords.y);
|
|
if (mesh.m_uvMax < maxCoordUV)
|
|
{
|
|
mesh.m_uvMax = maxCoordUV;
|
|
}
|
|
}
|
|
|
|
RCLog("Detected per-mesh uvMax value: %g", mesh.m_uvMax);
|
|
}
|
|
else
|
|
{
|
|
// user specified cache-wide uvMax value
|
|
mesh.m_uvMax = m_uvMax;
|
|
}
|
|
|
|
// Finally compile vertices to stored format
|
|
if (!CompileVertices(vertices, mesh, mesh.m_staticMeshData, false))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (mesh.m_animatedStreams != 0 && (mesh.m_animatedStreams & GeomCacheFile::eStream_Indices) == 0)
|
|
{
|
|
// Pass mesh to encoder to optimize vertex order for compression
|
|
if (!GeomCacheEncoder::OptimizeMeshForCompression(mesh, m_bMeshPrediction))
|
|
{
|
|
RCLogWarning(" Could not optimize for compression:\n %s", mesh.m_abcMesh.getFullName().c_str());
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AlembicCompiler::UpdateVertexData(GeomCache::Mesh& mesh, const size_t currentFrame)
|
|
{
|
|
const bool bHasNormals = mesh.m_bHasNormals;
|
|
const bool bHasTexcoords = mesh.m_bHasTexcoords;
|
|
const bool bHasColors = mesh.m_bHasColors;
|
|
|
|
const Alembic::Abc::chrono_t frameTime = m_frameTimes[currentFrame];
|
|
|
|
Alembic::AbcGeom::IPolyMeshSchema& meshSchema = mesh.m_abcMesh.getSchema();
|
|
Alembic::Abc::TimeSampling& meshTimeSampling = *meshSchema.getTimeSampling();
|
|
size_t numMeshSamples = meshSchema.getNumSamples();
|
|
|
|
auto index = meshTimeSampling.getNearIndex(frameTime, numMeshSamples);
|
|
|
|
Alembic::AbcGeom::IN3fGeomParam::Sample normalSample;
|
|
if (bHasNormals)
|
|
{
|
|
meshSchema.getNormalsParam().getIndexed(normalSample, index.first);
|
|
}
|
|
|
|
Alembic::AbcGeom::IV2fGeomParam::Sample texcoordSample;
|
|
if (bHasTexcoords)
|
|
{
|
|
meshSchema.getUVsParam().getIndexed(texcoordSample, index.first);
|
|
}
|
|
|
|
// Get & check mesh data of first frame
|
|
Alembic::AbcGeom::IPolyMeshSchema::Sample sample = meshSchema.getValue(index.first);
|
|
|
|
const auto& abcPositions = *sample.getPositions();
|
|
const size_t numAbcVertices = abcPositions.size();
|
|
const auto& abcFaceCounts = *sample.getFaceCounts();
|
|
const size_t numAbcFaces = abcFaceCounts.size();
|
|
const auto& abcIndices = *sample.getFaceIndices();
|
|
const size_t numAbcIndices = abcIndices.size();
|
|
|
|
const Alembic::Abc::N3fArraySamplePtr pAbcNormals = bHasNormals ? normalSample.getVals() : 0;
|
|
const size_t numAbcNormals = bHasNormals ? pAbcNormals->size() : 0;
|
|
const Alembic::Abc::UInt32ArraySamplePtr pAbcNormalIndices = bHasNormals ? normalSample.getIndices() : 0;
|
|
const size_t numAbcNormalIndices = bHasNormals ? pAbcNormalIndices->size() : 0;
|
|
|
|
const Alembic::Abc::V2fArraySamplePtr pAbcTexcoords = bHasTexcoords ? texcoordSample.getVals() : Alembic::Abc::V2fArraySamplePtr();
|
|
const size_t numAbcTexcoords = bHasTexcoords ? pAbcTexcoords->size() : 0;
|
|
const Alembic::Abc::UInt32ArraySamplePtr pAbcTexcoordIndices = bHasTexcoords ? texcoordSample.getIndices() : Alembic::Abc::UInt32ArraySamplePtr();
|
|
const size_t numAbcTexcoordsIndices = bHasTexcoords ? pAbcTexcoordIndices->size() : 0;
|
|
|
|
AlembicColorSampleArray colors = mesh.m_bHasColors ? AlembicColorSampleArray(mesh.m_colorParamName, meshSchema, index.first) : AlembicColorSampleArray();
|
|
|
|
std::vector<AlembicCompilerVertex> vertices;
|
|
vertices.resize(mesh.m_staticMeshData.m_positions.size());
|
|
|
|
size_t currentIndexArraysIndex = 0;
|
|
for (size_t face = 0; face < numAbcFaces; ++face)
|
|
{
|
|
size_t numFaceVertices = abcFaceCounts[face];
|
|
|
|
if (numFaceVertices < 3)
|
|
{
|
|
currentIndexArraysIndex += numFaceVertices;
|
|
continue;
|
|
}
|
|
|
|
// First loop through face and create indices/vertices
|
|
for (size_t faceVertexIndex = 0; faceVertexIndex < numFaceVertices; ++faceVertexIndex)
|
|
{
|
|
const int32 positionIndex = abcIndices[currentIndexArraysIndex];
|
|
const int32 normalIndex = bHasNormals ? (*pAbcNormalIndices)[currentIndexArraysIndex] : 0;
|
|
const int32 texcoordsIndex = bHasTexcoords ? (*pAbcTexcoordIndices)[currentIndexArraysIndex] : 0;
|
|
const int32 colorsIndex = bHasColors ? colors.getIndex(currentIndexArraysIndex) : 0;
|
|
|
|
// Get normal & texcoords for that vertex
|
|
const Imath::Vec3<float>& abcPosition = abcPositions[positionIndex];
|
|
const Imath::Vec3<float>& abcNormal = bHasNormals ? (*pAbcNormals)[normalIndex] : Imath::Vec3<float>(0.0f, 0.0f, 0.0f);
|
|
const Imath::Vec2<float>& abcTexcoord = bHasTexcoords ? (*pAbcTexcoords)[texcoordsIndex] : Imath::Vec2<float>(0.0f, 0.0f);
|
|
|
|
// Convert to geom cache vertex
|
|
AlembicCompilerVertex vertex;
|
|
vertex.m_position = FromAlembicPosition(abcPosition);
|
|
vertex.m_normal = Vec3(abcNormal.x, abcNormal.y, abcNormal.z);
|
|
vertex.m_texcoords = FromAlembicTexcoord(abcTexcoord);
|
|
vertex.m_rgba = bHasColors ? colors[colorsIndex] : Vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
|
|
|
if (currentIndexArraysIndex >= mesh.m_abcIndexToGeomCacheIndex.size())
|
|
{
|
|
RCLogError(" Invalid index mapping:\n %s", mesh.m_abcMesh.getFullName().c_str());
|
|
return false;
|
|
}
|
|
|
|
// Update the vertex in the index buffer. This write can happen multiple times to the same
|
|
// location, if the vertex is referred multiple times. The values are equal, so we don't care.
|
|
const uint32 vertexIndex = mesh.m_abcIndexToGeomCacheIndex[currentIndexArraysIndex];
|
|
vertices[vertexIndex] = vertex;
|
|
|
|
++currentIndexArraysIndex;
|
|
}
|
|
}
|
|
|
|
if (!mesh.m_bHasNormals)
|
|
{
|
|
CalculateSmoothNormals(vertices, mesh, abcFaceCounts, abcIndices, abcPositions);
|
|
}
|
|
|
|
if (!CompileVertices(vertices, mesh, mesh.m_meshDataBuffer.m_meshData, true))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void AlembicCompiler::CalculateSmoothNormals(std::vector<AlembicCompilerVertex>& vertices, GeomCache::Mesh& mesh,
|
|
const Alembic::Abc::Int32ArraySample& faceCounts, const Alembic::Abc::Int32ArraySample& faceIndices,
|
|
const Alembic::Abc::P3fArraySample& facePositions)
|
|
{
|
|
const size_t numFaces = faceCounts.size();
|
|
const size_t numPositions = facePositions.size();
|
|
|
|
std::vector<Vec3> normals(numPositions, Vec3(0.0f, 0.0f, 0.0f));
|
|
std::vector<Vec3> tempFacePositions;
|
|
|
|
// Compute face normals of alembic mesh and add up normals at each vertex
|
|
size_t currentIndexArraysIndex = 0;
|
|
for (size_t face = 0; face < numFaces; ++face)
|
|
{
|
|
size_t numFaceVertices = faceCounts[face];
|
|
|
|
if (numFaceVertices < 3)
|
|
{
|
|
currentIndexArraysIndex += numFaceVertices;
|
|
continue;
|
|
}
|
|
|
|
tempFacePositions.clear();
|
|
for (size_t i = 0; i < numFaceVertices; ++i)
|
|
{
|
|
uint index = faceIndices[currentIndexArraysIndex + i];
|
|
tempFacePositions.push_back(FromAlembicPosition(facePositions[index]));
|
|
}
|
|
|
|
for (size_t i = 1; i < numFaceVertices - 1; ++i)
|
|
{
|
|
Vec3 p1 = tempFacePositions[0];
|
|
Vec3 p2 = tempFacePositions[i + 1];
|
|
Vec3 p3 = tempFacePositions[i];
|
|
|
|
Vec3 edge12 = p2 - p1;
|
|
Vec3 edge23 = p3 - p2;
|
|
Vec3 edge31 = p1 - p3;
|
|
|
|
const float influence1 = edge12.Cross(edge31).GetLength();
|
|
const float influence2 = edge23.Cross(edge12).GetLength();
|
|
const float influence3 = edge31.Cross(edge23).GetLength();
|
|
|
|
Vec3 faceNormal = edge31.Cross(edge12);
|
|
faceNormal.Normalize();
|
|
|
|
normals[faceIndices[currentIndexArraysIndex]] += influence1 * faceNormal;
|
|
normals[faceIndices[currentIndexArraysIndex + i + 1]] += influence2 * faceNormal;
|
|
normals[faceIndices[currentIndexArraysIndex + i]] += influence3 * faceNormal;
|
|
}
|
|
|
|
currentIndexArraysIndex += numFaceVertices;
|
|
}
|
|
|
|
// Normalize all the normals
|
|
for (uint i = 0; i < numPositions; ++i)
|
|
{
|
|
normals[i].Normalize();
|
|
}
|
|
|
|
// Assign them to mesh vertices
|
|
currentIndexArraysIndex = 0;
|
|
for (size_t face = 0; face < numFaces; ++face)
|
|
{
|
|
size_t numFaceVertices = faceCounts[face];
|
|
|
|
if (numFaceVertices < 3)
|
|
{
|
|
currentIndexArraysIndex += numFaceVertices;
|
|
continue;
|
|
}
|
|
|
|
for (size_t i = 0; i < numFaceVertices; ++i)
|
|
{
|
|
uint index = mesh.m_abcIndexToGeomCacheIndex[currentIndexArraysIndex];
|
|
vertices[index].m_normal = normals[faceIndices[currentIndexArraysIndex++]];
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
GeomCacheFile::QTangent EncodeQTangent(Matrix33 frame, const bool bReflection)
|
|
{
|
|
frame.OrthonormalizeFast();
|
|
if (!frame.IsOrthonormalRH(0.1f))
|
|
{
|
|
frame.SetIdentity();
|
|
}
|
|
|
|
Quat qFrame(frame);
|
|
|
|
qFrame.v = -qFrame.v;
|
|
if (qFrame.w < 0.0f)
|
|
{
|
|
qFrame = -qFrame;
|
|
}
|
|
|
|
const float kMultiplier = float((2 << (GeomCacheFile::kTangentQuatPrecision - 1)) - 1);
|
|
|
|
// Make sure w is never 0 by applying the smallest possible bias.
|
|
static const float kBias = 1.0f / kMultiplier;
|
|
static const float kBiasScale = sqrtf(1.0f - kBias * kBias);
|
|
if (qFrame.w < kBias && qFrame.w > -kBias)
|
|
{
|
|
qFrame *= kBiasScale;
|
|
qFrame.w = kBias;
|
|
}
|
|
|
|
if (bReflection)
|
|
{
|
|
qFrame = -qFrame;
|
|
}
|
|
|
|
GeomCacheFile::QTangent compressedQTangent;
|
|
compressedQTangent[0] = static_cast<int16>(qFrame.v[0] * kMultiplier);
|
|
compressedQTangent[1] = static_cast<int16>(qFrame.v[1] * kMultiplier);
|
|
compressedQTangent[2] = static_cast<int16>(qFrame.v[2] * kMultiplier);
|
|
compressedQTangent[3] = static_cast<int16>(qFrame.w * kMultiplier);
|
|
|
|
return compressedQTangent;
|
|
}
|
|
}
|
|
|
|
bool AlembicCompiler::CompileVertices(std::vector<AlembicCompilerVertex>& vertices, GeomCache::Mesh& mesh, GeomCache::MeshData& meshData, const bool bUpdate)
|
|
{
|
|
// Resize arrays if necessary
|
|
if (meshData.m_positions.size() == 0)
|
|
{
|
|
meshData.m_positions.resize(vertices.size());
|
|
meshData.m_texcoords.resize(vertices.size());
|
|
meshData.m_qTangents.resize(vertices.size());
|
|
|
|
if (mesh.m_bHasColors)
|
|
{
|
|
meshData.m_reds.resize(vertices.size());
|
|
meshData.m_greens.resize(vertices.size());
|
|
meshData.m_blues.resize(vertices.size());
|
|
meshData.m_alphas.resize(vertices.size());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
assert(meshData.m_positions.size() == vertices.size());
|
|
if (meshData.m_positions.size() != vertices.size())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!bUpdate)
|
|
{
|
|
mesh.m_reflections.resize(vertices.size());
|
|
}
|
|
|
|
// Avoid division by zero if mesh has no extent in a dimension
|
|
Vec3 aabbSize = mesh.m_aabb.GetSize();
|
|
aabbSize.x = (aabbSize.x == 0.0f) ? 1.0f : aabbSize.x;
|
|
aabbSize.y = (aabbSize.y == 0.0f) ? 1.0f : aabbSize.y;
|
|
aabbSize.z = (aabbSize.z == 0.0f) ? 1.0f : aabbSize.z;
|
|
|
|
const float kMultiplierX = float((2 << (mesh.m_positionPrecision[0] - 1)) - 1);
|
|
const float kMultiplierY = float((2 << (mesh.m_positionPrecision[1] - 1)) - 1);
|
|
const float kMultiplierZ = float((2 << (mesh.m_positionPrecision[2] - 1)) - 1);
|
|
const size_t numVertices = meshData.m_positions.size();
|
|
const int verbosityLevel = m_CC.m_config->HasKey("verbose") ? m_CC.m_config->GetAsInt("verbose", 1, 1) : 0;
|
|
|
|
if (verbosityLevel > 2)
|
|
{
|
|
RCLog("Using uvMax of %g", mesh.m_uvMax);
|
|
}
|
|
|
|
// Quantize positions & texcoords
|
|
for (uint32 vertexIndex = 0; vertexIndex < numVertices; ++vertexIndex)
|
|
{
|
|
const AlembicCompilerVertex& vertex = vertices[vertexIndex];
|
|
|
|
// Remap position to [0, 1] range in AABB
|
|
Vec3 mappedPosition = (vertex.m_position - mesh.m_aabb.min) / aabbSize;
|
|
|
|
// Now map to range of 16 bit unsigned integer and store
|
|
GeomCacheFile::Position& compressedPosition = meshData.m_positions[vertexIndex];
|
|
compressedPosition.x = (uint16)(mappedPosition.x * kMultiplierX);
|
|
compressedPosition.y = (uint16)(mappedPosition.y * kMultiplierY);
|
|
compressedPosition.z = (uint16)(mappedPosition.z * kMultiplierZ);
|
|
|
|
// Wrap around texcoords at mesh.m_uvMax
|
|
Vec2 mappedTexcoords = Vec2(fmod(vertex.m_texcoords.x, mesh.m_uvMax),
|
|
fmod(vertex.m_texcoords.y, mesh.m_uvMax));
|
|
|
|
// Now map to range of 16 bit unsigned integer and store
|
|
GeomCacheFile::Texcoords& compressedTexcoords = meshData.m_texcoords[vertexIndex];
|
|
compressedTexcoords = (mappedTexcoords / mesh.m_uvMax) * 32767.0f;
|
|
|
|
if (mesh.m_bHasColors)
|
|
{
|
|
meshData.m_reds[vertexIndex] = static_cast<GeomCacheFile::Color>(clamp_tpl(vertex.m_rgba[0], 0.0f, 1.0f) * 255.0f);
|
|
meshData.m_greens[vertexIndex] = static_cast<GeomCacheFile::Color>(clamp_tpl(vertex.m_rgba[1], 0.0f, 1.0f) * 255.0f);
|
|
meshData.m_blues[vertexIndex] = static_cast<GeomCacheFile::Color>(clamp_tpl(vertex.m_rgba[2], 0.0f, 1.0f) * 255.0f);
|
|
meshData.m_alphas[vertexIndex] = static_cast<GeomCacheFile::Color>(clamp_tpl(vertex.m_rgba[3], 0.0f, 1.0f) * 255.0f);
|
|
}
|
|
}
|
|
|
|
// Compute new tangents
|
|
for (auto iter = mesh.m_indicesMap.begin(); iter != mesh.m_indicesMap.end(); ++iter)
|
|
{
|
|
const std::vector<uint32>& indices = iter->second;
|
|
assert (indices.size() % 3 == 0);
|
|
|
|
GeomCacheMeshTriangleInputProxy inputProxy(indices, vertices);
|
|
CTangentSpaceCalculation tangentSpaceCalculation;
|
|
|
|
string errorMessage;
|
|
eCalculateTangentSpaceErrorCode errorCode =
|
|
tangentSpaceCalculation.CalculateTangentSpace(inputProxy, true, errorMessage);
|
|
|
|
if (errorCode != CALCULATE_TANGENT_SPACE_NO_ERRORS)
|
|
{
|
|
RCLogError("Tangent space calculation failed");
|
|
return false;
|
|
}
|
|
|
|
// Get tangents back and convert them to qtangents
|
|
const size_t numTriangles = indices.size() / 3;
|
|
for (uint32 triangleIndex = 0; triangleIndex < numTriangles; ++triangleIndex)
|
|
{
|
|
uint32 triangleBaseIndices[3];
|
|
tangentSpaceCalculation.GetTriangleBaseIndices(triangleIndex, triangleBaseIndices);
|
|
|
|
for (uint vertex = 0; vertex < 3; ++vertex)
|
|
{
|
|
Vec3 tangent, biTangent, normal;
|
|
tangentSpaceCalculation.GetBase(triangleBaseIndices[vertex], (float*)&tangent, (float*)&biTangent, (float*)&normal);
|
|
|
|
// Convert to q tangent
|
|
Vec3 crossedNormal = tangent.Cross(biTangent).GetNormalized();
|
|
bool bReflected = crossedNormal.Dot(normal) < 0.0f;
|
|
|
|
const size_t index = indices[triangleIndex * 3 + vertex];
|
|
|
|
if (!bUpdate)
|
|
{
|
|
// Store tangent reflection values
|
|
mesh.m_reflections[index] = bReflected;
|
|
}
|
|
else if (bReflected != mesh.m_reflections[index])
|
|
{
|
|
// Enforce reflection of first frame
|
|
bReflected = !bReflected;
|
|
biTangent = -biTangent;
|
|
crossedNormal = tangent.Cross(biTangent).GetNormalized();
|
|
}
|
|
|
|
Matrix33 frame;
|
|
frame.SetRow(0, tangent);
|
|
frame.SetRow(1, biTangent);
|
|
frame.SetRow(2, crossedNormal);
|
|
|
|
// Quantize and store
|
|
meshData.m_qTangents[index] = EncodeQTangent(frame, bReflected);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void AlembicCompiler::AppendTransformFrameDataRec(GeomCache::Node& node, const uint bufferIndex) const
|
|
{
|
|
node.m_animatedNodeData.push_back(node.m_nodeDataBuffer);
|
|
|
|
const size_t numChildren = node.m_children.size();
|
|
for (size_t i = 0; i < numChildren; ++i)
|
|
{
|
|
AppendTransformFrameDataRec(*node.m_children[i], bufferIndex);
|
|
}
|
|
}
|
|
|
|
void AlembicCompiler::Cleanup()
|
|
{
|
|
m_rootNode.m_type = GeomCacheFile::eNodeType_Transform;
|
|
m_rootNode.m_transformType = GeomCacheFile::eTransformType_Constant;
|
|
m_rootNode.m_staticNodeData.m_bVisible = true;
|
|
m_rootNode.m_staticNodeData.m_transform.SetIdentity();
|
|
m_rootNode.m_pMesh.reset();
|
|
m_rootNode.m_physicsGeometry.clear();
|
|
m_rootNode.m_children.clear();
|
|
m_rootNode.m_abcObject = Alembic::Abc::IObject();
|
|
m_rootNode.m_abcXForms.clear();
|
|
m_rootNode.m_animatedNodeData.clear();
|
|
m_rootNode.m_name.clear();
|
|
|
|
m_timeSamplings.clear();
|
|
m_frameTimes.clear();
|
|
m_meshes.clear();
|
|
m_digestToMeshMap.clear();
|
|
m_errorCount = 0;
|
|
m_numAnimatedMeshes = 0;
|
|
}
|