Browse Source

Merge pull request #744 from lukaspj/Ribbon-Implementation

Ribbon and RibbonNode
Daniel Buckmaster 11 years ago
parent
commit
ef9bc91bff
27 changed files with 1753 additions and 0 deletions
  1. 707 0
      Engine/source/T3D/fx/ribbon.cpp
  2. 142 0
      Engine/source/T3D/fx/ribbon.h
  3. 324 0
      Engine/source/T3D/fx/ribbonNode.cpp
  4. 105 0
      Engine/source/T3D/fx/ribbonNode.h
  5. 9 0
      Engine/source/gfx/gfxVertexTypes.cpp
  6. 8 0
      Engine/source/gfx/gfxVertexTypes.h
  7. 48 0
      Templates/Empty/game/art/ribbons/materials.cs
  8. 23 0
      Templates/Empty/game/art/ribbons/ribbonExec.cs
  9. 44 0
      Templates/Empty/game/art/ribbons/ribbons.cs
  10. 1 0
      Templates/Empty/game/core/scripts/server/game.cs
  11. 1 0
      Templates/Empty/game/scripts/server/game.cs
  12. 18 0
      Templates/Empty/game/shaders/common/ribbons/basicRibbonShaderP.hlsl
  13. 34 0
      Templates/Empty/game/shaders/common/ribbons/basicRibbonShaderV.hlsl
  14. 8 0
      Templates/Empty/game/tools/worldEditor/gui/objectBuilderGui.ed.gui
  15. 1 0
      Templates/Empty/game/tools/worldEditor/scripts/editors/creator.ed.cs
  16. 77 0
      Templates/Full/game/art/ribbons/materials.cs
  17. BIN
      Templates/Full/game/art/ribbons/ribTex.png
  18. 23 0
      Templates/Full/game/art/ribbons/ribbonExec.cs
  19. 63 0
      Templates/Full/game/art/ribbons/ribbons.cs
  20. 1 0
      Templates/Full/game/core/scripts/server/game.cs
  21. 1 0
      Templates/Full/game/scripts/server/game.cs
  22. 18 0
      Templates/Full/game/shaders/common/ribbons/basicRibbonShaderP.hlsl
  23. 34 0
      Templates/Full/game/shaders/common/ribbons/basicRibbonShaderV.hlsl
  24. 20 0
      Templates/Full/game/shaders/common/ribbons/texRibbonShaderP.hlsl
  25. 34 0
      Templates/Full/game/shaders/common/ribbons/texRibbonShaderV.hlsl
  26. 8 0
      Templates/Full/game/tools/worldEditor/gui/objectBuilderGui.ed.gui
  27. 1 0
      Templates/Full/game/tools/worldEditor/scripts/editors/creator.ed.cs

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

@@ -0,0 +1,707 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2014 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 < NumFields; 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();
+
+   addGroup("Ribbon");
+
+   addField("size", TypeF32, Offset(mSizes, RibbonData), NumFields,
+      "The size of the ribbon at the specified keyframe.");
+   addField("color", TypeColorF, Offset(mColours, RibbonData), NumFields,
+      "The colour of the ribbon at the specified keyframe.");
+   addField("position", TypeF32, Offset(mTimes, RibbonData), NumFields,
+      "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("segmentsPerUpdate", TypeS32, Offset(segmentsPerUpdate, RibbonData),
+      "How many segments to add each update.");
+   addField("skipAmount", TypeS32, Offset(mSegmentSkipAmount, RibbonData),
+      "The amount of segments to skip each update.");
+
+   addField("useFadeOut", TypeBool, Offset(mUseFadeOut, RibbonData),
+      "If true, the ribbon will fade away after deletion.");
+   addField("fadeAwayStep", TypeF32, Offset(mFadeAwayStep, RibbonData),
+      "How much to fade the ribbon with each update, after deletion.");
+   addField("ribbonMaterial", TypeString, Offset(mMatName, RibbonData),
+      "The material the ribbon uses for rendering.");
+   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("texcoordsRelativeToDistance", TypeBool, Offset(mTexcoordsRelativeToDistance, RibbonData),
+      "If true, texture coordinates are scaled relative to distance, this prevents"
+      "'stretched' textures.");
+
+   endGroup("Ribbon");
+}
+
+
+//--------------------------------------------------------------------------
+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 < NumFields; 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 < NumFields; 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 < RibbonData::NumFields-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;
+}

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

@@ -0,0 +1,142 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2014 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"
+
+//--------------------------------------------------------------------------
+class RibbonData : public GameBaseData
+{
+   typedef GameBaseData Parent;
+
+protected:
+   bool onAdd();
+
+public:
+
+   enum Constants
+   {
+      NumFields = 4
+   };
+
+   F32 mSizes[NumFields];      ///< The radius for each keyframe.
+   ColorF mColours[NumFields]; ///< The colour of the ribbon for each keyframe.
+   F32 mTimes[NumFields];      ///< The relative time for each keyframe.
+
+   U32 mRibbonLength;      ///< The amount of segments that will make up the ribbon.
+   S32 segmentsPerUpdate;  ///< Amount of segments to add each update.
+   S32 mSegmentSkipAmount; ///< The amount of segments to skip each time segments are added.
+
+   bool mUseFadeOut;          ///< If true, the ribbon will fade away after deletion.
+   F32 mFadeAwayStep;         ///< How quickly the ribbons is faded away after deletion.
+   StringTableEntry mMatName; ///< The material for the ribbon.
+   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.
+
+   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;
+
+   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;
+   F32 mTravelledDistance; ///< How far the ribbon has travelled in it's lifetime.
+
+   Vector<Point3F> mSegmentPoints; ///< The points in space where the ribbon has spawned segments.
+   U32 mSegmentOffset;
+   U32 mSegmentIdx;
+
+   bool mUpdateBuffers; ///< If true, the vertex buffers need to be updated.
+   BaseMatInstance *mRibbonMat;
+   MaterialParameterHandle* mRadiusSC;
+   MaterialParameterHandle* mRibbonProjSC;
+   GFXPrimitiveBufferHandle primBuffer;
+   GFXVertexBufferHandle<GFXVertexPCNTT> verts;
+
+protected:
+
+   bool onAdd();
+   void processTick(const Move*);
+   void advanceTime(F32);
+   void interpolateTick(F32 delta);
+
+   // Rendering
+   void prepRenderImage(SceneRenderState *state);
+   void setShaderParams();
+
+   ///Checks to see if ribbon is too long
+   U32 checkRibbonDistance(S32 segments);
+
+   /// 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 onRemove();
+
+   /// Used to add another segment to the ribbon.
+   void addSegmentPoint(Point3F &point, MatrixF &mat);
+
+   /// Delete all segments.
+   void clearSegments() { mSegmentPoints.clear(); }
+
+   /// Delete the ribbon when all segments have been deleted.
+   void deleteOnEnd();
+};
+
+#endif // _H_RIBBON
+

+ 324 - 0
Engine/source/T3D/fx/ribbonNode.cpp

@@ -0,0 +1,324 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2014 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 "ribbonNode.h"
+#include "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+#include "T3D/fx/ribbon.h"
+#include "math/mathIO.h"
+#include "sim/netConnection.h"
+#include "console/engineAPI.h"
+
+IMPLEMENT_CO_DATABLOCK_V1(RibbonNodeData);
+IMPLEMENT_CO_NETOBJECT_V1(RibbonNode);
+
+ConsoleDocClass( RibbonNodeData,
+   "@brief Contains additional data to be associated with a RibbonNode."
+   "@ingroup FX\n"
+   );
+
+ConsoleDocClass( RibbonNode, ""
+   );
+
+
+//-----------------------------------------------------------------------------
+// RibbonNodeData
+//-----------------------------------------------------------------------------
+RibbonNodeData::RibbonNodeData()
+{
+}
+
+RibbonNodeData::~RibbonNodeData()
+{
+
+}
+
+//-----------------------------------------------------------------------------
+// initPersistFields
+//-----------------------------------------------------------------------------
+void RibbonNodeData::initPersistFields()
+{
+   Parent::initPersistFields();
+}
+
+
+//-----------------------------------------------------------------------------
+// RibbonNode
+//-----------------------------------------------------------------------------
+RibbonNode::RibbonNode()
+{
+   // Todo: ScopeAlways?
+   mNetFlags.set(Ghostable);
+   mTypeMask |= EnvironmentObjectType;
+
+   mActive = true;
+
+   mDataBlock          = NULL;
+   mRibbonDatablock   = NULL;
+   mRibbonDatablockId = 0;
+   mRibbon            = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Destructor
+//-----------------------------------------------------------------------------
+RibbonNode::~RibbonNode()
+{
+   //
+}
+
+//-----------------------------------------------------------------------------
+// initPersistFields
+//-----------------------------------------------------------------------------
+void RibbonNode::initPersistFields()
+{
+   addField( "active", TYPEID< bool >(), Offset(mActive,RibbonNode),
+      "Controls whether ribbon is emitted from this node." );
+   addField( "ribbon",  TYPEID< RibbonData >(), Offset(mRibbonDatablock, RibbonNode),
+      "Datablock to use for the ribbon." );
+
+   Parent::initPersistFields();
+}
+
+//-----------------------------------------------------------------------------
+// onAdd
+//-----------------------------------------------------------------------------
+bool RibbonNode::onAdd()
+{
+   if( !Parent::onAdd() )
+      return false;
+
+   if( !mRibbonDatablock && mRibbonDatablockId != 0 )
+   {
+      if( Sim::findObject(mRibbonDatablockId, mRibbonDatablock) == false )
+         Con::errorf(ConsoleLogEntry::General, "RibbonNode::onAdd: Invalid packet, bad datablockId(mRibbonDatablock): %d", mRibbonDatablockId);
+   }
+
+   if( isClientObject() )
+   {
+      setRibbonDatablock( mRibbonDatablock );
+   }
+   else
+   {
+      setMaskBits( StateMask | EmitterDBMask );
+   }
+
+   mObjBox.minExtents.set(-0.5, -0.5, -0.5);
+   mObjBox.maxExtents.set( 0.5,  0.5,  0.5);
+   resetWorldBox();
+   addToScene();
+
+   return true;
+}
+
+//-----------------------------------------------------------------------------
+// onRemove
+//-----------------------------------------------------------------------------
+void RibbonNode::onRemove()
+{
+   removeFromScene();
+   if( isClientObject() )
+   {
+      if( mRibbon )
+      {
+         mRibbon->deleteOnEnd();
+         mRibbon = NULL;
+      }
+   }
+
+   Parent::onRemove();
+}
+
+//-----------------------------------------------------------------------------
+// onNewDataBlock
+//-----------------------------------------------------------------------------
+bool RibbonNode::onNewDataBlock( GameBaseData *dptr, bool reload )
+{
+   mDataBlock = dynamic_cast<RibbonNodeData*>( dptr );
+   if ( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) )
+      return false;
+
+   // Todo: Uncomment if this is a "leaf" class
+   scriptOnNewDataBlock();
+   return true;
+}
+
+//-----------------------------------------------------------------------------
+void RibbonNode::inspectPostApply()
+{
+   Parent::inspectPostApply();
+   setMaskBits(StateMask | EmitterDBMask);
+}
+
+//-----------------------------------------------------------------------------
+// processTick
+//-----------------------------------------------------------------------------
+void RibbonNode::processTick(const Move* move)
+{
+   Parent::processTick(move);
+
+   if ( isMounted() )
+   {
+      MatrixF mat;
+      mMount.object->getMountTransform( mMount.node, mMount.xfm, &mat );
+      setTransform( mat );
+   }
+}
+
+//-----------------------------------------------------------------------------
+// advanceTime
+//-----------------------------------------------------------------------------
+void RibbonNode::advanceTime(F32 dt)
+{
+   Parent::advanceTime(dt);
+
+   if(!mActive || mRibbon.isNull() || !mDataBlock)
+      return;
+
+   MatrixF trans(getTransform());
+   Point3F pos = getPosition();
+   mRibbon->addSegmentPoint(pos, trans);
+}
+
+//-----------------------------------------------------------------------------
+// packUpdate
+//-----------------------------------------------------------------------------
+U32 RibbonNode::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
+{
+   U32 retMask = Parent::packUpdate(con, mask, stream);
+
+   if ( stream->writeFlag( mask & InitialUpdateMask ) )
+   {
+      mathWrite(*stream, getTransform());
+      mathWrite(*stream, getScale());
+   }
+
+   if ( stream->writeFlag( mask & EmitterDBMask ) )
+   {
+      if( stream->writeFlag(mRibbonDatablock != NULL) )
+      {
+         stream->writeRangedU32(mRibbonDatablock->getId(), DataBlockObjectIdFirst,
+            DataBlockObjectIdLast);
+      }
+   }
+
+   if ( stream->writeFlag( mask & StateMask ) )
+   {
+      stream->writeFlag( mActive );
+   }
+
+   return retMask;
+}
+
+//-----------------------------------------------------------------------------
+// unpackUpdate
+//-----------------------------------------------------------------------------
+void RibbonNode::unpackUpdate(NetConnection* con, BitStream* stream)
+{
+   Parent::unpackUpdate(con, stream);
+
+   if ( stream->readFlag() )
+   {
+      MatrixF temp;
+      Point3F tempScale;
+      mathRead(*stream, &temp);
+      mathRead(*stream, &tempScale);
+
+      setScale(tempScale);
+      setTransform(temp);
+   }
+
+   if ( stream->readFlag() )
+   {
+      mRibbonDatablockId = stream->readFlag() ?
+         stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast) : 0;
+
+      RibbonData *emitterDB = NULL;
+      Sim::findObject( mRibbonDatablockId, emitterDB );
+      if ( isProperlyAdded() )
+         setRibbonDatablock( emitterDB );
+   }
+
+   if ( stream->readFlag() )
+   {
+      mActive = stream->readFlag();
+   }
+}
+
+//-----------------------------------------------------------------------------
+// setRibbonDatablock
+//-----------------------------------------------------------------------------
+void RibbonNode::setRibbonDatablock(RibbonData* data)
+{
+   if ( isServerObject() )
+   {
+      setMaskBits( EmitterDBMask );
+   }
+   else
+   {
+      Ribbon* pRibbon = NULL;
+      if ( data )
+      {
+         // Create emitter with new datablock
+         pRibbon = new Ribbon;
+         pRibbon->onNewDataBlock( data, false );
+         if( pRibbon->registerObject() == false )
+         {
+            Con::warnf(ConsoleLogEntry::General, "Could not register base ribbon of class: %s", data->getName() ? data->getName() : data->getIdString() );
+            delete pRibbon;
+            return;
+         }
+      }
+
+      // Replace emitter
+      if ( mRibbon )
+         mRibbon->deleteOnEnd();
+
+      mRibbon = pRibbon;
+   }
+
+   mRibbonDatablock = data;
+}
+
+DefineEngineMethod(RibbonNode, setRibbonDatablock, void, (RibbonData* ribbonDatablock), (0),
+   "Assigns the datablock for this ribbon node.\n"
+   "@param ribbonDatablock RibbonData datablock to assign\n"
+   "@tsexample\n"
+   "// Assign a new emitter datablock\n"
+   "%emitter.setRibbonDatablock( %ribbonDatablock );\n"
+   "@endtsexample\n" )
+{
+   if ( !ribbonDatablock )
+   {
+      Con::errorf("RibbonData datablock could not be found when calling setRibbonDataBlock in ribbonNode.");
+      return;
+   }
+
+   object->setRibbonDatablock(ribbonDatablock);
+}
+
+DefineEngineMethod(RibbonNode, setActive, void, (bool active),,
+   "Turns the ribbon on or off.\n"
+   "@param active New ribbon state\n" )
+{
+   object->setActive( active );
+}

+ 105 - 0
Engine/source/T3D/fx/ribbonNode.h

@@ -0,0 +1,105 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2014 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_NODE_H_
+#define _RIBBON_NODE_H_
+
+#ifndef _GAMEBASE_H_
+#include "T3D/gameBase/gameBase.h"
+#endif
+
+class RibbonData;
+class Ribbon;
+
+//*****************************************************************************
+// ParticleEmitterNodeData
+//*****************************************************************************
+class RibbonNodeData : public GameBaseData
+{
+   typedef GameBaseData Parent;
+
+public:
+   F32 timeMultiple;
+
+public:
+   RibbonNodeData();
+   ~RibbonNodeData();
+
+   DECLARE_CONOBJECT(RibbonNodeData);
+   static void initPersistFields();
+};
+
+
+//*****************************************************************************
+// ParticleEmitterNode
+//*****************************************************************************
+class RibbonNode : public GameBase
+{
+   typedef GameBase Parent;
+
+   enum MaskBits
+   {
+      StateMask      = Parent::NextFreeMask << 0,
+      EmitterDBMask  = Parent::NextFreeMask << 1,
+      NextFreeMask   = Parent::NextFreeMask << 2,
+   };
+
+   RibbonNodeData* mDataBlock;
+
+protected:
+   bool onAdd();
+   void onRemove();
+   bool onNewDataBlock( GameBaseData *dptr, bool reload );
+   void inspectPostApply();
+
+   RibbonData* mRibbonDatablock;
+   S32 mRibbonDatablockId;
+
+   SimObjectPtr<Ribbon> mRibbon;
+
+   bool mActive;
+
+public:
+   RibbonNode();
+   ~RibbonNode();
+
+   Ribbon *getRibbonEmitter() {return mRibbon;}
+
+   // Time/Move Management
+
+   void processTick(const Move* move);
+   void advanceTime(F32 dt);
+
+   DECLARE_CONOBJECT(RibbonNode);
+   static void initPersistFields();
+
+   U32  packUpdate  (NetConnection *conn, U32 mask, BitStream* stream);
+   void unpackUpdate(NetConnection *conn,           BitStream* stream);
+
+   inline bool getActive( void )        { return mActive;                             };
+   inline void setActive( bool active ) { mActive = active; setMaskBits( StateMask ); };
+
+   void setRibbonDatablock(RibbonData* data);
+};
+
+#endif // _RIBBON_NODE_H_
+

+ 9 - 0
Engine/source/gfx/gfxVertexTypes.cpp

@@ -99,6 +99,15 @@ GFXImplementVertexFormat( GFXVertexPNTT )
    addElement( "TEXCOORD", GFXDeclType_Float2, 0 );
 }
 
+GFXImplementVertexFormat( GFXVertexPCNTT )
+{
+   addElement( "POSITION", GFXDeclType_Float3 );
+   addElement( "COLOR", GFXDeclType_Color );
+   addElement( "NORMAL", GFXDeclType_Float3 );
+   addElement( "TEXCOORD", GFXDeclType_Float2, 0 );
+   addElement( "TEXCOORD", GFXDeclType_Float2, 1 );
+}
+
 GFXImplementVertexFormat( GFXVertexPNTBT )
 {
    addElement( "POSITION", GFXDeclType_Float3 );

+ 8 - 0
Engine/source/gfx/gfxVertexTypes.h

@@ -112,6 +112,14 @@ GFXDeclareVertexFormat( GFXVertexPNTT )
    Point2F texCoord;
 };
 
+GFXDeclareVertexFormat( GFXVertexPCNTT )
+{
+   Point3F point;
+   GFXVertexColor color;
+   Point3F normal;
+   Point2F texCoord[2];
+};
+
 GFXDeclareVertexFormat( GFXVertexPNTBT )
 {
    Point3F point;

+ 48 - 0
Templates/Empty/game/art/ribbons/materials.cs

@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+// This material should work fine for uniformly colored ribbons.
+
+//Basic ribbon shader/////////////////////////////////////////////
+ 
+new ShaderData( BasicRibbonShader )
+{
+   DXVertexShaderFile   = "shaders/common/ribbons/basicRibbonShaderV.hlsl";
+   DXPixelShaderFile    = "shaders/common/ribbons/basicRibbonShaderP.hlsl";
+ 
+   pixVersion = 2.0;
+};
+ 
+singleton CustomMaterial( BasicRibbonMat )
+{
+   shader = BasicRibbonShader;
+   version = 2.0;
+   
+   emissive[0] = true;
+   
+   doubleSided = true;
+   translucent = true;
+   BlendOp = AddAlpha;
+   translucentBlendOp = AddAlpha;
+   
+   preload = true;
+};

+ 23 - 0
Templates/Empty/game/art/ribbons/ribbonExec.cs

@@ -0,0 +1,23 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+exec("./ribbons.cs");

+ 44 - 0
Templates/Empty/game/art/ribbons/ribbons.cs

@@ -0,0 +1,44 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+  
+datablock RibbonNodeData(DefaultRibbonNodeData)
+{
+   timeMultiple = 1.0;
+};
+
+//ribbon data////////////////////////////////////////
+
+datablock RibbonData(BasicRibbon)
+{
+   size[0] = 0.5;
+   color[0] = "1.0 0.0 0.0 1.0";
+   position[0] = 0.0;
+ 
+   size[1] = 0.0;
+   color[1] = "1.0 0.0 0.0 0.0";
+   position[1] = 1.0;
+ 
+   RibbonLength = 40;
+   fadeAwayStep = 0.1;
+   UseFadeOut = true;
+   RibbonMaterial = BasicRibbonMat;
+};

+ 1 - 0
Templates/Empty/game/core/scripts/server/game.cs

@@ -34,6 +34,7 @@ function onServerCreated()
    
    // Load up any objects or datablocks saved to the editor managed scripts
    %datablockFiles = new ArrayObject();
+   %datablockFiles.add( "art/ribbons/ribbonExec.cs" );   
    %datablockFiles.add( "art/particles/managedParticleData.cs" );
    %datablockFiles.add( "art/particles/managedParticleEmitterData.cs" );
    %datablockFiles.add( "art/decals/managedDecalData.cs" );

+ 1 - 0
Templates/Empty/game/scripts/server/game.cs

@@ -144,6 +144,7 @@ function onServerCreated()
    
    // Load up any objects or datablocks saved to the editor managed scripts
    %datablockFiles = new ArrayObject();
+   %datablockFiles.add( "art/ribbons/ribbonExec.cs" );   
    %datablockFiles.add( "art/particles/managedParticleData.cs" );
    %datablockFiles.add( "art/particles/managedParticleEmitterData.cs" );
    %datablockFiles.add( "art/decals/managedDecalData.cs" );

+ 18 - 0
Templates/Empty/game/shaders/common/ribbons/basicRibbonShaderP.hlsl

@@ -0,0 +1,18 @@
+#define IN_HLSL
+#include "../shdrConsts.h"
+ 
+struct v2f
+{
+       
+   float2 texCoord        : TEXCOORD0;
+   float2 shiftdata       : TEXCOORD1;
+   float4 color           : COLOR0;
+};
+ 
+float4 main(v2f IN) : COLOR0
+{
+   float fade = 1.0 - abs(IN.shiftdata.y - 0.5) * 2.0;
+   IN.color.xyz = IN.color.xyz + pow(fade, 4) / 10;
+   IN.color.a = IN.color.a * fade;
+   return IN.color;
+}

+ 34 - 0
Templates/Empty/game/shaders/common/ribbons/basicRibbonShaderV.hlsl

@@ -0,0 +1,34 @@
+#define IN_HLSL
+#include "../shdrConsts.h"
+ 
+struct a2v
+{
+        float2 texCoord        : TEXCOORD0;
+        float2 shiftdata       : TEXCOORD1;
+        float3 normal          : NORMAL;
+        float4 position        : POSITION;
+        float4 color           : COLOR0;
+};
+ 
+struct v2f
+{
+        float4 hpos            : POSITION;
+        float2 texCoord        : TEXCOORD0;
+        float2 shiftdata       : TEXCOORD1;
+        float4 color           : COLOR0;
+};
+ 
+uniform float4x4 modelview;
+uniform float3   eyePos;
+ 
+v2f main(a2v IN)
+{
+    v2f OUT;
+ 
+    OUT.hpos = mul(modelview, IN.position);
+    OUT.color = IN.color;
+    OUT.texCoord = IN.texCoord;
+    OUT.shiftdata = IN.shiftdata;
+   
+    return OUT;
+}

+ 8 - 0
Templates/Empty/game/tools/worldEditor/gui/objectBuilderGui.ed.gui

@@ -862,6 +862,14 @@ function ObjectBuilderGui::buildParticleEmitterNode(%this)
    %this.process();
 }
 
+function ObjectBuilderGui::buildRibbonNode(%this)
+{
+   %this.objectClassName = "RibbonNode";
+   %this.addField("dataBlock", "TypeDataBlock", "datablock", "RibbonNodeData");
+   %this.addField("ribbon",   "TypeDataBlock", "Ribbon data", "RibbonData");
+   %this.process();
+}
+
 function ObjectBuilderGui::buildParticleSimulation(%this)
 {
    %this.objectClassName = "ParticleSimulation";

+ 1 - 0
Templates/Empty/game/tools/worldEditor/scripts/editors/creator.ed.cs

@@ -46,6 +46,7 @@ function EWCreatorWindow::init( %this )
       %this.registerMissionObject( "SFXEmitter",          "Sound Emitter" );
       %this.registerMissionObject( "Precipitation" );
       %this.registerMissionObject( "ParticleEmitterNode", "Particle Emitter" );
+      %this.registerMissionObject( "RibbonNode", "Ribbon" );
       
       // Legacy features. Users should use Ground Cover and the Forest Editor.   
       //%this.registerMissionObject( "fxShapeReplicator",   "Shape Replicator" );

+ 77 - 0
Templates/Full/game/art/ribbons/materials.cs

@@ -0,0 +1,77 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+// This material should work fine for uniformly colored ribbons.
+
+//Basic ribbon shader/////////////////////////////////////////////
+ 
+new ShaderData( BasicRibbonShader )
+{
+   DXVertexShaderFile   = "shaders/common/ribbons/basicRibbonShaderV.hlsl";
+   DXPixelShaderFile    = "shaders/common/ribbons/basicRibbonShaderP.hlsl";
+ 
+   pixVersion = 2.0;
+};
+ 
+singleton CustomMaterial( BasicRibbonMat )
+{
+   shader = BasicRibbonShader;
+   version = 2.0;
+   
+   emissive[0] = true;
+   
+   doubleSided = true;
+   translucent = true;
+   BlendOp = AddAlpha;
+   translucentBlendOp = AddAlpha;
+   
+   preload = true;
+};
+
+// This material can render a texture on top of a ribbon.
+
+//Texture ribbon shader/////////////////////////////////////////////
+ 
+new ShaderData( TexturedRibbonShader )
+{
+   DXVertexShaderFile   = "shaders/common/ribbons/texRibbonShaderV.hlsl";
+   DXPixelShaderFile    = "shaders/common/ribbons/texRibbonShaderP.hlsl";
+ 
+   pixVersion = 2.0;
+};
+ 
+singleton CustomMaterial( TexturedRibbonMat )
+{
+   shader = TexturedRibbonShader;
+   version = 2.0;
+   
+   emissive[0] = true;
+   
+   doubleSided = true;
+   translucent = true;
+   BlendOp = AddAlpha;
+   translucentBlendOp = AddAlpha;
+
+   sampler["ribTex"] = "art/ribbons/ribTex.png";
+   
+   preload = true;
+};

BIN
Templates/Full/game/art/ribbons/ribTex.png


+ 23 - 0
Templates/Full/game/art/ribbons/ribbonExec.cs

@@ -0,0 +1,23 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+exec("./ribbons.cs");

+ 63 - 0
Templates/Full/game/art/ribbons/ribbons.cs

@@ -0,0 +1,63 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+  
+datablock RibbonNodeData(DefaultRibbonNodeData)
+{
+   timeMultiple = 1.0;
+};
+
+//ribbon data////////////////////////////////////////
+
+datablock RibbonData(BasicRibbon)
+{
+   size[0] = 0.5;
+   color[0] = "1.0 0.0 0.0 1.0";
+   position[0] = 0.0;
+ 
+   size[1] = 0.0;
+   color[1] = "1.0 0.0 0.0 0.0";
+   position[1] = 1.0;
+ 
+   RibbonLength = 40;
+   fadeAwayStep = 0.1;
+   UseFadeOut = true;
+   RibbonMaterial = BasicRibbonMat;
+};
+
+datablock RibbonData(TexturedRibbon)
+{
+   RibbonMaterial = TexturedRibbonMat;
+   size[0] = 0.5;
+   color[0] = "1.0 1.0 1.0 1.0";
+   position[0] = 0.0;
+ 
+   size[1] = 0.5;
+   color[1] = "1.0 1.0 1.0 1.0";
+   position[1] = 1.0;
+ 
+   RibbonLength = 40;
+   fadeAwayStep = 0.1;
+   UseFadeOut = true;
+   tileScale = 1;
+   fixedTexCoords = true;
+   TexcoordsRelativeToDistance = true;
+};

+ 1 - 0
Templates/Full/game/core/scripts/server/game.cs

@@ -34,6 +34,7 @@ function onServerCreated()
    
    // Load up any objects or datablocks saved to the editor managed scripts
    %datablockFiles = new ArrayObject();
+   %datablockFiles.add( "art/ribbons/ribbonExec.cs" );   
    %datablockFiles.add( "art/particles/managedParticleData.cs" );
    %datablockFiles.add( "art/particles/managedParticleEmitterData.cs" );
    %datablockFiles.add( "art/decals/managedDecalData.cs" );

+ 1 - 0
Templates/Full/game/scripts/server/game.cs

@@ -50,6 +50,7 @@ function onServerCreated()
 
    // Load up any objects or datablocks saved to the editor managed scripts
    %datablockFiles = new ArrayObject();
+   %datablockFiles.add( "art/ribbons/ribbonExec.cs" );   
    %datablockFiles.add( "art/particles/managedParticleData.cs" );
    %datablockFiles.add( "art/particles/managedParticleEmitterData.cs" );
    %datablockFiles.add( "art/decals/managedDecalData.cs" );

+ 18 - 0
Templates/Full/game/shaders/common/ribbons/basicRibbonShaderP.hlsl

@@ -0,0 +1,18 @@
+#define IN_HLSL
+#include "../shdrConsts.h"
+ 
+struct v2f
+{
+       
+   float2 texCoord        : TEXCOORD0;
+   float2 shiftdata       : TEXCOORD1;
+   float4 color           : COLOR0;
+};
+ 
+float4 main(v2f IN) : COLOR0
+{
+   float fade = 1.0 - abs(IN.shiftdata.y - 0.5) * 2.0;
+   IN.color.xyz = IN.color.xyz + pow(fade, 4) / 10;
+   IN.color.a = IN.color.a * fade;
+   return IN.color;
+}

+ 34 - 0
Templates/Full/game/shaders/common/ribbons/basicRibbonShaderV.hlsl

@@ -0,0 +1,34 @@
+#define IN_HLSL
+#include "../shdrConsts.h"
+ 
+struct a2v
+{
+        float2 texCoord        : TEXCOORD0;
+        float2 shiftdata       : TEXCOORD1;
+        float3 normal          : NORMAL;
+        float4 position        : POSITION;
+        float4 color           : COLOR0;
+};
+ 
+struct v2f
+{
+        float4 hpos            : POSITION;
+        float2 texCoord        : TEXCOORD0;
+        float2 shiftdata       : TEXCOORD1;
+        float4 color           : COLOR0;
+};
+ 
+uniform float4x4 modelview;
+uniform float3   eyePos;
+ 
+v2f main(a2v IN)
+{
+    v2f OUT;
+ 
+    OUT.hpos = mul(modelview, IN.position);
+    OUT.color = IN.color;
+    OUT.texCoord = IN.texCoord;
+    OUT.shiftdata = IN.shiftdata;
+   
+    return OUT;
+}

+ 20 - 0
Templates/Full/game/shaders/common/ribbons/texRibbonShaderP.hlsl

@@ -0,0 +1,20 @@
+#define IN_HLSL
+#include "../shdrConsts.h"
+#include "../torque.hlsl"
+ 
+uniform sampler2D ribTex : register(S0);
+ 
+struct v2f
+{
+           
+   float2 texCoord        : TEXCOORD0;
+   float2 shiftdata       : TEXCOORD1;
+   float4 color           : COLOR0;
+};
+ 
+float4 main(v2f IN) : COLOR0
+{
+    float4 Tex = tex2D(ribTex,IN.texCoord);
+	Tex.a *= IN.color.a;
+	return hdrEncode(Tex);
+}

+ 34 - 0
Templates/Full/game/shaders/common/ribbons/texRibbonShaderV.hlsl

@@ -0,0 +1,34 @@
+#define IN_HLSL
+#include "../shdrConsts.h"
+ 
+struct a2v
+{
+        float2 texCoord        : TEXCOORD0;
+        float2 shiftdata       : TEXCOORD1;
+        float3 normal          : NORMAL;
+        float4 position        : POSITION;
+        float4 color           : COLOR0;
+};
+ 
+struct v2f
+{
+        float4 hpos            : POSITION;
+        float2 texCoord        : TEXCOORD0;
+        float2 shiftdata       : TEXCOORD1;
+        float4 color           : COLOR0;
+};
+ 
+uniform float4x4 modelview;
+uniform float3   eyePos;
+ 
+v2f main(a2v IN)
+{
+    v2f OUT;
+ 
+    OUT.hpos = mul(modelview, IN.position);
+    OUT.color = IN.color;
+    OUT.texCoord = IN.texCoord;
+    OUT.shiftdata = IN.shiftdata;
+   
+    return OUT;
+}

+ 8 - 0
Templates/Full/game/tools/worldEditor/gui/objectBuilderGui.ed.gui

@@ -862,6 +862,14 @@ function ObjectBuilderGui::buildParticleEmitterNode(%this)
    %this.process();
 }
 
+function ObjectBuilderGui::buildRibbonNode(%this)
+{
+   %this.objectClassName = "RibbonNode";
+   %this.addField("dataBlock", "TypeDataBlock", "datablock", "RibbonNodeData");
+   %this.addField("ribbon",   "TypeDataBlock", "Ribbon data", "RibbonData");
+   %this.process();
+}
+
 function ObjectBuilderGui::buildParticleSimulation(%this)
 {
    %this.objectClassName = "ParticleSimulation";

+ 1 - 0
Templates/Full/game/tools/worldEditor/scripts/editors/creator.ed.cs

@@ -46,6 +46,7 @@ function EWCreatorWindow::init( %this )
       %this.registerMissionObject( "SFXEmitter",          "Sound Emitter" );
       %this.registerMissionObject( "Precipitation" );
       %this.registerMissionObject( "ParticleEmitterNode", "Particle Emitter" );
+      %this.registerMissionObject( "RibbonNode", "Ribbon" );
       
       // Legacy features. Users should use Ground Cover and the Forest Editor.   
       //%this.registerMissionObject( "fxShapeReplicator",   "Shape Replicator" );