GA_Detail::IOStatus GEO_CobIOTranslator::fileSaveToFile( const GEO_Detail *geo, ostream &os, const char *fileName )
{
	((ofstream&)os).close();
	
	GU_DetailHandle handle;
	handle.allocateAndSet( (GU_Detail*)geo, false );
	
	FromHoudiniGeometryConverterPtr converter = FromHoudiniGeometryConverter::create( handle );
	if ( !converter )
	{
		return false;
	}
	
	ObjectPtr object = converter->convert();
	if ( !object )
	{
		return false;
	}
	
	try
	{
		WriterPtr writer = Writer::create( object, fileName );
		writer->write();
	}
	catch ( IECore::Exception e )
	{
		return false;
	}
	
	return true;
}
ObjectPtr FromHoudiniGroupConverter::doDetailConversion( const GU_Detail *geo, const CompoundObject *operands ) const
{
	GU_DetailHandle handle;
	handle.allocateAndSet( (GU_Detail*)geo, false );

	FromHoudiniGeometryConverterPtr converter = FromHoudiniGeometryConverter::create( handle );
	if ( !converter || converter->isInstanceOf( FromHoudiniGroupConverter::staticTypeId() ) )
	{
		/// \todo: if we're in PrimitiveGroup mode, but names exist, we return 0 when we should be returning a Group
		return 0;
	}
	
	// transfer the common parameter values
	CompoundParameter *parameters = converter->parameters();
	const CompoundParameter::ParameterMap &parameterMap = parameters->parameters();
	const CompoundObject::ObjectMap &values = operands->members();
	for ( CompoundObject::ObjectMap::const_iterator it = values.begin(); it != values.end(); ++it )
	{
		CompoundParameter::ParameterMap::const_iterator pIt = parameterMap.find( it->first );
		if ( pIt != parameterMap.end() && pIt->second->defaultValue()->typeId() == it->second->typeId() )
		{
			parameters->setParameterValue( it->first, it->second );
		}
	}
	
	return converter->convert();
}
예제 #3
0
GEO_Primitive *GU_CortexPrimitive::doConvert( GU_ConvertParms &parms )
{
	if ( !m_object )
	{
		return 0;
	}
	
#if UT_MAJOR_VERSION_INT >= 13

	GA_PrimCompat::TypeMask type = parms.toType();

#else

	GA_PrimCompat::TypeMask type = parms.toType;

#endif

	/// \todo: should the GEO_PrimTypeCompat be registered with the converters?
	if ( m_object->isInstanceOf( IECore::MeshPrimitiveTypeId ) && type == GEO_PrimTypeCompat::GEOPRIMPOLY )
	{
		GU_DetailHandle handle;
		handle.allocateAndSet( (GU_Detail*)getParent(), false );
		ToHoudiniPolygonsConverterPtr converter = new ToHoudiniPolygonsConverter( IECore::runTimeCast<const IECore::MeshPrimitive>( m_object.get() ) );
		if ( !converter->convert( handle ) )
		{
			return 0;
		}
	}
	
	/// \todo: support for CurvesPrimitive, PointsPrimitive, and any other existing converters
	
	return 0;
}
예제 #4
0
ConstObjectPtr HoudiniScene::readObject( double time ) const
{
	OBJ_Node *objNode = retrieveNode( true )->castToOBJNode();
	if ( !objNode )
	{
		return 0;
	}
	
	if ( objNode->getObjectType() == OBJ_GEOMETRY )
	{
		OP_Context context( time );
		GU_DetailHandle handle = objNode->getRenderGeometryHandle( context, false );
		
		if ( !m_splitter || ( handle != m_splitter->handle() ) )
		{
			m_splitter = new DetailSplitter( handle );
		}
		
		GU_DetailHandle newHandle = m_splitter->split( contentPathValue() );
		FromHoudiniGeometryConverterPtr converter = FromHoudiniGeometryConverter::create( ( newHandle.isNull() ) ? handle : newHandle );
		if ( !converter )
		{
			return 0;
		}
		
		return converter->convert();
	}
	
	/// \todo: need to account for cameras and lights
	
	return 0;
}
예제 #5
0
GU_DetailHandle LiveScene::contentHandle() const
{
	std::string name;
	SceneInterface::Path path;
	relativeContentPath( path );
	pathToString( path, name );
	
	GU_DetailHandle handle = m_splitter->split( name.c_str() );
	
	// we need to try again, in case the user didn't use a / prefix on the shape name
	if ( handle.isNull() && m_contentIndex == 1 && !path.empty() && path[0] != "" )
	{
		handle = m_splitter->split( &name.c_str()[1] );
	}
	
	return handle;
}
예제 #6
0
void SOP_CortexConverter::doConvert( const GU_DetailHandle &handle, const std::string &name, ResultType type, const std::string &attributeFilter, bool convertStandardAttributes )
{
	if ( handle.isNull() )
	{
		addError( SOP_MESSAGE, ( "Could not extract the geometry named " + name ).c_str() );
		return;
	}
	
	FromHoudiniGeometryConverterPtr fromConverter = FromHoudiniGeometryConverter::create( handle );
	if ( !fromConverter )
	{
		addError( SOP_MESSAGE, ( "Could not convert the geometry named " + name ).c_str() );
		return;
	}
	
	IECore::ObjectPtr result = fromConverter->convert();
	if ( !result )
	{
		addError( SOP_MESSAGE, ( "Could not find Cortex Object named " + name + " on input geometry" ).c_str() );
		return;
	}
	
	if ( IECore::ParameterisedProcedural *procedural = IECore::runTimeCast<IECore::ParameterisedProcedural>( result.get() ) )
	{
		IECore::CapturingRendererPtr renderer = new IECore::CapturingRenderer();
		// We are acquiring and releasing the GIL here to ensure that it is released when we render. This has
		// to be done because a procedural might jump between c++ and python a few times (i.e. if it spawns
		// subprocedurals that are implemented in python). In a normal call to cookMySop, this wouldn't be an
		// issue, but if cookMySop was called from HOM, hou.Node.cook appears to be holding onto the GIL.
		IECorePython::ScopedGILLock gilLock;
		{
			IECorePython::ScopedGILRelease gilRelease;
			{
				IECore::WorldBlock worldBlock( renderer );
				procedural->render( renderer.get() );
			}
		}
		
		result = boost::const_pointer_cast<IECore::Object>( IECore::runTimeCast<const IECore::Object>( renderer->world() ) );
	}
	
	ToHoudiniGeometryConverterPtr converter = ( type == Cortex ) ? new ToHoudiniCortexObjectConverter( result.get() ) : ToHoudiniGeometryConverter::create( result.get() );
	converter->nameParameter()->setTypedValue( name );
	converter->attributeFilterParameter()->setTypedValue( attributeFilter );
	converter->convertStandardAttributesParameter()->setTypedValue( convertStandardAttributes );
	
	if ( !converter->convert( myGdpHandle ) )
	{
		addError( SOP_MESSAGE, ( "Could not convert the Cortex Object named " + name + " to Houdini geometry" ).c_str() );
	}
}
GA_Detail::IOStatus GEO_CobIOTranslator::fileLoad( GEO_Detail *geo, UT_IStream &is, int ate_magic )
{
	((UT_IFStream&)is).close();
	
	ConstVisibleRenderablePtr renderable = 0;
	try
	{
		ReaderPtr reader = Reader::create( is.getLabel() );
		if ( reader->isInstanceOf( ParticleReaderTypeId ) )
		{
			reader->parameters()->parameter<IntParameter>( "realType" )->setNumericValue( ParticleReader::Float );
		}
		
		renderable = runTimeCast<VisibleRenderable>( reader->read() );
	}
	catch ( IECore::Exception e )
	{
		return false;
	}
	
	if ( !renderable )
	{
		return false;
	}
	
	ToHoudiniGeometryConverterPtr converter = ToHoudiniGeometryConverter::create( renderable );
	if ( !converter )
	{
		return false;
	}
	
	GU_DetailHandle handle;
	handle.allocateAndSet( (GU_Detail*)geo, false );
	
	return converter->convert( handle );
}
예제 #8
0
GA_Detail::IOStatus GEO_CobIOTranslator::fileLoad( GEO_Detail *geo, UT_IStream &is, int ate_magic )
{
	((UT_IFStream&)is).close();
	
	ConstObjectPtr object = 0;
	try
	{
		ReaderPtr reader = Reader::create( is.getLabel() );
		if ( !reader )
		{
			return false;
		}
		
		object = reader->read();
	}
	catch ( IECore::Exception e )
	{
		return false;
	}
	
	if ( !object )
	{
		return false;
	}
	
	ToHoudiniGeometryConverterPtr converter = ToHoudiniGeometryConverter::create( object.get() );
	if ( !converter )
	{
		return false;
	}
	
	GU_DetailHandle handle;
	handle.allocateAndSet( (GU_Detail*)geo, false );
	
	return converter->convert( handle );
}
예제 #9
0
void SOP_CortexConverter::doPassThrough( const GU_DetailHandle &handle, const std::string &name )
{
	if ( handle.isNull() )
	{
		addError( SOP_MESSAGE, ( "Could not pass through the geometry named " + name ).c_str() );
		return;
	}
	
	GU_DetailHandleAutoReadLock readHandle( handle );
	const GU_Detail *inputGeo = readHandle.getGdp();
	if ( !inputGeo )
	{
		addError( SOP_MESSAGE, ( "Could not pass through the geometry named " + name ).c_str() );
		return;
	}
	
	gdp->merge( *inputGeo );
}
void IECoreMantra::ProceduralPrimitive::addVisibleRenderable( VisibleRenderablePtr renderable )
{
	ToHoudiniGeometryConverterPtr converter = ToHoudiniGeometryConverter::create( renderable );
	if( !converter ) 
	{
		msg( Msg::Warning, "ProceduralPrimitive::addVisibleRenderable", "converter could not be found" );
		return;
	}
	GU_Detail *gdp = allocateGeometry();
	GU_DetailHandle handle;
	handle.allocateAndSet( (GU_Detail*)gdp, false );
	bool converted = converter->convert( handle );
	if ( !converted )
	{
		msg( Msg::Warning, "ProceduralPrimitive::addVisibleRenderable", "converter failed" );
		return;
	}
	/// \todo ToHoudiniGeometryConverter does not create a Houdini style uv attribute.
	/// We make one from s and t. This code should probably live in a converter or in an Op that
	/// remaps IECore conventions to common Houdini ones.
	MeshPrimitivePtr mesh = runTimeCast<MeshPrimitive> (renderable);
	if ( mesh )
	{
		gdp->addTextureAttribute( GA_ATTRIB_VERTEX );
		GEO_AttributeHandle auv = gdp->getAttribute( GA_ATTRIB_VERTEX, "uv" );
		GEO_AttributeHandle as = gdp->getAttribute( GA_ATTRIB_VERTEX, "s" );
		GEO_AttributeHandle at = gdp->getAttribute( GA_ATTRIB_VERTEX, "t" );
		if ( auv.isAttributeValid() && as.isAttributeValid() && at.isAttributeValid() )
		{
			GA_GBPrimitiveIterator it( *gdp );
			GA_Primitive *p = it.getPrimitive();
			while ( p )
			{
				for (int i = 0; i < p->getVertexCount(); ++i)
				{
					GA_Offset v = p->getVertexOffset(i);
					as.setVertex(v);
					at.setVertex(v);
					auv.setVertex(v);
					auv.setF( as.getF(0), 0 );
					auv.setF( ((at.getF(0) * -1.0f) + 1.0f), 1 ); // wat, t arrives upside down for some reason.
					auv.setF( 0.0f, 2 );
				}
				++it;
				p = it.getPrimitive();
			}
		}
	}

	if ( m_renderer->m_motionType == RendererImplementation::Geometry )
	{
		msg(Msg::Debug, "IECoreMantra::ProceduralPrimitive::addVisibleRenderable", "MotionBlur:Geometry" );
		if ( !m_renderer->m_motionTimes.empty() )
		{
			if ( (size_t)m_renderer->m_motionSize == m_renderer->m_motionTimes.size() )
			{
				openGeometryObject();
			}
			addGeometry(gdp, m_renderer->m_motionTimes.front());
			m_renderer->m_motionTimes.pop_front();
			if ( m_renderer->m_motionTimes.empty() )
			{
				applySettings();
				closeObject();
			}
		}
	}
	else if ( m_renderer->m_motionType == RendererImplementation::ConcatTransform ||
			  m_renderer->m_motionType == RendererImplementation::SetTransform )
	{
		// It isn't clear that this will give correct results. 
		// ConcatTransform may need to interpolate transform snapshots.
		msg(Msg::Debug, "IECoreMantra::ProceduralPrimitive::addVisibleRenderable", "MotionBlur:Transform" );
		openGeometryObject();
			addGeometry(gdp, 0.0f);
			while ( !m_renderer->m_motionTimes.empty() )
			{
				setPreTransform( convert< UT_Matrix4T<float> >(m_renderer->m_motionTransforms.front()),
								 m_renderer->m_motionTimes.front() );
				m_renderer->m_motionTimes.pop_front();
				m_renderer->m_motionTransforms.pop_front();
			}
			applySettings();
		closeObject();
		m_renderer->m_motionType = RendererImplementation::Unknown;
	}
	else if ( m_renderer->m_motionType == RendererImplementation::Velocity )
	{
		msg(Msg::Debug, "IECoreMantra::ProceduralPrimitive::addVisibleRenderable", "MotionBlur:Velocity" );
		openGeometryObject();
			addGeometry(gdp, 0.0f);
			addVelocityBlurGeometry(gdp, m_preBlur, m_postBlur);
			applySettings();
		closeObject();
		m_renderer->m_motionType = RendererImplementation::Unknown;
	}
	else
	{
		msg(Msg::Debug, "IECoreMantra::ProceduralPrimitive::addVisibleRenderable", "MotionBlur:None" );
		openGeometryObject();
			addGeometry( gdp, 0.0f );
			setPreTransform( convert< UT_Matrix4T<float> >(m_renderer->m_transformStack.top()), 0.0f);
			applySettings();
		closeObject();
	}
}
bool ToHoudiniGroupConverter::doConversion( const VisibleRenderable *renderable, GU_Detail *geo ) const
{
    const Group *group = IECore::runTimeCast<const Group>( renderable );
    if ( !group )
    {
        return false;
    }

    Imath::M44f transform = ( runTimeCast<const M44fData>( m_transformParameter->getValue() ) )->readable();
    const Transform *groupTransform = group->getTransform();
    if ( groupTransform )
    {
        transform = transform * groupTransform->transform();
    }

    TransformOpPtr transformOp = new TransformOp();
    M44fDataPtr transformData = new M44fData( transform );
    transformOp->matrixParameter()->setValue( transformData );

    std::string groupName = nameParameter()->getTypedValue();
    if ( groupName == "" )
    {
        // backwards compatibility with older data
        if ( const StringData *groupNameData = group->blindData()->member<StringData>( "name" ) )
        {
            groupName = groupNameData->readable();
        }
    }

    const std::string &attribFilter = attributeFilterParameter()->getTypedValue();
    bool convertStandardAttributes = convertStandardAttributesParameter()->getTypedValue();

    size_t i = 0;
    const Group::ChildContainer &children = group->children();
    for ( Group::ChildContainer::const_iterator it=children.begin(); it != children.end(); ++it, ++i )
    {
        ConstVisibleRenderablePtr child = *it;

        ConstPrimitivePtr primitive = runTimeCast<const Primitive>( child );
        if ( primitive )
        {
            transformOp->inputParameter()->setValue( constPointerCast<Primitive>( primitive ) );
            child = staticPointerCast<VisibleRenderable>( transformOp->operate() );
        }

        ToHoudiniGeometryConverterPtr converter = ToHoudiniGeometryConverter::create( child );
        if ( !converter )
        {
            continue;
        }

        std::string name = groupName;
        if ( const StringData *childNameData = child->blindData()->member<StringData>( "name" ) )
        {
            const std::string &childName = childNameData->readable();
            if ( childName != "" )
            {
                if ( groupName != "" )
                {
                    name += "/";
                }

                name += childName;
            }
        }

        converter->nameParameter()->setTypedValue( name );
        converter->attributeFilterParameter()->setTypedValue( attribFilter );
        converter->convertStandardAttributesParameter()->setTypedValue( convertStandardAttributes );

        ToHoudiniGroupConverter *groupConverter = runTimeCast<ToHoudiniGroupConverter>( converter );
        if ( groupConverter )
        {
            groupConverter->transformParameter()->setValue( transformData );
        }

        GU_DetailHandle handle;
        handle.allocateAndSet( geo, false );

        if ( !converter->convert( handle ) )
        {
            continue;
        }
    }

    return true;
}
예제 #12
0
void HoudiniScene::readTags( NameList &tags, bool includeChildren ) const
{
	tags.clear();
	
	const OP_Node *node = retrieveNode();
	if ( !node )
	{
		return;
	}
	
	// add user supplied tags if we're not inside a SOP
	if ( !m_contentIndex && node->hasParm( pTags.getToken() ) )
	{
		UT_String parmTagStr;
		node->evalString( parmTagStr, pTags.getToken(), 0, 0 );
		if ( !parmTagStr.equal( UT_String::getEmptyString() ) )
		{
			UT_WorkArgs tokens;
			parmTagStr.tokenize( tokens, " " );
			for ( int i = 0; i < tokens.getArgc(); ++i )
			{
				tags.push_back( tokens[i] );
			}
		}
	}
	
	// add tags from the registered tag readers
	std::vector<CustomTagReader> &tagReaders = customTagReaders();
	for ( std::vector<CustomTagReader>::const_iterator it = tagReaders.begin(); it != tagReaders.end(); ++it )
	{
		NameList values;
		it->m_read( node, values, includeChildren );
		tags.insert( tags.end(), values.begin(), values.end() );
	}
	
	// add tags based on primitive groups
	OBJ_Node *contentNode = retrieveNode( true )->castToOBJNode();
	if ( contentNode && contentNode->getObjectType() == OBJ_GEOMETRY && m_splitter )
	{
		GU_DetailHandle newHandle = m_splitter->split( contentPathValue() );
		if ( !newHandle.isNull() )
		{
			GU_DetailHandleAutoReadLock readHandle( newHandle );
			if ( const GU_Detail *geo = readHandle.getGdp() )
			{
				GA_Range prims = geo->getPrimitiveRange();
				for ( GA_GroupTable::iterator<GA_ElementGroup> it=geo->primitiveGroups().beginTraverse(); !it.atEnd(); ++it )
				{
					GA_PrimitiveGroup *group = static_cast<GA_PrimitiveGroup*>( it.group() );
					if ( group->getInternal() || group->isEmpty() )
					{
						continue;
					}
					
					const UT_String &groupName = group->getName();
					if ( groupName.startsWith( tagGroupPrefix ) && group->containsAny( prims ) )
					{
						UT_String tag;
						groupName.substr( tag, tagGroupPrefix.length() );
						tag.substitute( "_", ":" );
						tags.push_back( tag.buffer() );
					}
				}
			}
		}
	}
}
예제 #13
0
bool HoudiniScene::hasTag( const Name &name, bool includeChildren ) const
{
	const OP_Node *node = retrieveNode();
	if ( !node )
	{
		return false;
	}
	
	// check for user supplied tags if we're not inside a SOP
	if ( !m_contentIndex && node->hasParm( pTags.getToken() ) )
	{
		UT_String parmTags;
		node->evalString( parmTags, pTags.getToken(), 0, 0 );
		if ( UT_String( name.c_str() ).multiMatch( parmTags ) )
		{
			return true;
		}
	}
	
	// check with the registered tag readers
	std::vector<CustomTagReader> &tagReaders = customTagReaders();
	for ( std::vector<CustomTagReader>::const_iterator it = tagReaders.begin(); it != tagReaders.end(); ++it )
	{
		if ( it->m_has( node, name ) )
		{
			return true;
		}
	}
	
	// check tags based on primitive groups
	OBJ_Node *contentNode = retrieveNode( true )->castToOBJNode();
	if ( contentNode && contentNode->getObjectType() == OBJ_GEOMETRY && m_splitter )
	{
		GU_DetailHandle newHandle = m_splitter->split( contentPathValue() );
		if ( !newHandle.isNull() )
		{
			GU_DetailHandleAutoReadLock readHandle( newHandle );
			if ( const GU_Detail *geo = readHandle.getGdp() )
			{
				GA_Range prims = geo->getPrimitiveRange();
				for ( GA_GroupTable::iterator<GA_ElementGroup> it=geo->primitiveGroups().beginTraverse(); !it.atEnd(); ++it )
				{
					GA_PrimitiveGroup *group = static_cast<GA_PrimitiveGroup*>( it.group() );
					if ( group->getInternal() || group->isEmpty() )
					{
						continue;
					}
					
					const UT_String &groupName = group->getName();
					if ( groupName.startsWith( tagGroupPrefix ) && group->containsAny( prims ) )
					{
						UT_String tag;
						groupName.substr( tag, tagGroupPrefix.length() );
						tag.substitute( "_", ":" );
						if ( tag.equal( name.c_str() ) )
						{
							return true;
						}
					}
				}
			}
		}
	}
	
	return false;
}
ObjectPtr FromHoudiniGroupConverter::doConversion( ConstCompoundObjectPtr operands ) const
{
	GroupPtr result = new Group();
	
	if ( operands->member<const IntData>( "groupingMode" )->readable() == NameAttribute )
	{
		DetailSplitterPtr splitter = new DetailSplitter( handle() );
		std::vector<std::string> children;
		splitter->values( children );
		
		if ( children.empty() )
		{
			doUnnamedConversion( GU_DetailHandleAutoReadLock( handle() ).getGdp(), result, operands );
			return result;
		}
		
		for ( std::vector<std::string>::iterator it = children.begin(); it != children.end(); ++it )
		{
			const std::string &name = *it;
			GU_DetailHandle childHandle = splitter->split( name );
			if ( childHandle.isNull() )
			{
				continue;
			}
			
			GU_DetailHandleAutoReadLock readHandle( childHandle );
			const GU_Detail *childGeo = readHandle.getGdp();
			ObjectPtr child = doDetailConversion( childGeo, operands );
			if ( !child )
			{
				// this happens when mismatched primitives share the same name
				doUnnamedConversion( childGeo, result, operands, name );
			}
			else if ( VisibleRenderablePtr renderable = IECore::runTimeCast<VisibleRenderable>( child ) )
			{
				if ( name != "" )
				{
					renderable->blindData()->member<StringData>( "name", false, true )->writable() = name;
				}
				
				result->addChild( renderable );
			}
		}
	}
	else
	{
		GU_DetailHandleAutoReadLock readHandle( handle() );
		const GU_Detail *geo = readHandle.getGdp();
		if ( !geo )
		{
			return 0;
		}

		size_t numResultPrims = 0;
		size_t numOrigPrims = geo->getNumPrimitives();

		for ( GA_GroupTable::iterator<GA_ElementGroup> it=geo->primitiveGroups().beginTraverse(); !it.atEnd(); ++it )
		{
			GA_PrimitiveGroup *group = static_cast<GA_PrimitiveGroup*>( it.group() );
			if ( group->getInternal() || group->isEmpty() )
			{
				continue;
			}

			VisibleRenderablePtr renderable = 0;
			numResultPrims += doGroupConversion( geo, group, renderable, operands );
			if( !renderable )
			{
				continue;
			}
			
			renderable->blindData()->member<StringData>( "name", false, true )->writable() = group->getName().toStdString();
			result->addChild( renderable );
		}

		if ( numOrigPrims == numResultPrims )
		{
			return result;
		}

		GU_Detail ungroupedGeo( (GU_Detail*)geo );
		GA_PrimitiveGroup *ungrouped = static_cast<GA_PrimitiveGroup*>( ungroupedGeo.createInternalElementGroup( GA_ATTRIB_PRIMITIVE, "FromHoudiniGroupConverter__ungroupedPrimitives" ) );
		for ( GA_GroupTable::iterator<GA_ElementGroup> it=geo->primitiveGroups().beginTraverse(); !it.atEnd(); ++it )
		{
			*ungrouped |= *static_cast<GA_PrimitiveGroup*>( it.group() );
		}
		ungrouped->toggleRange( ungroupedGeo.getPrimitiveRange() );

		if ( ungrouped->isEmpty() )
		{
			return result;
		}

		VisibleRenderablePtr renderable = 0;
		doGroupConversion( &ungroupedGeo, ungrouped, renderable, operands );
		if ( renderable )
		{
			result->addChild( renderable );
		}
	}
	
	return result;
}
예제 #15
0
//Do the interpolation calculations
bool SIM_SnowSolver::solveGasSubclass(SIM_Engine &engine, SIM_Object *obj, SIM_Time time, SIM_Time framerate){

	/// STEP #0: Retrieve all data objects from Houdini

	//Scalar params
	freal particle_mass = getPMass();
	freal YOUNGS_MODULUS = getYoungsModulus();
	freal POISSONS_RATIO = getPoissonsRatio();
	freal CRIT_COMPRESS = getCritComp();
	freal CRIT_STRETCH = getCritStretch();
	freal FLIP_PERCENT = getFlipPercent();
	freal HARDENING = getHardening();
	freal CFL = getCfl();
	freal COF = getCof();
	freal division_size = getDivSize();
	freal max_vel = getMaxVel();
	//Vector params
	vector3 GRAVITY = getGravity();
	vector3 bbox_min_limit = getBboxMin();
	vector3 bbox_max_limit = getBboxMax();

	//Particle params
	UT_String s_p, s_vol, s_den, s_vel, s_fe, s_fp;
	getParticles(s_p);
	getPVol(s_vol);
	getPD(s_den);
	getPVel(s_vel);
	getPFe(s_fe);
	getPFp(s_fp);

	SIM_Geometry* geometry = (SIM_Geometry*) obj->getNamedSubData(s_p);
	if (!geometry) return true;
	
	//Get particle data
	//Do we use the attribute name???
	// GU_DetailHandle gdh = geometry->getGeometry().getWriteableCopy();
	GU_DetailHandle gdh = geometry->getOwnGeometry();
	const GU_Detail* gdp_in = gdh.readLock(); // Must unlock later
	GU_Detail* gdp_out = gdh.writeLock();

	GA_RWAttributeRef p_ref_position = gdp_out->findPointAttribute("P");
	GA_RWHandleT<vector3> p_position(p_ref_position.getAttribute());

	GA_RWAttributeRef p_ref_volume = gdp_out->findPointAttribute(s_vol);
	GA_RWHandleT<freal> p_volume(p_ref_volume.getAttribute());

	GA_RWAttributeRef p_ref_density = gdp_out->findPointAttribute(s_den);
	GA_RWHandleT<freal> p_density(p_ref_density.getAttribute());

	GA_RWAttributeRef p_ref_vel = gdp_out->findPointAttribute(s_vel);
	GA_RWHandleT<vector3> p_vel(p_ref_vel.getAttribute());

	GA_RWAttributeRef p_ref_Fe = gdp_out->findPointAttribute(s_fe);
	GA_RWHandleT<matrix3> p_Fe(p_ref_Fe.getAttribute());

	GA_RWAttributeRef p_ref_Fp = gdp_out->findPointAttribute(s_fp);
	GA_RWHandleT<matrix3> p_Fp(p_ref_Fp.getAttribute());

	//EVALUATE PARAMETERS
	freal mu = YOUNGS_MODULUS/(2+2*POISSONS_RATIO);
	freal lambda = YOUNGS_MODULUS*POISSONS_RATIO/((1+POISSONS_RATIO)*(1-2*POISSONS_RATIO));

	//Get grid data
	SIM_ScalarField *g_mass_field;
	SIM_DataArray g_mass_data;
	getMatchingData(g_mass_data, obj, MPM_G_MASS);	
	g_mass_field = SIM_DATA_CAST(g_mass_data(0), SIM_ScalarField);

	SIM_VectorField *g_nvel_field;
	SIM_DataArray g_nvel_data;
	getMatchingData(g_nvel_data, obj, MPM_G_NVEL);
	g_nvel_field = SIM_DATA_CAST(g_nvel_data(0), SIM_VectorField);

	SIM_VectorField *g_ovel_field;
	SIM_DataArray g_ovel_data;
	getMatchingData(g_ovel_data, obj, MPM_G_OVEL);
	g_ovel_field = SIM_DATA_CAST(g_ovel_data(0), SIM_VectorField);

	SIM_ScalarField *g_active_field;
	SIM_DataArray g_active_data;
	getMatchingData(g_active_data, obj, MPM_G_ACTIVE);	
	g_active_field = SIM_DATA_CAST(g_active_data(0), SIM_ScalarField);

	SIM_ScalarField *g_density_field;
	SIM_DataArray g_density_data;
	getMatchingData(g_density_data, obj, MPM_G_DENSITY);	
	g_density_field = SIM_DATA_CAST(g_density_data(0), SIM_ScalarField);

	SIM_ScalarField *g_col_field;
	SIM_DataArray g_col_data;
	getMatchingData(g_col_data, obj, MPM_G_COL);	
	g_col_field = SIM_DATA_CAST(g_col_data(0), SIM_ScalarField);

	SIM_VectorField *g_colVel_field;
	SIM_DataArray g_colVel_data;
	getMatchingData(g_colVel_data, obj, MPM_G_COLVEL);	
	g_colVel_field = SIM_DATA_CAST(g_colVel_data(0), SIM_VectorField);

	SIM_VectorField *g_extForce_field;
	SIM_DataArray g_extForce_data;
	getMatchingData(g_extForce_data, obj, MPM_G_EXTFORCE);	
	g_extForce_field = SIM_DATA_CAST(g_extForce_data(0), SIM_VectorField);
	
	UT_VoxelArrayF
		*g_mass = g_mass_field->getField()->fieldNC(),
		*g_nvelX = g_nvel_field->getField(0)->fieldNC(),
		*g_nvelY = g_nvel_field->getField(1)->fieldNC(),
		*g_nvelZ = g_nvel_field->getField(2)->fieldNC(),
		*g_ovelX = g_ovel_field->getField(0)->fieldNC(),
		*g_ovelY = g_ovel_field->getField(1)->fieldNC(),
		*g_ovelZ = g_ovel_field->getField(2)->fieldNC(),
		*g_colVelX = g_colVel_field->getField(0)->fieldNC(),
		*g_colVelY = g_colVel_field->getField(1)->fieldNC(),
		*g_colVelZ = g_colVel_field->getField(2)->fieldNC(),
		*g_extForceX = g_extForce_field->getField(0)->fieldNC(),
		*g_extForceY = g_extForce_field->getField(1)->fieldNC(),
		*g_extForceZ = g_extForce_field->getField(2)->fieldNC(),
		*g_col = g_col_field->getField()->fieldNC(),
		*g_active = g_active_field->getField()->fieldNC();

	int point_count = gdp_out->getPointRange().getEntries();
	std::vector<boost::array<freal,64> > p_w(point_count);
	std::vector<boost::array<vector3,64> > p_wgh(point_count);

	//Get world-to-grid conversion ratios
	//Particle's grid position can be found via (pos - grid_origin)/voxel_dims
	vector3
		voxel_dims = g_mass_field->getVoxelSize(),
		grid_origin = g_mass_field->getOrig(),
		grid_divs = g_mass_field->getDivisions();
	//Houdini uses voxel centers for grid nodes, rather than grid corners
	grid_origin += voxel_dims/2.0;
	freal voxelArea = voxel_dims[0]*voxel_dims[1]*voxel_dims[2];
	
	/*
	//Reset grid
	for(int iX=0; iX < grid_divs[0]; iX++){
		for(int iY=0; iY < grid_divs[1]; iY++){
			for(int iZ=0; iZ < grid_divs[2]; iZ++){
				g_mass->setValue(iX,iY,iZ,0);
				g_active->setValue(iX,iY,iZ,0);
				g_ovelX->setValue(iX,iY,iZ,0);
				g_ovelY->setValue(iX,iY,iZ,0);
				g_ovelZ->setValue(iX,iY,iZ,0);
				g_nvelX->setValue(iX,iY,iZ,0);
				g_nvelY->setValue(iX,iY,iZ,0);
				g_nvelZ->setValue(iX,iY,iZ,0);
			}
		}
	}
	*/

	/// STEP #1: Transfer mass to grid

	if (p_position.isValid()){

		//Iterate through particles
		for (GA_Iterator it(gdp_out->getPointRange()); !it.atEnd(); it.advance()){
			int pid = it.getOffset();
							
			//Get grid position
			vector3 gpos = (p_position.get(pid) - grid_origin)/voxel_dims;
			int p_gridx = (int) gpos[0], p_gridy = (int) gpos[1], p_gridz = (int) gpos[2];
			//g_mass_field->posToIndex(p_position.get(pid),p_gridx,p_gridy,p_gridz);
			freal particle_density = p_density.get(pid);
			//Compute weights and transfer mass
			for (int idx=0, z=p_gridz-1, z_end=z+3; z<=z_end; z++){
				//Z-dimension interpolation
				freal z_pos = gpos[2]-z,
					wz = SIM_SnowSolver::bspline(z_pos),
					dz = SIM_SnowSolver::bsplineSlope(z_pos);
				for (int y=p_gridy-1, y_end=y+3; y<=y_end; y++){
					//Y-dimension interpolation
					freal y_pos = gpos[1]-y,
						wy = SIM_SnowSolver::bspline(y_pos),
						dy = SIM_SnowSolver::bsplineSlope(y_pos);
					for (int x=p_gridx-1, x_end=x+3; x<=x_end; x++, idx++){
						//X-dimension interpolation
						freal x_pos = gpos[0]-x,
							wx = SIM_SnowSolver::bspline(x_pos),
							dx = SIM_SnowSolver::bsplineSlope(x_pos);
						
						//Final weight is dyadic product of weights in each dimension
						freal weight = wx*wy*wz;
						p_w[pid-1][idx] = weight;

						//Weight gradient is a vector of partial derivatives
						p_wgh[pid-1][idx] = vector3(dx*wy*wz, wx*dy*wz, wx*wy*dz)/voxel_dims;

						//Interpolate mass
						freal node_mass = g_mass->getValue(x,y,z);
						node_mass += weight*particle_mass;
						g_mass->setValue(x,y,z,node_mass);
					}
				}
			}
		}
	}
	
	/// STEP #2: First timestep only - Estimate particle volumes using grid mass

	/*
	if (time == 0.0){
		//Iterate through particles
		for (GA_Iterator it(gdp_out->getPointRange()); !it.atEnd(); it.advance()){
			int pid = it.getOffset();
			freal density = 0;

			//Get grid position
			int p_gridx = 0, p_gridy = 0, p_gridz = 0;
			vector3 gpos = (p_position.get(pid) - grid_origin)/voxel_dims;
			int p_gridx = (int) gpos[0], p_gridy = (int) gpos[1], p_gridz = (int) gpos[2];
			//g_nvel_field->posToIndex(0,p_position.get(pid),p_gridx,p_gridy,p_gridz);
			//Transfer grid density (within radius) to particles
			for (int idx=0, z=p_gridz-1, z_end=z+3; z<=z_end; z++){
				for (int y=p_gridy-1, y_end=y+3; y<=y_end; y++){
					for (int x=p_gridx-1, x_end=x+3; x<=x_end; x++, idx++){
						freal w = p_w[pid-1][idx];
						if (w > EPSILON){
							//Transfer density
							density += w * g_mass->getValue(x,y,z);
						}
					}
				}
			}
			
			density /= voxelArea;
			p_density.set(pid,density);
			p_volume.set(pid, particle_mass/density);
		}
	}
	//*/
	
	/// STEP #3: Transfer velocity to grid

	//This must happen after transferring mass, to conserve momentum
	//Iterate through particles and transfer
	for (GA_Iterator it(gdp_in->getPointRange()); !it.atEnd(); it.advance()){
		int pid = it.getOffset();
		vector3 vel_fac = p_vel.get(pid)*particle_mass;

		//Get grid position
		vector3 gpos = (p_position.get(pid) - grid_origin)/voxel_dims;
		int p_gridx = (int) gpos[0], p_gridy = (int) gpos[1], p_gridz = (int) gpos[2];
		//g_nvel_field->posToIndex(0,p_position.get(pid),p_gridx,p_gridy,p_gridz);

		//Transfer to grid nodes within radius
		for (int idx=0, z=p_gridz-1, z_end=z+3; z<=z_end; z++){
			for (int y=p_gridy-1, y_end=y+3; y<=y_end; y++){
				for (int x=p_gridx-1, x_end=x+3; x<=x_end; x++, idx++){
					freal w = p_w[pid-1][idx];
					if (w > EPSILON){
						freal nodex_vel = g_ovelX->getValue(x,y,z) + vel_fac[0]*w;
						freal nodey_vel = g_ovelY->getValue(x,y,z) + vel_fac[1]*w;
						freal nodez_vel = g_ovelZ->getValue(x,y,z) + vel_fac[2]*w;
						g_ovelX->setValue(x,y,z,nodex_vel);
						g_ovelY->setValue(x,y,z,nodey_vel);
						g_ovelZ->setValue(x,y,z,nodez_vel);			
						g_active->setValue(x,y,z,1.0);			
					}
				}
			}
		}
	}
	//Division is slow (maybe?); we only want to do divide by mass once, for each active node
	for(int iX=0; iX < grid_divs[0]; iX++){
		for(int iY=0; iY < grid_divs[1]; iY++){
			for(int iZ=0; iZ < grid_divs[2]; iZ++){
				//Only check nodes that have mass
				if (g_active->getValue(iX,iY,iZ)){
					freal node_mass = 1/(g_mass->getValue(iX,iY,iZ));
					g_ovelX->setValue(iX,iY,iZ,(g_ovelX->getValue(iX,iY,iZ)*node_mass));
					g_ovelY->setValue(iX,iY,iZ,(g_ovelY->getValue(iX,iY,iZ)*node_mass));
					g_ovelZ->setValue(iX,iY,iZ,(g_ovelZ->getValue(iX,iY,iZ)*node_mass));
				}
			}
		}
	}
	
	/// STEP #4: Compute new grid velocities

	//Temporary variables for plasticity and force calculation
	//We need one set of variables for each thread that will be running
	eigen_matrix3 def_elastic, def_plastic, energy, svd_u, svd_v;
	Eigen::JacobiSVD<eigen_matrix3, Eigen::NoQRPreconditioner> svd;
	eigen_vector3 svd_e;
	matrix3  HDK_def_plastic, HDK_def_elastic, HDK_energy;
	freal* data_dp = HDK_def_plastic.data();
	freal* data_de = HDK_def_elastic.data();
	freal* data_energy = HDK_energy.data();
	//Map Eigen matrices to HDK matrices
	Eigen::Map<eigen_matrix3> data_dp_map(data_dp);
	Eigen::Map<eigen_matrix3> data_de_map(data_de);
	Eigen::Map<eigen_matrix3> data_energy_map(data_energy);	

	//Compute force at each particle and transfer to Eulerian grid
	//We use "nvel" to hold the grid force, since that variable is not in use
	for (GA_Iterator it(gdp_in->getPointRange()); !it.atEnd(); it.advance()){
		int pid = it.getOffset();
		
		//Apply plasticity to deformation gradient, before computing forces
		//We need to use the Eigen lib to do the SVD; transfer houdini matrices to Eigen matrices
		HDK_def_plastic = p_Fp.get(pid);
		HDK_def_elastic = p_Fe.get(pid);
		def_plastic = Eigen::Map<eigen_matrix3>(data_dp);
		def_elastic = Eigen::Map<eigen_matrix3>(data_de);
		
		//Compute singular value decomposition (uev*)
		svd.compute(def_elastic, Eigen::ComputeFullV | Eigen::ComputeFullU);
		svd_e = svd.singularValues();
		svd_u = svd.matrixU();
		svd_v = svd.matrixV();
		//Clamp singular values
		for (int i=0; i<3; i++){
			if (svd_e[i] < CRIT_COMPRESS) 
				svd_e[i] = CRIT_COMPRESS;
			else if (svd_e[i] > CRIT_STRETCH)
				svd_e[i] = CRIT_STRETCH;
		}
		//Put SVD back together for new elastic and plastic gradients
		def_plastic = svd_v * svd_e.asDiagonal().inverse() * svd_u.transpose() * def_elastic * def_plastic;
		svd_v.transposeInPlace();
		def_elastic = svd_u * svd_e.asDiagonal() * svd_v;
		
		//Now compute the energy partial derivative (which we use to get force at each grid node)
		energy = 2*mu*(def_elastic - svd_u*svd_v)*def_elastic.transpose();
		//Je is the determinant of def_elastic (equivalent to svd_e.prod())
		freal Je = svd_e.prod(),
			contour = lambda*Je*(Je-1),
			jp = def_plastic.determinant(),
			particle_vol = p_volume.get(pid);
		for (int i=0; i<3; i++)
			energy(i,i) += contour;
		energy *=  particle_vol * exp(HARDENING*(1-jp));
		
		//Transfer Eigen matrices back to HDK
		data_dp_map = def_plastic;
		data_de_map = def_elastic;
		data_energy_map = energy;
		
		p_Fp.set(pid,HDK_def_plastic);
		p_Fe.set(pid,HDK_def_elastic);
		
		//Transfer energy to surrounding grid nodes
		vector3 gpos = (p_position.get(pid) - grid_origin)/voxel_dims;
		int p_gridx = (int) gpos[0], p_gridy = (int) gpos[1], p_gridz = (int) gpos[2];
		for (int idx=0, z=p_gridz-1, z_end=z+3; z<=z_end; z++){
			for (int y=p_gridy-1, y_end=y+3; y<=y_end; y++){
				for (int x=p_gridx-1, x_end=x+3; x<=x_end; x++, idx++){
					freal w = p_w[pid-1][idx];
					if (w > EPSILON){
						vector3 ngrad = p_wgh[pid-1][idx];
						g_nvelX->setValue(x,y,z,g_nvelX->getValue(x,y,z) + ngrad.dot(HDK_energy[0]));
						g_nvelY->setValue(x,y,z,g_nvelY->getValue(x,y,z) + ngrad.dot(HDK_energy[1]));
						g_nvelZ->setValue(x,y,z,g_nvelZ->getValue(x,y,z) + ngrad.dot(HDK_energy[2]));						
					}
				}
			}
		}
	}

	//Use new forces to solve for new velocities
	for(int iX=0; iX < grid_divs[0]; iX++){
		for(int iY=0; iY < grid_divs[1]; iY++){
			for(int iZ=0; iZ < grid_divs[2]; iZ++){
				//Only compute for active nodes
				if (g_active->getValue(iX,iY,iZ)){
					freal nodex_ovel = g_ovelX->getValue(iX,iY,iZ);
					freal nodey_ovel = g_ovelY->getValue(iX,iY,iZ);
					freal nodez_ovel = g_ovelZ->getValue(iX,iY,iZ);
					freal forcex = g_nvelX->getValue(iX,iY,iZ);
					freal forcey = g_nvelY->getValue(iX,iY,iZ);
					freal forcez = g_nvelZ->getValue(iX,iY,iZ);
					freal node_mass = 1/(g_mass->getValue(iX,iY,iZ));
					freal ext_forceX = GRAVITY[0] + g_extForceX->getValue(iX,iY,iZ);
					freal ext_forceY = GRAVITY[1] + g_extForceY->getValue(iX,iY,iZ);
					freal ext_forceZ = GRAVITY[2] + g_extForceZ->getValue(iX,iY,iZ);
					nodex_ovel += framerate*(ext_forceX - forcex*node_mass);
					nodey_ovel += framerate*(ext_forceY - forcey*node_mass);
					nodez_ovel += framerate*(ext_forceZ - forcez*node_mass);
					
					//Limit velocity to max_vel
					vector3 g_nvel(nodex_ovel, nodey_ovel, nodez_ovel);
					freal nvelNorm = g_nvel.length();
					if(nvelNorm > max_vel){
						freal velRatio = max_vel/nvelNorm;
						g_nvel*= velRatio;
					}

					g_nvelX->setValue(iX,iY,iZ,g_nvel[0]);
					g_nvelY->setValue(iX,iY,iZ,g_nvel[1]);
					g_nvelZ->setValue(iX,iY,iZ,g_nvel[2]);

				}
			}
		}
	}

	/// STEP #5: Grid collision resolution

	vector3 sdf_normal;
	//*
	for(int iX=1; iX < grid_divs[0]-1; iX++){
		for(int iY=1; iY < grid_divs[1]-1; iY++){
			for(int iZ=1; iZ < grid_divs[2]-1; iZ++){
				if (g_active->getValue(iX,iY,iZ)){
					if (!computeSDFNormal(g_col, iX, iY, iZ, sdf_normal))
						continue;

					//Collider velocity
					vector3 vco(
						g_colVelX->getValue(iX,iY,iZ),
						g_colVelY->getValue(iX,iY,iZ),
						g_colVelZ->getValue(iX,iY,iZ)
					);
					//Grid velocity
					vector3 v(
						g_nvelX->getValue(iX,iY,iZ),
						g_nvelY->getValue(iX,iY,iZ),
						g_nvelZ->getValue(iX,iY,iZ)
					);
					//Skip if bodies are separating
					vector3 vrel = v - vco;
					
					freal vn = vrel.dot(sdf_normal);
					if (vn >= 0) continue;
					//Resolve collisions; also add velocity of collision object to snow velocity
					//Sticks to surface (too slow to overcome static friction)
					vector3 vt = vrel - (sdf_normal*vn);

					freal stick = vn*COF, vt_norm = vt.length();
					if (vt_norm <= -stick)
						vt = vco;
					//Dynamic friction
					else vt += stick*vt/vt_norm + vco;
					
					g_nvelX->setValue(iX,iY,iZ,vt[0]);	
					g_nvelY->setValue(iX,iY,iZ,vt[1]);
					g_nvelZ->setValue(iX,iY,iZ,vt[2]);
				}
			}
		}
	}
	//*/

	/// STEP #6: Transfer grid velocities to particles and integrate
	/// STEP #7: Particle collision resolution

	vector3 pic, flip, col_vel;
	matrix3 vel_grad;
	//Iterate through particles
	for (GA_Iterator it(gdp_in->getPointRange()); !it.atEnd(); it.advance()){
		int pid = it.getOffset();
		//Particle position
		vector3 pos(p_position.get(pid));
		
		//Reset velocity
		pic[0] = 0.0;
		pic[1] = 0.0;
		pic[2] = 0.0;
		flip = p_vel.get(pid);
		vel_grad.zero();
		freal density = 0;

		 //Get grid position
		vector3 gpos = (pos - grid_origin)/voxel_dims;
		int p_gridx = (int) gpos[0], p_gridy = (int) gpos[1], p_gridz = (int) gpos[2];
		for (int idx=0, z=p_gridz-1, z_end=z+3; z<=z_end; z++){
			for (int y=p_gridy-1, y_end=y+3; y<=y_end; y++){
				for (int x=p_gridx-1, x_end=x+3; x<=x_end; x++, idx++){
					freal w = p_w[pid-1][idx];
					if (w > EPSILON){
						const vector3 node_wg = p_wgh[pid-1][idx];
						const vector3 node_nvel(
							g_nvelX->getValue(x,y,z),
							g_nvelY->getValue(x,y,z),
							g_nvelZ->getValue(x,y,z)
						);

						//Transfer velocities
						pic += node_nvel*w;	
						flip[0] += (node_nvel[0] - g_ovelX->getValue(x,y,z))*w;	
						flip[1] += (node_nvel[1]- g_ovelY->getValue(x,y,z))*w;	
						flip[2] += (node_nvel[2] - g_ovelZ->getValue(x,y,z))*w;
						//Transfer density
						density += w * g_mass->getValue(x,y,z);
						//Transfer veloctiy gradient
						vel_grad.outerproductUpdate(1.0, node_nvel, node_wg);
					}
				}
			}
		}

		//Finalize velocity update
		vector3 vel = flip*FLIP_PERCENT + pic*(1-FLIP_PERCENT);
		
		//Reset collision data
		freal col_sdf = 0;
		sdf_normal[0] = 0;
		sdf_normal[1] = 0;
		sdf_normal[2] = 0;
		col_vel[0] = 0;
		col_vel[1] = 0;
		col_vel[2] = 0;
		
		//Interpolate surrounding nodes' SDF info to the particle (trilinear interpolation)

		for (int idx=0, z=p_gridz, z_end=z+1; z<=z_end; z++){
			freal w_z = gpos[2]-z;
			for (int y=p_gridy, y_end=y+1; y<=y_end; y++){
				freal w_zy = w_z*(gpos[1]-y);
				for (int x=p_gridx, x_end=x+1; x<=x_end; x++, idx++){
					freal weight = fabs(w_zy*(gpos[0]-x));
					//cout << w_zy << "," << (gpos[0]-x) << "," << weight << endl;
					vector3 temp_normal;
					computeSDFNormal(g_col, x, y, z, temp_normal);
						//goto SKIP_PCOLLIDE;
					//cout << g_col->getValue(x, y, z) << endl;
					//Interpolate
					sdf_normal += temp_normal*weight;
					col_sdf += g_col->getValue(x, y, z)*weight;
					col_vel[0] += g_colVelX->getValue(x, y, z)*weight;
					col_vel[1] += g_colVelY->getValue(x, y, z)*weight;
					col_vel[2] += g_colVelZ->getValue(x, y, z)*weight;
				}
			}
		}

		//Resolve particle collisions	
		//cout << col_sdf << endl;
		if (col_sdf > 0){
			vector3 vrel = vel - col_vel;
			freal vn = vrel.dot(sdf_normal);
			
			//Skip if bodies are separating
			if (vn < 0){
				
				//Resolve and add velocity of collision object to snow velocity
				//Sticks to surface (too slow to overcome static friction)
				vel = vrel - (sdf_normal*vn);
				freal stick = vn*COF, vel_norm = vel.length();
				if (vel_norm <= -stick)
					vel = col_vel;
				//Dynamic friction
				else vel += stick*vel/vel_norm + col_vel;
			}
		}
		
		SKIP_PCOLLIDE:

		//Finalize density update
		density /= voxelArea;
		p_density.set(pid,density);

		//Update particle position
		pos += framerate*vel;
		//Limit particle position
		int mask = 0;
		/*		
		for (int i=0; i<3; i++){
			if (pos[i] > bbox_max_limit[i]){
				pos[i] = bbox_max_limit[i];
				vel[i] = 0.0;
				mask |= 1 << i;
			}
			else if (pos[i] < bbox_min_limit[i]){
				pos[i] = bbox_min_limit[i];
				vel[i] = 0.0;
				mask |= 1 << i;
			}
		}
		//Slow particle down at bounds (not really necessary...)
		if (mask){
			for (int i=0; i<3; i++){
				if (mask & 0x1)
					vel[i] *= .05;
				mask >>= 1;
			}
		}//*/
		p_vel.set(pid,vel);
		p_position.set(pid,pos);

		//Update particle deformation gradient
		//Note: plasticity is computed on the next timestep...
		vel_grad *= framerate;
		vel_grad(0,0) += 1;
		vel_grad(1,1) += 1;
		vel_grad(2,2) += 1;
		
		p_Fe.set(pid, vel_grad*p_Fe.get(pid));
	}

	gdh.unlock(gdp_out);
    gdh.unlock(gdp_in);
	
	return true;
}