int removeUnselectedNodes(SceneNodePtr root)
{
    ESS_PROFILE_FUNC();

    int nNumNodes = 0;

    SceneNodePtr newRoot = root;

    std::list<FlattenStackElement> sceneStack;

    // push a reference to each child to the stack
    for (SceneChildIterator it = root->children.begin();
            it != root->children.end(); it++) {
        SceneNodePtr fileNode = *it;
        sceneStack.push_back(FlattenStackElement(fileNode, root));
    }
    // clear the children since we may be changing what is parented to it
    root->children.clear();

    while (!sceneStack.empty()) {
        FlattenStackElement sElement = sceneStack.back();
        SceneNodePtr fileNode = sElement.currNode;
        SceneNodePtr parentNode =
            sElement.currParentNode;  // a node from the original tree, its
        // childrens will have been cleared
        // we will add child nodes to it that meet the correct criteria
        sceneStack.pop_back();

        if (fileNode->selected) {
            parentNode->children.push_back(fileNode);
            fileNode->parent = parentNode.get();
            fileNode->selected = false;

            nNumNodes++;

            for (SceneChildIterator it = fileNode->children.begin();
                    it != fileNode->children.end(); it++) {
                sceneStack.push_back(FlattenStackElement(*it, fileNode));
            }
        }
        else {
            // if a shape node is not selected its parent transform should become an
            // ITRANSFORM
            if (isShapeNode(fileNode->type) && fileNode->parent &&
                    fileNode->parent->selected &&
                    fileNode->parent->type == SceneNode::ETRANSFORM) {
                fileNode->parent->type = SceneNode::ITRANSFORM;
            }

            for (SceneChildIterator it = fileNode->children.begin();
                    it != fileNode->children.end(); it++) {
                sceneStack.push_back(FlattenStackElement(*it, parentNode));
            }
        }

        fileNode->children.clear();
    }

    return nNumNodes;
}
int selectTransformNodes(SceneNodePtr root)
{
   ESS_PROFILE_FUNC();

   std::list<SelectChildrenStackElement> sceneStack;
   
   sceneStack.push_back(SelectChildrenStackElement(root, false));

   int nSelectionCount = 0;

   while( !sceneStack.empty() )
   {
      SelectChildrenStackElement sElement = sceneStack.back();
      SceneNodePtr eNode = sElement.eNode;
      sceneStack.pop_back();

      if(eNode->type == SceneNode::NAMESPACE_TRANSFORM || eNode->type == SceneNode::ETRANSFORM || eNode->type == SceneNode::ITRANSFORM){   
         eNode->selected = true;
         nSelectionCount++;
      }

      for( std::list<SceneNodePtr>::iterator it = eNode->children.begin(); it != eNode->children.end(); it++){
         sceneStack.push_back(SelectChildrenStackElement(*it, false));
      }
   }

   root->selected = true;

   return nSelectionCount;
}
void AlembicVisibilityController::GetValueLocalTime(TimeValue t, void *ptr,
                                                    Interval &valid,
                                                    GetSetMethod method)
{
  ESS_CPP_EXCEPTION_REPORTING_START
  ESS_PROFILE_FUNC();

  Interval interval = FOREVER;

  MCHAR const *strPath = NULL;
  this->pblock->GetValue(AlembicVisibilityController::ID_PATH, t, strPath,
                         interval);

  MCHAR const *strIdentifier = NULL;
  this->pblock->GetValue(AlembicVisibilityController::ID_IDENTIFIER, t,
                         strIdentifier, interval);

  float fTime;
  this->pblock->GetValue(AlembicVisibilityController::ID_TIME, t, fTime,
                         interval);

  BOOL bMuted;
  this->pblock->GetValue(AlembicVisibilityController::ID_MUTED, t, bMuted,
                         interval);

  if (bMuted || !strPath || !strIdentifier) {
    return;
  }

  std::string szPath = EC_MCHAR_to_UTF8(strPath);
  std::string szIdentifier = EC_MCHAR_to_UTF8(strIdentifier);

  AbcG::IObject iObj = getObjectFromArchive(szPath, szIdentifier);

  if (!iObj.valid()) {
    return;
  }

  alembic_fillvis_options visOptions;
  visOptions.pIObj = &iObj;
  visOptions.dTicks = t;
  visOptions.bOldVisibility = m_bOldVisibility;
  AlembicImport_FillInVis(visOptions);

  float fBool = visOptions.bVisibility ? 1.0f : 0.0f;
  m_bOldVisibility = visOptions.bVisibility;

  valid = interval;

  if (method == CTRL_ABSOLUTE) {
    float *fInVal = (float *)ptr;
    *fInVal = fBool;
  }
  else {  // CTRL_RELATIVE
    float *fInVal = (float *)ptr;
    *fInVal = fBool * (*fInVal);
  }

  ESS_CPP_EXCEPTION_REPORTING_END
}
Abc::FloatArraySamplePtr getKnotVector(AbcG::ICurves& obj)
{
  ESS_PROFILE_FUNC();

  Abc::ICompoundProperty arbGeom = obj.getSchema().getArbGeomParams();

  if (!arbGeom.valid()) {
    return Abc::FloatArraySamplePtr();
  }

  if (arbGeom.getPropertyHeader(".knot_vectors") != NULL) {
    Abc::IFloatArrayProperty knotProp =
        Abc::IFloatArrayProperty(arbGeom, ".knot_vectors");
    if (knotProp.valid() && knotProp.getNumSamples() != 0) {
      return knotProp.getValue(0);
    }
  }
  if (arbGeom.getPropertyHeader(".knot_vector") != NULL) {
    Abc::IFloatArrayProperty knotProp =
        Abc::IFloatArrayProperty(arbGeom, ".knot_vector");
    if (knotProp.valid() && knotProp.getNumSamples() != 0) {
      return knotProp.getValue(0);
    }
  }

  return Abc::FloatArraySamplePtr();
}
void addControllersToModifierV2(const std::string& modkey, const std::string& modname, std::vector<AbcProp>& props, 
                              const std::string& file, const std::string& identifier, const std::string& category, alembic_importoptions &options, INode* pNode)
{
   ESS_PROFILE_FUNC();

   std::stringstream evalStream;
   std::string nodeName("$");
   if(pNode){
      evalStream<<GET_MAXSCRIPT_NODE(pNode);
      nodeName = std::string("mynode2113");
   }

   for(int i=0; i<props.size(); i++){
      std::string propName = props[i].name;
      std::string& val = props[i].displayVal;
      bool& bConstant = props[i].bConstant;

      const AbcA::DataType& datatype = props[i].propHeader.getDataType();
      const AbcA::MetaData& metadata = props[i].propHeader.getMetaData();

      if(datatype.getPod() == AbcA::kFloat32POD){

         std::stringstream propStream;
         propStream<<propName;
         if(datatype.getExtent() == 1){
            addFloatControllerV2(evalStream, options, nodeName, modkey, propName, file, identifier, category, propStream.str());
         }
         else if(datatype.getExtent() == 3){

            std::stringstream xStream, yStream, zStream;

            xStream<<propStream.str()<<"."<<metadata.get("interpretation")<<".x";
            yStream<<propStream.str()<<"."<<metadata.get("interpretation")<<".y";
            zStream<<propStream.str()<<"."<<metadata.get("interpretation")<<".z";

            addFloatControllerV2(evalStream, options, nodeName, modkey, propName + std::string("x"), file, identifier, category, xStream.str());
            addFloatControllerV2(evalStream, options, nodeName, modkey, propName + std::string("y"), file, identifier, category, yStream.str());
            addFloatControllerV2(evalStream, options, nodeName, modkey, propName + std::string("z"), file, identifier, category, zStream.str());
         }
      }
      else if(datatype.getPod() == AbcA::kInt32POD){
         std::stringstream propStream;
         propStream<<propName;
         if(datatype.getExtent() == 1){
            addFloatControllerV2(evalStream, options, nodeName, modkey, propName, file, identifier, category, propStream.str());
         }
      }
      else{
         
      }
      evalStream<<"\n";
   }

  
   //ESS_LOG_WARNING(evalStream.str());

   ExecuteMAXScriptScript( EC_UTF8_to_TCHAR((char*)evalStream.str().c_str()));
}
bool validateCurveData(Abc::P3fArraySamplePtr pCurvePos,
                       Abc::Int32ArraySamplePtr pCurveNbVertices,
                       Abc::UInt16ArraySamplePtr pOrders,
                       Abc::FloatArraySamplePtr pKnotVec, AbcG::CurveType type)
{
  ESS_PROFILE_FUNC();

  int nDefaultOrder = 4;
  const int numCurves = (int)pCurveNbVertices->size();
  const int numControl = (int)pCurvePos->size();

  int numControlNB = 0;

  for (int i = 0; i < pCurveNbVertices->size(); i++) {
    numControlNB += pCurveNbVertices->get()[i];
  }

  if (numControl != numControlNB) {
    ESS_LOG_ERROR(
        "Size mismatch between vertices and nbVertices. Cannot load curve.");
    return false;
  }

  if (pOrders && pCurveNbVertices->size() != pOrders->size()) {
    ESS_LOG_ERROR(
        "Size mismatch between numOrders and nbVertices. Cannot load curve.");
    return false;
  }

  if (pKnotVec) {
    int abcTotalKnots = 0;  // numControl + numCurves * (nDefaultOrder - 2) ;

    if (pOrders) {
      // calculate the expected knot vec size
      for (int i = 0; i < pCurveNbVertices->size(); i++) {
        abcTotalKnots += pCurveNbVertices->get()[i] + pOrders->get()[i] - 2;
      }
    }
    else {  // single order
      int nOrder = 0;
      if (type == AbcG::kCubic) {
        nOrder = 4;
      }
      else if (type == AbcG::kLinear) {
        nOrder = 2;
      }

      abcTotalKnots = numControl + numCurves * (nOrder - 2);
    }

    if (abcTotalKnots != pKnotVec->size()) {
      ESS_LOG_ERROR("Knot vector has the wrong size. Cannot load curve.");
    }
  }

  return true;
}
void addControllersToModifier(const std::string& modkey, const std::string& modname, std::vector<AbcProp>& props, 
                              const std::string& target, const std::string& type,
                              const std::string& file, const std::string& identifier, alembic_importoptions &options)
{
   ESS_PROFILE_FUNC();

   //the script assumes a single object is selected

   std::stringstream evalStream;

   for(int i=0; i<props.size(); i++){
      std::string propName = props[i].name;
      std::string& val = props[i].displayVal;
      bool& bConstant = props[i].bConstant;

      const AbcA::DataType& datatype = props[i].propHeader.getDataType();
      const AbcA::MetaData& metadata = props[i].propHeader.getMetaData();

      if(datatype.getPod() == AbcA::kFloat32POD){

         std::stringstream propStream;
         propStream<<target<<"."<<type<<"."<<propName;
         if(datatype.getExtent() == 1){
            addFloatController(evalStream, options, modkey, propName, file, identifier, propStream.str());
         }
         else if(datatype.getExtent() == 3){

            std::stringstream xStream, yStream, zStream;

            xStream<<propStream.str()<<"."<<metadata.get("interpretation")<<".x";
            yStream<<propStream.str()<<"."<<metadata.get("interpretation")<<".y";
            zStream<<propStream.str()<<"."<<metadata.get("interpretation")<<".z";

            addFloatController(evalStream, options, modkey, propName + std::string("x"), file, identifier, xStream.str());
            addFloatController(evalStream, options, modkey, propName + std::string("y"), file, identifier, yStream.str());
            addFloatController(evalStream, options, modkey, propName + std::string("z"), file, identifier, zStream.str());
         }
      }
      else{
         
      }
      evalStream<<"\n";
   }

  
   //ESS_LOG_WARNING(evalStream.str());

   ExecuteMAXScriptScript( EC_UTF8_to_TCHAR((char*)evalStream.str().c_str()));
}
Abc::UInt16ArraySamplePtr getCurveOrders(AbcG::ICurves& obj)
{
   ESS_PROFILE_FUNC();

   Abc::ICompoundProperty arbGeom = obj.getSchema().getArbGeomParams();

   if(!arbGeom.valid()){
      return Abc::UInt16ArraySamplePtr();
   }

   if ( arbGeom.getPropertyHeader( ".orders" ) != NULL ){
      Abc::IUInt16ArrayProperty orders = Abc::IUInt16ArrayProperty( arbGeom, ".orders" );
      if(orders.valid() && orders.getNumSamples() != 0){
         return orders.getValue(0);
      }
   }

   return Abc::UInt16ArraySamplePtr();
}
void importMetadata(INode* pNode, AbcG::IObject& iObj)
{
  ESS_PROFILE_FUNC();

  AbcG::IObject* metadataChild = NULL;

  if (getCompoundFromObject(iObj).getPropertyHeader(".metadata") == NULL) {
    return;
  }

  Abc::IStringArrayProperty metaDataProp =
      Abc::IStringArrayProperty(getCompoundFromObject(iObj), ".metadata");
  Abc::StringArraySamplePtr ptr = metaDataProp.getValue(0);
  // for(unsigned int i=0;i<ptr->size();i++){
  //	const char* name = ptr->get()[i].c_str();
  //	Abc::StringArraySamplePtr ptr = metaDataProp.getValue(0);
  //
  //}

  std::stringstream exeBuffer;

  exeBuffer << GET_MAXSCRIPT_NODE(pNode);
  exeBuffer << "ImportMetadata mynode2113";

  exeBuffer << "#(";

  for (int i = 0; i < ptr->size() - 1; i++) {
    exeBuffer << "\"";
    exeBuffer << ptr->get()[i];
    exeBuffer << "\", ";
  }
  exeBuffer << "\"";
  exeBuffer << ptr->get()[ptr->size() - 1];
  exeBuffer << "\")";

  exeBuffer << " \n";

  ExecuteMAXScriptScript(EC_UTF8_to_TCHAR((char*)exeBuffer.str().c_str()));

  // delete[] szBuffer;
}
int renameConflictingNodes(SceneNodePtr root, bool bValidate)
{
    ESS_PROFILE_FUNC();

    clearIdentifierMap();

    std::list<SelectChildrenStackElement> sceneStack;

    sceneStack.push_back(SelectChildrenStackElement(root, false));

    int nRenameCount = 0;

    while (!sceneStack.empty()) {
        SelectChildrenStackElement sElement = sceneStack.back();
        SceneNodePtr eNode = sElement.eNode;
        sceneStack.pop_back();

        if (eNode->type == SceneNode::ETRANSFORM ||
                eNode->type == SceneNode::ITRANSFORM) {
            std::string parent;
            if (eNode->parent) {
                parent = eNode->parent->dccIdentifier;
            }
            bool bRenamed = false;
            eNode->name = getUniqueName(parent, eNode->name, bValidate, bRenamed);
            if (bRenamed) {
                nRenameCount++;
            }
        }

        for (std::list<SceneNodePtr>::iterator it = eNode->children.begin();
                it != eNode->children.end(); it++) {
            sceneStack.push_back(SelectChildrenStackElement(*it, false));
        }
    }

    clearIdentifierMap();

    return nRenameCount;
}
int selectPolyMeshShapeNodes(SceneNodePtr root)
{
   ESS_PROFILE_FUNC();

   std::list<SelectChildrenStackElement> sceneStack;
   
   sceneStack.push_back(SelectChildrenStackElement(root, false));

   int nSelectionCount = 0;

   while( !sceneStack.empty() )
   {
      SelectChildrenStackElement sElement = sceneStack.back();
      SceneNodePtr eNode = sElement.eNode;
      sceneStack.pop_back();

      if(eNode->type == SceneNode::POLYMESH){   
         eNode->selected = true;
         nSelectionCount++;

         SceneNode* currNode = eNode->parent;
         while(currNode){
            if(!currNode->selected) nSelectionCount++;
            currNode->selected = true;
            currNode = currNode->parent;
         }
      }

      for( std::list<SceneNodePtr>::iterator it = eNode->children.begin(); it != eNode->children.end(); it++){
         sceneStack.push_back(SelectChildrenStackElement(*it, false));
      }
   }

   root->selected = true;

   return nSelectionCount;
}
bool AlembicCurves::Save(double time, bool bLastFrame)
{
    ESS_PROFILE_FUNC();

    //TimeValue ticks = GET_MAX_INTERFACE()->GetTime();
    TimeValue ticks = GetTimeValueFromFrame(time);
	Object *obj = mMaxNode->EvalWorldState(ticks).obj;
	if(mNumSamples == 0){
		bForever = CheckIfObjIsValidForever(obj, ticks);
	}
	else{
		bool bNewForever = CheckIfObjIsValidForever(obj, ticks);
		if(bForever && bNewForever != bForever){
			ESS_LOG_INFO( "bForever has changed" );
		}
	}

	SaveMetaData(mMaxNode, this);

    // check if the spline is animated
    if(mNumSamples > 0) 
    {
        if(bForever)
        {
            return true;
        }
    }

    AbcG::OCurvesSchema::Sample curvesSample;

	std::vector<AbcA::int32_t> nbVertices;
    std::vector<Point3> vertices;
    std::vector<float> knotVector;
    std::vector<Abc::uint16_t> orders;

    if(obj->ClassID() == EDITABLE_SURF_CLASS_ID){

       NURBSSet nurbsSet;   
       BOOL success = GetNURBSSet(obj, ticks, nurbsSet, TRUE);   

       AbcG::CurvePeriodicity cPeriod = AbcG::kNonPeriodic;
       AbcG::CurveType cType = AbcG::kCubic;
       AbcG::BasisType cBasis = AbcG::kNoBasis;

       int n = nurbsSet.GetNumObjects();
       for(int i=0; i<n; i++){
          NURBSObject* pObject = nurbsSet.GetNURBSObject((int)i);

          //NURBSType type = pObject->GetType();
          if(!pObject){
             continue;
          }

          if( pObject->GetKind() == kNURBSCurve ){
             NURBSCurve* pNurbsCurve = (NURBSCurve*)pObject;

             int degree;
             int numCVs;
             NURBSCVTab cvs;
			 int numKnots;
		     NURBSKnotTab knots;
             pNurbsCurve->GetNURBSData(ticks, degree, numCVs, cvs, numKnots, knots);

             orders.push_back(degree+1);

             const int cvsCount = cvs.Count();
             const int knotCount = knots.Count();

             for(int j=0; j<cvs.Count(); j++){
                NURBSControlVertex cv = cvs[j];
                double x, y, z;
                cv.GetPosition(ticks, x, y, z);
                vertices.push_back( Point3((float)x, (float)y, (float)z) );
             }

             nbVertices.push_back(cvsCount);

             //skip the first and last entry because Maya and XSI use this format
             for(int j=1; j<knots.Count()-1; j++){
                knotVector.push_back((float)knots[j]);
             }

             if(i == 0){
                if(pNurbsCurve->IsClosed()){
                   cPeriod = AbcG::kPeriodic;
                }  
             }
             else{
                if(pNurbsCurve->IsClosed()){
                   if(cPeriod != AbcG::kPeriodic){
                      ESS_LOG_WARNING("Mixed curve wrap types not supported.");
                   }
                }
                else{
                   if(cPeriod != AbcG::kNonPeriodic){
                      ESS_LOG_WARNING("Mixed curve wrap types not supported.");
                   }
                }
             }

          }
          
       }
       

       curvesSample.setType(cType);
       curvesSample.setWrap(cPeriod);
       curvesSample.setBasis(cBasis);
    }
    else
    {
          BezierShape beziershape;
          PolyShape polyShape;
          bool bBezier = false;

          // Get a pointer to the spline shpae
          ShapeObject *pShapeObject = NULL;
          if (obj->IsShapeObject())
          {
              pShapeObject = reinterpret_cast<ShapeObject *>(obj);
          }
          else
          {
              return false;
          }

          // Determine if we are a bezier shape
          if (pShapeObject->CanMakeBezier())
          {
              pShapeObject->MakeBezier(ticks, beziershape);
              bBezier = true;
          }
          else
          {
              pShapeObject->MakePolyShape(ticks, polyShape);
              bBezier = false;
          }

          // Get the control points

          //std::vector<Point3> inTangents;
	      //std::vector<Point3> outTangents;
          if (bBezier)
          {
              int oldVerticesCount = (int)vertices.size();
              for (int i = 0; i < beziershape.SplineCount(); i += 1)
              {
                  Spline3D *pSpline = beziershape.GetSpline(i);
                  int knots = pSpline->KnotCount();
                  for(int ix = 0; ix < knots; ++ix) 
                  {
                      Point3 in = pSpline->GetInVec(ix);
                      Point3 p = pSpline->GetKnotPoint(ix);
                      Point3 out = pSpline->GetOutVec(ix);

                      vertices.push_back( p );
				      //inTangents.push_back( in );
				      //outTangents.push_back( out );
                  }

                  int nNumVerticesAdded = (int)vertices.size() - oldVerticesCount;
                  nbVertices.push_back( nNumVerticesAdded );
                  oldVerticesCount = (int)vertices.size();
              }
          }
          else
          {
              for (int i = 0; i < polyShape.numLines; i += 1)
              {
                  PolyLine &refLine = polyShape.lines[i];
                  nbVertices.push_back(refLine.numPts);
                  for (int j = 0; j < refLine.numPts; j += 1)
                  {
                      Point3 p = refLine.pts[j].p;
                      vertices.push_back(p);
                  }
              }
          }

          // set the type + wrapping
	      curvesSample.setType(bBezier ? AbcG::kCubic : AbcG::kLinear);
          curvesSample.setWrap(pShapeObject->CurveClosed(ticks, 0) ? AbcG::kPeriodic : AbcG::kNonPeriodic);
          curvesSample.setBasis(AbcG::kNoBasis);
    }

    if(nbVertices.size() == 0 || vertices.size() == 0){
       ESS_LOG_WARNING("No curve data to export.");
       return false;
    }
 

    const int vertCount = (int)vertices.size();

    // prepare the bounding box
    Abc::Box3d bbox;

    // allocate the points and normals
    std::vector<Abc::V3f> posVec(vertCount);
    Matrix3 wm = mMaxNode->GetObjTMAfterWSM(ticks);

    for(int i=0;i<vertCount;i++)
    {
        posVec[i] = ConvertMaxPointToAlembicPoint(vertices[i] );
        bbox.extendBy(posVec[i]);

        // Set the archive bounding box
        if (mJob)
        {
            Point3 worldMaxPoint = wm * vertices[i];
            Abc::V3f alembicWorldPoint = ConvertMaxPointToAlembicPoint(worldMaxPoint);
            mJob->GetArchiveBBox().extendBy(alembicWorldPoint);
        }
    }

    if(knotVector.size() > 0 && orders.size() > 0){
       if(!mKnotVectorProperty.valid()){
          mKnotVectorProperty = Abc::OFloatArrayProperty(mCurvesSchema.getArbGeomParams(), ".knot_vector", mCurvesSchema.getMetaData(), mJob->GetAnimatedTs() );
       }
       mKnotVectorProperty.set(Abc::FloatArraySample(knotVector));

       if(!mOrdersProperty.valid()){
          mOrdersProperty = Abc::OUInt16ArrayProperty(mCurvesSchema.getArbGeomParams(), ".orders", mCurvesSchema.getMetaData(), mJob->GetAnimatedTs() );
       }
       mOrdersProperty.set(Abc::UInt16ArraySample(orders));
    }

    // store the bbox
    curvesSample.setSelfBounds(bbox);
	mCurvesSchema.getChildBoundsProperty().set(bbox);

 
    Abc::Int32ArraySample nbVerticesSample(&nbVertices.front(),nbVertices.size());
    curvesSample.setCurvesNumVertices(nbVerticesSample);


 
    // allocate for the points and normals
    Abc::P3fArraySample posSample(&posVec.front(),posVec.size());
	curvesSample.setPositions(posSample);


    mCurvesSchema.set(curvesSample);

   mNumSamples++;

   return true;
}
void flattenSceneGraph(SceneNodePtr root, int nNumNodes)
{
   ESS_PROFILE_FUNC();

   SceneNodePtr newRoot = root;

   std::list<FlattenStackElement> sceneStack;

   //push a reference to each child to the stack
   for(SceneChildIterator it = root->children.begin(); it != root->children.end(); it++){
      SceneNodePtr fileNode = *it;
      sceneStack.push_back(FlattenStackElement(fileNode, root));
   }
   //clear the children since we may be changing what is parented to it
   root->children.clear();
 

   while( !sceneStack.empty() )
   {
      FlattenStackElement sElement = sceneStack.back();
      SceneNodePtr fileNode = sElement.currNode;
      SceneNodePtr parentNode = sElement.currParentNode;//a node from the original tree, its childrens will have been cleared
      //we will add child nodes to it that meet the correct criteria
      sceneStack.pop_back();

      //a nodes that we want keep, so connect it to the parent
      if (fileNode->type == SceneNode::NAMESPACE_TRANSFORM ||  //namespace transform (XSI model)
          fileNode->type == SceneNode::ETRANSFORM ||           //shape node parent transform
          isShapeNode(fileNode->type)                          //shape node
         ) { //mergedpolymesh node would apply here
            parentNode->children.push_back(fileNode);
            fileNode->parent = parentNode.get();
            
            nNumNodes++;
      }

      //here we set what the parent is going to be for the file node we pushing to the stack
      //    - if parentNode is passed instead of fileNode, the subtree starting at fileNode is being skipped
      if(fileNode->type == SceneNode::NAMESPACE_TRANSFORM){
         for(SceneChildIterator it = fileNode->children.begin(); it != fileNode->children.end(); it++){
            sceneStack.push_back( FlattenStackElement( *it, fileNode ) );
         }
      }
      else if(fileNode->type == SceneNode::ETRANSFORM){
         for(SceneChildIterator it = fileNode->children.begin(); it != fileNode->children.end(); it++){
            if(isShapeNode((*it)->type)){
               sceneStack.push_back( FlattenStackElement( *it, fileNode ) );
            }
            else{
               sceneStack.push_back( FlattenStackElement( *it, parentNode ) );
            }
         }
      }
      else{
         for(SceneChildIterator it = fileNode->children.begin(); it != fileNode->children.end(); it++){
            sceneStack.push_back( FlattenStackElement( *it, parentNode ) );
         }
      }

      fileNode->children.clear();
   }
}
Modifier* createDisplayModifier(std::string modkey, std::string modname, std::vector<AbcProp>& props, INode* pNode)
{
   ESS_PROFILE_FUNC();

   //the script assumes a single object is selected
   std::stringstream evalStream;
   std::string nodeName("$");
   if(pNode){
      evalStream<<GET_MAXSCRIPT_NODE(pNode);
      nodeName = std::string("mynode2113");
   }
   evalStream<<"propModifier = EmptyModifier()"<<"\n";
   evalStream<<"propModifier.name = \""<<modkey<<"\""<<"\n";
   evalStream<<"modCount = "<<nodeName<<".modifiers.count"<<"\n";
   evalStream<<"addmodifier "<<nodeName<<" propModifier before:modCount"<<"\n";
   evalStream<<nodeName<<".modifiers[\""<<modkey<<"\"].enabled = false"<<"\n";

   evalStream<<"propAttribute = attributes propAttribute"<<"\n";
   evalStream<<"("<<"\n";

   evalStream<<"parameters propAttributePRM1 rollout:propAttributeRLT1"<<"\n";
   evalStream<<"("<<"\n";
   for(int i=0; i<props.size(); i++){
      std::string& name = props[i].name;
      std::string& val = props[i].displayVal;
      bool& bConstant = props[i].bConstant;

      const AbcA::DataType& datatype = props[i].propHeader.getDataType();
      const AbcA::MetaData& metadata = props[i].propHeader.getMetaData();

      if(datatype.getPod() == AbcA::kInt32POD && datatype.getExtent() == 1){
         evalStream<<name<<" type:#integer animatable:true ui:e"<<name<<" default:"<<val;
      }
      else if(datatype.getPod() == AbcA::kFloat32POD && datatype.getExtent() == 1){
         evalStream<<name<<" type:#float animatable:true ui:e"<<name<<" default:"<<val;
      }
      else if(datatype.getPod() == AbcA::kFloat32POD && datatype.getExtent() == 3){
         //if(metadata.get("interpretation") == std::string("rgb")){
         //   evalStream<<name<<" type:#color animatable:false ui:e"<<name<<" default:(["<<val<<"] as color)"; 
         //}
         //else{
            //evalStream<<name<<" type:#point3 animatable:false ui:e"<<name<<" ["<<val<<"]"; 
            evalStream<<name<<"x type:#float animatable:true ui:e"<<name<<"x default:300\n";
            evalStream<<name<<"y type:#float animatable:true ui:e"<<name<<"y default:300\n";
            evalStream<<name<<"z type:#float animatable:true ui:e"<<name<<"z default:300\n";
         //}
      }
      else{
         evalStream<<name<<" type:#string animatable:false ui:e"<<name<<" default:\""<<val<<"\"";
      }
      evalStream<<"\n";
   }
   evalStream<<")"<<"\n";
   
   evalStream<<"rollout propAttributeRLT1 \""<<modname<<"\""<<"\n";
   evalStream<<"("<<"\n";

   for(int i=0; i<props.size(); i++){
      std::string& name = props[i].name;
      bool& bConstant = props[i].bConstant;
      const AbcA::DataType& datatype = props[i].propHeader.getDataType();
      const AbcA::MetaData& metadata = props[i].propHeader.getMetaData();
      const int nSize = (const int) props[i].displayVal.size();

      if(datatype.getPod() == AbcA::kInt32POD && datatype.getExtent() == 1){
         evalStream<<"label lbl"<<name<<" \""<<name<<"\" align:#left fieldWidth:140\n";
         evalStream<<"spinner e"<<name<<" \"\" type:#integer range:[-9999999,9999999,0] align:#left labelOnTop:true\n";
      }
      else if(datatype.getPod() == AbcA::kFloat32POD && datatype.getExtent() == 1){
         evalStream<<"label lbl"<<name<<" \""<<name<<"\" align:#left fieldWidth:140\n";
         evalStream<<"spinner e"<<name<<" \"\" type:#float range:[-9999999,9999999,0]align:#left labelOnTop:true\n";
      }
      else if(datatype.getPod() == AbcA::kFloat32POD && datatype.getExtent() == 3){

         evalStream<<"label lbl"<<name<<" \""<<name<<"\" align:#left fieldWidth:140\n";
         evalStream<<"spinner e"<<name<<"x\"\" across:3 type:#float range:[-9999999,9999999,0] fieldWidth:39 readOnly:true\n";
         evalStream<<"spinner e"<<name<<"y\"\" type:#float range:[-9999999,9999999,0] fieldWidth:39\n";
         evalStream<<"spinner e"<<name<<"z\"\" type:#float range:[-9999999,9999999,0] fieldWidth:39\n";
      }
      else{
         //TODO: better fit for large strings
         evalStream<<"edittext e"<<name<<" \" "<<name<<"\" fieldWidth:140 ";
         if(nSize > 140){
            evalStream<<"height:54";
         }
         evalStream<<" labelOnTop:true";
         if(bConstant) evalStream<<" readOnly:true";
      }

      evalStream<<"\n";
   }

   //evalStream<<"on propAttributePRM1 changed do\n";
   //evalStream<<"("<<"\n";

   //for(int i=0; i<props.size(); i++){
   //   std::string& name = props[i].name;

   //   const AbcA::DataType& datatype = props[i].propHeader.getDataType();
   //   const AbcA::MetaData& metadata = props[i].propHeader.getMetaData();

   //   evalStream<<"e"<<name<<".text = "<<name<<" as string";
   //   evalStream<<"\n";
   //}

   //evalStream<<")"<<"\n";
   evalStream<<")"<<"\n";
   evalStream<<")"<<"\n";


   evalStream<<"custattributes.add "<<nodeName<<".modifiers[\""<<modkey<<"\"] propAttribute baseobject:false"<<"\n";

   

   //ESS_LOG_WARNING(evalStream.str());

   evalStream<<nodeName<<".modifiers[\""<<modkey<<"\"]\n";
   FPValue fpVal;
   ExecuteMAXScriptScript( EC_UTF8_to_TCHAR((char*)evalStream.str().c_str()), 0, &fpVal);
   Modifier* pMod = (Modifier*)fpVal.r;
   return pMod;
}
int refineSelection(SceneNodePtr root, bool bSelectParents, bool bChildren, bool bSelectShapeNodes)
{
   ESS_PROFILE_FUNC();

   std::list<SelectChildrenStackElement> sceneStack;

   sceneStack.push_back(SelectChildrenStackElement(root, false));

   int nSelectionCount = 0;

   while( !sceneStack.empty() )
   {
      SelectChildrenStackElement sElement = sceneStack.back();
      SceneNodePtr eNode = sElement.eNode;
      sceneStack.pop_back();

      bool bSelected = false;
      //check if the node matches a full path

      if(eNode->type == SceneNode::ETRANSFORM || eNode->type == SceneNode::ITRANSFORM || eNode->type == SceneNode::NAMESPACE_TRANSFORM)
	  {   
         if(eNode->dccSelected){

            //this node's name matches one of the names from the selection map, so select it
            if(!eNode->selected) nSelectionCount++;
            eNode->selected = true;

            if(eNode->type == SceneNode::ETRANSFORM && bSelectShapeNodes)
			   {
               //Select the shape nodes first
			      for(std::list<SceneNodePtr>::iterator it=eNode->children.begin(); it != eNode->children.end(); it++)
               {
				     if(::isShapeNode((*it)->type)){
					    if(!(*it)->selected) nSelectionCount++;
					    (*it)->selected = true;
					    break;
				     }
			      }
            }

            //then select the parents
            if(bSelectParents){// select all parent nodes
               SceneNode* currNode = eNode->parent;
               while(currNode){
                  if(!currNode->selected) nSelectionCount++;
                  currNode->selected = true;
                  currNode = currNode->parent;
               }
            }

            if(bChildren){// select the children
               bSelected = true;
            }
         }	
      }
      if(sElement.bSelectChildren){
         bSelected = true;
         if(!eNode->selected) nSelectionCount++;
         eNode->selected = true;
      }

      for( std::list<SceneNodePtr>::iterator it = eNode->children.begin(); it != eNode->children.end(); it++){
         sceneStack.push_back(SelectChildrenStackElement(*it, bSelected));
      }
   }

   root->selected = true;

   return nSelectionCount;
}
int selectNodes(SceneNodePtr root, SceneNode::SelectionT& selectionMap, bool bSelectParents, bool bChildren, bool bSelectShapeNodes, bool isMaya)
{
   ESS_PROFILE_FUNC();

   SceneNode::SelectionT::iterator selectionEnd = selectionMap.end();

   std::list<SelectChildrenStackElement> sceneStack;

   sceneStack.push_back(SelectChildrenStackElement(root, false));

   int nSelectionCount = 0;

   while( !sceneStack.empty() )
   {
      SelectChildrenStackElement sElement = sceneStack.back();
      SceneNodePtr eNode = sElement.eNode;
      sceneStack.pop_back();

      bool bSelected = false;
      //check if the node matches a full path

      if(eNode->type == SceneNode::ETRANSFORM || eNode->type == SceneNode::ITRANSFORM || eNode->type == SceneNode::NAMESPACE_TRANSFORM || eNode->type == SceneNode::HAIR)// removed to be able to export hair properly in Maya!
	  {   

         SceneNode::SelectionT::iterator selectionIt = selectionMap.find(eNode->dccIdentifier);
         if(selectionIt != selectionEnd){
            //to keep track which ones resolved
            selectionMap[eNode->dccIdentifier] = true;
         }

         //otherwise, check if the node names match
         if(selectionIt == selectionEnd){
            std::string nodeName = removeXfoSuffix(eNode->name);
            selectionIt = selectionMap.find(nodeName);

            if(selectionIt != selectionEnd){
               //to keep track which ones resolved
               selectionMap[nodeName] = true;
            }
         }

         if(selectionIt != selectionEnd){

            //this node's name matches one of the names from the selection map, so select it
            if(!eNode->selected) nSelectionCount++;
            eNode->selected = true;

            if(eNode->type == SceneNode::ETRANSFORM && bSelectShapeNodes)
			   {
               //Select the shape nodes first
				   if (isMaya)
				   {
					   for(std::list<SceneNodePtr>::reverse_iterator it=eNode->children.rbegin(); it != eNode->children.rend(); ++it)
					   {
						   if(::isShapeNode((*it)->type))//MergedPolyMesh node should work here...
						   {
							   if(!(*it)->selected) nSelectionCount++;
							   (*it)->selected = true;
							   break;
						   }
					   }	
				   }
				   else
				   {
				      for(std::list<SceneNodePtr>::iterator it=eNode->children.begin(); it != eNode->children.end(); it++)
                  {
					     if(::isShapeNode((*it)->type)){
						    if(!(*it)->selected) nSelectionCount++;
						    (*it)->selected = true;
						    break;
					     }
				      }
				   }
            }

            //then select the parents
            if(bSelectParents){// select all parent nodes
               SceneNode* currNode = eNode->parent;
               while(currNode){
                  if(!currNode->selected) nSelectionCount++;
                  currNode->selected = true;
                  currNode = currNode->parent;
               }
            }

            if(bChildren){// select the children
               bSelected = true;
            }
         }	// if(selectionIt != 
      }	// if(eNode->type == 
      if(sElement.bSelectChildren){
         bSelected = true;
         if(!eNode->selected) nSelectionCount++;
         eNode->selected = true;
      }

      for( std::list<SceneNodePtr>::iterator it = eNode->children.begin(); it != eNode->children.end(); it++){
         sceneStack.push_back(SelectChildrenStackElement(*it, bSelected));
      }
   }

   root->selected = true;

   return nSelectionCount;
}
//we have to save out all mesh encountered on the current frame of this object immediately, because 
//the pointers will no longer be valid when the object is evaluated at a new time
void AlembicPoints::saveCurrentFrameMeshes()
{
    ESS_PROFILE_FUNC();

	for(int i=0; i<mMeshesToSaveForCurrentFrame.size(); i++){

        //TODO: save immediately instead accumulating in a vector
		meshInfo* mi = mMeshesToSaveForCurrentFrame[i];

		if(mi->pMesh){
			Mesh* pMesh = mi->pMesh;
			//mi->pMesh = NULL;//each mesh only needs to be saved once

			//Matrix3 worldTrans;
			//worldTrans.IdentityMatrix();

			CommonOptions options;
         options.SetOption("exportNormals", mJob->GetOption("exportNormals"));
			options.SetOption("exportMaterialIds", mJob->GetOption("exportMaterialIds"));

			//gather the mesh data
			mi->meshTM.IdentityMatrix();
			IntermediatePolyMesh3DSMax finalPolyMesh;
            {
            ESS_PROFILE_SCOPE("AlembicPoints::saveCurrentFrameMeshes - finalPolyMesh.Save");

            Imath::M44f transform44f;
            ConvertMaxMatrixToAlembicMatrix( mi->meshTM, transform44f );
            SceneNodeMaxParticlesPtr inputSceneNode(new SceneNodeMaxParticles(pMesh, NULL, mi->nMatId));
            finalPolyMesh.Save(inputSceneNode, transform44f, options, 0.0);

            }

            AbcG::OPolyMeshSchema::Sample meshSample;
            AbcG::OPolyMeshSchema meshSchema;
            {
            ESS_PROFILE_SCOPE("AlembicPoints::saveCurrentFrameMeshes - save xforms, bbox, geo, topo");
			//save out the mesh xForm
			//std::string xformName = mi->name + "Xfo";
			AbcG::OXform xform(mJob->GetArchive().getTop(), mi->name, GetCurrentJob()->GetAnimatedTs());
			AbcG::OXformSchema& xformSchema = xform.getSchema();//mi->xformSchema;

			std::string meshName = mi->name + "Shape";
			AbcG::OPolyMesh mesh(xform, meshName, GetCurrentJob()->GetAnimatedTs());
			meshSchema = mesh.getSchema();

			AbcG::XformSample xformSample;

			Matrix3 alembicMatrix;
			alembicMatrix.IdentityMatrix();
			alembicMatrix.SetTrans(Point3(-10000.0f, -10000.0f, -10000.0f));
			Abc::M44d iMatrix;
			ConvertMaxMatrixToAlembicMatrix(alembicMatrix, iMatrix);

			xformSample.setMatrix(iMatrix);
			xformSchema.set(xformSample);

			//update the archive bounding box
			Abc::Box3d bbox;
			bbox.min = finalPolyMesh.bbox.min * iMatrix;
			bbox.max = finalPolyMesh.bbox.max * iMatrix;

			mJob->GetArchiveBBox().extendBy(bbox);

			//save out the mesh data			
			meshSample.setPositions(Abc::P3fArraySample(finalPolyMesh.posVec));
			meshSample.setSelfBounds(finalPolyMesh.bbox);
			meshSchema.getChildBoundsProperty().set(finalPolyMesh.bbox);

			meshSample.setFaceCounts(Abc::Int32ArraySample(finalPolyMesh.mFaceCountVec));
			meshSample.setFaceIndices(Abc::Int32ArraySample(finalPolyMesh.mFaceIndicesVec));
            }

			if(mJob->GetOption("validateMeshTopology")){
                ESS_PROFILE_SCOPE("AlembicPoints::saveCurrentFrameMeshes - validateMeshTopology");
				mJob->mMeshErrors += validateAlembicMeshTopo(finalPolyMesh.mFaceCountVec, finalPolyMesh.mFaceIndicesVec, mi->name);
			}

			if(mJob->GetOption("exportNormals")){
                ESS_PROFILE_SCOPE("AlembicPoints::saveCurrentFrameMeshes - exportNormals");
				AbcG::ON3fGeomParam::Sample normalSample;
				normalSample.setScope(AbcG::kFacevaryingScope);
				normalSample.setVals(Abc::N3fArraySample(finalPolyMesh.mIndexedNormals.values));
				normalSample.setIndices(Abc::UInt32ArraySample(finalPolyMesh.mIndexedNormals.indices));
				meshSample.setNormals(normalSample);
			}

			if(mJob->GetOption("exportMaterialIds")){
                ESS_PROFILE_SCOPE("AlembicPoints::saveCurrentFrameMeshes - exportMaterialIds");
				Abc::OUInt32ArrayProperty mMatIdProperty = 
					Abc::OUInt32ArrayProperty(meshSchema, ".materialids", meshSchema.getMetaData(), mJob->GetAnimatedTs());
				mMatIdProperty.set(Abc::UInt32ArraySample(finalPolyMesh.mMatIdIndexVec));

				for ( facesetmap_it it=finalPolyMesh.mFaceSets.begin(); it != finalPolyMesh.mFaceSets.end(); it++)
				{
					std::stringstream nameStream;
					int nMaterialId = it->first+1;
					nameStream<<it->second.name<<"_"<<nMaterialId;

					std::vector<Abc::int32_t>& faceSetVec = it->second.faceIds;

					AbcG::OFaceSet faceSet = meshSchema.createFaceSet(nameStream.str());
					AbcG::OFaceSetSchema::Sample faceSetSample(Abc::Int32ArraySample(&faceSetVec.front(), faceSetVec.size()));
					faceSet.getSchema().set(faceSetSample);
				}
			}

			if(mJob->GetOption("exportUVs")){
				//TODO...
			}

            {
            ESS_PROFILE_SCOPE("AlembicPoints::saveCurrentFrameMeshes meshSchema.set");
			meshSchema.set(meshSample);
            }

			if(mi->bNeedDelete){
				delete mi->pMesh;
				mi->bNeedDelete = FALSE;
			}
			mi->pMesh = NULL;
		}
	}
	
	mMeshesToSaveForCurrentFrame.clear();
}
bool AlembicPoints::Save(double time, bool bLastFrame)
{
   ESS_PROFILE_FUNC();

    // Note: Particles are always considered to be animated even though
    //       the node does not have the IsAnimated() flag.

    // Extract our particle emitter at the given time
    TimeValue ticks = GetTimeValueFromFrame(time);
    Object *obj = mMaxNode->EvalWorldState(ticks).obj;

	SaveMetaData(mMaxNode, this);

	SimpleParticle* pSimpleParticle = (SimpleParticle*)obj->GetInterface(I_SIMPLEPARTICLEOBJ);
	IPFSystem* ipfSystem = GetPFSystemInterface(obj);
	IParticleObjectExt* particlesExt = GetParticleObjectExtInterface(obj);
	
#ifdef THINKING_PARTICLES
	ParticleMat* pThinkingParticleMat = NULL;
	TP_MasterSystemInterface* pTPMasterSystemInt = NULL;
	if(obj->CanConvertToType(MATTERWAVES_CLASS_ID))
	{
		pThinkingParticleMat = reinterpret_cast<ParticleMat*>(obj->ConvertToType(ticks, MATTERWAVES_CLASS_ID));
		pTPMasterSystemInt = reinterpret_cast<TP_MasterSystemInterface*>(obj->GetInterface(IID_TP_MASTERSYSTEM));     

	}
#endif

	const bool bAutomaticInstancing = GetCurrentJob()->GetOption("automaticInstancing");

	if( 
#ifdef THINKING_PARTICLES
		!pThinkingParticleMat && 
#endif
		!particlesExt && !pSimpleParticle){
		return false;
	}


	//We have to put the particle system into the renders state so that PFOperatorMaterialFrequency::Proceed will set the materialID channel
	//Note: settting the render state to true breaks the shape node instancing export
	bool bRenderStateForced = false;
	if(bAutomaticInstancing && ipfSystem && !ipfSystem->IsRenderState()){
		ipfSystem->SetRenderState(true);
		bRenderStateForced = true;
	}

	int numParticles = 0;
#ifdef THINKING_PARTICLES
	if(pThinkingParticleMat){
		numParticles = pThinkingParticleMat->NumParticles();	
	}
	else 
#endif
	if(particlesExt){
		particlesExt->UpdateParticles(mMaxNode, ticks);
		numParticles = particlesExt->NumParticles();
	}
	else if(pSimpleParticle){
		pSimpleParticle->Update(ticks, mMaxNode);
		numParticles = pSimpleParticle->parts.points.Count();
	}


    // Store positions, velocity, width/size, scale, id, bounding box
    std::vector<Abc::V3f> positionVec;
    std::vector<Abc::V3f> velocityVec;
    std::vector<Abc::V3f> scaleVec;
    std::vector<float> widthVec;
    std::vector<float> ageVec;
    std::vector<float> massVec;
    std::vector<float> shapeTimeVec;
    std::vector<Abc::uint64_t> idVec;
    std::vector<Abc::uint16_t> shapeTypeVec;
    std::vector<Abc::uint16_t> shapeInstanceIDVec;
    std::vector<Abc::Quatf> orientationVec;
    std::vector<Abc::Quatf> angularVelocityVec;
    std::vector<Abc::C4f> colorVec;

    positionVec.reserve(numParticles);
    velocityVec.reserve(numParticles);
    scaleVec.reserve(numParticles);
    widthVec.reserve(numParticles);
    ageVec.reserve(numParticles);
    massVec.reserve(numParticles);
    shapeTimeVec.reserve(numParticles);
    idVec.reserve(numParticles);
    shapeTypeVec.reserve(numParticles);
    shapeInstanceIDVec.reserve(numParticles);
    orientationVec.reserve(numParticles);
    angularVelocityVec.reserve(numParticles);
    colorVec.reserve(numParticles);

    //std::vector<std::string> instanceNamesVec;
    Abc::Box3d bbox;
    bool constantPos = true;
    bool constantVel = true;
    bool constantScale = true;
    bool constantWidth = true;
    bool constantAge = true;
    bool constantOrientation = true;
    bool constantAngularVel = true;
	bool constantColor = true;


	if(bAutomaticInstancing){
		SetMaxSceneTime(ticks);
	}

	//The MAX interfaces return everything in world coordinates,
	//so we need to multiply the inverse the node world transform matrix
    Matrix3 nodeWorldTM = mMaxNode->GetObjTMAfterWSM(ticks);
    // Convert the max transform to alembic
    Matrix3 alembicMatrix;
    ConvertMaxMatrixToAlembicMatrix(nodeWorldTM, alembicMatrix);
    Abc::M44d nodeWorldTrans(	alembicMatrix.GetRow(0).x,  alembicMatrix.GetRow(0).y,  alembicMatrix.GetRow(0).z,  0,
										alembicMatrix.GetRow(1).x,  alembicMatrix.GetRow(1).y,  alembicMatrix.GetRow(1).z,  0,
										alembicMatrix.GetRow(2).x,  alembicMatrix.GetRow(2).y,  alembicMatrix.GetRow(2).z,  0,
										alembicMatrix.GetRow(3).x,  alembicMatrix.GetRow(3).y,  alembicMatrix.GetRow(3).z,  1);
	Abc::M44d nodeWorldTransInv = nodeWorldTrans.inverse();


	//ESS_LOG_WARNING("tick: "<<ticks<<"   numParticles: "<<numParticles<<"\n");

	ExoNullView nullView;
	particleGroupInterface groupInterface(particlesExt, obj, mMaxNode, &nullView);

    {
    ESS_PROFILE_SCOPE("AlembicPoints::SAVE - numParticlesLoop");

	for (int i = 0; i < numParticles; ++i)
	{
		Abc::V3f pos(0.0);
		Abc::V3f vel(0.0);
		Abc::V3f scale(1.0);
		Abc::C4f color(0.5, 0.5, 0.5, 1.0);
		float age = 0;
		Abc::uint64_t id = 0;
	    Abc::Quatd orientation(0.0, 0.0, 1.0, 0.0);
		Abc::Quatd spin(0.0, 0.0, 1.0, 0.0);
		// Particle size is a uniform scale multiplier in XSI.  In Max, I need to learn where to get this 
		// For now, we'll just default to 1
		float width = 1.0f;

		ShapeType shapetype = ShapeType_Point;
		float shapeInstanceTime = (float)time;
		Abc::uint16_t shapeInstanceId = 0;

#ifdef THINKING_PARTICLES
		if(pThinkingParticleMat){
            

			if(pTPMasterSystemInt->IsAlive(i) == FALSE){
				continue;
			}

			//TimeValue ageValue = particlesExt->GetParticleAgeByIndex(i);
			TimeValue ageValue = pTPMasterSystemInt->Age(i);
			if(ageValue == -1){
				continue;
			}

            ESS_PROFILE_SCOPE("AlembicPoints::SAVE - numParticlesLoop - ThinkingParticles");
			age = (float)GetSecondsFromTimeValue(ageValue);

			//pos = ConvertMaxPointToAlembicPoint(*particlesExt->GetParticlePositionByIndex(i));
			pos = ConvertMaxPointToAlembicPoint(pTPMasterSystemInt->Position(i));
			//vel = ConvertMaxVectorToAlembicVector(*particlesExt->GetParticleSpeedByIndex(i) * TIME_TICKSPERSEC);
			vel = ConvertMaxVectorToAlembicVector(pTPMasterSystemInt->Velocity(i) * TIME_TICKSPERSEC);
			scale = ConvertMaxScaleToAlembicScale(pTPMasterSystemInt->Scale(i));
			scale *= pTPMasterSystemInt->Size(i);
			
			//ConvertMaxEulerXYZToAlembicQuat(*particlesExt->GetParticleOrientationByIndex(i), orientation);

			Matrix3 alignmentMatMax = pTPMasterSystemInt->Alignment(i);
			Abc::M44d alignmentMat;
			ConvertMaxMatrixToAlembicMatrix(alignmentMatMax, alignmentMat);
			/*alignmentMat = Abc::M44d( alignmentMatMax.GetRow(0).x,  alignmentMatMax.GetRow(0).y,  alignmentMatMax.GetRow(0).z,  0,
                                 alignmentMatMax.GetRow(1).x,  alignmentMatMax.GetRow(1).y,  alignmentMatMax.GetRow(1).z,  0,
                                 alignmentMatMax.GetRow(2).x,  alignmentMatMax.GetRow(2).y,  alignmentMatMax.GetRow(2).z,  0,
                                 alignmentMatMax.GetRow(3).x,  alignmentMatMax.GetRow(3).y,  alignmentMatMax.GetRow(3).z,  1);*/
			//orientation = ConvertMaxQuatToAlembicQuat(extracctuat(alignmentMat), true);

			alignmentMat = alignmentMat * nodeWorldTransInv;
			orientation = extractQuat(alignmentMat);

			//ConvertMaxAngAxisToAlembicQuat(*particlesExt->GetParticleSpinByIndex(i), spin);
			ConvertMaxAngAxisToAlembicQuat(pTPMasterSystemInt->Spin(i), spin);


			id = particlesExt->GetParticleBornIndex(i);

			//seems to always return 0
			//int nPid = pThinkingParticleMat->ParticleID(i);
					
			int nMatId = -1;
			Matrix3 meshTM;
			meshTM.IdentityMatrix();
			BOOL bNeedDelete = FALSE;
			BOOL bChanged = FALSE;
            Mesh* pMesh = NULL;
            {
            ESS_PROFILE_SCOPE("AlembicPoints::SAVE - numParticlesLoop - ThinkingParticles - GetParticleRenderMesh");
			pMesh = pThinkingParticleMat->GetParticleRenderMesh(ticks, mMaxNode, nullView, bNeedDelete, i, meshTM, bChanged);
            }

			if(pMesh){
                ESS_PROFILE_SCOPE("AlembicPoints::SAVE - numParticlesLoop - ThinkingParticles - CacheShapeMesh");
            meshInfo mi = CacheShapeMesh(pMesh, bNeedDelete, meshTM, nMatId, i, ticks, shapetype, shapeInstanceId, shapeInstanceTime);
            Abc::V3d min = pos + mi.bbox.min;
            Abc::V3d max = pos + mi.bbox.max;
            bbox.extendBy(min);
            bbox.extendBy(max);
			}
			else{
				shapetype = ShapeType_Point;
			}
		}
		else 
#endif
		if(particlesExt && ipfSystem){

			TimeValue ageValue = particlesExt->GetParticleAgeByIndex(i);
			if(ageValue == -1){
				continue;
			}
			age = (float)GetSecondsFromTimeValue(ageValue);

			pos = ConvertMaxPointToAlembicPoint(*particlesExt->GetParticlePositionByIndex(i));
			vel = ConvertMaxVectorToAlembicVector(*particlesExt->GetParticleSpeedByIndex(i) * TIME_TICKSPERSEC);
			scale = ConvertMaxScaleToAlembicScale(*particlesExt->GetParticleScaleXYZByIndex(i));
			ConvertMaxEulerXYZToAlembicQuat(*particlesExt->GetParticleOrientationByIndex(i), orientation);
			ConvertMaxAngAxisToAlembicQuat(*particlesExt->GetParticleSpinByIndex(i), spin);
			//age = (float)GetSecondsFromTimeValue(particlesExt->GetParticleAgeByIndex(i));
			id = particlesExt->GetParticleBornIndex(i);
			if(bAutomaticInstancing){

				int nMatId = -1;
				if(ipfSystem){
					if( groupInterface.setCurrentParticle(ticks, i) ){
						nMatId = groupInterface.getCurrentMtlId();
					}
					else{
						ESS_LOG_WARNING("Error: cound retrieve material ID for particle mesh "<<i);
					}
				}

				Matrix3 meshTM;
				meshTM.IdentityMatrix();
				BOOL bNeedDelete = FALSE;
				BOOL bChanged = FALSE;
				Mesh* pMesh = pMesh = particlesExt->GetParticleShapeByIndex(i);

				if(pMesh){
					meshInfo mi = CacheShapeMesh(pMesh, bNeedDelete, meshTM, nMatId, i, ticks, shapetype, shapeInstanceId, shapeInstanceTime);
               Abc::V3d min = pos + mi.bbox.min;
               Abc::V3d max = pos + mi.bbox.max;
               bbox.extendBy(min);
               bbox.extendBy(max);
				}
				else{
					shapetype = ShapeType_Point;
				}
			}
			else{
				GetShapeType(particlesExt, i, ticks, shapetype, shapeInstanceId, shapeInstanceTime);
			}
			color = GetColor(particlesExt, i, ticks);
		}
		else if(pSimpleParticle){
			if( ! pSimpleParticle->parts.Alive( i ) ) {
				continue;
			}

			pos = ConvertMaxPointToAlembicPoint(pSimpleParticle->ParticlePosition(ticks, i));
			vel = ConvertMaxVectorToAlembicVector(pSimpleParticle->ParticleVelocity(ticks, i));
			//simple particles have no scale?
			//simple particles have no orientation?
			age = (float)GetSecondsFromTimeValue( pSimpleParticle->ParticleAge(ticks, i) );
			//simple particles have born index
			width = pSimpleParticle->ParticleSize(ticks, i);

         Abc::V3d min(pos.x - width/2, pos.y - width/2, pos.z - width/2);
         Abc::V3d max(pos.x + width/2, pos.y + width/2, pos.z + width/2);
         bbox.extendBy(min);
         bbox.extendBy(max);
		}

        {
        ESS_PROFILE_SCOPE("AlembicPoints::SAVE - numParticlesLoop - end loop save");

		//move everything from world space to local space
		pos = pos * nodeWorldTransInv;

		Abc::V4f vel4(vel.x, vel.y, vel.z, 0.0);
		vel4 = vel4 * nodeWorldTransInv;
		vel.setValue(vel4.x, vel4.y, vel4.z);

		//scale = scale * nodeWorldTransInv;
		//orientation = Abc::extractQuat(orientation.toMatrix44() * nodeWorldTransInv);
		//spin = Abc::extractQuat(spin.toMatrix44() * nodeWorldTransInv);

		bbox.extendBy( pos );




		positionVec.push_back( pos );
		velocityVec.push_back( vel );
		scaleVec.push_back( scale );
		widthVec.push_back( width );
		ageVec.push_back( age );
		idVec.push_back( id );
		orientationVec.push_back( orientation );
		angularVelocityVec.push_back( spin );

        shapeTypeVec.push_back( shapetype );
        shapeInstanceIDVec.push_back( shapeInstanceId );
        shapeTimeVec.push_back( shapeInstanceTime );
		colorVec.push_back( color );

		constantPos &= (pos == positionVec[0]);
		constantVel &= (vel == velocityVec[0]);
		constantScale &= (scale == scaleVec[0]);
		constantWidth &= (width == widthVec[0]);
		constantAge &= (age == ageVec[0]);
		constantOrientation &= (orientation == orientationVec[0]);
		constantAngularVel &= (spin == angularVelocityVec[0]);
		constantColor &= (color == colorVec[0]);

		// Set the archive bounding box
		// Positions for particles are already cnsider to be in world space
		if (mJob)
		{
			mJob->GetArchiveBBox().extendBy(pos);
		}

        }
	}

    }

  //  if (numParticles > 1)
  //  {
  //     ESS_PROFILE_SCOPE("AlembicPoints::Save - vectorResize");
  //      if (constantPos)        { positionVec.resize(1); }
  //      if (constantVel)        { velocityVec.resize(1); }
  //      if (constantScale)      { scaleVec.resize(1); }
  //      if (constantWidth)      { widthVec.resize(1); }
  //      if (constantAge)        { ageVec.resize(1); }
  //      if (constantOrientation){ orientationVec.resize(1); }
  //      if (constantAngularVel) { angularVelocityVec.resize(1); }
		//if (constantColor)		{ colorVec.resize(1); }
  //  }

    {
    ESS_PROFILE_SCOPE("AlembicPoints::Save - sample writing");
    // Store the information into our properties and points schema
    Abc::P3fArraySample positionSample( positionVec);
    Abc::P3fArraySample velocitySample(velocityVec);
    Abc::P3fArraySample scaleSample(scaleVec);
    Abc::FloatArraySample widthSample(widthVec);
    Abc::FloatArraySample ageSample(ageVec);
    Abc::FloatArraySample massSample(massVec);
    Abc::FloatArraySample shapeTimeSample(shapeTimeVec);
    Abc::UInt64ArraySample idSample(idVec);
    Abc::UInt16ArraySample shapeTypeSample(shapeTypeVec);
    Abc::UInt16ArraySample shapeInstanceIDSample(shapeInstanceIDVec);
    Abc::QuatfArraySample orientationSample(orientationVec);
    Abc::QuatfArraySample angularVelocitySample(angularVelocityVec);
    Abc::C4fArraySample colorSample(colorVec);  

    mScaleProperty.set(scaleSample);
    mAgeProperty.set(ageSample);
    mMassProperty.set(massSample);
    mShapeTimeProperty.set(shapeTimeSample);
    mShapeTypeProperty.set(shapeTypeSample);
    mShapeInstanceIDProperty.set(shapeInstanceIDSample);
    mOrientationProperty.set(orientationSample);
    mAngularVelocityProperty.set(angularVelocitySample);
    mColorProperty.set(colorSample);

    mPointsSample.setPositions(positionSample);
    mPointsSample.setVelocities(velocitySample);
    mPointsSample.setWidths(AbcG::OFloatGeomParam::Sample(widthSample, AbcG::kVertexScope));
    mPointsSample.setIds(idSample);
    mPointsSample.setSelfBounds(bbox);
	mPointsSchema.getChildBoundsProperty().set( bbox);

    mPointsSchema.set(mPointsSample);
    }

    mNumSamples++;

	//mInstanceNames.pop_back();

	if(bAutomaticInstancing){
		saveCurrentFrameMeshes();
	}

	if(bRenderStateForced){
		ipfSystem->SetRenderState(false);
	}

    if(bLastFrame){
       ESS_PROFILE_SCOPE("AlembicParticles::Save - save instance names property");

       std::vector<std::string> instanceNames(mNumShapeMeshes);

       for(faceVertexHashToShapeMap::iterator it = mShapeMeshCache.begin(); it != mShapeMeshCache.end(); it++){
          std::stringstream pathStream;
          pathStream << "/" << it->second.name<< "/" << it->second.name <<"Shape";
          instanceNames[it->second.nMeshInstanceId] = pathStream.str();
       }

	   //for some reason the .dims property is not written when there is exactly one entry if we don't push an empty string
	   //having an extra unreferenced entry seems to be harmless
	   instanceNames.push_back("");

       mInstanceNamesProperty.set(Abc::StringArraySample(instanceNames));
    }

    return true;
}
AlembicPoints::meshInfo AlembicPoints::CacheShapeMesh(Mesh* pShapeMesh, BOOL bNeedDelete, Matrix3 meshTM, int nMatId, int particleId, TimeValue ticks, ShapeType &type, Abc::uint16_t &instanceId, float &animationTime)
{
    ESS_PROFILE_FUNC();

	type = ShapeType_Instance;
	//animationTime = 0;
	
	//Mesh* pShapeMesh = pExt->GetParticleShapeByIndex(particleId);

	if(pShapeMesh->getNumFaces() == 0 || pShapeMesh->getNumVerts() == 0){
		meshInfo mi;
      return mi;
	}

    meshDigests digests;
    {
    ESS_PROFILE_SCOPE("AlembicPoints::CacheShapeMesh - compute hash");
	AbcU::MurmurHash3_x64_128( pShapeMesh->verts, pShapeMesh->numVerts * sizeof(Point3), sizeof(Point3), digests.Vertices.words );
	AbcU::MurmurHash3_x64_128( pShapeMesh->faces, pShapeMesh->numFaces * sizeof(Face), sizeof(Face), digests.Faces.words );
	if(mJob->GetOption("exportMaterialIds")){

		std::vector<MtlID> matIds;

		if(nMatId < 0){
			matIds.reserve(pShapeMesh->getNumFaces());
			for(int i=0; i<pShapeMesh->getNumFaces(); i++){
				matIds.push_back(pShapeMesh->getFaceMtlIndex(i));
			}
		}
		else{
			matIds.push_back(nMatId);
		}

		AbcU::MurmurHash3_x64_128( &matIds[0], matIds.size() * sizeof(MtlID), sizeof(MtlID), digests.MatIds.words );
	}
	if(mJob->GetOption("exportUVs")){
		//TODO...
	}
    }
	
	mTotalShapeMeshes++;

	meshInfo currShapeInfo;
	faceVertexHashToShapeMap::iterator it = mShapeMeshCache.find(digests);
	if( it != mShapeMeshCache.end() ){
		currShapeInfo = it->second;
	}
	else{
		meshInfo& mi = mShapeMeshCache[digests];
		mi.pMesh = pShapeMesh;

      mi.bbox = computeBoundingBox(pShapeMesh);

		mi.nMatId = nMatId;
		
		std::stringstream nameStream;
		nameStream<< EC_MCHAR_to_UTF8( mMaxNode->GetName() ) <<"_";
		nameStream<<"InstanceMesh"<<mNumShapeMeshes;
		mi.name=nameStream.str();

		mi.bNeedDelete = bNeedDelete;
		mi.meshTM = meshTM;
        mi.nMeshInstanceId = mNumShapeMeshes;

		currShapeInfo = mi;
		mNumShapeMeshes++;

		mMeshesToSaveForCurrentFrame.push_back(&mi);

		//ESS_LOG_WARNING("ticks: "<<ticks<<" particleId: "<<particleId);
		//ESS_LOG_WARNING("Adding shape with hash("<<vertexDigest.str()<<", "<<faceDigest.str()<<") \n auto assigned name "<<mi.name <<" efficiency:"<<(double)mNumShapeMeshes/mTotalShapeMeshes);
	}

    instanceId = currShapeInfo.nMeshInstanceId;

 //   {
 //      ESS_PROFILE_SCOPE("CacheShapeMesh push_back instance name");

	//std::string pathName("/");
	//pathName += currShapeInfo.name;

	//instanceId = FindInstanceName(pathName);
	//if (instanceId == USHRT_MAX){
	//	mInstanceNames.push_back(pathName);
	//	instanceId = (Abc::uint16_t)mInstanceNames.size()-1;
	//}

 //   }

    return currShapeInfo;
}