Beispiel #1
0
UT_Matrix3 RainData::computeRotationMatrix(UT_Vector3 rainDirection)
{
    static const UT_Vector3 up(0.0, -1.0, 0.0);
    UT_Matrix3 rotDirMatrix(1.0);
    rainDirection.normalize();
    UT_Vector3 axis = cross (up, rainDirection);
    axis.normalize();
    fpreal angle = acos (dot (up, rainDirection));
    rotDirMatrix.rotate (axis, -angle); // minus for proper generation
                                        // of the noise in
                                        // RainData::computeInitialPositions
    return rotDirMatrix;
}
Beispiel #2
0
void SOP_FlexSolver::copySourceParticles()
{
    if (mySource)
    {
        for (GA_Offset srcptoff = 0; srcptoff < maxParticles; ++srcptoff)
        {
            const UT_Vector3 pos = mySource->getPos3(srcptoff);
            UT_Vector3 vel;
            if (mySourceVel.isValid())
                vel = mySourceVel.get(srcptoff);
            else
                vel = UT_Vector3(0,0,0);
            uint index = static_cast<int>(srcptoff);
            uint p = index * 4;
            uint v = index * 3;
            particles[p]    = pos.x();
            particles[p+1]  = pos.y();
            particles[p+2]  = pos.z();
            particles[p+3]  = 1.0;
            velocities[v]   = vel.x();
            velocities[v+1] = vel.y();
            velocities[v+2] = vel.z();

            gdp->insertPointCopy(srcptoff);
            gdp->setPos3(srcptoff, pos);
        }
    }

}
void
SOP_PrimGroupCentroid::baryCenter(const GU_Detail *input_geo,
                                  GA_Range &pr_range,
                                  const GA_PrimitiveList &prim_list,
                                  UT_Vector3 &pos)
{
    GA_Range                    pt_range;

    GA_OffsetArray              points;
    GA_OffsetArray::const_iterator points_it;

    // We need to iterate over each primitive in the range and
    // find out which points it references.
    for (GA_Iterator pr_it(pr_range); !pr_it.atEnd(); ++pr_it)
    {
        // Get the range of points for the primitive using the
        // offset from the primitive list.
        pt_range = prim_list.get(*pr_it)->getPointRange();

        // Add each point's offset to the array, checking for duplicates.
        for (GA_Iterator pt_it(pt_range); !pt_it.atEnd(); ++pt_it)
            points.append(*pt_it, true);
    }

    // Reset the position.
    pos.assign(0,0,0);

    // Add the positions for all the points.
    for (points_it = points.begin(); !points_it.atEnd(); ++points_it)
        pos += input_geo->getPos3(*points_it);

    // Store the average position for all the points we found.
    pos /= points.entries();
}
void
SOP_PrimGroupCentroid::centerOfMass(GA_Range &pr_range,
                                    const GA_PrimitiveList &prim_list,
                                    UT_Vector3 &pos)
{
    fpreal                      area, total_area;

    const GEO_Primitive         *prim;

    // Set the position and total area to 0.
    pos.assign(0,0,0);
    total_area = 0;

    // Iterate over all the primitives in the range.
    for (GA_Iterator it(pr_range); !it.atEnd(); ++it)
    {
        // Get the primitive.
        prim = (const GEO_Primitive *) prim_list.get(*it);
        // Calculate the area of the primitive.
        area = prim->calcArea();
        // Add the barycenter multiplied by the area to the position.
        pos += prim->baryCenter() * area;
        // Add this primitive's area to the total area.
        total_area += area;
    }

    // If the total area is not 0, divide the position by the total area.
    if (total_area)
        pos /= total_area;
}
Beispiel #5
0
void
SOP_FlexSolver::timeStep(fpreal now)
{
    UT_Vector3 force(FX(now), FY(now), FZ(now));
    // int nbirth = BIRTH(now);

    if (error() >= UT_ERROR_ABORT)
        return;

   for (GA_Offset srcptoff = 0; srcptoff < maxParticles; ++srcptoff)
    {
        UT_Vector3 vel;
        if (mySourceVel.isValid())
            vel = mySourceVel.get(srcptoff);
        else
            vel = UT_Vector3(0,0,0);

        vel += force;
        uint index = static_cast<int>(srcptoff);
        uint v = index * 3;
        velocities[v]   = vel.x();
        velocities[v+1] = vel.y();
        velocities[v+2] = vel.z();
    }

    InitFlexParams(*myParms, now);
    flexSetParams(mySolver, myParms);
    flexSetVelocities(mySolver, &velocities[0], maxParticles, eFlexMemoryHost);

     const float dt = 1.0 / 24.0;
     const int substeps = 1;

    // tick solver
    flexUpdateSolver(mySolver, dt, substeps, myTimer);
    // update GPU data asynchronously
  
    flexGetParticles(mySolver, (float*)&particles[0], maxParticles, eFlexMemoryHost);

    for (GA_Offset srcptoff = 0; srcptoff < maxParticles; ++srcptoff)
    {
       uint p = static_cast<int>(srcptoff) * 4;
       const UT_Vector3 pos = UT_Vector3(particles[p], particles[p+1], particles[p+2]);
       gdp->setPos3(srcptoff, pos);
    }
}
OP_ERROR
SOP_Smoke_Source::cookMySop(OP_Context &context)
{
    // We must lock our inputs before we try to access their geometry.
    // OP_AutoLockInputs will automatically unlock our inputs when we return.
    // NOTE: Don't call unlockInputs yourself when using this!
    OP_AutoLockInputs inputs(this);
    if (inputs.lock(context) >= UT_ERROR_ABORT)
        return error();

    fpreal now = context.getTime();

    duplicateSource(0, context);

    // These three lines enable the local variable support.  This allows
    // $CR to get the red colour, for example, as well as supporting
    // any varmap created by the Attribute Create SOP.
    // Note that if you override evalVariableValue for your own
    // local variables (like SOP_Star does) it is essential you
    // still call the SOP_Node::evalVariableValue or you'll not
    // get any of the benefit of the built in local variables.

    // The variable order controls precedence for which attribute will be
    // be bound first if the same named variable shows up in multiple
    // places.  This ordering ensures point attributes get precedence.
    setVariableOrder(3, 2, 0, 1);

    // The setCur* functions track which part of the gdp is currently
    // being processed - it is what is used in the evalVariableValue
    // callback as the current point.  The 0 is for the first input,
    // you can have two inputs so $CR2 would get the second input's
    // value.
    setCurGdh(0, myGdpHandle);

    // Builds the lookup table matching attributes to the local variables.
    setupLocalVars();

    // Here we determine which groups we have to work on.  We only
    //	handle point groups.
    if (error() < UT_ERROR_ABORT && cookInputGroups(context) < UT_ERROR_ABORT &&
        (!myGroup || !myGroup->isEmpty()))
    {
        UT_AutoInterrupt progress("Flattening Points");

        // Handle all position, normal, and vector attributes.
        // It's not entirely clear what to do for quaternion or transform attributes.
        // We bump the data IDs of the attributes to modify in advance,
        // since we're already looping over them, and we want to avoid
        // bumping them all for each point, in case that's slow.
        UT_Array<GA_RWHandleV3> positionattribs(1);
        UT_Array<GA_RWHandleV3> normalattribs;
        UT_Array<GA_RWHandleV3> vectorattribs;
        GA_Attribute *attrib;
        GA_FOR_ALL_POINT_ATTRIBUTES(gdp, attrib)
        {
            // Skip non-transforming attributes
            if (!attrib->needsTransform())
                continue;

            GA_TypeInfo typeinfo = attrib->getTypeInfo();
            if (typeinfo == GA_TYPE_POINT || typeinfo == GA_TYPE_HPOINT)
            {
                GA_RWHandleV3 handle(attrib);
                if (handle.isValid())
                {
                    positionattribs.append(handle);
                    attrib->bumpDataId();
                }
            }
            else if (typeinfo == GA_TYPE_NORMAL)
            {
                GA_RWHandleV3 handle(attrib);
                if (handle.isValid())
                {
                    normalattribs.append(handle);
                    attrib->bumpDataId();
                }
            }
            else if (typeinfo == GA_TYPE_VECTOR)
            {
                GA_RWHandleV3 handle(attrib);
                if (handle.isValid())
                {
                    vectorattribs.append(handle);
                    attrib->bumpDataId();
                }
            }
        }

        // Iterate over points up to GA_PAGE_SIZE at a time using blockAdvance.
        GA_Offset start;
        GA_Offset end;
        for (GA_Iterator it(gdp->getPointRange(myGroup)); it.blockAdvance(start, end);)
        {
            // Check if user requested abort
            if (progress.wasInterrupted())
                break;

            for (GA_Offset ptoff = start; ptoff < end; ++ptoff)
            {
                // This sets the current point that is beint processed to
                // ptoff.  This means that ptoff will be used for any
                // local variable for any parameter evaluation that occurs
                // after this point.
                // NOTE: Local variables and repeated parameter evaluation
                //       is significantly slower and sometimes more complicated
                //       than having a string parameter that specifies the name
                //       of an attribute whose values should be used instead.
                //       That parameter would only need to be evaluated once,
                //       the attribute could be looked up once, and quickly
                //       accessed; however, a separate point attribute would
                //       be needed for each property that varies per point.
                //       Local variable evaluation isn't threadsafe either,
                //       whereas attributes can be read safely from multiple
                //       threads.
                //
                //       Long story short: *Local variables are terrible.*
                myCurPtOff[0] = ptoff;
                float dist = DIST(now);
                UT_Vector3 normal;
                if (!DIRPOP())
                {
                    switch (ORIENT())
                    {
                        case 0 : // XY Plane
                            normal.assign(0, 0, 1);
                            break;
                        case 1 : // YZ Plane
                            normal.assign(1, 0, 0);
                            break;
                        case 2 : // XZ Plane
                            normal.assign(0, 1, 0);
                            break;
                    }
                }
                else
                {
                    normal.assign(NX(now), NY(now), NZ(now));
                    normal.normalize();
                }

                // Project positions onto the plane by subtracting
                // off the normal component.
                for (exint i = 0; i < positionattribs.size(); ++i)
                {
                    UT_Vector3 p = positionattribs(i).get(ptoff);
                    p -= normal * (dot(normal, p) - dist);
                    positionattribs(i).set(ptoff, p);
                }

                // Normals will now all either be normal or -normal.
                for (exint i = 0; i < normalattribs.size(); ++i)
                {
                    UT_Vector3 n = normalattribs(i).get(ptoff);
                    if (dot(normal, n) < 0)
                        n = -normal;
                    else
                        n = normal;
                    normalattribs(i).set(ptoff, n);
                }

                // Project vectors onto the plane through the origin by
                // subtracting off the normal component.
                for (exint i = 0; i < vectorattribs.size(); ++i)
                {
                    UT_Vector3 v = vectorattribs(i).get(ptoff);
                    v -= normal * dot(normal, v);
                    vectorattribs(i).set(ptoff, v);
                }
            }
        }
    }
static void exportParticlesDetail( const GU_Detail* gdp,
                                  const std::string& filePath,
                                  const std::map<std::string,
                                  channel_type>& desiredChannels )
{
	prt_ofstream ostream;

	static std::map<std::string, std::string> s_reservedChannels;
	if( s_reservedChannels.empty() ) {
		s_reservedChannels[ gdp->getStdAttributeName( GEO_ATTRIBUTE_NORMAL ) ] = "Normal";
		s_reservedChannels[ gdp->getStdAttributeName( GEO_ATTRIBUTE_TEXTURE ) ] = "TextureCoord";
		s_reservedChannels[ gdp->getStdAttributeName( GEO_ATTRIBUTE_VELOCITY ) ] = "Velocity";
		s_reservedChannels[ gdp->getStdAttributeName( GEO_ATTRIBUTE_DIFFUSE ) ] = "Color";
		//s_reservedChannels[ gdp->getStdAttributeName( GEO_ATTRIBUTE_ALPHA ) ] = "Density";
		//s_reservedChannels[ gdp->getStdAttributeName( GEO_ATTRIBUTE_MASS ) ] = "Density";
		s_reservedChannels[ gdp->getStdAttributeName( GEO_ATTRIBUTE_LIFE ) ] = "";
		s_reservedChannels[ gdp->getStdAttributeName( GEO_ATTRIBUTE_ID ) ] = "ID";
		s_reservedChannels[ gdp->getStdAttributeName( GEO_ATTRIBUTE_PSCALE ) ] = "Scale";
		s_reservedChannels[ "accel" ] = "Acceleration";
	}

	float posVal[3];
	float lifeVal[2];
	
	ostream.bind( "Position", posVal, 3 );

	//We handle the life channel in a special manner
	GA_ROAttributeRef lifeAttrib = gdp->findPointAttribute( gdp->getStdAttributeName( GEO_ATTRIBUTE_LIFE ) );
	if( lifeAttrib.isValid() ){
		std::map<std::string,channel_type>::const_iterator it;
		
		it = desiredChannels.find( "Age" );
		if( it != desiredChannels.end() && it->second.second == 1 )
			ostream.bind( "Age", &lifeVal[0], 1, it->second.first );
		else if( desiredChannels.empty() )
			ostream.bind( "Age", &lifeVal[0], 1, prtio::data_types::type_float16 );

		it = desiredChannels.find( "LifeSpan" );
		if( it != desiredChannels.end() && it->second.second == 1 )
			ostream.bind( "LifeSpan", &lifeVal[1], 1, it->second.first );
		else if( desiredChannels.empty() )
			ostream.bind( "LifeSpan", &lifeVal[1], 1, prtio::data_types::type_float16 );
	}
	
	//Using a deque to prevent the memory from moving around after adding the bound_attribute to the container.
	std::deque< bound_attribute<int> > m_intAttrs;
	std::deque< bound_attribute<float> > m_floatAttrs;
	std::deque< bound_attribute<float> > m_vectorAttrs;
	
	for ( GA_AttributeDict::iterator it = gdp->getAttributes().getDict(GA_ATTRIB_POINT).begin(GA_SCOPE_PUBLIC); !it.atEnd(); ++it) {
		
		GA_Attribute *node = it.attrib();

		std::string channelName = node->getName();

		//Translate special names
		std::map<std::string,std::string>::const_iterator itResChannel = s_reservedChannels.find( channelName );
		if( itResChannel != s_reservedChannels.end() ){
			//If its empty, that means we reserve some sort of special handling.
			if( itResChannel->second.empty() )
				continue;
			channelName = itResChannel->second;
		}
		
		//Skip channels that aren't on the list.
		std::map<std::string,channel_type>::const_iterator itChannel = desiredChannels.find( channelName );
		bool channelIsDesired = ( itChannel != desiredChannels.end() );
		
		if( !desiredChannels.empty() && !channelIsDesired )
			continue;
			
		prtio::data_types::enum_t type;
		
		//Only add valid channel names
		if( detail::is_valid_channel_name( channelName.c_str() ) ) {
			//I add the new item to the deque, THEN initialize it since a deque will not move the object around and this allows
			//me to allocate the float array and not have to worry about the object getting deleted too early.
			switch( node->getStorageClass() ){
			case GA_STORECLASS_FLOAT:
				if( node->getTupleSize()==3 ){
					m_vectorAttrs.push_back( bound_attribute<float>() );
					m_vectorAttrs.back().attr =	gdp->findPointAttribute(node->getName());
					m_vectorAttrs.back().count = node->getTupleSize();
					m_vectorAttrs.back().data = new float[m_vectorAttrs.back().count];

					type = prtio::data_types::type_float16;
					if( channelIsDesired ){
						type = itChannel->second.first;
						if( itChannel->second.second != m_vectorAttrs.back().count )
							continue;
					}

					ostream.bind( channelName, m_vectorAttrs.back().data, m_vectorAttrs.back().count, type );

				} else {
					m_floatAttrs.push_back( bound_attribute<float>() );
					m_floatAttrs.back().attr =	gdp->findPointAttribute( node->getName() );
					m_floatAttrs.back().count = node->getTupleSize();
					m_floatAttrs.back().data = new float[m_floatAttrs.back().count];

					type = prtio::data_types::type_float16;
					if( channelIsDesired ){
						type = itChannel->second.first;
						if( itChannel->second.second != m_floatAttrs.back().count )
							continue;
					}

					ostream.bind( channelName, m_floatAttrs.back().data, m_floatAttrs.back().count, type );
				}
				break;
			case GA_STORECLASS_INT:
				m_intAttrs.push_back( bound_attribute<int>() );
				m_intAttrs.back().attr = gdp->findPointAttribute( node->getName() );
				m_intAttrs.back().count = node->getTupleSize();
				m_intAttrs.back().data = new int[m_intAttrs.back().count];

				type = prtio::data_types::type_int32;
				if( channelIsDesired ){
					type = itChannel->second.first;
					if( itChannel->second.second != m_intAttrs.back().count )
						continue;
				}
			
				ostream.bind( channelName, m_intAttrs.back().data, m_intAttrs.back().count, type );
				break;
			default:
				break;
			}
		}
	}

	try{
		ostream.open( filePath );
	} catch( const std::ios::failure& e ) {
		std::cerr << e.what() << std::endl;
		throw HOM_OperationFailed( "Failed to open the file" );
	}
		
	GA_IndexMap map = gdp->getPointMap();
	UT_Vector3 p;
	GEO_Point* pt;
	GA_Index indexSize = map.indexSize();
	GA_Offset offset;

	for( int i = 0 ; i < indexSize; i++ ) {
		offset = map.offsetFromIndex( i );
		p = gdp->getPos3( offset );
		posVal[0] = p.x();
		posVal[1] = p.y();
		posVal[2] = -1 * p.z();
		
		//TODO: Remove the GEO_Point object that is now deprecated. 
		pt = ( GEO_Point* )gdp->getGBPoint( offset );
		
		//TODO: Convert this into appropriate time values. Is it seconds or frames or what?!
		if( lifeAttrib.isValid() ) 
			pt->get( lifeAttrib, lifeVal, 2 );

		for( std::deque< bound_attribute<float> >::iterator it = m_floatAttrs.begin(), itEnd = m_floatAttrs.end(); it != itEnd; ++it )
			pt->get( it->attr, it->data, it->count );

		for( std::deque< bound_attribute<float> >::iterator it = m_vectorAttrs.begin(), itEnd = m_vectorAttrs.end(); it != itEnd; ++it ) {
			pt->get( it->attr, it->data, it->count );
				
			//TODO: Optionally transform into some consistent world space for PRT files.
		}

		for( std::deque< bound_attribute<int> >::iterator it = m_intAttrs.begin(), itEnd = m_intAttrs.end(); it != itEnd; ++it )
			pt->get( it->attr, it->data, it->count );

		ostream.write_next_particle();
	}
	
	ostream.close();
}
Beispiel #8
0
void ParallelShift::operator()(const GA_SplittableRange &r) const
    {
        fpreal actualSpeed;
        fpreal parm1x,parm2x,parm1y,parm2y,parm1z,parm2z, parmInitial;
        UT_Vector3 p1x, p2x, p1y, p2y, p1z, p2z, p1, p2, initialPosition, p;
        fpreal pathLength, pathPeriod;
        fpreal integralPart;
        UT_Vector3 boundPoints[2];
        UT_Vector3 vAttributeVector;
        
        GA_WOAttributeRef vAttributeRef = 
                                gdp_->addFloatTuple(GA_ATTRIB_POINT, "v", 3);
        const GA_AIFTuple* vAttribInterface = vAttributeRef.getAIFTuple();


        for(GA_PageIterator pit = r.beginPages(); !pit.atEnd(); ++pit)
        {
            GA_Offset       start, end;
            for (GA_Iterator it(pit.begin()); it.blockAdvance(start, end); )
            {
                for (GA_Offset i = start; i < end; ++i)
                {
                    actualSpeed = pRain_->getActualSpeed(i);
                    vAttributeVector = pRain_->rainDirection_*actualSpeed;
                    vAttribInterface->set(  vAttributeRef.getAttribute(), i,
                                            vAttributeVector.data(), 3);
                    
                    initialPosition = pRain_->getInitialPosition(i);

                    parm1x = (pRain_->maximumBounds_[0] - initialPosition[0]) / 
                             pRain_->rainDirection_[0];
                    parm2x = (pRain_->minimumBounds_[0] - initialPosition[0]) / 
                             pRain_->rainDirection_[0];
                    parm1y = (pRain_->maximumBounds_[1] - initialPosition[1]) / 
                             pRain_->rainDirection_[1];
                    parm2y = (pRain_->minimumBounds_[1] - initialPosition[1]) / 
                             pRain_->rainDirection_[1];
                    parm1z = (pRain_->maximumBounds_[2] - initialPosition[2]) / 
                             pRain_->rainDirection_[2];
                    parm2z = (pRain_->minimumBounds_[2] - initialPosition[2]) / 
                             pRain_->rainDirection_[2];
                    p1x = initialPosition + parm1x*pRain_->rainDirection_;
                    p2x = initialPosition + parm2x*pRain_->rainDirection_; 
                    p1y = initialPosition + parm1y*pRain_->rainDirection_;
                    p2y = initialPosition + parm2y*pRain_->rainDirection_;
                    p1z = initialPosition + parm1z*pRain_->rainDirection_;
                    p2z = initialPosition + parm2z*pRain_->rainDirection_; 
                    
                    UT_Vector3 endsArray [6] = {p1x, p2x, p1y, p2y, p1z, p2z};

                    // ########### sort ########################################
                    UT_Vector3 tmpPoint;  
                    bool inBound; 
                    int endsFound = 0;
                    int k = 0;
                    while(k<6 && endsFound <2)
                    {   
                        inBound = false;        
                        inBound =   (endsArray[k][0] >= 
                                                pRain_->minimumBounds_[0]) &&
                                    (endsArray[k][1] >= 
                                                pRain_->minimumBounds_[1]) &&
                                    (endsArray[k][2] >= 
                                                pRain_->minimumBounds_[2]) &&
                                    (endsArray[k][0] <= 
                                                pRain_->maximumBounds_[0]) &&
                                    (endsArray[k][1] <= 
                                                pRain_->maximumBounds_[1]) &&
                                    (endsArray[k][2] <= 
                                                pRain_->maximumBounds_[2]);
                        if (inBound == true)
                        {
                            boundPoints[endsFound] = endsArray[k];
                            endsFound++;
                        }         
                        k++;
                    }
                    if (boundPoints[0][1]<boundPoints[1][1])
                    {
                        tmpPoint = boundPoints[1];
                        boundPoints[1] = boundPoints[0];
                        boundPoints[0] = tmpPoint;
                    }
                    // E#############end of sort ###############################


                    pathLength = (boundPoints[0] - boundPoints[1]).length();
                    pathPeriod = pathLength / actualSpeed;
                    parmInitial =   (initialPosition - boundPoints[0]).length()/ 
                                    pathLength;
                    integralPart = parmInitial + pRain_->now_ / pathPeriod;                   

                    p = boundPoints[0] +
                        (integralPart - (int)integralPart) * 
                        (boundPoints[1] - boundPoints[0]);
                    
                    gdp_->setPos3(i, p);
                }                  
            }           
        } 
    }
Beispiel #9
0
void GR_rmanPtc::renderWire( GU_Detail *gdp,
    RE_Render &ren,
    const GR_AttribOffset &ptinfo,
    const GR_DisplayOption *dopt,
    float lod,
    const GU_PrimGroupClosure *hidden_geometry
    )
{
    int			 i, nprim;
    GEO_Primitive 	*prim;
    UT_Vector3		 v3;

    rmanPtcSop::rmanPtcDetail *detail = dynamic_cast<rmanPtcSop::rmanPtcDetail*>(gdp);
    if ( !detail )
        return;

    // rebuild our display list
    if ( detail->redraw )
    {
        srand(0);

        GEO_PointList &points = detail->points();
        int display_channel = detail->display_channel;
        // render as points
        GEO_Point *pt = 0;
        UT_Vector4 pos;
        float col[3] = {1.0,1.0,1.0};

        if ( !detail->use_disk )
        {
            ren.pushPointSize(detail->point_size);
            ren.beginPoint();
        }

        for ( unsigned int i=0; i<points.entries(); ++i )
        {
            if ( rand()/(float)RAND_MAX<=detail->display_probability )
            {
                // point position
                pt = points[i];
                pos = pt->getPos();

                // display colour
                
                float *ptr = NULL;
                if (detail->attributes.size() >0) {
                    ptr = pt->castAttribData<float>(
                            detail->attributes[display_channel] );
                    if (ptr) {
                        if ( detail->attribute_size[display_channel]==1)
                            col[0] = col[1] = col[2] = ptr[0];
                        else
                        {
                            col[0] = ptr[0];
                            col[1] = ptr[1];
                            col[2] = ptr[2];
                        }
                    }
                }
                // draw point
                if ( !detail->use_cull_bbox ||
                        detail->cull_bbox.isInside( pos ) )
                {
                    if ( !detail->use_disk )
                    {
                        // render as points
                        ren.setColor( col[0], col[1], col[2], 1 );
                        ren.vertex3DW( pos.x(), pos.y(), pos.z() );
                    }
                    else
                    {
                        // render as disks
                        UT_Vector3 n = *pt->castAttribData<UT_Vector3>(detail->N_attrib);
                        float r = *pt->castAttribData<float>(detail->R_attrib);
                        n.normalize();
                        UT_Vector3 ref(1,0,0);
                        UT_Vector3 up = ref;
                        up.cross(n);
                        up.normalize();
                        UT_Vector3 right = up;
                        right.cross(n);
                        right.normalize();
                        UT_DMatrix4 mat(
                                right.x(), right.y(), right.z(), 0,
                                up.x(), up.y(), up.z(), 0,
                                n.x(), n.y(), n.z(), 0,
                                pos.x(), pos.y(), pos.z(), 1 );
                        ren.pushMatrix();
                        ren.multiplyMatrix(mat);
                        ren.pushColor( UT_Color( UT_RGB, col[0], col[1], col[2] ) );
                        ren.circlefW( 0, 0, detail->point_size * r, 8 );
                        ren.popColor();
                        ren.popMatrix();
                    }
                }
            }
        }

        if ( !detail->use_disk )
        {
            ren.endPoint();
        }
    }
}