#include "CacheReaderAlembic.h"
#include "CacheAlembicUtil.h"
#include "gpuCacheUtil.h"
#include "gpuCacheStrings.h"
#include <Alembic/AbcCoreFactory/IFactory.h>
#include <Alembic/AbcGeom/Visibility.h>
#include <Alembic/AbcGeom/ArchiveBounds.h>
#include <maya/MStringArray.h>
#include <maya/MAnimControl.h>
#include <maya/MString.h>
#include <maya/MGlobal.h>
#include <maya/MFnMesh.h>
#include <maya/MFnNurbsCurve.h>
#include <maya/MFnNurbsCurveData.h>
#include <maya/MTrimBoundaryArray.h>
#include <maya/MFloatArray.h>
#include <maya/MFloatVector.h>
#include <maya/MPointArray.h>
#include <maya/MFloatPointArray.h>
#include <maya/MFloatVectorArray.h>
#include <maya/MUintArray.h>
#include <boost/ref.hpp>
#include <fstream>
#include <cassert>
using namespace CacheAlembicUtil;
namespace GPUCache {
namespace CacheReaderAlembicPrivate {
template <class ArrayProperty>
boost::shared_ptr<ReadableArray<typename AlembicArray<ArrayProperty>::T> >
AlembicArray<ArrayProperty>::create(
    const ArraySamplePtr& arraySamplePtr, const Digest& digest ) 
{
    const size_t size = (arraySamplePtr->size() *
                         ArrayProperty::traits_type::dataType().getExtent());
#ifndef NDEBUG
    
    
    Digest checkDigest;
    Alembic::Util::MurmurHash3_x64_128(
        arraySamplePtr->get(), size * sizeof(T), sizeof(T), checkDigest.words);
    assert(digest == checkDigest);
#endif
        
    
    
    
    boost::shared_ptr<ReadableArray<T> > ret;
    {
        tbb::mutex::scoped_lock lock(ArrayRegistry<T>::mutex());
        
        
        ret = ArrayRegistry<T>::lookupReadable(digest, size);
        
        if (!ret) {
            ret = boost::make_shared<AlembicArray<ArrayProperty> >(
                arraySamplePtr, digest);
            ArrayRegistry<T>::insert(ret);
        }
    }
    return ret;
}
template <class ArrayProperty>
AlembicArray<ArrayProperty>::~AlembicArray() {}
template <class ArrayProperty>
const typename AlembicArray<ArrayProperty>::T*
AlembicArray<ArrayProperty>::get() const 
{
    return reinterpret_cast<const T*>(fArraySamplePtr->get());
}
template <typename PROPERTY>
typename ArrayPropertyCacheWithConverter<PROPERTY>::ConvertionMap
ArrayPropertyCacheWithConverter<PROPERTY>::fsConvertionMap;
template class ArrayPropertyCacheWithConverter<
    Alembic::Abc::IInt32ArrayProperty>;
class ScopedUnlockAlembic : boost::noncopyable
{
public:
    ScopedUnlockAlembic()
    {
        gsAlembicMutex.unlock();
    }
    ~ScopedUnlockAlembic()
    {
        gsAlembicMutex.lock();
    }
};
void CheckInterruptAndPause(const char* state)
{
    GlobalReaderCache& readerCache = GlobalReaderCache::theCache();
    if (readerCache.isInterrupted()) {
        
        throw CacheReaderInterruptException(state);
    }
    else if (readerCache.isPaused()) {
        
        ScopedUnlockAlembic unlock;
        readerCache.pauseUntilNotified();
    }
}
template<class INFO>
DataProvider::DataProvider(
        Alembic::AbcGeom::IGeomBaseSchema<INFO>& abcGeom,
        Alembic::Abc::TimeSamplingPtr            timeSampling,
        size_t numSamples,
        bool   needUVs)
    : fAnimTimeRange(TimeInterval::kInvalid),
      fBBoxAndVisValidityInterval(TimeInterval::kInvalid),
      fValidityInterval(TimeInterval::kInvalid),
      fNeedUVs(needUVs)
{
    Alembic::Abc::IObject shapeObject = abcGeom.getObject();
    
    Alembic::AbcGeom::IVisibilityProperty visibility = 
                    Alembic::AbcGeom::GetVisibilityProperty(shapeObject);
    if (visibility) {
        fVisibilityCache.init(visibility);
    }
    
    fBoundingBoxCache.init(abcGeom.getSelfBoundsProperty());
    
    std::vector<Alembic::Abc::IObject> parents;
    Alembic::Abc::IObject current = shapeObject.getParent();
    while (current.valid()) {
        parents.push_back(current);
        current = current.getParent();
    }
    
    fParentVisibilityCache.resize(parents.size());
    for (size_t i = 0; i < fParentVisibilityCache.size(); i++) {
        Alembic::AbcGeom::IVisibilityProperty visibilityProp = 
            Alembic::AbcGeom::GetVisibilityProperty(parents[i]);
        if (visibilityProp) {
            fParentVisibilityCache[i].init(visibilityProp);
        }
    }
    
    fAnimTimeRange = TimeInterval(
        timeSampling->getSampleTime(0),
        timeSampling->getSampleTime(numSamples > 0 ? numSamples-1 : 0)
    );
}
DataProvider::~DataProvider()
{
    fValidityInterval = TimeInterval::kInvalid;
    
    fVisibilityCache.reset();
    fBoundingBoxCache.reset();
    fParentVisibilityCache.clear();
}
bool DataProvider::valid() const
{
    return fBoundingBoxCache.valid();
}
boost::shared_ptr<const ShapeSample> 
DataProvider::getBBoxPlaceHolderSample(double seconds)
{
    boost::shared_ptr<ShapeSample> sample = 
        ShapeSample::createBoundingBoxPlaceHolderSample(
            seconds,
            getBoundingBox(),
            isVisible()
        );
    return sample;
}
void DataProvider::fillBBoxAndVisSample(chrono_t time)
{
    fBBoxAndVisValidityInterval = updateBBoxAndVisCache(time);
    assert(fBBoxAndVisValidityInterval.valid());
}
void DataProvider::fillTopoAndAttrSample(chrono_t time)
{
    fValidityInterval = updateCache(time);
    assert(fValidityInterval.valid());
}
TimeInterval DataProvider::updateBBoxAndVisCache(chrono_t time)
{
    
    
    
    
    
    if (fVisibilityCache.valid()) {
        fVisibilityCache.setTime(time);
    }
    fBoundingBoxCache.setTime(time);
    BOOST_FOREACH (ScalarPropertyCache<Alembic::Abc::ICharProperty>& 
            parentVisPropCache, fParentVisibilityCache) {
        if (parentVisPropCache.valid()) { 
            parentVisPropCache.setTime(time);
        }
    }
    
    TimeInterval validityInterval(TimeInterval::kInfinite);
    if (fVisibilityCache.valid()) {
        validityInterval &= fVisibilityCache.getValidityInterval();
    }
    validityInterval &= fBoundingBoxCache.getValidityInterval();
    BOOST_FOREACH (ScalarPropertyCache<Alembic::Abc::ICharProperty>& 
            parentVisPropCache, fParentVisibilityCache) {
        if (parentVisPropCache.valid()) { 
            validityInterval &= parentVisPropCache.getValidityInterval();
        }
    }
    return validityInterval;
}
TimeInterval DataProvider::updateCache(chrono_t time)
{
    return updateBBoxAndVisCache(time);
}
bool DataProvider::isVisible() const
{
    
    if (fVisibilityCache.valid() && 
            fVisibilityCache.getValue() == char(Alembic::AbcGeom::kVisibilityHidden)) {
        return false;
    }
    
    BOOST_FOREACH (const ScalarPropertyCache<Alembic::Abc::ICharProperty>& 
            parentVisPropCache, fParentVisibilityCache) {
        if (parentVisPropCache.valid() && 
                parentVisPropCache.getValue() == char(Alembic::AbcGeom::kVisibilityHidden)) {
            return false;
        }
    }
    
    return true;
}
template<class SCHEMA>
PolyDataProvider::PolyDataProvider(
    SCHEMA&                         abcMesh,
    const bool                      needUVs)
  : DataProvider(abcMesh, abcMesh.getTimeSampling(), 
                 abcMesh.getNumSamples(), needUVs)
{
    
    fFaceCountsCache.init(abcMesh.getFaceCountsProperty());
    
    fPositionsCache.init(abcMesh.getPositionsProperty());
}
PolyDataProvider::~PolyDataProvider()
{
    
    fFaceCountsCache.reset();
    fPositionsCache.reset();
}
bool PolyDataProvider::valid() const
{
    return DataProvider::valid() &&
            fFaceCountsCache.valid() &&
            fPositionsCache.valid();
}
TimeInterval
PolyDataProvider::updateCache(chrono_t time)
{
    TimeInterval validityInterval(DataProvider::updateCache(time));
    
    fFaceCountsCache.setTime(time);
    fPositionsCache.setTime(time);
    
    validityInterval &= fFaceCountsCache.getValidityInterval();
    validityInterval &= fPositionsCache.getValidityInterval();
    return validityInterval;
}
RawDataProvider::RawDataProvider(
    Alembic::AbcGeom::IPolyMeshSchema& abcMesh,
    const bool needUVs)
    : PolyDataProvider(abcMesh, needUVs),
      fFaceIndicesCache(correctPolygonWinding)
{
    
    fFaceIndicesCache.init(abcMesh.getFaceIndicesProperty());
    
    if (abcMesh.getPropertyHeader(kCustomPropertyWireIndices) != NULL) {
        fWireIndicesCache.init(
            Alembic::Abc::IInt32ArrayProperty(abcMesh.getPtr(), kCustomPropertyWireIndices));
    }
    else if (abcMesh.getPropertyHeader(kCustomPropertyWireIndicesOld) != NULL) {
        fWireIndicesCache.init(
            Alembic::Abc::IInt32ArrayProperty(abcMesh.getPtr(), kCustomPropertyWireIndicesOld));
    }
    
    if (abcMesh.getPropertyHeader(kCustomPropertyShadingGroupSizes) != NULL) {
        fGroupSizesCache.init(
            Alembic::Abc::IInt32ArrayProperty(abcMesh.getPtr(), kCustomPropertyShadingGroupSizes));
    }
    
    if (abcMesh.getPropertyHeader(kCustomPropertyDiffuseColor) != NULL) {
        fDiffuseColorCache.init(
            Alembic::Abc::IC4fProperty(abcMesh.getPtr(), kCustomPropertyDiffuseColor));
    }
    
    Alembic::AbcGeom::IN3fGeomParam normals = abcMesh.getNormalsParam();
    if (normals.valid()) {
        assert(!normals.isIndexed());
        assert(normals.getScope() == Alembic::AbcGeom::kVertexScope);
        fNormalsCache.init(normals.getValueProperty());
    }
    if (fNeedUVs) {
        
        Alembic::AbcGeom::IV2fGeomParam UVs = abcMesh.getUVsParam();
        if (UVs.valid()) {
            assert(!UVs.isIndexed());
            assert(UVs.getScope() == Alembic::AbcGeom::kVertexScope);
            fUVsCache.init(UVs.getValueProperty());
        }
    }
}
RawDataProvider::~RawDataProvider()
{
    
    fFaceIndicesCache.reset();
    fWireIndicesCache.reset();
    fGroupSizesCache.reset();
    fDiffuseColorCache.reset();
    fNormalsCache.reset();
    fUVsCache.reset();
}
bool RawDataProvider::valid() const
{
    return PolyDataProvider::valid() &&
            fFaceIndicesCache.valid() &&
            fWireIndicesCache.valid() &&
            fDiffuseColorCache.valid() &&
            fNormalsCache.valid();
}
boost::shared_ptr<const ShapeSample> 
RawDataProvider::getSample(double seconds)
{
    std::vector<boost::shared_ptr<IndexBuffer> > triangleVertIndices;
    const boost::shared_ptr<ReadableArray<IndexBuffer::index_t> > indexBuffer =
        fFaceIndicesCache.getValue();
    if (fGroupSizesCache.valid()) {
        const boost::shared_ptr<ReadableArray<IndexBuffer::index_t> > groupSizes =
            fGroupSizesCache.getValue();
        
        const IndexBuffer::index_t* groupSizesPtr = groupSizes->get();
        for(size_t i=0, offset=0; i<groupSizes->size(); offset+=3*groupSizesPtr[i], ++i) {
            triangleVertIndices.push_back(
                IndexBuffer::create(
                    indexBuffer, offset, offset+3*groupSizesPtr[i]));
        }
    }
    else {
        triangleVertIndices.push_back(IndexBuffer::create(indexBuffer));
    }
    Alembic::Abc::C4f diffuseColor = fDiffuseColorCache.getValue();
    boost::shared_ptr<ShapeSample> sample = ShapeSample::create(
        seconds,                                                   
        fWireIndicesCache.getValue()->size() / 2,                  
        fPositionsCache.getValue()->size() / 3,                    
        IndexBuffer::create(fWireIndicesCache.getValue()),         
        triangleVertIndices,                                       
        VertexBuffer::createPositions(fPositionsCache.getValue()), 
        getBoundingBox(),                                          
        MColor(diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a),
 
        isVisible()
    );
    if (fNormalsCache.valid()) {
        sample->setNormals(
            VertexBuffer::createNormals(fNormalsCache.getValue()));
    }
    if (fUVsCache.valid()) {
        sample->setUVs(
            VertexBuffer::createUVs(fUVsCache.getValue()));
    }
    return sample;
}
boost::shared_ptr<ReadableArray<IndexBuffer::index_t> >
    RawDataProvider::correctPolygonWinding(const Alembic::Abc::Int32ArraySamplePtr& indices)
{
    size_t count = (*indices).size();
    boost::shared_array<IndexBuffer::index_t> faceIndicesCCW(
        new IndexBuffer::index_t[count]);
    for (size_t i = 0; i < count; i += 3) {
        faceIndicesCCW[i + 2] = (*indices)[i + 0];
        faceIndicesCCW[i + 1] = (*indices)[i + 1];
        faceIndicesCCW[i + 0] = (*indices)[i + 2];
    }
    return SharedArray<IndexBuffer::index_t>::create(
        faceIndicesCCW, count);
}
TimeInterval RawDataProvider::updateCache(chrono_t time)
{
    TimeInterval validityInterval(PolyDataProvider::updateCache(time));
    
    fFaceIndicesCache.setTime(time);
    fWireIndicesCache.setTime(time);
    if (fGroupSizesCache.valid()) {
        fGroupSizesCache.setTime(time);
    }
    fNormalsCache.setTime(time);
    if (fUVsCache.valid()) {
        fUVsCache.setTime(time);
    }
    fDiffuseColorCache.setTime(time);
    
    validityInterval &= fFaceIndicesCache.getValidityInterval();
    validityInterval &= fWireIndicesCache.getValidityInterval();
    if (fGroupSizesCache.valid()) {
        validityInterval &= fGroupSizesCache.getValidityInterval();
    }
    validityInterval &= fNormalsCache.getValidityInterval();
    if (fUVsCache.valid()) {
        validityInterval &= fUVsCache.getValidityInterval();
    }
    validityInterval &= fDiffuseColorCache.getValidityInterval();
    
    size_t numVerts     = fPositionsCache.getValue()->size() / 3;
    size_t numTriangles = fFaceIndicesCache.getValue()->size() / 3;
    if (fFaceCountsCache.getValue()->size() != numTriangles) {
        assert(fFaceCountsCache.getValue()->size() == numTriangles);
        return TimeInterval::kInvalid;
    }
    if (fNormalsCache.getValue()->size() / 3 != numVerts) {
        assert(fNormalsCache.getValue()->size() / 3 == numVerts);
        return TimeInterval::kInvalid;
    }
    if (fUVsCache.valid()) {
        if (fUVsCache.getValue()->size() / 2 != numVerts) {
            assert(fUVsCache.getValue()->size() / 2 == numVerts);
            return TimeInterval::kInvalid;
        }
    }
    
    return validityInterval;
}
Triangulator::Triangulator(
    Alembic::AbcGeom::IPolyMeshSchema& abcMesh,
    const bool needUVs)
    : PolyDataProvider(abcMesh, needUVs),
    fNormalsScope(Alembic::AbcGeom::kUnknownScope), fUVsScope(Alembic::AbcGeom::kUnknownScope)
{
    
    fFaceIndicesCache.init(abcMesh.getFaceIndicesProperty());
    
    Alembic::AbcGeom::IN3fGeomParam normals = abcMesh.getNormalsParam();
    if (normals.valid()) {
        fNormalsScope = normals.getScope();
        if (fNormalsScope == Alembic::AbcGeom::kVaryingScope ||
                fNormalsScope == Alembic::AbcGeom::kVertexScope ||
                fNormalsScope == Alembic::AbcGeom::kFacevaryingScope) {
            fNormalsCache.init(normals.getValueProperty());
            if (normals.isIndexed()) {
                fNormalIndicesCache.init(normals.getIndexProperty());
            }
        }
    }
    
    if (fNeedUVs) {
        Alembic::AbcGeom::IV2fGeomParam UVs = abcMesh.getUVsParam();
        if (UVs.valid()) {
            fUVsScope = UVs.getScope();
            if (fUVsScope == Alembic::AbcGeom::kVaryingScope ||
                fUVsScope == Alembic::AbcGeom::kVertexScope ||
                fUVsScope == Alembic::AbcGeom::kFacevaryingScope) {
                fUVsCache.init(UVs.getValueProperty());
                if (UVs.isIndexed()) {
                    fUVIndicesCache.init(UVs.getIndexProperty());
                }
            }
        }
    }
}
Triangulator::~Triangulator()
{
    
    fFaceIndicesCache.reset();
    fNormalsScope = Alembic::AbcGeom::kUnknownScope;
    fNormalsCache.reset();
    fNormalIndicesCache.reset();
    fUVsScope = Alembic::AbcGeom::kUnknownScope;
    fUVsCache.reset();
    fUVIndicesCache.reset();
}
bool Triangulator::valid() const
{
    return PolyDataProvider::valid() &&
            fFaceIndicesCache.valid();
}
boost::shared_ptr<const ShapeSample> 
Triangulator::getSample(double seconds)
{
    
    if (!fWireIndices || !fTriangleIndices) {
        boost::shared_ptr<ShapeSample> sample =
            ShapeSample::createEmptySample(seconds);
        return sample;
    }
    
    
    std::vector<boost::shared_ptr<IndexBuffer> > triangleVertIndices;
    triangleVertIndices.push_back(IndexBuffer::create(fTriangleIndices));
    boost::shared_ptr<ShapeSample> sample = ShapeSample::create(
        seconds,                                           
        fWireIndices->size() / 2,                          
        fMappedPositions->size() / 3,                      
        IndexBuffer::create(fWireIndices),                 
        triangleVertIndices,                               
        VertexBuffer::createPositions(fMappedPositions),   
        getBoundingBox(),                                  
        Config::kDefaultGrayColor,                         
        isVisible()
    );
    if (fMappedNormals) {
        sample->setNormals(
            VertexBuffer::createNormals(fMappedNormals));
    }
    if (fMappedUVs) {
        sample->setUVs(
            VertexBuffer::createUVs(fMappedUVs));
    }
    return sample;
}
TimeInterval Triangulator::updateCache(chrono_t time)
{
    
    
    bool topologyChanged = fFaceCountsCache.setTime(time);
    bool positionChanged = fPositionsCache.setTime(time);
    TimeInterval validityInterval(PolyDataProvider::updateCache(time));
    
    topologyChanged = fFaceIndicesCache.setTime(time) || topologyChanged;
    if (fNormalsCache.valid()) {
        fNormalsCache.setTime(time);
        if (fNormalIndicesCache.valid()) {
            fNormalIndicesCache.setTime(time);
        }
    }
    if (fUVsCache.valid()) {
        fUVsCache.setTime(time);
        if (fUVIndicesCache.valid()) {
            fUVIndicesCache.setTime(time);
        }
    }
    
    validityInterval &= fFaceIndicesCache.getValidityInterval();
    if (fNormalsCache.valid()) {
        validityInterval &= fNormalsCache.getValidityInterval();
        if (fNormalIndicesCache.valid()) {
            validityInterval &= fNormalIndicesCache.getValidityInterval();
        }
    }
    if (fUVsCache.valid()) {
        validityInterval &= fUVsCache.getValidityInterval();
        if (fUVIndicesCache.valid()) {
            validityInterval &= fUVIndicesCache.getValidityInterval();
        }
    }
    
    check();
    
    if (positionChanged || topologyChanged || !fComputedNormals) {
        
        computeNormals();
    }
    if (topologyChanged || !fVertAttribsIndices) {
        
        convertMultiIndexedStreams();
    }
    remapVertAttribs();
    if (topologyChanged || !fWireIndices) {
        
        computeWireIndices();
    }
    if (topologyChanged || !fTriangleIndices) {
        
        triangulate();
    }
    return validityInterval;
}
template<size_t SIZE>
boost::shared_ptr<ReadableArray<float> > Triangulator::convertMultiIndexedStream(
    boost::shared_ptr<ReadableArray<float> > attribArray,
    boost::shared_ptr<ReadableArray<IndexBuffer::index_t> > indexArray)
{
    
    size_t numVerts                                 = indexArray->size();
    const float* srcAttribs                         = attribArray->get();
    const IndexBuffer::index_t* srcIndices          = indexArray->get();
    boost::shared_array<float> mappedAttribs(new float[numVerts * SIZE]);
    for (size_t i = 0; i < numVerts; i++) {
        for (size_t j = 0; j < SIZE; j++) {
            mappedAttribs[i * SIZE + j] = srcAttribs[srcIndices[i] * SIZE + j];
        }
    }
    return SharedArray<float>::create(mappedAttribs, numVerts * SIZE);
}
void Triangulator::check()
{
    size_t numFaceIndices = fFaceIndicesCache.getValue()->size();
    size_t numVerts       = fPositionsCache.getValue()->size() / 3;
    
    size_t numExpectedNormals = 0;
    if (fNormalsScope == Alembic::AbcGeom::kVaryingScope ||
            fNormalsScope == Alembic::AbcGeom::kVertexScope) {
        numExpectedNormals = numVerts;
    }
    else if (fNormalsScope == Alembic::AbcGeom::kFacevaryingScope) {
        numExpectedNormals = numFaceIndices;
    }
    size_t numActualNormals = 0;
    if (fNormalsCache.valid()) {
        if (fNormalIndicesCache.valid()) {
            numActualNormals = fNormalIndicesCache.getValue()->size();
        }
        else {
            numActualNormals = fNormalsCache.getValue()->size() / 3;
        }
    }
    
    fCheckedNormalsScope = Alembic::AbcGeom::kUnknownScope;
    fCheckedNormals.reset();
    fCheckedNormalIndices.reset();
    
    if (numExpectedNormals == numActualNormals) {
        if (fNormalsCache.valid()) {
            fCheckedNormalsScope = fNormalsScope;
            fCheckedNormals      = fNormalsCache.getValue();
            if (fNormalIndicesCache.valid()) {
                fCheckedNormalIndices = fNormalIndicesCache.getValue();
            }
        }
    }
    else {
        DisplayWarning(kBadNormalsMsg);
    }
    
    size_t numExpectedUVs = 0;
    if (fUVsScope == Alembic::AbcGeom::kVaryingScope ||
        fUVsScope == Alembic::AbcGeom::kVertexScope) {
            numExpectedUVs = numVerts;
    }
    else if (fUVsScope == Alembic::AbcGeom::kFacevaryingScope) {
        numExpectedUVs = numFaceIndices;
    }
    size_t numActualUVs = 0;
    if (fUVsCache.valid()) {
        if (fUVIndicesCache.valid()) {
            numActualUVs = fUVIndicesCache.getValue()->size();
        }
        else {
            numActualUVs = fUVsCache.getValue()->size() / 2;
        }
    }
    
    fCheckedUVsScope = Alembic::AbcGeom::kUnknownScope;
    fCheckedUVs.reset();
    fCheckedUVIndices.reset();
    
    if (numExpectedUVs == numActualUVs) {
        if (fUVsCache.valid()) {
            fCheckedUVsScope = fUVsScope;
            fCheckedUVs      = fUVsCache.getValue();
            if (fUVIndicesCache.valid()) {
                fCheckedUVIndices = fUVIndicesCache.getValue();
            }
        }
    }
    else {
        DisplayWarning(kBadUVsMsg);
    }
}
void Triangulator::computeNormals()
{
    
    
    
    if (fCheckedNormals && (fCheckedNormalsScope == Alembic::AbcGeom::kVaryingScope
            || fCheckedNormalsScope == Alembic::AbcGeom::kVertexScope
            || fCheckedNormalsScope == Alembic::AbcGeom::kFacevaryingScope)) {
        
        fComputedNormals      = fCheckedNormals;
        fComputedNormalsScope = fCheckedNormalsScope;
        fComputedNormalIndices.reset();
        if (fCheckedNormalIndices) {
            fComputedNormalIndices = fCheckedNormalIndices;
        }
        return;
    }
    
    size_t numFaceCounts           = fFaceCountsCache.getValue()->size();
    const unsigned int* faceCounts = fFaceCountsCache.getValue()->get();
    const IndexBuffer::index_t* faceIndices = fFaceIndicesCache.getValue()->get();
    size_t    numPositions = fPositionsCache.getValue()->size();
    const float* positions = fPositionsCache.getValue()->get();
    size_t numVerts = numPositions / 3;
    if (numVerts == 0) {
        fComputedNormalsScope = Alembic::AbcGeom::kUnknownScope;
        fComputedNormals.reset();
        fComputedNormalIndices.reset();
        return;
    }
    
    boost::shared_array<float> computedFaceNormals(new float[numFaceCounts * 3]);
    boost::shared_array<float> computedNormals(new float[numVerts * 3]);
    
    for (size_t i = 0, polyVertOffset = 0; i < numFaceCounts; polyVertOffset += faceCounts[i], i++) {
        size_t numPoints = faceCounts[i];
        
        for (size_t j = 0; j < numPoints; j++) {
            size_t thisJ = numPoints - j - 1;
            size_t nextJ = numPoints - ((j + 1) % numPoints) - 1;
            const float* thisPoint = &positions[faceIndices[polyVertOffset + thisJ] * 3];
            const float* nextPoint = &positions[faceIndices[polyVertOffset + nextJ] * 3];
            faceNormal.
x += (thisPoint[1] - nextPoint[1]) * (thisPoint[2] + nextPoint[2]);
            faceNormal.
y += (thisPoint[2] - nextPoint[2]) * (thisPoint[0] + nextPoint[0]);
            faceNormal.
z += (thisPoint[0] - nextPoint[0]) * (thisPoint[1] + nextPoint[1]);
        }
        computedFaceNormals[i * 3 + 0] = faceNormal.
x;
        computedFaceNormals[i * 3 + 1] = faceNormal.
y;
        computedFaceNormals[i * 3 + 2] = faceNormal.
z;
    }
    
    memset(&computedNormals[0], 0, sizeof(float) * numVerts * 3);
    for (size_t i = 0, polyVertOffset = 0; i < numFaceCounts; polyVertOffset += faceCounts[i], i++) {
        size_t numPoints = faceCounts[i];
        const float* faceNormal = &computedFaceNormals[i * 3];
        
        for (size_t j = 0; j < numPoints; j++) {
            float* normal = &computedNormals[faceIndices[polyVertOffset + j] * 3];
            normal[0] += faceNormal[0];
            normal[1] += faceNormal[1];
            normal[2] += faceNormal[2];
        }
    }
    
    for (size_t i = 0; i < numVerts; i++) {
        float* normal = &computedNormals[i * 3];
    }
    fComputedNormalsScope = Alembic::AbcGeom::kVertexScope;
    fComputedNormals      = SharedArray<float>::create(computedNormals, numVerts * 3);
    fComputedNormalIndices.reset();
}
void Triangulator::convertMultiIndexedStreams()
{
    
    
    
    
    size_t                      numFaceIndices = fFaceIndicesCache.getValue()->size();
    const IndexBuffer::index_t* faceIndices = fFaceIndicesCache.getValue()->get();
    
    bool                        normalFaceVarying = false;
    const IndexBuffer::index_t* normalIndices     = NULL;
    if (fComputedNormals) {
        normalFaceVarying = (fComputedNormalsScope == Alembic::AbcGeom::kFacevaryingScope);
        if (fComputedNormalIndices) {
            normalIndices = fComputedNormalIndices->get();
            assert(fComputedNormalIndices->size() == numFaceIndices);
        }
    }
    
    bool                        uvFaceVarying = false;
    const IndexBuffer::index_t* uvIndices     = NULL;
    if (fCheckedUVs) {
        uvFaceVarying = (fCheckedUVsScope == Alembic::AbcGeom::kFacevaryingScope);
        if (fCheckedUVIndices) {
            uvIndices = fCheckedUVIndices->get();
            assert(fCheckedUVIndices->size() == numFaceIndices);
        }
    }
    
    MultiIndexedStreamsConverter<IndexBuffer::index_t> converter(
            numFaceIndices, faceIndices);
    if (normalFaceVarying) {
        converter.addMultiIndexedStream(normalIndices);
    }
    if (uvFaceVarying) {
        converter.addMultiIndexedStream(uvIndices);
    }
    
    if (converter.numStreams() == 1) {
        fVertAttribsIndices.reset();
        fMappedFaceIndices = fFaceIndicesCache.getValue();
        fNumVertices       = fPositionsCache.getValue()->size() / 3;
        return;
    }
    
    converter.compute();
    
    fMappedFaceIndices = SharedArray<IndexBuffer::index_t>::create(
        converter.mappedFaceIndices(), numFaceIndices);
    
    fVertAttribsIndices = converter.vertAttribsIndices();
    fNumVertices        = converter.numVertices();
}
void Triangulator::remapVertAttribs()
{
    
    
    
    
    if (!fVertAttribsIndices) {
        
        fMappedPositions = fPositionsCache.getValue();
        
        if (fComputedNormals) {
            if (fComputedNormalIndices) {
                fMappedNormals = convertMultiIndexedStream<3>(
                    fComputedNormals, fComputedNormalIndices);
            }
            else {
                fMappedNormals = fComputedNormals;
            }
        }
        
        if (fCheckedUVs) {
            if (fCheckedUVIndices) {
                fMappedUVs = convertMultiIndexedStream<2>(
                    fCheckedUVs, fCheckedUVIndices);
            }
            else {
                fMappedUVs = fCheckedUVs;
            }
        }
        return;
    }
    
    const float*                positions   = fPositionsCache.getValue()->get();
    const IndexBuffer::index_t* faceIndices = fFaceIndicesCache.getValue()->get();
    
    const float*                normals       = NULL;
    const IndexBuffer::index_t* normalIndices = NULL;
    if (fComputedNormals) {
        normals = fComputedNormals->get();
        if (fComputedNormalIndices) {
            normalIndices = fComputedNormalIndices->get();
        }
    }
    
    const float*                UVs       = NULL;
    const IndexBuffer::index_t* uvIndices = NULL;
    if (fCheckedUVs) {
        UVs = fCheckedUVs->get();
        if (fCheckedUVIndices) {
            uvIndices = fCheckedUVIndices->get();
        }
    }
    
    MultiIndexedStreamsRemapper<IndexBuffer::index_t> remapper(
        faceIndices, fNumVertices, fVertAttribsIndices.get());
    remapper.addMultiIndexedStream(positions, NULL, false, 3);
    if (normals) {
        remapper.addMultiIndexedStream(normals, normalIndices,
            fComputedNormalsScope == Alembic::AbcGeom::kFacevaryingScope, 3);
    }
    if (UVs) {
        remapper.addMultiIndexedStream(UVs, uvIndices, 
            fCheckedUVsScope == Alembic::AbcGeom::kFacevaryingScope, 2);
    }
    
    remapper.compute();
    fMappedPositions = SharedArray<float>::create(
        remapper.mappedVertAttribs(0), fNumVertices * 3);
    unsigned int streamIndex = 1;
    if (normals) {
        fMappedNormals = SharedArray<float>::create(
            remapper.mappedVertAttribs(streamIndex++), fNumVertices * 3);
    }
    if (UVs) {
        fMappedUVs = SharedArray<float>::create(
            remapper.mappedVertAttribs(streamIndex++), fNumVertices * 2);
    }
}
void Triangulator::computeWireIndices()
{
    
    
    
    size_t           numFaceCounts = fFaceCountsCache.getValue()->size();
    const unsigned int* faceCounts = fFaceCountsCache.getValue()->get();
    size_t                   numFaceIndices = fFaceIndicesCache.getValue()->size();
    const IndexBuffer::index_t* faceIndices = fFaceIndicesCache.getValue()->get();
    const IndexBuffer::index_t* mappedFaceIndices = fMappedFaceIndices->get();
    
    WireIndicesGenerator<IndexBuffer::index_t> wireIndicesGenerator(
            numFaceCounts, faceCounts, numFaceIndices, faceIndices, mappedFaceIndices);
    wireIndicesGenerator.compute();
    if (wireIndicesGenerator.numWires() == 0) {
        fWireIndices.reset();
        return;
    }
    fWireIndices = SharedArray<IndexBuffer::index_t>::create(
        wireIndicesGenerator.wireIndices(), wireIndicesGenerator.numWires() * 2);
}
void Triangulator::triangulate()
{
    
    
    
    
    size_t           numFaceCounts = fFaceCountsCache.getValue()->size();
    const unsigned int* faceCounts = fFaceCountsCache.getValue()->get();
    const IndexBuffer::index_t* faceIndices = fMappedFaceIndices->get();
    const float* positions = fMappedPositions->get();
    const float* normals   = fMappedNormals ? fMappedNormals->get() : NULL;
    if (numFaceCounts == 0) {
        fTriangleIndices.reset();
        return;
    }
    
    PolyTriangulator<IndexBuffer::index_t> polyTriangulator(
        numFaceCounts, faceCounts, faceIndices, true, positions, normals);
    polyTriangulator.compute();
    
    fTriangleIndices = SharedArray<IndexBuffer::index_t>::create(
        polyTriangulator.triangleIndices(), polyTriangulator.numTriangles() * 3);
}
NurbsTessellator::NurbsTessellator(
    Alembic::AbcGeom::INuPatchSchema& abcNurbs,
    const bool needUVs)
    : DataProvider(abcNurbs, abcNurbs.getTimeSampling(),
                   abcNurbs.getNumSamples(), needUVs),
    fSurfaceValid(false)
{
    
    fPositionsCache.init(abcNurbs.getPositionsProperty());
    
    fNumUCache.init(Alembic::Abc::IInt32Property(abcNurbs.getPtr(), "nu"));
    fNumVCache.init(Alembic::Abc::IInt32Property(abcNurbs.getPtr(), "nv"));
    
    fUOrderCache.init(Alembic::Abc::IInt32Property(abcNurbs.getPtr(), "uOrder"));
    fVOrderCache.init(Alembic::Abc::IInt32Property(abcNurbs.getPtr(), "vOrder"));
    
    fUKnotCache.init(abcNurbs.getUKnotsProperty());
    fVKnotCache.init(abcNurbs.getVKnotsProperty());
    
    Alembic::AbcGeom::IFloatArrayProperty positionWeights = abcNurbs.getPositionWeightsProperty();
    if (positionWeights.valid()) {
        fPositionWeightsCache.init(positionWeights);
    }
    
    if (abcNurbs.hasTrimCurve()) {
        
        fTrimNumLoopsCache.init(
            Alembic::Abc::IInt32Property(abcNurbs.getPtr(), "trim_nloops"));
        
        fTrimNumCurvesCache.init(
            Alembic::Abc::IInt32ArrayProperty(abcNurbs.getPtr(), "trim_ncurves"));
        
        fTrimNumVerticesCache.init(
            Alembic::Abc::IInt32ArrayProperty(abcNurbs.getPtr(), "trim_n"));
        
        fTrimOrderCache.init(
            Alembic::Abc::IInt32ArrayProperty(abcNurbs.getPtr(), "trim_order"));
        
        fTrimKnotCache.init(
            Alembic::Abc::IFloatArrayProperty(abcNurbs.getPtr(), "trim_knot"));
        
        fTrimUCache.init(
            Alembic::Abc::IFloatArrayProperty(abcNurbs.getPtr(), "trim_u"));
        
        fTrimVCache.init(
            Alembic::Abc::IFloatArrayProperty(abcNurbs.getPtr(), "trim_v"));
        
        fTrimWCache.init(
            Alembic::Abc::IFloatArrayProperty(abcNurbs.getPtr(), "trim_w"));
    }
}
NurbsTessellator::~NurbsTessellator()
{
    
    fPositionsCache.reset();
    fNumUCache.reset();
    fNumVCache.reset();
    fUOrderCache.reset();
    fVOrderCache.reset();
    fUKnotCache.reset();
    fVKnotCache.reset();
    fPositionWeightsCache.reset();
    fTrimNumLoopsCache.reset();
    fTrimNumCurvesCache.reset();
    fTrimNumVerticesCache.reset();
    fTrimOrderCache.reset();
    fTrimKnotCache.reset();
    fTrimUCache.reset();
    fTrimVCache.reset();
    fTrimWCache.reset();
}
bool NurbsTessellator::valid() const
{
    return DataProvider::valid() &&
            fPositionsCache.valid() &&
            fNumUCache.valid() &&
            fNumVCache.valid() &&
            fUOrderCache.valid() &&
            fVOrderCache.valid() &&
            fUKnotCache.valid() &&
            fVKnotCache.valid();
}
boost::shared_ptr<const ShapeSample> 
NurbsTessellator::getSample(double seconds)
{
    
    if (!fWireIndices || !fTriangleIndices) {
        boost::shared_ptr<ShapeSample> sample =
            ShapeSample::createEmptySample(seconds);
        return sample;
    }
    
    
    std::vector<boost::shared_ptr<IndexBuffer> > triangleVertIndices;
    triangleVertIndices.push_back(IndexBuffer::create(fTriangleIndices));
    boost::shared_ptr<ShapeSample> sample = ShapeSample::create(
        seconds,                                     
        fWireIndices->size() / 2,                    
        fPositions->size() / 3,                      
        IndexBuffer::create(fWireIndices),           
        triangleVertIndices,                         
        VertexBuffer::createPositions(fPositions),   
        getBoundingBox(),                            
        Config::kDefaultGrayColor,                   
        isVisible()
    );
    
    if (fNormals) {
        sample->setNormals(
            VertexBuffer::createNormals(fNormals));
    }
    if (fUVs) {
        sample->setUVs(
            VertexBuffer::createUVs(fUVs));
    }
    return sample;
}
TimeInterval NurbsTessellator::updateCache(chrono_t time)
{
    TimeInterval validityInterval(DataProvider::updateCache(time));
    
    bool positionsChanged = fPositionsCache.setTime(time);
    bool topologyChanged = fNumUCache.setTime(time);
    topologyChanged      = fNumVCache.setTime(time)   || topologyChanged;
    topologyChanged      = fUOrderCache.setTime(time) || topologyChanged;
    topologyChanged      = fVOrderCache.setTime(time) || topologyChanged;
    bool knotChanged = fUKnotCache.setTime(time);
    knotChanged      = fVKnotCache.setTime(time) || knotChanged;
    if (fPositionWeightsCache.valid()) {
        positionsChanged = fPositionWeightsCache.setTime(time) || positionsChanged;
    }
    bool trimCurvesChanged = false;
    if (fTrimNumLoopsCache.valid()) {
        trimCurvesChanged = fTrimNumLoopsCache.setTime(time)    || trimCurvesChanged;
        trimCurvesChanged = fTrimNumCurvesCache.setTime(time)   || trimCurvesChanged;
        trimCurvesChanged = fTrimNumVerticesCache.setTime(time) || trimCurvesChanged;
        trimCurvesChanged = fTrimOrderCache.setTime(time)       || trimCurvesChanged;
        trimCurvesChanged = fTrimKnotCache.setTime(time)        || trimCurvesChanged;
        trimCurvesChanged = fTrimUCache.setTime(time)           || trimCurvesChanged;
        trimCurvesChanged = fTrimVCache.setTime(time)           || trimCurvesChanged;
        trimCurvesChanged = fTrimWCache.setTime(time)           || trimCurvesChanged;
    }
    
    
    validityInterval &= fPositionsCache.getValidityInterval();
    validityInterval &= fNumUCache.getValidityInterval();
    validityInterval &= fNumVCache.getValidityInterval();
    validityInterval &= fUOrderCache.getValidityInterval();
    validityInterval &= fVOrderCache.getValidityInterval();
    validityInterval &= fUKnotCache.getValidityInterval();
    validityInterval &= fVKnotCache.getValidityInterval();
    if (fPositionWeightsCache.valid()) {
        validityInterval &= fPositionWeightsCache.getValidityInterval();
    }
    if (fTrimNumLoopsCache.valid()) {
        validityInterval &= fTrimNumLoopsCache.getValidityInterval();
        validityInterval &= fTrimNumCurvesCache.getValidityInterval();
        validityInterval &= fTrimNumVerticesCache.getValidityInterval();
        validityInterval &= fTrimOrderCache.getValidityInterval();
        validityInterval &= fTrimKnotCache.getValidityInterval();
        validityInterval &= fTrimUCache.getValidityInterval();
        validityInterval &= fTrimVCache.getValidityInterval();
        validityInterval &= fTrimWCache.getValidityInterval();
    }
    
    check();
    
    bool rebuild = topologyChanged || knotChanged || 
                   trimCurvesChanged || fNurbsData.object().isNull();
    setNurbs(rebuild, positionsChanged);
    
    if (rebuild || positionsChanged) {
        tessellate();
    }
    if (isVisible()) {
        convertToPoly();
    }
    return validityInterval;
}
void NurbsTessellator::check()
{
    
    fSurfaceValid = true;
    
    int uDegree   = fUOrderCache.getValue() - 1;
    int vDegree   = fVOrderCache.getValue() - 1;
    int numUCV    = fNumUCache.getValue();
    int numVCV    = fNumVCache.getValue();
    int numUKnots = (int)fUKnotCache.getValue()->size();
    int numVKnots = (int)fVKnotCache.getValue()->size();
    if (numUKnots != numUCV + uDegree + 1 || numVKnots != numVCV + vDegree + 1) {
        fSurfaceValid = false;
        DisplayWarning(kBadNurbsMsg);
        return;
    }
    
    size_t numCVs = numUCV * numVCV;
    if (numCVs * 3 != fPositionsCache.getValue()->size()) {
        fSurfaceValid = false;
        DisplayWarning(kBadNurbsMsg);
        return;
    }
    
    if (fPositionWeightsCache.valid() &&
            numCVs != fPositionWeightsCache.getValue()->size()) {
        fSurfaceValid = false;
        DisplayWarning(kBadNurbsMsg);
        return;
    }
}
void NurbsTessellator::setNurbs(bool rebuild, bool positionsChanged)
{
    if (!fSurfaceValid) {
        
        return;
    }
    
    unsigned int numU = 0;
    unsigned int numV = 0;
    if (rebuild || positionsChanged) {
        numU = fNumUCache.getValue();
        numV = fNumVCache.getValue();
        
        const float* positions = fPositionsCache.getValue()->
get();
 
        const float* positionWeights = NULL;
        if (fPositionWeightsCache.valid()) {
            positionWeights = fPositionWeightsCache.getValue()->get();
        }
        
        
        for (unsigned int u = 0; u < numU; u++) {
            for (unsigned int v = 0; v < numV; v++) {
                unsigned int alembicIndex = v * numU + u;
                unsigned int mayaIndex    = u * numV + (numV - v - 1);
                MPoint point(positions[alembicIndex * 3 + 0],
 
                             positions[alembicIndex * 3 + 1],
                             positions[alembicIndex * 3 + 2],
                             1.0);
                if (positionWeights) {
                    point.w = positionWeights[alembicIndex];
                }
                mayaPositions[mayaIndex] = point;
            }
        }
    }
    if (rebuild) {
        
        unsigned int uDegree = fUOrderCache.getValue() - 1;
        unsigned int vDegree = fVOrderCache.getValue() - 1;
        
        
        
        
        
        
        bool notOpen = true;
        for (unsigned int v = 0; notOpen && v < numV; v++) {
            for (unsigned int u = 0; u < uDegree; u++) {
                unsigned int firstIndex = u * numV + (numV - v - 1);
                unsigned int lastPeriodicIndex = (numU - uDegree + u) * numV + (numV - v - 1);
                if (!mayaPositions[firstIndex].isEquivalent(mayaPositions[lastPeriodicIndex])) {
                    notOpen = false;
                    break;
                }
            }
        }
            for (unsigned int v = 0; v < numV; v++) {
                unsigned int lastUIndex = (numU - 1) * numV + (numV - v - 1);
                if (!mayaPositions[numV-v-1].isEquivalent(mayaPositions[lastUIndex])) {
                    break;
                }
            }
        }
        notOpen = true;
        for (unsigned int u = 0; notOpen && u < numU; u++) {
            for (unsigned int v = 0; v < vDegree; v++) {
                unsigned int firstIndex = u * numV + (numV - v - 1);
                unsigned int lastPeriodicIndex =  u * numV + (vDegree - v - 1); 
                if (!mayaPositions[firstIndex].isEquivalent(mayaPositions[lastPeriodicIndex])) {
                    notOpen = false;
                    break;
                }
            }
        }
            for (unsigned int u = 0; u < numU; u++) {
                if (!mayaPositions[u*numV+(numV-1)].isEquivalent(mayaPositions[u*numV])) {
                    break;
                }
            }
        }
        
        
        
        
        
        unsigned int numUKnot = (unsigned int)fUKnotCache.getValue()->size();
        unsigned int numVKnot = (unsigned int)fVKnotCache.getValue()->size();
        const float* uKnot    = fUKnotCache.getValue()->get();
        const float* vKnot    = fVKnotCache.getValue()->get();
        
        MObject nurbsData = fNurbsData.create();
 
        MObject nurbs     = fNurbs.create(mayaPositions,
 
                                          mayaUKnots, mayaVKnots,
                                          uDegree, vDegree,
                                          uForm,
                                          vForm,
                                          true,
                                          nurbsData,
                                          &status);
            
            return;
        }
        
        if (fTrimNumLoopsCache.valid()) {
            unsigned int trimNumLoops = fTrimNumLoopsCache.getValue();
            
            double startU, endU, startV, endV;
            fNurbs.getKnotDomain(startU, endU, startV, endV);
            double offsetV = startV + endV;
            const unsigned int* trimNumCurves = 
                    fTrimNumCurvesCache.getValue()->
get();
            const unsigned int* trimNumVertices =
                    fTrimNumVerticesCache.getValue()->
get();
            const unsigned int* trimOrder =
                    fTrimOrderCache.getValue()->get();
            const float* trimKnot = fTrimKnotCache.getValue()->get();
            const float* trimU    = fTrimUCache.getValue()->get();
            const float* trimV    = fTrimVCache.getValue()->get();
            const float* trimW    = fTrimWCache.getValue()->get();
            for (unsigned int i = 0; i < trimNumLoops; i++) {
                
                unsigned int numCurves = *trimNumCurves;
                for (unsigned int j = 0; j < numCurves; j++) {
                    
                    unsigned int numVertices = *trimNumVertices;
                    unsigned int degree      = *trimOrder - 1;
                    unsigned int numKnots    = numVertices + degree + 1;
                    for (unsigned int k = 0; k < numVertices; k++) {
                        MPoint point(trimU[k], offsetV - trimV[k], 0, trimW[k]);
 
                        controlPoints[k] = point;
                    }
                    
                        boundary[j] = curveDataObject;
                    }
                    
                    trimNumVertices++;
                    trimOrder++;
                    trimKnot += numKnots;
                    trimU    += numVertices;
                    trimV    += numVertices;
                    trimW    += numVertices;
                }
                boundaryArray.
append(boundary);
                
                trimNumCurves++;
            }
            for (
unsigned int i = 0; i < boundaryArray.
length(); i++) {
 
                if (i > 0) {
                    
                    bool isOuterBoundary = false;
                    double       length  = loop.
length();
 
                    unsigned int segment = std::max(loop.
numCVs(), 10);
 
                    for (unsigned int j = 0; j < segment; j++) {
                    }
                    
                    MPoint       rightMostPoint = curvePoints[0];
 
                    unsigned int rightMostIndex = 0;
                    for (unsigned int j = 0; j < curvePoints.length(); j++) {
                        if (rightMostPoint.
x < curvePoints[j].x) {
 
                            rightMostPoint = curvePoints[j];
                            rightMostIndex = j;
                        }
                    }
                    
                    unsigned int beforeIndex = (rightMostIndex == 0) ? curvePoints.length() - 1 : rightMostIndex - 1;
                    unsigned int afterIndex  = (rightMostIndex == curvePoints.length() - 1) ? 0 : rightMostIndex + 1;
                    for (unsigned int j = 0; j < curvePoints.length(); j++) {
                        if (fabs(curvePoints[beforeIndex].x - curvePoints[rightMostIndex].x) < 1e-5) {
                            beforeIndex = (beforeIndex == 0) ? curvePoints.length() - 1 : beforeIndex - 1;
                        }
                    }
                    for (unsigned int j = 0; j < curvePoints.length(); j++) {
                        if (fabs(curvePoints[afterIndex].x - curvePoints[rightMostIndex].x) < 1e-5) {
                            afterIndex = (afterIndex == curvePoints.length() - 1) ? 0 : afterIndex + 1;
                        }
                    }
                    
                    if (fabs(curvePoints[afterIndex].x - curvePoints[rightMostIndex].x) < 1e-5 &&
                        fabs(curvePoints[beforeIndex].x - curvePoints[rightMostIndex].x) < 1e-5) {
                        continue;
                    }
                    
                    MVector vector1 = curvePoints[beforeIndex] - curvePoints[rightMostIndex];
 
                    MVector vector2 = curvePoints[afterIndex]  - curvePoints[rightMostIndex];
 
                    if ((vector1 ^ vector2).z < 0) {
                        isOuterBoundary = true;
                    }
                    
                    if (isOuterBoundary) {
                        status = fNurbs.trimWithBoundaries(oneRegion, false, 1e-3, 1e-5, true);
                            return;
                        }
                    }
                }
                oneRegion.
append(boundaryArray[i]);
            }
            status = fNurbs.trimWithBoundaries(oneRegion, false, 1e-3, 1e-5, true);
                return;
            }
        }
    }
    else {
        assert(!fNurbsData.object().isNull());
        if (positionsChanged) {
            fNurbs.setCVs(mayaPositions);
        }
    }
}
void NurbsTessellator::tessellate()
{
    if (!fSurfaceValid || fNurbsData.object().isNull()) {
        return;
    }
    
    MObject polyMeshData = fPolyMeshData.create();
 
    
    
    MObject polyObject = fNurbs.tesselate(params, polyMeshData, &status);
 
        
        return;
    }
    status = fPolyMesh.setObject(polyObject);
}
void NurbsTessellator::convertToPoly()
{
    if (!fSurfaceValid || fPolyMeshData.object().isNull() ||
            fPolyMesh.numVertices() == 0 || fPolyMesh.numFaceVertices() == 0) {
        fTriangleIndices.reset();
        fWireIndices.reset();
        fPositions.reset();
        fNormals.reset();
        fUVs.reset();
        return;
    }
    
    MayaMeshExtractor<IndexBuffer::index_t> extractor(fPolyMeshData.object());
    extractor.setWantUVs(fNeedUVs);
    extractor.compute();
    fTriangleIndices = extractor.triangleIndices();
    fWireIndices     = extractor.wireIndices();
    fPositions       = extractor.positions();
    fNormals         = extractor.normals();
    fUVs.reset();
    if (fNeedUVs) {
        fUVs         = extractor.uvs();
    }
}
SubDSmoother::SubDSmoother(
        Alembic::AbcGeom::ISubDSchema&  abcSubD,
        const bool                      needUVs)
    : PolyDataProvider(abcSubD, needUVs)
{
    
    fFaceIndicesCache.init(abcSubD.getFaceIndicesProperty());
    
    Alembic::Abc::IInt32ArrayProperty creaseIndicesProp =
            abcSubD.getCreaseIndicesProperty();
    Alembic::Abc::IInt32ArrayProperty creaseLengthsProp =
            abcSubD.getCreaseLengthsProperty();
    Alembic::Abc::IFloatArrayProperty creaseSharpnessesProp = 
            abcSubD.getCreaseSharpnessesProperty();
    if (creaseIndicesProp.valid() &&
            creaseLengthsProp.valid() &&
            creaseSharpnessesProp.valid()) {
        fCreaseIndicesCache.init(creaseIndicesProp);
        fCreaseLengthsCache.init(creaseLengthsProp);
        fCreaseSharpnessesCache.init(creaseSharpnessesProp);
    }
    
    Alembic::Abc::IInt32ArrayProperty cornerIndicesProp = 
            abcSubD.getCornerIndicesProperty();
    Alembic::Abc::IFloatArrayProperty cornerSharpnessesProp =
            abcSubD.getCornerSharpnessesProperty();
    if (cornerIndicesProp.valid() && cornerSharpnessesProp.valid()) {
        fCornerIndicesCache.init(cornerIndicesProp);
        fCornerSharpnessesCache.init(cornerSharpnessesProp);
    }
    
    Alembic::Abc::IInt32ArrayProperty holesProp =
            abcSubD.getHolesProperty();
    if (holesProp.valid()) {
        fHolesCache.init(holesProp);
    }
    
    if (fNeedUVs) {
        Alembic::AbcGeom::IV2fGeomParam UVs = abcSubD.getUVsParam();
        if (UVs.valid()) {
            fUVsScope = UVs.getScope();
            if (fUVsScope == Alembic::AbcGeom::kVaryingScope ||
                    fUVsScope == Alembic::AbcGeom::kVertexScope ||
                    fUVsScope == Alembic::AbcGeom::kFacevaryingScope) {
                fUVsCache.init(UVs.getValueProperty());
                if (UVs.isIndexed()) {
                    fUVIndicesCache.init(UVs.getIndexProperty());
                }
            }
        }
    }
}
SubDSmoother::~SubDSmoother()
{
    
    fFaceIndicesCache.reset();
    fCreaseIndicesCache.reset();
    fCreaseLengthsCache.reset();
    fCreaseSharpnessesCache.reset();
    fCornerIndicesCache.reset();
    fCornerSharpnessesCache.reset();
    fHolesCache.reset();
    fUVsScope = Alembic::AbcGeom::kUnknownScope;
    fUVsCache.reset();
    fUVIndicesCache.reset();
}
bool SubDSmoother::valid() const
{
    return PolyDataProvider::valid() &&
            fFaceIndicesCache.valid();
}
boost::shared_ptr<const ShapeSample> 
SubDSmoother::getSample(double seconds)
{
    
    if (!fWireIndices || !fTriangleIndices) {
        boost::shared_ptr<ShapeSample> sample =
            ShapeSample::createEmptySample(seconds);
        return sample;
    }
    
    
    std::vector<boost::shared_ptr<IndexBuffer> > triangleVertIndices;
    triangleVertIndices.push_back(IndexBuffer::create(fTriangleIndices));
    boost::shared_ptr<ShapeSample> sample = ShapeSample::create(
        seconds,                                     
        fWireIndices->size() / 2,                    
        fPositions->size() / 3,                      
        IndexBuffer::create(fWireIndices),           
        triangleVertIndices,                         
        VertexBuffer::createPositions(fPositions),   
        getBoundingBox(),                            
        Config::kDefaultGrayColor,                   
        isVisible()
    );
    if (fNormals) {
        sample->setNormals(
            VertexBuffer::createNormals(fNormals));
    }
    if (fUVs) {
        sample->setUVs(
            VertexBuffer::createUVs(fUVs));
    }
    return sample;
}
TimeInterval SubDSmoother::updateCache(chrono_t time)
{
    
    
    bool topologyChanged = fFaceCountsCache.setTime(time);
    bool positionChanged = fPositionsCache.setTime(time);
    TimeInterval validityInterval(PolyDataProvider::updateCache(time));
    
    topologyChanged = fFaceIndicesCache.setTime(time) || topologyChanged;
    bool creaseEdgeChanged = false;
    if (fCreaseSharpnessesCache.valid()) {
        creaseEdgeChanged = fCreaseIndicesCache.setTime(time) || creaseEdgeChanged;
        creaseEdgeChanged = fCreaseLengthsCache.setTime(time) || creaseEdgeChanged;
        creaseEdgeChanged = fCreaseSharpnessesCache.setTime(time) || creaseEdgeChanged;
    }
    bool creaseVertexChanged = false;
    if (fCornerSharpnessesCache.valid()) {
        creaseVertexChanged = fCornerIndicesCache.setTime(time) || creaseVertexChanged;
        creaseVertexChanged = fCornerSharpnessesCache.setTime(time) || creaseVertexChanged;
    }
    bool invisibleFaceChanged = false;
    if (fHolesCache.valid()) {
        invisibleFaceChanged = fHolesCache.setTime(time);
    }
    bool uvChanged = false;
    if (fUVsCache.valid()) {
        uvChanged = fUVsCache.setTime(time);
        if (fUVIndicesCache.valid()) {
            uvChanged = fUVIndicesCache.setTime(time) || uvChanged;
        }
    }
    
    validityInterval &= fFaceIndicesCache.getValidityInterval();
    if (fCreaseSharpnessesCache.valid()) {
        validityInterval &= fCreaseIndicesCache.getValidityInterval();
        validityInterval &= fCreaseLengthsCache.getValidityInterval();
        validityInterval &= fCreaseSharpnessesCache.getValidityInterval();
    }
    if (fCornerSharpnessesCache.valid()) {
        validityInterval &= fCornerIndicesCache.getValidityInterval();
        validityInterval &= fCornerSharpnessesCache.getValidityInterval();
    }
    if (fHolesCache.valid()) {
        validityInterval &= fHolesCache.getValidityInterval();
    }
    if (fUVsCache.valid()) {
        validityInterval &= fUVsCache.getValidityInterval();
        if (fUVIndicesCache.valid()) {
            validityInterval &= fUVIndicesCache.getValidityInterval();
        }
    }
    
    check();
    if (topologyChanged || creaseEdgeChanged || creaseVertexChanged ||
            invisibleFaceChanged || fSubDData.object().isNull()) {
        rebuildSubD();
        setCreaseEdges();
        setCreaseVertices();
        setInvisibleFaces();
        setUVs();
    }
    else {
        if (positionChanged) {
            setPositions();
        }
        if (uvChanged) {
            setUVs();
        }
    }
    if (isVisible()) {
        convertToPoly();
    }
    return validityInterval;
}
void SubDSmoother::check()
{
    size_t numFaceIndices = fFaceIndicesCache.getValue()->size();
    size_t numVerts       = fPositionsCache.getValue()->size() / 3;
    
    size_t numExpectedUVs = 0;
    if (fUVsScope == Alembic::AbcGeom::kVaryingScope ||
            fUVsScope == Alembic::AbcGeom::kVertexScope) {
        numExpectedUVs = numVerts;
    }
    else if (fUVsScope == Alembic::AbcGeom::kFacevaryingScope) {
        numExpectedUVs = numFaceIndices;
    }
    size_t numActualUVs = 0;
    if (fUVsCache.valid()) {
        if (fUVIndicesCache.valid()) {
            numActualUVs = fUVIndicesCache.getValue()->size();
        }
        else {
            numActualUVs = fUVsCache.getValue()->size() / 2;
        }
    }
    
    fCheckedUVsScope = Alembic::AbcGeom::kUnknownScope;
    fCheckedUVs.reset();
    fCheckedUVIndices.reset();
    
    if (numExpectedUVs == numActualUVs) {
        if (fUVsCache.valid()) {
            fCheckedUVsScope = fUVsScope;
            fCheckedUVs      = fUVsCache.getValue();
            if (fUVIndicesCache.valid()) {
                fCheckedUVIndices = fUVIndicesCache.getValue();
            }
        }
    }
    else {
        DisplayWarning(kBadUVsMsg);
    }
}
void SubDSmoother::rebuildSubD()
{
    
    size_t numFaceCounts           = fFaceCountsCache.getValue()->size();
    const unsigned int* faceCounts = fFaceCountsCache.getValue()->get();
    size_t numFaceIndices                   = fFaceIndicesCache.getValue()->size();
    const IndexBuffer::index_t* faceIndices = fFaceIndicesCache.getValue()->get();
    size_t numPositions    = fPositionsCache.getValue()->size();
    const float* positions = fPositionsCache.getValue()->get();
    size_t numVertices = numPositions / 3;
    
    mayaCounts.
setLength((
unsigned int)numFaceCounts);
    mayaConnects.
setLength((
unsigned int)numFaceIndices);
    for (unsigned int i = 0, polyVertOffset = 0; i < numFaceCounts; i++) {
        unsigned int faceCount = mayaCounts[i] = faceCounts[i];
        
        for (unsigned int j = 0; j < faceCount; j++) {
            
            mayaConnects[polyVertOffset + j] = faceIndices[polyVertOffset + faceCount - j - 1];
        }
        polyVertOffset += faceCount;
    }
    
    mayaPositions.
setLength((
unsigned int)numVertices);
    for (unsigned int i = 0; i < numVertices; i++) {
                                       positions[i * 3 + 1],
                                       positions[i * 3 + 2]);
    }
    
    MObject subdData = fSubDData.create(&status);
 
    fSubD.setCheckSamePointTwice(false);
    MObject subd = fSubD.create((
int)numVertices, (
int)numFaceCounts,
 
            mayaPositions, mayaCounts, mayaConnects, subdData, &status);
    }
}
void SubDSmoother::setPositions()
{
    if (fSubDData.object().isNull()) {
        return;
    }
    
    size_t numPositions    = fPositionsCache.getValue()->size();
    const float* positions = fPositionsCache.getValue()->get();
    size_t numVertices = numPositions / 3;
    
    mayaPositions.
setLength((
unsigned int)numVertices);
    for (unsigned int i = 0; i < numVertices; i++) {
            positions[i * 3 + 1],
            positions[i * 3 + 2]);
    }
    fSubD.setPoints(mayaPositions);
}
void SubDSmoother::setCreaseEdges()
{
    if (fSubDData.object().isNull() ||
            !fCreaseIndicesCache.valid() ||
            !fCreaseLengthsCache.valid() ||
            !fCreaseSharpnessesCache.valid()) {
        return;
    }
    
    size_t           numCreaseIndices = fCreaseIndicesCache.getValue()->size();
    const unsigned int* creaseIndices = fCreaseIndicesCache.getValue()->get();
    size_t           numCreaseLengths = fCreaseLengthsCache.getValue()->size();
    const unsigned int* creaseLengths = fCreaseLengthsCache.getValue()->get();
    size_t    numCreaseSharpnesses = fCreaseSharpnessesCache.getValue()->size();
    const float* creaseSharpnesses = fCreaseSharpnessesCache.getValue()->get();
    if (numCreaseSharpnesses == 0) {
        return;
    }
    
    typedef boost::unordered_map<std::pair<int,int>,int> EdgeMap;
    EdgeMap edgeMap(size_t(fSubD.numEdges() / 0.75f));
    int numEdges = fSubD.numEdges();
    for (int i = 0; i < numEdges; i++) {
        int2 vertexList;
        fSubD.getEdgeVertices(i, vertexList);
        if (vertexList[0] > vertexList[1]) {
            std::swap(vertexList[0], vertexList[1]);
        }
        edgeMap.insert(std::make_pair(std::make_pair(vertexList[0], vertexList[1]), i));
    }
    
    for (size_t i = 0, index = 0; i < numCreaseLengths && i < numCreaseSharpnesses; i++) {
        
        unsigned int length    = creaseLengths[i];
        float        sharpness = creaseSharpnesses[i];
        if (length == 2 && index + length <= numCreaseIndices) {
            
            std::pair<int,int> edge = std::make_pair(creaseIndices[index],creaseIndices[index+1]);
            if (edge.first > edge.second) {
                std::swap(edge.first, edge.second);
            }
            EdgeMap::iterator iter = edgeMap.find(edge);
            if (iter != edgeMap.end() && iter->second < numEdges) {
                
                mayaEdgeIds.
append(iter->second);
                mayaCreaseData.
append(sharpness);
            }
        }
        index += length;
    }
    
    status = fSubD.setCreaseEdges(mayaEdgeIds, mayaCreaseData);
}
void SubDSmoother::setCreaseVertices()
{
    if (fSubDData.object().isNull() ||
            !fCornerIndicesCache.valid() ||
            !fCornerSharpnessesCache.valid()) {
        return;
    }
    
    size_t           numCornerIndices = fCornerIndicesCache.getValue()->size();
    const unsigned int* cornerIndices = fCornerIndicesCache.getValue()->get();
    size_t    numCornerSharpnesses = fCornerSharpnessesCache.getValue()->size();
    const float* cornerSharpnesses = fCornerSharpnessesCache.getValue()->get();
    if (cornerSharpnesses == 0) {
        return;
    }
    
    size_t numCreaseVertices = std::min(numCornerIndices, numCornerSharpnesses);
    mayaVertexIds.
setLength((
unsigned int)numCreaseVertices);
    mayaCreaseData.
setLength((
unsigned int)numCreaseVertices);
    for (unsigned int i = 0; i < numCreaseVertices; i++) {
        mayaVertexIds[i]  = cornerIndices[i];
        mayaCreaseData[i] = cornerSharpnesses[i];
    }
    
    status = fSubD.setCreaseVertices(mayaVertexIds, mayaCreaseData);
}
void SubDSmoother::setInvisibleFaces()
{
    if (fSubDData.object().isNull() ||
            !fHolesCache.valid()) {
        return;
    }
    
    size_t           numHoles = fHolesCache.getValue()->size();
    const unsigned int* holes = fHolesCache.getValue()->get();
    if (numHoles == 0) {
        return;
    }
    
    MUintArray mayaFaceIds(holes, (
unsigned int)numHoles);
 
    
    status = fSubD.setInvisibleFaces(mayaFaceIds);
}
void SubDSmoother::setUVs()
{
    if (fSubDData.object().isNull()) {
        return;
    }
    
    if (fCheckedUVsScope != Alembic::AbcGeom::kVaryingScope && 
            fCheckedUVsScope != Alembic::AbcGeom::kVertexScope &&
            fCheckedUVsScope != Alembic::AbcGeom::kFacevaryingScope) {
        return;
    }
    
    if (!fCheckedUVs) {
        return;
    }
    
    size_t           numFaceCounts = fFaceCountsCache.getValue()->size();
    const unsigned int* faceCounts = fFaceCountsCache.getValue()->get();
    size_t                      numFaceIndices = fFaceIndicesCache.getValue()->size();
    const IndexBuffer::index_t* faceIndices = fFaceIndicesCache.getValue()->get();
    size_t    numUVs = fCheckedUVs->size();
    const float* UVs = fCheckedUVs->get();
    const IndexBuffer::index_t* uvIndices = NULL;
    if (fCheckedUVIndices) {
        uvIndices    = fCheckedUVIndices->get();
    }
    
    
    if (int(numUVs) != fSubD.numUVs()) {
        fSubD.clearUVs();
    }
    
    if (numUVs == 0) {
        return;
    }
    
    for (unsigned int i = 0; i < numUVs; i++) {
        mayaUArray[i] = UVs[i * 2 + 0];
        mayaVArray[i] = UVs[i * 2 + 1];
    }
    
    MIntArray mayaUVCounts((
unsigned int)numFaceCounts);
 
    MIntArray mayaUVIds((
unsigned int)numFaceIndices);
 
    for (unsigned int i = 0, polyVertOffset = 0; i < numFaceCounts; i++) {
        unsigned int faceCount = mayaUVCounts[i] = faceCounts[i];
        for (unsigned int j = 0; j < faceCount; j++) {
            unsigned int uvIndex = 0;
            
            unsigned int polyVertIndex = polyVertOffset + faceCount - j - 1;
            if (fCheckedUVsScope == Alembic::AbcGeom::kVaryingScope ||
                    fCheckedUVsScope == Alembic::AbcGeom::kVertexScope) {
                
                unsigned int vertIndex = faceIndices[polyVertIndex];
                uvIndex = uvIndices ? uvIndices[vertIndex] : vertIndex;
            }
            else if (fCheckedUVsScope == Alembic::AbcGeom::kFacevaryingScope) {
                
                uvIndex = uvIndices ? uvIndices[polyVertIndex] : polyVertIndex;
            }
            else {
                assert(0);
            }
            mayaUVIds[polyVertOffset + j] = uvIndex;
        }
        polyVertOffset += faceCount;
    }
    
    status = fSubD.setUVs(mayaUArray, mayaVArray);
    status = fSubD.assignUVs(mayaUVCounts, mayaUVIds);
}
void SubDSmoother::convertToPoly()
{
    if (fSubDData.object().isNull() ||
            fSubD.numVertices() == 0 || fSubD.numFaceVertices() == 0) {
        fTriangleIndices.reset();
        fWireIndices.reset();
        fPositions.reset();
        fNormals.reset();
        fUVs.reset();
        return;
    }
    
    
    MObject smoothMeshObj = fSubD.generateSmoothMesh(smoothMeshDataObj, &smoothOptions);
 
    MayaMeshExtractor<IndexBuffer::index_t> extractor(smoothMeshDataObj);
    extractor.setWantUVs(fNeedUVs);
    extractor.compute();
    fTriangleIndices = extractor.triangleIndices();
    fWireIndices     = extractor.wireIndices();
    fPositions       = extractor.positions();
    fNormals         = extractor.normals();
    fUVs.reset();
    if (fNeedUVs) {
        fUVs         = extractor.uvs();
    }
}
AlembicCacheObjectReader::Ptr
AlembicCacheObjectReader::create(Alembic::Abc::IObject& abcObj, bool needUVs)
{
    CheckInterruptAndPause("reader initialization");
    
    if (Alembic::AbcGeom::IPolyMesh::matches(abcObj.getHeader()) ||
        Alembic::AbcGeom::INuPatch::matches(abcObj.getHeader()) ||
        Alembic::AbcGeom::ISubD::matches(abcObj.getHeader())) {
        Ptr reader = boost::make_shared<AlembicCacheMeshReader>(abcObj, needUVs);
        return reader->valid() ? reader : Ptr();
    }
    
    if (Alembic::AbcGeom::IXform::matches(abcObj.getHeader())) {
        Ptr reader = boost::make_shared<AlembicCacheXformReader>(abcObj, needUVs);
        return reader->valid() ? reader : Ptr();
    }
    return Ptr();
}
AlembicCacheObjectReader::~AlembicCacheObjectReader()
{}
AlembicCacheTopReader::AlembicCacheTopReader(
    Alembic::Abc::IObject abcObj,
    const bool needUVs
)
 : fBoundingBoxValidityInterval(TimeInterval::kInvalid)
{
    fXformData = XformData::create();
    size_t numChildren = abcObj.getNumChildren();
    for (size_t ii=0; ii<numChildren; ++ii)
    {
        Alembic::Abc::IObject child(abcObj, abcObj.getChildHeader(ii).getName());
        Ptr childReader = create(child, needUVs);
        if (childReader)
            fChildren.push_back(childReader);
    }
    
    TimeInterval animTimeRange(TimeInterval::kInvalid);
    BOOST_FOREACH(const AlembicCacheObjectReader::Ptr& childReader, fChildren) {
        animTimeRange |= childReader->getAnimTimeRange();
    }
    fXformData->setAnimTimeRange(animTimeRange);
}
AlembicCacheTopReader::~AlembicCacheTopReader()
{}
bool AlembicCacheTopReader::valid() const
{
    return true;
}
TimeInterval AlembicCacheTopReader::sampleHierarchy(double seconds, 
    const MMatrix& rootMatrix, TimeInterval rootMatrixInterval)
 
{
    TimeInterval validityInterval(TimeInterval::kInfinite);
    TimeInterval bboxValIntrvl(TimeInterval::kInfinite);
    BOOST_FOREACH(const AlembicCacheObjectReader::Ptr& childReader, fChildren) {
        validityInterval &= childReader->sampleHierarchy(seconds, 
                rootMatrix, rootMatrixInterval);
        bbox.
expand(childReader->getBoundingBox());
        bboxValIntrvl &= childReader->getBoundingBoxValidityInterval();
    }
    
    assert(validityInterval.contains(seconds));
    
    
    assert(!(fBoundingBoxValidityInterval & bboxValIntrvl).valid() ||
            fBoundingBoxValidityInterval == bboxValIntrvl);
    if (seconds == bboxValIntrvl.startTime()) {
        fBoundingBox                 = bbox;
        fBoundingBoxValidityInterval = bboxValIntrvl;
        boost::shared_ptr<GPUCache::XformSample> sample =
            GPUCache::XformSample::create(
                seconds,
                fBoundingBox,
                true
            );
        fXformData->addSample(sample);
    }
    return validityInterval;
}
TimeInterval AlembicCacheTopReader::sampleShape(double seconds)
{
    
    assert(0);
    return TimeInterval(TimeInterval::kInvalid);
}
SubNode::MPtr AlembicCacheTopReader::get() const
{
    SubNode::MPtr node =
        SubNode::create(
MString(
"|"), fXformData);
    BOOST_FOREACH(const AlembicCacheObjectReader::Ptr& childReader, fChildren) {
        SubNode::MPtr child = childReader->get();
        if (child) 
            SubNode::connect(node, child);
    }
    if (node->getChildren().empty()) {
        return SubNode::MPtr();
    }
    
    return node;
}
{
    return fBoundingBox;
}
TimeInterval AlembicCacheTopReader::getBoundingBoxValidityInterval() const
{
    return fBoundingBoxValidityInterval;
}
TimeInterval AlembicCacheTopReader::getAnimTimeRange() const
{
    return fXformData->animTimeRange();
}
void AlembicCacheTopReader::saveAndReset(AlembicCacheReader& cacheReader)
{
    
    BOOST_FOREACH (const AlembicCacheObjectReader::Ptr& childReader, fChildren) {
        childReader->saveAndReset(cacheReader);
    }
}
AlembicCacheXformReader::AlembicCacheXformReader(
    Alembic::Abc::IObject abcObj,
    const bool needUVs
)
    : fName(abcObj.getName()),
      fValidityInterval(TimeInterval::kInvalid),
      fBoundingBoxValidityInterval(TimeInterval::kInvalid)
{
    Alembic::AbcGeom::IXform xform(abcObj, Alembic::Abc::kWrapExisting);
    
    Alembic::AbcGeom::IXformSchema schema = xform.getSchema();
        
    
    fXformCache.init(schema);
    
    Alembic::AbcGeom::IVisibilityProperty visibility = 
                    Alembic::AbcGeom::GetVisibilityProperty(abcObj);
    if (visibility) {
        fVisibilityCache.init(visibility);
    }
    fXformData = XformData::create();
    const size_t numChildren = abcObj.getNumChildren();
    for (size_t ii=0; ii<numChildren; ++ii)
    {
        Alembic::Abc::IObject child(abcObj, abcObj.getChildHeader(ii).getName());
        Ptr childReader = create(child, needUVs);
        if (childReader)
            fChildren.push_back(childReader);
    }
    
    Alembic::Abc::TimeSamplingPtr timeSampling = schema.getTimeSampling();
    size_t numSamples = schema.getNumSamples();
    TimeInterval animTimeRange(
        timeSampling->getSampleTime(0),
        timeSampling->getSampleTime(numSamples > 0 ? numSamples-1 : 0) );
    BOOST_FOREACH(const AlembicCacheObjectReader::Ptr& childReader, fChildren) {
        animTimeRange |= childReader->getAnimTimeRange();
    }
    fXformData->setAnimTimeRange(animTimeRange);
}
AlembicCacheXformReader::~AlembicCacheXformReader()
{}
bool AlembicCacheXformReader::valid() const
{
    return fXformCache.valid();
}
TimeInterval AlembicCacheXformReader::sampleHierarchy(double seconds, 
    const MMatrix& rootMatrix, TimeInterval rootMatrixInterval)
 
{
    
    if (!fValidityInterval.contains(seconds)) {
        fillTopoAndAttrSample(seconds);
    }
    
    MMatrix newRootMatrix = fXformCache.getValue() * rootMatrix;
 
    TimeInterval newRootMatrixInterval = 
        fXformCache.getValidityInterval() & rootMatrixInterval;
    TimeInterval validityInterval = fValidityInterval;
    TimeInterval bboxValIntrvl(TimeInterval::kInfinite);
    BOOST_FOREACH(const AlembicCacheObjectReader::Ptr& childReader, fChildren) {
        validityInterval &= childReader->sampleHierarchy(seconds, 
            newRootMatrix, newRootMatrixInterval);
        bbox.
expand(childReader->getBoundingBox());
        bboxValIntrvl &= childReader->getBoundingBoxValidityInterval();
    }
    
    assert(validityInterval.contains(seconds));
    
    
    assert(!(fBoundingBoxValidityInterval & bboxValIntrvl).valid() ||
            fBoundingBoxValidityInterval == bboxValIntrvl);
    if (seconds == (fValidityInterval & bboxValIntrvl).startTime()) {
        fBoundingBox                 = bbox;
        fBoundingBoxValidityInterval = bboxValIntrvl;
        boost::shared_ptr<GPUCache::XformSample> sample =
            GPUCache::XformSample::create(
                seconds,
                fXformCache.getValue(),
                fBoundingBox,
                isVisible()
            );
        fXformData->addSample(sample);
    }
    return validityInterval;
}
TimeInterval AlembicCacheXformReader::sampleShape(double seconds)
{
    
    assert(0);
    return TimeInterval(TimeInterval::kInvalid);
}
SubNode::MPtr AlembicCacheXformReader::get() const
{
    SubNode::MPtr node =
        SubNode::create(
MString(fName.c_str()), fXformData);
    BOOST_FOREACH(const AlembicCacheObjectReader::Ptr& childReader, fChildren) {
        SubNode::MPtr child = childReader->get();
        if (child) 
            SubNode::connect(node, child);
    }
    if (node->getChildren().empty()) {
        return SubNode::MPtr();
    }
    return node;
}
        
void AlembicCacheXformReader::fillTopoAndAttrSample(chrono_t time)
{
    
    
    
    
    
    fXformCache.setTime(time);
    if (fVisibilityCache.valid()) {
        fVisibilityCache.setTime(time);
    }
    
    TimeInterval validityInterval(TimeInterval::kInfinite);
    validityInterval &= fXformCache.getValidityInterval();
    if (fVisibilityCache.valid()) {
        validityInterval &= fVisibilityCache.getValidityInterval();
    }
    assert(validityInterval.valid());
    fValidityInterval = validityInterval;
}
bool AlembicCacheXformReader::isVisible() const
{
    
    if (fVisibilityCache.valid() && 
            fVisibilityCache.getValue() == char(Alembic::AbcGeom::kVisibilityHidden)) {
        return false;
    }
    
    return true;
}
MBoundingBox AlembicCacheXformReader::getBoundingBox()
 const 
{
    return fBoundingBox;
}
TimeInterval AlembicCacheXformReader::getBoundingBoxValidityInterval() const
{
    return fBoundingBoxValidityInterval;
}
TimeInterval AlembicCacheXformReader::getAnimTimeRange() const
{
    return fXformData->animTimeRange();
}
void AlembicCacheXformReader::saveAndReset(AlembicCacheReader& cacheReader)
{
    
    BOOST_FOREACH (const AlembicCacheObjectReader::Ptr& childReader, fChildren) {
        childReader->saveAndReset(cacheReader);
    }
}
AlembicCacheMeshReader::AlembicCacheMeshReader(
    Alembic::Abc::IObject object,
    const bool needUVs
) 
    : fName(object.getName()),
      fFullName(object.getFullName()),
      fBoundingBoxValidityInterval(TimeInterval::kInvalid),
      fNumTransparentSample(0)
{
    
    if (Alembic::AbcGeom::IPolyMesh::matches(object.getHeader())) {
        Alembic::AbcGeom::IPolyMesh       meshObj(object, Alembic::Abc::kWrapExisting);
        Alembic::AbcGeom::IPolyMeshSchema schema = meshObj.getSchema();
        
        
        if (schema.getPropertyHeader(kCustomPropertyWireIndices) != NULL ||
            schema.getPropertyHeader(kCustomPropertyWireIndicesOld) != NULL) {
            fDataProvider.reset(new RawDataProvider(schema, needUVs));
        }
        else {
            fDataProvider.reset(new Triangulator(schema, needUVs));
        }
    }
    else if (Alembic::AbcGeom::INuPatch::matches(object.getHeader())) {
        Alembic::AbcGeom::INuPatch       nurbsObj(object, Alembic::Abc::kWrapExisting);
        Alembic::AbcGeom::INuPatchSchema schema = nurbsObj.getSchema();
        fDataProvider.reset(new NurbsTessellator(schema, needUVs));
    }
    else if (Alembic::AbcGeom::ISubD::matches(object.getHeader())) {
        Alembic::AbcGeom::ISubD       subdObj(object, Alembic::Abc::kWrapExisting);
        Alembic::AbcGeom::ISubDSchema schema = subdObj.getSchema();
        fDataProvider.reset(new SubDSmoother(schema, needUVs));
    }
    else {
        DisplayWarning(kUnsupportedGeomMsg);
    }
    fShapeData = ShapeData::create();    
    fShapeData->setAnimTimeRange(fDataProvider->getAnimTimeRange());
    
    std::string materialAssignmentPath;
    if (Alembic::AbcMaterial::getMaterialAssignmentPath(
            object, materialAssignmentPath)) {
        
        std::string prefix = "/";
        prefix += kMaterialsObject;
        prefix += "/";
        if (std::equal(prefix.begin(), prefix.end(), materialAssignmentPath.begin())) {
            std::string objectName = materialAssignmentPath.substr(prefix.size()).c_str();
            
            if (objectName.find("/") == std::string::npos) {
                material = objectName.c_str();
            }
        }
    }
        fShapeData->setMaterial(material);
    }
}
AlembicCacheMeshReader::~AlembicCacheMeshReader()
{
    fDataProvider.reset();
}
bool AlembicCacheMeshReader::valid() const
{
    return fDataProvider && fDataProvider->valid();
}
TimeInterval AlembicCacheMeshReader::sampleHierarchy(double seconds, 
    const MMatrix& rootMatrix, TimeInterval rootMatrixInterval)
 
{
    CheckInterruptAndPause("sampling hierarchy");
    
    if (!fDataProvider->getBBoxAndVisValidityInterval().contains(seconds)) {
        
        fDataProvider->fillBBoxAndVisSample(seconds);
    }
    TimeInterval validityInterval = fDataProvider->getBBoxAndVisValidityInterval();
    
    fBoundingBox = fDataProvider->getBoundingBox();
    fBoundingBox.transformUsing(rootMatrix);
    fBoundingBoxValidityInterval = rootMatrixInterval &
        fDataProvider->getBoundingBoxValidityInterval();
    
    
    if (seconds == validityInterval.startTime()) {
        boost::shared_ptr<const ShapeSample> sample = 
            fDataProvider->getBBoxPlaceHolderSample(seconds);
        fShapeData->addSample(sample);
    }
    return validityInterval;
}
TimeInterval AlembicCacheMeshReader::sampleShape(double seconds)
{
    CheckInterruptAndPause("sampling shape");
    
    if (!fDataProvider->getValidityInterval().contains(seconds)) {
        fDataProvider->fillTopoAndAttrSample(seconds);
    }
    TimeInterval validityInterval = fDataProvider->getValidityInterval();
    
    
    if (seconds == validityInterval.startTime()) {
        if (fDataProvider->isVisible()) {
            boost::shared_ptr<const ShapeSample> sample = 
                fDataProvider->getSample(seconds);
            fShapeData->addSample(sample);
            float alpha = sample->diffuseColor()[3];
            if (alpha > 0.0f && alpha < 1.0f) {
                fNumTransparentSample++;
            }
        }
        else {
            
            boost::shared_ptr<ShapeSample> sample =
                ShapeSample::createEmptySample(seconds);
            fShapeData->addSample(sample);
        }
    }
    return validityInterval;
}
SubNode::MPtr AlembicCacheMeshReader::get() const
{
    if (fShapeData->getSamples().size() == 1 &&
        !fShapeData->getSamples().begin()->second->visibility()) {
        
        return SubNode::MPtr();
    }
    
    SubNode::MPtr subNode = SubNode::create(
MString(fName.c_str()), fShapeData);
    if (fNumTransparentSample == 0) {
        subNode->setTransparentType(SubNode::kOpaque);
    }
    else if (fNumTransparentSample == fShapeData->getSamples().size()) {
        subNode->setTransparentType(SubNode::kTransparent);
    }
    else {
        subNode->setTransparentType(SubNode::kOpaqueAndTransparent);
    }
    return subNode;
}
        
{
    return fBoundingBox;
}
TimeInterval AlembicCacheMeshReader::getBoundingBoxValidityInterval() const
{
    return fBoundingBoxValidityInterval;
}
TimeInterval AlembicCacheMeshReader::getAnimTimeRange() const
{
    return fShapeData->animTimeRange();
}
void AlembicCacheMeshReader::saveAndReset(AlembicCacheReader& cacheReader)
{
    
    fBoundingBoxValidityInterval = TimeInterval(TimeInterval::kInvalid);
    fNumTransparentSample = 0;
    
    ShapeData::MPtr newShapeData = ShapeData::create();
    
    newShapeData->setAnimTimeRange(fShapeData->animTimeRange());
    newShapeData->setMaterials(fShapeData->getMaterials());
    
    fShapeData = newShapeData;
    Ptr thisPtr = shared_from_this();
    cacheReader.saveReader(fFullName, thisPtr);
}
AlembicCacheMaterialReader::AlembicCacheMaterialReader(Alembic::Abc::IObject abcObj)
    : fName(abcObj.getName()),
      fValidityInterval(TimeInterval::kInvalid)
{
    
    Alembic::AbcMaterial::IMaterial material(abcObj, Alembic::Abc::kWrapExisting);
    
    Alembic::AbcMaterial::IMaterialSchema schema = material.getSchema();
    
    fMaterialGraph = boost::make_shared<MaterialGraph>(
MString(fName.c_str()));
    
    size_t numNetworkNodes = schema.getNumNetworkNodes();
    
    typedef std::pair<Alembic::AbcMaterial::IMaterialSchema::NetworkNode,MaterialNode::MPtr> NodePair;
    typedef boost::unordered_map<std::string,NodePair> NodeMap;
    NodeMap nodeMap;
    
    for (size_t i = 0; i < numNetworkNodes; i++) {
        Alembic::AbcMaterial::IMaterialSchema::NetworkNode abcNode = schema.getNetworkNode(i);
        std::string target;
        if (!abcNode.valid() || !abcNode.getTarget(target) || target != kMaterialsGpuCacheTarget) {
            continue;  
        }
        std::string type;
        if (!abcNode.getNodeType(type) || type.empty()) {
            continue;  
        }
        
        std::string name = abcNode.getName();
        assert(!name.empty());
        
        MaterialNode::MPtr node = MaterialNode::create(name.c_str(), type.c_str());
        assert(node);
        fMaterialGraph->addNode(node);
        nodeMap.insert(std::make_pair(name, std::make_pair(abcNode, node)));
    }
    
    BOOST_FOREACH (NodeMap::value_type& val, nodeMap) {
        Alembic::AbcMaterial::IMaterialSchema::NetworkNode& abcNode = val.second.first;
        MaterialNode::MPtr& node = val.second.second;
        
        Alembic::Abc::ICompoundProperty compoundProp = abcNode.getParameters();
        size_t numProps = compoundProp.getNumProperties();
        for (size_t i = 0; i < numProps; i++) {
            const Alembic::Abc::PropertyHeader& header = compoundProp.getPropertyHeader(i);
            const std::string propName = header.getName();
            if (Alembic::Abc::IBoolProperty::matches(header)) {
                fBoolCaches.push_back(
                    ScalarMaterialProp<Alembic::Abc::IBoolProperty>(compoundProp, propName, node));
            }
            else if (Alembic::Abc::IInt32Property::matches(header)) {
                fInt32Caches.push_back(
                    ScalarMaterialProp<Alembic::Abc::IInt32Property>(compoundProp, propName, node));
            }
            else if (Alembic::Abc::IFloatProperty::matches(header)) {
                fFloatCaches.push_back(
                    ScalarMaterialProp<Alembic::Abc::IFloatProperty>(compoundProp, propName, node));
            }
            else if (Alembic::Abc::IV2fProperty::matches(header)) {
                fFloat2Caches.push_back(
                    ScalarMaterialProp<Alembic::Abc::IV2fProperty>(compoundProp, propName, node));
            }
            else if (Alembic::Abc::IV3fProperty::matches(header)) {
                fFloat3Caches.push_back(
                    ScalarMaterialProp<Alembic::Abc::IV3fProperty>(compoundProp, propName, node));
            }
            else if (Alembic::Abc::IC3fProperty::matches(header)) {
                fRGBCaches.push_back(
                    ScalarMaterialProp<Alembic::Abc::IC3fProperty>(compoundProp, propName, node));
            }
            else if (Alembic::Abc::IWstringProperty::matches(header)) {
                fStringCaches.push_back(
                    ScalarMaterialProp<Alembic::Abc::IWstringProperty>(compoundProp, propName, node));
            }
        }
    }
    
    BOOST_FOREACH (NodeMap::value_type& val, nodeMap) {
        Alembic::AbcMaterial::IMaterialSchema::NetworkNode& abcNode = val.second.first;
        MaterialNode::MPtr& node = val.second.second;
        
        size_t numConnections = abcNode.getNumConnections();
        for (size_t i = 0; i < numConnections; i++) {
            std::string inputName, connectedNodeName, connectedOutputName;
            abcNode.getConnection(i, inputName, connectedNodeName, connectedOutputName);
            
            MaterialProperty::MPtr prop = node->findProperty(inputName.c_str());
            
            MaterialNode::MPtr srcNode;
            NodeMap::iterator it = nodeMap.find(connectedNodeName);
            if (it != nodeMap.end()) {
                srcNode = (*it).second.second;
            }
            
            MaterialProperty::MPtr srcProp;
            if (srcNode) {
                srcProp = srcNode->findProperty(connectedOutputName.c_str());
            }
            
            if (prop && srcNode && srcProp) {
                prop->connect(srcNode, srcProp);
            }
        }
    }
    
    std::string rootNodeName, rootOutput;
    if (schema.getNetworkTerminal(kMaterialsGpuCacheTarget, kMaterialsGpuCacheType, rootNodeName, rootOutput)) {
        NodeMap::iterator it = nodeMap.find(rootNodeName);
        if (it != nodeMap.end()) {
            fMaterialGraph->setRootNode((*it).second.second);
        }
    }
}
AlembicCacheMaterialReader::~AlembicCacheMaterialReader()
{}
TimeInterval AlembicCacheMaterialReader::sampleMaterial(double seconds)
{
    TimeInterval validityInterval(TimeInterval::kInfinite);
    BOOST_FOREACH (ScalarMaterialProp<Alembic::Abc::IBoolProperty>& cache, fBoolCaches) {
        validityInterval &= cache.sample(seconds);
    }
    BOOST_FOREACH (ScalarMaterialProp<Alembic::Abc::IInt32Property>& cache, fInt32Caches) {
        validityInterval &= cache.sample(seconds);
    }
    BOOST_FOREACH (ScalarMaterialProp<Alembic::Abc::IFloatProperty>& cache, fFloatCaches) {
        validityInterval &= cache.sample(seconds);
    }
    BOOST_FOREACH (ScalarMaterialProp<Alembic::Abc::IV2fProperty>& cache, fFloat2Caches) {
        validityInterval &= cache.sample(seconds);
    }
    BOOST_FOREACH (ScalarMaterialProp<Alembic::Abc::IV3fProperty>& cache, fFloat3Caches) {
        validityInterval &= cache.sample(seconds);
    }
    BOOST_FOREACH (ScalarMaterialProp<Alembic::Abc::IC3fProperty>& cache, fRGBCaches) {
        validityInterval &= cache.sample(seconds);
    }
    BOOST_FOREACH (ScalarMaterialProp<Alembic::Abc::IWstringProperty>& cache, fStringCaches) {
        validityInterval &= cache.sample(seconds);
    }
    return validityInterval;
}
MaterialGraph::MPtr AlembicCacheMaterialReader::get() const
{
    
    if (!fMaterialGraph || !fMaterialGraph->rootNode() || fMaterialGraph->getNodes().empty()) {
        return MaterialGraph::MPtr();
    }
    return fMaterialGraph;
}
} 
boost::shared_ptr<CacheReader> AlembicCacheReader::create(
const MFileObject& file)
{
    return boost::make_shared<AlembicCacheReader>(file);
}
AlembicCacheReader::AlembicCacheReader(
const MFileObject& file)
    : fFile(file)
{
    
    try {
        tbb::mutex::scoped_lock alembicLock(gsAlembicMutex);
        if (resolvedFullName.
length() != 0 && std::ifstream(resolvedFullName.
asChar()).good()) {
 
            Alembic::AbcCoreFactory::IFactory factory;
            
            
            factory.setSampleCache( Alembic::AbcCoreAbstract::ReadArraySampleCachePtr());
            factory.setPolicy(Alembic::Abc::ErrorHandler::kThrowPolicy);
            fAbcArchive = factory.getArchive(resolvedFullName.
asChar());
            
            if (!fAbcArchive.valid()) {
            }
        }
        else {
            
        }
    }
    catch (CacheReaderInterruptException& ex) {
        
        throw ex;
    }
    catch (std::exception& ex) {
        
        
        DisplayError(kCacheOpenFileErrorMsg, file.
rawFullName(), ex.what());
    }
}
AlembicCacheReader::~AlembicCacheReader()
{
    try {
        tbb::mutex::scoped_lock alembicLock(gsAlembicMutex);
        fAbcArchive.reset();
    }
    catch (CacheReaderInterruptException& ex) {
        
        throw ex;
    }
    catch (std::exception& ex) {
        DisplayError(kCloseFileErrorMsg, fFile.resolvedFullName(), ex.what());
    }
}
bool AlembicCacheReader::valid() const
{
    tbb::mutex::scoped_lock alembicLock(gsAlembicMutex);
    return fAbcArchive.valid();
}
bool AlembicCacheReader::validateGeomPath(
{
    if (!valid()) {
        return false;
    }
    try {
        tbb::mutex::scoped_lock alembicLock(gsAlembicMutex);
        
        geomPath.
split(
'|', pathArray);
        bool valid = true;
        
        
        Alembic::Abc::IObject current = fAbcArchive.getTop();
        for (
unsigned int i = 0; i < pathArray.
length(); i++) {
 
            current = current.getChild(step.
asChar());
            if (!current.valid()) {
                valid = false;
                break;
            }
            validatedGeomPath += step;
        }
        if (validatedGeomPath.
length() == 0) {
 
        }
    
        return valid;
    }
    catch (CacheReaderInterruptException& ex) {
        
        throw ex;
    }
    catch (std::exception& ex) {
        DisplayError(kReadMeshErrorMsg, fFile.resolvedFullName(), geomPath, ex.what());
        return false;
    }
}
SubNode::Ptr AlembicCacheReader::readScene(
    const MString& geomPath, 
bool needUVs)
 
{
    
    SubNode::Ptr top = readHierarchy(geomPath, needUVs);
    if (!top) return SubNode::Ptr();
    
    ShapePathVisitor::ShapePathAndSubNodeList shapeGeomPaths;
    ShapePathVisitor shapePathVisitor(shapeGeomPaths);
    top->accept(shapePathVisitor);
    
    int lastStep = geomPath.
rindexW(
'|');
 
    if (lastStep > 0) {
    }
    
    BOOST_FOREACH (const ShapePathVisitor::ShapePathAndSubNode& pair, shapeGeomPaths) {
        SubNode::Ptr shape = readShape(prefix + pair.first, needUVs);
        if (shape && pair.first.length() > 0) {
            ReplaceSubNodeData(top, shape, pair.first);
        }
    }
    
    SubNodeTransparentTypeVisitor transparentTypeVisitor;
    top->accept(transparentTypeVisitor);
    return top;
}
SubNode::Ptr AlembicCacheReader::readHierarchy(
    const MString& geomPath, 
bool needUVs)
 
{
    using namespace CacheReaderAlembicPrivate;
    if (!valid()) return SubNode::Ptr();
    try {
        tbb::mutex::scoped_lock alembicLock(gsAlembicMutex);
        
        geomPath.
split(
'|', pathArray);
        Alembic::Abc::IObject current = fAbcArchive.getTop();
        AlembicCacheObjectReader::Ptr reader;
        if (pathArray.
length() == 0) {
 
            
            
            size_t numChildren = 0;
            size_t lastChild   = 0;
            for (size_t i = 0; i < current.getNumChildren(); i++) {
                if (Alembic::AbcGeom::IPolyMesh::matches(current.getChildHeader(i)) ||
                    Alembic::AbcGeom::INuPatch::matches(current.getChildHeader(i)) ||
                    Alembic::AbcGeom::ISubD::matches(current.getChildHeader(i)) ||
                    Alembic::AbcGeom::IXform::matches(current.getChildHeader(i))) {
                        numChildren++;
                        lastChild = i;
                }
            }
            if (numChildren == 1) {
                current = Alembic::Abc::IObject(
                    current, current.getChildHeader(lastChild).getName());
                if (current.valid())
                    reader = AlembicCacheObjectReader::create(
                    current, needUVs);
            }
            else if (numChildren > 1) {
                
                
                
                reader = boost::make_shared<AlembicCacheTopReader>(
                    current, needUVs);
            }
        }
        else {
            
            bool geometryFound = true;
            for (
unsigned int i = 0; i < pathArray.
length(); i++) {
 
                current = current.getChild(step.
asChar());
                if (!current.valid()) {
                    geometryFound = false;
                    break;
                }
            }
            if (geometryFound)
                reader = AlembicCacheObjectReader::create(current, needUVs);
        }
        if (!reader || !reader->valid()) return SubNode::Ptr();
        
        
        
        
        {
            TimeInterval interval = reader->sampleHierarchy(
                -std::numeric_limits<double>::max(),
            while (interval.endTime() != std::numeric_limits<double>::max()) {
                interval = reader->sampleHierarchy(
                    interval.endTime(), 
            }
        }
        
        SubNode::Ptr top = reader->get();
        
        reader->saveAndReset(*this);
        return top;
    }
    catch (CacheReaderInterruptException& ex) {
        
        throw ex;
    }
    catch (std::exception& ex) {
        DisplayError(kReadMeshErrorMsg, fFile.resolvedFullName(), geomPath, ex.what());
        return SubNode::Ptr();
    }
}
SubNode::Ptr AlembicCacheReader::readShape(
    const MString& geomPath, 
bool needUVs)
 
{
    using namespace CacheReaderAlembicPrivate;
    if (!valid()) return SubNode::Ptr();
    try {
        tbb::mutex::scoped_lock alembicLock(gsAlembicMutex);
        AlembicCacheObjectReader::Ptr reader;
        
        ObjectReaderMap::iterator iter = fSavedReaders.find(geomPath.
asChar());
        if (iter != fSavedReaders.end()) {
            reader = (*iter).second;
        }
        else {
            
            geomPath.
split(
'|', pathArray);
            Alembic::Abc::IObject current = fAbcArchive.getTop();
                
                bool geometryFound = true;
                for (
unsigned int i = 0; i < pathArray.
length(); i++) {
 
                    current = current.getChild(step.
asChar());
                    if (!current.valid()) {
                        geometryFound = false;
                        break;
                    }
                }
                if (geometryFound) {
                    reader = AlembicCacheObjectReader::create(current, needUVs);
                }
            }
        }
        if (!reader || !reader->valid()) return SubNode::Ptr();
        
        
        
        
        {
            TimeInterval interval = reader->sampleShape(
                -std::numeric_limits<double>::max());
            while (interval.endTime() != std::numeric_limits<double>::max()) {
                interval = reader->sampleShape(
                    interval.endTime());
            }
        }
        
        SubNode::Ptr top = reader->get();
        
        reader->saveAndReset(*this);
        return top;
    }
    catch (CacheReaderInterruptException& ex) {
        
        throw ex;
    }
    catch (std::exception& ex) {
        DisplayError(kReadMeshErrorMsg, fFile.resolvedFullName(), geomPath, ex.what());
        return SubNode::Ptr();
    }
}
MaterialGraphMap::Ptr AlembicCacheReader::readMaterials()
{
    using namespace CacheReaderAlembicPrivate;
    if (!valid()) return MaterialGraphMap::Ptr();
    try {
        tbb::mutex::scoped_lock alembicLock(gsAlembicMutex);
        
        Alembic::Abc::IObject topObject = fAbcArchive.getTop();
        Alembic::Abc::IObject materialsObject = topObject.getChild(kMaterialsObject);
        
        if (!materialsObject.valid()) {
            return MaterialGraphMap::Ptr();
        }
        MaterialGraphMap::MPtr materials = boost::make_shared<MaterialGraphMap>();
        
        for (size_t i = 0; i < materialsObject.getNumChildren(); i++) {
            Alembic::Abc::IObject object = materialsObject.getChild(i);
            if (Alembic::AbcMaterial::IMaterial::matches(object.getHeader())) {
                AlembicCacheMaterialReader reader(object);
                
                TimeInterval interval = reader.sampleMaterial(
                    -std::numeric_limits<double>::max());
                while (interval.endTime() != std::numeric_limits<double>::max()) {
                    interval = reader.sampleMaterial(interval.endTime());
                }
                MaterialGraph::MPtr graph = reader.get();
                if (graph) {
                    materials->addMaterialGraph(graph);
                }
            }
        }
        
        if (materials->getGraphs().empty()) {
            return MaterialGraphMap::Ptr();
        }
        return materials;
    }
    catch (CacheReaderInterruptException& ex) {
        
        throw ex;
    }
    catch (std::exception& ex) {
        DisplayError(kReadFileErrorMsg, fFile.resolvedFullName(), ex.what());
        return MaterialGraphMap::Ptr();
    }
}
bool AlembicCacheReader::readAnimTimeRange(GPUCache::TimeInterval& range)
{
    if (!valid()) return false;
    try {
        tbb::mutex::scoped_lock alembicLock(gsAlembicMutex);
        
        double samplesMin = std::numeric_limits<double>::infinity();
        double samplesMax = -std::numeric_limits<double>::infinity();
        unsigned int numTimeSamplings = fAbcArchive.getNumTimeSamplings();
        for (unsigned int i = 0; i < numTimeSamplings; i++) {
            
            std::stringstream propName;
            propName << i << ".samples";
            Alembic::Abc::IUInt32Property samplesProp(
                fAbcArchive.getTop().getProperties(), 
                propName.str(),
                Alembic::Abc::ErrorHandler::kQuietNoopPolicy
            );
            
            Alembic::Abc::TimeSamplingPtr timeSampling = fAbcArchive.getTimeSampling(i);
            if (samplesProp && timeSampling) {
                unsigned int numSamples = 0;
                samplesProp.get(numSamples);
                if (numSamples > 0) {
                    samplesMin = std::min(samplesMin, timeSampling->getSampleTime(0));
                    samplesMax = std::max(samplesMax, timeSampling->getSampleTime(numSamples - 1));
                }
            }
        }
        
        if (samplesMin <= samplesMax) {
            range = TimeInterval(samplesMin, samplesMax);
            return true;
        }
        
        Alembic::Abc::IBox3dProperty boxProp = Alembic::AbcGeom::GetIArchiveBounds(
            fAbcArchive, Alembic::Abc::ErrorHandler::kQuietNoopPolicy);
        if (boxProp) {
            
            size_t numSamples                          = boxProp.getNumSamples();
            Alembic::Abc::TimeSamplingPtr timeSampling = boxProp.getTimeSampling();
            if (numSamples > 0 && timeSampling) {
                range = TimeInterval(
                    timeSampling->getSampleTime(0),
                    timeSampling->getSampleTime(numSamples - 1)
                );
                return true;
            }
        }
        
        return false;
    }
    catch (CacheReaderInterruptException& ex) {
        
        throw ex;
    }
    catch (std::exception& ex) {
        DisplayError(kReadFileErrorMsg, fFile.resolvedFullName(), ex.what());
        return false;
    }
}
void AlembicCacheReader::saveReader(
    const std::string& fullName,
    CacheReaderAlembicPrivate::AlembicCacheObjectReader::Ptr& reader
)
{
    
    
    
    
    
    
    
    if (reader && reader->valid()) {
        std::string geometryPath = fullName;
        std::replace(geometryPath.begin(), geometryPath.end(), '/', '|');
        fSavedReaders.insert(std::make_pair(geometryPath, reader));
    }
}
}