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; }
void BlobMesh::BuildMesh(TimeValue t) { //TODO: Implement the code to build the mesh representation of the object // using its parameter settings at the time passed. The plug-in should // use the data member mesh to store the built mesh. int numberOfNodes = pblock2->Count(pb_nodelist); float size, tension, renderCoarseness, viewCoarseness,coarseness; // Interval valid; pblock2->GetValue(pb_size,t,size,ivalid); pblock2->GetValue(pb_tension,t,tension,ivalid); if (tension < 0.011f) tension = 0.011f; if (tension > 1.0f) tension = 1.0f; pblock2->GetValue(pb_render,t,renderCoarseness,ivalid); pblock2->GetValue(pb_viewport,t,viewCoarseness,ivalid); BOOL autoCoarseness; pblock2->GetValue(pb_relativecoarseness,t,autoCoarseness,ivalid); BOOL largeDataSetOpt; pblock2->GetValue(pb_oldmetaballmethod,t,largeDataSetOpt,ivalid); BOOL useSoftSel; float minSize; pblock2->GetValue(pb_usesoftsel,t,useSoftSel,ivalid); pblock2->GetValue(pb_minsize,t,minSize,ivalid); if (inRender) coarseness = renderCoarseness; else coarseness = viewCoarseness; if (autoCoarseness) coarseness = size/coarseness; Tab<INode *> pfList; BOOL useAllPFEvents; pblock2->GetValue(pb_useallpf,0,useAllPFEvents,FOREVER); int pfCt = pblock2->Count(pb_pfeventlist); BOOL offInView; pblock2->GetValue(pb_offinview,0,offInView,FOREVER); for (int i = 0; i < pfCt; i++) { INode* node = NULL; pblock2->GetValue(pb_pfeventlist,0,node,FOREVER,i); if (node) pfList.Append(1,&node); } float thres = 0.6f; //need to get our tm if (selfNode == NULL) { MyEnumProc dep(true); DoEnumDependents(&dep); if (dep.Nodes.Count() > 0) selfNode = dep.Nodes[0]; } Matrix3 baseTM(1); if (selfNode != NULL) baseTM = Inverse(selfNode->GetObjectTM(t)); //loop throght the nodes if (!inRender) { if (offInView) numberOfNodes = 0; } std::vector<SphereData> data; data.reserve(500); for (int i = 0; i < numberOfNodes; i++) { INode* node = NULL; pblock2->GetValue(pb_nodelist,t,node,ivalid,i); if (node) { //get the nodes tm Matrix3 objectTM = node->GetObjectTM(t); Matrix3 toLocalSpace = objectTM*baseTM; ObjectState tos = node->EvalWorldState(t,TRUE); if (tos.obj->IsParticleSystem()) { SimpleParticle* pobj = NULL; IParticleObjectExt* epobj = NULL; pobj = static_cast<SimpleParticle*>( tos.obj->GetInterface(I_SIMPLEPARTICLEOBJ) ); if (pobj) { pobj->UpdateParticles(t, node); int count = pobj->parts.Count(); data.reserve(data.size() + count); float closest = 999999999.9f; SphereData d; for (int pid = 0; pid < count; pid++) { TimeValue age = pobj->ParticleAge(t,pid); TimeValue life = pobj->ParticleLife(t,pid); if (age != -1) { float psize = pobj->ParticleSize(t,pid); Point3 curval = pobj->parts.points[pid]; d.center = curval * baseTM; d.radius = psize; d.oradius = psize; d.rsquare = psize * psize; d.tover4 = tension * d.rsquare *d.rsquare ; data.push_back(d); } } } else { epobj = (IParticleObjectExt*) tos.obj->GetInterface(PARTICLEOBJECTEXT_INTERFACE); if (epobj) { epobj->UpdateParticles(node, t); int count = epobj->NumParticles(); data.reserve(data.size() + count); for (int pid = 0; pid < count; pid++) { TimeValue age = epobj->GetParticleAgeByIndex(pid); if (age!=-1) { INode *node = epobj->GetParticleGroup(pid); Point3 *curval = epobj->GetParticlePositionByIndex(pid); BOOL useParticle = TRUE; if (!useAllPFEvents) { useParticle = FALSE; for (int k = 0; k < pfList.Count(); k++) { if (node == pfList[k]) { useParticle = TRUE; k = pfList.Count(); } } } if ((curval) && (useParticle)) { float scale = epobj->GetParticleScaleByIndex(pid) ; float psize = scale; SphereData d; d.center = *curval*baseTM; d.radius = psize; d.oradius = psize; d.rsquare = psize * psize; d.tover4 = tension * d.rsquare *d.rsquare ; data.push_back(d); } } } } } } else if (tos.obj->IsShapeObject()) { PolyShape shape; ShapeObject *pathOb = (ShapeObject*)tos.obj; pathOb->MakePolyShape(t, shape); // first find out how many points there are: size_t num_points = 0; for (int i = 0; i < shape.numLines; i++) { PolyLine& line = shape.lines[i]; num_points += line.numPts; } data.reserve(data.size() + num_points); for (int i = 0; i < shape.numLines; i++) { PolyLine line = shape.lines[i]; for (int j = 0; j < line.numPts; j++) { SphereData d; float tsize = size; d.center = line.pts[j].p*toLocalSpace; d.radius = tsize; d.oradius = tsize; d.rsquare = tsize * tsize; d.tover4 = tension * d.rsquare *d.rsquare ; data.push_back(d); } } } else if (tos.obj->SuperClassID()==GEOMOBJECT_CLASS_ID) { SphereData d; BOOL converted = FALSE; TriObject *triObj = NULL; if(tos.obj->IsSubClassOf(triObjectClassID)) { triObj = (TriObject *)tos.obj; } // If it can convert to a TriObject, do it else if(tos.obj->CanConvertToType(triObjectClassID)) { triObj = (TriObject *)tos.obj->ConvertToType(t, triObjectClassID); converted = TRUE; } if (triObj != NULL) { Mesh* mesh = &triObj->GetMesh(); if (mesh) { int vcount = mesh->getNumVerts(); float *vsw = mesh->getVSelectionWeights (); BitArray vsel = mesh->VertSel(); data.reserve(data.size() + vcount); for (int j = 0; j < vcount; j++) { float tsize = size; if (useSoftSel) { tsize = 0.0f; if (vsw) { float v = 0.0f; if (vsel[j]) v = 1.0f; else { if (vsw) v = vsw[j]; } if (v == 0.0f) tsize = 0.0f; else { tsize = minSize + (size -minSize)*v; } } else { float v = 0.0f; if (vsel[j]) v = 1.0f; tsize = minSize + (size -minSize)*v; } } if (tsize != 0.0f) { d.center = mesh->getVert(j)*toLocalSpace; d.radius = tsize; d.oradius = tsize; d.rsquare = tsize * tsize; d.tover4 = tension * d.rsquare *d.rsquare ; data.push_back(d); } } } if (converted) triObj->DeleteThis(); } } else { SphereData d; d.center = Point3(0.0f,0.0f,0.0f)*toLocalSpace; d.radius = size; d.oradius = size; d.rsquare = size * size; d.tover4 = tension * d.rsquare *d.rsquare ; data.push_back(d); } } } if ((data.size() == 0) && (numberOfNodes==0)) { data.resize(1); data[0].center = Point3(0.0f,0.0f,0.0f); data[0].radius = size; data[0].oradius = size; data[0].rsquare = size * size; data[0].tover4 = tension * data[0].rsquare *data[0].rsquare ; } if (data.size() > 0) { int iRes = 1; if (!largeDataSetOpt) { MetaParticle oldBlob; iRes = oldBlob.CreatePodMetas(&data[0],(int)data.size(),&mesh,thres,coarseness); } else { MetaParticleFast blob; iRes = blob.CreatePodMetas(&data[0],(int)data.size(),&mesh,thres,coarseness); } // An out of memory error is the only reason iRes would be zero in either case if( (iRes == 0) && GetCOREInterface() ) GetCOREInterface()->DisplayTempPrompt(GetString(IDS_NOT_ENOUGH_MEM), 5000 ); } else { mesh.setNumFaces(0); mesh.setNumVerts(0); } mesh.InvalidateTopologyCache(); ivalid.Set(t,t); }