예제 #1
0
//-----------------------------------------------------------------------------
//
// VActorPhysicsController::isMoving();
//
// Is the Actor currently Moving?
//
//-----------------------------------------------------------------------------
const bool VActorPhysicsController::isMoving( void )
{
    return ( !mIsZero( getVelocity().lenSquared() ) );
}
예제 #2
0
void TerrainCellMaterial::_updateMaterialConsts( Pass *pass )
{
   PROFILE_SCOPE( TerrainCellMaterial_UpdateMaterialConsts );

   for ( U32 j=0; j < pass->materials.size(); j++ )
   {
      MaterialInfo *matInfo = pass->materials[j];

      F32 detailSize = matInfo->mat->getDetailSize();
      F32 detailScale = 1.0f;
      if ( !mIsZero( detailSize ) )
         detailScale = mTerrain->getWorldBlockSize() / detailSize;

      // Scale the distance by the global scalar.
      const F32 distance = mTerrain->smDetailScale * matInfo->mat->getDetailDistance();

      // NOTE: The negation of the y scale is to make up for 
      // my mistake early in development and passing the wrong
      // y texture coord into the system.
      //
      // This negation fixes detail, normal, and parallax mapping
      // without harming the layer id blending code.
      //
      // Eventually we should rework this to correct this little
      // mistake, but there isn't really a hurry to.
      //
      Point4F detailScaleAndFade(   detailScale,
                                    -detailScale,
                                    distance, 
                                    0 );

      if ( !mIsZero( distance ) )
         detailScaleAndFade.w = 1.0f / distance;

      Point3F detailIdStrengthParallax( matInfo->layerId,
                                        matInfo->mat->getDetailStrength(),
                                        matInfo->mat->getParallaxScale() );

      pass->consts->setSafe( matInfo->detailInfoVConst, detailScaleAndFade );
      pass->consts->setSafe( matInfo->detailInfoPConst, detailIdStrengthParallax );

	// macro texture info

      F32 macroSize = matInfo->mat->getMacroSize();
      F32 macroScale = 1.0f;
      if ( !mIsZero( macroSize ) )
         macroScale = mTerrain->getWorldBlockSize() / macroSize;

      // Scale the distance by the global scalar.
      const F32 macroDistance = mTerrain->smDetailScale * matInfo->mat->getMacroDistance();

      Point4F macroScaleAndFade(   macroScale,
                                    -macroScale,
                                    macroDistance, 
                                    0 );

      if ( !mIsZero( macroDistance ) )
         macroScaleAndFade.w = 1.0f / macroDistance;

      Point3F macroIdStrengthParallax( matInfo->layerId,
                                        matInfo->mat->getMacroStrength(),
                                        0 );

      pass->consts->setSafe( matInfo->macroInfoVConst, macroScaleAndFade );
      pass->consts->setSafe( matInfo->macroInfoPConst, macroIdStrengthParallax );
   }
}
예제 #3
0
//--------------------------------------------------------------------------
// from Graphics Gems I: pp 738-742
U32 mSolveCubic_c(F32 a, F32 b, F32 c, F32 d, F32 * x)
{
   if(mIsZero(a))
      return(mSolveQuadratic(b, c, d, x));

   // normal form: x^3 + Ax^2 + BX + C = 0
   F32 A = b / a;
   F32 B = c / a;
   F32 C = d / a;

   // substitute x = y - A/3 to eliminate quadric term and depress
   // the cubic equation to (x^3 + px + q = 0)
   F32 A2 = A * A;
   F32 A3 = A2 * A;

   F32 p = (1.f/3.f) * (((-1.f/3.f) * A2) + B);
   F32 q = (1.f/2.f) * (((2.f/27.f) * A3) - ((1.f/3.f) * A * B) + C);

   // use Cardano's fomula to solve the depressed cubic
   F32 p3 = p * p * p;
   F32 q2 = q * q;

   F32 D = q2 + p3;

   U32 num = 0;

   if(mIsZero(D))          // 1 or 2 solutions
   {
      if(mIsZero(q)) // 1 triple solution
      {
         x[0] = 0.f;
         num = 1;
      }
      else // 1 single and 1 double
      {
         F32 u = mCbrt(-q);
         x[0] = 2.f * u;
         x[1] = -u;
         num = 2;
      }
   }
   else if(D < 0.f)        // 3 solutions: casus irreducibilis
   {
      F32 phi = (1.f/3.f) * mAcos(-q / mSqrt(-p3));
      F32 t = 2.f * mSqrt(-p);

      x[0] = t * mCos(phi);
      x[1] = -t * mCos(phi + (M_PI / 3.f));
      x[2] = -t * mCos(phi - (M_PI / 3.f));
      num = 3;
   }
   else                    // 1 solution
   {
      F32 sqrtD = mSqrt(D);
      F32 u = mCbrt(sqrtD - q);
      F32 v = -mCbrt(sqrtD + q);

      x[0] = u + v;
      num = 1;
   }

   // resubstitute
   F32 sub = (1.f/3.f) * A;
   for(U32 i = 0; i < num; i++)
      x[i] -= sub;

   // sort the roots
   for(S32 j = 0; j < (num - 1); j++)
      for(S32 k = j + 1; k < num; k++)
         if(x[k] < x[j])
            swap(x[k], x[j]);

   return(num);
}
예제 #4
0
/**
 * This method calculates the moves for the AI player
 *
 * @param movePtr Pointer to move the move list into
 */
bool AIPlayer::getAIMove(Move *movePtr)
{
   *movePtr = NullMove;

   // Use the eye as the current position.
   MatrixF eye;
   getEyeTransform(&eye);
   Point3F location = eye.getPosition();
   Point3F rotation = getRotation();

   // Orient towards the aim point, aim object, or towards
   // our destination.
   if (mAimObject || mAimLocationSet || mMoveState != ModeStop) 
   {
      // Update the aim position if we're aiming for an object
      if (mAimObject)
         mAimLocation = mAimObject->getPosition() + mAimOffset;
      else
         if (!mAimLocationSet)
            mAimLocation = mMoveDestination;

      F32 xDiff = mAimLocation.x - location.x;
      F32 yDiff = mAimLocation.y - location.y;

      if (!mIsZero(xDiff) || !mIsZero(yDiff)) 
      {
         // First do Yaw
         // use the cur yaw between -Pi and Pi
         F32 curYaw = rotation.z;
         while (curYaw > M_2PI_F)
            curYaw -= M_2PI_F;
         while (curYaw < -M_2PI_F)
            curYaw += M_2PI_F;

         // find the yaw offset
         F32 newYaw = mAtan2( xDiff, yDiff );
         F32 yawDiff = newYaw - curYaw;

         // make it between 0 and 2PI
         if( yawDiff < 0.0f )
            yawDiff += M_2PI_F;
         else if( yawDiff >= M_2PI_F )
            yawDiff -= M_2PI_F;

         // now make sure we take the short way around the circle
         if( yawDiff > M_PI_F )
            yawDiff -= M_2PI_F;
         else if( yawDiff < -M_PI_F )
            yawDiff += M_2PI_F;

         movePtr->yaw = yawDiff;

         // Next do pitch.
         if (!mAimObject && !mAimLocationSet) 
         {
            // Level out if were just looking at our next way point.
            Point3F headRotation = getHeadRotation();
            movePtr->pitch = -headRotation.x;
         }
         else 
         {
            // This should be adjusted to run from the
            // eye point to the object's center position. Though this
            // works well enough for now.
            F32 vertDist = mAimLocation.z - location.z;
            F32 horzDist = mSqrt(xDiff * xDiff + yDiff * yDiff);
            F32 newPitch = mAtan2( horzDist, vertDist ) - ( M_PI_F / 2.0f );
            if (mFabs(newPitch) > 0.01f) 
            {
               Point3F headRotation = getHeadRotation();
               movePtr->pitch = newPitch - headRotation.x;
            }
         }
      }
   }
   else 
   {
      // Level out if we're not doing anything else
      Point3F headRotation = getHeadRotation();
      movePtr->pitch = -headRotation.x;
   }

   // Move towards the destination
   if (mMoveState != ModeStop) 
   {
      F32 xDiff = mMoveDestination.x - location.x;
      F32 yDiff = mMoveDestination.y - location.y;

      // Check if we should mMove, or if we are 'close enough'
      if (mFabs(xDiff) < mMoveTolerance && mFabs(yDiff) < mMoveTolerance) 
      {
         mMoveState = ModeStop;
         throwCallback("onReachDestination");
      }
      else 
      {
         // Build move direction in world space
         if (mIsZero(xDiff))
            movePtr->y = (location.y > mMoveDestination.y) ? -1.0f : 1.0f;
         else
            if (mIsZero(yDiff))
               movePtr->x = (location.x > mMoveDestination.x) ? -1.0f : 1.0f;
            else
               if (mFabs(xDiff) > mFabs(yDiff)) 
               {
                  F32 value = mFabs(yDiff / xDiff);
                  movePtr->y = (location.y > mMoveDestination.y) ? -value : value;
                  movePtr->x = (location.x > mMoveDestination.x) ? -1.0f : 1.0f;
               }
               else 
               {
                  F32 value = mFabs(xDiff / yDiff);
                  movePtr->x = (location.x > mMoveDestination.x) ? -value : value;
                  movePtr->y = (location.y > mMoveDestination.y) ? -1.0f : 1.0f;
               }

         // Rotate the move into object space (this really only needs
         // a 2D matrix)
         Point3F newMove;
         MatrixF moveMatrix;
         moveMatrix.set(EulerF(0.0f, 0.0f, -(rotation.z + movePtr->yaw)));
         moveMatrix.mulV( Point3F( movePtr->x, movePtr->y, 0.0f ), &newMove );
         movePtr->x = newMove.x;
         movePtr->y = newMove.y;

         // Set movement speed.  We'll slow down once we get close
         // to try and stop on the spot...
         if (mMoveSlowdown) 
         {
            F32 speed = mMoveSpeed;
            F32 dist = mSqrt(xDiff*xDiff + yDiff*yDiff);
            F32 maxDist = 5.0f;
            if (dist < maxDist)
               speed *= dist / maxDist;
            movePtr->x *= speed;
            movePtr->y *= speed;

            mMoveState = ModeSlowing;
         }
         else 
         {
            movePtr->x *= mMoveSpeed;
            movePtr->y *= mMoveSpeed;

            mMoveState = ModeMove;
         }

         if (mMoveStuckTestCountdown > 0)
            --mMoveStuckTestCountdown;
         else
         {
            // We should check to see if we are stuck...
            F32 locationDelta = (location - mLastLocation).len();
            if (locationDelta < mMoveStuckTolerance && mDamageState == Enabled) 
            {
               // If we are slowing down, then it's likely that our location delta will be less than
               // our move stuck tolerance. Because we can be both slowing and stuck
               // we should TRY to check if we've moved. This could use better detection.
               if ( mMoveState != ModeSlowing || locationDelta == 0 )
               {
                  mMoveState = ModeStuck;
                  throwCallback("onMoveStuck");
               }
            }
         }
      }
   }

   // Test for target location in sight if it's an object. The LOS is
   // run from the eye position to the center of the object's bounding,
   // which is not very accurate.
   if (mAimObject) {
      MatrixF eyeMat;
      getEyeTransform(&eyeMat);
      eyeMat.getColumn(3,&location);
      Point3F targetLoc = mAimObject->getBoxCenter();

      // This ray ignores non-static shapes. Cast Ray returns true
      // if it hit something.
      RayInfo dummy;
      if (getContainer()->castRay( location, targetLoc,
            InteriorObjectType | StaticShapeObjectType | StaticObjectType |
            TerrainObjectType, &dummy)) {
         if (mTargetInLOS) {
            throwCallback( "onTargetExitLOS" );
            mTargetInLOS = false;
         }
      }
      else
         if (!mTargetInLOS) {
            throwCallback( "onTargetEnterLOS" );
            mTargetInLOS = true;
         }
   }

   // Replicate the trigger state into the move so that
   // triggers can be controlled from scripts.
   for( int i = 0; i < MaxTriggerKeys; i++ )
      movePtr->trigger[i] = getImageTriggerState(i);

   mLastLocation = location;

   return true;
}
예제 #5
0
/**
 * This method gets the move list for an object, in the case
 * of the AI, it actually calculates the moves, and then
 * sends them down the pipe.
 *
 * @param movePtr Pointer to move the move list into
 * @param numMoves Number of moves in the move list
 */
U32 AIClient::getMoveList( Move **movePtr,U32 *numMoves ) {
   //initialize the move structure and return pointers
   mMove = NullMove;
   *movePtr = &mMove;
   *numMoves = 1;

   // Check if we got a player
   mPlayer = NULL;
   mPlayer = dynamic_cast<Player *>( getControlObject() );

   // We got a something controling us?
   if( !mPlayer )
      return 1;

   
   // What is The Matrix?
   MatrixF moveMatrix;
   moveMatrix.set( EulerF( 0, 0, 0 ) );
   moveMatrix.setColumn( 3, Point3F( 0, 0, 0 ) );
   moveMatrix.transpose();
      
   // Position / rotation variables
   F32 curYaw, curPitch;
   F32 newYaw, newPitch;
   F32 xDiff, yDiff;

   
   F32 moveSpeed = mMoveSpeed;

   switch( mMoveMode ) {

   case ModeStop:
      return 1;     // Stop means no action
      break;

   case ModeStuck:
      // Fall through, so we still try to move
   case ModeMove:
   
      // Get my location
      MatrixF const& myTransform = mPlayer->getTransform();
      myTransform.getColumn( 3, &mLocation );
   
      // Set rotation variables
      Point3F rotation = mPlayer->getRotation();
      Point3F headRotation = mPlayer->getHeadRotation();
      curYaw = rotation.z;
      curPitch = headRotation.x;
      xDiff = mAimLocation.x - mLocation.x;
      yDiff = mAimLocation.y - mLocation.y;
   
      // first do Yaw
      if( !mIsZero( xDiff ) || !mIsZero( yDiff ) ) {
         // use the cur yaw between -Pi and Pi
         while( curYaw > M_2PI_F )
            curYaw -= M_2PI_F;
         while( curYaw < -M_2PI_F )
            curYaw += M_2PI_F;
      
         // find the new yaw
         newYaw = mAtan2( xDiff, yDiff );
      
         // find the yaw diff 
         F32 yawDiff = newYaw - curYaw;
      
         // make it between 0 and 2PI
         if( yawDiff < 0.0f )
            yawDiff += M_2PI_F;
         else if( yawDiff >= M_2PI_F )
            yawDiff -= M_2PI_F;
      
         // now make sure we take the short way around the circle
         if( yawDiff > M_2PI_F )
            yawDiff -= M_2PI_F;
         else if( yawDiff < -M_2PI_F )
            yawDiff += M_2PI_F;
      
         mMove.yaw = yawDiff;
      
         // set up the movement matrix
         moveMatrix.set( EulerF( 0.0f, 0.0f, newYaw ) );
      }
      else
         moveMatrix.set( EulerF( 0.0f, 0.0f, curYaw ) );
   
      // next do pitch
      F32 horzDist = Point2F( mAimLocation.x, mAimLocation.y ).len();

      if( !mIsZero( horzDist ) ) {
         //we shoot from the gun, not the eye...
         F32 vertDist = mAimLocation.z;
      
         newPitch = mAtan2( horzDist, vertDist ) - ( M_2PI_F / 2.0f );
      
         F32 pitchDiff = newPitch - curPitch;
         mMove.pitch = pitchDiff;
      }
   
      // finally, mMove towards mMoveDestination
      xDiff = mMoveDestination.x - mLocation.x;
      yDiff = mMoveDestination.y - mLocation.y;


      // Check if we should mMove, or if we are 'close enough'
      if( ( ( mFabs( xDiff ) > mMoveTolerance ) || 
            ( mFabs( yDiff ) > mMoveTolerance ) ) && ( !mIsZero( mMoveSpeed ) ) )
      {
         if( mIsZero( xDiff ) )
            mMove.y = ( mLocation.y > mMoveDestination.y ? -moveSpeed : moveSpeed );
         else if( mIsZero( yDiff ) )
            mMove.x = ( mLocation.x > mMoveDestination.x ? -moveSpeed : moveSpeed );
         else if( mFabs( xDiff ) > mFabs( yDiff ) ) {
            F32 value = mFabs( yDiff / xDiff ) * mMoveSpeed;
            mMove.y = ( mLocation.y > mMoveDestination.y ? -value : value );
            mMove.x = ( mLocation.x > mMoveDestination.x ? -moveSpeed : moveSpeed );
         }
         else {
            F32 value = mFabs( xDiff / yDiff ) * mMoveSpeed;
            mMove.x = ( mLocation.x > mMoveDestination.x ? -value : value );
            mMove.y = ( mLocation.y > mMoveDestination.y ? -moveSpeed : moveSpeed );
         }
      
         //now multiply the mMove vector by the transpose of the object rotation matrix
         moveMatrix.transpose();
         Point3F newMove;
         moveMatrix.mulP( Point3F( mMove.x, mMove.y, 0.0f ), &newMove );
      
         //and sub the result back in the mMove structure
         mMove.x = newMove.x;
         mMove.y = newMove.y;

         // We should check to see if we are stuck...
         if( mLocation.x == mLastLocation.x && 
             mLocation.y == mLastLocation.y &&
             mLocation.z == mLastLocation.z ) {

            // We're stuck...probably
            setMoveMode( ModeStuck );
         }
         else
            setMoveMode( ModeMove );
      }
      else {
         // Ok, we are close enough, lets stop

         // setMoveMode( ModeStop ); // DON'T use this, it'll throw the wrong callback
         mMoveMode = ModeStop;
         throwCallback(  "onReachDestination" ); // Callback

      }
      break;
   }

   // Test for target location in sight
   RayInfo dummy;
   Point3F targetLoc = mMoveDestination; // Change this

   if( mPlayer ) {
      if( !mPlayer->getContainer()->castRay( mLocation, targetLoc, 
                                                StaticShapeObjectType | StaticObjectType |
                                                TerrainObjectType, &dummy ) ) {
         if( !mTargetInLOS )
            throwCallback( "onTargetEnterLOS" );
      }
      else {
         if( mTargetInLOS )
            throwCallback( "onTargetExitLOS" );
            
      }
   }
   
   // Copy over the trigger status
   for( int i = 0; i < MaxTriggerKeys; i++ ) {
      mMove.trigger[i] = mTriggers[i];
      mTriggers[i] = false;
   }

   return 1;
}
예제 #6
0
//--------------------------------------------------------------------------
// from Graphics Gems I: pp 738-742
U32 mSolveQuartic_c(F32 a, F32 b, F32 c, F32 d, F32 e, F32 * x)
{
   if(mIsZero(a))
      return(mSolveCubic(b, c, d, e, x));

   // normal form: x^4 + ax^3 + bx^2 + cx + d = 0
   F32 A = b / a;
   F32 B = c / a;
   F32 C = d / a;
   F32 D = e / a;

   // substitue x = y - A/4 to eliminate cubic term:
   // x^4 + px^2 + qx + r = 0
   F32 A2 = A * A;
   F32 A3 = A2 * A;
   F32 A4 = A2 * A2;

   F32 p = ((-3.f/8.f) * A2) + B;
   F32 q = ((1.f/8.f) * A3) - ((1.f/2.f) * A * B) + C;
   F32 r = ((-3.f/256.f) * A4) + ((1.f/16.f) * A2 * B) - ((1.f/4.f) * A * C) + D;

   U32 num = 0;
   if(mIsZero(r)) // no absolute term: y(y^3 + py + q) = 0
   {
      num = mSolveCubic(1.f, 0.f, p, q, x);
      x[num++] = 0.f;
   }
   else
   {
      // solve the resolvent cubic
      F32 q2 = q * q;

      a = 1.f;
      b = (-1.f/2.f) * p;
      c = -r;
      d = ((1.f/2.f) * r * p) - ((1.f/8.f) * q2);

      mSolveCubic(a, b, c, d, x);

      F32 z = x[0];

      // build 2 quadratic equations from the one solution
      F32 u = (z * z) - r;
      F32 v = (2.f * z) - p;

      if(mIsZero(u))
         u = 0.f;
      else if(u > 0.f)
         u = mSqrt(u);
      else
         return(0);

      if(mIsZero(v))
         v = 0.f;
      else if(v > 0.f)
         v = mSqrt(v);
      else
         return(0);

      // solve the two quadratics
      a = 1.f;
      b = v;
      c = z - u;
      num = mSolveQuadratic(a, b, c, x);

      a = 1.f;
      b = -v;
      c = z + u;
      num += mSolveQuadratic(a, b, c, x + num);
   }

   // resubstitute
   F32 sub = (1.f/4.f) * A;
   for(U32 i = 0; i < num; i++)
      x[i] -= sub;

   // sort the roots
   for(S32 j = 0; j < (num - 1); j++)
      for(S32 k = j + 1; k < num; k++)
         if(x[k] < x[j])
            swap(x[k], x[j]);

   return(num);
}
예제 #7
0
bool TurretShape::onNewDataBlock(GameBaseData* dptr, bool reload)
{
   mDataBlock = dynamic_cast<TurretShapeData*>(dptr);
   if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
      return false;

   // Mark these nodes for control by code only (will not animate in a sequence)
   if (mDataBlock->headingNode != -1)
      mShapeInstance->setNodeAnimationState(mDataBlock->headingNode, TSShapeInstance::MaskNodeHandsOff);
   if (mDataBlock->pitchNode != -1)
      mShapeInstance->setNodeAnimationState(mDataBlock->pitchNode, TSShapeInstance::MaskNodeHandsOff);
   for (U32 i=0; i<TurretShapeData::NumMirrorDirectionNodes; ++i)
   {
      if (mDataBlock->pitchNodes[i] != -1)
      {
         mShapeInstance->setNodeAnimationState(mDataBlock->pitchNodes[i], TSShapeInstance::MaskNodeHandsOff);
      }

      if (mDataBlock->headingNodes[i] != -1)
      {
         mShapeInstance->setNodeAnimationState(mDataBlock->headingNodes[i], TSShapeInstance::MaskNodeHandsOff);
      }
   }

   if (mIsZero(mDataBlock->pitchRate))
   {
      mPitchAllowed = false;
   }
   else
   {
      mPitchAllowed = true;
      if (mDataBlock->pitchRate > 0)
      {
         mPitchRate = mDegToRad(mDataBlock->pitchRate);
      }
      else
      {
         mPitchRate = -1;
      }
   }

   if (mIsZero(mDataBlock->headingRate))
   {
      mHeadingAllowed = false;
   }
   else
   {
      mHeadingAllowed = true;
      if (mDataBlock->headingRate > 0)
      {
         mHeadingRate = mDegToRad(mDataBlock->headingRate);
      }
      else
      {
         mHeadingRate = -1;
      }
   }

   mPitchUp = -mDegToRad(mDataBlock->maxPitch);
   mPitchDown = mDegToRad(mDataBlock->minPitch);
   mHeadingMax = mDegToRad(mDataBlock->maxHeading);

   // Create Recoil thread if any recoil sequences are specified.
   // Note that the server player does not play this animation.
   mRecoilThread = 0;
   if (isGhost())
      for (U32 s = 0; s < TurretShapeData::NumRecoilSequences; s++)
         if (mDataBlock->recoilSequence[s] != -1) {
            mRecoilThread = mShapeInstance->addThread();
            mShapeInstance->setSequence(mRecoilThread, mDataBlock->recoilSequence[s], 0);
            mShapeInstance->setTimeScale(mRecoilThread, 0);
            break;
         }

   // Reset the image state driven animation thread.  This will be properly built
   // in onImageStateAnimation() when needed.
   mImageStateThread = 0;

   // Optional rotation threads.  These only play on the client.
   mPitchThread = 0;
   mHeadingThread = 0;
   if (isGhost())
   {
      if (mDataBlock->pitchSequence != -1)
      {
         mPitchThread = mShapeInstance->addThread();
         mShapeInstance->setSequence(mPitchThread, mDataBlock->pitchSequence, 0);
         mShapeInstance->setTimeScale(mPitchThread, 0);
      }
      if (mDataBlock->headingSequence != -1)
      {
         mHeadingThread = mShapeInstance->addThread();
         mShapeInstance->setSequence(mHeadingThread, mDataBlock->headingSequence, 0);
         mShapeInstance->setTimeScale(mHeadingThread, 0);
      }
   }

   if (!mSubclassTurretShapeHandlesScene)
   {
      scriptOnNewDataBlock();
   }

   return true;
}
예제 #8
0
void Portal::_traverseConnectedZoneSpaces( SceneTraversalState* state )
{
   PROFILE_SCOPE( Portal_traverseConnectedZoneSpaces );

   // Don't traverse out from the portal if it is invalid.

   if( mClassification == InvalidPortal )
      return;

   AssertFatal( !mIsGeometryDirty, "Portal::_traverseConnectedZoneSpaces - Geometry not up-to-date!" );

   // When starting traversal within a portal zone, we cannot really use the portal
   // plane itself to direct our visibility queries.  For example, the camera might
   // actually be located in front of the portal plane and thus cannot actually look
   // through the portal, though it will still see what lies on front of where the
   // portal leads.
   //
   // So if we're the start of the traversal chain, i.e. the traversal has started
   // out in the portal zone, then just put the traversal through to SceneZoneSpace
   // so it can hand it over to all connected zone managers.
   //
   // Otherwise, just do a normal traversal by stepping through the portal.

   if( state->getTraversalDepth() == 1 )
   {
      Parent::_traverseConnectedZoneSpaces( state );
      return;
   }
   
   SceneCullingState* cullingState = state->getCullingState();
   const SceneCameraState& cameraState = cullingState->getCameraState();

   // Get the data of the zone we're coming from.  Note that at this point
   // the portal zone itself is already on top of the traversal stack, so
   // we skip over the bottom-most entry.

   const U32 sourceZoneId = state->getZoneIdFromStack( 1 );
   const SceneZoneSpace* sourceZoneSpace = state->getZoneFromStack( 1 );

   // Find out which side of the portal we are on given the
   // source zone.

   const Portal::Side currentZoneSide =
      sourceZoneId == SceneZoneSpaceManager::RootZoneId
      ? ( getInteriorSideOfExteriorPortal() == FrontSide ? BackSide : FrontSide )
      : getSideRelativeToPortalPlane( sourceZoneSpace->getPosition() );

   // Don't step through portal if the side we're interested in isn't passable.

   if( !isSidePassable( currentZoneSide ) )
      return;

   // If the viewpoint isn't on the same side of the portal as the source zone,
   // then stepping through the portal would mean we are stepping back towards
   // the viewpoint which doesn't make sense; so, skip the portal.

   const Point3F& viewPos = cameraState.getViewPosition();
   const F32 viewPosDistToPortal = mFabs( getPortalPlane().distToPlane( viewPos ) );
   if( !mIsZero( viewPosDistToPortal ) && getSideRelativeToPortalPlane( viewPos ) != currentZoneSide )
      return;

   // Before we go ahead and do the real work, try to find out whether
   // the portal is at a perpendicular or near-perpendicular angle to the view
   // direction.  If so, there's no point in going further since we can't really
   // see much through the portal anyway.  It also prevents us from stepping
   // over front/back side ambiguities.

   Point3F viewDirection = cameraState.getViewDirection();
   const F32 dotProduct = mDot( viewDirection, getPortalPlane() );
   if( mIsZero( dotProduct ) )
      return;

   // Finally, if we have come through a portal to the current zone, check if the target
   // portal we are trying to step through now completely lies on the "backside"--i.e.
   // the side of portal on which our current zone lies--of the source portal.  If so,
   // we can be sure this portal leads us in the wrong direction.  This prevents the
   // outdoor zone from having just arrived through a window on one side of a house just
   // to go round the house and re-enter it from the other side.

   Portal* sourcePortal = state->getTraversalDepth() > 2 ? dynamic_cast< Portal* >( state->getZoneFromStack( 2 ) ) : NULL;
   if( sourcePortal != NULL )
   {
      const Side sourcePortalFrontSide =
         sourceZoneId == SceneZoneSpaceManager::RootZoneId
         ? sourcePortal->getInteriorSideOfExteriorPortal()
         : sourcePortal->getSideRelativeToPortalPlane( sourceZoneSpace->getPosition() );
      const PlaneF::Side sourcePortalPlaneFrontSide =
         sourcePortalFrontSide == FrontSide ? PlaneF::Front : PlaneF::Back;

      bool allPortalVerticesOnBackside = true;
      const U32 numVertices = mPortalPolygonWS.size();
      for( U32 i = 0; i < numVertices; ++ i )
      {
         // Not using getSideRelativeToPortalPlane here since we want PlaneF::On to be
         // counted as backside here.
         if( sourcePortal->mPortalPlane.whichSide( mPortalPolygonWS[ i ] ) == sourcePortalPlaneFrontSide )
         {
            allPortalVerticesOnBackside = false;
            break;
         }
      }

      if( allPortalVerticesOnBackside )
         return;
   }

   // If we come from the outdoor zone, then we don't want to step through any portal
   // where the interior zones are actually on the same side as our camera since that
   // would mean we are stepping into an interior through the backside of a portal.

   if( sourceZoneId == SceneZoneSpaceManager::RootZoneId )
   {
      const Portal::Side cameraSide = getSideRelativeToPortalPlane( viewPos );
      if( cameraSide == getInteriorSideOfExteriorPortal() )
         return;
   }

   // Clip the current culling volume against the portal's polygon.  If the polygon
   // lies completely outside the volume or for some other reason there's no good resulting
   // volume, _generateCullingVolume() will return false and we terminate this portal sequence
   // here.
   //
   // However, don't attempt to clip the portal if we are standing really close to or
   // even below the near distance away from the portal plane.  In that case, trying to
   // clip the portal will only result in trouble so we stick to the original culling volume
   // in that case.

   bool haveClipped = false;
   if( viewPosDistToPortal > ( cameraState.getFrustum().getNearDist() + 0.1f ) )
   {
      SceneCullingVolume volume;
      if( !_generateCullingVolume( state, volume ) )
         return;

      state->pushCullingVolume( volume );
      haveClipped = true;
   }

   // Short-circuit things if we are stepping from an interior zone outside.  In this
   // case we know that the only zone we care about is the outdoor zone so head straight
   // into it.

   if( isExteriorPortal() && sourceZoneId != SceneZoneSpaceManager::RootZoneId )
      getSceneManager()->getZoneManager()->getRootZone()->traverseZones( state );
   else
   {
      // Go through the zones that the portal connects to and
      // traverse into them.

      for( ZoneSpaceRef* ref = mConnectedZoneSpaces; ref != NULL; ref = ref->mNext )
      {
         SceneZoneSpace* targetSpace = ref->mZoneSpace;
         if( targetSpace == sourceZoneSpace )
            continue; // Skip space we originated from.

         // We have handled the case of stepping into the outdoor zone above and
         // by skipping the zone we originated from, we have implicitly handled the
         // case of stepping out of the outdoor zone.  Thus, we should not see the
         // outdoor zone here.  Important as getPosition() is meaningless for it.
         AssertFatal( targetSpace->getZoneRangeStart() != SceneZoneSpaceManager::RootZoneId,
            "Portal::_traverseConnectedZoneSpaces - Outdoor zone must have been handled already" );

         // Skip zones that lie on the same side as the zone
         // we originated from.

         if( getSideRelativeToPortalPlane( targetSpace->getPosition() ) == currentZoneSide )
            continue;

         // Traverse into the space.

         targetSpace->traverseZones( state );
      }
   }

   // If we have pushed our own clipping volume,
   // remove that from the stack now.

   if( haveClipped )
      state->popCullingVolume();
}
예제 #9
0
void ConvexShape::Geometry::generate( const Vector< PlaneF > &planes, const Vector< Point3F > &tangents )
{
   PROFILE_SCOPE( Geometry_generate );

   points.clear();
   faces.clear();	

   AssertFatal( planes.size() == tangents.size(), "ConvexShape - incorrect plane/tangent count." );

#ifdef TORQUE_ENABLE_ASSERTS
   for ( S32 i = 0; i < planes.size(); i++ )
   {
      F32 dt = mDot( planes[i], tangents[i] );
      AssertFatal( mIsZero( dt, 0.0001f ), "ConvexShape - non perpendicular input vectors." );
      AssertFatal( planes[i].isUnitLength() && tangents[i].isUnitLength(), "ConvexShape - non unit length input vector." );
   }
#endif

   const U32 planeCount = planes.size();

   Point3F linePt, lineDir;   

   for ( S32 i = 0; i < planeCount; i++ )
   {      
      Vector< MathUtils::Line > collideLines;

      // Find the lines defined by the intersection of this plane with all others.

      for ( S32 j = 0; j < planeCount; j++ )
      {         
         if ( i == j )
            continue;

         if ( planes[i].intersect( planes[j], linePt, lineDir ) )
         {
            collideLines.increment();
            MathUtils::Line &line = collideLines.last();
            line.origin = linePt;
            line.direction = lineDir;   
         }         
      }

      if ( collideLines.empty() )
         continue;

      // Find edges and points defined by the intersection of these lines.
      // As we find them we fill them into our working ConvexShape::Face
      // structure.
      
      Face newFace;

      for ( S32 j = 0; j < collideLines.size(); j++ )
      {
         Vector< Point3F > collidePoints;

         for ( S32 k = 0; k < collideLines.size(); k++ )
         {
            if ( j == k )
               continue;

            MathUtils::LineSegment segment;
            MathUtils::mShortestSegmentBetweenLines( collideLines[j], collideLines[k], &segment );

            F32 dist = ( segment.p0 - segment.p1 ).len();

            if ( dist < 0.0005f )
            {
               S32 l = 0;
               for ( ; l < planeCount; l++ )
               {
                  if ( planes[l].whichSide( segment.p0 ) == PlaneF::Front )
                     break;
               }

               if ( l == planeCount )
                  collidePoints.push_back( segment.p0 );
            }
         }

         //AssertFatal( collidePoints.size() <= 2, "A line can't collide with more than 2 other lines in a convex shape..." );

         if ( collidePoints.size() != 2 )
            continue;

         // Push back collision points into our points vector
         // if they are not duplicates and determine the id
         // index for those points to be used by Edge(s).    

         const Point3F &pnt0 = collidePoints[0];
         const Point3F &pnt1 = collidePoints[1];
         S32 idx0 = -1;
         S32 idx1 = -1;

         for ( S32 k = 0; k < points.size(); k++ )
         {
            if ( pnt0.equal( points[k] ) )
            {
               idx0 = k;
               break;
            }
         }

         for ( S32 k = 0; k < points.size(); k++ )
         {
            if ( pnt1.equal( points[k] ) )
            {
               idx1 = k;
               break;
            }
         }

         if ( idx0 == -1 )
         {
            points.push_back( pnt0 );               
            idx0 = points.size() - 1;
         }

         if ( idx1 == -1 )
         {
            points.push_back( pnt1 );
            idx1 = points.size() - 1;
         }

         // Construct the Face::Edge defined by this collision.

         S32 localIdx0 = newFace.points.push_back_unique( idx0 );
         S32 localIdx1 = newFace.points.push_back_unique( idx1 );

         newFace.edges.increment();
         ConvexShape::Edge &newEdge = newFace.edges.last();
         newEdge.p0 = localIdx0;
         newEdge.p1 = localIdx1;
      }    

      if ( newFace.points.size() < 3 )
         continue;

      //AssertFatal( newFace.points.size() == newFace.edges.size(), "ConvexShape - face point count does not equal edge count." );


		// Fill in some basic Face information.

		newFace.id = i;
		newFace.normal = planes[i];
		newFace.tangent = tangents[i];


		// Make a working array of Point3Fs on this face.

		U32 pntCount = newFace.points.size();		
		Point3F *workPoints = new Point3F[ pntCount ];

		for ( S32 j = 0; j < pntCount; j++ )
			workPoints[j] = points[ newFace.points[j] ];


      // Calculate the average point for calculating winding order.

      Point3F averagePnt = Point3F::Zero;

		for ( S32 j = 0; j < pntCount; j++ )
			averagePnt += workPoints[j];

		averagePnt /= pntCount;		


		// Sort points in correct winding order.

		U32 *vertMap = new U32[pntCount];

      MatrixF quadMat( true );
      quadMat.setPosition( averagePnt );
      quadMat.setColumn( 0, newFace.tangent );
      quadMat.setColumn( 1, mCross( newFace.normal, newFace.tangent ) );
      quadMat.setColumn( 2, newFace.normal );
		quadMat.inverse();

      // Transform working points into quad space 
      // so we can work with them as 2D points.

      for ( S32 j = 0; j < pntCount; j++ )
         quadMat.mulP( workPoints[j] );

		MathUtils::sortQuadWindingOrder( true, workPoints, vertMap, pntCount );

      // Save points in winding order.

      for ( S32 j = 0; j < pntCount; j++ )
         newFace.winding.push_back( vertMap[j] );

      // Calculate the area and centroid of the face.

      newFace.area = 0.0f;
      for ( S32 j = 0; j < pntCount; j++ )
      {
         S32 k = ( j + 1 ) % pntCount;
         const Point3F &p0 = workPoints[ vertMap[j] ];
         const Point3F &p1 = workPoints[ vertMap[k] ];
         
         // Note that this calculation returns positive area for clockwise winding
         // and negative area for counterclockwise winding.
         newFace.area += p0.y * p1.x;
         newFace.area -= p0.x * p1.y;                  
      }

      //AssertFatal( newFace.area > 0.0f, "ConvexShape - face area was not positive." );
      if ( newFace.area > 0.0f )
         newFace.area /= 2.0f;      

      F32 factor;
      F32 cx = 0.0f, cy = 0.0f;
      
      for ( S32 j = 0; j < pntCount; j++ )
      {
         S32 k = ( j + 1 ) % pntCount;
         const Point3F &p0 = workPoints[ vertMap[j] ];
         const Point3F &p1 = workPoints[ vertMap[k] ];

         factor = p0.x * p1.y - p1.x * p0.y;
         cx += ( p0.x + p1.x ) * factor;
         cy += ( p0.y + p1.y ) * factor;
      }
      
      factor = 1.0f / ( newFace.area * 6.0f );
      newFace.centroid.set( cx * factor, cy * factor, 0.0f );
      quadMat.inverse();
      quadMat.mulP( newFace.centroid );

      delete [] workPoints;
      workPoints = NULL;

		// Make polygons / triangles for this face.

		const U32 polyCount = pntCount - 2;

		newFace.triangles.setSize( polyCount );

		for ( S32 j = 0; j < polyCount; j++ )
		{
			ConvexShape::Triangle &poly = newFace.triangles[j];

			poly.p0 = vertMap[0];

			if ( j == 0 )
			{
				poly.p1 = vertMap[ 1 ];
				poly.p2 = vertMap[ 2 ];
			}
			else
			{
				poly.p1 = vertMap[ 1 + j ];
				poly.p2 = vertMap[ 2 + j ];
			}
		}

		delete [] vertMap;


		// Calculate texture coordinates for each point in this face.

		const Point3F binormal = mCross( newFace.normal, newFace.tangent );
		PlaneF planey( newFace.centroid - 0.5f * binormal, binormal );
		PlaneF planex( newFace.centroid - 0.5f * newFace.tangent, newFace.tangent );

		newFace.texcoords.setSize( newFace.points.size() );

		for ( S32 j = 0; j < newFace.points.size(); j++ )
		{
			F32 x = planex.distToPlane( points[ newFace.points[ j ] ] );
			F32 y = planey.distToPlane( points[ newFace.points[ j ] ] );

			newFace.texcoords[j].set( -x, -y );
		}

      // Data verification tests.
#ifdef TORQUE_ENABLE_ASSERTS
      //S32 triCount = newFace.triangles.size();
      //S32 edgeCount = newFace.edges.size();
      //AssertFatal( triCount == edgeCount - 2, "ConvexShape - triangle/edge count do not match." );

      /*
      for ( S32 j = 0; j < triCount; j++ )
      {
         F32 area = MathUtils::mTriangleArea( points[ newFace.points[ newFace.triangles[j][0] ] ], 
                                              points[ newFace.points[ newFace.triangles[j][1] ] ],
                                              points[ newFace.points[ newFace.triangles[j][2] ] ] );
         AssertFatal( area > 0.0f, "ConvexShape - triangle winding bad." );
      }*/
#endif


      // Done with this Face.
      
      faces.push_back( newFace );
   }
}
예제 #10
0
/**
 * This method calculates the moves for the AI player
 *
 * @param movePtr Pointer to move the move list into
 */
bool AIPlayer::getAIMove(Move *movePtr)
{
   *movePtr = NullMove;

   // Use the eye as the current position.
   MatrixF eye;
   getEyeTransform(&eye);
   Point3F location = eye.getPosition();
   Point3F rotation = getRotation();

#ifdef TORQUE_NAVIGATION_ENABLED
   if(mDamageState == Enabled)
   {
      if(mMoveState != ModeStop)
         updateNavMesh();
      if(!mFollowData.object.isNull())
      {
         if(mPathData.path.isNull())
         {
            if((getPosition() - mFollowData.object->getPosition()).len() > mFollowData.radius)
               followObject(mFollowData.object, mFollowData.radius);
         }
         else
         {
            if((mPathData.path->mTo - mFollowData.object->getPosition()).len() > mFollowData.radius)
               repath();
            else if((getPosition() - mFollowData.object->getPosition()).len() < mFollowData.radius)
            {
               clearPath();
               mMoveState = ModeStop;
			   throwCallback("onTargetInRange");
            }
            else if((getPosition() - mFollowData.object->getPosition()).len() < mAttackRadius)
            {
			   throwCallback("onTargetInFiringRange");
            }
         }
      }
   }
#endif // TORQUE_NAVIGATION_ENABLED

   // Orient towards the aim point, aim object, or towards
   // our destination.
   if (mAimObject || mAimLocationSet || mMoveState != ModeStop) 
   {
      // Update the aim position if we're aiming for an object
      if (mAimObject)
         mAimLocation = mAimObject->getPosition() + mAimOffset;
      else
         if (!mAimLocationSet)
            mAimLocation = mMoveDestination;

      F32 xDiff = mAimLocation.x - location.x;
      F32 yDiff = mAimLocation.y - location.y;

      if (!mIsZero(xDiff) || !mIsZero(yDiff)) 
      {
         // First do Yaw
         // use the cur yaw between -Pi and Pi
         F32 curYaw = rotation.z;
         while (curYaw > M_2PI_F)
            curYaw -= M_2PI_F;
         while (curYaw < -M_2PI_F)
            curYaw += M_2PI_F;

         // find the yaw offset
         F32 newYaw = mAtan2( xDiff, yDiff );
         F32 yawDiff = newYaw - curYaw;

         // make it between 0 and 2PI
         if( yawDiff < 0.0f )
            yawDiff += M_2PI_F;
         else if( yawDiff >= M_2PI_F )
            yawDiff -= M_2PI_F;

         // now make sure we take the short way around the circle
         if( yawDiff > M_PI_F )
            yawDiff -= M_2PI_F;
         else if( yawDiff < -M_PI_F )
            yawDiff += M_2PI_F;

         movePtr->yaw = yawDiff;

         // Next do pitch.
         if (!mAimObject && !mAimLocationSet) 
         {
            // Level out if were just looking at our next way point.
            Point3F headRotation = getHeadRotation();
            movePtr->pitch = -headRotation.x;
         }
         else 
         {
            // This should be adjusted to run from the
            // eye point to the object's center position. Though this
            // works well enough for now.
            F32 vertDist = mAimLocation.z - location.z;
            F32 horzDist = mSqrt(xDiff * xDiff + yDiff * yDiff);
            F32 newPitch = mAtan2( horzDist, vertDist ) - ( M_PI_F / 2.0f );
            if (mFabs(newPitch) > 0.01f) 
            {
               Point3F headRotation = getHeadRotation();
               movePtr->pitch = newPitch - headRotation.x;
            }
         }
      }
   }
   else 
   {
      // Level out if we're not doing anything else
      Point3F headRotation = getHeadRotation();
      movePtr->pitch = -headRotation.x;
   }

   // Move towards the destination
   if (mMoveState != ModeStop) 
   {
      F32 xDiff = mMoveDestination.x - location.x;
      F32 yDiff = mMoveDestination.y - location.y;

      // Check if we should mMove, or if we are 'close enough'
      if (mFabs(xDiff) < mMoveTolerance && mFabs(yDiff) < mMoveTolerance) 
      {
         mMoveState = ModeStop;
         onReachDestination();
      }
      else 
      {
         // Build move direction in world space
         if (mIsZero(xDiff))
            movePtr->y = (location.y > mMoveDestination.y) ? -1.0f : 1.0f;
         else
            if (mIsZero(yDiff))
               movePtr->x = (location.x > mMoveDestination.x) ? -1.0f : 1.0f;
            else
               if (mFabs(xDiff) > mFabs(yDiff)) 
               {
                  F32 value = mFabs(yDiff / xDiff);
                  movePtr->y = (location.y > mMoveDestination.y) ? -value : value;
                  movePtr->x = (location.x > mMoveDestination.x) ? -1.0f : 1.0f;
               }
               else 
               {
                  F32 value = mFabs(xDiff / yDiff);
                  movePtr->x = (location.x > mMoveDestination.x) ? -value : value;
                  movePtr->y = (location.y > mMoveDestination.y) ? -1.0f : 1.0f;
               }

         // Rotate the move into object space (this really only needs
         // a 2D matrix)
         Point3F newMove;
         MatrixF moveMatrix;
         moveMatrix.set(EulerF(0.0f, 0.0f, -(rotation.z + movePtr->yaw)));
         moveMatrix.mulV( Point3F( movePtr->x, movePtr->y, 0.0f ), &newMove );
         movePtr->x = newMove.x;
         movePtr->y = newMove.y;

         // Set movement speed.  We'll slow down once we get close
         // to try and stop on the spot...
         if (mMoveSlowdown) 
         {
            F32 speed = mMoveSpeed;
            F32 dist = mSqrt(xDiff*xDiff + yDiff*yDiff);
            F32 maxDist = mMoveTolerance*2;
            if (dist < maxDist)
               speed *= dist / maxDist;
            movePtr->x *= speed;
            movePtr->y *= speed;

            mMoveState = ModeSlowing;
         }
         else 
         {
            movePtr->x *= mMoveSpeed;
            movePtr->y *= mMoveSpeed;

            mMoveState = ModeMove;
         }

         if (mMoveStuckTestCountdown > 0)
            --mMoveStuckTestCountdown;
         else
         {
            // We should check to see if we are stuck...
            F32 locationDelta = (location - mLastLocation).len();
            if (locationDelta < mMoveStuckTolerance && mDamageState == Enabled) 
            {
               // If we are slowing down, then it's likely that our location delta will be less than
               // our move stuck tolerance. Because we can be both slowing and stuck
               // we should TRY to check if we've moved. This could use better detection.
               if ( mMoveState != ModeSlowing || locationDelta == 0 )
               {
                  mMoveState = ModeStuck;
                  onStuck();
               }
            }
         }
      }
   }

   // Test for target location in sight if it's an object. The LOS is
   // run from the eye position to the center of the object's bounding,
   // which is not very accurate.
   if (mAimObject)
   {
      if (checkInLos(mAimObject.getPointer()))
      {
         if (!mTargetInLOS)
         {
            throwCallback( "onTargetEnterLOS" );
            mTargetInLOS = true;
         }
   }
      else if (mTargetInLOS)
      {
            throwCallback( "onTargetExitLOS" );
            mTargetInLOS = false;
         }
      }

   Pose desiredPose = mPose;

   if ( mSwimming )  
      desiredPose = SwimPose;   
   else if ( mAiPose == 1 && canCrouch() )   
      desiredPose = CrouchPose;  
   else if ( mAiPose == 2 && canProne() )  
      desiredPose = PronePose;  
   else if ( mAiPose == 3 && canSprint() )  
      desiredPose = SprintPose;  
   else if ( canStand() )  
      desiredPose = StandPose;  
  
   setPose( desiredPose );
   
   // Replicate the trigger state into the move so that
   // triggers can be controlled from scripts.
   for( U32 i = 0; i < MaxTriggerKeys; i++ )
      movePtr->trigger[ i ] = getImageTriggerState( i );

#ifdef TORQUE_NAVIGATION_ENABLED
   if(mJump == Now)
   {
      movePtr->trigger[2] = true;
      mJump = None;
   }
   else if(mJump == Ledge)
   {
      // If we're not touching the ground, jump!
      RayInfo info;
      if(!getContainer()->castRay(getPosition(), getPosition() - Point3F(0, 0, 0.4f), StaticShapeObjectType, &info))
      {
         movePtr->trigger[2] = true;
         mJump = None;
      }
   }
#endif // TORQUE_NAVIGATION_ENABLED

   mLastLocation = location;

   return true;
}
예제 #11
0
void LightFlareData::prepRender( SceneRenderState *state, LightFlareState *flareState )
{
   PROFILE_SCOPE( LightFlareData_prepRender );

   const LightInfo *lightInfo = flareState->lightInfo;

   if (  mIsZero( flareState->fullBrightness ) ||
         mIsZero( lightInfo->getBrightness() ) )
      return;

   // Figure out the element count to render.
   U32 elementCount = mElementCount;
   const bool isReflectPass = state->isReflectPass();
   if ( isReflectPass )
   {
      // Then we don't render anything this pass.
      if ( !mRenderReflectPass )
         return;

      // Find the zero distance elements which make 
      // up the corona of the light flare.
      elementCount = 0.0f;
      for ( U32 i=0; i < mElementCount; i++ )
         if ( mIsZero( mElementDist[i] ) )
            elementCount++;
   }

   // Better have something to render.
   if ( elementCount == 0 )
      return;
  
   U32 visDelta = U32_MAX;
   F32 occlusionFade = 1.0f;
   Point3F lightPosSS;
   bool lightVisible = _testVisibility( state, flareState, &visDelta, &occlusionFade, &lightPosSS );
   
   // We can only skip rendering if the light is not 
   // visible, and it has elapsed the fade out time.
   if (  mIsZero( occlusionFade ) ||
         !lightVisible && visDelta > FadeOutTime )
      return;

   const RectI &viewport = GFX->getViewport();
   Point3F oneOverViewportExtent( 1.0f / (F32)viewport.extent.x, 1.0f / (F32)viewport.extent.y, 0.0f );

   // Really convert it to screen space.
   lightPosSS.x -= viewport.point.x;
   lightPosSS.y -= viewport.point.y;
   lightPosSS *= oneOverViewportExtent;
   lightPosSS = ( lightPosSS * 2.0f ) - Point3F::One;
   lightPosSS.y = -lightPosSS.y;
   lightPosSS.z = 0.0f;

   // Take any projection offset into account so that the point where the flare's
   // elements converge is at the 'eye' point rather than the center of the viewport.
   const Point2F& projOffset = state->getCameraFrustum().getProjectionOffset();
   Point3F flareVec( -lightPosSS + Point3F(projOffset.x, projOffset.y, 0.0f) );
   const F32 flareLength = flareVec.len();
   if ( flareLength > 0.0f )
      flareVec *= 1.0f / flareLength;

   // Setup the flare quad points.
   Point3F rotatedBasePoints[4];
   dMemcpy(rotatedBasePoints, sBasePoints, sizeof( sBasePoints ));

   // Rotate the flare quad.
   F32 rot = mAcos( -1.0f * flareVec.x );
   rot *= flareVec.y > 0.0f ? -1.0f : 1.0f;
   MathUtils::vectorRotateZAxis( rot, rotatedBasePoints, 4 );

   // Here we calculate a the light source's influence on 
   // the effect's size and brightness.

   // Scale based on the current light brightness compared to its normal output.
   F32 lightSourceBrightnessScale = lightInfo->getBrightness() / flareState->fullBrightness;

   const Point3F &camPos = state->getCameraPosition();
   const Point3F &lightPos = flareState->lightMat.getPosition();   
   const bool isVectorLight = lightInfo->getType() == LightInfo::Vector;

   // Scale based on world space distance from camera to light source.
   F32 distToCamera = ( camPos - lightPos ).len();
   F32 lightSourceWSDistanceScale = isVectorLight && distToCamera > 0.0f ? 1.0f : getMin( 10.0f / distToCamera, 10.0f );

   // Scale based on screen space distance from screen position of light source to the screen center.
   F32 lightSourceSSDistanceScale = getMax( ( 1.5f - flareLength ) / 1.5f, 0.0f );

   // Scale based on recent visibility changes, fading in or out.
   F32 fadeInOutScale = 1.0f;   
   if (  lightVisible &&
         visDelta < FadeInTime && 
         flareState->occlusion > 0.0f )
      fadeInOutScale = (F32)visDelta / (F32)FadeInTime;
   else if (   !lightVisible && 
               visDelta < FadeOutTime )
      fadeInOutScale = 1.0f - (F32)visDelta / (F32)FadeOutTime;

   // This combined scale influences the size of all elements this effect renders.
   // Note we also add in a scale that is user specified in the Light.
   F32 lightSourceIntensityScale = lightSourceBrightnessScale * 
                                   lightSourceWSDistanceScale * 
                                   lightSourceSSDistanceScale * 
                                   fadeInOutScale * 
                                   flareState->scale *
                                   occlusionFade;

   if ( mIsZero( lightSourceIntensityScale ) )
      return;

   // The baseColor which modulates the color of all elements.
   //
   // These are the factors which affect the "alpha" of the flare effect.
   // Modulate more in as appropriate.
   ColorF baseColor = ColorF::WHITE * lightSourceBrightnessScale * occlusionFade;

   // Setup the vertex buffer for the maximum flare elements.
   const U32 vertCount = 4 * mElementCount;
   if (  flareState->vertBuffer.isNull() || 
         flareState->vertBuffer->mNumVerts != vertCount )
         flareState->vertBuffer.set( GFX, vertCount, GFXBufferTypeDynamic );

   GFXVertexPCT *vert = flareState->vertBuffer.lock();

   const Point2F oneOverTexSize( 1.0f / (F32)mFlareTexture.getWidth(), 1.0f / (F32)mFlareTexture.getHeight() );

   for ( U32 i = 0; i < mElementCount; i++ )
   {      
      // Skip non-zero elements for reflections.
      if ( isReflectPass && mElementDist[i] > 0.0f )
         continue;

      Point3F *basePos = mElementRotate[i] ? rotatedBasePoints : sBasePoints;

      ColorF color( baseColor * mElementTint[i] );
      if ( mElementUseLightColor[i] )
         color *= lightInfo->getColor();
      color.clamp();

      Point3F pos( lightPosSS + flareVec * mElementDist[i] * flareLength );

      const RectF &rect = mElementRect[i];
      Point3F size( rect.extent.x, rect.extent.y, 1.0f );
      size *= mElementScale[i] * mScale * lightSourceIntensityScale;

      AssertFatal( size.x >= 0.0f, "LightFlareData::prepRender - Got a negative element size?" );

      if ( size.x < 100.0f )
      {
         F32 alphaScale = mPow( size.x / 100.0f, 2 );
         color *= alphaScale;
      }

      Point2F texCoordMin, texCoordMax;
      texCoordMin = rect.point * oneOverTexSize;
      texCoordMax = ( rect.point + rect.extent ) * oneOverTexSize;          

      size.x = getMax( size.x, 1.0f );
      size.y = getMax( size.y, 1.0f );
      size *= oneOverViewportExtent;

      vert->color = color;
      vert->point = ( basePos[0] * size ) + pos;      
      vert->texCoord.set( texCoordMin.x, texCoordMax.y );
      vert++;

      vert->color = color;
      vert->point = ( basePos[1] * size ) + pos;
      vert->texCoord.set( texCoordMax.x, texCoordMax.y );
      vert++;

      vert->color = color;
      vert->point = ( basePos[2] * size ) + pos;
      vert->texCoord.set( texCoordMax.x, texCoordMin.y );
      vert++;

      vert->color = color;
      vert->point = ( basePos[3] * size ) + pos;
      vert->texCoord.set( texCoordMin.x, texCoordMin.y );
      vert++;
   }   

   flareState->vertBuffer.unlock();   

   RenderPassManager *rpm = state->getRenderPass();

   // Create and submit the render instance.   
   ParticleRenderInst *ri = rpm->allocInst<ParticleRenderInst>();
   ri->type = RenderPassManager::RIT_Particle;
   ri->vertBuff = &flareState->vertBuffer;
   ri->primBuff = &mFlarePrimBuffer;
   ri->translucentSort = true;
   ri->sortDistSq = ( lightPos - camPos ).lenSquared();
   ri->modelViewProj = &MatrixF::Identity;
   ri->bbModelViewProj = &MatrixF::Identity;
   ri->count = elementCount;
   ri->blendStyle = ParticleRenderInst::BlendGreyscale;
   ri->diffuseTex = mFlareTexture;
   ri->softnessDistance = 1.0f; 
   ri->defaultKey = ri->diffuseTex ? (uintptr_t)ri->diffuseTex : (uintptr_t)ri->vertBuff; // Sort by texture too.

   // NOTE: Offscreen partical code is currently disabled.
   ri->systemState = PSS_AwaitingHighResDraw;

   rpm->addInst( ri );
}