Sfoglia il codice sorgente

Ribbon class implementation.
This class is based on Tim Newell from MaxGaming Technologies code, it's a
Ribbon class which is easy to use from other classes.

Lukas Joergensen 11 anni fa
parent
commit
cb9cfea1c4
2 ha cambiato i file con 828 aggiunte e 0 eliminazioni
  1. 701 0
      Engine/source/T3D/fx/ribbon.cpp
  2. 127 0
      Engine/source/T3D/fx/ribbon.h

+ 701 - 0
Engine/source/T3D/fx/ribbon.cpp

@@ -0,0 +1,701 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "console/consoleTypes.h"
+#include "console/typeValidators.h"
+#include "core/stream/bitStream.h"
+#include "T3D/shapeBase.h"
+#include "ts/tsShapeInstance.h"
+#include "T3D/fx/ribbon.h"
+#include "math/mathUtils.h"
+#include "math/mathIO.h"
+#include "sim/netConnection.h"
+#include "gfx/primBuilder.h"
+#include "gfx/gfxDrawUtil.h"
+#include "materials/sceneData.h"
+#include "materials/matInstance.h"
+#include "gui/3d/guiTSControl.h"
+#include "materials/materialManager.h"
+#include "materials/processedShaderMaterial.h"
+#include "gfx/gfxTransformSaver.h"
+
+
+IMPLEMENT_CO_DATABLOCK_V1(RibbonData);
+IMPLEMENT_CO_NETOBJECT_V1(Ribbon);
+
+
+//--------------------------------------------------------------------------
+//
+RibbonData::RibbonData()
+{
+   for (U8 i = 0; i < RIBBON_NUM_FIELDS; i++) {
+      mSizes[i] = 0.0f;
+      mColours[i].set(0.0f, 0.0f, 0.0f, 1.0f);
+      mTimes[i] = -1.0f;
+   }
+
+   mRibbonLength = 0;
+   mUseFadeOut = false;
+   mFadeAwayStep = 0.032f;
+   segmentsPerUpdate = 1;
+   mMatName = StringTable->insert("");
+   mTileScale = 1.0f;
+   mFixedTexcoords = false;
+   mSegmentSkipAmount = 0;
+   mTexcoordsRelativeToDistance = false;
+}
+
+//--------------------------------------------------------------------------
+
+void RibbonData::initPersistFields()
+{
+   Parent::initPersistFields();
+
+   addField("size", TypeF32, Offset(mSizes, RibbonData), RIBBON_NUM_FIELDS, 
+      "The size of the ribbon at the specified keyframe.");
+   addField("color", TypeColorF, Offset(mColours, RibbonData), RIBBON_NUM_FIELDS,
+      "The colour of the ribbon at the specified keyframe.");
+   addField("position", TypeF32, Offset(mTimes, RibbonData), RIBBON_NUM_FIELDS,
+      "The position of the keyframe along the lifetime of the ribbon.");
+   addField("RibbonLength", TypeS32, Offset(mRibbonLength, RibbonData),
+      "The amount of segments the Ribbon can maximally have in length.");
+   addField("UseFadeOut", TypeBool, Offset(mUseFadeOut, RibbonData),
+      "If true, the ribbon will fade away after deletion.");
+   addField("RibbonMaterial", TypeString, Offset(mMatName, RibbonData),
+      "The material the ribbon uses for rendering.");
+   addField("fadeAwayStep", TypeF32, Offset(mFadeAwayStep, RibbonData),
+      "How much to fade the ribbon with each update, after deletion.");
+   addField("segmentsPerUpdate", TypeS32, Offset(segmentsPerUpdate, RibbonData),
+      "How many segments to add each update.");
+   addField("tileScale", TypeF32, Offset(mTileScale, RibbonData), 
+      "How much to scale each 'tile' with, where 1 means the material is stretched"
+      "across the whole ribbon. (If TexcoordsRelativeToDistance is true, this is in meters.)");
+   addField("fixedTexcoords", TypeBool, Offset(mFixedTexcoords, RibbonData),
+      "If true, this prevents 'floating' texture coordinates.");
+   addField("skipAmount", TypeS32, Offset(mSegmentSkipAmount, RibbonData),
+      "The amount of segments to skip each update.");
+   addField("TexcoordsRelativeToDistance", TypeBool, Offset(mTexcoordsRelativeToDistance, RibbonData),
+      "If true, texture coordinates are scaled relative to distance, this prevents"
+      "'stretched' textures.");
+}
+
+
+//--------------------------------------------------------------------------
+bool RibbonData::onAdd()
+{
+   if(!Parent::onAdd())
+      return false;
+
+   return true;
+}
+
+
+bool RibbonData::preload(bool server, String &errorBuffer)
+{
+   if (Parent::preload(server, errorBuffer) == false)
+      return false;
+
+   return true;
+}
+
+//--------------------------------------------------------------------------
+void RibbonData::packData(BitStream* stream)
+{
+   Parent::packData(stream);
+
+   for (U8 i = 0; i < RIBBON_NUM_FIELDS; i++) {
+      stream->write(mSizes[i]);
+      stream->write(mColours[i]);
+      stream->write(mTimes[i]);
+   }
+
+   stream->write(mRibbonLength);
+   stream->writeString(mMatName);
+   stream->writeFlag(mUseFadeOut);
+   stream->write(mFadeAwayStep);
+   stream->write(segmentsPerUpdate);
+   stream->write(mTileScale);
+   stream->writeFlag(mFixedTexcoords);
+   stream->writeFlag(mTexcoordsRelativeToDistance);
+}
+
+void RibbonData::unpackData(BitStream* stream)
+{
+   Parent::unpackData(stream);
+
+   for (U8 i = 0; i < RIBBON_NUM_FIELDS; i++) {
+      stream->read(&mSizes[i]);
+      stream->read(&mColours[i]);
+      stream->read(&mTimes[i]);
+   }
+
+   stream->read(&mRibbonLength);
+   mMatName = StringTable->insert(stream->readSTString());
+   mUseFadeOut = stream->readFlag();
+   stream->read(&mFadeAwayStep);
+   stream->read(&segmentsPerUpdate);
+   stream->read(&mTileScale);
+   mFixedTexcoords = stream->readFlag();
+   mTexcoordsRelativeToDistance = stream->readFlag();
+}
+
+
+//--------------------------------------------------------------------------
+//--------------------------------------
+//
+Ribbon::Ribbon()
+{
+   mTypeMask |= StaticObjectType;
+
+   VECTOR_SET_ASSOCIATION(mSegmentPoints);
+   mSegmentPoints.clear();
+
+   mRibbonMat = NULL;
+
+   mUpdateBuffers = true;
+   mDeleteOnEnd = false;
+   mUseFadeOut = false;
+   mFadeAwayStep = 1.0f;
+   mFadeOut = 1.0f;
+
+   mNetFlags.clear(Ghostable);
+   mNetFlags.set(IsGhost);
+
+   mRadiusSC = NULL;
+   mRibbonProjSC = NULL;
+
+   mSegmentOffset = 0;
+   mSegmentIdx = 0;
+
+   mTravelledDistance = 0;
+}
+
+Ribbon::~Ribbon()
+{
+   //Make sure we cleanup
+   SAFE_DELETE(mRibbonMat);
+}
+
+//--------------------------------------------------------------------------
+void Ribbon::initPersistFields()
+{
+   Parent::initPersistFields();
+}
+
+bool Ribbon::onAdd()
+{
+   if(!Parent::onAdd())
+      return false;
+
+   // add to client side mission cleanup
+   SimGroup *cleanup = dynamic_cast<SimGroup *>( Sim::findObject( "ClientMissionCleanup") );
+   if( cleanup != NULL )
+   {
+      cleanup->addObject( this );
+   }
+   else
+   {
+      AssertFatal( false, "Error, could not find ClientMissionCleanup group" );
+      return false;
+   }
+
+   if (!isServerObject()) {
+
+      if(GFX->getPixelShaderVersion() >= 1.1 && dStrlen(mDataBlock->mMatName) > 0 )
+      {
+         mRibbonMat = MATMGR->createMatInstance( mDataBlock->mMatName );
+         GFXStateBlockDesc desc;
+         desc.setZReadWrite( true, false );
+         desc.cullDefined = true;
+         desc.cullMode = GFXCullNone;
+         desc.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha);
+
+         desc.samplersDefined = true;
+
+         GFXSamplerStateDesc sDesc(GFXSamplerStateDesc::getClampLinear());
+         sDesc.addressModeV = GFXAddressWrap;
+
+         desc.samplers[0] = sDesc;
+
+         mRibbonMat->addStateBlockDesc( desc );
+         mRibbonMat->init(MATMGR->getDefaultFeatures(), getGFXVertexFormat<GFXVertexPCNTT>());
+
+         mRadiusSC = mRibbonMat->getMaterialParameterHandle( "$radius" );
+         mRibbonProjSC = mRibbonMat->getMaterialParameterHandle( "$ribbonProj" );
+
+      } else {
+         Con::warnf( "Invalid Material name: %s: for Ribbon", mDataBlock->mMatName );
+#ifdef TORQUE_DEBUG
+         Con::warnf( "- This could be caused by having the shader data datablocks in server-only code." );
+#endif
+         mRibbonMat = NULL;
+      }
+   }
+
+   mObjBox.minExtents.set( 1.0f, 1.0f, 1.0f );
+   mObjBox.maxExtents.set(  2.0f, 2.0f,  2.0f );
+   // Reset the World Box.
+   resetWorldBox();
+   // Set the Render Transform.
+   setRenderTransform(mObjToWorld);
+
+   addToScene();
+
+   return true;
+}
+
+
+void Ribbon::onRemove()
+{
+
+   removeFromScene();
+   SAFE_DELETE(mRibbonMat);
+
+   Parent::onRemove();
+}
+
+
+bool Ribbon::onNewDataBlock(GameBaseData* dptr, bool reload)
+{
+   mDataBlock = dynamic_cast<RibbonData*>(dptr);
+   if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
+      return false;
+
+   return true;
+}
+
+void Ribbon::processTick(const Move* move)
+{
+   Parent::processTick(move);
+
+   if (mDeleteOnEnd) {
+
+      if (mUseFadeOut) {
+
+         if (mFadeOut <= 0.0f) {
+            mFadeOut = 0.0f;
+            //delete this class
+            mDeleteOnEnd = false;
+            safeDeleteObject();
+            return;
+         }
+         mFadeOut -= mFadeAwayStep;
+         if (mFadeOut < 0.0f) {
+            mFadeOut = 0.0f;
+         }
+
+         mUpdateBuffers = true;
+
+      } else {
+         //if (mSegmentPoints.size() == 0) {
+         //delete this class
+         mDeleteOnEnd = false;
+         safeDeleteObject();
+         return;
+         //}
+         //mSegmentPoints.pop_back();	
+      }
+
+
+   }
+}
+
+void Ribbon::advanceTime(F32 dt)
+{
+   Parent::advanceTime(dt);
+}
+
+void Ribbon::interpolateTick(F32 delta)
+{
+   Parent::interpolateTick(delta);
+}
+
+void Ribbon::addSegmentPoint(Point3F &point, MatrixF &mat) {
+
+   //update our position
+   setRenderTransform(mat);
+   MatrixF xform(true);
+   xform.setColumn(3, point);
+   setTransform(xform);
+
+   if(mSegmentIdx < mDataBlock->mSegmentSkipAmount)
+   {
+      mSegmentIdx++;
+      return;
+   }
+
+   mSegmentIdx = 0;
+
+   U32 segmentsToDelete = checkRibbonDistance(mDataBlock->segmentsPerUpdate);
+
+   for (U32 i = 0; i < segmentsToDelete; i++) {
+      U32 last = mSegmentPoints.size() - 1;
+      if (last < 0)
+         break;
+      mTravelledDistance += last ? (mSegmentPoints[last] - mSegmentPoints[last-1]).len() : 0;
+      mSegmentPoints.pop_back();
+      mUpdateBuffers = true;
+      mSegmentOffset++;
+   }
+
+   //If there is no other points, just add a new one.
+   if (mSegmentPoints.size() == 0) {
+
+      mSegmentPoints.push_front(point);
+      mUpdateBuffers = true;
+      return;
+   }
+
+   Point3F startPoint = mSegmentPoints[0];
+
+   //add X points based on how many segments Per Update from last point to current point
+   for (U32 i = 0; i < mDataBlock->segmentsPerUpdate; i++) {
+
+      F32 interp = (F32(i+1) / (F32)mDataBlock->segmentsPerUpdate);
+      //(end - start) * percentage) + start
+      Point3F derivedPoint = ((point - startPoint) * interp) + startPoint;
+
+      mSegmentPoints.push_front(derivedPoint);
+      mUpdateBuffers = true;
+   }
+
+   if (mSegmentPoints.size() > 1) {
+
+      Point3F pointA = mSegmentPoints[mSegmentPoints.size()-1];
+      Point3F pointB = mSegmentPoints[0];
+
+      Point3F diffSize = pointA - pointB;
+
+      if (diffSize.x == 0.0f)
+         diffSize.x = 1.0f;
+
+      if (diffSize.y == 0.0f)
+         diffSize.y = 1.0f;
+
+      if (diffSize.z == 0.0f)
+         diffSize.z = 1.0f;
+
+      Box3F objBox;
+      objBox.minExtents.set( diffSize * -1 );
+      objBox.maxExtents.set( diffSize  );
+
+      if (objBox.minExtents.x > objBox.maxExtents.x) {
+         F32 tmp = objBox.minExtents.x;
+         objBox.minExtents.x = objBox.maxExtents.x;
+         objBox.maxExtents.x = tmp;
+      }
+      if (objBox.minExtents.y > objBox.maxExtents.y) {
+         F32 tmp = objBox.minExtents.y;
+         objBox.minExtents.y = objBox.maxExtents.y;
+         objBox.maxExtents.y = tmp;
+      }
+      if (objBox.minExtents.z > objBox.maxExtents.z) {
+         F32 tmp = objBox.minExtents.z;
+         objBox.minExtents.z = objBox.maxExtents.z;
+         objBox.maxExtents.z = tmp;
+      }
+
+
+
+      if (objBox.isValidBox()) {
+         mObjBox = objBox;
+         // Reset the World Box.
+         resetWorldBox();
+      }
+   }
+
+}
+
+void Ribbon::deleteOnEnd() {
+
+   mDeleteOnEnd = true;
+   mUseFadeOut = mDataBlock->mUseFadeOut;
+   mFadeAwayStep = mDataBlock->mFadeAwayStep;
+
+}
+
+U32 Ribbon::checkRibbonDistance(S32 segments) {
+
+   S32 len = mSegmentPoints.size();
+   S32 difference = (mDataBlock->mRibbonLength/(mDataBlock->mSegmentSkipAmount+1)) - len;
+
+   if (difference < 0)
+      return mAbs(difference);
+
+   return 0;  //do not delete any points
+}
+
+void Ribbon::setShaderParams() {
+
+   F32 numSegments = (F32)mSegmentPoints.size();
+   F32 length = (F32)mDataBlock->mRibbonLength;
+   Point3F radius(numSegments / length, numSegments, length);
+   MaterialParameters* matParams = mRibbonMat->getMaterialParameters();
+   matParams->setSafe( mRadiusSC, radius );	
+}
+
+//--------------------------------------------------------------------------
+//U32 Ribbon::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
+//{
+//   U32 retMask = Parent::packUpdate(con, mask, stream);
+//   return retMask;
+//}
+//
+//void Ribbon::unpackUpdate(NetConnection* con, BitStream* stream)
+//{
+//   Parent::unpackUpdate(con, stream);
+//}
+
+//--------------------------------------------------------------------------
+void Ribbon::prepRenderImage(SceneRenderState *state)
+{
+   if (mFadeOut == 0.0f)
+      return;
+
+   if(!mRibbonMat)
+      return;
+
+   if (mDeleteOnEnd == true && mUseFadeOut == false) {
+      return;
+   }
+
+   // We only render during the normal diffuse render pass.
+   if( !state->isDiffusePass() )
+      return;
+
+   U32 segments = mSegmentPoints.size();
+   if (segments < 2)
+      return;
+
+   MeshRenderInst *ri = state->getRenderPass()->allocInst<MeshRenderInst>();
+   ri->type = RenderPassManager::RIT_Translucent;
+   ri->translucentSort = true;
+   ri->sortDistSq = ( mSegmentPoints[0] - state->getCameraPosition() ).lenSquared();
+
+   RenderPassManager *renderPass = state->getRenderPass();
+   MatrixF *proj = renderPass->allocUniqueXform(MatrixF( true ));
+   proj->mul(GFX->getProjectionMatrix());
+   proj->mul(GFX->getWorldMatrix());
+   ri->objectToWorld = &MatrixF::Identity;
+   ri->worldToCamera = &MatrixF::Identity;
+   ri->projection    = proj;
+   ri->matInst = mRibbonMat;
+
+   // Set up our vertex buffer and primitive buffer
+   if(mUpdateBuffers)
+      createBuffers(state, verts, primBuffer, segments);
+
+   ri->vertBuff = &verts;
+   ri->primBuff = &primBuffer;
+   ri->visibility = 1.0f;
+
+   ri->prim = renderPass->allocPrim();
+   ri->prim->type = GFXTriangleList;
+   ri->prim->minIndex = 0;
+   ri->prim->startIndex = 0;
+   ri->prim->numPrimitives = (segments-1) * 2;
+   ri->prim->startVertex = 0;
+   ri->prim->numVertices = segments * 2;
+
+   if (mRibbonMat) {
+      ri->defaultKey = mRibbonMat->getStateHint();
+   } else {
+      ri->defaultKey = 1;
+   }
+   ri->defaultKey2 = (U32)ri->vertBuff; // Not 64bit safe!
+
+   state->getRenderPass()->addInst(ri);
+}
+
+void Ribbon::createBuffers(SceneRenderState *state, GFXVertexBufferHandle<GFXVertexPCNTT> &verts, GFXPrimitiveBufferHandle &pb, U32 segments) {
+   PROFILE_SCOPE( Ribbon_createBuffers );
+   Point3F cameraPos = state->getCameraPosition();
+   U32 count = 0;
+   U32 indexCount = 0;
+   verts.set(GFX, (segments*2), GFXBufferTypeDynamic);
+
+   // create index buffer based on that size
+   U32 indexListSize = (segments-1) * 6;
+   pb.set( GFX, indexListSize, 0, GFXBufferTypeDynamic );
+   U16 *indices = NULL;
+
+   verts.lock();
+   pb.lock( &indices );
+   F32 totalLength = 0;
+   if(mDataBlock->mTexcoordsRelativeToDistance)
+   {
+      for (U32 i = 0; i < segments; i++)
+         if (i != 0)
+            totalLength += (mSegmentPoints[i] - mSegmentPoints[i-1]).len();
+   }
+
+   U8 fixedAppend = 0;
+   F32 curLength = 0;
+   for (U32 i = 0; i < segments; i++) {
+
+      F32 interpol = ((F32)i / (F32)(segments-1));
+      Point3F leftvert = mSegmentPoints[i];
+      Point3F rightvert = mSegmentPoints[i];
+      F32 tRadius = mDataBlock->mSizes[0];
+      ColorF tColor = mDataBlock->mColours[0];
+
+      for (U8 j = 0; j < RIBBON_NUM_FIELDS-1; j++) {
+
+         F32 curPosition = mDataBlock->mTimes[j];
+         F32 curRadius = mDataBlock->mSizes[j];
+         ColorF curColor = mDataBlock->mColours[j];
+         F32 nextPosition = mDataBlock->mTimes[j+1];
+         F32 nextRadius = mDataBlock->mSizes[j+1];
+         ColorF nextColor = mDataBlock->mColours[j+1];
+
+         if (  curPosition < 0
+            || curPosition > interpol )
+            break;
+         F32 positionDiff = (interpol - curPosition) / (nextPosition - curPosition);
+
+         tRadius = curRadius + (nextRadius - curRadius) * positionDiff;
+         tColor.interpolate(curColor, nextColor, positionDiff);
+      }
+
+      Point3F diff;
+      F32 length;
+      if (i == 0) {
+         diff = mSegmentPoints[i+1] - mSegmentPoints[i];
+         length = 0;
+      } else if (i == segments-1) {
+         diff = mSegmentPoints[i] - mSegmentPoints[i-1];
+         length = diff.len();
+      } else {
+         diff = mSegmentPoints[i+1] - mSegmentPoints[i-1];
+         length = (mSegmentPoints[i] - mSegmentPoints[i-1]).len();
+      }
+
+      //left point
+      Point3F eyeMinPos = cameraPos - leftvert;
+      Point3F perpendicular = mCross(diff, eyeMinPos);
+      perpendicular.normalize();
+      perpendicular = perpendicular * tRadius * -1.0f;
+      perpendicular += mSegmentPoints[i];
+
+      verts[count].point.set(perpendicular);
+      ColorF color = tColor;
+
+      if (mDataBlock->mUseFadeOut)
+         color.alpha *= mFadeOut;
+
+      F32 texCoords;
+      if(mDataBlock->mFixedTexcoords && !mDataBlock->mTexcoordsRelativeToDistance)
+      {
+         U32 fixedIdx = (i+mDataBlock->mRibbonLength-mSegmentOffset)%mDataBlock->mRibbonLength;
+         if(fixedIdx == 0 && i > 0)
+            fixedAppend++;
+         F32 fixedInterpol = (F32)fixedIdx / (F32)(mDataBlock->mRibbonLength);
+         fixedInterpol += fixedAppend;
+         texCoords = (1.0f - fixedInterpol)*mDataBlock->mTileScale;
+      }
+      else if(mDataBlock->mTexcoordsRelativeToDistance)
+         texCoords = (mTravelledDistance + (totalLength - (curLength + length)))*mDataBlock->mTileScale;
+      else
+         texCoords = (1.0f - interpol)*mDataBlock->mTileScale;
+
+      verts[count].color = color;
+      verts[count].texCoord[1] = Point2F(interpol, 0);
+      verts[count].texCoord[0] = Point2F(0.0f, texCoords);
+      verts[count].normal.set(diff);
+
+      //Triangle strip style indexing, so grab last 2
+      if (count > 1) {
+         indices[indexCount] = count-2;
+         indexCount++;
+         indices[indexCount] = count;
+         indexCount++;
+         indices[indexCount] = count-1;
+         indexCount++;
+      }
+      count++;
+
+      eyeMinPos = cameraPos - rightvert;
+      perpendicular = mCross(diff, eyeMinPos);
+      perpendicular.normalize();
+      perpendicular = perpendicular * tRadius;
+      perpendicular += mSegmentPoints[i];
+
+      verts[count].point.set(perpendicular);
+      color = tColor;
+
+      if (mDataBlock->mUseFadeOut)
+         color.alpha *= mFadeOut;
+
+      verts[count].color = color;
+      verts[count].texCoord[1] = Point2F(interpol, 1);
+      verts[count].texCoord[0] = Point2F(1.0f, texCoords);
+      verts[count].normal.set(diff);
+
+      //Triangle strip style indexing, so grab last 2
+      if (count > 1) {
+         indices[indexCount] = count-2;
+         indexCount++;
+         indices[indexCount] = count-1;
+         indexCount++;
+         indices[indexCount] = count;
+         indexCount++;
+      }
+      count++;
+      curLength += length;
+   }
+
+   Point3F pointA = verts[count-1].point;
+   Point3F pointB = verts[0].point;
+
+   verts.unlock();
+   pb.unlock();
+
+   Point3F diffSize = pointA - pointB;
+
+   Box3F objBox;
+   objBox.minExtents.set( diffSize * -1 );
+   objBox.maxExtents.set( diffSize  );
+
+   if (objBox.minExtents.x > objBox.maxExtents.x) {
+      F32 tmp = objBox.minExtents.x;
+      objBox.minExtents.x = objBox.maxExtents.x;
+      objBox.maxExtents.x = tmp;
+   }
+   if (objBox.minExtents.y > objBox.maxExtents.y) {
+      F32 tmp = objBox.minExtents.y;
+      objBox.minExtents.y = objBox.maxExtents.y;
+      objBox.maxExtents.y = tmp;
+   }
+   if (objBox.minExtents.z > objBox.maxExtents.z) {
+      F32 tmp = objBox.minExtents.z;
+      objBox.minExtents.z = objBox.maxExtents.z;
+      objBox.maxExtents.z = tmp;
+   }
+
+   if (objBox.isValidBox()) {
+      mObjBox = objBox;
+      // Reset the World Box.
+      resetWorldBox();
+   }
+
+   mUpdateBuffers = false;
+}

+ 127 - 0
Engine/source/T3D/fx/ribbon.h

@@ -0,0 +1,127 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#ifndef _RIBBON_H_
+#define _RIBBON_H_
+
+#ifndef _GAMEBASE_H_
+#include "T3D/gameBase/gameBase.h"
+#endif
+
+#ifndef _GFXPRIMITIVEBUFFER_H_
+#include "gfx/gfxPrimitiveBuffer.h"
+#endif
+
+#ifndef _GFXVERTEXBUFFER_H_
+#include "gfx/gfxVertexBuffer.h"
+#endif
+
+#include "materials/materialParameters.h"
+#include "math/util/matrixSet.h"
+
+#define RIBBON_NUM_FIELDS 4
+
+//--------------------------------------------------------------------------
+class RibbonData : public GameBaseData
+{
+   typedef GameBaseData Parent;
+
+protected:
+   bool onAdd();
+
+public:
+
+   U32 mRibbonLength; ///< The amount of segments that will make up the ribbon.
+   F32 mSizes[RIBBON_NUM_FIELDS]; ///< The radius for each keyframe.
+   ColorF mColours[RIBBON_NUM_FIELDS]; ///< The colour of the ribbon for each keyframe.
+   F32 mTimes[RIBBON_NUM_FIELDS]; ///< The relative time for each keyframe.
+   StringTableEntry mMatName; ///< The material for the ribbon.
+   bool mUseFadeOut; ///< If true, the ribbon will fade away after deletion.
+   F32 mFadeAwayStep; ///< How quickly the ribbons is faded away after deletion.
+   S32 segmentsPerUpdate; ///< Amount of segments to add each update.
+   F32 mTileScale; ///< A scalar to scale the texcoord.
+   bool mFixedTexcoords; ///< If true, texcoords will stay the same over the lifetime for each segment.
+   bool mTexcoordsRelativeToDistance; ///< If true, texcoords will not be stretched if the distance between 2 segments are long.
+   S32 mSegmentSkipAmount; ///< The amount of segments to skip each time segments are added.
+
+   RibbonData();
+
+   void packData(BitStream*);
+   void unpackData(BitStream*);
+   bool preload(bool server, String &errorBuffer);
+
+   static void initPersistFields();
+   DECLARE_CONOBJECT(RibbonData);
+};
+
+//--------------------------------------------------------------------------
+class Ribbon : public GameBase
+{
+   typedef GameBase Parent;
+   RibbonData* mDataBlock;
+   Vector<Point3F> mSegmentPoints; ///< The points in space where the ribbon has spawned segments.
+   BaseMatInstance *mRibbonMat;
+   MaterialParameterHandle* mRadiusSC;
+   MaterialParameterHandle* mRibbonProjSC;
+   GFXPrimitiveBufferHandle primBuffer;
+   GFXVertexBufferHandle<GFXVertexPCNTT> verts;
+   bool mUpdateBuffers; ///< If true, the vertex buffers need to be updated.
+   bool mDeleteOnEnd; ///< If true, the ribbon should delete itself as soon as the last segment is deleted
+   bool mUseFadeOut; ///< If true, the ribbon will fade away upon deletion
+   F32 mFadeAwayStep; ///< How quickly the ribbons is faded away after deletion.
+   F32 mFadeOut;
+   U32 mSegmentOffset;
+   U32 mSegmentIdx;
+   F32 mTravelledDistance; ///< How far the ribbon has travelled in it's lifetime.
+
+protected:
+
+   bool onAdd();
+   void processTick(const Move*);
+   void advanceTime(F32);
+   void interpolateTick(F32 delta);
+
+   // Rendering
+   void prepRenderImage(SceneRenderState *state);
+
+   ///Checks to see if ribbon is too long
+   U32 checkRibbonDistance(S32 segments);
+   void setShaderParams();
+   /// Construct the vertex and primitive buffers
+   void createBuffers(SceneRenderState *state, GFXVertexBufferHandle<GFXVertexPCNTT> &verts, GFXPrimitiveBufferHandle &pb, U32 segments);
+
+public:
+   Ribbon();
+   ~Ribbon();
+
+   DECLARE_CONOBJECT(Ribbon);
+   static void initPersistFields();
+   bool onNewDataBlock(GameBaseData*,bool);
+   void addSegmentPoint(Point3F &point, MatrixF &mat);  ///< Used to add another segment to the ribbon.
+   void clearSegments() { mSegmentPoints.clear(); } ///< Delete all segments.
+   void deleteOnEnd(); ///< Delete the ribbon when all segments have been deleted.
+   void onRemove();
+
+};
+
+#endif // _H_RIBBON
+