Esempio n. 1
0
void Matrix::AxisRotate(const Vector3F& mvAxis, float angle)
{
	Vector3F mvNormalizedAxis = mvAxis;
	mvNormalizedAxis.Normalize();

	float c = cosf(angle);
	float s = sinf(angle);
	float x = mvNormalizedAxis.x, y = mvNormalizedAxis.y, z = mvNormalizedAxis.z;

	_11 = x * x * (1 - c) + c;
	_21 = x * y * (1 - c) - (z * s);
	_31 = x * z * (1 - c) + (y * s);
	_41 = 0;

	_12 = y * x * (1 - c) + (z * s);
	_22 = y * y * (1 - c) + c;
	_32 = y * z * (1 - c) - (x * s);
	_42 = 0;

	_13 = z * x * (1 - c) - (y * s);
	_23 = z * y * (1 - c) + (x * s);
	_33 = z * z * (1 - c) + c;
	_43 = 0;

	_14 = 0;
	_24 = 0;
	_34 = 0;
	_44 = 1;
}
Esempio n. 2
0
void Plane::ProjectToPlane( const Vector3F& _vector, Vector3F* projected ) const
{
	// A || B = B x (Ax B / |B|) / |B|
	Vector3F vector = _vector;
	vector.Normalize();

	Vector3F AcB;
	CrossProduct( vector, n, &AcB );
	CrossProduct( n, AcB, projected );
	projected->Normalize();


	/*
	GLASSERT( Equal( n.Length(), 1.0f, EPSILON ) );

	Vector3F vector = _vector;
	vector.Normalize();

	Vector3F pn;	
	CrossProduct( n, vector, &pn );
	CrossProduct( pn, n, projected );
	*/
	
	#ifdef DEBUG
	{
		float len = projected->Length();
		GLASSERT( Equal( len, 1.0f, EPSILON ) );

		Vector3F test = { 0.0f, 0.0f, Z( 0.0f, 0.0f ) };
		test = test + (*projected);
		float z = Z( projected->x, projected->y );
		GLASSERT( Equal( test.z, z, 0.0001f ) );
	}
	#endif
}
Esempio n. 3
0
void Lilith3D::IntersectRayFromScreen( int x, int y, int flags, LilithObjectList* vec )
{
	double p0x, p0y, p0z, p1x, p1y, p1z;

	double modelView[ 16 ];
	glGetDoublev( GL_MODELVIEW_MATRIX, modelView );
	double projection[ 16 ];
	glGetDoublev( GL_PROJECTION_MATRIX, projection );
	int viewport[ 4 ];
	glGetIntegerv( GL_VIEWPORT, viewport );

	gluUnProject( (double) x, (double) ( viewport[3]-y ), 0,
							  modelView, projection, viewport,
							  &p0x, &p0y, &p0z );

	gluUnProject( (double) x, (double) ( viewport[3]-y ), 1,
							  modelView, projection, viewport,
							  &p1x, &p1y, &p1z );

	//GLOUTPUT( "ret0=%d ret1=%d\n", ret0, ret1 );

	Vector3F point = { (float) p0x, (float) p0y, (float) p0z };
	Vector3F dir   = { (float) ( p1x-p0x ), (float) ( p1y-p0y ), float( p1z-p0z ) };

	dir.Normalize();
	Ray ray;
	ray.origin = point;
	ray.direction = dir;
	ray.length = FAR_PLANE_DISTANCE;

	IntersectRay( ray, flags, vec );
}
Esempio n. 4
0
void Scene::MoveImpl(float dx, float dy, Engine* engine)
{
    Camera& camera = engine->camera;
    const Vector3F* dir = camera.EyeDir3();

    Vector3F right = dir[2];
    Vector3F forward = dir[0];
    right.y = 0;
    forward.y = 0;
    right.Normalize();
    forward.Normalize();

    Vector3F pos = camera.PosWC();
    pos = pos + dx*right + dy*forward;

    camera.SetPosWC(pos);
}
Esempio n. 5
0
bool CPointGravityEntity::DoForceEnvelopeCollision(CBaseEntity * pCollidingEntity)
{
	if (!m_pFoceEnvelopeBV) return 0;
	
	Vector3F cp, cn;
	if (m_pFoceEnvelopeBV->Intersect(pCollidingEntity->GetContactBV(), cp, cn)) {
		//Assume the the colliding entity is a CBSphere
		Vector3F dir = ((CBSphere*)m_pContactBV)->PosW() -
			((CBSphere*)pCollidingEntity->GetContactBV())->PosW();

		if(gravitySign != pCollidingEntity->gravitySign) {
			pCollidingEntity->AddToForce(m_fGravity*dir.Normalize());
		} else {
			pCollidingEntity->AddToForce(-m_fGravity*dir.Normalize());
		}
	}

	return 1;
}
Esempio n. 6
0
//----------------------------------------------------------------------------
void Player::UpdateShot(Double deltaTime, const Vector2F& rCursorPosition)
{
	// If not shooting, exit
	if (mShoot < 1)
	{
		Float alpha = mShoot < 0 ? 0 : mShoot;
		mspMuzzleflashMaterialState->Ambient.A() = alpha;

		mspMuzzleflashLight->Color = mMuzzleflashLightColor * alpha;

		mShoot -= static_cast<Float>(deltaTime)*10.0F;
		return;
	}

	mShoot -= static_cast<Float>(deltaTime)*10.0F;

	Vector3F origin;
	Vector3F direction;
	mspCamera->GetPickRay(rCursorPosition, origin, direction);
	direction.Normalize();
	btVector3 rayStart = PhysicsWorld::Convert(origin);
	btVector3 rayEnd = rayStart + PhysicsWorld::Convert(direction * mMaximumShootingDistance);

	btCollisionWorld::ClosestRayResultCallback hitCallback(rayStart, rayEnd);

	if (!mpPhysicsWorld)
	{
		return;
	}

	mpPhysicsWorld->Get()->rayTest(rayStart, rayEnd, hitCallback);
	if (hitCallback.hasHit()) 
	{
		btCollisionObject* pColObj = hitCallback.m_collisionObject;
 		if (!pColObj->isStaticOrKinematicObject())
 		{
			btRigidBody* pRigidBody = btRigidBody::upcast(pColObj);
			if (pRigidBody)
			{
				btVector3 y = hitCallback.m_hitPointWorld - pRigidBody->getCenterOfMassPosition();
				pColObj->activate(true);
				pRigidBody->applyImpulse(PhysicsWorld::Convert(direction)*7.5F, y);
			}
 		}

		ProbeRobot* pProbeRobotController = static_cast<ProbeRobot*>(hitCallback.m_collisionObject->getUserPointer());
		if (pProbeRobotController) 
		{
			pProbeRobotController->TakeDamage(5.0F);
		}
	}
}
Esempio n. 7
0
//----------------------------------------------------------------------------
void Player::UpdateGun(Double deltaTime)
{
	Float width = Application::GetApplication()->GetWidthF();
	Float height = Application::GetApplication()->GetHeightF();
	Vector2F cursorPosition(mLookAt.X()*2/width, mLookAt.Y()*2/height);

	Matrix4F projectionMatrix = mspCamera->GetProjectionMatrix();
	Vector3F pickDirection(-cursorPosition.X() / projectionMatrix(0, 0),
		cursorPosition.Y() / projectionMatrix(1, 1), 1);

	Vector3F tempUp(0, 1, 0);
	Vector3F right = pickDirection.Cross(tempUp);
	Vector3F up = right.Cross(pickDirection);
	pickDirection.Normalize();
	right.Normalize();
	up.Normalize();
	Matrix3F mat(-right, up, pickDirection, true);

	// to reduce the Wiimote's tilt jitter we average the last few sampled
	// tilt values
	Float tilt = 0;
	if (mRolls.GetQuantity() > 0)
	{
		for (UInt i = 0; i < mRolls.GetQuantity(); i++)
		{
			tilt += mRolls[i];
		}

		tilt /= mRolls.GetQuantity();
	}

	Matrix3F roll(Vector3F(0, 0, 1), tilt);
	mpGun->Local.SetRotate(mat * roll);

	UpdateShot(deltaTime, cursorPosition);
}
void BoltEffect::Draw( const Vector3F* eyeDir )
{
	if (    d1 > d0
		 && !done ) 
	{
		Vector3F q0 = p0 + normal*d0;
		Vector3F q1 = p0 + normal*d1;

		Vector3F right;
		CrossProduct( eyeDir[Camera::NORMAL], q1-q0, &right );
		right.Normalize();

		float halfWidth = width*0.5f;

		// FIXME: hardcoded texture coordinates
		static const float tx = 0.50f;
		static const float ty = 0.0f;
		PTVertex pV[4];

		pV[0].pos = q0 - right*halfWidth;
		pV[0].tex.Set( tx+0.0f, ty+0.0f );
			
		pV[1].pos = q0 + right*halfWidth;
		pV[1].tex.Set( tx+0.25f, ty+0.0f );

		pV[2].pos = q1 + right*halfWidth;
		pV[2].tex.Set( tx+0.25f, ty+0.25f );

		pV[3].pos = q1 - right*halfWidth;
		pV[3].tex.Set( tx+0.0f, ty+0.25f );

		static const U16 index[6] = { 0, 1, 2, 0, 2, 3 };

		QuadParticleShader shader;
		shader.SetTexture0( TextureManager::Instance()->GetTexture( "particleQuad" ) );

		GPUStream stream;
		stream.stride = sizeof( pV[0] );
		stream.nPos = 3;
		stream.posOffset = 0;
		stream.nTexture0 = 2;
		stream.texture0Offset = 12;
		shader.SetColor( color );

		shader.SetStream( stream, pV, 6, index );
		shader.Draw();
	}
}
Vector3F ArcBallCameraController::Direction() const
{
	//R v R' where v = (0,0,-1,0)
	//The equation can be reduced because we know the following things:
	//  1.  We're using unit quaternions
	//  2.  The initial aspect does not change
	//The reduced form of the same equation follows
	Vector3F dir = Vector3F::Zero();
	dir.x = -2.0f *
		((m_ArcBallOrientation.x * m_ArcBallOrientation.z) + (m_ArcBallOrientation.w * m_ArcBallOrientation.y));
	dir.y = 2.0f *
		((m_ArcBallOrientation.w * m_ArcBallOrientation.x) - (m_ArcBallOrientation.y * m_ArcBallOrientation.z));
	dir.z =
		((m_ArcBallOrientation.x * m_ArcBallOrientation.x) + (m_ArcBallOrientation.y * m_ArcBallOrientation.y)) -
		((m_ArcBallOrientation.z * m_ArcBallOrientation.z) + (m_ArcBallOrientation.w * m_ArcBallOrientation.w));
	dir.Normalize();
	return dir;
}
/// <summary>
/// Sets up a quaternion & position from vector camera components
/// and oriented the camera up
/// </summary>
/// <param name="eye">The camera position</param>
/// <param name="lookAt">The camera's look-at point</param>
/// <param name="up"></param>
void ArcBallCameraController::SetCamera(Vector3F position, Vector3F target, Vector3F up)
{
	m_bRecomputeViewMatrix = true;

	//Create a look at matrix, to simplify matters a bit
	Matrix4 temp;
	temp.LookAt(position, target, up);

	//invert the matrix, since we're determining the
	//orientation from the rotation matrix in RH coords
	temp.Invert();

	//set the position
	m_Target = target;

	//set distance
	m_fDistance = (target - position).Length();

	//create the new aspect from the look-at matrix
	m_ArcBallOrientation.FromMatrix(temp);

	//When setting a new eye-view direction 
	//in one of the gamble-locked modes, the yaw and
	//pitch gimble must be calculated.

	//first, get the direction projected on the x/z plne
	Vector3F dir = Direction();
	dir.y = 0.0f;
	if (dir.Length() == 0.0f)
	{
		dir = Vector3F::Forward();
	}
	dir.Normalize();

	//find the yaw of the direction on the x/z plane
	//and use the sign of the x-component since we have 360 degrees
	//of freedom
	m_fArcBallYaw = (acosf(-dir.z) * Sign(dir.x));

	//Get the pitch from the angle formed by the Up vector and the 
	//the forward direction, then subtracting Pi / 2, since 
	//we pitch is zero at Forward, not Up.
	m_fArcBallPitch = -(acosf(Vector3F::Dot(Vector3F::Up(), Direction())) - MATH_PI_DIV_2);
}
Esempio n. 11
0
Bolt* LumosChitBag::NewBolt(	const Vector3F& pos,
								const Vector3F& _dir,
								int effectFlags,
								int chitID,
								float damage,
								float speed,
								bool trail )
{
	GLASSERT(pos.y > 0);
	GLASSERT(pos.y < 4);

	Bolt* bolt = ChitBag::NewBolt();

	Vector3F dir = _dir;
	dir.Normalize();
	GLASSERT(Equal(dir.Length(), 1.0f));

	bolt->head = pos + dir * 0.5f;
	bolt->len = 0.5f;
	bolt->dir = dir;

	const Game::Palette* palette = Game::GetMainPalette();

	switch( effectFlags & (GameItem::EFFECT_FIRE | GameItem::EFFECT_SHOCK) ) {
	case 0:													bolt->color = palette->Get4F( 1, PAL_GREEN );	break;
	case GameItem::EFFECT_FIRE:								bolt->color = palette->Get4F( 1, PAL_RED );		break;
	case GameItem::EFFECT_SHOCK:							bolt->color = palette->Get4F( 1, PAL_BLUE );	break;
	case GameItem::EFFECT_FIRE | GameItem::EFFECT_SHOCK:	bolt->color = palette->Get4F( 1, PAL_PURPLE );	break;
	default:
		GLASSERT( 0 );
		break;
	}

	bolt->chitID = chitID;
	bolt->damage = damage;
	bolt->effect = effectFlags;
	bolt->particle  = trail;
	bolt->speed = speed;
	bolt->frameCount = 0;

	return bolt;
}
Esempio n. 12
0
void XenoAudio::SetChannelPos(int i)
{
	if (!audioOn) return;

	if (sounds[i].pos.IsZero()) {
		Mix_SetPosition(i, 0, 0);
	}
	else {
		Vector3F delta = sounds[i].pos - listenerPos;
		float len = delta.Length();
		float df = len / MAX_DISTANCE;
		int d = LRintf(df*255.0f);
		d = Clamp(d, 0, 255);

		if (delta.LengthSquared() > 0.001f) {
			delta.Normalize();
		}
		else {
			delta.Set(0, 0, -1);
		}

		static const Vector3F UP = { 0, 1, 0 };
		Vector3F listenerRight = CrossProduct(listenerDir, UP);

		float dotFront = DotProduct(delta, listenerDir);
		float dotRight = DotProduct(delta, listenerRight);

		// 0 is north, 90 is east, etc. WTF.
		float rad = atan2(dotRight, dotFront);
		float deg = rad * 180.0f / PI;
		int degi = int(deg);
		if (degi < 0) degi += 360;

		int result = Mix_SetPosition(i, degi, d);
		GLASSERT(result != 0);
		(void)result;
	}
}
Esempio n. 13
0
BridgeMorph::BridgeMorph( float width, float originX, float originY, float destX, float destY ) : TerrainMorph()
{
	float a = ( destX - originX );
	float b = ( destY - originY );
	float half = width / 2.0f;
	Vector3F at;
	bool inWater;
	Lilith3D* lilith = Lilith3D::Instance();

	float originH = lilith->GetTerrainMesh()->CalcHeight( originX, originY, &at, &inWater );
	float destH   = lilith->GetTerrainMesh()->CalcHeight( destX, destY, &at, &inWater );

	Vector3F widthNormal = { -b, a };
	widthNormal.Normalize();

	LineLoop loop;
	loop.AddAtEnd( new LineNode( originX + widthNormal.x*half, originY + widthNormal.y*half, originH ) );
	loop.AddAtEnd( new LineNode( originX - widthNormal.x*half, originY - widthNormal.y*half, originH ) );
	loop.AddAtEnd( new LineNode( destX - widthNormal.x*half,   destY - widthNormal.y*half, destH ) );
	loop.AddAtEnd( new LineNode( destX + widthNormal.x*half,   destY + widthNormal.y*half, destH ) );
	GenerateHeightMap( &loop, 1.0f, 1.0f, &maxDelta );
	startTime = Lilith3D::GetTimeClock()->Msec();
}
Esempio n. 14
0
int grinliz::IntersectRayAllAABB(	const Vector3F& origin, const Vector3F& dir,
                                    const Rectangle3F& aabb,
                                    int* inTest,
                                    Vector3F* inIntersect,
                                    int *outTest,
                                    Vector3F* outIntersect )
{
    // Could be more efficient, but looking for simplicity as I write this.
    float t;

    // First check if we hit the box at all, and that establishes in test.
    *inTest = IntersectRayAABB( origin, dir, aabb, inIntersect, &t );
    if ( *inTest == grinliz::REJECT ) {
        *outTest = grinliz::REJECT;
        return grinliz::REJECT;
    }

    // Could get fancy and intersect from the inside. But that's hard, so I'll run
    // the ray out and shoot it backwards.
    float deltaLen = aabb.SizeX() + aabb.SizeY() + aabb.SizeZ();

    Vector3F dirNormal = dir;
    dirNormal.Normalize();
    Vector3F invOrigin = origin + dir*deltaLen;
    Vector3F invDir = -dirNormal;

    *outTest = IntersectRayAABB( invOrigin, invDir, aabb, outIntersect, &t );

    GLASSERT( *outTest != grinliz::INSIDE );	// bad algorith.
    if ( *outTest == grinliz::REJECT ) {
        // some strange floating point thing. Hit a corner. Reject everything.
        *inTest = grinliz::REJECT;
        return grinliz::REJECT;
    }
    return grinliz::INTERSECT;
}
Esempio n. 15
0
int GravParticles::PrepareStreamOut()
{
	// normal:		from the particle to the camera.
	// right:		the "to the right" vectior
	// up:			an up vector,
	// upPrime:		a vector at camera up

	// right   = up X normal
	// upPrime = normal X right

	const Vector3F up = { 0.0f, 0.0f, 1.0f };
	Lilith3D* lilith = Lilith3D::Instance();
	Vector3F camera = lilith->GetCamera()->Eye();

	Vector3F right;
	Vector3F upPrime;
	//Color4F c;

	int index = 0;
	for( int i=0; i<numParticle; ++i, index += 4 )
	{
		Vector3F normal = { 	camera.x - particle[i].loc.x,
								camera.y - particle[i].loc.y,
								camera.z - particle[i].loc.z	};
		normal.Normalize();

		CrossProduct( up, normal, &right );
		CrossProduct( normal, right, &upPrime );

		float halfSize = particle[i].size / 2.0f;

		GLASSERT( InRange( color[i*4].a, 0.0f, 1.0f ) );

//		c.r = particle[i].color.r; 
//		c.g = particle[i].color.g; 
//		c.b = particle[i].color.b; 
//		c.a = particle[i].energy;
//
//		color[ index+0 ] = color[ index+1 ] = color[ index+2 ] = color[ index+3 ] = c;
	
//		texUV[ index+0 ].x = 0.0f;
//		texUV[ index+0 ].y = 0.0f;
		quads[ index+0 ].x = particle[i].loc.x - halfSize*right.x - halfSize*upPrime.x;
		quads[ index+0 ].y = particle[i].loc.y - halfSize*right.y - halfSize*upPrime.y;
		quads[ index+0 ].z = particle[i].loc.z - halfSize*right.z - halfSize*upPrime.z;

//		texUV[ index+1 ].x = 1.0f;
//		texUV[ index+1 ].y = 0.0f;
		quads[ index+1 ].x = particle[i].loc.x + halfSize*right.x - halfSize*upPrime.x;
		quads[ index+1 ].y = particle[i].loc.y + halfSize*right.y - halfSize*upPrime.y;
		quads[ index+1 ].z = particle[i].loc.z + halfSize*right.z - halfSize*upPrime.z;

//		texUV[ index+2 ].x = 1.0f;
//		texUV[ index+2 ].y = 1.0f;
		quads[ index+2 ].x = particle[i].loc.x + halfSize*right.x + halfSize*upPrime.x;
		quads[ index+2 ].y = particle[i].loc.y + halfSize*right.y + halfSize*upPrime.y;
		quads[ index+2 ].z = particle[i].loc.z + halfSize*right.z + halfSize*upPrime.z;

//		texUV[ index+3 ].x = 0.0f;
//		texUV[ index+3 ].y = 1.0f;		
		quads[ index+3 ].x = particle[i].loc.x - halfSize*right.x + halfSize*upPrime.x;
		quads[ index+3 ].y = particle[i].loc.y - halfSize*right.y + halfSize*upPrime.y;
		quads[ index+3 ].z = particle[i].loc.z - halfSize*right.z + halfSize*upPrime.z;
	}
	GLASSERT( index <= MAX_PARTICLE*4 );
	return index;
}
Model* SpaceTree::QueryRay( const Vector3F& _origin, 
							const Vector3F& _direction, 
							int required, int excluded, const Model** ignore,
							HitTestMethod testType,
							Vector3F* intersection ) 
{
	//GLOUTPUT(( "query ray\n" ));
	modelRoot = 0;
	nodesVisited = 0;
	modelsFound = 0;
	requiredFlags = required;
	excludedFlags = excluded | Model::MODEL_HIDDEN_FROM_TREE;
	++queryID;

	Vector3F dummy;
	if ( !intersection ) {
		intersection = &dummy;
	}

	Vector3F dir = _direction;
	dir.Normalize();

	Rectangle3F aabb;
	aabb.min.Set( 0, yMin, 0 );
	aabb.max.Set( Map::SIZE, yMax, Map::SIZE );

	// Where does this ray enter and leave the spaceTree?
	// It enters at 'p0' and leaves at 'p1'
	int p0Test, p1Test;
	Vector3F p0, p1;
	int test = IntersectRayAllAABB( _origin, dir, aabb, &p0Test, &p0, &p1Test, &p1 );
	if ( test != grinliz::INTERSECT ) {
		// Can click outside of AABB pretty commonly, actually.
		return 0;
	}
	Plane planes[6];
	Rectangle3F rect;
	rect.FromPair( p0, p1 );
	Plane::CreatePlanes( rect, planes );

	Model* modelRoot = Query( planes, 6, required, excluded );

	// We now have a batch of models. Are any of them a hit??
	GLASSERT( testType == TEST_HIT_AABB || testType == TEST_TRI );

	float close = FLT_MAX;
	Model* closeModel = 0;
	Vector3F testInt;
	float t;

	for( Model* root=modelRoot; root; root=root->next ) {
		if ( Ignore( root, ignore ) )
			continue;

		//GLOUTPUT(( "Consider: %s\n", root->GetResource()->header.name ));
		int result = grinliz::REJECT;

		if ( testType == TEST_HIT_AABB ) {
			Rectangle3F modelAABB;

			root->CalcHitAABB( &modelAABB );
			result = IntersectRayAABB( p0, dir, modelAABB, &testInt, &t );
		}
		else if ( testType == TEST_TRI ) {
			t = FLT_MAX;
			result = root->IntersectRay( p0, dir, &testInt );

			if ( result == grinliz::INTERSECT ) {
				Vector3F delta = p0 - testInt;
				t = delta.LengthSquared();
				//GLOUTPUT(( "Hit: %s t=%.2f\n", root->GetResource()->header.name, t ));
			}	
		}

		if ( result == grinliz::INTERSECT ) {
			// Ugly little bug: check for t>=0, else could collide with objects
			// that touch the bounding box but are before the ray starts.
			if ( t >= 0.0f && t < close ) {
				closeModel = root;
				*intersection = testInt;
				close = t;
			}
		}
	}
	if ( closeModel ) {
		return closeModel;
	}
	return 0;
}
Esempio n. 17
0
//----------------------------------------------------------------------------
void Mesh::GenerateNormals(Bool ignoreHardEdges)
{
	VertexBuffer* pPositions = GetPositionBuffer();
	if (!pPositions)
	{
		return;
	}

	VertexBuffer* pNormals = GetNormalBuffer();
	if (!pNormals)
	{
		return;
	}

	UShort* const pIndices = mspIndexBuffer->GetData();

	// collect the triangles each vertex is part of
	TArray<TArray<UInt> > buckets(pPositions->GetQuantity());
	buckets.SetQuantity(pPositions->GetQuantity());

	UInt triIndex = 0;
	for (UInt i = 0; i < mspIndexBuffer->GetQuantity(); i += 3)
	{
		buckets[pIndices[i]].Append(triIndex);
		buckets[pIndices[i+1]].Append(triIndex);
		buckets[pIndices[i+2]].Append(triIndex);
		triIndex++;
	}

	if (ignoreHardEdges)
	{
		for (UInt j = 0; j < pPositions->GetQuantity(); j++)
		{
			const Vector3F& vertex = pPositions->Position3(j);
			for (UInt i = j+1; i < pPositions->GetQuantity(); i++)
			{
				if (vertex == pPositions->Position3(i))
				{
					UInt origCount = buckets[j].GetQuantity();
					for (UInt k = 0; k < buckets[i].GetQuantity(); k++)
					{
						buckets[j].Append(buckets[i][k]);
					}

					for (UInt k = 0; k < origCount; k++)
					{
						buckets[i].Append(buckets[j][k]);
					}
				}
			}
		}
	}

	// calculate the normals of the individual triangles
	TArray<Vector3F> faceNormals(mspIndexBuffer->GetQuantity()/3);
	for (UInt i = 0; i < mspIndexBuffer->GetQuantity(); i +=3)
	{
		Vector3F v1 = pPositions->Position3(pIndices[i+1]) - pPositions->
			Position3(pIndices[i]);
		Vector3F v2 = pPositions->Position3(pIndices[i+2]) - pPositions->
			Position3(pIndices[i+1]);

		Vector3F normal = v2.Cross(v1);
		normal.Normalize();
		faceNormals.Append(normal);
	}

	// calculate the normal of the vertex from the normals of its faces
	for (UInt i = 0; i < buckets.GetQuantity(); i++)
	{
		Vector3F normal(Vector3F::ZERO);
		for (UInt j = 0; j < buckets[i].GetQuantity(); j++)
		{
			normal += faceNormals[buckets[i][j]];
		}

		if (buckets[i].GetQuantity() > 0)
		{
			normal /= static_cast<Float>(buckets[i].GetQuantity());
			normal.Normalize();
		}
		else
		{
			// vertex not used in mesh, use a default normal
			normal = Vector3F::UNIT_X;
		}

		pNormals->Normal3(i) = normal;
	}
}