//input vertex number is 2, constant
inline void computePlanarRotationMatrix(
	const double *_Q, const int strideQ, const double *_Weight, const int strideW, 
	double3x3 &rot)
{
	Vector3d& X = *((Vector3d *)&rot.x[0]);
	Vector3d& Y = *((Vector3d *)&rot.x[3]);
	Vector3d& Z = *((Vector3d *)&rot.x[6]);
	Vector3d q1 = _getVertex3dUsingStride(_Q, 0, strideQ);
	Vector3d q2 = _getVertex3dUsingStride(_Q, 1, strideQ);
	const double w1 = _getDoubleUsingStride(_Weight, 0, strideW);
	const double w2 = _getDoubleUsingStride(_Weight, 1, strideW);
	Z = CrossProd(q1, q2);
	const double l2 = Magnitude2(Z);
	if (l2<1e-32){
		X = q1; X.normalize();
		const double nx = fabs(X.x);
		const double ny = fabs(X.y);
		const double nz = fabs(X.z);
		const double maxnxyz = _MAX3_(nx, ny, nz);
		if (nx==maxnxyz) 
			Z = Vector3d(0,0,1);
		else if (ny==maxnxyz) 
			Z = Vector3d(0,0,1);
		else 
			Z = Vector3d(-1,0,0);
		Y = CrossProd(Z, X); Y.normalize();
		Z = CrossProd(X, Y);
	}
	else{
		Z *= 1.0/sqrt(l2); //normalize Z
		q1.normalize(); q2.normalize();
		Y = w1*q1+w2*q2; Y.normalize();
		X = CrossProd(Y, Z);
	}
}
//use the center plane of the dihedral angle as the reference plane
inline void getReferencePlanesForQuadPair(
    const Vector3d &p1, const Vector3d &p2, const Vector3d &p3, const Vector3d &p4, const Vector3d &p5,
    Vector3d &facenorm0, Vector3d &facenorm1, double3x3& mat, double &xlen)
{
    Vector3d Y0, Y1;
    Vector3d &Z0 = facenorm0;
    Vector3d &Z1 = facenorm1;
    Vector3d &X = *((Vector3d*)(&mat.x[0]));
    Vector3d &Y = *((Vector3d*)(&mat.x[3]));
    Vector3d &Z = *((Vector3d*)(&mat.x[6]));
    const Vector3d p23 = (p2+p3)*0.5;	//quad center
    const Vector3d p45 = (p4+p5)*0.5;	//quad center
    X = p1;
    xlen = Magnitude(X);
    X /= xlen;	//normalize
    Y0 = p23;
    Z0 = CrossProd(X, Y0);
    Y1 = p45;
    Z1 = CrossProd(Y1, X);
    Z0.Normalize();
    Z1.Normalize();
    Z = Z0+Z1;
    Z.Normalize();
    Y = CrossProd(Z, X);
}
Example #3
0
void InterpolatorKNN::KollinearWithDistinctPositions(NNCandidate candidates[3], Vertex v) {
	// project v onto line <v_01, v_02> with hesse normal form	
	D3DXVECTOR3 v_01 = candidates[1].vertex.pos-candidates[0].vertex.pos;
	D3DXVECTOR3 v_02 = candidates[2].vertex.pos-candidates[0].vertex.pos;
	D3DXVECTOR3 v_0v = v.pos-candidates[0].vertex.pos;
	D3DXVECTOR3 v_1v = v.pos-candidates[1].vertex.pos;
	D3DXVECTOR3 v_2v = v.pos-candidates[2].vertex.pos;
	D3DXVECTOR3 v_2 = CrossProd(v_01, v_0v); 
	if(v_2.x != 0.0f || v_2.y != 0.0f || v_2.z != 0.0f) {
		D3DXVECTOR3 normal = Normalize(CrossProd(v_01, v_2));
		D3DXVECTOR3 pointOnPlane = candidates[0].vertex.pos;
		float d = DotProd(normal, pointOnPlane);
		float distance = DotProd(normal, v.pos) - d;
		v.pos = v.pos + distance * (-normal);
	}

	if(DotProd(v_01, v_02) < -0.9f) {
		// 0 is in the middle
		if(DotProd(v_01, v_0v) > 0.9f){
			// v on the same side as 1
			InterpolateLinear(candidates[0], candidates[1], v);
			candidates[2].weight = 0.0f;
		}
		else{
			// v on the same side as 2
			InterpolateLinear(candidates[0], candidates[2], v);
			candidates[1].weight = 0.0f;
		}
	}
	if(Length(v_01) < Length(v_02)) {
		// 1 is in the middle
		if(DotProd(-v_01, v_1v) > 0.9f){
			// v on the same side as 0
			InterpolateLinear(candidates[1], candidates[0], v);
			candidates[2].weight = 0.0f;
		}
		else{
			// v on the same side as 2
			InterpolateLinear(candidates[1], candidates[2], v);
			candidates[0].weight = 0.0f;
		}
	}
	else{
		// 2 is in the middle
		if(DotProd(-v_02, v_2v) > 0.9f){
			// v on the same side as 0
			InterpolateLinear(candidates[2], candidates[0], v);
			candidates[1].weight = 0.0f;
		}
		else{
			// v on the same side as 1
			InterpolateLinear(candidates[2], candidates[1], v);
			candidates[0].weight = 0.0f;
		}
	}
}
void SplineData::AlignCrossSection(int splineIndex, int crossSectionIndex,Point3 vec)
{
	if ((splineIndex >= 0) && (splineIndex < mSplineElementData.Count()))
	{
		int numCrossSections = NumberOfCrossSections(splineIndex);

		if ((crossSectionIndex >= 0) && (crossSectionIndex < numCrossSections))
		{

			//put the vec in spline space
			SplineCrossSection *crossSection = GetCrossSection(splineIndex,crossSectionIndex);


			Matrix3 crossSectionTM = crossSection->mTM;
			crossSectionTM.NoScale();
			crossSectionTM.NoTrans();
			Matrix3 icrossSectionTM = Inverse(crossSectionTM);


			Point3 crossSectionZVec = crossSection->mTangentNormalized;


			Point3 yvec = Normalize(vec);

			Point3 xvec = Normalize(CrossProd(yvec,crossSectionZVec));
			Point3 zvec = Normalize(CrossProd(xvec,yvec));

			Matrix3 rtm(1);
			rtm.SetRow(0,xvec);
			rtm.SetRow(1,yvec);
			rtm.SetRow(2,zvec);
			rtm.SetRow(3,Point3(0.0f,0.0f,0.0f));

			Matrix3 relativeTM =  crossSection->mIBaseTM * rtm;

			Quat q(relativeTM);


			q = TransformQuat(crossSection->mIBaseTM,q);

			crossSection->mQuat = q;

			return;
		}
	}
	DbgAssert(0);


}
hsBool plMaxNodeBase::Contains(const Point3& worldPt)
{
    TimeValue currTime = 0;//hsConverterUtils::Instance().GetTime(GetInterface());
    Object *obj = EvalWorldState(currTime).obj;
    if( !obj )
        return false;

    Matrix3 l2w = GetObjectTM(currTime);
    Matrix3 w2l = Inverse(l2w);
    Point3 pt = w2l * worldPt;

    if( obj->ClassID() == Class_ID(DUMMY_CLASS_ID,0) )
    {
        DummyObject* dummy = (DummyObject*)obj;
        Box3 bnd = dummy->GetBox();
        return bnd.Contains(pt);
    }
    if( obj->CanConvertToType(triObjectClassID) )
    {
        TriObject   *meshObj = (TriObject *)obj->ConvertToType(currTime, triObjectClassID);
        if( !meshObj )
            return false;

        Mesh& mesh = meshObj->mesh;
        Box3 bnd = mesh.getBoundingBox();
        if( !bnd.Contains(pt) )
        {
            if( meshObj != obj )
                meshObj->DeleteThis();
            return false;
        }

        hsBool retVal = true;
        int i;
        for( i = 0; i < mesh.getNumFaces(); i++ )
        {
            Face& face = mesh.faces[i];

            Point3 p0 = mesh.verts[face.v[0]];
            Point3 p1 = mesh.verts[face.v[1]];
            Point3 p2 = mesh.verts[face.v[2]];

            Point3 n = CrossProd(p1 - p0, p2 - p0);

            if( DotProd(pt, n) > DotProd(p0, n) )
            {
                retVal = false;
                break;
            }
        }

        if( meshObj != obj )
            meshObj->DeleteThis();

        return retVal;
    }

    // If we can't figure out what it is, the point isn't inside it.
    return false;
}
Example #6
0
void VNormal::Normalize() 
{
	VNormal	*Ptr,*Prev;
	Matrix3	Mat;

	Ptr  = m_Next;
	Prev = this;

	while(Ptr) 
	{
		if(Ptr->m_Smooth & m_Smooth) 
		{
			m_Normal += Ptr->m_Normal;
			m_S		 += Ptr->m_S;
			m_T      += Ptr->m_T;

			Prev->m_Next = Ptr->m_Next;
			Ptr->m_Next = NULL;
			delete Ptr;
			Ptr = Prev->m_Next;
		} 
		else 
		{
			Prev = Ptr;
			Ptr  = Ptr->m_Next;
		}
	}


	Point3Normalize(m_Normal);
	Point3Normalize(m_S);

	m_T	= CrossProd(m_S,m_Normal);
	Point3Normalize(m_T);

	m_S	= CrossProd(m_Normal,m_T);
	Point3Normalize(m_S);

	m_SxT = m_Normal;

	if(m_Next)
	{
		 m_Next->Normalize();
	}

}
Example #7
0
static inline 
void _computeLocalFrame(const Vector3f& v0, const Vector3f& v1, const Vector3f& v2, 
						Vector3f& X, Vector3f& Y, Vector3f& Z, float& avg_edgelen) 
{
	const Vector3f d0 = v1 - v0;
	const Vector3f d1 = v2 - v1;
	const Vector3f d2 = v0 - v2;

	Z = CrossProd(d0, d1);
	X = d0;
	Y = CrossProd(Z, X);

	avg_edgelen = DotProd(d0, d0) + DotProd(d1, d1) + DotProd(d2, d2);
	X.normalize();
	Y.normalize();
	Z.normalize();
}
Example #8
0
Point3 Gradient::EvalNormalPerturb(ShadeContext& sc) 
	{
	Point3 dPdu, dPdv;
	if (!sc.doMaps) return Point3(0,0,0);
	if (gbufID) sc.SetGBufferID(gbufID);
	Point2 dM = uvGen->EvalDeriv(sc,&mysamp);
	uvGen->GetBumpDP(sc,dPdu,dPdv);

#if 0
	// Blinn's algorithm
	Point3 N = sc.Normal();
	Point3 uVec = CrossProd(N,dPdv);
	Point3 vVec = CrossProd(N,dPdu);
	Point3 np = -dM.x*uVec+dM.y*vVec;
#else 
	// Lazy algorithm
	Point3 np = dM.x*dPdu+dM.y*dPdv;
//	return texout->Filter(dM.x*dPdu+dM.y*dPdv);
#endif
	Texmap* sub[3];
	for (int i=0; i<3; i++) 
		sub[i] = mapOn[i]?subTex[i]:NULL;
	if (sub[0]||sub[1]||sub[2]) {
		// d((1-k)*a + k*b ) = dk*(b-a) + k*(db-da) + da
		float a,b,k;
		Point3 da,db;
		Point2 UV, dUV;
		uvGen->GetUV(sc, UV,dUV);
		k = gradFunc(UV.x,UV.y);
		if (k<=center) {	
			k = k/center; 		
			EVALSUBPERTURB(a,da,2);
			EVALSUBPERTURB(b,db,1);
			} 
		else {
			k = (k-center)/(1.0f-center);		
			EVALSUBPERTURB(a,da,1);
			EVALSUBPERTURB(b,db,0);
			}
		np = (b-a)*np + k*(db-da) + da;
		}
	return texout->Filter(np);
	}
Example #9
0
int direction(Point3 *v) {
	Point3 a = v[0]-v[2];
	Point3 b = v[1]-v[0];
	Point3 n = CrossProd(a,b);
	switch(MaxComponent(n)) {
		case 0: return (n.x<0)?NEGX:POSX;
		case 1: return (n.y<0)?NEGY:POSY;
		case 2: return (n.z<0)?NEGZ:POSZ;
		}
	return 0;
	}
inline void CThinshell2Element::_computeLocalTransformMatrix2Tagent(
	const Vector3d p[], const int stride, const Vector3d norm[], double3x3& mat)
{
	Vector3d &X = *((Vector3d*)(&mat.x[0]));
	Vector3d &Y = *((Vector3d*)(&mat.x[3]));
	Vector3d &Z = *((Vector3d*)(&mat.x[6]));
	Vector3d p0 = _getVertexUsingStride(p, m_nCenterID, stride);
	const int ii = 0;
	Vector3d p1 = _getVertexUsingStride(p, m_nNodeID[ii], stride);
	const int len = m_nRod >> 1;		//div by 2
	for (int i=1; i<len; i++){
		const int j = m_nNodeID[i]; 
		p1 += _getVertexUsingStride(p, j, stride);
	}

	//Z = _computeAccurateVertexNormal(p, stride, m_nCenterID, m_1ringTri, m_n1RingPoly);
	Z = _computeAccurateVertexNormal2(norm, m_n1RingPolyID, m_n1RingPoly);
	X = p1 - p0; 
	Y = CrossProd(Z, X); Y.Normalize();
	X = CrossProd(Y, Z);
}
//construct the two local coord sys. of the shell element using current nodal positions
//the aixs of the coord sys are stored in two 3x3 matrices
//static inline 
inline void getReferencePlanesForTrianglePair(
	const Vector3d &q1, const Vector3d &q2, const Vector3d &q3, 
	Vector3d &facenorm0, Vector3d &facenorm1, double3x3& mat, double &xlen)
{
	Vector3d Y0, Y1;
	Vector3d &Z0 = facenorm0;
	Vector3d &Z1 = facenorm1;
	Vector3d &X = *((Vector3d*)(&mat.x[0]));
	Vector3d &Y = *((Vector3d*)(&mat.x[3]));
	Vector3d &Z = *((Vector3d*)(&mat.x[6]));
	X  = q1; 
	xlen = Magnitude(X);
	X /= xlen;	//normalize
	Y0 = q2;
	Z0 = CrossProd(X, Y0); 
	Y1 = q3;	
	Z1 = CrossProd(Y1, X); 
	Z0.Normalize();
	Z1.Normalize();
	Z = Z0+Z1; Z.Normalize();
	Y = CrossProd(Z, X); 
}
Example #12
0
void
TrackMouseCallBack::draw_marker(ViewExp& vpt, Point3 p, Point3 norm)
{
return;   // sorry, this doesn't work yet - I'll post it later
	// set GW tm to orientation specified by norm and draw a circle
	Matrix3 tm;
	Point3 zdir, ydir, xdir;
	// compute the direction of the z axis to be.
	// the positive z axis points AWAY from the target.
	zdir = Normalize(norm);
	// compute direction of the X axis before roll.
	xdir = Normalize(CrossProd(zdir.x > 0 ? Point3(0, 0, 1) : Point3(0, 0, -1), zdir));
	// compute direction of the Y axis before roll.
	ydir = Normalize(CrossProd(zdir, xdir));
	tm.SetRow(0, xdir);
	tm.SetRow(1, ydir);
	tm.SetRow(2, zdir);

	vpt.getGW()->setTransform(tm);
	vpt.getGW()->setColor(LINE_COLOR, MARKER_COLOR);
	vpt.getGW()->marker(&p, CIRCLE_MRKR);
}
Example #13
0
void CGLWin::prepareMirrorMatrix(const Vector3d &x, const Vector3d & z, const Vector3d &p, Matrix &mat)
{
	Vector3d X = Normalize(x);
	Vector3d Z = Normalize(z);
	Vector3d Y = CrossProd(Z, X);
	Y.normalize();

	Matrix t0; SetTranslationMatrix(-p, t0);
	Matrix r0; GenRotationMatrix(X, Y, Z, r0);
	Matrix m0; MirrorZ(m0);
	Matrix r1 = r0; 
	r1.transpose();
	Matrix rr = r1*r0;
	Matrix t1; SetTranslationMatrix(p, t1);
	mat = t0*r0*m0*r1*t1;
}
Example #14
0
Point3 UVW_ChannelClass::GeomFaceNormal(int index)
{
	if (index < 0) return Point3(0.0f,0.0f,1.0f);
	if (index >= f.Count()) return Point3(0.0f,0.0f,1.0f);

	if (f[index]->count < 3) 
		return Point3(0.0f,0.0f,1.0f);

	Point3 vec1,vec2;
	if (f[index]->count == 3)
	{
		int a = f[index]->v[0];
		int b = f[index]->v[1];
		int c = f[index]->v[2];
		vec1 = Normalize(geomPoints[b]-geomPoints[a]);
		vec2 = Normalize(geomPoints[c]-geomPoints[a]);
		Point3 norm = CrossProd(vec1,vec2);
		return Normalize(norm);
	}
	else
	{

		int i;
		Point3 tempC(0.0f,0.0f,0.0f); // Don't mess with f[fc].c.
		int deg = f[index]->count;
		for (i = 0; i < deg; i++) 
		{
			int a = f[index]->v[i];
			Point3 p = geomPoints[a];
			tempC += p;
		}

		tempC = tempC/((float)deg);
		Point3 norm = Point3(0.0f,0.0f,0.0f);
		for (i=0; i< deg; i++) 
		{
			int a =  f[index]->v[i];
			int b =  f[index]->v[(i+1)%deg];
			norm += (geomPoints[a] - tempC) ^ (geomPoints[b] - tempC);
		}
		norm = Normalize(norm);
		return norm;
	}



}
Example #15
0
// specular reflectivity, no colors yet, all vectors assumed normalized 
float GaussHighlight( float gloss, float aniso, float orient,  
                 Point3& N, Point3& V, Point3& L, Point3& T, float* pNL )
{
   float out = 0.0f;

   float asz = (1.0f - gloss) * ALPHA_SZ;
   float ax = ALPHA_MIN + asz;
   float ay = ALPHA_MIN + asz * (1.0f-aniso);
// DbgAssert( ax >= 0.0f && ay >= 0.0f );
   LBound( ax ); LBound( ay );

   Point3 H = Normalize(L - V); // (L + -V)/2
   float NH = DotProd(N, H);   
   if (NH > 0.0f) {
      float axy = /* normalizeOn ? ax * ay : */ DEFAULT_GLOSS2;
      float norm = 1.0f / (4.0f * PI * axy );
      float NV = -DotProd(N, V );
      if ( NV <= 0.001f)
         NV = 0.001f;

      float NL = pNL ? *pNL : DotProd( N, L );
      float g = 1.0f / (float)sqrt( NL * NV );
      if ( g > 3.0f ) g = 3.0f;

      // Apply Orientation rotation here
      float or = orient * 180.0f;
      Point3 T1 = T;
      if ( or != 0.0f )
         T1 = RotateVec( T, N, DegToRdn(or));

      // get binormal
      Point3 B = CrossProd( T1, N );

      float x = Dot( H, T1 ) / ax;
      float y = Dot( H, B ) / ay;
      float e = (float)exp( -2.0 * (x*x + y*y) / (1.0+NH) );

      out = norm * g * e;
   }
   return SPEC_MAX * out;  // does not have speclev or light color or kL
}
Example #16
0
Point3 UVW_ChannelClass::UVFaceNormal(int index)
{
	if (index < 0) return Point3(0.0f,0.0f,1.0f);
	if (index >= f.Count()) return Point3(0.0f,0.0f,1.0f);

	if (f[index]->count < 3) 
		return Point3(0.0f,0.0f,1.0f);



	Point3 vec1,vec2;
	if (f[index]->count == 3)
	{
		int a = f[index]->t[0];
		int b = f[index]->t[1];
		int c = f[index]->t[2];
		vec1 = Normalize(v[b].GetP()-v[a].GetP());
		vec2 = Normalize(v[c].GetP()-v[a].GetP());

	}
	else
	{
		int a = f[index]->t[0];
		int b = f[index]->t[1];
		
		vec1 = Normalize(v[b].GetP()-v[a].GetP());
		for (int i = 2; i < f[index]->count; i++)
		{
			b = f[index]->t[i];
			vec2 = Normalize(v[b].GetP()-v[a].GetP());
			float dot = DotProd(vec1,vec2);
			if (fabs(dot) != 1.0f) 
				i = f[index]->count;
		}

	}

	Point3 norm = CrossProd(vec1,vec2);
	return Normalize(norm);

}
Example #17
0
//ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
//             ÄÄÄÄÄ>> Normal Calculating Funtions  <<ÄÄÄÄÄÄ
//ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
void GetNormal(WORD v0, WORD v1, WORD v2, float *fvertbuf, float *nbuf)
{
  dot3d w, n, normal;

/* Get the Normal factors in order  0-1 2-1 */
  w.x = fvertbuf[v0*3] - fvertbuf[v1*3];
  w.y = fvertbuf[(v0*3)+1] - fvertbuf[(v1*3)+1];
  w.z = fvertbuf[(v0*3)+2] - fvertbuf[(v1*3)+2]; 

  n.x = fvertbuf[v2*3] - fvertbuf[v1*3];
  n.y = fvertbuf[(v2*3)+1] - fvertbuf[(v1*3)+1];
  n.z = fvertbuf[(v2*3)+2] - fvertbuf[(v1*3)+2]; 

/* Extract normal */
  normal = CrossProd(&w, &n);

  nbuf[0] = normal.x;
  nbuf[1] = normal.y;
  nbuf[2] = normal.z;

}
Example #18
0
void InterpolatorKNN::CalculateBaryzentricCoordinates(NNCandidate candidates[3], Vertex v) {
	D3DXVECTOR3 v_01 = Normalize(candidates[1].vertex.pos-candidates[0].vertex.pos);
	D3DXVECTOR3 v_02 = Normalize(candidates[2].vertex.pos-candidates[0].vertex.pos);	
	
	// project v onto plane <v_01, v_02> with hesse normal form
	D3DXVECTOR3 normal = Normalize(CrossProd(v_01, v_02));
	D3DXVECTOR3 pointOnPlane = candidates[0].vertex.pos;
	float d = DotProd(normal, pointOnPlane);
	float distance = DotProd(normal, v.pos) - d;
	v.pos = v.pos + distance * (-normal);
	
	float translate = 0.0f;
	if(v.pos.x == 0 && v.pos.y == 0 && v.pos.z == 0) {
		translate = 10;
	}
		
	candidates[0].vertex.pos.x += translate;
	candidates[1].vertex.pos.x += translate;
	candidates[2].vertex.pos.x += translate;
	v.pos.x += translate;
	
	D3DXVECTOR3 res = SolveLES( candidates[0].vertex.pos, 
															candidates[1].vertex.pos, 
															candidates[2].vertex.pos, 
															v.pos);
	
	candidates[0].weight = res.x;
	candidates[1].weight = res.y;
	candidates[2].weight = res.z;

	D3DXVECTOR3 check = candidates[0].weight * candidates[0].vertex.pos + 
											candidates[1].weight * candidates[1].vertex.pos +
											candidates[2].weight * candidates[2].vertex.pos -
											v.pos;

	float error = abs(check.x) + abs(check.y) + abs(check.z);
	if(error > 0.00001) {
		PD(L"big error solving lgs: ", error);
	}
}
Example #19
0
Matrix3 UVW_ChannelClass::MatrixFromUVFace(int index)
{
	Matrix3 tm(1);

	if (f[index]->count < 3) 
		return Matrix3(1);

	Point3 xvec,yvec,zvec;
	zvec = UVFaceNormal(index);
	int a,b;
	a = f[index]->t[0];
	b = f[index]->t[1];
	xvec = Normalize(v[b].GetP()-v[a].GetP());
	yvec = Normalize(CrossProd(xvec,zvec));

	tm.SetRow(0,xvec);
	tm.SetRow(1,yvec);
	tm.SetRow(2,zvec);
	tm.SetRow(3,v[a].GetP());

	return tm;
}
Example #20
0
Matrix3 UVW_ChannelClass::MatrixFromGeoFace(int index)
{

	if (f[index]->count < 3) 
		return Matrix3(1);

	Matrix3 tm(1);
	Point3 xvec,yvec,zvec;
	zvec = GeomFaceNormal(index);
	int a,b;
	a = f[index]->v[0];
	b = f[index]->v[1];
	xvec = Normalize(geomPoints[b]-geomPoints[a]);
	yvec = Normalize(CrossProd(xvec,zvec));

	tm.SetRow(0,xvec);
	tm.SetRow(1,yvec);
	tm.SetRow(2,zvec);
	tm.SetRow(3,geomPoints[a]);

	return tm;
}
Example #21
0
//=================================================================
// Methods for DumpNodesTEP
//
int DumpNodesTEP::callback(INode *pnode)
{
	ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!");

	if (::FNodeMarkedToSkip(pnode))
		return TREE_CONTINUE;

	// Get node's parent
	INode *pnodeParent;
	pnodeParent = pnode->GetParentNode();
	
	// The model's root is a child of the real "scene root"
	TSTR strNodeName(pnode->GetName());
	BOOL fNodeIsRoot = pnodeParent->IsRootNode( );
	
	int iNode = ::GetIndexOfINode(pnode);
	int iNodeParent = ::GetIndexOfINode(pnodeParent, !fNodeIsRoot/*fAssertPropExists*/);

	// Convenient time to cache this
	m_phec->m_rgmaxnode[iNode].imaxnodeParent = fNodeIsRoot ? SmdExportClass::UNDESIRABLE_NODE_MARKER : iNodeParent;

	// Root node has no parent, thus no translation
	if (fNodeIsRoot)
		iNodeParent = -1;
		
	// check to see if the matrix isn't right handed
	m_phec->m_rgmaxnode[iNode].isMirrored = DotProd( CrossProd( m_phec->m_rgmaxnode[iNode].mat3ObjectTM.GetRow(0).Normalize(), m_phec->m_rgmaxnode[iNode].mat3ObjectTM.GetRow(1).Normalize() ).Normalize(), m_phec->m_rgmaxnode[iNode].mat3ObjectTM.GetRow(2).Normalize() ) < 0;

	// Dump node description
	fprintf(m_pfile, "%3d \"%s\" %3d\n", 
		iNode, 
		strNodeName, 
		iNodeParent );

	return TREE_CONTINUE;
}
Example #22
0
bool bgGlobalMax::CheckNegativeTM(Matrix3& m)
{
	return (DotProd(CrossProd(m.GetRow(0), m.GetRow(1)), m.GetRow(2)) < 0.0) ? 1 : 0;
}
bool PFOperatorSimpleSpeed::Proceed(IObject* pCont, 
									 PreciseTimeValue timeStart, 
									 PreciseTimeValue& timeEnd,
									 Object* pSystem,
									 INode* pNode,
									 INode* actionNode,
									 IPFIntegrator* integrator)
{
	// acquire all necessary channels, create additional if needed
	IParticleChannelNewR* chNew = GetParticleChannelNewRInterface(pCont);
	if(chNew == NULL) return false;
	IParticleChannelPTVR* chTime = GetParticleChannelTimeRInterface(pCont);
	if(chTime == NULL) return false;
	IParticleChannelAmountR* chAmount = GetParticleChannelAmountRInterface(pCont);
	if(chAmount == NULL) return false;
	// the position channel may not be present. For some option configurations it is okay
	IParticleChannelPoint3R* chPos = GetParticleChannelPositionRInterface(pCont);
	int iDir = _pblock()->GetInt(kSimpleSpeed_direction, timeStart);
	if ((chPos == NULL) && ((iDir == kSS_Icon_Center_Out) || (iDir == kSS_Icon_Arrow_Out)))
		return false;

	IChannelContainer* chCont;
	chCont = GetChannelContainerInterface(pCont);
	if (chCont == NULL) return false;

	// the channel of interest
	bool initSpeed = false;
	IParticleChannelPoint3W* chSpeed = (IParticleChannelPoint3W*)chCont->EnsureInterface(PARTICLECHANNELSPEEDW_INTERFACE,
																			ParticleChannelPoint3_Class_ID,
																			true, PARTICLECHANNELSPEEDR_INTERFACE,
																			PARTICLECHANNELSPEEDW_INTERFACE, true,
																			actionNode, (Object*)NULL, &initSpeed);
	IParticleChannelPoint3R* chSpeedR = GetParticleChannelSpeedRInterface(pCont);
	if ((chSpeed == NULL) || (chSpeedR == NULL)) return false;

	// there are no new particles
	if (chNew->IsAllOld()) return true;

	float fUPFScale = 1.0f/TIME_TICKSPERSEC; // conversion units per seconds to units per tick
	Point3 pt3SpeedVec;
	RandGenerator* prg = randLinker().GetRandGenerator(pCont);
	int iQuant = chAmount->Count();
	bool wasIgnoringEmitterTMChange = IsIgnoringEmitterTMChange();
	if (!wasIgnoringEmitterTMChange) SetIgnoreEmitterTMChange();
	for(int i = 0; i < iQuant; i++) {
		if(chNew->IsNew(i)) { // apply only to new particles
			TimeValue tv = chTime->GetValue(i).TimeValue();
			Matrix3 nodeTM = pNode->GetObjectTM(tv);
			float fSpeedParam = fUPFScale * GetPFFloat(pblock(), kSimpleSpeed_speed, tv);
			// change speed in user selected direction
			switch(iDir) {
				case kSS_Along_Icon_Arrow: {
						// icon arrow appears to be in the negative z direction
						pt3SpeedVec = -Normalize(nodeTM.GetRow(2));
					}
					break;
				case kSS_Icon_Center_Out: {
						Point3 pt3IconCenter = nodeTM.GetTrans();
						Point3 pt3PartPos = chPos->GetValue(i);
						pt3SpeedVec = Normalize(pt3PartPos - pt3IconCenter);
					}
					break;
				case kSS_Icon_Arrow_Out: {
						Point3 pt3PartPos = chPos->GetValue(i);
						Point3 pt3ArrowVec = nodeTM.GetRow(2);
						Point3 pt3Tmp = CrossProd(pt3PartPos - nodeTM.GetTrans(), pt3ArrowVec);
						pt3SpeedVec = Normalize(CrossProd(pt3ArrowVec, pt3Tmp));
					}
					break;
				case kSS_Rand_3D: {
						pt3SpeedVec = RandSphereSurface(prg);
					}
					break;
				case kSS_Rand_Horiz: {
						float fAng = TWOPI * prg->Rand01();
						// establish x, y coordinates of random angle, z component zero
						float x = cos(fAng); float y = sin(fAng); float z = 0.0f;
						pt3SpeedVec = Point3(x, y, z);
					}
					break;
				case kSS_Inherit_Prev: {
						if (initSpeed) 
							pt3SpeedVec = Point3::Origin;
						else
							pt3SpeedVec = Normalize(chSpeedR->GetValue(i));
					}
					break;
			}
			// account for reverse check box
			int iRev = _pblock()->GetInt(kSimpleSpeed_reverse, 0);
			float fDirMult = iRev > 0 ? -1.f : 1.f;
			// calculate variation
			float fVar = fUPFScale * GetPFFloat(pblock(), kSimpleSpeed_variation, tv);
			if(fVar > 0.f)
				fSpeedParam = fSpeedParam + fVar * prg->Rand11();
			pt3SpeedVec = fDirMult * fSpeedParam * pt3SpeedVec;
			// calculate divergence
			float fDiv = GetPFFloat(pblock(), kSimpleSpeed_divergence, tv);
			pt3SpeedVec = DivergeVectorRandom(pt3SpeedVec, prg, fDiv);

			chSpeed->SetValue(i, pt3SpeedVec);
		}
	}
	if (!wasIgnoringEmitterTMChange) ClearIgnoreEmitterTMChange();

	return true;
}
Example #24
0
void RenderMesh::ComputeVertexNormals(Mesh *aMesh, Tab<BasisVert> &FNorms, Tab<VNormal> &VNorms, bool NegScale) 
{
	Face			*Face;	
	Point3			*Verts;
	Point3			Vt0,Vt1,Vt2;
	Point3			Normal;
	int				i,j,k,A,B,C;
	int				NumUV,NumVert,NumFace;
	UVVert			*UVVert;
	TVFace			*UVFace;
    Vert3			Vert[3];
	Point3			S,T;
	Point3			Edge01,Edge02;
	Point3			Cp;
	float			U0,V0,U1,V1,U2,V2;
	int				UVCount;
	unsigned long	Sg;

	NumUV   = aMesh->getNumMaps();	
	NumVert	= aMesh->getNumVerts();
	NumFace = aMesh->getNumFaces(); 
	Face	= aMesh->faces;	
	Verts	= aMesh->verts;

	VNorms.SetCount(NumVert);
	FNorms.SetCount(NumFace);

	if(NumUV > MAX_TMUS + 1)
	{
		NumUV = MAX_TMUS + 1;
	}

	for(i=0; i < NumVert; i++) 
	{
		VNorms[i].Clear();
	}

	for(i=0; i < NumFace; i++, Face++) 
	{
		A = Face->v[gVIndex[0]];
		B = Face->v[gVIndex[1]];
		C = Face->v[gVIndex[2]];

		Vt0 = Verts[A];
		Vt1 = Verts[B];
		Vt2 = Verts[C];

		Normal = (Vt1 - Vt0) ^ (Vt2 - Vt0);

		Point3Normalize(Normal);

		for(j=0; j < 3; j++) 
		{
			UVCount = 0;

			for(k=0;k<m_MapChannels.Count();k++)
			{	
				int index = m_MapChannels[k];
				if(aMesh->getNumMapVerts(index))
				{
					UVVert = aMesh->mapVerts(index);
					UVFace = aMesh->mapFaces(index);

					Vert[j].m_UV[k].x = UVVert[UVFace[i].t[gVIndex[j]]].x;
					Vert[j].m_UV[k].y = UVVert[UVFace[i].t[gVIndex[j]]].y;


				}
				else
				{
					Vert[j].m_UV[k].x = 0.0f;
					Vert[j].m_UV[k].y = 0.0f;

				}
			}

		}

		S.Set(0.0f,0.0f,0.0f);
		T.Set(0.0f,0.0f,0.0f);

		U0 = -Vert[0].m_UV[NORMAL_UV].x;
		V0 = Vert[0].m_UV[NORMAL_UV].y;

		Rotate2DPoint(U0,V0,DEG_RAD(180.0f));

		U1 = -Vert[1].m_UV[NORMAL_UV].x;
		V1 = Vert[1].m_UV[NORMAL_UV].y;

		Rotate2DPoint(U1,V1,DEG_RAD(180.0f));

		U2 = -Vert[2].m_UV[NORMAL_UV].x;
		V2 = Vert[2].m_UV[NORMAL_UV].y;

		Rotate2DPoint(U2,V2,DEG_RAD(180.0f));

		// x, s, t
		Edge01 = Point3(Vt1.x - Vt0.x, 
						U1 - U0, 
						V1 - V0);

		Edge02 = Point3(Vt2.x - Vt0.x, 
						U2 - U0, 
						V2 - V0);


		Cp = CrossProd(Edge01,Edge02);
        Point3Normalize(Cp);

		if(fabs(Cp.x) > 0.0001f)
		{
			S.x = -Cp.y / Cp.x;
			T.x = -Cp.z / Cp.x;
		}

		// y, s, t
		Edge01 = Point3(Vt1.y - Vt0.y, 
					    U1 - U0, 
					    V1 - V0);

		Edge02 = Point3(Vt2.y - Vt0.y, 
				   	    U2 - U0, 
						V2 - V0);

		Cp = CrossProd(Edge01,Edge02);
        Point3Normalize(Cp);

		if(fabs(Cp.x) > 0.0001f)
		{
			S.y = -Cp.y / Cp.x;
			T.y = -Cp.z / Cp.x;
		}

		// z, s, t
		Edge01 = Point3(Vt1.z - Vt0.z, 
					    U1 - U0, 
					    V1 - V0);

		Edge02 = Point3(Vt2.z - Vt0.z, 
					    U2 - U0, 
					    V2 - V0);

		Cp = CrossProd(Edge01,Edge02);
        Point3Normalize(Cp);

		if(fabs(Cp.x) > 0.0001f)
		{
			S.z = -Cp.y / Cp.x;
			T.z = -Cp.z / Cp.x;
		}

		Point3Normalize(S);
		Point3Normalize(T);

		Sg = Face->smGroup;
		
		if(Sg)
		{
			VNorms[A].AddNormal(Normal,Sg,S,T);
			VNorms[B].AddNormal(Normal,Sg,S,T);
			VNorms[C].AddNormal(Normal,Sg,S,T);
		}
		else
		{
			T = CrossProd(S,Normal);
			Point3Normalize(T);

			S = CrossProd(Normal,T);
			Point3Normalize(S);

			FNorms[i].m_Normal = Normal;
			FNorms[i].m_S	   = S;
			FNorms[i].m_T	   = T;
			FNorms[i].m_SxT	   = Normal;
		}

	}

	for(i=0; i < NumVert; i++) 
	{
		VNorms[i].Normalize();
	}

}
Example #25
0
void ResetVert (PatchMesh *patch)
{
	// Make a edge table
	// Static table to avoid alloc prb
	CVertexNeighborhood& edgeTab=vertexNeighborhoodGlobal;
	edgeTab.build (*patch);

	// For each vertices
	for (int nV=0; nV<patch->numVerts; nV++)
	{
		// Selected ?
		if (patch->vertSel[nV])
		{
			Point3 vert=patch->verts[nV].p;
			Point3 normal (0,0,0);

			// Count of neigbor for vertex n
			uint listSize=edgeTab.getNeighborCount (nV);

			// List of neigbor
			const uint* pList=edgeTab.getNeighborList (nV);

			// For each neigbor
			uint nn;
			for (nn=0; nn<listSize; nn++)
			{
#if (MAX_RELEASE < 4000)
				// Compute average plane
				if (patch->edges[pList[nn]].patch1!=-1)
					normal+=patch->PatchNormal(patch->edges[pList[nn]].patch1);
				if (patch->edges[pList[nn]].patch2!=-1)
					normal+=patch->PatchNormal(patch->edges[pList[nn]].patch2);
#else // (MAX_RELEASE <= 4000)
				// Compute average plane
				if (patch->edges[pList[nn]].patches[0]!=-1)
					normal+=patch->PatchNormal(patch->edges[pList[nn]].patches[0]);
				if (patch->edges[pList[nn]].patches[1]!=-1)
					normal+=patch->PatchNormal(patch->edges[pList[nn]].patches[1]);
#endif // (MAX_RELEASE <= 4000)
			}
			
			// Normalize
			normal=normal.Normalize();
			
			// Plane
			float fD=-DotProd(normal, vert);

			// Reset normales
			float fNorme=0.f;

			// For each neigbor
			for (nn=0; nn<listSize; nn++)
			{
				Point3 vect2=patch->verts[(patch->edges[pList[nn]].v1==nV)?patch->edges[pList[nn]].v2:patch->edges[pList[nn]].v1].p;
				vect2-=vert;
				vect2/=3.f;
				Point3 tmp1=CrossProd (vect2, normal);
				tmp1=CrossProd (normal, tmp1);
				tmp1=Normalize(tmp1);
				int nTang=(patch->edges[pList[nn]].v1==nV)?patch->edges[pList[nn]].vec12:patch->edges[pList[nn]].vec21;
				patch->vecs[nTang].p=vert+tmp1*DotProd (tmp1,vect2);
				tmp1=patch->vecs[nTang].p;
				tmp1-=vert;
				fNorme+=tmp1.Length();
			}

			// Renorme new normal
			/*fNorme/=(float)edgeTab[nV].size();
			ite=edgeTab[nV].begin();
			while (ite!=edgeTab[nV].end())
			{
				int nTang=(patch->edges[pList[nn]].v1==nV)?patch->edges[pList[nn]].vec12:patch->edges[pList[nn]].vec21;
				patch->vecs[nTang].p=fNorme*(Normalize(patch->vecs[nTang].p-vert))+vert;

				ite++;
			}*/
		}
	}
	patch->computeInteriors();
	patch->InvalidateGeomCache ();
}
Example #26
0
bool Exporter::TMNegParity(const Matrix3 &m)
{
	return (DotProd(CrossProd(m.GetRow(0),m.GetRow(1)),m.GetRow(2))<0.0)?true:false;
}
Example #27
0
GFA::Vector GFA::Vector::operator*(const Matrix &rhs) const
{
    Vector temp;
    CrossProd(temp, rhs);
    return temp;
}
Example #28
0
int CPointObj::LoadPltFileWithoutHeader(FILE *fp, const int nv, const int, const int nTotalAttrib)
{
	//read vertices;
	m_nVertexCount = nv;
	m_nPolygonCount = nv;
	LoadPltVertices(fp, nv, nTotalAttrib);

	//assign the radii for particles
	m_pRadius = new float[m_nPolygonCount];
	assert(m_pRadius!=NULL);
	float *pInputRadius = NULL;
	int rpos= this->GetFAttributeIndexByName("R");
    if (rpos>0) pInputRadius = m_pFAttributes[rpos];
	if (pInputRadius){
		for (int i=0; i<nv; i++) 
            m_pRadius[i] = pInputRadius[i];
	}
	else{
		for (int i=0; i<nv; i++) 
            m_pRadius[i] = 0;
	}
	
	//assign ellipsoid transform matrices
	m_pMatrix = NULL;
	const float K = 1.0f;
	int evXpos= this->GetFAttributeIndexByName("A");
	int etXXpos= this->GetFAttributeIndexByName("AX");
	int etXYpos= this->GetFAttributeIndexByName("AY");
	int etXZpos= this->GetFAttributeIndexByName("AZ");
	int evYpos= this->GetFAttributeIndexByName("B");
	int etYXpos= this->GetFAttributeIndexByName("BX");
	int etYYpos= this->GetFAttributeIndexByName("BY");
	int etYZpos= this->GetFAttributeIndexByName("BZ");
	int evZpos= this->GetFAttributeIndexByName("C");
	int etZXpos= this->GetFAttributeIndexByName("CX");
	int etZYpos= this->GetFAttributeIndexByName("CY");
	int etZZpos= this->GetFAttributeIndexByName("CZ");
	if (evXpos>=0 && etXXpos>=0 && etXYpos>=0 && etXZpos>=0 &&
		evYpos>=0 && etYXpos>=0 && etYYpos>=0 && etYZpos>=0 &&
		evZpos>=0 && etZXpos>=0 && etZYpos>=0 && etZZpos>=0){
		float *rx = m_pFAttributes[evXpos];
		float *ry = m_pFAttributes[evYpos];
		float *rz = m_pFAttributes[evZpos];
		float *vxx = m_pFAttributes[etXXpos];
		float *vxy = m_pFAttributes[etXYpos];
		float *vxz = m_pFAttributes[etXZpos];
		float *vyx = m_pFAttributes[etYXpos];
		float *vyy = m_pFAttributes[etYYpos];
		float *vyz = m_pFAttributes[etYZpos];
		float *vzx = m_pFAttributes[etZXpos];
		float *vzy = m_pFAttributes[etZYpos];
		float *vzz = m_pFAttributes[etZZpos];
		m_pMatrix = new float3x3[nv];
		for (int i=0; i<nv; i++){
			float3x3& m = m_pMatrix[i];
			Vector3f* dx = (Vector3f*)(&m.x[0]);
			Vector3f* dy = (Vector3f*)(&m.x[3]);
			Vector3f* dz = (Vector3f*)(&m.x[6]);
			//normalize, right-handrize the 3 axes
			*dx = Vector3f(vxx[i], vxy[i], vxz[i]);
			(*dx).normalize();
			*dy = Vector3f(vyx[i], vyy[i], vyz[i]);
			(*dy).normalize();
			(*dz) = CrossProd(*dx, *dy);
			//*dz = Vector3f(vzx[i], vzy[i], vzz[i]);
			(*dz).normalize();
			float3x3 s; 
			s.setIdentityMatrix();
			s.x[0]=(fabs(rx[i]))*K;
			s.x[4]=(fabs(ry[i]))*K;
			s.x[8]=(fabs(rz[i]))*K;
			m*=s;
		}
	}

	return 1;
}
Example #29
0
// Determine is the node has negative scaling.
// This is used for mirrored objects for example. They have a negative scale factor
// so when calculating the normal we should take the vertices counter clockwise.
// If we don't compensate for this the objects will be 'inverted'.
BOOL Exporter::TMNegParity(Matrix3 &m)
{
	return (DotProd(CrossProd(m.GetRow(0),m.GetRow(1)),m.GetRow(2))<0.0)?1:0;
}
Example #30
0
int ExportQuake3Model(const TCHAR *filename, ExpInterface *ei, Interface *gi, int start_time, std::list<ExportNode> lTags, std::list<ExportNode> lMeshes)
{
	FILE *file;
	int i, j, totalTags, totalMeshes, current_time = 0;
	long pos_current, totalTris = 0, totalVerts = 0;
	std::list<FrameRange>::iterator range_i;
	std::vector<Point3> lFrameBBoxMin;
	std::vector<Point3> lFrameBBoxMax;
	long pos_tagstart;
	long pos_tagend;
	long pos_filesize;
	long pos_framestart;
	int lazynamesfixed = 0;
	const Point3 x_axis(1, 0, 0);
	const Point3 z_axis(0, 0, 1);

	SceneEnumProc checkScene(ei->theScene, start_time, gi);
	totalTags = (int)lTags.size();
	if (g_tag_for_pivot)
		totalTags++;
	totalMeshes = (int)lMeshes.size();

	// open file
	file = _tfopen(filename, _T("wb"));
	if (!file)
	{
		ExportError("Cannot open file '%s'.", filename);
		return FALSE;
	}
	ExportDebug("%s:", filename);

	// sync pattern and version
	putChars("IDP3", 4, file);
	put32(15, file);
	putChars("Darkplaces MD3 Exporter", 64, file);
	put32(0, file);   // flags
	
	// MD3 header
	ExportState("Writing MD3 header");
	put32(g_total_frames, file);      // how many frames
	put32(totalTags, file);	  // tagsnum
	put32(totalMeshes, file); // meshnum
	put32(1, file);   // maxskinnum
	put32(108, file); // headersize
	pos_tagstart = ftell(file); put32(0, file);   // tagstart
	pos_tagend	= ftell(file);  put32(256, file); // tagend
	pos_filesize = ftell(file); put32(512, file); // filesize
	ExportDebug("    %i frames, %i tags, %i meshes", g_total_frames, totalTags, totalMeshes);

	// frame info
	// bbox arrays get filled while exported mesh and written back then
	ExportState("Writing frame info");
	pos_framestart = ftell(file);
	lFrameBBoxMin.resize(g_total_frames);
	lFrameBBoxMax.resize(g_total_frames);
	for (i = 0; i < g_total_frames; i++)
	{
		// init frame data
		lFrameBBoxMin[i].Set(0, 0, 0);
		lFrameBBoxMax[i].Set(0, 0, 0);
		// put data
		putFloat(-1.0f, file); // bbox min vector
		putFloat(-1.0f, file);
		putFloat(-1.0f, file);	
		putFloat( 1.0f, file); // bbox max vector
		putFloat(1.0f, file);
		putFloat(1.0f, file);
		putFloat(0.0f, file);  // local origin (usually 0 0 0)
		putFloat(0.0f, file);
		putFloat(0.0f, file);
		putFloat(1.0f, file);  // radius of bounding sphere
		putChars("", 16, file);
	}

	// tags
	pos_current = ftell(file);
	fseek(file, pos_tagstart, SEEK_SET);
	put32(pos_current, file);
	fseek(file, pos_current, SEEK_SET);
	
	// for each frame range cycle all frames and write out each tag
	long pos_tags = pos_current;
	if (totalTags)
	{
		long current_frame = 0;
		ExportState("Writing %i tags", totalTags);
		for (range_i = g_frame_ranges.begin(); range_i != g_frame_ranges.end(); range_i++)
		{
			for (i = (*range_i).first; i <= (int)(*range_i).last; i++, current_frame++)
			{
				SceneEnumProc current_scene(ei->theScene, i * g_ticks_per_frame, gi);
				current_time = current_scene.time;

				// write out tags
				if (lTags.size())
				{
					for (std::list<ExportNode>::iterator tag_i = lTags.begin(); tag_i != lTags.end(); tag_i++)
					{
						INode *node	= current_scene[tag_i->i]->node;
						Matrix3	tm = node->GetObjTMAfterWSM(current_time);

						ExportState("Writing '%s' frame %i of %i", tag_i->name, i, g_total_frames);

						// tagname
						putChars(tag_i->name, 64, file);
						// origin, rotation matrix
						Point3 row = tm.GetRow(3);
						putFloat(row.x, file);
						putFloat(row.y, file);
						putFloat(row.z, file);
						row = tm.GetRow(0);
						putFloat(row.x, file);
						putFloat(row.y, file);
						putFloat(row.z, file);
						row = tm.GetRow(1);
						putFloat(row.x, file);
						putFloat(row.y, file);
						putFloat(row.z, file);
						row = tm.GetRow(2);
						putFloat(row.x, file);
						putFloat(row.y, file);
						putFloat(row.z, file);
					}
				}

				// write the center of mass tag_pivot which is avg of all objects's pivots
				if (g_tag_for_pivot)
				{
					ExportState("Writing 'tag_pivot' frame %i of %i", i, g_total_frames);

					// write the null data as tag_pivot need to be written after actual geometry
					// (it needs information on frame bound boxes to get proper blendings)
					putChars("tag_pivot", 64, file);
					putFloat(0, file);
					putFloat(0, file);
					putFloat(0, file);
					putFloat(1, file);
					putFloat(0, file);
					putFloat(0, file);
					putFloat(0, file);
					putFloat(1, file);
					putFloat(0, file);
					putFloat(0, file);
					putFloat(0, file);
					putFloat(1, file);
				}
			}
		}
	}

	// write the tag object offsets
	pos_current = ftell(file);
	fseek(file, pos_tagend, SEEK_SET);
	put32(pos_current, file);
	fseek(file, pos_current, SEEK_SET);

	// allocate the structs used to calculate tag_pivot
	std::vector<Point3> tag_pivot_origin;
	std::vector<double> tag_pivot_volume;
	if (g_tag_for_pivot)
	{
		tag_pivot_origin.resize(g_total_frames);
		tag_pivot_volume.resize(g_total_frames);
	}

	// mesh objects
	// for each mesh object write uv and frames
	SceneEnumProc scratch(ei->theScene, start_time, gi);
	ExportState("Writing %i meshes", (int)lMeshes.size());
	for (std::list<ExportNode>::iterator mesh_i = lMeshes.begin(); mesh_i != lMeshes.end(); mesh_i++)
	{
		bool needsDel;

		ExportState("Start mesh #%i", mesh_i);
		INode *node = checkScene[mesh_i->i]->node;
		Matrix3 tm	= node->GetObjTMAfterWSM(start_time);
		TriObject *tri = GetTriObjectFromNode(node, start_time, needsDel);
		if (!tri)
			continue;

		// get mesh, compute normals
		Mesh &mesh = tri->GetMesh();
		MeshNormalSpec *meshNormalSpec = mesh.GetSpecifiedNormals();
		if (meshNormalSpec)
		{
			if (!meshNormalSpec->GetNumFaces())
				meshNormalSpec = NULL;
			else
			{
				meshNormalSpec->SetParent(&mesh);
				meshNormalSpec->CheckNormals();
			}
		}
		mesh.checkNormals(TRUE);

		// fix lazy object names
		ExportState("Attempt to fix mesh name '%s'", mesh_i->name);
		char  meshname[64];
		size_t meshnamelen = min(63, strlen(mesh_i->name));
		memset(meshname, 0, 64);
		strncpy(meshname, mesh_i->name, meshnamelen);
		meshname[meshnamelen] = 0;
		if (!strncmp("Box", meshname, 3)    || !strncmp("Sphere", meshname, 6)  || !strncmp("Cylinder", meshname, 8) ||
            !strncmp("Torus", meshname, 5)  || !strncmp("Cone", meshname, 4)    || !strncmp("GeoSphere", meshname, 9) ||
			!strncmp("Tube", meshname, 4)   || !strncmp("Pyramid", meshname, 7) || !strncmp("Plane", meshname, 5) ||
			!strncmp("Teapot", meshname, 6) || !strncmp("Object", meshname, 6))
		{
name_conflict:
			lazynamesfixed++;
			if (lazynamesfixed == 1)
				strcpy(meshname, "base");
			else
				sprintf(meshname, "base%i", lazynamesfixed);

			// check if it's not used by another mesh
			for (std::list<ExportNode>::iterator m_i = lMeshes.begin(); m_i != lMeshes.end(); m_i++)
				if (!strncmp(m_i->name, meshname, strlen(meshname)))
					goto name_conflict;
			// approve name
			ExportWarning("Lazy object name '%s' (mesh renamed to '%s').", node->GetName(), meshname);
		}

		// special mesh check
		bool shadow_or_collision = false;
		if (g_mesh_special)
			  if (!strncmp("collision", meshname, 9) || !strncmp("shadow", meshname, 6))
				shadow_or_collision = true;

		// get material
		const char *shadername = NULL;
		Texmap *tex = 0;
		Mtl *mtl = 0;
		if (!shadow_or_collision)
		{
			mtl = node->GetMtl();
			if (mtl)
			{
				// check for multi-material
				if (mtl->IsMultiMtl())
				{
					// check if it's truly multi material
					// we do support multi-material with only one texture (some importers set it)
					bool multi_material = false;
					MtlID matId = mesh.faces[0].getMatID();
					for (i = 1; i < mesh.getNumFaces(); i++)
						if (mesh.faces[i].getMatID() != matId)
							multi_material = true;

					if (multi_material)
						if (g_mesh_multimaterials == MULTIMATERIALS_NONE)
							ExportWarning("Object '%s' is multimaterial and using multiple materials on its faces, that case is not yet supported (truncating to first submaterial).", node->GetName());
					
					// switch to submaterial
					mtl = mtl->GetSubMtl(matId);
				}

				// get shader from material if supplied
				char *materialname = GetChar(mtl->GetName());
				if (g_mesh_materialasshader && (strstr(materialname, "/") != NULL || strstr(materialname, "\\") != NULL))
					shadername = GetChar(mtl->GetName());
				else
				{
					// get texture
					tex = mtl->GetSubTexmap(ID_DI);
					if (tex)
					{
						if (tex->ClassID() == Class_ID(BMTEX_CLASS_ID, 0x00))
						{
							shadername = GetChar(((BitmapTex *)tex)->GetMapName());
							if (shadername == NULL || !shadername[0])
								ExportWarning("Object '%s' material '%s' has no bitmap.", tex->GetName(), node->GetName());
						}
						else
						{
							tex = NULL;
							ExportWarning("Object '%s' has material with wrong texture type (only Bitmap are supported).", node->GetName());
						}
					}
					else
						ExportWarning("Object '%s' has material but no texture.", node->GetName());
				}
			}
			else
				ExportWarning("Object '%s' has no material.", node->GetName());
		}

		long pos_meshstart = ftell(file);

		// surface object
		ExportState("Writing mesh '%s' header", meshname);
		putChars("IDP3", 4, file);
		putChars(meshname, 64, file);
		put32(0, file); // flags
		put32(g_total_frames, file);                          // framecount
		put32(1, file);                                       // skincount
		long pos_vertexnum = ftell(file); put32(0, file);     // vertexcount
		put32(mesh.getNumFaces(), file);                      // trianglecount
		long pos_trianglestart = ftell(file); put32(0, file); // start triangles
		put32(108, file);                                     // header size
		long pos_texvecstart = ftell(file); put32(0, file);   // texvecstart
		long pos_vertexstart = ftell(file); put32(16, file);  // vertexstart
		long pos_meshsize = ftell(file); put32(32, file);	  // meshsize

		// write out a single 'skin'
		ExportState("Writing mesh %s texture", meshname);
		if (shadow_or_collision)
			putChars(meshname, 64, file);
		else if (shadername) 
			putMaterial(shadername, mtl, tex, file);
		else
			putChars("noshader", 64, file);
		put32(0, file); // flags

		// build geometry
		ExportState("Building vertexes/triangles");
		std::vector<ExportVertex>vVertexes;
		std::vector<ExportTriangle>vTriangles;
		vVertexes.resize(mesh.getNumVerts());
		int vExtraVerts = mesh.getNumVerts();
		for (i = 0; i < mesh.getNumVerts(); i++)
		{
			vVertexes[i].vert = i;
			vVertexes[i].normalfilled = false;
			// todo: check for coincident verts
		}
		int vNumExtraVerts = 0;

		// check normals
		if (!mesh.normalsBuilt && !shadow_or_collision)
			ExportWarning("Object '%s' does not have normals contructed.", node->GetName());

		// get info for triangles
		const float normal_epsilon = 0.01f;
		vTriangles.resize(mesh.getNumFaces());
		for (i = 0; i < mesh.getNumFaces(); i++)
		{
			DWORD smGroup = mesh.faces[i].getSmGroup();
			ExportState("Mesh %s: checking normals for face %i of %i", meshname, i, mesh.getNumFaces());
			for (j = 0; j < 3; j++)
			{
				int vert = mesh.faces[i].getVert(j);
				vTriangles[i].e[j] = vert;
				// find a right normal for this vertex and save its 'address'
				int vni;
				Point3 vn;
				if (!mesh.normalsBuilt || shadow_or_collision)
				{
					vn.Set(0, 0, 0);
					vni = 0;
				}
				else
				{
					int numNormals;
					RVertex *rv = mesh.getRVertPtr(vert);
					if (meshNormalSpec)
					{  
						ExportState("face %i vert %i have normal specified", i, j);
						// mesh have explicit normals (i.e. Edit Normals modifier)
						vn = meshNormalSpec->GetNormal(i, j);
						vni = meshNormalSpec->GetNormalIndex(i, j);
					}
					else if (rv && rv->rFlags & SPECIFIED_NORMAL)
					{
						ExportState("face %i vert %i have SPECIFIED_NORMAL flag", i, j);
						// SPECIFIED_NORMAL flag
						vn = rv->rn.getNormal();
						vni = 0;
					}
					else if (rv && (numNormals = rv->rFlags & NORCT_MASK) && smGroup)
					{
						// If there is only one vertex is found in the rn member.
						if (numNormals == 1)
						{
							ExportState("face %i vert %i have solid smooth group", i, j);
							vn = rv->rn.getNormal();
							vni = 0;
							
						}
						else
						{
							ExportState("face %i vert %i have mixed smoothing groups", i, j);
							// If two or more vertices are there you need to step through them
							// and find the vertex with the same smoothing group as the current face.
							// You will find multiple normals in the ern member.
							for (int k = 0; k < numNormals; k++)
							{
								if (rv->ern[k].getSmGroup() & smGroup)
								{
									vn = rv->ern[k].getNormal();
									vni = 1 + k;
								}
							}
						}
					}
					else
					{
						ExportState("face %i vert %i flat shaded", i, j);
						// Get the normal from the Face if no smoothing groups are there
						vn = mesh.getFaceNormal(i);
						vni = 0 - (i + 1);
					}
				}

				// subdivide to get all normals right
				if (!vVertexes[vert].normalfilled)
				{
					vVertexes[vert].normal = vn;
					vVertexes[vert].normalindex = vni;
					vVertexes[vert].normalfilled = true;
				}
				else if ((vVertexes[vert].normal - vn).Length() >= normal_epsilon)
				{
					// current vertex not matching normal - it was already filled by different smoothing group
					// find a vert in extra verts in case it was already created
					bool vert_found = false;
					for (int ev = vExtraVerts; ev < (int)vVertexes.size(); ev++)
					{
						if (vVertexes[ev].vert == vert && (vVertexes[ev].normal - vn).Length() < normal_epsilon)
						{
							vert_found = true;
							vTriangles[i].e[j] = ev;
							break;
						}
					}
					// we havent found a vertex, create new
					if (!vert_found)
					{
						ExportVertex NewVert;
						NewVert.vert = vVertexes[vert].vert;
						NewVert.normal = vn;
						NewVert.normalindex = vni;
						NewVert.normalfilled = true;
						vTriangles[i].e[j] = (int)vVertexes.size();
						vVertexes.push_back(NewVert);
						vNumExtraVerts++;
					}
				}
			}
		}
		int vNumExtraVertsForSmoothGroups = vNumExtraVerts;

		// generate UV map
		// VorteX: use direct maps reading since getNumTVerts()/getTVert is deprecated
		//  max sets two default mesh maps: 0 - vertex color, 1 : UVW, 2 & up are custom ones
		ExportState("Building UV map");
		std::vector<ExportUV>vUVMap;
		vUVMap.resize(vVertexes.size());
		int meshMap = 1;
		if (!mesh.mapSupport(meshMap) || !mesh.getNumMapVerts(meshMap) || shadow_or_collision)
		{
			for (i = 0; i < mesh.getNumVerts(); i++)
			{
				vUVMap[i].u = 0.5;
				vUVMap[i].v = 0.5;
			}
			if (!shadow_or_collision)
				ExportWarning("No UV mapping was found on object '%s'.", node->GetName());
		}
		else
		{
			UVVert *meshUV = mesh.mapVerts(meshMap);
			for (i = 0; i < (int)vTriangles.size(); i++)
			{
				ExportState("Mesh %s: converting tvert for face %i of %i", meshname, i, (int)vTriangles.size());
				// for 3 face vertexes
				for (j = 0; j < 3; j++)
				{
					int vert = vTriangles[i].e[j];
					int tv = mesh.tvFace[i].t[j];
					UVVert &UV = meshUV[tv];

					if (!vUVMap[vert].filled)
					{
						// fill uvMap vertex
						vUVMap[vert].u = UV.x;
						vUVMap[vert].v = UV.y;
						vUVMap[vert].filled = true;
						vUVMap[vert].tvert = tv;
					}
					else if (tv != vUVMap[vert].tvert)
					{
						// uvMap slot for this vertex has been filled
						// we should arrange triangle to other vertex, which not filled and having same shading and uv
						// check if any of the extra vertices can fit
						bool vert_found = false;
						for (int ev = vExtraVerts; ev < (int)vVertexes.size(); ev++)
						{
							if (vVertexes[ev].vert == vert && vUVMap[vert].u == UV.x &&vUVMap[vert].v == UV.y  && (vVertexes[ev].normal - vVertexes[vert].normal).Length() < normal_epsilon)
							{
								vert_found = true;
								vTriangles[i].e[j] = vVertexes[ev].vert;
								break;
							}
						}
						if (!vert_found)
						{
							// create new vert
							ExportVertex NewVert;
							NewVert.vert = vVertexes[vert].vert;
							NewVert.normal = vVertexes[vert].normal;
							NewVert.normalindex = vVertexes[vert].normalindex;
							NewVert.normalfilled = vVertexes[vert].normalfilled;
							vTriangles[i].e[j] = (int)vVertexes.size();
							vVertexes.push_back(NewVert);
							vNumExtraVerts++;
							// create new TVert
							ExportUV newUV;
							newUV.filled = true;
							newUV.u = UV.x;
							newUV.v = UV.y;
							newUV.tvert = tv;
							vUVMap.push_back(newUV);
						}
					}
				}
			}
		}
		int vNumExtraVertsForUV = (vNumExtraVerts - vNumExtraVertsForSmoothGroups);

		// print some debug stats
		ExportDebug("    mesh %s: %i vertexes +%i %s +%i UV, %i triangles", meshname, ((int)vVertexes.size() - vNumExtraVerts), vNumExtraVertsForSmoothGroups, meshNormalSpec ? "EditNormals" : "SmoothGroups", vNumExtraVertsForUV, (int)vTriangles.size());

		// fill in triangle start
		pos_current = ftell(file);
		fseek(file, pos_trianglestart, SEEK_SET);
		put32(pos_current - pos_meshstart, file);
		fseek(file, pos_current, SEEK_SET);

		// detect if object have negative scale (mirrored)
		// in this canse we should rearrange triangles counterclockwise
		// so stuff will not be inverted
		ExportState("Mesh %s: writing %i triangles", meshname, (int)vTriangles.size());
		if (DotProd(CrossProd(tm.GetRow(0), tm.GetRow(1)), tm.GetRow(2)) < 0.0)
		{
			ExportWarning("Object '%s' is mirrored (having negative scale on it's transformation)", node->GetName());
			for (i = 0; i < (int)vTriangles.size(); i++)
			{
				put32(vTriangles[i].b, file);	// vertex index
				put32(vTriangles[i].c, file);	// for 3 vertices
				put32(vTriangles[i].a, file);	// of triangle
			}
		}
		else
		{
			for (i = 0; i < (int)vTriangles.size(); i++)
			{
				put32(vTriangles[i].a, file);	// vertex index
				put32(vTriangles[i].c, file);	// for 3 vertices
				put32(vTriangles[i].b, file);	// of triangle
			}
		}

		// fill in texvecstart
		// write out UV mapping coords.
		ExportState("Mesh %s: writing %i UV vertexes", meshname, (int)vUVMap.size());
		pos_current = ftell(file);
		fseek(file, pos_texvecstart, SEEK_SET);
		put32(pos_current - pos_meshstart, file);
		fseek(file, pos_current, SEEK_SET);
		for (i = 0; i < (int)vUVMap.size(); i++)
		{
			putFloat(vUVMap[i].u, file); // texture coord u,v
			putFloat(1.0f - vUVMap[i].v, file);	// for vertex
		}
		vUVMap.clear();

		// fill in vertexstart
		pos_current = ftell(file);
		fseek(file, pos_vertexstart, SEEK_SET);
		put32(pos_current - pos_meshstart, file);
		fseek(file, pos_current, SEEK_SET);

		// fill in vertexnum
		pos_current = ftell(file);
		fseek(file, pos_vertexnum, SEEK_SET);
		put32((int)vVertexes.size(), file);
		fseek(file, pos_current, SEEK_SET);

		// write out for each frame the position of each vertex
		long current_frame = 0;
		ExportState("Mesh %s: writing %i frames", meshname, g_total_frames);
		for (range_i = g_frame_ranges.begin(); range_i != g_frame_ranges.end(); range_i++)
		{
			for (i = (*range_i).first; i <= (int)(*range_i).last; i++, current_frame++)
			{
				bool _needsDel;

				// get triobject for current frame
				SceneEnumProc current_scene(ei->theScene, i * g_ticks_per_frame, gi);
				current_time = current_scene.time;
				INode *_node = current_scene[mesh_i->i]->node;
				TriObject *_tri	= GetTriObjectFromNode(_node, current_time, _needsDel);
				if (!_tri)
					continue;

				// get mesh, compute normals
				Mesh &_mesh	= _tri->GetMesh();
				MeshNormalSpec *_meshNormalSpec = _mesh.GetSpecifiedNormals();
				if (_meshNormalSpec)
				{
					if (!_meshNormalSpec->GetNumFaces())
						_meshNormalSpec = NULL;
					else
					{
						_meshNormalSpec->SetParent(&_mesh);
						_meshNormalSpec->CheckNormals();
					}
				}
				_mesh.checkNormals(TRUE);

				// get transformations for current frame
				Matrix3 _tm	= _node->GetObjTMAfterWSM(current_time);

				ExportState("Mesh %s: writing frame %i of %i", meshname, current_frame, g_total_frames);

				Point3 BoxMin(0, 0, 0);
				Point3 BoxMax(0, 0, 0);
				for (j = 0; j < (int)vVertexes.size(); j++) // number of vertices
				{
					ExportState("Mesh %s: transform vertex %i of %i", meshname, j, (int)vVertexes.size());

					int vert = vVertexes[j].vert;
					Point3 &v = _tm.PointTransform(_mesh.getVert(vert));
					
					// populate bbox data
					if (!shadow_or_collision)
					{
						BoxMin.x = min(BoxMin.x, v.x);
						BoxMin.y = min(BoxMin.y, v.y);
						BoxMin.z = min(BoxMin.z, v.z);
						BoxMax.x = max(BoxMax.x, v.x);
						BoxMax.y = max(BoxMax.y, v.y);
						BoxMax.z = max(BoxMax.z, v.z);
					}

					// write vertex
					double f;
					f = v.x * 64.0f; if (f < -32768.0) f = -32768.0; if (f > 32767.0) f = 32767.0; put16((short)f, file);
					f = v.y * 64.0f; if (f < -32768.0) f = -32768.0; if (f > 32767.0) f = 32767.0; put16((short)f, file);
					f = v.z * 64.0f; if (f < -32768.0) f = -32768.0; if (f > 32767.0) f = 32767.0; put16((short)f, file);

					// get normal
					ExportState("Mesh %s: transform vertex normal %i of %i", meshname, j, (int)vVertexes.size());
					Point3 n;
					if (_meshNormalSpec) // mesh have explicit normals (i.e. Edit Normals modifier)
						n = _meshNormalSpec->Normal(vVertexes[j].normalindex);
					else if (!vVertexes[j].normalfilled || !_mesh.normalsBuilt)
						n = _mesh.getNormal(vert);
					else
					{
						RVertex *rv = _mesh.getRVertPtr(vert);
						if (vVertexes[j].normalindex < 0)
							n = _mesh.getFaceNormal((0 - vVertexes[j].normalindex) - 1);
						else if (vVertexes[j].normalindex == 0)
							n = rv->rn.getNormal();
						else 
							n = rv->ern[vVertexes[j].normalindex - 1].getNormal();
					}

					// transform normal
					Point3 &nt = _tm.VectorTransform(n).Normalize();

					// encode a normal vector into a 16-bit latitude-longitude value
					double lng = acos(nt.z) * 255 / (2 * pi);
					double lat = atan2(nt.y, nt.x) * 255 / (2 * pi);
					put16((((int)lat & 0xFF) << 8) | ((int)lng & 0xFF), file);
				}

				// blend the pivot positions for tag_pivot using mesh's volumes for blending power
				if (g_tag_for_pivot && !shadow_or_collision)
				{
					ExportState("Mesh %s: writing tag_pivot", meshname);

					Point3 Size = BoxMax - BoxMin;
					double BoxVolume = pow(Size.x * Size.y * Size.z, 0.333f);

					// blend matrices
					float blend = (float)(BoxVolume / (BoxVolume + tag_pivot_volume[current_frame]));
					float iblend = 1 - blend;
					tag_pivot_volume[current_frame]   = tag_pivot_volume[current_frame] + BoxVolume;
					Point3 row = _tm.GetRow(3) - _node->GetObjOffsetPos();
					tag_pivot_origin[current_frame].x = tag_pivot_origin[current_frame].x * iblend + row.x * blend;
					tag_pivot_origin[current_frame].y = tag_pivot_origin[current_frame].y * iblend + row.y * blend;
					tag_pivot_origin[current_frame].z = tag_pivot_origin[current_frame].z * iblend + row.z * blend;
				}

				// populate bbox data for frames
				lFrameBBoxMin[current_frame].x = min(lFrameBBoxMin[current_frame].x, BoxMin.x);
				lFrameBBoxMin[current_frame].y = min(lFrameBBoxMin[current_frame].y, BoxMin.y);
				lFrameBBoxMin[current_frame].z = min(lFrameBBoxMin[current_frame].z, BoxMin.z);
				lFrameBBoxMax[current_frame].x = max(lFrameBBoxMax[current_frame].x, BoxMax.x);
				lFrameBBoxMax[current_frame].y = max(lFrameBBoxMax[current_frame].y, BoxMax.y);
				lFrameBBoxMax[current_frame].z = max(lFrameBBoxMax[current_frame].z, BoxMax.z);

				// delete the working object, if necessary.
				if (_needsDel)
					delete _tri;
			}
		}

		// delete if necessary
		if (needsDel)
			delete tri;

		// fill in meshsize
		pos_current = ftell(file);
		fseek(file, pos_meshsize, SEEK_SET);
		put32(pos_current - pos_meshstart, file);
		fseek(file, pos_current, SEEK_SET);  

		// reset back to first frame
		SceneEnumProc scratch(ei->theScene, start_time, gi);
		totalTris += (long)vTriangles.size();
		totalVerts += (long)vVertexes.size();
		vTriangles.clear();
		vVertexes.clear();
	}

	// write tag_pivot
	ExportState("Writing tag_pivot positions");
	if (g_tag_for_pivot)
	{
		pos_current = ftell(file);
		long current_frame = 0;
		for (range_i = g_frame_ranges.begin(); range_i != g_frame_ranges.end(); range_i++)
		{
			for (i = (*range_i).first; i <= (int)(*range_i).last; i++, current_frame++)
			{
				fseek(file, pos_tags + totalTags*112*current_frame + (int)lTags.size()*112 + 64, SEEK_SET);
				// origin
				putFloat(tag_pivot_origin[current_frame].x, file);
				putFloat(tag_pivot_origin[current_frame].y, file);
				putFloat(tag_pivot_origin[current_frame].z, file);
			}
		}
		fseek(file, pos_current, SEEK_SET);
	}
	tag_pivot_volume.clear();
	tag_pivot_origin.clear();

	// write frame data
	ExportState("Writing culling info");
	long current_frame = 0;
	pos_current = ftell(file);
	for (range_i = g_frame_ranges.begin(); range_i != g_frame_ranges.end(); range_i++)
	{
		for (i = (*range_i).first; i <= (int)(*range_i).last; i++, current_frame++)
		{
			fseek(file, pos_framestart + current_frame*56, SEEK_SET);
			putFloat(lFrameBBoxMin[current_frame].x, file);	// bbox min vector
			putFloat(lFrameBBoxMin[current_frame].y, file);
			putFloat(lFrameBBoxMin[current_frame].z, file);	
			putFloat(lFrameBBoxMax[current_frame].x, file); // bbox max vector
			putFloat(lFrameBBoxMax[current_frame].y, file);
			putFloat(lFrameBBoxMax[current_frame].z, file);
			putFloat(0, file); // local origin (usually 0 0 0)
			putFloat(0, file);
			putFloat(0, file);
			putFloat(max(lFrameBBoxMin[current_frame].Length(), lFrameBBoxMax[current_frame].Length()) , file); // radius of bounding sphere
		}
	}
	fseek(file, pos_current, SEEK_SET);
	lFrameBBoxMin.clear();
	lFrameBBoxMax.clear();

	// fill in filesize
	pos_current = ftell(file);
	fseek(file, pos_filesize, SEEK_SET);
	put32(pos_current, file);
	fseek(file, pos_current, SEEK_SET);

	fclose(file);

	ExportDebug("    total: %i vertexes, %i triangles", totalVerts, totalTris);

	return TRUE;
}